我想谈谈使用 fastlane 的移动应用程序的持续集成和交付。 我们如何在所有移动应用程序上实施 CI/CD、我们如何实现这一目标以及最终发生了什么。
网络上已经有足够的关于该工具的资料,而我们一开始就缺乏这些资料,所以我故意不详细描述该工具,而仅参考我们当时拥有的内容:
文章由两部分组成:
- 公司移动CI/CD出现的背景
- N应用CI/CD落地技术方案
第一部分更多的是对旧时光的怀念,第二部分是你可以运用到自己身上的经历。
历史上就是这样发生的
2015年
我们刚刚开始开发移动应用程序,然后我们对持续集成、DevOps 和其他时髦的东西一无所知。 每个应用程序更新都是由开发人员亲自在他的机器上推出的。 对于 Android 来说,这非常简单 - 组装、签名 .apk
并将其上传到 Google 开发者控制台,然后对于 iOS,通过 Xcode 的分发工具给我们留下了美好的夜晚 - 尝试下载存档经常以错误结束,我们不得不重试。 事实证明,最高级的开发人员并不是每月编写几次代码,而是发布应用程序。
2016年
我们长大了,我们已经考虑过如何将开发人员从一整天的发布中解放出来,第二个应用程序也出现了,这只会让我们更加走向自动化。 同年,我们第一次安装了 Jenkins,并编写了一堆可怕的脚本,与 fastlane 在其文档中显示的脚本非常相似。
$ xcodebuild clean archive -archivePath build/MyApp
-scheme MyApp
$ xcodebuild -exportArchive
-exportFormat ipa
-archivePath "build/MyApp.xcarchive"
-exportPath "build/MyApp.ipa"
-exportProvisioningProfile "ProvisioningProfileName"
$ cd /Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/Frameworks/ITunesSoftwareService.framework/Versions/A/Support/
$ ./altool —upload-app
-f {abs path to your project}/build/{release scheme}.ipa
-u "[email protected]"
-p "PASS_APPLE_ID"
不幸的是,到目前为止,只有我们的开发人员知道这些脚本是如何工作的,以及为什么需要无穷无尽的密钥堆栈,当再次出现问题时,他们得到了分析日志的“美好夜晚”。
2017年
今年我们了解到有“快车道”这样的东西。 那时的信息并不像现在那么多——如何启动、如何使用它。 而且当时这个工具本身还很粗糙:不断的错误只会让我们失望,很难相信他们所承诺的神奇自动化。
然而,fastlane 核心中包含的主要实用程序是 gym
и pilot
,我们成功启动了它。
我们的脚本有了一些改进。
$ fastlane gym —-workspace "Example.xcworkspace"
--scheme "AppName"
—-buildlog_path "/tmp"
-—clean
它们已经得到了改进,只是因为并非所有必要的参数 xcodebuild
,您需要指出 - gym
将独立了解位置和内容。 为了进行更多微调,您可以指定与中相同的键 xcodebuild
,只是按键的命名更加清晰。
这次,多亏了gym和内置的xcpretty格式化程序,构建日志变得更加清晰。 这开始节省修复损坏的程序集的时间,有时发布团队可以自己解决这个问题。
不幸的是,装配速度测量 xcodebuild
и gym
我们没有这样做,但我们会相信文档 - 速度提升高达 30%。
适用于所有应用程序的单一流程
2018年至今
到 2018 年,构建和推出应用程序的过程完全转移到 Jenkins,开发人员停止从他们的机器上发布,只有发布团队有权发布。
我们已经希望改进测试和静态分析的启动,并且我们的脚本不断增长。 与我们的应用程序一起成长和变化。 当时大约有 10 个应用程序,考虑到我们有两个平台,大约有 20 个“活”脚本。
每次我们想要向脚本添加新步骤时,我们都必须将这些片段复制粘贴到所有 shell 脚本中。 也许我们可以更仔细地工作,但此类更改通常会以拼写错误而告终,这让发布团队花费了很多时间来修复脚本并找出哪个聪明人添加了此命令以及它实际上做了什么。 一般来说,不能说一个平台的汇编脚本至少有些相似。 尽管他们确实做了同样的事情。
为了启动新应用程序的流程,有必要花一天的时间来选择这些脚本的“新”版本,对其进行调试并说“是的,它有效”。
2018年夏天,我们再次将目光投向仍在发展的快车道。
任务#1:总结所有脚本步骤并在 Fastfile 中重写它们
当我们开始时,我们的脚本看起来就像一块脚布,由 Jenkins 的一个 shell 脚本中的所有步骤和拐杖组成。 我们还没有转向管道和阶段划分。
我们查看了现有的内容并确定了符合 CI/CD 描述的 4 个步骤:
- 构建 - 安装依赖项,组装存档,
- 测试 - 运行开发人员单元测试,计算覆盖率,
- sonar - 启动所有 linter 并将报告发送到 SonarQube,
- 部署 — 将工件发送到 alpha (TestFlight)。
如果你不深入细节,省略操作中使用的键,你将得到这个 Fastfile:
default_platform(:ios)
platform :ios do
before_all do
unlock
end
desc "Build stage"
lane :build do
match
prepare_build
gym
end
desc "Prepare build stage: carthage and cocoapods"
lane :prepare_build do
pathCartfile = ""
Dir.chdir("..") do
pathCartfile = File.join(Dir.pwd, "/Cartfile")
end
if File.exist?(pathCartfile)
carthage
end
pathPodfile = ""
Dir.chdir("..") do
pathPodfile = File.join(Dir.pwd, "/Podfile")
end
if File.exist?(pathPodfile)
cocoapods
end
end
desc "Test stage"
lane :test do
scan
xcov
end
desc "Sonar stage (after run test!)"
lane :run_sonar do
slather
lizard
swiftlint
sonar
end
desc "Deploy to testflight stage"
lane :deploy do
pilot
end
desc "Unlock keychain"
private_lane :unlock do
pass = ENV['KEYCHAIN_PASSWORD']
unlock_keychain(
password: pass
)
end
end
事实上,考虑到我们仍然需要的一些拐杖以及我们替换的参数数量,我们的第一个 Fastfile 被证明是可怕的:
lane :build do
carthage(
command: "update",
use_binaries: false,
platform: "ios",
cache_builds: true)
cocoapods(
clean: true,
podfile: "./Podfile",
use_bundle_exec: false)
gym(
workspace: "MyApp.xcworkspace",
configuration: "Release",
scheme: "MyApp",
clean: true,
output_directory: "/build",
output_name: "my-app.ipa")
end
lane :deploy do
pilot(
username: "[email protected]",
app_identifier: "com.example.app",
dev_portal_team_id: "TEAM_ID_NUMBER_DEV",
team_id: "ITS_TEAM_ID")
end
在上面的示例中,我们只需要指定部分参数:这些是构建参数 - 架构、配置、Provision Profile 名称,以及分发参数 - 开发者帐户的 Apple ID、密码、应用程序 ID 等在。 作为第一个近似,我们将所有这些密钥放入特殊文件中 - Gymfile
, Matchfile
и Appfile
.
现在,在 Jenkins 中,您可以调用简短的命令,这些命令不会模糊视图并且易于肉眼读取:
# fastlane ios <lane_name>
$ fastlane ios build
$ fastlane ios test
$ fastlane ios run_sonar
$ fastlane ios deploy
万岁,我们很棒
你得到了什么? 每一步都有清晰的命令。 清理脚本,整齐地排列在 fastlane 文件中。 我们很高兴地跑向开发人员,要求他们将所需的一切添加到他们的存储库中。
但我们及时意识到我们会遇到同样的困难 - 我们仍然会有 20 个汇编脚本,它们会以某种方式开始过自己的生活,编辑它们会更加困难,因为脚本会移动到存储库,我们无法访问那里。 一般来说,这种方式不可能解决我们的痛苦。
任务#2:为 N 个应用程序获取单个 Fastfile
现在看来,解决问题并没有那么困难——设置好变量,然后开始吧。 是的,事实上,问题就是这样解决的。 但在我们把事情搞砸的那一刻,我们既没有 fastlane 本身的专业知识,也没有编写 fastlane 的 Ruby 的专业知识,也没有网络上有用的示例——当时每个写过 fastlane 的人都仅限于一个应用程序的示例。一名开发商。
Fastlane 可以处理环境变量,我们已经通过设置 Keychain 密码进行了尝试:
ENV['KEYCHAIN_PASSWORD']
查看我们的脚本后,我们确定了公共部分:
#for build, test and deploy
APPLICATION_SCHEME_NAME=appScheme
APPLICATION_PROJECT_NAME=app.xcodeproj
APPLICATION_WORKSPACE_NAME=app.xcworkspace
APPLICATION_NAME=appName
OUTPUT_IPA_NAME=appName.ipa
#app info
APP_BUNDLE_IDENTIFIER=com.example.appName
[email protected]
TEAM_ID=ABCD1234
FASTLANE_ITC_TEAM_ID=123456789
现在,为了开始在 fastlane 文件中使用这些密钥,我们必须弄清楚如何将它们传递到那里。 Fastlane 对此有一个解决方案: .env
, .env.default
, .env.development
.
然后我们决定以稍微不同的方式使用这个库。 让我们在开发人员的存储库中放置的不是 fastlane 脚本及其元信息,而是该应用程序在文件中的唯一键 .env.appName
.
他们自己 Fastfile
, Appfile
, Matchfile
и Gymfile
,我们将其隐藏在一个单独的存储库中。 那里隐藏着一个带有来自其他服务的密码密钥的附加文件 - .env
.
你可以看一个例子
在 CI 上,调用没有太大变化;添加了针对特定应用程序的配置密钥:
# fastlane ios <lane_name> --env appName
$ fastlane ios build --env appName
$ fastlane ios test --env appName
$ fastlane ios run_sonar --env appName
$ fastlane ios deploy --env appName
在运行命令之前,我们使用脚本加载存储库。 看起来不太好看:
git clone [email protected]/FastlaneCICD.git fastlane_temp
cp ./fastlane_temp/fastlane/* ./fastlane/
cp ./fastlane_temp/fastlane/.env fastlane/.env
尽管 Fastlane 有一个通过下载 Fastfile 的解决方案,但暂时保留此解决方案 import_from_git
,但仅适用于 Fastfile,不适用于其他文件。 如果你想要“真漂亮”,你可以自己写 action
.
Android 应用程序和 ReactNative 也做了类似的设置,文件位于同一个存储库中,但位于不同的分支中 iOS
, android
и react_native
.
当发布团队想要添加一些新步骤时,脚本中的更改会通过 git 中的 MR 进行记录,不再需要寻找损坏脚本的罪魁祸首,而且一般来说,现在您必须尝试破坏它。
现在可以肯定的是
以前,我们花时间维护所有脚本、更新它们并修复更新的所有后果。 当发布中的错误和停机的原因是简单的拼写错误而很难在混乱的 shell 脚本中跟踪时,这是非常令人失望的。 现在此类错误已减少到最低限度。 更改会立即推广到所有应用程序。 将新应用程序放入流程中需要 15 分钟 - 在 CI 上设置模板管道并将密钥添加到开发人员的存储库中。
似乎 Android 版 Fastfile 和应用程序签名的要点仍然无法解释;如果这篇文章有趣,我会继续写一篇。 我很高兴在评论或 Telegram 上看到您的问题或建议“您将如何解决这个问题”
来源: habr.com