TheInitialProject/Assets/CaoCao/Scripts/Editor/XAsset/Build/Builder.cs
2024-10-23 16:59:02 +08:00

459 lines
18 KiB
C#

using CaoCao.XAsset;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEngine;
namespace CaoCao.Editor
{
public static class Builder
{
public static IBuildPipeline BuildPipeline { get; set; } = new DefaultBuildPipeline();
public static Action<Build[], Settings> PreprocessBuildBundles { get; set; } = null;
public static Action<string[]> PostprocessBuildBundles { get; set; } = null;
//打包前所有文件
public static List<string> BuildBeforeAllFiles = new List<string>();
//打包后所有文件
public static List<string> BuildAfterAllFiles = new List<string>();
public static void BuildBundles(params Build[] builds)
{
FindBuildBeforeAllFiles();
BuildBundlesInternal(false, false, builds);
}
public static void BuildHotfix(params Build[] builds)
{
FindBuildBeforeAllFiles();
BuildBundlesInternal(false, true, builds);
}
public static void BuildBundlesWithLastBuild(params Build[] builds)
{
BuildBundlesInternal(true, false, builds);
}
private static void BuildBundlesInternal(bool withLastBuild, bool isHotfix, params Build[] builds)
{
var settings = Settings.GetDefaultSettings();
if (builds.Length == 0)
builds = Settings.FindAssets<Build>();
PreprocessBuildBundles?.Invoke(builds, settings);
if (settings.bundle.checkReference && FindReferences())
return;
CreateDirectories();
//获取到所有Build配置
var assets = Array.ConvertAll(builds, AssetDatabase.GetAssetPath);
if (assets.Length == 0)
{
XAsset.Logger.I("Nothing to build.");
return;
}
var watch = new Stopwatch();
watch.Start();
var changes = new List<string>();
foreach (var asset in assets)
{
var build = AssetDatabase.LoadAssetAtPath<Build>(asset);
var parameters = build.parameters;
parameters.name = build.name;
////只打hotfix 只收集
//if (isHotfix && build.name != "HotfixB")
//{
// var taskHotfix = BuildJob.StartNew(parameters, new LoadBuildAssets());
// continue;
//}
var tmps = build.name.Split('_');
#if UNITY_ANDROID
//HotfixB_Android
if (tmps.Length == 2)
{
if (tmps[1] != "Android")
continue;
}
#elif UNITY_IOS
//HotfixB_IOS
if (tmps.Length == 2)
{
if (tmps[1] != "IOS")
continue;
}
#else
//HotfixB_Win
if (tmps.Length == 2)
{
if (tmps[1] != "Win")
continue;
}
#endif
//只打hotfix
if (isHotfix && tmps[0] != "HotfixB")
{
//var taskHotfix = BuildJob.StartNew(parameters, new LoadBuildAssets());
continue;
}
var task = withLastBuild
? BuildJob.StartNew(parameters, new LoadBuildAssets(), new BuildBundles(), new BuildVersions())
: parameters.optimizeDependentAssets
? BuildJob.StartNew(parameters, new CollectAssets(), new ClearDuplicateAssets(),
new OptimizeDependentAssets(),
new SaveBuildAssets(),
new BuildBundles(),
new BuildVersions())
: BuildJob.StartNew(parameters, new CollectAssets(), new ClearDuplicateAssets(),
new SaveBuildAssets(),
new BuildBundles(),
new BuildVersions());
if (!string.IsNullOrEmpty(task.error))
{
XAsset.Logger.E(task.error);
}
else
{
if (task.changes.Count <= 0)
continue;
foreach (var change in task.changes)
changes.Add(Settings.GetDataPath(change));
}
}
watch.Stop();
XAsset.Logger.I($"Finish {nameof(BuildBundles)} with {watch.ElapsedMilliseconds / 1000f}s.");
if (changes.Count <= 0)
return;
PostprocessBuildBundles?.Invoke(changes.ToArray());
SaveVersions(changes);
}
private static void SaveVersions(List<string> changes)
{
var versions = Settings.GetDefaultVersions();
var path = Settings.GetDataPath(versions.GetFilename());
var configs = Settings.FindAssets<AssetPack>();
var packs = new List<string>();
foreach (var config in configs)
if (config.deliveryMode == AssetPack.DeliveryMode.InstallTime ||
config.deliveryMode == AssetPack.DeliveryMode.FastFollow)
packs.Add(config.name);
versions.preloadPacks = packs.ToArray();
versions.Save(Settings.GetCachePath(Versions.Filename));
versions.Save(path);
changes.Add(path);
PostprocessBuildBundles?.Invoke(changes.ToArray());
var file = new FileInfo(path);
BuildUpdateInfo(versions, XAsset.Utility.ComputeHash(path), file.Length);
// updateInfo.
var size = GetChanges(changes.ToArray(), versions.GetFilename());
var savePath = Settings.GetCachePath(BuildRecords.Filename);
var records = XAsset.Utility.LoadFromFile<BuildRecords>(savePath);
records.Set(versions.GetFilename(), changes.ToArray(), size);
var json = JsonUtility.ToJson(records);
File.WriteAllText(savePath, json);
CopyDifferenceFiles($"versions_v{versions.ToString()}");
}
public static void BuildUpdateInfo(Versions versions, string hash, long size)
{
var settings = Settings.GetDefaultSettings();
var downloadURL = $"{settings.downloadURL}{Assets.Bundles}/{Settings.Platform}/";
var updateInfoPath = Settings.GetCachePath(UpdateInfo.Filename);
var updateInfo = CaoCao.XAsset.Utility.LoadFromFile<UpdateInfo>(updateInfoPath);
updateInfo.hash = hash;
updateInfo.size = (ulong) size;
updateInfo.file = versions.GetFilename();
updateInfo.downloadURL = downloadURL;
//updateInfo.bigVersion = settings.BigVersion;
File.WriteAllText(updateInfoPath, JsonUtility.ToJson(updateInfo));
}
private static void CreateDirectories()
{
var directories = new List<string>
{
Settings.PlatformCachePath, Settings.PlatformDataPath
};
foreach (var directory in directories)
if (!Directory.Exists(directory))
Directory.CreateDirectory(directory);
}
public static bool FindReferences()
{
var builds = Settings.FindAssets<Build>();
if (builds.Length == 0)
{
XAsset.Logger.I("Nothing to build.");
return false;
}
var assets = new List<BuildAsset>();
foreach (var build in builds)
{
var item = build.parameters;
item.name = build.name;
var task = item.optimizeDependentAssets
? BuildJob.StartNew(item, new CollectAssets(), new OptimizeDependentAssets())
: BuildJob.StartNew(item, new CollectAssets());
if (!string.IsNullOrEmpty(task.error))
return true;
foreach (var asset in task.bundledAssets)
assets.Add(asset);
foreach (var asset in task.rawAssets)
assets.Add(asset);
}
var assetWithGroups = new Dictionary<string, HashSet<string>>();
foreach (var asset in assets)
{
if (!assetWithGroups.TryGetValue(asset.path, out var refs))
{
refs = new HashSet<string>();
assetWithGroups.Add(asset.path, refs);
}
refs.Add($"{asset.group.build}-{asset.group.name}");
}
var sb = new StringBuilder();
foreach (var pair in assetWithGroups.Where(pair => pair.Value.Count > 1))
{
sb.AppendLine(pair.Key);
foreach (var s in pair.Value)
sb.AppendLine(" - " + s);
}
var content = sb.ToString();
if (string.IsNullOrEmpty(content))
{
XAsset.Logger.I("Checking completed, Everything is ok.");
return false;
}
const string filename = "MultipleReferences.txt";
File.WriteAllText(filename, content);
if (EditorUtility.DisplayDialog("提示", "检测到多重引用关系,是否打开查看?", "确定"))
EditorUtility.OpenWithDefaultApp(filename);
return true;
}
public static void RemoveBundles(params string[] assetPaths)
{
var bundles = new HashSet<ManifestBundle>();
var versions = Settings.GetDefaultVersions();
foreach (var version in versions.data)
{
var manifest = CaoCao.XAsset.Utility.LoadFromFile<Manifest>(Settings.GetDataPath(version.file));
foreach (var assetPath in assetPaths)
{
var bundle = manifest.GetBundle(assetPath);
if (bundle != null) bundles.Add(bundle);
}
}
var files = new List<string>();
foreach (var bundle in bundles)
{
files.Add(Settings.GetCachePath(bundle.name));
files.Add(Settings.GetCachePath(bundle.name + ".manifest"));
files.Add(Settings.GetDataPath(bundle.file));
}
var sb = new StringBuilder();
sb.AppendLine("Delete Files:");
foreach (var file in files)
{
if (!File.Exists(file)) continue;
File.Delete(file);
sb.AppendLine($"Delete: {file}");
}
CaoCao.XAsset.Logger.I(sb.ToString());
}
public static ManifestBundle[] GetBundlesInBuild(Settings settings, Versions versions)
{
var set = new HashSet<ManifestBundle>();
var assets = new HashSet<string>();
//只有Settings中PlayerAssetsSplitMode设置为SplitByAssetPacksWithInstallTime或IncludeAllAssets才算
switch (settings.playerAssetsSplitMode)
{
case PlayerAssetsSplitMode.SplitByAssetPacksWithInstallTime:
var assetPackConfigs = Settings.FindAssets<AssetPack>();
var assetPacks = new List<AssetPack>();
//AssetPack中交付模式设置为InstallTime才算
foreach (var assetPack in assetPackConfigs)
{
if (assetPack.deliveryMode == AssetPack.DeliveryMode.InstallTime)
assetPacks.Add(assetPack);
}
foreach (var config in assetPacks)
{
//找到这个AssetPack中所有的asset
Settings.Collect(config, assets);
//只有在versions.data中的bundle才算
foreach (var version in versions.data)
{
var manifest = XAsset.Utility.LoadFromFile<Manifest>(Settings.GetDataPath(version.file));
foreach (var path in assets)
{
var bundle = manifest.GetBundle(path);
if (bundle == null)
continue;
set.Add(bundle);
set.UnionWith(manifest.GetDependencies(bundle));
}
}
}
break;
case PlayerAssetsSplitMode.ExcludeAllAssets:
break;
case PlayerAssetsSplitMode.IncludeAllAssets:
foreach (var version in versions.data)
{
var path = Settings.GetDataPath(version.file);
var manifest = XAsset.Utility.LoadFromFile<Manifest>(path);
set.UnionWith(manifest.bundles);
}
break;
}
return set.ToArray();
}
public static ulong GetChanges(IEnumerable<string> changes, string name)
{
var sb = new StringBuilder();
var size = 0UL;
var files = new List<FileInfo>();
foreach (var change in changes)
{
var file = new FileInfo(change);
if (!file.Exists) continue;
size += (ulong) file.Length;
files.Add(file);
}
files.Sort((a, b) => b.Length.CompareTo(a.Length));
foreach (var file in files) sb.AppendLine($"{file.FullName}({CaoCao.XAsset.Utility.FormatBytes((ulong) file.Length)})");
XAsset.Logger.I(size > 0
? $"GetChanges from {name} with following files({CaoCao.XAsset.Utility.FormatBytes(size)}):\n {sb}"
: "Nothing changed.");
return size;
}
public static void BuildReferences()
{
var builds = Settings.FindAssets<Build>();
var assets = new List<BuildAsset>();
foreach (var build in builds)
{
var job = BuildJob.StartNew(build.parameters, new CollectAssets(), new ClearDuplicateAssets(),
new OptimizeDependentAssets(),
new SaveBuildAssets());
assets.AddRange(job.bundledAssets);
assets.AddRange(job.rawAssets);
}
var cache = Settings.GetBuildAssetCache();
var newFiles = assets.Count - cache.data.Count;
XAsset.Logger.I($"Build Asset References with {newFiles} files.");
cache.data = assets;
cache.BuildReferences();
EditorUtility.SetDirty(cache);
AssetDatabase.SaveAssets();
Selection.activeObject = cache;
EditorUtility.FocusProjectWindow();
}
//-------------------------------------------------------比对差异文件-----------------------------------------------------
public static void FindBuildBeforeAllFiles()
{
string folderPath = Settings.PlatformDataPath + "/";
UnityEngine.Debug.Log($"热更资源文件夹 = {folderPath}");
if (!Directory.Exists(folderPath))
return;
BuildBeforeAllFiles = Directory.GetFiles(folderPath, "*", SearchOption.AllDirectories).ToList();
UnityEngine.Debug.Log($"打包前:文件数量 = {BuildBeforeAllFiles.Count}");
}
public static void CopyDifferenceFiles(string dirName)
{
string newFolder = $"{Environment.CurrentDirectory.Replace('\\', '/')}/{Assets.Bundles}/{dirName}";
if (!Directory.Exists(newFolder))
Directory.CreateDirectory(newFolder);
string folderPath = Settings.PlatformDataPath + "/";
BuildAfterAllFiles = Directory.GetFiles(folderPath, "*", SearchOption.AllDirectories).ToList();
UnityEngine.Debug.Log($"打包后:文件数量 = {BuildAfterAllFiles.Count}");
foreach(var fileName in BuildAfterAllFiles)
{
if (BuildBeforeAllFiles.Contains(fileName))
continue;
//分层建立文件夹 F:/HxMobileProject/Bundles/iOS/assetspackb/xxx.bundle
string t = fileName.Replace('\\', '/');
//assetspackb/xxx.bundle
string tmp = t.Replace(folderPath, "");
string[] tmps = tmp.Split('/');
foreach(var ss in tmps)
{
//xxx.bundle
if (ss.EndsWith(".bundle")
|| ss.EndsWith(".json"))
continue;
//ss = assetspackb
//F:/HxMobileProject/Bundles/iOS/assetspackb
string tmpDir = newFolder + "/" + ss;
if (!Directory.Exists(tmpDir))
Directory.CreateDirectory(tmpDir);
}
string dest = newFolder + "/" + tmp;
File.Copy(fileName, dest);
}
//复制updateinfo
var updateInfoPath = Settings.GetCachePath(UpdateInfo.Filename);
string destPath = newFolder + "/" + UpdateInfo.Filename;
File.Copy(updateInfoPath, destPath);
}
}
}