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 160

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 209

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 180

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 155

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 165

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 appropriate plugin class

[ GitHub ]

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

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

.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 201

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

  src.new(locked_opts.merge("uri" => locked_opts["remote"]))
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 103

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
    builder.check_primary_source_safety
    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 150

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 222

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.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 129

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 38

def install(names, options)
  raise InvalidOption, "You cannot specify `--branch` and `--ref` at the same time." if options["branch"] && options["ref"]

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

  save_plugins names, specs
rescue PluginError
  specs_to_delete = specs.select {|k, _v| names.include?(k) && !index.commands.values.include?(k) }
  specs_to_delete.each_value {|spec| Bundler.rm_rf(spec.full_gem_path) }

  raise
end

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

currently only intended for specs

Returns:

  • (String, nil)

    installed path

[ GitHub ]

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

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

.list (mod_func)

List installed plugins and commands

[ GitHub ]

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

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 336

def load_plugin(name)
  return unless name && !name.empty?
  return if loaded?(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)

  paths = index.load_paths(name)
  invalid_paths = paths.reject {|p| File.directory?(p) }

  if invalid_paths.any?
    Bundler.ui.warn <<~MESSAGE
      The following plugin paths don't exist: #{invalid_paths.join(", ")}.

      This can happen if the plugin was installed with a different version of Ruby that has since been uninstalled.

      If you would like to reinstall the plugin, run:

      bundler plugin uninstall #{name} && bundler plugin install #{name}

      Continuing without installing plugin #{name}.
    MESSAGE

    return
  end

  Gem.add_to_load_path(*paths)

  load path.join(PLUGIN_FILE_NAME)

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

.loaded?(plugin) ⇒ true, false (mod_func)

Returns:

  • (true, false)

    whether the plugin is loaded

[ GitHub ]

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

def loaded?(plugin)
  @loaded_plugin_names.include?(plugin)
end

.local_root (mod_func)

[ GitHub ]

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

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 300

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
  Gem.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 22

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 137

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

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

Validates and registers a plugin.

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 283

def save_plugin(name, spec, optional_plugin = false)
  validate_plugin! Pathname.new(spec.full_gem_path)
  installed = register_plugin(name, spec, optional_plugin)
  Bundler.ui.info "Installed plugin #{name}" if installed
rescue PluginError => e
  raise PluginInstallError, "Failed to install plugin `#{spec.name}`, due to #{e.class} (#{e.message})"
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 254

def save_plugins(plugins, specs, optional_plugins = [])
  plugins.each do |name|
    next if index.installed?(name)

    spec = specs[name]

    save_plugin(name, spec, optional_plugins.include?(name))
  end
end

.source(name) ⇒ Class (mod_func)

Returns:

Raises:

[ GitHub ]

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

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 185

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

.uninstall(names, options) (mod_func)

Uninstalls plugins by the given names

Parameters:

  • names (Array<String>)

    the names of plugins to be uninstalled

[ GitHub ]

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

def uninstall(names, options)
  if names.empty? && !options[:all]
    Bundler.ui.error "No plugins to uninstall. Specify at least 1 plugin to uninstall.\n"\
      "Use --all option to uninstall all the installed plugins."
    return
  end

  names = index.installed_plugins if options[:all]
  if names.any?
    names.each do |name|
      if index.installed?(name)
        path = index.plugin_path(name).to_s
        Bundler.rm_rf(path) if index.installed_in_plugin_root?(name)
        index.unregister_plugin(name)
        Bundler.ui.info "Uninstalled plugin #{name}"
      else
        Bundler.ui.error "Plugin #{name} is not installed \n"
      end
    end
  else
    Bundler.ui.info "No plugins installed"
  end
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 270

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