├── HotUpdater
├── HotUpdater.asmdef
└── HotUpdater.cs
├── JenkinsBuild.cs
├── LICENSE
└── README.md
/HotUpdater/HotUpdater.asmdef:
--------------------------------------------------------------------------------
1 | {
2 | "name": "HotUpdater",
3 | "rootNamespace": "",
4 | "references": [
5 | "GUID:9e24947de15b9834991c9d8411ea37cf",
6 | "GUID:84651a3751eca9349aac36a66bba901b",
7 | "GUID:13ba8ce62aa80c74598530029cb2d649"
8 | ],
9 | "includePlatforms": [],
10 | "excludePlatforms": [],
11 | "allowUnsafeCode": false,
12 | "overrideReferences": false,
13 | "precompiledReferences": [],
14 | "autoReferenced": true,
15 | "defineConstraints": [],
16 | "versionDefines": [],
17 | "noEngineReferences": false
18 | }
--------------------------------------------------------------------------------
/HotUpdater/HotUpdater.cs:
--------------------------------------------------------------------------------
1 | // HotUpdater
2 | // Shepherd Zhu
3 | // Fetch the update and load dlls before enter the game.
4 | using System;
5 | using System.Collections;
6 | using System.Collections.Generic;
7 | using System.IO;
8 | using UnityEngine;
9 | using UnityEngine.AddressableAssets;
10 | using UnityEngine.ResourceManagement.AsyncOperations;
11 | using System.Reflection;
12 | using HybridCLR;
13 |
14 | public class HotUpdater : MonoBehaviour
15 | {
16 | public string versionNumber;
17 | private string progressText;
18 |
19 | private void OnGUI()
20 | {
21 | GUIStyle style = new GUIStyle(GUI.skin.label);
22 | style.alignment = TextAnchor.LowerRight;
23 | style.normal.textColor = Color.white;
24 |
25 | GUI.Label(new Rect(Screen.width - 100, Screen.height - 30, 100, 30), "Version: " + versionNumber, style);
26 | GUI.Label(new Rect(Screen.width - 100, Screen.height - 60, 100, 30), "ResVer: " + PlayerPrefs.GetString("ResVersion"), style);
27 |
28 | GUIStyle progressStyle = new GUIStyle(GUI.skin.label);
29 | style.alignment = TextAnchor.MiddleCenter;
30 | style.normal.textColor = Color.white;
31 | GUI.Label(new Rect(Screen.width * 0.5f, Screen.height * 0.5f,300,30), progressText, progressStyle);
32 | }
33 |
34 | private void Start()
35 | {
36 | progressText = "Hot Updater Initializing...";
37 | StartCoroutine(StartCheckForResDownload());
38 | }
39 |
40 | IEnumerator StartCheckForResDownload()
41 | {
42 | Debug.Log("[HotUpdater] Start looking for local catalog cache.");
43 | string _catalogPath = Application.persistentDataPath + "/com.unity.addressables";
44 | if (Directory.Exists(_catalogPath))
45 | {
46 | try
47 | {
48 | Directory.Delete(_catalogPath, true);
49 | Debug.Log("[HotUpdater] Successfully delete catalog cache!");
50 | }
51 | catch (Exception e)
52 | {
53 | Debug.LogWarning($"[HotUpdater] Unable to delete catalog cache. \n{e.ToString()}");
54 | }
55 | }
56 | Debug.Log("[HotUpdater] Looking for local catalog cache is complete!");
57 | var init = Addressables.InitializeAsync(false);
58 | yield return init;
59 | if (init.Status == AsyncOperationStatus.Succeeded)
60 | {
61 | var checkUpdateCatlogHandle = Addressables.CheckForCatalogUpdates(false);
62 | yield return checkUpdateCatlogHandle;
63 | if (checkUpdateCatlogHandle.Status == AsyncOperationStatus.Failed)
64 | {
65 | progressText = "Failed on downloading updates!";
66 | Debug.LogError("[HotUpdater] Failed on Addressables.CheckForCatalogUpdates!");
67 | yield break;
68 | }
69 | else if (checkUpdateCatlogHandle.Result.Count > 0)
70 | {
71 | var updateCatlogHandle = Addressables.UpdateCatalogs(checkUpdateCatlogHandle.Result, false);
72 | yield return updateCatlogHandle;
73 | if(updateCatlogHandle.Status == AsyncOperationStatus.Failed)
74 | {
75 | progressText = "Failed on downloading updates!";
76 | Debug.LogError("[HotUpdater] Failed on Addressables.UpdateCatalogs!");
77 | yield break;
78 | }
79 | else if(updateCatlogHandle.Status == AsyncOperationStatus.Succeeded)
80 | {
81 | Debug.Log("[HotUpdater] GetDownloadSizeAsync Started!");
82 | var downloadSize = Addressables.GetDownloadSizeAsync("default");
83 | yield return downloadSize;
84 | float size = (float)downloadSize.Result / 1024 / 1024;
85 | Debug.Log($"[HotUpdater] DownloadSize is {size}");
86 | if (updateCatlogHandle.Status == AsyncOperationStatus.Succeeded && downloadSize.Result > 0)
87 | {
88 | Debug.Log("[HotUpdater] Start downloading addressables.");
89 | yield return StartCoroutine(DownloadAddressables());
90 | yield break;
91 | }
92 | else if(updateCatlogHandle.Status == AsyncOperationStatus.Failed)
93 | {
94 | progressText = "Failed on downloading updates!";
95 | Debug.LogError("[HotUpdater] Failed on Addressables.GetDownloadSizeAsync!");
96 | yield break;
97 | }
98 | }
99 | Addressables.Release(updateCatlogHandle);
100 | }
101 | Addressables.Release(checkUpdateCatlogHandle);
102 | }
103 | else if(init.Status == AsyncOperationStatus.Failed)
104 | {
105 | progressText = "Failed on Addressables.InitializeAsync!";
106 | Debug.LogError("[HotUpdater] Failed on Addressables.InitializeAsync!");
107 | yield break;
108 | }
109 | Addressables.Release(init);
110 |
111 | StartCoroutine(EnterGame());
112 | }
113 |
114 | IEnumerator DownloadAddressables()
115 | {
116 | var downloadDependenciesHandle = Addressables.DownloadDependenciesAsync("default", false);
117 | while (downloadDependenciesHandle.Status == AsyncOperationStatus.None)
118 | {
119 | var progress = downloadDependenciesHandle.GetDownloadStatus().Percent;
120 | progressText = $"Downloading...{progress}";
121 | yield return null;
122 | }
123 | yield return downloadDependenciesHandle;
124 | if (downloadDependenciesHandle.Status == AsyncOperationStatus.Succeeded)
125 | {
126 | Addressables.Release(downloadDependenciesHandle);
127 | string time = string.Format("{0:yyyyMMddHHMMss}", DateTime.Now);
128 | PlayerPrefs.SetString("ResVersion", time);
129 | progressText = "Patching...";
130 | StartCoroutine(EnterGame());
131 | }
132 | else
133 | {
134 | progressText = "Failed on downloading updates!";
135 | Debug.LogError("[HotUpdater] Failed on Addressables.DownloadDependenciesAsync!");
136 | yield break;
137 | }
138 | }
139 |
140 | ///
141 | /// Load hot update dlls.
142 | ///
143 | IEnumerator LoadHotfixDLLs()
144 | {
145 | // Can't load dlls in Editor because it will duplicate the reference to scripts and cause issues.
146 | #if !UNITY_EDITOR
147 | var hotFixDllLabelHandle = Addressables.LoadAssetsAsync("HotUpdateDLL", null);
148 | yield return hotFixDllLabelHandle;
149 | var hotFixDlls = hotFixDllLabelHandle.Result;
150 | Addressables.Release(hotFixDllLabelHandle);
151 | foreach (var hotFixDll in hotFixDlls) {
152 | Assembly.Load(hotFixDll.bytes);
153 | }
154 | Debug.Log("[HotUpdater] LoadHotfixDLLs complete!");
155 | #endif
156 | yield return null;
157 | }
158 | ///
159 | /// Load AOT metadata dlls.
160 | ///
161 | ///
162 | IEnumerator LoadMetadataForAOTDLLs()
163 | {
164 | // Can't load dlls in Editor because it will duplicate the reference to scripts and cause issues.
165 | #if !UNITY_EDITOR
166 | HomologousImageMode mode = HomologousImageMode.SuperSet;
167 | var aotMetadateDllHandle = Addressables.LoadAssetsAsync("AOTMetadataDLL", null);
168 | yield return aotMetadateDllHandle;
169 | var AOTMetadataDlls = aotMetadateDllHandle.Result;
170 | foreach (var AOTMetadataDll in AOTMetadataDlls)
171 | {
172 | LoadImageErrorCode err = RuntimeApi.LoadMetadataForAOTAssembly(AOTMetadataDll.bytes, mode);
173 | Debug.Log($"[HotUpdater] LoadMetadataForAOTAssembly:{AOTMetadataDll.name}. mode:{mode} ret:{err}");
174 | }
175 | Addressables.Release(aotMetadateDllHandle);
176 | Debug.Log("[HotUpdater] LoadMetadataForAOTDLLs complete!");
177 | #endif
178 | yield return null;
179 | }
180 |
181 | ///
182 | /// Load hot update dlls and AOT metadata dlls here and enter the main menu scene or somewhere
183 | ///
184 | ///
185 | IEnumerator EnterGame()
186 | {
187 | yield return LoadHotfixDLLs();
188 | yield return LoadMetadataForAOTDLLs();
189 |
190 | Addressables.LoadSceneAsync("MainMenu").Completed += (obj) =>
191 | {
192 | if (obj.Status == AsyncOperationStatus.Succeeded)
193 | {
194 | Debug.Log("[HotUpdater] Successfully load into MainMenu.");
195 | }
196 | else
197 | {
198 | Debug.LogError("[HotUpdater] Failed to load MainMenu scene!");
199 | }
200 | };
201 | }
202 | }
203 |
--------------------------------------------------------------------------------
/JenkinsBuild.cs:
--------------------------------------------------------------------------------
1 | // JenkinsBuild
2 | // Shepherd Zhu
3 | // Jenkins Build Helper
4 |
5 | using System;
6 | using System.Collections.Generic;
7 | using System.IO;
8 | using System.Linq;
9 | using System.Text;
10 | using HybridCLR.Editor;
11 | using HybridCLR.Editor.Commands;
12 | using HybridCLR.Editor.Settings;
13 | using UnityEditor;
14 | using UnityEditor.AddressableAssets;
15 | using UnityEditor.AddressableAssets.Settings;
16 | using UnityEngine;
17 |
18 | public static class JenkinsBuild
19 | {
20 | // 重要提醒:建议先在工作电脑上配好Groups和Labels,本脚本虽说遇到新文件可以添加到Addressable,但是不太可靠。
21 |
22 | ///
23 | /// 开始执行HybridCLR热更打包,默认打当前平台
24 | ///
25 | [MenuItem("Shepherd0619/Build Hot Update")]
26 | public static void BuildHotUpdate()
27 | {
28 | if (EditorUtility.DisplayDialog("Warning",
29 | $"You are attempting to build hot update for {EditorUserBuildSettings.activeBuildTarget}.",
30 | "Proceed", "Quit"))
31 | BuildHotUpdate(EditorUserBuildSettings.selectedBuildTargetGroup, EditorUserBuildSettings.activeBuildTarget);
32 | }
33 |
34 | [MenuItem("Shepherd0619/Build Playground Win Server")]
35 | public static void BuildPlaygroundWinServer()
36 | {
37 | var currentTarget = EditorUserBuildSettings.activeBuildTarget;
38 |
39 | if (!EditorUtility.DisplayDialog("Warning",
40 | "You are attempting to build a Windows Server.",
41 | "Proceed", "Quit"))
42 | return;
43 |
44 | var options = new BuildPlayerOptions();
45 | options.target = BuildTarget.StandaloneWindows64;
46 | options.subtarget = (int)StandaloneBuildSubtarget.Server;
47 | options.scenes = new[] { "Assets/_Project/Scenes/Playground.unity" };
48 | options.locationPathName =
49 | Path.Combine(Application.dataPath,
50 | $"../Build/WindowsServer/{new DateTimeOffset(DateTime.UtcNow).ToUnixTimeSeconds()}/PSXGame.exe");
51 |
52 | // 关闭热更新
53 | HybridCLRSettings.Instance.enable = false;
54 | HybridCLRSettings.Save();
55 |
56 | BuildPipeline.BuildPlayer(options);
57 |
58 | EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup.Standalone, currentTarget);
59 | HybridCLRSettings.Instance.enable = true;
60 | HybridCLRSettings.Save();
61 | }
62 |
63 | ///
64 | /// 开始执行HybridCLR热更打包
65 | ///
66 | /// 目标平台
67 | public static void BuildHotUpdate(BuildTargetGroup group, BuildTarget target)
68 | {
69 | Console.WriteLine(
70 | $"[JenkinsBuild] Start building hot update for {Enum.GetName(typeof(BuildTarget), target)}"
71 | );
72 |
73 | EditorUserBuildSettings.SwitchActiveBuildTarget(group, target);
74 |
75 | // 打开热更新
76 | HybridCLRSettings.Instance.enable = true;
77 | HybridCLRSettings.Save();
78 |
79 | try
80 | {
81 | CompileDllCommand.CompileDll(target);
82 | Il2CppDefGeneratorCommand.GenerateIl2CppDef();
83 |
84 | // 这几个生成依赖HotUpdateDlls
85 | LinkGeneratorCommand.GenerateLinkXml(target);
86 |
87 | // 生成裁剪后的aot dll
88 | StripAOTDllCommand.GenerateStripedAOTDlls(target);
89 |
90 | // 桥接函数生成依赖于AOT dll,必须保证已经build过,生成AOT dll
91 | MethodBridgeGeneratorCommand.GenerateMethodBridgeAndReversePInvokeWrapper(target);
92 | AOTReferenceGeneratorCommand.GenerateAOTGenericReference(target);
93 | }
94 | catch (Exception e)
95 | {
96 | Console.WriteLine(
97 | $"[JenkinsBuild] ERROR while building hot update! Message:\n{e}"
98 | );
99 | return;
100 | }
101 |
102 | // 复制打出来的DLL并进行替换
103 | var sourcePath = Path.Combine(
104 | Application.dataPath,
105 | $"../{SettingsUtil.GetHotUpdateDllsOutputDirByTarget(target)}"
106 | );
107 | var destinationPath = Path.Combine(Application.dataPath, "HotUpdateDLLs");
108 |
109 | if (!Directory.Exists(sourcePath))
110 | {
111 | Console.WriteLine(
112 | "[JenkinsBuild] Source directory does not exist! Possibly HybridCLR build failed!"
113 | );
114 | return;
115 | }
116 |
117 | if (!Directory.Exists(destinationPath))
118 | {
119 | Console.WriteLine(
120 | "[JenkinsBuild] Destination directory does not exist!"
121 | );
122 | Directory.CreateDirectory(destinationPath);
123 | }
124 |
125 | // string[] dllFiles = Directory.GetFiles(sourcePath, "*.dll");
126 |
127 | // foreach (string dllFile in dllFiles)
128 | // {
129 | // string fileName = Path.GetFileName(dllFile);
130 | // string destinationFile = Path.Combine(destinationPath, fileName + ".bytes");
131 | // Console.WriteLine($"[JenkinsBuild] Copy: {dllFile}");
132 | // File.Copy(dllFile, destinationFile, true);
133 | // }
134 |
135 | var hotUpdateAssemblyNames = SettingsUtil.HotUpdateAssemblyNamesExcludePreserved;
136 | for (var i = 0; i < hotUpdateAssemblyNames.Count; i++)
137 | {
138 | Console.WriteLine($"[JenkinsBuild] Copy: {hotUpdateAssemblyNames[i] + ".dll"}");
139 | File.Copy(sourcePath + "/" + hotUpdateAssemblyNames[i] + ".dll",
140 | Path.Combine(destinationPath, hotUpdateAssemblyNames[i] + ".dll.bytes"), true);
141 | }
142 |
143 | Console.WriteLine("[JenkinsBuild] Hot Update DLLs copied successfully!");
144 |
145 | // 复制打出来的AOT元数据DLL并进行替换
146 | Console.WriteLine("[JenkinsBuild] Start copying AOT Metadata DLLs!");
147 | sourcePath = Path.Combine(
148 | Application.dataPath,
149 | $"../{SettingsUtil.GetAssembliesPostIl2CppStripDir(target)}"
150 | );
151 | destinationPath = Path.Combine(Application.dataPath, "HotUpdateDLLs/AOTMetadata");
152 |
153 | if (!Directory.Exists(sourcePath))
154 | {
155 | Console.WriteLine(
156 | "[JenkinsBuild] Source directory does not exist! Possibly HybridCLR build failed!"
157 | );
158 | return;
159 | }
160 |
161 | if (!Directory.Exists(destinationPath))
162 | {
163 | Console.WriteLine(
164 | "[JenkinsBuild] Destination directory does not exist!"
165 | );
166 | Directory.CreateDirectory(destinationPath);
167 | }
168 |
169 | // 获取AOTGenericReferences.cs文件的路径
170 | var aotReferencesFilePath = Path.Combine(
171 | Application.dataPath,
172 | SettingsUtil.HybridCLRSettings.outputAOTGenericReferenceFile
173 | );
174 |
175 | if (!File.Exists(aotReferencesFilePath))
176 | {
177 | Console.WriteLine(
178 | "[JenkinsBuild] AOTGenericReferences.cs file does not exist! Abort the build!"
179 | );
180 | return;
181 | }
182 |
183 | // 读取AOTGenericReferences.cs文件内容
184 | var aotReferencesFileContent = File.ReadAllLines(aotReferencesFilePath);
185 |
186 | // 查找PatchedAOTAssemblyList列表
187 | var patchedAOTAssemblyList = new List();
188 |
189 | for (var i = 0; i < aotReferencesFileContent.Length; i++)
190 | if (aotReferencesFileContent[i].Contains("PatchedAOTAssemblyList"))
191 | {
192 | while (!aotReferencesFileContent[i].Contains("};"))
193 | {
194 | if (aotReferencesFileContent[i].Contains("\""))
195 | {
196 | var startIndex = aotReferencesFileContent[i].IndexOf("\"") + 1;
197 | var endIndex = aotReferencesFileContent[i].LastIndexOf("\"");
198 | var dllName = aotReferencesFileContent[i].Substring(
199 | startIndex,
200 | endIndex - startIndex
201 | );
202 | patchedAOTAssemblyList.Add(dllName);
203 | }
204 |
205 | i++;
206 | }
207 |
208 | break;
209 | }
210 |
211 | // 复制DLL文件到目标文件夹,并添加后缀名".bytes"
212 | foreach (var dllName in patchedAOTAssemblyList)
213 | {
214 | var sourceFile = Path.Combine(sourcePath, dllName);
215 | var destinationFile = Path.Combine(
216 | destinationPath,
217 | Path.GetFileName(dllName) + ".bytes"
218 | );
219 |
220 | if (File.Exists(sourceFile))
221 | {
222 | Console.WriteLine($"[JenkinsBuild] Copy: {sourceFile}");
223 | File.Copy(sourceFile, destinationFile, true);
224 | //SetAOTMetadataDllLabel("Assets/HotUpdateDLLs/" + Path.GetFileName(dllName) + ".bytes");
225 | }
226 | else
227 | {
228 | Console.WriteLine("[JenkinsBuild] AOTMetadata DLL file not found: " + dllName);
229 | }
230 | }
231 |
232 | AssetDatabase.SaveAssets();
233 |
234 | Console.WriteLine("[JenkinsBuild] BuildHotUpdate complete!");
235 |
236 | AssetDatabase.Refresh();
237 |
238 | // 刷新后开始给DLL加标签
239 | //SetHotUpdateDllLabel("Assets/HotUpdateDLLs/Assembly-CSharp.dll.bytes");
240 | for (var i = 0; i < hotUpdateAssemblyNames.Count; i++)
241 | SetHotUpdateDllLabel("Assets/HotUpdateDLLs/" + hotUpdateAssemblyNames[i] + ".dll.bytes");
242 |
243 | foreach (var dllName in patchedAOTAssemblyList)
244 | SetAOTMetadataDllLabel("Assets/HotUpdateDLLs/AOTMetadata/" + Path.GetFileName(dllName) + ".bytes");
245 |
246 | Console.WriteLine("[JenkinsBuild] Start building Addressables!");
247 | BuildAddressableContent();
248 | }
249 |
250 | public static void BuildHotUpdateForWindows64()
251 | {
252 | BuildHotUpdate(BuildTargetGroup.Standalone, BuildTarget.StandaloneWindows64);
253 | }
254 |
255 | public static void BuildHotUpdateForiOS()
256 | {
257 | BuildHotUpdate(BuildTargetGroup.iOS, BuildTarget.iOS);
258 | }
259 |
260 | public static void BuildHotUpdateForLinux64()
261 | {
262 | BuildHotUpdate(BuildTargetGroup.Standalone, BuildTarget.StandaloneLinux64);
263 | }
264 |
265 | public static void BuildHotUpdateForAndroid()
266 | {
267 | BuildHotUpdate(BuildTargetGroup.Android, BuildTarget.Android);
268 | }
269 |
270 | public static void BuildWindowsServer()
271 | {
272 | // 获取命令行参数
273 | var args = Environment.GetCommandLineArgs();
274 |
275 | // 获取scenes参数
276 | var scenes = GetArgument(args, "scenes");
277 | if (scenes == null || scenes.Length <= 0) return;
278 |
279 | // 获取targetPath参数
280 | var targetPath = GetArgument(args, "targetPath");
281 | if (targetPath == null || targetPath.Length <= 0) return;
282 |
283 | var options = new BuildPlayerOptions();
284 | options.target = BuildTarget.StandaloneWindows64;
285 | options.subtarget = (int)StandaloneBuildSubtarget.Server;
286 | options.scenes = scenes;
287 | options.locationPathName = targetPath[0];
288 |
289 | // 关闭热更新
290 | HybridCLRSettings.Instance.enable = false;
291 | HybridCLRSettings.Save();
292 |
293 | var sb = new StringBuilder();
294 | for (var i = 0; i < scenes.Length; i++) sb.Append(scenes[i] + "; ");
295 | Console.WriteLine($"[JenkinsBuild] Start building WindowsServer! Scenes: {sb}, TargetPath: {targetPath[0]}");
296 | BuildPipeline.BuildPlayer(options);
297 | }
298 |
299 | public static void BuildLinuxServer()
300 | {
301 | // 获取命令行参数
302 | var args = Environment.GetCommandLineArgs();
303 |
304 | // 获取scenes参数
305 | var scenes = GetArgument(args, "scenes");
306 | if (scenes == null || scenes.Length <= 0) return;
307 |
308 | // 获取targetPath参数
309 | var targetPath = GetArgument(args, "targetPath");
310 | if (targetPath == null || targetPath.Length <= 0) return;
311 |
312 | var options = new BuildPlayerOptions();
313 | options.target = BuildTarget.StandaloneLinux64;
314 | options.subtarget = (int)StandaloneBuildSubtarget.Server;
315 | options.scenes = scenes;
316 | options.locationPathName = targetPath[0];
317 |
318 | // 关闭热更新
319 | HybridCLRSettings.Instance.enable = false;
320 | HybridCLRSettings.Save();
321 |
322 | var sb = new StringBuilder();
323 | for (var i = 0; i < scenes.Length; i++) sb.Append(scenes[i] + "\n");
324 | Console.WriteLine($"[JenkinsBuild] Start building LinuxServer! Scenes: {sb}, TargetPath: {targetPath[0]}");
325 | BuildPipeline.BuildPlayer(options);
326 | }
327 |
328 | ///
329 | /// 获取某个参数
330 | ///
331 | /// 全部的命令行参数
332 | /// 要获取的参数名称
333 | /// 所求参数
334 | private static string[] GetArgument(string[] args, string name)
335 | {
336 | var start = Array.FindIndex(args, arg => arg == $"-{name}");
337 | if (start < 0)
338 | {
339 | Console.WriteLine($"[JenkinsBuild.GetArgument] Can not find argument: {name}");
340 | return null;
341 | }
342 |
343 | start++;
344 | var end = Array.FindIndex(args, start, arg => arg[0] == '-');
345 | if (end < 0) end = args.Length;
346 | var count = end - start;
347 | if (count <= 0)
348 | {
349 | Console.WriteLine(
350 | $"[JenkinsBuild.GetArgument] Can not find argument value: {name}, Count: {count}, Start: {start}, End: {end}");
351 | return null;
352 | }
353 |
354 | var result = args.Skip(start).Take(count).ToArray();
355 | return result;
356 | }
357 |
358 | ///
359 | /// 将热更DLL加入到Addressable
360 | ///
361 | /// DLL完整路径
362 | private static void SetHotUpdateDllLabel(string dllPath)
363 | {
364 | var settings = AddressableAssetSettingsDefaultObject.Settings;
365 | var group = settings.FindGroup("DLLs");
366 | var guid = AssetDatabase.AssetPathToGUID(dllPath);
367 | if (settings.FindAssetEntry(guid) != null)
368 | {
369 | Console.WriteLine(
370 | $"[JenkinsBuild.SetHotUpdateDLLLabel] {dllPath} already exist in Addressables. Abort!"
371 | );
372 | return;
373 | }
374 |
375 | var entry = settings.CreateOrMoveEntry(guid, group);
376 | entry.labels.Add("default");
377 | entry.labels.Add("HotUpdateDLL");
378 | entry.address = Path.GetFileName(dllPath);
379 | settings.SetDirty(AddressableAssetSettings.ModificationEvent.EntryMoved, entry, true);
380 | }
381 |
382 | ///
383 | /// 将AOT元数据DLL加入到Addressable
384 | ///
385 | /// DLL完整路径
386 | private static void SetAOTMetadataDllLabel(string dllPath)
387 | {
388 | var settings = AddressableAssetSettingsDefaultObject.Settings;
389 | var group = settings.FindGroup("DLLs");
390 | var guid = AssetDatabase.AssetPathToGUID(dllPath);
391 | if (settings.FindAssetEntry(guid) != null)
392 | {
393 | Console.WriteLine(
394 | $"[JenkinsBuild.SetAOTMetadataDLLLabel] {dllPath} already exist in Addressables. Abort!"
395 | );
396 | return;
397 | }
398 |
399 | var entry = settings.CreateOrMoveEntry(guid, group);
400 | entry.labels.Add("default");
401 | entry.labels.Add("AOTMetadataDLL");
402 | entry.address = Path.GetFileName(dllPath);
403 | settings.SetDirty(AddressableAssetSettings.ModificationEvent.EntryMoved, entry, true);
404 | }
405 |
406 | ///
407 | /// 打当前平台的打包Addressable
408 | ///
409 | ///
410 | private static bool BuildAddressableContent()
411 | {
412 | var path = Path.Combine(Application.dataPath,
413 | "../ServerData/" + Enum.GetName(typeof(BuildTarget), EditorUserBuildSettings.activeBuildTarget));
414 | if (Directory.Exists(path)) Directory.Delete(path, true);
415 |
416 | AddressableAssetSettings.BuildPlayerContent(out var result);
417 | var success = string.IsNullOrEmpty(result.Error);
418 |
419 | if (!success)
420 | Console.WriteLine("[JenkinsBuild.BuildAddressableContent] Addressable build error encountered: " +
421 | result.Error);
422 | return success;
423 | }
424 | }
425 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2024, Shepherd
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, are permitted provided that the following conditions are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright notice, this
9 | list of conditions and the following disclaimer.
10 |
11 | 2. Redistributions in binary form must reproduce the above copyright notice,
12 | this list of conditions and the following disclaimer in the documentation
13 | and/or other materials provided with the distribution.
14 |
15 | 3. Neither the name of the copyright holder nor the names of its
16 | contributors may be used to endorse or promote products derived from
17 | this software without specific prior written permission.
18 |
19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # JenkinsBuildUnity
2 | A little script that connect Unity (with HybridCLR hot update) and Jenkins together.
3 |
4 | # Goal
5 | To make CI/CD more easier to be integrated into any Unity project has hot update feature.
6 |
7 | # What is CI/CD?
8 | In software engineering, CI/CD or CICD is the combined practices of continuous integration (CI) and continuous delivery (CD) or, less often, continuous deployment. They are sometimes referred to collectively as continuous development or continuous software development. -- Wikipedia
9 |
10 | Alright, in humans word, a computer that keeps building the app till death and you don't have to pay the salary to do so.
11 |
12 | # What is Hot Update?
13 | Hotfixes can also solve many of the same issues as a patch, but it is applied to a “hot” system—a live system—to fix an issue:
14 |
15 | 1. Immediately
16 | 2. Without creating system downtimes or outages.
17 |
18 | Hotfixes are also known as QFE updates, short for quick-fix engineering updates, a name that illustrates the urgency.
19 |
20 | Normally, you’ll create a hotfix quickly, as an urgent measure against issues that need to be fixed immediately and outside of your normal development flow. Unlike patches, hotfixes address very specific issues like adding a new feature, bug, or security fix
21 |
22 | -- Chrissy Kidd from bmc.com
23 |
24 | In humans word, a solution that only build the modified part and deliver it to players/testers/your boss/... as fast as possible.
25 |
26 | # Why do I need CI/CD?
27 | 1. You will no longer have to pause your current work because of building on your PC.
28 | 2. You will no longer have to perform the complicated building process again and again.
29 | 3. You will no longer have to check your collaborator's commit manually.
30 | 4. Testers/boss can build the client by themselves, instead of a phone call asking you to put down what you are doing and go help them.
31 | 5. ......
32 |
33 | # Why do I need Hot Update?
34 | 1. Push fixes to players as quickly as possible without waiting for App Store/Google Play's review.
35 | 2. No need to compile the full client over and over again which drive many of you crazy.
36 | 3. ......
37 |
38 | # How to use?
39 | In simple, it should be like the following:
40 | 1. Install [Jenkins](https://www.jenkins.io/) and unity3d plugin on your server or another PC.
41 | 2. Import [HybridCLR](https://github.com/focus-creative-games/hybridclr_unity) and Addressables into your Unity project and do some configurations.
42 | 3. Place "JenkinsBuild.cs" into "Assets/Editor".
43 | 4. Add a Addressables group called "DLLs" and labels "HotUpdateDLL" and "AOTMetadataDLL".
44 | 5. Start HybridCLR build.
45 | 6. ~~Copy and paste the dlls under "HybridCLRData/HotUpdateDlls" and "HybridCLRData/AssembliesPostIl2CppStrip" the project need. (**No need to copy all of them.** )~~
46 | 7. ~~Add ".bytes" to the end of the name of dll you copied.~~
47 | ~~Like "Assembly-CSharp.dll.bytes".~~
48 | 8. ~~Add those dlls into "DLLs" group and add the corresponding label to them.~~
49 |
50 | No need to do 6, 7, 8 anymore since the script can do them automatically.
51 | **Make sure your HybridCLRSettings and Addressables Settings are correct since the script do these jobs depends on them.**
52 |
53 | 9. Create a new Assembly Definition and a script to download & apply updates before loading into main scene.
54 | (**Example scripts included!** )
55 | 10. Set DisableCatalogUpdateOnStart to true which can be found in Addressable Settings since the script will take over the catalog update.
56 | 11. Start Jenkins job and enjoy!
57 | For unity editor command line, please type like this:
58 | ```-nographics -batchmode -quit -executeMethod JenkinsBuild.BuildHotUpdateForWindows64```
59 |
60 | For details, you can visit the following docs and tutorials created by others to understand the content above better:
61 | 1. https://www.youtube.com/watch?v=WdIG0af7S0g
62 | 2. https://hybridclr.doc.code-philosophy.com/en/docs/basic
63 | 3. https://docs.unity3d.com/Packages/com.unity.addressables@1.20/manual/index.html
64 |
65 | Still feel confused? ~~You may wait for my step by step tutorial on [YouTube](https://www.youtube.com/channel/UCRQdc3lSimZvrvIAkt3bTuw) about it (**should be coming soon with an example project**).~~
66 |
67 | **I'm not very good at video editing, but don't worry, I will use Markdown documents and sample projects to do the tutorial instead.**
68 |
69 | For Chinese, there are already tutorials on my CSDN blog!
70 |
71 | 来自中国的朋友!你们可以访问我的CSDN博客来获取相关教程!
72 |
73 | 1. https://blog.csdn.net/u012587406/article/details/135260464
74 | 2. https://blog.csdn.net/u012587406/article/details/135441946
75 |
76 | # References
77 | 1. [0oSTrAngeRo0/AutoBuild](https://github.com/0oSTrAngeRo0/AutoBuild) for command line args.
78 |
--------------------------------------------------------------------------------