写点什么

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

  • 2016-10-17
  • 本文字数:5739 字

    阅读完需:约 19 分钟

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

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

站在这个角度上,今天我们就来介绍一下 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:123236

评论

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

我滴个乖乖!首次公布Java10W字面经,Github访问量破百万

热爱java的分享家

Java 面试 程序人生 编程语言 经验分享

Java Collectors API实践

FunTester

Java API 测试开发 FunTester Collectors

直播预告|11.27(周六)观测云&思否联合举办的开发者线上沙龙巡演第3站来啦

观测云

直播 沙龙

顺丰科技 x StarRocks :双十一实时运单分析实践

StarRocks

数据库 大数据 数据分析 StarRocks

【死磕Java并发】-----J.U.C之AQS:阻塞和唤醒线程

chenssy

11月日更 死磕 Java 死磕 Java 并发

一物一码可追溯!看区块链如何帮助消费者

CECBC

Function Mesh:Serverless 在消息与流数据场景下的火花

Apache Pulsar

大数据 架构 云原生 Apache Pulsar pulsar社区

2021年底Java最新学习路线图

程序员万金游

Java

博文推荐|腾讯专家深度解析 Apache Pulsar 五大应用场景

Apache Pulsar

架构 分布式 云原生 Apache Pulsar 消息中间件

博文推荐 | Apache Pulsar 三大跨地域复制解决方案

Apache Pulsar

Java 架构 分布式 云原生 Apache Pulsar

豪华阵容!13位专家力荐Spring5为企业级开发提供一站式方案

热爱java的分享家

Java 面试 程序人生 编程语言 经验分享

声网下一代视频引擎架构探索与实践

声网

音视频 视频处理 RTE 技术详解

关于JAVA中顺序IO的基本操作

编程江湖

公司刚来的阿里p8,看完我构建的springboot框架,甩给我一份文档

热爱java的分享家

Java 面试 程序人生 经验分享 P8

激荡十年,从未来窗口 re:Invent 看云计算发展变迁 | Q推荐

亚马逊云科技 (Amazon Web Services)

数据库 云计算 云原生 re:Invent

活动预告|AICon全球人工智能与机器学习技术大会

第四范式开发者社区

机器学习 开源 OpenMLDB

DPDK 网络协议栈-vpp-OvS-DDos-虚拟化专家之路

赖猫

Linux 网络协议栈 DPDK

天翼账号网关系统架构演进历程

架构 网关 亿级流量 双十一

Java线程的生命周期包括哪几种状态?

程序员万金游

java线程

封神总结!蚂蚁金服+滴滴+美团+拼多多+腾讯15万字Java面试题

热爱java的分享家

Java 面试 程序人生 编程语言 经验分享

博文推荐|深度解析如何在 Pulsar 中实现隔离

Apache Pulsar

Java 架构 分布式 云原生 Apache Pulsar

数据倾斜的产生和解决办法?

编程江湖

学习Java需要掌握哪些技能?

程序员万金游

Java

头条观察 | 元宇宙成为必然趋势的三种可能

CECBC

JavaScript 数组展平方法: flat() 和 flatMap()

devpoint

11月日更 flat flatMap

程序员如何应对职业天花板

石云升

职场天花板 职场经验 11月日更

Python Qt GUI设计:QSpinBox计数器类(基础篇—15)

不脱发的程序猿

Python PyQt GUI设计 QSpinBox计数器类

Tapdata 在线研讨会:DaaS vs 大数据平台,是竞争还是共处?

MongoDB中文社区

mongodb

为什么要学习linux内核源码以及如何学习Linux内核源码

赖猫

c++ Linux 运维 嵌入式 Linux内核

在 JavaScript 中如何检查对象为空

编程江湖

(文末福利)如果代码莫名其妙跑起来了,就不要去动它了……吗?

Zilliz

数据库

Fastlane实战(二):Action和Plugin机制_语言 & 开发_邢天宇_InfoQ精选文章