Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for keyword arguments for lanes in Ruby 3 #21587

Merged
merged 8 commits into from Oct 19, 2023
10 changes: 9 additions & 1 deletion fastlane/lib/fastlane/lane.rb
Expand Up @@ -24,11 +24,19 @@ def initialize(platform: nil, name: nil, description: nil, block: nil, is_privat
self.platform = platform
self.name = name
self.description = description
self.block = block
# We want to support _both_ lanes expecting a `Hash` (like `lane :foo do |options|`), and lanes expecting
# keyword parameters (like `lane :foo do |param1:, param2:, param3: 'default value'|`)
block_expects_keywords = !block.nil? && block.parameters.any? { |type, _| [:key, :keyreq].include?(type) }
# Conversion of the `Hash` parameters (passed by `Lane#call`) into keywords has to be explicit in Ruby 3
# https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/
self.block = block_expects_keywords ? proc { |options| block.call(**options) } : block
self.is_private = is_private
end

# Execute this lane
#
# @param [Hash] parameters The Hash of parameters to pass to the lane
#
def call(parameters)
block.call(parameters || {})
end
Expand Down
2 changes: 1 addition & 1 deletion fastlane/lib/fastlane/runner.rb
Expand Up @@ -15,7 +15,7 @@ def full_lane_name

# This will take care of executing **one** lane. That's when the user triggers a lane from the CLI for example
# This method is **not** executed when switching a lane
# @param lane_name The name of the lane to execute
# @param lane The name of the lane to execute
# @param platform The name of the platform to execute
# @param parameters [Hash] The parameters passed from the command line to the lane
def execute(lane, platform = nil, parameters = nil)
Expand Down
13 changes: 13 additions & 0 deletions fastlane/spec/fixtures/fastfiles/FastfileLaneKeywordParams
@@ -0,0 +1,13 @@
platform :ios do
lane :lane_no_param do
'No parameter'
end

lane :lane_hash_param do |options|
"name: #{options[:name].inspect}; version: #{options[:version].inspect}; interactive: #{options[:interactive].inspect}"
end

lane :lane_kw_params do |name:, version:, interactive: true|
"name: #{name.inspect}; version: #{version.inspect}; interactive: #{interactive.inspect}"
end
end
80 changes: 80 additions & 0 deletions fastlane/spec/runner_spec.rb
Expand Up @@ -20,6 +20,7 @@
it "doesn't show private lanes" do
expect(@ff.runner.available_lanes).to_not(include('android such_private'))
end

describe "step_name override" do
it "handle overriding of step_name" do
allow(Fastlane::Actions).to receive(:execute_action).with('Let it Frame')
Expand All @@ -32,5 +33,84 @@
end
end
end

describe "#execute" do
before do
@ff = Fastlane::FastFile.new('./fastlane/spec/fixtures/fastfiles/FastfileLaneKeywordParams')
end

context 'when a lane does not expect any parameter' do
it 'accepts calling the lane with no parameter' do
result = @ff.runner.execute(:lane_no_param, :ios)
expect(result).to eq('No parameter')
end

it 'accepts calling the lane with arbitrary (unused) parameter' do
result = @ff.runner.execute(:lane_no_param, :ios, { unused1: 42, unused2: true })
expect(result).to eq('No parameter')
end
end

context 'when a lane expects its parameters as a Hash' do
it 'accepts calling the lane with no parameter at all' do
result = @ff.runner.execute(:lane_hash_param, :ios)
expect(result).to eq('name: nil; version: nil; interactive: nil')
end

it 'accepts calling the lane with less parameters than used by the lane' do
result = @ff.runner.execute(:lane_hash_param, :ios, { version: '12.3' })
expect(result).to eq('name: nil; version: "12.3"; interactive: nil')
end

it 'accepts calling the lane with more parameters than used by the lane' do
result = @ff.runner.execute(:lane_hash_param, :ios, { name: 'test', version: '12.3', interactive: true, unused: 42 })
expect(result).to eq('name: "test"; version: "12.3"; interactive: true')
end
end

context 'when a lane expects its parameters as keywords' do
def keywords_list(list)
# The way keyword lists appear in error messages is different in Windows & Linux vs macOS
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🫨

if FastlaneCore::Helper.windows? || FastlaneCore::Helper.linux?
list.map(&:to_s).join(', ') # On Windows and Linux, keyword names don't show the `:` prefix in error messages
else
list.map(&:inspect).join(', ') # On other platforms, they do have `:` in front or keyword names
end
end

it 'fails when calling the lane with no parameter at all' do
AliSoftware marked this conversation as resolved.
Show resolved Hide resolved
expect do
@ff.runner.execute(:lane_kw_params, :ios)
end.to raise_error(ArgumentError, "missing keywords: #{keywords_list(%i[name version])}")
end

it 'fails when calling the lane with some missing parameters' do
expect do
@ff.runner.execute(:lane_kw_params, :ios, { name: 'test', interactive: true })
end.to raise_error(ArgumentError, "missing keyword: #{keywords_list(%i[version])}")
end

it 'fails when calling the lane with extra parameters' do
expect do
@ff.runner.execute(:lane_kw_params, :ios, { name: 'test', version: '12.3', interactive: true, unexpected: 42 })
end.to raise_error(ArgumentError, "unknown keyword: #{keywords_list(%i[unexpected])}")
end

it 'takes all parameters into account when all are passed explicitly' do
result = @ff.runner.execute(:lane_kw_params, :ios, { name: 'test', version: "12.3", interactive: false })
expect(result).to eq('name: "test"; version: "12.3"; interactive: false')
end

it 'uses default values of parameters not provided explicitly' do
result = @ff.runner.execute(:lane_kw_params, :ios, { name: 'test', version: "12.3" })
expect(result).to eq('name: "test"; version: "12.3"; interactive: true')
end

it 'allows parameters to be provided in arbitrary order' do
result = @ff.runner.execute(:lane_kw_params, :ios, { version: "12.3", interactive: true, name: 'test' })
expect(result).to eq('name: "test"; version: "12.3"; interactive: true')
end
end
end
end
end