【工具】SDK Package导入工具

main
刘涛 2026-05-12 15:39:49 +08:00
parent 15744a6b72
commit 93d41280a9
6 changed files with 593 additions and 0 deletions

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c92da9377e664a79832cc4c0a673bebe
timeCreated: 1778561581

View File

@ -0,0 +1,152 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Sirenix.OdinInspector;
using Sirenix.OdinInspector.Editor;
using Sirenix.Utilities;
using Sirenix.Utilities.Editor;
using UnityEditor;
using UnityEngine;
namespace SDKPackage.Editor
{
public class SDKPackageEditorWindow : OdinEditorWindow
{
[TableList(AlwaysExpanded = true, ShowIndexLabels = false,
NumberOfItemsPerPage = 20, IsReadOnly = false)]
[ShowInInspector]
[PropertyOrder(1)]
[HideLabel]
private List<SDKPackageEntry> _packageEntries = new List<SDKPackageEntry>();
[HorizontalGroup("Actions", Width = 0.5f)]
[Button("全选", ButtonSizes.Small)]
private void SelectAll()
{
foreach (var entry in _packageEntries)
entry.selected = true;
}
[HorizontalGroup("Actions")]
[Button("全不选", ButtonSizes.Small)]
private void DeselectAll()
{
foreach (var entry in _packageEntries)
entry.selected = false;
}
[HorizontalGroup("Actions")]
[Button("刷新列表", ButtonSizes.Small)]
private void RefreshList()
{
_packageEntries = SDKPackageHelper.BuildPackageEntries();
ClearPreview();
}
[PropertySpace(4)]
[HorizontalGroup("InstallActions")]
[Button("预览变更", ButtonSizes.Medium)]
[EnableIf("HasSelectedPackages")]
private void Preview()
{
UpdatePreview(SDKPackageHelper.GetChangePreview(_packageEntries));
}
[HorizontalGroup("InstallActions")]
[Button("安装选中包", ButtonSizes.Medium)]
[EnableIf("HasSelectedPackages")]
private void Install()
{
_logContent = "";
SDKPackageHelper.InstallPackages(_packageEntries, Log);
_currentDefines = SDKPackageHelper.GetCurrentDefines();
UpdatePreview(SDKPackageHelper.GetChangePreview(_packageEntries));
}
[ShowIf("_hasNewDefines")]
[Title("新增宏定义", titleAlignment: TitleAlignments.Left)]
[ListDrawerSettings(IsReadOnly = true)]
[ShowInInspector]
[PropertyOrder(3)]
private List<string> _newDefines = new List<string>();
[ShowIf("_hasSkippedDefines")]
[Title("跳过宏定义(已存在)", titleAlignment: TitleAlignments.Left)]
[ListDrawerSettings(IsReadOnly = true)]
[ShowInInspector]
[PropertyOrder(3)]
private List<string> _skippedDefines = new List<string>();
[ShowIf("_hasPreviewFiles")]
[Title("文件变更预览", titleAlignment: TitleAlignments.Left)]
[ListDrawerSettings(IsReadOnly = true, NumberOfItemsPerPage = 10)]
[ShowInInspector]
[PropertyOrder(4)]
private List<PackageFilePreview> _previewFiles = new List<PackageFilePreview>();
[Title("当前宏定义", titleAlignment: TitleAlignments.Left)]
[PropertyOrder(5)]
[HideLabel]
[DisplayAsString]
[MultiLineProperty(3)]
[ShowInInspector]
private string _currentDefines;
[Title("安装日志", titleAlignment: TitleAlignments.Left)]
[PropertyOrder(6)]
[MultiLineProperty(8)]
[HideLabel]
[ShowInInspector]
private string _logContent = "";
private bool _hasNewDefines;
private bool _hasSkippedDefines;
private bool _hasPreviewFiles;
[MenuItem("Tools/*SDK包管理器", false, 210)]
public static void ShowWindow()
{
var window = GetWindow<SDKPackageEditorWindow>();
window.titleContent = new GUIContent("SDK包管理器");
window.position = GUIHelper.GetEditorWindowRect().AlignCenter(800, 700);
window.Show();
}
protected override void OnEnable()
{
base.OnEnable();
_packageEntries = SDKPackageHelper.BuildPackageEntries();
_currentDefines = SDKPackageHelper.GetCurrentDefines();
_logContent = "";
ClearPreview();
}
private bool HasSelectedPackages => _packageEntries != null && _packageEntries.Any(e => e.selected);
private void UpdatePreview(ChangePreview preview)
{
_newDefines = preview?.NewDefines ?? new List<string>();
_skippedDefines = preview?.SkippedDefines ?? new List<string>();
_previewFiles = preview?.PackageFiles ?? new List<PackageFilePreview>();
_hasNewDefines = _newDefines.Count > 0;
_hasSkippedDefines = _skippedDefines.Count > 0;
_hasPreviewFiles = _previewFiles.Count > 0;
}
private void ClearPreview()
{
_newDefines = new List<string>();
_skippedDefines = new List<string>();
_previewFiles = new List<PackageFilePreview>();
_hasNewDefines = false;
_hasSkippedDefines = false;
_hasPreviewFiles = false;
}
private void Log(string message)
{
_logContent = string.IsNullOrEmpty(_logContent) ? message : $"{_logContent}\n{message}";
Debug.Log($"[SDKPackage] {message}");
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ebc7622de91e4bad95683a495d987f44
timeCreated: 1778561591

View File

@ -0,0 +1,423 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Sirenix.OdinInspector;
using UnityEditor;
using UnityEngine;
using YamlDotNet.Serialization;
namespace SDKPackage.Editor
{
public class SDKPackageInfo
{
public string Name;
public List<string> Versions = new List<string>();
public string LatestVersion;
}
public class OtherConfig
{
public List<string> Define_list { get; set; }
}
public class PackageFilePreview
{
[ShowInInspector] [HideLabel] [DisplayAsString] [HorizontalGroup("Name", Width = 0.5f)]
public string PackageName;
[ShowInInspector] [HideLabel] [DisplayAsString] [HorizontalGroup("Name", Width = 0.3f)]
public string Version;
[ShowIf("@NewFiles != null && NewFiles.Count > 0")]
[LabelText("新增文件")]
[ListDrawerSettings(IsReadOnly = true, NumberOfItemsPerPage = 5)]
[ShowInInspector]
public List<string> NewFiles = new List<string>();
[ShowIf("@OverwriteFiles != null && OverwriteFiles.Count > 0")]
[LabelText("覆盖文件")]
[ListDrawerSettings(IsReadOnly = true, NumberOfItemsPerPage = 5)]
[ShowInInspector]
public List<string> OverwriteFiles = new List<string>();
}
public class ChangePreview
{
public List<string> NewDefines = new List<string>();
public List<string> SkippedDefines = new List<string>();
public List<PackageFilePreview> PackageFiles = new List<PackageFilePreview>();
}
[Serializable]
public class SDKPackageEntry
{
[ToggleLeft]
[TableColumnWidth(40, Resizable = false)]
public bool selected;
[ReadOnly]
[TableColumnWidth(160)]
[DisplayAsString]
public string packageName;
[ValueDropdown("allVersions")]
[TableColumnWidth(100)]
[HideLabel]
public string version;
[HideInInspector]
public List<string> allVersions = new List<string>();
[ReadOnly]
[TableColumnWidth(100)]
[DisplayAsString]
[HideIf("@string.IsNullOrEmpty(latestVersion)")]
public string latestVersion;
}
public static class SDKPackageHelper
{
private static readonly string[] SDKPackageSubDirs = { "Assets", "ProjectSettings" };
public static string GetSDKPackagesPath()
{
string projectRoot = Directory.GetParent(Application.dataPath)?.Parent?.FullName;
if (string.IsNullOrEmpty(projectRoot))
{
Debug.LogError("[SDKPackage] 无法定位项目根目录");
return null;
}
string path = Path.Combine(projectRoot, "Tool", "sdk_packages");
if (!Directory.Exists(path))
{
Debug.LogError($"[SDKPackage] SDK包目录不存在: {path}");
return null;
}
return path;
}
public static List<SDKPackageInfo> ScanSDKPackages()
{
var result = new List<SDKPackageInfo>();
string sdkPath = GetSDKPackagesPath();
if (string.IsNullOrEmpty(sdkPath)) return result;
try
{
var dirs = Directory.GetDirectories(sdkPath);
foreach (var dir in dirs)
{
var info = new SDKPackageInfo
{
Name = Path.GetFileName(dir)
};
var versionDirs = Directory.GetDirectories(dir);
foreach (var vDir in versionDirs)
{
string versionName = Path.GetFileName(vDir);
info.Versions.Add(versionName);
}
string latestFile = Path.Combine(dir, "latest.txt");
if (File.Exists(latestFile))
{
info.LatestVersion = File.ReadAllText(latestFile).Trim();
}
if (info.Versions.Count > 0)
{
result.Add(info);
}
}
result = result.OrderBy(p => p.Name).ToList();
}
catch (Exception e)
{
Debug.LogError($"[SDKPackage] 扫描SDK包失败: {e.Message}");
}
return result;
}
public static List<SDKPackageEntry> BuildPackageEntries()
{
var infos = ScanSDKPackages();
var entries = new List<SDKPackageEntry>();
foreach (var info in infos)
{
var entry = new SDKPackageEntry
{
packageName = info.Name,
allVersions = new List<string>(info.Versions),
latestVersion = info.LatestVersion,
version = !string.IsNullOrEmpty(info.LatestVersion) ? info.LatestVersion : info.Versions[0]
};
if (!string.IsNullOrEmpty(entry.latestVersion) && entry.allVersions.Contains(entry.latestVersion))
{
entry.allVersions.Remove(entry.latestVersion);
entry.allVersions.Insert(0, entry.latestVersion);
}
entries.Add(entry);
}
return entries;
}
public static OtherConfig ReadOtherConfig(string packageName, string version)
{
string sdkPath = GetSDKPackagesPath();
if (string.IsNullOrEmpty(sdkPath)) return null;
string configPath = Path.Combine(sdkPath, packageName, version, "OtherConfig.yaml");
if (!File.Exists(configPath))
{
Debug.Log($"[SDKPackage] 未找到 OtherConfig.yaml: {configPath}");
return null;
}
try
{
string yamlContent = File.ReadAllText(configPath);
var deserializer = new DeserializerBuilder().IgnoreUnmatchedProperties().Build();
return deserializer.Deserialize<OtherConfig>(yamlContent);
}
catch (Exception e)
{
Debug.LogError($"[SDKPackage] 解析 OtherConfig.yaml 失败: {e.Message}");
return null;
}
}
public static ChangePreview GetChangePreview(List<SDKPackageEntry> entries)
{
var preview = new ChangePreview();
string sdkPath = GetSDKPackagesPath();
if (string.IsNullOrEmpty(sdkPath)) return preview;
string projectRoot = Directory.GetParent(Application.dataPath)?.FullName;
if (string.IsNullOrEmpty(projectRoot)) return preview;
BuildTargetGroup buildTargetGroup = BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget);
string currentDefines = PlayerSettings.GetScriptingDefineSymbolsForGroup(buildTargetGroup);
var existingDefines = new HashSet<string>(currentDefines.Split(';'), StringComparer.OrdinalIgnoreCase);
var allNewDefines = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var entry in entries)
{
if (!entry.selected) continue;
OtherConfig config = ReadOtherConfig(entry.packageName, entry.version);
if (config?.Define_list != null)
{
foreach (string define in config.Define_list)
{
if (existingDefines.Contains(define))
{
if (!preview.SkippedDefines.Contains(define))
preview.SkippedDefines.Add(define);
}
else if (!allNewDefines.Contains(define))
{
allNewDefines.Add(define);
preview.NewDefines.Add(define);
}
}
}
var filePreview = ScanPackageFiles(sdkPath, projectRoot, entry.packageName, entry.version);
if (filePreview != null)
{
preview.PackageFiles.Add(filePreview);
}
}
return preview;
}
public static void InstallPackages(List<SDKPackageEntry> entries, Action<string> logCallback)
{
BuildTargetGroup buildTargetGroup = BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget);
string currentDefines = PlayerSettings.GetScriptingDefineSymbolsForGroup(buildTargetGroup);
var defineSet = new HashSet<string>(currentDefines.Split(';'), StringComparer.OrdinalIgnoreCase);
bool definesChanged = false;
var selectedEntries = entries.Where(e => e.selected).ToList();
if (selectedEntries.Count == 0)
{
logCallback?.Invoke("未选择任何SDK包");
return;
}
foreach (var entry in selectedEntries)
{
logCallback?.Invoke($"━━━ 开始安装 {entry.packageName}@{entry.version} ━━━");
OtherConfig config = ReadOtherConfig(entry.packageName, entry.version);
bool copySuccess = CopySDKPackage(entry.packageName, entry.version, logCallback);
if (!copySuccess)
{
logCallback?.Invoke($"✗ 安装失败: {entry.packageName}@{entry.version}");
continue;
}
if (config?.Define_list != null)
{
foreach (string define in config.Define_list)
{
if (!defineSet.Contains(define))
{
defineSet.Add(define);
definesChanged = true;
logCallback?.Invoke($" 添加宏: {define}");
}
else
{
logCallback?.Invoke($" 跳过宏(已存在): {define}");
}
}
}
logCallback?.Invoke($"✓ 安装完成: {entry.packageName}@{entry.version}");
}
if (definesChanged)
{
string newDefineStr = string.Join(";", defineSet.Where(d => !string.IsNullOrEmpty(d)));
PlayerSettings.SetScriptingDefineSymbolsForGroup(buildTargetGroup, newDefineStr);
logCallback?.Invoke("宏定义已更新到 PlayerSettings");
}
AssetDatabase.Refresh();
logCallback?.Invoke("AssetDatabase 已刷新");
}
public static string GetCurrentDefines()
{
BuildTargetGroup buildTargetGroup = BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget);
return PlayerSettings.GetScriptingDefineSymbolsForGroup(buildTargetGroup);
}
private static PackageFilePreview ScanPackageFiles(string sdkPath, string projectRoot, string packageName, string version)
{
string versionPath = Path.Combine(sdkPath, packageName, version);
if (!Directory.Exists(versionPath)) return null;
var preview = new PackageFilePreview
{
PackageName = packageName,
Version = version
};
foreach (string subDir in SDKPackageSubDirs)
{
string srcDir = Path.Combine(versionPath, subDir);
if (!Directory.Exists(srcDir)) continue;
CollectFileStatus(srcDir, Path.Combine(projectRoot, subDir), srcDir, preview);
}
return preview;
}
private static void CollectFileStatus(string srcDir, string dstDir, string rootSrcDir, PackageFilePreview preview)
{
if (!Directory.Exists(srcDir)) return;
foreach (string file in Directory.GetFiles(srcDir))
{
string relativePath = file.Substring(rootSrcDir.Length + 1);
string dstFile = Path.Combine(dstDir, Path.GetFileName(file));
if (File.Exists(dstFile))
{
preview.OverwriteFiles.Add(relativePath);
}
else
{
preview.NewFiles.Add(relativePath);
}
}
foreach (string dir in Directory.GetDirectories(srcDir))
{
string dirName = Path.GetFileName(dir);
string srcSub = Path.Combine(srcDir, dirName);
string dstSub = Path.Combine(dstDir, dirName);
CollectFileStatus(srcSub, dstSub, rootSrcDir, preview);
}
}
private static bool CopySDKPackage(string packageName, string version, Action<string> logCallback)
{
string sdkPath = GetSDKPackagesPath();
if (string.IsNullOrEmpty(sdkPath)) return false;
string versionPath = Path.Combine(sdkPath, packageName, version);
if (!Directory.Exists(versionPath))
{
Debug.LogError($"[SDKPackage] 版本目录不存在: {versionPath}");
return false;
}
string projectRoot = Directory.GetParent(Application.dataPath)?.FullName;
if (string.IsNullOrEmpty(projectRoot))
{
Debug.LogError("[SDKPackage] 无法定位项目Assets目录");
return false;
}
foreach (string subDir in SDKPackageSubDirs)
{
string srcPath = Path.Combine(versionPath, subDir);
if (!Directory.Exists(srcPath)) continue;
string dstPath = Path.Combine(projectRoot, subDir);
logCallback?.Invoke($" 拷贝 {subDir}/");
CopyDirectory(srcPath, dstPath, logCallback);
}
return true;
}
private static void CopyDirectory(string srcPath, string dstPath, Action<string> logCallback)
{
if (!Directory.Exists(dstPath))
{
Directory.CreateDirectory(dstPath);
}
foreach (string file in Directory.GetFiles(srcPath))
{
string fileName = Path.GetFileName(file);
string dstFile = Path.Combine(dstPath, fileName);
if (File.Exists(dstFile))
{
logCallback?.Invoke($" 覆盖: {fileName}");
}
else
{
logCallback?.Invoke($" 新增: {fileName}");
}
File.Copy(file, dstFile, true);
}
foreach (string dir in Directory.GetDirectories(srcPath))
{
string dirName = Path.GetFileName(dir);
CopyDirectory(Path.Combine(srcPath, dirName), Path.Combine(dstPath, dirName), logCallback);
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 91f518022d8ed4949b3c33034a0ba563
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -24,6 +24,7 @@ public enum NLDMenuID
// 构建和打包工具 - 200序列
BuildTools = 200, // Tools/打包工具
SDKPackageManager = 210, // Tools/SDK包管理器
// 角色工具 - 250序列
CharacterTools = 250, // Tools/角色工具