iOS App自动化打包发布(Jenkins + Fastlane)

============2018.5.15 updte=================
xcode 9 需要在gym action加入 export_xcargs: “-allowProvisioningUpdates”,
参考: https://www.jianshu.com/p/e0c51ab314f7
=========================================

目标

搭建一个自动化打包发布的平台。无论是App Store的发布,是开发期间持续化集成,都需要一个自动化的方案,代替人工来处理那些繁琐的打包发布流程。
本文采用的方案:jenkins + fastlane + svn + firim

Jenkins

关于搭建jenkins 看这篇文集就可以了 -> 手把手教你利用Jenkins持续集成iOS项目
只需要按步骤完成安装、新建job、源码管理就可以了,构建环境暂时不需要设置。

  • 不要使用Xcode构建,因为在导出ipa的时候,jenkins会使用到PackageApplication,这个玩意在xcode 8.3已经被删除了 (当然你可以从旧版本的Xcode拷贝一个过来
  • 多安装一个插件 Message Injector Plugin 用来配置环境变量

FastLane

fastlane 就是一个自动化发布的命令行工具-> 小团队的自动化发布-Fastlane带来的全自动化发布 或者 fastlane github && fastlane doc

安装命令 跟上述文章略有不同

    [sudo] gem install fastlane -n /usr/local/bin

加上 -n /usr/local/bin是因为Mac OS X 10.11 已经禁止修改/usr/bin目录了,那就换个目录安装

Firim

    [sudo] gem install firim -n /usr/local/bin

还需要到fir.im注册个账号,上传ipa需要一个token。

脚本

在工程目录下 完成 fastlane init之后,会生成fastlane文件夹,里面有三个文件需要修改

  • Fastfile => 用来定义所有的lane任务Fastfile帮助
  • Appfile => 是用来存储一些公共信息的,比如app_identifier,apple_id,team_id,itc_team_id等。Appfile帮助
  • Deliverfile => deliver的配置文件Deliverfile帮助

安装帮助文档应该可以轻松完成脚本编写,然而你的工程如果有多个Tareget,你可能还需要在fastlane目录下定义一个env配置文件。

    touch .env.TargetName

.env.TargetName文件内容:

    APP_IDENTIFIER = "com.xxx.xxxxx"

    #苹果开发者账号
    APPLE_ID = "xxxxxxxxxx@xxx.cn"
    FASTLANE_PASSWORD = "xxxxxxx"
    
    TEAM_ID = "xxxxxxxxxx"

    SCHEME_NAME = "TargetName"

    #App 元数据及截图存放路径
    METADATA_PATH = "./metadata/XXXXXXXX"
    SCREENSHOTS_PATH = "./screenshots/XXXXXXXXX"

    #App 元数据及截图下载时 直接覆盖 不询问
    DELIER_FORCE_OVERWRITE= true

    #版本号 如果不使用,就按照Xcode配置
    #APP_VERSION = 1.0.1

    #证书
    CODESIGNING_IDENTITY_TO_FIRIM = "iPhone Developer: XXXXX (XXXXXXX)"
    CODESIGNING_IDENTITY_TO_APPSTORE = "iPhone Distribution: XXXXX (XXXXXXXXX)"

Appfile文件:ENV[‘xxxx’] 就是读取.env文件里面的配置

    app_identifier ENV['APP_IDENTIFIER'] # The bundle identifier of your app
    apple_id ENV['APPLE_ID'] # Your Apple email address

    team_id ENV['TEAM_ID'] # Developer Portal Team ID

    # you can even provide different app identifiers, Apple IDs and team names per lane:
    # More information: https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Appfile.md

Deliverfile文件:如果你想使用deliver上传元数据截图以及完成提交,就需要根据帮助文档完善后续的参数

    ###################### More Options ######################
    # If you want to have even more control, check out the documentation
    # https://github.com/fastlane/fastlane/blob/master/deliver/Deliverfile.md


    ###################### Automatically generated ######################
    # Feel free to remove the following line if you use fastlane (which you should)

    # The bundle identifier of your app
    app_identifier ENV['APP_IDENTIFIER'] 

    # your Apple ID user
    username ENV['APPLE_ID'] 

    # 元数据的路径
    metadata_path ENV['METADATA_PATH']
    screenshots_path ENV['SCREENSHOTS_PATH']

    #App store 中待发布的 App 版本,若每个 target 的版本号不同,可以通过.env 文件来分别定义
    app_version ENV['APP_VERSION']

    #下载 metadata 及 screenshots 时直接覆盖,不询问
    force true

可以使用以下命令 从iTC上下载 元数据和截图

    fastlane deliver download_metadata -a  com.xxx.xxxxx -m ./metadata/XXXXXXXX -u  xxxxxxxxxx@xxx.cn
    fastlane deliver download_screenshots -a com.xxx.xxxxx -m ./screenshots/XXXXXXXX -u  xxxxxxxxxx@xxx.cn

接下来就是重头戏 fastfile文件:

   fastlane_version "2.28.3"

   default_platform :ios

   platform :ios do

   desc "Submit one more new Beta Build to Fir.im"
   lane :to_firim_multi do | options |
      schemes = options[:'schemes']
      schemes = schemes[1..-2]
      schemes = schemes.split(",")
      schemes.each do |i| 
        puts "do fastlane to_firim --env " + i.to_str
        sh "fastlane to_firim --env " + i.to_str
     end
   end

   desc "Submit one more new Release Build to AppStore"
   lane :to_appStore_multi do | options |
      schemes = options[:'schemes']
      schemes = schemes[1..-2]
      schemes = schemes.split(",")
      schemes.each do |i| 
         puts "do fastlane to_appStore --env " + i.to_str
         sh "fastlane to_appStore --env " + i.to_str
      end
   end

   desc "Submit a new Beta Build to Fir.im"
   lane :to_firim do | options |
   buildScheme = ENV['SCHEME_NAME']#options[:buildScheme]
   # 如果你用 pod install
   # cocoapods
   # 以下两个action来自fastlane-plugin-versioning,
   # 第一个递增 Build,第二个设定Version。
   # 如果你有多个target,就必须指定target的值,否则它会直接找找到的第一个plist修改
   # 在这里我建议每一个打的包的Build都要不一样,这样crash了拿到日志,可以对应到ipa上
   increment_build_number_in_plist(target: buildScheme)
   # increment_version_number_in_plist(
   #   target: buildScheme,
   #   version_number: ENV['APP_VERSION']
   #   )

   # gym用来编译ipa
   outputDir = "~/firim/ipa/#{Time.now.strftime('%y%m%d')}"
   outputName = "#{buildScheme}-#{Time.now.strftime("%Y-%m-%d %H:%M:%S")}"
   gym(
      scheme: buildScheme,
      workspace: "xxxxxxx.xcworkspace",
      configuration: "Release",
      output_directory: outputDir,
      output_name: outputName,
      include_bitcode: false,
      include_symbols: true,
      codesigning_identity: ENV["CODESIGNING_IDENTITY_TO_FIRIM"],
      silent: true,
      export_options: {
        method: "development", 
        thinning: "<none>"
      }
  )
   # 上传ipa到fir.im服务器,在fir.im获取firim_api_token
   firim(firim_api_token: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
   end

    desc "Submit a new Release Build to AppStore"
    lane :to_appStore do | options |
    buildScheme = ENV['SCHEME_NAME']#options[:buildScheme]
    # 如果你用 pod install
    # cocoapods

    # 以下两个action来自fastlane-plugin-versioning,
    # 第一个递增 Build,第二个设定Version。
    # 如果你有多个target,就必须指定target的值,否则它会直接找找到的第一个plist修改
    # 在这里我建议每一个打的包的Build都要不一样,这样crash了拿到日志,可以对应到ipa上
    increment_build_number_in_plist(target: buildScheme)
    # increment_version_number_in_plist(
    #   target: buildScheme,
    #   version_number: ENV['APP_VERSION']
    #   )
    # gym用来编译ipa
    outputDir = "~/AppStore/ipa/#{Time.now.strftime('%y%m%d')}"
    outputName = "#{buildScheme}-#{Time.now.strftime("%Y-%m-%d %H:%M:%S")}"
    gym(
      scheme: buildScheme,
      workspace: "xxxxxxx.xcworkspace",
      configuration: "Distribution",
      output_directory: outputDir,
      output_name: outputName,
      include_bitcode: false,
      include_symbols: true,
      codesigning_identity: ENV["CODESIGNING_IDENTITY_TO_APPSTORE"],
      silent: true,
      export_options: {
        method: "app-store", 
        thinning: "<none>"
      }
      )
     # 上传appstore 只传ipa包
    deliver(
      skip_screenshots: true,
      skip_metadata: true,
      force: true
     )
     end

完成配置文件的编写就可以使用脚本来自动打包发布

    #完成打包后,自动上传firim,用于开发测试 
    fastlane to_frim_multi schemes:[Target1,Target2,Target3,...] 

    #完成打包后,自动上传AppStore,用于正式发布 
    fastlane to_appStore_multi schemes:[Target1,Target2,Target3,...] 

证书 profile之类的配置都在Xcode中完成,要确保使用Xcode可以正常编译签名

最后一步

在jenkins新增两个构建步骤

《iOS App自动化打包发布(Jenkins + Fastlane)》 屏幕快照

第一步导入一个环境变量
第二步就是执行fastlane命令
最后要做的就是提交到svn,执行jenkins的job就可以自动完成打包发布啦
总的来说,程序员的懒,一定程度上推动了技术的进步~

参考资料

  1. 24. 自动化工具Fastlane笔记一: 安装, 打包,上传(testFlight,app store)
  2. 使用 fastlane 实现对 iOS Multi-Target 的一键打包部署
    原文作者:施孝达
    原文地址: https://www.jianshu.com/p/ae5b492b5338
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞