459 lines
18 KiB
C#
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);
|
|
}
|
|
}
|
|
} |