Class: Fastlane::PluginManager
- Inherits:
-
Object
- Object
- Fastlane::PluginManager
- Defined in:
- 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://github.com/fastlane/fastlane/blob/master/fastlane/docs/PluginsTroubleshooting.md"
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 ⇒ 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
235 236 237 238 239 240 241 242 243 |
# File 'lib/fastlane/plugins/plugin_manager.rb', line 235 def self.code_to_attach if FastlaneFolder.path fastlane_folder_name = File.basename(FastlaneFolder.path) else fastlane_folder_name = "fastlane" end "plugins_path = File.join(File.dirname(__FILE__), '#{fastlane_folder_name}', '#{PluginManager::PLUGINFILE_NAME}')\n" \ "eval(File.read(plugins_path), binding) if File.exist?(plugins_path)" end |
.fetch_gem_info_from_rubygems(gem_name) ⇒ Object
147 148 149 150 151 152 153 154 155 156 |
# File 'lib/fastlane/plugins/plugin_manager.rb', line 147 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
44 45 46 |
# File 'lib/fastlane/plugins/plugin_manager.rb', line 44 def self.plugin_prefix FASTLANE_PLUGIN_PREFIX end |
.to_gem_name(plugin_name) ⇒ Object
48 49 50 |
# File 'lib/fastlane/plugins/plugin_manager.rb', line 48 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
78 79 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 |
# File 'lib/fastlane/plugins/plugin_manager.rb', line 78 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 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
132 133 134 135 136 137 138 139 140 141 |
# File 'lib/fastlane/plugins/plugin_manager.rb', line 132 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
53 54 55 56 57 |
# File 'lib/fastlane/plugins/plugin_manager.rb', line 53 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)
61 62 63 64 65 |
# File 'lib/fastlane/plugins/plugin_manager.rb', line 61 def available_plugins available_gems.keep_if do |current| current.start_with?(self.class.plugin_prefix) end end |
#ensure_plugins_attached! ⇒ Object
250 251 252 253 |
# File 'lib/fastlane/plugins/plugin_manager.rb', line 250 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)
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
# File 'lib/fastlane/plugins/plugin_manager.rb', line 107 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 = UI.select( "Seems like the plugin is not available on RubyGems, what do you want to do?", [selection_git_url, selection_path, selection_rubygems] ) 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 "" else UI.user_error!("Unknown input #{selection}") end end |
#gemfile_content ⇒ Object
32 33 34 |
# File 'lib/fastlane/plugins/plugin_manager.rb', line 32 def gemfile_content File.read(gemfile_path) if gemfile_path && File.exist?(gemfile_path) end |
#gemfile_path ⇒ Object
16 17 18 19 20 21 22 |
# File 'lib/fastlane/plugins/plugin_manager.rb', line 16 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
164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/fastlane/plugins/plugin_manager.rb', line 164 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 $verbose cmd << " && echo 'Successfully installed plugins'" UI.command(cmd) if $verbose exec(cmd) end end |
#load_plugins ⇒ 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
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 |
# File 'lib/fastlane/plugins/plugin_manager.rb', line 264 def load_plugins 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) require gem_name.tr("-", "/") # from "fastlane-plugin-xcversion" to "fastlane/plugin/xcversion" store_plugin_reference(gem_name) loaded_plugins = true rescue => ex 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? # 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
336 337 338 |
# File 'lib/fastlane/plugins/plugin_manager.rb', line 336 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
69 70 71 72 |
# File 'lib/fastlane/plugins/plugin_manager.rb', line 69 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]
}
331 332 333 |
# File 'lib/fastlane/plugins/plugin_manager.rb', line 331 def plugin_references @plugin_references ||= {} end |
#pluginfile_content ⇒ Object
36 37 38 |
# File 'lib/fastlane/plugins/plugin_manager.rb', line 36 def pluginfile_content File.read(pluginfile_path) if pluginfile_path && File.exist?(pluginfile_path) end |
#pluginfile_path ⇒ Object
24 25 26 27 28 29 30 |
# File 'lib/fastlane/plugins/plugin_manager.rb', line 24 def pluginfile_path if FastlaneFolder.path return File.join(FastlaneFolder.path, PLUGINFILE_NAME) else return nil end end |
#plugins_attached? ⇒ Boolean
Makes sure, the user’s Gemfile actually loads the Plugins file
246 247 248 |
# File 'lib/fastlane/plugins/plugin_manager.rb', line 246 def plugins_attached? gemfile_path && gemfile_content.include?(self.class.code_to_attach) end |
#print_plugin_information(references) ⇒ Object
Prints a table all the plugins that were loaded
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 |
# File 'lib/fastlane/plugins/plugin_manager.rb', line 303 def print_plugin_information(references) rows = references.collect do |current| if current[1][:actions].empty? # Something is wrong with this plugin, no available actions [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 puts Terminal::Table.new({ rows: rows, title: "Used plugins".green, headings: ["Plugin", "Version", "Action"] }) puts "" end |
#setup ⇒ Object
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 |
# File 'lib/fastlane/plugins/plugin_manager.rb', line 210 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 neccessary 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
340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 |
# File 'lib/fastlane/plugins/plugin_manager.rb', line 340 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
179 180 181 182 183 184 185 186 187 188 189 |
# File 'lib/fastlane/plugins/plugin_manager.rb', line 179 def update_dependencies! puts "Updating plugin dependencies..." ensure_plugins_attached! with_clean_bundler_env do cmd = "bundle update" cmd << " --quiet" unless $verbose cmd << " && echo 'Successfully updated plugins'" UI.command(cmd) if $verbose exec(cmd) end end |
#with_clean_bundler_env ⇒ Object
191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
# File 'lib/fastlane/plugins/plugin_manager.rb', line 191 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 |