iOS基于fastlane和jenkins的自动化打包

在开发中经常需要打测试包,然后上传至蒲公英等三方平台,这其中需要经历的操作为:

  • 拉取代码
  • 设置项目的打包环境
  • 利用xcode进行打包
  • 上传至蒲公英等三方平台

每一次打包上面的过程必不可少,而且都是手工的,本篇文章我们采用CD(Continuous Delivery)持续交付CI(Continuous Integration)持续集成来进行自动化打包一键操作,解放双手,拒绝手动的重复低效率劳动。

CD(Continuous Delivery)

工厂里的装配线以快速、自动化、可重复的方式从原材料生产出消费品。同样,软件交付管道以快速、自动化和可重复的方式从源代码生成发布版本,如何完成这项工作的总体设计称为“持续交付”(CD),启动装配线的过程称为“持续集成”(CI)。持续交付是将应用程序推送到交付环境的自动化,大多数开发团队通常具有一个或多个开发和测试环境,在该环境中会进行应用程序更改以进行测试和审查,本文介绍的Jenkins就可以完成上面打包过程必经步骤的后三步。

CI(Continuous Integration)

CD往往和CI是一起配合着使用的,CI的概念很广(可以自己百度)在本文主要用来完成上面打包过程必经步骤的第一步,并执行CD的动作, 这样就完成了上面所说的四个步骤,同时在指定时间内执行CI动作,本文使用jenkins来完成CI动作。

使用CICD后开发的具体流程将会是如下图所示的:

fastlane和jenkins的安装

fastlane的安装

首先附上官网地址https://docs.fastlane.tools/里面会有详细的使用教程。

  • 安装Xcode Command Line Tools
    由于fastlane需要使用Xcode Command Line Tools所以使用此命令进行安装xcode-select --install,安装完成后需要在Xcode -> Preferences -> Locations中选择刚刚安装的Command Line Tools

  • 安装bundle
    由于fastlane需要使用bundlegem来管理fastlane所使用的一些依赖,使用gem install bundler命名来进行安装。

  • 安装xcbeautify
    由于打包需要使用使用xcodebuild来进行打包,使用brew install xcbeautify进行安装,不然会报如下错误:

  • 安装fastlane
    使用brew install fastlane命令进行安装,当然先要安装Homebrew

jenkins的安装

直接使用brew install jenkins命令进行安装,安装完成后使用brew services start jenkins来启动jenkins(同样brew services stop jenkins 是关闭jenkins)在浏览器中输入http://localhost:8080/进入到jenkinsGUI管理页面。接下来打开Jenkins后会让去一个填写password的页面如下图,存储password的地方就是图片上那行红色字体目录下,使用终端cat +红色字体路径就看到了。

然后安装推荐的插件:


进入后首先第一件事就是安装Xcode integration插件,选择系统管理’ -- ‘插件管理’‘,然后搜索进行安装。

至此上面的二个算是初步安装配置完成。

项目的创建

为了更好的演示整个流程,我们采用新建一个项目进行讲解,同时利用.xcconfig文件来进行环境的管理,具体build configuration文件的使用可见我另外一篇文章iOS开发中xconfig和script脚本的使用,项目新建了一个stage环境:


同时利用Custom Flags来切换环境:


stage.xcconfig中定了APP_NAME = AutoBuildStagedebug.xcconfig中定了APP_NAME = AutoBuildDebugrelease.xcconfig中定了APP_NAME = AutoBuildRelease,同时并在info.plist增加Bundle display name这个key并设置为$(APP_NAME)


ViewController中的代码如下:

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

这样配置后在Edit Scheme中选择不同的build configuration则会envLabel显示不用的环境信息,同时安装的APP名字也会是.xcconfig文件中所设置的值。

利用fastlane初始化项目

cd到项目所在的目录利用fastlane init swift进行初始化,会让选择构建的方式,这里输入4采用自定义的方式进行构建。

fastlane现在是支持使用swift的,这里我们采用熟悉的swift来编写脚本,当然swift写的脚本也是可以装换成Ruby格式的


构建完成会自动生成如下文件:


项目设置手动签名

在写打包代码前,首先设置项目的签名采用手动签名,项目较大团队较多时一定要采用手动签名的方式,不要使用Xcode的自动签名方式,因为一旦某个人的签名出问题就会导致其他人拉完代码后也出问题,所以本项目采用手动签名的方式,在此先导出自己的.p12证书和描述文件以备后面使用。

编写自动打包的代码

打开fastlane/swift/FastlaneSwiftRunner/FastlaneSwiftRunner.xcodeproj所在的工程文件,并打开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类中函数以lane结尾的称之为一个lane,在终端中通过fastlane <laneName>命令来执行这个lane,其实就等于执行这个函数。

  • 首先新建一个Configuration协议
    因为项目配置了DebugReleasestage三个Configuration,所以利用协议能更好的改变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枚举
    新建一个ProjectSetting枚举来保存一些项目打包过程中需要的常用配置信息。
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"
    // 采用环境变量的方式更加安全
    static let keyChainDefaultPath = environmentVariable(get: "KEYCHAIN_DEFAULT_PATH").replacingOccurrences(of: "\"", with: "")
    static let certificatePassword = ""
    static let sdk = "iphoneos15.2"
    
    // 蒲公英平台的信息
    static let pgyerApiKey = "xxxxxxxxxxxx";// 填入自己在平台申请的
    static let pgyerUserKey = "xxxxxxxxxxxxx";//  填入自己在平台申请的
}

对于keyChainDefaultPath变量采用了environmentVariable(get:)的方法来获取系统的环境变量,系统的环境变量在终端中设置的命令为:export keyChainDefaultPath =”YOUR_ keyChainDefaultPath”,采用系统变量的好处是更加安全,不用在代码中体现敏感信息。

  • 定义一个Configuration
struct Staging: Configuration {
    var certificate = "AppleDis"
    var provisioningProfile = "AdHocMLife"
    var buildConfiguration = "stage"
    var appIdentifier = "com.mamba.iOSAutoBuild"
    var exportMethod = "ad-hoc"
}

这样想切换环境时只需要更改buildConfiguration变量即可。

  • 打包时的签名操作
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)")
        }
        
        // 新建一个以项目名命名的钥匙串
        createKeychain(
            name: "\(keyChainName)",
            password: stubKeyChainPassword,
            defaultKeychain: false,
            unlock: true,
            timeout: 3600,
            lockWhenSleeps: true
        )
        
        // 导入证书到自定义的钥匙串
        importCertificate(
            certificatePath: "\(ProjectSetting.codeSigningPath)/\(config.certificate).p12",
            certificatePassword: "\(ProjectSetting.certificatePassword)",
            keychainName: keyChainName,
            keychainPassword: "\(stubKeyChainPassword)"
        )
        
        // 更新项目的签名设置
        updateProjectProvisioning(
            xcodeproj: "\(ProjectSetting.project)",
            profile: "\(ProjectSetting.codeSigningPath)/\(config.provisioningProfile).mobileprovision",
            targetFilter: "^\(ProjectSetting.target)$",
            buildConfiguration: "\(config.buildConfiguration)",
            certificate: "\(config.certificate).p12"
        )
}
}

特别注意updateProjectProvisioning是会更改项目的设置的,所以最好是打包的机器是额外的一台机器(熟称打包机),当然CD是不会提交代码到远端仓库的,上面的一些action可以查看fastlane的官网,里面有详细介绍。

  • 打包构建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是当前时间变量,代码中没有体现。

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

至此我们就可以利用fastlane进行打包了,在终端中输入bundle exec fastlane developerStrage即可,最后会有打包完成的成功信息大致如下;

联动jenkins

上面的部分只是完成了打包,现在继续利用jenkins来完成定时的远端代码拉取,并打包同时长传蒲公英。fastlane现在支持swift,同时也是支持第三方插件的,首先对上面的fastlane过程中加入cocoapods和蒲公英的插件。

加入cocoapods

由于前面说了fastlane是利用gembundle来管理三方依赖的,所以打开Gemfile文件,增加gem "cocoapods"保存,然后当我们在终端执行bundle install — path vendor/bundler时会安装Gemfile文件中的依赖,当然一般我们的打包机是自己安装了cocoapods的。

  • 执行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)

按照官网的说法是需要链接gemfile和后面生成的Pluginfile文件,cd到项目所在的目录执行sudo fastlane add_plugin pgyer,会发现增加了一个Pluginfile文件,具体的改变直接放官网说的吧:

  • 执行pgyer的action
    直接使用下面代码上传至蒲公英
  pgyer(apiKey:"\(ProjectSetting.pgyerApiKey)", userKey: "\(ProjectSetting.pgyerUserKey)")

新建jenkins任务

进入jenkins主页,点击新建任务,输入任务名后选择构建自由风格的软件项目


源码管理处填入远端仓库地址。


构建触发器中输入定时构建的时间。

具体的时间语法百度即可,这里是每天每隔半小时自动执行任务。

构建处选择执行shell,并填入需要执行的命令。

注意:这里可以填入上文提到的系统变量,例如在cd语句的后面加上export CERTIFICATE_PASSWORD=”xxx”或者bundle install — path vendor/bundler

最后点击保存即可,可在构建历史中查看历史构建记录。

构建任务的过程中可能会报bundle :fastlane command not find的错误,解决办法是在jenkins系统管理->系统设置->全局属性->环境变量 增加键PATH值:终端输出值:(echo $PATH

总结:

本文主要介绍了CDCI在软件开发中的运用,利用fastlanejenkins,并配合swift来实现iOS项目的自动化打包,并进行了实际的Demo演示,实际操作可能因环境不同报错,只需要仔细阅读终端错误提示并进行相应修复即可,fastlane的错误提示机制还是相对友善的。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容