BuildTask.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  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", ".bat" };
  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. //以文件名标识分组
  98. for (int j = 0; j < buildSetting.dirTypeList.Count; j++)
  99. {
  100. string str = buildSetting.dirTypeList[j];
  101. if (bundle.IndexOf(str) >= 0)
  102. {
  103. bundle = str.Substring(0, str.Length - 1);
  104. break;
  105. }
  106. }
  107. bundledAssets.Add(new Asset
  108. {
  109. path = filePath,
  110. bundle = bundle
  111. });
  112. }
  113. });
  114. //以子文件夹为单位分组
  115. foreach (var dir in dirBundleList)
  116. {
  117. var dirPath = Path.Combine(Environment.CurrentDirectory, dir);
  118. if (!GFGEditor.FileUtil.CheckPathInParent(dirPath, path))
  119. {
  120. continue;
  121. }
  122. GFGEditor.FileUtil.ForeachDirInDir(dirPath, (string subDirPath) =>
  123. {
  124. var targetDirPath = subDirPath.Replace('\\', '/');
  125. string curDirPath = Environment.CurrentDirectory.Replace("\\", "/");
  126. string subDir = targetDirPath.Replace(curDirPath + "/", "");
  127. var bundle = subDir.Replace($"Assets/{resRootDirName}/", "");
  128. bundle = bundle.Replace('/', '_');
  129. bundle = bundle.ToLower();
  130. GFGEditor.FileUtil.ForeachFileInDir(subDirPath, null, (string file) =>
  131. {
  132. var ext = Path.GetExtension(file);
  133. file = file.Replace('\\', '/');
  134. string curDir = Environment.CurrentDirectory.Replace("\\", "/");
  135. string filePath = file.Replace(curDir + "/", "");
  136. EditorUtility.DisplayProgressBar("采集资源", filePath, 1f);
  137. bundledAssets.Add(new Asset
  138. {
  139. path = filePath,
  140. bundle = bundle
  141. });
  142. });
  143. });
  144. }
  145. }
  146. private void CheckAssets()
  147. {
  148. for (var i = 0; i < bundledAssets.Count; i++)
  149. {
  150. var asset = bundledAssets[i];
  151. //去重
  152. if (!pathWithAssets.TryGetValue(asset.path, out var ba))
  153. {
  154. pathWithAssets[asset.path] = asset;
  155. }
  156. else
  157. {
  158. bundledAssets.RemoveAt(i);
  159. i--;
  160. Debug.LogWarningFormat("{0} can't pack with {1}, because already pack to {2}", asset.path,
  161. asset.bundle, ba.bundle);
  162. }
  163. }
  164. }
  165. private void FinishBuild()
  166. {
  167. var bundles = new List<ManifestBundle>();
  168. var dictionary = new Dictionary<string, List<string>>();
  169. //分组
  170. foreach (var asset in bundledAssets)
  171. {
  172. if (!dictionary.TryGetValue(asset.bundle, out var assets))
  173. {
  174. assets = new List<string>();
  175. dictionary.Add(asset.bundle, assets);
  176. bundles.Add(new ManifestBundle
  177. {
  178. name = asset.bundle,
  179. assets = assets
  180. });
  181. }
  182. assets.Add(asset.path);
  183. }
  184. var unityBuildPath = Settings.PlatformBuildPath;
  185. if (!string.IsNullOrEmpty(GFGGame.LauncherConfig.resKey))
  186. {
  187. unityBuildPath = Settings.UnityBuildPath;
  188. }
  189. if (bundles.Count <= 0) return;
  190. var manifest = BuildPipeline.BuildAssetBundles(unityBuildPath, bundles.ConvertAll(bundle =>
  191. new AssetBundleBuild
  192. {
  193. assetNames = bundle.assets.ToArray(),
  194. assetBundleName = bundle.name
  195. }).ToArray(),
  196. buildAssetBundleOptions | BuildAssetBundleOptions.AppendHashToAssetBundleName,
  197. EditorUserBuildSettings.activeBuildTarget);
  198. if (manifest == null)
  199. {
  200. Debug.LogErrorFormat("Failed to build {0}.", name);
  201. return;
  202. }
  203. //if (!string.IsNullOrEmpty(GFGGame.LauncherConfig.resKey))
  204. //{
  205. // string buildPath = Settings.PlatformBuildPath;
  206. // CreateEncryptAssets(unityBuildPath, buildPath, manifest, GFGGame.LauncherConfig.resKey);
  207. //}
  208. AfterBuildBundles(bundles, manifest);
  209. }
  210. /// <summary>
  211. /// 创建加密的AssetBundle
  212. /// </summary>
  213. //public static void CreateEncryptAssets(string bundlePackagePath, string encryptAssetPath, AssetBundleManifest manifest, string secretKey)
  214. //{
  215. // if (!Directory.Exists(encryptAssetPath))
  216. // {
  217. // Directory.CreateDirectory(encryptAssetPath);
  218. // }
  219. // string[] assetBundles = manifest.GetAllAssetBundles();
  220. // foreach (string assetBundle in assetBundles)
  221. // {
  222. // string bundlePath = Path.Combine(bundlePackagePath, assetBundle);
  223. // byte[] encryptBytes = EncryptHelper.CreateEncryptData(bundlePath, secretKey);
  224. // using (FileStream fs = new FileStream(Path.Combine(encryptAssetPath, assetBundle), FileMode.OpenOrCreate))
  225. // {
  226. // fs.SetLength(0);
  227. // fs.Write(encryptBytes, 0, encryptBytes.Length);
  228. // }
  229. // }
  230. //}
  231. /// <summary>
  232. /// 创建加密的AssetBundle
  233. /// </summary>
  234. public static void CreateEncryptAsset(string unityBundlesPath, string bundlesPath, string unityBundle, string secretKey)
  235. {
  236. if (!Directory.Exists(bundlesPath))
  237. {
  238. Directory.CreateDirectory(bundlesPath);
  239. }
  240. string unityBundlePath = Path.Combine(unityBundlesPath, unityBundle);
  241. byte[] encryptBytes = EncryptHelper.CreateEncryptData(unityBundlePath, secretKey);
  242. using (FileStream fs = new FileStream(Path.Combine(bundlesPath, unityBundle), FileMode.OpenOrCreate))
  243. {
  244. fs.SetLength(0);
  245. fs.Write(encryptBytes, 0, encryptBytes.Length);
  246. }
  247. }
  248. private string GetOriginBundle(string assetBundle)
  249. {
  250. var pos = assetBundle.LastIndexOf("_", StringComparison.Ordinal) + 1;
  251. var hash = assetBundle.Substring(pos);
  252. if (!string.IsNullOrEmpty(bundleExtension)) hash = hash.Replace(bundleExtension, "");
  253. var originBundle = $"{assetBundle.Replace("_" + hash, "")}";
  254. return originBundle;
  255. }
  256. private void AfterBuildBundles(List<ManifestBundle> bundles,
  257. AssetBundleManifest manifest)
  258. {
  259. var nameWithBundles = new Dictionary<string, ManifestBundle>();
  260. for (var i = 0; i < bundles.Count; i++)
  261. {
  262. var bundle = bundles[i];
  263. bundle.id = i;
  264. nameWithBundles[bundle.name] = bundle;
  265. }
  266. if (manifest != null)
  267. {
  268. var assetBundles = manifest.GetAllAssetBundles();
  269. foreach (var assetBundle in assetBundles)
  270. {
  271. var originBundle = GetOriginBundle(assetBundle);
  272. var dependencies = Array.ConvertAll(manifest.GetAllDependencies(assetBundle), GetOriginBundle);
  273. if (nameWithBundles.TryGetValue(originBundle, out var manifestBundle))
  274. {
  275. manifestBundle.nameWithAppendHash = assetBundle;
  276. manifestBundle.dependencies = Array.ConvertAll(dependencies, input => nameWithBundles[input].id);
  277. }
  278. else
  279. {
  280. Debug.LogErrorFormat("Bundle not exist: {0}", originBundle);
  281. }
  282. }
  283. }
  284. CreateManifest(bundles);
  285. }
  286. private void CreateManifest(List<ManifestBundle> bundles)
  287. {
  288. var manifest = Settings.GetManifest();
  289. manifest.version++;
  290. manifest.appVersion = UnityEditor.PlayerSettings.bundleVersion;
  291. var getBundles = manifest.GetBundles();
  292. var newFiles = new List<string>();
  293. var newSize = 0L;
  294. foreach (var bundle in bundles)
  295. {
  296. if (!getBundles.TryGetValue(bundle.name, out var value) ||
  297. value.nameWithAppendHash != bundle.nameWithAppendHash)
  298. {
  299. newFiles.Add(bundle.nameWithAppendHash);
  300. if (!string.IsNullOrEmpty(GFGGame.LauncherConfig.resKey))
  301. {
  302. string buildPath = Settings.PlatformBuildPath;
  303. CreateEncryptAsset(Settings.UnityBuildPath, buildPath, bundle.nameWithAppendHash, GFGGame.LauncherConfig.resKey);
  304. }
  305. }
  306. var file = Settings.GetBuildPath(bundle.nameWithAppendHash);
  307. if (File.Exists(file))
  308. using (var stream = File.OpenRead(file))
  309. {
  310. bundle.size = stream.Length;
  311. bundle.crc = Utility.ComputeCRC32(stream);
  312. }
  313. else
  314. Debug.LogErrorFormat("File not found: {0}", file);
  315. newSize += bundle.size;
  316. }
  317. manifest.bundles = bundles;
  318. var newFilesSize = Utility.FormatBytes(newSize);
  319. newFiles.AddRange(WriteManifest(manifest));
  320. // write upload files
  321. var filename = Settings.GetBuildPath($"upload_files_for_{manifest.name}_{manifest.version}.txt");
  322. File.WriteAllText(filename, string.Join("\n", newFiles.ToArray()));
  323. record = new Record
  324. {
  325. build = name,
  326. version = manifest.version,
  327. files = newFiles,
  328. size = newSize,
  329. time = DateTime.Now.ToFileTime()
  330. };
  331. WriteRecord(record);
  332. Debug.LogFormat("Build bundles with {0}({1}) files with version {2} for {3}.", newFiles.Count, newFilesSize,
  333. manifest.version, manifest.name);
  334. }
  335. private static IEnumerable<string> WriteManifest(Manifest manifest)
  336. {
  337. var newFiles = new List<string>();
  338. var filename = $"{manifest.name}";
  339. var version = manifest.version;
  340. WriteJson(manifest, filename, newFiles);
  341. var path = Settings.GetBuildPath(filename);
  342. var crc = Utility.ComputeCRC32(path);
  343. var info = new FileInfo(path);
  344. WriteJson(manifest, $"{filename}_v{version}_{crc}", newFiles);
  345. // for version file
  346. var manifestVersion = ScriptableObject.CreateInstance<ManifestVersion>();
  347. manifestVersion.crc = crc;
  348. manifestVersion.size = info.Length;
  349. manifestVersion.version = version;
  350. manifestVersion.appVersion = manifest.appVersion;
  351. WriteJson(manifestVersion, Manifest.GetVersionFile(filename), newFiles);
  352. WriteJson(manifestVersion, $"{filename}_v{version}_{crc}.version", newFiles);
  353. return newFiles;
  354. }
  355. private static void WriteJson(ScriptableObject so, string file, List<string> newFiles)
  356. {
  357. newFiles.Add(file);
  358. var json = JsonUtility.ToJson(so);
  359. File.WriteAllText(Settings.GetBuildPath(file), json);
  360. }
  361. }
  362. }