123456789_123456789_123456789_123456789_123456789_

Module: Bundler::Plugin

Overview

This is the interfacing class represents the API that we intend to provide the plugins to use.

For plugins to be independent of the ::Bundler internals they shall limit their interactions to methods of this class only. This will save them from breaking when some internal change.

Currently we are delegating the methods defined in ::Bundler class to itself. So, this class acts as a buffer.

If there is some change in the ::Bundler class that is incompatible to its previous behavior or if otherwise desired, we can reimplement(or implement) the method to preserve compatibility.

To use this, either the class can inherit this class or use it directly. For example of both types of use, refer the file spec/plugins/command.rb

To use it without inheriting, you will have to create an object of this to use the functions (except for declaration functions like command, source, and hooks).

Constant Summary

Class Method Summary

Class Method Details

.add_command(command, cls) (mod_func)

To be called via the Plugin::API to register to handle a command

[ GitHub ]

  
# File 'lib/bundler/plugin.rb', line 109

def add_command(command, cls)
  @commands[command] = cls
end

.add_hook(event, &block) (mod_func)

To be called via the Plugin::API to register a hooks and corresponding block that will be called to handle the hook

[ GitHub ]

  
# File 'lib/bundler/plugin.rb', line 158

def add_hook(event, &block)
  unless Events.defined_event?(event)
    raise ArgumentError, "Event '#{event}' not defined in Bundler::Plugin::Events"
  end
  @hooks_by_event[event.to_s] << block
end

.add_source(source, cls) (mod_func)

To be called via the Plugin::API to register to handle a source plugin

[ GitHub ]

  
# File 'lib/bundler/plugin.rb', line 129

def add_source(source, cls)
  @sources[source] = cls
end

.add_to_load_path(load_paths) (mod_func)

[ GitHub ]

  
# File 'lib/bundler/plugin.rb', line 279

def add_to_load_path(load_paths)
  if insert_index = Bundler.rubygems.load_path_insert_index
    $LOAD_PATH.insert(insert_index, *load_paths)
  else
    $LOAD_PATH.unshift(*load_paths)
  end
end

.cache (mod_func)

The cache directory for plugin stuffs

[ GitHub ]

  
# File 'lib/bundler/plugin.rb', line 104

def cache
  @cache ||= root.join("cache")
end

.command?(command) ⇒ Boolean (mod_func)

Checks if any plugin handles the command

[ GitHub ]

  
# File 'lib/bundler/plugin.rb', line 114

def command?(command)
  !index.command_plugin(command).nil?
end

.exec_command(command, args) (mod_func)

To be called from Cli class to pass the command and argument to approriate plugin class

[ GitHub ]

  
# File 'lib/bundler/plugin.rb', line 120

def exec_command(command, args)
  raise UndefinedCommandError, "Command `#{command}` not found" unless command? command

  load_plugin index.command_plugin(command) unless @commands.key? command

  @commands[command].new.exec(command, args)
end

.gemfile_install(gemfile = nil, &inline) (mod_func)

Evaluates the Gemfile with a limited Plugin::DSL and installs the plugins specified by plugin method

Parameters:

  • gemfile (Pathname) (defaults to: nil)

    path

  • block (Proc)

    that can be evaluated for (inline) Gemfile

[ GitHub ]

  
# File 'lib/bundler/plugin.rb', line 55

def gemfile_install(gemfile = nil, &inline)
  builder = DSL.new
  if block_given?
    builder.instance_eval(&inline)
  else
    builder.eval_gemfile(gemfile)
  end
  definition = builder.to_definition(nil, true)

  return if definition.dependencies.empty?

  plugins = definition.dependencies.map(&:name).reject {|p| index.installed? p }
  installed_specs = Installer.new.install_definition(definition)

  save_plugins plugins, installed_specs, builder.inferred_plugins
rescue RuntimeError => e
  unless e.is_a?(GemfileError)
    Bundler.ui.error "Failed to install plugin: #{e.message}\n  #{e.backtrace[0]}"
  end
  raise
end

.global_root (mod_func)

The global directory root for all plugin related data

[ GitHub ]

  
# File 'lib/bundler/plugin.rb', line 99

def global_root
  Bundler.user_bundle_path("plugin")
end

.hook(event, *args, &arg_blk) (mod_func)

Runs all the hooks that are registered for the passed event

It passes the passed arguments and block to the block registered with the api.

Parameters:

  • event (String)
[ GitHub ]

  
# File 'lib/bundler/plugin.rb', line 171

def hook(event, *args, &arg_blk)
  return unless Bundler.feature_flag.plugins?
  unless Events.defined_event?(event)
    raise ArgumentError, "Event '#{event}' not defined in Bundler::Plugin::Events"
  end

  plugins = index.hook_plugins(event)
  return unless plugins.any?

  (plugins - @loaded_plugin_names).each {|name| load_plugin(name) }

  @hooks_by_event[event].each {|blk| blk.call(*args, &arg_blk) }
end

.index (mod_func)

The index object used to store the details about the plugin

[ GitHub ]

  
# File 'lib/bundler/plugin.rb', line 78

def index
  @index ||= Index.new
end

.install(names, options) (mod_func)

Installs a new plugin by the given name

Parameters:

  • names (Array<String>)

    the name of plugin to be installed

  • options (Hash)

    various parameters as described in description. Refer to cli/plugin for available options

[ GitHub ]

  
# File 'lib/bundler/plugin.rb', line 37

def install(names, options)
  specs = Installer.new.install(names, options)

  save_plugins names, specs
rescue PluginError => e
  if specs
    specs_to_delete = Hash[specs.select {|k, _v| names.include?(k) && !index.commands.values.include?(k) }]
    specs_to_delete.values.each {|spec| Bundler.rm_rf(spec.full_gem_path) }
  end

  Bundler.ui.error "Failed to install plugin #{name}: #{e.message}\n  #{e.backtrace.join("\n ")}"
end

.installed?(plugin) ⇒ String? (mod_func)

currently only intended for specs

Returns:

  • (String, nil)

    installed path

[ GitHub ]

  
# File 'lib/bundler/plugin.rb', line 188

def installed?(plugin)
  Index.new.installed?(plugin)
end

.load_plugin(name) (mod_func)

Executes the plugins.rb file

Parameters:

  • name (String)

    of the plugin

[ GitHub ]

  
# File 'lib/bundler/plugin.rb', line 263

def load_plugin(name)
  # Need to ensure before this that plugin root where the rest of gems
  # are installed to be on load path to support plugin deps. Currently not
  # done to avoid conflicts
  path = index.plugin_path(name)

  add_to_load_path(index.load_paths(name))

  load path.join(PLUGIN_FILE_NAME)

  @loaded_plugin_names << name
rescue RuntimeError => e
  Bundler.ui.error "Failed loading plugin #{name}: #{e.message}"
  raise
end

.local_root (mod_func)

[ GitHub ]

  
# File 'lib/bundler/plugin.rb', line 94

def local_root
  Bundler.app_config_path.join("plugin")
end

.register_plugin(name, spec, optional_plugin = false) (mod_func)

Runs the plugins.rb file in an isolated namespace, records the plugin actions it registers for and then passes the data to index to be stored.

Parameters:

  • name (String)

    the name of the plugin

  • spec (Specification)

    of installed plugin

  • optional_plugin, (Boolean)

    removed if there is conflict with any other plugin (used for default source plugins)

Raises:

[ GitHub ]

  
# File 'lib/bundler/plugin.rb', line 227

def register_plugin(name, spec, optional_plugin = false)
  commands = @commands
  sources = @sources
  hooks = @hooks_by_event

  @commands = {}
  @sources = {}
  @hooks_by_event = Hash.new {|h, k| h[k] = [] }

  load_paths = spec.load_paths
  add_to_load_path(load_paths)
  path = Pathname.new spec.full_gem_path

  begin
    load path.join(PLUGIN_FILE_NAME), true
  rescue StandardError => e
    raise MalformattedPlugin, "#{e.class}: #{e.message}"
  end

  if optional_plugin && @sources.keys.any? {|s| source? s }
    Bundler.rm_rf(path)
    false
  else
    index.register_plugin(name, path.to_s, load_paths, @commands.keys,
      @sources.keys, @hooks_by_event.keys)
    true
  end
ensure
  @commands = commands
  @sources = sources
  @hooks_by_event = hooks
end

.reset! (mod_func)

[ GitHub ]

  
# File 'lib/bundler/plugin.rb', line 21

def reset!
  instance_variables.each {|i| remove_instance_variable(i) }

  @sources = {}
  @commands = {}
  @hooks_by_event = Hash.new {|h, k| h[k] = [] }
  @loaded_plugin_names = []
end

.root (mod_func)

The directory root for all plugin related data

If run in an app, points to local root, in app_config_path Otherwise, points to global root, in Bundler.user_bundle_path(“plugin”)

[ GitHub ]

  
# File 'lib/bundler/plugin.rb', line 86

def root
  @root ||= if SharedHelpers.in_bundle?
    local_root
  else
    global_root
  end
end

.save_plugins(plugins, specs, optional_plugins = []) (mod_func)

Post installation processing and registering with index

Parameters:

  • plugins (Array<String>)

    list to be installed

  • specs (Hash)

    of plugins mapped to installation path (currently they contain all the installed specs, including plugins)

  • names (Array<String>)

    of inferred source plugins that can be ignored

[ GitHub ]

  
# File 'lib/bundler/plugin.rb', line 198

def save_plugins(plugins, specs, optional_plugins = [])
  plugins.each do |name|
    spec = specs[name]
    validate_plugin! Pathname.new(spec.full_gem_path)
    installed = register_plugin(name, spec, optional_plugins.include?(name))
    Bundler.ui.info "Installed plugin #{name}" if installed
  end
end

.source(name) ⇒ Class (mod_func)

Returns:

Raises:

[ GitHub ]

  
# File 'lib/bundler/plugin.rb', line 139

def source(name)
  raise UnknownSourceError, "Source #{name} not found" unless source? name

  load_plugin(index.source_plugin(name)) unless @sources.key? name

  @sources[name]
end

.source?(name) ⇒ Boolean (mod_func)

Checks if any plugin declares the source

[ GitHub ]

  
# File 'lib/bundler/plugin.rb', line 134

def source?(name)
  !index.source_plugin(name.to_s).nil?
end

.source_from_lock(locked_opts) ⇒ API::Source (mod_func)

Parameters:

  • The (Hash)

    options that are present in the lock file

Returns:

  • (API::Source)

    the instance of the class that handles the source type passed in locked_opts

[ GitHub ]

  
# File 'lib/bundler/plugin.rb', line 150

def source_from_lock(locked_opts)
  src = source(locked_opts["type"])

  src.new(locked_opts.merge("uri" => locked_opts["remote"]))
end

.validate_plugin!(plugin_path) (mod_func)

Checks if the gem is good to be a plugin

At present it only checks whether it contains plugins.rb file

Parameters:

  • plugin_path (Pathname)

    the path plugin is installed at

Raises:

[ GitHub ]

  
# File 'lib/bundler/plugin.rb', line 213

def validate_plugin!(plugin_path)
  plugin_file = plugin_path.join(PLUGIN_FILE_NAME)
  raise MalformattedPlugin, "#{PLUGIN_FILE_NAME} was not found in the plugin." unless plugin_file.file?
end