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 Versions = new List(); public string LatestVersion; } public class OtherConfig { public List 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 NewFiles = new List(); [ShowIf("@OverwriteFiles != null && OverwriteFiles.Count > 0")] [LabelText("覆盖文件")] [ListDrawerSettings(IsReadOnly = true, NumberOfItemsPerPage = 5)] [ShowInInspector] public List OverwriteFiles = new List(); } public class ChangePreview { public List NewDefines = new List(); public List SkippedDefines = new List(); public List PackageFiles = new List(); } [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 allVersions = new List(); [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 ScanSDKPackages() { var result = new List(); 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 BuildPackageEntries() { var infos = ScanSDKPackages(); var entries = new List(); foreach (var info in infos) { var entry = new SDKPackageEntry { packageName = info.Name, allVersions = new List(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(yamlContent); } catch (Exception e) { Debug.LogError($"[SDKPackage] 解析 OtherConfig.yaml 失败: {e.Message}"); return null; } } public static ChangePreview GetChangePreview(List 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(currentDefines.Split(';'), StringComparer.OrdinalIgnoreCase); var allNewDefines = new HashSet(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 entries, Action logCallback) { BuildTargetGroup buildTargetGroup = BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget); string currentDefines = PlayerSettings.GetScriptingDefineSymbolsForGroup(buildTargetGroup); var defineSet = new HashSet(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 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 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); } } } }