前言:可持續(xù)集成自動(dòng)化的話題已經(jīng)老生常談了抡四。目前市面上比較流行的自動(dòng)化流程工具——Fastlane,F(xiàn)astlane是用Ruby語(yǔ)言編寫的一套自動(dòng)化工具集和框架,F(xiàn)astlane的工具集基本上涵蓋了打包胸私,簽名,測(cè)試阔涉,部署瑰排,發(fā)布暖侨,庫(kù)管理等等用起來(lái)比較方便,配合Jenkins可持續(xù)化集成京郑,基本可以滿足大部分的流程自動(dòng)化。
一. 打包
實(shí)現(xiàn)打包有很多種跟狱,例如xcodebuild户魏,但已經(jīng)有好用的工具集為何不用呢?
跟著打包的流程寫腳本关翎,例如我想打包笤休,得提供給別人選擇哪個(gè)分支症副,采用什么類型,及時(shí)通知等
- Jenkins上裝了Git parameter plug-In 0.9.12版本的插件進(jìn)行分支選擇
- 想暴露什么參數(shù)在Jenkins上自定義
- 利用fastlane gym
- 上傳蒲公英
- 由于之前蒲公英掛過一次闹啦,不能完全依賴第三方分發(fā)平臺(tái)窍奋,自己再自建一個(gè)OTA服務(wù)器來(lái)內(nèi)測(cè)分發(fā)
- 自定義內(nèi)測(cè)的二維碼采用python myqr生成
- 消息通知:我司采用企業(yè)微信酱畅,那就搞個(gè)機(jī)器人webhook一下,當(dāng)然也可以腳本發(fā)個(gè)郵件
- 符號(hào)表選擇是否上傳
desc "ad_Hoc 版本"
lane :beta do |options|
# 新建build號(hào)
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
# 獲取版本號(hào)
version = get_version_number(
xcodeproj: "xxxxx.xcodeproj",
target: "xxxxx"
)
# 打包環(huán)境
configuration = (options[:configuration] ? options[:configuration] : "Release")
ipaName="xxxxx"
ipaPath=configuration + "/" + version + "." + new_build + "/"
# 導(dǎo)出ipa包地址
output_directory = "/Users/admin/WebSites/app/ipa/" + ipaPath
#manifest.plilst需要的參數(shù)
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
},
}
)
# 參數(shù)傳給內(nèi)測(cè)分發(fā)網(wǎng)頁(yè)
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 工具碎紊,需要設(shè)置環(huán)境變量
以上已經(jīng)實(shí)現(xiàn)了打包樊诺,接下來(lái)上傳蒲公英
# 上傳蒲公英
def uploadPgy(desc)
begin
pgyer(api_key: "xxx",user_key: "xxx",update_description:"xxx")
rescue
retry
xxx
end
如果實(shí)現(xiàn)企業(yè)微信通知,其實(shí)就是發(fā)送一個(gè)請(qǐng)求秃嗜,此時(shí)要注意的是fastlane 是ruby 環(huán)境,執(zhí)行shell腳本的 & 或是 雙引號(hào)需要轉(zhuǎn)義:\ 螺句,并非一個(gè)\橡类,例如轉(zhuǎn)義&:\&
<img src="https://zhonghphuan.github.io/images/iOS-自動(dòng)化-打包/WX20200820-170210@2x.png" width = "300" alt="" align=center />
基本以上已經(jīng)實(shí)現(xiàn)了打包的日常需求了,gym中的export_options是自建內(nèi)測(cè)分發(fā)的manifest配置
export_options:{
manifest: {
appURL: ipaUrl,
displayImageURL: disImg,
fullSizeImageURL: disImg
},
}
以下簡(jiǎn)單描述一下自建OTA服務(wù)
- 啟動(dòng)Web服務(wù) - Mac自帶Apache
? ~ httpd -v
Server version: Apache/2.4.41 (Unix)
Server built: Apr 17 2020 19:06:36
- 啟動(dòng):sudo apachectl start
- 停止:sudo apachectl stop
- 重啟:sudo apachectl restart
啟動(dòng)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谱邪,去掉以下兩個(gè)模塊的注釋
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
- 重啟服務(wù):sudo apachectl restart喇澡,瀏覽器輸入具體ip地址
- 配置目錄
$ sudo mkdir ipa
$ sudo mkdir icon
$ sudo mkdir ssl
$ sudo mkdir plist
拷貝/private/etc/apache2/ssl/ip211.crt 到 這個(gè)ssl目錄下:
sudo cp /private/etc/apache2/ssl/ip211.crt ~/WebSites/app/ssl/ip211.crt
- 制作一個(gè)簡(jiǎn)單的頁(yè)面
解析鏈接中的itms-services:// 實(shí)現(xiàn)OTA
<!DOCTYPE html>
<html>
<head lang="zh-cmn-Hans">
<meta charset="UTF-8">
<title>分發(fā)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">
更新時(shí)間:
</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 = "更新時(shí)間:" + 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;" >下載證書</a>
<p style="display:block;margin: 30px;">點(diǎn)擊下載證書,下載安裝配置文件</p>
<p style="display:block;margin: 30px;">在設(shè)置-通用-描述文件與設(shè)備管理中尔当,選擇已下載的配置文件琅催,進(jìn)行安裝</p>
<p style="display:block;margin: 30px;">在設(shè)置-通用-關(guān)于本機(jī)-證書信任設(shè)置中將完全信任打開</p>
</body>
</html>
二. testflight 自動(dòng)化公測(cè)
- 方案一: 使用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就不自動(dòng)提審了
skip_waiting_for_build_processing: false,
beta_app_feedback_email:"xxxxx@xxx.net",
beta_app_description:options[:desc],
demo_account_required: true,
#構(gòu)建是否應(yīng)該分發(fā)給外部測(cè)試人員藤抡?
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]
}
}
)
但這樣有個(gè)問題药蜻,需要雙重驗(yàn)證替饿,通過fastlane spaceauth 生成的session一個(gè)月就過期了
#!/bin/bash
# 雙重驗(yàn)證session一個(gè)月過期视卢,執(zhí)行下面方法輸入驗(yàn)證碼繼續(xù)一個(gè)月
# 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
那這個(gè)方法其實(shí)不太能完美廊驼,只能利用 蘋果自動(dòng)化api來(lái)實(shí)現(xiàn)
- 方案二:蘋果自動(dòng)化api
先ruby封裝幾個(gè)函數(shù)
require "base64"
require "jwt"
require 'json'
# 準(zhǔn)備分支信息
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
# 審核狀態(tài)
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
# 內(nèi)審狀態(tài)
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
# 測(cè)試人員添加測(cè)試組中
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 "將測(cè)試人員添加到組中: #{betaTesters}"
betaTestersData = JSON.parse(betaTesters)
id = betaTestersData["data"]["id"]
rescue
sleep 5 * 60
retry
end
end
# 創(chuàng)建組
def createGroup(groups)
jwt_token = getToken()
puts "令牌:#{jwt_token}"
# 創(chuàng)建組
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添加測(cè)試組中
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
# 啟用公測(cè)鏈接
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 "鏈接請(qǐng)求: #{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
# 獲取蘋果憑據(jù)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
此處根據(jù) 蘋果自動(dòng)化api文檔先本地通過postman去調(diào)試驗(yàn)證,如下圖酝掩,header中的Authorization為key,value為 "Bearer 蘋果憑據(jù)token"
具體實(shí)現(xiàn)
desc "發(fā)布testflight版本"
lane :testflight do |options|
#新建build號(hào)
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
# 獲取版本號(hào)
version = get_version_number(
xcodeproj: "xxxx.xcodeproj",
target: "xxxx"
)
# 打包環(huán)境
configuration = "Release"
ipaName="xxxx"
ipaPath=configuration + "/" + version + "." + new_build + "/"
# 導(dǎo)出ipa包地址
output_directory = "/Users/admin/WebSites/app/ipa/" + ipaPath
#manifest.plilst需要的參數(shù)
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 "======================== 驗(yàn)證出錯(cuò) ========================"
exit
end
puts "======================== 驗(yàn)證成功 ========================"
`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 "======================== 上傳出錯(cuò) ========================"
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 = "公測(cè)包:"+ groups
UI.message "description:#{description}"
# 獲取build
buildJson = getBetaBuild(new_build)
puts "buildid:#{buildid}"
# 輪詢
internalBuildStat = getInternalBuildState(buildid)
puts "提交內(nèi)審狀態(tài):#{internalBuildStat}"
while !(internalBuildStat.casecmp?("IN_BETA_TESTING")) do
sleep 5 * 60
internalBuildStat = getInternalBuildState(buildid)
puts "提交內(nèi)審狀態(tài):#{internalBuildStat}"
end
# 發(fā)出企業(yè)微信通知:可以提交審核
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 "審核請(qǐng)求結(jié)果:#{betaAppReviewSubmissions}"
# 獲取審核狀態(tài)
buildstate = getBuildState(buildid)
puts "審核狀態(tài):#{buildstate}"
laststate = buildstate
if buildstate.casecmp?("WAITING_FOR_BETA_REVIEW")
# 發(fā)出企業(yè)微信通知:等待審核狀態(tài)
end
if buildstate.casecmp?("IN_REVIEW")
# 發(fā)出企業(yè)微信通知
end
# 輪詢查看審核狀態(tài)(每隔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"))
# 發(fā)出企業(yè)微信通知
else
# 發(fā)出企業(yè)微信通知
end
end
laststate = buildstate;
puts "審核狀態(tài):#{buildstate}"
end
if (buildstate.casecmp?("REJECTED") || buildstate.casecmp?("BETA_REJECTED"))
# 發(fā)出企業(yè)微信通知:等待審核狀態(tài)
puts "#{groups} 公測(cè)審核被拒竿拆,請(qǐng)前往App Store查看原因"
exit
end
if (buildstate.casecmp?("IN_BETA_TESTING") || buildstate.casecmp?("APPROVED") || buildstate.casecmp?("BETA_APPROVED"))
jwt_token = getToken()
puts "令牌:#{jwt_token}"
# 創(chuàng)建組
groupid = createGroup(groups)
puts "獲取到組id:#{groupid}"
sleep 5
# 將測(cè)試人員添加到組中
getBetaTesters(groupid)
sleep 5
# 將build添加到組中
addBetaGroups(groupid,buildid)
sleep 5
#獲取本地化id
betaBuildLocalizationsid = getBetaBuildLocalizationsid(buildid,options[:desc])
puts "betaBuildLocalizationsid:#{betaBuildLocalizationsid}"
#修改本地化測(cè)試信息
patchBetaBuildLocalizations(betaBuildLocalizationsid,options[:desc])
# 啟用公測(cè)鏈接
public_link = getPublic_link(groupid,groups)
puts "公測(cè)鏈接: #{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
三. 總結(jié)
- 公測(cè)自動(dòng)化實(shí)現(xiàn)后丙笋,App Store打包通過打包驗(yàn)證和上傳也很容易實(shí)現(xiàn)
- Jenkins + fastlane 較為方便的實(shí)現(xiàn)可持續(xù)集成自動(dòng)化的流程
- python煌贴、ruby、shell等語(yǔ)言實(shí)現(xiàn)腳本思想一樣牛郑,哪個(gè)方便用哪個(gè)
- 能工具化提高效率的盡量工具化自動(dòng)化,為公司節(jié)省人力笙各,提高工作效率
- 消息通知最終流程過程或結(jié)果可以采用郵件、webhook機(jī)器人消息等