写点什么

Fastlane 实战(二):Action 和 Plugin 机制

2016 年 10 月 17 日

作为架构师的我们常常要面临的一个难题就是技术选型。现在无论是商业项目也好,开源项目也好,可供选择的方案实在是太多,其中优秀的方案也是层出不穷,这就要求我们在做技术选型的时候,需要从多个维度进行考量,其中良好的扩展性是我们重点考量的对象。

任何一个优秀的框架或平台都应该具有良好的扩展性,以满足不断变化的业务场景和个性化要求,而这种扩展性的其中一个方面则体现在:是否能够提供一种机制,这种机制既能满足二次开发的便捷性,又最小化甚至不会对原有的系统产生任何的侵入或破坏。

站在这个角度上,今天我们就来介绍一下 Fastlane 的两种扩展机制,Action 和 Plugin。

Fastlane 的 Action 机制

Fastlane 本身包含两大模块,一个是其内核部分,另外一个就是 Action 了。Action 是 Fastlane 自动化流程中的最小执行单元,直观上来讲就是 Fastfile 脚本中的一个个命令,比如:git_pull,deliver,pod_install 等等,而这些命令背后都对应一个用 Ruby 编写的脚本。

我猜想,Fastlane 的作者们在项目的早期甚至规划的阶段,应该就考虑到了这一点:在移动开发中,自动化的业务场景太多,每个团队都有自己的特殊要求,单靠一两个人的力量是无法满足的,所以如何将涉及到实际业务的功能开发,用优雅的方式交给开源社区中庞大工程师们来维护,成为 Fastlane 架构中需要重点考虑的内容。

于是经过不断的探索,讨论和实践,Action 这种扩展机制应运而生。我们可以理解为 Fastlane 建立了一套完整的规则,这个规则是如此的简单易行,无论是官方的工程师还是开源社区的工程师们,大家都在这个规则里进行游戏,这样不但降低了扩展的门槛,吸引工程师们来完善 Fastlane 本身;同时又增强了约束,减少不必要的沟通和代码检查成本。所以我们可以看到无论是官方贡献的,还是 Github 社区贡献的 Action 们,无一例外都隶属于 Action 扩展的一部分。

到目前为止 Fastlane 包含大约 170 多个 Action,大约分为如下几类:

  1. 和移动端持续交付相关的 15 个核心的工具链:如:deliver(上传 ipa,截屏和 meta 信息到 ITC),supply(上传 apk,截屏和 meta 信息到 Google Play),sigh(iOS Provisioning 文件管理)等等,详情如下: https://github.com/fastlane/fastlane#fastlane-toolchain
  2. 和 iOS 相关的,如:ipa,xcode_install 等等
  3. 和 Android 相关的,如:gradle,adb 等等
  4. 和版本控制相关的,如 git_pull,hg_push 等等
  5. 和 iOS 依赖库管理相关的,如:cocoapods,carthage 等等
  6. 第三方平台对接相关的,如:hipchat,jira,twitter,slack 等等

这些 Action 的详情和使用方法可以查看这个链接: https://docs.fastlane.tools/actions/Actions/

应该说几乎涵盖了所有常见的场景,但是如果仍然无法完全满足你的要求的话,就得自己来动手自定义一个了。

场景分析

那么如何来自定义一个 Action 呢?按照习惯,为了便于大家理解,我们还是先从一个业务场景入手。在上一篇文章中,我曾经举过一个例子:私有 Pod 的发布,其步骤如下:

  1. 增加 Podspec 中的版本号
  2. 执行 pod lib lint 命令进行库验证
  3. Git Commit 代码
  4. Git Push 代码到远端
  5. 打一个 Git Tag
  6. 将 Tag Push 到远端
  7. 执行 pod repo push 命令发布库到私有仓库

然后对应以上的几个步骤,我们都可以找到现成的 Action 来实现,所以我们可以在 Fastfile 中增加如下 Lane:

复制代码
desc "Release new private pod version"
lane :do_release_lib do |options|
target_version = options[:version]
project = options[:project]
path = "#{project}.podspec"
git_pull
ensure_git_branch # 确认 master 分支
pod_install
pod_lib_lint(verbose: true, allow_warnings: true, sources:
SOURCES, use_bundle_exec: true, fail_fast: true)
version_bump_podspec(path: path, version_number: target_version) # 更新 podspec
git_commit_all(message: "Bump version to #{target_version}") # 提交版本号修改
add_git_tag(tag: target_version) # 设置 tag
push_to_git_remote # 推送到 git 仓库
pod_push(path: path, repo: "GMSpecs", allow_warnings: true, sources: SOURCES) # 提交到私有仓库
end

自定义 Action

讲到这里,大家可能会问,这不都写完了吗,哪里还需要自定义 Action 啊?别急,其实大约 3 个月前,笔者编写这个 Fastfile 的时候,Fastlane 正好缺少一个 Action 能够支持 Cocoapods 的这个命令,即:

pod lib lint这个命令是用来验证私有的 Pod 库是否正确,所以当时无奈之下,只能自己动手写一个了。写完后发现,这个工作也并没有想象中的那么困难,Fastlane 已经为我们提供了现成的模板,即使你对 Ruby 的语法不熟悉,也没有关系,Fastlane 是开源的嘛,可以直接下载源码看看别人的 Action 是怎么写的就知道了,我们可以在这个目录下找到所有的 Action 文件:

fastlane/fastlane/lib/fastlane/actions/自定义 Action 的流程大约如下,首先,我们在终端中执行命令:

fastlane new_action然后根据提示,在命令行中敲入 action 的名字 pod_lib_lint,然后 Fastlane 会在当前目录的 actions 文件夹中帮我们创建了一个 pod_lib_lint.rb 的 Ruby 文件,内容大致如下(省略了非重点部分):

复制代码
module Fastlane
module Actions
class PodLibLintAction < Action
def self.run(params)
UI.message "Parameter API Token: #{params[:api_token]}"
end
......
def self.available_options
[
FastlaneCore::ConfigItem.new(key: :api_token,
env_name: "FL_POD_LIB_LINT_API_TOKEN", # The name of the environment variable
description: "API Token for PodLibLintAction", # a short description of this parameter
verify_block: proc do |value|
UI.user_error!("No API token for PodLibLintAction given,
pass using `api_token: 'token'`") unless (value and not value.empty?)
end),
......
]
end
end
end

大家可以看到,自定义的 Action 都是隶属于 Fastlane/Actions 这个 module,并且继承自 Action 这个父类。虽然模板中的内容还挺多,不过不用担心,大部分内容都是一些简单的文本描述,对于我们来说只需要重点关注这两个方法就行:

  1. self.run 方法:这里放置的是实际的业务处理代码。
  2. self.available_options 方法:这里声明需要对外暴露出的参数,没有声明的参数在执行过程中无法使用。

在开始编写实际的业务代码之前,我们需要了解清楚这个 Action 具体包含的业务逻辑,所以我们首先来分析一下 Cocoapods 的 pod lib lint 命令,在终端执行

pod lib lint --help终端打印出(只保留重点部分)

复制代码
Usage:
$ pod lib lint
Validates the Pod using the files in the working directory.
Options:
--quick Lint skips checks that would
require to download and build
the spec
--allow-warnings Lint validates even if warnings
......

可以看出这个命令包含了不少选项(Options),而我们需要做的是将这些选项映射到 action 中的参数,所以接下来我们根据选项的类型,在 self.available_options 中进行声明,代码如下(只保留重点部分):

复制代码
def self.available_options
[
FastlaneCore::ConfigItem.new(key: :use_bundle_exec,
description: "Use bundle exec when there is a Gemfile presented",
is_string: false,
default_value: true),
FastlaneCore::ConfigItem.new(key: :verbose,
description: "Allow ouput detail in console",
optional: true,
is_string: false)
......
]
end

声明完毕后,在 self.run 方法中编写最终的业务逻辑,同时将上面的 options 通过 params 暴露出去,这样在运行 pod_lib_lint 这个 action 的时候,我们就可以传入对应的参数,从而 Fastlane 可以执行携带各种选项的完整命令,代码如下(只保留重点部分):

复制代码
def self.run(params)
command = []
command << "bundle exec" if File.exist?("Gemfile") && params[:use_bundle_exec]
command << "pod lib lint"
command << "--verbose" if params[:verbose]
command << "--allow-warnings" if params[:allow_warnings]
......
result = Actions.sh(command.join(' '))
UI.success("Pod lib lint Successfully")
return result
end

从这段代码可以看出,关键点在于 Actions.sh() 这句话,所以我们要保证这里的 sh 方法执行的 command 和 pod lib lint 命令在终端中输出的一致,例如:

pod lib lint --quick --verbose --allow-warnings --use-libraries最后,我们将 pod_lib_lint.rb 拷贝到 iOS 项目下的 fastlane/actions 文件夹中,然后在该项目目录下,执行如下命令:

fastlane action pod_lib_lint如果没有错误的话,终端中会输出如下内容:

(点击放大图像)

其实,最初写这个Action,我只是打算在团队内部使用,并没有贡献到Github 上的计划,所以只实现了一部分参数。我们自己使用了一段时间,感觉比较稳定的时候,才将所有参数都补齐,然后贡献到了Fastlane 的主仓库中,地址如下:

https://github.com/fastlane/fastlane/blob/master/fastlane/lib/fastlane/actions/pod_lib_lint.rb

这里说一个题外话
对于开源项目的代码提交,整个过程会比较严格,除了功能无 Bug,单元测试需要完全覆盖之外,对于语法格式等软指标也有一定的要求。当提交 pull request 的时候,Github 会先使用自动化工具(HoundCI 和 CircleCI)进行全面的检查,通过后才会交给 Code Review 团队人工 Check,所以平常代码习惯不好的同学需要多加注意。

Fastlane 的 Plugin 机制

我们在使用 Fastlane 的时候常常会遇到这样的场景:

  1. 我的自定义 Action 需要在多个内部项目中使用
  2. 我觉得这个自定义 Action 很不错,想共享给其他的团队使用

此时,拷贝粘贴虽然可以解决问题,但并不是一个聪明的方案。将 Action 发布到 Fastlane 的官方仓库倒是一个不错的选择,但是官方仓库本身对 Action 的要求比较高,并不会接收非通用性的 Action,即使接收了,整个发布周期也会比较长,而且以后无论是升级还是 Bug 修复,都依赖 Fastlane 本身的发版,大大降低了灵活性。

所以从 1.93 开始,Fastlane 提供了一种 Plugin 的机制来解决这种问题。大家可以理解为:Plugin 就是在 Action 的基础上做了一层包装,这个包装巧妙的利用了 RubyGems 这个相当成熟的 Ruby 库管理系统,所以其可以独立于 Fastlane 主仓库进行查找,安装,发布和删除。

我们甚至可以简单的认为:Plugin 就是 RubyGem 封装的 Action,我们可以像管理 RubyGems 一样来管理 Fastlane 的 Plugin。

安装 Plugin

到目前为止,大约有 30 个 Plugin 发布到了 RubyGems 下,我们可以通过如下命令来查找:

fastlane search_plugins [query]详情可以看这里
AvailablePlugins

假设我们的项目中需要使用一个名叫 version_from_last_tag,用于获取 git 的最近一个 tag,那么我们在终端的项目目录下执行:

fastlane add_plugin version_from_last_tag添加完成后,项目中会多出一个 Gemfile,Gemfile.lock,fastlane/Pluginfile 三个文件,其中这个 Pluginfile 实际上就是一个 Gemfile,里面包含对于 Plugin 的引用,格式如下:

复制代码
# Autogenerated by fastlane
#
# Ensure this file is checked in to source control!
gem 'fastlane-plugin-version_from_last_tag'

而 Pluginfile 本身又被 Gemfile 引用,所以又印证上上文中的那句话:对 Plugin 的管理其实就是对 RubyGem 的管理。

此后的 Plugin 是实际用法和使用 Action 是一致的,所以就不在此赘述了。

发布 Plugin

如果你想发布一个 Plugin,可以选择直接作为一个 Gem 发布到 RubyGems 上,这样大家就可以通过 search_plugins 命令搜索到了;也可以选择只提交代码到 Github 上,然后提供一个 github 的地址给其它项目或团队使用,这时需要在 Pluginfile 中这样声明:

复制代码
gem "fastlane-plugin-version_from_last_tag", git: "https://github.com/jeeftor/fastlane-plugin-version_from_last_tag"

发布之前,为了本地调试方便,可以将 gem 指向本地,在 Pluginfile 这样声明:

复制代码
gem "fastlane-plugin-version_from_last_tag", path: "../fastlane-plugin-version_from_last_tag"

有了 Plugin 之后,Fastlane 的更新频率大大降低,主仓库上 Action 的数量将维持在目前的水平上,取而代之的是 Plugin 的不断增多。企业和团队可以选择适合自己的 Plugin,也可以随时随地发布 Plugin 给别的团队使用。

结语

有了 Action,Fastlane 的可扩展性大大的增强,我们可以非常方便的编写适合自己业务场景的工具;Plugin 的出现,又在扩展性的基础之上大大增强了其灵活性,两者结合在一起使用可以将 Fastlane 的优势充分的的发挥出来。

通常情况下,如果一个工具只打算在一个项目中使用,那么建议直接用 Action,毕竟一个 Ruby 脚本就能解决,成本比较低;如果打算在多个项目中甚至跨团队使用,那么则建议使用 Plugin。

关于 Action 和 Plugin 更为详细的描述可以查看官方提供的文档:

Action: https://docs.fastlane.tools/actions/Actions/
Plugin: https://docs.fastlane.tools/plugins/CreatePlugin/

目前的两篇文章中的内容和场景都和 iOS 相关,接下来的一篇文章中,我将详细讲解一下如何将 Fastlane 应用于 Andriod 平台。


感谢徐川对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

2016 年 10 月 17 日 17:122189

评论

发布
暂无评论
发现更多内容

密码学因区块链更“值钱” 区块链因密码学更“完善”

CECBC区块链专委会

区块链技术 去中心化 密码学 记账权

架构师训练营 - 第二周作业

极客大学架构师训练营

第二周课程总结

考尔菲德

架构师训练营-作业2

紫极

优化Cache类

GalaxyCreater

架构

架构师训练营第二章学习总结

JUN

Week 02 作业

鱼_XueTr

架构week2 homework

蜡笔小晗

如何理解依赖倒置?

青莲

面向对象设计原则 设计原则

架构师训练营学习总结(第二周)

战峰

第二周学习总结

依赖倒置原则

旁听生

极客大学架构师训练营 依赖倒置原则

Week 02 总结

鱼_XueTr

第二周命题作业

冯凯

软件架构

设计原则的一些感悟

紫极

架构师训练营 - 第二周 - 总结

亮灯

架构师训练营第二周 总结

Benjamin

极客大学架构师训练营

架构师训练营 第二周 命题作业

RZC

架构师训练营-week02 作业

GunShotPanda

架构训练营第二周 - 作业

无心水

架构师 极客大学架构师训练营

架构师训练营-第2周作业

seng man

架构师训练 Week2 - 学习总结

伊利是个圈

学习 极客大学架构师训练营

架构师训练营 第二周 作业

CR

极客大学架构师训练营

【架构思维学习】 week02

chun1123

依赖倒置 接口隔离

产出高质量代码的秘密

kk

编码习惯 代码质量

框架设计

一点点..

架构师 0 期 | 设计模式练习

刁架构

极客大学架构师训练营

学习总结-架构师训练营-第2周

seng man

spring中的依赖倒置

Geek_bobo

依赖倒置原则

Geek_bobo

架构师训练营 第二周 学习总结

RZC

InfoQ 极客传媒开发者生态共创计划线上发布会

InfoQ 极客传媒开发者生态共创计划线上发布会

Fastlane实战(二):Action和Plugin机制-InfoQ