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/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的运行用户修改为当前登录账户,这里面的原因可以参考这个老哥的博客,感觉很有道理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/585828.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用 LlamaIndex 和 Llama 2-Chat 构建知识驱动的对话应用程序

文章目录 使用 LlamaIndex 和 Llama 2-Chat 构建知识驱动的对话应用程序Llama 2-70B-聊天LlamaIndex 解决方案概述先决条件使用 SageMaker JumpStart 部署 GPT-J 嵌入模型使用 SageMaker Python SDK 进行部署在 SageMaker Studio 中使用 SageMaker JumpStart 进行部署使用 Sage…

Dashboard 介绍

Dashboard 介绍 一、K8S Dashboard简介 简单的说&#xff0c;K8S Dashboard是官方的一个基于WEB的用户界面&#xff0c;专门用来管理K8S集群&#xff0c;并可展示集群的状态。K8S集群安装好后默认没有包含Dashboard&#xff0c;我们需要额外创建它 二、RABC简介 还是那句话&a…

关于下载上传的sheetjs

一、背景 需要讲后端返回来的表格数据通过前端设置导出其中某些字段&#xff0c;而且得是xlsx格式的。 那就考虑使用控件SheetJS。如果是几年前&#xff0c;一般来说&#xff0c;保存excel的文件都是后端去处理&#xff0c;处理完成给前端一个接口&#xff0c;前端调用了打开…

SQLite的扩展函数Carray()表值函数(三十八)

返回&#xff1a;SQLite—系列文章目录 上一篇:SQLite如何处理CSV 虚拟表 下一篇&#xff1a;SQLite—系列文章目录 ​ 1. 概述 Carray()是一个具有单列的表值函数(名为 “value”)和零行或多行。 carray() 中每一行的“值”取自 C 语言数组 由应用程序通过参数绑定提…

如何进行面向对象分析、面向对象设计和面向对象编程

目录 1.引言 2.案例介绍和难点剖析 3.如何进行面向对象分析 4.如何进行面向对象设计 5.如何进行面向对象编程 6.总结 1.引言 面向对象分析(OOA)、面向对象设计(00D)和面向对象编程(OOP)是面向对象开发的3个主要环节。 在以往的工作中&#xff0c;作者发现&#xff0c;很多…

JavaScript原型链深度剖析

目录 前言 一、原型链 1.原型链的主要组成 原型&#xff08;Prototype&#xff09; 构造函数&#xff08;Constructor&#xff09; 实例&#xff08;Instance&#xff09; 2.原型链的工作原理 前言 在JavaScript的世界中&#xff0c;原型链&#xff08;Prototype Chain&…

Amazon云计算AWS之[4]非关系型数据库服务SimpleDB和DynamoDB

文章目录 简介非关系型VS关系数据库SimpleDB域条目属性值SimpleDB的使用 DynamoDBSimpleDB VS DynamoDB 简介 非关系型数据库服务主要用于存储结构化的数据&#xff0c;并为这些数据提供查找、删除等基本的数据库功能。AWS中提供的非关系型数据库主要包括SimpleDB和DynamoDB …

聚醚醚酮(Polyether Ether Ketone)PEEK在粘接使用时可以使用UV胶水吗?要注意哪些事项?

一般情况下&#xff0c;聚醚醚酮&#xff08;Polyether Ether Ketone&#xff0c;PEEK&#xff09;是一种难以黏附的高性能工程塑料&#xff0c;而UV胶水通常不是与PEEK进行粘接的首选方法。PEEK表面的化学性质和高温性能使得它对常规胶水的附着性较低。然而&#xff0c;有一些…

(成品论文22页)24深圳杯数学建模A题1-4问完整代码+参考论文重磅更新!!!!

论文如下&#xff1a; 基于三球定位的多个火箭残骸的准确定位 针对问题一&#xff1a;为了进行单个残骸的精确定位&#xff0c;确定单个火箭残骸发生音爆 时的精确位置和时间&#xff0c;本文基于三球定位模型&#xff0c;考虑到解的存在性和唯一性&#xff0c; 选取了四个监测…

用HTML5实现播放gif文件

用HTML5实现播放gif文件 在HTML5中&#xff0c;你可以使用<img>标签来播放GIF文件。GIF文件本质上是一种图像格式&#xff0c;它支持动画效果&#xff0c;因此当在网页上加载时&#xff0c;它会自动播放动画。先看一个简单的示例&#xff1a; <!DOCTYPE html> &l…

清华同方电脑文件删除怎么恢复

在日常使用清华同方电脑的过程中&#xff0c;我们难免会遇到误删重要文件的情况。文件丢失不仅可能导致数据损失&#xff0c;还可能影响到我们的工作、学习甚至是生活。那么&#xff0c;当在清华同方电脑上删除了重要文件后&#xff0c;我们应该如何恢复呢&#xff1f;本文将为…

Linux服务器安全基础 - 查看入侵痕迹

1. 常见系统日志 /var/log/cron 记录了系统定时任务相关的日志 /var/log/dmesg 记录了系统在开机时内核自检的信息&#xff0c;也可以使用dmesg命令直接查看内核自检信息 /var/log/secure:记录登录系统存取数据的文件;例如:pop3,ssh,telnet,ftp等都会记录在此. /var/log/btmp:记…

服务器被攻击,为什么后台任务管理器无法打开?

在服务器遭受DDoS攻击后&#xff0c;当后台任务管理器由于系统资源耗尽无法打开时&#xff0c;管理员需要依赖间接手段来进行攻击类型的判断和解决措施的实施。由于涉及真实代码可能涉及到敏感操作&#xff0c;这里将以概念性伪代码和示例指令的方式来说明。 判断攻击类型 步…

C#---使用Coravel实现定时任务

Coravel是一款框架轻&#xff0c;使用简单&#xff0c;支持秒级定时任务。 1.添加NuGet引用 2.定义自己的工作任务 using Coravel.Invocable; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; using System.Thread…

【Java笔记】JVM:对象在内存中是什么样的?如何计算对象占用的内存大小?

文章目录 Java对象的内存布局计算对象占用的内存大小Openjdk jol来算几个Object o new Object() 该对象在内存中占用多少字节&#xff1f;基本数据类型作为成员变量的对象有实例对象作为成员变量的对象 Java对象的内存布局 Java中&#xff0c;一个实例对象在内存中的组成主要包…

国家开放大学2024年春《Matlab语言及其应用》实验五Simulink系统 建模与仿真参考答案

答案&#xff1a;更多答案&#xff0c;请关注【电大搜题】微信公众号 答案&#xff1a;更多答案&#xff0c;请关注【电大搜题】微信公众号 答案&#xff1a;更多答案&#xff0c;请关注【电大搜题】微信公众号 实验报告 姓名&#xff1a; 学号&#xff1a; 实验五名称…

【JAVA进阶篇教学】第九篇:MyBatis-Plus用法介绍

博主打算从0-1讲解下java进阶篇教学&#xff0c;今天教学第九篇&#xff1a;MyBatis-Plus用法介绍。 在 MyBatis-Plus 3.5.0 中&#xff0c;LambdaQueryWrapper支持多种条件构造方式&#xff0c;除了等于&#xff08;eq&#xff09;、不等于&#xff08;ne&#xff09;、大于&a…

大模型公开课-大模型的语言解码游戏学习总结

在当今快速发展的人工智能领域&#xff0c;深度学习作为其中的一项关键技术&#xff0c;正引领着科技的新潮流。而对于初学者来说&#xff0c;了解大型语言模型的解码游戏&#xff0c;对于理解深度学习的基本概念至关重要。本篇博客将对一次关于大型语言模型解码游戏的视频教学…

SQL如何利用Bitmap思想优化array_contains()函数

目录 0 问题描述 1 位图思想 2 案例实战 3 小结 0 问题描述 在工作中&#xff0c;我们往往使用array_contains()函数来进行存在性问题分析&#xff0c;如判断某个数是否在某个数组中&#xff0c;但是当表数据量过多&#xff0c;存在大量array_contains()函数时&#xff0c;…

ollama-python-Python快速部署Llama 3等大型语言模型最简单方法

ollama介绍 在本地启动并运行大型语言模型。运行Llama 3、Phi 3、Mistral、Gemma和其他型号。 Llama 3 Meta Llama 3 是 Meta Inc. 开发的一系列最先进的模型&#xff0c;提供8B和70B参数大小&#xff08;预训练或指令调整&#xff09;。 Llama 3 指令调整模型针对对话/聊天用…