com.alicizax.unity.packagem.../Editor/PackageManager/PackageManagerCheckTool.cs

319 lines
11 KiB
C#
Raw Normal View History

2026-04-17 14:21:41 +08:00
using Newtonsoft.Json;
2025-02-20 10:48:57 +08:00
namespace AlicizaX.PackageManager.Editor
{
using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEditor.PackageManager;
using UnityEditor.PackageManager.Requests;
2026-04-17 14:21:41 +08:00
internal enum InstalledSource
{
None,
Git,
Embedded,
LocalPath,
Registry,
BuiltIn,
Unknown
}
internal sealed class LocalPackageInfo
{
public string Name;
public string ManifestVersion;
public string LockVersion;
public string ResolvedHash;
public string InstalledVersion;
public InstalledSource Source;
public bool IsInstalled;
}
2025-02-20 10:48:57 +08:00
public static class PackageManagerCheckTool
{
2026-04-17 14:21:41 +08:00
private const string ManifestPath = "Packages/manifest.json";
private const string PackagesLockPath = "Packages/packages-lock.json";
private static readonly Dictionary<string, string> InstalledPackageVersions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
private static Dictionary<string, string> s_manifestDependencies;
private static Dictionary<string, LockDependency> s_lockDependencies;
private static ListRequest s_listRequest;
private static Action<IReadOnlyDictionary<string, string>> s_listCallback;
2025-02-20 10:48:57 +08:00
[Serializable]
2026-04-17 14:21:41 +08:00
private sealed class ManifestFile
2025-02-20 10:48:57 +08:00
{
public Dictionary<string, string> dependencies = new Dictionary<string, string>();
}
2026-04-17 14:21:41 +08:00
[Serializable]
private sealed class PackagesLockFile
{
public Dictionary<string, LockDependency> dependencies = new Dictionary<string, LockDependency>();
}
[Serializable]
private sealed class LockDependency
{
public string version;
public string source;
public string hash;
}
2025-02-20 10:48:57 +08:00
public static void ValidatePackageStates(List<RepositoryPackageData> packages)
{
2026-04-17 14:21:41 +08:00
if (packages == null)
{
return;
}
RefreshLocalCache();
2025-02-20 10:48:57 +08:00
foreach (var package in packages)
{
2026-04-17 14:21:41 +08:00
var localPackage = GetLocalPackageInfo(package.name);
ApplyLocalState(package, localPackage);
2025-02-20 10:48:57 +08:00
}
}
2026-04-17 14:21:41 +08:00
public static void RefreshInstalledPackages(Action<IReadOnlyDictionary<string, string>> callback)
2025-02-20 10:48:57 +08:00
{
2026-04-17 14:21:41 +08:00
if (s_listRequest != null && !s_listRequest.IsCompleted)
{
return;
}
2025-02-20 10:48:57 +08:00
2026-04-17 14:21:41 +08:00
s_listCallback = callback;
s_listRequest = Client.List(true, false);
EditorApplication.update -= OnListRequestProgress;
EditorApplication.update += OnListRequestProgress;
2025-02-20 10:48:57 +08:00
}
2026-04-17 14:21:41 +08:00
public static void InstallPackage(string packageIdentifier, Action<bool, string> callback = null)
2025-02-20 10:48:57 +08:00
{
2026-04-17 14:21:41 +08:00
PackageOperationRunner.RunAdd(packageIdentifier, callback);
2025-02-20 10:48:57 +08:00
}
2026-04-17 14:21:41 +08:00
public static void UpdatePackage(RepositoryPackageData package, Action<bool, string> callback = null)
2025-02-20 10:48:57 +08:00
{
2026-04-17 14:21:41 +08:00
if (package == null)
{
callback?.Invoke(false, "Package data is null.");
return;
}
PackageOperationRunner.RunAdd(package.cloneUrl, callback);
2025-02-20 10:48:57 +08:00
}
2026-04-17 14:21:41 +08:00
public static void UninstallPackage(string packageName, Action<bool, string> callback = null)
2025-02-20 10:48:57 +08:00
{
2026-04-17 14:21:41 +08:00
PackageOperationRunner.RunRemove(packageName, callback);
2025-02-20 10:48:57 +08:00
}
2026-04-17 14:21:41 +08:00
internal static LocalPackageInfo GetLocalPackageInfo(string packageName)
2025-02-20 10:48:57 +08:00
{
2026-04-17 14:21:41 +08:00
RefreshLocalCache();
var info = new LocalPackageInfo
{
Name = packageName,
Source = InstalledSource.None
};
if (string.IsNullOrEmpty(packageName))
{
return info;
}
if (s_manifestDependencies != null && s_manifestDependencies.TryGetValue(packageName, out var manifestVersion))
{
info.ManifestVersion = manifestVersion;
}
if (s_lockDependencies != null && s_lockDependencies.TryGetValue(packageName, out var lockDependency))
{
info.LockVersion = lockDependency.version;
info.ResolvedHash = lockDependency.hash;
info.Source = ParseSource(lockDependency.source);
info.IsInstalled = info.Source != InstalledSource.None;
}
if (InstalledPackageVersions.TryGetValue(packageName, out var installedVersion))
{
info.InstalledVersion = installedVersion;
info.IsInstalled = true;
}
if (info.Source == InstalledSource.None && !string.IsNullOrEmpty(info.ManifestVersion))
{
info.Source = GuessSourceFromManifest(info.ManifestVersion);
info.IsInstalled = info.Source == InstalledSource.Embedded || info.Source == InstalledSource.LocalPath;
}
return info;
2025-02-20 10:48:57 +08:00
}
2026-04-17 14:21:41 +08:00
public static bool HasInstalledPackageVersion(string packageName, out string version)
2025-02-20 10:48:57 +08:00
{
2026-04-17 14:21:41 +08:00
return InstalledPackageVersions.TryGetValue(packageName, out version);
}
2025-02-20 10:48:57 +08:00
2026-04-17 14:21:41 +08:00
private static void OnListRequestProgress()
{
if (s_listRequest == null || !s_listRequest.IsCompleted)
2025-02-20 10:48:57 +08:00
{
2026-04-17 14:21:41 +08:00
return;
}
EditorApplication.update -= OnListRequestProgress;
InstalledPackageVersions.Clear();
if (s_listRequest.Status == StatusCode.Success)
{
foreach (var package in s_listRequest.Result)
2025-02-20 10:48:57 +08:00
{
2026-04-17 14:21:41 +08:00
InstalledPackageVersions[package.name] = package.version;
2025-02-20 10:48:57 +08:00
}
2026-04-17 14:21:41 +08:00
}
var callback = s_listCallback;
s_listCallback = null;
s_listRequest = null;
callback?.Invoke(InstalledPackageVersions);
}
private static void RefreshLocalCache()
{
s_manifestDependencies = ReadManifestDependencies();
s_lockDependencies = ReadPackagesLockDependencies();
}
private static void ApplyLocalState(RepositoryPackageData package, LocalPackageInfo localPackage)
{
package.ManifestVersion = localPackage.ManifestVersion;
package.InstalledVersion = !string.IsNullOrEmpty(localPackage.InstalledVersion)
? localPackage.InstalledVersion
: localPackage.LockVersion;
package.InstalledHash = localPackage.ResolvedHash;
package.InstalledSource = localPackage.Source.ToString();
switch (localPackage.Source)
{
case InstalledSource.Embedded:
case InstalledSource.LocalPath:
package.PackageState = PackageState.InstallLocal;
break;
case InstalledSource.Git:
package.PackageState = HasRemoteUpdate(package, localPackage)
? PackageState.Update
: PackageState.Install;
break;
case InstalledSource.Registry:
case InstalledSource.BuiltIn:
case InstalledSource.Unknown:
package.PackageState = localPackage.IsInstalled ? PackageState.Install : PackageState.UnInstall;
break;
default:
package.PackageState = localPackage.IsInstalled ? PackageState.Install : PackageState.UnInstall;
break;
}
}
private static bool HasRemoteUpdate(RepositoryPackageData package, LocalPackageInfo localPackage)
{
if (string.IsNullOrEmpty(localPackage.ResolvedHash) || string.IsNullOrEmpty(package.remoteHead))
{
return false;
}
return !string.Equals(localPackage.ResolvedHash, package.remoteHead, StringComparison.OrdinalIgnoreCase);
}
private static Dictionary<string, string> ReadManifestDependencies()
{
var path = Path.GetFullPath(ManifestPath);
if (!File.Exists(path))
{
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
2025-02-20 10:48:57 +08:00
2026-04-17 14:21:41 +08:00
try
{
var json = File.ReadAllText(path);
var manifest = JsonConvert.DeserializeObject<ManifestFile>(json);
return manifest?.dependencies != null
? new Dictionary<string, string>(manifest.dependencies, StringComparer.OrdinalIgnoreCase)
: new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
catch (Exception e)
{
UnityEngine.Debug.LogError($"Read manifest failed: {e.Message}");
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
2025-02-20 10:48:57 +08:00
}
2026-04-17 14:21:41 +08:00
private static Dictionary<string, LockDependency> ReadPackagesLockDependencies()
2025-02-20 10:48:57 +08:00
{
2026-04-17 14:21:41 +08:00
var path = Path.GetFullPath(PackagesLockPath);
if (!File.Exists(path))
{
return new Dictionary<string, LockDependency>(StringComparer.OrdinalIgnoreCase);
}
2025-02-20 10:48:57 +08:00
try
{
2026-04-17 14:21:41 +08:00
var json = File.ReadAllText(path);
var packagesLock = JsonConvert.DeserializeObject<PackagesLockFile>(json);
return packagesLock?.dependencies != null
? new Dictionary<string, LockDependency>(packagesLock.dependencies, StringComparer.OrdinalIgnoreCase)
: new Dictionary<string, LockDependency>(StringComparer.OrdinalIgnoreCase);
2025-02-20 10:48:57 +08:00
}
2026-04-17 14:21:41 +08:00
catch (Exception e)
2025-02-20 10:48:57 +08:00
{
2026-04-17 14:21:41 +08:00
UnityEngine.Debug.LogError($"Read packages-lock failed: {e.Message}");
return new Dictionary<string, LockDependency>(StringComparer.OrdinalIgnoreCase);
2025-02-20 10:48:57 +08:00
}
}
2026-04-17 14:21:41 +08:00
private static InstalledSource ParseSource(string source)
{
return source switch
{
"git" => InstalledSource.Git,
"embedded" => InstalledSource.Embedded,
"local" => InstalledSource.LocalPath,
"registry" => InstalledSource.Registry,
"builtin" => InstalledSource.BuiltIn,
_ => string.IsNullOrEmpty(source) ? InstalledSource.None : InstalledSource.Unknown
};
}
private static InstalledSource GuessSourceFromManifest(string manifestVersion)
{
if (string.IsNullOrEmpty(manifestVersion))
{
return InstalledSource.None;
}
if (manifestVersion.StartsWith("file:", StringComparison.OrdinalIgnoreCase))
{
return InstalledSource.LocalPath;
}
if (manifestVersion.EndsWith(".git", StringComparison.OrdinalIgnoreCase) ||
manifestVersion.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ||
manifestVersion.StartsWith("https://", StringComparison.OrdinalIgnoreCase) ||
manifestVersion.StartsWith("ssh://", StringComparison.OrdinalIgnoreCase) ||
manifestVersion.StartsWith("git@", StringComparison.OrdinalIgnoreCase))
{
return InstalledSource.Git;
}
return InstalledSource.Registry;
}
2025-02-20 10:48:57 +08:00
}
}