阅读 1629

IOS/Android 通过 fastlane 持续集成 Jenkins 自动化打包发布(一)

本篇主要介绍 ios 如何通过 fastlane 打包发布到所需平台 Android 打包将会在第二篇 Jenkins 集成将会在第三篇

前言

在之前开发 APP 的时候,公司的技术总监一直让我研究 app 自动化打包持续集成部署这种工程化的任务,对于这种技术我肯定是非常感兴趣去折腾的,鉴于当时 app 业务开发任务繁重,和优先级相对较低的原因,等到不怎么忙了才有时间来研究,最近看了一些相关文档,总结如下:

  • 持续集成:

    大多是采用 jenkins,我们后端的服务也是集成了 jenkins,所以 CI 平台无需考虑;

  • ios 打包:

    大多数文章是基于在 jenkins安装 xcode 相关插件,以构建出 xcode 打包环境,以及配置证书、认证文件、钥匙串的相关插件,整体流程比较繁琐,稍有流程顺序和配置出错都会导致打包失败,而且听说 jenkins 的 xcode 插件更新频率极慢,可能不能满足最新版 xcode 的使用,我在参考一些文章的配置之后,打包出了测试版本的 ipa,并且推送到蒲公英,但是发布到 appstore 的构建一直未果;

折腾了两到三天,有点心累了,直到我发现了 fastlane!

正文

关于 fastlane 的介绍我这里就不罗列了,网上介绍它的文章太多了,可以参考中文文档快速了解。

看了文档之后,感觉简直打开了新世界的大门,随便几个功能都足够吸引力。

比如:

就这几个功能就已经省去相当多的时间了,身为一个 app 开发者,相信每个人都有过跟 xcode 的证书、认证文件各种折腾的爱恨情仇!

借用一张图总结 fastlane 的流程和功能:

fastlane 管理许多工具集合,这些工具也都是通过 ruby 开发,每个工具只负责一项任务,可以通过 fastlane 的配置文件灵活的构建这些工具的使用和流程,从而实现

打包 => 自动化测试 => 发布测试包 => 发布正式包
复制代码

一条龙服务!

就问你妙不妙?

开始折腾.....

经过两天的折腾,顺利完成了

  • ipa 文件推送到 TestFlight、蒲公英、firm;
  • 自动上传元数据到 ITC;
  • 自动上传 ipa 到 ITC 并提交appstore审核;

所以,如果看文章的你想要实现以上需求,可以参考以下我分享的内容。

fastlane安装

1.安装 xcode 命令行工具

xcode-select --install
复制代码

若提示如下,说明已经安装了Xcode命令行工具;否则会弹出对话框,选择安装即可。

$ xcode-select --install
xcode-select: error: command line tools are already installed, use "Software Update" to install updates
复制代码

安装界面:

2.通过 RubyGems 安装 fastlane

推荐使用RubyGems来安装,fastlane就是用 ruby 写的一个工具,所以这种安装方式比较友好,后续不易出错。

如果你是 Mac 用户,其实 Mac 自带 ruby 环境,但是仅仅这样不够,需要2.0及以上版本,检查你的版本:

ruby -v
复制代码

目前我的版本:

$ ruby -v
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin19]
复制代码

ruby 管理推荐使用 rvm,类似 node 的 nvm 的工具,可以提供一个便捷的多版本 Ruby 环境的管理和切换。

检查你当前正在使用系统Ruby

which ruby
复制代码

如果结果是:

/usr/bin/ruby
复制代码

说明目前是 Mac 系统自带的 ruby 环境,所以首先安装 rvm,而安装 rvm 又需要 gpg,还要安装mpapis公钥

(觉得麻烦不?)

没事,坑都趟完了,跟着走就对了....

3. ruby 环境搭建

  1. 通过 Homebrew 安装 gpg
brew install gnupg
复制代码
  1. 安装完gpg之后,安装mpapis公钥

查看最新公钥: rvm.io/rvm/install

拿过来安装

gpg --keyserver hkp://pgp.mit.edu --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
复制代码
  1. 装最新版本的Ruby的RVM
\curl -sSL https://get.rvm.io | bash -s stable --ruby
复制代码
  1. 列出可供RVM使用的Ruby版本rvm list
$ rvm list
=* ruby-2.6.3 [ x86_64 ]

# => - current
# =* - current && default
#  * - default
复制代码
  1. 再次使用which ruby查看当前Ruby版本

返回 /Users/xxx/.rvm/rubies/ruby-2.6.3/bin/ruby

则说明当前使用了 rvm 安装的 ruby,ruby 环境至此告一段落!


通过RubyGems安装:

sudo gem install fastlane -NV
复制代码

当然你也可以通过homebrew安装(费了这么大劲你肯定不会选这个):

brew cask install fastlane
复制代码

4.初始化

在项目目录下执行fastlane init

安装的时候出现了下面的选项

   
1. 📸  Automate screenshots (自动截屏功能配置)

2. 👩‍✈️  Automate beta distribution to TestFlight (发布到Testfilght配置)

3. 🚀  Automate App Store distribution (自动发布到 appstore配置)

4. 🛠  Manual setup - manually setup your project to automate your (选择手动配置)

复制代码

新手在这里肯定会纠结,根据情况,自动截屏功能肯定不是首选的刚需,发布到 appstore 也不是一上来就要搞定的,手动配置刚上来你会配置吗?

所以这里选择 2 ,然后一路回车,会让你输入apple 开发者ID,就是你的邮箱,以及密码,通过命令行登录苹果开发者来下载你的证书等配置,登录 ITC 和从 ITC 拉取项目已存在的信息,如果开启了苹果的双重认证,会提示输入验证码登录

Two-factor Authentication (6 digits code) is enabled for account 'xxx@xxx.com'
复制代码

为避免每次登录输验证码,官方给出解决方案

命令中也有相关 issue 以供查阅解决方案,界面和提示非常入门化和友好,这真是我用过最贴心的的命令行工具了!

执行完毕后,会生成fastlane文件夹、Gemfile文件,AppfileFastfile文件。

Appfile: 存储有关开发者账号相关信息

Fastfile: 核心文件,用于命令行调用和处理具体的流程,lane相对于一个action方法或函数

Gemfile 类似于cocopods 的Podfile文件

.env 配置环境变量(在fastlane init进行初始化后并不会自动生成,如果需要可以自己创建

打开项目,看看刚生成的文件,你可能发现都没有代码高亮.... 因为 fastlane 使用 ruby 写的,所以要支持 ruby 啊 我这里用的是 VS Code,搜索 ruby 插件安装。

(webstorm 没有发现 ruby 插件)

5.安装所需插件

Fastlane的插件是一个或者一组action的打包,单独发布在fastlane之外。

你可以通过命令行搜索插件

#查看所有插件
fastlane search_plugins
复制代码
# 安装方法
fastlane add_plugin [name] 

#常用插件
fastlane add_plugin versioning
fastlane add_plugin firim
fastlane add_plugin pgyer
复制代码

首次安装插件会生成Pluginfile文件

# Autogenerated by fastlane
#
# Ensure this file is checked in to source control!

gem 'fastlane-plugin-firim'
gem 'fastlane-plugin-pgyer'
gem 'fastlane-plugin-versioning'

复制代码

Gemfile文件会自动添加一下内容

plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
eval_gemfile(plugins_path) if File.exist?(plugins_path)
复制代码

插件说明:

  • fastlane-plugin-versioning

    用来修改 build 版本号和 version 版本号,fastlane 内嵌的actionincrement_build_number使用的是苹果提供的agvtool, 在更改Build的时候会改变所有target的版本号。如果你在一个工程里有多个target,每次编译,所有的Build都要加1。 有了fastlane-plugin-versioning不仅可以指定target增加Build,当然也可以直接设定Version, 并且可以指定版本号的版本(major/miner/patch),这一点非常重要,而且这个插件也可以非常方便的修改 android 的版本号,插件排行榜第一位。

  • fastlane-plugin-pgyer

    上传到蒲公英分发平台。

  • fastlane-plugin-firim

    上传到 firim。

6. 配置环境变量

新建.env文件,这里可以配置所有的账号、秘钥、路径等自定义变量,定义之后,然后Fastlane的三个配置文件(Appfile、Deliverfile、Fastfile)分别从.env文件中读取配置信息。

官方文档参考

你可能又发现 env 文件中没有语法高亮,强迫症又表示不舒服,可以在 VS Code 安装 dotEnv 插件。

github.com/bkeepers/do…

给出.env文件配置做参考:

App_Identifier = "com.xx.xxx"

# Apple email address
Apple_Id = "xxx@sina.com" 

# TeamId
Team_Id = "xxx"

# target scheme
Scheme = "xxx"

# xcodeproj
Xcodeproj ="xxx.xcodeproj"

# xcworkspace
Workspace="xxx.xcworkspace"

# ipa输出路径-- Appstore
Appstore_Output_Path  = "builds/appstore"

# ipa输出路径-- 蒲公英
Pgy_Output_Path  = "builds/pgy"

# ipa输出路径-- TestFlight
TF_Output_Path = "./builds/testflight"

# 蒲公英 的api_key
Pgy_Api_Key = "xxx"

# 蒲公英 的user_key
Pgy_User_Key = "xxx"

复制代码

使用方式:ENV['Apple_Id']

apple_id("xxx@xxx.com") ===> apple_id ENV['Apple_Id']
复制代码

Fastfile 配置:

default_platform(:ios)

platform :ios do

    # 所有lane执行之前
    # 使用环境变量提供这个密码给fastlane,解决双重认证生成的特殊密码
    before_all do
    ENV["FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD"] = "xxx"

    end

  desc "发布 测试版本到 TestFlight"
  lane :beta_tf do
    increment_build_number_in_plist(target: [target_name])
    increment_version_number_in_plist(
        target: [target_name],
       )

    get_certificates( # Create or get certificate, and install it
      output_path: "./builds" # Download certificate in the build folder (you don't need to create the folder)
    )

    get_provisioning_profile( # Create or get provisioning profile
      output_path: "./builds",  # Download provisioning profile in the build folder
      filename: "provisioning.mobileprovision" # Rename the local provisioning profile
    )

    update_project_provisioning( # Set the project provisioning profile (related in Xcode to the General > Signing Release section)
      xcodeproj: ENV['Xcodeproj'],
      target_filter: ENV['Scheme'], # Name of your project
      profile: "./builds/provisioning.mobileprovision",
      build_configuration: "Release"
    )

    update_project_team( # Set the right team on your project
      teamid: CredentialsManager::AppfileConfig.try_fetch_value(:team_id)
    )
    
    gym(
        workspace: ENV['Workspace'],
        scheme: ENV['Scheme'],
        clean: true,
        export_method: "ad-hoc", # Valid values are: app-store, ad-hoc, package, enterprise, development
        export_options: {
          provisioningProfiles: { 
              "com.xxx.xxx" => "xxx",
          }
        },
        build_path: "./builds/testflight",
        output_directory: "./builds/testflight",
        output_name: logDirectory
    )
    upload_to_testflight
  end

  # 发布到蒲公英
  lane :beta_pgy do
    desc "发布 测试版本 到 蒲公英"

    # 这里是打的 Debug 版本
    gym(
    scheme: ENV['Scheme'],
    clean:true, #打包前clean项目
    export_method: "development", #导出方式  Defaults to 'Release'
    configuration: "Debug",#环境
    output_directory: ENV['Pgy_Output_Path'],
    output_name: logDirectory,
    export_options: {
          provisioningProfiles: { 
              "com.xxx.xxx" => "xxx",
          }
        }
    )
    # 上传版本到蒲公英平台
    pgyer(
      api_key: ENV["Pgy_Api_Key"], 
      user_key: ENV["Pgy_User_Key"],
      update_description: "update by fastlane"
    )
    # 在macOS 通知栏发送通知
    notification(subtitle: "上传完成", message: "最新测试包已经上传至蒲公英平台")
  end

  lane :release do
    desc "发布正式版到 appstore"

    # 下载证书
    get_certificates(
        output_path: "./builds"
    );    

    # 下载认证文件
    get_provisioning_profile(
        output_path: "./builds"
    ); 

    #get_push_certificate # 获取正式推送证书

    # 拉取最新代码
    git_pull

    # 增加 version number
    # 使用文档 https://github.com/SiarheiFedartsou/fastlane-plugin-versioning
    # eg: version_number: '2.1.1' # Set a specific version number
    # eg: bump_type: 'minor' # Automatically increment minor version number
    increment_version_number_in_plist(
        bump_type: 'patch', # Automatically increment patch version number(patch/minor/major)
        target: ENV['Scheme']
        );

    # 从最新 release 分支名中获取 version.
    # `pattern` is pattern by which version number will be found, `#` is place where action must find version number.
    # Default value is 'release-#'(for instance for branch name 'releases/release-1.5.0' will extract '1.5.0')
    # version = get_version_number_from_git_branch(pattern: 'release-#')

    # 增加 build number
    latest_build_number = lane_context[SharedValues::LATEST_BUILD_NUMBER];

    # Automatically increments the last part of the build number.
    increment_build_number_in_plist(
        target: ENV['Scheme']
    );

    # see code signing guide for more information
    #sync_code_signing(type: "appstore");  

    # 打包
    gym(
        scheme: ENV['Scheme'],
        configuration: "Release",
        clean: true,
        export_method:"app-store",
        export_options: {
            provisioningProfiles: { 
                "com.xxx.lph" => "xxx",
            }
          }
        );

    # 上传到 ITC
    deliver 

    # 在macOS 通知栏发送通知
    notification(subtitle: "上传完成", message: "最新正式包已经上传至 appstore")
  end

end

复制代码

7. 配置 metadata

执行以下命令初始化 metadata,几分钟过后,会将 ITC 的所有元数据,截图等信息下载到本地

fastlane deliver init
复制代码

metadata 文件夹中的文件比较多且杂,仅一个属性的配置却使用一个单独的.txt 文件来存储,官方文档表示可以创Deliverfile文件来配置,且优先级高于 matadata 中.txt 文件中的配置,所以我们要创建Deliverfile

下边给出我的配置:

# The Deliverfile allows you to store various App Store Connect metadata
# For more information, check out the docs
# https://docs.fastlane.tools/actions/deliver/

############################# 基本信息 ####################################


# 1 bundle identifier
app_identifier "xxx"

# 2 用户名,Apple ID电子邮件地址
username "xx@xx.com"

# 3 版权声明
copyright "xxx"

# 4 支持的语言
supportedLanguages = {
    "cmn-Hans" => "zh-Hans"
}

# 5 App应用名称
name(
  'zh-Hans' => "xxx"
)

# 6 副标题
subtitle(
  'zh-Hans' => "xxx"
)

# 应用说明
description({
  'zh-Hans' => "
  xxx
  "
})

###################################  类别配置 ###################################

# 类别列表设置参见 https://github.com/fastlane/fastlane/blob/master/deliver/Reference.md
# 设置 App 的类别.这里可以设置一个主要类别,一个次要类别.

# 主要类别 
primary_category "xxx"

# 主要类别第一个子类别
primary_first_sub_category 

# 主要类别第二个子类别
primary_second_sub_category 

# 要设置的次要类别 无
secondary_category 

# 设置的次要第一个子类别 无
secondary_first_sub_category 

# 设置的次要第二个子类别 无
secondary_second_sub_category 


################################## 关键字\描述等信息 ###################################


# 关键字
keywords(
  'zh-Hans' => "xxx,xxx,xxx"
)

# 营销地址
marketing_url({
  'zh-Hans' => "http://xxx.cn"
})

# 隐私地址
privacy_url({
  'zh-Hans' => "http://xxx.cn"
})

# 支持网址
support_url({
  'zh-Hans' => "http://xxx.cn"
})

# 发行说明,版本更新内容
release_notes({
  'zh-Hans' => "修复了一些 bug,提高使用体验,推荐更新!"
})


################################# 提交信息等 #########################################


# 1 提交审核信息:加密, idfa 等
submission_information({    
    export_compliance_encryption_updated: false,
    export_compliance_uses_encryption: false,
    content_rights_contains_third_party_content: false,
    add_id_info_uses_idfa: false
})

# 2 应用审核小组的联系信息 app 审核信息
app_review_information(
  first_name: "x",
  last_name: "x",
  phone_number: "+86xxx",
  email_address: "xxx@sina.com",
  demo_user: "xxx",
  demo_password: "xxx",
  notes: ""
)

# 出口合规证明文件
trade_representative_contact_information(
  first_name: "",
  last_name: "",
  address_line1: "xxx",
  address_line2: "",
  address_line3: "",
  city_name: "Beijing",
  state: "",
  country: "China",
  postal_code: "xxx",
  phone_number: "",
  email_address: "",
  trad_name: "xxx",
  is_displayed_on_app_store: false
)


####################################### 其他信息 ###################################


# 1 App价格
price_tier 0

# 2 自动发布 app: false,则需要手动发布
automatic_release false

# 3 图标路径
app_icon './fastlane/metadata/app_icon.jpg'

# 跳过HTML报告文件验证
force true

# 上传完成后提交以供审核
submit_for_review false
复制代码

配置好Deliverfile 后,可以删除 metadata 文件夹中的文本配置。