summaryrefslogtreecommitdiff
path: root/lib/bundler/plugin
diff options
context:
space:
mode:
Diffstat (limited to 'lib/bundler/plugin')
-rw-r--r--lib/bundler/plugin/api.rb2
-rw-r--r--lib/bundler/plugin/api/source.rb62
-rw-r--r--lib/bundler/plugin/dsl.rb2
-rw-r--r--lib/bundler/plugin/events.rb78
-rw-r--r--lib/bundler/plugin/index.rb105
-rw-r--r--lib/bundler/plugin/installer.rb83
-rw-r--r--lib/bundler/plugin/installer/git.rb4
-rw-r--r--lib/bundler/plugin/installer/path.rb26
-rw-r--r--lib/bundler/plugin/installer/rubygems.rb10
-rw-r--r--lib/bundler/plugin/source_list.rb8
10 files changed, 297 insertions, 83 deletions
diff --git a/lib/bundler/plugin/api.rb b/lib/bundler/plugin/api.rb
index a2d5cbb4ac..ee2bffe3ab 100644
--- a/lib/bundler/plugin/api.rb
+++ b/lib/bundler/plugin/api.rb
@@ -23,7 +23,7 @@ module Bundler
# and hooks).
module Plugin
class API
- autoload :Source, "bundler/plugin/api/source"
+ autoload :Source, File.expand_path("api/source", __dir__)
# The plugins should declare that they handle a command through this helper.
#
diff --git a/lib/bundler/plugin/api/source.rb b/lib/bundler/plugin/api/source.rb
index 586477efb5..798326673a 100644
--- a/lib/bundler/plugin/api/source.rb
+++ b/lib/bundler/plugin/api/source.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
-require "uri"
-
module Bundler
module Plugin
class API
@@ -37,11 +35,11 @@ module Bundler
#
# @!attribute [rw] dependency_names
# @return [Array<String>] Names of dependencies that the source should
- # try to resolve. It is not necessary to use this list intenally. This
+ # try to resolve. It is not necessary to use this list internally. This
# is present to be compatible with `Definition` and is used by
# rubygems source.
module Source
- attr_reader :uri, :options, :name
+ attr_reader :uri, :options, :name, :checksum_store
attr_accessor :dependency_names
def initialize(opts)
@@ -50,6 +48,7 @@ module Bundler
@uri = opts["uri"]
@type = opts["type"]
@name = opts["name"] || "#{@type} at #{@uri}"
+ @checksum_store = Checksum::Store.new
end
# This is used by the default `spec` method to constructs the
@@ -68,13 +67,21 @@ module Bundler
# to check out same version of gem later.
#
# There options are passed when the source plugin is created from the
- # lock file.
+ # lockfile.
#
# @return [Hash]
def options_to_lock
{}
end
+ # Download the gem specified by the spec at appropriate path.
+ #
+ # A source plugin can implement this method to split the download and the
+ # installation of a gem.
+ #
+ # @return [Boolean] Whether the download of the gem succeeded.
+ def download(spec, opts); end
+
# Install the gem specified by the spec at appropriate path.
# `install_path` provides a sufficient default, if the source can only
# satisfy one gem, but is not binding.
@@ -97,7 +104,7 @@ module Bundler
#
# Note: Do not override if you don't know what you are doing.
def post_install(spec, disable_exts = false)
- opts = { :env_shebang => false, :disable_extensions => disable_exts }
+ opts = { env_shebang: false, disable_extensions: disable_exts }
installer = Bundler::Source::Path::Installer.new(spec, opts)
installer.post_install
end
@@ -108,7 +115,7 @@ module Bundler
def install_path
@install_path ||=
begin
- base_name = File.basename(URI.parse(uri).normalize.path)
+ base_name = File.basename(Gem::URI.parse(uri).normalize.path)
gem_install_dir.join("#{base_name}-#{uri_hash[0..11]}")
end
@@ -132,7 +139,7 @@ module Bundler
Bundler::Index.build do |index|
files.each do |file|
next unless spec = Bundler.load_gemspec(file)
- Bundler.rubygems.set_installed_by_version(spec)
+ spec.installed_by_version = Gem::VERSION
spec.source = self
Bundler.rubygems.validate(spec)
@@ -142,6 +149,13 @@ module Bundler
end
end
+ # Set internal representation to fetch the gems/specs locally.
+ #
+ # When this is called, the source should try to fetch the specs and
+ # install from the local system.
+ def local!
+ end
+
# Set internal representation to fetch the gems/specs from remote.
#
# When this is called, the source should try to fetch the specs and
@@ -170,7 +184,7 @@ module Bundler
#
# This is used by `app_cache_path`
def app_cache_dirname
- base_name = File.basename(URI.parse(uri).normalize.path)
+ base_name = File.basename(Gem::URI.parse(uri).normalize.path)
"#{base_name}-#{uri_hash}"
end
@@ -190,13 +204,14 @@ module Bundler
FileUtils.rm_rf(new_cache_path)
FileUtils.cp_r(install_path, new_cache_path)
+ FileUtils.rm_rf(app_cache_path.join(".git"))
FileUtils.touch(app_cache_path.join(".bundlecache"))
end
# This shall check if two source object represent the same source.
#
# The comparison shall take place only on the attribute that can be
- # inferred from the options passed from Gemfile and not on attibutes
+ # inferred from the options passed from Gemfile and not on attributes
# that are used to pin down the gem to specific version (e.g. Git
# sources should compare on branch and tag but not on commit hash)
#
@@ -239,7 +254,21 @@ module Bundler
specs.unmet_dependency_names
end
+ # Used by definition.
+ #
+ # Note: Do not override if you don't know what you are doing.
+ def spec_names
+ specs.spec_names
+ end
+
+ # Used by definition.
+ #
# Note: Do not override if you don't know what you are doing.
+ def add_dependency_names(names)
+ @dependencies |= Array(names)
+ end
+
+ # NOTE: Do not override if you don't know what you are doing.
def can_lock?(spec)
spec.source == self
end
@@ -262,10 +291,11 @@ module Bundler
end
def to_s
- "plugin source for #{options[:type]} with uri #{uri}"
+ "plugin source for #{@type} with uri #{@uri}"
end
+ alias_method :identifier, :to_s
- # Note: Do not override if you don't know what you are doing.
+ # NOTE: Do not override if you don't know what you are doing.
def include?(other)
other == self
end
@@ -274,7 +304,7 @@ module Bundler
SharedHelpers.digest(:SHA1).hexdigest(uri)
end
- # Note: Do not override if you don't know what you are doing.
+ # NOTE: Do not override if you don't know what you are doing.
def gem_install_dir
Bundler.install_path
end
@@ -289,12 +319,6 @@ module Bundler
end
# @private
- # Returns true
- def bundler_plugin_api_source?
- true
- end
-
- # @private
# This API on source might not be stable, and for now we expect plugins
# to download all specs in `#specs`, so we implement the method for
# compatibility purposes and leave it undocumented (and don't support)
diff --git a/lib/bundler/plugin/dsl.rb b/lib/bundler/plugin/dsl.rb
index 4bfc8437e0..da751d1774 100644
--- a/lib/bundler/plugin/dsl.rb
+++ b/lib/bundler/plugin/dsl.rb
@@ -10,7 +10,7 @@ module Bundler
# So that we don't have to override all there methods to dummy ones
# explicitly.
# They will be handled by method_missing
- [:gemspec, :gem, :path, :install_if, :platforms, :env].each {|m| undef_method m }
+ [:gemspec, :gem, :install_if, :platforms, :env].each {|m| undef_method m }
# This lists the plugins that was added automatically and not specified by
# the user.
diff --git a/lib/bundler/plugin/events.rb b/lib/bundler/plugin/events.rb
index bc037d1af5..3fbf60307e 100644
--- a/lib/bundler/plugin/events.rb
+++ b/lib/bundler/plugin/events.rb
@@ -31,6 +31,54 @@ module Bundler
end
# @!parse
+ # A hook called before the Gemfile is evaluated
+ # Includes the Gemfile path and the Lockfile path
+ # GEM_BEFORE_EVAL = "before-eval"
+ define :GEM_BEFORE_EVAL, "before-eval"
+
+ # @!parse
+ # A hook called after the Gemfile is evaluated
+ # Includes a Bundler::Definition
+ # GEM_AFTER_EVAL = "after-eval"
+ define :GEM_AFTER_EVAL, "after-eval"
+
+ # @!parse
+ # A hook called before any gems install
+ # Includes an Array of Bundler::Dependency objects
+ # GEM_BEFORE_INSTALL_ALL = "before-install-all"
+ define :GEM_BEFORE_INSTALL_ALL, "before-install-all"
+
+ # @!parse
+ # A hook called before each individual gem is downloaded from a remote source.
+ # Includes a spec-like object responding to the Gem::Specification API
+ # (for example, a Bundler spec proxy such as Bundler::EndpointSpecification
+ # or Bundler::RemoteSpecification). Does not fire when the gem is already
+ # present at the initial download-cache check.
+ # GEM_BEFORE_FETCH = "before-fetch"
+ define :GEM_BEFORE_FETCH, "before-fetch"
+
+ # @!parse
+ # A hook called after each individual gem is downloaded from a remote source.
+ # Includes a spec-like object responding to the Gem::Specification API
+ # (for example, a Bundler spec proxy such as Bundler::EndpointSpecification
+ # or Bundler::RemoteSpecification). Does not fire when the gem is already
+ # present at the initial download-cache check.
+ # GEM_AFTER_FETCH = "after-fetch"
+ define :GEM_AFTER_FETCH, "after-fetch"
+
+ # @!parse
+ # A hook called before a git source is fetched or checked out.
+ # Includes a Bundler::Source::Git reference.
+ # GIT_BEFORE_FETCH = "before-git-fetch"
+ define :GIT_BEFORE_FETCH, "before-git-fetch"
+
+ # @!parse
+ # A hook called after a git source is fetched or checked out.
+ # Includes a Bundler::Source::Git reference.
+ # GIT_AFTER_FETCH = "after-git-fetch"
+ define :GIT_AFTER_FETCH, "after-git-fetch"
+
+ # @!parse
# A hook called before each individual gem is installed
# Includes a Bundler::ParallelInstaller::SpecInstallation.
# No state, error, post_install_message will be present as nothing has installed yet
@@ -46,16 +94,34 @@ module Bundler
define :GEM_AFTER_INSTALL, "after-install"
# @!parse
- # A hook called before any gems install
- # Includes an Array of Bundler::Dependency objects
- # GEM_BEFORE_INSTALL_ALL = "before-install-all"
- define :GEM_BEFORE_INSTALL_ALL, "before-install-all"
-
- # @!parse
# A hook called after any gems install
# Includes an Array of Bundler::Dependency objects
# GEM_AFTER_INSTALL_ALL = "after-install-all"
define :GEM_AFTER_INSTALL_ALL, "after-install-all"
+
+ # @!parse
+ # A hook called before any gems require
+ # Includes an Array of Bundler::Dependency objects.
+ # GEM_BEFORE_REQUIRE_ALL = "before-require-all"
+ define :GEM_BEFORE_REQUIRE_ALL, "before-require-all"
+
+ # @!parse
+ # A hook called before each individual gem is required
+ # Includes a Bundler::Dependency.
+ # GEM_BEFORE_REQUIRE = "before-require"
+ define :GEM_BEFORE_REQUIRE, "before-require"
+
+ # @!parse
+ # A hook called after each individual gem is required
+ # Includes a Bundler::Dependency.
+ # GEM_AFTER_REQUIRE = "after-require"
+ define :GEM_AFTER_REQUIRE, "after-require"
+
+ # @!parse
+ # A hook called after all gems required
+ # Includes an Array of Bundler::Dependency objects.
+ # GEM_AFTER_REQUIRE_ALL = "after-require-all"
+ define :GEM_AFTER_REQUIRE_ALL, "after-require-all"
end
end
end
diff --git a/lib/bundler/plugin/index.rb b/lib/bundler/plugin/index.rb
index f09587dfda..1dfb061304 100644
--- a/lib/bundler/plugin/index.rb
+++ b/lib/bundler/plugin/index.rb
@@ -31,9 +31,13 @@ module Bundler
begin
load_index(global_index_file, true)
- rescue GenericSystemCallError
+ rescue PermissionError
# no need to fail when on a read-only FS, for example
nil
+ rescue ArgumentError => e
+ # ruby 3.4 checks writability in Dir.tmpdir
+ raise unless e.message&.include?("could not find a temporary directory")
+ nil
end
load_index(local_index_file) if SharedHelpers.in_bundle?
end
@@ -58,7 +62,10 @@ module Bundler
raise SourceConflict.new(name, common) unless common.empty?
sources.each {|k| @sources[k] = name }
- hooks.each {|e| (@hooks[e] ||= []) << name }
+ hooks.each do |event|
+ event_hooks = (@hooks[event] ||= []) << name
+ event_hooks.uniq!
+ end
@plugin_paths[name] = path
@load_paths[name] = load_paths
@@ -68,6 +75,18 @@ module Bundler
raise
end
+ def unregister_plugin(name)
+ @commands.delete_if {|_, v| v == name }
+ @sources.delete_if {|_, v| v == name }
+ @hooks.each do |hook, names|
+ names.delete(name)
+ @hooks.delete(hook) if names.empty?
+ end
+ @plugin_paths.delete(name)
+ @load_paths.delete(name)
+ save_index
+ end
+
# Path of default index file
def index_file
Plugin.root.join("index")
@@ -100,6 +119,20 @@ module Bundler
@plugin_paths[name]
end
+ def up_to_date?(spec)
+ path = installed?(spec.name)
+
+ path == spec.full_gem_path
+ end
+
+ def installed_plugins
+ @plugin_paths.keys
+ end
+
+ def plugin_commands(plugin)
+ @commands.find_all {|_, n| n == plugin }.map(&:first)
+ end
+
def source?(source)
@sources.key? source
end
@@ -113,7 +146,15 @@ module Bundler
@hooks[event] || []
end
- private
+ # This plugin is installed inside the .bundle/plugin directory,
+ # and thus is managed solely by Bundler
+ def installed_in_plugin_root?(name)
+ return false unless (path = installed?(name))
+
+ path.start_with?("#{Plugin.root}/")
+ end
+
+ private
# Reads the index file from the directory and initializes the instance
# variables.
@@ -122,19 +163,21 @@ module Bundler
# @param [Pathname] index file path
# @param [Boolean] is the index file global index
def load_index(index_file, global = false)
+ base = base_for_index(global)
+
SharedHelpers.filesystem_access(index_file, :read) do |index_f|
- valid_file = index_f && index_f.exist? && !index_f.size.zero?
+ valid_file = index_f&.exist? && !index_f.size.zero?
break unless valid_file
data = index_f.read
- require "bundler/yaml_serializer"
+ require_relative "../yaml_serializer"
index = YAMLSerializer.load(data)
@commands.merge!(index["commands"])
@hooks.merge!(index["hooks"])
- @load_paths.merge!(index["load_paths"])
- @plugin_paths.merge!(index["plugin_paths"])
+ @load_paths.merge!(transform_index_paths(index["load_paths"]) {|p| absolutize_path(p, base) })
+ @plugin_paths.merge!(transform_index_paths(index["plugin_paths"]) {|p| absolutize_path(p, base) })
@sources.merge!(index["sources"]) unless global
end
end
@@ -143,20 +186,56 @@ module Bundler
# instance variables in YAML format. (The instance variables are supposed
# to be only String key value pairs)
def save_index
+ base = base_for_index(false)
+
index = {
- "commands" => @commands,
- "hooks" => @hooks,
- "load_paths" => @load_paths,
- "plugin_paths" => @plugin_paths,
- "sources" => @sources,
+ "commands" => @commands,
+ "hooks" => @hooks,
+ "load_paths" => transform_index_paths(@load_paths) {|p| relativize_path(p, base) },
+ "plugin_paths" => transform_index_paths(@plugin_paths) {|p| relativize_path(p, base) },
+ "sources" => @sources,
}
- require "bundler/yaml_serializer"
+ require_relative "../yaml_serializer"
SharedHelpers.filesystem_access(index_file) do |index_f|
FileUtils.mkdir_p(index_f.dirname)
File.open(index_f, "w") {|f| f.puts YAMLSerializer.dump(index) }
end
end
+
+ def base_for_index(global)
+ global ? Plugin.global_root : Plugin.root
+ end
+
+ def transform_index_paths(paths)
+ return {} unless paths
+
+ paths.transform_values do |value|
+ if value.is_a?(Array)
+ value.map {|path| yield path }
+ else
+ yield value
+ end
+ end
+ end
+
+ def relativize_path(path, base)
+ pathname = Pathname.new(path)
+ return path unless pathname.absolute?
+
+ base_path = Pathname.new(base)
+ if pathname == base_path || pathname.to_s.start_with?(base_path.to_s + File::SEPARATOR)
+ pathname.relative_path_from(base_path).to_s
+ else
+ path
+ end
+ end
+
+ def absolutize_path(path, base)
+ pathname = Pathname.new(path)
+ pathname = Pathname.new(base).join(pathname) unless pathname.absolute?
+ pathname.to_s
+ end
end
end
end
diff --git a/lib/bundler/plugin/installer.rb b/lib/bundler/plugin/installer.rb
index 5379c38979..9be8b36843 100644
--- a/lib/bundler/plugin/installer.rb
+++ b/lib/bundler/plugin/installer.rb
@@ -8,18 +8,22 @@ module Bundler
# are heavily dependent on the Gemfile.
module Plugin
class Installer
- autoload :Rubygems, "bundler/plugin/installer/rubygems"
- autoload :Git, "bundler/plugin/installer/git"
+ autoload :Rubygems, File.expand_path("installer/rubygems", __dir__)
+ autoload :Git, File.expand_path("installer/git", __dir__)
+ autoload :Path, File.expand_path("installer/path", __dir__)
def install(names, options)
+ check_sources_consistency!(options)
+
version = options[:version] || [">= 0"]
- Bundler.settings.temporary(:lockfile_uses_separate_rubygems_sources => false, :disable_multisource => false) do
- if options[:git]
- install_git(names, version, options)
- else
- sources = options[:source] || Bundler.rubygems.sources
- install_rubygems(names, version, sources)
- end
+
+ if options[:git]
+ install_git(names, version, options)
+ elsif options[:path]
+ install_path(names, version, options[:path])
+ else
+ sources = options[:source] || Gem.sources
+ install_rubygems(names, version, sources)
end
end
@@ -30,30 +34,42 @@ module Bundler
# @return [Hash] map of names to their specs they are installed with
def install_definition(definition)
def definition.lock(*); end
- definition.resolve_remotely!
+ definition.remotely!
specs = definition.specs
install_from_specs specs
end
- private
+ private
- def install_git(names, version, options)
- uri = options.delete(:git)
- options["uri"] = uri
+ def check_sources_consistency!(options)
+ if (options.keys & [:source, :git, :path]).length > 1
+ raise InvalidOption, "Only one of --source, --git, or --path may be specified"
+ end
- source_list = SourceList.new
- source_list.add_git_source(options)
+ if (options.key?(:branch) || options.key?(:ref)) && !options.key?(:git)
+ raise InvalidOption, "--#{options.key?(:branch) ? "branch" : "ref"} can only be used with git sources"
+ end
- # To support both sources
- if options[:source]
- source_list.add_rubygems_source("remotes" => options[:source])
+ if options.key?(:branch) && options.key?(:ref)
+ raise InvalidOption, "--branch and --ref can't be both specified"
end
+ end
- deps = names.map {|name| Dependency.new name, version }
+ def install_git(names, version, options)
+ source_list = SourceList.new
+ source = source_list.add_git_source({ "uri" => options[:git],
+ "branch" => options[:branch],
+ "ref" => options[:ref] })
- definition = Definition.new(nil, deps, source_list, true)
- install_definition(definition)
+ install_all_sources(names, version, source_list, source)
+ end
+
+ def install_path(names, version, path)
+ source_list = SourceList.new
+ source = source_list.add_path_source({ "path" => path, "root_path" => SharedHelpers.pwd })
+
+ install_all_sources(names, version, source_list, source)
end
# Installs the plugin from rubygems source and returns the path where the
@@ -65,13 +81,23 @@ module Bundler
#
# @return [Hash] map of names to the specs of plugins installed
def install_rubygems(names, version, sources)
- deps = names.map {|name| Dependency.new name, version }
-
source_list = SourceList.new
- source_list.add_rubygems_source("remotes" => sources)
- definition = Definition.new(nil, deps, source_list, true)
- install_definition(definition)
+ Array(sources).each {|remote| source_list.add_global_rubygems_remote(remote) }
+
+ install_all_sources(names, version, source_list)
+ end
+
+ def install_all_sources(names, version, source_list, source = nil)
+ deps = names.map {|name| Dependency.new(name, version, { "source" => source }) }
+
+ Bundler.configure_gem_home_and_path(Plugin.root)
+
+ Bundler.settings.temporary(deployment: false, frozen: false) do
+ definition = Definition.new(nil, deps, source_list, true)
+
+ install_definition(definition)
+ end
end
# Installs the plugins and deps from the provided specs and returns map of
@@ -84,7 +110,8 @@ module Bundler
paths = {}
specs.each do |spec|
- spec.source.install spec
+ spec.source.download(spec)
+ spec.source.install(spec)
paths[spec.name] = spec
end
diff --git a/lib/bundler/plugin/installer/git.rb b/lib/bundler/plugin/installer/git.rb
index fbb6c5e40e..deec5e99b3 100644
--- a/lib/bundler/plugin/installer/git.rb
+++ b/lib/bundler/plugin/installer/git.rb
@@ -20,10 +20,6 @@ module Bundler
end
end
- def version_message(spec)
- "#{spec.name} #{spec.version}"
- end
-
def root
Plugin.root
end
diff --git a/lib/bundler/plugin/installer/path.rb b/lib/bundler/plugin/installer/path.rb
new file mode 100644
index 0000000000..58c4924eb0
--- /dev/null
+++ b/lib/bundler/plugin/installer/path.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module Bundler
+ module Plugin
+ class Installer
+ class Path < Bundler::Source::Path
+ def root
+ SharedHelpers.in_bundle? ? Bundler.root : Plugin.root
+ end
+
+ def eql?(other)
+ return unless other.class == self.class
+ expanded_original_path == other.expanded_original_path &&
+ version == other.version
+ end
+
+ alias_method :==, :eql?
+
+ def generate_bin(spec, disable_extensions = false)
+ # Need to find a way without code duplication
+ # For now, we can ignore this
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bundler/plugin/installer/rubygems.rb b/lib/bundler/plugin/installer/rubygems.rb
index 7ae74fa93b..cb5db9c30e 100644
--- a/lib/bundler/plugin/installer/rubygems.rb
+++ b/lib/bundler/plugin/installer/rubygems.rb
@@ -4,15 +4,7 @@ module Bundler
module Plugin
class Installer
class Rubygems < Bundler::Source::Rubygems
- def version_message(spec)
- "#{spec.name} #{spec.version}"
- end
-
- private
-
- def requires_sudo?
- false # Will change on implementation of project level plugins
- end
+ private
def rubygems_dir
Plugin.root
diff --git a/lib/bundler/plugin/source_list.rb b/lib/bundler/plugin/source_list.rb
index f0e212205f..d929ade29e 100644
--- a/lib/bundler/plugin/source_list.rb
+++ b/lib/bundler/plugin/source_list.rb
@@ -9,6 +9,10 @@ module Bundler
add_source_to_list Plugin::Installer::Git.new(options), git_sources
end
+ def add_path_source(options = {})
+ add_source_to_list Plugin::Installer::Path.new(options), path_sources
+ end
+
def add_rubygems_source(options = {})
add_source_to_list Plugin::Installer::Rubygems.new(options), @rubygems_sources
end
@@ -17,9 +21,9 @@ module Bundler
path_sources + git_sources + rubygems_sources + [metadata_source]
end
- private
+ private
- def rubygems_aggregate_class
+ def source_class
Plugin::Installer::Rubygems
end
end