BuildTask.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using UnityEditor;
  5. using UnityEngine;
  6. namespace VEngine.Editor.Builds
  7. {
  8. public class BuildTask
  9. {
  10. private readonly string[] EXCLUDE_EXTS = new string[] { ".meta" };
  11. private readonly BuildAssetBundleOptions buildAssetBundleOptions;
  12. private readonly List<Asset> bundledAssets = new List<Asset>();
  13. private readonly string bundleExtension;
  14. public readonly string name;
  15. private readonly Dictionary<string, Asset> pathWithAssets = new Dictionary<string, Asset>();
  16. public BuildTask()
  17. {
  18. name = nameof(Manifest);
  19. buildAssetBundleOptions = BuildAssetBundleOptions.ChunkBasedCompression |
  20. BuildAssetBundleOptions.AppendHashToAssetBundleName;
  21. bundleExtension = ".unity3d";
  22. }
  23. public Record record { get; private set; }
  24. private static string GetRecordsPath(string buildName)
  25. {
  26. return Settings.GetBuildPath($"build_records_for_{buildName}.json");
  27. }
  28. private static void WriteRecord(Record record)
  29. {
  30. var records = GetRecords(record.build);
  31. records.data.Insert(0, record);
  32. File.WriteAllText(GetRecordsPath(record.build), JsonUtility.ToJson(records));
  33. }
  34. private static Records GetRecords(string build)
  35. {
  36. var records = ScriptableObject.CreateInstance<Records>();
  37. var path = GetRecordsPath(build);
  38. if (File.Exists(path)) JsonUtility.FromJsonOverwrite(File.ReadAllText(path), records);
  39. return records;
  40. }
  41. private static void DisplayProgressBar(string title, string content, int index, int max)
  42. {
  43. EditorUtility.DisplayProgressBar($"{title}({index}/{max}) ", content,
  44. index * 1f / max);
  45. }
  46. public void BuildBundles()
  47. {
  48. var assetBundleNames = AssetDatabase.GetAllAssetBundleNames();
  49. for (var i = 0; i < assetBundleNames.Length; i++)
  50. {
  51. var assetBundleName = assetBundleNames[i];
  52. DisplayProgressBar("采集资源", assetBundleName, i, assetBundleNames.Length);
  53. var assetNames = AssetDatabase.GetAssetPathsFromAssetBundle(assetBundleName);
  54. bundledAssets.AddRange(Array.ConvertAll(assetNames, input => new Asset
  55. {
  56. path = input,
  57. bundle = assetBundleName
  58. }));
  59. }
  60. CheckAssets();
  61. EditorUtility.ClearProgressBar();
  62. FinishBuild();
  63. }
  64. public void BuildCustomBundles(string[] resRootDirNames)
  65. {
  66. foreach(var resRootDirName in resRootDirNames)
  67. {
  68. CreateBundles(resRootDirName);
  69. }
  70. CheckAssets();
  71. EditorUtility.ClearProgressBar();
  72. FinishBuild();
  73. }
  74. private void CreateBundles(string resRootDirName)
  75. {
  76. var path = Path.Combine(Application.dataPath, resRootDirName);
  77. var buildSetting = GFGEditor.BuildSetting.GetBuildSetting();
  78. var dirBundleList = buildSetting.dirBundleList;
  79. GFGEditor.FileUtil.ForeachFileInDir(path, dirBundleList, (string file) =>
  80. {
  81. var ext = Path.GetExtension(file);
  82. if (Array.IndexOf(EXCLUDE_EXTS, ext) < 0)
  83. {
  84. file = file.Replace('\\', '/');
  85. string curDir = Environment.CurrentDirectory.Replace("\\", "/");
  86. string filePath = file.Replace(curDir + "/", "");
  87. EditorUtility.DisplayProgressBar("采集资源", filePath, 1f);
  88. var bundle = filePath.Replace($"Assets/{resRootDirName}/", "");
  89. bundle = bundle.Replace('/', '_');
  90. var i = bundle.IndexOf(".");
  91. bundle = bundle.Substring(0, i);
  92. bundle = bundle.ToLower();
  93. bundledAssets.Add(new Asset
  94. {
  95. path = filePath,
  96. bundle = bundle
  97. });
  98. }
  99. });
  100. foreach (var dir in dirBundleList)
  101. {
  102. var dirPath = Path.Combine(Environment.CurrentDirectory, dir);
  103. if(!GFGEditor.FileUtil.CheckPathInParent(dirPath, path))
  104. {
  105. continue;
  106. }
  107. GFGEditor.FileUtil.ForeachDirInDir(dirPath, (string subDirPath) =>
  108. {
  109. var targetDirPath = subDirPath.Replace('\\', '/');
  110. string curDirPath = Environment.CurrentDirectory.Replace("\\", "/");
  111. string subDir = targetDirPath.Replace(curDirPath + "/", "");
  112. var bundle = subDir.Replace($"Assets/{resRootDirName}/", "");
  113. bundle = bundle.Replace('/', '_');
  114. bundle = bundle.ToLower();
  115. GFGEditor.FileUtil.ForeachFileInDir(subDirPath, null, (string file) =>
  116. {
  117. var ext = Path.GetExtension(file);
  118. file = file.Replace('\\', '/');
  119. string curDir = Environment.CurrentDirectory.Replace("\\", "/");
  120. string filePath = file.Replace(curDir + "/", "");
  121. EditorUtility.DisplayProgressBar("采集资源", filePath, 1f);
  122. bundledAssets.Add(new Asset
  123. {
  124. path = filePath,
  125. bundle = bundle
  126. });
  127. });
  128. });
  129. }
  130. }
  131. private void CheckAssets()
  132. {
  133. for (var i = 0; i < bundledAssets.Count; i++)
  134. {
  135. var asset = bundledAssets[i];
  136. if (!pathWithAssets.TryGetValue(asset.path, out var ba))
  137. {
  138. pathWithAssets[asset.path] = asset;
  139. }
  140. else
  141. {
  142. bundledAssets.RemoveAt(i);
  143. i--;
  144. Debug.LogWarningFormat("{0} can't pack with {1}, because already pack to {2}", asset.path,
  145. asset.bundle, ba.bundle);
  146. }
  147. }
  148. }
  149. private void FinishBuild()
  150. {
  151. var bundles = new List<ManifestBundle>();
  152. var dictionary = new Dictionary<string, List<string>>();
  153. foreach (var asset in bundledAssets)
  154. {
  155. if (!dictionary.TryGetValue(asset.bundle, out var assets))
  156. {
  157. assets = new List<string>();
  158. dictionary.Add(asset.bundle, assets);
  159. bundles.Add(new ManifestBundle
  160. {
  161. name = asset.bundle,
  162. assets = assets
  163. });
  164. }
  165. assets.Add(asset.path);
  166. }
  167. var outputPath = Settings.PlatformBuildPath;
  168. if (bundles.Count <= 0) return;
  169. var manifest = BuildPipeline.BuildAssetBundles(outputPath, bundles.ConvertAll(bundle =>
  170. new AssetBundleBuild
  171. {
  172. assetNames = bundle.assets.ToArray(),
  173. assetBundleName = bundle.name
  174. }).ToArray(),
  175. buildAssetBundleOptions | BuildAssetBundleOptions.AppendHashToAssetBundleName,
  176. EditorUserBuildSettings.activeBuildTarget);
  177. if (manifest == null)
  178. {
  179. Debug.LogErrorFormat("Failed to build {0}.", name);
  180. return;
  181. }
  182. AfterBuildBundles(bundles, manifest);
  183. }
  184. private string GetOriginBundle(string assetBundle)
  185. {
  186. var pos = assetBundle.LastIndexOf("_", StringComparison.Ordinal) + 1;
  187. var hash = assetBundle.Substring(pos);
  188. if (!string.IsNullOrEmpty(bundleExtension)) hash = hash.Replace(bundleExtension, "");
  189. var originBundle = $"{assetBundle.Replace("_" + hash, "")}";
  190. return originBundle;
  191. }
  192. private void AfterBuildBundles(List<ManifestBundle> bundles,
  193. AssetBundleManifest manifest)
  194. {
  195. var nameWithBundles = new Dictionary<string, ManifestBundle>();
  196. for (var i = 0; i < bundles.Count; i++)
  197. {
  198. var bundle = bundles[i];
  199. bundle.id = i;
  200. nameWithBundles[bundle.name] = bundle;
  201. }
  202. if (manifest != null)
  203. {
  204. var assetBundles = manifest.GetAllAssetBundles();
  205. foreach (var assetBundle in assetBundles)
  206. {
  207. var originBundle = GetOriginBundle(assetBundle);
  208. var dependencies =
  209. Array.ConvertAll(manifest.GetAllDependencies(assetBundle), GetOriginBundle);
  210. if (nameWithBundles.TryGetValue(originBundle, out var manifestBundle))
  211. {
  212. manifestBundle.nameWithAppendHash = assetBundle;
  213. manifestBundle.dependencies =
  214. Array.ConvertAll(dependencies, input => nameWithBundles[input].id);
  215. var file = Settings.GetBuildPath(assetBundle);
  216. if (File.Exists(file))
  217. using (var stream = File.OpenRead(file))
  218. {
  219. manifestBundle.size = stream.Length;
  220. manifestBundle.crc = Utility.ComputeCRC32(stream);
  221. }
  222. else
  223. Debug.LogErrorFormat("File not found: {0}", file);
  224. }
  225. else
  226. {
  227. Debug.LogErrorFormat("Bundle not exist: {0}", originBundle);
  228. }
  229. }
  230. }
  231. CreateManifest(bundles);
  232. }
  233. private void CreateManifest(List<ManifestBundle> bundles)
  234. {
  235. var manifest = Settings.GetManifest();
  236. manifest.version++;
  237. manifest.appVersion = UnityEditor.PlayerSettings.bundleVersion;
  238. var getBundles = manifest.GetBundles();
  239. var newFiles = new List<string>();
  240. var newSize = 0L;
  241. foreach (var bundle in bundles)
  242. if (!getBundles.TryGetValue(bundle.name, out var value) ||
  243. value.nameWithAppendHash != bundle.nameWithAppendHash)
  244. {
  245. newFiles.Add(bundle.nameWithAppendHash);
  246. newSize += bundle.size;
  247. }
  248. manifest.bundles = bundles;
  249. var newFilesSize = Utility.FormatBytes(newSize);
  250. newFiles.AddRange(WriteManifest(manifest));
  251. // write upload files
  252. var filename = Settings.GetBuildPath($"upload_files_for_{manifest.name}_{manifest.version}.txt");
  253. File.WriteAllText(filename, string.Join("\n", newFiles.ToArray()));
  254. record = new Record
  255. {
  256. build = name,
  257. version = manifest.version,
  258. files = newFiles,
  259. size = newSize,
  260. time = DateTime.Now.ToFileTime()
  261. };
  262. WriteRecord(record);
  263. Debug.LogFormat("Build bundles with {0}({1}) files with version {2} for {3}.", newFiles.Count, newFilesSize,
  264. manifest.version, manifest.name);
  265. }
  266. private static IEnumerable<string> WriteManifest(Manifest manifest)
  267. {
  268. var newFiles = new List<string>();
  269. var filename = $"{manifest.name}";
  270. var version = manifest.version;
  271. WriteJson(manifest, filename, newFiles);
  272. var path = Settings.GetBuildPath(filename);
  273. var crc = Utility.ComputeCRC32(path);
  274. var info = new FileInfo(path);
  275. WriteJson(manifest, $"{filename}_v{version}_{crc}", newFiles);
  276. // for version file
  277. var manifestVersion = ScriptableObject.CreateInstance<ManifestVersion>();
  278. manifestVersion.crc = crc;
  279. manifestVersion.size = info.Length;
  280. manifestVersion.version = version;
  281. manifestVersion.appVersion = manifest.appVersion;
  282. WriteJson(manifestVersion, Manifest.GetVersionFile(filename), newFiles);
  283. WriteJson(manifestVersion, $"{filename}_v{version}_{crc}.version", newFiles);
  284. return newFiles;
  285. }
  286. private static void WriteJson(ScriptableObject so, string file, List<string> newFiles)
  287. {
  288. newFiles.Add(file);
  289. var json = JsonUtility.ToJson(so);
  290. File.WriteAllText(Settings.GetBuildPath(file), json);
  291. }
  292. }
  293. }