前言
Jenkins是一个开源的持续集成工具,用人话来说就是没有感情的打包机。一般常见于公司项目用于持续交付。
可能有个人开发者对打包机很陌生或者不是很在意(比如在写这篇文章之前的我),项目小,本机写完本机打,甚至能边写边打,也没觉得怎么样。直到后来打包卡电脑没法做其他事情的时候,是时候单拎出来个电脑做个打包机了,要不耽误事情。
Jenkins侧
安装
前往官网下载安装,有国内下载站,直接下Windows的LTS版本即可。
接着就按照安装向导一步一步走,直到你进入Jenkins的WebUI界面。
现在请点击左侧Manage Jenkins,然后点右侧的Plugins。
单击左侧的Available Plugins,搜索Unity并安装。
配置
接下来需要告诉Jenkins你的Unity编辑器安装在哪里。
回到Manage Jenkins,点开Tools,一直下拉到底部添加安装
创建工程
选择Freestyle project,写好名字点击OK
这些选项你可以根据个人喜好配置,包括Github库位置(若需要更新CI/CD状态)、Git账号啥的。
但建议打开时间戳log和打包无响应自动叫停。
Build Steps那里添加指令,通过命令行启动Unity编辑器进行后台打包编译。
命令行参数可参考如下:
-projectPath "工程路径" -nographics -batchmode -quit -executeMethod JenkinsBuild.BuildWindows64 "${JOB_Name}" "工程打包存放位置\${BUILD_NUMBER}\output"
至此,Jenkins配置告一段落,接下来需要写一个和Jenkins联调的Unity编辑器脚本。
Unity侧
联调脚本
在你的工程下,在Assets文件夹下创建Editor文件夹,然后再创建一个JenkinsBuild.cs脚本,内容可参考如下:
// -------------------------------------------------------------------------------------------------
// Assets/Editor/JenkinsBuild.cs
// -------------------------------------------------------------------------------------------------
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using UnityEditor.Build.Reporting;
// ------------------------------------------------------------------------
// https://docs.unity3d.com/Manual/CommandLineArguments.html
// ------------------------------------------------------------------------
public class JenkinsBuild
{
static string[] EnabledScenes = FindEnabledEditorScenes();
// ------------------------------------------------------------------------
// called from Jenkins
// ------------------------------------------------------------------------
public static void BuildMacOS()
{
var args = FindArgs();
string fullPathAndName = args.targetDir + args.appName + ".app";
BuildProject(EnabledScenes, fullPathAndName, BuildTargetGroup.Standalone, BuildTarget.StandaloneOSX, BuildOptions.None);
}
// ------------------------------------------------------------------------
// called from Jenkins
// ------------------------------------------------------------------------
public static void BuildWindows64()
{
var args = FindArgs();
string fullPathAndName = args.targetDir + args.appName;
BuildProject(EnabledScenes, fullPathAndName, BuildTargetGroup.Standalone, BuildTarget.StandaloneWindows64, BuildOptions.None);
}
// ------------------------------------------------------------------------
// called from Jenkins
// ------------------------------------------------------------------------
public static void BuildLinux()
{
var args = FindArgs();
string fullPathAndName = args.targetDir + args.appName;
BuildProject(EnabledScenes, fullPathAndName, BuildTargetGroup.Standalone, BuildTarget.StandaloneLinux64, BuildOptions.None);
}
// ------------------------------------------------------------------------
// called from Jenkins
// ------------------------------------------------------------------------
public static void BuildLinuxServer()
{
var args = FindArgs();
string fullPathAndName = args.targetDir + args.appName;
BuildProject(EnabledScenes, fullPathAndName, BuildTargetGroup.Standalone, BuildTarget.LinuxHeadlessSimulation, BuildOptions.None);
}
// ------------------------------------------------------------------------
// called from Jenkins
// ------------------------------------------------------------------------
public static void BuildAndroid()
{
var args = FindArgs();
string fullPathAndName = args.targetDir + args.appName;
BuildProject(EnabledScenes, fullPathAndName, BuildTargetGroup.Standalone, BuildTarget.Android, BuildOptions.None);
}
private static Args FindArgs()
{
var returnValue = new Args();
// find: -executeMethod
// +1: JenkinsBuild.BuildMacOS
// +2: FindTheGnome
// +3: D:\Jenkins\Builds\Find the Gnome\47\output
string[] args = System.Environment.GetCommandLineArgs();
var execMethodArgPos = -1;
bool allArgsFound = false;
for (int i = 0; i < args.Length; i++)
{
if (args[i] == "-executeMethod")
{
execMethodArgPos = i;
}
var realPos = execMethodArgPos == -1 ? -1 : i - execMethodArgPos - 2;
if (realPos < 0)
continue;
if (realPos == 0)
returnValue.appName = args[i];
if (realPos == 1)
{
returnValue.targetDir = args[i];
if (!returnValue.targetDir.EndsWith(System.IO.Path.DirectorySeparatorChar + ""))
returnValue.targetDir += System.IO.Path.DirectorySeparatorChar;
allArgsFound = true;
}
}
if (!allArgsFound)
System.Console.WriteLine("[JenkinsBuild] Incorrect Parameters for -executeMethod Format: -executeMethod JenkinsBuild.BuildWindows64 <app name> <output dir>");
return returnValue;
}
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
private static string[] FindEnabledEditorScenes()
{
List<string> EditorScenes = new List<string>();
foreach (EditorBuildSettingsScene scene in EditorBuildSettings.scenes)
if (scene.enabled)
EditorScenes.Add(scene.path);
return EditorScenes.ToArray();
}
// ------------------------------------------------------------------------
// e.g. BuildTargetGroup.Standalone, BuildTarget.StandaloneOSX
// ------------------------------------------------------------------------
private static void BuildProject(string[] scenes, string targetDir, BuildTargetGroup buildTargetGroup, BuildTarget buildTarget, BuildOptions buildOptions)
{
System.Console.WriteLine("[JenkinsBuild] Building:" + targetDir + " buildTargetGroup:" + buildTargetGroup.ToString() + " buildTarget:" + buildTarget.ToString());
// https://docs.unity3d.com/ScriptReference/EditorUserBuildSettings.SwitchActiveBuildTarget.html
bool switchResult = EditorUserBuildSettings.SwitchActiveBuildTarget(buildTargetGroup, buildTarget);
if (switchResult)
{
System.Console.WriteLine("[JenkinsBuild] Successfully changed Build Target to: " + buildTarget.ToString());
}
else
{
System.Console.WriteLine("[JenkinsBuild] Unable to change Build Target to: " + buildTarget.ToString() + " Exiting...");
return;
}
// https://docs.unity3d.com/ScriptReference/BuildPipeline.BuildPlayer.html
BuildReport buildReport = BuildPipeline.BuildPlayer(scenes, targetDir, buildTarget, buildOptions);
BuildSummary buildSummary = buildReport.summary;
if (buildSummary.result == BuildResult.Succeeded)
{
System.Console.WriteLine("[JenkinsBuild] Build Success: Time:" + buildSummary.totalTime + " Size:" + buildSummary.totalSize + " bytes");
}
else
{
System.Console.WriteLine("[JenkinsBuild] Build Failed: Time:" + buildSummary.totalTime + " Total Errors:" + buildSummary.totalErrors);
}
}
private class Args
{
public string appName = "AppName";
public string targetDir = "~/Desktop";
}
}
至此Unity侧配置完毕
开始打包
现在你可以单击Build Now开始打包,也请注意,Jenkins显示的打包成功状态并不是很准,如果Unity工程那里出现了C#脚本语法错误等,Jenkins大概率是不会识别出来这是个错误的,因为Unity只要不是编辑器进程退出的过程中出了阻断性问题,Jenkins就会认为我正常走完了没有什么问题。
所以每次打完包记得一定要看log确认。
Happy programming!