BuildTask.cs 15 KB

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