Class: Fastlane::FastFile

Inherits:
Object
  • Object
show all
Defined in:
fastlane/lib/fastlane/fast_file.rb

Constant Summary collapse

SharedValues =
Fastlane::Actions::SharedValues

Instance Attribute Summary collapse

DSL collapse

Other things collapse

Versioning helpers collapse

Overwriting Ruby methods collapse

Instance Method Summary collapse

Constructor Details

#initialize(path = nil) ⇒ Object

Returns The runner which can be executed to trigger the given actions.


15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'fastlane/lib/fastlane/fast_file.rb', line 15

def initialize(path = nil)
  return unless (path || '').length > 0
  UI.user_error!("Could not find Fastfile at path '#{path}'") unless File.exist?(path)
  @path = File.expand_path(path)
  content = File.read(path, encoding: "utf-8")

  # From https://github.com/orta/danger/blob/master/lib/danger/Dangerfile.rb
  if content.tr!('“”‘’‛', %(""'''))
    UI.error("Your #{File.basename(path)} has had smart quotes sanitised. " \
            'To avoid issues in the future, you should not use ' \
            'TextEdit for editing it. If you are not using TextEdit, ' \
            'you should turn off smart quotes in your editor of choice.')
  end

  content.scan(/^\s*require (.*)/).each do |current|
    gem_name = current.last
    next if gem_name.include?(".") # these are local gems
    UI.important("You have required a gem, if this is a third party gem, please use `fastlane_require #{gem_name}` to ensure the gem is installed locally.")
  end

  parse(content, @path)
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_sym, *arguments, &_block) ⇒ Object

Is used to look if the method is implemented as an action


153
154
155
# File 'fastlane/lib/fastlane/fast_file.rb', line 153

def method_missing(method_sym, *arguments, &_block)
  self.runner.trigger_action_by_name(method_sym, nil, false, *arguments)
end

Instance Attribute Details

#current_platformObject

the platform in which we're currently in when parsing the Fastfile This is used to identify the platform in which the lane is in


10
11
12
# File 'fastlane/lib/fastlane/fast_file.rb', line 10

def current_platform
  @current_platform
end

#runnerObject

Stores all relevant information from the currently running process


6
7
8
# File 'fastlane/lib/fastlane/fast_file.rb', line 6

def runner
  @runner
end

Class Method Details

.sh(*command, log: true, error_callback: nil, &b) ⇒ Object


189
190
191
192
193
194
# File 'fastlane/lib/fastlane/fast_file.rb', line 189

def self.sh(*command, log: true, error_callback: nil, &b)
  command_header = log ? Actions.shell_command_from_args(*command) : "shell command"
  Actions.execute_action(command_header) do
    Actions.sh_no_action(*command, log: log, error_callback: error_callback, &b)
  end
end

Instance Method Details

#action_completed(action_name, status: nil) ⇒ Object


340
341
342
343
344
345
# File 'fastlane/lib/fastlane/fast_file.rb', line 340

def action_completed(action_name, status: nil)
  completion_context = FastlaneCore::ActionCompletionContext.context_for_action_name(action_name,
                                                                                     args: ARGV,
                                                                                     status: status)
  FastlaneCore.session.action_completed(completion_context: completion_context)
end

#action_launched(action_name) ⇒ Object


333
334
335
336
337
338
# File 'fastlane/lib/fastlane/fast_file.rb', line 333

def action_launched(action_name)
  action_launch_context = FastlaneCore::ActionLaunchContext.context_for_action_name(action_name,
                                                                                    fastlane_client_language: :ruby,
                                                                                    args: ARGV)
  FastlaneCore.session.action_launched(launch_context: action_launch_context)
end

#actions_path(path) ⇒ Object


178
179
180
181
182
# File 'fastlane/lib/fastlane/fast_file.rb', line 178

def actions_path(path)
  UI.crash!("Path '#{path}' not found!") unless File.directory?(path)

  Actions.load_external_actions(path)
end

#after_all(&block) ⇒ Object

Is executed after each test run


138
139
140
# File 'fastlane/lib/fastlane/fast_file.rb', line 138

def after_all(&block)
  @runner.set_after_all(@current_platform, block)
end

#after_each(&block) ⇒ Object

Is executed before each lane


143
144
145
# File 'fastlane/lib/fastlane/fast_file.rb', line 143

def after_each(&block)
  @runner.set_after_each(@current_platform, block)
end

#before_all(&block) ⇒ Object

Is executed before each test run


128
129
130
# File 'fastlane/lib/fastlane/fast_file.rb', line 128

def before_all(&block)
  @runner.set_before_all(@current_platform, block)
end

#before_each(&block) ⇒ Object

Is executed before each lane


133
134
135
# File 'fastlane/lib/fastlane/fast_file.rb', line 133

def before_each(&block)
  @runner.set_before_each(@current_platform, block)
end

#desc(string) ⇒ Object


196
197
198
# File 'fastlane/lib/fastlane/fast_file.rb', line 196

def desc(string)
  desc_collection << string
end

#desc_collectionObject


200
201
202
# File 'fastlane/lib/fastlane/fast_file.rb', line 200

def desc_collection
  @desc_collection ||= []
end

#error(&block) ⇒ Object

Is executed if an error occurred during fastlane execution


148
149
150
# File 'fastlane/lib/fastlane/fast_file.rb', line 148

def error(&block)
  @runner.set_error(@current_platform, block)
end

#fastlane_require(gem_name) ⇒ Object


204
205
206
# File 'fastlane/lib/fastlane/fast_file.rb', line 204

def fastlane_require(gem_name)
  FastlaneRequire.install_gem_if_needed(gem_name: gem_name, require_gem: true)
end

#fetch_remote_tags(folder: nil) ⇒ Object


291
292
293
294
295
296
297
298
299
300
301
302
303
# File 'fastlane/lib/fastlane/fast_file.rb', line 291

def fetch_remote_tags(folder: nil)
  UI.message("Fetching remote git tags...")
  Actions.sh("cd '#{folder}' && GIT_TERMINAL_PROMPT=0 git fetch --all --tags -q")

  # Fetch all possible tags
  git_tags_string = Actions.sh("cd '#{folder}' && git tag -l")
  git_tags = git_tags_string.split("\n")

  # Sort tags based on their version number
  return git_tags
         .select { |tag| FastlaneCore::TagVersion.correct?(tag) }
         .sort_by { |tag| FastlaneCore::TagVersion.new(tag) }
end

#generated_fastfile_id(id) ⇒ Object


208
209
210
# File 'fastlane/lib/fastlane/fast_file.rb', line 208

def generated_fastfile_id(id)
  UI.important("The `generated_fastfile_id` action was deprecated, you can remove the line from your `Fastfile`")
end

#import(path = nil) ⇒ Object


212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'fastlane/lib/fastlane/fast_file.rb', line 212

def import(path = nil)
  UI.user_error!("Please pass a path to the `import` action") unless path

  path = path.dup.gsub("~", Dir.home)
  unless Pathname.new(path).absolute? # unless an absolute path
    path = File.join(File.expand_path('..', @path), path)
  end

  UI.user_error!("Could not find Fastfile at path '#{path}'") unless File.exist?(path)

  # First check if there are local actions to import in the same directory as the Fastfile
  actions_path = File.join(File.expand_path("..", path), 'actions')
  Fastlane::Actions.load_external_actions(actions_path) if File.directory?(actions_path)

  action_launched('import')

  return_value = parse(File.read(path), path)

  action_completed('import', status: FastlaneCore::ActionCompletionStatus::SUCCESS)

  return return_value
end

#import_from_git(url: nil, branch: 'HEAD', path: 'fastlane/Fastfile', version: nil) ⇒ Object

Parameters:

  • url (String) (defaults to: nil)

    The git URL to clone the repository from

  • branch (String) (defaults to: 'HEAD')

    The branch to checkout in the repository

  • path (String) (defaults to: 'fastlane/Fastfile')

    The path to the Fastfile

  • version (String, Array) (defaults to: nil)

    Version requirement for repo tags


239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'fastlane/lib/fastlane/fast_file.rb', line 239

def import_from_git(url: nil, branch: 'HEAD', path: 'fastlane/Fastfile', version: nil)
  UI.user_error!("Please pass a path to the `import_from_git` action") if url.to_s.length == 0

  Actions.execute_action('import_from_git') do
    require 'tmpdir'

    action_launched('import_from_git')

    # Checkout the repo
    repo_name = url.split("/").last
    checkout_param = branch

    Dir.mktmpdir("fl_clone") do |tmp_path|
      clone_folder = File.join(tmp_path, repo_name)

      branch_option = "--branch #{branch}" if branch != 'HEAD'

      UI.message("Cloning remote git repo...")
      Actions.sh("GIT_TERMINAL_PROMPT=0 git clone '#{url}' '#{clone_folder}' --depth 1 -n #{branch_option}")

      unless version.nil?
        req = Gem::Requirement.new(version)
        all_tags = fetch_remote_tags(folder: clone_folder)
        checkout_param = all_tags.select { |t| req =~ FastlaneCore::TagVersion.new(t) }.last
        UI.user_error!("No tag found matching #{version.inspect}") if checkout_param.nil?
      end

      Actions.sh("cd '#{clone_folder}' && git checkout #{checkout_param} '#{path}'")

      # We also want to check out all the local actions of this fastlane setup
      containing = path.split(File::SEPARATOR)[0..-2]
      containing = "." if containing.count == 0
      actions_folder = File.join(containing, "actions")
      begin
        Actions.sh("cd '#{clone_folder}' && git checkout #{checkout_param} '#{actions_folder}'")
      rescue
        # We don't care about a failure here, as local actions are optional
      end

      return_value = import(File.join(clone_folder, path))

      action_completed('import_from_git', status: FastlaneCore::ActionCompletionStatus::SUCCESS)

      return return_value
    end
  end
end

#is_platform_block?(key) ⇒ Boolean

Is the given key a platform block or a lane?

Returns:


162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'fastlane/lib/fastlane/fast_file.rb', line 162

def is_platform_block?(key)
  UI.crash!('No key given') unless key

  return false if self.runner.lanes.fetch(nil, {}).fetch(key.to_sym, nil)
  return true if self.runner.lanes[key.to_sym].kind_of?(Hash)

  if key.to_sym == :update
    # The user ran `fastlane update`, instead of `fastlane update_fastlane`
    # We're gonna be nice and understand what the user is trying to do
    require 'fastlane/one_off'
    Fastlane::OneOff.run(action: "update_fastlane", parameters: {})
  else
    UI.user_error!("Could not find '#{key}'. Available lanes: #{self.runner.available_lanes.join(', ')}")
  end
end

#lane(lane_name, &block) ⇒ Object

User defines a new lane


78
79
80
81
82
83
84
85
86
87
88
# File 'fastlane/lib/fastlane/fast_file.rb', line 78

def lane(lane_name, &block)
  UI.user_error!("You have to pass a block using 'do' for lane '#{lane_name}'. Make sure you read the docs on GitHub.") unless block

  self.runner.add_lane(Lane.new(platform: self.current_platform,
                                   block: block,
                             description: desc_collection,
                                    name: lane_name,
                              is_private: false))

  @desc_collection = nil # reset the collected description again for the next lane
end

#override_lane(lane_name, &block) ⇒ Object

User defines a lane that can overwrite existing lanes. Useful when importing a Fastfile


104
105
106
107
108
109
110
111
112
113
114
# File 'fastlane/lib/fastlane/fast_file.rb', line 104

def override_lane(lane_name, &block)
  UI.user_error!("You have to pass a block using 'do' for lane '#{lane_name}'. Make sure you read the docs on GitHub.") unless block

  self.runner.add_lane(Lane.new(platform: self.current_platform,
                                   block: block,
                             description: desc_collection,
                                    name: lane_name,
                              is_private: false), true)

  @desc_collection = nil # reset the collected description again for the next lane
end

#parse(data, path = nil) ⇒ Object


42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'fastlane/lib/fastlane/fast_file.rb', line 42

def parse(data, path = nil)
  @runner ||= Runner.new

  Dir.chdir(FastlaneCore::FastlaneFolder.path || Dir.pwd) do # context: fastlane subfolder
    # create nice path that we want to print in case of some problem
    relative_path = path.nil? ? '(eval)' : Pathname.new(path).relative_path_from(Pathname.new(Dir.pwd)).to_s

    begin
      # We have to use #get_binding method, because some test files defines method called `path` (for example SwitcherFastfile)
      # and local variable has higher priority, so it causes to remove content of original Fastfile for example. With #get_binding
      # is this always clear and safe to declare any local variables we want, because the eval function uses the instance scope
      # instead of local.

      # rubocop:disable Security/Eval
      eval(data, parsing_binding, relative_path) # using eval is ok for this case
      # rubocop:enable Security/Eval
    rescue SyntaxError => ex
      match = ex.to_s.match(/#{Regexp.escape(relative_path)}:(\d+)/)
      if match
        line = match[1]
        UI.content_error(data, line)
        UI.user_error!("Syntax error in your Fastfile on line #{line}: #{ex}")
      else
        UI.user_error!("Syntax error in your Fastfile: #{ex}")
      end
    end
  end

  self
end

#parsing_bindingObject


38
39
40
# File 'fastlane/lib/fastlane/fast_file.rb', line 38

def parsing_binding
  binding
end

#platform(platform_name) ⇒ Object

User defines a platform block


117
118
119
120
121
122
123
124
125
# File 'fastlane/lib/fastlane/fast_file.rb', line 117

def platform(platform_name)
  SupportedPlatforms.verify!(platform_name)

  self.current_platform = platform_name

  yield

  self.current_platform = nil
end

#private_lane(lane_name, &block) ⇒ Object

User defines a new private lane, which can't be called from the CLI


91
92
93
94
95
96
97
98
99
100
101
# File 'fastlane/lib/fastlane/fast_file.rb', line 91

def private_lane(lane_name, &block)
  UI.user_error!("You have to pass a block using 'do' for lane '#{lane_name}'. Make sure you read the docs on GitHub.") unless block

  self.runner.add_lane(Lane.new(platform: self.current_platform,
                                   block: block,
                             description: desc_collection,
                                    name: lane_name,
                              is_private: true))

  @desc_collection = nil # reset the collected description again for the next lane
end

#puts(value) ⇒ Object


318
319
320
321
322
323
324
325
326
# File 'fastlane/lib/fastlane/fast_file.rb', line 318

def puts(value)
  # Overwrite this, since there is already a 'puts' method defined in the Ruby standard library
  value ||= yield if block_given?

  action_launched('puts')
  return_value = Fastlane::Actions::PutsAction.run([value])
  action_completed('puts', status: FastlaneCore::ActionCompletionStatus::SUCCESS)
  return return_value
end

#say(value) ⇒ Object

Speak out loud


310
311
312
313
314
315
316
# File 'fastlane/lib/fastlane/fast_file.rb', line 310

def say(value)
  # Overwrite this, since there is already a 'say' method defined in the Ruby standard library
  value ||= yield

  value = { text: value } if value.kind_of?(String) || value.kind_of?(Array)
  self.runner.trigger_action_by_name(:say, nil, false, value)
end

#sh(*command, log: true, error_callback: nil, &b) ⇒ Object

Execute shell command


185
186
187
# File 'fastlane/lib/fastlane/fast_file.rb', line 185

def sh(*command, log: true, error_callback: nil, &b)
  FastFile.sh(*command, log: log, error_callback: error_callback, &b)
end

#test(params = {}) ⇒ Object


328
329
330
331
# File 'fastlane/lib/fastlane/fast_file.rb', line 328

def test(params = {})
  # Overwrite this, since there is already a 'test' method defined in the Ruby standard library
  self.runner.try_switch_to_lane(:test, [params])
end