Unity Mac Jenkins自动化打包之Jenkins+Unity+xcode+fir+钉钉群通知

目录

Unity Mac Jenkins+Unity+xCode+fir+钉钉群通知

1.安装Jenkins

这里推荐使用命令行安装,这样基本不会弹出权限问题,如果直接使用dmg包进行安装,在自动化的过程中会弹出很多权限弹窗。
Mac Jenkins下载地址:https://www.jenkins.io/download/lts/macos/
进入以后出现以下界面
在这里插入图片描述

打开终端,复制命令 安装Jenkisn

 brew install jenkins-lts

等待结束之后就安装成功了。

2.启动Jenkins

然后输入命令:

brew services start jenkins-lts

启动Jenkisn

启动完成后打开浏览器 输入:http://localhost:8080 进入Jenkins后台。

在进入之前需要输入管理员密码以及下载一堆插件,需要等待些时间。

等插件安装完成之后我们的Jenkins就算跑起来了。

3.Jenkins网页配置

然后点击 系统管理-插件管理 进入插件下载界面

接着在可选插件中 搜索Unity 安装一下Unity3d Plugins ,我这里已经安装过了。
在这里插入图片描述
Unity3d Plugins 安装完成之后 点击 系统管理-全局工具配置
在这里插入图片描述
进入之后配置一下我们打包的Unity引擎路径以及版本号
在这里插入图片描述

配置完成之后,我们就可以去创建打包工程了。

4.Jenkins打包工程创建

1.新建任务
在这里插入图片描述
在这里插入图片描述
工程创建完成之后,根据项目需求配置一下Unity打包时需要的参数,一般来说常用的是布尔,选项参数(枚举),和String 字符
在这里插入图片描述
工程路径一定要配上
在这里插入图片描述
其余的参数跟据自己的需求随意配置。
配置完成之后,我们就可以把参数传给Unity了。

5.写入参数给Unity打包使用

这里笔者把参数写成了一个txt文件,放到了Unity Asset同级的目录下,以便Unity在打包时,能够得到参数。

#写入参数
echo "Version:${Version}:VersionCode:${VersionCode}:Name:${Name}:MulRedering:${MulRedering}:Release:${Release}:OpenLog:${OpenLog}:isShowVersion:${isShowVersion}:isConfusion:${isConfusion}:teamCode:${teamCode}"> ${WorkPath}/jenkinsParam.txt

${version} 表示取version的值
以:进行拼接,key为Version,value为Version的值。
拼接完成后写入 workPath工程目录下,名字为JenkinsParam.txt

配置如下:
在这里插入图片描述

6.调用Unity打包方法进行打包

#调用Unity方法进行打包
/Applications/Unity/Unity.app/Contents/MacOS/Unity -projectPath ${WorkPath} -executeMethod BuildApp.BuildiOS -quit -batchmode
cd ${WorkPath}

在使用Jenkins打包时要确保该工程已经关闭,否则打包会出错。

上面方法表面调用 Unity工程下的BuildApp.BuildiOS方法

7.Unity C# 打包方法

using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System;
using System.IO;
using LitJson;
/// <summary>
/// 发布APP
/// </summary>
public class BuildApp
{
    public static string m_AndroidPath = Application.dataPath + "/../BuildTarget/Android/";
    public static string m_IOSPath = Application.dataPath + "/../BuildTarget/IOS/";
    public static string m_JenkinsParamPath = Application.dataPath + "/../jenkinsParam.txt";

    [MenuItem("Build/IOS")]
    public static void BuildiOS()
    {
        PlayerSettings.iOS.buildNumber = "1.0";

#if UNITY_WINDOW 
        BuildSeting buildSeting = GetAndroidBuildSetring();
#else
        BuildSeting_iOS buildSeting = MacGetiOSSeting();
#endif
        string suffx = iOSSeting(buildSeting);
        AssetDatabase.Refresh();
        //绝对储存路径
        //string savePath = m_IOSPath + buildSeting.Name + "_iOS" + suffx + string.Format("_{0:yyyy_MM_dd_HH_mm}", DateTime.Now);
        string savePath = m_IOSPath;
        if (File.Exists(savePath))
        {
            JenkinsTools.DeleteDir(savePath);
        }
        Debug.Log(savePath);
        BuildPipeline.BuildPlayer(FindEnableEditorrScenes(), savePath, EditorUserBuildSettings.activeBuildTarget, BuildOptions.None);
    }


    private static string[] FindEnableEditorrScenes()
    {
        List<string> editorScenes = new List<string>();
        foreach (EditorBuildSettingsScene scene in EditorBuildSettings.scenes)
        {
            if (!scene.enabled) continue;
            editorScenes.Add(scene.path);
        }
        return editorScenes.ToArray();
    }
    [MenuItem("Build/打开日志")]
    public static void LoadReprot()
    {
        GameObject obj = GameObject.Find("Reporter");
        if (obj == null)
        {
            GameObject reportObj = GameObject.Instantiate(AssetDatabase.LoadAssetAtPath<GameObject>("Assets/ThirdParty/Unity-Logs-Viewer/Reporter.prefab"));
            reportObj.name = "Reporter";
            AssetDatabase.SaveAssets();
    
            AssetDatabase.Refresh();
        }
    }
    [MenuItem("Build/关闭日志")]
    public static void DestoryReprot()
    {
        GameObject obj = GameObject.Find("Reporter");
        if (obj != null)
        {
            GameObject.DestroyImmediate(obj);
            AssetDatabase.SaveAssets();
            AssetDatabase.Refresh();
        }
    }


    [MenuItem("Build/读取IOS参数")]
    public static BuildSeting_iOS MacGetiOSSeting()
    {
        BuildSeting_iOS seting = new BuildSeting_iOS();
        string jenkinsParam = File.ReadAllText(m_JenkinsParamPath);
        Debug.Log(jenkinsParam);
        string[] Params = jenkinsParam.Split(':');
        for (int i = 0; i < Params.Length; i++)
        {
            Debug.Log(Params[i]);
            string key = Params[i];
            string value = "";
            if (i < Params.Length - 1)// key vlaue Out index
            {
                value = Params[i + 1];// Get Value
            }
            if (string.Equals(key, "Version"))
            {
                seting.Version = value;
            }
            else if (string.Equals(key, "VersionCode"))
            {
                seting.VersionCode = value;
            }
            else if (string.Equals(key, "Name"))
            {
                seting.Name = value;
            }
            else if (string.Equals(key, "MulRedering"))
            {
                bool.TryParse(value, out seting.MulRedering);
            }
            else if (string.Equals(key, "Release"))
            {
                bool.TryParse(value, out seting.Release);
            }
            else if (string.Equals(key, "OpenLog"))
            {
                bool.TryParse(value, out seting.OpenLog);
            }
            else if (string.Equals(key, "isShowVersion"))
            {
                bool.TryParse(value, out seting.isShowVersion);
            }
            else if (string.Equals(key, "isConfusion"))
            {
                bool.TryParse(value, out seting.isConfusion);
            }
            else if (string.Equals(key, "teamCode"))
            {
                seting.teamCode = value;
            }
        }
        Debug.Log("seting BuildCode: " + seting.VersionCode);
        Debug.Log("seting Version:" + seting.Version);
        Debug.Log("seting Name:" + seting.Name);
        Debug.Log("seting MulRedering:" + seting.MulRedering);
        Debug.Log("seting Debug:" + seting.Release);
        Debug.Log("seting OpenLog:" + seting.OpenLog);
        Debug.Log("seting UpdateFishAB:" + seting.isConfusion);
        return seting;
    }

    /// 根据读取的数据 在Unity中设置对应的参数 
    private static string iOSSeting(BuildSeting_iOS seting)
    {
        string suffx = "_";

        Debug.unityLogger.logEnabled = Debuger.EnableLog = seting.OpenLog;
        if (seting.OpenLog)
        {
            LoadReprot();
        }
        PlayerSettings.MTRendering = seting.MulRedering == true ? true : false;

        if (seting.Release == true) //测试包 处理测试配置
        {
            CustomTools.SwitchReleaseServer();
            suffx += "Release";
        }
        else
        {
            CustomTools.SwitchTestServer();
            // 其他渠道包待定
            suffx += "Test";
        }
        if (!string.IsNullOrEmpty(seting.Version))
        {
            PlayerSettings.bundleVersion = seting.Version;
            suffx += "_" + seting.Version;
        }
        if (!string.IsNullOrEmpty(seting.VersionCode))
        {
            PlayerSettings.iOS.buildNumber = seting.VersionCode;
            suffx += "_" + seting.VersionCode;
        }
        if (!string.IsNullOrEmpty(seting.Name))
            PlayerSettings.productName = seting.Name; //包名

        if (seting.Release)
        {
            EditorUserBuildSettings.development = false;
            suffx += "_Release";
        }
        else
        {
            EditorUserBuildSettings.development = true;
            EditorUserBuildSettings.connectProfiler = true;
        }
        if (seting.isShowVersion)
        {
            string saveStrs = ConvertToJson(seting.isShowVersion);
            Utils.WriteFileWithEnding(Application.dataPath + "/Resources/" + ResourcesFolder.Txts, ResourcesFile.buildInfo, saveStrs);
        }
        else
        {
            Utils.DeleteFile(Application.dataPath + "/Resources/" + ResourcesFolder.Txts + ResourcesFile.buildInfo + ".txt");
        }
        if (seting.teamCode.Trim() == "teamcpdexxx")//主账号
        {
            Debug.LogError("设置子账号包名:" + "com.baoming.xx");
#if UNITY_IOS
            BL_BuildPostProcess.bundleIdentifier = "com.baoming.xx";
#endif
        }
        else
        {
            Debug.LogError("设置子账号包名:" + "com.xxxx.xx");
#if UNITY_IOS
            BL_BuildPostProcess.bundleIdentifier = "com.xxxx.xx";
#endif

        }
        AssetDatabase.Refresh();
        return suffx;
    }
}

public class BuildSeting_iOS
{
    //版本
    public string Version = "";
    //版本🐎
    public string VersionCode = "";
    //
    public string Name = "";
    // 
    public bool MulRedering = false;
    // 
    public bool Release = false;
    // 
    public bool OpenLog = false;
    //是否把代码编译成C++
    public bool isShowVersion = false;
    //  
    public bool isConfusion = true;
    public string teamCode = "";

}

到这里就已经能正常导出xcode工程了

xcode工程导出路径已经声明好了: Application.dataPath + “/…/BuildTarget/IOS/”;

导出xcode工程后确保手动使用xcode在不修改配置的情况下能正常导出ipa,然后在进行下面的操作。

8.调用xcode归档 ipa

导出ipa为们需要两个文件,一个是export.sh 一个是自己归档时的plist文件
这里都会提供。

首先创建一个export.sh 和plist文件,按照以下目录存放 ,路径是在自己项目的Asset同级下,文件名不要搞错。
在这里插入图片描述

1.export.sh 文件

M_XCODE_PATH="../IOS"
M_XCODE_NAME="Unity-iPhone"
M_ARCHIVE_PATH="../ArchiveTemp/Output/$2/archive"
M_EXPORT_PATH="../ArchiveTemp/Output/$2/app"
M_ExportOptionsPlist="../IOSBuildTools/export.plist"
M_DEVELOPMENT_TEAM=$1

echo "M_ARCHIVE_PATH=${M_ARCHIVE_PATH} M_EXPORT_PATH=${M_EXPORT_PATH} M_ExportOptionsPlist=${M_ExportOptionsPlist} M_DEVELOPMENT_TEAM=${M_DEVELOPMENT_TEAM}"

cd ${M_XCODE_PATH}
echo $PWD
echo $M_TIME
echo $M_ARCHIVE_PATH
echo $M_EXPORT_PATH
#remove plist
echo "Remove file${M_ExportOptionsPlist}"
rm -rf ${M_ExportOptionsPlist}
#new plist

echo ${M_ExportOptionsPlist}

echo "<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>compileBitcode</key>
    <false/>
    <key>destination</key>
    <string>export</string>
    <key>method</key>
    <string>development</string>
    <key>signingStyle</key>
    <string>automatic</string>
    <key>stripSwiftSymbols</key>
    <true/>
    <key>teamID</key>
    <string>"${M_DEVELOPMENT_TEAM}"</string>
    <key>thinning</key>
    <string>&lt;none&gt;</string>
</dict>
</plist>
" > ${M_ExportOptionsPlist}
xcodebuild  -workspace ${M_XCODE_NAME}.xcworkspace -scheme ${M_XCODE_NAME} -configuration Release DEVELOPMENT_TEAM="${M_DEVELOPMENT_TEAM}" CODE_SIGN_IDENTITY="iPhone Developer"  CODE_SIGN_STYLE="Automatic" clean archive -archivePath "${M_ARCHIVE_PATH}" -allowProvisioningUpdates

2.export.plist 文件

<?xml version=1.0 encoding=UTF-8?>
<!DOCTYPE plist PUBLIC -//Apple//DTD PLIST 1.0//EN http://www.apple.com/DTDs/PropertyList-1.0.dtd>
<plist version=1.0>
<dict>
    <key>compileBitcode</key>
    <false/>
    <key>destination</key>
    <string>export</string>
    <key>method</key>
    <string>development</string>
    <key>signingStyle</key>
    <string>automatic</string>
    <key>stripSwiftSymbols</key>
    <true/>
    <key>teamID</key>
    <string>9XXXXXXXC</string>
    <key>thinning</key>
    <string>&lt;none&gt;</string>
</dict>
</plist>

接下来回到Jenkisn配置一下 export.sh脚本的调用

如下:

#!/bin/sh -e
#运行xcode导出ipa
chmod +x export.sh
cd ${WorkPath}/BuildTarget/IOSBuildTools
sh export.sh ${teamCode} Target

上面命令主要意思,首先给 export.sh 予权限 然后cd到该文件所在目录下,执行该sh文件并传入参数

${teamCode} 是自己打包证书的证书码,取自jenkins配置参数,target表示归档出的ipa的文件夹,如果没有则自动创建。

jenkins构建名配置一览:
在这里插入图片描述

到了这一步,如果流程配置以及路径没有问题,那么就能正常的导出一个Ipa包了。

有两个重点需要强调一下:

1.首先要保证通过Jenkins能正常导出Xcode工程。
2.其次保证手动使用导出的Xcode能够在不修改xcode工程下能正常归档出ipa

注意:Xcode相关的配置可以放到Unity导出Xcode时[PostProcessBuild]标记的静态方法下进行配置。如果不知道如何配置,可以百度一下,这里就不细讲了。

下面介绍下如果把ipa上传到fir并进行下载

9.上传ipa到fir分发平台

下面是笔者上传fir的配置
在这里插入图片描述

上传命令:

#!/bin/bash --login 
#上传到fir分发平台
cd ${WorkPath}/BuildTarget/ArchiveTemp/Output/Target/app
mv "xxxxxxxx.ipa" "${AppName}.ipa" #修改ipa名字
echo "开始登陆fir"
fir login ${FirToken} 
echo "登陆完成"
echo "开始上传到fir"
fir publish ${WorkPath}/BuildTarget/ArchiveTemp/Output/Target/app/${AppName}.ipa
echo "上传fir完成"

这里有一点需要注意 ${FirToken} 是取自jenkins配置好的参数,因登陆fir需要token验证,所以笔者就直接把token配置到了参数中,firToken可以到fir官网,个人信息内找到。

温馨提示:可以先手动尝试输入命令进行上传,上传成功后,在布置到jenkins中。

下面是笔者手动上传的演示:

第一个框 是cd到ipa所在的目录下

然后修改ipa的名字

修改完成后,登陆fir

第二个框是把Ipa上传到fir,需要传ipa的绝对路径。

上传过程中如下图。
在这里插入图片描述

上传完成后我们就拿到了一个下载地址,这个地址我们可以直接放到浏览器中打开,下载下来的ipa就可以直接装到手机上。

我们也可以拿该链接做顶顶通知。

注意:该地址为固定地址,不管我们这个包上传多少次,该地址都不会变。

10.Jenkins集成钉钉通知流程

继承钉钉通知之前需要先在jenkins中安装一些插件:

DingTalk

Environment Injector Plugin

Parameterized Trigger plugin
如下:
1.
在这里插入图片描述
2.
在这里插入图片描述
3.
在这里插入图片描述

在安装插件期间,我们可以打开钉钉,创建一个打包通知群 创建教程:
https://blog.csdn.net/qq_41980563/article/details/107045894

我们的钉钉群创建完成后和插件安装完成后,点击 系统管理-系统设置,添加一下钉钉的配置id 不用填,jenkins会自动生成,
我们要填的只有名称和webhook
在这里插入图片描述

钉钉信息配置完成后,我们回到jenkins工程配置里添加一下构建操作和构建后操作:

我们在这里注入一下环境变量,供我们钉钉通知时使用取出APkName
在这里插入图片描述

设置环境变量
在这里插入图片描述

11.创建钉钉通知工程

这里需要创建一个流水线工程
在这里插入图片描述
设置两个参数
在这里插入图片描述
配置工作流水线
在这里插入图片描述

流水线源码:

pipeline {
    agent any
     environment {
           version = '0.1'
     }
    stages {
        stage('link'){
            steps {
                echo "${env.version}"
                echo '测试 LINK 消息...'
                echo "${params.apkName}"
            }
            post {
                success {
                    script {
                     if(params.buildType=='APkName-IOS'){
                       dingtalk (
                        robot: '55fd7ff3-1f55-499d-ba4d-1500d25f702b',
                        type: 'LINK',
                        title: 'APKName',
                        text: [
                            '点击标题下载',
                            "${params.apkName}",
                        ],
                        messageUrl: '自己的fir下载地址',
                        picUrl: '自己的钉钉通知显示的应用图标下载地址'
                        )
                            }
                    else if(params.buildType=='YallaLudoHD-Android'){
                    dingtalk (
                        robot: '55fd7ff3-1f55-499d-ba4d-1500d25f702b',
                        type: 'LINK',
                        title: 'APKname-Android',
                        text: [
                            '点击标题下载',
                            "${params.apkName}",
                        ],
                        messageUrl: '自己的fir下载地址',
                        picUrl: '自己的钉钉通知显示的应用图标下载地址'
                        )
                    }
                    }
                }
            }
        }
    }
}

根据自己的需求进行修改。

12.增加构建后通知

回到自己的Jenkins打包项目内 在增加一个构建后操作,操作的就是刚刚配置好的流水线。

在这里插入图片描述

在这里插入图片描述

然后就可以通过Jenkins打包进行测试了,如果要测试钉钉通知,可以先把前面的步骤给注视掉,只保留钉钉通知,测试即可。
在这里插入图片描述
整个过程是没有讲的特别细,但是都是核心,只要流程上不出问题,就成功了。

温馨提示:Jenkins第一次打包期间最好是能够翻墙,不然有的配置Jenkins下载不到,他自己就会包错。

13.集成完成

觉得有用的话,动动小手点个关注,更多好用工具、干货文章着你!

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

铸梦xy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值