全自动 Android 构建管道

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡/ 赠书活动

目前,正在 星球 内带小伙伴们做第一个项目:全栈前后端分离博客项目,采用技术栈 Spring Boot + Mybatis Plus + Vue 3.x + Vite 4手把手,前端 + 后端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,陪伴式直到项目上线,目前已更新了 204 小节,累计 32w+ 字,讲解图:1416 张,还在持续爆肝中,后续还会上新更多项目,目标是将 Java 领域典型的项目都整上,如秒杀系统、在线商城、IM 即时通讯、权限管理等等,已有 870+ 小伙伴加入,欢迎点击围观

在我目前的工作过程中,我不得不将构建 Android 应用程序的工作自动化。这篇文章旨在描述我遇到的痛点,以免读者在打算这样做时浪费时间。

环境如下:

  • Puppet 使基础设施自动化
  • 用于 CI 服务器的 Jenkins
  • 安卓项目
  • 用于构建它的 Gradle 构建文件
  • Robolectric 作为主要测试框架

木偶和詹金斯

我的出发点确实很合理。同事们已经自动安装了 Jenkins 服务器、所需的包——包括 Java,以及为创建工作提供了可重用的 Puppet 类。 Jenkins 作业依赖于单个 config.xml 文件,该文件是不同部分的组合。每个部分都由专用模板处理。在这一点上,我认为创建一个简单的 Gradle 作业就像在公园里散步一样,最多需要几天时间,我很快就会被分配到另一项任务。

第一步非常简单:只需更新现有的 Puppet 清单,将 Gradle 插件添加到 Jenkins。

Gradle 包装器

这个博客的普通读者知道我对 Gradle 的看法。然而,我承认无论安装的工具版本如何,保证构建都能正常工作是 Maven 所缺乏的——而且应该有。为实现这一点,Gradle 通过 JAR、shell 脚本和属性文件提供所谓的包装器机制,后者包含 Gradle ZIP 分发的 URL。这三个都需要存储在 SCM 中。

这是我麻烦的开始。必须在企业环境中下载意味着要通过代理并对其进行身份验证。最简单的选择是在作业配置中设置所有内容……包括代理凭据。然而,从安全的角度来看,这样做并不是很可靠,因为任何有权访问 Jenkins 界面或文件系统的人都可以读取这些凭据。需要另一种选择。

客户已经有一个工作的 Nexus 存储库和配置的代理。在那里上传所需的 Gradle 发行版并更新 gradle.properties 以指向它非常容易。

安卓开发工具包

Android SDK 只是一个 ZIP。我重复使用相同的策略:下载然后上传到 Nexus。此时,现有的 Puppet 脚本负责下载、解压并设置正确的权限。

然而,这一步才是真正问题的开始。 Android 开发人员知道 Android SDK 只是一个管理器:必须手动检查所需的平台和工具才能将它们下载到本地文件系统。对于 Android 开发人员来说,在他的机器上执行一个简单的步骤对于自动化来说确实是一场噩梦,尽管有一个命令行相当于通过 SDK 安装/更新包(使用 --no-ui 参数)。有关完整说明,请查看 此链接

谷歌工程师未能提供 2 个重要参数:

  • 代理凭据 – 登录名/密码
  • 接受许可协议

Web 上有很多无效的答案,最诱人的是配置文件。我发现它们都不起作用。但是,我使用 expect 命令找到了 一个创造性的解决方案 。 Expect 是一个 漂亮的命令 ,它读取标准输出并相应地填充标准输入。 expect 的好处是它接受正则表达式。因此,当它要求代理登录时,您输入登录名,当它要求输入密码时,您也这样做,当它要求接受许可时,您输入“y”。这非常简单——尽管我进行了很多试验和错误才能达到预期的结果。

我最初的设计是将所有必要的 Android 包与 Puppet 一起安装,作为服务器配置的一部分。对于标准操作,例如文件创建或系统包安装,Puppet 能够确定是否需要提供, 例如 ,如果文件存在,则无需创建它,如果包已安装,则同样如此。最后,Puppet 在日志中报告它执行的每个操作。起初,我试图通过告诉 Puppet 在配置期间创建了哪些包来实现这种缓存,因为 Android SDK 为每个包创建一个文件夹。第一个问题是 Puppet 只接受单个文件夹进行验证。然后,对于某些包,没有版本信息(例如,Google Play 服务就是这种情况)。

因此,一位同事想到了将此更新从 Puppet 供应转移到每项工作的预先步骤。这解决了非幂等问题。此外,它使每个作业都可以配置运行更新。

机器人电子

至此,我以为大功告成了。不幸的是,由于图书馆 – Robolectric,情况并非如此。

当时我还不知道 Robolectric ,我只知道它是一个 Android 测试库,提供了一种无需连接物理设备即可运行测试的方法。在尝试在 Jenkins 上运行构建时,我偶然发现了一个“有趣”的问题:尽管 Roboletric 提供了一个包含依赖项的 POM,但 MavenDependencyResolver 类硬编码了从何处下载的存储库。

唯一 提供的解决方法 是扩展上述类以破解您自己的实现。我的使用了上面提到的企业 Nexus 存储库。

上传和发布任务

实施的结束相对容易。只是缺少将工件上传到 Nexus 存储库和 SCM 中的发布标签。

为了实现前者,我刚刚添加了一个自定义 Gradle 任务以从 settings.xml (由 Puppet 提供)中获取 Nexus 设置。然后我设法让 upload 任务依赖于这个。最后,对于每一种 assemble 任务执行,我都将输出文件添加到待上传的工件集中。这样,无论构建文件中配置了什么风格,以下命令将只上传风格 XXX 和 YYY:


 ./gradlew assembleXXX assembleYYY upload

对于发布,它更简单:唯一需要的是设置 这个 Gradle 插件,它添加了一个 release 任务,类似于 Maven 的部署。

结论

作为后端开发人员,我已经习惯了持续集成设置,并且几乎可以肯定我可以在几天内处理好 Android CI 流程。我对 Android 生态系统在 CI 方面缺乏成熟度感到非常惊讶。每一步都很痛苦,记录很糟糕(如果有的话),解决方案看起来更像是黑客而不是其他任何东西。如果你想走这条路,你已经被警告过……我祝你好运。