博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Fastlane(二):结构
阅读量:6277 次
发布时间:2019-06-22

本文共 14428 字,大约阅读时间需要 48 分钟。

前言

在终端中执行fastlane lane_name之后,fastlane会去执行Fastfile中定义的同名lane,这个是如何实现的。 本文按照解析参数这一主线,尝试解释fastlane的执行逻辑和内部结构。

在开始正文之前,有一些概念和名称需要解释一下,在之前的文章中,已经提到过一些fastlane的领域专用名称,比如platform、lane、action等,除了这些以外,还有两个重要的名称需要了解一下,Command和Tool。

1. Tool和Command

fastlane是一个庞大的工具集,为了更好的使用和管理这些工具,将功能相似的工具划分在一起组成一个Tool,每一种Tool都代表fastlane的一个大的功能点。

fastlane中的Tool列表:

TOOLS = [    :fastlane,    :pilot,    :spaceship,    :produce,    :deliver,    :frameit,    :pem,    :snapshot,    :screengrab,    :supply,    :cert,    :sigh,    :match,    :scan,    :gym,    :precheck  ]复制代码

每一个Tool都有其特定的应用领域,比如cert用于证书相关,sigh用于签名相关,gym用于打包相关,等等。 其中,fastlane是默认的Tool,比如fastlane lane_namefastlane initfastlane action action_namefastlane add_plugin plugin_name等,因为这些命令都没有显式的指定Tool,所以使用的都是fastlane这个Tool,它是fastlane库中最重要的Tool。

每一种Tool下都有多个Command,如果把Tool看做是某个领域的专用工具,Command则是其中的一个操作,比如cert就是专门用于签名证书相关的Tool,当需要创建新的签名证书时,可以使用cert下的create这个Command,其具体的执行命令是fastlane cert creat,因为create是默认命令,所以也可以使用fastlane cert;当需要移除过期证书时,则可以使用revoke_expired这个Command,其具体的命令是fastlane cert revoke_expired

上文中提到的几条命令,fastlane init中的initfastlane action action_name中的actonfastlane add_plugin plugin_name中的add_plugin等,这些都是fastlane这个默认Tool的Command。而fastlane lane_name使用的是默认Tool的默认Command:trigger

Command必须和Tool结合起来才有意义,因为不同Tool下的Command可能会出现同名的情况,fastlane允许这种情况出现。只有确定了Tool之后,才能确定真正的Command。

2. lane、action

之前在中有讲到lane和action的简单使用,这里再结合Tool和Command,谈一谈它们的联系和区别。

default_platform :ioslane :build do    match(git_url: your_git_url)    gym(export_method: 'enterprise')end复制代码

上述代码中的build是一个lane,matchgym都是action。

想一想如何执行build这个lane

fastlane build复制代码

只要在终端执行上述命令行就可以了

那么,执行了上述命令之后,fastlane库最终会调用哪一个Tool和Command呢 之前的文章中已经说过了,当没有显式指定Tool和Command时,使用默认的Tool:fastlane和默认Tool的默认Command:trigger

fastlane build的完整命令

fastlane fastlane trigger build复制代码

当使用在Fastfile中定义的lane进行打包、测试和发布时,最终调用的都是trigger这个Command。

lane和action是trigger这个Command内部定义的领域名称,它们只能在trigger中使用,它们和Command不是同一个层次的。只要说起lane和action,那么就默认了Tool是fastlane,Command是trigger

当执行build这个lane之后,最终目的是去执行它包含的action,build内部包含了两个action,分别是matchgym,而这两个action最终会去调用它们同名的Tool。 除了fastlane这个默认的Tool,其他所有的Tool都有其同名的action,通过在lane中添加action,可以调用其他所有的Tool。

除了这些与Tool同名的action,fastlane还内置了其他很多action,比如关于git和pod的。

3. fastlane执行流程

fastlane中所有命令的执行都可以简单的分为两步:

  1. 解析Command
  2. 执行Command

比如常用的fastlane lane_name,这条命令没有显式的指定Tool和Command,所以,fastlane会使用默认Tool:fastlane和默认Tool的默认Command:trigger,然后执行trigger

3.1. 解析Command

fastlane库中几乎所有命令都可以写成下列格式:(如果把fastlane-credentials也当做是一种Tool的话,那这个几乎就可以去掉了。)

fastlane [tool] [command] [args][--key value]复制代码

tool和command指定使用的Tool和其Command;args通常是一个或多个字符串组成的数组;类似--key value-k value格式的组合会被当做option。args和option会被当做参数传给Command。 其中tool、command、args和option用[]包含起来,表示它们可以被省略。如果省略了command和tool,则会使用默认的tool和默认tool的默认command。

下图中展示的是解析Command的简易流程

下列以两个例子来说明

  1. 获取ARGV 例一:终端输入fastlane lane_name,则ARGV = ["lane_name"]; 例二:终端输入fastlane cert --username "your_usernmae" --development false,则ARGV = ["cert", "--username", "your_username", "--development", "false"]

  2. 解析Tool 不同Tool包含的Command不同,确定了Tool,才能真正确定Command。如果ARGV.first是一个Tool的名字,比如:fastlane、cert等,则加载这个Tool,require 'tool_name/commands_generator';如果ARGV.first等于 "fastlane-credentials",则加载require 'credentials_manager';如果都不是,则加载fastlane这个默认的Tool,require "fastlane/commands_generator"。 如果匹配上了Tool之后,删除ARGV.first。 例一:使用默认Tool:fastlaneARGV = [ "lane_name"] 例二:使用Tool:certARGV = ["--username", "your_username", "--development", "false"]

  3. 解析Command 将ARGV复制给一个新数组,在新数组中去掉所有以-开头的字符串对象,然后使用数组的第一个对象去匹配此Tool下的command列表,如果能匹配上,则使用匹配到的Command;如果不能,则使用默认Command。 如果匹配上,则将匹配上的字符串对象从ARGV中删除。 例一:使用fastlane这个Tool的默认Command:triggerARGV = [ "lane_name"] 例二:使用cert这个Tool的默认Command:createARGV = ["--username", "your_username", "--development", "false"] 这里有个问题需要注意一下,当在终端输入fastlane match --type enterprise时,这条命令的初衷是想使用match这个Tool的默认Command:run,但按照本步骤的方法,最终使用的是enterprise这个Command。所以在这里最好显示指定要使用的Command,fastlane match run --type enterprise

  4. 解析command对应的option 遍历ARGV,如果字符串是以---开头,则将此字符串对象和其后的字符串对象作为一对key-value值,并从ARGV中删除这两个对象。遍历完毕之后,将ARGV中剩余的的参数赋值给args。 例一:option等于nil,args等于lane_name 例二:option等于{"username":"your_username", "development": false},args等于nil

  5. 执行command 每个command都会设置一个对应的block,匹配到这个command并解析完option之后,则执行其对应的block,并将[步骤4]中获取的option和args传给这个block。 从这个地方开始,业务代码才会真正开始执行。

上述解析过程描述的非常粗糙,如果想了解详细的解析过程,可以参考****,fastlane内部通过这个库来解析这些参数的。

把这个过程再丰富一下,就变成了下图

(由于篇幅原因,图中只画出了
cert
sigh
fastlane这三个Tool)

3.2. 执行Command

到了这一步,就开始深入到各个Tool的核心内容了,在fastlane这个库中,Tool共有16个,在这里并不会对所有的Tool展开讨论,这里只讨论默认Command:trigger

4. trigger

trigger是fastlane这个Tool的默认命令,其作用是运行一个指定的lane,而fastlane这个Tool又是fastlane库的默认Tool,所以一般在运行lane的时候,可以省略掉Tool和Command,只需要执行命令fastlane [platform_name] lane_name,如果设置了default_platform,platform_name也可以省略。

trigger的目的是去运行一个指定的lane,而运行lane的目的是去执行其中的action,根据这一需求,作图如下

下面以例子的方式来了解这一过程,本文准备了两个自定义action,分别是example_actionexample_action_second,fastlane会将它们加载作为外部action。

4.1. 前提条件

相关文件的目录结构

-fastlane  -Fastfile  -actions    -example_action.rb    -example_action_second.rb复制代码

fastfile

default_platform :iosplatform :ios do    lane :test do |options|        puts "lane options #{options}"        example_action(foo:"ruby", bar:"ios")        example_action_second(foo:"ruby", bar:"ios")    end endlane :test_without_platform do    puts "lane whithout platform"end复制代码

example_action.rb

module Fastlane  module Actions    class ExampleActionAction < Action      def self.run(options)          binding.pry        puts "this is example_action action"          puts options      end       def self.is_supported?(platform)        true      end       def self.available_options        []        end     end   end end复制代码

example_action_second.rb

module Fastlane  module Actions    class ExampleActionSecondAction < Action      def self.run(options)        puts "this is example action second action, options:"        puts "foo:#{options[:foo]}"        puts "bar:#{options[:bar]}"      end      def self.is_supported?(platform)        true      end      def self.available_options          [            FastlaneCore::ConfigItem.new(key: :foo,                                     short_option: "-f",                                     description: "this is foo"),            FastlaneCore::ConfigItem.new(key: :bar,                                     short_option: "-b",                              description: "this is bar")          ]      end    end  endend复制代码

4.2. 执行trigger

在终端执行fastlane test key1:value1 key2:value2 --env local1,local2,按照上文所说的,第一步解析command后,fastlane库找到需要执行的目标command:trigger,然后执行此command对应的block。

fastlane库中trigger命令的定义

command :trigger do |c|        c.syntax = 'fastlane [lane]'        c.description = 'Run a specific lane. Pass the lane name and optionally the platform first.'        c.option('--env STRING[,STRING2]', String, 'Add environment(s) to use with `dotenv`')        c.option('--disable_runner_upgrades', 'Prevents fastlane from attempting to update FastlaneRunner swift project')        c.action do |args, options|          if ensure_fastfile            Fastlane::CommandLineHandler.handle(args, options)          end        end      end复制代码

trigger支持两种option,分别是--env STRING[,STRING2]disable_runner_upgrades,其中第一个option的作用是指定文件名,这些文件会被加载,用来配置环境变量。在当前这个例子中,设置了--env local1,local2,如果.env.local1.env.local2这两个文件存在于Fastfile所在的文件夹或其上级文件夹,则dotenv会去加载它们来设置环境变量。(不管--env有没有设置,都默认加载.env.env.default

执行trigger就是执行下列代码

c.action do |args, options|    if ensure_fastfile       Fastlane::CommandLineHandler.handle(args, options)    end end复制代码

当fastlane库执行这个block时,传入了两个参数,argsoptions,通过解析命令字符串可知,其中args的值为["test", "key1:value1", "key2:value2"]options的值是一个Options类型的对象,且options.env 的值为 "local1,local2"

4.3. 解析lane

解析lane的目的就是获取Fastfile中定义的Lane类型的对象

在这个阶段,fastlane库会加载Fastfile,并将其中定义的lane转换成Fastlane::Lane类型的对象,并将这些对象保存在一个Hash类型的对象lanes中。

Fastlane::Lane中定义的变量

module Fastlane  # Represents a lane  class Lane    attr_accessor :platform    attr_accessor :name    # @return [Array]     attr_accessor :description    attr_accessor :block    # @return [Boolean] Is that a private lane that can't be called from the CLI?    attr_accessor :is_private  endend复制代码

Fastlane::Lane类型的对象中保存了一个lane的所有信息,:platform指定lane使用的平台,:name指定lane的名字,:block保存了lane对应的执行代码。

在本节例子中,lanes保存了所有Fastlane::Lane类型的对象,它的具体结构如下:

{  ios:          {                    test: Lane.new                },  nil:          {                    test_without_platform: lane.new                }}复制代码

fastlane库使用lanes这个Hash对象结合之前得到的args来获取对应Lane类型对象 其伪代码如下:

#使用platform_lane_info保存platform名称和lane名称platform_lane_info = [] #过滤掉带有冒号":"的字符串对象args.each do |current|     unless current.include?(":")         platform_lane_info << current     endend#获取platform名称和lane名称platform_name = nillane_name = nilif platform_lane_info.size >= 2    platform_name = platform_lane_info[0]    lane_name = platform_lane_info[1]else    if platform_lane_info.first 是一个平台名字 || platform_lane_info是空数组        platform_name = platform_lane_info.first        lane_name = 在终端打印一个lane列表供用户选择    else        lane_name = platform_lane_info.first        if platform==nil && lanes[nil][lane_name]==nil            platform = default_platform        end    endend#返回lane对象return lanes[platform][lane_name]复制代码

args的值为["test", "key1:value1", "key2:value2"],把argslanes带入到上述伪代码中,可以得到相应的Lane类型对象。

4.4. 解析lane的options

回顾一下,之前在Fastfile文件中定义test这个lane的代码

platform :ios do    lane :test do |options|        puts "lane options #{options}"        example_action(foo:"ruby", bar:"ios")        example_action_second(foo:"ruby", bar:"ios")    end end复制代码

本步骤的目的就是要获取传给testoptions,它是一个Hash类型的对象。

这个options参数的值是如何得到的,其实,也是通过解析args获取的。

其实现逻辑如下

options = {} args.each do |current|    if current.include?(":")         key, value = current.split(":", 2)        if key.empty?            报错        end        value = true if value == 'true' || value == 'yes'        value = false if value == 'false' || value == 'no'        options[key.to_sym] = value    endend复制代码

上述代码是在fastlane库源代码的基础上作了一些修改

args带入到上述代码中,可以得出lane:test的options的值为{key1:value1, key2:value2}

fastlane test key1:value1 key2:value2 --env local1,local2,在终端执行后,一部分输出如下

[16:37:43]: ------------------------------[16:37:43]: --- Step: default_platform ---[16:37:43]: ------------------------------[16:37:43]: Driving the lane 'ios test' ?[16:37:43]: lane options {:key1=>"value1", :key2=>"value2"}复制代码

4.5. 解析action

解析action的目的是找到action_name对应的类,本例中,需要执行两个action,其action_name分别是example_actionexample_action_second,其对应类分别是ExampleActionActionExampleActionSecondAction

其实现逻辑如下

tmp = action_name.delete("?")class_name = tmp.split("_").collect!(&:capitalize).join + "Action"class_ref = Fastlane::Actions.const_get(class_name)unless class_ref    class_ref = 尝试把action_name当做别名,重新加载endif action_name 是一个lane的名字    执行这个laneelsif class_ref && class_ref.respond_to?(:run)    解析action的options    执行actionelse    报错end复制代码

4.6. 解析action的options

action的options指的是传给action的参数,比如example_action_second这个action的options是{foo:"ruby", bar:"ios"},准确的来说应该是[{foo:"ruby", bar:"ios"}],不过一般都只是用这个数组的第一个对象,所以接下来会去掉外面的一层数组。 本步骤的目的是将传给action的options转换成Configuration类型的对象,并且在转换过程中,验证options中keyvalue的合法性。 action和Configuration类型的对象是一一对应的,Configuration类的作用主要是存储:availabel_options:values,在执行action的时候,也就是在执行action响应类的run方法时,把Configuration类型的对象当做参数传入,然后action响应类使用它来获取key对应的value。

Configuration中定义的实例变量

module FastlaneCore  class Configuration    attr_accessor :available_options    attr_accessor :values    # @return [Array]     attr_reader :all_keys    # @return [String]    attr_accessor :config_file_name    # @return [Hash]     attr_accessor :config_file_options  endend复制代码

:availabel_options表示action响应类中定义的available_options,比如example_action_second这个action,它的响应类是ExampleActionSecondActionExampleActionSecondAction中类方法available_options的定义

def self.available_options          [               FastlaneCore::ConfigItem.new(key: :foo,                                     short_option: "-f",                                     description: "this is foo"),            FastlaneCore::ConfigItem.new(key: :bar,                                     short_option: "-b",                                     description: "this is bar")          ]         end 复制代码

:values表示传给action的options,给:values赋值之后还需要验证它的key、value是否合法,如果不合法,程序中止。比如example_action_second这个action的options是{foo:"ruby", bar:"ios"}

:all_key表示:available_options中的key的数组,具体代码:@available_options.collect(&:key)

:config_file_name:config_file_options:在action的响应类中,可以使用Configuration.load_configuration_file(config_file_name)来加载这个action专有的配置文件,然后把文件中的数据以key:value的方式存储在:cofnig_file_options变量中。

其实现代码如下

values = 传给action的optionsaction_responder = action响应类first_element = (action_responder.available_options || []).firstif (first_element && first_element kind_of?(FastlaneCore::ConfigItem)) || first_element == nil    values = {} if first_element==nil    return FastlaneCore::Configuration.create(action_responder.available_options, values)else    #action响应类中定义了available_options类方法,且其返回对象的第一个元素的类型不是FastlaneCore::ConfigItem,则不对values做任何处理,直接返回。    return valuesend复制代码

创建FastlaneCore::Configuration时,内部的验证逻辑

values = 传给action的optionsaction_responder = action响应类available_options = action_responder.available_options#available_options必须是一个Array,且其内部的元素都必须是FastlaneCore::ConfigItem的类型verify_input_types#values中的每一个key都必须在available_options中定义过,如果在创建FastlaneCore::ConfigItem类型的对象时,设置了type和verify_block,则values中对应的value都必须满足。verify_value_exists#不能再available_options中重复定义同一个keyverify_no_duplicates#在定义FastlaneCore::ConfigItem类型的对象时,可以设置与自己冲突的key,在values中,不能同时存在冲突的两个key。verify_conflicts#在定义FastlaneCore::ConfigItem类型的对象时,同时设置了default_value和verify_block,且values中没有设置这个key,则需要调用verify_block验证default_value的合法性。verify_default_value_matches_verify_block复制代码

4.7. 执行action

执行action就是执行action响应类的类方法run,同时将[步骤6]的解析结果传给run作为参数。类方法run中包含了这个action的所有业务代码,fastlane库中所有的内置action都遵循这一设定,同样,在定义外部action时,也应该这样做。

例子中actionexample_action_second的响应类ExampleActionSecondAction中的run的定义

def self.run(options)    puts "this is example action second action, options:"    puts "foo:#{options[:foo]}"    puts "bar:#{options[:bar]}"end复制代码

其中参数options是一个FastlaneCore::Configuration的对象,可以通过options[key]options.fetch(key)的方式来获取key对应的value。

5. trigger总结

之前一节,以图1的步骤详细讲解了
trigger命令的执行过程,图中的几个步骤完全是从使用者的角度来划分的,单看这几个步骤并不能对fastlane库有一个直观的了解,下列两个图在图一的基础上增加了一些细节。

图2中描述了trigger命令的部分执行过程,大致可以和图1中的前三个步骤相对应。相比之前的执行步骤,图2中增加了一些细节步骤,并且将这些步骤以泳道的方式进行划分。除了之外,其他步骤的执行者比如CLIToolsDistributorCommandsGenerator等都是fastlane库中定义的类,而则是fastlane库引用的外部库。

图3承接图2的步骤,主要描述了Fastfile中定义的lane的执行过程,大致可以和图1中的后三个步骤相对应,图3中步骤的执行者基本上都是Runner这个类。

转载地址:http://ntgpa.baihongyu.com/

你可能感兴趣的文章
Jenkins将致力于提升稳定性、易用性和云原生兼容性
查看>>
Facebook开源工具包LASER,支持93种语言
查看>>
禁止eclipse校验JavaScript
查看>>
从微服务迁移到工作流的经验之谈
查看>>
Oracle再发力,区块链平台多项更新
查看>>
微软发布用于Serverless架构的Azure API Management
查看>>
MongoDB Mobile Sync for iOS推出Beta版本
查看>>
Visual Studio 2015价格大幅下调
查看>>
QCon演讲速递:异步处理在分布式系统中的优化作用
查看>>
Java 20年:转角遇到Go
查看>>
软件测试自动化的最新趋势
查看>>
SpringOne大会上发布了一个实验性的反应式关系型数据库连接驱动R2DBC
查看>>
新JSON绑定库JSON-B发布公开预览版
查看>>
机器人操作系统来到Windows
查看>>
.NET Core运行时和基础类库性能提升
查看>>
Eclipse Open J9:Eclipse OMR项目提供的开源JVM
查看>>
HTTP内容分发——《HTTP权威指南》系列
查看>>
PHP autoload 机制详解
查看>>
302. Smallest Rectangle Enclosing Black Pixels
查看>>
从面向服务架构(SOA)学习:微服务时代应该借鉴的5条经验教训
查看>>