summaryrefslogtreecommitdiff
path: root/lib/bundler/plugin.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/bundler/plugin.rb')
-rw-r--r--lib/bundler/plugin.rb116
1 files changed, 96 insertions, 20 deletions
diff --git a/lib/bundler/plugin.rb b/lib/bundler/plugin.rb
index f4dd435df4..faca6bea53 100644
--- a/lib/bundler/plugin.rb
+++ b/lib/bundler/plugin.rb
@@ -13,10 +13,11 @@ module Bundler
class MalformattedPlugin < PluginError; end
class UndefinedCommandError < PluginError; end
class UnknownSourceError < PluginError; end
+ class PluginInstallError < PluginError; end
- PLUGIN_FILE_NAME = "plugins.rb".freeze
+ PLUGIN_FILE_NAME = "plugins.rb"
- module_function
+ module_function
def reset!
instance_variables.each {|i| remove_instance_variable(i) }
@@ -35,16 +36,43 @@ module Bundler
# @param [Hash] options various parameters as described in description.
# Refer to cli/plugin for available options
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 => 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) }
+ 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
+
+ # Uninstalls plugins by the given names
+ #
+ # @param [Array<String>] names the names of plugins to be uninstalled
+ 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
- Bundler.ui.error "Failed to install plugin #{name}: #{e.message}\n #{e.backtrace.join("\n ")}"
+ 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
# List installed plugins and commands
@@ -73,18 +101,19 @@ module Bundler
# @param [Pathname] gemfile path
# @param [Proc] block that can be evaluated for (inline) Gemfile
def gemfile_install(gemfile = nil, &inline)
- Bundler.settings.temporary(:frozen => false, :deployment => false) do
+ 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 }
+ plugins = definition.dependencies.map(&:name)
installed_specs = Installer.new.install_definition(definition)
save_plugins plugins, installed_specs, builder.inferred_plugins
@@ -138,7 +167,7 @@ module Bundler
end
# To be called from Cli class to pass the command and argument to
- # approriate plugin class
+ # appropriate plugin class
def exec_command(command, args)
raise UndefinedCommandError, "Command `#{command}` not found" unless command? command
@@ -157,7 +186,7 @@ module Bundler
!index.source_plugin(name.to_s).nil?
end
- # @return [Class] that handles the source. The calss includes API::Source
+ # @return [Class] that handles the source. The class includes API::Source
def source(name)
raise UnknownSourceError, "Source #{name} not found" unless source? name
@@ -166,10 +195,10 @@ module Bundler
@sources[name]
end
- # @param [Hash] The options that are present in the lock file
+ # @param [Hash] The options that are present in the lockfile
# @return [API::Source] the instance of the class that handles the source
# type passed in locked_opts
- def source_from_lock(locked_opts)
+ def from_lock(locked_opts)
src = source(locked_opts["type"])
src.new(locked_opts.merge("uri" => locked_opts["remote"]))
@@ -191,7 +220,7 @@ module Bundler
#
# @param [String] event
def hook(event, *args, &arg_blk)
- return unless Bundler.feature_flag.plugins?
+ return unless Bundler.settings[:plugins]
unless Events.defined_event?(event)
raise ArgumentError, "Event '#{event}' not defined in Bundler::Plugin::Events"
end
@@ -199,7 +228,7 @@ module Bundler
plugins = index.hook_plugins(event)
return unless plugins.any?
- (plugins - @loaded_plugin_names).each {|name| load_plugin(name) }
+ plugins.each {|name| load_plugin(name) }
@hooks_by_event[event].each {|blk| blk.call(*args, &arg_blk) }
end
@@ -211,6 +240,11 @@ module Bundler
Index.new.installed?(plugin)
end
+ # @return [true, false] whether the plugin is loaded
+ def loaded?(plugin)
+ @loaded_plugin_names.include?(plugin)
+ end
+
# Post installation processing and registering with index
#
# @param [Array<String>] plugins list to be installed
@@ -220,9 +254,13 @@ module Bundler
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
+
+ # It's possible that the `plugin` found in the Gemfile don't appear in the specs. For instance when
+ # calling `BUNDLE_WITHOUT=default bundle install`, the plugins will not get installed.
+ next if spec.nil?
+ next if index.up_to_date?(spec)
+
+ save_plugin(name, spec, optional_plugins.include?(name))
end
end
@@ -237,6 +275,22 @@ module Bundler
raise MalformattedPlugin, "#{PLUGIN_FILE_NAME} was not found in the plugin." unless plugin_file.file?
end
+ # Validates and registers a plugin.
+ #
+ # @param [String] name the name of the plugin
+ # @param [Specification] spec of installed plugin
+ # @param [Boolean] optional_plugin, removed if there is conflict with any
+ # other plugin (used for default source plugins)
+ #
+ # @raise [PluginInstallError] if validation or registration raises any error
+ 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
+
# 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.
#
@@ -256,7 +310,7 @@ module Bundler
@hooks_by_event = Hash.new {|h, k| h[k] = [] }
load_paths = spec.load_paths
- Bundler.rubygems.add_to_load_path(load_paths)
+ Gem.add_to_load_path(*load_paths)
path = Pathname.new spec.full_gem_path
begin
@@ -283,12 +337,34 @@ module Bundler
#
# @param [String] name of the plugin
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)
- Bundler.rubygems.add_to_load_path(index.load_paths(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)