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项目实战