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

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

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

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

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


image

其实,最初写这个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/

最后,附上一个我们团队正在使用到自定义Actions:https://github.com/thierryxing/Fastfiles/tree/master/fastlane/actions

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,511评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,495评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,595评论 0 225
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,558评论 0 190
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,715评论 3 270
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,672评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,112评论 2 291
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,837评论 0 181
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,417评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,928评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,316评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,773评论 2 234
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,253评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,827评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,440评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,523评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,583评论 2 249

推荐阅读更多精彩内容