Fastlane(二):結(jié)構(gòu)

前言

在終端中執(zhí)行fastlane lane_name之后,fastlane會(huì)去執(zhí)行Fastfile中定義的同名lane,這個(gè)是如何實(shí)現(xiàn)的。
本文按照解析參數(shù)這一主線,嘗試解釋fastlane的執(zhí)行邏輯和內(nèi)部結(jié)構(gòu)。

在開(kāi)始正文之前,有一些概念和名稱需要解釋一下,在之前的文章中,已經(jīng)提到過(guò)一些fastlane的領(lǐng)域?qū)S妹Q,比如platform、lane、action等,除了這些以外,還有兩個(gè)重要的名稱需要了解一下,Command和Tool。

1. Tool和Command

fastlane是一個(gè)龐大的工具集,為了更好的使用和管理這些工具,將功能相似的工具劃分在一起組成一個(gè)Tool,每一種Tool都代表fastlane的一個(gè)大的功能點(diǎn)。

fastlane中的Tool列表:

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

每一個(gè)Tool都有其特定的應(yīng)用領(lǐng)域,比如cert用于證書(shū)相關(guān),sigh用于簽名相關(guān),gym用于打包相關(guān),等等。
其中,fastlane是默認(rèn)的Tool,比如fastlane lane_name、fastlane init、fastlane action action_namefastlane add_plugin plugin_name等,因?yàn)檫@些命令都沒(méi)有顯式的指定Tool,所以使用的都是fastlane這個(gè)Tool,它是fastlane庫(kù)中最重要的Tool。

每一種Tool下都有多個(gè)Command,如果把Tool看做是某個(gè)領(lǐng)域的專用工具,Command則是其中的一個(gè)操作,比如cert就是專門用于簽名證書(shū)相關(guān)的Tool,當(dāng)需要?jiǎng)?chuàng)建新的簽名證書(shū)時(shí),可以使用cert下的create這個(gè)Command,其具體的執(zhí)行命令是fastlane cert creat,因?yàn)?code>create是默認(rèn)命令,所以也可以使用fastlane cert;當(dāng)需要移除過(guò)期證書(shū)時(shí),則可以使用revoke_expired這個(gè)Command,其具體的命令是fastlane cert revoke_expired。

上文中提到的幾條命令,fastlane init中的init,fastlane action action_name中的acton,fastlane add_plugin plugin_name中的add_plugin等,這些都是fastlane這個(gè)默認(rèn)Tool的Command。而fastlane lane_name使用的是默認(rèn)Tool的默認(rèn)Command:trigger

Command必須和Tool結(jié)合起來(lái)才有意義,因?yàn)椴煌琓ool下的Command可能會(huì)出現(xiàn)同名的情況,fastlane允許這種情況出現(xiàn)。只有確定了Tool之后,才能確定真正的Command。

2. lane、action

之前在Fastlane用法中有講到lane和action的簡(jiǎn)單使用,這里再結(jié)合Tool和Command,談一談它們的聯(lián)系和區(qū)別。

default_platform :ios

lane :build do
    match(git_url: your_git_url)
    gym(export_method: 'enterprise')
end

上述代碼中的build是一個(gè)lane,matchgym都是action。

想一想如何執(zhí)行build這個(gè)lane

fastlane build

只要在終端執(zhí)行上述命令行就可以了

那么,執(zhí)行了上述命令之后,fastlane庫(kù)最終會(huì)調(diào)用哪一個(gè)Tool和Command呢
之前的文章中已經(jīng)說(shuō)過(guò)了,當(dāng)沒(méi)有顯式指定Tool和Command時(shí),使用默認(rèn)的Tool:fastlane和默認(rèn)Tool的默認(rèn)Command:trigger。

fastlane build的完整命令

fastlane fastlane trigger build

當(dāng)使用在Fastfile中定義的lane進(jìn)行打包、測(cè)試和發(fā)布時(shí),最終調(diào)用的都是trigger這個(gè)Command。

lane和action是trigger這個(gè)Command內(nèi)部定義的領(lǐng)域名稱,它們只能在trigger中使用,它們和Command不是同一個(gè)層次的。只要說(shuō)起lane和action,那么就默認(rèn)了Tool是fastlane,Command是trigger。

當(dāng)執(zhí)行build這個(gè)lane之后,最終目的是去執(zhí)行它包含的action,build內(nèi)部包含了兩個(gè)action,分別是matchgym,而這兩個(gè)action最終會(huì)去調(diào)用它們同名的Tool。
除了fastlane這個(gè)默認(rèn)的Tool,其他所有的Tool都有其同名的action,通過(guò)在lane中添加action,可以調(diào)用其他所有的Tool。

除了這些與Tool同名的action,fastlane還內(nèi)置了其他很多action,比如關(guān)于git和pod的。

3. fastlane執(zhí)行流程

fastlane中所有命令的執(zhí)行都可以簡(jiǎn)單的分為兩步:

  1. 解析Command
  2. 執(zhí)行Command

比如常用的fastlane lane_name,這條命令沒(méi)有顯式的指定Tool和Command,所以,fastlane會(huì)使用默認(rèn)Tool:fastlane和默認(rèn)Tool的默認(rèn)Command:trigger,然后執(zhí)行trigger。

3.1. 解析Command

fastlane庫(kù)中幾乎所有命令都可以寫(xiě)成下列格式:(如果把fastlane-credentials也當(dāng)做是一種Tool的話,那這個(gè)幾乎就可以去掉了。)

fastlane [tool] [command] [args][--key value]

tool和command指定使用的Tool和其Command;args通常是一個(gè)或多個(gè)字符串組成的數(shù)組;類似--key value-k value格式的組合會(huì)被當(dāng)做option。args和option會(huì)被當(dāng)做參數(shù)傳給Command。
其中tool、command、args和option用[]包含起來(lái),表示它們可以被省略。如果省略了command和tool,則會(huì)使用默認(rèn)的tool和默認(rèn)tool的默認(rèn)command。

下圖中展示的是解析Command的簡(jiǎn)易流程


下列以兩個(gè)例子來(lái)說(shuō)明

  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是一個(gè)Tool的名字,比如:fastlane、cert等,則加載這個(gè)Tool,require 'tool_name/commands_generator';如果ARGV.first等于 "fastlane-credentials",則加載require 'credentials_manager';如果都不是,則加載fastlane這個(gè)默認(rèn)的Tool,require "fastlane/commands_generator"
    如果匹配上了Tool之后,刪除ARGV.first。
    例一:使用默認(rèn)Tool:fastlane,ARGV = [ "lane_name"]
    例二:使用Tool:certARGV = ["--username", "your_username", "--development", "false"]

  3. 解析Command
    將ARGV復(fù)制給一個(gè)新數(shù)組,在新數(shù)組中去掉所有以-開(kāi)頭的字符串對(duì)象,然后使用數(shù)組的第一個(gè)對(duì)象去匹配此Tool下的command列表,如果能匹配上,則使用匹配到的Command;如果不能,則使用默認(rèn)Command。
    如果匹配上,則將匹配上的字符串對(duì)象從ARGV中刪除。
    例一:使用fastlane這個(gè)Tool的默認(rèn)Command:trigger,ARGV = [ "lane_name"]
    例二:使用cert這個(gè)Tool的默認(rèn)Command:createARGV = ["--username", "your_username", "--development", "false"]
    這里有個(gè)問(wèn)題需要注意一下,當(dāng)在終端輸入fastlane match --type enterprise時(shí),這條命令的初衷是想使用match這個(gè)Tool的默認(rèn)Command:run,但按照本步驟的方法,最終使用的是enterprise這個(gè)Command。所以在這里最好顯示指定要使用的Command,fastlane match run --type enterprise。

  4. 解析command對(duì)應(yīng)的option
    遍歷ARGV,如果字符串是以---開(kāi)頭,則將此字符串對(duì)象和其后的字符串對(duì)象作為一對(duì)key-value值,并從ARGV中刪除這兩個(gè)對(duì)象。遍歷完畢之后,將ARGV中剩余的的參數(shù)賦值給args。
    例一:option等于nil,args等于lane_name
    例二:option等于{"username":"your_username", "development": false},args等于nil

  1. 執(zhí)行command
    每個(gè)command都會(huì)設(shè)置一個(gè)對(duì)應(yīng)的block,匹配到這個(gè)command并解析完option之后,則執(zhí)行其對(duì)應(yīng)的block,并將[步驟4]中獲取的option和args傳給這個(gè)block。
    從這個(gè)地方開(kāi)始,業(yè)務(wù)代碼才會(huì)真正開(kāi)始執(zhí)行。

上述解析過(guò)程描述的非常粗糙,如果想了解詳細(xì)的解析過(guò)程,可以參考commander,fastlane內(nèi)部通過(guò)這個(gè)庫(kù)來(lái)解析這些參數(shù)的。

把這個(gè)過(guò)程再豐富一下,就變成了下圖


(由于篇幅原因,圖中只畫(huà)出了cert、sighfastlane這三個(gè)Tool)

3.2. 執(zhí)行Command

到了這一步,就開(kāi)始深入到各個(gè)Tool的核心內(nèi)容了,在fastlane這個(gè)庫(kù)中,Tool共有16個(gè),在這里并不會(huì)對(duì)所有的Tool展開(kāi)討論,這里只討論默認(rèn)Command:trigger。

4. trigger

trigger是fastlane這個(gè)Tool的默認(rèn)命令,其作用是運(yùn)行一個(gè)指定的lane,而fastlane這個(gè)Tool又是fastlane庫(kù)的默認(rèn)Tool,所以一般在運(yùn)行l(wèi)ane的時(shí)候,可以省略掉Tool和Command,只需要執(zhí)行命令fastlane [platform_name] lane_name,如果設(shè)置了default_platform,platform_name也可以省略。

trigger的目的是去運(yùn)行一個(gè)指定的lane,而運(yùn)行l(wèi)ane的目的是去執(zhí)行其中的action,根據(jù)這一需求,作圖如下

下面以例子的方式來(lái)了解這一過(guò)程,本文準(zhǔn)備了兩個(gè)自定義action,分別是example_actionexample_action_second,fastlane會(huì)將它們加載作為外部action。

1. 前提條件
相關(guān)文件的目錄結(jié)構(gòu)

-fastlane
  -Fastfile
  -actions
    -example_action.rb
    -example_action_second.rb

fastfile

default_platform :ios

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

lane :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
  end
end

2. 執(zhí)行trigger
在終端執(zhí)行fastlane test key1:value1 key2:value2 --env local1,local2,按照上文所說(shuō)的,第一步解析command后,fastlane庫(kù)找到需要執(zhí)行的目標(biāo)command:trigger,然后執(zhí)行此command對(duì)應(yīng)的block。

fastlane庫(kù)中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,其中第一個(gè)option的作用是指定文件名,這些文件會(huì)被dotenv加載,用來(lái)配置環(huán)境變量。在當(dāng)前這個(gè)例子中,設(shè)置了--env local1,local2,如果.env.local1.env.local2這兩個(gè)文件存在于Fastfile所在的文件夾或其上級(jí)文件夾,則dotenv會(huì)去加載它們來(lái)設(shè)置環(huán)境變量。(不管--env有沒(méi)有設(shè)置,dotenv都默認(rèn)加載.env.env.default

執(zhí)行trigger就是執(zhí)行下列代碼

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

當(dāng)fastlane庫(kù)執(zhí)行這個(gè)block時(shí),傳入了兩個(gè)參數(shù),argsoptions,通過(guò)解析命令字符串可知,其中args的值為["test", "key1:value1", "key2:value2"]options的值是一個(gè)Options類型的對(duì)象,且options.env 的值為 "local1,local2"。

3. 解析lane
解析lane的目的就是獲取Fastfile中定義的Lane類型的對(duì)象

在這個(gè)階段,fastlane庫(kù)會(huì)加載Fastfile,并將其中定義的lane轉(zhuǎn)換成Fastlane::Lane類型的對(duì)象,并將這些對(duì)象保存在一個(gè)Hash類型的對(duì)象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
  end
end

Fastlane::Lane類型的對(duì)象中保存了一個(gè)lane的所有信息,:platform指定lane使用的平臺(tái),:name指定lane的名字,:block保存了lane對(duì)應(yīng)的執(zhí)行代碼。

在本節(jié)例子中,lanes保存了所有Fastlane::Lane類型的對(duì)象,它的具體結(jié)構(gòu)如下:

{
  ios:          {
                    test: Lane.new
                },
  nil:          {
                    test_without_platform: lane.new
                }
}

fastlane庫(kù)使用lanes這個(gè)Hash對(duì)象結(jié)合之前得到的args來(lái)獲取對(duì)應(yīng)Lane類型對(duì)象
其偽代碼如下:

#使用platform_lane_info保存platform名稱和lane名稱
platform_lane_info = [] 
#過(guò)濾掉帶有冒號(hào)":"的字符串對(duì)象
args.each do |current|
     unless current.include?(":")
         platform_lane_info << current
     end
end

#獲取platform名稱和lane名稱
platform_name = nil
lane_name = nil
if platform_lane_info.size >= 2
    platform_name = platform_lane_info[0]
    lane_name = platform_lane_info[1]
else
    if platform_lane_info.first 是一個(gè)平臺(tái)名字 || platform_lane_info是空數(shù)組
        platform_name = platform_lane_info.first
        lane_name = 在終端打印一個(gè)lane列表供用戶選擇
    else
        lane_name = platform_lane_info.first
        if platform==nil && lanes[nil][lane_name]==nil
            platform = default_platform
        end
    end
end
#返回lane對(duì)象
return lanes[platform][lane_name]

args的值為["test", "key1:value1", "key2:value2"],把argslanes帶入到上述偽代碼中,可以得到相應(yīng)的Lane類型對(duì)象。

4. 解析lane的options
回顧一下,之前在Fastfile文件中定義test這個(gè)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,它是一個(gè)Hash類型的對(duì)象。

這個(gè)options參數(shù)的值是如何得到的,其實(shí),也是通過(guò)解析args獲取的。

其實(shí)現(xiàn)邏輯如下

options = {} 
args.each do |current|
    if current.include?(":") 
        key, value = current.split(":", 2)
        if key.empty?
            報(bào)錯(cuò)
        end
        value = true if value == 'true' || value == 'yes'
        value = false if value == 'false' || value == 'no'
        options[key.to_sym] = value
    end
end

上述代碼是在fastlane庫(kù)源代碼的基礎(chǔ)上作了一些修改

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

fastlane test key1:value1 key2:value2 --env local1,local2,在終端執(zhí)行后,一部分輸出如下

[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"}

5. 解析action
解析action的目的是找到action_name對(duì)應(yīng)的類,本例中,需要執(zhí)行兩個(gè)action,其action_name分別是example_actionexample_action_second,其對(duì)應(yīng)類分別是ExampleActionActionExampleActionSecondAction

其實(shí)現(xiàn)邏輯如下

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 = 嘗試把a(bǔ)ction_name當(dāng)做別名,重新加載
end

if action_name 是一個(gè)lane的名字
    執(zhí)行這個(gè)lane
elsif class_ref && class_ref.respond_to?(:run)
    解析action的options
    執(zhí)行action
else
    報(bào)錯(cuò)
end

6. 解析action的options
action的options指的是傳給action的參數(shù),比如example_action_second這個(gè)action的options是{foo:"ruby", bar:"ios"},準(zhǔn)確的來(lái)說(shuō)應(yīng)該是[{foo:"ruby", bar:"ios"}],不過(guò)一般都只是用這個(gè)數(shù)組的第一個(gè)對(duì)象,所以接下來(lái)會(huì)去掉外面的一層數(shù)組。
本步驟的目的是將傳給action的options轉(zhuǎn)換成Configuration類型的對(duì)象,并且在轉(zhuǎn)換過(guò)程中,驗(yàn)證options中keyvalue的合法性。
action和Configuration類型的對(duì)象是一一對(duì)應(yīng)的,Configuration類的作用主要是存儲(chǔ):availabel_options:values,在執(zhí)行action的時(shí)候,也就是在執(zhí)行action響應(yīng)類的run方法時(shí),把Configuration類型的對(duì)象當(dāng)做參數(shù)傳入,然后action響應(yīng)類使用它來(lái)獲取key對(duì)應(yīng)的value。

Configuration中定義的實(shí)例變量

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
  end
end

:availabel_options表示action響應(yīng)類中定義的available_options,比如example_action_second這個(gè)action,它的響應(yīng)類是ExampleActionSecondAction,ExampleActionSecondAction中類方法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賦值之后還需要驗(yàn)證它的key、value是否合法,如果不合法,程序中止。比如example_action_second這個(gè)action的options是{foo:"ruby", bar:"ios"}。

:all_key表示:available_options中的key的數(shù)組,具體代碼:@available_options.collect(&:key)

:config_file_name:config_file_options:在action的響應(yīng)類中,可以使用Configuration.load_configuration_file(config_file_name)來(lái)加載這個(gè)action專有的配置文件,然后把文件中的數(shù)據(jù)以key:value的方式存儲(chǔ)在:cofnig_file_options變量中。

其實(shí)現(xiàn)代碼如下

values = 傳給action的options
action_responder = action響應(yīng)類
first_element = (action_responder.available_options || []).first

if (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響應(yīng)類中定義了available_options類方法,且其返回對(duì)象的第一個(gè)元素的類型不是FastlaneCore::ConfigItem,則不對(duì)values做任何處理,直接返回。
    return values
end

創(chuàng)建FastlaneCore::Configuration時(shí),內(nèi)部的驗(yàn)證邏輯

values = 傳給action的options
action_responder = action響應(yīng)類
available_options = action_responder.available_options

#available_options必須是一個(gè)Array,且其內(nèi)部的元素都必須是FastlaneCore::ConfigItem的類型
verify_input_types

#values中的每一個(gè)key都必須在available_options中定義過(guò),如果在創(chuàng)建FastlaneCore::ConfigItem類型的對(duì)象時(shí),設(shè)置了type和verify_block,則values中對(duì)應(yīng)的value都必須滿足。
verify_value_exists

#不能再available_options中重復(fù)定義同一個(gè)key
verify_no_duplicates

#在定義FastlaneCore::ConfigItem類型的對(duì)象時(shí),可以設(shè)置與自己沖突的key,在values中,不能同時(shí)存在沖突的兩個(gè)key。
verify_conflicts

#在定義FastlaneCore::ConfigItem類型的對(duì)象時(shí),同時(shí)設(shè)置了default_value和verify_block,且values中沒(méi)有設(shè)置這個(gè)key,則需要調(diào)用verify_block驗(yàn)證default_value的合法性。
verify_default_value_matches_verify_block

7. 執(zhí)行action
執(zhí)行action就是執(zhí)行action響應(yīng)類的類方法run,同時(shí)將[步驟6]的解析結(jié)果傳給run作為參數(shù)。類方法run中包含了這個(gè)action的所有業(yè)務(wù)代碼,fastlane庫(kù)中所有的內(nèi)置action都遵循這一設(shè)定,同樣,在定義外部action時(shí),也應(yīng)該這樣做。

例子中actionexample_action_second的響應(yīng)類ExampleActionSecondAction中的run的定義

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

其中參數(shù)options是一個(gè)FastlaneCore::Configuration的對(duì)象,可以通過(guò)options[key]options.fetch(key)的方式來(lái)獲取key對(duì)應(yīng)的value。

4. trigger總結(jié)

1

之前一節(jié),以圖1的步驟詳細(xì)講解了trigger命令的執(zhí)行過(guò)程,圖中的幾個(gè)步驟完全是從使用者的角度來(lái)劃分的,單看這幾個(gè)步驟并不能對(duì)fastlane庫(kù)有一個(gè)直觀的了解,下列兩個(gè)圖在圖一的基礎(chǔ)上增加了一些細(xì)節(jié)。

2

3

圖2中描述了trigger命令的部分執(zhí)行過(guò)程,大致可以和圖1中的前三個(gè)步驟相對(duì)應(yīng)。相比之前的執(zhí)行步驟,圖2中增加了一些細(xì)節(jié)步驟,并且將這些步驟以泳道的方式進(jìn)行劃分。除了Commander之外,其他步驟的執(zhí)行者比如CLIToolsDistributor、CommandsGenerator等都是fastlane庫(kù)中定義的類,而Commander則是fastlane庫(kù)引用的外部庫(kù)。

圖3承接圖2的步驟,主要描述了Fastfile中定義的lane的執(zhí)行過(guò)程,大致可以和圖1中的后三個(gè)步驟相對(duì)應(yīng),圖3中步驟的執(zhí)行者基本上都是Runner這個(gè)類。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • fastlane運(yùn)行所需要的環(huán)境: OS X 10.9以上 Ruby 2.0 以上 Xcode 擁有一個(gè)開(kāi)發(fā)者賬號(hào)...
    阿姣_0405閱讀 3,026評(píng)論 0 4
  • 什么是Fastlane? 官方自己的定義是這樣的: fastlane is a tool for iOS, Mac...
    會(huì)武功的蚊子閱讀 7,489評(píng)論 5 15
  • fastlane是一個(gè)自動(dòng)化構(gòu)建工具,主要包含測(cè)試、打包、發(fā)布等功能,它內(nèi)部是由ruby實(shí)現(xiàn)的,是一款自動(dòng)化非常高...
    wangzzzzz閱讀 6,566評(píng)論 3 19
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,969評(píng)論 19 139
  • 早晨七點(diǎn),正值學(xué)生上學(xué)的高峰期。附近有一所小學(xué),只見(jiàn)背著大大的書(shū)包,穿著比自身大出好幾碼校服的小學(xué)生們?nèi)齼蓛傻赝?..
    琬珂玥閱讀 268評(píng)論 2 1