fastlane

354 阅读4分钟

fastlane是脚本自动化工具链

fastlane官方文档

安装xcode command line tools

xcode-select --install

初始化bundle环境

bundle init

修改gemfile添加fastlane依赖

gem "fastlane", '>=2.158.0', '<3.0'

#修改完执行命令
bundle install

#初始化fastlane
bundle exec fastlane init

#运行fastlane
bundle exec fastlane hello

配置fastlane环境变量

export LC_ALL=en_US.UTF-8 
export LANG=en_US.UTF-8
export LANGUAGE=en_US:en

fastlane目录结构

.                    #action的作用域
├── Gemfile
├── Gemfile.lock
└── fastlane         #lane的作用域
    ├── Appfile
    ├── Fastfile
    ├── README.md
    └── report.xml

fastlane 项目架构

  • bundle exec fastlane xxx

    • lane
    • 自定义action
    • 自定义plugin

自定义lane

touch my_lane.rb

================my_lane.rb====================
lane :my_lane do |options|
    UI.success "hello world".blue
    UI.important("lane :#{Dir.pwd}".blue)
    
    Dir.chdir("..") do
        UI.important("lane :#{Dir.pwd}".blue)
    end

    puts options
end

================Fastfile====================
import 'my_lane.rb'

default_platform(:ios)

platform :ios do
  desc "Description of what the lane does"
  lane :hello do
    my_lane
    my_lane(name: 'hanghang', age: 3)
  end
end

#执行命令
bundle exec fastlane hello 或
bundle exec fastlane my_lane name:'hanghang' age:3

lane与lane之间调用

lane :lane1  do |options|

  addr = options[:addr]
  name = options[:name]
  age = options[:age]

  UI.message("name: #{name}".blue)
  UI.message("age: #{age}".blue)

  if addr == "北京" 
    UI.success("addr: #{addr}".blue)
  else
    UI.error("❌ 地址为空!!!")
  end
end


lane :lane2  do |options|

  h = {name: '杭杭', age: 33}

  if false
    h[:addr] = '北京'
  else  
    h[:addr] = '杭州'
  end
  
  lane1(h)
end

lane 返回值与ruby方法一样

lane :lane1  do |options|

  puts lane2

end


lane :lane2  do |options|
  1
end

lane_context

require 'pp'

lane :lane1  do |options|

  pp lane_context[:myName]

end


lane :lane2  do |options|
#  pp lane_context {:PLATFORM_NAME=>nil, :LANE_NAME=>"lane2"}
lane_context[:myName] = '杭杭'

lane1

end

private_lane

lane与lane之间随便调用,命令行不能调用private_lane

private_lane :lane1  do |options|

  pp lane_context[:myName]
  next #结束lane执行
end


lane :lane2  do |options|
#  pp lane_context {:PLATFORM_NAME=>nil, :LANE_NAME=>"lane2"}
lane_context[:myName] = '杭杭'

lane1

end

lane 调用hook

error do |lane, execption, options|
  UI.message("--------[error] #{lane}  (#{lane.class})".blue)
end

before_each do |lane, options|
  UI.message("--------[before_each] #{lane}  (#{lane.class})".blue)
end

before_all do |lane, options|
  UI.message("--------[before_all] #{lane}  (#{lane.class})".blue)
end

after_all do |lane, options|
  UI.message("--------[after_all] #{lane}  (#{lane.class})".blue)
end

after_each do |lane, options|
  UI.message("--------[after_each] #{lane}  (#{lane.class})".blue)
end

lane :test1 do

end

lane :test2 do
  
end

lane :test3 do
  # UI.user_error!("❌ 产生错误")
end

lane :test do

  test1
  test2
  test3

end

环境变量

# export jenkins_ci=1 设置环境变量
# unset jenkins_ci    取消环境变量
if ENV['gitlab_ci']

  error do |lane, execption, options|
   puts '<' * 60
  end

elsif ENV['jenkins_ci']

  error do |lane, execption, options|
    puts '>' * 60
  end
  
end

lane :test3 do
  UI.user_error!("❌ 产生错误")
end

lane :test do
  
  test3

end

send

private_lane :pipeline_prepare do |options|
  puts "pipeline_prepare: #{options}"
end

private_lane :pipeline_doing do |options|
  puts "pipeline_doing: #{options}"
end

private_lane :pipeline_finish do |options|
  puts "pipeline_finish: #{options}"
end

lane :pipeline do |options|
  state = options[:state]
  send("pipeline_#{state}".to_sym, options)
end

环境变量

#方式一 终端
export gitlab_ci=1

#方式二 Fastfile
ENV['gitlab_ci']="ios"

#方式三 dotenv touch .env
WORKSPACE=YourApp.xcworkspace 

require 与 fastlane_require区别

fastlane_require 提示信息更加准确

调用外部可执行文件

lane :hello do
  #第一种方式
  #返回输出结果
  output = Actions.sh('ls -l')
  puts '*' * 60
  UI.message("#{output}".blue)

  #第二种方式
  #返回布尔值
   puts system('ls -l')

  #第三种方式
  #返回输出结果
  output = `ls -l`
  puts '*' * 60
  UI.message("#{output}".blue)

  #第四种方式
  #返回输出结果
  output = sh("ls -l")
  puts '*' * 60
  UI.message("#{output}".blue)
end

拼接多行执行语句

lane :hello do
  # 方式一 空格分隔
  cmds = []
  cmds << "ls"
  cmds << "-l"
  cmds_tos = cmds.join(" ")
  Actions.sh(cmds_tos)

    # 方式二 ;分隔 第一条语句报错 不会终止 会继续执行命令
    cmds = []
    cmds << "cd xxx"
    cmds << "ls -l"
    cmds_tos = cmds.join(";")
    Actions.sh(cmds_tos)

       # 方式三 &&分隔 第一条语句报错 会终止 不会继续执行命令
       cmds = []
       cmds << "cd xxx"
       cmds << "ls -l"
       cmds_tos = cmds.join("&&")
       Actions.sh(cmds_tos)
end

fastlane当做一个 ruby库使用

============== main.rb ==============
require 'fastlane'
Fastlane::FastFile.new.parse(File.read('Fastfile')).runner.execute(:test)

============== Fastfile ==============
lane :test do
  UI.message("hello world".blue)
end

action

set_info_plist_value

lane :hello do |options|

  set_info_plist_value(
    key: 'CFBundleShortVersionString',
    value: '2.0',
    path: '/Users/yutangzhao/Movies/PadZone/PadZone/Classes/Other/System/Info.plist'
  )
end

命令行查看action使用说明

bundle exec fastlane action set_info_plist_value

+-------------------------------------------------------------------------+
|                          set_info_plist_value                           |
+-------------------------------------------------------------------------+
| Sets value to Info.plist of your project as native Ruby data structures |
|                                                                         |
| Created by kohtenko, uwehollatz                                         |
+-------------------------------------------------------------------------+

+------------------+------------------+------------------+---------+
|                   set_info_plist_value Options                   |
+------------------+------------------+------------------+---------+
| Key              | Description      | Env Var(s)       | Default |
+------------------+------------------+------------------+---------+
| key              | Name of key in   | FL_SET_INFO_PLI  |         |
|                  | plist            | ST_PARAM_NAME    |         |
| subkey           | Name of subkey   | FL_SET_INFO_PLI  |         |
|                  | in plist         | ST_SUBPARAM_NAM  |         |
|                  |                  | E                |         |
| value            | Value to setup   | FL_SET_INFO_PLI  |         |
|                  |                  | ST_PARAM_VALUE   |         |
| path             | Path to plist    | FL_SET_INFO_PLI  |         |
|                  | file you want    | ST_PATH          |         |
|                  | to update        |                  |         |
| output_file_nam  | Path to the      | FL_SET_INFO_PLI  |         |
| e                | output file you  | ST_OUTPUT_FILE_  |         |
|                  | want to          | NAME             |         |
|                  | generate         |                  |         |
+------------------+------------------+------------------+---------+

创建自己的action

bundle exec fastlane new_action
# 回车之后输入自己的action名字
# 创建在 ./fastlane/actions/git_clone.rb

调用action

命令行调用action

bundle exec fastlane run git_clone name:'杭杭' age=3

lane中调用action

lane :hello do |options|

  git_clone(
    name: '杭杭',
    age: '3'
  )
 
end

校验 action 合法性

bundle exec fastlane action git_clone

用户交互、文本输出

#1.
UI.message "杭杭爱吃肉 (usually white)"
UI.success "杭杭爱吃肉 (usually green)"
UI.error "杭杭爱吃肉 (usually red)"

#2.
UI.header "inputs"

#3.
name = UI.input("what's your name ?")
if UI.confirm("Are you #{name}")
UI.success "oh yeah  (usually yellow)"
else
UI.error "Wups,  invalid"
end

#4.
pwd = UI.password("your password please:")
UI.message "你的密码是: #{pwd}"

#5.
sex = UI.select("请选择你的性别: ", ["男", "女"])
UI.message "你的性别是: #{sex}"

#6.
UI.user_error!("你写的代码有问题")
UI.crash!("你写的代码有问题") #会打印调用栈
UI.deprecated("你使用的方法已过期")

actions_path 指定action的路径

actions_path('/Users/yutangzhao/Movies/Ruby/fastlane_app/fastlane/actions')

action参数

  • 字符串类型

def self.available_options
 [
  FastlaneCore::ConfigItem.new(
                        key: :name, #参数的名称 是一个symbol对象
                        env_name: "GIT_CLONE_NAME" , #通过环境变量来传参
                        description: "这是一个参数",
                        type: String , #参数的类型
                            )
 ]
end

#第一种方式 通过lane传参
lane :hello do |options|
  git_clone(
    name:'杭杭'
  )
end
      
      
#第二种方式 通过环境变量传参

export GIT_CLONE_NAME='hanghang'
bundle exec fastlane run git_clone

  • 布尔类型参数
def self.available_options
 [
  FastlaneCore::ConfigItem.new(
                        key: :name, #参数的名称 是一个symbol对象
                        env_name: "GIT_CLONE_NAME" , #通过环境变量来传参
                        description: "这是一个参数",
                        type: Boolean , #参数的类型
                            )
 ]
end

#第一种方式 通过lane传参
lane :hello do |options|
  git_clone(
    name: true
  )
end
      
      
#第二种方式 通过环境变量传参

export GIT_CLONE_NAME=true
bundle exec fastlane run git_clone
  • 数组类型参数
def self.available_options
 [
  FastlaneCore::ConfigItem.new(
                        key: :name, #参数的名称 是一个symbol对象
                        env_name: "GIT_CLONE_NAME" , #通过环境变量来传参
                        description: "这是一个参数",
                        type: Array , #参数的类型
                            )
 ]
end

#第一种方式 通过lane传参
lane :hello do |options|
  git_clone(
    name: %W[aaa bbb ccc]
  )
end
      
      
#第二种方式 通过环境变量传参

export GIT_CLONE_NAME=aaa,bbb,ccc
bundle exec fastlane run git_clone
  • 任意类型参数
===================Fastfile=====================
lane :hello do |options|

  git_clone( name: 1 )

  git_clone(  name: true )

  git_clone( name: 'hanghang' )

  git_clone(name: %W[aaa bbb ccc] )

  git_clone( name: {name: 'hanghang'} )
 
end

===================git_clone.rb=====================

def self.available_options
        [          FastlaneCore::ConfigItem.new(                                       key: :name, #参数的名称 是一个symbol对象                                       env_name: "GIT_CLONE_NAME" , #通过环境变量来传参                                       description: "这是一个参数",                                       is_string: false, #=> 这个参数是万能类型的                                       verify_block: ->(value) { verify_option(value)}                                       )        ]
      end

      #参数类型校验
      def self.verify_option(value)
        case value
        when String
          @@polymorphic_option = value
        when Array
          @@polymorphic_option = value.join( )
        when Hash
          @@polymorphic_option = value.to_s
        else
          UI.user_error! "Invalid option: #{value.inspect}"
        end
      end
  • Proc回调类型
 def self.run(params)

callback = params[:callback]
callback.call(['aaa', 'bbb']) if callback

end

def self.available_options
[
    FastlaneCore::ConfigItem.new(
                                 key: :callback, #参数的名称 是一个symbol对象
                                 env_name: "GIT_CLONE_NAME" , #通过环境变量来传参
                                 description: "这是一个参数",
                                 type: Proc
                                 )
]
end

#调用
  callback = lambda do |options| 
    puts options
  end
  git_clone(callback: callback)

callback几种调用方式

  #方式一
  callback = lambda do |options| 
    puts options
  end

    #方式二
  callback = Proc.new do |options| 
    puts options
  end

  #方式三
  callback = ->(value) {
    puts value
  }

校验传入的参数

#action 校验
def self.available_options
        [
          FastlaneCore::ConfigItem.new(
                                       key: :url, #参数的名称 是一个symbol对象
                                       env_name: "GIT_CLONE_NAME" , #通过环境变量来传参
                                       description: "这是一个参数",
                                       type: String,
                                       verify_block: ->(value) {
                                        uri = URI(value)
                                        UI.user_error!("Invalide scheme #{uri.scheme}") unless uri.scheme == "http" || uri.scheme == "https"
                                       }
                                       )
        ]
      end
      
#调用
git_clone(url: 'https://baidu.com')

互斥参数

def self.available_options
        [
          FastlaneCore::ConfigItem.new(
                                       key: :arg1, #参数的名称 是一个symbol对象
                                       type: String,
                                       conflicting_options: [:arg2],
                                       conflict_block: conflictBlock
                                       ),
          FastlaneCore::ConfigItem.new(
                                        key: :arg2, #参数的名称 是一个symbol对象
                                        type: String,
                                        conflicting_options: [:arg1]
                                        )
        ]
      end

      #参数类型校验
      def self.conflictBlock
         -> (value) do
          puts "这个参数多余"
         end
      end
      
      
 git_clone(url: 'https://baidu.com')
 git_clone(arg1: 'https://baidu.com')
 # arg1和arg2只能传一个
 git_clone(arg1: 'https://baidu.com', arg2: 'https://baidu.com')

参数默认值

# 第一种方式
  FastlaneCore::ConfigItem.new(
                                       key: :url, #参数的名称 是一个symbol对象
                                       env_name: "GIT_CLONE_NAME" , #通过环境变量来传参
                                       description: "这是一个参数",
                                       type: String,
                                       optional: true
                                       default_value: "https://www.baidu.com"
                                       )
# 第二种方式
def self.run(params)
config = prams[:url] || "Release"
end

从文件读取参数

==================actionParams==================
name('hanghang')
age(3)

==================git_clone.rb==================
 def self.run(params)

       params.load_configuration_file("actionParams")
       name = params[:name]
       age = params[:age]

       UI.success "姓名是: #{name} 年龄: #{age}".blue
      end
      
      def self.available_options
        [
          FastlaneCore::ConfigItem.new(
                                       key: :name, 
                                       type: String
                                       ),
          FastlaneCore::ConfigItem.new(
                                        key: :age, 
                                        type: Integer
                                        )
        ]
      end
      
     ==================Fastfile===================== 
     lane :hello do |options| 
     git_clone
     end

action嵌套action代码封装到helper

=============eat.rb=============
require 'fastlane_core/ui/ui'

module Fastlane 
    UI = Fastlane::UI unless Fastlane.const_defined?('UI')
    module Helper
        class EatHelper
            def self.show_message
                UI.message "[helper eat ......]".blue
            end
        end
    end
end

=============git_clone.rb=============
 class GitCloneAction < Action
      def self.run(params)
        require 'eat'
        Helper::EatHelper.show_message
      end
end

=============Fastfile=============
helper = File.expand_path('helper',__dir__)
$LOAD_PATH.unshift(helper) unless $LOAD_PATH.include?(helper)

lane :hello do |options|
  git_clone
end

真实案例

=============git_clone.rb=============

 class GitCloneAction < Action
      def self.run(params)
        git = params[:git]
        branch = params[:branch]
        tag = params[:tag]
        depth = params[:depth]
        dest = params[:dest]

        cmds = ["git clone"]
        cmds << git
        cmds << "-b #{branch}" if branch
        cmds << "-b #{tag}" if tag
        cmds << "--depth #{depth}" if depth
        cmds << dest
        cmd_tos = cmds.join(' ')
        puts cmd_tos
        Action.sh(cmd_tos)
        
      end
      
      def self.available_options
        [
          FastlaneCore::ConfigItem.new(
          key: :git,
          description: '克隆地址',
          type: String,
      ),
      FastlaneCore::ConfigItem.new(
          key: :branch,
          description: '分支名',
          type: String,
          optional:true,
          conflicting_options: %i[tag]
      ),
      FastlaneCore::ConfigItem.new(
          key: :tag,
          description: '标签名',
          type: String,
          optional:true,
          conflicting_options: %i[branch]
      ),
      FastlaneCore::ConfigItem.new(
          key: :depth,
          description: '层次',
          type: Integer,
          optional:true
      ),
      FastlaneCore::ConfigItem.new(
          key: :dest,
          description: '克隆路径',
          type: String
      )
        ]
      end
      
      
      =============Fastfile=============
      
  lane :hello do |options|

  git_clone(
    git: 'https://github.com/mackwic/colored.git', 
    branch: 'master', 
    depth: 1, 
    dest: '/Users/yutangzhao/Movies/output'
    )
end
      

pulgin

action缺点:

只能在本地fastlane项目中使用或拷贝给别人使用,复用性查

添加plugin依赖

bundle exec fastlane  add_plugin git_clone

==========Pluginfile=========
gem 'fastlane-plugin-pgyer'

安装plugin

#第一种方式
bundle exec fastlane install_plugins
#第二种方式
bundle install

删除plugin

# 第一步删除Pluginfile种的pulgin
# gem 'fastlane-plugin-pgyer'

#第二步 执行 bundle install

plugin本质

本质就是对action的包装 以gem包的形式提供给外界使用

创建plugin项目

#创建plugin
bundle exec fastlane new_plugin open_finder

#将fastlane-plugin-open_dock拖到桌面,编辑action

===========Fastfile===========
lane :test do
  open_dock(path: '/Users/yutangzhao/Desktop')
end

===========open_dock_action.rb===========

require 'fastlane/action'
require_relative '../helper/open_dock_helper'

module Fastlane
  module Actions
    class OpenDockAction < Action
      def self.run(params)
        path = params[:path]
        system("open #{path}")
        Fastlane::Helper::OpenDockHelper.show_message
      end

      def self.description
        "this is a took for mac os x to open finder"
      end

      def self.authors
        ["tanggev587"]
      end

      def self.return_value
        # If your method provides a return value, you can describe here what it does
      end

      def self.details
        "this is a took for mac os x to open finder ."
      end

      def self.available_options
        [
          FastlaneCore::ConfigItem.new(key: :path,
                               description: "传入一个要打开的路径",
                                      type: String)
        ]
      end

      def self.is_supported?(platform)
        true
      end
    end
  end
end





#查看action
bundle exec fastlane action open_dock

[✔] 🚀 
+---------------------------+---------+-----------+
|                  Used plugins                   |
+---------------------------+---------+-----------+
| Plugin                    | Version | Action    |
+---------------------------+---------+-----------+
| fastlane-plugin-open_dock | 0.1.0   | open_dock |
+---------------------------+---------+-----------+

Loading documentation for open_dock:

+----------------------------------------------+
|                  open_dock                   |
+----------------------------------------------+
| this is a took for mac os x to open finder   |
|                                              |
| this is a took for mac os x to open finder . |
|                                              |
| Created by tanggev587                        |
+----------------------------------------------+

+------+-------------+------------+---------+
|             open_dock Options             |
+------+-------------+------------+---------+
| Key  | Description | Env Var(s) | Default |
+------+-------------+------------+---------+
| path | 传入一个要打开的路径  |            |         |
+------+-------------+------------+---------+
* = default value is dependent on the user's system

#执行action
bundle exec fastlane test

使用本地plugin


===========Pluginfile===========
gem 'fastlane-plugin-open_dock', path: '/Users/yutangzhao/Desktop/fastlane-plugin-open_dock'

===========Fastfile===========
lane :test do
  open_dock(path: '/Users/yutangzhao/Desktop')
end

#1.
bundle install

#2.
bundle exec fastlane hello

发布plugin

关联git

#1.
cd fastlane-plugin-open_dock

#2.
git init

#3.
git add .

#4.
git commit -m '创建plugin'

#5。
git remote add origin https://gitee.com/TanggeV587/fastlane-plugin-open_dock.git

#6.
git push -u origin master

发布

参考 ruby项目实战