franciscochonge 发表于 2022-12-22 18:46

TeamCity配合Unity工作流01

一、前言

关于TeamCity的安装教程以及Unity插件安装无需赘述,参考:
但是需要补充一点:生产环境下数据库不要用内置的数据库。在TeamCity的安装说明中也有明确提示 HSQLDB用于测试及评估:
During the server setup you can select either an internal database or an existing external database. By default, TeamCity uses an HSQLDB database that does not require configuring. This database suites the purposes of testing and evaluating the system.
For production purposes, using a standalone external database is recommended.为什么要额外提一嘴这个呢,因为如果你用这个作为生产环境数据库TeamCity会崩. 构建物及日志是存在本地的,这个倒不会丢失,但是无法将他们恢复到崩溃前的样子 比如构建日志及对应构建物的依赖关系。
为什么我会知道这个呢,那当然是因为我的HSQLDB崩过,TeamCity运行了一段时间之后,忽然有一天巨卡无比,重启了电脑之后登录TeamCity卡在了connect to database这一步,并且不动了 最后按照官方文档进行了部分数据恢复。
言归正传,目前项目热更新方案是使用的HybridCLR;资源管理使用的是YooAsset. 整个构建过程是囊括了以上两个方案的流程。
二、版本控制

2.1 VCS checkout

    目前项目分支结构为 v1/release、v1/hotfix。出包使用release分支,同时将release分支内容同步到hotfix分支,后续热更新往hotfix分支提交。需要出新包的时候就是新建v2/release分支。
    起初是准备在TeamCity上面创建两个Build,分别监听hotfix和release分支 为了省一点硬盘空间 将两个Build的目录指向同一个,但两个不同Build启动的时候会将当前目录清空,重新拉取仓库内容,unity切换平台导入资源每次都会耗时2个小时,所以后来hotfix、release共用一个Build,构建步骤用条件进行区分




头三个checkout模式在两个不同的Build任务执行时都会将当前的目录重新checkout,最后作罢选择了两个流程共用一个Build。
2.2 Artifact

jenkins中每个step可以做一些成功或失败后的操作,TeamCity这边暂时没发现。所以release分支及hotfix分支的构建产物我直接在 Artifact paths中一起配置了,构建成功之后没收集到对应规则的产物会有警告,但不会终止当前构建。
例:
+:release/%build.number%.apk
+:Bundles/Android/%build.number% => %build.number%三、构建流程

3.1 出包

流程整理如下图



出包流程

出包期间会执行到HybridCLR与YooAsset的构建过程

[*]流程大概

[*]HybridCLR:

[*]出一次包:获取裁剪后的dll
[*]生成桥接函数(如果有新增)
[*]编译hotfix部分的dll
[*]重新出包

[*]YooAsset的大概流程为:

[*]调用AssetBundleBuilder的Run方法就好了(需要提前给到一些参数的,例如:构建模式[增量、重构];资源版本号等)


[*]配置步骤

[*]HybridCLR

[*]编译DLL:CustomBuild.BuildDLL
[*]编译HotFix工程:CustomBuild.BuildHotfixProject
[*]导出工程: CustomBuild.Build

[*]YooAsset

[*]CustomBuild.BuildInternal

[*]生成APK

[*]Flaovr参考之前的jenkins出包

[*]Git提交代码

[*]TeamCity不支持push操作,所以需要自己写命令行提交
[*]在构建过程中手动push会触发配置的分支Triggers,所以先禁用Trigger,但build就变成了半自动
[*]挖个坑:尝试使用TeamCity的Dependencies,让增量更新依赖上次的构建,这样就不用从git上拉取之前的版本内容了


Gradle任务中的自定义参数在Additional Gradle command line parameters中填写,需要注意的是参数前面需要追加一个“-P”不留空格。这样就可以将值传到AndroidStudio工程gradle.properties 中的配置字段了


伪代码部分
public class CustomBuild
{
    ///<summary>
    ///BuildDll伪代码
    ///</summary>
    public static void BuildDLL()
    {
      string[] args = System.Environment.GetCommandLineArgs();//获取命令行参数
         var outDir = System.Environment.CurrentDirectory + "/../output/" + buildTarget.ToString();
      //...do something
      string[] scenes = new[] { "Assets/Main.unity" };
      PlayerSettings.SetScriptingBackend(buildTargetGroup, ScriptingImplementation.IL2CPP);
      BuildPipeline.BuildPlayer(scenes, outDir, buildTarget, BuildOptions.CompressWithLz4);
    }

    ///<summary>
    ///HotFix工程编译伪代码
    ///</summary>
    public static void BuildHotfixProject(BuildTarget target)
    {
      //HybridCLR编译DLL
      HybridCLR.Editor.CompileDllCommand.CompileDll(target);

      //复制DLL到资源目录
      string dllPath = $"{HybridCLR.Editor.SettingsUtil.GetHotFixDllsOutputDirByTarget(target)}/{dll}";
      var myPath = "xxx/xxx/xxx.bytes";
      File.Copy(dllPath, myPath, true);
      //dll加密
      //EncryptStaticData();
    }

    ///<summary>
    ///资源构建伪代码
    ///</summary>
    public static void BuildInternal()
    {   
      string[] args = System.Environment.GetCommandLineArgs();
      //获取目标平台参数:args=="-buildTarget"
      //获取资源版本号参数: args == "-buildVersion"
      //获取构建模式:args=="-buildType"(EBuildMode.ForceRebuild、EBuildMode.IncrementalBuild)

      // YooAsset构建参数
      string defaultOutputRoot = AssetBundleBuilderHelper.GetDefaultOutputRoot();
      BuildParameters buildParameters = new BuildParameters();
      buildParameters.VerifyBuildingResult = true;
      buildParameters.OutputRoot = defaultOutputRoot;
      buildParameters.BuildTarget = buildTarget;
      buildParameters.BuildVersion = buildVersion;
      buildParameters.CompressOption = ECompressOption.LZ4;
      //注意:
      //不追加后缀默认导出的的Android项目会因为unityStreamingAssets配置项中内容过长导致build失败
      //1.追加后缀
      //2.unityStreamingAssets内删除所有"YooAsset/哈希"内容
      buildParameters.AppendFileExtension = true;
      buildParameters.BuildMode = buildType;
      
      // 执行构建
      AssetBundleBuilder builder = new AssetBundleBuilder();
      builder.Run(buildParameters);   
    }

TeamCity中有Unity插件 所以只需要在Execute method处填上对应的方法即可,其他追加的参数填在Command line arguments处。


在jenkins中使用命令行操作
"%unity%" -quit -batchmode -logFile log/${UNITY_LOG_BUILD_DLL} -projectPath ../unity -executeMethod CustomBuild.BuildDLL ${BUILD_TARGET} 3.2 资源更新



资源更新部分其实就是把打包过程中YooAsset部分的构建模式换为增量,以及给一个版本号即可,YooAsset的增量更新每次都是把所有文件打出来了,为了节省上传时间及本地磁盘占用 我在YooAsset的任务中追加了一个剔除重复资源的BuildTask,每次打包的资源与首包进行对比。
using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;

namespace YooAsset.Editor
{
   
    public class TaskDeleteDuplicate : IBuildTask
    {
      void IBuildTask.Run(BuildContext context)
      {
            var buildParameters = context.GetContextObject<AssetBundleBuilder.BuildParametersContext>();
            var buildMode = buildParameters.Parameters.BuildMode;
            
            if (buildMode == EBuildMode.IncrementalBuild && buildParameters.Parameters.DeleteDuplicate)
            {
                ComparePatch(buildParameters);
            }
      }
      private void ComparePatch(AssetBundleBuilder.BuildParametersContext buildParameters)
      {
            string packageDirectory = buildParameters.GetPackageDirectory();
            
            PatchManifest patchManifest1 = AssetBundleBuilderHelper.LoadPatchManifestFile(buildParameters.PipelineOutputDirectory, 1);
            PatchManifest patchManifest2 = AssetBundleBuilderHelper.LoadPatchManifestFile(buildParameters.PipelineOutputDirectory, buildParameters.Parameters.BuildVersion);
            
            //删除重复资源
            foreach (var patchBundle2 in patchManifest2.BundleList)
            {
                    if (patchManifest1.Bundles.TryGetValue(patchBundle2.BundleName, out PatchBundle patchBundle1))
                    {
                            if (patchBundle2.Hash == patchBundle1.Hash)
                            {
                        string delPath = $"{packageDirectory}/{patchBundle2.Hash}";
                        File.Delete(delPath);
                  }
                  else
                  {
                        Debug.Log($"变动文件:{patchBundle2.BundleName}");
                  }
                    }
                else
                {
                  Debug.Log($"新增文件:{patchBundle2.BundleName}");
                }
            }
            Debug.Log("重复资源删除完成!");
      }
    }
}
先结束
页: [1]
查看完整版本: TeamCity配合Unity工作流01