AICon上海|与字节、阿里、腾讯等企业共同探索Agent 时代的落地应用 了解详情
写点什么

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:123429

评论

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

DOM编程

大熊G

JavaScript 前端 6月月更

深圳java培训 | Java设计模式之建造者模式

@零度

设计模式 JAVA开发

2022年盲盒软件开发商业模式深度解析

开源直播系统源码

软件开发 盲盒开发 盲盒软件

本周四晚19:00战码先锋第7期直播丨三方应用开发者如何为开源做贡献

OpenHarmony开发者

OpenHarmony

Java 字符串引用(String Interning)

HoneyMoose

5道面试题,拿捏String底层原理!

码农参上

string 底层原理 死磕 Java 基础

Linux开发_curl调用网络接口

DS小龙哥

6月月更

Java String 手工引用指针

HoneyMoose

Java String 性能和优化

HoneyMoose

一文走近ZMQ

No Silver Bullet

6月月更 ZMQ

钟珊珊:被爆锤后的工程师会起飞|OneFlow U

OneFlow

自我介绍 实习

缓存穿透利器之「布隆过滤器」

Ayue、

redis 布隆过滤器

社区文章|MOSN 构建 Subset 优化思路分享

SOFAStack

负载均衡 开源 开发者 算法

我对新能源汽车的一些看法(37/100)

hackstoic

新能源汽车 生活杂谈

Java String 构造方法中的内存分配

HoneyMoose

如何进行高效简洁的电子文档管理

小炮

智能合约dapp系统开发模式定制方案

开发微hkkf5566

攻防演练 | 基于ATT&CK的威胁狩猎实践案例

青藤云安全

网络安全 攻防演练 威胁狩猎

Eureka的InstanceInfoReplicator类(服务注册辅助工具)

程序员欣宸

Java SpringCloud 6月月更

Gitlab运维篇之——备份及恢复

阿呆

#GitLab 备份恢复

【直播回顾】战码先锋第六期:共建测试子系统,赋能开发者提高代码质量

OpenHarmony开发者

OpenHarmony

社恐?自我介绍时大脑一片空白?

阿Q说代码

个人成长 社恐 职场破冰 入职公司

Fabric.js IText 手动设置斜体 🎋

德育处主任

JavaScript 前端 canvas Fabric.js 6月月更

IDE 的主题应该用亮色还是暗色?终极答案来了!

胡说云原生

ide vscode IDEA goland ide主题

Java自定义Once方法保障单次执行

FunTester

PHP开发的网站,如何实现批量打印快递单的功能?

迷彩

编程 记录 开发 php扩展 6月月更

axios框架入门教程

倔强的牛角

axios 6月月更

Java String 文字(Literal)和 对象(Object)初始化

HoneyMoose

面试突击59:一个表中可以有多个自增列吗?

王磊

Java 面试

DingOS服务提交IPO配置指南

鼎道智联

前端 开发 IPO配置

今天,SysAK 是如何实现业务抖动监控及诊断?&手把手带你体验Anolis OS|第25-26期

OpenAnolis小助手

Linux sig 龙蜥大讲堂 开源贡献 社区运维

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