NLDClient-yudde/ProjectNLD/Assets/Editor/SDKPackage/SDKPackageHelper.cs

423 lines
15 KiB
C#

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);
}
}
}
}