Class: Fastlane::PluginManager
- Inherits:
-
Object
- Object
- Fastlane::PluginManager
- Defined in:
- fastlane/lib/fastlane/plugins/plugin_manager.rb
Constant Summary collapse
- PLUGINFILE_NAME =
"Pluginfile".freeze
- DEFAULT_GEMFILE_PATH =
"Gemfile".freeze
- AUTOGENERATED_LINE =
"# Autogenerated by fastlane\n#\n# Ensure this file is checked in to source control!\n\n"
- GEMFILE_SOURCE_LINE =
"source \"https://rubygems.org\"\n"
- FASTLANE_PLUGIN_PREFIX =
"fastlane-plugin-"
- TROUBLESHOOTING_URL =
"https://docs.fastlane.tools/plugins/plugins-troubleshooting/"
Reading the files and their paths collapse
- #gemfile_content ⇒ Object
- #gemfile_path ⇒ Object
- #pluginfile_content ⇒ Object
- #pluginfile_path ⇒ Object
Helpers collapse
- .plugin_prefix ⇒ Object
- .to_gem_name(plugin_name) ⇒ Object
-
#available_gems ⇒ Object
Returns an array of gems that are added to the Gemfile or Pluginfile.
-
#available_plugins ⇒ Object
Returns an array of fastlane plugins that are added to the Gemfile or Pluginfile The returned array contains the string with their prefixes (e.g. fastlane-plugin-xcversion).
-
#plugin_is_added_as_dependency?(plugin_name) ⇒ Boolean
Check if a plugin is added as dependency to either the Gemfile or the Pluginfile.
Modifying dependencies collapse
- #add_dependency(plugin_name) ⇒ Object
-
#attach_plugins_to_gemfile!(path_to_gemfile) ⇒ Object
Modify the user’s Gemfile to load the plugins.
-
#gem_dependency_suffix(plugin_name) ⇒ Object
Get a suffix (e.g. ‘path` or `git` for the gem dependency).
Accessing RubyGems collapse
Installing and updating dependencies collapse
-
#install_dependencies! ⇒ Object
Warning: This will exec out This is necessary since the user might be prompted for their password.
-
#update_dependencies! ⇒ Object
Warning: This will exec out This is necessary since the user might be prompted for their password.
- #with_clean_bundler_env ⇒ Object
Initial setup collapse
-
.code_to_attach ⇒ Object
The code required to load the Plugins file.
- #ensure_plugins_attached! ⇒ Object
-
#plugins_attached? ⇒ Boolean
Makes sure, the user’s Gemfile actually loads the Plugins file.
- #setup ⇒ Object
Requiring the plugins collapse
-
#load_plugins(print_table: true) ⇒ Object
Iterate over all available plugins which follow the naming convention fastlane-plugin- This will make sure to load the action and all its helpers.
-
#print_plugin_information(references) ⇒ Object
Prints a table all the plugins that were loaded.
Reference between plugins to actions collapse
-
#loaded_fastlane_actions ⇒ Object
Contains an array of symbols for the action classes.
-
#plugin_references ⇒ Object
Connection between plugins and their actions Example value of plugin_references => => { version_number: “0.1.0”, actions: [:rspec, :rubocop] }.
- #store_plugin_reference(gem_name) ⇒ Object
Class Method Details
.code_to_attach ⇒ Object
The code required to load the Plugins file
252 253 254 255 256 257 258 259 260 |
# File 'fastlane/lib/fastlane/plugins/plugin_manager.rb', line 252 def self.code_to_attach if FastlaneCore::FastlaneFolder.path fastlane_folder_name = File.basename(FastlaneCore::FastlaneFolder.path) else fastlane_folder_name = "fastlane" end "plugins_path = File.join(File.dirname(__FILE__), '#{fastlane_folder_name}', '#{PluginManager::PLUGINFILE_NAME}')\n" \ "eval_gemfile(plugins_path) if File.exist?(plugins_path)" end |
.fetch_gem_info_from_rubygems(gem_name) ⇒ Object
157 158 159 160 161 162 163 164 165 166 |
# File 'fastlane/lib/fastlane/plugins/plugin_manager.rb', line 157 def self.fetch_gem_info_from_rubygems(gem_name) require 'open-uri' require 'json' url = "https://rubygems.org/api/v1/gems/#{gem_name}.json" begin JSON.parse(open(url).read) rescue nil end end |
.plugin_prefix ⇒ Object
46 47 48 |
# File 'fastlane/lib/fastlane/plugins/plugin_manager.rb', line 46 def self.plugin_prefix FASTLANE_PLUGIN_PREFIX end |
.to_gem_name(plugin_name) ⇒ Object
50 51 52 |
# File 'fastlane/lib/fastlane/plugins/plugin_manager.rb', line 50 def self.to_gem_name(plugin_name) plugin_name.start_with?(plugin_prefix) ? plugin_name : (plugin_prefix + plugin_name) end |
Instance Method Details
#add_dependency(plugin_name) ⇒ Object
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'fastlane/lib/fastlane/plugins/plugin_manager.rb', line 80 def add_dependency(plugin_name) UI.user_error!("fastlane is not setup for this project, make sure you have a fastlane folder") unless pluginfile_path plugin_name = self.class.plugin_prefix + plugin_name unless plugin_name.start_with?(self.class.plugin_prefix) if plugin_name.gsub(self.class.plugin_prefix, '').include?("-") # e.g. "fastlane-plugin-ya_tu-sabes" (which is invalid) UI.user_error!("Plugin name must not contain a '-', did you mean '_'?") end unless plugin_is_added_as_dependency?(plugin_name) content = pluginfile_content || AUTOGENERATED_LINE unless content.end_with?("\n") content += "\n" end line_to_add = "gem '#{plugin_name}'" line_to_add += gem_dependency_suffix(plugin_name) UI.verbose("Adding line: #{line_to_add}") content += "#{line_to_add}\n" File.write(pluginfile_path, content) UI.success("Plugin '#{plugin_name}' was added to '#{pluginfile_path}'") end # We do this *after* creating the Plugin file # Since `bundle exec` would be broken if something fails on the way ensure_plugins_attached! true end |
#attach_plugins_to_gemfile!(path_to_gemfile) ⇒ Object
Modify the user’s Gemfile to load the plugins
142 143 144 145 146 147 148 149 150 151 |
# File 'fastlane/lib/fastlane/plugins/plugin_manager.rb', line 142 def attach_plugins_to_gemfile!(path_to_gemfile) content = gemfile_content || (AUTOGENERATED_LINE + GEMFILE_SOURCE_LINE) # We have to make sure fastlane is also added to the Gemfile, since we now use # bundler to run fastlane content += "\ngem 'fastlane'\n" unless available_gems.include?('fastlane') content += "\n#{self.class.code_to_attach}\n" File.write(path_to_gemfile, content) end |
#available_gems ⇒ Object
Returns an array of gems that are added to the Gemfile or Pluginfile
55 56 57 58 59 |
# File 'fastlane/lib/fastlane/plugins/plugin_manager.rb', line 55 def available_gems return [] unless gemfile_path dsl = Bundler::Dsl.evaluate(gemfile_path, nil, true) return dsl.dependencies.map(&:name) end |
#available_plugins ⇒ Object
Returns an array of fastlane plugins that are added to the Gemfile or Pluginfile The returned array contains the string with their prefixes (e.g. fastlane-plugin-xcversion)
63 64 65 66 67 |
# File 'fastlane/lib/fastlane/plugins/plugin_manager.rb', line 63 def available_plugins available_gems.keep_if do |current| current.start_with?(self.class.plugin_prefix) end end |
#ensure_plugins_attached! ⇒ Object
267 268 269 270 |
# File 'fastlane/lib/fastlane/plugins/plugin_manager.rb', line 267 def ensure_plugins_attached! return if plugins_attached? self.setup end |
#gem_dependency_suffix(plugin_name) ⇒ Object
Get a suffix (e.g. ‘path` or `git` for the gem dependency)
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
# File 'fastlane/lib/fastlane/plugins/plugin_manager.rb', line 113 def gem_dependency_suffix(plugin_name) return "" unless self.class.fetch_gem_info_from_rubygems(plugin_name).nil? selection_git_url = "Git URL" selection_path = "Local Path" selection_rubygems = "RubyGems.org ('#{plugin_name}' seems to not be available there)" selection_gem_server = "Other Gem Server" selection = UI.select( "Seems like the plugin is not available on RubyGems, what do you want to do?", [selection_git_url, selection_path, selection_rubygems, selection_gem_server] ) if selection == selection_git_url git_url = UI.input('Please enter the URL to the plugin, including the protocol (e.g. https:// or git://)') return ", git: '#{git_url}'" elsif selection == selection_path path = UI.input('Please enter the relative path to the plugin you want to use. It has to point to the directory containing the .gemspec file') return ", path: '#{path}'" elsif selection == selection_rubygems return "" elsif selection == selection_gem_server source_url = UI.input('Please enter the gem source URL which hosts the plugin you want to use, including the protocol (e.g. https:// or git://)') return ", source: '#{source_url}'" else UI.user_error!("Unknown input #{selection}") end end |
#gemfile_content ⇒ Object
34 35 36 |
# File 'fastlane/lib/fastlane/plugins/plugin_manager.rb', line 34 def gemfile_content File.read(gemfile_path) if gemfile_path && File.exist?(gemfile_path) end |
#gemfile_path ⇒ Object
18 19 20 21 22 23 24 |
# File 'fastlane/lib/fastlane/plugins/plugin_manager.rb', line 18 def gemfile_path # This is pretty important, since we don't know what kind of # Gemfile the user has (e.g. Gemfile, gems.rb, or custom env variable) Bundler::SharedHelpers.default_gemfile.to_s rescue Bundler::GemfileNotFound nil end |
#install_dependencies! ⇒ Object
Warning: This will exec out This is necessary since the user might be prompted for their password
174 175 176 177 178 179 180 181 182 183 184 185 186 |
# File 'fastlane/lib/fastlane/plugins/plugin_manager.rb', line 174 def install_dependencies! # Using puts instead of `UI` to have the same style as the `echo` puts("Installing plugin dependencies...") ensure_plugins_attached! with_clean_bundler_env do cmd = "bundle install" cmd << " --quiet" unless FastlaneCore::Globals.verbose? cmd << " && bundle exec fastlane generate_swift" if FastlaneCore::FastlaneFolder.swift? cmd << " && echo 'Successfully installed plugins'" UI.command(cmd) if FastlaneCore::Globals.verbose? exec(cmd) end end |
#load_plugins(print_table: true) ⇒ Object
Iterate over all available plugins which follow the naming convention
fastlane-plugin-[plugin_name]
This will make sure to load the action and all its helpers
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 |
# File 'fastlane/lib/fastlane/plugins/plugin_manager.rb', line 281 def load_plugins(print_table: true) UI.verbose("Checking if there are any plugins that should be loaded...") loaded_plugins = false available_plugins.each do |gem_name| UI.verbose("Loading '#{gem_name}' plugin") begin # BEFORE requiring the gem, we get a list of loaded actions # This way we can check inside `store_plugin_reference` if # any actions were overwritten self.loaded_fastlane_actions.concat(Fastlane::Actions.constants) FastlaneRequire.install_gem_if_needed(gem_name: gem_name, require_gem: true) store_plugin_reference(gem_name) loaded_plugins = true rescue StandardError, ScriptError => ex # some errors, like ScriptError are not caught unless explicitly UI.error("Error loading plugin '#{gem_name}': #{ex}") # We'll still add it to the table, to make the error # much more visible and obvious self.plugin_references[gem_name] = { version_number: Fastlane::ActionCollector.determine_version(gem_name), actions: [] } end end if !loaded_plugins && self.pluginfile_content.to_s.include?(PluginManager.plugin_prefix) UI.error("It seems like you wanted to load some plugins, however they couldn't be loaded") UI.error("Please follow the troubleshooting guide: #{TROUBLESHOOTING_URL}") end skip_print_plugin_info = self.plugin_references.empty? || CLIToolsDistributor.running_version_command? || !print_table # We want to avoid printing output other than the version number if we are running `fastlane -v` print_plugin_information(self.plugin_references) unless skip_print_plugin_info end |
#loaded_fastlane_actions ⇒ Object
Contains an array of symbols for the action classes
365 366 367 |
# File 'fastlane/lib/fastlane/plugins/plugin_manager.rb', line 365 def loaded_fastlane_actions @fastlane_actions ||= [] end |
#plugin_is_added_as_dependency?(plugin_name) ⇒ Boolean
Check if a plugin is added as dependency to either the Gemfile or the Pluginfile
71 72 73 74 |
# File 'fastlane/lib/fastlane/plugins/plugin_manager.rb', line 71 def plugin_is_added_as_dependency?(plugin_name) UI.user_error!("fastlane plugins must start with '#{self.class.plugin_prefix}' string") unless plugin_name.start_with?(self.class.plugin_prefix) return available_plugins.include?(plugin_name) end |
#plugin_references ⇒ Object
Connection between plugins and their actions Example value of plugin_references
> => {
version_number: "0.1.0",
actions: [:rspec, :rubocop]
}
360 361 362 |
# File 'fastlane/lib/fastlane/plugins/plugin_manager.rb', line 360 def plugin_references @plugin_references ||= {} end |
#pluginfile_content ⇒ Object
38 39 40 |
# File 'fastlane/lib/fastlane/plugins/plugin_manager.rb', line 38 def pluginfile_content File.read(pluginfile_path) if pluginfile_path && File.exist?(pluginfile_path) end |
#pluginfile_path ⇒ Object
26 27 28 29 30 31 32 |
# File 'fastlane/lib/fastlane/plugins/plugin_manager.rb', line 26 def pluginfile_path if FastlaneCore::FastlaneFolder.path return File.join(FastlaneCore::FastlaneFolder.path, PLUGINFILE_NAME) else return nil end end |
#plugins_attached? ⇒ Boolean
Makes sure, the user’s Gemfile actually loads the Plugins file
263 264 265 |
# File 'fastlane/lib/fastlane/plugins/plugin_manager.rb', line 263 def plugins_attached? gemfile_path && gemfile_content.include?(PluginManager::PLUGINFILE_NAME) end |
#print_plugin_information(references) ⇒ Object
Prints a table all the plugins that were loaded
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 |
# File 'fastlane/lib/fastlane/plugins/plugin_manager.rb', line 321 def print_plugin_information(references) no_action_found = false rows = references.collect do |current| if current[1][:actions].empty? # Something is wrong with this plugin, no available actions no_action_found = true [current[0].red, current[1][:version_number], "No actions found".red] else [current[0], current[1][:version_number], current[1][:actions].join("\n")] end end require 'terminal-table' puts(Terminal::Table.new({ rows: FastlaneCore::PrintTable.transform_output(rows), title: "Used plugins".green, headings: ["Plugin", "Version", "Action"] })) puts("") if no_action_found puts("[!] No actions were found while loading one or more plugins".red) puts(" Please use `bundle exec fastlane` with plugins".red) puts(" More info - https://docs.fastlane.tools/plugins/using-plugins/#run-with-plugins".red) puts("") end end |
#setup ⇒ Object
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 |
# File 'fastlane/lib/fastlane/plugins/plugin_manager.rb', line 227 def setup UI.important("It looks like fastlane plugins are not yet set up for this project.") path_to_gemfile = gemfile_path || DEFAULT_GEMFILE_PATH if gemfile_content.to_s.length > 0 UI.important("fastlane will modify your existing Gemfile at path '#{path_to_gemfile}'") else UI.important("fastlane will create a new Gemfile at path '#{path_to_gemfile}'") end UI.important("This change is necessary for fastlane plugins to work") unless UI.confirm("Should fastlane modify the Gemfile at path '#{path_to_gemfile}' for you?") UI.important("Please add the following code to '#{path_to_gemfile}':") puts("") puts(self.class.code_to_attach.magenta) # we use `puts` instead of `UI` to make it easier to copy and paste UI.user_error!("Please update '#{path_to_gemfile} and run fastlane again") end attach_plugins_to_gemfile!(path_to_gemfile) UI.success("Successfully modified '#{path_to_gemfile}'") end |
#store_plugin_reference(gem_name) ⇒ Object
369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 |
# File 'fastlane/lib/fastlane/plugins/plugin_manager.rb', line 369 def store_plugin_reference(gem_name) module_name = gem_name.gsub(PluginManager.plugin_prefix, '').fastlane_class # We store a collection of the imported plugins # This way we can tell which action came from what plugin # (a plugin may contain any number of actions) version_number = Fastlane::ActionCollector.determine_version(gem_name) references = Fastlane.const_get(module_name).all_classes.collect do |path| next unless File.dirname(path).end_with?("/actions") # we only want to match actions File.basename(path).gsub("_action", "").gsub(".rb", "").to_sym # the _action is optional end references.compact! # Check if this overwrites a built-in action and # show a warning if that's the case references.each do |current_ref| # current_ref is a symbol, e.g. :emoji_fetcher class_name = (current_ref.to_s.fastlane_class + 'Action').to_sym if self.loaded_fastlane_actions.include?(class_name) UI.important("Plugin '#{module_name}' overwrites already loaded action '#{current_ref}'") end end self.plugin_references[gem_name] = { version_number: version_number, actions: references } end |
#update_dependencies! ⇒ Object
Warning: This will exec out This is necessary since the user might be prompted for their password
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 |
# File 'fastlane/lib/fastlane/plugins/plugin_manager.rb', line 190 def update_dependencies! puts("Updating plugin dependencies...") ensure_plugins_attached! plugins = available_plugins if plugins.empty? UI.user_error!("No plugins are installed") end with_clean_bundler_env do cmd = "bundle update" cmd << " #{plugins.join(' ')}" cmd << " --quiet" unless FastlaneCore::Globals.verbose? cmd << " && bundle exec fastlane generate_swift" if FastlaneCore::FastlaneFolder.swift? cmd << " && echo 'Successfully updated plugins'" UI.command(cmd) if FastlaneCore::Globals.verbose? exec(cmd) end end |
#with_clean_bundler_env ⇒ Object
208 209 210 211 212 213 214 215 216 217 218 219 220 221 |
# File 'fastlane/lib/fastlane/plugins/plugin_manager.rb', line 208 def with_clean_bundler_env # There is an interesting problem with using exec to call back into Bundler # The `bundle ________` command that we exec, inherits all of the Bundler # state we'd already built up during this run. That was causing the command # to fail, telling us to install the Gem we'd just introduced, even though # that is exactly what we are trying to do! # # Bundler.with_clean_env solves this problem by resetting Bundler state before the # exec'd call gets merged into this process. Bundler.with_clean_env do yield if block_given? end end |