theme: smartblue
在以前的 《 Android 和 iOS 打包提交審核指南》 里介紹了 Flutter 下打包 Android 和 iOS 的指南,不過這部分內(nèi)容主要介紹的是如何在本地打包發(fā)布流程。
但事實(shí)上一般的產(chǎn)品發(fā)布流程,都會(huì)有專門的機(jī)器用于打包服務(wù),在統(tǒng)一干凈的環(huán)境下進(jìn)行打包更有利于發(fā)布的管理,避免各種本地環(huán)境差異問題。
當(dāng)然大多數(shù)時(shí)候可以直接使用第三方的 CI 服務(wù),但是專門支持 Flutter 的第三方服務(wù)并不多,并且自己動(dòng)手還免費(fèi),所以本篇主要介紹自己搭建獨(dú)立打包服務(wù)的過程。
由于 Android 的命令打包服務(wù)比較簡(jiǎn)單,這里主要介紹配置搭建 iOS 下的 Flutter 打包和發(fā)布 CI ,其實(shí)主要也是 iOS 的 CI 。
一、參數(shù)支持
首先在 iOS 上很多的配置信息都是寫在 info.plist 文件,所以一開始需要解決打包時(shí)支持動(dòng)態(tài)修改 info.plist 的參數(shù),這樣有利于我們?cè)谳敵霾煌h(huán)境的包配置,如:QA、Release、Dev 等等。
/usr/libexec/PlistBuddy -c "Set CFBundleVersion ${CFBundleVersion}" ./Runner/Info.plist
/usr/libexec/PlistBuddy -c "Print CFBundleVersion " ./Runner/Info.plist
在 Mac 上其實(shí)本身就自帶了滿足需求的命令行工具:PlistBuddy, 如上命令所示
- 通過
Set命令可以直接動(dòng)態(tài)配置plist下的版本號(hào)、 code 和第三方 App Id 等相關(guān)配置; - 通過
Print命令直接輸出對(duì)應(yīng)的plist信息;
完成 plist 配置的支持, 接下來就需要在機(jī)器上配置開發(fā)者信息,最簡(jiǎn)單的做法就是打開 Xcode 然后直接登陸上開發(fā)者賬號(hào),通過賬號(hào)直接讓 Xcode 的 Automatically manage signing 幫助我們完成整個(gè)開發(fā)信息的配置過程。
但是我個(gè)人不推薦這種方式,打包機(jī)器本身可能會(huì)涉及多個(gè)項(xiàng)目組使用,都把自己的開發(fā)賬號(hào)登陸在一個(gè)公用機(jī)器上存在風(fēng)險(xiǎn),而且多個(gè)賬號(hào)同時(shí)登陸容易混亂,最后直接登陸也不利于證書和描述和管理。
所以要實(shí)現(xiàn)一個(gè)較為安全和通用的服務(wù),這里比較推薦:通過在機(jī)器上配置證書和 mobile provision 等文件的方式來完成打包認(rèn)證。
二、手動(dòng)配置證書
手動(dòng)配置證書和 mobile provision 會(huì)比較麻煩,但是它可以讓服務(wù)更加通用,也讓你更熟悉 iOS 打包的流程。
1、首先通過本地鑰匙串創(chuàng)建 CertificateSigningRequest.certSigningRequest 文件,如圖所示自動(dòng)生成就可以了。
2、在蘋果官方的 developer 上點(diǎn)擊創(chuàng)建證書,上傳步驟 1 中的 CertificateSigningRequest.certSigningRequest 文件,然后下載 .cer 證書文件。
3、這里需要注意不能直接把這個(gè) .cer 證書文件安裝到打包服務(wù)上,而是把這個(gè) .cer 先安裝到上面第 1 步中生成的 CertificateSigningRequest.certSigningRequest 的機(jī)器上,然后通過導(dǎo)出證書生成帶有密碼的 p12 證書文件,這個(gè)文件才是可以安裝到打包機(jī)器上的證書文件。
4、安裝證書,把 p12 文件放置到打包服務(wù)上,然后點(diǎn)擊證書,輸入 3 中創(chuàng)建時(shí)輸入的密碼,安裝到鑰匙串的 “登陸” ,這時(shí)候就可以看到鑰匙串證書里帶有 TeamId 的 Apple Distribution 證書。
5、需要額外注意安裝后可能會(huì)看到說“證書不受信任”的提示,這可能是因?yàn)闄C(jī)器上缺少 AppleWWDRCA (Apple Worldwide Developer Relations Certification Authority)證書,可以通過下面的地址進(jìn)行安裝解決:
三、配置描述文件
配置完證書后就是配置描述文件,在蘋果開發(fā)者網(wǎng)站的 Profiles 創(chuàng)建對(duì)應(yīng)的 mobile provision 。
1、選擇 Distribution - App Store 創(chuàng)建對(duì)應(yīng)的打包模式,如果是 QA 的話一般選擇 Ad Hoc ,也就是需要文件綁定設(shè)備 UDID ,而不需要上架 Store 的模式。
2、選擇需要支持的 App Id ,也就是 bundle Id 。
3、選擇前面生成的 Distribution 證書 ,這里主要一定要選擇同意同一個(gè)。
4、最后輸入 Provisioning Profile Name ,這個(gè) Name 在后面會(huì)有作用,另外如果是 Ad Hoc 的話,在這一步可以選擇已經(jīng)添加的 Devices 的 UDID 。
5、完成配置后下載這個(gè) mobile provision 文件,將它放到打包機(jī)器上的 /Users/你的賬號(hào)/Library/MobileDevice/Provisioning Profiles 目錄下,后面會(huì)需要用到它。
如果是 store 版本的就選擇
Distribution-App Store, 如果是 QA 版本的就選擇Distribution-Ad Hoc, 因?yàn)?App Store打出來的包只能通過 Store 或者官方 TestFight 下載,而Ad Hoc打包的可以通過內(nèi)部自定義分發(fā)下載(通過添加測(cè)試設(shè)備的 UDID)。
四、配置項(xiàng)目
完成了證書和描述文件的配置后,接下來就是針對(duì)項(xiàng)目的配置。
首先將需要打包的項(xiàng)目 clone 到打包機(jī)器上(只是為了做測(cè)試配置),然后打開項(xiàng)目 ios/Runner.xcworkspace 目錄,這時(shí)候可以看到項(xiàng)目因?yàn)闆]有開發(fā)者賬號(hào),是如下圖所示的狀態(tài):
然后我們?nèi)∠x購(gòu) Automatically manage signing , 然后選中我們前面放置的描述文件,就可以看到 Xcode 會(huì)自動(dòng)匹配到鑰匙串里的證書,然后顯示正常的證書和描述文件配置了。
這里有一個(gè)需要注意的點(diǎn),那就是項(xiàng)目在我們本地開發(fā)默認(rèn)使用的就是 Automatically manage signing 的方式,因?yàn)檫@樣比較方便,所以我們其實(shí)是需要在打包時(shí)讓它變成手動(dòng)簽名,并且指定 mobile provision 文件的模式。
所以前面在打包機(jī)器上操作 Xcode 取消 Automatically manage signing 指定描述文件后,其實(shí)已經(jīng)修改了項(xiàng)目的 ios/Runner.xcodeproj/project.pbxproj ,所以這時(shí)候你只需要通過 git diff 命令就可以導(dǎo)出一個(gè) patch 文件,這樣在項(xiàng)目被 clone 下來后,通過 git apply 直接調(diào)整項(xiàng)目的描述文件。
git diff >./release.patch
如果有多種編譯模式,比如一個(gè)項(xiàng)目打包多個(gè) bundleId 和描述文件(QA 、Release), 那就可以生成多個(gè) .patch 文件。
?? 注意:第三方打包機(jī)器上每次打包都是
clone一個(gè)新項(xiàng)目,打包后刪除該項(xiàng)目,這樣可以保證每次打包的獨(dú)立和干凈,而通過改生成不同的.patch文件,我們可以指向不同的mobile provision,從而加載不同的證書,甚至是同一個(gè)項(xiàng)目打包出不同的bundle id。
五、開始打包
1、開發(fā)打包之前,需要先執(zhí)行 security unlock-keychain -p xxxxx ,解鎖下 keychain ,這里的 xxxxx 就是你 Mac 上的密碼。
2、通過 flutter build ios --release 打包出 release 模式的 App.framework 和 Flutter.framework 。
3、通過 xcodebuild 命令,如下開始編譯 iOS 代碼了,其中 $PWD 是所在工作目錄:
xcodebuild -workspace Runner.xcworkspace -scheme Runner -sdk iphoneos -configuration Release archive -archivePath $PWD/build/Runner.xcarchive
??這里有一個(gè)需要注意,那就是打包過程中如果出現(xiàn) .sh 腳本的相關(guān)報(bào)錯(cuò),比如
xcode_backend.sh" embed_and_thin或者PhaseScriptExecution Thin\ Binary /Users/xxxxx/Library/Developer/Xcode/DerivedData/的錯(cuò)誤,推薦先在打包機(jī)上用 Xcode 執(zhí)行一次完整的Archive流程,在首次執(zhí)行過程應(yīng)該會(huì)出現(xiàn)關(guān)于某些 sh 的授權(quán)執(zhí)行彈框,輸入密碼點(diǎn)始終完成,然后再重新執(zhí)行上述腳本。
4、執(zhí)行完 Archive 之后,就可以進(jìn)入 export 階段,exportArchive 之前需要先準(zhǔn)備一個(gè) ExportOptions.plist 文件用戶指定到處的配置,模板類似:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>destination</key>
<string>export</string>
<key>method</key>
<string>app-store</string>
<key>provisioningProfiles</key>
<dict>
<key>你的 bundleId </key>
<string>前面 provision 定義的 name</string>
</dict>
<key>signingCertificate</key>
<string>Apple Distribution</string>
<key>signingStyle</key>
<string>manual</string>
<key>stripSwiftSymbols</key>
<true/>
<key>teamID</key>
<string>你的開發(fā)證書的 Team Id</string>
<key>uploadBitcode</key>
<false/>
<key>uploadSymbols</key>
<false/>
</dict>
</plist>
其中
-
method的數(shù)值如果是 store 就寫app-store,如果是 QA 就寫ad-hoc; -
provisioningProfiles的<dict>需要bundleId和前面provision定義的name; -
teamID需要的是你的開發(fā)證書的Team Id; - 如果是 store 可以增加
uploadBitcode和uploadSymbols的配置,如果是 QA 則可以不指定,然后 QA 可以也指定thinning模式;
接著通過指定命令 exportArchive ,指定 ExportOptions.plist ,如果是有不同 id 或者不同模式,一般需要配置 QA 和 Prod 兩種 ExportOptions.plist ,最終輸出到 package_path 這時(shí)候你就得到了一個(gè) ipa 文件。
xcodebuild -exportArchive -exportOptionsPlist ExportOptions.plist -archivePath $PWD/build/Runner.xcarchive -exportPath $package_path -allowProvisioningUpdates
最后如果是 store 模式的,接下來你只需要通過 Mac 的 Transporter 將 ipa 上傳到 App Store Connect,或者使用命令行工具將自己的應(yīng)用或內(nèi)容上傳至 App Store Connect 。
$ xcrun altool --validate-app -f file -t platform -u username [-p password] [--output-format xml]
$ xcrun altool --upload-app -f file -t platform -u username [-p password] [—output-format xml]
一般 altool 位于 /Applications/Xcode.app/Contents/Developer/usr/bin/altool ,更多可見 https://help.apple.com/asc/appsaltool/
如果你是 QA 模式,那么你需要先準(zhǔn)備一個(gè) html 文件,如下所示例子,通過 a 標(biāo)簽配置 itms-service 指定一個(gè) DistributionSummary.plist 文件。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<a href=itms-services://?action=download-manifest&url=https://xxxx.xxxx.cn/aaaa/bbbbb/ios/DistributionSummary.plist>install</a>
</body>
</html>
然后在 DistributionSummary.plist 文件中指定 software-package 的 ipa 下載地址,這樣就可以完成 QA 的內(nèi)部自助分發(fā)了。(只能安裝 QA provision 里已經(jīng)配置了 UDID 那些機(jī)器)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>items</key>
<array>
<dict>
<key>assets</key>
<array>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://xxxx.xxxxxx.cn/xxxx/Runner.ipa</string>
</dict>
<dict>
<key>kind</key>
<string>full-size-image</string>
<key>needs-shine</key>
<true/>
<key>url</key>
<string>http://xxxx.xxxxxx.cn/assets/applog/icon.png</string>
</dict>
<dict>
<key>kind</key>
<string>display-image</string>
<key>needs-shine</key>
<true/>
<key>url</key>
<string>http://xxxx.xxxxxx.cn/assets/applog/icon.png</string>
</dict>
</array>
<key>metadata</key>
<dict>
<key>bundle-identifier</key>
<string>com.xxxx.demo</string>
<key>bundle-version</key>
<string>1.0.0</string>
<key>kind</key>
<string>software</string>
<key>title</key>
<string>XXXX App download</string>
</dict>
</dict>
</array>
</dict>
</plist>
六、多 Flutter 版本環(huán)境
如果需求有存在多個(gè)項(xiàng)目需要在一個(gè)機(jī)器打包,但是不同項(xiàng)目的 Flutter 等版本都不同,那么對(duì)于 Mac 可以開啟多個(gè)不同的登陸用戶,這樣就可以得到不同的打包環(huán)境,當(dāng)然這里主要注意的是 CocoaPod 的版本問題,因?yàn)楸热?:
Flutter 1.22 版本默認(rèn)是使用 1.8.0 之類的 Pod 版本,如果在 Flutter 1.22 上使用 1.10.0 的 Pod 版本會(huì)導(dǎo)致 logo 錯(cuò)誤等問題;
Flutter 2.0 需要的是 1.10.0 的 Pod 版本;
而在 Mac 上默認(rèn) CocoaPod 是安裝在 usr/local/bin 目錄,這個(gè)目錄其實(shí)是多賬號(hào)共享,所以為了解決這個(gè)問題,需要在每個(gè)賬戶環(huán)境下安裝 rvm ,用于管理獨(dú)立的 CocoaPod 版本。
簡(jiǎn)單地說:
- 1、先通過 curl 安裝 rvm;
curl -L get.rvm.io | bash -s stable && source ~/.rvm/scripts/rvm
- 2、通過
rvm install 2.5.5安裝對(duì)應(yīng)的 ruby 版本,具體可以通過rvm list known選中你想要需要的版本
這里需要注意
rvm install可能會(huì)失敗,一般和 brew 需要 update 還有網(wǎng)絡(luò)情況有關(guān)系;
- 3、可以安裝多個(gè) ruby 版本,然后通過
rvm use <Version> --default或者rvm use <Version>來使用具體版本
不加
defalut的話,下次啟動(dòng)命令行會(huì)變成原來的defalut版本;
- 4、在當(dāng)前 ruby 版本下安裝想要的 cocoapods 版本,這樣當(dāng)使用
rvm use切換版本時(shí),cocoapods 版本也會(huì)跟著切換。
sudo gem install cocoapods -v <Version> -n /usr/local/bin
事實(shí)上在不同用戶下安裝了 rvm 之后,彼此之間的 Pod 版本就已經(jīng)分割開了。
七、最后
說了那么多,其實(shí) Xcode 自動(dòng)打包確實(shí)舒服很多,但是通過整個(gè)配置過程,也可以幫助你了解到以前不知道的打包和認(rèn)證過程。
這里最后額外補(bǔ)充一句,通過如下命令,在打包 Android 或者 iOS 時(shí),可以通過 --dart-define 來指定不同的 dart 參數(shù).
flutter build ios --release --dart-define=CHANNEL=GSY --dart-define=LANGUAGE=Dart
在 dart 代碼里可以通過 String.fromEnvironment 獲取到對(duì)應(yīng)的自定義配置參數(shù)。
const CHANNEL = String.fromEnvironment('CHANNEL');
const LANGUAGE = String.fromEnvironment('LANGUAGE');