懒到极致之怒撸一键打包发布系统
一切得從上個版本的打包發布說起。
開發中本人負責了iOS包的版本發布工作。iOS打包:不就是選一下證書,再在Xcode上點幾下按鈕,IDE全都給你設置好流程了,有必要這么麻煩嗎?
誠然,如果只是打包,在不考慮團隊協同合作、打包效率、重復工作量的前提下,使用Xcode自帶的打包方式當然是沒問題的。但實際開發中,每次打包大概包含以下流程: 拉取最新代碼(SVN或Git) → 編譯通過 → 設置打包環境(開發、測試、生成等) → 導出IPA包 → 上傳IPA包(App Store或者企業包上傳至指定服務器)
可以看出,其中的很多步驟都是機械重復的,特別當進入測試驗收階段,有時每修復幾個bug就要重新打包發布測試,比如上個版本的時候新功能主要是隊友在開發,測試到最后頻繁地讓我打包發布,不停地打斷我的工作去重復機械的事情,這簡直就是在浪費人生啊!!!???
雖然之前為了打包方便,我已經整理了一份腳本打包的流程(傳送門:Shell腳本——Xcode腳本打包),但還是不夠方便快捷,乘著新版發布后的空檔期,擼出 一鍵打包發布系統 ,功能包括: 自動拉取Git最新代碼 → 自動選擇簽名證書并打包導出ipa文件 → Git自動同步代碼→ 自動上傳ipa包(我們是企業包,上傳至自己的服務器,這一步是可選的)
一鍵打包
項目鏈接地址
一鍵打包資源(ArchiveSource)簡介:
- Code文件夾下是打包應用程序源代碼
- Source文件夾下是打包資源
Source文件夾:
一鍵打包步驟:
啟動Xcode,手動選擇簽名證書(如果默認簽名失敗的話)
打包生成的IPA包路徑說明:
../項目所在路徑/Archiving/(以AppId命名的文件夾)/(App名+版本號命名的文件夾)/(以打包時間命名的文件夾)
示例: ../Archiving/(AppID)/(App名)_3_1_29/2018_04_10_11:18:51
一鍵打包原理
一鍵打包發布系統其實很簡單:開發一款Mac應用,應用啟動時讀取本機已有的Certificates和Provisioning Profiles信息,再在應用內調用Shell腳本,主要通過腳本來實現Git同步以及打包的相關操作。
-
Shell腳本調用
Objective-C中調用Shell腳本可以使用 NSTask 。通過NSTask,我們可以在應用中調用另一個程序或運行一段腳本并獲得其執行狀態和最終結果,NSTask最為常用的一個場景是為命令行操作提供圖形化的界面。
//創建一個新的TaskNSTask *optTask = [[NSTask alloc] init];//設置調用路徑optTask.launchPath = shellPath;//設置調用參數(被調用程序命令)optTask.arguments = @[@"-ls"];//創建輸出PipeNSPipe *outputPipe = [NSPipe pipe];[optTask setStandardOutput:outputPipe];//創建錯誤輸出PipeNSPipe *errorPipe = [NSPipe pipe];[optTask setStandardError:errorPipe];//執行完成Block 通知optTask.terminationHandler = ^(NSTask *theTask) {[theTask.standardOutput fileHandleForReading].readabilityHandler = nil;[theTask.standardError fileHandleForReading].readabilityHandler = nil;};//錯誤輸出[[errorPipe fileHandleForReading] setReadabilityHandler:^(NSFileHandle *file) {NSData *data = [file availableData];NSString *errorMsg = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];NSLog(@"errorMsg = %@",errorMsg);}];//執行結果輸出[[outputPipe fileHandleForReading] setReadabilityHandler:^(NSFileHandle *file) {NSData *data = [file availableData];NSString *msg = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];NSLog(@"msg = %@",msg);}];//開啟執行[optTask launch];//阻塞直到執行完畢(NSTask默認是異步執行,如果有同步需求,可調用waitUntilExit()方法)[optTask waitUntilExit];[[outputPipe fileHandleForReading] closeFile];[[errorPipe fileHandleForReading] closeFile]; 復制代碼-
自動選擇簽名證書
獲取電腦中以iPhone Distribution和iPhone Developer命名開頭的Certificates:
+ (void)loadCerListBlock:(CerListBlock)listBlock {NSDictionary *options = @{(__bridge id)kSecClass: (__bridge id)kSecClassCertificate,(__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitAll};CFArrayRef certs = NULL;__unused OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)options, (CFTypeRef *)&certs);NSArray *certificates = CFBridgingRelease(certs);NSMutableArray *tempArray=[NSMutableArray array];for (int i=0;i<[certificates count];i++) {SecCertificateRef certificate = (__bridge SecCertificateRef)([certificates objectAtIndex:i]);NSString *name = CFBridgingRelease(SecCertificateCopySubjectSummary(certificate));if ([name hasPrefix:@"iPhone Distribution"]||[name hasPrefix:@"iPhone Developer"]) {[tempArray addObject:name];}}listBlock(tempArray); } 復制代碼獲取電腦中Provisioning Profiles:
//獲取電腦中滿足條件的Provisioning Profiles的路徑 + (NSArray *)getAllProvisioningProfileList{NSString *path=[NSString stringWithFormat:@"%@/%@", NSHomeDirectory(), kMobileprovisionDirName];NSFileManager *fileManager=[NSFileManager defaultManager];NSArray *provisioningProfiles =[fileManager contentsOfDirectoryAtPath:path error:nil];provisioningProfiles = [provisioningProfiles filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"pathExtension IN %@", @[@"mobileprovision", @"provisionprofile"]]];return provisioningProfiles; }//自定義 YAProvisioningProfile ,將profile轉換為對應實體 YAProvisioningProfile *profile = [[YAProvisioningProfile alloc] initWithPath:path]; 復制代碼根據App ID匹配 Profile 描述文件:
//根據appBundleIdentifier匹配 profile 描述文件 YAProvisioningProfile *selectProfile = nil; for (YAProvisioningProfile *profile in profileArray) {// 查找與bundleIdentifier相等,描述文件不以 XC: 命名開頭,而且是最新的文件if ([profile.bundleIdentifier isEqualToString:appBundleIdentifier] && ![profile.name hasPrefix:@"XC:"] && profile.newest) {selectProfile = profile;break;} } 復制代碼-
讀寫Xcode工程配置
右鍵 XXX.xcodeproj 文件,顯示包內容,可以看到project.pbxproj。project.pbxproj存儲著 Xcode 工程的各項配置參數,我們可以通過腳本來直接讀取或修改其配置,執行效果等同于在Xcode的General、Build Settings中的修改。
讀取App Bundler Identifier:
# Pbxproj_Path指向 project.pbxproj 文件路徑 configuration=$(grep -i "PRODUCT_BUNDLE_IDENTIFIER =" ${Pbxproj_Path}) Array=($(echo $configuration)) # project.pbxproj 中存在多行PRODUCT_BUNDLE_IDENTIFIER信息,從后往前讀取 Bundle_Identifier=${Array[5]} if [ ! -n $Bundle_Identifier ] thenBundle_Identifier=${Array[3]} fi echo "Bundle_Identifier = ${Bundle_Identifier}" 復制代碼修改配置信息: 修改 project.pbxproj 配置可以通過 sed 命令實現,比如將簽名類型指定為Manual
key="CODE_SIGN_STYLE" value="Manual;" # 修改 project.pbxproj 配置,指定簽名類型為手動選擇 sed -i "" "s/$key =.*$/$key = $value/g" $Pbxproj_Path 復制代碼如果程序成功讀取到對應的證書簽名,那么打包前需要修改的配置包括:
#修改 PRODUCT_BUNDLE_IDENTIFIER changeConfiguration "PRODUCT_BUNDLE_IDENTIFIER" "${APP_ID};"#修改 PROVISIONING_PROFILE changeConfiguration "PROVISIONING_PROFILE" "\"${Profile_Name}\";"#修改 PROVISIONING_PROFILE_SPECIFIER changeConfiguration "PROVISIONING_PROFILE_SPECIFIER" "${Profile_Specifier};"#修改 PRODUCT_NAME changeConfiguration "PRODUCT_NAME" "${Scheme_Name};"#修改 "CODE_SIGN_IDENTITY[sdk=iphoneos*]" changeConfiguration "\"CODE_SIGN_IDENTITY\[sdk=iphoneos\*\]\"" '"iPhone Distribution";'#修改 CODE_SIGN_STYLE changeConfiguration "CODE_SIGN_STYLE" "Manual;"#修改 ProvisioningStyle changeConfiguration "ProvisioningStyle" "Manual;"#修改 DEVELOPMENT_TEAM changeConfiguration "DEVELOPMENT_TEAM" "${Development_Team};"#修改 DevelopmentTeam changeConfiguration "DevelopmentTeam" "${Development_Team};" 復制代碼修改項目版本號:
Info.plist文件中CFBundleShortVersionString對應Version號,CFBundleVersion對應Build號,使用腳本可以直接指定版本號
# info.plist文件路徑 InfoPlist_Path="${Project_Path}/${App_Name}/Info.plist" # 如果是舊的項目,info.plist文件對應的名字為 (App_Name)-Info.plist if [ ! -e "${InfoPlist_Path}" ];thenInfoPlist_Path="${Project_Path}/${App_Name}/${App_Name}-Info.plist" fi/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString ${Version_String}" ${InfoPlist_Path} /usr/libexec/PlistBuddy -c "Set :CFBundleVersion ${Version_String}" ${InfoPlist_Path} 復制代碼-
打包項目
# 指定打包模式為ReleaseConfiguration="Release"# 判斷編譯的項目類型是workspace還是projectif [[ ${Is_Workspace} == "YES" ]]; then# 編譯前清理工程,>> ${Log_path} 表示將日志輸出寫入到Log_path文件xcodebuild clean -configuration "${Configuration}" -alltargets >> ${Log_path}# 自定義簽名,指定簽名證書if [[ ${Custom_Sign} == "YES" ]]; thenxcodebuild archive -workspace "${Workspace_Path}" \-scheme "${Scheme_Name}" \-archivePath "${Xcarchive_path}" \-configuration "${Configuration}" \PROVISIONING_PROFILE="${Profile_Name}" \CODE_SIGN_IDENTITY="${Sign_Identity}" \>> ${Log_path}elsexcodebuild archive -workspace "${Workspace_Path}" \-scheme "${Scheme_Name}" \-archivePath "${Xcarchive_path}" \-configuration "${Configuration}" \>> ${Log_path}fielse# 編譯前清理工程xcodebuild clean -configuration "${Configuration}" -alltargets >> ${Log_path}if [[ ${Custom_Sign} == "YES" ]]; thenxcodebuild archive -project "${Xcodeproj_Path}" \-scheme "${Scheme_Name}" \-archivePath "${Xcarchive_path}" \-configuration "${Configuration}" \PROVISIONING_PROFILE="${Profile_Name}" \CODE_SIGN_IDENTITY="${Sign_Identity}"\>> ${Log_path}elsexcodebuild archive -project "${Xcodeproj_Path}" \-scheme "${Scheme_Name}" \-archivePath "${Xcarchive_path}" \-configuration "${Configuration}" \>> ${Log_path}fifi 復制代碼 -
導出IPA包
xcodebuild -exportArchive -archivePath "${Xcarchive_path}" -exportPath "${IPA_Archiving_Path}" -exportOptionsPlist "${ExportOptionsPlistPath}" >> ${Log_path} 復制代碼
Xcarchive_path指向上一步打包生成的*.xcarchive文件路徑;IPA_Archiving_Path指向導出的*.ipa文件所在路徑;ExportOptionsPlistPath對應ExportOptions.plist文件路徑,ExportOptions.plist是使用xcodebuild -exportArchive指令導出ipa包時需要指定的配置文件
- compileBitcode:不上架App Store,Xcode是否啟用Bitcode重新編譯,默認為YES。
- method:歸檔類型,包括app-store、ad-hoc、 package、enterprise、development以及developer-id。
- provisioningProfiles:打包證書信息,包含的字典信息格式:<key為App ID>:<value為對應的profile文件名>。
- uploadBitcode:上線App Store是否開啟Bitcode,默認為YES。
- uploadSymbols:上線App Store,是否開啟符號序列化,這是與查crash相關的,默認為YES。
關于更多的xcodebuild指令,可以通過xcodebuild -help查看。
-
其他
其他部分還包括Git代碼管理、IPA包上傳、無效資源刪除等,都可以直接通過NSTask執行腳本命令實現,比如拉取Git代碼:git pull origin,當然前提是你本地的Git已經配置為免密操作。 如果你是使用SourceTree進行Git管理,而且是http模式,那么可以這樣設置免密操作,第3步將遠程倉庫路徑編輯為 http://用戶名:密碼@倉庫地址 這樣的方式;當然如果你是SSH模式,那么本身就支持免密碼操作了。
上傳IPA包。 我這里打的是企業包,所以只要將ipa文件上傳到指定服務器就能夠下載了(想了解更多關于企業包的下載信息,可以參照我的另一篇文章iOS如何部署企業包,以供他人下載)。一開始想著將自動上傳也集成到打包程序內,但最后發現這涉及到內網間不同服務器以及賬號驗證過程,太過復雜暫時將這一部分閹割了……
寫在最后
以上便是一鍵打包系統的功能講解,想了解更多可以查看源碼
CJShellDemo 說明:
ArchiveSource 一鍵打包程序資源 CrashScript 線上crash查找腳本 ReleaseDir Xcode打包腳本
歡迎點贊以及GitHub Star?
轉載于:https://juejin.im/post/5ad9aeb66fb9a07aa6315431
總結
以上是生活随笔為你收集整理的懒到极致之怒撸一键打包发布系统的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【C++多线程系列】【四】将类的成员函数
- 下一篇: 联通和阿里云合作 试点打通全国IT系统