423 lines
15 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|
|
} |