介绍

本文介绍如何使用 Python 整合打包流程。

系列

为什么用 Python?

如果只是单纯的打包,看起来 Unity 自己就可以做好。但现实往往是需求在一点一点增多,例如:

  1. 刚开始只需要打包 apk
  2. 后来需要支持引入第三方 SDK,刚开始只需要放到 Plugins/Android 中就可以了
  3. 随着需要支持的第三方 SDK 越来越多,第三方 SDK 要求修改的东西也越来越多,Unity 打包不能满足需求
  4. Unity 内部的 Gradle 版本是固定的,如果想要使用其他版本的 Gradle 就需要 Unity 导出 Gradle 工程
  5. 打包完成后还有部署,需要将生成的 apk 传到指定的 FTP/CDN 等服务器上
  6. 打包出来的 AssetBundle 需要部署到 CDN 上,可能会使用云厂商提供的 COS OSS 之类的命令行工具
  7. 需要自定义构建成功的通知消息,增加版本控制日志并发送到 企业微信/钉钉/飞书/Slack 等等
  8. 。。。

因为这是一整套持续集成的工作,因此才需要使用 Python 来支持。

优点

  • 启动速度快(如果使用 Unity 运行 C# 脚本,Unity 本身启动要数十秒)
  • 跨平台,同时支持 Windows、macOS、Linux
  • 简单易上手,易于编写与修改,入门难度很低
  • 社区支持强大,有大量的各种功能的第三方库
  • 文档完善,支持中文1,现在翻译进度大概 70%+2

环境

  • Python 3.10
  • Windows 10 21H2

准备

版本选择

确定要支持的操作系统版本,例如支持 Windows 7 的 Python 版本是 3.8.x。确定好版本后不要轻易改动,否则所有脚本与功能都需要重新测试。

推荐下载时使用离线包,而不是包管理器 Homebrew、Scoop 之类的进行安装,因为包管理器只有最新版本,软件版本升级后无法使用包管理器安装旧版本,导致无法维持环境一致性。

IDE 支持

工欲善其事,必先利其器

如果需要写 Python,推荐不要折腾各种编辑器了,什么 Visual Studio Code + Python 扩展之类的都是邪路,不如选择一个完全整合好的 IDE:PyCharm。PyCharm Community 版本完全可以满足需求,而且还是免费的。

PyCharm 现在支持中文,有完整的中文语言包,使用上非常方便。支持跨平台,每年都会发布三个版本,增加大量功能,适配各种中间件等等。

库整合方式

这里直接说结论,推荐使用修改 sys.path 方式修改搜索路径,以支持第三方库。

1
2
import os, sys
sys.path.append(os.path.join(os.path.dirname(__file__), "lib"))

由于涉及到的内容较多,推荐前往另一篇文章了解详情:

注意:很多第三方库使用 Native 库实现某些功能,Python 相关的样板 .gitignore 往往会忽略库文件,因此需要手动添加 so dll 等文件到 Git 版本控制中。

流程

  1. 准备阶段
    1. 准备好打包需要的环境,例如终止上次未结束的 Unity 进程
    2. 更新仓库,清理无用文件,恢复到初始状态
  2. 导出阶段
    1. Unity 导出工程
  3. 打包阶段
    1. Gradle 打包 apk / Xcode 打包 ipa
  4. 部署阶段
    1. 部署 apk/ipa 到 FTP/CDN
    2. 部署 AssetBundle 等更新用的资源到 CDN
  5. 通知阶段
    1. 编写自定义的构建成功通知信息

可以根据自己的实际情况划分阶段,这里依然强烈推荐将导出工程与打包工程分开,因为这两步基本上是最耗时的且输出最多的,分开有助于查找问题。

编写

逻辑

基本上编写逻辑不复杂,只需要简单直白地写。大部分时间是使用 Google 搜索想要做的事情,例如:Python run program。这里可以参考一些现成的代码:

环境变量

灵活使用环境变量作为传递数据的方法,始终记住环境变量只能由父进程传给子进程。维基百科英文版对这个概念讲得比较多:

命令行参数

使用 Python 调用外部程序,并且通过不同的参数控制外部程序的行为。这里特别需要注意的是:外部程序提供的命令行参数有长版本和短版本,推荐在编写 Python 代码时,尽可能使用长版本参数,这样便于他人阅读代码了解程序做了什么,而不是强迫他人翻阅外部程序的帮助文档。

工作目录

在调用外部程序时,需要显式地指定工作目录,而不是通过修改当前程序的工作目录,然后让子进程外部程序继承修改后的工作目录。因为程序都会假定当前的工作目录是程序运行后不会发生变化的,如果中途改变了会导致后续的代码出错。

Git 就有 [-C <path>] 参数指定工作目录,因此在调用时需要这样编写:

1
command = f'\"{git_path}\" -C \"{project_path}\" log'

构建成功通知

这里通过一个构建结果通知的实例介绍 Python 编写时大概用到哪些东西。从下面的示例中可以看到,Python 擅长的是调用其他程序获得输出来达到想要的效果,因此建议灵活使用 Python 调用第三方程序。

消息截断

使用 data = data[:75] 这种形式直接截断,Python 3 里面使用的是 Unicode,所以这里的截断的是字符,而不是字节。

获取当前提交 SHA-1

git rev-parse --verify HEAD

获取提交历史

git log HEAD..remote/branch

1
2
3
command = f'\"{git_path}\" -C \"{project_path}\" log --no-merges --format="> %s" ' \
              f'{last_git_commit}..origin/{git_branch} '
git_logs = subprocess.check_output(command, encoding="utf-8", universal_newlines=True)

获取版本号

1
2
3
4
5
6
7
8
9
def get_version(project_path):
    project_settings_path = os.path.join(project_path, 'ProjectSettings/ProjectSettings.asset')
    with open(project_settings_path, 'r', newline='', encoding='utf-8') as openfile:
        content = openfile.read()
    # noinspection RegExpRepeatedSpace
    match = re.search(r'  bundleVersion: (.*?)\r*\n', content)
    if match:
        return match.group(1)
    return ''

获取可读的文件大小

获取人类易读的文件大小实际使用的是一个现成的实现,回答中提到了大部分答案在接近下一档大小时有问题,只有这个实现是无 Bug 版本。

cURL 转换

cURL 转换为 requests 使用网页工具直接转换,可以将机器人消息 cURL 请求转换为 Python requests 代码。

书籍

这里引用之前写的另一篇文章 Unity 持续集成 - 狂飙 中的内容:

《Python编程快速上手 让繁琐工作自动化》这本书非常实用,可以说把实际中执行的操作都写了,因此在编写构建脚本时可以当作工具书参考,要比去网上查找快。

另外,作者还将此书英文版开源放在网上。

其实这本书最大的意义在于将所有自动化相关的内容整合到了一起,推荐做持续集成的人可以先通读一遍书籍,然后再着手编写代码,这样会事半功倍。


  1. Python官方文档中文翻译终于达到 62%!|python|程序员|docs_网易订阅 ↩︎

  2. python/python-docs-zh-cn: zh_CN translation of the Python documentation ↩︎