背景
由于项目之前一直在赶进度,没有统一集成的App打包平台,测试阶段的打包方式一直采用的是开发在本地手动打包提交给测试的模式,这种方式产生了以下几个问题:
- 打包效率低下
手动打包受开发电脑工作环境影响,如果电脑环境出了问题一时半会没法打包或打包卡住,会导致测试工作无法正常开展 - 测试节奏严重依赖开发打包节奏
比如每天打一个包,测试每天只能对当天打的包进行验证,开发不出包就无法进行新一轮的测试和已修复bug的回归验证 - 无法保证代码的时效性
由于第3点产生,测试针对旧代码提出的bug,可能开发今天已经进行过修复或优化,那么测试就浪费了宝贵的测试时间去测试本身就错误的代码 - 不可避免的人为错误
本地代码与远端仓库是否保持一致,是否会出现未提交或没拉取的代码,是否会出现选择了错误的分支打包,依赖打包开发人员的自觉性
为了解决上述几个问题,有必要对项目搭建一个集成持续打包平台,方便后续开发和测试同事工作的开展。
使用集成持续打包平台的好处:
- 服务器自动打包,想什么时候要就什么时候要,不局限于开发是否有空
- 打包发版转交给测试进行实施,安全闭环进一步锁牢,风险降低
- 持续集成,可配置相关UI自动化脚本形成持续集成,高大上
网上有很多基于Jenkins服务搭建集成打包平台的教程,但是关于使用代理模式的资料相对较少。由于公司已经搭建有Jenkins服务,但是服务器上有很多旧的项目在维护,内存不足,配置无法满足Flutter项目高效打包的要求,正好公司有一台空闲的iMac电脑可以用来打包(非mac环境不能打包ios),所以我采用的是master-node的代理模式来搭建Jenkins持续集成打包平台,同时,借助蒲公英来生成打包出来的apk/ipa文件的下载链接和二维码,方便下载安装,最后配置了钉钉机器人推送构建成功的消息到指定群通知,并且@到对应的测试人员。
该平台主要实现的功能:
- 支持指定BUILD_TYPE类型打生产或测试包
- 可选择BUILD_BRANCH分支打包
- 打包完成后生成下载二维码和链接
- 构建完成后通过钉钉推送到指定群通知
流程
首先我们需要准备两台电脑,一台为打包机,作为真正执行构建任务的代理节点,一台为控制机(部署了Jenkins服务器或者任意一台可登录Jenkins服务器的电脑),这里我用的是自己的工作电脑,用来配置Jenkins上的项目构建参数以及权限等,接下来我们的搭建工作也将分两台电脑的部分去执行
Android
一、打包机(代理机)
- 安装Flutter、Android、JDK等构建所需SDK,以及各种环境变量,ios打包的话还要配置好项目相关证书,确保在本地使用flutter build命令能够正常打包;
- 使用brew安装jenkins----命令行安装
brew install jenkins-lts
二、控制机
- 用SSH方式添加agent节点
Jenkins- 插件管理- 可选插件- 搜索SSH Agent、Environment Injector- 安装完成后重启jenkins服务
名称:自定义一个节点名称
描述:使得该代理更加容易被识别的可选项
Number of executors:可以同时执行的job线程数,随便写个数字
远程工作目录:代理机上Workspace目录
标签:自定义,方便后期识别,作为限制项目运行节点的标记
用法:只允许运行绑定到这台机器的Job
启动方式:Launch agent agents via SSH
主机:输入要连接的远程代理机IP地址
Credentials:添加远程代理机的ROOT账号和密码的凭据
Host Key Verification Strategy:这项选择Non verifying Verifcation Stragegy
-
新建项目
-
配置项目打包参数
WORKSPACE_MASTER:Jenkins服务器上的任务工作目录
WORKSPACE_MASTER:代理机上的任务工作目录
BUILD_APP_NAME:安装包的包名
MASTER_ROOT_USERNAME:Jenkins服务器的管理员用户名
MASTER_IP:Jenkins服务器的ip地址
#!/bin/sh
if [ ${BUILD_TYPE} = "prod" ]; then
flutter clean
flutter build apk --release --flavor prod -t lib/entry/entry_pro.dart --dart-define=APP_CHANNEL=GP
else
flutter clean
flutter build apk --release --flavor qa -t lib/entry/entry_qa.dart --dart-define=APP_CHANNEL=HOME
fi
exit 0
代理机终端输入
echo $PATH
,将打印的内容复制粘贴到键PATH对应的值里- 使用 Jenkins 插件上传应用到蒲公英
1.下载插件:jenkins -> 系统管理 -> 插件管理,搜索 Upload to pgyer、description setter,点击下载,下载完的效果如下图:
pgyer api_key:蒲公英的 api_key(需注册蒲公英账号申请,参考申请pgyer api_key)
scandir:ipa/apk 所在目录
file widcard:上传文件的通配符
解决方法:将执行打包出来的APP拷贝至部署Jenkins的机器上,然后将Upload to pgyer with apiV2里的文件目录修改成Master节点的文件目录
scp ${WORKSPACE_AGENT}/build/app/outputs/flutter-apk/app-${BUILD_TYPE}-release.apk ${MASTER_ROOT_USERNAME}@${MASTER_IP}:${WORKSPACE_MASTER}/${BUILD_APP_NAME}.apk
#!/bin/sh
if [ ${BUILD_TYPE} = "prod" ]; then
flutter clean
flutter build apk --release --flavor prod -t lib/entry/entry_pro.dart --dart-define=APP_CHANNEL=GP
else
flutter clean
flutter build apk --release --flavor qa -t lib/entry/entry_qa.dart --dart-define=APP_CHANNEL=HOME
fi
ssh ${MASTER_ROOT_USERNAME}@${MASTER_IP} "cd ${WORKSPACE_MASTER};rm -f ${WORKSPACE_MASTER}/*"
scp ${WORKSPACE_AGENT}/build/app/outputs/flutter-apk/app-${BUILD_TYPE}-release.apk ${MASTER_ROOT_USERNAME}@${MASTER_IP}:${WORKSPACE_MASTER}/${BUILD_APP_NAME}.apk
exit 0
ssh ${MASTER_ROOT_USERNAME}@${MASTER_IP} "cd ${WORKSPACE_MASTER};rm -f ${WORKSPACE_MASTER}/*"
这句命令的作用是每次构建删除之前打包存储在Jenkins服务器的产物,非必须。
再次执行构建成功,蒲公英上传插件将输出相应的 log,如下图:
上传蒲公英成功后,可在 jenkins 中的其他构建中使用蒲公英上传成功后返回的参数,这会将蒲公英返回的参数注入为jenkins的全局变量,在其他构建步骤的使用方法直接引用这个全局变量即可,变量名称直接使用返回的 key值。例如:
${appBuildURL}
,蒲公英上传成功后返回参数的参考请看这里。- 构建成功发送钉钉机器人通知
1.安装插件DingTalk
iOS
iOS打包配置基本和Android的一样,除了构建脚本,这里需要借助fastlane来打ipa包。首先在打包机上安装fastlane,确保fastlane的工作目录位于工程的ios目录下修改fastlane脚本
default_platform(:ios)
platform :ios do
lane :export_ipa_qa do
increment_build_number(xcodeproj: "Runner.xcodeproj")
version = get_version_number(target: "Runner")
build_num = get_build_number()
ipa_name = "AppName_iOS_v" + version + "_" + "qa" + "_" +Time.new.strftime('%Y-%m-%d') + ".ipa"
build_app(
export_method: "development",
clean: true,
scheme: "dev",
configuration: "Release-dev",
output_directory: "~/workspace/AppName_iOS/build/ipa",
output_name: ipa_name,
)
end
lane :export_ipa_prod do
increment_build_number(xcodeproj: "Runner.xcodeproj")
version = get_version_number(target: "Runner")
build_num = get_build_number()
ipa_name = "AppName_iOS_v" + version + "_" + "prod" + "_" +Time.new.strftime('%Y-%m-%d') + ".ipa"
build_app(
export_method: "development",
clean: true,
scheme: "prod",
configuration: "Release-prod",
output_directory: "~/workspace/AppName_iOS/build/ipa",
output_name: ipa_name,
)
end
end
Jenkins构建脚本如下
#!/bin/sh
export LANG=en_US.UTF-8
export LANGUAGE=en_US.UTF-8
export LC_ALL=en_US.UTF-8
if [ ${BUILD_TYPE} = "prod" ]; then
flutter clean
flutter build ios --release --flavor prod -t lib/entry/entry_pro.dart --dart-define=APP_CHANNEL=IOS
cd ios
pod install
fastlane export_ipa_prod
else
flutter clean
flutter build ios --release --flavor dev -t lib/entry/entry_qa.dart --dart-define=APP_CHANNEL=IOS
cd ios
pod install
fastlane export_ipa_qa
fi
ssh ${MASTER_ROOT_USERNAME}@${MASTER_IP} "cd ${WORKSPACE_MASTER};rm -f ${WORKSPACE_MASTER}/*"
scp ${WORKSPACE_AGENT}/build/ipa/${BUILD_APP_NAME}.ipa ${MASTER_ROOT_USERNAME}@${MASTER_IP}:${WORKSPACE_MASTER}/${BUILD_APP_NAME}.ipa
exit 0
问题记录(遇到的坑)
构建过程中flutter pub get命令报403错误,一开始以为是文件读写权限的问题,检查了打包机的文件操作权限都是没问题的,然后推测是pub缓存的问题,就删除了整个任务的内容,再重新构建拉取源码,还是不行,又试了网上的几种方案都行不通,最后解决该问题的方式是自己试出来的,很简单,在打包机本地工程上执行一下flutter pub get命令就可以了。
总结
本次实现了从0到1的持续集成打包平台的搭建,期间遇到了很多问题,踩了很多坑,很多问题网上也找不到对应的解决方案,需要自己一步步去摸索实践,但最终完成下来确实是有所收获。
完结撒花🌸🌸🌸🌸🌸🌸🌸🌸🌸🌸🌸