iOS基于fastlane和jenkins的自動(dòng)化打包

在開(kāi)發(fā)中經(jīng)常需要打測(cè)試包,然后上傳至蒲公英等三方平臺(tái),這其中需要經(jīng)歷的操作為:

  • 拉取代碼
  • 設(shè)置項(xiàng)目的打包環(huán)境
  • 利用xcode進(jìn)行打包
  • 上傳至蒲公英等三方平臺(tái)

每一次打包上面的過(guò)程必不可少,而且都是手工的,本篇文章我們采用CD(Continuous Delivery)持續(xù)交付CI(Continuous Integration)持續(xù)集成來(lái)進(jìn)行自動(dòng)化打包一鍵操作,解放雙手,拒絕手動(dòng)的重復(fù)低效率勞動(dòng)。

CD(Continuous Delivery)

工廠里的裝配線以快速、自動(dòng)化、可重復(fù)的方式從原材料生產(chǎn)出消費(fèi)品。同樣,軟件交付管道以快速、自動(dòng)化和可重復(fù)的方式從源代碼生成發(fā)布版本,如何完成這項(xiàng)工作的總體設(shè)計(jì)稱為“持續(xù)交付”(CD),啟動(dòng)裝配線的過(guò)程稱為“持續(xù)集成”(CI)。持續(xù)交付是將應(yīng)用程序推送到交付環(huán)境的自動(dòng)化,大多數(shù)開(kāi)發(fā)團(tuán)隊(duì)通常具有一個(gè)或多個(gè)開(kāi)發(fā)和測(cè)試環(huán)境,在該環(huán)境中會(huì)進(jìn)行應(yīng)用程序更改以進(jìn)行測(cè)試和審查,本文介紹的Jenkins就可以完成上面打包過(guò)程必經(jīng)步驟的后三步。

CI(Continuous Integration)

CD往往和CI是一起配合著使用的,CI的概念很廣(可以自己百度)在本文主要用來(lái)完成上面打包過(guò)程必經(jīng)步驟的第一步,并執(zhí)行CD的動(dòng)作, 這樣就完成了上面所說(shuō)的四個(gè)步驟,同時(shí)在指定時(shí)間內(nèi)執(zhí)行CI動(dòng)作,本文使用jenkins來(lái)完成CI動(dòng)作。

使用CICD后開(kāi)發(fā)的具體流程將會(huì)是如下圖所示的:

fastlane和jenkins的安裝

fastlane的安裝

首先附上官網(wǎng)地址https://docs.fastlane.tools/里面會(huì)有詳細(xì)的使用教程。

  • 安裝Xcode Command Line Tools
    由于fastlane需要使用Xcode Command Line Tools所以使用此命令進(jìn)行安裝xcode-select --install,安裝完成后需要在Xcode -> Preferences -> Locations中選擇剛剛安裝的Command Line Tools。

  • 安裝bundle
    由于fastlane需要使用bundlegem來(lái)管理fastlane所使用的一些依賴,使用gem install bundler命名來(lái)進(jìn)行安裝。

  • 安裝xcbeautify
    由于打包需要使用使用xcodebuild來(lái)進(jìn)行打包,使用brew install xcbeautify進(jìn)行安裝,不然會(huì)報(bào)如下錯(cuò)誤:

  • 安裝fastlane
    使用brew install fastlane命令進(jìn)行安裝,當(dāng)然先要安裝Homebrew。

jenkins的安裝

直接使用brew install jenkins命令進(jìn)行安裝,安裝完成后使用brew services start jenkins來(lái)啟動(dòng)jenkins(同樣brew services stop jenkins 是關(guān)閉jenkins)在瀏覽器中輸入http://localhost:8080/進(jìn)入到jenkinsGUI管理頁(yè)面。接下來(lái)打開(kāi)Jenkins后會(huì)讓去一個(gè)填寫(xiě)password的頁(yè)面如下圖,存儲(chǔ)password的地方就是圖片上那行紅色字體目錄下,使用終端cat +紅色字體路徑就看到了。

然后安裝推薦的插件:


進(jìn)入后首先第一件事就是安裝Xcode integration插件,選擇系統(tǒng)管理’ -- ‘插件管理’‘,然后搜索進(jìn)行安裝。

至此上面的二個(gè)算是初步安裝配置完成。

項(xiàng)目的創(chuàng)建

為了更好的演示整個(gè)流程,我們采用新建一個(gè)項(xiàng)目進(jìn)行講解,同時(shí)利用.xcconfig文件來(lái)進(jìn)行環(huán)境的管理,具體build configuration文件的使用可見(jiàn)我另外一篇文章iOS開(kāi)發(fā)中xconfig和script腳本的使用,項(xiàng)目新建了一個(gè)stage環(huán)境:


同時(shí)利用Custom Flags來(lái)切換環(huán)境:


stage.xcconfig中定了APP_NAME = AutoBuildStagedebug.xcconfig中定了APP_NAME = AutoBuildDebug,release.xcconfig中定了APP_NAME = AutoBuildRelease,同時(shí)并在info.plist增加Bundle display name這個(gè)key并設(shè)置為$(APP_NAME)


ViewController中的代碼如下:

import UIKit
class ViewController: UIViewController {
    @IBOutlet weak var envLabel: UILabel!
    override func viewDidLoad() {
        super.viewDidLoad()
        #if Debug
        envLabel.text = "環(huán)境是Debug"
        #elseif STG
        envLabel.text = "環(huán)境是Stage"
        #elseif Release
        envLabel.text = "環(huán)境是Release"
        #endif        
    }
}

這樣配置后在Edit Scheme中選擇不同的build configuration則會(huì)envLabel顯示不用的環(huán)境信息,同時(shí)安裝的APP名字也會(huì)是.xcconfig文件中所設(shè)置的值。

利用fastlane初始化項(xiàng)目

cd到項(xiàng)目所在的目錄利用fastlane init swift進(jìn)行初始化,會(huì)讓選擇構(gòu)建的方式,這里輸入4采用自定義的方式進(jìn)行構(gòu)建。

fastlane現(xiàn)在是支持使用swift的,這里我們采用熟悉的swift來(lái)編寫(xiě)腳本,當(dāng)然swift寫(xiě)的腳本也是可以裝換成Ruby格式的


構(gòu)建完成會(huì)自動(dòng)生成如下文件:


項(xiàng)目設(shè)置手動(dòng)簽名

在寫(xiě)打包代碼前,首先設(shè)置項(xiàng)目的簽名采用手動(dòng)簽名,項(xiàng)目較大團(tuán)隊(duì)較多時(shí)一定要采用手動(dòng)簽名的方式,不要使用Xcode的自動(dòng)簽名方式,因?yàn)橐坏┠硞€(gè)人的簽名出問(wèn)題就會(huì)導(dǎo)致其他人拉完代碼后也出問(wèn)題,所以本項(xiàng)目采用手動(dòng)簽名的方式,在此先導(dǎo)出自己的.p12證書(shū)和描述文件以備后面使用。

編寫(xiě)自動(dòng)打包的代碼

打開(kāi)fastlane/swift/FastlaneSwiftRunner/FastlaneSwiftRunner.xcodeproj所在的工程文件,并打開(kāi)Fastfile.swift文件里面代碼如下所示:

import Foundation
class Fastfile: LaneFile {
    func customLane() {
    desc("Description of what the lane does")
        // add actions here: https://docs.fastlane.tools/actions
    }
}

Fastfile類中函數(shù)以lane結(jié)尾的稱之為一個(gè)lane,在終端中通過(guò)fastlane <laneName>命令來(lái)執(zhí)行這個(gè)lane,其實(shí)就等于執(zhí)行這個(gè)函數(shù)。

  • 首先新建一個(gè)Configuration協(xié)議
    因?yàn)轫?xiàng)目配置了DebugReleasestage三個(gè)Configuration,所以利用協(xié)議能更好的改變Configuration。
protocol Configuration {
    /// file name of the certificate
    var certificate: String { get }
    /// file name of the provisioning profile
    var provisioningProfile: String { get }
    /// configuration name in xcode project
    var buildConfiguration: String { get }
    /// the app id for this configuration
    var appIdentifier: String { get }
    /// export methods, such as "ad-doc" or "appstore"
    var exportMethod: String { get }
}
  • 新建ProjectSetting枚舉
    新建一個(gè)ProjectSetting枚舉來(lái)保存一些項(xiàng)目打包過(guò)程中需要的常用配置信息。
enum ProjectSetting {
    static var workspace = " iOSAutoBuild.xcworkspace"
    static var project = "iOSAutoBuild.xcodeproj"
    static var scheme = "iOSAutoBuild"
    static var target = "iOSAutoBuild"
    static var productName = "iOSAutoBuild"
    static let devices: [String] = ["iPhone 8", "iPad Air"]

    static let codeSigningPath = "certs"
    // 采用環(huán)境變量的方式更加安全
    static let keyChainDefaultPath = environmentVariable(get: "KEYCHAIN_DEFAULT_PATH").replacingOccurrences(of: "\"", with: "")
    static let certificatePassword = ""
    static let sdk = "iphoneos15.2"
    
    // 蒲公英平臺(tái)的信息
    static let pgyerApiKey = "xxxxxxxxxxxx";// 填入自己在平臺(tái)申請(qǐng)的
    static let pgyerUserKey = "xxxxxxxxxxxxx";//  填入自己在平臺(tái)申請(qǐng)的
}

對(duì)于keyChainDefaultPath變量采用了environmentVariable(get:)的方法來(lái)獲取系統(tǒng)的環(huán)境變量,系統(tǒng)的環(huán)境變量在終端中設(shè)置的命令為:export keyChainDefaultPath =”YOUR_ keyChainDefaultPath”,采用系統(tǒng)變量的好處是更加安全,不用在代碼中體現(xiàn)敏感信息。

  • 定義一個(gè)Configuration
struct Staging: Configuration {
    var certificate = "AppleDis"
    var provisioningProfile = "AdHocMLife"
    var buildConfiguration = "stage"
    var appIdentifier = "com.mamba.iOSAutoBuild"
    var exportMethod = "ad-hoc"
}

這樣想切換環(huán)境時(shí)只需要更改buildConfiguration變量即可。

  • 打包時(shí)的簽名操作
class Fastfile: LaneFile {
    var stubKeyChainPassword: String = environmentVariable(get: "KEYCHAIN_PASSWORD")
    var keyChainName: String {
        return "\(ProjectSetting.productName).keychain"
    }
    var keyChainDefaultFilePath: String {
        return "\(ProjectSetting.keyChainDefaultPath)/\(keyChainName)-db"
    }
   func package(config: Configuration) {
     if FileManager.default.fileExists(atPath: keyChainDefaultFilePath) {
            deleteKeychain(name: "\(keyChainName)")
        }
        
        // 新建一個(gè)以項(xiàng)目名命名的鑰匙串
        createKeychain(
            name: "\(keyChainName)",
            password: stubKeyChainPassword,
            defaultKeychain: false,
            unlock: true,
            timeout: 3600,
            lockWhenSleeps: true
        )
        
        // 導(dǎo)入證書(shū)到自定義的鑰匙串
        importCertificate(
            certificatePath: "\(ProjectSetting.codeSigningPath)/\(config.certificate).p12",
            certificatePassword: "\(ProjectSetting.certificatePassword)",
            keychainName: keyChainName,
            keychainPassword: "\(stubKeyChainPassword)"
        )
        
        // 更新項(xiàng)目的簽名設(shè)置
        updateProjectProvisioning(
            xcodeproj: "\(ProjectSetting.project)",
            profile: "\(ProjectSetting.codeSigningPath)/\(config.provisioningProfile).mobileprovision",
            targetFilter: "^\(ProjectSetting.target)$",
            buildConfiguration: "\(config.buildConfiguration)",
            certificate: "\(config.certificate).p12"
        )
}
}

特別注意updateProjectProvisioning是會(huì)更改項(xiàng)目的設(shè)置的,所以最好是打包的機(jī)器是額外的一臺(tái)機(jī)器(熟稱打包機(jī)),當(dāng)然CD是不會(huì)提交代碼到遠(yuǎn)端倉(cāng)庫(kù)的,上面的一些action可以查看fastlane的官網(wǎng),里面有詳細(xì)介紹。

  • 打包構(gòu)建buildApp
      buildApp(
            scheme: "\(ProjectSetting.scheme)",
            clean: true,
            outputDirectory: "./打包目錄",
            outputName: "\(ProjectSetting.productName)_\(dateStr).ipa",
            configuration: "\(config.buildConfiguration)",
            silent: true,
            exportMethod: "\(config.exportMethod)",
            exportOptions: optionsArr,
            sdk: "\(ProjectSetting.sdk)"
        )

dateStr是當(dāng)前時(shí)間變量,代碼中沒(méi)有體現(xiàn)。

  • 最后是打包
    func developerStrageLane() {
        desc("Mamba Create a developer stage")
        package(config: Staging())
    }

至此我們就可以利用fastlane進(jìn)行打包了,在終端中輸入bundle exec fastlane developerStrage即可,最后會(huì)有打包完成的成功信息大致如下;

聯(lián)動(dòng)jenkins

上面的部分只是完成了打包,現(xiàn)在繼續(xù)利用jenkins來(lái)完成定時(shí)的遠(yuǎn)端代碼拉取,并打包同時(shí)長(zhǎng)傳蒲公英。fastlane現(xiàn)在支持swift,同時(shí)也是支持第三方插件的,首先對(duì)上面的fastlane過(guò)程中加入cocoapods和蒲公英的插件。

加入cocoapods

由于前面說(shuō)了fastlane是利用gembundle來(lái)管理三方依賴的,所以打開(kāi)Gemfile文件,增加gem "cocoapods"保存,然后當(dāng)我們?cè)诮K端執(zhí)行bundle install — path vendor/bundler時(shí)會(huì)安裝Gemfile文件中的依賴,當(dāng)然一般我們的打包機(jī)是自己安裝了cocoapods的。

  • 執(zhí)行cocoapods
    在上面的package方法上面增加如下代碼:
    func beforeAll() {
        cocoapods()
    }

加入蒲公英插件

  • pgyer插件的安裝
    插件安裝前修改gemfile文件,增加如下信息:
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
eval_gemfile(plugins_path) if File.exist?(plugins_path)

按照官網(wǎng)的說(shuō)法是需要鏈接gemfile和后面生成的Pluginfile文件,cd到項(xiàng)目所在的目錄執(zhí)行sudo fastlane add_plugin pgyer,會(huì)發(fā)現(xiàn)增加了一個(gè)Pluginfile文件,具體的改變直接放官網(wǎng)說(shuō)的吧:

  • 執(zhí)行pgyer的action
    直接使用下面代碼上傳至蒲公英
  pgyer(apiKey:"\(ProjectSetting.pgyerApiKey)", userKey: "\(ProjectSetting.pgyerUserKey)")

新建jenkins任務(wù)

進(jìn)入jenkins主頁(yè),點(diǎn)擊新建任務(wù),輸入任務(wù)名后選擇構(gòu)建自由風(fēng)格的軟件項(xiàng)目。


源碼管理處填入遠(yuǎn)端倉(cāng)庫(kù)地址。


構(gòu)建觸發(fā)器中輸入定時(shí)構(gòu)建的時(shí)間。

具體的時(shí)間語(yǔ)法百度即可,這里是每天每隔半小時(shí)自動(dòng)執(zhí)行任務(wù)。

構(gòu)建處選擇執(zhí)行shell,并填入需要執(zhí)行的命令。

注意:這里可以填入上文提到的系統(tǒng)變量,例如在cd語(yǔ)句的后面加上export CERTIFICATE_PASSWORD=”xxx”或者bundle install — path vendor/bundler。

最后點(diǎn)擊保存即可,可在構(gòu)建歷史中查看歷史構(gòu)建記錄。

構(gòu)建任務(wù)的過(guò)程中可能會(huì)報(bào)bundle :fastlane command not find的錯(cuò)誤,解決辦法是在jenkins系統(tǒng)管理->系統(tǒng)設(shè)置->全局屬性->環(huán)境變量 增加鍵PATH值:終端輸出值:(echo $PATH

總結(jié):

本文主要介紹了CDCI在軟件開(kāi)發(fā)中的運(yùn)用,利用fastlanejenkins,并配合swift來(lái)實(shí)現(xiàn)iOS項(xiàng)目的自動(dòng)化打包,并進(jìn)行了實(shí)際的Demo演示,實(shí)際操作可能因環(huán)境不同報(bào)錯(cuò),只需要仔細(xì)閱讀終端錯(cuò)誤提示并進(jìn)行相應(yīng)修復(fù)即可,fastlane的錯(cuò)誤提示機(jī)制還是相對(duì)友善的。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容