前言:可持续集成自动化的话题已经老生常谈了。目前市面上比较流行的自动化流程工具——Fastlane,Fastlane是用Ruby语言编写的一套自动化工具集和框架,Fastlane的工具集基本上涵盖了打包,签名,测试,部署,发布,库管理等等用起来比较方便,配合Jenkins可持续化集成,基本可以满足大部分的流程自动化。
一. 打包
实现打包有很多种,例如xcodebuild,但已经有好用的工具集为何不用呢?
跟着打包的流程写脚本,例如我想打包,得提供给别人选择哪个分支,采用什么类型,及时通知等
- Jenkins上装了Git parameter plug-In 0.9.12版本的插件进行分支选择
- 想暴露什么参数在Jenkins上自定义
- 利用fastlane gym
- 上传蒲公英
- 由于之前蒲公英挂过一次,不能完全依赖第三方分发平台,自己再自建一个OTA服务器来内测分发
- 自定义内测的二维码采用python myqr生成
- 消息通知:我司采用企业微信,那就搞个机器人webhook一下,当然也可以脚本发个邮件
- 符号表选择是否上传
desc "ad_Hoc 版本"
lane :beta do |options|
# 新建build号
new_build = options[:new_build]
time = Time.new.strftime("%Y-%m-%d-%H:%M:%S")
increment_build_number(
build_number: new_build,
xcodeproj: "xxxxx.xcodeproj"
)
sh("pod repo update")
# 拉取代码
cocoapods
# 获取版本号
version = get_version_number(
xcodeproj: "xxxxx.xcodeproj",
target: "xxxxx"
)
# 打包环境
configuration = (options[:configuration] ? options[:configuration] : "Release")
ipaName="xxxxx"
ipaPath=configuration + "/" + version + "." + new_build + "/"
# 导出ipa包地址
output_directory = "/Users/admin/WebSites/app/ipa/" + ipaPath
#manifest.plilst需要的参数
ipaUrl='https://10.104.33.114/app/ipa/' + ipaPath + ipaName + '.ipa'
plistPath = 'https://10.104.33.114/app/ipa/' + ipaPath + 'manifest.plist'
pngName = version + "." + new_build + '.png'
disImg ='https://10.104.33.114/app/icon/' + pngName
gym(
scheme: "xxxxx",
workspace: "xxxxx.xcworkspace",
export_method:"ad-hoc",
output_directory: output_directory,#文件路径
clean: true,
configuration: configuration,
export_options:{
manifest: {
appURL: ipaUrl,
displayImageURL: disImg,
fullSizeImageURL: disImg
},
}
)
# 参数传给内测分发网页
size =`echo $(wc -c < #{output_directory}#{ipaName}.ipa)`
desc = URI::encode(options[:desc])
appBuildURL = "http://10.104.33.114/app/index.html?" + "version=" + version + "&" + "build=" + new_build + "&" + "size=" + size.strip + "&" + "time=" + time + "&" + "desc=" + desc + "&" + "pngName=" + pngName + "&" + "plistUrl=" + plistPath
myqrAppBuildURL = "http://10.104.33.114/app/index.html?" + "version=" + version + "\\&" + "build=" + new_build + "\\&" + "size=" + size.strip + "\\&" + "time=" + time + "\\&" + "desc=" + desc + "\\&" + "pngName=" + pngName + "\\&" + "plistUrl=" + plistPath
appQRCodeURL = "http://10.104.33.114/app/icon/" + pngName
cpath = sh("pwd").strip
`rm -rf #{cpath}/qrcode.png`
# myqr生成二维码
`myqr #{myqrAppBuildURL}`
`mv #{cpath}/qrcode.png /Users/admin/WebSites/app/icon/#{pngName}`
UI.message "appBuildURL:#{appBuildURL}"
UI.message "appQRCodeURL:#{appQRCodeURL}"
# 上传蒲公英
uploadPgy(options[:desc])
versionDes = version + " ( build "+ new_build + " )"
description = "打包完成,版本:"+ versionDes + ",包体积:" + size.strip
end
注意 myqr 是生成二维码的python 工具,需要设置环境变量
以上已经实现了打包,接下来上传蒲公英
# 上传蒲公英
def uploadPgy(desc)
begin
pgyer(api_key: "xxx",user_key: "xxx",update_description:"xxx")
rescue
retry
xxx
end
如果实现企业微信通知,其实就是发送一个请求,此时要注意的是fastlane 是ruby 环境,执行shell脚本的 & 或是 双引号需要转义:\ ,并非一个\,例如转义&:\&
<img src="https://zhonghphuan.github.io/images/iOS-自动化-打包/WX20200820-170210@2x.png" width = "300" alt="" align=center />
基本以上已经实现了打包的日常需求了,gym中的export_options是自建内测分发的manifest配置
export_options:{
manifest: {
appURL: ipaUrl,
displayImageURL: disImg,
fullSizeImageURL: disImg
},
}
以下简单描述一下自建OTA服务
- 启动Web服务 - Mac自带Apache
➜ ~ httpd -v
Server version: Apache/2.4.41 (Unix)
Server built: Apr 17 2020 19:06:36
- 启动:sudo apachectl start
- 停止:sudo apachectl stop
- 重启:sudo apachectl restart
启动sudo apachectl start后浏览器http://127.0.0.1,显示It Works即成功
- SSL签名证书
➜ ~ cd /private/etc/apache2/
➜ apache2 sudo mkdir ssl
➜ apache2 cd ssl
➜ ssl sudo openssl genrsa -out ip211.key 2048
Generating RSA private key, 2048 bit long modulus
...................+++
..............................................................+++
e is 65537 (0x10001)
➜ ssl sudo openssl req -new -key ip211.key -out ip211.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:127.0.0.1(此处填具体的ip地址)
Email Address []:
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
➜ ssl sudo openssl x509 -req -days 365000 -in ip211.csr -signkey ip211.key -out ip211.crt
Signature ok
subject=/C=AU/ST=Some-State/O=Internet Widgits Pty Ltd/CN=127.0.0.1
Getting Private key
➜ ssl sudo openssl rsa -in ip211.key -out ip211-nopass.key
writing RSA key
➜ ssl ls -l
total 32
-rw-r--r-- 1 root wheel 1679 8 20 17:26 ip211-nopass.key
-rw-r--r-- 1 root wheel 1168 8 20 17:25 ip211.crt
-rw-r--r-- 1 root wheel 985 8 20 17:23 ip211.csr
-rw-r--r-- 1 root wheel 1679 8 20 17:20 ip211.key
只有Common Name填写具体的ip地址
- 修改conf文件
➜ ssl sudo cp /private/etc/apache2/httpd.conf /private/etc/apache2/httpd.conf.bak
Password:
➜ ssl sudo cp /private/etc/apache2/extra/httpd-ssl.conf /private/etc/apache2/extra/httpd-ssl.conf.bak
➜ ssl sudo cp /private/etc/apache2/mime.types /private/etc/apache2/mime.types.bak
➜ ssl sudo vim /private/etc/apache2/httpd.conf
➜ ssl sudo vim /private/etc/apache2/extra/httpd-ssl.conf
➜ ssl sudo vim /private/etc/apache2/mime.types
1)修改/private/etc/apache2/httpd.conf,去掉以下两个模块的注释
LoadModule socache_shmcb_module libexec/apache2/mod_socache_shmcb.so
LoadModule ssl_module libexec/apache2/mod_ssl.so
Include /private/etc/apache2/extra/httpd-ssl.conf
2)修改/private/etc/apache2/extra/httpd-ssl.conf,去掉以下三处的注释
ServerName 127.0.0.1(具体的ip地址)
SSLCertificateFile "/private/etc/apache2/ssl/ip211.crt"
SSLCertificateKeyFile "/private/etc/apache2/ssl/ip211-nopass.key"
3)修改/private/etc/apache2/mime.types,加入以下两条
application/octet-stream ipa
text/xml plist
- 重启服务:sudo apachectl restart,浏览器输入具体ip地址
- 配置目录
$ sudo mkdir ipa
$ sudo mkdir icon
$ sudo mkdir ssl
$ sudo mkdir plist
拷贝/private/etc/apache2/ssl/ip211.crt 到 这个ssl目录下:
sudo cp /private/etc/apache2/ssl/ip211.crt ~/WebSites/app/ssl/ip211.crt
- 制作一个简单的页面
解析链接中的itms-services:// 实现OTA
<!DOCTYPE html>
<html>
<head lang="zh-cmn-Hans">
<meta charset="UTF-8">
<title>分发ipa包管理</title>
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
<meta name="viewport" content="width=device-width,initial-scale=0.5,user-scalable=no" />
</head>
<style>
.img {
text-align: center;
}
.btn {
text-align: center;
background: #35AF5D;
color: #000;
padding: 20px;
margin: 30px;
font-size: 24px;
border-radius: 4px;
box-shadow: 4px 2px 10px #999;
}
.btn:active {
opacity: .7;
box-shadow: 4px 2px 10px #555;
}
</style>
<body>
<h1 style="text-align:center;color:#35AF5D">
工程名
</h1>
<p id="p1" style="text-align:center">
版本:
</p>
<p id="p2" style="text-align:center">
大小:
</p>
<p id="p3" style="text-align:center">
更新时间:
</p>
<p id="p4" style="text-align:center">
更新描述:
</p>
<div class="img">
<img id="imgid" src="./icon/*.png" height="300" width="300" />
</div>
<div class="btn" onclick="installApp()">安装app</div>
<script>
document.getElementById("p1").innerHTML = "版本:" + getUrlParam("version") + " ( build " + getUrlParam("build") + " )"
document.getElementById("p2").innerHTML = "大小:" + getUrlParam("size") + " KB"
document.getElementById("p4").innerHTML = "更新时间:" + getUrlParam("time")
document.getElementById("p3").innerHTML = "更新描述:" + decodeURIComponent(getUrlParam("desc"))
document.getElementById('imgid').src = "./icon/" + getUrlParam("pngName")
function getUrlParam(variable) {
let query = window.location.search.substring(1);
let vars = query.split("&");
for (let i = 0; i < vars.length; i++) {
let pair = vars[i].split("=");
if (pair[0] == variable) { return pair[1]; }
}
return (false);
}
function installApp() {
var plistUrl = decodeURI(getUrlParam("plistUrl"));
window.location.href = "itms-services://?action=download-manifest&url=" + plistUrl;
}
</script>
<a style="display:block;margin: 30px;" href="http://10.104.33.114/app/ssl/ip211.crt">下载证书</a>
<p style="display:block;margin: 30px;">点击下载证书,下载安装配置文件</p>
<p style="display:block;margin: 30px;">在设置-通用-描述文件与设备管理中,选择已下载的配置文件,进行安装</p>
<p style="display:block;margin: 30px;">在设置-通用-关于本机-证书信任设置中将完全信任打开</p>
</body>
</html>
二. testflight 自动化公测
- 方案一: 使用fastlane的upload_to_testflight
upload_to_testflight(
beta_app_review_info: {
contact_email: "xxxxx@xxx.net",
contact_first_name: "xx",
contact_last_name: "xx",
contact_phone: "+xxxxxx",
demo_account_name: "xxxxxx",
demo_account_password: "xxxxx"
},
first_name: "xxx",
last_name: "xxxx",
email: "xxxxx@xxx.net",
# true就不自动提审了
skip_waiting_for_build_processing: false,
beta_app_feedback_email:"xxxxx@xxx.net",
beta_app_description:options[:desc],
demo_account_required: true,
#构建是否应该分发给外部测试人员?
distribute_external: true,
notify_external_testers: true,
groups: groups,
changelog:options[:desc],
ipa: ipa_path,
localized_app_info: {
"default": {
feedback_email: "xxxxx@xxx.net",
description: "xxxxxxxxxxx"
},
"zh-Hans": {
feedback_email: "xxxxx@xxx.net",
description: "xxxxxxxxx。"
}
},
localized_build_info: {
"default": {
whats_new: options[:desc]
},
"zh-Hans": {
whats_new: options[:desc]
}
}
)
但这样有个问题,需要双重验证,通过fastlane spaceauth 生成的session一个月就过期了
#!/bin/bash
# 双重验证session一个月过期,执行下面方法输入验证码继续一个月
# fastlane spaceauth -u ios-develop@xxxxx.net
export FASTLANE_SESSION='---\n- !ruby/object:HTTP::Cookie\n ........'
export FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD=nqfn-rljf-jipw-kevb
那这个方法其实不太能完美,只能利用 苹果自动化api来实现
- 方案二:苹果自动化api
先ruby封装几个函数
require "base64"
require "jwt"
require 'json'
# 准备分支信息
def prepare(branch,version,new_build,channel)
sh "git checkout #{branch}"
sh "git pull origin #{branch}"
increment_build_number(
build_number: new_build,
xcodeproj: "xxxx.xcodeproj"
)
increment_version_number(version_number: version)
tag_string = "#{channel}_#{version}.#{new_build}"
sh 'git add .'
git_commit(path: '.', message: tag_string)
push_to_git_remote(tags: false)
add_git_tag(tag: tag_string)
end
# 上传蒲公英
def uploadPgy(desc)
begin
pgyer(api_key: "xxxx",user_key: "xxxx",update_description:"#{desc}")
rescue
retry
end
end
# 审核状态
def getBuildState(buildid)
begin
jwt_token = getToken()
externalBuildState = %x(curl -H "Authorization: Bearer #{jwt_token}" -H "Content-type: application/json" -s -X GET https://api.appstoreconnect.apple.com/v1/buildBetaDetails/#{buildid} )
state = JSON.parse(externalBuildState)
buildstate = state["data"]["attributes"]["externalBuildState"]
rescue
retry
end
end
# 内审状态
def getInternalBuildState(buildid)
begin
jwt_token = getToken()
externalBuildState = %x(curl -H "Authorization: Bearer #{jwt_token}" -H "Content-type: application/json" -s -X GET https://api.appstoreconnect.apple.com/v1/buildBetaDetails/#{buildid} )
state = JSON.parse(externalBuildState)
buildstate = state["data"]["attributes"]["internalBuildState"]
rescue
retry
end
end
# 获取build
def getBetaBuild(new_build)
begin
jwt_token = getToken()
buildJson = %x(curl -H "Authorization: Bearer #{jwt_token}" -H "Content-type: application/json" -X GET https://api.appstoreconnect.apple.com/v1/builds?filter[version]=#{new_build})
buildJsonParse = JSON.parse(buildJson)
buildid = buildJsonParse["data"][0]["id"]
rescue
sleep 5 * 60
retry
end
end
# 测试人员添加测试组中
def getBetaTesters(groupid)
begin
jwt_token = getToken()
betaTesters = %x(curl -H "Authorization: Bearer #{jwt_token}" -H "Content-type: application/json" -X POST -d '{"data": {"type": "betaTesters","attributes": {"firstName":"xx","lastName":"xx","email":"xx@xxx.net"},"relationships": {"betaGroups":{"data":[{"type":"betaGroups","id":"#{groupid}"}]}}}}' https://api.appstoreconnect.apple.com/v1/betaTesters)
puts "将测试人员添加到组中: #{betaTesters}"
betaTestersData = JSON.parse(betaTesters)
id = betaTestersData["data"]["id"]
rescue
sleep 5 * 60
retry
end
end
# 创建组
def createGroup(groups)
jwt_token = getToken()
puts "令牌:#{jwt_token}"
# 创建组
groupJson = %x(curl -H "Authorization: Bearer #{jwt_token}" -H "Content-type: application/json" -X POST -d '{"data": {"type": "betaGroups","attributes": {"name":"#{groups}"},"relationships": {"app": {"data":{"type":"apps","id":"xxxx"}}}}}' https://api.appstoreconnect.apple.com/v1/betaGroups)
groupJsonParse = JSON.parse(groupJson)
groupid = groupJsonParse["data"]["id"]
end
# build添加测试组中
def addBetaGroups(groupid,buildid)
jwt_token = getToken()
# 将版本添加到组中
insertBetaGroups = %x(curl -H "Authorization: Bearer #{jwt_token}" -H "Content-type: application/json" -X POST -d '{"data": [{"type": "builds","id":"#{buildid}"}]}' https://api.appstoreconnect.apple.com/v1/betaGroups/#{groupid}/relationships/builds)
puts "将版本添加到组中: #{insertBetaGroups}"
end
# 获取本地化id
def getBetaBuildLocalizationsid(buildid,desc)
jwt_token = getToken()
createBetaBuildLocalizationsJson = %x(curl -H "Authorization: Bearer #{jwt_token}" -H "Content-type: application/json" -X POST -d '{ "data": {"type": "betaBuildLocalizations","attributes": {"whatsNew": "#{desc}","locale":"zh-Hans"},"relationships": {"build":{"data":{"id":"#{buildid}","type":"builds"}}}}}' https://api.appstoreconnect.apple.com/v1/betaBuildLocalizations)
puts "createBetaBuildLocalizationsJson: #{createBetaBuildLocalizationsJson}"
betaBuildLocalizationsJson = %x(curl -H "Authorization: Bearer #{jwt_token}" -H "Content-type: application/json" -X GET https://api.appstoreconnect.apple.com/v1/betaBuildLocalizations?filter[build]=#{buildid}&filter[locale]=zh-Hans)
betaBuildLocalizationsParse = JSON.parse(betaBuildLocalizationsJson)
puts "betaBuildLocalizationsJson: #{betaBuildLocalizationsJson}"
betaBuildLocalizationsid = betaBuildLocalizationsParse["data"][0]["id"]
end
# 本地化信息
def patchBetaBuildLocalizations(betaBuildLocalizationsid,desc)
jwt_token = getToken()
patchBetaBuildLocalizations = %x(curl -H "Authorization: Bearer #{jwt_token}" -H "Content-type: application/json" -X PATCH -d '{ "data": {"type": "betaBuildLocalizations","attributes": {"whatsNew": "#{desc}"},"id": "#{betaBuildLocalizationsid}"}}' https://api.appstoreconnect.apple.com/v1/betaBuildLocalizations/#{betaBuildLocalizationsid})
puts "本地化信息: #{patchBetaBuildLocalizations}"
end
# 启用公测链接
def getPublic_link(groupid,groups)
begin
jwt_token = getToken()
public_link_json = %x(curl -H "Authorization: Bearer #{jwt_token}" -H "Content-type: application/json" -X PATCH -d '{"data": {"type": "betaGroups","id": "#{groupid}","attributes": {"name": "#{groups}","publicLinkEnabled": true,"publicLinkLimitEnabled": false,"publicLinkLimit": null,"feedbackEnabled": true}}}' https://api.appstoreconnect.apple.com/v1/betaGroups/#{groupid})
puts "链接请求: #{public_link_json}"
public_link_json_parse = JSON.parse(public_link_json)
public_link = public_link_json_parse["data"]["attributes"]["publicLink"]
rescue
sleep 5 * 60
retry
end
end
# 获取苹果凭据token
def getToken
private_key = OpenSSL::PKey.read(File.read("/Users/admin/AuthKey_xxxxx.p8"))
token = JWT.encode(
{
iss: "xxxxx-xxxx-xxxxxx-xxxx-xxxxxx",
exp: Time.now.to_i + 20 * 60,
aud: "appstoreconnect-v1"
},
private_key,
"ES256",
header_fields={kid: "xxxxx" }
)
end
此处根据 苹果自动化api文档先本地通过postman去调试验证,如下图,header中的Authorization为key,value为 "Bearer 苹果凭据token"
具体实现
desc "发布testflight版本"
lane :testflight do |options|
#新建build号
new_build = options[:new_build]
desc = options[:desc]
puts "desc:#{desc}"
time = Time.new.strftime("%Y-%m-%d-%H:%M:%S")
increment_build_number(
build_number: new_build,
xcodeproj: "xxxx.xcodeproj"
)
new_version = options[:new_version]
if !new_version.empty?
increment_version_number(version_number: new_version)
end
sh("pod repo update")
# 拉取代码
cocoapods
# 获取版本号
version = get_version_number(
xcodeproj: "xxxx.xcodeproj",
target: "xxxx"
)
# 打包环境
configuration = "Release"
ipaName="xxxx"
ipaPath=configuration + "/" + version + "." + new_build + "/"
# 导出ipa包地址
output_directory = "/Users/admin/WebSites/app/ipa/" + ipaPath
#manifest.plilst需要的参数
ipaUrl='https://10.104.33.114/app/ipa/' + ipaPath + ipaName + '.ipa'
plistPath = 'https://10.104.33.114/app/ipa/' + ipaPath + 'manifest.plist'
pngName = version + "." + new_build + '.png'
disImg ='https://10.104.33.114/app/icon/' + pngName
gym(
scheme: "xxxx",
workspace: "xxx.xcworkspace",
export_method:"app-store",
export_xcargs: "-allowProvisioningUpdates",
output_directory: output_directory,#文件路径
clean: true,
configuration: configuration,
export_options:{
manifest: {
appURL: ipaUrl,
displayImageURL: disImg,
fullSizeImageURL: disImg
},
}
)
ipa_path = output_directory + ipaName + '.ipa'
groups = version + "." + new_build
apiIssuer = "xxxxxxxxxxxxxxx";
apiKey = "xxxxxx";
`xcrun altool --validate-app -f #{ipa_path} -t ios --apiKey #{apiKey} --apiIssuer #{apiIssuer}`
validate_status = `echo $?`
puts "======================== validate ========================"
puts "#{validate_status}"
if Integer(validate_status) != 0
puts "======================== 验证出错 ========================"
exit
end
puts "======================== 验证成功 ========================"
`xcrun altool --upload-app -f #{ipa_path} -t ios --apiKey #{apiKey} --apiIssuer #{apiIssuer}`
upload_status = `echo $?`
puts "======================== upload ========================"
puts "#{upload_status}"
if Integer(upload_status) != 0
puts "======================== 上传出错 ========================"
exit
end
puts "======================== 上传成功 ========================"
size =`echo $(wc -c < #{output_directory}#{ipaName}.ipa)`
desc = URI::encode(options[:desc])
appBuildURL = "http://10.104.33.114/app/index.html?" + "version=" + version + "&" + "build=" + new_build + "&" + "size=" + size.strip + "&" + "time=" + time + "&" + "desc=" + desc + "&" + "pngName=" + pngName + "&" + "plistUrl=" + plistPath
myqrAppBuildURL = "http://10.104.33.114/app/index.html?" + "version=" + version + "\\&" + "build=" + new_build + "\\&" + "size=" + size.strip + "\\&" + "time=" + time + "\\&" + "desc=" + desc + "\\&" + "pngName=" + pngName + "\\&" + "plistUrl=" + plistPath
appQRCodeURL = "http://10.104.33.114/app/icon/" + pngName
cpath = sh("pwd").strip
`rm -rf #{cpath}/qrcode.png`
`myqr #{myqrAppBuildURL}`
`mv #{cpath}/qrcode.png /Users/admin/WebSites/app/icon/#{pngName}`
UI.message "appBuildURL:#{appBuildURL}"
UI.message "appQRCodeURL:#{appQRCodeURL}"
description = "公测包:"+ groups
UI.message "description:#{description}"
# 获取build
buildJson = getBetaBuild(new_build)
puts "buildid:#{buildid}"
# 轮询
internalBuildStat = getInternalBuildState(buildid)
puts "提交内审状态:#{internalBuildStat}"
while !(internalBuildStat.casecmp?("IN_BETA_TESTING")) do
sleep 5 * 60
internalBuildStat = getInternalBuildState(buildid)
puts "提交内审状态:#{internalBuildStat}"
end
# 发出企业微信通知:可以提交审核
sleep 5 * 60
jwt_token = getToken()
# 提交审核
betaAppReviewSubmissions = %x(curl -H "Authorization: Bearer #{jwt_token}" -H "Content-type: application/json" -X POST -d '{"data": {"type": "betaAppReviewSubmissions","relationships": {"build": {"data":{"type":"builds","id":"#{buildid}"}}}}}' https://api.appstoreconnect.apple.com/v1/betaAppReviewSubmissions)
puts "审核请求结果:#{betaAppReviewSubmissions}"
# 获取审核状态
buildstate = getBuildState(buildid)
puts "审核状态:#{buildstate}"
laststate = buildstate
if buildstate.casecmp?("WAITING_FOR_BETA_REVIEW")
# 发出企业微信通知:等待审核状态
end
if buildstate.casecmp?("IN_REVIEW")
# 发出企业微信通知
end
# 轮询查看审核状态(每隔10分钟)
while !(buildstate.casecmp?("IN_BETA_TESTING") || buildstate.casecmp?("APPROVED") || buildstate.casecmp?("REJECTED") || buildstate.casecmp?("BETA_APPROVED") || buildstate.casecmp?("BETA_REJECTED")) do
sleep 10 * 60
buildstate = getBuildState(buildid)
if !laststate.casecmp?(buildstate)
if (buildstate.casecmp?("IN_REVIEW") || buildstate.casecmp?("IN_BETA_REVIEW"))
# 发出企业微信通知
else
# 发出企业微信通知
end
end
laststate = buildstate;
puts "审核状态:#{buildstate}"
end
if (buildstate.casecmp?("REJECTED") || buildstate.casecmp?("BETA_REJECTED"))
# 发出企业微信通知:等待审核状态
puts "#{groups} 公测审核被拒,请前往App Store查看原因"
exit
end
if (buildstate.casecmp?("IN_BETA_TESTING") || buildstate.casecmp?("APPROVED") || buildstate.casecmp?("BETA_APPROVED"))
jwt_token = getToken()
puts "令牌:#{jwt_token}"
# 创建组
groupid = createGroup(groups)
puts "获取到组id:#{groupid}"
sleep 5
# 将测试人员添加到组中
getBetaTesters(groupid)
sleep 5
# 将build添加到组中
addBetaGroups(groupid,buildid)
sleep 5
#获取本地化id
betaBuildLocalizationsid = getBetaBuildLocalizationsid(buildid,options[:desc])
puts "betaBuildLocalizationsid:#{betaBuildLocalizationsid}"
#修改本地化测试信息
patchBetaBuildLocalizations(betaBuildLocalizationsid,options[:desc])
# 启用公测链接
public_link = getPublic_link(groupid,groups)
puts "公测链接: #{public_link}"
new_branch = options[:new_branch]
prepare(new_branch,version,new_build,'testflight')
push_git_tags
# 上传bugly
dsymFilePath = output_directory + 'xxxx.app.dSYM.zip'
upload_dsym_to_bugly(
file_path: "#{dsymFilePath}",
file_name: "%e8%b6%axxxxxxx%.app.dSYM.zip",
app_key: "xxxxxxx",
app_id:"xxxxxxx",
api_version: 1,
symbol_type: 2, # iOS => 2, Android => 1
bundle_id: 'com.xxxx.xxxx',
product_version: "#{groups}"
)
end
end
三. 总结
- 公测自动化实现后,App Store打包通过打包验证和上传也很容易实现
- Jenkins + fastlane 较为方便的实现可持续集成自动化的流程
- python、ruby、shell等语言实现脚本思想一样,哪个方便用哪个
- 能工具化提高效率的尽量工具化自动化,为公司节省人力,提高工作效率
- 消息通知最终流程过程或结果可以采用邮件、webhook机器人消息等