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 131

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 180

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 151

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

.cache (mod_func)

The cache directory for plugin stuffs

[ GitHub ]

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

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 136

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 142

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 75

def gemfile_install(gemfile = nil, &inline)
  Bundler.settings.temporary(:frozen => false, :deployment => false) do
    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
  end
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 121

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 193

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 100

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 210

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

.list (mod_func)

List installed plugins and commands

[ GitHub ]

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

def list
  installed_plugins = index.installed_plugins
  if installed_plugins.any?
    output = String.new
    installed_plugins.each do |plugin|
      output << "#{plugin}\n"
      output << "-----\n"
      index.plugin_commands(plugin).each do |command|
        output << "  #{command}\n"
      end
      output << "\n"
    end
  else
    output = "No plugins installed"
  end
  Bundler.ui.info output
end

.load_plugin(name) (mod_func)

Executes the plugins.rb file

Parameters:

  • name (String)

    of the plugin

[ GitHub ]

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

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)

  Bundler.rubygems.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 116

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 249

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
  Bundler.rubygems.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 108

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 220

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 161

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 156

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 172

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 235

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