前言

Jenkins是一个开源的持续集成工具,用人话来说就是没有感情的打包机。一般常见于公司项目用于持续交付。

可能有个人开发者对打包机很陌生或者不是很在意(比如在写这篇文章之前的我),项目小,本机写完本机打,甚至能边写边打,也没觉得怎么样。直到后来打包卡电脑没法做其他事情的时候,是时候单拎出来个电脑做个打包机了,要不耽误事情。

Jenkins侧

安装

jenkins打包可以自己输入版本号吗 jenkins怎么打包_运维

前往官网下载安装,有国内下载站,直接下Windows的LTS版本即可。

jenkins打包可以自己输入版本号吗 jenkins怎么打包_unity_02

接着就按照安装向导一步一步走,直到你进入Jenkins的WebUI界面。

jenkins打包可以自己输入版本号吗 jenkins怎么打包_游戏引擎_03

现在请点击左侧Manage Jenkins,然后点右侧的Plugins。

jenkins打包可以自己输入版本号吗 jenkins怎么打包_运维_04

单击左侧的Available Plugins,搜索Unity并安装。

配置

接下来需要告诉Jenkins你的Unity编辑器安装在哪里。

回到Manage Jenkins,点开Tools,一直下拉到底部添加安装

jenkins打包可以自己输入版本号吗 jenkins怎么打包_运维_05

jenkins打包可以自己输入版本号吗 jenkins怎么打包_jenkins_06

创建工程

jenkins打包可以自己输入版本号吗 jenkins怎么打包_jenkins_07

选择Freestyle project,写好名字点击OK

jenkins打包可以自己输入版本号吗 jenkins怎么打包_jenkins_08

这些选项你可以根据个人喜好配置,包括Github库位置(若需要更新CI/CD状态)、Git账号啥的。

但建议打开时间戳log和打包无响应自动叫停。

jenkins打包可以自己输入版本号吗 jenkins怎么打包_unity_09

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侧配置完毕

开始打包

jenkins打包可以自己输入版本号吗 jenkins怎么打包_jenkins_10

现在你可以单击Build Now开始打包,也请注意,Jenkins显示的打包成功状态并不是很准,如果Unity工程那里出现了C#脚本语法错误等,Jenkins大概率是不会识别出来这是个错误的,因为Unity只要不是编辑器进程退出的过程中出了阻断性问题,Jenkins就会认为我正常走完了没有什么问题。

jenkins打包可以自己输入版本号吗 jenkins怎么打包_jenkins打包可以自己输入版本号吗_11

所以每次打完包记得一定要看log确认。

jenkins打包可以自己输入版本号吗 jenkins怎么打包_jenkins打包可以自己输入版本号吗_12

Happy programming!