Fastlane实战(五):高级用法

96
老邢Thierry
2016.12.11 13:49* 字数 2502

经过前4篇文章的基础知识和不同的场景介绍后,相信大家已经对Fastlane有了一个较为完整的认识,同样,今天我还是结合几个实际的场景,来讲讲Fastlane的一些高级用法。

前言

软件开发就像是完成一件艺术品一样,是一个循序渐进,不断打磨的过程。刚开始的时候由于我们对某个语言或框架了解的不够充分,所以往往只停留在实现功能的阶段,而不会过多的考虑到是否能够有更好的特性和更高效的方法来解决问题。

随着编程经验的积累和对语言框架的日益了解,发现之前写过的很多代码其实都存在不少问题,都有不少优化和提升的空间,于是出于责任心和对编程的热爱,我们一定会花时间不断的重构和优化代码,直到自己满意为止。

使用Fastlane的过程也同样如此,在第一次发现了这样一个新工具时,心中莫名的兴奋,于是不管三七二十一,立刻就动手使用了,然后随着使用的场景增多,问题也逐渐凸显了出来,于是我们开始探寻Fastlane的一些高级用法,以便更高效,更优雅的解决问题。

前置和后置Action

一般情况,我们在处理iOS的自动化流程时,会面临着单元测试,库编译发布,APP打包等等多种不同的场景,而APP打包又需要区分不同的环境,比如:Test,Adhoc和AppStore,而这些场景和环境的处理方式是有所不同的,所以针对每种情况,我们都需要编写对应的Lane,比如我们新建一个名叫ios_fastfile的文件,内容如下:

# 单元测试的Lane
lane :test do |options|
  git_pull
  cocoapods
  xctest
end

# AdHoc环境打包的Lane
lane :adhoc do |options|
  git_pull
  cocoapods
  increment_build_number
  gym
  upload_to_fir
end

# AppStore环境打包的Lane
lane :appstore do |options|
  git_pull
  cocoapods
  increment_build_number
  gym
  deliver
end

当然以上的这个些Lane都是简写,只是个示意,实际情况会复杂的多。从这些Lane中我们可以看到,每个流程其实都有前置的条件:

  1. git pull最新的代码
  2. 更新最新的cocoapods依赖

所以,大家肯定会问,是否有类似Ruby on Rails中controller的前置过滤器之类的机制,能够方便的处理这个情况。答案是肯定的,类似这样,在一个Fastfile中,每个Lane共有的前置流程的情况,我们可以借助Fastlane提供的before_all方法来处理,在ios_fastfile中我们增加如下的代码:

before_all do |lane, options|
  git_pull
  cocoapods
end

before_all顾名思义,就是在在执行每个方法之前首先执行的代码,使用before_all我们可以将ios_fastfile的代码就可以简化为:

before_all do |lane, options|
  git_pull
  cocoapods
end

# 单元测试的Lane
lane :test do |options|
  xctest
end

# AdHoc环境打包的Lane
lane :adhoc do |options|
  increment_build_number
  gym
  upload_to_fir
end

# AppStore环境打包的Lane
lane :appstore do |options|
  increment_build_number
  gym
  deliver
end

嗯,看上去代码简化不少,而且以后要添加公用的前置代码,都可以放在before_all中进行处理,维护起来非常方便。

另外,Lane本身和使用到的Options参数们也可以很方便的传递给before_all方法,这样可以更加方便的处理各种特殊情况,比如:有的Lane没有处理git_pull的流程,那么我们只需要在方法中加一个判断即可。

当然,除了before_all之外,Fastlane还提供after_all来处理共有的后置逻辑,比如:我们在所有的lane之后,要通过Slack或Hipchat通知到相关的工程师们,那么我们就可以把这些action写在after_all方法中。

after_all do |lane,options|
    slack(message: "fastlane was successful", success: true)
end

对于每个Lane在执行过程中,如果遇到错误,我们也需要通过Slack或Hipchat等工具通知到大家,此时,我们可以在Fastfile中添加一个全局的error方法:

error do |lane, exception|
  slack(message: exception.message, success: false)
end

引用机制

当一项新的技术在团队内部引入的时候,往往会从一个非主业务的项目中进行灰度尝试。我们也不例外,所以我们首先将Fastlane引入到我们的医生版的iOS客户端中,使用一段时间感觉不错之后,就推广到了Android平台和用户版客户端中,最后引入到各种私有库项目的管理中。目前大约涉及到了30多个项目,在这个过程中,我们发现了两个问题:

  1. 由于我们自定义了很多action,那么这些action都需要拷贝到各个项目的fastlane目录中,这样就导致了一个维护的问题,即:添加或修改任意的自定义action,都需要去各个项目中处理一遍。
  2. 我们目前的项目按类型分为4种:iOS App,iOS Pod库,Android App,Android AAR库,对于同样的项目类型,lane的处理流程基本上是一样的,所以每个相同类型的项目的Fastfile基本上都是一致的,这就带来了和1一样的问题:如果要修改任意一个项目的Fastfile,那么就意味着同样也得修改其它同类型项目中Fastfile,因为这些Fastfile都是放在各自项目目录下的fastlane文件夹中的。

刚开始项目比较少的时候,手工还可以处理,当项目逐渐增多的时候,问题就愈发严重了,不但要改的地方多,而且还容易出错,于是我们思考是否有能够由一种引用的机制存在,能够在顶层维护一份公用的action和fastfile,这样在项目中只需要引入这些顶层的文件们就能解决上面的问题。

经过仔细研究Fastlane的特性之后,我们发现这个问题早就在其作者的考虑之中了。Fastlane不但提供了引用机制import,还提供了远程引用模式,即:import_from_git。

有了这个机制之后,我们不但可以在将自定义action们和Fastfile们统一管理,而且还可以放在git上进行远程分布式管理,其带来的好处就是:各个项目只需要在自己的Fastfile的顶部进行引用声明即可:

# 远程Git引用:
import_from_git(url: 'https://github.com/GengmeiRD/Fastfiles', branch: 'master')

lane :appstore do |options|
  # ...
end

这样,每次执行fastlane的命令时,首先会从git上将需要的文件clone这个项目到本地的临时文件夹中,然后再执行相关命令。当然,如果某个项目中,你不想使用远端上的某个lane,而是需要自定义一份,那么只需要在项目中的Fastfile中复写这个lane即可:

# 远程Git引用:
import_from_git(url: 'https://github.com/thierryxing/Fastfiles', branch: 'master')

# 复写发布项目的lane
lane :do_deliver_app do |options|
  # ...
end

当然,如果你觉得远程管理这些action和fastfile们比较麻烦的话,同样可以使用本地引用进行管理,fastlane提供了两个命令,分别用来引入本地的Fastfile和action目录:

import "../GeneralFastfile"
actions_path '../custom_actions_folder/'

lane :appstore do |options|
  # ...
end

我们将团队内部使用到的远程action和Fastfile管理已经发布到了github上,有兴趣的同学可以参考:
https://github.com/thierryxing/Fastfiles

上下文常量

在使用Fastlane的过程中,我们往往需要一些和上下文相关的常量,比如:AppStore的账号和密码,ipa和dsym文件的输出的地址,模拟器或设备的UDID等等。这些常量如果直接写死在Fastfile中显然是不利于维护的。所以我们可以考虑使用以下的方式来处理:

使用dotenv

首先使用gem安装dotenv,然后新增一个.env文件,然后将所有用到的常量定义在里面,比如:

WORKSPACE=YourApp.xcworkspace
ITUNESCONNECT_ACCOUNT=your-itunesconnect-account

然后在fastfile中使用ENV进行调用:

lane :appstore do |options|
  increment_build_number
  gym(workspace: ENV['WORKSPACE'])
  deliver(username: ENV['ITUNESCONNECT_ACCOUNT'],)
end

最后将这个.env文件拷贝到和Fastfile的同级目录即可。当然如果有常量需要在多个项目中公用的话(比如:iTunesConnect的账号),可以建立一个软连接指向同一个.env文件,然后在目录中创建一个.env.default文件,放置本项目下专属的常量。

使用export命令

如果你使用的是Mac或linux操作系统,可以在系统的环境的变量中使用export命令直接定义系统级别的常量,当然为了不和其它常量冲突,建议增加一个FASTLANE前缀,比如:

#set env vars
export FASTLANE_WORKSPACE="<YourApp.xcworkspace>"
export FASTLANE_ITUNESCONNECT_ACCOUNT="<your-itunesconnect-account>"

调用方法和使用.env一样。

lane :appstore do |options|
  increment_build_number
  gym(workspace: ENV['FASTLANE_WORKSPACE'])
  deliver(username: ENV['FASTLANE_ITUNESCONNECT_ACCOUNT'],)
end

结语

以上的几个高级用法只是笔者在使用过程中遇到的一隅,更多的惊喜还等待大家自己去探索去发现,官方文档中有全面的介绍:https://docs.fastlane.tools/advanced/

Fastlane是一个持续维护,快速发展的工具,从笔者的第一篇文章到现在,短短两个月的时间,Fastlane已经发布了20多个版本,在Github上新增了2000多个star,同时又多了100多位工程师加入到了Contributors的大家庭中。事实再次证明优秀的开源项目总能得到大家的认可,关注和参与。

本文是Fastlane实战系列的第五篇文章也是最后一篇,希望这个系列的文章能够真正帮助大家了解Fastlane,也希望看完这个系列的文章后,有更多的同学借助Fastlane和各种自动化工具来提升自己的效率,毕竟重复性的劳动和流程化的工作就交给机器们去做吧。

最后祝大家使用Fastlane愉快。

Fastlane实战