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.rb87
1 files changed, 69 insertions, 18 deletions
diff --git a/lib/bundler/plugin.rb b/lib/bundler/plugin.rb
index da3f468da5..faca6bea53 100644
--- a/lib/bundler/plugin.rb
+++ b/lib/bundler/plugin.rb
@@ -13,8 +13,9 @@ 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
@@ -35,15 +36,16 @@ 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
+ 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) }
- names_list = names.map {|name| "`#{name}`" }.join(", ")
- Bundler.ui.error "Failed to install the following plugins: #{names_list}. The underlying error was: #{e.message}.\n #{e.backtrace.join("\n ")}"
+ raise
end
# Uninstalls plugins by the given names
@@ -60,7 +62,8 @@ module Bundler
if names.any?
names.each do |name|
if index.installed?(name)
- Bundler.rm_rf(index.plugin_path(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
@@ -98,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
@@ -163,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
@@ -182,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
@@ -191,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"]))
@@ -216,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
@@ -224,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
@@ -236,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
@@ -245,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
@@ -262,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.
#
@@ -281,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
@@ -308,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)