提交 68202b1c 编写于 作者: 魔术师Dix's avatar 魔术师Dix

添加UPM包的拉取功能;

上级 dd9ba22a
......@@ -63,11 +63,15 @@
<Compile Include="Assets\Scritps\Core\NativeProgram\ProcessOutputStreamReader.cs" />
<Compile Include="Assets\Scritps\UI\UIManager.cs" />
<Compile Include="Assets\Scritps\UI\E_WindowType.cs" />
<Compile Include="Assets\Scritps\Core\Fetch\DependencyInfo.cs" />
<Compile Include="Assets\Scritps\Utils\Logger.cs" />
<Compile Include="Assets\Scritps\Core\NativeProgram\NativeProgram.cs" />
<Compile Include="Assets\Scritps\Utils\Event\GYEventsCenter.cs" />
<Compile Include="Assets\Scritps\Core\Fetch\FetchResult.cs" />
<Compile Include="Assets\Scritps\Core\Fetch\UpmPackageVersion.cs" />
<Compile Include="Assets\Scritps\UI\UIWindow.cs" />
<Compile Include="Assets\Scritps\Utils\Event\GYEventInfo.cs" />
<Compile Include="Assets\Scritps\Core\Fetch\SemVersion.cs" />
<Compile Include="Assets\Scritps\Utils\Lib.cs" />
<Compile Include="Assets\Scritps\UI\MainWindow.cs" />
<Compile Include="Assets\Scritps\Core\Manifest\UnityManifest.cs" />
......
fileFormatVersion: 2
guid: 5ee01bba7d2580c47b969fde2bb2837f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
// nodePath:
// Windows: C:\Program Files\Unity\Editor\Data\Tools\nodejs\node.exe
// OSX: /Applications/Unity/Unity.app/Contents/Tools/nodejs/bin/node
// Linux: /opt/unity/Editor/Data/Tools/nodejs/bin/node
//
// Example:
// {nodePath} fetch-packages.js {repositoryUrl}
const { execSync } = require("child_process");
const fs = require("fs");
const path = require("path");
const { hashCode, mkdirSyncRecrusive, shouldFetch, lock, unlock, loadJs, touch } = require("./utils");
const version = '2.0.0'
// Input
console.log(`cwd: ${process.cwd()}\nInput`);
const rawRepositoryUrl = process.argv[2];
const matchUrl = rawRepositoryUrl.match(/^(git\+)?(.*?)(\?path=([^#]*))?(#.*)?$/);
const repositoryUrl = matchUrl[2];
const subDir = `${matchUrl[4] || '.'}`
.replace(/(^\/|\/$)/g, "") + '/';
const repositoryUrlWithPath = subDir !== "./"
? `${repositoryUrl}?path=${subDir}`
: repositoryUrl;
const repositoryId = repositoryUrl
.replace(/^(ssh:\/\/|http:\/\/|https:\/\/|file:\/\/|git:\/\/|git@)/, "")
.replace(/\.git$/, "");
const subDirId = subDir != './' ? '@' + subDir.slice(0, -1) : "";
const id = (repositoryId + subDirId)
.replace(/[:\/]/g, "~")
.toLowerCase();
const repoDir = `Repositories/${id}`;
const resultDir = `Results-${version}`;
const outputFile = path.resolve(`${resultDir}/${id}.json`);
console.log(` rawRepositoryUrl: ${rawRepositoryUrl}`);
console.log(` repositoryUrl: ${repositoryUrl}`);
console.log(` subDir: ${subDir}`);
console.log(` id: ${id}`);
console.log(` repoDir: ${repoDir}`);
console.log(` outputFile: ${outputFile}`);
const parseRef = text => {
try {
const regRefName = /^refs\/(tags\/|remotes\/origin\/)([^\/]+)$/;
const hash = text.split(/\s+/)[0];
const ref = text.split(/\s+/)[1];
const refName = ref.match(regRefName)[2];
console.log(` checkout: ${ref}`);
execSync(`git checkout -q ${ref} -- ${subDir}package.json ${subDir}package.json.meta`);
const p = JSON.parse(fs.readFileSync(`${subDir}package.json`, "utf8"));
// Check supported Unity version.
const unity = p.unity || "2018.3";
const unityRelease = p.unityRelease || "0a0";
// Parse author.
var author = "";
if (!p.author) { }
else if (typeof p.author === 'string') {
author = /^[^<(]*/.exec(p.author)[0].trim();
}
else if (typeof p.author === 'object') {
author = p.author.name;
}
// Parse dependencies.
var m_Dependencies = [];
if (p.dependencies && typeof p.dependencies === 'object') {
m_Dependencies = Object.keys(p.dependencies)
.map(key => { return { m_Name: key, m_Version: p.dependencies[key] } });
}
//
var docUrl = "";
if (p.documentationUrl) docUrl = p.documentationUrl;
var changeUrl = "";
if (p.changelogUrl) changeUrl = p.changelogUrl;
//
return {
refName,
hash,
m_MinimumUnityVersion: `${unity}.${unityRelease}`,
m_DisplayName: p.displayName,
m_Description: p.description,
m_PackageUniqueId: p.name,
m_Name: p.name,
m_PackageId: `${p.name}@${repositoryUrlWithPath}#${refName}`,
m_IsUnityPackage: false,
m_IsInstalled: false,
m_IsFullyFetched: true,
m_Author: author,
m_VersionString: p.version,
m_Tag: 4,
m_Version: p.version,
m_Source: 5,
m_Dependencies,
m_HasRepository: true,
m_Repository: {
m_Type: "git",
m_Url: repositoryUrl,
m_Revision: refName,
m_Path: subDir,
},
packageInfo: {
m_PackageId: `${p.name}@${repositoryUrlWithPath}#${refName}`,
m_Name: p.name,
m_DisplayName: p.displayName,
m_Description: p.description,
m_Version: p.version,
m_Source: 5,
m_Dependencies,
m_Git: {
m_Hash: hash,
m_Revision: refName,
},
m_Author: {
m_Name: author,
},
m_HasRepository: true,
m_Repository: {
m_Type: "git",
m_Url: repositoryUrl,
m_Revision: refName,
m_Path: subDir,
},
m_DocumentationUrl: docUrl,
m_ChangelogUrl: changeUrl,
}
};
} catch (e) {
return undefined;
}
};
// Start task to get available packages
console.log("\n#### Start task to get available packages ####");
// Make dir and change current dir
console.log("\n>> Make directory and change current working directory");
mkdirSyncRecrusive("Assets");
mkdirSyncRecrusive(repoDir);
mkdirSyncRecrusive(resultDir);
process.chdir(repoDir);
console.log(`cwd: ${process.cwd()}`);
// Check lock file and keep time
if (!shouldFetch(outputFile))
process.exit(0);
try {
// Lock.
lock(outputFile);
// Init git.
if (!fs.existsSync(".git")) {
console.log(`\n>> Init git at ${repoDir}. origin is ${repositoryUrl}`);
execSync("git init");
execSync(`git remote add origin ${repositoryUrl.replace(/^git\+/, "")}`);
}
// Fast fetch repo to get refs
console.log("\n>> Fast fetch repo to get refs");
execSync(
'git fetch --depth=1 -fq --prune origin "refs/tags/*:refs/tags/*" "+refs/heads/*:refs/remotes/origin/*"'
);
// Get revisions
console.log("\n>> Get revisions");
const refs = execSync("git show-ref", { encoding: "utf-8" });
console.log(refs);
// Check previous hash.
var hash = hashCode(version + id + refs);
console.log(`hash: ${hash}`);
if (hash === loadJs(outputFile).hash) {
console.warn("previous result has same hash. update modified-timestamp.");
touch(outputFile);
unlock(outputFile);
process.exit(0);
}
console.log("\n>> Get package version from revisions");
const versions = refs
.split(/[\r\n]+/)
.map(x => parseRef(x))
.filter(x => x);
// Output valid package references to file
console.log(`\n>> Output valid package (${versions.length} versions) references to file: ${outputFile}`);
// console.dir(versions, { depth: 5 });
fs.writeFileSync(outputFile, JSON.stringify({ id, url: rawRepositoryUrl, hash, versions }, space = 2), "utf-8");
console.log("\n######## Complete ########");
} finally {
// Unlock
unlock(outputFile);
}
fileFormatVersion: 2
guid: 51acca1b6897a7d4f82b4f94a785ede2
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
const fs = require("fs");
const hashCode = str => {
return Array.from(str)
.reduce((s, c) => Math.imul(31, s) + c.charCodeAt(0) | 0, 0)
}
const mkdirSyncRecrusive = postPath => {
postPath.split("/").reduce((acc, item) => {
const path = item ? (acc ? [acc, item].join("/") : item) : "";
if (path && !fs.existsSync(path)) {
console.log(` Make dir: ${path}`);
try {
fs.mkdirSync(path);
} catch (e) {
}
}
return path;
}, "");
};
const shouldFetch = filePath => {
// Check previous result (keep: 2 min).
console.log(`\n>> Check previous result (keep: 5 min)`);
if (fs.existsSync(filePath)) {
const diff = Date.now() - fs.statSync(filePath).mtime.getTime();
const diffMinutes = Math.floor(diff / 1000 / 60);
console.log(`last modified: ${diffMinutes} minute(s) ago`);
if (diffMinutes < 5) {
console.warn(`keep previous result.`);
return false;
}
}
// Check .lock file (timeout: 5 min).
console.log(`\n>> Check .lock file (timeout: 5 min)`);
const lockFile = `${filePath}.lock`;
if (fs.existsSync(lockFile)) {
const diff = Date.now() - fs.statSync(lockFile).mtime.getTime();
const diffMinutes = Math.floor(diff / 1000 / 60);
console.log(`last modified: ${diffMinutes} minute(s) ago`);
if (diffMinutes < 5) {
console.warn("previous task is running.");
return false;
} else {
console.warn("previous task is timeout.");
fs.unlinkSync(lockFile);
}
}
return true;
};
const touch = (filePath) => {
const now = new Date();
fs.utimesSync(filePath, now, now);
}
const lock = (filePath) => {
const lockFile = `${filePath}.lock`;
console.log(`\n>> Lock file cleated: ${lockFile}`);
fs.writeFileSync(lockFile, "", "utf-8");
}
const unlock = (filePath) => {
const lockFile = `${filePath}.lock`;
console.log(`\n>> Lock file will be removed: ${lockFile}`);
if (fs.existsSync(lockFile)) fs.unlinkSync(lockFile);
}
const loadJs = (filePath) => {
try {
return JSON.parse(fs.readFileSync(filePath, "utf8"));
} catch (e) {
return {};
}
}
module.exports = { hashCode, mkdirSyncRecrusive, shouldFetch, lock, unlock, loadJs, touch };
fileFormatVersion: 2
guid: 10bc320b12cbd4255b51ca9b0ec056fe
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
......@@ -542,6 +542,9 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
TestAsset: {fileID: 4900000, guid: bfa32699dcf2c2847b4cb1205abefca5, type: 3}
NodeJsPath: Tools/nodejs/node.exe
FetchPackagePath: Assets/Commands/fetch-packages.js
WorkingDirectory: E:/GitFetchResult
--- !u!4 &1734765437
Transform:
m_ObjectHideFlags: 0
......
......@@ -7,8 +7,8 @@
*版本: 1.0
*/
using Newtonsoft.Json;
using Sirenix.OdinInspector;
using System.IO;
using UnityEngine;
namespace UpmGitTool
......@@ -33,27 +33,61 @@ namespace UpmGitTool
{
if (TestAsset == null) return;
var info = JsonConvert.DeserializeObject<UnityManifes>(TestAsset.text);
Debug.LogError("Info : " + info.dependencies);
var pkgs = info.GenPackageItems();
foreach (var pkg in pkgs)
{
if (!pkg.IsGitUrl) return;
Debug.LogError($"{pkg.Name} : {pkg.GitPath} | {pkg.BranchName}");
}
AppState.LoadUnityManifes(TestAsset.text);
}
[LabelText("Node.exe路径")]
[FilePath]
public string NodeJsPath;
[LabelText("Js命令路径")]
[FilePath]
public string FetchPackagePath;
[LabelText("工作路径")]
[FolderPath(AbsolutePath = true)]
public string WorkingDirectory;
[Button]
public void Fetch()
{
var L = AppState.unityManifes.ListPackageItems;
int length = L.Count;
for (int i = 0; i < length; i++)
{
var pkg = L[i];
if (!pkg.IsGitUrl) return;
var url = pkg.GitPath;
var args = $"\"{Path.GetFullPath(FetchPackagePath)}\" {url}";
var p = new NativeProgram(NodeJsPath, args, WorkingDirectory);
p.Start((_, __) =>
{
var exitCode = p._process.ExitCode;
if (exitCode != 0)
{
Debug.LogError(p.GetAllOutput());
}
pkg.OnFetchResult(exitCode);
});
}
}
[Button]
public void ReadFetchResult()
{
string resultDir = $"{WorkingDirectory}/Results-2.0.0";
foreach (var file in Directory.GetFiles(resultDir, "*.json"))
{
//var path = Path.Combine(resultDir, file);
var text = File.ReadAllText(file, System.Text.Encoding.UTF8);
var result = JsonUtility.FromJson<FetchResult>(text);
Debug.Log(result.versions);
}
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace UpmGitTool
{
......@@ -11,7 +7,23 @@ namespace UpmGitTool
/// </summary>
public static class AppState
{
public static UnityManifes unityManifes { get; private set; }
}
public static void LoadUnityManifes(string jsonStr)
{
unityManifes = JsonConvert.DeserializeObject<UnityManifes>(jsonStr);
unityManifes.OnSerialized();
}
/// <summary>
/// NodeJs的文件路径
/// </summary>
public static string Path_NodeJsExe;
/// <summary>
/// Fetch Js 命令
/// </summary>
public static string Path_FetchJsCmd;
}
}
\ No newline at end of file
fileFormatVersion: 2
guid: c75b2bdf7eef36f43b46094a3bac36ac
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
/*
*Copyright(C) 2023 by Cocklebur All rights reserved.
*Unity版本:2022.2.1f1c1
*作者:Chief
*创建日期: 2022-12-22
*模块说明:包信息模块
*版本: 1.0
*/
using System;
using UnityEngine;
namespace UpmGitTool
{
/// <summary>
/// 包依赖
/// </summary>
[Serializable]
public struct DependencyInfo
{
[SerializeField]
private string m_Name;
[SerializeField]
private string m_Version;
public string version => m_Version;
public string name => m_Name;
}
}
\ No newline at end of file
fileFormatVersion: 2
guid: 8bf38ddcba1275e448ab6b4553fdec9f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
/*
*Copyright(C) 2023 by Cocklebur All rights reserved.
*Unity版本:2022.2.1f1c1
*作者:Chief
*创建日期: 2022-12-22
*模块说明:Git拉取结果部分
*版本: 1.0
*/
using System;
using UnityEngine;
namespace UpmGitTool
{
/// <summary>
/// 拉取结果
/// </summary>
[Serializable]
public class FetchResult : ISerializationCallbackReceiver
{
public string id;
public string url;
public int hash;
public UpmPackageVersion[] versions;
public override int GetHashCode() { return hash; }
public void OnBeforeSerialize()
{
}
public void OnAfterDeserialize()
{
}
}
}
\ No newline at end of file
fileFormatVersion: 2
guid: 1be230d9831113b44a521f1291be7d0d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
/*
*Copyright(C) 2023 by Cocklebur All rights reserved.
*Unity版本:2022.2.1f1c1
*作者:Chief
*创建日期: 2022-12-22
*模块说明:包信息模块
*版本: 1.0
*/
using System;
namespace UpmGitTool
{
/// <summary>
/// 版本
/// </summary>
public struct SemVersion : IEquatable<SemVersion>, IComparable<SemVersion>, IComparable
{
public int Major;
public int Minor;
public int Patch;
public string Prerelease;
public string Build;
public SemVersion(int major, int minor = 0, int patch = 0, string prerelease = "", string build = "")
{
Major = major;
Minor = minor;
Patch = patch;
Prerelease = prerelease ?? "";
Build = build ?? "";
}
public static int Compare(SemVersion versionA, SemVersion versionB)
{
return versionA.CompareTo(versionB);
}
public static bool operator ==(SemVersion left, SemVersion right)
{
return object.Equals(left, right);
}
public static bool operator !=(SemVersion left, SemVersion right)
{
return !object.Equals(left, right);
}
public static bool operator >(SemVersion left, SemVersion right)
{
return Compare(left, right) > 0;
}
public static bool operator >=(SemVersion left, SemVersion right)
{
return left == right || left > right;
}
public static bool operator <(SemVersion left, SemVersion right)
{
return Compare(left, right) < 0;
}
public static bool operator <=(SemVersion left, SemVersion right)
{
return left == right || left < right;
}
public bool Equals(SemVersion other)
{
return Major == other.Major && Minor == other.Minor && Patch == other.Patch && string.Equals(Prerelease, other.Prerelease, StringComparison.Ordinal);
}
public override bool Equals(object other)
{
if (other == null)
{
return false;
}
return (object)other.GetType() == GetType() && Equals((SemVersion)other);
}
public int CompareTo(SemVersion other)
{
int num = Major.CompareTo(other.Major);
if (num != 0)
{
return num;
}
num = Minor.CompareTo(other.Minor);
if (num != 0)
{
return num;
}
num = Patch.CompareTo(other.Patch);
if (num != 0)
{
return num;
}
num = CompareExtension(Prerelease, other.Prerelease, lower: true);
if (num != 0)
{
return num;
}
return 0;
}
private static int CompareExtension(string current, string other, bool lower = false)
{
bool flag = string.IsNullOrEmpty(current);
bool flag2 = string.IsNullOrEmpty(other);
if (flag && flag2)
{
return 0;
}
if (flag)
{
return lower ? 1 : (-1);
}
if (flag2)
{
return (!lower) ? 1 : (-1);
}
string[] array = current.Split('.');
string[] array2 = other.Split('.');
for (int i = 0; i < Math.Min(array.Length, array2.Length); i++)
{
string text = array[i];
string text2 = array2[i];
int result;
bool flag3 = int.TryParse(text, out result);
int result2;
bool flag4 = int.TryParse(text2, out result2);
int num;
if (flag3 && flag4)
{
num = result.CompareTo(result2);
if (num != 0)
{
return num;
}
continue;
}
if (flag3)
{
return -1;
}
if (flag4)
{
return 1;
}
num = string.CompareOrdinal(text, text2);
if (num != 0)
{
return num;
}
}
return array.Length.CompareTo(array2.Length);
}
public int CompareTo(object obj)
{
return CompareTo((SemVersion)obj);
}
public override string ToString()
{
string text = $"{Major}.{Minor}.{Patch}";
if (!string.IsNullOrEmpty(Prerelease))
{
text = text + "-" + Prerelease;
}
if (!string.IsNullOrEmpty(Build))
{
text = text + "+" + Build;
}
return text;
}
public override int GetHashCode()
{
int hashCode = Major.GetHashCode();
hashCode = hashCode * 31 + Minor.GetHashCode();
hashCode = hashCode * 31 + Patch.GetHashCode();
hashCode = hashCode * 31 + Prerelease.GetHashCode();
return hashCode * 31 + Build.GetHashCode();
}
public static SemVersion Parse(string version)
{
int cursor = 0;
int num = 0;
int minor = 0;
int patch = 0;
string prerelease = null;
string build = null;
try
{
num = int.Parse(ConsumeVersionComponentFromString(version, ref cursor, (char x) => !char.IsDigit(x)));
if (cursor < version.Length && version[cursor] == '.')
{
cursor++;
minor = int.Parse(ConsumeVersionComponentFromString(version, ref cursor, (char x) => !char.IsDigit(x)) ?? "0");
}
if (cursor < version.Length && version[cursor] == '.')
{
cursor++;
patch = int.Parse(ConsumeVersionComponentFromString(version, ref cursor, (char x) => !char.IsDigit(x)) ?? "0");
}
if (cursor < version.Length && version[cursor] == '-')
{
cursor++;
prerelease = ConsumeVersionComponentFromString(version, ref cursor, (char x) => x == '+');
}
if (cursor < version.Length && version[cursor] == '+')
{
cursor++;
build = ConsumeVersionComponentFromString(version, ref cursor, (char x) => x == '\0');
}
}
catch (Exception)
{
throw new ArgumentException(version + " is not valid Semantic Version");
}
return new SemVersion(num, minor, patch, prerelease, build);
}
public static string ConsumeVersionComponentFromString(string value, ref int cursor, Func<char, bool> isEnd)
{
int num = 0;
for (int i = cursor; i < value.Length && !isEnd(value[i]); i++)
{
num++;
}
int startIndex = cursor;
cursor += num;
return value.Substring(startIndex, num);
}
}
}
\ No newline at end of file
fileFormatVersion: 2
guid: f78e958a394c08d4aadb77348756cd9d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
/*
*Copyright(C) 2023 by Cocklebur All rights reserved.
*Unity版本:2022.2.1f1c1
*作者:Chief
*创建日期: 2022-12-22
*模块说明:Git拉取结果部分
*版本: 1.0
*/
using System;
using UnityEngine;
namespace UpmGitTool
{
/// <summary>
/// UPM包版本
/// 这个只针对Git下载的包处理
/// </summary>
[Serializable]
public class UpmPackageVersion
{
[SerializeField] private string refName;
[SerializeField] private string hash;
[SerializeField] private string m_MinimumUnityVersion;
[SerializeField] private string m_DisplayName;
[SerializeField] private string m_Description;
[SerializeField] private string m_PackageUniqueId;
[SerializeField] private string m_Name;
[SerializeField] private string m_PackageId;
[SerializeField] private string m_Author;
[SerializeField] private string m_VersionString;
[SerializeField] private SemVersion? m_Version;
[SerializeField] private DependencyInfo[] m_Dependencies;
[SerializeField] private string m_DocumentationUrl;
[SerializeField] private string m_ChangelogUrl;
}
}
\ No newline at end of file
fileFormatVersion: 2
guid: e89fc6e9b216f2a418e6b3a68b862f0b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
......@@ -49,6 +49,10 @@ namespace UpmGitTool
public string BranchName { get; private set; }
public void OnFetchResult(int exitCode)
{
}
}
}
\ No newline at end of file
......@@ -23,10 +23,11 @@ namespace UpmGitTool
[SerializeField]
public Dictionary<string, string> dependencies;
public List<PackageItem> ListPackageItems { get; private set; }
public List<PackageItem> GenPackageItems()
public void OnSerialized()
{
var L = new List<PackageItem>();
ListPackageItems = new List<PackageItem>();
foreach (var item in dependencies)
{
......@@ -34,10 +35,8 @@ namespace UpmGitTool
var value = item.Value;
var pkg = new PackageItem(key, value);
L.Add(pkg);
ListPackageItems.Add(pkg);
}
return L;
}
}
......
......@@ -32,5 +32,20 @@ namespace UpmGitTool
_process.StartInfo = startInfo;
}
public NativeProgram(string executable, string arguments,string workingDirectory)
{
ProcessStartInfo startInfo = new ProcessStartInfo
{
Arguments = arguments,
CreateNoWindow = true,
FileName = executable,
RedirectStandardError = true,
RedirectStandardOutput = true,
WorkingDirectory = workingDirectory,
UseShellExecute = false
};
_process.StartInfo = startInfo;
}
}
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册