基于Python和Jenkins的Unity自动化打包方案总结(6)

688 阅读5分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第29天,点击查看活动详情

基于Python的构建脚本(续)

创建C#命令行打包所需要的配置文件

基于Python和Jenkins的Unity自动化打包方案总结(2)中,我们给C#端的命令行打包接口设计了使用json配置文件接收打包参数的方案。该方案的好处是不需要繁琐的解析很多Unity命令行参数,且添加打包参数更加方便且不易出错。而该方案需要我们生成一个json配置文件,也就是这儿python脚本接下来要做的事情。先上代码:

def main()
    # 接上面
    ############# create build config json	############
    json_path = f'{output_folder}/buildConfig.json'
    print(f'Creating config json to {json_path}')

    build_config = {}
    if BUILD_TYPE == 'PC':
            build_config['OS'] = 0
    elif BUILD_TYPE == 'Android':
            build_config['OS'] = 3
    build_config['Store'] = 0    
    build_config['VersionBase'] = VERSION_NO_BASE
    build_config['SvnReversion'] = SVN_VERSION_NO
    build_config['CleanAssetsCache'] = True
    build_config['VersionType'] = VERSION_TYPE
    build_config['BuildDateTime'] = BUILD_TIMESTAMP
    build_config['OutputFolder'] = output_folder
    build_config['DevelopmentMode'] = DEBUG_MODE
    build_config['AppName'] = APP_NAME
    build_config['BuildScenes'] = BUILD_SCENES

    with open(json_path, 'w', encoding='utf-8') as file_obj:
            json.dump(build_config, file_obj, indent=4, ensure_ascii=False)
  • 首先,我们确定这个json文件的路径json_path,也是放到我们的打包输出目录中。
  • 然后,构建一个build_config字典,该字典的key就是参数名,value为参数值。该字典可以直接dump到json文件中。
  • 根据我们上面获取到的jenkins打包参数,来填充字典。需要说明的是,jenkins参数和C#端的打包参数不一定是一一对应的,比如这儿的BUILD_TYPEjenkins参数,对应到C#端则是OS参数,这所以这么做,是因为C#端的参数可能更细致,而Jenkins端为了避免选择太多的参数,而可能将一个参数进行"打包"。比如这儿的BUILD_TYPE,当值是'PC'时,只是打包一个普通的PC版本,因此只是设置C#端的OS为对应的枚举值。当如果我们打包一个Steam商店版本,那么BUILD_TYPE可以设置为Steam,这样对应的C#参数不光是OS,还包含Store参数为Steam商店对应的枚举值。我们给出的例子里面并没有实现Steam商店,因此这儿的Store参数就统一设置为默认值了。总之,思路就是根据业务需求,设计Jenkins端的参数,并转换成C#端的参数,保存到字典中。
  • 将build_config字典输出到json文件中。这儿我们使用了json模块的dump方法,当然首先我们要打开文件,获得文件句柄。dump方法的indent参数大于0时,可以获得有缩进格式的json,方便我们查看,ensure_ascii参数设置为False,可以包含中文字符。

执行Unity批处理模式进行打包

终于,万事俱备,可以执行Unity命令行打包了。根据之前的介绍,打包被分为多遍,以应对域切换可能造成的level 0崩溃问题。我们这儿由于没有assets编译,因此只分为两遍。

命令行log输出问题

由于我们是在一个Python脚本中进行处理所有的构建步骤,而每个步骤我们都会输出log,这些log最终在Jenkins的控制台可以看到。但如果我们使用os.system执行控制台命令,就会阻塞log的输出。因此在执行命令行之前,需要flush一下:

def main()
    # 接上面
    ############### start build ##########################
    print('Start Unity building, please wait...')
    sys.stdout.flush()

执行Unity命令行

Unity命令行打包的参数之前已经说明过了,本方案我们只需要传入一个自定义参数,即-configFilePath传入我们刚刚生成的json文件的路径。此外就是Unity需要的参数了,比如-projectPath-logFile。这个logFile参数是Unity自己输出的log文件,这个文件有助于分析打包的过程以及失败原因。且每次执行Unity命令行都要指定一个独立的文件,避免互相覆盖。

def main()
    # 接上面
    proj_path = WORKSPACE
    log_path_1 = f'{log_path}_1.txt'
    exe_result = os.system(f'"{unity}" -batchmode -nographics -executeMethod BuilderCommandline.Build_SwitchEnv -projectPath {proj_path} -logFile {log_path_1} -configFilePath:{json_path}')	
    if exe_result != 0:
        print(f'call Unity Build_SwitchEnv failed. result={exe_result}')	
        exit(1)

    log_path_2 = f'{log_path}_2.txt'
    exe_result = os.system(f'"{unity}" -batchmode -quit -nographics -executeMethod BuilderCommandline.Build_MakePackage -projectPath {proj_path} -logFile {log_path_2} -configFilePath:{json_path}')	
    if exe_result != 0:
        print(f'call Unity Build_MakePackage failed. result={exe_result}')	
        exit(1)

    print("Build Successful!")

这儿我们对于os.system的返回结果进行分析,如果不为0,则表示Unity打包失败,此时我们需要使用exit(1)退出python构建脚本,这将触发Jenkins构建job的失败,从而发送失败邮件。如果一切正常,我们就可以继续最后的步骤了。

拷贝版本到共享目录

打包成功之后,我们往往需要将版本包拷贝到一个共享目录,方便大家去下载测试。这样可以避免Jenkins服务器的访问需求,毕竟这个服务器是比较重要的,应该只有管理员可以访问。这儿直接给出main函数最后的一部分代码:

def main()
    # 接上面
    ############ copy package to shared folder #############
    if PACKAGE_SHARED_FOLDER=='':
            print("Warning: Shared folder is empty, skip copy to shared.")
            exit(1)

    package_name = f'{APP_NAME}_{VERSION_TYPE}_{BUILD_TIMESTAMP}_{SVN_VERSION_NO}'	
    #TODO: add Store Name here if need    	
    if BUILD_TYPE == 'PC':
            package_name += '.zip'
    elif BUILD_TYPE == 'Android':
            package_name += '.apk'
    package_path = f'{output_folder}/{package_name}'
    package_path = package_path.replace('/','\\')  #xcopy need \

    print(f'Copy package {package_path} to shared folder: {PACKAGE_SHARED_FOLDER}')
    sys.stdout.flush();

    exe_result = os.system(f'net use {PACKAGE_SHARED_FOLDER}  "xxx" /user:"xxx"')
    if exe_result != 0:
            print(f'net use shared folder failed. result={exe_result}')	
            exit(1)

    exe_result = os.system(f'xcopy /s /y {package_path} {PACKAGE_SHARED_FOLDER}')
    if exe_result != 0:
            print(f'xcopy to shared folder failed. result={exe_result}')	
            exit(1)

    print('Copy package to shared folder successful! All done!')

共享文件夹的位置也是在Jenkins里面配置的,我们需要使用net use命令去获取该文件夹的权限,这儿需要使用用户名和密码。然后使用xcopy命令进行拷贝。如果成功,python脚本就正常退出,Jenkins那边就会判断构建成功,发送成功邮件。

最终总结

本方案到这儿就总结完毕了。但实际上,还是有可以改进和扩展的地方。比如包名称,现在是在C#和python里面分别拼出来的,这其实应该在Jenkins参数里面给出拼接规则,然后从配置文件传入到C#里面。这样更灵活也避免出错。另外打包之后,现在只是简单的copy到局域网一个共享目录中。对于apk或者ipa来说,最好再实现一个OTA服务器,直接将版本包自动部署到OTA服务器上,在邮件中给出OTA下载链接,这样就可以直接下载安装了。