Jenkins自动化搭建记录

news/2024/9/20 1:54:52/ 标签: jenkins, 自动化, 运维

每一份努力都是有一份期盼,每一份付出都是为了有更多的收获。

本文记录一次搭建Jenkins自动参数化打包APK的实现过程和碰到的问题,实现了在Windows和Mac系统下的自动化打包流程。

因为Jenkins的安装过程在网上的教程很多,这里就不在赘述。

介绍

准备工作,因为要实现自动化打包APK,所以就需要从Jenkins调动对应系统的执行文件然后通过命令行调用Unity中的静态方法,因此首先我们首先需要一个Unity里面的静态方法来调用构建方法,具体脚本内容参考下面代码BuildTools.cs,因为是在Windows和Mac都可以自动构建,这时候在Windows系统下我们还需要准备一个bat文件来执行Unity命令行调用构建方法(具体内容参考下面脚本BuildClient.bat),在Mac系统下需要一个sh文件来执行Unity命令行调用构建方法(具体内容参考下面脚本BuildClient.sh

BuildTools.cs

针对这篇代码这里进行一下说明:

一开始我们的代码入口是BuildApk方法,通过上述Bat或者sh脚本调进来的Unity的方法,在这个方法里面我们通过System.Environment.GetCommandLineArgs()获取到了当前Jenkins运行环境下的环境变量,来实现参数的传递,之后我们调用了ExecuteBuild方法,这个方法里面我们实现了AB包的构建,针对代码中自己的AB包构建方式,可以考虑删除这几个方式替换为自己的或者直接删除这几个方法(ExecuteBuildGetBuildPackageVersionCreateEncryptionServicesInstance

在构建完成之后我们跳转到下一个方法StartBuildApk方法,这个方法里面就是主要构建的内容了,首先我们定义一个BuildPlayerOptions结构体,这个结构体里面的参数就是控制我们打包的一些参数,在当前脚本中我们分别设置了需要打包的场景列表,打包之后的位置,这次构建的目标平台,是否为开发包,之后调用BuildPipeline.BuildPlayer方式去构建,这构建之后注释的这些代码为在实现过程中碰到打包失败问题的时候打出Log来定位问题(这个过程碰到的问题太多,且往下看)。最后一个方法GetPathName就简单了,根据当前的时间创建对应的文件,来设置对应打好之后的包的目录。

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
using System.IO;
using System;
using YooAsset;
using YooAsset.Editor;
using System.Linq;public class BuildTools
{[MenuItem("Build/Build APK")]public static void BuildApk(){string[] args= System.Environment.GetCommandLineArgs();bool isDebug = false;foreach (string arg in args){if (arg.Contains("--productName:")){string productName = arg.Split(':')[1];PlayerSettings.productName = productName;}if (arg.Contains("--version:")){string version = arg.Split(':')[1];PlayerSettings.bundleVersion = version;}if (arg.Contains("--isDebug:")){string debug = arg.Split(':')[1];isDebug = bool.Parse(debug);}}ExecuteBuild(isDebug);}/// <summary>////// </summary>private static void ExecuteBuild(bool isDebug){BuildParameters buildParameters = new BuildParameters();buildParameters.StreamingAssetsRoot = AssetBundleBuilderHelper.GetDefaultStreamingAssetsRoot();buildParameters.BuildOutputRoot = AssetBundleBuilderHelper.GetDefaultBuildOutputRoot();buildParameters.BuildTarget = EditorUserBuildSettings.activeBuildTarget;buildParameters.BuildPipeline = AssetBundleBuilderSettingData.Setting.BuildPipeline;buildParameters.BuildMode = AssetBundleBuilderSettingData.Setting.BuildMode;buildParameters.PackageName = AssetBundleBuilderSettingData.Setting.BuildPackage;buildParameters.PackageVersion = GetBuildPackageVersion();buildParameters.VerifyBuildingResult = true;buildParameters.SharedPackRule = new ZeroRedundancySharedPackRule();buildParameters.EncryptionServices = CreateEncryptionServicesInstance();buildParameters.CompressOption = AssetBundleBuilderSettingData.Setting.CompressOption;buildParameters.OutputNameStyle = AssetBundleBuilderSettingData.Setting.OutputNameStyle;buildParameters.CopyBuildinFileOption = AssetBundleBuilderSettingData.Setting.CopyBuildinFileOption;buildParameters.CopyBuildinFileTags = AssetBundleBuilderSettingData.Setting.CopyBuildinFileTags;if (AssetBundleBuilderSettingData.Setting.BuildPipeline == EBuildPipeline.ScriptableBuildPipeline){buildParameters.SBPParameters = new BuildParameters.SBPBuildParameters();buildParameters.SBPParameters.WriteLinkXML = true;}var builder = new AssetBundleBuilder();var buildResult = builder.Run(buildParameters);if (buildResult.Success){//EditorUtility.RevealInFinder(buildResult.OutputPackageDirectory);StartBuildApk(isDebug);}}private static string GetBuildPackageVersion(){int totalMinutes = DateTime.Now.Hour * 60 + DateTime.Now.Minute;return DateTime.Now.ToString("yyyy-MM-dd") + "-" + totalMinutes;}//private static List<Type> GetEncryptionServicesClassTypes()//{//    return EditorTools.GetAssignableTypes(typeof(IEncryptionServices));//}private static IEncryptionServices CreateEncryptionServicesInstance(){//var classType = GetEncryptionServicesClassTypes()[0];return (IEncryptionServices)Activator.CreateInstance(typeof(EncryptionNone));}private static void StartBuildApk(bool isDebug){BuildPlayerOptions buildPlayerOptions = new BuildPlayerOptions();// 获取已配置的场景列表string[] scenes = EditorBuildSettings.scenes.Where(scene => scene.enabled).Select(scene => scene.path).ToArray();buildPlayerOptions.scenes = scenes;//buildPlayerOptions.locationPathName = "D:\\XXX\\XXX\\ClientTest.apk";//因为Windows和Mac平台下的路径不一样,这里进行区别对待buildPlayerOptions.locationPathName = GetPathName(isDebug);buildPlayerOptions.target = BuildTarget.Android;buildPlayerOptions.options = isDebug ? BuildOptions.Development : BuildOptions.None;UnityEditor.Build.Reporting.BuildReport report = BuildPipeline.BuildPlayer(buildPlayerOptions);//Debug.LogError("Start Build App Done!");//UnityEditor.Build.Reporting.BuildSummary summary = report.summary;//var a = report.steps;//for (int i = 0; i < a.Length; i++)//{//    var b = a[i].messages;//    for (int j = 0; j < b.Length; j++)//    {//        Debug.LogError("Build Step = " + b[j].content);//    }//}//Debug.LogError("Build Error count = " + summary.totalErrors);//if (summary.result == UnityEditor.Build.Reporting.BuildResult.Succeeded)//{//    EditorUtility.RevealInFinder("/XXX/XXX/XXX/DoneProject");//    Debug.LogError("Build App Done!");//}}/// <summary>/// mac path name/// </summary>/// <returns></returns>private static string GetPathName(bool isDebug){string path = "/XXX/XXX/XXX/DoneProject";if (isDebug){path = string.Format("{0}/Debug",path);}DateTime nowTime = DateTime.Now;string dayName = nowTime.Month + "_" + nowTime.Day;path = string.Format("{0}/{1}", path, dayName);if (!Directory.Exists(path)){Directory.CreateDirectory(path);}path = string.Format("{0}/Client{1}.apk", path, nowTime.ToLongTimeString());return path;}
}

BuildClient.bat

bat文件说明

Windows系统下命令行模式运行Unity调用Unity方法的脚本,这里有些问题比如项目的运行路径其实应该为Jenkins的工作空间下创建的临时项目路径,所以这个脚本里面的项目路径和Log路径应该通过环境变量从Jenkins那边传过来,具体实现方式可以参考下面Mac环境下传参的逻辑(大家自己动手实现Windows版本哦)

  • -quit :在其他命令执行完毕后退出 Unity Editor。这可能导致错误消息被隐藏(但是,它们仍会出现在 Editor.log 文件中)。
  • -batchmode:以批处理模式运行 Unity。请始终将此命令与其他命令行参数结合使用,从而确保不会出现弹出窗口且无需任何人为干预。在执行脚本代码期间发生异常时,资源服务器更新失败时,或其他操作失败时,Unity 将立即退出并返回代码 1。请注意,在批处理模式下,Unity 会将其日志输出的最小版本发送到控制台。但是,日志文件仍包含完整的日志信息。当 Editor 打开某个项目时,您无法以批处理模式打开相同的项目;一次只能运行一个 Unity 实例。要检查是否正在以批处理模式运行 Editor 或独立平台播放器,请使用 Application.isBatchMode 运算符。如果在使用 -batchmode 时还没有导入项目,则目标平台为默认平台。要强制选择其他平台,请使用 -buildTarget 选项。
  • -buildTarget Android:在加载项目之前选择有效的构建目标。可能的选项包括:
    Standalone、Win、Win64、OSXUniversal、Linux64、iOS、Android、WebGL、XboxOne、PS4、WindowsStoreApps、Switch、tvOS。
  • -projectPath:在指定路径下打开项目。如果路径名包含空格,请将其用引号引起来。
  • -executeMethod:执行的Unity的静态方法
  • -logFile:Log文件路径
  • --productName:项目名称,传过来的参数
  • --version:版本号,传过来的参数

这些命令的意义和其他更多命令行参数可以参考Unity官方文档。

"D:\XXX\Unity 2020.3.29f1\Editor\Unity.exe" ^
-quit ^
-batchmode ^
-buildTarget Android ^
-projectPath "D:\XXX\XXX\XXX" ^
-executeMethod BuildTools.BuildApk  ^
-logFile "D:\XXX\XXX\XXX\Logs\AssetImportWorker0.log" ^
--productName:%1 ^
--version:%2

BuildClient.sh

sh文件说明:

Mac系统下命令行模式运行Unity调用Unity方法的脚本,因为在Jenkins中设置了Svn拉取项目,所以这里面用到的项目路径和Log文件路径都需要通过Jenkins那边传过来,这里用到了环境变量来传参,更多可控参数打包大家自己也可以通过相同的方式举一反三哦。具体的命令和含义参考上面Windows的解释。

#!/bin/bashUNITY_PATH="/Applications/Unity/Hub/Editor/2020.3.29f1/Unity.app/Contents/MacOS/Unity"PROJECT_PATH=${WORKSPACE}LOG_FILE="${PROJECT_PATH}/Logs/AssetImportWorker0.log"EXECUTE_METHOD="BuildTools.BuildApk"PRODUCTNAME=${PRODUCT_NAME}
VERSION=${PRODUCT_VERSION}
ISDEBUG=${IS_DEBUG}${UNITY_PATH}  -quit  -batchmode -buildTarget Android -projectPath ${PROJECT_PATH} -executeMethod ${EXECUTE_METHOD} -logFile ${LOG_FILE} --productName:${PRODUCTNAME} --version:${VERSION} --isDebug:${ISDEBUG}

好了现在准备工作做好了,让我们开始配置一个Jenkins工程,打开Jenkins的管理网址,一般为:你部署Jenkins的IP:8080

我们这里选择一个自由风格的项目,

到这个界面

选择参数化构建过程,这里面定义的参数最后打包的时候可以选择传递给脚本去,就是我们上面用到的

因为我们的每一次打包都需要将项目更到最新,因为项目使用的是Svn这里选择源码管理,Subversion这里配置自己的仓库地址和验证信息,如果没有这个选项,需要在Jenkins插件管理里面下载。

下面在构建步骤里面我们开始增加构建步骤选择执行Shell或者执行Window批处理命令,类似于bat脚本

在Shell里面增加环境变量传递参数,这里面说明一下$ProductName这种方式是获取我们上面配置的参数化构建的参数的名字,然后通过环境变量的方式传给执行脚本里面去。

再添加一个Shell,将生成的APK文件上传到对应的局域网服务器中去,这里有多种方式选择,比如FTP文件服务器,rsync命令等。

到此整个流程已经完成。

问题

部署的过程是艰辛的碰到的问题也是多种多样的,这里记录一下:

1.设置平台

刚开始的时候执行Unity命令行的时候设置的项目是本地固定文件的项目,结果在构建的过程中发现无论怎么修改都不生效,后面才知道这个Jenkins是会从在自己的工作空间下通过版本控制器拉取一份新的代码,这时候也随之衍生了很多问题,比如项目是Android平台的,但是从Svn拉取下来的项目默认是Windows平台的,对于这种问题找到了两种解决方案:

方案一:通过Unity打开Jenkins工作空间下的项目,选择Android平台,但是这样做有一个缺点,就是只要Jenkins中配置了下图这个,那么每次都会重新拉一个新项目,那我们这种方式也就失效了,不过一般应该没人这么做

 

方案二:使用Unity命令行的时候Unity提供了一个命令来选择有效的构建目标

 2.包体大小不对

构建出来的包的大小始终跟用手点击打出来的包的大小不一样,后面发现项目中有的文件使用了SVN外链的形式,正常情况下Jenkins下载出来的项目是不完全的,这个问题的解决方案也是很简单的,在工程配置界面找到下图这个,不要勾选就行啦(这个可能默认是不勾选的,但是我在配置过程中可能手贱勾选了,后面导致出问题了)

 3.Jenkins用户不同于登录用户

这里记录一个致命问题,刚开始的时候在配置的过程中一切都挺顺利,也能出包,后面因为修改了Jenkins运行的用户,导致始终无法正常打包,不是长时间卡在了Unity构建的时候,就是在构建的时候报错等等问题,刚开始还想着去解决这些问题,经过了大量时间的验证,后面发现这条路走不通,还是将Jenkins的运行用户修改为当前登录账户,这里面的原因可以参考这个老哥的博客,感觉很有道理。


http://www.ppmy.cn/news/1451993.html

相关文章

华为试题之删除最少字符

题目描述 删除字符串中出现次数最少的字符 如果多个字符出现次数一样则都删除 输入描述 输入只包含小写字母 输出描述 输出删除后剩余的字符 若删除后字符串长度为0&#xff0c;则输出empty 我的思路是将字符串中的字符对应的数量和key统计后放到对应的字典中&#xff0c; 对字…

python项目==一个web项目,配置模板指定文件清洗规则,调用模板规则清洗文件

代码地址 一个小工具。 一个web项目&#xff0c;配置模板指定文件清洗规则&#xff0c;调用模板规则清洗文件 https://github.com/hebian1994/csv-transfer-all 技术栈&#xff1a; SQLite python flask vue3 elementplus 功能介绍&#xff1a; A WEB tool for cleaning…

OpenWRT有线桥接部署教程

前言 之前咱们讲到OpenWRT部署WAN实现PPPoE拨号上网和自动获取IP模式上网的办法&#xff1a; OpenWRT设置PPPoE拨号教程 OpenWRT设置自动获取IP&#xff0c;作为二级路由器 这一次&#xff0c;咱们尝试用OpenWRT有线桥接上一级路由器的教程。 可能有小伙伴敏锐地发现了&am…

阿里云API网关 产品的使用笔记

阿里云的产品虽多&#xff0c;还是一如既往的一用一个看不懂&#xff0c;该模块的文档依旧保持“稳定”发挥&#xff0c;磕了半天才全部跑通。 用阿里云API网关的原因是&#xff0c;在Agent中写插件调用API的时候&#xff0c;需要使用Https协议&#xff0c;又嫌搞备案、证书等事…

《Mask2Former》算法详解

文章地址&#xff1a;《Masked-attention Mask Transformer for Universal Image Segmentation》 代码地址&#xff1a;https://github.com/facebookresearch/Mask2Former 文章为发表在CVPR2022的一篇文章。从名字可以看出文章像提出一个可以统一处理各种分割任务&#xff08;…

望仙谷听谿涛

望仙谿涛 近来不知为何&#xff0c;染上喝咖啡的恶习&#xff0c;称为“恶”&#xff0c;是因为要花钱&#xff0c;而且非得是那种口感好的。 网络流行“人生无解&#xff0c;来杯拿铁”。 大抵是因为咖啡再苦&#xff0c;也比不过生活吧&#xff0c;至少咖啡可以加糖&#xff…

数据库管理-第179期 分库分表vs分布式(20240430

数据库管理179期 2024-04-30 数据库管理-第179期 分库分表vs分布式&#xff08;20240430&#xff09;1 分库分表1.1 分库1.2 分表1.3 组合1.4 问题 2 分布式3 常见分布式数据库4 期望总结 数据库管理-第179期 分库分表vs分布式&#xff08;20240430&#xff09; 作者&#xff1…

C++ 函数与指针

函数内部数据是地址需要传递给调用函数&#xff0c;返回的当然是指针了&#xff01;当然&#xff0c;这个返回地址也可以通过函数参数返回&#xff01; 函数的参数是指针可以输出函数多个结果&#xff0c;返回值本身就是返回数据&#xff0c;什么时候需要返回指针呢&#xff1f…

WebSocket 的封装

websocket 具体内容参考了 原文 import {ref, onUnmounted} from vue; import dayjs from "dayjs";class Socket {url;ws null;opts;reconnectAttempts 0;listeners {};heartbeatInterval null;constructor(url, opts {}) {this.url url;this.opts {// 心跳检…

Java image-processing 包依赖错误

错误的信息为&#xff1a; [ERROR] Failed to execute goal on project image-processing: Could not resolve dependencies for project com.ossez:image-processing:jar:0.0.2-SNAPSHOT: Failed to collect dependencies at org.openimaj:core-image:jar:1.3.10 -> org.op…

机器学习每周挑战——二手车车辆信息交易售价数据

这是数据集的截图 目录 背景描述 数据说明 车型对照&#xff1a; 燃料类型对照&#xff1a; 老规矩&#xff0c;第一步先导入用到的库 第二步&#xff0c;读入数据&#xff1a; 第三步&#xff0c;数据预处理 第四步&#xff1a;对数据的分析 第五步&#xff1a;模型建…

ES集群分布式查询原理

集群分布式查询 elasticsearch的查询分成两个阶段&#xff1a; scatter phase&#xff1a;分散阶段&#xff0c;coordinating node会把请求分发到每一个分片gather phase&#xff1a;聚集阶段&#xff0c;coordinating node汇总data node的搜索结果&#xff0c;并处理为最终结…

设计模式的原则与分类

一、设计模式的原则 1、单一职责原则 一个类只需要负责一种职责即可&#xff0c;一个类发生变化的原因&#xff0c;必然是所负责的职责发生变化 2、接口隔离原则 单一职责原则是接口隔离原则的基础&#xff0c;单一职责原则注重职责的划分&#xff0c;从职责角度进行类和接口…

使用docker创建rocketMQ主从结构,使用

1、 创建目录 mkdir -p /docker/rocketmq/logs/nameserver-a mkdir -p /docker/rocketmq/logs/nameserver-b mkdir -p /docker/rocketmq/logs/broker-a mkdir -p /docker/rocketmq/logs/broker-b mkdir -p /docker/rocketmq/store/broker-a mkdir -p /docker/rocketmq/store/b…

【数据结构与算法】力扣 347. 前 K 个高频元素

题目描述 给你一个整数数组 nums 和一个整数 k &#xff0c;请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。 示例 1: 输入: nums [1,1,1,2,2,3], k 2 输出: [1,2]示例 2: 输入: nums [1], k 1 输出: [1]提示&#xff1a; 1 < nums.length < …

KIE关键信息抽取——SDMG-R

https://arxiv.org/pdf/2103.14470https://arxiv.org/pdf/2103.14470 1.概述 背景:传统的关键信息提取方法依赖于模板匹配,这使它们难以泛化到未见过的模板,且对文本识别错误不够鲁棒。SDMG-R方法:提出一种端到端的双模态图推理方法,通过构建双模态图(视觉和文本特征),…

多级留言/评论的功能实现——SpringBoot3后端篇

目录 功能描述数据库表设计后端接口设计实体类entity 完整实体类dto 封装请求数据dto 封装分页请求数据vo 请求返回数据 Controller控制层Service层接口实现类 Mapper层Mybatis 操作数据库 补充&#xff1a;返回的数据结构自动创建实体类 最近毕设做完了&#xff0c;开始来梳理…

【CTF MISC】XCTF GFSJ0512 give_you_flag Writeup(图像处理+QR Code识别)

give_you_flag 菜狗找到了文件中的彩蛋很开心&#xff0c;给菜猫发了个表情包 解法 图片的最后一帧好像闪过了什么东西。 用 Photoshop 打开&#xff0c;检查时间轴。 找到一张二维码&#xff0c;但是缺了三个角&#xff08;定位图案&#xff09;&#xff0c;无法识别。 找一…

《QT实用小工具·四十九》QT开发的轮播图

1、概述 源码放在文章末尾 该项目实现了界面轮播图的效果&#xff0c;包含如下特点&#xff1a; 左右轮播 鼠标悬浮切换&#xff0c;无需点击 自动定时轮播 自动裁剪和缩放不同尺寸图片 任意添加、插入、删除 单击事件&#xff0c;支持索引和自定义文本 界面美观&#xff0c;圆…

matlab绘制热点图

在MATLAB中&#xff0c;通常使用imagesc、pcolor、heatmap&#xff08;需要Statistics and Machine Learning Toolbox&#xff09;等函数来绘制热点图&#xff08;也称为热力图&#xff09;。热点图通常用于可视化矩阵数据&#xff0c;其中每个单元格的颜色表示矩阵中相应元素的…