本文章主要圍繞本人在Github上的一個(gè)開(kāi)源腳本:
GitHub - Shepherd0619/JenkinsBuildUnity: A little script that connect Unity (with HybridCLR hot update) and Jenkins together.
這個(gè)腳本是一個(gè)用于在Jenkins中構(gòu)建Unity項(xiàng)目的輔助工具。它的主要功能是構(gòu)建HybridCLR熱更新,并將生成的DLL文件和AOT元數(shù)據(jù)DLL文件復(fù)制到指定的目錄,并將它們添加到Unity的Addressable Assets系統(tǒng)中。
如果您還不知道Jenkins和HybridCLR的話,建議先閱讀一下往期博客和相關(guān)官方文檔。
【Unity實(shí)戰(zhàn)】HybridCLR熱更快速集成-CSDN博客
Unity與Jenkins打包機(jī)實(shí)戰(zhàn)-CSDN博客
腳本講解
首先,我們來(lái)看一下這個(gè)腳本的結(jié)構(gòu)。它是一個(gè)繼承自MonoBehaviour的類(lèi),并且包含了一些靜態(tài)方法用于構(gòu)建熱更新(也必須得是靜態(tài),否則的話后續(xù)Jenkins通過(guò)命令行調(diào)起Unity會(huì)比較麻煩,找不到這個(gè)函數(shù))。
// JenkinsBuild
// Shepherd Zhu
// Jenkins Build Helper
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using HybridCLR.Editor;
using HybridCLR.Editor.Commands;
using HybridCLR.Editor.Settings;
using UnityEditor;
using UnityEditor.AddressableAssets;
using UnityEditor.AddressableAssets.Build;
using UnityEditor.AddressableAssets.Settings;
using UnityEngine;
public class JenkinsBuild : MonoBehaviour
{
// 重要提醒:建議先在工作電腦上配好Groups和Labels,本腳本雖說(shuō)遇到新文件可以添加到Addressables,但是不太可靠。
[MenuItem("Shepherd0619/Build Hot Update")]
/// <summary>
/// 開(kāi)始執(zhí)行HybridCLR熱更打包,默認(rèn)打當(dāng)前平臺(tái)
/// </summary>
public static void BuildHotUpdate()
{
BuildHotUpdate(EditorUserBuildSettings.activeBuildTarget);
}
/// <summary>
/// 開(kāi)始執(zhí)行HybridCLR熱更打包
/// </summary>
/// <param name="target">目標(biāo)平臺(tái)</param>
public static void BuildHotUpdate(BuildTarget target)
{
}
public static void BuildHotUpdateForWindows64()
{
BuildHotUpdate(BuildTarget.StandaloneWindows64);
}
public static void BuildHotUpdateForiOS()
{
BuildHotUpdate(BuildTarget.iOS);
}
public static void BuildHotUpdateForLinux64()
{
BuildHotUpdate(BuildTarget.StandaloneLinux64);
}
public static void BuildHotUpdateForAndroid()
{
BuildHotUpdate(BuildTarget.Android);
}
/// <summary>
/// 將熱更DLL加入到Addressables
/// </summary>
/// <param name="dllPath">DLL完整路徑</param>
private static void SetHotUpdateDllLabel(string dllPath)
{
}
/// <summary>
/// 將AOT元數(shù)據(jù)DLL加入到Addressables
/// </summary>
/// <param name="dllPath">DLL完整路徑</param>
private static void SetAOTMetadataDllLabel(string dllPath)
{
}
private static bool buildAddressableContent()
{
}
}
整個(gè)腳本的核心是`BuildHotUpdate`方法。這個(gè)方法接受一個(gè)`BuildTarget`參數(shù),用于指定構(gòu)建的目標(biāo)平臺(tái)。在方法中,首先打印出正在構(gòu)建的目標(biāo)平臺(tái),然后依次執(zhí)行一系列構(gòu)建熱更新所需的命令。
/// <summary>
/// 開(kāi)始執(zhí)行HybridCLR熱更打包
/// </summary>
/// <param name="target">目標(biāo)平臺(tái)</param>
public static void BuildHotUpdate(BuildTarget target)
{
Console.WriteLine(
$"[JenkinsBuild] Start building hot update for {Enum.GetName(typeof(BuildTarget), target)}"
);
try
{
CompileDllCommand.CompileDll(target);
Il2CppDefGeneratorCommand.GenerateIl2CppDef();
// 這幾個(gè)生成依賴(lài)HotUpdateDlls
LinkGeneratorCommand.GenerateLinkXml(target);
// 生成裁剪后的aot dll
StripAOTDllCommand.GenerateStripedAOTDlls(target);
// 橋接函數(shù)生成依賴(lài)于AOT dll,必須保證已經(jīng)build過(guò),生成AOT dll
MethodBridgeGeneratorCommand.GenerateMethodBridge(target);
ReversePInvokeWrapperGeneratorCommand.GenerateReversePInvokeWrapper(target);
AOTReferenceGeneratorCommand.GenerateAOTGenericReference(target);
}
catch (Exception e)
{
Console.WriteLine(
$"[JenkinsBuild] ERROR while building hot update! Message:\n{e.ToString()}"
);
return;
}
// 復(fù)制打出來(lái)的DLL并進(jìn)行替換
string sourcePath = Path.Combine(
Application.dataPath,
$"../{SettingsUtil.GetHotUpdateDllsOutputDirByTarget(target)}"
);
string destinationPath = Path.Combine(Application.dataPath, "HotUpdateDLLs");
if (!Directory.Exists(sourcePath))
{
Console.WriteLine(
"[JenkinsBuild] Source directory does not exist! Possibly HybridCLR build failed!"
);
return;
}
if (!Directory.Exists(destinationPath))
{
Console.WriteLine(
"[JenkinsBuild] Destination directory does not exist! Abort the build!"
);
return;
}
// string[] dllFiles = Directory.GetFiles(sourcePath, "*.dll");
// foreach (string dllFile in dllFiles)
// {
// string fileName = Path.GetFileName(dllFile);
// string destinationFile = Path.Combine(destinationPath, fileName + ".bytes");
// Console.WriteLine($"[JenkinsBuild] Copy: {dllFile}");
// File.Copy(dllFile, destinationFile, true);
// }
List<string> hotUpdateAssemblyNames = SettingsUtil.HotUpdateAssemblyNamesExcludePreserved;
for (int i = 0; i < hotUpdateAssemblyNames.Count; i++)
{
Console.WriteLine($"[JenkinsBuild] Copy: {hotUpdateAssemblyNames[i] + ".dll"}");
File.Copy(sourcePath + "/" + hotUpdateAssemblyNames[i] + ".dll", Path.Combine(destinationPath, hotUpdateAssemblyNames[i] + ".dll.bytes"), true);
}
Console.WriteLine("[JenkinsBuild] Hot Update DLLs copied successfully!");
// 復(fù)制打出來(lái)的AOT元數(shù)據(jù)DLL并進(jìn)行替換
Console.WriteLine("[JenkinsBuild] Start copying AOT Metadata DLLs!");
sourcePath = Path.Combine(
Application.dataPath,
$"../{SettingsUtil.GetAssembliesPostIl2CppStripDir(target)}"
);
destinationPath = Path.Combine(Application.dataPath, "HotUpdateDLLs/AOTMetadata");
if (!Directory.Exists(sourcePath))
{
Console.WriteLine(
"[JenkinsBuild] Source directory does not exist! Possibly HybridCLR build failed!"
);
return;
}
if (!Directory.Exists(destinationPath))
{
Console.WriteLine(
"[JenkinsBuild] Destination directory does not exist! Abort the build!"
);
return;
}
// 獲取AOTGenericReferences.cs文件的路徑
string aotReferencesFilePath = Path.Combine(
Application.dataPath,
SettingsUtil.HybridCLRSettings.outputAOTGenericReferenceFile
);
if (!File.Exists(aotReferencesFilePath))
{
Console.WriteLine(
"[JenkinsBuild] AOTGenericReferences.cs file does not exist! Abort the build!"
);
return;
}
// 讀取AOTGenericReferences.cs文件內(nèi)容
string[] aotReferencesFileContent = File.ReadAllLines(aotReferencesFilePath);
// 查找PatchedAOTAssemblyList列表
List<string> patchedAOTAssemblyList = new List<string>();
for (int i = 0; i < aotReferencesFileContent.Length; i++)
{
if (aotReferencesFileContent[i].Contains("PatchedAOTAssemblyList"))
{
while (!aotReferencesFileContent[i].Contains("};"))
{
if (aotReferencesFileContent[i].Contains("\""))
{
int startIndex = aotReferencesFileContent[i].IndexOf("\"") + 1;
int endIndex = aotReferencesFileContent[i].LastIndexOf("\"");
string dllName = aotReferencesFileContent[i].Substring(
startIndex,
endIndex - startIndex
);
patchedAOTAssemblyList.Add(dllName);
}
i++;
}
break;
}
}
// 復(fù)制DLL文件到目標(biāo)文件夾,并添加后綴名".bytes"
foreach (string dllName in patchedAOTAssemblyList)
{
string sourceFile = Path.Combine(sourcePath, dllName);
string destinationFile = Path.Combine(
destinationPath,
Path.GetFileName(dllName) + ".bytes"
);
if (File.Exists(sourceFile))
{
Console.WriteLine($"[JenkinsBuild] Copy: {sourceFile}");
File.Copy(sourceFile, destinationFile, true);
//SetAOTMetadataDllLabel("Assets/HotUpdateDLLs/" + Path.GetFileName(dllName) + ".bytes");
}
else
{
Console.WriteLine("[JenkinsBuild] AOTMetadata DLL file not found: " + dllName);
}
}
AssetDatabase.SaveAssets();
Console.WriteLine("[JenkinsBuild] BuildHotUpdate complete!");
AssetDatabase.Refresh();
// 刷新后開(kāi)始給DLL加標(biāo)簽
//SetHotUpdateDllLabel("Assets/HotUpdateDLLs/Assembly-CSharp.dll.bytes");
for (int i = 0; i < hotUpdateAssemblyNames.Count; i++)
{
SetHotUpdateDllLabel("Assets/HotUpdateDLLs/" + hotUpdateAssemblyNames[i] + ".dll.bytes");
}
foreach(string dllName in patchedAOTAssemblyList)
{
SetAOTMetadataDllLabel("Assets/HotUpdateDLLs/AOTMetadata/" + Path.GetFileName(dllName) + ".bytes");
}
Console.WriteLine("[JenkinsBuild] Start building Addressables!");
buildAddressableContent();
}
在`BuildHotUpdate`方法中,首先調(diào)用`CompileDllCommand.CompileDll`方法來(lái)編譯DLL文件。然后調(diào)用`Il2CppDefGeneratorCommand.GenerateIl2CppDef`方法來(lái)生成Il2Cpp的定義文件。接下來(lái),調(diào)用`LinkGeneratorCommand.GenerateLinkXml`方法來(lái)生成鏈接文件。然后,調(diào)用`StripAOTDllCommand.GenerateStripedAOTDlls`方法來(lái)生成裁剪后的AOT DLL文件。接著,調(diào)用`MethodBridgeGeneratorCommand.GenerateMethodBridge`方法和`ReversePInvokeWrapperGeneratorCommand.GenerateReversePInvokeWrapper`方法來(lái)生成橋接函數(shù)和反向PInvoke包裝器。最后,調(diào)用`AOTReferenceGeneratorCommand.GenerateAOTGenericReference`方法來(lái)生成AOT泛型引用。
在執(zhí)行完所有的構(gòu)建命令后,腳本會(huì)將生成的DLL文件和AOT元數(shù)據(jù)DLL文件復(fù)制到指定的目錄。這里使用了`File.Copy`方法來(lái)實(shí)現(xiàn)復(fù)制。復(fù)制完成后,腳本會(huì)打印出復(fù)制成功的消息。
接下來(lái),腳本會(huì)將生成的DLL文件和AOT元數(shù)據(jù)DLL文件添加到Unity的Addressable Assets系統(tǒng)中。這里使用了`AddressableAssetSettings`類(lèi)來(lái)實(shí)現(xiàn)。首先通過(guò)`AddressableAssetSettingsDefaultObject.Settings`屬性獲取到Addressable Assets的設(shè)置對(duì)象,然后通過(guò)`FindGroup`方法找到指定的Group,接著使用`CreateOrMoveEntry`方法創(chuàng)建或移動(dòng)Asset Entry,并將其添加到指定的Group中。最后,通過(guò)設(shè)置Entry的標(biāo)簽和地址來(lái)完成添加操作。
當(dāng)然這一通操作完,得必須SetDirty以通知Unity這塊有改動(dòng)。
/// <summary>
/// 將熱更DLL加入到Addressables
/// </summary>
/// <param name="dllPath">DLL完整路徑</param>
private static void SetHotUpdateDllLabel(string dllPath)
{
var settings = AddressableAssetSettingsDefaultObject.Settings;
AddressableAssetGroup group = settings.FindGroup("DLLs");
var guid = AssetDatabase.AssetPathToGUID(dllPath);
if (settings.FindAssetEntry(guid) != null)
{
Console.WriteLine(
$"[JenkinsBuild.SetHotUpdateDLLLabel] {dllPath} already exist in Addressables. Abort!"
);
return;
}
var entry = settings.CreateOrMoveEntry(guid, group);
entry.labels.Add("default");
entry.labels.Add("HotUpdateDLL");
entry.address = Path.GetFileName(dllPath);
settings.SetDirty(AddressableAssetSettings.ModificationEvent.EntryMoved, entry, true);
}
/// <summary>
/// 將AOT元數(shù)據(jù)DLL加入到Addressables
/// </summary>
/// <param name="dllPath">DLL完整路徑</param>
private static void SetAOTMetadataDllLabel(string dllPath)
{
var settings = AddressableAssetSettingsDefaultObject.Settings;
AddressableAssetGroup group = settings.FindGroup("DLLs");
var guid = AssetDatabase.AssetPathToGUID(dllPath);
if (settings.FindAssetEntry(guid) != null)
{
Console.WriteLine(
$"[JenkinsBuild.SetAOTMetadataDLLLabel] {dllPath} already exist in Addressables. Abort!"
);
return;
}
var entry = settings.CreateOrMoveEntry(guid, group);
entry.labels.Add("default");
entry.labels.Add("AOTMetadataDLL");
entry.address = Path.GetFileName(dllPath);
settings.SetDirty(AddressableAssetSettings.ModificationEvent.EntryMoved, entry, true);
}
最后,腳本會(huì)調(diào)用`buildAddressableContent`方法來(lái)構(gòu)建Addressable Assets的內(nèi)容。這里使用了`AddressableAssetSettings.BuildPlayerContent`方法來(lái)實(shí)現(xiàn)構(gòu)建。構(gòu)建完成后,腳本會(huì)打印出構(gòu)建結(jié)果,并返回構(gòu)建是否成功的標(biāo)志。
private static bool buildAddressableContent()
{
string path = Path.Combine(Application.dataPath, "../ServerData/"+Enum.GetName(typeof(BuildTarget), EditorUserBuildSettings.activeBuildTarget));
if(Directory.Exists(path)){
Directory.Delete(path, true);
}
AddressableAssetSettings.BuildPlayerContent(out AddressablesPlayerBuildResult result);
bool success = string.IsNullOrEmpty(result.Error);
if (!success)
{
Console.WriteLine("[JenkinsBuild.buildAddressableContent] Addressables build error encountered: " + result.Error);
}
return success;
}
以上就是這個(gè)腳本的主要功能和實(shí)現(xiàn)邏輯。通過(guò)這個(gè)腳本,我們可以方便地在Jenkins中構(gòu)建Unity項(xiàng)目的熱更新,并將生成的DLL文件和AOT元數(shù)據(jù)DLL文件添加到Unity的Addressable Assets系統(tǒng)中。
命令行參數(shù)樣例
Unity.exe -nographics -batchmode -quit -executeMethod JenkinsBuild.BuildHotUpdateForWindows64
這個(gè)會(huì)只打熱更新,不會(huì)打完整客戶端。
更多Unity編輯器命令行參數(shù),請(qǐng)查閱官方文檔
Unity - Manual: Unity Editor command line arguments
希望這篇博客能給你帶來(lái)一些思路。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-822023.html
(理論上我應(yīng)該出個(gè)圖文講解視頻,但是Hmm,我實(shí)在是在視頻剪輯這塊拉夸的一批)文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-822023.html
到了這里,關(guān)于【Unity實(shí)戰(zhàn)】Jenkins打包機(jī)聯(lián)調(diào)腳本的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!