Jenkins自动化搭建记录

news2024/12/22 15:46:51

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

本文记录一次搭建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/bash

UNITY_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.coloradmin.cn/o/1644509.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

手动实现简易版RPC(三)

手动实现简易版RPC(三) 往期内容 手动实现简易版RPC&#xff08;一&#xff09;&#xff1a;RPC简介及系统架构 手动实现简易版RPC&#xff08;二&#xff09;&#xff1a;简单RPC框架实现 前言 接上两篇博客我们实现了最简易RPC框架&#xff0c;接下来的几期重点在简易版…

深入解析C#特殊字符:概念、分类与使用方法

文章目录 前言一、特殊字符的定义二、特殊字符的分类与作用1.转义字符2. 格式字符3. 逻辑运算符4. 运算符5. 字符串6. 注释 三、$&#xff1a;字符串内插&#xff08;String Interpolation&#xff09;四、&#xff1a;逐字字符串&#xff08;Verbatim Strings&#xff09;五、…

ai绘画的今天,新人还能入行吗?

现在24年了&#xff0c;大趋势已经很明显&#xff0c;ai绘画现阶段处于如日中天的时候&#xff0c;无论是绘画&#xff0c;电商&#xff0c;小说推文。等等一系列的行业都能用到ai绘画&#xff01; 说明一下&#xff0c;我自己就是23年初偶然间刷到了ai绘画这个赛道&#xff0c…

SpringDI方式及Redis应用场景的分享

1、为什么Spring和IDEA 都不推荐使用 Autowired 注解 大家在使用IDEA开发的时候有没有注意到过一个提示&#xff0c;在字段上使用Spring的依赖注入注解Autowired后会出现如下警告Field injection is not recommended (字段注入是不被推荐的)&#xff1b;但是使用Resource却不会…

Git笔记-常用指令

Git笔记-常用指令 一、概述二、仓库管理二、缓存区操作1. 添加文件到缓存区2. 取消缓存文件3. 忽略列表 三、日志状态信息四、分支操作五、六、 一、概述 这里记录一些git常用的指令。 二、仓库管理 # 本地仓库初始化 git init# 克隆仓库 git clone git_url # git clone ht…

wordpress子比主题给文章内容加上密码查看

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、pandas是什么?二、使用步骤1.引入库2.读入数据第三步:文章内添加代码前言 提示:这里可以添加本文要记录的大概内容: 例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,…

三维重建(SFM)与实时定位建图(SLAM)的区分与联系

1、SLAM SLAM是Simultaneous Location and Mapping&#xff0c;同时定位与地图构建。是指搭载特定传感器的主体&#xff0c;在没有环境先验信息的情况下&#xff0c;于运动过程中建立环境的模型&#xff0c;同时估计自己的运动。目的是解决自主机器人“定位”和“建图”两个问题…

Spring JdbcTemplate使用临时表+事务会话管理实现数据新增、查询及自动清除功能

需求描述&#xff1a; 由于某些情况下当查询过滤参数过大时&#xff0c;执行sql由于参数过大而报错&#xff0c;此时 需要使用临时表的方式&#xff0c;即 当参数超过某个阀值&#xff08;如 1000&#xff0c;可调整&#xff09;新增一张临时表&#xff0c;将原表 与 该临时表进…

Vue3+vite优化基础架构(3)--- 优化vue-i18n国际化配置

Vue3vite优化基础架构&#xff08;3&#xff09;--- 优化vue-i18n国际化配置 说明全部页面进行中英文使用测试中英文切换对ElementPlus里面的所有组件进行中英文切换 说明 这里记录下自己在Vue3vite的项目增加全局中英文切换按钮对页面进行中英文切换及同时对ElementPlus里面的…

stm32单片机开发五、I2C通信

I2C这部分的时序单元可以看我另外一篇博客I2C基本知识 I2C发送数据时是高位先行&#xff0c;也就是先发送高位的&#xff0c;比如一个字节有8位B0-B7&#xff0c;先发送B7高位 I2C用的是同步时序&#xff0c;它有一个好处&#xff0c;比如传输过程中&#xff0c;出现了中断&…

深度学习之基于Pytorch姿态估计的仰卧起坐计数系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景 仰卧起坐作为一种常见的健身动作&#xff0c;被广泛用于腹部肌肉的锻炼。然而&#xff0c;对于仰卧起…

(四)机器学习在银行中的典型应用场景(模型) #CDA学习打卡

本文总结了机器学习在银行中的典型业务应用场景&#xff0c;包括客户管理、零售智能营销、公司智能营销、自然语言处理、运营管理以及图像识别。

智慧园区sip对讲广播解决方案

智慧园区sip对讲广播解决方案18123651365 目前而言智慧园区管理者主要需要解决的问题是&#xff1a; 1.面对庞大的园区小区规模&#xff0c;能源管理全部依赖人工已经无法实现&#xff1b; 2.节能管理工作难度大&#xff0c;面对问题&#xff0c;没有好的解决方案&#xff1b; …

pyqt6的安装,使用-1

pyqt6安装的话&#xff0c;在自己所用的环境里安就可以&#xff0c;之前写有yolov5,yolov8的环境配置&#xff0c;python3.8的我安装就没有问题&#xff0c;打开anaconda&#xff0c;激活自己的环境 把这复制进去 pip install pyqt6 pip install pyqt6-tools 这个安装完了之后…

【JavaWeb】网上蛋糕项目商城-关键字搜索,购物车以及商品详情功能

概念 上文中已经实现热销和新品的商品列表功能&#xff0c;本文篇幅中实现关键字搜索商品&#xff0c;将商品加入购物车&#xff0c;以及查看商品的详情信息等功能 关键字搜索实现步骤 在head.jsp头部页面中&#xff0c;鼠标移动至搜索图标会显示隐藏的搜索框进行输入关键信…

数据结构(C):玩转链表

&#x1f37a;0.前言 言C之言&#xff0c;聊C之识&#xff0c;以C会友&#xff0c;共向远方。各位博友的各位你们好啊&#xff0c;这里是持续分享数据结构知识的小赵同学&#xff0c;今天要分享的数据结构知识是链表&#xff0c;在这一章&#xff0c;小赵将会向大家展开聊聊链表…

c语言排序算法之八(桶排序)

前言 以下内容是被验证可以有效理解桶排序&#xff0c;代码也较容易理解。如果你发现还有很多需要增加的&#xff0c;欢迎留言。 为什么要单独写排序算法这一系列&#xff0c;看过一些贴子普遍篇幅较长。看完依旧难以直观理解原理及整个过程。代码永远是基于理解的基础上才能…

Carla基础 | Carla预编译版安装与ROS联合仿真图文教程

目录 1 什么是Carla&#xff1f;2 Carla预编译版安装2.1 独立显卡配置2.2 安装ROS2.3 启动虚拟环境2.4 安装Carla预编译版2.5 安装carla-ros-bridge 3 测试案例常见问题 1 什么是Carla&#xff1f; Carla是由西班牙巴塞罗那自治大学计算机视觉中心指导开发的开源仿真模拟器&…

Redis-五大数据类型-Set(集合)

五大数据类型-Set&#xff08;集合&#xff09; 简介 与List类似是一个列表功能&#xff0c;但Set是自动排重的&#xff0c;当需要存储一个列表数据&#xff0c;又不希望出现重复数据时&#xff0c;Set是一个很好的选择。 Set是String类型的无序集合&#xff0c;它底层其实是…

学生宿舍智能电表系统改造升级意义

石家庄光大远通电气有限公司学生宿舍智能电表控制系统改造升级功能与意义** 一、实时监测 宿舍智能电表控制系统具备实时监测功能&#xff0c;能够实时收集、记录和分析每个宿舍的用电数据。这种监测不仅可以帮助管理者掌握用电情况&#xff0c;还可以为用户提供详细的用电报…