From 5485ff927a2e803acffcb4c1bdf6a2d96d2f552c Mon Sep 17 00:00:00 2001 From: mob-sakai Date: Wed, 26 Aug 2020 17:31:06 +0900 Subject: [PATCH] feat: deterministic package installation Record the commit hash of the auto-installed package in `packages-lock.git.json` --- .../Editor/GitDependencyResolver.cs | 6 +- .../Editor/GitUtils.cs | 29 +++++++- .../Editor/PackageMeta.cs | 72 ++++++++++++++++++- Packages/packages-lock.git.json | 9 +++ 4 files changed, 111 insertions(+), 5 deletions(-) create mode 100644 Packages/packages-lock.git.json diff --git a/Packages/com.coffee.git-dependency-resolver/Editor/GitDependencyResolver.cs b/Packages/com.coffee.git-dependency-resolver/Editor/GitDependencyResolver.cs index 4d3e59f..3679ced 100644 --- a/Packages/com.coffee.git-dependency-resolver/Editor/GitDependencyResolver.cs +++ b/Packages/com.coffee.git-dependency-resolver/Editor/GitDependencyResolver.cs @@ -95,6 +95,7 @@ private static void UninstallUnusedPackages() { needToCheck = true; Log("Uninstall the unused package '{0}@{1}'", p.name, p.version); + PackageMeta.GitUnlock(p); FileUtil.DeleteFileOrDirectory(p.repository); } } @@ -106,6 +107,7 @@ private static void StartResolve() var needToCheck = true; Log("Start dependency resolution."); + PackageMeta.LoadGitLock(); AssetDatabase.StartAssetEditing(); while (needToCheck) { @@ -186,16 +188,18 @@ private static void StartResolve() DirUtils.Delete(installPath); DirUtils.Create(installPath); DirUtils.Move(pkgPath, installPath, p => p != ".git"); - AssetDatabase.ImportAsset(installPath, ImportAssetOptions.ForceUpdate | ImportAssetOptions.ForceSynchronousImport | ImportAssetOptions.ImportRecursive); Log("A package '{0}@{1}' has been installed.", package.name, package.version); needToRefresh = true; + AssetDatabase.ImportAsset(installPath, ImportAssetOptions.ForceUpdate | ImportAssetOptions.ForceSynchronousImport | ImportAssetOptions.ImportRecursive); + PackageMeta.GitLock(package); } EditorUtility.ClearProgressBar(); } AssetDatabase.StopAssetEditing(); + PackageMeta.SaveGitLock(); Log("Dependency resolution complete. refresh = {0}", needToRefresh); if (needToRefresh) diff --git a/Packages/com.coffee.git-dependency-resolver/Editor/GitUtils.cs b/Packages/com.coffee.git-dependency-resolver/Editor/GitUtils.cs index f4b4960..5a0801f 100644 --- a/Packages/com.coffee.git-dependency-resolver/Editor/GitUtils.cs +++ b/Packages/com.coffee.git-dependency-resolver/Editor/GitUtils.cs @@ -4,6 +4,7 @@ namespace Coffee.GitDependencyResolver { + //TODO: It's better to implement it in javascript. internal static class GitUtils { private static readonly StringBuilder s_sbError = new StringBuilder(); @@ -16,11 +17,32 @@ internal static class GitUtils public static bool ClonePackage(PackageMeta package, string clonePath) { Directory.CreateDirectory(clonePath); - var args = string.Format("clone --depth=1 --branch {0} --single-branch {1} {2}", package.rev, package.url, clonePath); - return ExecuteGitCommand(args); + + ExecuteGitCommand("init", dir: clonePath); + ExecuteGitCommand("remote add origin " + package.repository, dir: clonePath); + if (!string.IsNullOrEmpty(package.path)) + { + ExecuteGitCommand("config core.sparsecheckout true", dir: clonePath); + File.WriteAllText(Path.Combine(clonePath, ".git/info/sparse-checkout"), package.path); + } + + var revision = !string.IsNullOrEmpty(package.hash) + ? package.hash + : !string.IsNullOrEmpty(package.revision) + ? package.revision + : "HEAD"; + + return + ExecuteGitCommand("fetch --depth 1 origin " + revision, dir: clonePath) + && ExecuteGitCommand("reset --hard FETCH_HEAD", dir: clonePath) + && ExecuteGitCommand("rev-parse HEAD", (success, output) => + { + if (!success) return; + package.hash = output.Trim(); + }, dir: clonePath); } - static bool ExecuteGitCommand(string args, GitCommandCallback callback = null, bool waitForExit = true) + static bool ExecuteGitCommand(string args, GitCommandCallback callback = null, bool waitForExit = true, string dir = ".") { var startInfo = new System.Diagnostics.ProcessStartInfo { @@ -30,6 +52,7 @@ static bool ExecuteGitCommand(string args, GitCommandCallback callback = null, b RedirectStandardError = true, RedirectStandardOutput = true, UseShellExecute = false, + WorkingDirectory = dir, }; var launchProcess = System.Diagnostics.Process.Start(startInfo); diff --git a/Packages/com.coffee.git-dependency-resolver/Editor/PackageMeta.cs b/Packages/com.coffee.git-dependency-resolver/Editor/PackageMeta.cs index db32ca1..5e98383 100644 --- a/Packages/com.coffee.git-dependency-resolver/Editor/PackageMeta.cs +++ b/Packages/com.coffee.git-dependency-resolver/Editor/PackageMeta.cs @@ -1,10 +1,38 @@ +using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; +using UnityEngine; namespace Coffee.GitDependencyResolver { + [Serializable] + internal class GitLock + { + public List dependencies = new List(); + + [Serializable] + internal struct Entry + { + public string name; + public string hash; + public string url; + + public Entry(PackageMeta package) + { + name = package.name; + hash = package.hash; + url = package.url; + } + + public bool IsValid(PackageMeta package) + { + return name == package.name && url == package.url; + } + } + } + internal class PackageMeta { #if NETSTANDARD @@ -12,6 +40,8 @@ internal class PackageMeta #else const RegexOptions k_RegOption = RegexOptions.CultureInvariant | RegexOptions.Compiled | RegexOptions.ExplicitCapture; #endif + private const string k_GitLockFile = "Packages/packages-lock.git.json"; + private static readonly Regex s_PackageUrlReg = new Regex( @"^(git\+)?" + @@ -26,6 +56,8 @@ internal class PackageMeta @"(git@|git://|http://|https://|ssh://)", k_RegOption); + private static readonly GitLock s_GitLock = new GitLock(); + public string name { get; private set; } internal SemVersion version { get; private set; } public string revision { get; private set; } @@ -33,6 +65,8 @@ internal class PackageMeta public string path { get; private set; } public PackageMeta[] dependencies { get; private set; } public PackageMeta[] gitDependencies { get; private set; } + public string hash { get; set; } + public string url { get; set; } private PackageMeta() { @@ -40,6 +74,8 @@ private PackageMeta() repository = ""; revision = ""; path = ""; + hash = ""; + url = ""; version = new SemVersion(0); dependencies = new PackageMeta [0]; gitDependencies = new PackageMeta [0]; @@ -126,6 +162,8 @@ public static PackageMeta FromNameAndUrl(string name, string url) package.repository = m.Groups["repository"].Value; package.revision = m.Groups["revision"].Value; + package.url = url; + package.hash = s_GitLock.dependencies.FirstOrDefault(x => x.IsValid(package)).hash ?? ""; // Get version from revision/branch/tag package.SetVersion(package.revision); @@ -182,7 +220,39 @@ public string GetDirectoryName() public override string ToString() { - return string.Format("{0}@{1} ({2}) [{3}]", name, version, revision, path); + return string.Format("{0}@{1} ({2}) [{3}] <{4}>", name, version, revision, path, url); + } + + public static void LoadGitLock() + { + var text = File.Exists(k_GitLockFile) + ? File.ReadAllText(k_GitLockFile) + : "{}"; + + JsonUtility.FromJsonOverwrite(text, s_GitLock); + } + + public static void GitLock(PackageMeta package) + { + s_GitLock.dependencies.RemoveAll(e => e.name == package.name); + s_GitLock.dependencies.Add(new GitLock.Entry(package)); + } + + public static void GitUnlock(PackageMeta package) + { + s_GitLock.dependencies.RemoveAll(e => e.name == package.name); + } + + public static void SaveGitLock() + { + var text = File.Exists(k_GitLockFile) + ? File.ReadAllText(k_GitLockFile) + : "{}"; + + var text2 = JsonUtility.ToJson(s_GitLock, true); + if (text == text2) return; + + File.WriteAllText(k_GitLockFile, text2); } } } diff --git a/Packages/packages-lock.git.json b/Packages/packages-lock.git.json new file mode 100644 index 0000000..9fc98a1 --- /dev/null +++ b/Packages/packages-lock.git.json @@ -0,0 +1,9 @@ +{ + "dependencies": [ + { + "name": "com.coffee.ugd.math", + "hash": "46720ffb837eb050452d5e78503ddd08cfe8ff5c", + "url": "https://github.com/mob-sakai/UnityGitDependencyTest.git?path=Packages/math" + } + ] +} \ No newline at end of file