summaryrefslogtreecommitdiff
path: root/lib/bundler
diff options
context:
space:
mode:
authorhsbt <hsbt@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2017-11-01 23:29:38 +0000
committerhsbt <hsbt@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2017-11-01 23:29:38 +0000
commitbe7b5929126cb3e696ef222339237faba9b8fe5a (patch)
tree51eae376f93c09bc82dde5a657a91df2c89062e4 /lib/bundler
parentae49dbd392083f69026f2a0fff4a1d5f42d172a7 (diff)
Update bundled bundler to 1.16.0.
* lib/bundler, spec/bundler: Merge bundler-1.16.0. * common.mk: rspec examples of bundler-1.16.0 needs require option. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@60603 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'lib/bundler')
-rw-r--r--lib/bundler/build_metadata.rb36
-rw-r--r--lib/bundler/capistrano.rb5
-rw-r--r--lib/bundler/cli.rb222
-rw-r--r--lib/bundler/cli/add.rb1
-rw-r--r--lib/bundler/cli/binstubs.rb16
-rw-r--r--lib/bundler/cli/cache.rb9
-rw-r--r--lib/bundler/cli/check.rb8
-rw-r--r--lib/bundler/cli/clean.rb11
-rw-r--r--lib/bundler/cli/common.rb13
-rw-r--r--lib/bundler/cli/config.rb3
-rw-r--r--lib/bundler/cli/console.rb3
-rw-r--r--lib/bundler/cli/doctor.rb1
-rw-r--r--lib/bundler/cli/exec.rb5
-rw-r--r--lib/bundler/cli/gem.rb5
-rw-r--r--lib/bundler/cli/info.rb1
-rw-r--r--lib/bundler/cli/init.rb23
-rw-r--r--lib/bundler/cli/inject.rb1
-rw-r--r--lib/bundler/cli/install.rb122
-rw-r--r--lib/bundler/cli/issue.rb2
-rw-r--r--lib/bundler/cli/list.rb22
-rw-r--r--lib/bundler/cli/lock.rb1
-rw-r--r--lib/bundler/cli/open.rb4
-rw-r--r--lib/bundler/cli/outdated.rb21
-rw-r--r--lib/bundler/cli/package.rb15
-rw-r--r--lib/bundler/cli/platform.rb1
-rw-r--r--lib/bundler/cli/plugin.rb1
-rw-r--r--lib/bundler/cli/pristine.rb11
-rw-r--r--lib/bundler/cli/show.rb1
-rw-r--r--lib/bundler/cli/update.rb36
-rw-r--r--lib/bundler/cli/viz.rb1
-rw-r--r--lib/bundler/compact_index_client.rb1
-rw-r--r--lib/bundler/compact_index_client/cache.rb3
-rw-r--r--lib/bundler/compact_index_client/updater.rb7
-rw-r--r--lib/bundler/compatibility_guard.rb14
-rw-r--r--lib/bundler/constants.rb1
-rw-r--r--lib/bundler/current_ruby.rb9
-rw-r--r--lib/bundler/definition.rb266
-rw-r--r--lib/bundler/dep_proxy.rb2
-rw-r--r--lib/bundler/dependency.rb13
-rw-r--r--lib/bundler/deployment.rb2
-rw-r--r--lib/bundler/deprecate.rb17
-rw-r--r--lib/bundler/dsl.rb159
-rw-r--r--lib/bundler/endpoint_specification.rb9
-rw-r--r--lib/bundler/env.rb113
-rw-r--r--lib/bundler/environment_preserver.rb33
-rw-r--r--lib/bundler/errors.rb1
-rw-r--r--lib/bundler/feature_flag.rb43
-rw-r--r--lib/bundler/fetcher.rb23
-rw-r--r--lib/bundler/fetcher/base.rb1
-rw-r--r--lib/bundler/fetcher/compact_index.rb13
-rw-r--r--lib/bundler/fetcher/dependency.rb3
-rw-r--r--lib/bundler/fetcher/downloader.rb1
-rw-r--r--lib/bundler/fetcher/index.rb1
-rw-r--r--lib/bundler/friendly_errors.rb5
-rw-r--r--lib/bundler/gem_helper.rb27
-rw-r--r--lib/bundler/gem_helpers.rb1
-rw-r--r--lib/bundler/gem_remote_fetcher.rb1
-rw-r--r--lib/bundler/gem_tasks.rb1
-rw-r--r--lib/bundler/gem_version_promoter.rb1
-rw-r--r--lib/bundler/gemdeps.rb1
-rw-r--r--lib/bundler/graph.rb1
-rw-r--r--lib/bundler/index.rb16
-rw-r--r--lib/bundler/injector.rb47
-rw-r--r--lib/bundler/inline.rb12
-rw-r--r--lib/bundler/installer.rb154
-rw-r--r--lib/bundler/installer/gem_installer.rb2
-rw-r--r--lib/bundler/installer/parallel_installer.rb115
-rw-r--r--lib/bundler/installer/standalone.rb1
-rw-r--r--lib/bundler/lazy_specification.rb3
-rw-r--r--lib/bundler/lockfile_generator.rb95
-rw-r--r--lib/bundler/lockfile_parser.rb14
-rw-r--r--lib/bundler/match_platform.rb1
-rw-r--r--lib/bundler/mirror.rb9
-rw-r--r--lib/bundler/plugin.rb1
-rw-r--r--lib/bundler/plugin/api/source.rb11
-rw-r--r--lib/bundler/plugin/installer.rb13
-rw-r--r--lib/bundler/plugin/source_list.rb15
-rw-r--r--lib/bundler/process_lock.rb24
-rw-r--r--lib/bundler/psyched_yaml.rb10
-rw-r--r--lib/bundler/remote_specification.rb1
-rw-r--r--lib/bundler/resolver.rb345
-rw-r--r--lib/bundler/resolver/spec_group.rb111
-rw-r--r--lib/bundler/retry.rb1
-rw-r--r--lib/bundler/ruby_dsl.rb1
-rw-r--r--lib/bundler/ruby_version.rb1
-rw-r--r--lib/bundler/rubygems_ext.rb9
-rw-r--r--lib/bundler/rubygems_gem_installer.rb25
-rw-r--r--lib/bundler/rubygems_integration.rb84
-rw-r--r--lib/bundler/runtime.rb8
-rw-r--r--lib/bundler/settings.rb254
-rw-r--r--lib/bundler/settings/validator.rb79
-rw-r--r--lib/bundler/setup.rb1
-rw-r--r--lib/bundler/shared_helpers.rb119
-rw-r--r--lib/bundler/similarity_detector.rb1
-rw-r--r--lib/bundler/source.rb36
-rw-r--r--lib/bundler/source/gemspec.rb1
-rw-r--r--lib/bundler/source/git.rb40
-rw-r--r--lib/bundler/source/git/git_proxy.rb25
-rw-r--r--lib/bundler/source/metadata.rb63
-rw-r--r--lib/bundler/source/path.rb16
-rw-r--r--lib/bundler/source/path/installer.rb2
-rw-r--r--lib/bundler/source/rubygems.rb238
-rw-r--r--lib/bundler/source/rubygems/remote.rb5
-rw-r--r--lib/bundler/source_list.rb90
-rw-r--r--lib/bundler/spec_set.rb10
-rw-r--r--lib/bundler/ssl_certs/certificate_manager.rb3
-rw-r--r--lib/bundler/stub_specification.rb1
-rwxr-xr-xlib/bundler/templates/Executable4
-rw-r--r--lib/bundler/templates/Executable.bundler105
-rw-r--r--lib/bundler/templates/Gemfile1
-rw-r--r--lib/bundler/templates/gems.rb8
-rw-r--r--lib/bundler/templates/newgem/README.md.tt2
-rw-r--r--lib/bundler/templates/newgem/gitignore.tt1
-rw-r--r--lib/bundler/templates/newgem/newgem.gemspec.tt5
-rw-r--r--lib/bundler/templates/newgem/rspec.tt1
-rw-r--r--lib/bundler/templates/newgem/spec/newgem_spec.rb.tt2
-rw-r--r--lib/bundler/ui.rb1
-rw-r--r--lib/bundler/ui/rg_proxy.rb1
-rw-r--r--lib/bundler/ui/shell.rb19
-rw-r--r--lib/bundler/ui/silent.rb1
-rw-r--r--lib/bundler/uri_credentials_filter.rb1
-rw-r--r--lib/bundler/vendor/fileutils/lib/fileutils.rb1638
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo.rb2
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/compatibility.rb26
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/delegates/resolution_state.rb7
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/delegates/specification_provider.rb1
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb5
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/action.rb1
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb1
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rb1
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rb1
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb1
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/log.rb1
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rb1
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/tag.rb1
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/vertex.rb7
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/errors.rb75
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb3
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/modules/specification_provider.rb1
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/modules/ui.rb4
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/resolution.rb639
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/resolver.rb1
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/state.rb12
-rw-r--r--lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb2
-rw-r--r--lib/bundler/vendor/thor/lib/thor/runner.rb4
-rw-r--r--lib/bundler/vendored_fileutils.rb9
-rw-r--r--lib/bundler/vendored_molinillo.rb1
-rw-r--r--lib/bundler/vendored_persistent.rb35
-rw-r--r--lib/bundler/vendored_thor.rb1
-rw-r--r--lib/bundler/version.rb8
-rw-r--r--lib/bundler/version_ranges.rb1
-rw-r--r--lib/bundler/vlad.rb5
-rw-r--r--lib/bundler/worker.rb1
-rw-r--r--lib/bundler/yaml_serializer.rb6
154 files changed, 4879 insertions, 1262 deletions
diff --git a/lib/bundler/build_metadata.rb b/lib/bundler/build_metadata.rb
new file mode 100644
index 0000000000..54436f982d
--- /dev/null
+++ b/lib/bundler/build_metadata.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module Bundler
+ # Represents metadata from when the Bundler gem was built.
+ module BuildMetadata
+ # begin ivars
+ @release = false
+ # end ivars
+
+ # A hash representation of the build metadata.
+ def self.to_h
+ {
+ "Built At" => built_at,
+ "Git SHA" => git_commit_sha,
+ "Released Version" => release?,
+ }
+ end
+
+ # A string representing the date the bundler gem was built.
+ def self.built_at
+ @built_at ||= Time.now.utc.strftime("%Y-%m-%d").freeze
+ end
+
+ # The SHA for the git commit the bundler gem was built from.
+ def self.git_commit_sha
+ @git_commit_sha ||= Dir.chdir(File.expand_path("..", __FILE__)) do
+ `git rev-parse --short HEAD`.strip.freeze
+ end
+ end
+
+ # Whether this is an official release build of Bundler.
+ def self.release?
+ @release
+ end
+ end
+end
diff --git a/lib/bundler/capistrano.rb b/lib/bundler/capistrano.rb
index 7b0bbbd6d2..1b7145b72b 100644
--- a/lib/bundler/capistrano.rb
+++ b/lib/bundler/capistrano.rb
@@ -1,4 +1,9 @@
# frozen_string_literal: true
+
+require "bundler/shared_helpers"
+Bundler::SharedHelpers.major_deprecation 2,
+ "The Bundler task for Capistrano. Please use http://github.com/capistrano/bundler"
+
# Capistrano task for Bundler.
#
# Add "require 'bundler/capistrano'" in your Capistrano deploy.rb, and
diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb
index 03e08e25a1..05e1851c18 100644
--- a/lib/bundler/cli.rb
+++ b/lib/bundler/cli.rb
@@ -1,13 +1,18 @@
# frozen_string_literal: true
+
require "bundler"
require "bundler/vendored_thor"
module Bundler
class CLI < Thor
- AUTO_INSTALL_CMDS = %w(show binstubs outdated exec open console licenses clean).freeze
- PARSEABLE_COMMANDS = %w(
+ require "bundler/cli/common"
+
+ package_name "Bundler"
+
+ AUTO_INSTALL_CMDS = %w[show binstubs outdated exec open console licenses clean].freeze
+ PARSEABLE_COMMANDS = %w[
check config help exec platform show version
- ).freeze
+ ].freeze
def self.start(*)
super
@@ -30,11 +35,11 @@ module Bundler
custom_gemfile = options[:gemfile] || Bundler.settings[:gemfile]
if custom_gemfile && !custom_gemfile.empty?
- ENV["BUNDLE_GEMFILE"] = File.expand_path(custom_gemfile)
+ Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", File.expand_path(custom_gemfile)
Bundler.reset_paths!
end
- Bundler.settings[:retry] = options[:retry] if options[:retry]
+ Bundler.settings.set_command_option_if_given :retry, options[:retry]
current_cmd = args.last[:current_command].name
auto_install if AUTO_INSTALL_CMDS.include?(current_cmd)
@@ -42,7 +47,6 @@ module Bundler
raise InvalidOption, e.message
ensure
self.options ||= {}
- Bundler.settings.cli_flags_given = !options.empty?
unprinted_warnings = Bundler.ui.unprinted_warnings
Bundler.ui = UI::Shell.new(options)
Bundler.ui.level = "debug" if options["verbose"]
@@ -57,10 +61,41 @@ module Bundler
end
end
+ def self.deprecated_option(*args, &blk)
+ return if Bundler.feature_flag.forget_cli_options?
+ method_option(*args, &blk)
+ end
+
check_unknown_options!(:except => [:config, :exec])
stop_on_unknown_option! :exec
- default_task :install
+ desc "cli_help", "Prints a summary of bundler commands", :hide => true
+ def cli_help
+ version
+ Bundler.ui.info "\n"
+
+ primary_commands = ["install", "update",
+ Bundler.feature_flag.cache_command_is_package? ? "cache" : "package",
+ "exec", "config", "help"]
+
+ list = self.class.printable_commands(true)
+ by_name = list.group_by {|name, _message| name.match(/^bundle (\w+)/)[1] }
+ utilities = by_name.keys.sort - primary_commands
+ primary_commands.map! {|name| (by_name[name] || raise("no primary command #{name}")).first }
+ utilities.map! {|name| by_name[name].first }
+
+ shell.say "Bundler commands:\n\n"
+
+ shell.say " Primary commands:\n"
+ shell.print_table(primary_commands, :indent => 4, :truncate => true)
+ shell.say
+ shell.say " Utilities:\n"
+ shell.print_table(utilities, :indent => 4, :truncate => true)
+ shell.say
+ self.class.send(:class_options_help, shell)
+ end
+ default_task(Bundler.feature_flag.default_cli_command)
+
class_option "no-color", :type => :boolean, :desc => "Disable colorization in output"
class_option "retry", :type => :numeric, :aliases => "-r", :banner => "NUM",
:desc => "Specify the number of times you wish to attempt network commands"
@@ -107,7 +142,7 @@ module Bundler
Gemfile to a gem with a gemspec, the --gemspec option will automatically add each
dependency listed in the gemspec file to the newly created Gemfile.
D
- method_option "gemspec", :type => :string, :banner => "Use the specified .gemspec to create the Gemfile"
+ deprecated_option "gemspec", :type => :string, :banner => "Use the specified .gemspec to create the Gemfile"
def init
require "bundler/cli/init"
Init.new(options.dup).run
@@ -124,7 +159,7 @@ module Bundler
method_option "gemfile", :type => :string, :banner =>
"Use the specified gemfile instead of Gemfile"
method_option "path", :type => :string, :banner =>
- "Specify a different path than the system default ($BUNDLE_PATH or $GEM_HOME). Bundler will remember this value for future installs on this machine"
+ "Specify a different path than the system default ($BUNDLE_PATH or $GEM_HOME).#{" Bundler will remember this value for future installs on this machine" unless Bundler.feature_flag.forget_cli_options?}"
map "c" => "check"
def check
require "bundler/cli/check"
@@ -142,13 +177,13 @@ module Bundler
If the bundle has already been installed, bundler will tell you so and then exit.
D
- method_option "binstubs", :type => :string, :lazy_default => "bin", :banner =>
+ deprecated_option "binstubs", :type => :string, :lazy_default => "bin", :banner =>
"Generate bin stubs for bundled gems to ./bin"
- method_option "clean", :type => :boolean, :banner =>
+ deprecated_option "clean", :type => :boolean, :banner =>
"Run bundle clean automatically after install"
- method_option "deployment", :type => :boolean, :banner =>
+ deprecated_option "deployment", :type => :boolean, :banner =>
"Install using defaults tuned for deployment environments"
- method_option "frozen", :type => :boolean, :banner =>
+ deprecated_option "frozen", :type => :boolean, :banner =>
"Do not allow the Gemfile.lock to be updated after this install"
method_option "full-index", :type => :boolean, :banner =>
"Fall back to using the single-file index of all gems"
@@ -158,28 +193,29 @@ module Bundler
"Specify the number of jobs to run in parallel"
method_option "local", :type => :boolean, :banner =>
"Do not attempt to fetch gems remotely and use the gem cache instead"
- method_option "no-cache", :type => :boolean, :banner =>
+ deprecated_option "no-cache", :type => :boolean, :banner =>
"Don't update the existing gem cache."
- method_option "force", :type => :boolean, :banner =>
+ method_option "redownload", :type => :boolean, :aliases =>
+ [Bundler.feature_flag.forget_cli_options? ? nil : "--force"].compact, :banner =>
"Force downloading every gem."
- method_option "no-prune", :type => :boolean, :banner =>
+ deprecated_option "no-prune", :type => :boolean, :banner =>
"Don't remove stale gems from the cache."
- method_option "path", :type => :string, :banner =>
+ deprecated_option "path", :type => :string, :banner =>
"Specify a different path than the system default ($BUNDLE_PATH or $GEM_HOME). Bundler will remember this value for future installs on this machine"
method_option "quiet", :type => :boolean, :banner =>
"Only output warnings and errors."
- method_option "shebang", :type => :string, :banner =>
+ deprecated_option "shebang", :type => :string, :banner =>
"Specify a different shebang executable name than the default (usually 'ruby')"
method_option "standalone", :type => :array, :lazy_default => [], :banner =>
"Make a bundle that can work without the Bundler runtime"
- method_option "system", :type => :boolean, :banner =>
+ deprecated_option "system", :type => :boolean, :banner =>
"Install to the system location ($BUNDLE_PATH or $GEM_HOME) even if the bundle was previously installed somewhere else for this application"
method_option "trust-policy", :alias => "P", :type => :string, :banner =>
"Gem trust policy (like gem install -P). Must be one of " +
Bundler.rubygems.security_policy_keys.join("|")
- method_option "without", :type => :array, :banner =>
+ deprecated_option "without", :type => :array, :banner =>
"Exclude gems that are part of the specified named group."
- method_option "with", :type => :array, :banner =>
+ deprecated_option "with", :type => :array, :banner =>
"Include gems that are part of the specified named group."
map "i" => "install"
def install
@@ -189,7 +225,7 @@ module Bundler
end
end
- desc "update [OPTIONS]", "update the current environment"
+ desc "update [OPTIONS]", "Update the current environment"
long_desc <<-D
Update will install the newest versions of the gems listed in the Gemfile. Use
update when you have changed the Gemfile, or if you want to get the newest
@@ -223,6 +259,8 @@ module Bundler
"Do not allow any gem to be updated past latest --patch | --minor | --major"
method_option "conservative", :type => :boolean, :banner =>
"Use bundle install conservative update behavior and do not allow shared dependencies to be updated."
+ method_option "all", :type => :boolean, :banner =>
+ "Update everything."
def update(*gems)
require "bundler/cli/update"
Update.new(options, gems).run
@@ -238,12 +276,24 @@ module Bundler
method_option "outdated", :type => :boolean,
:banner => "Show verbose output including whether gems are outdated."
def show(gem_name = nil)
- Bundler::SharedHelpers.major_deprecation("use `bundle show` instead of `bundle list`") if ARGV[0] == "list"
+ Bundler::SharedHelpers.major_deprecation(2, "use `bundle list` instead of `bundle show`") if ARGV[0] == "show"
require "bundler/cli/show"
Show.new(options, gem_name).run
end
- # TODO: 2.0 remove `bundle list`
- map %w(list) => "show"
+ # TODO: 2.0 remove `bundle show`
+
+ if Bundler.feature_flag.list_command?
+ desc "list", "List all gems in the bundle"
+ method_option "name-only", :type => :boolean, :banner => "print only the gem names"
+ def list
+ require "bundler/cli/list"
+ List.new(options).run
+ end
+
+ map %w[ls] => "list"
+ else
+ map %w[list] => "show"
+ end
desc "info GEM [OPTIONS]", "Show information for the given gem"
method_option "path", :type => :boolean, :banner => "Print full path to gem"
@@ -262,6 +312,8 @@ module Bundler
"Overwrite existing binstubs if they exist"
method_option "path", :type => :string, :lazy_default => "bin", :banner =>
"Binstub destination directory (default bin)"
+ method_option "shebang", :type => :string, :banner =>
+ "Specify a different shebang executable name than the default (usually 'ruby')"
method_option "standalone", :type => :boolean, :banner =>
"Make binstubs that can work without the Bundler runtime"
def binstubs(*gems)
@@ -282,7 +334,7 @@ module Bundler
Add.new(options.dup, gem_name).run
end
- desc "outdated GEM [OPTIONS]", "list installed gems with newer versions available"
+ desc "outdated GEM [OPTIONS]", "List installed gems with newer versions available"
long_desc <<-D
Outdated lists the names and versions of gems that have a newer version available
in the given source. Calling outdated with [GEM [GEM]] will only check for newer
@@ -292,8 +344,8 @@ module Bundler
For more information on patch level options (--major, --minor, --patch,
--update-strict) see documentation on the same options on the update command.
D
- method_option "group", :aliases => "--group", :type => :string, :banner => "List gems from a specific group"
- method_option "groups", :aliases => "--groups", :type => :boolean, :banner => "List gems organized by groups"
+ method_option "group", :type => :string, :banner => "List gems from a specific group"
+ method_option "groups", :type => :boolean, :banner => "List gems organized by groups"
method_option "local", :type => :boolean, :banner =>
"Do not attempt to fetch gems remotely and use the gem cache instead"
method_option "pre", :type => :boolean, :banner => "Check for newer pre-release gems"
@@ -315,17 +367,27 @@ module Bundler
Outdated.new(options, gems).run
end
- desc "cache [OPTIONS]", "Cache all the gems to vendor/cache", :hide => true
- method_option "all", :type => :boolean, :banner => "Include all sources (including path and git)."
- method_option "all-platforms", :type => :boolean, :banner => "Include gems for all platforms present in the lockfile, not only the current one"
- method_option "no-prune", :type => :boolean, :banner => "Don't remove stale gems from the cache."
- def cache
- require "bundler/cli/cache"
- Cache.new(options).run
+ if Bundler.feature_flag.cache_command_is_package?
+ map %w[cache] => :package
+ else
+ desc "cache [OPTIONS]", "Cache all the gems to vendor/cache", :hide => true
+ unless Bundler.feature_flag.cache_command_is_package?
+ method_option "all", :type => :boolean,
+ :banner => "Include all sources (including path and git)."
+ end
+ method_option "all-platforms", :type => :boolean, :banner => "Include gems for all platforms present in the lockfile, not only the current one"
+ method_option "no-prune", :type => :boolean, :banner => "Don't remove stale gems from the cache."
+ def cache
+ require "bundler/cli/cache"
+ Cache.new(options).run
+ end
end
- desc "package [OPTIONS]", "Locks and then caches all of the gems into vendor/cache"
- method_option "all", :type => :boolean, :banner => "Include all sources (including path and git)."
+ desc "#{Bundler.feature_flag.cache_command_is_package? ? :cache : :package} [OPTIONS]", "Locks and then caches all of the gems into vendor/cache"
+ unless Bundler.feature_flag.cache_command_is_package?
+ method_option "all", :type => :boolean,
+ :banner => "Include all sources (including path and git)."
+ end
method_option "all-platforms", :type => :boolean, :banner => "Include gems for all platforms present in the lockfile, not only the current one"
method_option "cache-path", :type => :string, :banner =>
"Specify a different cache path than the default (vendor/cache)."
@@ -347,14 +409,14 @@ module Bundler
require "bundler/cli/package"
Package.new(options).run
end
- map %w(pack) => :package
+ map %w[pack] => :package
desc "exec [OPTIONS]", "Run the command in context of the bundle"
method_option :keep_file_descriptors, :type => :boolean, :default => false
long_desc <<-D
Exec runs a command, providing it access to the gems in the bundle. While using
bundle exec you can require and call the bundled gems as if they were installed
- into the system wide Rubygems repository.
+ into the system wide RubyGems repository.
D
map "e" => "exec"
def exec(*args)
@@ -362,7 +424,7 @@ module Bundler
Exec.new(options, args).run
end
- desc "config NAME [VALUE]", "retrieve or set a configuration value"
+ desc "config NAME [VALUE]", "Retrieve or set a configuration value"
long_desc <<-D
Retrieves or sets a configuration value. If only one parameter is provided, retrieve the value. If two parameters are provided, replace the
existing value with the newly provided one.
@@ -386,18 +448,28 @@ module Bundler
Open.new(options, name).run
end
- desc "console [GROUP]", "Opens an IRB session with the bundle pre-loaded"
- def console(group = nil)
- # TODO: Remove for 2.0
- require "bundler/cli/console"
- Console.new(options, group).run
+ if Bundler.feature_flag.console_command?
+ desc "console [GROUP]", "Opens an IRB session with the bundle pre-loaded"
+ def console(group = nil)
+ require "bundler/cli/console"
+ Console.new(options, group).run
+ end
end
desc "version", "Prints the bundler's version information"
def version
- Bundler.ui.info "Bundler version #{Bundler::VERSION}"
+ cli_help = current_command.name == "cli_help"
+ if cli_help || ARGV.include?("version")
+ build_info = " (#{BuildMetadata.built_at} commit #{BuildMetadata.git_commit_sha})"
+ end
+
+ if !cli_help && Bundler.feature_flag.print_only_version_number?
+ Bundler.ui.info "#{Bundler::VERSION}#{build_info}"
+ else
+ Bundler.ui.info "Bundler version #{Bundler::VERSION}#{build_info}"
+ end
end
- map %w(-v --version) => :version
+ map %w[-v --version] => :version
desc "licenses", "Prints the license of all gems in the bundle"
def licenses
@@ -413,7 +485,7 @@ module Bundler
end
end
- desc "viz [OPTIONS]", "Generates a visual dependency graph"
+ desc "viz [OPTIONS]", "Generates a visual dependency graph", :hide => true
long_desc <<-D
Viz generates a PNG file of the current Gemfile as a dependency graph.
Viz requires the ruby-graphviz gem (and its dependencies).
@@ -431,7 +503,7 @@ module Bundler
old_gem = instance_method(:gem)
- desc "gem GEM [OPTIONS]", "Creates a skeleton for creating a rubygem"
+ desc "gem NAME [OPTIONS]", "Creates a skeleton for creating a rubygem"
method_option :exe, :type => :boolean, :default => false, :aliases => ["--bin", "-b"], :desc => "Generate a binary executable for your library."
method_option :coc, :type => :boolean, :desc => "Generate a code of conduct file. Set a default with `bundle config gem.coc true`."
method_option :edit, :type => :string, :aliases => "-e", :required => false, :banner => "EDITOR",
@@ -470,7 +542,7 @@ module Bundler
File.expand_path(File.join(File.dirname(__FILE__), "templates"))
end
- desc "clean [OPTIONS]", "Cleans up unused gems in your bundler directory"
+ desc "clean [OPTIONS]", "Cleans up unused gems in your bundler directory", :hide => true
method_option "dry-run", :type => :boolean, :default => false, :banner =>
"Only print out changes, do not clean gems"
method_option "force", :type => :boolean, :default => false, :banner =>
@@ -488,13 +560,13 @@ module Bundler
Platform.new(options).run
end
- desc "inject GEM VERSION", "Add the named gem, with version requirements, to the resolved Gemfile"
+ desc "inject GEM VERSION", "Add the named gem, with version requirements, to the resolved Gemfile", :hide => true
method_option "source", :type => :string, :banner =>
"Install gem from the given source"
method_option "group", :type => :string, :banner =>
"Install gem into a bundler group"
def inject(name, version)
- SharedHelpers.major_deprecation "The `inject` command has been replaced by the `add` command"
+ SharedHelpers.major_deprecation 2, "The `inject` command has been replaced by the `add` command"
require "bundler/cli/inject"
Inject.new(options.dup, name, version).run
end
@@ -531,7 +603,7 @@ module Bundler
desc "env", "Print information about the environment Bundler is running under"
def env
- Env.new.write($stdout)
+ Env.write($stdout)
end
desc "doctor [OPTIONS]", "Checks the bundle for common problems"
@@ -555,15 +627,20 @@ module Bundler
Issue.new.run
end
- desc "pristine", "Restores installed gems to pristine condition from files located in the gem cache. Gem installed from a git repository will be issued `git checkout --force`."
- def pristine
+ desc "pristine [GEMS...]", "Restores installed gems to pristine condition"
+ long_desc <<-D
+ Restores installed gems to pristine condition from files located in the
+ gem cache. Gems installed from a git repository will be issued `git
+ checkout --force`.
+ D
+ def pristine(*gems)
require "bundler/cli/pristine"
- Pristine.new.run
+ Pristine.new(gems).run
end
if Bundler.feature_flag.plugins?
require "bundler/cli/plugin"
- desc "plugin SUBCOMMAND ...ARGS", "manage the bundler plugins"
+ desc "plugin", "Manage the bundler plugins"
subcommand "plugin", Plugin
end
@@ -571,14 +648,14 @@ module Bundler
# into the corresponding `bundle help #{command}` call
def self.reformatted_help_args(args)
bundler_commands = all_commands.keys
- help_flags = %w(--help -h)
- exec_commands = %w(e ex exe exec)
+ help_flags = %w[--help -h]
+ exec_commands = %w[e ex exe exec]
help_used = args.index {|a| help_flags.include? a }
exec_used = args.index {|a| exec_commands.include? a }
command = args.find {|a| bundler_commands.include? a }
if exec_used && help_used
if exec_used + help_used == 1
- %w(help exec)
+ %w[help exec]
else
args
end
@@ -613,16 +690,20 @@ module Bundler
end
end
+ def current_command
+ _, _, config = @_initializer
+ config[:current_command]
+ end
+
def print_command
return unless Bundler.ui.debug?
- _, _, config = @_initializer
- current_command = config[:current_command]
- command_name = current_command.name
+ cmd = current_command
+ command_name = cmd.name
return if PARSEABLE_COMMANDS.include?(command_name)
command = ["bundle", command_name] + args
options_to_print = options.dup
options_to_print.delete_if do |k, v|
- next unless o = current_command.options[k]
+ next unless o = cmd.options[k]
o.default == v
end
command << Thor::Options.to_switches(options_to_print.sort_by(&:first)).strip
@@ -633,8 +714,6 @@ module Bundler
def warn_on_outdated_bundler
return if Bundler.settings[:disable_version_check]
- _, _, config = @_initializer
- current_command = config[:current_command]
command_name = current_command.name
return if PARSEABLE_COMMANDS.include?(command_name)
@@ -649,8 +728,17 @@ module Bundler
current = Gem::Version.new(VERSION)
return if current >= latest
+ latest_installed = Bundler.rubygems.find_name("bundler").map(&:version).max
+
+ installation = "To install the latest version, run `gem install bundler#{" --pre" if latest.prerelease?}`"
+ if latest_installed && latest_installed > current
+ suggestion = "To update to the most recent installed version (#{latest_installed}), run `bundle update --bundler`"
+ suggestion = "#{installation}\n#{suggestion}" if latest_installed < latest
+ else
+ suggestion = installation
+ end
- Bundler.ui.warn "The latest bundler is #{latest}, but you are currently running #{current}.\nTo update, run `gem install bundler#{" --pre" if latest.prerelease?}`"
+ Bundler.ui.warn "The latest bundler is #{latest}, but you are currently running #{current}.\n#{suggestion}"
rescue
nil
end
diff --git a/lib/bundler/cli/add.rb b/lib/bundler/cli/add.rb
index e80c775433..1fcbd22f28 100644
--- a/lib/bundler/cli/add.rb
+++ b/lib/bundler/cli/add.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require "bundler/cli/common"
module Bundler
class CLI::Add
diff --git a/lib/bundler/cli/binstubs.rb b/lib/bundler/cli/binstubs.rb
index 95103b7dd8..449204d821 100644
--- a/lib/bundler/cli/binstubs.rb
+++ b/lib/bundler/cli/binstubs.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require "bundler/cli/common"
module Bundler
class CLI::Binstubs
@@ -11,8 +10,10 @@ module Bundler
def run
Bundler.definition.validate_runtime!
- Bundler.settings[:bin] = options["path"] if options["path"]
- Bundler.settings[:bin] = nil if options["path"] && options["path"].empty?
+ path_option = options["path"]
+ path_option = nil if path_option && path_option.empty?
+ Bundler.settings.set_command_option :bin, path_option if options["path"]
+ Bundler.settings.set_command_option_if_given :shebang, options["shebang"]
installer = Installer.new(Bundler.root, Bundler.definition)
if gems.empty?
@@ -28,10 +29,11 @@ module Bundler
)
end
- if spec.name == "bundler"
- Bundler.ui.warn "Sorry, Bundler can only be run via Rubygems."
- elsif options[:standalone]
- installer.generate_standalone_bundler_executable_stubs(spec)
+ if options[:standalone]
+ next Bundler.ui.warn("Sorry, Bundler can only be run via RubyGems.") if gem_name == "bundler"
+ Bundler.settings.temporary(:path => (Bundler.settings[:path] || Bundler.root)) do
+ installer.generate_standalone_bundler_executable_stubs(spec)
+ end
else
installer.generate_bundler_executable_stubs(spec, :force => options[:force], :binstubs_cmd => true)
end
diff --git a/lib/bundler/cli/cache.rb b/lib/bundler/cli/cache.rb
index 5ba105a31d..9d2ba87d34 100644
--- a/lib/bundler/cli/cache.rb
+++ b/lib/bundler/cli/cache.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler
class CLI::Cache
attr_reader :options
@@ -10,9 +11,9 @@ module Bundler
Bundler.definition.validate_runtime!
Bundler.definition.resolve_with_cache!
setup_cache_all
- Bundler.settings[:cache_all_platforms] = options["all-platforms"] if options.key?("all-platforms")
+ Bundler.settings.set_command_option_if_given :cache_all_platforms, options["all-platforms"]
Bundler.load.cache
- Bundler.settings[:no_prune] = true if options["no-prune"]
+ Bundler.settings.set_command_option_if_given :no_prune, options["no-prune"]
Bundler.load.lock
rescue GemNotFound => e
Bundler.ui.error(e.message)
@@ -23,9 +24,9 @@ module Bundler
private
def setup_cache_all
- Bundler.settings[:cache_all] = options[:all] if options.key?("all")
+ Bundler.settings.set_command_option_if_given :cache_all, options[:all]
- if Bundler.definition.has_local_dependencies? && !Bundler.settings[:cache_all]
+ if Bundler.definition.has_local_dependencies? && !Bundler.feature_flag.cache_all?
Bundler.ui.warn "Your Gemfile contains path and git dependencies. If you want " \
"to package them as well, please pass the --all flag. This will be the default " \
"on Bundler 2.0."
diff --git a/lib/bundler/cli/check.rb b/lib/bundler/cli/check.rb
index 057a7e5695..e572787dc4 100644
--- a/lib/bundler/cli/check.rb
+++ b/lib/bundler/cli/check.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler
class CLI::Check
attr_reader :options
@@ -8,10 +9,7 @@ module Bundler
end
def run
- if options[:path]
- Bundler.settings[:path] = File.expand_path(options[:path])
- Bundler.settings[:disable_shared_gems] = true
- end
+ Bundler.settings.set_command_option_if_given :path, options[:path]
begin
definition = Bundler.definition
@@ -28,7 +26,7 @@ module Bundler
not_installed.each {|s| Bundler.ui.error " * #{s.name} (#{s.version})" }
Bundler.ui.warn "Install missing gems with `bundle install`"
exit 1
- elsif !Bundler.default_lockfile.file? && Bundler.settings[:frozen]
+ elsif !Bundler.default_lockfile.file? && Bundler.frozen?
Bundler.ui.error "This bundle has been frozen, but there is no #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)} present"
exit 1
else
diff --git a/lib/bundler/cli/clean.rb b/lib/bundler/cli/clean.rb
index 5eba09c6bc..4a407fbae7 100644
--- a/lib/bundler/cli/clean.rb
+++ b/lib/bundler/cli/clean.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler
class CLI::Clean
attr_reader :options
@@ -15,12 +16,10 @@ module Bundler
protected
def require_path_or_force
- if !Bundler.settings[:path] && !options[:force]
- Bundler.ui.error "Cleaning all the gems on your system is dangerous! " \
- "If you're sure you want to remove every system gem not in this " \
- "bundle, run `bundle clean --force`."
- exit 1
- end
+ return unless Bundler.use_system_gems? && !options[:force]
+ raise InvalidOption, "Cleaning all the gems on your system is dangerous! " \
+ "If you're sure you want to remove every system gem not in this " \
+ "bundle, run `bundle clean --force`."
end
end
end
diff --git a/lib/bundler/cli/common.rb b/lib/bundler/cli/common.rb
index bacbb2edc5..9d40ee9dfd 100644
--- a/lib/bundler/cli/common.rb
+++ b/lib/bundler/cli/common.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler
module CLI::Common
def self.output_post_install_messages(messages)
@@ -14,12 +15,12 @@ module Bundler
end
def self.output_without_groups_message
- return unless Bundler.settings.without.any?
+ return if Bundler.settings[:without].empty?
Bundler.ui.confirm without_groups_message
end
def self.without_groups_message
- groups = Bundler.settings.without
+ groups = Bundler.settings[:without]
group_list = [groups[0...-1].join(", "), groups[-1..-1]].
reject {|s| s.to_s.empty? }.join(" and ")
group_str = (groups.size == 1) ? "group" : "groups"
@@ -89,5 +90,13 @@ module Bundler
def self.patch_level_options(options)
[:major, :minor, :patch].select {|v| options.keys.include?(v.to_s) }
end
+
+ def self.clean_after_install?
+ clean = Bundler.settings[:clean]
+ return clean unless clean.nil?
+ clean ||= Bundler.feature_flag.auto_clean_without_path? && Bundler.settings[:path].nil?
+ clean &&= !Bundler.use_system_gems?
+ clean
+ end
end
end
diff --git a/lib/bundler/cli/config.rb b/lib/bundler/cli/config.rb
index e8f13620ec..12f71ea8fe 100644
--- a/lib/bundler/cli/config.rb
+++ b/lib/bundler/cli/config.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler
class CLI::Config
attr_reader :name, :options, :scope, :thor
@@ -112,7 +113,7 @@ module Bundler
end
def valid_scope?(scope)
- %w(delete local global).include?(scope)
+ %w[delete local global].include?(scope)
end
end
end
diff --git a/lib/bundler/cli/console.rb b/lib/bundler/cli/console.rb
index 715abf2554..853eca8358 100644
--- a/lib/bundler/cli/console.rb
+++ b/lib/bundler/cli/console.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler
class CLI::Console
attr_reader :options, :group
@@ -8,7 +9,7 @@ module Bundler
end
def run
- Bundler::SharedHelpers.major_deprecation "bundle console will be replaced " \
+ Bundler::SharedHelpers.major_deprecation 2, "bundle console will be replaced " \
"by `bin/console` generated by `bundle gem <name>`"
group ? Bundler.require(:default, *(group.split.map!(&:to_sym))) : Bundler.require
diff --git a/lib/bundler/cli/doctor.rb b/lib/bundler/cli/doctor.rb
index ae27983240..7f28a5eb13 100644
--- a/lib/bundler/cli/doctor.rb
+++ b/lib/bundler/cli/doctor.rb
@@ -62,6 +62,7 @@ module Bundler
def run
Bundler.ui.level = "error" if options[:quiet]
+ Bundler.settings.validate!
check!
definition = Bundler.definition
diff --git a/lib/bundler/cli/exec.rb b/lib/bundler/cli/exec.rb
index 62f7bc26cb..2fdc614fbb 100644
--- a/lib/bundler/cli/exec.rb
+++ b/lib/bundler/cli/exec.rb
@@ -1,11 +1,12 @@
# frozen_string_literal: true
+
require "bundler/current_ruby"
module Bundler
class CLI::Exec
attr_reader :options, :args, :cmd
- RESERVED_SIGNALS = %w(SEGV BUS ILL FPE VTALRM KILL STOP).freeze
+ RESERVED_SIGNALS = %w[SEGV BUS ILL FPE VTALRM KILL STOP].freeze
def initialize(options, args)
@options = options
@@ -72,7 +73,7 @@ module Bundler
signals = Signal.list.keys - RESERVED_SIGNALS
signals.each {|s| trap(s, "DEFAULT") }
Kernel.load(file)
- rescue SystemExit
+ rescue SystemExit, SignalException
raise
rescue Exception => e # rubocop:disable Lint/RescueException
Bundler.ui = ui
diff --git a/lib/bundler/cli/gem.rb b/lib/bundler/cli/gem.rb
index fed904e9aa..885578e819 100644
--- a/lib/bundler/cli/gem.rb
+++ b/lib/bundler/cli/gem.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require "pathname"
module Bundler
@@ -71,10 +72,10 @@ module Bundler
"bin/setup.tt" => "bin/setup"
}
- executables = %w(
+ executables = %w[
bin/console
bin/setup
- )
+ ]
templates.merge!("gitignore.tt" => ".gitignore") if Bundler.git_present?
diff --git a/lib/bundler/cli/info.rb b/lib/bundler/cli/info.rb
index 4465fba9d4..958b525067 100644
--- a/lib/bundler/cli/info.rb
+++ b/lib/bundler/cli/info.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require "bundler/cli/common"
module Bundler
class CLI::Info
diff --git a/lib/bundler/cli/init.rb b/lib/bundler/cli/init.rb
index 8ffd1db41a..50e01f54fb 100644
--- a/lib/bundler/cli/init.rb
+++ b/lib/bundler/cli/init.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler
class CLI::Init
attr_reader :options
@@ -7,8 +8,8 @@ module Bundler
end
def run
- if File.exist?("Gemfile")
- Bundler.ui.error "Gemfile already exists at #{SharedHelpers.pwd}/Gemfile"
+ if File.exist?(gemfile)
+ Bundler.ui.error "#{gemfile} already exists at #{File.expand_path(gemfile)}"
exit 1
end
@@ -21,14 +22,24 @@ module Bundler
spec = Bundler.load_gemspec_uncached(gemspec)
- puts "Writing new Gemfile to #{SharedHelpers.pwd}/Gemfile"
- File.open("Gemfile", "wb") do |file|
+ File.open(gemfile, "wb") do |file|
file << "# Generated from #{gemspec}\n"
file << spec.to_gemfile
end
else
- puts "Writing new Gemfile to #{SharedHelpers.pwd}/Gemfile"
- FileUtils.cp(File.expand_path("../../templates/Gemfile", __FILE__), "Gemfile")
+ FileUtils.cp(File.expand_path("../../templates/#{gemfile}", __FILE__), gemfile)
+ end
+
+ puts "Writing new #{gemfile} to #{SharedHelpers.pwd}/#{gemfile}"
+ end
+
+ private
+
+ def gemfile
+ @gemfile ||= begin
+ Bundler.default_gemfile
+ rescue GemfileNotFound
+ Bundler.feature_flag.init_gems_rb? ? "gems.rb" : "Gemfile"
end
end
end
diff --git a/lib/bundler/cli/inject.rb b/lib/bundler/cli/inject.rb
index b17292643f..b00675d348 100644
--- a/lib/bundler/cli/inject.rb
+++ b/lib/bundler/cli/inject.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler
class CLI::Inject
attr_reader :options, :name, :version, :group, :source, :gems
diff --git a/lib/bundler/cli/install.rb b/lib/bundler/cli/install.rb
index ff6bedd9fd..f0b821ed84 100644
--- a/lib/bundler/cli/install.rb
+++ b/lib/bundler/cli/install.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require "bundler/cli/common"
module Bundler
class CLI::Install
@@ -13,17 +12,9 @@ module Bundler
warn_if_root
- [:with, :without].each do |option|
- if options[option]
- options[option] = options[option].join(":").tr(" ", ":").split(":")
- end
- end
-
- check_for_group_conflicts
-
normalize_groups
- ENV["RB_USER_INSTALL"] = "1" if Bundler::FREEBSD
+ Bundler::SharedHelpers.set_env "RB_USER_INSTALL", "1" if Bundler::FREEBSD
# Disable color in deployment mode
Bundler.ui.shell = Thor::Shell::Basic.new if options[:deployment]
@@ -32,22 +23,28 @@ module Bundler
check_trust_policy
- if options[:deployment] || options[:frozen]
+ if options[:deployment] || options[:frozen] || Bundler.frozen?
unless Bundler.default_lockfile.exist?
- flag = options[:deployment] ? "--deployment" : "--frozen"
- raise ProductionError, "The #{flag} flag requires a #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)}. Please make " \
+ flag = "--deployment flag" if options[:deployment]
+ flag ||= "--frozen flag" if options[:frozen]
+ flag ||= "deployment setting"
+ raise ProductionError, "The #{flag} requires a #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)}. Please make " \
"sure you have checked your #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)} into version control " \
"before deploying."
end
options[:local] = true if Bundler.app_cache.exist?
- Bundler.settings[:frozen] = "1"
+ if Bundler.feature_flag.deployment_means_frozen?
+ Bundler.settings.set_command_option :deployment, true
+ else
+ Bundler.settings.set_command_option :frozen, true
+ end
end
# When install is called with --no-deployment, disable deployment mode
if options[:deployment] == false
- Bundler.settings.delete(:frozen)
+ Bundler.settings.set_command_option :frozen, nil
options[:system] = true
end
@@ -56,7 +53,7 @@ module Bundler
Bundler::Fetcher.disable_endpoint = options["full-index"]
if options["binstubs"]
- Bundler::SharedHelpers.major_deprecation \
+ Bundler::SharedHelpers.major_deprecation 2,
"The --binstubs option will be removed in favor of `bundle binstubs`"
end
@@ -66,24 +63,24 @@ module Bundler
definition.validate_runtime!
installer = Installer.install(Bundler.root, definition, options)
- Bundler.load.cache if Bundler.app_cache.exist? && !options["no-cache"] && !Bundler.settings[:frozen]
+ Bundler.load.cache if Bundler.app_cache.exist? && !options["no-cache"] && !Bundler.frozen?
Bundler.ui.confirm "Bundle complete! #{dependencies_count_for(definition)}, #{gems_installed_for(definition)}."
Bundler::CLI::Common.output_without_groups_message
- if Bundler.settings[:path]
- absolute_path = File.expand_path(Bundler.settings[:path])
- relative_path = absolute_path.sub(File.expand_path(".") + File::SEPARATOR, "." + File::SEPARATOR)
- Bundler.ui.confirm "Bundled gems are installed into #{relative_path}."
- else
+ if Bundler.use_system_gems?
Bundler.ui.confirm "Use `bundle info [gemname]` to see where a bundled gem is installed."
+ else
+ absolute_path = File.expand_path(Bundler.configured_bundle_path.base_path)
+ relative_path = absolute_path.sub(File.expand_path(".") + File::SEPARATOR, "." + File::SEPARATOR)
+ Bundler.ui.confirm "Bundled gems are installed into `#{relative_path}`"
end
Bundler::CLI::Common.output_post_install_messages installer.post_install_messages
warn_ambiguous_gems
- if Bundler.settings[:clean] && Bundler.settings[:path]
+ if CLI::Common.clean_after_install?
require "bundler/cli/clean"
Bundler::CLI::Clean.new(options).run
end
@@ -124,15 +121,11 @@ module Bundler
"#{count} #{count == 1 ? "gem" : "gems"} now installed"
end
- def check_for_group_conflicts
- if options[:without] && options[:with]
- conflicting_groups = options[:without] & options[:with]
- unless conflicting_groups.empty?
- Bundler.ui.error "You can't list a group in both, --with and --without." \
- " The offending groups are: #{conflicting_groups.join(", ")}."
- exit 1
- end
- end
+ def check_for_group_conflicts_in_cli_options
+ conflicting_groups = Array(options[:without]) & Array(options[:with])
+ return if conflicting_groups.empty?
+ raise InvalidOption, "You can't list a group in both with and without." \
+ " The offending groups are: #{conflicting_groups.join(", ")}."
end
def check_for_options_conflicts
@@ -145,28 +138,29 @@ module Bundler
end
def check_trust_policy
- if options["trust-policy"]
- unless Bundler.rubygems.security_policies.keys.include?(options["trust-policy"])
- Bundler.ui.error "Rubygems doesn't know about trust policy '#{options["trust-policy"]}'. " \
- "The known policies are: #{Bundler.rubygems.security_policies.keys.join(", ")}."
- exit 1
- end
- Bundler.settings["trust-policy"] = options["trust-policy"]
- else
- Bundler.settings["trust-policy"] = nil if Bundler.settings["trust-policy"]
+ trust_policy = options["trust-policy"]
+ unless Bundler.rubygems.security_policies.keys.unshift(nil).include?(trust_policy)
+ raise InvalidOption, "RubyGems doesn't know about trust policy '#{trust_policy}'. " \
+ "The known policies are: #{Bundler.rubygems.security_policies.keys.join(", ")}."
end
+ Bundler.settings.set_command_option_if_given :"trust-policy", trust_policy
end
def normalize_groups
- Bundler.settings.with = [] if options[:with] && options[:with].empty?
- Bundler.settings.without = [] if options[:without] && options[:without].empty?
+ options[:with] &&= options[:with].join(":").tr(" ", ":").split(":")
+ options[:without] &&= options[:without].join(":").tr(" ", ":").split(":")
+
+ check_for_group_conflicts_in_cli_options
- with = options.fetch("with", [])
- with |= Bundler.settings.with.map(&:to_s)
+ Bundler.settings.set_command_option :with, nil if options[:with] == []
+ Bundler.settings.set_command_option :without, nil if options[:without] == []
+
+ with = options.fetch(:with, [])
+ with |= Bundler.settings[:with].map(&:to_s)
with -= options[:without] if options[:without]
- without = options.fetch("without", [])
- without |= Bundler.settings.without.map(&:to_s)
+ without = options.fetch(:without, [])
+ without |= Bundler.settings[:without].map(&:to_s)
without -= options[:with] if options[:with]
options[:with] = with
@@ -174,28 +168,34 @@ module Bundler
end
def normalize_settings
- Bundler.settings[:path] = nil if options[:system]
- Bundler.settings[:path] = "vendor/bundle" if options[:deployment]
- Bundler.settings[:path] = options["path"] if options["path"]
- Bundler.settings[:path] ||= "bundle" if options["standalone"]
+ Bundler.settings.set_command_option :path, nil if options[:system]
+ Bundler.settings.set_command_option :path, "vendor/bundle" if options[:deployment]
+ Bundler.settings.set_command_option_if_given :path, options["path"]
+ Bundler.settings.set_command_option :path, "bundle" if options["standalone"] && Bundler.settings[:path].nil?
- Bundler.settings[:bin] = options["binstubs"] if options["binstubs"]
- Bundler.settings[:bin] = nil if options["binstubs"] && options["binstubs"].empty?
+ bin_option = options["binstubs"]
+ bin_option = nil if bin_option && bin_option.empty?
+ Bundler.settings.set_command_option :bin, bin_option if options["binstubs"]
- Bundler.settings[:shebang] = options["shebang"] if options["shebang"]
+ Bundler.settings.set_command_option_if_given :shebang, options["shebang"]
- Bundler.settings[:jobs] = options["jobs"] if options["jobs"]
+ Bundler.settings.set_command_option_if_given :jobs, options["jobs"]
- Bundler.settings[:no_prune] = true if options["no-prune"]
+ Bundler.settings.set_command_option_if_given :no_prune, options["no-prune"]
- Bundler.settings[:no_install] = true if options["no-install"]
+ Bundler.settings.set_command_option_if_given :no_install, options["no-install"]
- Bundler.settings[:clean] = options["clean"] if options["clean"]
+ Bundler.settings.set_command_option_if_given :clean, options["clean"]
- Bundler.settings.without = options[:without]
- Bundler.settings.with = options[:with]
+ unless Bundler.settings[:without] == options[:without] && Bundler.settings[:with] == options[:with]
+ # need to nil them out first to get around validation for backwards compatibility
+ Bundler.settings.set_command_option :without, nil
+ Bundler.settings.set_command_option :with, nil
+ Bundler.settings.set_command_option :without, options[:without] - options[:with]
+ Bundler.settings.set_command_option :with, options[:with]
+ end
- Bundler.settings[:disable_shared_gems] = Bundler.settings[:path] ? true : nil
+ options[:force] = options[:redownload]
end
def warn_ambiguous_gems
diff --git a/lib/bundler/cli/issue.rb b/lib/bundler/cli/issue.rb
index ace0f985a9..91f827ea99 100644
--- a/lib/bundler/cli/issue.rb
+++ b/lib/bundler/cli/issue.rb
@@ -26,7 +26,7 @@ module Bundler
EOS
- Bundler.ui.info Bundler::Env.new.report
+ Bundler.ui.info Bundler::Env.report
Bundler.ui.info "\n## Bundle Doctor"
doctor
diff --git a/lib/bundler/cli/list.rb b/lib/bundler/cli/list.rb
new file mode 100644
index 0000000000..b5e7c1e650
--- /dev/null
+++ b/lib/bundler/cli/list.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Bundler
+ class CLI::List
+ def initialize(options)
+ @options = options
+ end
+
+ def run
+ specs = Bundler.load.specs.reject {|s| s.name == "bundler" }.sort_by(&:name)
+ return specs.each {|s| Bundler.ui.info s.name } if @options["name-only"]
+
+ return Bundler.ui.info "No gems in the Gemfile" if specs.empty?
+ Bundler.ui.info "Gems included by the bundle:"
+ specs.each do |s|
+ Bundler.ui.info " * #{s.name} (#{s.version}#{s.git_version})"
+ end
+
+ Bundler.ui.info "Use `bundle info` to print more detailed information about a gem"
+ end
+ end
+end
diff --git a/lib/bundler/cli/lock.rb b/lib/bundler/cli/lock.rb
index 223db9419f..7dd078b1ef 100644
--- a/lib/bundler/cli/lock.rb
+++ b/lib/bundler/cli/lock.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require "bundler/cli/common"
module Bundler
class CLI::Lock
diff --git a/lib/bundler/cli/open.rb b/lib/bundler/cli/open.rb
index 9a21f6811c..552fe6f128 100644
--- a/lib/bundler/cli/open.rb
+++ b/lib/bundler/cli/open.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
-require "bundler/cli/common"
+
require "shellwords"
module Bundler
@@ -17,7 +17,7 @@ module Bundler
path = spec.full_gem_path
Dir.chdir(path) do
command = Shellwords.split(editor) + [path]
- Bundler.with_clean_env do
+ Bundler.with_original_env do
system(*command)
end || Bundler.ui.info("Could not run '#{command.join(" ")}'")
end
diff --git a/lib/bundler/cli/outdated.rb b/lib/bundler/cli/outdated.rb
index 863d0dd388..5125cc710b 100644
--- a/lib/bundler/cli/outdated.rb
+++ b/lib/bundler/cli/outdated.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require "bundler/cli/common"
module Bundler
class CLI::Outdated
@@ -46,7 +45,7 @@ module Bundler
Bundler::CLI::Common.patch_level_options(options).any?
filter_options_patch = options.keys &
- %w(filter-major filter-minor filter-patch)
+ %w[filter-major filter-minor filter-patch]
definition_resolution = proc do
options[:local] ? definition.resolve_with_cache! : definition.resolve_remotely!
@@ -214,13 +213,19 @@ module Bundler
end
def check_for_deployment_mode
- if Bundler.settings[:frozen]
- raise ProductionError, "You are trying to check outdated gems in " \
- "deployment mode. Run `bundle outdated` elsewhere.\n" \
- "\nIf this is a development machine, remove the " \
- "#{Bundler.default_gemfile} freeze" \
- "\nby running `bundle install --no-deployment`."
+ return unless Bundler.frozen?
+ suggested_command = if Bundler.settings.locations("frozen")[:global]
+ "bundle config --delete frozen"
+ elsif Bundler.settings.locations("deployment").keys.&([:global, :local]).any?
+ "bundle config --delete deployment"
+ else
+ "bundle install --no-deployment"
end
+ raise ProductionError, "You are trying to check outdated gems in " \
+ "deployment mode. Run `bundle outdated` elsewhere.\n" \
+ "\nIf this is a development machine, remove the " \
+ "#{Bundler.default_gemfile} freeze" \
+ "\nby running `#{suggested_command}`."
end
def update_present_via_semver_portions(current_spec, active_spec, options)
diff --git a/lib/bundler/cli/package.rb b/lib/bundler/cli/package.rb
index cf65e8a68c..2dcd0e1e29 100644
--- a/lib/bundler/cli/package.rb
+++ b/lib/bundler/cli/package.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler
class CLI::Package
attr_reader :options
@@ -9,15 +10,15 @@ module Bundler
def run
Bundler.ui.level = "error" if options[:quiet]
- Bundler.settings[:path] = File.expand_path(options[:path]) if options[:path]
- Bundler.settings[:cache_all_platforms] = options["all-platforms"] if options.key?("all-platforms")
- Bundler.settings[:cache_path] = options["cache-path"] if options.key?("cache-path")
+ Bundler.settings.set_command_option_if_given :path, options[:path]
+ Bundler.settings.set_command_option_if_given :cache_all_platforms, options["all-platforms"]
+ Bundler.settings.set_command_option_if_given :cache_path, options["cache-path"]
setup_cache_all
install
# TODO: move cache contents here now that all bundles are locked
- custom_path = Pathname.new(options[:path]) if options[:path]
+ custom_path = Bundler.settings[:path] if options[:path]
Bundler.load.cache(custom_path)
end
@@ -34,9 +35,11 @@ module Bundler
end
def setup_cache_all
- Bundler.settings[:cache_all] = options[:all] if options.key?("all")
+ all = options.fetch(:all, Bundler.feature_flag.cache_command_is_package? || nil)
+
+ Bundler.settings.set_command_option_if_given :cache_all, all
- if Bundler.definition.has_local_dependencies? && !Bundler.settings[:cache_all]
+ if Bundler.definition.has_local_dependencies? && !Bundler.feature_flag.cache_all?
Bundler.ui.warn "Your Gemfile contains path and git dependencies. If you want " \
"to package them as well, please pass the --all flag. This will be the default " \
"on Bundler 2.0."
diff --git a/lib/bundler/cli/platform.rb b/lib/bundler/cli/platform.rb
index 9fdab0a53c..e97cad49a4 100644
--- a/lib/bundler/cli/platform.rb
+++ b/lib/bundler/cli/platform.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler
class CLI::Platform
attr_reader :options
diff --git a/lib/bundler/cli/plugin.rb b/lib/bundler/cli/plugin.rb
index 277822dafc..5488a9f28d 100644
--- a/lib/bundler/cli/plugin.rb
+++ b/lib/bundler/cli/plugin.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require "bundler/vendored_thor"
module Bundler
class CLI::Plugin < Thor
diff --git a/lib/bundler/cli/pristine.rb b/lib/bundler/cli/pristine.rb
index 10d03b4b41..9b9cdaa9b3 100644
--- a/lib/bundler/cli/pristine.rb
+++ b/lib/bundler/cli/pristine.rb
@@ -1,15 +1,20 @@
# frozen_string_literal: true
-require "bundler/cli/common"
module Bundler
class CLI::Pristine
+ def initialize(gems)
+ @gems = gems
+ end
+
def run
+ CLI::Common.ensure_all_gems_in_lockfile!(@gems)
definition = Bundler.definition
definition.validate_runtime!
installer = Bundler::Installer.new(Bundler.root, definition)
Bundler.load.specs.each do |spec|
next if spec.name == "bundler" # Source::Rubygems doesn't install bundler
+ next if !@gems.empty? && !@gems.include?(spec.name)
gem_name = "#{spec.name} (#{spec.version}#{spec.git_version})"
gem_name += " (#{spec.platform})" if !spec.platform.nil? && spec.platform != Gem::Platform::RUBY
@@ -21,13 +26,15 @@ module Bundler
Bundler.ui.error("Failed to pristine #{gem_name}. Cached gem #{cached_gem} does not exist.")
next
end
+
+ FileUtils.rm_rf spec.full_gem_path
when Source::Git
source.remote!
+ FileUtils.rm_rf spec.full_gem_path
else
Bundler.ui.warn("Cannot pristine #{gem_name}. Gem is sourced from local path.")
next
end
- FileUtils.rm_rf spec.full_gem_path
Bundler::GemInstaller.new(spec, installer, false, 0, true).install_from_spec
end
diff --git a/lib/bundler/cli/show.rb b/lib/bundler/cli/show.rb
index 47d4470aec..61756801b2 100644
--- a/lib/bundler/cli/show.rb
+++ b/lib/bundler/cli/show.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require "bundler/cli/common"
module Bundler
class CLI::Show
diff --git a/lib/bundler/cli/update.rb b/lib/bundler/cli/update.rb
index df7524f004..5de11e84e4 100644
--- a/lib/bundler/cli/update.rb
+++ b/lib/bundler/cli/update.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require "bundler/cli/common"
module Bundler
class CLI::Update
@@ -17,7 +16,18 @@ module Bundler
sources = Array(options[:source])
groups = Array(options[:group]).map(&:to_sym)
- if gems.empty? && sources.empty? && groups.empty? && !options[:ruby] && !options[:bundler]
+ full_update = gems.empty? && sources.empty? && groups.empty? && !options[:ruby] && !options[:bundler]
+
+ if full_update && !options[:all]
+ if Bundler.feature_flag.update_requires_all_flag?
+ raise InvalidOption, "To update everything, pass the `--all` flag."
+ end
+ SharedHelpers.major_deprecation 2, "Pass --all to `bundle update` to update everything"
+ elsif !full_update && options[:all]
+ raise InvalidOption, "Cannot specify --all along with specific options."
+ end
+
+ if full_update
# We're doing a full update
Bundler.definition(true)
else
@@ -33,7 +43,8 @@ module Bundler
end
Bundler.definition(:gems => gems, :sources => sources, :ruby => options[:ruby],
- :lock_shared_dependencies => options[:conservative])
+ :lock_shared_dependencies => options[:conservative],
+ :bundler => options[:bundler])
end
Bundler::CLI::Common.configure_gem_version_promoter(Bundler.definition, options)
@@ -44,17 +55,32 @@ module Bundler
opts["update"] = true
opts["local"] = options[:local]
- Bundler.settings[:jobs] = opts["jobs"] if opts["jobs"]
+ Bundler.settings.set_command_option_if_given :jobs, opts["jobs"]
Bundler.definition.validate_runtime!
installer = Installer.install Bundler.root, Bundler.definition, opts
Bundler.load.cache if Bundler.app_cache.exist?
- if Bundler.settings[:clean] && Bundler.settings[:path]
+ if CLI::Common.clean_after_install?
require "bundler/cli/clean"
Bundler::CLI::Clean.new(options).run
end
+ if locked_gems = Bundler.definition.locked_gems
+ gems.each do |name|
+ locked_version = locked_gems.specs.find {|s| s.name == name }.version
+ new_version = Bundler.definition.specs[name].first
+ new_version &&= new_version.version
+ if !new_version
+ Bundler.ui.warn "Bundler attempted to update #{name} but it was removed from the bundle"
+ elsif new_version < locked_version
+ Bundler.ui.warn "Bundler attempted to update #{name} but its version regressed from #{locked_version} to #{new_version}"
+ elsif new_version == locked_version
+ Bundler.ui.warn "Bundler attempted to update #{name} but its version stayed the same"
+ end
+ end
+ end
+
Bundler.ui.confirm "Bundle updated!"
Bundler::CLI::Common.output_without_groups_message
Bundler::CLI::Common.output_post_install_messages installer.post_install_messages
diff --git a/lib/bundler/cli/viz.rb b/lib/bundler/cli/viz.rb
index 767fe8f3de..644f9b25cf 100644
--- a/lib/bundler/cli/viz.rb
+++ b/lib/bundler/cli/viz.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler
class CLI::Viz
attr_reader :options, :gem_name
diff --git a/lib/bundler/compact_index_client.rb b/lib/bundler/compact_index_client.rb
index 3ed05ca484..6c241ca07a 100644
--- a/lib/bundler/compact_index_client.rb
+++ b/lib/bundler/compact_index_client.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require "pathname"
require "set"
diff --git a/lib/bundler/compact_index_client/cache.rb b/lib/bundler/compact_index_client/cache.rb
index e44f05dc7e..f6105d3bb3 100644
--- a/lib/bundler/compact_index_client/cache.rb
+++ b/lib/bundler/compact_index_client/cache.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require "digest/md5"
module Bundler
class CompactIndexClient
@@ -68,7 +67,7 @@ module Bundler
def info_path(name)
name = name.to_s
if name =~ /[^a-z0-9_-]/
- name += "-#{Digest::MD5.hexdigest(name).downcase}"
+ name += "-#{SharedHelpers.digest(:MD5).hexdigest(name).downcase}"
info_roots.last.join(name)
else
info_roots.first.join(name)
diff --git a/lib/bundler/compact_index_client/updater.rb b/lib/bundler/compact_index_client/updater.rb
index dc26095040..3a4e4441ca 100644
--- a/lib/bundler/compact_index_client/updater.rb
+++ b/lib/bundler/compact_index_client/updater.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require "fileutils"
+
+require "bundler/vendored_fileutils"
require "stringio"
-require "tmpdir"
require "zlib"
module Bundler
@@ -22,6 +22,7 @@ module Bundler
def initialize(fetcher)
@fetcher = fetcher
+ require "tmpdir"
end
def update(local_path, remote_path, retrying = nil)
@@ -98,7 +99,7 @@ module Bundler
# because we need to preserve \n line endings on windows when calculating
# the checksum
SharedHelpers.filesystem_access(path, :read) do
- Digest::MD5.hexdigest(IO.read(path))
+ SharedHelpers.digest(:MD5).hexdigest(IO.read(path))
end
end
end
diff --git a/lib/bundler/compatibility_guard.rb b/lib/bundler/compatibility_guard.rb
new file mode 100644
index 0000000000..750a1db04f
--- /dev/null
+++ b/lib/bundler/compatibility_guard.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: false
+
+require "rubygems"
+require "bundler/version"
+
+if Bundler::VERSION.split(".").first.to_i >= 2
+ if Gem::Version.new(Object::RUBY_VERSION.dup) < Gem::Version.new("2.3")
+ abort "Bundler 2 requires Ruby 2.3 or later. Either install bundler 1 or update to a supported Ruby version."
+ end
+
+ if Gem::Version.new(Gem::VERSION.dup) < Gem::Version.new("2.5")
+ abort "Bundler 2 requires RubyGems 2.5 or later. Either install bundler 1 or update to a supported RubyGems version."
+ end
+end
diff --git a/lib/bundler/constants.rb b/lib/bundler/constants.rb
index 5b1c0a8cb1..2e4ebb37ee 100644
--- a/lib/bundler/constants.rb
+++ b/lib/bundler/constants.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler
WINDOWS = RbConfig::CONFIG["host_os"] =~ /(msdos|mswin|djgpp|mingw)/
FREEBSD = RbConfig::CONFIG["host_os"] =~ /bsd/
diff --git a/lib/bundler/current_ruby.rb b/lib/bundler/current_ruby.rb
index cca40100ad..31532d108d 100644
--- a/lib/bundler/current_ruby.rb
+++ b/lib/bundler/current_ruby.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler
# Returns current version of Ruby
#
@@ -8,7 +9,7 @@ module Bundler
end
class CurrentRuby
- KNOWN_MINOR_VERSIONS = %w(
+ KNOWN_MINOR_VERSIONS = %w[
1.8
1.9
2.0
@@ -17,11 +18,11 @@ module Bundler
2.3
2.4
2.5
- ).freeze
+ ].freeze
KNOWN_MAJOR_VERSIONS = KNOWN_MINOR_VERSIONS.map {|v| v.split(".", 2).first }.uniq.freeze
- KNOWN_PLATFORMS = %w(
+ KNOWN_PLATFORMS = %w[
jruby
maglev
mingw
@@ -31,7 +32,7 @@ module Bundler
rbx
ruby
x64_mingw
- ).freeze
+ ].freeze
def ruby?
!mswin? && (!defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby" || RUBY_ENGINE == "rbx" || RUBY_ENGINE == "maglev")
diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb
index 3e5b1bc447..f93ed76226 100644
--- a/lib/bundler/definition.rb
+++ b/lib/bundler/definition.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
+
require "bundler/lockfile_parser"
-require "digest/sha1"
require "set"
module Bundler
@@ -14,7 +14,9 @@ module Bundler
:locked_gems,
:platforms,
:requires,
- :ruby_version
+ :ruby_version,
+ :lockfile,
+ :gemfiles
)
# Given a gemfile and lockfile creates a Bundler definition
@@ -51,8 +53,16 @@ module Bundler
# to be updated or true if all gems should be updated
# @param ruby_version [Bundler::RubyVersion, nil] Requested Ruby Version
# @param optional_groups [Array(String)] A list of optional groups
- def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil, optional_groups = [])
- @unlocking = unlock == true || !unlock.empty?
+ def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil, optional_groups = [], gemfiles = [])
+ if [true, false].include?(unlock)
+ @unlocking_bundler = false
+ @unlocking = unlock
+ else
+ unlock = unlock.dup
+ @unlocking_bundler = unlock.delete(:bundler)
+ unlock.delete_if {|_k, v| Array(v).empty? }
+ @unlocking = !unlock.empty?
+ end
@dependencies = dependencies
@sources = sources
@@ -61,6 +71,7 @@ module Bundler
@remote = false
@specs = nil
@ruby_version = ruby_version
+ @gemfiles = gemfiles
@lockfile = lockfile
@lockfile_contents = String.new
@@ -102,7 +113,7 @@ module Bundler
end
@unlocking ||= @unlock[:ruby] ||= (!@locked_ruby_version ^ !@ruby_version)
- add_current_platform unless Bundler.settings[:frozen]
+ add_current_platform unless Bundler.frozen?
converge_path_sources_to_gemspec_sources
@path_changes = converge_paths
@@ -167,9 +178,8 @@ module Bundler
"to a different version of #{locked_gem} that hasn't been removed in order to install."
end
unless specs["bundler"].any?
- local = Bundler.settings[:frozen] ? rubygems_index : index
- bundler = local.search(Gem::Dependency.new("bundler", VERSION)).last
- specs["bundler"] = bundler if bundler
+ bundler = sources.metadata_source.specs.search(Gem::Dependency.new("bundler", VERSION)).last
+ specs["bundler"] = bundler
end
specs
@@ -194,10 +204,19 @@ module Bundler
missing
end
- def missing_dependencies
- missing = []
- resolve.materialize(current_dependencies, missing)
- missing
+ def missing_specs?
+ missing = missing_specs
+ return false if missing.empty?
+ Bundler.ui.debug "The definition is missing #{missing.map(&:full_name)}"
+ true
+ rescue BundlerError => e
+ @index = nil
+ @resolve = nil
+ @specs = nil
+ @gem_version_promoter = create_gem_version_promoter
+
+ Bundler.ui.debug "The definition is missing dependencies, failed to resolve & materialize locally (#{e})"
+ true
end
def requested_specs
@@ -226,7 +245,10 @@ module Bundler
def resolve
@resolve ||= begin
last_resolve = converge_locked_specs
- if Bundler.settings[:frozen] || (!unlocking? && nothing_changed?)
+ if Bundler.frozen?
+ Bundler.ui.debug "Frozen, using resolution from the lockfile"
+ last_resolve
+ elsif !unlocking? && nothing_changed?
Bundler.ui.debug("Found no changes, using resolution from the lockfile")
last_resolve
else
@@ -242,25 +264,44 @@ module Bundler
dependency_names = @dependencies.map(&:name)
sources.all_sources.each do |source|
- source.dependency_names = dependency_names.dup
+ source.dependency_names = dependency_names - pinned_spec_names(source)
idx.add_source source.specs
- dependency_names -= pinned_spec_names(source.specs)
dependency_names.concat(source.unmet_deps).uniq!
end
- idx << Gem::Specification.new("ruby\0", RubyVersion.system.to_gem_version_with_patchlevel)
- idx << Gem::Specification.new("rubygems\0", Gem::VERSION)
- end
- end
- # used when frozen is enabled so we can find the bundler
- # spec, even if (say) a git gem is not checked out.
- def rubygems_index
- @rubygems_index ||= Index.build do |idx|
- sources.rubygems_sources.each do |rubygems|
- idx.add_source rubygems.specs
+ double_check_for_index(idx, dependency_names)
+ end
+ end
+
+ # Suppose the gem Foo depends on the gem Bar. Foo exists in Source A. Bar has some versions that exist in both
+ # sources A and B. At this point, the API request will have found all the versions of Bar in source A,
+ # but will not have found any versions of Bar from source B, which is a problem if the requested version
+ # of Foo specifically depends on a version of Bar that is only found in source B. This ensures that for
+ # each spec we found, we add all possible versions from all sources to the index.
+ def double_check_for_index(idx, dependency_names)
+ pinned_names = pinned_spec_names
+ loop do
+ idxcount = idx.size
+
+ names = :names # do this so we only have to traverse to get dependency_names from the index once
+ unmet_dependency_names = lambda do
+ return names unless names == :names
+ new_names = sources.all_sources.map(&:dependency_names_to_double_check)
+ return names = nil if new_names.compact!
+ names = new_names.flatten(1).concat(dependency_names)
+ names.uniq!
+ names -= pinned_names
+ names
end
+
+ sources.all_sources.each do |source|
+ source.double_check_for(unmet_dependency_names, :override_dupes)
+ end
+
+ break if idxcount == idx.size
end
end
+ private :double_check_for_index
def has_rubygems_remotes?
sources.rubygems_sources.any? {|s| s.remotes.any? }
@@ -295,10 +336,10 @@ module Bundler
end
end
- preserve_unknown_sections ||= !updating_major && (Bundler.settings[:frozen] || !unlocking?)
+ preserve_unknown_sections ||= !updating_major && (Bundler.frozen? || !(unlocking? || @unlocking_bundler))
return if lockfiles_equal?(@lockfile_contents, contents, preserve_unknown_sections)
- if Bundler.settings[:frozen]
+ if Bundler.frozen?
Bundler.ui.error "Cannot write a changed lockfile while frozen."
return
end
@@ -338,51 +379,8 @@ module Bundler
end
def to_lock
- out = String.new
-
- sources.lock_sources.each do |source|
- # Add the source header
- out << source.to_lock
- # Find all specs for this source
- resolve.
- select {|s| source.can_lock?(s) }.
- # This needs to be sorted by full name so that
- # gems with the same name, but different platform
- # are ordered consistently
- sort_by(&:full_name).
- each do |spec|
- next if spec.name == "bundler"
- out << spec.to_lock
- end
- out << "\n"
- end
-
- out << "PLATFORMS\n"
-
- platforms.map(&:to_s).sort.each do |p|
- out << " #{p}\n"
- end
-
- out << "\n"
- out << "DEPENDENCIES\n"
-
- handled = []
- dependencies.sort_by(&:to_s).each do |dep|
- next if handled.include?(dep.name)
- out << dep.to_lock
- handled << dep.name
- end
-
- if locked_ruby_version
- out << "\nRUBY VERSION\n"
- out << " #{locked_ruby_version}\n"
- end
-
- # Record the version of Bundler that was used to create the lockfile
- out << "\nBUNDLED WITH\n"
- out << " #{locked_bundler_version}\n"
-
- out
+ require "bundler/lockfile_generator"
+ LockfileGenerator.generate(self)
end
def ensure_equivalent_gemfile_and_lockfile(explicit_flag = false)
@@ -392,8 +390,13 @@ module Bundler
"updated #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)} to version control."
unless explicit_flag
-
- suggested_command = Bundler.settings.locations("frozen")[:global] == "1" ? "bundle config --delete frozen" : "bundle install --no-deployment"
+ suggested_command = if Bundler.settings.locations("frozen")[:global]
+ "bundle config --delete frozen"
+ elsif Bundler.settings.locations("deployment").keys.&([:global, :local]).any?
+ "bundle config --delete deployment"
+ else
+ "bundle install --no-deployment"
+ end
msg << "\n\nIf this is a development machine, remove the #{Bundler.default_gemfile} " \
"freeze \nby running `#{suggested_command}`."
end
@@ -417,8 +420,8 @@ module Bundler
# Check if it is possible that the source is only changed thing
if (new_deps.empty? && deleted_deps.empty?) && (!new_sources.empty? && !deleted_sources.empty?)
- new_sources.reject! {|source| source.is_a_path? && source.path.exist? }
- deleted_sources.reject! {|source| source.is_a_path? && source.path.exist? }
+ new_sources.reject! {|source| (source.path? && source.path.exist?) || equivalent_rubygems_remotes?(source) }
+ deleted_sources.reject! {|source| (source.path? && source.path.exist?) || equivalent_rubygems_remotes?(source) }
end
if @locked_sources != gemfile_sources
@@ -511,7 +514,7 @@ module Bundler
def add_current_platform
current_platform = Bundler.local_platform
- add_platform(current_platform) if Bundler.settings[:specific_platform]
+ add_platform(current_platform) if Bundler.feature_flag.specific_platform?
add_platform(generic(current_platform))
end
@@ -558,10 +561,7 @@ module Bundler
end
def pretty_dep(dep, source = false)
- msg = String.new(dep.name)
- msg << " (#{dep.requirement})" unless dep.requirement == Gem::Requirement.default
- msg << " from the `#{dep.source}` source" if source && dep.source
- msg
+ SharedHelpers.pretty_dependency(dep, source)
end
# Check if the specs of the given source changed
@@ -585,6 +585,9 @@ module Bundler
# order here matters, since Index#== is checking source.specs.include?(locked_index)
locked_index != source.specs
+ rescue PathError, GitError => e
+ Bundler.ui.debug "Assuming that #{source} has not changed since fetching its specs errored (#{e})"
+ false
end
# Get all locals and override their matching sources.
@@ -632,22 +635,32 @@ module Bundler
end
end
- def converge_sources
+ def converge_rubygems_sources
+ return false if Bundler.feature_flag.lockfile_uses_separate_rubygems_sources?
+
changes = false
- # Get the Rubygems sources from the Gemfile.lock
+ # Get the RubyGems sources from the Gemfile.lock
locked_gem_sources = @locked_sources.select {|s| s.is_a?(Source::Rubygems) }
- # Get the Rubygems remotes from the Gemfile
+ # Get the RubyGems remotes from the Gemfile
actual_remotes = sources.rubygems_remotes
- # If there is a Rubygems source in both
+ # If there is a RubyGems source in both
if !locked_gem_sources.empty? && !actual_remotes.empty?
locked_gem_sources.each do |locked_gem|
# Merge the remotes from the Gemfile into the Gemfile.lock
- changes |= locked_gem.replace_remotes(actual_remotes)
+ changes |= locked_gem.replace_remotes(actual_remotes, Bundler.settings[:allow_deployment_source_credential_changes])
end
end
+ changes
+ end
+
+ def converge_sources
+ changes = false
+
+ changes |= converge_rubygems_sources
+
# Replace the sources from the Gemfile with the sources from the Gemfile.lock,
# if they exist in the Gemfile.lock and are `==`. If you can't find an equivalent
# source in the Gemfile.lock, use the one from the Gemfile.
@@ -669,7 +682,7 @@ module Bundler
end
def converge_dependencies
- frozen = Bundler.settings[:frozen]
+ frozen = Bundler.frozen?
(@dependencies + @locked_deps.values).each do |dep|
locked_source = @locked_deps[dep.name]
# This is to make sure that if bundler is installing in deployment mode and
@@ -739,6 +752,8 @@ module Bundler
end
end
+ unlock_source_unlocks_spec = Bundler.feature_flag.unlock_source_unlocks_spec?
+
converged = []
@locked_specs.each do |s|
# Replace the locked dependency's source with the equivalent source from the Gemfile
@@ -746,21 +761,33 @@ module Bundler
s.source = (dep && dep.source) || sources.get(s.source)
# Don't add a spec to the list if its source is expired. For example,
- # if you change a Git gem to Rubygems.
+ # if you change a Git gem to RubyGems.
next if s.source.nil?
next if @unlock[:sources].include?(s.source.name)
# XXX This is a backwards-compatibility fix to preserve the ability to
# unlock a single gem by passing its name via `--source`. See issue #3759
# TODO: delete in Bundler 2
- next if @unlock[:sources].include?(s.name)
+ next if unlock_source_unlocks_spec && @unlock[:sources].include?(s.name)
# If the spec is from a path source and it doesn't exist anymore
# then we unlock it.
# Path sources have special logic
if s.source.instance_of?(Source::Path) || s.source.instance_of?(Source::Gemspec)
- other = s.source.specs[s].first
+ other_sources_specs = begin
+ s.source.specs
+ rescue PathError, GitError
+ # if we won't need the source (according to the lockfile),
+ # don't error if the path/git source isn't available
+ next if @locked_specs.
+ for(requested_dependencies, [], false, true, false).
+ none? {|locked_spec| locked_spec.source == s.source }
+
+ raise
+ end
+
+ other = other_sources_specs[s].first
# If the spec is no longer in the path source, unlock it. This
# commonly happens if the version changed in the gemspec
@@ -807,17 +834,21 @@ module Bundler
# the metadata dependencies here
def expanded_dependencies
@expanded_dependencies ||= begin
+ expand_dependencies(dependencies + metadata_dependencies, @remote)
+ end
+ end
+
+ def metadata_dependencies
+ @metadata_dependencies ||= begin
ruby_versions = concat_ruby_version_requirements(@ruby_version)
if ruby_versions.empty? || !@ruby_version.exact?
concat_ruby_version_requirements(RubyVersion.system)
concat_ruby_version_requirements(locked_ruby_version_object) unless @unlock[:ruby]
end
-
- metadata_dependencies = [
+ [
Dependency.new("ruby\0", ruby_versions),
Dependency.new("rubygems\0", Gem::VERSION),
]
- expand_dependencies(dependencies + metadata_dependencies, @remote)
end
end
@@ -838,11 +869,12 @@ module Bundler
end
def expand_dependencies(dependencies, remote = false)
+ sorted_platforms = Resolver.sort_platforms(@platforms)
deps = []
dependencies.each do |dep|
dep = Dependency.new(dep, ">= 0") unless dep.respond_to?(:name)
next if !remote && !dep.current_platform?
- platforms = dep.gem_platforms(@platforms)
+ platforms = dep.gem_platforms(sorted_platforms)
if platforms.empty?
mapped_platforms = dep.platforms.map {|p| Dependency::PLATFORM_MAP[p] }
Bundler.ui.warn \
@@ -872,30 +904,33 @@ module Bundler
# Record the specs available in each gem's source, so that those
# specs will be available later when the resolver knows where to
# look for that gemspec (or its dependencies)
- source_requirements = {}
+ default = sources.default_source
+ source_requirements = { :default => default }
+ default = nil unless Bundler.feature_flag.lockfile_uses_separate_rubygems_sources?
dependencies.each do |dep|
- next unless dep.source
- source_requirements[dep.name] = dep.source.specs
+ next unless source = dep.source || default
+ source_requirements[dep.name] = source
end
+ metadata_dependencies.each do |dep|
+ source_requirements[dep.name] = sources.metadata_source
+ end
+ source_requirements["bundler"] = sources.metadata_source # needs to come last to override
source_requirements
end
- def pinned_spec_names(specs)
- names = []
- specs.each do |s|
- # TODO: when two sources without blocks is an error, we can change
- # this check to !s.source.is_a?(Source::LocalRubygems). For now,
- # we need to ask every Rubygems for every gem name.
- if s.source.is_a?(Source::Git) || s.source.is_a?(Source::Path)
- names << s.name
- end
+ def pinned_spec_names(skip = nil)
+ pinned_names = []
+ default = Bundler.feature_flag.lockfile_uses_separate_rubygems_sources? && sources.default_source
+ @dependencies.each do |dep|
+ next unless dep_source = dep.source || default
+ next if dep_source == skip
+ pinned_names << dep.name
end
- names.uniq!
- names
+ pinned_names
end
def requested_groups
- groups - Bundler.settings.without - @optional_groups + Bundler.settings.with
+ groups - Bundler.settings[:without] - @optional_groups + Bundler.settings[:with]
end
def lockfiles_equal?(current, proposed, preserve_unknown_sections)
@@ -930,11 +965,20 @@ module Bundler
def additional_base_requirements_for_resolve
return [] unless @locked_gems && Bundler.feature_flag.only_update_to_newer_versions?
+ dependencies_by_name = dependencies.group_by(&:name)
@locked_gems.specs.reduce({}) do |requirements, locked_spec|
- dep = Gem::Dependency.new(locked_spec.name, ">= #{locked_spec.version}")
- requirements[locked_spec.name] = DepProxy.new(dep, locked_spec.platform)
+ name = locked_spec.name
+ next requirements if @locked_deps[name] != dependencies_by_name[name]
+ dep = Gem::Dependency.new(name, ">= #{locked_spec.version}")
+ requirements[name] = DepProxy.new(dep, locked_spec.platform)
requirements
end.values
end
+
+ def equivalent_rubygems_remotes?(source)
+ return false unless source.is_a?(Source::Rubygems)
+
+ Bundler.settings[:allow_deployment_source_credential_changes] && source.equivalent_remotes?(sources.rubygems_remotes)
+ end
end
end
diff --git a/lib/bundler/dep_proxy.rb b/lib/bundler/dep_proxy.rb
index 998975bbaf..7a9423b14a 100644
--- a/lib/bundler/dep_proxy.rb
+++ b/lib/bundler/dep_proxy.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler
class DepProxy
attr_reader :__platform, :dep
@@ -13,6 +14,7 @@ module Bundler
end
def ==(other)
+ return if other.nil?
dep == other.dep && __platform == other.__platform
end
diff --git a/lib/bundler/dependency.rb b/lib/bundler/dependency.rb
index d2bac66cdb..24257bc113 100644
--- a/lib/bundler/dependency.rb
+++ b/lib/bundler/dependency.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require "rubygems/dependency"
require "bundler/shared_helpers"
require "bundler/rubygems_ext"
@@ -90,16 +91,14 @@ module Bundler
@autorequire = Array(options["require"] || []) if options.key?("require")
end
+ # Returns the platforms this dependency is valid for, in the same order as
+ # passed in the `valid_platforms` parameter
def gem_platforms(valid_platforms)
return valid_platforms if @platforms.empty?
- platforms = []
- @platforms.each do |p|
- platform = PLATFORM_MAP[p]
- next unless valid_platforms.include?(platform)
- platforms |= [platform]
- end
- platforms
+ @gem_platforms ||= @platforms.map {|pl| PLATFORM_MAP[pl] }.compact.uniq
+
+ valid_platforms & @gem_platforms
end
def should_include?
diff --git a/lib/bundler/deployment.rb b/lib/bundler/deployment.rb
index 94f2fac620..291e158ca0 100644
--- a/lib/bundler/deployment.rb
+++ b/lib/bundler/deployment.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require "bundler/shared_helpers"
-Bundler::SharedHelpers.major_deprecation "Bundler no longer integrates with " \
+Bundler::SharedHelpers.major_deprecation 2, "Bundler no longer integrates with " \
"Capistrano, but Capistrano provides its own integration with " \
"Bundler via the capistrano-bundler gem. Use it instead."
diff --git a/lib/bundler/deprecate.rb b/lib/bundler/deprecate.rb
index b978c0df6c..387f632a39 100644
--- a/lib/bundler/deprecate.rb
+++ b/lib/bundler/deprecate.rb
@@ -1,11 +1,22 @@
# frozen_string_literal: true
+
+begin
+ require "rubygems/deprecate"
+rescue LoadError
+ # it's fine if it doesn't exist on the current RubyGems...
+ nil
+end
+
module Bundler
- if defined? ::Deprecate
+ if defined? Bundler::Deprecate
+ # nothing to do!
+ elsif defined? ::Deprecate
Deprecate = ::Deprecate
elsif defined? Gem::Deprecate
Deprecate = Gem::Deprecate
else
- class Deprecate; end
+ class Deprecate
+ end
end
unless Deprecate.respond_to?(:skip_during)
@@ -20,7 +31,7 @@ module Bundler
unless Deprecate.respond_to?(:skip)
def Deprecate.skip
- @skip
+ @skip ||= false
end
end
diff --git a/lib/bundler/dsl.rb b/lib/bundler/dsl.rb
index e4c257d267..8681163277 100644
--- a/lib/bundler/dsl.rb
+++ b/lib/bundler/dsl.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require "bundler/dependency"
require "bundler/ruby_dsl"
@@ -14,6 +15,9 @@ module Bundler
VALID_PLATFORMS = Bundler::Dependency::PLATFORM_MAP.keys.freeze
+ VALID_KEYS = %w[group groups git path glob name branch ref tag require submodules
+ platform platforms type source install_if].freeze
+
attr_reader :gemspecs
attr_accessor :dependencies
@@ -30,14 +34,16 @@ module Bundler
@ruby_version = nil
@gemspecs = []
@gemfile = nil
+ @gemfiles = []
add_git_sources
end
def eval_gemfile(gemfile, contents = nil)
- expanded_gemfile_path = Pathname.new(gemfile).expand_path
+ expanded_gemfile_path = Pathname.new(gemfile).expand_path(@gemfile && @gemfile.parent)
original_gemfile = @gemfile
@gemfile = expanded_gemfile_path
- contents ||= Bundler.read_file(gemfile.to_s)
+ @gemfiles << expanded_gemfile_path
+ contents ||= Bundler.read_file(@gemfile.to_s)
instance_eval(contents.dup.untaint, gemfile.to_s, 1)
rescue Exception => e
message = "There was an error " \
@@ -95,10 +101,10 @@ module Bundler
# if there's already a dependency with this name we try to prefer one
if current = @dependencies.find {|d| d.name == dep.name }
+ deleted_dep = @dependencies.delete(current) if current.type == :development
+
if current.requirement != dep.requirement
- if current.type == :development
- @dependencies.delete current
- else
+ unless deleted_dep
return if dep.type == :development
raise GemfileError, "You cannot specify the same gem twice with different version requirements.\n" \
"You specified: #{current.name} (#{current.requirement}) and #{dep.name} (#{dep.requirement})"
@@ -111,9 +117,7 @@ module Bundler
end
if current.source != dep.source
- if current.type == :development
- @dependencies.delete current
- else
+ unless deleted_dep
return if dep.type == :development
raise GemfileError, "You cannot specify the same gem twice coming from different sources.\n" \
"You specified that #{dep.name} (#{dep.requirement}) should come from " \
@@ -128,10 +132,12 @@ module Bundler
def source(source, *args, &blk)
options = args.last.is_a?(Hash) ? args.pop.dup : {}
options = normalize_hash(options)
+ source = normalize_source(source)
+
if options.key?("type")
options["type"] = options["type"].to_s
unless Plugin.source?(options["type"])
- raise "No sources available for #{options["type"]}"
+ raise InvalidOption, "No plugin sources available for #{options["type"]}"
end
unless block_given?
@@ -141,12 +147,10 @@ module Bundler
source_opts = options.merge("uri" => source)
with_source(@sources.add_plugin_source(options["type"], source_opts), &blk)
elsif block_given?
- source = normalize_source(source)
with_source(@sources.add_rubygems_source("remotes" => source), &blk)
else
- source = normalize_source(source)
check_primary_source_safety(@sources)
- @sources.add_rubygems_remote(source)
+ @sources.global_rubygems_source = source
end
end
@@ -164,6 +168,19 @@ module Bundler
end
def path(path, options = {}, &blk)
+ unless block_given?
+ msg = "You can no longer specify a path source by itself. Instead, \n" \
+ "either use the :path option on a gem, or specify the gems that \n" \
+ "bundler should find in the path source by passing a block to \n" \
+ "the path method, like: \n\n" \
+ " path 'dir/containing/rails' do\n" \
+ " gem 'rails'\n" \
+ " end\n\n"
+
+ raise DeprecatedError, msg if Bundler.feature_flag.disable_multisource?
+ SharedHelpers.major_deprecation(2, msg.strip)
+ end
+
source_options = normalize_hash(options).merge(
"path" => Pathname.new(path),
"root_path" => gemfile_root,
@@ -190,6 +207,7 @@ module Bundler
def github(repo, options = {})
raise ArgumentError, "GitHub sources require a block" unless block_given?
+ raise DeprecatedError, "The #github method has been removed" if Bundler.feature_flag.skip_default_git_sources?
github_uri = @git_sources["github"].call(repo)
git_options = normalize_hash(options).merge("uri" => github_uri)
git_source = @sources.add_git_source(git_options)
@@ -197,16 +215,16 @@ module Bundler
end
def to_definition(lockfile, unlock)
- Definition.new(lockfile, @dependencies, @sources, unlock, @ruby_version, @optional_groups)
+ Definition.new(lockfile, @dependencies, @sources, unlock, @ruby_version, @optional_groups, @gemfiles)
end
def group(*args, &blk)
- opts = Hash === args.last ? args.pop.dup : {}
- normalize_group_options(opts, args)
+ options = args.last.is_a?(Hash) ? args.pop.dup : {}
+ normalize_group_options(options, args)
@groups.concat args
- if opts["optional"]
+ if options["optional"]
optional_groups = args - @optional_groups
@optional_groups.concat optional_groups
end
@@ -216,9 +234,9 @@ module Bundler
args.each { @groups.pop }
end
- def install_if(*args, &blk)
+ def install_if(*args)
@install_conditionals.concat args
- blk.call
+ yield
ensure
args.each { @install_conditionals.pop }
end
@@ -250,7 +268,12 @@ module Bundler
private
def add_git_sources
+ return if Bundler.feature_flag.skip_default_git_sources?
+
git_source(:github) do |repo_name|
+ warn_deprecated_git_source(:github, <<-'RUBY'.strip, 'Change any "reponame" :github sources to "username/reponame".')
+"https://github.com/#{repo_name}.git"
+ RUBY
# It would be better to use https instead of the git protocol, but this
# can break deployment of existing locked bundles when switching between
# different versions of Bundler. The change will be made in 2.0, which
@@ -267,23 +290,29 @@ module Bundler
repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
# TODO: 2.0 upgrade this setting to the default
if Bundler.settings["github.https"]
+ Bundler::SharedHelpers.major_deprecation 2, "The `github.https` setting will be removed"
"https://github.com/#{repo_name}.git"
else
- warn_github_source_change(repo_name)
"git://github.com/#{repo_name}.git"
end
end
# TODO: 2.0 remove this deprecated git source
git_source(:gist) do |repo_name|
- warn_deprecated_git_source(:gist, 'https://gist.github.com/#{repo_name}.git')
+ warn_deprecated_git_source(:gist, '"https://gist.github.com/#{repo_name}.git"')
+
"https://gist.github.com/#{repo_name}.git"
end
# TODO: 2.0 remove this deprecated git source
git_source(:bitbucket) do |repo_name|
- user_name, repo_name = repo_name.split "/"
- warn_deprecated_git_source(:bitbucket, 'https://#{user_name}@bitbucket.org/#{user_name}/#{repo_name}.git')
+ warn_deprecated_git_source(:bitbucket, <<-'RUBY'.strip)
+user_name, repo_name = repo_name.split("/")
+repo_name ||= user_name
+"https://#{user_name}@bitbucket.org/#{user_name}/#{repo_name}.git"
+ RUBY
+
+ user_name, repo_name = repo_name.split("/")
repo_name ||= user_name
"https://#{user_name}@bitbucket.org/#{user_name}/#{repo_name}.git"
end
@@ -308,7 +337,7 @@ module Bundler
end
def valid_keys
- @valid_keys ||= %w(group groups git path glob name branch ref tag require submodules platform platforms type source install_if)
+ @valid_keys ||= VALID_KEYS
end
def normalize_options(name, version, opts)
@@ -318,6 +347,9 @@ module Bundler
if name =~ /\s/
raise GemfileError, %('#{name}' is not a valid gem name because it contains whitespace)
end
+ if name.empty?
+ raise GemfileError, %(an empty gem name is not valid)
+ end
normalize_hash(opts)
@@ -355,7 +387,7 @@ module Bundler
opts["git"] = @git_sources[git_name].call(opts[git_name])
end
- %w(git path).each do |type|
+ %w[git path].each do |type|
next unless param = opts[type]
if version.first && version.first =~ /^\s*=?\s*(\d[^\s]*)\s*$/
options = opts.merge("name" => name, "version" => $1)
@@ -366,8 +398,8 @@ module Bundler
opts["source"] = source
end
- opts["source"] ||= @source
- opts["env"] ||= @env
+ opts["source"] ||= @source
+ opts["env"] ||= @env
opts["platforms"] = platforms.dup
opts["group"] = groups
opts["should_include"] = install_if
@@ -377,7 +409,7 @@ module Bundler
normalize_hash(opts)
groups = groups.map {|group| ":#{group}" }.join(", ")
- validate_keys("group #{groups}", opts, %w(optional))
+ validate_keys("group #{groups}", opts, %w[optional])
opts["optional"] ||= false
end
@@ -390,25 +422,25 @@ module Bundler
raise GemfileError, %(The `branch` option for `#{command}` is not allowed. Only gems with a git source can specify a branch)
end
- if invalid_keys.any?
- message = String.new
- message << "You passed #{invalid_keys.map {|k| ":" + k }.join(", ")} "
- message << if invalid_keys.size > 1
- "as options for #{command}, but they are invalid."
- else
- "as an option for #{command}, but it is invalid."
- end
-
- message << " Valid options are: #{valid_keys.join(", ")}."
- message << " You may be able to resolve this by upgrading Bundler to the newest version."
- raise InvalidOption, message
- end
+ return true unless invalid_keys.any?
+
+ message = String.new
+ message << "You passed #{invalid_keys.map {|k| ":" + k }.join(", ")} "
+ message << if invalid_keys.size > 1
+ "as options for #{command}, but they are invalid."
+ else
+ "as an option for #{command}, but it is invalid."
+ end
+
+ message << " Valid options are: #{valid_keys.join(", ")}."
+ message << " You may be able to resolve this by upgrading Bundler to the newest version."
+ raise InvalidOption, message
end
def normalize_source(source)
case source
when :gemcutter, :rubygems, :rubyforge
- Bundler::SharedHelpers.major_deprecation "The source :#{source} is deprecated because HTTP " \
+ Bundler::SharedHelpers.major_deprecation 2, "The source :#{source} is deprecated because HTTP " \
"requests are insecure.\nPlease change your source to 'https://" \
"rubygems.org' if possible, or 'http://rubygems.org' if not."
"http://rubygems.org"
@@ -419,17 +451,20 @@ module Bundler
end
end
- def check_primary_source_safety(source)
- return unless source.rubygems_primary_remotes.any?
+ def check_primary_source_safety(source_list)
+ return if source_list.rubygems_primary_remotes.empty? && source_list.global_rubygems_source.nil?
- # TODO: 2.0 upgrade from setting to default
- if Bundler.settings[:disable_multisource]
- raise GemfileError, "Warning: this Gemfile contains multiple primary sources. " \
+ if Bundler.feature_flag.disable_multisource?
+ msg = "This Gemfile contains multiple primary sources. " \
"Each source after the first must include a block to indicate which gems " \
- "should come from that source. To downgrade this error to a warning, run " \
- "`bundle config --delete disable_multisource`"
+ "should come from that source"
+ unless Bundler.feature_flag.bundler_2_mode?
+ msg += ". To downgrade this error to a warning, run " \
+ "`bundle config --delete disable_multisource`"
+ end
+ raise GemfileEvalError, msg
else
- Bundler::SharedHelpers.major_deprecation "Your Gemfile contains multiple primary sources. " \
+ Bundler::SharedHelpers.major_deprecation 2, "Your Gemfile contains multiple primary sources. " \
"Using `source` more than once without a block is a security risk, and " \
"may result in installing unexpected gems. To resolve this warning, use " \
"a block to indicate which gems should come from the secondary source. " \
@@ -438,20 +473,20 @@ module Bundler
end
end
- def warn_github_source_change(repo_name)
+ def warn_deprecated_git_source(name, replacement, additional_message = nil)
# TODO: 2.0 remove deprecation
- Bundler::SharedHelpers.major_deprecation "The :github option uses the git: protocol, which is not secure. " \
- "Bundler 2.0 will use the https: protocol, which is secure. Enable this change now by " \
- "running `bundle config github.https true`."
- end
+ additional_message &&= " #{additional_message}"
+ replacement = if replacement.count("\n").zero?
+ "{|repo_name| #{replacement} }"
+ else
+ "do |repo_name|\n#{replacement.to_s.gsub(/^/, " ")}\n end"
+ end
+
+ Bundler::SharedHelpers.major_deprecation 2, <<-EOS
+The :#{name} git source is deprecated, and will be removed in Bundler 2.0.#{additional_message} Add this code to the top of your Gemfile to ensure it continues to work:
+
+ git_source(:#{name}) #{replacement}
- def warn_deprecated_git_source(name, repo_string)
- # TODO: 2.0 remove deprecation
- Bundler::SharedHelpers.major_deprecation <<-EOS
-The :#{name} git source is deprecated, and will be removed in Bundler 2.0. Add this code to your Gemfile to ensure it continues to work:
- git_source(:#{name}) do |repo_name|
- "#{repo_string}"
- end
EOS
end
@@ -530,7 +565,7 @@ The :#{name} git source is deprecated, and will be removed in Bundler 2.0. Add t
lines = contents.lines.to_a
indent = " # "
indicator = indent.tr("#", ">")
- first_line = (line_numer.zero?)
+ first_line = line_numer.zero?
last_line = (line_numer == (lines.count - 1))
m << "\n"
diff --git a/lib/bundler/endpoint_specification.rb b/lib/bundler/endpoint_specification.rb
index 5a1deeea47..8668c4ea7f 100644
--- a/lib/bundler/endpoint_specification.rb
+++ b/lib/bundler/endpoint_specification.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler
# used for Creating Specifications from the Gemcutter Endpoint
class EndpointSpecification < Gem::Specification
@@ -9,11 +10,15 @@ module Bundler
attr_accessor :source, :remote, :dependencies
def initialize(name, version, platform, dependencies, metadata = nil)
+ super()
@name = name
@version = Gem::Version.create version
@platform = platform
@dependencies = dependencies.map {|dep, reqs| build_dependency(dep, reqs) }
+ @loaded_from = nil
+ @remote_specification = nil
+
parse_metadata(metadata)
end
@@ -71,6 +76,8 @@ module Bundler
@remote_specification.post_install_message
elsif _local_specification
_local_specification.post_install_message
+ else
+ super
end
end
@@ -80,6 +87,8 @@ module Bundler
@remote_specification.extensions
elsif _local_specification
_local_specification.extensions
+ else
+ super
end
end
diff --git a/lib/bundler/env.rb b/lib/bundler/env.rb
index 8b990baf40..58fe20dbe7 100644
--- a/lib/bundler/env.rb
+++ b/lib/bundler/env.rb
@@ -1,33 +1,21 @@
# frozen_string_literal: true
+
require "bundler/rubygems_integration"
require "bundler/source/git/git_proxy"
module Bundler
class Env
- def write(io)
+ def self.write(io)
io.write report
end
- def report(options = {})
+ def self.report(options = {})
print_gemfile = options.delete(:print_gemfile) { true }
print_gemspecs = options.delete(:print_gemspecs) { true }
- out = String.new("## Environment\n\n```\n")
- out << "Bundler #{Bundler::VERSION}\n"
- out << "Rubygems #{Gem::VERSION}\n"
- out << "Ruby #{ruby_version}"
- out << "GEM_HOME #{ENV["GEM_HOME"]}\n" unless ENV["GEM_HOME"].nil? || ENV["GEM_HOME"].empty?
- out << "GEM_PATH #{ENV["GEM_PATH"]}\n" unless ENV["GEM_PATH"] == ENV["GEM_HOME"]
- out << "RVM #{ENV["rvm_version"]}\n" if ENV["rvm_version"]
- out << "Git #{git_version}\n"
- out << "Platform #{Gem::Platform.local}\n"
- out << "OpenSSL #{OpenSSL::OPENSSL_VERSION}\n" if defined?(OpenSSL::OPENSSL_VERSION)
- %w(rubygems-bundler open_gem).each do |name|
- specs = Bundler.rubygems.find_name(name)
- out << "#{name} (#{specs.map(&:version).join(",")})\n" unless specs.empty?
- end
-
- out << "```\n"
+ out = String.new
+ append_formatted_table("Environment", environment, out)
+ append_formatted_table("Bundler Build Metadata", BuildMetadata.to_h, out)
unless Bundler.settings.all.empty?
out << "\n## Bundler settings\n\n```\n"
@@ -43,9 +31,18 @@ module Bundler
return out unless SharedHelpers.in_bundle?
if print_gemfile
+ gemfiles = [Bundler.default_gemfile]
+ begin
+ gemfiles = Bundler.definition.gemfiles
+ rescue GemfileNotFound
+ nil
+ end
+
out << "\n## Gemfile\n"
- out << "\n### #{Bundler.default_gemfile.relative_path_from(SharedHelpers.pwd)}\n\n"
- out << "```ruby\n" << read_file(Bundler.default_gemfile).chomp << "\n```\n"
+ gemfiles.each do |gemfile|
+ out << "\n### #{Pathname.new(gemfile).relative_path_from(SharedHelpers.pwd)}\n\n"
+ out << "```ruby\n" << read_file(gemfile).chomp << "\n```\n"
+ end
out << "\n### #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)}\n\n"
out << "```\n" << read_file(Bundler.default_lockfile).chomp << "\n```\n"
@@ -63,9 +60,7 @@ module Bundler
out
end
- private
-
- def read_file(filename)
+ def self.read_file(filename)
File.read(filename.to_s).strip
rescue Errno::ENOENT
"<No #{filename} found>"
@@ -73,22 +68,86 @@ module Bundler
"#{e.class}: #{e.message}"
end
- def ruby_version
+ def self.ruby_version
str = String.new("#{RUBY_VERSION}")
if RUBY_VERSION < "1.9"
str << " (#{RUBY_RELEASE_DATE}"
str << " patchlevel #{RUBY_PATCHLEVEL}" if defined? RUBY_PATCHLEVEL
- str << ") [#{RUBY_PLATFORM}]\n"
+ str << ") [#{RUBY_PLATFORM}]"
else
str << "p#{RUBY_PATCHLEVEL}" if defined? RUBY_PATCHLEVEL
- str << " (#{RUBY_RELEASE_DATE} revision #{RUBY_REVISION}) [#{RUBY_PLATFORM}]\n"
+ str << " (#{RUBY_RELEASE_DATE} revision #{RUBY_REVISION}) [#{RUBY_PLATFORM}]"
end
end
- def git_version
+ def self.git_version
Bundler::Source::Git::GitProxy.new(nil, nil, nil).full_version
rescue Bundler::Source::Git::GitNotInstalledError
"not installed"
end
+
+ def self.version_of(script)
+ return "not installed" unless Bundler.which(script)
+ `#{script} --version`
+ end
+
+ def self.chruby_version
+ return "not installed" unless Bundler.which("chruby-exec")
+ `chruby-exec -- chruby --version`.
+ sub(/.*^chruby: (#{Gem::Version::VERSION_PATTERN}).*/m, '\1')
+ end
+
+ def self.environment
+ out = []
+
+ out << ["Bundler", Bundler::VERSION]
+ out << [" Platforms", Gem.platforms.join(", ")]
+ out << ["Ruby", ruby_version]
+ out << [" Full Path", Gem.ruby]
+ out << [" Config Dir", Pathname.new(Gem::ConfigFile::SYSTEM_WIDE_CONFIG_FILE).dirname]
+ out << ["RubyGems", Gem::VERSION]
+ out << [" Gem Home", ENV.fetch("GEM_HOME") { Gem.dir }]
+ out << [" Gem Path", ENV.fetch("GEM_PATH") { Gem.path.join(File::PATH_SEPARATOR) }]
+ out << [" User Path", Gem.user_dir]
+ out << [" Bin Dir", Gem.bindir]
+ out << ["OpenSSL"] if defined?(OpenSSL)
+ out << [" Compiled", OpenSSL::OPENSSL_VERSION] if defined?(OpenSSL::OPENSSL_VERSION)
+ out << [" Loaded", OpenSSL::OPENSSL_LIBRARY_VERSION] if defined?(OpenSSL::OPENSSL_LIBRARY_VERSION)
+ out << [" Cert File", OpenSSL::X509::DEFAULT_CERT_FILE] if defined?(OpenSSL::X509::DEFAULT_CERT_FILE)
+ out << [" Cert Dir", OpenSSL::X509::DEFAULT_CERT_DIR] if defined?(OpenSSL::X509::DEFAULT_CERT_DIR)
+ out << ["Tools"]
+ out << [" Git", git_version]
+ out << [" RVM", ENV.fetch("rvm_version") { version_of("rvm") }]
+ out << [" rbenv", version_of("rbenv")]
+ out << [" chruby", chruby_version]
+
+ %w[rubygems-bundler open_gem].each do |name|
+ specs = Bundler.rubygems.find_name(name)
+ out << [" #{name}", "(#{specs.map(&:version).join(",")})"] unless specs.empty?
+ end
+ if (exe = caller.last.split(":").first) && exe =~ %r{(exe|bin)/bundler?\z}
+ shebang = File.read(exe).lines.first
+ shebang.sub!(/^#!\s*/, "")
+ unless shebang.start_with?(Gem.ruby, "/usr/bin/env ruby")
+ out << ["Gem.ruby", Gem.ruby]
+ out << ["bundle #!", shebang]
+ end
+ end
+
+ out
+ end
+
+ def self.append_formatted_table(title, pairs, out)
+ return if pairs.empty?
+ out << "\n" unless out.empty?
+ out << "## #{title}\n\n```\n"
+ ljust = pairs.map {|k, _v| k.to_s.length }.max
+ pairs.each do |k, v|
+ out << "#{k.to_s.ljust(ljust)} #{v}\n"
+ end
+ out << "```\n"
+ end
+
+ private_class_method :read_file, :ruby_version, :git_version, :append_formatted_table, :version_of, :chruby_version
end
end
diff --git a/lib/bundler/environment_preserver.rb b/lib/bundler/environment_preserver.rb
index a891f4854d..af7c1ef0a4 100644
--- a/lib/bundler/environment_preserver.rb
+++ b/lib/bundler/environment_preserver.rb
@@ -1,12 +1,29 @@
# frozen_string_literal: true
+
module Bundler
class EnvironmentPreserver
+ INTENTIONALLY_NIL = "BUNDLER_ENVIRONMENT_PRESERVER_INTENTIONALLY_NIL".freeze
+ BUNDLER_KEYS = %w[
+ BUNDLE_BIN_PATH
+ BUNDLE_GEMFILE
+ BUNDLER_ORIG_MANPATH
+ BUNDLER_VERSION
+ GEM_HOME
+ GEM_PATH
+ MANPATH
+ PATH
+ RB_USER_INSTALL
+ RUBYLIB
+ RUBYOPT
+ ].map(&:freeze).freeze
+ BUNDLER_PREFIX = "BUNDLER_ORIG_".freeze
+
# @param env [ENV]
# @param keys [Array<String>]
def initialize(env, keys)
@original = env.to_hash
@keys = keys
- @prefix = "BUNDLER_ORIG_"
+ @prefix = BUNDLER_PREFIX
end
# @return [Hash]
@@ -14,9 +31,10 @@ module Bundler
env = @original.clone
@keys.each do |key|
value = env[key]
- original_value = env[@prefix + key]
- if !value.nil? && !value.empty? && original_value.nil?
- env[@prefix + key] = value
+ if !value.nil? && !value.empty?
+ env[@prefix + key] ||= value
+ elsif value.nil?
+ env[@prefix + key] ||= INTENTIONALLY_NIL
end
end
env
@@ -27,10 +45,13 @@ module Bundler
env = @original.clone
@keys.each do |key|
value_original = env[@prefix + key]
- unless value_original.nil? || value_original.empty?
+ next if value_original.nil? || value_original.empty?
+ if value_original == INTENTIONALLY_NIL
+ env.delete(key)
+ else
env[key] = value_original
- env.delete(@prefix + key)
end
+ env.delete(@prefix + key)
end
env
end
diff --git a/lib/bundler/errors.rb b/lib/bundler/errors.rb
index 6ce8493ea7..e471bce0b6 100644
--- a/lib/bundler/errors.rb
+++ b/lib/bundler/errors.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler
class BundlerError < StandardError
def self.status_code(code)
diff --git a/lib/bundler/feature_flag.rb b/lib/bundler/feature_flag.rb
index 150cac1e67..6a1809cd40 100644
--- a/lib/bundler/feature_flag.rb
+++ b/lib/bundler/feature_flag.rb
@@ -1,22 +1,59 @@
# frozen_string_literal: true
+
module Bundler
class FeatureFlag
def self.settings_flag(flag, &default)
unless Bundler::Settings::BOOL_KEYS.include?(flag.to_s)
raise "Cannot use `#{flag}` as a settings feature flag since it isn't a bool key"
end
- define_method("#{flag}?") do
- value = Bundler.settings[flag]
+
+ settings_method("#{flag}?", flag, &default)
+ end
+ private_class_method :settings_flag
+
+ def self.settings_option(key, &default)
+ settings_method(key, key, &default)
+ end
+ private_class_method :settings_option
+
+ def self.settings_method(name, key, &default)
+ define_method(name) do
+ value = Bundler.settings[key]
value = instance_eval(&default) if value.nil? && !default.nil?
value
end
end
+ private_class_method :settings_method
(1..10).each {|v| define_method("bundler_#{v}_mode?") { major_version >= v } }
+ settings_flag(:allow_bundler_dependency_conflicts) { bundler_2_mode? }
settings_flag(:allow_offline_install) { bundler_2_mode? }
+ settings_flag(:auto_clean_without_path) { bundler_2_mode? }
+ settings_flag(:cache_all) { bundler_2_mode? }
+ settings_flag(:cache_command_is_package) { bundler_2_mode? }
+ settings_flag(:console_command) { !bundler_2_mode? }
+ settings_flag(:default_install_uses_path) { bundler_2_mode? }
+ settings_flag(:deployment_means_frozen) { bundler_2_mode? }
+ settings_flag(:disable_multisource) { bundler_2_mode? }
+ settings_flag(:error_on_stderr) { bundler_2_mode? }
+ settings_flag(:forget_cli_options) { bundler_2_mode? }
+ settings_flag(:global_gem_cache) { bundler_2_mode? }
+ settings_flag(:init_gems_rb) { bundler_2_mode? }
+ settings_flag(:list_command) { bundler_2_mode? }
+ settings_flag(:lockfile_uses_separate_rubygems_sources) { bundler_2_mode? }
settings_flag(:only_update_to_newer_versions) { bundler_2_mode? }
settings_flag(:plugins) { @bundler_version >= Gem::Version.new("1.14") }
+ settings_flag(:prefer_gems_rb) { bundler_2_mode? }
+ settings_flag(:print_only_version_number) { bundler_2_mode? }
+ settings_flag(:setup_makes_kernel_gem_public) { !bundler_2_mode? }
+ settings_flag(:skip_default_git_sources) { bundler_2_mode? }
+ settings_flag(:specific_platform) { bundler_2_mode? }
+ settings_flag(:suppress_install_using_messages) { bundler_2_mode? }
+ settings_flag(:unlock_source_unlocks_spec) { !bundler_2_mode? }
+ settings_flag(:update_requires_all_flag) { bundler_2_mode? }
+
+ settings_option(:default_cli_command) { bundler_2_mode? ? :cli_help : :install }
def initialize(bundler_version)
@bundler_version = Gem::Version.create(bundler_version)
@@ -26,7 +63,5 @@ module Bundler
@bundler_version.segments.first
end
private :major_version
-
- class << self; private :settings_flag; end
end
end
diff --git a/lib/bundler/fetcher.rb b/lib/bundler/fetcher.rb
index 9e208e4957..03ff528826 100644
--- a/lib/bundler/fetcher.rb
+++ b/lib/bundler/fetcher.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require "bundler/vendored_persistent"
require "cgi"
require "securerandom"
@@ -237,7 +238,7 @@ module Bundler
Bundler.settings[:ssl_client_cert]
raise SSLError if needs_ssl && !defined?(OpenSSL::SSL)
- con = Bundler::Persistent::Net::HTTP::Persistent.new "bundler", :ENV
+ con = PersistentHTTP.new "bundler", :ENV
if gem_proxy = Bundler.rubygems.configuration[:http_proxy]
con.proxy = URI.parse(gem_proxy) if gem_proxy != :no_proxy
end
@@ -248,8 +249,11 @@ module Bundler
con.cert_store = bundler_cert_store
end
- if Bundler.settings[:ssl_client_cert]
- pem = File.read(Bundler.settings[:ssl_client_cert])
+ ssl_client_cert = Bundler.settings[:ssl_client_cert] ||
+ (Bundler.rubygems.configuration.ssl_client_cert if
+ Bundler.rubygems.configuration.respond_to?(:ssl_client_cert))
+ if ssl_client_cert
+ pem = File.read(ssl_client_cert)
con.cert = OpenSSL::X509::Certificate.new(pem)
con.key = OpenSSL::PKey::RSA.new(pem)
end
@@ -273,16 +277,19 @@ module Bundler
Timeout::Error, EOFError, SocketError, Errno::ENETDOWN, Errno::ENETUNREACH,
Errno::EINVAL, Errno::ECONNRESET, Errno::ETIMEDOUT, Errno::EAGAIN,
Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError,
- Bundler::Persistent::Net::HTTP::Persistent::Error, Zlib::BufError, Errno::EHOSTUNREACH
+ PersistentHTTP::Error, Zlib::BufError, Errno::EHOSTUNREACH
].freeze
def bundler_cert_store
store = OpenSSL::X509::Store.new
- if Bundler.settings[:ssl_ca_cert]
- if File.directory? Bundler.settings[:ssl_ca_cert]
- store.add_path Bundler.settings[:ssl_ca_cert]
+ ssl_ca_cert = Bundler.settings[:ssl_ca_cert] ||
+ (Bundler.rubygems.configuration.ssl_ca_cert if
+ Bundler.rubygems.configuration.respond_to?(:ssl_ca_cert))
+ if ssl_ca_cert
+ if File.directory? ssl_ca_cert
+ store.add_path ssl_ca_cert
else
- store.add_file Bundler.settings[:ssl_ca_cert]
+ store.add_file ssl_ca_cert
end
else
store.set_default_paths
diff --git a/lib/bundler/fetcher/base.rb b/lib/bundler/fetcher/base.rb
index 271729a534..27987f670a 100644
--- a/lib/bundler/fetcher/base.rb
+++ b/lib/bundler/fetcher/base.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler
class Fetcher
class Base
diff --git a/lib/bundler/fetcher/compact_index.rb b/lib/bundler/fetcher/compact_index.rb
index 97de88101b..cfc74d642c 100644
--- a/lib/bundler/fetcher/compact_index.rb
+++ b/lib/bundler/fetcher/compact_index.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require "bundler/fetcher/base"
require "bundler/worker"
@@ -61,7 +62,7 @@ module Bundler
compact_index_request :fetch_spec
def available?
- return nil unless md5_available?
+ return nil unless SharedHelpers.md5_available?
user_home = Bundler.user_home
return nil unless user_home.directory? && user_home.writable?
# Read info file checksums out of /versions, so we can know if gems are up to date
@@ -120,16 +121,6 @@ module Bundler
Net::HTTPNotModified.new(nil, nil, nil)
end
end
-
- def md5_available?
- require "openssl"
- OpenSSL::Digest::MD5.digest("")
- true
- rescue LoadError
- true
- rescue OpenSSL::Digest::DigestError
- false
- end
end
end
end
diff --git a/lib/bundler/fetcher/dependency.rb b/lib/bundler/fetcher/dependency.rb
index 445b0f2332..1430d1ebeb 100644
--- a/lib/bundler/fetcher/dependency.rb
+++ b/lib/bundler/fetcher/dependency.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require "bundler/fetcher/base"
require "cgi"
@@ -6,7 +7,7 @@ module Bundler
class Fetcher
class Dependency < Base
def available?
- fetch_uri.scheme != "file" && downloader.fetch(dependency_api_uri)
+ @available ||= fetch_uri.scheme != "file" && downloader.fetch(dependency_api_uri)
rescue NetworkDownError => e
raise HTTPError, e.message
rescue AuthenticationRequiredError
diff --git a/lib/bundler/fetcher/downloader.rb b/lib/bundler/fetcher/downloader.rb
index 453e4645eb..cbc5e220bd 100644
--- a/lib/bundler/fetcher/downloader.rb
+++ b/lib/bundler/fetcher/downloader.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler
class Fetcher
class Downloader
diff --git a/lib/bundler/fetcher/index.rb b/lib/bundler/fetcher/index.rb
index d8e212989e..9529944391 100644
--- a/lib/bundler/fetcher/index.rb
+++ b/lib/bundler/fetcher/index.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require "bundler/fetcher/base"
require "rubygems/remote_fetcher"
diff --git a/lib/bundler/friendly_errors.rb b/lib/bundler/friendly_errors.rb
index 3ba3dcdd91..f624185773 100644
--- a/lib/bundler/friendly_errors.rb
+++ b/lib/bundler/friendly_errors.rb
@@ -1,5 +1,6 @@
# encoding: utf-8
# frozen_string_literal: true
+
require "cgi"
require "bundler/vendored_thor"
@@ -92,7 +93,7 @@ module Bundler
#{e.backtrace && e.backtrace.join("\n ").chomp}
```
- #{Bundler::Env.new.report}
+ #{Bundler::Env.report}
--- TEMPLATE END ----------------------------------------------------------------
EOS
@@ -119,6 +120,8 @@ module Bundler
def self.with_friendly_errors
yield
+ rescue SignalException
+ raise
rescue Exception => e
FriendlyErrors.log_error(e)
exit FriendlyErrors.exit_status(e)
diff --git a/lib/bundler/gem_helper.rb b/lib/bundler/gem_helper.rb
index 936d1361fa..1d7fc508d5 100644
--- a/lib/bundler/gem_helper.rb
+++ b/lib/bundler/gem_helper.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require "bundler/vendored_thor" unless defined?(Thor)
require "bundler"
@@ -50,8 +51,8 @@ module Bundler
install_gem(built_gem_path, :local)
end
- desc "Create tag #{version_tag} and build and push #{name}-#{version}.gem to Rubygems\n" \
- "To prevent publishing in Rubygems use `gem_push=no rake release`"
+ desc "Create tag #{version_tag} and build and push #{name}-#{version}.gem to #{gem_push_host}\n" \
+ "To prevent publishing in RubyGems use `gem_push=no rake release`"
task "release", [:remote] => ["build", "release:guard_clean",
"release:source_control_push", "release:rubygem_push"] do
end
@@ -92,18 +93,14 @@ module Bundler
protected
def rubygem_push(path)
- allowed_push_host = nil
gem_command = "gem push '#{path}'"
gem_command += " --key #{gem_key}" if gem_key
- if @gemspec.respond_to?(:metadata)
- allowed_push_host = @gemspec.metadata["allowed_push_host"]
- gem_command += " --host #{allowed_push_host}" if allowed_push_host
- end
+ gem_command += " --host #{allowed_push_host}" if allowed_push_host
unless allowed_push_host || Bundler.user_home.join(".gem/credentials").file?
raise "Your rubygems.org credentials aren't set. Run `gem push` to set them."
end
sh(gem_command)
- Bundler.ui.confirm "Pushed #{name} #{version} to #{allowed_push_host ? allowed_push_host : "rubygems.org."}"
+ Bundler.ui.confirm "Pushed #{name} #{version} to #{gem_push_host}"
end
def built_gem_path
@@ -116,6 +113,18 @@ module Bundler
Bundler.ui.confirm "Pushed git commits and tags."
end
+ def allowed_push_host
+ @gemspec.metadata["allowed_push_host"] if @gemspec.respond_to?(:metadata)
+ end
+
+ def gem_push_host
+ env_rubygems_host = ENV["RUBYGEMS_HOST"]
+ env_rubygems_host = nil if
+ env_rubygems_host && env_rubygems_host.empty?
+
+ allowed_push_host || env_rubygems_host || "rubygems.org"
+ end
+
def perform_git_push(options = "")
cmd = "git push #{options}"
out, code = sh_with_code(cmd)
@@ -187,7 +196,7 @@ module Bundler
end
def gem_push?
- !%w(n no nil false off 0).include?(ENV["gem_push"].to_s.downcase)
+ !%w[n no nil false off 0].include?(ENV["gem_push"].to_s.downcase)
end
end
end
diff --git a/lib/bundler/gem_helpers.rb b/lib/bundler/gem_helpers.rb
index 955834ff01..019ae10c66 100644
--- a/lib/bundler/gem_helpers.rb
+++ b/lib/bundler/gem_helpers.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler
module GemHelpers
GENERIC_CACHE = {} # rubocop:disable MutableConstant
diff --git a/lib/bundler/gem_remote_fetcher.rb b/lib/bundler/gem_remote_fetcher.rb
index 481838a5e2..9577535d63 100644
--- a/lib/bundler/gem_remote_fetcher.rb
+++ b/lib/bundler/gem_remote_fetcher.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require "rubygems/remote_fetcher"
module Bundler
diff --git a/lib/bundler/gem_tasks.rb b/lib/bundler/gem_tasks.rb
index 230e7f28f2..f736517bd7 100644
--- a/lib/bundler/gem_tasks.rb
+++ b/lib/bundler/gem_tasks.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require "rake/clean"
CLOBBER.include "pkg"
diff --git a/lib/bundler/gem_version_promoter.rb b/lib/bundler/gem_version_promoter.rb
index d60d823d9c..52b5386045 100644
--- a/lib/bundler/gem_version_promoter.rb
+++ b/lib/bundler/gem_version_promoter.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler
# This class contains all of the logic for determining the next version of a
# Gem to update to based on the requested level (patch, minor, major).
diff --git a/lib/bundler/gemdeps.rb b/lib/bundler/gemdeps.rb
index 8595b8c7ea..cd4b25d0e6 100644
--- a/lib/bundler/gemdeps.rb
+++ b/lib/bundler/gemdeps.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler
class Gemdeps
def initialize(runtime)
diff --git a/lib/bundler/graph.rb b/lib/bundler/graph.rb
index e145590430..de6bba0214 100644
--- a/lib/bundler/graph.rb
+++ b/lib/bundler/graph.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require "set"
module Bundler
class Graph
diff --git a/lib/bundler/index.rb b/lib/bundler/index.rb
index 5f54796fa2..9166a92738 100644
--- a/lib/bundler/index.rb
+++ b/lib/bundler/index.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require "set"
module Bundler
@@ -111,6 +112,13 @@ module Bundler
spec_sets.values.each(&blk)
end
sources.each {|s| s.each(&blk) }
+ self
+ end
+
+ def spec_names
+ names = specs.keys + sources.map(&:spec_names)
+ names.uniq!
+ names
end
# returns a list of the dependencies
@@ -191,14 +199,6 @@ module Bundler
end
end
- wants_prerelease = dependency.requirement.prerelease?
- wants_prerelease ||= base && base.any? {|base_spec| base_spec.version.prerelease? }
- only_prerelease = specs.all? {|spec| spec.version.prerelease? }
-
- unless wants_prerelease || only_prerelease
- found.reject! {|spec| spec.version.prerelease? }
- end
-
found
end
end
diff --git a/lib/bundler/injector.rb b/lib/bundler/injector.rb
index cba1b3d5e5..7fe6a91ddd 100644
--- a/lib/bundler/injector.rb
+++ b/lib/bundler/injector.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler
class Injector
def self.inject(new_deps, options = {})
@@ -12,38 +13,40 @@ module Bundler
end
def inject(gemfile_path, lockfile_path)
- if Bundler.settings[:frozen]
+ if Bundler.frozen?
# ensure the lock and Gemfile are synced
Bundler.definition.ensure_equivalent_gemfile_and_lockfile(true)
- # temporarily remove frozen while we inject
- frozen = Bundler.settings.delete(:frozen)
end
- # evaluate the Gemfile we have now
- builder = Dsl.new
- builder.eval_gemfile(gemfile_path)
+ # temporarily unfreeze
+ Bundler.settings.temporary(:deployment => false, :frozen => false) do
+ # evaluate the Gemfile we have now
+ builder = Dsl.new
+ builder.eval_gemfile(gemfile_path)
+
+ # don't inject any gems that are already in the Gemfile
+ @new_deps -= builder.dependencies
- # don't inject any gems that are already in the Gemfile
- @new_deps -= builder.dependencies
+ # add new deps to the end of the in-memory Gemfile
+ # Set conservative versioning to false because we want to let the resolver resolve the version first
+ builder.eval_gemfile("injected gems", build_gem_lines(false)) if @new_deps.any?
- # add new deps to the end of the in-memory Gemfile
- # Set conservative versioining to false because we want to let the resolver resolve the version first
- builder.eval_gemfile("injected gems", build_gem_lines(false)) if @new_deps.any?
+ # resolve to see if the new deps broke anything
+ @definition = builder.to_definition(lockfile_path, {})
+ @definition.resolve_remotely!
- # resolve to see if the new deps broke anything
- @definition = builder.to_definition(lockfile_path, {})
- @definition.resolve_remotely!
+ # since nothing broke, we can add those gems to the gemfile
+ append_to(gemfile_path, build_gem_lines(@options[:conservative_versioning])) if @new_deps.any?
- # since nothing broke, we can add those gems to the gemfile
- append_to(gemfile_path, build_gem_lines(@options[:conservative_versioning])) if @new_deps.any?
+ # since we resolved successfully, write out the lockfile
+ @definition.lock(Bundler.default_lockfile)
- # since we resolved successfully, write out the lockfile
- @definition.lock(Bundler.default_lockfile)
+ # invalidate the cached Bundler.definition
+ Bundler.reset_paths!
- # return an array of the deps that we added
- return @new_deps
- ensure
- Bundler.settings[:frozen] = "1" if frozen
+ # return an array of the deps that we added
+ @new_deps
+ end
end
private
diff --git a/lib/bundler/inline.rb b/lib/bundler/inline.rb
index 38dcda6b5b..9d25f3261a 100644
--- a/lib/bundler/inline.rb
+++ b/lib/bundler/inline.rb
@@ -1,4 +1,7 @@
# frozen_string_literal: true
+
+require "bundler/compatibility_guard"
+
# Allows for declaring a Gemfile inline in a ruby script, optionally installing
# any gems that aren't already installed on the user's system.
#
@@ -39,7 +42,7 @@ def gemfile(install = false, options = {}, &gemfile)
def Bundler.root
Bundler::SharedHelpers.pwd.expand_path
end
- ENV["BUNDLE_GEMFILE"] = "Gemfile"
+ Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", "Gemfile"
Bundler::Plugin.gemfile_install(&gemfile) if Bundler.feature_flag.plugins?
builder = Bundler::Dsl.new
@@ -50,12 +53,7 @@ def gemfile(install = false, options = {}, &gemfile)
definition.validate_runtime!
missing_specs = proc do
- begin
- !definition.missing_specs.empty?
- rescue Bundler::GemNotFound, Bundler::GitError
- definition.instance_variable_set(:@index, nil)
- true
- end
+ definition.missing_specs?
end
Bundler.ui = ui if install
diff --git a/lib/bundler/installer.rb b/lib/bundler/installer.rb
index bce0e46393..d1066c9c19 100644
--- a/lib/bundler/installer.rb
+++ b/lib/bundler/installer.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require "erb"
require "rubygems/dependency_installer"
require "bundler/worker"
@@ -33,25 +34,26 @@ module Bundler
# Runs the install procedures for a specific Gemfile.
#
- # Firstly, this method will check to see if Bundler.bundle_path exists
- # and if not then will create it. This is usually the location of gems
- # on the system, be it RVM or at a system path.
+ # Firstly, this method will check to see if `Bundler.bundle_path` exists
+ # and if not then Bundler will create the directory. This is usually the same
+ # location as RubyGems which typically is the `~/.gem` directory
+ # unless other specified.
#
- # Secondly, it checks if Bundler has been configured to be "frozen"
+ # Secondly, it checks if Bundler has been configured to be "frozen".
# Frozen ensures that the Gemfile and the Gemfile.lock file are matching.
# This stops a situation where a developer may update the Gemfile but may not run
# `bundle install`, which leads to the Gemfile.lock file not being correctly updated.
# If this file is not correctly updated then any other developer running
# `bundle install` will potentially not install the correct gems.
#
- # Thirdly, Bundler checks if there are any dependencies specified in the Gemfile using
- # Bundler::Environment#dependencies. If there are no dependencies specified then
- # Bundler returns a warning message stating so and this method returns.
+ # Thirdly, Bundler checks if there are any dependencies specified in the Gemfile.
+ # If there are no dependencies specified then Bundler returns a warning message stating
+ # so and this method returns.
#
- # Fourthly, Bundler checks if the default lockfile (Gemfile.lock) exists, and if so
- # then proceeds to set up a definition based on the default gemfile (Gemfile) and the
- # default lock file (Gemfile.lock). However, this is not the case if the platform is different
- # to that which is specified in Gemfile.lock, or if there are any missing specs for the gems.
+ # Fourthly, Bundler checks if the Gemfile.lock exists, and if so
+ # then proceeds to set up a definition based on the Gemfile and the Gemfile.lock.
+ # During this step Bundler will also download information about any new gems
+ # that are not in the Gemfile.lock and resolve any dependencies if needed.
#
# Fifthly, Bundler resolves the dependencies either through a cache of gems or by remote.
# This then leads into the gems being installed, along with stubs for their executables,
@@ -61,26 +63,36 @@ module Bundler
# Sixthly, a new Gemfile.lock is created from the installed gems to ensure that the next time
# that a user runs `bundle install` they will receive any updates from this process.
#
- # Finally: TODO add documentation for how the standalone process works.
+ # Finally, if the user has specified the standalone flag, Bundler will generate the needed
+ # require paths and save them in a `setup.rb` file. See `bundle standalone --help` for more
+ # information.
def run(options)
create_bundle_path
- if Bundler.settings[:frozen]
- @definition.ensure_equivalent_gemfile_and_lockfile(options[:deployment])
- end
+ ProcessLock.lock do
+ if Bundler.frozen?
+ @definition.ensure_equivalent_gemfile_and_lockfile(options[:deployment])
+ end
- if @definition.dependencies.empty?
- Bundler.ui.warn "The Gemfile specifies no dependencies"
- lock
- return
- end
+ if @definition.dependencies.empty?
+ Bundler.ui.warn "The Gemfile specifies no dependencies"
+ lock
+ return
+ end
- resolve_if_need(options)
- ensure_specs_are_compatible!
- install(options)
+ if resolve_if_needed(options)
+ ensure_specs_are_compatible!
+ warn_on_incompatible_bundler_deps
+ load_plugins
+ options.delete(:jobs)
+ else
+ options[:jobs] = 1 # to avoid the overhead of Bundler::Worker
+ end
+ install(options)
- lock unless Bundler.settings[:frozen]
- Standalone.new(options[:standalone], @definition).generate if options[:standalone]
+ lock unless Bundler.frozen?
+ Standalone.new(options[:standalone], @definition).generate if options[:standalone]
+ end
end
def generate_bundler_executable_stubs(spec, options = {})
@@ -101,15 +113,21 @@ module Bundler
end
# double-assignment to avoid warnings about variables that will be used by ERB
- bin_path = bin_path = Bundler.bin_path
- template = template = File.read(File.expand_path("../templates/Executable", __FILE__))
- relative_gemfile_path = relative_gemfile_path = Bundler.default_gemfile.relative_path_from(bin_path)
- ruby_command = ruby_command = Thor::Util.ruby_command
+ bin_path = Bundler.bin_path
+ bin_path = bin_path
+ relative_gemfile_path = Bundler.default_gemfile.relative_path_from(bin_path)
+ relative_gemfile_path = relative_gemfile_path
+ ruby_command = Thor::Util.ruby_command
+ ruby_command = ruby_command
+ template_path = File.expand_path("../templates/Executable", __FILE__)
+ if spec.name == "bundler"
+ template_path += ".bundler"
+ spec.executables = %(bundle)
+ end
+ template = File.read(template_path)
exists = []
spec.executables.each do |executable|
- next if executable == "bundle"
-
binstub_path = "#{bin_path}/#{executable}"
if File.exist?(binstub_path) && !options[:force]
exists << executable
@@ -139,13 +157,19 @@ module Bundler
def generate_standalone_bundler_executable_stubs(spec)
# double-assignment to avoid warnings about variables that will be used by ERB
bin_path = Bundler.bin_path
- standalone_path = standalone_path = Bundler.root.join(Bundler.settings[:path]).relative_path_from(bin_path)
+ unless path = Bundler.settings[:path]
+ raise "Can't standalone without an explicit path set"
+ end
+ standalone_path = Bundler.root.join(path).relative_path_from(bin_path)
+ standalone_path = standalone_path
template = File.read(File.expand_path("../templates/Executable.standalone", __FILE__))
- ruby_command = ruby_command = Thor::Util.ruby_command
+ ruby_command = Thor::Util.ruby_command
+ ruby_command = ruby_command
spec.executables.each do |executable|
next if executable == "bundle"
- executable_path = executable_path = Pathname(spec.full_gem_path).join(spec.bindir, executable).relative_path_from(bin_path)
+ executable_path = Pathname(spec.full_gem_path).join(spec.bindir, executable).relative_path_from(bin_path)
+ executable_path = executable_path
File.open "#{bin_path}/#{executable}", "w", 0o755 do |f|
f.puts ERB.new(template, nil, "-").result(binding)
end
@@ -159,13 +183,32 @@ module Bundler
# that said, it's a rare situation (other than rake), and parallel
# installation is SO MUCH FASTER. so we let people opt in.
def install(options)
- Bundler.rubygems.load_plugins
force = options["force"]
- jobs = 1
- jobs = [Bundler.settings[:jobs].to_i - 1, 1].max if can_install_in_parallel?
+ jobs = options.delete(:jobs) do
+ if can_install_in_parallel?
+ [Bundler.settings[:jobs].to_i - 1, 1].max
+ else
+ 1
+ end
+ end
install_in_parallel jobs, options[:standalone], force
end
+ def load_plugins
+ Bundler.rubygems.load_plugins
+
+ requested_path_gems = @definition.requested_specs.select {|s| s.source.is_a?(Source::Path) }
+ path_plugin_files = requested_path_gems.map do |spec|
+ begin
+ Bundler.rubygems.spec_matches_for_glob(spec, "rubygems_plugin#{Bundler.rubygems.suffix_pattern}")
+ rescue TypeError
+ error_message = "#{spec.name} #{spec.version} has an invalid gemspec"
+ raise Gem::InvalidSpecificationException, error_message
+ end
+ end.flatten
+ Bundler.rubygems.load_plugin_files(path_plugin_files)
+ end
+
def ensure_specs_are_compatible!
system_ruby = Bundler::RubyVersion.system
rubygems_version = Gem::Version.create(Gem::VERSION)
@@ -184,12 +227,28 @@ module Bundler
end
end
+ def warn_on_incompatible_bundler_deps
+ bundler_version = Gem::Version.create(Bundler::VERSION)
+ @definition.specs.each do |spec|
+ spec.dependencies.each do |dep|
+ next if dep.type == :development
+ next unless dep.name == "bundler".freeze
+ next if dep.requirement.satisfied_by?(bundler_version)
+
+ Bundler.ui.warn "#{spec.name} (#{spec.version}) has dependency" \
+ " #{SharedHelpers.pretty_dependency(dep)}" \
+ ", which is unsatisfied by the current bundler version #{VERSION}" \
+ ", so the dependency is being ignored"
+ end
+ end
+ end
+
def can_install_in_parallel?
if Bundler.rubygems.provides?(">= 2.1.0")
true
else
- Bundler.ui.warn "Rubygems #{Gem::VERSION} is not threadsafe, so your "\
- "gems will be installed one at a time. Upgrade to Rubygems 2.1.0 " \
+ Bundler.ui.warn "RubyGems #{Gem::VERSION} is not threadsafe, so your "\
+ "gems will be installed one at a time. Upgrade to RubyGems 2.1.0 " \
"or higher to enable parallel gem installation."
false
end
@@ -207,23 +266,18 @@ module Bundler
Bundler.mkdir_p(p)
end unless Bundler.bundle_path.exist?
rescue Errno::EEXIST
- raise PathError, "Could not install to path `#{Bundler.settings[:path]}` " \
+ raise PathError, "Could not install to path `#{Bundler.bundle_path}` " \
"because a file already exists at that path. Either remove or rename the file so the directory can be created."
end
- def resolve_if_need(options)
- if !options["update"] && !options["force"] && !Bundler.settings[:inline] && Bundler.default_lockfile.file?
- local = Bundler.ui.silence do
- begin
- tmpdef = Definition.build(Bundler.default_gemfile, Bundler.default_lockfile, nil)
- true unless tmpdef.new_platform? || tmpdef.missing_dependencies.any?
- rescue BundlerError
- end
- end
+ # returns whether or not a re-resolve was needed
+ def resolve_if_needed(options)
+ if !@definition.unlocking? && !options["force"] && !Bundler.settings[:inline] && Bundler.default_lockfile.file?
+ return false if @definition.nothing_changed? && !@definition.missing_specs?
end
- return if local
options["local"] ? @definition.resolve_with_cache! : @definition.resolve_remotely!
+ true
end
def lock(opts = {})
diff --git a/lib/bundler/installer/gem_installer.rb b/lib/bundler/installer/gem_installer.rb
index a4d9bcaa07..086b763d20 100644
--- a/lib/bundler/installer/gem_installer.rb
+++ b/lib/bundler/installer/gem_installer.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler
class GemInstaller
attr_reader :spec, :standalone, :worker, :force, :installer
@@ -65,6 +66,7 @@ module Bundler
end
def generate_executable_stubs
+ return if Bundler.feature_flag.forget_cli_options?
return if Bundler.settings[:inline]
if Bundler.settings[:bin] && standalone
installer.generate_standalone_bundler_executable_stubs(spec)
diff --git a/lib/bundler/installer/parallel_installer.rb b/lib/bundler/installer/parallel_installer.rb
index 97c124e015..95d9575c44 100644
--- a/lib/bundler/installer/parallel_installer.rb
+++ b/lib/bundler/installer/parallel_installer.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require "bundler/worker"
require "bundler/installer/gem_installer"
@@ -77,11 +78,6 @@ module Bundler
new(*args).call
end
- # Returns max number of threads machine can handle with a min of 1
- def self.max_threads
- [Bundler.settings[:jobs].to_i - 1, 1].max
- end
-
attr_reader :size
def initialize(installer, all_specs, size, standalone, force)
@@ -99,49 +95,19 @@ module Bundler
require "bundler/gem_remote_fetcher" if RUBY_VERSION < "1.9"
check_for_corrupt_lockfile
- enqueue_specs
- process_specs until @specs.all?(&:installed?) || @specs.any?(&:failed?)
+
+ if @size > 1
+ install_with_worker
+ else
+ install_serially
+ end
+
handle_error if @specs.any?(&:failed?)
@specs
ensure
worker_pool && worker_pool.stop
end
- def worker_pool
- @worker_pool ||= Bundler::Worker.new @size, "Parallel Installer", lambda { |spec_install, worker_num|
- gem_installer = Bundler::GemInstaller.new(
- spec_install.spec, @installer, @standalone, worker_num, @force
- )
- success, message = gem_installer.install_from_spec
- if success && !message.nil?
- spec_install.post_install_message = message
- elsif !success
- spec_install.state = :failed
- spec_install.error = "#{message}\n\n#{require_tree_for_spec(spec_install.spec)}"
- end
- spec_install
- }
- end
-
- # Dequeue a spec and save its post-install message and then enqueue the
- # remaining specs.
- # Some specs might've had to wait til this spec was installed to be
- # processed so the call to `enqueue_specs` is important after every
- # dequeue.
- def process_specs
- spec = worker_pool.deq
- spec.state = :installed unless spec.failed?
- enqueue_specs
- end
-
- def handle_error
- errors = @specs.select(&:failed?).map(&:error)
- if exception = errors.find {|e| e.is_a?(Bundler::BundlerError) }
- raise exception
- end
- raise Bundler::InstallError, errors.map(&:to_s).join("\n\n")
- end
-
def check_for_corrupt_lockfile
missing_dependencies = @specs.map do |s|
[
@@ -167,6 +133,71 @@ module Bundler
Bundler.ui.warn(warning.join("\n"))
end
+ private
+
+ def install_with_worker
+ enqueue_specs
+ process_specs until finished_installing?
+ end
+
+ def install_serially
+ until finished_installing?
+ raise "failed to find a spec to enqueue while installing serially" unless spec_install = @specs.find(&:ready_to_enqueue?)
+ spec_install.state = :enqueued
+ do_install(spec_install, 0)
+ end
+ end
+
+ def worker_pool
+ @worker_pool ||= Bundler::Worker.new @size, "Parallel Installer", lambda { |spec_install, worker_num|
+ do_install(spec_install, worker_num)
+ }
+ end
+
+ def do_install(spec_install, worker_num)
+ gem_installer = Bundler::GemInstaller.new(
+ spec_install.spec, @installer, @standalone, worker_num, @force
+ )
+ success, message = begin
+ gem_installer.install_from_spec
+ rescue => e
+ raise e, "#{e}\n\n#{require_tree_for_spec(spec_install.spec)}"
+ end
+ if success
+ spec_install.state = :installed
+ spec_install.post_install_message = message unless message.nil?
+ else
+ spec_install.state = :failed
+ spec_install.error = "#{message}\n\n#{require_tree_for_spec(spec_install.spec)}"
+ end
+ spec_install
+ end
+
+ # Dequeue a spec and save its post-install message and then enqueue the
+ # remaining specs.
+ # Some specs might've had to wait til this spec was installed to be
+ # processed so the call to `enqueue_specs` is important after every
+ # dequeue.
+ def process_specs
+ worker_pool.deq
+ enqueue_specs
+ end
+
+ def finished_installing?
+ @specs.all? do |spec|
+ return true if spec.failed?
+ spec.installed?
+ end
+ end
+
+ def handle_error
+ errors = @specs.select(&:failed?).map(&:error)
+ if exception = errors.find {|e| e.is_a?(Bundler::BundlerError) }
+ raise exception
+ end
+ raise Bundler::InstallError, errors.map(&:to_s).join("\n\n")
+ end
+
def require_tree_for_spec(spec)
tree = @spec_set.what_required(spec)
t = String.new("In #{File.basename(SharedHelpers.default_gemfile)}:\n")
diff --git a/lib/bundler/installer/standalone.rb b/lib/bundler/installer/standalone.rb
index 03411d85e2..ce0c9df1eb 100644
--- a/lib/bundler/installer/standalone.rb
+++ b/lib/bundler/installer/standalone.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler
class Standalone
def initialize(groups, definition)
diff --git a/lib/bundler/lazy_specification.rb b/lib/bundler/lazy_specification.rb
index 8d9a02c2b8..993952c23b 100644
--- a/lib/bundler/lazy_specification.rb
+++ b/lib/bundler/lazy_specification.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require "uri"
require "bundler/match_platform"
@@ -68,7 +69,7 @@ module Bundler
end
def __materialize__
- search_object = Bundler.settings[:specific_platform] || Bundler.settings[:force_ruby_platform] ? self : Dependency.new(name, version)
+ search_object = Bundler.feature_flag.specific_platform? || Bundler.settings[:force_ruby_platform] ? self : Dependency.new(name, version)
@specification = if source.is_a?(Source::Gemspec) && source.gemspec.name == name
source.gemspec.tap {|s| s.source = source }
else
diff --git a/lib/bundler/lockfile_generator.rb b/lib/bundler/lockfile_generator.rb
new file mode 100644
index 0000000000..585077d18d
--- /dev/null
+++ b/lib/bundler/lockfile_generator.rb
@@ -0,0 +1,95 @@
+# frozen_string_literal: true
+
+module Bundler
+ class LockfileGenerator
+ attr_reader :definition
+ attr_reader :out
+
+ # @private
+ def initialize(definition)
+ @definition = definition
+ @out = String.new
+ end
+
+ def self.generate(definition)
+ new(definition).generate!
+ end
+
+ def generate!
+ add_sources
+ add_platforms
+ add_dependencies
+ add_locked_ruby_version
+ add_bundled_with
+
+ out
+ end
+
+ private
+
+ def add_sources
+ definition.send(:sources).lock_sources.each_with_index do |source, idx|
+ out << "\n" unless idx.zero?
+
+ # Add the source header
+ out << source.to_lock
+
+ # Find all specs for this source
+ specs = definition.resolve.select {|s| source.can_lock?(s) }
+ add_specs(specs)
+ end
+ end
+
+ def add_specs(specs)
+ # This needs to be sorted by full name so that
+ # gems with the same name, but different platform
+ # are ordered consistently
+ specs.sort_by(&:full_name).each do |spec|
+ next if spec.name == "bundler".freeze
+ out << spec.to_lock
+ end
+ end
+
+ def add_platforms
+ add_section("PLATFORMS", definition.platforms)
+ end
+
+ def add_dependencies
+ out << "\nDEPENDENCIES\n"
+
+ handled = []
+ definition.dependencies.sort_by(&:to_s).each do |dep|
+ next if handled.include?(dep.name)
+ out << dep.to_lock
+ handled << dep.name
+ end
+ end
+
+ def add_locked_ruby_version
+ return unless locked_ruby_version = definition.locked_ruby_version
+ add_section("RUBY VERSION", locked_ruby_version.to_s)
+ end
+
+ def add_bundled_with
+ add_section("BUNDLED WITH", definition.locked_bundler_version.to_s)
+ end
+
+ def add_section(name, value)
+ out << "\n#{name}\n"
+ case value
+ when Array
+ value.map(&:to_s).sort.each do |val|
+ out << " #{val}\n"
+ end
+ when Hash
+ value.to_a.sort_by {|k, _| k.to_s }.each do |key, val|
+ out << " #{key}: #{val}\n"
+ end
+ when String
+ out << " #{value}\n"
+ else
+ raise ArgumentError, "#{value.inspect} can't be serialized in a lockfile"
+ end
+ end
+ end
+end
diff --git a/lib/bundler/lockfile_parser.rb b/lib/bundler/lockfile_parser.rb
index dbf8926690..ff706fca1d 100644
--- a/lib/bundler/lockfile_parser.rb
+++ b/lib/bundler/lockfile_parser.rb
@@ -90,7 +90,7 @@ module Bundler
send("parse_#{@state}", line)
end
end
- @sources << @rubygems_aggregate
+ @sources << @rubygems_aggregate unless Bundler.feature_flag.lockfile_uses_separate_rubygems_sources?
@specs = @specs.values.sort_by(&:identifier)
warn_for_outdated_bundler_version
rescue ArgumentError => e
@@ -141,10 +141,16 @@ module Bundler
@sources << @current_source
end
when GEM
- Array(@opts["remote"]).each do |url|
- @rubygems_aggregate.add_remote(url)
+ if Bundler.feature_flag.lockfile_uses_separate_rubygems_sources?
+ @opts["remotes"] = @opts.delete("remote")
+ @current_source = TYPES[@type].from_lock(@opts)
+ @sources << @current_source
+ else
+ Array(@opts["remote"]).each do |url|
+ @rubygems_aggregate.add_remote(url)
+ end
+ @current_source = @rubygems_aggregate
end
- @current_source = @rubygems_aggregate
when PLUGIN
@current_source = Plugin.source_from_lock(@opts)
@sources << @current_source
diff --git a/lib/bundler/match_platform.rb b/lib/bundler/match_platform.rb
index 050cd0efd3..56cbbfb95d 100644
--- a/lib/bundler/match_platform.rb
+++ b/lib/bundler/match_platform.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require "bundler/gem_helpers"
module Bundler
diff --git a/lib/bundler/mirror.rb b/lib/bundler/mirror.rb
index 97a6776adb..a6fa070eb8 100644
--- a/lib/bundler/mirror.rb
+++ b/lib/bundler/mirror.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require "socket"
module Bundler
@@ -37,7 +38,7 @@ module Bundler
mirror = if config.all?
@all
else
- (@mirrors[config.uri] = @mirrors[config.uri] || Mirror.new)
+ @mirrors[config.uri] ||= Mirror.new
end
config.update_mirror(mirror)
end
@@ -45,7 +46,9 @@ module Bundler
private
def fetch_valid_mirror_for(uri)
- mirror = (@mirrors[URI(uri.to_s.downcase)] || @mirrors[URI(uri.to_s).host] || Mirror.new(uri)).validate!(@prober)
+ downcased = uri.to_s.downcase
+ mirror = @mirrors[downcased] || @mirrors[URI(downcased).host] || Mirror.new(uri)
+ mirror.validate!(@prober)
mirror = Mirror.new(uri) unless mirror.valid?
mirror
end
@@ -117,7 +120,7 @@ module Bundler
def initialize(config_line, value)
uri, fallback =
- config_line.match(%r{^mirror\.(all|.+?)(\.fallback_timeout)?\/?$}).captures
+ config_line.match(%r{\Amirror\.(all|.+?)(\.fallback_timeout)?\/?\z}).captures
@fallback = !fallback.nil?
@all = false
if uri == "all"
diff --git a/lib/bundler/plugin.rb b/lib/bundler/plugin.rb
index 66f485ef8e..99c9a867b0 100644
--- a/lib/bundler/plugin.rb
+++ b/lib/bundler/plugin.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require "bundler/plugin/api"
module Bundler
diff --git a/lib/bundler/plugin/api/source.rb b/lib/bundler/plugin/api/source.rb
index 5d3f58df92..586477efb5 100644
--- a/lib/bundler/plugin/api/source.rb
+++ b/lib/bundler/plugin/api/source.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
+
require "uri"
-require "digest/sha1"
module Bundler
module Plugin
@@ -271,7 +271,7 @@ module Bundler
end
def uri_hash
- Digest::SHA1.hexdigest(uri)
+ SharedHelpers.digest(:SHA1).hexdigest(uri)
end
# Note: Do not override if you don't know what you are doing.
@@ -293,6 +293,13 @@ module Bundler
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)
+ # overriding it)
+ def double_check_for(*); end
end
end
end
diff --git a/lib/bundler/plugin/installer.rb b/lib/bundler/plugin/installer.rb
index a50d0ceedd..5379c38979 100644
--- a/lib/bundler/plugin/installer.rb
+++ b/lib/bundler/plugin/installer.rb
@@ -13,12 +13,13 @@ module Bundler
def install(names, options)
version = options[:version] || [">= 0"]
-
- if options[:git]
- install_git(names, version, options)
- else
- sources = options[:source] || Bundler.rubygems.sources
- install_rubygems(names, version, sources)
+ 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
end
end
diff --git a/lib/bundler/plugin/source_list.rb b/lib/bundler/plugin/source_list.rb
index 33f5e5afbd..f0e212205f 100644
--- a/lib/bundler/plugin/source_list.rb
+++ b/lib/bundler/plugin/source_list.rb
@@ -5,13 +5,6 @@ module Bundler
# approptiate options to be used with Source classes for plugin installation
module Plugin
class SourceList < Bundler::SourceList
- def initialize
- @path_sources = []
- @git_sources = []
- @rubygems_aggregate = Plugin::Installer::Rubygems.new
- @rubygems_sources = []
- end
-
def add_git_source(options = {})
add_source_to_list Plugin::Installer::Git.new(options), git_sources
end
@@ -21,7 +14,13 @@ module Bundler
end
def all_sources
- path_sources + git_sources + rubygems_sources
+ path_sources + git_sources + rubygems_sources + [metadata_source]
+ end
+
+ private
+
+ def rubygems_aggregate_class
+ Plugin::Installer::Rubygems
end
end
end
diff --git a/lib/bundler/process_lock.rb b/lib/bundler/process_lock.rb
new file mode 100644
index 0000000000..4bd6931577
--- /dev/null
+++ b/lib/bundler/process_lock.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Bundler
+ class ProcessLock
+ def self.lock(bundle_path = Bundler.bundle_path)
+ lock_file_path = File.join(bundle_path, "bundler.lock")
+ has_lock = false
+
+ File.open(lock_file_path, "w") do |f|
+ f.flock(File::LOCK_EX)
+ has_lock = true
+ yield
+ f.flock(File::LOCK_UN)
+ end
+ rescue Errno::EACCES, Errno::ENOLCK
+ # In the case the user does not have access to
+ # create the lock file or is using NFS where
+ # locks are not available we skip locking.
+ yield
+ ensure
+ FileUtils.rm_f(lock_file_path) if has_lock
+ end
+ end
+end
diff --git a/lib/bundler/psyched_yaml.rb b/lib/bundler/psyched_yaml.rb
index 69d2ae78c5..e654416a5a 100644
--- a/lib/bundler/psyched_yaml.rb
+++ b/lib/bundler/psyched_yaml.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
# Psych could be a gem, so try to ask for it
begin
gem "psych"
@@ -25,3 +26,12 @@ module Bundler
YamlLibrarySyntaxError = ::ArgumentError
end
end
+
+require "bundler/deprecate"
+begin
+ Bundler::Deprecate.skip_during do
+ require "rubygems/safe_yaml"
+ end
+rescue LoadError
+ # it's OK if the file isn't there
+end
diff --git a/lib/bundler/remote_specification.rb b/lib/bundler/remote_specification.rb
index 208ee1d4b7..23e1234330 100644
--- a/lib/bundler/remote_specification.rb
+++ b/lib/bundler/remote_specification.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require "uri"
module Bundler
diff --git a/lib/bundler/resolver.rb b/lib/bundler/resolver.rb
index db2ae496a4..ddc1d702e0 100644
--- a/lib/bundler/resolver.rb
+++ b/lib/bundler/resolver.rb
@@ -1,178 +1,9 @@
# frozen_string_literal: true
+
module Bundler
class Resolver
require "bundler/vendored_molinillo"
-
- class Molinillo::VersionConflict
- def printable_dep(dep)
- if dep.is_a?(Bundler::Dependency)
- DepProxy.new(dep, dep.platforms.join(", ")).to_s.strip
- else
- dep.to_s
- end
- end
-
- def message
- conflicts.sort.reduce(String.new) do |o, (name, conflict)|
- o << %(\nBundler could not find compatible versions for gem "#{name}":\n)
- if conflict.locked_requirement
- o << %( In snapshot (#{Bundler.default_lockfile.basename}):\n)
- o << %( #{printable_dep(conflict.locked_requirement)}\n)
- o << %(\n)
- end
- o << %( In Gemfile:\n)
- trees = conflict.requirement_trees
-
- maximal = 1.upto(trees.size).map do |size|
- trees.map(&:last).flatten(1).combination(size).to_a
- end.flatten(1).select do |deps|
- Bundler::VersionRanges.empty?(*Bundler::VersionRanges.for_many(deps.map(&:requirement)))
- end.min_by(&:size)
- trees.reject! {|t| !maximal.include?(t.last) } if maximal
-
- o << trees.sort_by {|t| t.reverse.map(&:name) }.map do |tree|
- t = String.new
- depth = 2
- tree.each do |req|
- t << " " * depth << req.to_s
- unless tree.last == req
- if spec = conflict.activated_by_name[req.name]
- t << %( was resolved to #{spec.version}, which)
- end
- t << %( depends on)
- end
- t << %(\n)
- depth += 1
- end
- t
- end.join("\n")
-
- if name == "bundler"
- o << %(\n Current Bundler version:\n bundler (#{Bundler::VERSION}))
- other_bundler_required = !conflict.requirement.requirement.satisfied_by?(Gem::Version.new Bundler::VERSION)
- end
-
- if name == "bundler" && other_bundler_required
- o << "\n"
- o << "This Gemfile requires a different version of Bundler.\n"
- o << "Perhaps you need to update Bundler by running `gem install bundler`?\n"
- end
- if conflict.locked_requirement
- o << "\n"
- o << %(Running `bundle update` will rebuild your snapshot from scratch, using only\n)
- o << %(the gems in your Gemfile, which may resolve the conflict.\n)
- elsif !conflict.existing
- o << "\n"
- if conflict.requirement_trees.first.size > 1
- o << "Could not find gem '#{conflict.requirement}', which is required by "
- o << "gem '#{conflict.requirement_trees.first[-2]}', in any of the sources."
- else
- o << "Could not find gem '#{conflict.requirement}' in any of the sources\n"
- end
- end
- o
- end.strip
- end
- end
-
- class SpecGroup < Array
- include GemHelpers
-
- attr_reader :activated
-
- def initialize(a)
- super
- @required_by = []
- @activated_platforms = []
- @dependencies = nil
- @specs = Hash.new do |specs, platform|
- specs[platform] = select_best_platform_match(self, platform)
- end
- end
-
- def initialize_copy(o)
- super
- @activated_platforms = o.activated.dup
- end
-
- def to_specs
- @activated_platforms.map do |p|
- next unless s = @specs[p]
- lazy_spec = LazySpecification.new(name, version, s.platform, source)
- lazy_spec.dependencies.replace s.dependencies
- lazy_spec
- end.compact
- end
-
- def activate_platform!(platform)
- return unless for?(platform)
- return if @activated_platforms.include?(platform)
- @activated_platforms << platform
- end
-
- def name
- @name ||= first.name
- end
-
- def version
- @version ||= first.version
- end
-
- def source
- @source ||= first.source
- end
-
- def for?(platform)
- spec = @specs[platform]
- !spec.nil?
- end
-
- def to_s
- "#{name} (#{version})"
- end
-
- def dependencies_for_activated_platforms
- dependencies = @activated_platforms.map {|p| __dependencies[p] }
- metadata_dependencies = @activated_platforms.map do |platform|
- metadata_dependencies(@specs[platform], platform)
- end
- dependencies.concat(metadata_dependencies).flatten
- end
-
- def platforms_for_dependency_named(dependency)
- __dependencies.select {|_, deps| deps.map(&:name).include? dependency }.keys
- end
-
- private
-
- def __dependencies
- @dependencies = Hash.new do |dependencies, platform|
- dependencies[platform] = []
- if spec = @specs[platform]
- spec.dependencies.each do |dep|
- next if dep.type == :development
- dependencies[platform] << DepProxy.new(dep, platform)
- end
- end
- dependencies[platform]
- end
- end
-
- def metadata_dependencies(spec, platform)
- return [] unless spec
- # Only allow endpoint specifications since they won't hit the network to
- # fetch the full gemspec when calling required_ruby_version
- return [] if !spec.is_a?(EndpointSpecification) && !spec.is_a?(Gem::Specification)
- dependencies = []
- if !spec.required_ruby_version.nil? && !spec.required_ruby_version.none?
- dependencies << DepProxy.new(Gem::Dependency.new("ruby\0", spec.required_ruby_version), platform)
- end
- if !spec.required_rubygems_version.nil? && !spec.required_rubygems_version.none?
- dependencies << DepProxy.new(Gem::Dependency.new("rubygems\0", spec.required_rubygems_version), platform)
- end
- dependencies
- end
- end
+ require "bundler/resolver/spec_group"
# Figures out the best possible configuration of gems that satisfies
# the list of passed dependencies and any child dependencies without
@@ -206,16 +37,22 @@ module Bundler
additional_base_requirements.each {|d| @base_dg.add_vertex(d.name, d) }
@platforms = platforms
@gem_version_promoter = gem_version_promoter
+ @allow_bundler_dependency_conflicts = Bundler.feature_flag.allow_bundler_dependency_conflicts?
+ @lockfile_uses_separate_rubygems_sources = Bundler.feature_flag.lockfile_uses_separate_rubygems_sources?
end
def start(requirements)
+ @prerelease_specified = {}
+ requirements.each {|dep| @prerelease_specified[dep.name] ||= dep.prerelease? }
+
verify_gemfile_dependencies_are_found!(requirements)
dg = @resolver.resolve(requirements, @base_dg)
dg.map(&:payload).
reject {|sg| sg.name.end_with?("\0") }.
map(&:to_specs).flatten
rescue Molinillo::VersionConflict => e
- raise VersionConflict.new(e.conflicts.keys.uniq, e.message)
+ message = version_conflict_message(e)
+ raise VersionConflict.new(e.conflicts.keys.uniq, message)
rescue Molinillo::CircularDependencyError => e
names = e.dependencies.sort_by(&:name).map {|d| "gem '#{d.name}'" }
raise CyclicDependencyError, "Your bundle requires gems that depend" \
@@ -266,6 +103,14 @@ module Bundler
search = @search_for[dependency] ||= begin
index = index_for(dependency)
results = index.search(dependency, @base[dependency.name])
+
+ unless @prerelease_specified[dependency.name]
+ # Move prereleases to the beginning of the list, so they're considered
+ # last during resolution.
+ pre, results = results.partition {|spec| spec.version.prerelease? }
+ results = pre + results
+ end
+
if vertex = @base_dg.vertex_named(dependency.name)
locked_requirement = vertex.payload.requirement
end
@@ -281,7 +126,9 @@ module Bundler
end
nested.reduce([]) do |groups, (version, specs)|
next groups if locked_requirement && !locked_requirement.satisfied_by?(version)
- groups << SpecGroup.new(specs)
+ spec_group = SpecGroup.new(specs)
+ spec_group.ignores_bundler_dependencies = @allow_bundler_dependency_conflicts
+ groups << spec_group
end
else
[]
@@ -298,7 +145,20 @@ module Bundler
end
def index_for(dependency)
- @source_requirements[dependency.name] || @index
+ source = @source_requirements[dependency.name]
+ if source
+ source.specs
+ elsif @lockfile_uses_separate_rubygems_sources
+ Index.build do |idx|
+ if dependency.all_sources
+ dependency.all_sources.each {|s| idx.add_source(s.specs) if s }
+ else
+ idx.add_source @source_requirements[:default].specs
+ end
+ end
+ else
+ @index
+ end
end
def name_for(dependency)
@@ -319,23 +179,53 @@ module Bundler
def requirement_satisfied_by?(requirement, activated, spec)
return false unless requirement.matches_spec?(spec) || spec.source.is_a?(Source::Gemspec)
+ if spec.version.prerelease? && !requirement.prerelease? && search_for(requirement).any? {|sg| !sg.version.prerelease? }
+ vertex = activated.vertex_named(spec.name)
+ return false if vertex.requirements.none?(&:prerelease?)
+ end
spec.activate_platform!(requirement.__platform) if !@platforms || @platforms.include?(requirement.__platform)
true
end
+ def relevant_sources_for_vertex(vertex)
+ if vertex.root?
+ [@source_requirements[vertex.name]]
+ elsif @lockfile_uses_separate_rubygems_sources
+ vertex.recursive_predecessors.map do |v|
+ @source_requirements[v.name]
+ end << @source_requirements[:default]
+ end
+ end
+
def sort_dependencies(dependencies, activated, conflicts)
dependencies.sort_by do |dependency|
+ dependency.all_sources = relevant_sources_for_vertex(activated.vertex_named(dependency.name))
name = name_for(dependency)
+ vertex = activated.vertex_named(name)
[
@base_dg.vertex_named(name) ? 0 : 1,
- activated.vertex_named(name).payload ? 0 : 1,
+ vertex.payload ? 0 : 1,
+ vertex.root? ? 0 : 1,
amount_constrained(dependency),
conflicts[name] ? 0 : 1,
- activated.vertex_named(name).payload ? 0 : search_for(dependency).count,
+ vertex.payload ? 0 : search_for(dependency).count,
+ self.class.platform_sort_key(dependency.__platform),
]
end
end
+ # Sort platforms from most general to most specific
+ def self.sort_platforms(platforms)
+ platforms.sort_by do |platform|
+ platform_sort_key(platform)
+ end
+ end
+
+ def self.platform_sort_key(platform)
+ return ["", "", ""] if Gem::Platform::RUBY == platform
+ platform.to_a.map {|part| part || "" }
+ end
+
private
# returns an integer \in (-\infty, 0]
@@ -364,32 +254,34 @@ module Bundler
def verify_gemfile_dependencies_are_found!(requirements)
requirements.each do |requirement|
- next if requirement.name == "bundler"
+ name = requirement.name
+ next if name == "bundler"
next unless search_for(requirement).empty?
- if (base = @base[requirement.name]) && !base.empty?
+
+ cache_message = begin
+ " or in gems cached in #{Bundler.settings.app_cache_path}" if Bundler.app_cache.exist?
+ rescue GemfileNotFound
+ nil
+ end
+
+ if (base = @base[name]) && !base.empty?
version = base.first.version
message = "You have requested:\n" \
- " #{requirement.name} #{requirement.requirement}\n\n" \
- "The bundle currently has #{requirement.name} locked at #{version}.\n" \
- "Try running `bundle update #{requirement.name}`\n\n" \
+ " #{name} #{requirement.requirement}\n\n" \
+ "The bundle currently has #{name} locked at #{version}.\n" \
+ "Try running `bundle update #{name}`\n\n" \
"If you are updating multiple gems in your Gemfile at once,\n" \
"try passing them all to `bundle update`"
- elsif requirement.source
- name = requirement.name
- specs = @source_requirements[name][name]
+ elsif source = @source_requirements[name]
+ specs = source.specs[name]
versions_with_platforms = specs.map {|s| [s.version, s.platform] }
- message = String.new("Could not find gem '#{requirement}' in #{requirement.source}.\n")
+ message = String.new("Could not find gem '#{SharedHelpers.pretty_dependency(requirement)}' in #{source}#{cache_message}.\n")
message << if versions_with_platforms.any?
- "Source contains '#{name}' at: #{formatted_versions_with_platforms(versions_with_platforms)}"
+ "The source contains '#{name}' at: #{formatted_versions_with_platforms(versions_with_platforms)}"
else
- "Source does not contain any versions of '#{requirement}'"
+ "The source does not contain any versions of '#{name}'"
end
else
- cache_message = begin
- " or in gems cached in #{Bundler.settings.app_cache_path}" if Bundler.app_cache.exist?
- rescue GemfileNotFound
- nil
- end
message = "Could not find gem '#{requirement}' in any of the gem sources " \
"listed in your Gemfile#{cache_message}."
end
@@ -402,9 +294,76 @@ module Bundler
version = vwp.first
platform = vwp.last
version_platform_str = String.new(version.to_s)
- version_platform_str << " #{platform}" unless platform.nil?
+ version_platform_str << " #{platform}" unless platform.nil? || platform == Gem::Platform::RUBY
+ version_platform_str
end
version_platform_strs.join(", ")
end
+
+ def version_conflict_message(e)
+ e.message_with_trees(
+ :solver_name => "Bundler",
+ :possibility_type => "gem",
+ :reduce_trees => lambda do |trees|
+ maximal = 1.upto(trees.size).map do |size|
+ trees.map(&:last).flatten(1).combination(size).to_a
+ end.flatten(1).select do |deps|
+ Bundler::VersionRanges.empty?(*Bundler::VersionRanges.for_many(deps.map(&:requirement)))
+ end.min_by(&:size)
+ trees.reject! {|t| !maximal.include?(t.last) } if maximal
+
+ trees = trees.sort_by {|t| t.flatten.map(&:to_s) }
+ trees.uniq! {|t| t.flatten.map {|dep| [dep.name, dep.requirement] } }
+
+ trees.sort_by {|t| t.reverse.map(&:name) }
+ end,
+ :printable_requirement => lambda {|req| SharedHelpers.pretty_dependency(req) },
+ :additional_message_for_conflict => lambda do |o, name, conflict|
+ if name == "bundler"
+ o << %(\n Current Bundler version:\n bundler (#{Bundler::VERSION}))
+ other_bundler_required = !conflict.requirement.requirement.satisfied_by?(Gem::Version.new Bundler::VERSION)
+ end
+
+ if name == "bundler" && other_bundler_required
+ o << "\n"
+ o << "This Gemfile requires a different version of Bundler.\n"
+ o << "Perhaps you need to update Bundler by running `gem install bundler`?\n"
+ end
+ if conflict.locked_requirement
+ o << "\n"
+ o << %(Running `bundle update` will rebuild your snapshot from scratch, using only\n)
+ o << %(the gems in your Gemfile, which may resolve the conflict.\n)
+ elsif !conflict.existing
+ o << "\n"
+
+ relevant_sources = if conflict.requirement.source
+ [conflict.requirement.source]
+ elsif conflict.requirement.all_sources
+ conflict.requirement.all_sources
+ elsif @lockfile_uses_separate_rubygems_sources
+ # every conflict should have an explicit group of sources when we
+ # enforce strict pinning
+ raise "no source set for #{conflict}"
+ else
+ []
+ end.compact.map(&:to_s).uniq.sort
+
+ o << "Could not find gem '#{SharedHelpers.pretty_dependency(conflict.requirement)}'"
+ if conflict.requirement_trees.first.size > 1
+ o << ", which is required by "
+ o << "gem '#{SharedHelpers.pretty_dependency(conflict.requirement_trees.first[-2])}',"
+ end
+ o << " "
+
+ o << if relevant_sources.empty?
+ "in any of the sources.\n"
+ else
+ "in any of the relevant sources:\n #{relevant_sources * "\n "}\n"
+ end
+ end
+ end,
+ :version_for_spec => lambda {|spec| spec.version }
+ )
+ end
end
end
diff --git a/lib/bundler/resolver/spec_group.rb b/lib/bundler/resolver/spec_group.rb
new file mode 100644
index 0000000000..9c10a4b733
--- /dev/null
+++ b/lib/bundler/resolver/spec_group.rb
@@ -0,0 +1,111 @@
+# frozen_string_literal: true
+
+module Bundler
+ class Resolver
+ class SpecGroup
+ include GemHelpers
+
+ attr_accessor :name, :version, :source
+ attr_accessor :ignores_bundler_dependencies
+
+ def initialize(all_specs)
+ raise ArgumentError, "cannot initialize with an empty value" unless exemplary_spec = all_specs.first
+ @name = exemplary_spec.name
+ @version = exemplary_spec.version
+ @source = exemplary_spec.source
+
+ @required_by = []
+ @activated_platforms = []
+ @dependencies = nil
+ @specs = Hash.new do |specs, platform|
+ specs[platform] = select_best_platform_match(all_specs, platform)
+ end
+ @ignores_bundler_dependencies = true
+ end
+
+ def to_specs
+ @activated_platforms.map do |p|
+ next unless s = @specs[p]
+ lazy_spec = LazySpecification.new(name, version, s.platform, source)
+ lazy_spec.dependencies.replace s.dependencies
+ lazy_spec
+ end.compact
+ end
+
+ def activate_platform!(platform)
+ return unless for?(platform)
+ return if @activated_platforms.include?(platform)
+ @activated_platforms << platform
+ end
+
+ def for?(platform)
+ spec = @specs[platform]
+ !spec.nil?
+ end
+
+ def to_s
+ @to_s ||= "#{name} (#{version})"
+ end
+
+ def dependencies_for_activated_platforms
+ dependencies = @activated_platforms.map {|p| __dependencies[p] }
+ metadata_dependencies = @activated_platforms.map do |platform|
+ metadata_dependencies(@specs[platform], platform)
+ end
+ dependencies.concat(metadata_dependencies).flatten
+ end
+
+ def platforms_for_dependency_named(dependency)
+ __dependencies.select {|_, deps| deps.map(&:name).include? dependency }.keys
+ end
+
+ def ==(other)
+ return unless other.is_a?(SpecGroup)
+ name == other.name &&
+ version == other.version &&
+ source == other.source
+ end
+
+ def eql?(other)
+ name.eql?(other.name) &&
+ version.eql?(other.version) &&
+ source.eql?(other.source)
+ end
+
+ def hash
+ to_s.hash ^ source.hash
+ end
+
+ private
+
+ def __dependencies
+ @dependencies = Hash.new do |dependencies, platform|
+ dependencies[platform] = []
+ if spec = @specs[platform]
+ spec.dependencies.each do |dep|
+ next if dep.type == :development
+ next if @ignores_bundler_dependencies && dep.name == "bundler".freeze
+ dependencies[platform] << DepProxy.new(dep, platform)
+ end
+ end
+ dependencies[platform]
+ end
+ end
+
+ def metadata_dependencies(spec, platform)
+ return [] unless spec
+ # Only allow endpoint specifications since they won't hit the network to
+ # fetch the full gemspec when calling required_ruby_version
+ return [] if !spec.is_a?(EndpointSpecification) && !spec.is_a?(Gem::Specification)
+ dependencies = []
+ if !spec.required_ruby_version.nil? && !spec.required_ruby_version.none?
+ dependencies << DepProxy.new(Gem::Dependency.new("ruby\0", spec.required_ruby_version), platform)
+ end
+ if !spec.required_rubygems_version.nil? && !spec.required_rubygems_version.none?
+ dependencies << DepProxy.new(Gem::Dependency.new("rubygems\0", spec.required_rubygems_version), platform)
+ end
+ dependencies
+ end
+ end
+ end
+end
diff --git a/lib/bundler/retry.rb b/lib/bundler/retry.rb
index 092fb866b3..244606dcc9 100644
--- a/lib/bundler/retry.rb
+++ b/lib/bundler/retry.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler
# General purpose class for retrying code that may fail
class Retry
diff --git a/lib/bundler/ruby_dsl.rb b/lib/bundler/ruby_dsl.rb
index a410b7f3d7..f6ba220cd5 100644
--- a/lib/bundler/ruby_dsl.rb
+++ b/lib/bundler/ruby_dsl.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler
module RubyDsl
def ruby(*ruby_version)
diff --git a/lib/bundler/ruby_version.rb b/lib/bundler/ruby_version.rb
index f0a001d296..d4e1bdbfd5 100644
--- a/lib/bundler/ruby_version.rb
+++ b/lib/bundler/ruby_version.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler
class RubyVersion
attr_reader :versions,
diff --git a/lib/bundler/rubygems_ext.rb b/lib/bundler/rubygems_ext.rb
index a0f8fa848b..e9f0eac355 100644
--- a/lib/bundler/rubygems_ext.rb
+++ b/lib/bundler/rubygems_ext.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require "pathname"
if defined?(Gem::QuickLoader)
@@ -14,7 +15,7 @@ begin
# shouldn't be deferred.
require "rubygems/source"
rescue LoadError
- # Not available before Rubygems 2.0.0, ignore
+ # Not available before RubyGems 2.0.0, ignore
nil
end
@@ -135,7 +136,7 @@ module Gem
end
class Dependency
- attr_accessor :source, :groups
+ attr_accessor :source, :groups, :all_sources
alias_method :eql?, :==
@@ -146,7 +147,7 @@ module Gem
end
def to_yaml_properties
- instance_variables.reject {|p| ["@source", "@groups"].include?(p.to_s) }
+ instance_variables.reject {|p| ["@source", "@groups", "@all_sources"].include?(p.to_s) }
end
def to_lock
@@ -158,7 +159,7 @@ module Gem
out
end
- # Backport of performance enhancement added to Rubygems 1.4
+ # Backport of performance enhancement added to RubyGems 1.4
def matches_spec?(spec)
# name can be a Regexp, so use ===
return false unless name === spec.name
diff --git a/lib/bundler/rubygems_gem_installer.rb b/lib/bundler/rubygems_gem_installer.rb
index 977e13d948..2b7fa8e0f6 100644
--- a/lib/bundler/rubygems_gem_installer.rb
+++ b/lib/bundler/rubygems_gem_installer.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require "rubygems/installer"
module Bundler
@@ -17,6 +18,28 @@ module Bundler
super && validate_bundler_checksum(options[:bundler_expected_checksum])
end
+ def build_extensions
+ extension_cache_path = options[:bundler_extension_cache_path]
+ return super unless extension_cache_path && extension_dir = Bundler.rubygems.spec_extension_dir(spec)
+
+ extension_dir = Pathname.new(extension_dir)
+ build_complete = SharedHelpers.filesystem_access(extension_cache_path.join("gem.build_complete"), :read, &:file?)
+ if build_complete && !options[:force]
+ SharedHelpers.filesystem_access(extension_dir.parent, &:mkpath)
+ SharedHelpers.filesystem_access(extension_cache_path) do
+ FileUtils.cp_r extension_cache_path, spec.extension_dir
+ end
+ else
+ super
+ if extension_dir.directory? # not made for gems without extensions
+ SharedHelpers.filesystem_access(extension_cache_path.parent, &:mkpath)
+ SharedHelpers.filesystem_access(extension_cache_path) do
+ FileUtils.cp_r extension_dir, extension_cache_path
+ end
+ end
+ end
+ end
+
private
def validate_bundler_checksum(checksum)
@@ -25,7 +48,7 @@ module Bundler
return true unless source = @package.instance_variable_get(:@gem)
return true unless source.respond_to?(:with_read_io)
digest = source.with_read_io do |io|
- digest = Digest::SHA256.new
+ digest = SharedHelpers.digest(:SHA256).new
digest << io.read(16_384) until io.eof?
io.rewind
send(checksum_type(checksum), digest)
diff --git a/lib/bundler/rubygems_integration.rb b/lib/bundler/rubygems_integration.rb
index c3e16e086c..0f16b6231d 100644
--- a/lib/bundler/rubygems_integration.rb
+++ b/lib/bundler/rubygems_integration.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require "monitor"
require "rubygems"
require "rubygems/config_file"
@@ -84,6 +85,19 @@ module Bundler
spec.respond_to?(:default_gem?) && spec.default_gem?
end
+ def spec_matches_for_glob(spec, glob)
+ return spec.matches_for_glob(glob) if spec.respond_to?(:matches_for_glob)
+
+ spec.load_paths.map do |lp|
+ Dir["#{lp}/#{glob}#{suffix_pattern}"]
+ end.flatten(1)
+ end
+
+ def spec_extension_dir(spec)
+ return unless spec.respond_to?(:extension_dir)
+ spec.extension_dir
+ end
+
def stub_set_spec(stub, spec)
stub.instance_variable_set(:@spec, spec)
end
@@ -158,6 +172,10 @@ module Bundler
Gem.post_reset_hooks
end
+ def suffix_pattern
+ Gem.suffix_pattern
+ end
+
def gem_cache
gem_path.map {|p| File.expand_path("cache", p) }
end
@@ -165,7 +183,7 @@ module Bundler
def spec_cache_dirs
@spec_cache_dirs ||= begin
dirs = gem_path.map {|dir| File.join(dir, "specifications") }
- dirs << Gem.spec_cache_dir if Gem.respond_to?(:spec_cache_dir) # Not in Rubygems 2.0.3 or earlier
+ dirs << Gem.spec_cache_dir if Gem.respond_to?(:spec_cache_dir) # Not in RubyGems 2.0.3 or earlier
dirs.uniq.select {|dir| File.directory? dir }
end
end
@@ -179,7 +197,7 @@ module Bundler
end
def repository_subdirectories
- %w(cache doc gems specifications)
+ %w[cache doc gems specifications]
end
def clear_paths
@@ -190,8 +208,12 @@ module Bundler
Gem.bin_path(gem, bin, ver)
end
+ def path_separator
+ File::PATH_SEPARATOR
+ end
+
def preserve_paths
- # this is a no-op outside of Rubygems 1.8
+ # this is a no-op outside of RubyGems 1.8
yield
end
@@ -212,6 +234,10 @@ module Bundler
Gem.load_plugins if Gem.respond_to?(:load_plugins)
end
+ def load_plugin_files(files)
+ Gem.load_plugin_files(files) if Gem.respond_to?(:load_plugin_files)
+ end
+
def ui=(obj)
Gem::DefaultUserInteraction.ui = obj
end
@@ -233,9 +259,9 @@ module Bundler
{} # if we can't download them, there aren't any
end
- # TODO: This is for older versions of Rubygems... should we support the
+ # TODO: This is for older versions of RubyGems... should we support the
# X-Gemfile-Source header on these old versions?
- # Maybe the newer implementation will work on older Rubygems?
+ # Maybe the newer implementation will work on older RubyGems?
# It seems difficult to keep this implementation and still send the header.
def fetch_all_remote_specs(remote)
old_sources = Bundler.rubygems.sources
@@ -273,6 +299,7 @@ module Bundler
def spec_from_gem(path, policy = nil)
require "rubygems/security"
+ require "bundler/psyched_yaml"
gem_from_path(path, security_policies[policy]).spec
rescue Gem::Package::FormatError
raise GemspecError, "Could not read gem at #{path}. It may be corrupted."
@@ -306,7 +333,7 @@ module Bundler
end
def security_policy_keys
- %w(High Medium Low AlmostNo No).map {|level| "#{level}Security" }
+ %w[High Medium Low AlmostNo No].map {|level| "#{level}Security" }
end
def security_policies
@@ -377,9 +404,8 @@ module Bundler
raise e
end
- # TODO: delete this in 2.0, it's a backwards compatibility shim
- # see https://github.com/bundler/bundler/issues/5102
- kernel_class.send(:public, :gem)
+ # backwards compatibility shim, see https://github.com/bundler/bundler/issues/5102
+ kernel_class.send(:public, :gem) if Bundler.feature_flag.setup_makes_kernel_gem_public?
end
end
@@ -434,9 +460,9 @@ module Bundler
raise Gem::Exception, "no default executable for #{spec.full_name}" unless exec_name ||= spec.default_executable
- unless spec.name == name
- Bundler::SharedHelpers.major_deprecation \
- "Bundler is using a binstub that was created for a different gem.\n" \
+ unless spec.name == gem_name
+ Bundler::SharedHelpers.major_deprecation 2,
+ "Bundler is using a binstub that was created for a different gem (#{spec.name}).\n" \
"You should run `bundle binstub #{gem_name}` " \
"to work around a system/bundle conflict."
end
@@ -476,7 +502,7 @@ module Bundler
redefine_method(gem_class, :refresh) {}
end
- # Replace or hook into Rubygems to provide a bundlerized view
+ # Replace or hook into RubyGems to provide a bundlerized view
# of the world.
def replace_entrypoints(specs)
specs_by_name = specs.reduce({}) do |h, s|
@@ -492,8 +518,8 @@ module Bundler
Gem.clear_paths
end
- # This backports the correct segment generation code from Rubygems 1.4+
- # by monkeypatching it into the method in Rubygems 1.3.6 and 1.3.7.
+ # This backports the correct segment generation code from RubyGems 1.4+
+ # by monkeypatching it into the method in RubyGems 1.3.6 and 1.3.7.
def backport_segment_generation
redefine_method(Gem::Version, :segments) do
@segments ||= @version.scan(/[0-9]+|[a-z]+/i).map do |s|
@@ -512,7 +538,7 @@ module Bundler
end
# This backports base_dir which replaces installation path
- # Rubygems 1.8+
+ # RubyGems 1.8+
def backport_base_dir
redefine_method(Gem::Specification, :base_dir) do
return Gem.dir unless loaded_from
@@ -581,7 +607,7 @@ module Bundler
end
end
- # Rubygems 1.4 through 1.6
+ # RubyGems 1.4 through 1.6
class Legacy < RubygemsIntegration
def initialize
super
@@ -592,7 +618,7 @@ module Bundler
end
def stub_rubygems(specs)
- # Rubygems versions lower than 1.7 use SourceIndex#from_gems_in
+ # RubyGems versions lower than 1.7 use SourceIndex#from_gems_in
source_index_class = (class << Gem::SourceIndex; self; end)
redefine_method(source_index_class, :from_gems_in) do |*args|
Gem::SourceIndex.new.tap do |source_index|
@@ -624,7 +650,7 @@ module Bundler
end
end
- # Rubygems versions 1.3.6 and 1.3.7
+ # RubyGems versions 1.3.6 and 1.3.7
class Ancient < Legacy
def initialize
super
@@ -632,7 +658,7 @@ module Bundler
end
end
- # Rubygems 1.7
+ # RubyGems 1.7
class Transitional < Legacy
def stub_rubygems(specs)
stub_source_index(specs)
@@ -646,7 +672,7 @@ module Bundler
end
end
- # Rubygems 1.8.5-1.8.19
+ # RubyGems 1.8.5-1.8.19
class Modern < RubygemsIntegration
def stub_rubygems(specs)
Gem::Specification.all = specs
@@ -667,9 +693,9 @@ module Bundler
end
end
- # Rubygems 1.8.0 to 1.8.4
+ # RubyGems 1.8.0 to 1.8.4
class AlmostModern < Modern
- # Rubygems [>= 1.8.0, < 1.8.5] has a bug that changes Gem.dir whenever
+ # RubyGems [>= 1.8.0, < 1.8.5] has a bug that changes Gem.dir whenever
# you call Gem::Installer#install with an :install_dir set. We have to
# change it back for our sudo mode to work.
def preserve_paths
@@ -680,9 +706,9 @@ module Bundler
end
end
- # Rubygems 1.8.20+
+ # RubyGems 1.8.20+
class MoreModern < Modern
- # Rubygems 1.8.20 and adds the skip_validation parameter, so that's
+ # RubyGems 1.8.20 and adds the skip_validation parameter, so that's
# when we start passing it through.
def build(spec, skip_validation = false)
require "rubygems/builder"
@@ -690,7 +716,7 @@ module Bundler
end
end
- # Rubygems 2.0
+ # RubyGems 2.0
class Future < RubygemsIntegration
def stub_rubygems(specs)
Gem::Specification.all = specs
@@ -767,6 +793,10 @@ module Bundler
def install_with_build_args(args)
yield
end
+
+ def path_separator
+ Gem.path_separator
+ end
end
# RubyGems 2.1.0
@@ -855,7 +885,7 @@ module Bundler
RubygemsIntegration::Transitional.new
elsif RubygemsIntegration.provides?(">= 1.4.0")
RubygemsIntegration::Legacy.new
- else # Rubygems 1.3.6 and 1.3.7
+ else # RubyGems 1.3.6 and 1.3.7
RubygemsIntegration::Ancient.new
end
end
diff --git a/lib/bundler/runtime.rb b/lib/bundler/runtime.rb
index 5540509d74..f27597b854 100644
--- a/lib/bundler/runtime.rb
+++ b/lib/bundler/runtime.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require "digest/sha1"
module Bundler
class Runtime
@@ -11,7 +10,7 @@ module Bundler
end
def setup(*groups)
- @definition.ensure_equivalent_gemfile_and_lockfile if Bundler.settings[:frozen]
+ @definition.ensure_equivalent_gemfile_and_lockfile if Bundler.frozen?
groups.map!(&:to_sym)
@@ -262,9 +261,6 @@ module Bundler
end
def setup_manpath
- # Store original MANPATH for restoration later in with_clean_env()
- ENV["BUNDLER_ORIG_MANPATH"] = ENV["MANPATH"]
-
# Add man/ subdirectories from activated bundles to MANPATH for man(1)
manuals = $LOAD_PATH.map do |path|
man_subdir = path.sub(/lib$/, "man")
@@ -272,7 +268,7 @@ module Bundler
end.compact
return if manuals.empty?
- ENV["MANPATH"] = manuals.concat(
+ Bundler::SharedHelpers.set_env "MANPATH", manuals.concat(
ENV["MANPATH"].to_s.split(File::PATH_SEPARATOR)
).uniq.join(File::PATH_SEPARATOR)
end
diff --git a/lib/bundler/settings.rb b/lib/bundler/settings.rb
index 1898738b7c..f33e9453be 100644
--- a/lib/bundler/settings.rb
+++ b/lib/bundler/settings.rb
@@ -1,60 +1,89 @@
# frozen_string_literal: true
+
require "uri"
module Bundler
class Settings
autoload :Mirror, "bundler/mirror"
autoload :Mirrors, "bundler/mirror"
+ autoload :Validator, "bundler/settings/validator"
- BOOL_KEYS = %w(
+ BOOL_KEYS = %w[
+ allow_bundler_dependency_conflicts
+ allow_deployment_source_credential_changes
allow_offline_install
+ auto_clean_without_path
auto_install
cache_all
cache_all_platforms
+ cache_command_is_package
+ console_command
+ default_install_uses_path
+ deployment
+ deployment_means_frozen
disable_checksum_validation
disable_exec_load
disable_local_branch_check
+ disable_multisource
disable_shared_gems
disable_version_check
+ error_on_stderr
force_ruby_platform
+ forget_cli_options
frozen
gem.coc
gem.mit
+ global_gem_cache
ignore_messages
+ init_gems_rb
+ list_command
+ lockfile_uses_separate_rubygems_sources
major_deprecations
no_install
no_prune
only_update_to_newer_versions
+ path.system
plugins
+ prefer_gems_rb
+ print_only_version_number
+ setup_makes_kernel_gem_public
silence_root_warning
- ).freeze
-
- NUMBER_KEYS = %w(
+ skip_default_git_sources
+ specific_platform
+ suppress_install_using_messages
+ unlock_source_unlocks_spec
+ update_requires_all_flag
+ ].freeze
+
+ NUMBER_KEYS = %w[
redirect
retry
ssl_verify_mode
timeout
- ).freeze
+ ].freeze
+
+ ARRAY_KEYS = %w[
+ with
+ without
+ ].freeze
DEFAULT_CONFIG = {
+ :disable_version_check => true,
:redirect => 5,
:retry => 3,
:timeout => 10,
}.freeze
- attr_accessor :cli_flags_given
-
def initialize(root = nil)
@root = root
@local_config = load_config(local_config_file)
@global_config = load_config(global_config_file)
- @cli_flags_given = false
@temporary = {}
end
def [](name)
key = key_for(name)
- value = @temporary.fetch(name) do
+ value = @temporary.fetch(key) do
@local_config.fetch(key) do
ENV.fetch(key) do
@global_config.fetch(key) do
@@ -65,48 +94,59 @@ module Bundler
converted_value(value, name)
end
- def []=(key, value)
- if cli_flags_given
+ def set_command_option(key, value)
+ if Bundler.feature_flag.forget_cli_options?
+ temporary(key => value)
+ value
+ else
command = if value.nil?
"bundle config --delete #{key}"
else
"bundle config #{key} #{Array(value).join(":")}"
end
- Bundler::SharedHelpers.major_deprecation \
+ Bundler::SharedHelpers.major_deprecation 2,\
"flags passed to commands " \
"will no longer be automatically remembered. Instead please set flags " \
"you want remembered between commands using `bundle config " \
"<setting name> <setting value>`, i.e. `#{command}`"
+
+ set_local(key, value)
end
+ end
+
+ def set_command_option_if_given(key, value)
+ return if value.nil?
+ set_command_option(key, value)
+ end
+
+ def set_local(key, value)
local_config_file || raise(GemfileNotFound, "Could not locate Gemfile")
+
set_key(key, value, @local_config, local_config_file)
end
- alias_method :set_local, :[]=
def temporary(update)
- existing = Hash[update.map {|k, _| [k, @temporary[k]] }]
- @temporary.update(update)
+ existing = Hash[update.map {|k, _| [k, @temporary[key_for(k)]] }]
+ update.each do |k, v|
+ set_key(k, v, @temporary, nil)
+ end
return unless block_given?
begin
yield
ensure
- existing.each {|k, v| v.nil? ? @temporary.delete(k) : @temporary[k] = v }
+ existing.each {|k, v| set_key(k, v, @temporary, nil) }
end
end
- def delete(key)
- @local_config.delete(key_for(key))
- end
-
def set_global(key, value)
set_key(key, value, @global_config, global_config_file)
end
def all
- env_keys = ENV.keys.select {|k| k =~ /BUNDLE_.*/ }
+ env_keys = ENV.keys.grep(/\ABUNDLE_.+/)
- keys = @global_config.keys | @local_config.keys | env_keys
+ keys = @temporary.keys | @global_config.keys | @local_config.keys | env_keys
keys.map do |key|
key.sub(/^BUNDLE_/, "").gsub(/__/, ".").downcase
@@ -132,7 +172,7 @@ module Bundler
def gem_mirrors
all.inject(Mirrors.new) do |mirrors, k|
- mirrors.parse(k, self[k]) if k =~ /^mirror\./
+ mirrors.parse(k, self[k]) if k.start_with?("mirror.")
mirrors
end
end
@@ -140,6 +180,7 @@ module Bundler
def locations(key)
key = key_for(key)
locations = {}
+ locations[:temporary] = @temporary[key] if @temporary.key?(key)
locations[:local] = @local_config[key] if @local_config.key?(key)
locations[:env] = ENV[key] if ENV[key]
locations[:global] = @global_config[key] if @global_config.key?(key)
@@ -151,6 +192,11 @@ module Bundler
key = key_for(exposed_key)
locations = []
+
+ if @temporary.key?(key)
+ locations << "Set for the current command: #{converted_value(@temporary[key], exposed_key).inspect}"
+ end
+
if @local_config.key?(key)
locations << "Set for your local app (#{local_config_file}): #{converted_value(@local_config[key], exposed_key).inspect}"
end
@@ -167,37 +213,56 @@ module Bundler
locations
end
- def without=(array)
- set_array(:without, array)
- end
+ # for legacy reasons, the ruby scope isnt appended when the setting comes from ENV or the global config,
+ # nor do we respect :disable_shared_gems
+ def path
+ key = key_for(:path)
+ path = ENV[key] || @global_config[key]
+ if path && !@temporary.key?(key) && !@local_config.key?(key)
+ return Path.new(path, false, false, false)
+ end
- def with=(array)
- set_array(:with, array)
+ system_path = self["path.system"] || (self[:disable_shared_gems] == false)
+ Path.new(self[:path], true, system_path, Bundler.feature_flag.default_install_uses_path?)
end
- def without
- get_array(:without)
- end
+ Path = Struct.new(:explicit_path, :append_ruby_scope, :system_path, :default_install_uses_path) do
+ def path
+ path = base_path
+ path = File.join(path, Bundler.ruby_scope) if append_ruby_scope && !use_system_gems?
+ path
+ end
- def with
- get_array(:with)
- end
+ def use_system_gems?
+ return true if system_path
+ return false if explicit_path
+ !default_install_uses_path
+ end
- # @local_config["BUNDLE_PATH"] should be prioritized over ENV["BUNDLE_PATH"]
- def path
- key = key_for(:path)
- path = ENV[key] || @global_config[key]
- return path if path && !@local_config.key?(key)
+ def base_path
+ path = explicit_path
+ path ||= ".bundle" unless use_system_gems?
+ path ||= Bundler.rubygems.gem_dir
+ path
+ end
- if path = self[:path]
- "#{path}/#{Bundler.ruby_scope}"
- else
- Bundler.rubygems.gem_dir
+ def validate!
+ return unless explicit_path && system_path
+ path = Bundler.settings.pretty_values_for(:path)
+ path.unshift(nil, "path:") unless path.empty?
+ system_path = Bundler.settings.pretty_values_for("path.system")
+ system_path.unshift(nil, "path.system:") unless system_path.empty?
+ disable_shared_gems = Bundler.settings.pretty_values_for(:disable_shared_gems)
+ disable_shared_gems.unshift(nil, "disable_shared_gems:") unless disable_shared_gems.empty?
+ raise InvalidOption,
+ "Using a custom path while using system gems is unsupported.\n#{path.join("\n")}\n#{system_path.join("\n")}\n#{disable_shared_gems.join("\n")}"
end
end
def allow_sudo?
- !@local_config.key?(key_for(:path))
+ key = key_for(:path)
+ path_configured = @temporary.key?(key) || @local_config.key?(key)
+ !path_configured
end
def ignore_config?
@@ -205,14 +270,17 @@ module Bundler
end
def app_cache_path
- @app_cache_path ||= begin
- path = self[:cache_path] || "vendor/cache"
- raise InvalidOption, "Cache path must be relative to the bundle path" if path.start_with?("/")
- path
- end
+ @app_cache_path ||= self[:cache_path] || "vendor/cache"
end
- private
+ def validate!
+ all.each do |raw_key|
+ [@local_config, ENV, @global_config].each do |settings|
+ value = converted_value(settings[key_for(raw_key)], raw_key)
+ Validator.validate!(raw_key, value, settings.to_hash.dup)
+ end
+ end
+ end
def key_for(key)
key = Settings.normalize_uri(key).to_s if key.is_a?(String) && /https?:/ =~ key
@@ -220,15 +288,17 @@ module Bundler
"BUNDLE_#{key}"
end
+ private
+
def parent_setting_for(name)
- split_specfic_setting_for(name)[0]
+ split_specific_setting_for(name)[0]
end
- def specfic_gem_for(name)
- split_specfic_setting_for(name)[1]
+ def specific_gem_for(name)
+ split_specific_setting_for(name)[1]
end
- def split_specfic_setting_for(name)
+ def split_specific_setting_for(name)
name.split(".")
end
@@ -245,43 +315,57 @@ module Bundler
end
end
- def is_num(value)
- NUMBER_KEYS.include?(value.to_s)
+ def is_num(key)
+ NUMBER_KEYS.include?(key.to_s)
end
- def get_array(key)
- self[key] ? self[key].split(":").map(&:to_sym) : []
+ def is_array(key)
+ ARRAY_KEYS.include?(key.to_s)
end
- def set_array(key, array)
- self[key] = (array.empty? ? nil : array.join(":")) if array
+ def to_array(value)
+ return [] unless value
+ value.split(":").map(&:to_sym)
end
- def set_key(key, value, hash, file)
- key = key_for(key)
+ def array_to_s(array)
+ array = Array(array)
+ return nil if array.empty?
+ array.join(":").tr(" ", ":")
+ end
- unless hash[key] == value
- hash[key] = value
- hash.delete(key) if value.nil?
- SharedHelpers.filesystem_access(file) do |p|
- FileUtils.mkdir_p(p.dirname)
- require "bundler/yaml_serializer"
- p.open("w") {|f| f.write(YAMLSerializer.dump(hash)) }
- end
- end
+ def set_key(raw_key, value, hash, file)
+ raw_key = raw_key.to_s
+ value = array_to_s(value) if is_array(raw_key)
+
+ key = key_for(raw_key)
+
+ return if hash[key] == value
+
+ hash[key] = value
+ hash.delete(key) if value.nil?
- value
+ Validator.validate!(raw_key, converted_value(value, raw_key), hash)
+
+ return unless file
+ SharedHelpers.filesystem_access(file) do |p|
+ FileUtils.mkdir_p(p.dirname)
+ require "bundler/yaml_serializer"
+ p.open("w") {|f| f.write(YAMLSerializer.dump(hash)) }
+ end
end
def converted_value(value, key)
- if value.nil?
+ if is_array(key)
+ to_array(value)
+ elsif value.nil?
nil
elsif is_bool(key) || value == "false"
to_bool(value)
elsif is_num(key)
value.to_i
else
- value
+ value.to_s
end
end
@@ -325,16 +409,34 @@ module Bundler
end
end
+ PER_URI_OPTIONS = %w[
+ fallback_timeout
+ ].freeze
+
+ NORMALIZE_URI_OPTIONS_PATTERN =
+ /
+ \A
+ (\w+\.)? # optional prefix key
+ (https?.*?) # URI
+ (\.#{Regexp.union(PER_URI_OPTIONS)})? # optional suffix key
+ \z
+ /ix
+
# TODO: duplicates Rubygems#normalize_uri
# TODO: is this the correct place to validate mirror URIs?
def self.normalize_uri(uri)
uri = uri.to_s
- uri = "#{uri}/" unless uri =~ %r{/\Z}
+ if uri =~ NORMALIZE_URI_OPTIONS_PATTERN
+ prefix = $1
+ uri = $2
+ suffix = $3
+ end
+ uri = "#{uri}/" unless uri.end_with?("/")
uri = URI(uri)
unless uri.absolute?
raise ArgumentError, format("Gem sources must be absolute. You provided '%s'.", uri)
end
- uri
+ "#{prefix}#{uri}#{suffix}"
end
end
end
diff --git a/lib/bundler/settings/validator.rb b/lib/bundler/settings/validator.rb
new file mode 100644
index 0000000000..9aa1627fb2
--- /dev/null
+++ b/lib/bundler/settings/validator.rb
@@ -0,0 +1,79 @@
+# frozen_string_literal: true
+
+module Bundler
+ class Settings
+ class Validator
+ class Rule
+ attr_reader :description
+
+ def initialize(keys, description, &validate)
+ @keys = keys
+ @description = description
+ @validate = validate
+ end
+
+ def validate!(key, value, settings)
+ instance_exec(key, value, settings, &@validate)
+ end
+
+ def fail!(key, value, *reasons)
+ reasons.unshift @description
+ raise InvalidOption, "Setting `#{key}` to #{value.inspect} failed:\n#{reasons.map {|r| " - #{r}" }.join("\n")}"
+ end
+
+ def set(settings, key, value, *reasons)
+ hash_key = k(key)
+ return if settings[hash_key] == value
+ reasons.unshift @description
+ Bundler.ui.info "Setting `#{key}` to #{value.inspect}, since #{reasons.join(", ")}"
+ if value.nil?
+ settings.delete(hash_key)
+ else
+ settings[hash_key] = value
+ end
+ end
+
+ def k(key)
+ Bundler.settings.key_for(key)
+ end
+ end
+
+ def self.rules
+ @rules ||= Hash.new {|h, k| h[k] = [] }
+ end
+ private_class_method :rules
+
+ def self.rule(keys, description, &blk)
+ rule = Rule.new(keys, description, &blk)
+ keys.each {|k| rules[k] << rule }
+ end
+ private_class_method :rule
+
+ def self.validate!(key, value, settings)
+ rules_to_validate = rules[key]
+ rules_to_validate.each {|rule| rule.validate!(key, value, settings) }
+ end
+
+ rule %w[path path.system], "path and path.system are mutually exclusive" do |key, value, settings|
+ if key == "path" && value
+ set(settings, "path.system", nil)
+ elsif key == "path.system" && value
+ set(settings, :path, nil)
+ end
+ end
+
+ rule %w[with without], "a group cannot be in both `with` & `without` simultaneously" do |key, value, settings|
+ with = settings.fetch(k(:with), "").split(":").map(&:to_sym)
+ without = settings.fetch(k(:without), "").split(":").map(&:to_sym)
+
+ other_key = key == "with" ? :without : :with
+ other_setting = key == "with" ? without : with
+
+ conflicting = with & without
+ if conflicting.any?
+ fail!(key, value, "`#{other_key}` is current set to #{other_setting.inspect}", "the `#{conflicting.join("`, `")}` groups conflict")
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bundler/setup.rb b/lib/bundler/setup.rb
index 9aae6478cd..ac6a5bf861 100644
--- a/lib/bundler/setup.rb
+++ b/lib/bundler/setup.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require "bundler/shared_helpers"
if Bundler::SharedHelpers.in_bundle?
diff --git a/lib/bundler/shared_helpers.rb b/lib/bundler/shared_helpers.rb
index a9141a1346..5566bc5832 100644
--- a/lib/bundler/shared_helpers.rb
+++ b/lib/bundler/shared_helpers.rb
@@ -1,7 +1,11 @@
# frozen_string_literal: true
+
+require "bundler/compatibility_guard"
+
require "pathname"
require "rubygems"
+require "bundler/version"
require "bundler/constants"
require "bundler/rubygems_integration"
require "bundler/current_ruby"
@@ -19,10 +23,16 @@ end
module Bundler
module SharedHelpers
- def default_gemfile
+ def root
gemfile = find_gemfile
raise GemfileNotFound, "Could not locate Gemfile" unless gemfile
- Pathname.new(gemfile).untaint
+ Pathname.new(gemfile).untaint.expand_path.parent
+ end
+
+ def default_gemfile
+ gemfile = find_gemfile(:order_matters)
+ raise GemfileNotFound, "Could not locate Gemfile" unless gemfile
+ Pathname.new(gemfile).untaint.expand_path
end
def default_lockfile
@@ -63,7 +73,7 @@ module Bundler
end
def with_clean_git_env(&block)
- keys = %w(GIT_DIR GIT_WORK_TREE)
+ keys = %w[GIT_DIR GIT_WORK_TREE]
old_env = keys.inject({}) do |h, k|
h.update(k => ENV[k])
end
@@ -129,20 +139,34 @@ module Bundler
namespace.const_get(constant_name)
end
- def major_deprecation(message)
+ def major_deprecation(major_version, message)
+ if Bundler.bundler_major_version >= major_version
+ require "bundler/errors"
+ raise DeprecatedError, "[REMOVED FROM #{major_version}.0] #{message}"
+ end
+
return unless prints_major_deprecations?
@major_deprecation_ui ||= Bundler::UI::Shell.new("no-color" => true)
ui = Bundler.ui.is_a?(@major_deprecation_ui.class) ? Bundler.ui : @major_deprecation_ui
- ui.warn("[DEPRECATED FOR #{Bundler::VERSION.split(".").first.to_i + 1}.0] #{message}")
+ ui.warn("[DEPRECATED FOR #{major_version}.0] #{message}")
end
def print_major_deprecations!
- deprecate_gemfile(find_gemfile) if find_gemfile == find_file("Gemfile")
+ multiple_gemfiles = search_up(".") do |dir|
+ gemfiles = gemfile_names.select {|gf| File.file? File.expand_path(gf, dir) }
+ next if gemfiles.empty?
+ break false if gemfiles.size == 1
+ end
+ if multiple_gemfiles && Bundler.bundler_major_version == 1
+ Bundler::SharedHelpers.major_deprecation 2, \
+ "gems.rb and gems.locked will be preferred to Gemfile and Gemfile.lock."
+ end
+
if RUBY_VERSION < "2"
- major_deprecation("Bundler will only support ruby >= 2.0, you are running #{RUBY_VERSION}")
+ major_deprecation(2, "Bundler will only support ruby >= 2.0, you are running #{RUBY_VERSION}")
end
return if Bundler.rubygems.provides?(">= 2")
- major_deprecation("Bundler will only support rubygems >= 2.0, you are running #{Bundler.rubygems.version}")
+ major_deprecation(2, "Bundler will only support rubygems >= 2.0, you are running #{Bundler.rubygems.version}")
end
def trap(signal, override = false, &block)
@@ -170,23 +194,59 @@ module Bundler
"\nEither installing with `--full-index` or running `bundle update #{spec.name}` should fix the problem."
end
+ def pretty_dependency(dep, print_source = false)
+ msg = String.new(dep.name)
+ msg << " (#{dep.requirement})" unless dep.requirement == Gem::Requirement.default
+ if dep.is_a?(Bundler::Dependency)
+ platform_string = dep.platforms.join(", ")
+ msg << " " << platform_string if !platform_string.empty? && platform_string != Gem::Platform::RUBY
+ end
+ msg << " from the `#{dep.source}` source" if print_source && dep.source
+ msg
+ end
+
+ def md5_available?
+ return @md5_available if defined?(@md5_available)
+ @md5_available = begin
+ require "openssl"
+ OpenSSL::Digest::MD5.digest("")
+ true
+ rescue LoadError
+ true
+ rescue OpenSSL::Digest::DigestError
+ false
+ end
+ end
+
+ def digest(name)
+ require "digest"
+ Digest(name)
+ end
+
private
def validate_bundle_path
- return unless Bundler.bundle_path.to_s.include?(File::PATH_SEPARATOR)
- message = "Your bundle path contains a '#{File::PATH_SEPARATOR}', " \
+ path_separator = Bundler.rubygems.path_separator
+ return unless Bundler.bundle_path.to_s.split(path_separator).size > 1
+ message = "Your bundle path contains text matching #{path_separator.inspect}, " \
"which is the path separator for your system. Bundler cannot " \
"function correctly when the Bundle path contains the " \
"system's PATH separator. Please change your " \
- "bundle path to not include '#{File::PATH_SEPARATOR}'." \
+ "bundle path to not match #{path_separator.inspect}." \
"\nYour current bundle path is '#{Bundler.bundle_path}'."
raise Bundler::PathError, message
end
- def find_gemfile
+ def find_gemfile(order_matters = false)
given = ENV["BUNDLE_GEMFILE"]
return given if given && !given.empty?
- find_file("Gemfile", "gems.rb")
+ names = gemfile_names
+ names.reverse! if order_matters && Bundler.feature_flag.prefer_gems_rb?
+ find_file(*names)
+ end
+
+ def gemfile_names
+ ["Gemfile", "gems.rb"]
end
def find_file(*names)
@@ -226,40 +286,51 @@ module Bundler
end
end
+ def set_env(key, value)
+ raise ArgumentError, "new key #{key}" unless EnvironmentPreserver::BUNDLER_KEYS.include?(key)
+ orig_key = "#{EnvironmentPreserver::BUNDLER_PREFIX}#{key}"
+ orig = ENV[key]
+ orig ||= EnvironmentPreserver::INTENTIONALLY_NIL
+ ENV[orig_key] ||= orig
+
+ ENV[key] = value
+ end
+ public :set_env
+
def set_bundle_variables
begin
- ENV["BUNDLE_BIN_PATH"] = Bundler.rubygems.bin_path("bundler", "bundle", VERSION)
+ Bundler::SharedHelpers.set_env "BUNDLE_BIN_PATH", Bundler.rubygems.bin_path("bundler", "bundle", VERSION)
rescue Gem::GemNotFoundException
if File.exist?(File.expand_path("../../../exe/bundle", __FILE__))
- ENV["BUNDLE_BIN_PATH"] = File.expand_path("../../../exe/bundle", __FILE__)
+ Bundler::SharedHelpers.set_env "BUNDLE_BIN_PATH", File.expand_path("../../../exe/bundle", __FILE__)
else
- ENV["BUNDLE_BIN_PATH"] = File.expand_path("../../../../bin/bundle", __FILE__)
+ Bundler::SharedHelpers.set_env "BUNDLE_BIN_PATH", File.expand_path("../../../../bin/bundle", __FILE__)
end
end
# Set BUNDLE_GEMFILE
- ENV["BUNDLE_GEMFILE"] = find_gemfile.to_s
- ENV["BUNDLER_VERSION"] = Bundler::VERSION
+ Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", find_gemfile(:order_matters).to_s
+ Bundler::SharedHelpers.set_env "BUNDLER_VERSION", Bundler::VERSION
end
def set_path
validate_bundle_path
paths = (ENV["PATH"] || "").split(File::PATH_SEPARATOR)
paths.unshift "#{Bundler.bundle_path}/bin"
- ENV["PATH"] = paths.uniq.join(File::PATH_SEPARATOR)
+ Bundler::SharedHelpers.set_env "PATH", paths.uniq.join(File::PATH_SEPARATOR)
end
def set_rubyopt
rubyopt = [ENV["RUBYOPT"]].compact
return if !rubyopt.empty? && rubyopt.first =~ %r{-rbundler/setup}
rubyopt.unshift %(-rbundler/setup)
- ENV["RUBYOPT"] = rubyopt.join(" ")
+ Bundler::SharedHelpers.set_env "RUBYOPT", rubyopt.join(" ")
end
def set_rubylib
rubylib = (ENV["RUBYLIB"] || "").split(File::PATH_SEPARATOR)
rubylib.unshift bundler_ruby_lib
- ENV["RUBYLIB"] = rubylib.uniq.join(File::PATH_SEPARATOR)
+ Bundler::SharedHelpers.set_env "RUBYLIB", rubylib.uniq.join(File::PATH_SEPARATOR)
end
def bundler_ruby_lib
@@ -290,12 +361,6 @@ module Bundler
true
end
- def deprecate_gemfile(gemfile)
- return unless gemfile && File.basename(gemfile) == "Gemfile"
- Bundler::SharedHelpers.major_deprecation \
- "gems.rb and gems.locked will be preferred to Gemfile and Gemfile.lock."
- end
-
extend self
end
end
diff --git a/lib/bundler/similarity_detector.rb b/lib/bundler/similarity_detector.rb
index e9c1413ea3..b7f3ee7afa 100644
--- a/lib/bundler/similarity_detector.rb
+++ b/lib/bundler/similarity_detector.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler
class SimilarityDetector
SimilarityScore = Struct.new(:string, :distance)
diff --git a/lib/bundler/source.rb b/lib/bundler/source.rb
index cf56ed1cc1..5a1f05098b 100644
--- a/lib/bundler/source.rb
+++ b/lib/bundler/source.rb
@@ -1,8 +1,10 @@
# frozen_string_literal: true
+
module Bundler
class Source
autoload :Gemspec, "bundler/source/gemspec"
autoload :Git, "bundler/source/git"
+ autoload :Metadata, "bundler/source/metadata"
autoload :Path, "bundler/source/path"
autoload :Rubygems, "bundler/source/rubygems"
@@ -31,6 +33,15 @@ module Bundler
spec.source == self
end
+ # it's possible that gems from one source depend on gems from some
+ # other source, so now we download gemspecs and iterate over those
+ # dependencies, looking for gems we don't have info on yet.
+ def double_check_for(*); end
+
+ def dependency_names_to_double_check
+ specs.dependency_names
+ end
+
def include?(other)
other == self
end
@@ -39,6 +50,10 @@ module Bundler
"#<#{self.class}:0x#{object_id} #{self}>"
end
+ def path?
+ instance_of?(Bundler::Source::Path)
+ end
+
private
def version_color(spec_version, locked_spec_version)
@@ -54,5 +69,26 @@ module Bundler
def earlier_version?(spec_version, locked_spec_version)
Gem::Version.new(spec_version) < Gem::Version.new(locked_spec_version)
end
+
+ def print_using_message(message)
+ if !message.include?("(was ") && Bundler.feature_flag.suppress_install_using_messages?
+ Bundler.ui.debug message
+ else
+ Bundler.ui.info message
+ end
+ end
+
+ def extension_cache_path(spec)
+ return unless Bundler.feature_flag.global_gem_cache?
+ return unless source_slug = extension_cache_slug(spec)
+ Bundler.user_cache.join(
+ "extensions", Gem::Platform.local.to_s, Bundler.ruby_scope,
+ source_slug, spec.full_name
+ )
+ end
+
+ def extension_cache_slug(_)
+ nil
+ end
end
end
diff --git a/lib/bundler/source/gemspec.rb b/lib/bundler/source/gemspec.rb
index 05e613277f..7e3447e776 100644
--- a/lib/bundler/source/gemspec.rb
+++ b/lib/bundler/source/gemspec.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler
class Source
class Gemspec < Path
diff --git a/lib/bundler/source/git.rb b/lib/bundler/source/git.rb
index b3e218e390..a1a59ddce5 100644
--- a/lib/bundler/source/git.rb
+++ b/lib/bundler/source/git.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require "fileutils"
+
+require "bundler/vendored_fileutils"
require "uri"
-require "digest/sha1"
module Bundler
class Source
@@ -18,7 +18,7 @@ module Bundler
@allow_remote = false
# Stringify options that could be set as symbols
- %w(ref branch tag revision).each {|k| options[k] = options[k].to_s if options[k] }
+ %w[ref branch tag revision].each {|k| options[k] = options[k].to_s if options[k] }
@uri = options["uri"] || ""
@branch = options["branch"]
@@ -39,7 +39,7 @@ module Bundler
out = String.new("GIT\n")
out << " remote: #{@uri}\n"
out << " revision: #{revision}\n"
- %w(ref branch tag submodules).each do |opt|
+ %w[ref branch tag submodules].each do |opt|
out << " #{opt}: #{options[opt]}\n" if options[opt]
end
out << " glob: #{@glob}\n" unless @glob == DEFAULT_GLOB
@@ -169,15 +169,13 @@ module Bundler
def install(spec, options = {})
force = options[:force]
- Bundler.ui.info "Using #{version_message(spec)} from #{self}"
+ print_using_message "Using #{version_message(spec)} from #{self}"
- if requires_checkout? && !@copied && !force
+ if (requires_checkout? && !@copied) || force
Bundler.ui.debug " * Checking out revision: #{ref}"
git_proxy.copy_to(install_path, submodules)
serialize_gemspecs_in(install_path)
@copied = true
- elsif force
- git_proxy.copy_to(install_path, submodules)
end
generate_bin_options = { :disable_extensions => !Bundler.rubygems.spec_missing_extensions?(spec), :build_args => options[:build_args] }
@@ -188,7 +186,7 @@ module Bundler
def cache(spec, custom_path = nil)
app_cache_path = app_cache_path(custom_path)
- return unless Bundler.settings[:cache_all]
+ return unless Bundler.feature_flag.cache_all?
return if path == app_cache_path
cached!
FileUtils.rm_rf(app_cache_path)
@@ -210,13 +208,11 @@ module Bundler
# When using local git repos, this is set to the local repo.
def cache_path
@cache_path ||= begin
- git_scope = "#{base_name}-#{uri_hash}"
-
- if Bundler.requires_sudo?
- Bundler.user_bundle_path.join("cache/git", git_scope)
+ if Bundler.requires_sudo? || Bundler.feature_flag.global_gem_cache?
+ Bundler.user_cache
else
- Bundler.cache.join("git", git_scope)
- end
+ Bundler.bundle_path.join("cache", "bundler")
+ end.join("git", git_scope)
end
end
@@ -287,7 +283,7 @@ module Bundler
# If there is no URI scheme, assume it is an ssh/git URI
input = uri
end
- Digest::SHA1.hexdigest(input)
+ SharedHelpers.digest(:SHA1).hexdigest(input)
end
def cached_revision
@@ -304,9 +300,9 @@ module Bundler
def fetch
git_proxy.checkout
- rescue GitError
+ rescue GitError => e
raise unless Bundler.feature_flag.allow_offline_install?
- Bundler.ui.warn "Using cached git data because of network errors"
+ Bundler.ui.warn "Using cached git data because of network errors:\n#{e}"
end
# no-op, since we validate when re-serializing the gemspec
@@ -319,6 +315,14 @@ module Bundler
StubSpecification.from_stub(stub)
end
end
+
+ def git_scope
+ "#{base_name}-#{uri_hash}"
+ end
+
+ def extension_cache_slug(_)
+ extension_dir_name
+ end
end
end
end
diff --git a/lib/bundler/source/git/git_proxy.rb b/lib/bundler/source/git/git_proxy.rb
index c05d7a5afa..c56dda66ea 100644
--- a/lib/bundler/source/git/git_proxy.rb
+++ b/lib/bundler/source/git/git_proxy.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require "shellwords"
require "tempfile"
module Bundler
@@ -62,7 +63,7 @@ module Bundler
begin
@revision ||= find_local_revision
rescue GitCommandError
- raise MissingGitRevisionError.new(ref, uri)
+ raise MissingGitRevisionError.new(ref, URICredentialsFilter.credential_filtered_uri(uri))
end
@revision
@@ -90,18 +91,21 @@ module Bundler
end
def checkout
- if path.exist?
- return if has_revision_cached?
- Bundler.ui.info "Fetching #{URICredentialsFilter.credential_filtered_uri(uri)}"
- in_path do
- git_retry %(fetch --force --quiet --tags #{uri_escaped_with_configured_credentials} "refs/heads/*:refs/heads/*")
- end
- else
- Bundler.ui.info "Fetching #{URICredentialsFilter.credential_filtered_uri(uri)}"
+ return if path.exist? && has_revision_cached?
+ extra_ref = "#{Shellwords.shellescape(ref)}:#{Shellwords.shellescape(ref)}" if ref && ref.start_with?("refs/")
+
+ Bundler.ui.info "Fetching #{URICredentialsFilter.credential_filtered_uri(uri)}"
+
+ unless path.exist?
SharedHelpers.filesystem_access(path.dirname) do |p|
FileUtils.mkdir_p(p)
end
git_retry %(clone #{uri_escaped_with_configured_credentials} "#{path}" --bare --no-hardlinks --quiet)
+ return unless extra_ref
+ end
+
+ in_path do
+ git_retry %(fetch --force --quiet --tags #{uri_escaped_with_configured_credentials} "refs/heads/*:refs/heads/*" #{extra_ref})
end
end
@@ -149,7 +153,7 @@ module Bundler
end
def git_retry(command)
- Bundler::Retry.new("`git #{command}`", GitNotAllowedError).attempts do
+ Bundler::Retry.new("`git #{URICredentialsFilter.credential_filtered_string(command, uri)}`", GitNotAllowedError).attempts do
git(command)
end
end
@@ -217,6 +221,7 @@ module Bundler
def in_path(&blk)
checkout unless path.exist?
+ _ = URICredentialsFilter # load it before we chdir
SharedHelpers.chdir(path, &blk)
end
diff --git a/lib/bundler/source/metadata.rb b/lib/bundler/source/metadata.rb
new file mode 100644
index 0000000000..93909002c7
--- /dev/null
+++ b/lib/bundler/source/metadata.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+module Bundler
+ class Source
+ class Metadata < Source
+ def specs
+ @specs ||= Index.build do |idx|
+ idx << Gem::Specification.new("ruby\0", RubyVersion.system.to_gem_version_with_patchlevel)
+ idx << Gem::Specification.new("rubygems\0", Gem::VERSION)
+
+ idx << Gem::Specification.new do |s|
+ s.name = "bundler"
+ s.version = VERSION
+ s.platform = Gem::Platform::RUBY
+ s.source = self
+ s.authors = ["bundler team"]
+ s.bindir = "exe"
+ s.executables = %w[bundle]
+ # can't point to the actual gemspec or else the require paths will be wrong
+ s.loaded_from = File.expand_path("..", __FILE__)
+ end
+ if loaded_spec = nil && Bundler.rubygems.loaded_specs("bundler")
+ idx << loaded_spec # this has to come after the fake gemspec, to override it
+ elsif local_spec = Bundler.rubygems.find_name("bundler").find {|s| s.version.to_s == VERSION }
+ idx << local_spec
+ end
+
+ idx.each {|s| s.source = self }
+ end
+ end
+
+ def cached!; end
+
+ def remote!; end
+
+ def options
+ {}
+ end
+
+ def install(spec, _opts = {})
+ print_using_message "Using #{version_message(spec)}"
+ nil
+ end
+
+ def to_s
+ "the local ruby installation"
+ end
+
+ def ==(other)
+ self.class == other.class
+ end
+ alias_method :eql?, :==
+
+ def hash
+ self.class.hash
+ end
+
+ def version_message(spec)
+ "#{spec.name} #{spec.version}"
+ end
+ end
+ end
+end
diff --git a/lib/bundler/source/path.rb b/lib/bundler/source/path.rb
index 8dd0763cc1..ed734bf549 100644
--- a/lib/bundler/source/path.rb
+++ b/lib/bundler/source/path.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler
class Source
class Path < Source
@@ -35,10 +36,12 @@ module Bundler
end
def remote!
+ @local_specs = nil
@allow_remote = true
end
def cached!
+ @local_specs = nil
@allow_cached = true
end
@@ -74,14 +77,14 @@ module Bundler
end
def install(spec, options = {})
- Bundler.ui.info "Using #{version_message(spec)} from #{self}"
+ print_using_message "Using #{version_message(spec)} from #{self}"
generate_bin(spec, :disable_extensions => true)
nil # no post-install message
end
def cache(spec, custom_path = nil)
app_cache_path = app_cache_path(custom_path)
- return unless Bundler.settings[:cache_all]
+ return unless Bundler.feature_flag.cache_all?
return if expand(@original_path).to_s.index(root_path.to_s + "/") == 0
unless @original_path.exist?
@@ -113,10 +116,6 @@ module Bundler
Bundler.root
end
- def is_a_path?
- instance_of?(Path)
- end
-
def expanded_original_path
@expanded_original_path ||= expand(original_path)
end
@@ -228,7 +227,8 @@ module Bundler
spec,
:env_shebang => false,
:disable_extensions => options[:disable_extensions],
- :build_args => options[:build_args]
+ :build_args => options[:build_args],
+ :bundler_extension_cache_path => extension_cache_path(spec)
)
installer.post_install
rescue Gem::InvalidSpecificationException => e
@@ -242,7 +242,7 @@ module Bundler
"to modify their .gemspec so it can work with `gem build`."
end
- Bundler.ui.warn "The validation message from Rubygems was:\n #{e.message}"
+ Bundler.ui.warn "The validation message from RubyGems was:\n #{e.message}"
end
end
end
diff --git a/lib/bundler/source/path/installer.rb b/lib/bundler/source/path/installer.rb
index 9c2f74a31b..a0357ffa39 100644
--- a/lib/bundler/source/path/installer.rb
+++ b/lib/bundler/source/path/installer.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler
class Source
class Path
@@ -6,6 +7,7 @@ module Bundler
attr_reader :spec
def initialize(spec, options = {})
+ @options = options
@spec = spec
@gem_dir = Bundler.rubygems.path(spec.full_gem_path)
@wrappers = true
diff --git a/lib/bundler/source/rubygems.rb b/lib/bundler/source/rubygems.rb
index 353194f53f..6f4157364f 100644
--- a/lib/bundler/source/rubygems.rb
+++ b/lib/bundler/source/rubygems.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require "uri"
require "rubygems/user_interaction"
@@ -31,6 +32,7 @@ module Bundler
end
def cached!
+ @specs = nil
@allow_cached = true
end
@@ -49,6 +51,7 @@ module Bundler
end
def can_lock?(spec)
+ return super if Bundler.feature_flag.lockfile_uses_separate_rubygems_sources?
spec.source.is_a?(Rubygems)
end
@@ -69,8 +72,12 @@ module Bundler
end
def to_s
- remote_names = remotes.map(&:to_s).join(", ")
- "rubygems repository #{remote_names}"
+ if remotes.empty?
+ "locally installed gems"
+ else
+ remote_names = remotes.map(&:to_s).join(", ")
+ "rubygems repository #{remote_names} or installed locally"
+ end
end
alias_method :name, :to_s
@@ -99,8 +106,8 @@ module Bundler
end
end
- if installed?(spec) && (!force || spec.name.eql?("bundler"))
- Bundler.ui.info "Using #{version_message(spec)}"
+ if installed?(spec) && !force
+ print_using_message "Using #{version_message(spec)}"
return nil # no post-install message
end
@@ -141,7 +148,8 @@ module Bundler
:wrappers => true,
:env_shebang => true,
:build_args => opts[:build_args],
- :bundler_expected_checksum => spec.respond_to?(:checksum) && spec.checksum
+ :bundler_expected_checksum => spec.respond_to?(:checksum) && spec.checksum,
+ :bundler_extension_cache_path => extension_cache_path(spec)
).install
end
spec.full_gem_path = installed_spec.full_gem_path
@@ -212,13 +220,21 @@ module Bundler
@remotes.unshift(uri) unless @remotes.include?(uri)
end
- def replace_remotes(other_remotes)
+ def equivalent_remotes?(other_remotes)
+ other_remotes.map(&method(:remove_auth)) == @remotes.map(&method(:remove_auth))
+ end
+
+ def replace_remotes(other_remotes, allow_equivalent = false)
return false if other_remotes == @remotes
+ equivalent = allow_equivalent && equivalent_remotes?(other_remotes)
+
@remotes = []
other_remotes.reverse_each do |r|
add_remote r.to_s
end
+
+ !equivalent
end
def unmet_deps
@@ -236,6 +252,43 @@ module Bundler
end
end
+ def double_check_for(unmet_dependency_names, override_dupes = false, index = specs)
+ return unless @allow_remote
+ raise ArgumentError, "missing index" unless index
+
+ return unless api_fetchers.any?
+
+ unmet_dependency_names = unmet_dependency_names.call
+ unless unmet_dependency_names.nil?
+ if api_fetchers.size <= 1
+ # can't do this when there are multiple fetchers because then we might not fetch from _all_
+ # of them
+ unmet_dependency_names -= remote_specs.spec_names # avoid re-fetching things we've already gotten
+ end
+ return if unmet_dependency_names.empty?
+ end
+
+ Bundler.ui.debug "Double checking for #{unmet_dependency_names || "all specs (due to the size of the request)"} in #{self}"
+
+ fetch_names(api_fetchers, unmet_dependency_names, index, override_dupes)
+ end
+
+ def dependency_names_to_double_check
+ names = []
+ remote_specs.each do |spec|
+ case spec
+ when EndpointSpecification, Gem::Specification, StubSpecification, LazySpecification
+ names.concat(spec.runtime_dependencies)
+ when RemoteSpecification # from the full index
+ return nil
+ else
+ raise "unhandled spec type (#{spec.inspect})"
+ end
+ end
+ names.map!(&:name) if names
+ names
+ end
+
protected
def credless_remotes
@@ -276,7 +329,7 @@ module Bundler
end
def suppress_configured_credentials(remote)
- remote_nouser = remote.dup.tap {|uri| uri.user = uri.password = nil }.to_s
+ remote_nouser = remove_auth(remote)
if remote.userinfo && remote.userinfo == Bundler.settings[remote_nouser]
remote_nouser
else
@@ -284,15 +337,14 @@ module Bundler
end
end
+ def remove_auth(remote)
+ remote.dup.tap {|uri| uri.user = uri.password = nil }.to_s
+ end
+
def installed_specs
- @installed_specs ||= begin
- idx = Index.new
- have_bundler = false
+ @installed_specs ||= Index.build do |idx|
Bundler.rubygems.all_specs.reverse_each do |spec|
- if spec.name == "bundler"
- next unless spec.version.to_s == VERSION
- have_bundler = true
- end
+ next if spec.name == "bundler"
spec.source = self
if Bundler.rubygems.spec_missing_extensions?(spec, false)
Bundler.ui.debug "Source #{self} is ignoring #{spec} because it is missing extensions"
@@ -300,23 +352,6 @@ module Bundler
end
idx << spec
end
-
- # Always have bundler locally
- unless have_bundler
- # We're running bundler directly from the source
- # so, let's create a fake gemspec for it (it's a path)
- # gemspec
- bundler = Gem::Specification.new do |s|
- s.name = "bundler"
- s.version = VERSION
- s.platform = Gem::Platform::RUBY
- s.source = self
- s.authors = ["bundler team"]
- s.loaded_from = File.expand_path("..", __FILE__)
- end
- idx << bundler
- end
- idx
end
end
@@ -334,9 +369,9 @@ module Bundler
end
idx << s
end
- end
- idx
+ idx
+ end
end
def api_fetchers
@@ -348,71 +383,36 @@ module Bundler
index_fetchers = fetchers - api_fetchers
# gather lists from non-api sites
- index_fetchers.each do |f|
- Bundler.ui.info "Fetching source index from #{f.uri}"
- idx.use f.specs_with_retry(nil, self)
- end
+ fetch_names(index_fetchers, nil, idx, false)
# because ensuring we have all the gems we need involves downloading
# the gemspecs of those gems, if the non-api sites contain more than
- # about 100 gems, we treat all sites as non-api for speed.
+ # about 500 gems, we treat all sites as non-api for speed.
allow_api = idx.size < API_REQUEST_LIMIT && dependency_names.size < API_REQUEST_LIMIT
Bundler.ui.debug "Need to query more than #{API_REQUEST_LIMIT} gems." \
" Downloading full index instead..." unless allow_api
- if allow_api
- api_fetchers.each do |f|
- Bundler.ui.info "Fetching gem metadata from #{f.uri}", Bundler.ui.debug?
- idx.use f.specs_with_retry(dependency_names, self)
- Bundler.ui.info "" unless Bundler.ui.debug? # new line now that the dots are over
- end
-
- # Suppose the gem Foo depends on the gem Bar. Foo exists in Source A. Bar has some versions that exist in both
- # sources A and B. At this point, the API request will have found all the versions of Bar in source A,
- # but will not have found any versions of Bar from source B, which is a problem if the requested version
- # of Foo specifically depends on a version of Bar that is only found in source B. This ensures that for
- # each spec we found, we add all possible versions from all sources to the index.
- loop do
- idxcount = idx.size
- api_fetchers.each do |f|
- Bundler.ui.info "Fetching version metadata from #{f.uri}", Bundler.ui.debug?
- idx.use f.specs_with_retry(idx.dependency_names, self), true
- Bundler.ui.info "" unless Bundler.ui.debug? # new line now that the dots are over
- end
- break if idxcount == idx.size
- end
-
- if api_fetchers.any?
- # it's possible that gems from one source depend on gems from some
- # other source, so now we download gemspecs and iterate over those
- # dependencies, looking for gems we don't have info on yet.
- unmet = idx.unmet_dependency_names
-
- # if there are any cross-site gems we missed, get them now
- api_fetchers.each do |f|
- Bundler.ui.info "Fetching dependency metadata from #{f.uri}", Bundler.ui.debug?
- idx.use f.specs_with_retry(unmet, self)
- Bundler.ui.info "" unless Bundler.ui.debug? # new line now that the dots are over
- end if unmet.any?
- else
- allow_api = false
- end
- end
+ fetch_names(api_fetchers, allow_api && dependency_names, idx, false)
+ end
+ end
- unless allow_api
- api_fetchers.each do |f|
- Bundler.ui.info "Fetching source index from #{f.uri}"
- idx.use f.specs_with_retry(nil, self)
- end
+ def fetch_names(fetchers, dependency_names, index, override_dupes)
+ fetchers.each do |f|
+ if dependency_names
+ Bundler.ui.info "Fetching gem metadata from #{f.uri}", Bundler.ui.debug?
+ index.use f.specs_with_retry(dependency_names, self), override_dupes
+ Bundler.ui.info "" unless Bundler.ui.debug? # new line now that the dots are over
+ else
+ Bundler.ui.info "Fetching source index from #{f.uri}"
+ index.use f.specs_with_retry(nil, self), override_dupes
end
end
end
def fetch_gem(spec)
return false unless spec.remote
- uri = spec.remote.uri
+
spec.fetch_platform
- Bundler.ui.confirm("Fetching #{version_message(spec)}")
download_path = requires_sudo? ? Bundler.tmp(spec.full_name) : rubygems_dir
gem_path = "#{rubygems_dir}/cache/#{spec.full_name}.gem"
@@ -420,7 +420,7 @@ module Bundler
SharedHelpers.filesystem_access("#{download_path}/cache") do |p|
FileUtils.mkdir_p(p)
end
- Bundler.rubygems.download_gem(spec, uri, download_path)
+ download_gem(spec, download_path)
if requires_sudo?
SharedHelpers.filesystem_access("#{rubygems_dir}/cache") do |p|
@@ -457,6 +457,76 @@ module Bundler
def cache_path
Bundler.app_cache
end
+
+ private
+
+ # Checks if the requested spec exists in the global cache. If it does,
+ # we copy it to the download path, and if it does not, we download it.
+ #
+ # @param [Specification] spec
+ # the spec we want to download or retrieve from the cache.
+ #
+ # @param [String] download_path
+ # the local directory the .gem will end up in.
+ #
+ def download_gem(spec, download_path)
+ local_path = File.join(download_path, "cache/#{spec.full_name}.gem")
+
+ if (cache_path = download_cache_path(spec)) && cache_path.file?
+ SharedHelpers.filesystem_access(local_path) do
+ FileUtils.cp(cache_path, local_path)
+ end
+ else
+ uri = spec.remote.uri
+ Bundler.ui.confirm("Fetching #{version_message(spec)}")
+ Bundler.rubygems.download_gem(spec, uri, download_path)
+ cache_globally(spec, local_path)
+ end
+ end
+
+ # Checks if the requested spec exists in the global cache. If it does
+ # not, we create the relevant global cache subdirectory if it does not
+ # exist and copy the spec from the local cache to the global cache.
+ #
+ # @param [Specification] spec
+ # the spec we want to copy to the global cache.
+ #
+ # @param [String] local_cache_path
+ # the local directory from which we want to copy the .gem.
+ #
+ def cache_globally(spec, local_cache_path)
+ return unless cache_path = download_cache_path(spec)
+ return if cache_path.exist?
+
+ SharedHelpers.filesystem_access(cache_path.dirname, &:mkpath)
+ SharedHelpers.filesystem_access(cache_path) do
+ FileUtils.cp(local_cache_path, cache_path)
+ end
+ end
+
+ # Returns the global cache path of the calling Rubygems::Source object.
+ #
+ # Note that the Source determines the path's subdirectory. We use this
+ # subdirectory in the global cache path so that gems with the same name
+ # -- and possibly different versions -- from different sources are saved
+ # to their respective subdirectories and do not override one another.
+ #
+ # @param [Gem::Specification] specification
+ #
+ # @return [Pathname] The global cache path.
+ #
+ def download_cache_path(spec)
+ return unless Bundler.feature_flag.global_gem_cache?
+ return unless remote = spec.remote
+ return unless cache_slug = remote.cache_slug
+
+ Bundler.user_cache.join("gems", cache_slug, spec.file_name)
+ end
+
+ def extension_cache_slug(spec)
+ return unless remote = spec.remote
+ remote.cache_slug
+ end
end
end
end
diff --git a/lib/bundler/source/rubygems/remote.rb b/lib/bundler/source/rubygems/remote.rb
index b49e645506..e73baaa992 100644
--- a/lib/bundler/source/rubygems/remote.rb
+++ b/lib/bundler/source/rubygems/remote.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler
class Source
class Rubygems
@@ -20,10 +21,12 @@ module Bundler
#
def cache_slug
@cache_slug ||= begin
+ return nil unless SharedHelpers.md5_available?
+
cache_uri = original_uri || uri
uri_parts = [cache_uri.host, cache_uri.user, cache_uri.port, cache_uri.path]
- uri_digest = Digest::MD5.hexdigest(uri_parts.compact.join("."))
+ uri_digest = SharedHelpers.digest(:MD5).hexdigest(uri_parts.compact.join("."))
uri_parts[-1] = uri_digest
uri_parts.compact.join(".")
diff --git a/lib/bundler/source_list.rb b/lib/bundler/source_list.rb
index b6ce6029c8..ac2adacb3d 100644
--- a/lib/bundler/source_list.rb
+++ b/lib/bundler/source_list.rb
@@ -1,16 +1,21 @@
# frozen_string_literal: true
+
module Bundler
class SourceList
attr_reader :path_sources,
:git_sources,
- :plugin_sources
+ :plugin_sources,
+ :global_rubygems_source,
+ :metadata_source
def initialize
- @path_sources = []
- @git_sources = []
- @plugin_sources = []
- @rubygems_aggregate = Source::Rubygems.new
- @rubygems_sources = []
+ @path_sources = []
+ @git_sources = []
+ @plugin_sources = []
+ @global_rubygems_source = nil
+ @rubygems_aggregate = rubygems_aggregate_class.new
+ @rubygems_sources = []
+ @metadata_source = Source::Metadata.new
end
def add_path_source(options = {})
@@ -35,13 +40,28 @@ module Bundler
add_source_to_list Plugin.source(source).new(options), @plugin_sources
end
+ def global_rubygems_source=(uri)
+ if Bundler.feature_flag.lockfile_uses_separate_rubygems_sources?
+ @global_rubygems_source ||= rubygems_aggregate_class.new("remotes" => uri)
+ end
+ add_rubygems_remote(uri)
+ end
+
def add_rubygems_remote(uri)
+ if Bundler.feature_flag.lockfile_uses_separate_rubygems_sources?
+ return if Bundler.feature_flag.disable_multisource?
+ raise InvalidOption, "`lockfile_uses_separate_rubygems_sources` cannot be set without `disable_multisource` being set"
+ end
@rubygems_aggregate.add_remote(uri)
@rubygems_aggregate
end
+ def default_source
+ global_rubygems_source || @rubygems_aggregate
+ end
+
def rubygems_sources
- @rubygems_sources + [@rubygems_aggregate]
+ @rubygems_sources + [default_source]
end
def rubygems_remotes
@@ -49,18 +69,25 @@ module Bundler
end
def all_sources
- path_sources + git_sources + plugin_sources + rubygems_sources
+ path_sources + git_sources + plugin_sources + rubygems_sources + [metadata_source]
end
def get(source)
- source_list_for(source).find {|s| source == s }
+ source_list_for(source).find {|s| equal_source?(source, s) || equivalent_source?(source, s) }
end
def lock_sources
- lock_sources = (path_sources + git_sources + plugin_sources).sort_by(&:to_s)
- lock_sources << combine_rubygems_sources
+ if Bundler.feature_flag.lockfile_uses_separate_rubygems_sources?
+ [[default_source], @rubygems_sources, git_sources, path_sources, plugin_sources].map do |sources|
+ sources.sort_by(&:to_s)
+ end.flatten(1)
+ else
+ lock_sources = (path_sources + git_sources + plugin_sources).sort_by(&:to_s)
+ lock_sources << combine_rubygems_sources
+ end
end
+ # Returns true if there are changes
def replace_sources!(replacement_sources)
return true if replacement_sources.empty?
@@ -70,13 +97,14 @@ module Bundler
end
end
- replacement_rubygems =
+ replacement_rubygems = !Bundler.feature_flag.lockfile_uses_separate_rubygems_sources? &&
replacement_sources.detect {|s| s.is_a?(Source::Rubygems) }
@rubygems_aggregate = replacement_rubygems if replacement_rubygems
- # Return true if there were changes
- lock_sources.to_set != replacement_sources.to_set ||
- rubygems_remotes.to_set != replacement_rubygems.remotes.to_set
+ return true if !equal_sources?(lock_sources, replacement_sources) && !equivalent_sources?(lock_sources, replacement_sources)
+ return true if replacement_rubygems && rubygems_remotes.to_set != replacement_rubygems.remotes.to_set
+
+ false
end
def cached!
@@ -93,6 +121,10 @@ module Bundler
private
+ def rubygems_aggregate_class
+ Source::Rubygems
+ end
+
def add_source_to_list(source, list)
list.unshift(source).uniq!
source
@@ -122,5 +154,33 @@ module Bundler
"protocol to keep your data secure."
end
end
+
+ def equal_sources?(lock_sources, replacement_sources)
+ lock_sources.to_set == replacement_sources.to_set
+ end
+
+ def equal_source?(source, other_source)
+ source == other_source
+ end
+
+ def equivalent_source?(source, other_source)
+ return false unless Bundler.settings[:allow_deployment_source_credential_changes] && source.is_a?(Source::Rubygems)
+
+ equivalent_rubygems_sources?([source], [other_source])
+ end
+
+ def equivalent_sources?(lock_sources, replacement_sources)
+ return false unless Bundler.settings[:allow_deployment_source_credential_changes]
+
+ lock_rubygems_sources, lock_other_sources = lock_sources.partition {|s| s.is_a?(Source::Rubygems) }
+ replacement_rubygems_sources, replacement_other_sources = replacement_sources.partition {|s| s.is_a?(Source::Rubygems) }
+
+ equivalent_rubygems_sources?(lock_rubygems_sources, replacement_rubygems_sources) && equal_sources?(lock_other_sources, replacement_other_sources)
+ end
+
+ def equivalent_rubygems_sources?(lock_sources, replacement_sources)
+ actual_remotes = replacement_sources.map(&:remotes).flatten.uniq
+ lock_sources.all? {|s| s.equivalent_remotes?(actual_remotes) }
+ end
end
end
diff --git a/lib/bundler/spec_set.rb b/lib/bundler/spec_set.rb
index 9642633578..7cd3021997 100644
--- a/lib/bundler/spec_set.rb
+++ b/lib/bundler/spec_set.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require "tsort"
require "forwardable"
require "set"
@@ -76,7 +77,7 @@ module Bundler
end
def materialize(deps, missing_specs = nil)
- materialized = self.for(deps, [], false, true, missing_specs).to_a
+ materialized = self.for(deps, [], false, true, !missing_specs).to_a
deps = materialized.map(&:name).uniq
materialized.map! do |s|
next s unless s.is_a?(LazySpecification)
@@ -109,9 +110,10 @@ module Bundler
def merge(set)
arr = sorted.dup
- set.each do |s|
- next if arr.any? {|s2| s2.name == s.name && s2.version == s.version && s2.platform == s.platform }
- arr << s
+ set.each do |set_spec|
+ full_name = set_spec.full_name
+ next if arr.any? {|spec| spec.full_name == full_name }
+ arr << set_spec
end
SpecSet.new(arr)
end
diff --git a/lib/bundler/ssl_certs/certificate_manager.rb b/lib/bundler/ssl_certs/certificate_manager.rb
index a5e5d84b64..26fc38ec18 100644
--- a/lib/bundler/ssl_certs/certificate_manager.rb
+++ b/lib/bundler/ssl_certs/certificate_manager.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: true
-require "fileutils"
+
+require "bundler/vendored_fileutils"
require "net/https"
require "openssl"
diff --git a/lib/bundler/stub_specification.rb b/lib/bundler/stub_specification.rb
index aeacf245a3..0dd024024a 100644
--- a/lib/bundler/stub_specification.rb
+++ b/lib/bundler/stub_specification.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require "bundler/remote_specification"
module Bundler
diff --git a/lib/bundler/templates/Executable b/lib/bundler/templates/Executable
index fe22de0a6d..9289debc26 100755
--- a/lib/bundler/templates/Executable
+++ b/lib/bundler/templates/Executable
@@ -1,5 +1,6 @@
#!/usr/bin/env <%= Bundler.settings[:shebang] || RbConfig::CONFIG["ruby_install_name"] %>
# frozen_string_literal: true
+
#
# This file was generated by Bundler.
#
@@ -7,6 +8,9 @@
# this file is here to facilitate running it.
#
+bundle_binstub = File.expand_path("../bundle", __FILE__)
+load(bundle_binstub) if File.file?(bundle_binstub)
+
require "pathname"
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../<%= relative_gemfile_path %>",
Pathname.new(__FILE__).realpath)
diff --git a/lib/bundler/templates/Executable.bundler b/lib/bundler/templates/Executable.bundler
new file mode 100644
index 0000000000..eeda90b584
--- /dev/null
+++ b/lib/bundler/templates/Executable.bundler
@@ -0,0 +1,105 @@
+#!/usr/bin/env <%= Bundler.settings[:shebang] || RbConfig::CONFIG["ruby_install_name"] %>
+# frozen_string_literal: true
+
+#
+# This file was generated by Bundler.
+#
+# The application '<%= executable %>' is installed as part of a gem, and
+# this file is here to facilitate running it.
+#
+
+require "rubygems"
+
+m = Module.new do
+ module_function
+
+ def invoked_as_script?
+ File.expand_path($0) == File.expand_path(__FILE__)
+ end
+
+ def env_var_version
+ ENV["BUNDLER_VERSION"]
+ end
+
+ def cli_arg_version
+ return unless invoked_as_script? # don't want to hijack other binstubs
+ return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update`
+ bundler_version = nil
+ update_index = nil
+ ARGV.each_with_index do |a, i|
+ if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN
+ bundler_version = a
+ end
+ next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
+ bundler_version = $1 || ">= 0.a"
+ update_index = i
+ end
+ bundler_version
+ end
+
+ def gemfile
+ gemfile = ENV["BUNDLE_GEMFILE"]
+ return gemfile if gemfile && !gemfile.empty?
+
+ File.expand_path("../<%= relative_gemfile_path %>", __FILE__)
+ end
+
+ def lockfile
+ lockfile =
+ case File.basename(gemfile)
+ when "gems.rb" then gemfile.sub(/\.rb$/, gemfile)
+ else "#{gemfile}.lock"
+ end
+ File.expand_path(lockfile)
+ end
+
+ def lockfile_version
+ return unless File.file?(lockfile)
+ lockfile_contents = File.read(lockfile)
+ return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
+ Regexp.last_match(1)
+ end
+
+ def bundler_version
+ @bundler_version ||= begin
+ env_var_version || cli_arg_version ||
+ lockfile_version || "#{Gem::Requirement.default}.a"
+ end
+ end
+
+ def load_bundler!
+ ENV["BUNDLE_GEMFILE"] ||= gemfile
+
+ # must dup string for RG < 1.8 compatibility
+ activate_bundler(bundler_version.dup)
+ end
+
+ def activate_bundler(bundler_version)
+ if Gem::Version.correct?(bundler_version) && Gem::Version.new(bundler_version).release < Gem::Version.new("2.0")
+ bundler_version = "< 2"
+ end
+ gem_error = activation_error_handling do
+ gem "bundler", bundler_version
+ end
+ return if gem_error.nil?
+ require_error = activation_error_handling do
+ require "bundler/version"
+ end
+ return if require_error.nil? && Gem::Requirement.new(bundler_version).satisfied_by?(Gem::Version.new(Bundler::VERSION))
+ warn "Activating bundler (#{bundler_version}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_version}'`"
+ exit 42
+ end
+
+ def activation_error_handling
+ yield
+ nil
+ rescue StandardError, LoadError => e
+ e
+ end
+end
+
+m.load_bundler!
+
+if m.invoked_as_script?
+ load Gem.bin_path("<%= spec.name %>", "<%= executable %>")
+end
diff --git a/lib/bundler/templates/Gemfile b/lib/bundler/templates/Gemfile
index 21c6283123..1afd2cce67 100644
--- a/lib/bundler/templates/Gemfile
+++ b/lib/bundler/templates/Gemfile
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
source "https://rubygems.org"
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
diff --git a/lib/bundler/templates/gems.rb b/lib/bundler/templates/gems.rb
new file mode 100644
index 0000000000..547cd6e8d9
--- /dev/null
+++ b/lib/bundler/templates/gems.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+# A sample gems.rb
+source "https://rubygems.org"
+
+git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
+
+# gem "rails"
diff --git a/lib/bundler/templates/newgem/README.md.tt b/lib/bundler/templates/newgem/README.md.tt
index edbe55dabe..868a0afe67 100644
--- a/lib/bundler/templates/newgem/README.md.tt
+++ b/lib/bundler/templates/newgem/README.md.tt
@@ -37,7 +37,7 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/<%= co
## License
-The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
+The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
<% end -%>
<% if config[:coc] -%>
diff --git a/lib/bundler/templates/newgem/gitignore.tt b/lib/bundler/templates/newgem/gitignore.tt
index 573d76b4c2..b1c9f9986c 100644
--- a/lib/bundler/templates/newgem/gitignore.tt
+++ b/lib/bundler/templates/newgem/gitignore.tt
@@ -1,6 +1,5 @@
/.bundle/
/.yardoc
-/Gemfile.lock
/_yardoc/
/coverage/
/doc/
diff --git a/lib/bundler/templates/newgem/newgem.gemspec.tt b/lib/bundler/templates/newgem/newgem.gemspec.tt
index caea7fe7be..9a87a1374a 100644
--- a/lib/bundler/templates/newgem/newgem.gemspec.tt
+++ b/lib/bundler/templates/newgem/newgem.gemspec.tt
@@ -1,4 +1,7 @@
+<%- if RUBY_VERSION < "2.0.0" -%>
# coding: utf-8
+<%- end -%>
+
lib = File.expand_path("../lib", __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require "<%= config[:namespaced_path] %>/version"
@@ -9,7 +12,7 @@ Gem::Specification.new do |spec|
spec.authors = [<%= config[:author].inspect %>]
spec.email = [<%= config[:email].inspect %>]
- spec.summary = %q{TODO: Write a short summary, because Rubygems requires one.}
+ spec.summary = %q{TODO: Write a short summary, because RubyGems requires one.}
spec.description = %q{TODO: Write a longer description or delete this line.}
spec.homepage = "TODO: Put your gem's website or public repo URL here."
<%- if config[:mit] -%>
diff --git a/lib/bundler/templates/newgem/rspec.tt b/lib/bundler/templates/newgem/rspec.tt
index 8c18f1abdd..34c5164d9b 100644
--- a/lib/bundler/templates/newgem/rspec.tt
+++ b/lib/bundler/templates/newgem/rspec.tt
@@ -1,2 +1,3 @@
--format documentation
--color
+--require spec_helper
diff --git a/lib/bundler/templates/newgem/spec/newgem_spec.rb.tt b/lib/bundler/templates/newgem/spec/newgem_spec.rb.tt
index b7ef7f9e4a..c63b487830 100644
--- a/lib/bundler/templates/newgem/spec/newgem_spec.rb.tt
+++ b/lib/bundler/templates/newgem/spec/newgem_spec.rb.tt
@@ -1,5 +1,3 @@
-require "spec_helper"
-
RSpec.describe <%= config[:constant_name] %> do
it "has a version number" do
expect(<%= config[:constant_name] %>::VERSION).not_to be nil
diff --git a/lib/bundler/ui.rb b/lib/bundler/ui.rb
index 794c000dc4..8138b30d38 100644
--- a/lib/bundler/ui.rb
+++ b/lib/bundler/ui.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler
module UI
autoload :RGProxy, "bundler/ui/rg_proxy"
diff --git a/lib/bundler/ui/rg_proxy.rb b/lib/bundler/ui/rg_proxy.rb
index 95a1ecdf0c..e2f98481db 100644
--- a/lib/bundler/ui/rg_proxy.rb
+++ b/lib/bundler/ui/rg_proxy.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require "bundler/ui"
require "rubygems/user_interaction"
diff --git a/lib/bundler/ui/shell.rb b/lib/bundler/ui/shell.rb
index 87a92471fb..3b3b6bfb53 100644
--- a/lib/bundler/ui/shell.rb
+++ b/lib/bundler/ui/shell.rb
@@ -1,15 +1,16 @@
# frozen_string_literal: true
+
require "bundler/vendored_thor"
module Bundler
module UI
class Shell
- LEVELS = %w(silent error warn confirm info debug).freeze
+ LEVELS = %w[silent error warn confirm info debug].freeze
attr_writer :shell
def initialize(options = {})
- if options["no-color"] || !STDOUT.tty?
+ if options["no-color"] || !$stdout.tty?
Thor::Base.shell = Thor::Shell::Basic
end
@shell = Thor::Base.shell.new
@@ -30,13 +31,18 @@ module Bundler
end
def warn(msg, newline = nil)
+ return unless level("warn")
return if @warning_history.include? msg
@warning_history << msg
- tell_me(msg, :yellow, newline) if level("warn")
+
+ return tell_err(msg, :yellow, newline) if Bundler.feature_flag.error_on_stderr?
+ tell_me(msg, :yellow, newline)
end
def error(msg, newline = nil)
- tell_me(msg, :red, newline) if level("error")
+ return unless level("error")
+ return tell_err(msg, :red, newline) if Bundler.feature_flag.error_on_stderr?
+ tell_me(msg, :red, newline)
end
def debug(msg, newline = nil)
@@ -103,6 +109,11 @@ module Bundler
end
def tell_err(message, color = nil, newline = nil)
+ newline = message.to_s !~ /( |\t)\Z/ unless newline
+ message = word_wrap(message) if newline.is_a?(Hash) && newline[:wrap]
+
+ color = nil if color && !$stderr.tty?
+
buffer = @shell.send(:prepare_message, message, *color)
buffer << "\n" if newline && !message.to_s.end_with?("\n")
diff --git a/lib/bundler/ui/silent.rb b/lib/bundler/ui/silent.rb
index 48390b7198..dca1b2ac86 100644
--- a/lib/bundler/ui/silent.rb
+++ b/lib/bundler/ui/silent.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler
module UI
class Silent
diff --git a/lib/bundler/uri_credentials_filter.rb b/lib/bundler/uri_credentials_filter.rb
index 997a307533..ee3692268c 100644
--- a/lib/bundler/uri_credentials_filter.rb
+++ b/lib/bundler/uri_credentials_filter.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler
module URICredentialsFilter
module_function
diff --git a/lib/bundler/vendor/fileutils/lib/fileutils.rb b/lib/bundler/vendor/fileutils/lib/fileutils.rb
new file mode 100644
index 0000000000..cc69740845
--- /dev/null
+++ b/lib/bundler/vendor/fileutils/lib/fileutils.rb
@@ -0,0 +1,1638 @@
+# frozen_string_literal: true
+#
+# = fileutils.rb
+#
+# Copyright (c) 2000-2007 Minero Aoki
+#
+# This program is free software.
+# You can distribute/modify this program under the same terms of ruby.
+#
+# == module Bundler::FileUtils
+#
+# Namespace for several file utility methods for copying, moving, removing, etc.
+#
+# === Module Functions
+#
+# require 'bundler/vendor/fileutils/lib/fileutils'
+#
+# Bundler::FileUtils.cd(dir, options)
+# Bundler::FileUtils.cd(dir, options) {|dir| block }
+# Bundler::FileUtils.pwd()
+# Bundler::FileUtils.mkdir(dir, options)
+# Bundler::FileUtils.mkdir(list, options)
+# Bundler::FileUtils.mkdir_p(dir, options)
+# Bundler::FileUtils.mkdir_p(list, options)
+# Bundler::FileUtils.rmdir(dir, options)
+# Bundler::FileUtils.rmdir(list, options)
+# Bundler::FileUtils.ln(target, link, options)
+# Bundler::FileUtils.ln(targets, dir, options)
+# Bundler::FileUtils.ln_s(target, link, options)
+# Bundler::FileUtils.ln_s(targets, dir, options)
+# Bundler::FileUtils.ln_sf(target, link, options)
+# Bundler::FileUtils.cp(src, dest, options)
+# Bundler::FileUtils.cp(list, dir, options)
+# Bundler::FileUtils.cp_r(src, dest, options)
+# Bundler::FileUtils.cp_r(list, dir, options)
+# Bundler::FileUtils.mv(src, dest, options)
+# Bundler::FileUtils.mv(list, dir, options)
+# Bundler::FileUtils.rm(list, options)
+# Bundler::FileUtils.rm_r(list, options)
+# Bundler::FileUtils.rm_rf(list, options)
+# Bundler::FileUtils.install(src, dest, options)
+# Bundler::FileUtils.chmod(mode, list, options)
+# Bundler::FileUtils.chmod_R(mode, list, options)
+# Bundler::FileUtils.chown(user, group, list, options)
+# Bundler::FileUtils.chown_R(user, group, list, options)
+# Bundler::FileUtils.touch(list, options)
+#
+# The <tt>options</tt> parameter is a hash of options, taken from the list
+# <tt>:force</tt>, <tt>:noop</tt>, <tt>:preserve</tt>, and <tt>:verbose</tt>.
+# <tt>:noop</tt> means that no changes are made. The other three are obvious.
+# Each method documents the options that it honours.
+#
+# All methods that have the concept of a "source" file or directory can take
+# either one file or a list of files in that argument. See the method
+# documentation for examples.
+#
+# There are some `low level' methods, which do not accept any option:
+#
+# Bundler::FileUtils.copy_entry(src, dest, preserve = false, dereference = false)
+# Bundler::FileUtils.copy_file(src, dest, preserve = false, dereference = true)
+# Bundler::FileUtils.copy_stream(srcstream, deststream)
+# Bundler::FileUtils.remove_entry(path, force = false)
+# Bundler::FileUtils.remove_entry_secure(path, force = false)
+# Bundler::FileUtils.remove_file(path, force = false)
+# Bundler::FileUtils.compare_file(path_a, path_b)
+# Bundler::FileUtils.compare_stream(stream_a, stream_b)
+# Bundler::FileUtils.uptodate?(file, cmp_list)
+#
+# == module Bundler::FileUtils::Verbose
+#
+# This module has all methods of Bundler::FileUtils module, but it outputs messages
+# before acting. This equates to passing the <tt>:verbose</tt> flag to methods
+# in Bundler::FileUtils.
+#
+# == module Bundler::FileUtils::NoWrite
+#
+# This module has all methods of Bundler::FileUtils module, but never changes
+# files/directories. This equates to passing the <tt>:noop</tt> flag to methods
+# in Bundler::FileUtils.
+#
+# == module Bundler::FileUtils::DryRun
+#
+# This module has all methods of Bundler::FileUtils module, but never changes
+# files/directories. This equates to passing the <tt>:noop</tt> and
+# <tt>:verbose</tt> flags to methods in Bundler::FileUtils.
+#
+
+module Bundler::FileUtils
+
+ def self.private_module_function(name) #:nodoc:
+ module_function name
+ private_class_method name
+ end
+
+ #
+ # Returns the name of the current directory.
+ #
+ def pwd
+ Dir.pwd
+ end
+ module_function :pwd
+
+ alias getwd pwd
+ module_function :getwd
+
+ #
+ # Changes the current directory to the directory +dir+.
+ #
+ # If this method is called with block, resumes to the old
+ # working directory after the block execution finished.
+ #
+ # Bundler::FileUtils.cd('/', :verbose => true) # chdir and report it
+ #
+ # Bundler::FileUtils.cd('/') do # chdir
+ # # ... # do something
+ # end # return to original directory
+ #
+ def cd(dir, verbose: nil, &block) # :yield: dir
+ fu_output_message "cd #{dir}" if verbose
+ Dir.chdir(dir, &block)
+ fu_output_message 'cd -' if verbose and block
+ end
+ module_function :cd
+
+ alias chdir cd
+ module_function :chdir
+
+ #
+ # Returns true if +new+ is newer than all +old_list+.
+ # Non-existent files are older than any file.
+ #
+ # Bundler::FileUtils.uptodate?('hello.o', %w(hello.c hello.h)) or \
+ # system 'make hello.o'
+ #
+ def uptodate?(new, old_list)
+ return false unless File.exist?(new)
+ new_time = File.mtime(new)
+ old_list.each do |old|
+ if File.exist?(old)
+ return false unless new_time > File.mtime(old)
+ end
+ end
+ true
+ end
+ module_function :uptodate?
+
+ def remove_trailing_slash(dir) #:nodoc:
+ dir == '/' ? dir : dir.chomp(?/)
+ end
+ private_module_function :remove_trailing_slash
+
+ #
+ # Creates one or more directories.
+ #
+ # Bundler::FileUtils.mkdir 'test'
+ # Bundler::FileUtils.mkdir %w( tmp data )
+ # Bundler::FileUtils.mkdir 'notexist', :noop => true # Does not really create.
+ # Bundler::FileUtils.mkdir 'tmp', :mode => 0700
+ #
+ def mkdir(list, mode: nil, noop: nil, verbose: nil)
+ list = fu_list(list)
+ fu_output_message "mkdir #{mode ? ('-m %03o ' % mode) : ''}#{list.join ' '}" if verbose
+ return if noop
+
+ list.each do |dir|
+ fu_mkdir dir, mode
+ end
+ end
+ module_function :mkdir
+
+ #
+ # Creates a directory and all its parent directories.
+ # For example,
+ #
+ # Bundler::FileUtils.mkdir_p '/usr/local/lib/ruby'
+ #
+ # causes to make following directories, if it does not exist.
+ #
+ # * /usr
+ # * /usr/local
+ # * /usr/local/lib
+ # * /usr/local/lib/ruby
+ #
+ # You can pass several directories at a time in a list.
+ #
+ def mkdir_p(list, mode: nil, noop: nil, verbose: nil)
+ list = fu_list(list)
+ fu_output_message "mkdir -p #{mode ? ('-m %03o ' % mode) : ''}#{list.join ' '}" if verbose
+ return *list if noop
+
+ list.map {|path| remove_trailing_slash(path)}.each do |path|
+ # optimize for the most common case
+ begin
+ fu_mkdir path, mode
+ next
+ rescue SystemCallError
+ next if File.directory?(path)
+ end
+
+ stack = []
+ until path == stack.last # dirname("/")=="/", dirname("C:/")=="C:/"
+ stack.push path
+ path = File.dirname(path)
+ end
+ stack.pop # root directory should exist
+ stack.reverse_each do |dir|
+ begin
+ fu_mkdir dir, mode
+ rescue SystemCallError
+ raise unless File.directory?(dir)
+ end
+ end
+ end
+
+ return *list
+ end
+ module_function :mkdir_p
+
+ alias mkpath mkdir_p
+ alias makedirs mkdir_p
+ module_function :mkpath
+ module_function :makedirs
+
+ def fu_mkdir(path, mode) #:nodoc:
+ path = remove_trailing_slash(path)
+ if mode
+ Dir.mkdir path, mode
+ File.chmod mode, path
+ else
+ Dir.mkdir path
+ end
+ end
+ private_module_function :fu_mkdir
+
+ #
+ # Removes one or more directories.
+ #
+ # Bundler::FileUtils.rmdir 'somedir'
+ # Bundler::FileUtils.rmdir %w(somedir anydir otherdir)
+ # # Does not really remove directory; outputs message.
+ # Bundler::FileUtils.rmdir 'somedir', :verbose => true, :noop => true
+ #
+ def rmdir(list, parents: nil, noop: nil, verbose: nil)
+ list = fu_list(list)
+ fu_output_message "rmdir #{parents ? '-p ' : ''}#{list.join ' '}" if verbose
+ return if noop
+ list.each do |dir|
+ begin
+ Dir.rmdir(dir = remove_trailing_slash(dir))
+ if parents
+ until (parent = File.dirname(dir)) == '.' or parent == dir
+ dir = parent
+ Dir.rmdir(dir)
+ end
+ end
+ rescue Errno::ENOTEMPTY, Errno::EEXIST, Errno::ENOENT
+ end
+ end
+ end
+ module_function :rmdir
+
+ #
+ # :call-seq:
+ # Bundler::FileUtils.ln(target, link, force: nil, noop: nil, verbose: nil)
+ # Bundler::FileUtils.ln(target, dir, force: nil, noop: nil, verbose: nil)
+ # Bundler::FileUtils.ln(targets, dir, force: nil, noop: nil, verbose: nil)
+ #
+ # In the first form, creates a hard link +link+ which points to +target+.
+ # If +link+ already exists, raises Errno::EEXIST.
+ # But if the :force option is set, overwrites +link+.
+ #
+ # Bundler::FileUtils.ln 'gcc', 'cc', verbose: true
+ # Bundler::FileUtils.ln '/usr/bin/emacs21', '/usr/bin/emacs'
+ #
+ # In the second form, creates a link +dir/target+ pointing to +target+.
+ # In the third form, creates several hard links in the directory +dir+,
+ # pointing to each item in +targets+.
+ # If +dir+ is not a directory, raises Errno::ENOTDIR.
+ #
+ # Bundler::FileUtils.cd '/sbin'
+ # Bundler::FileUtils.ln %w(cp mv mkdir), '/bin' # Now /sbin/cp and /bin/cp are linked.
+ #
+ def ln(src, dest, force: nil, noop: nil, verbose: nil)
+ fu_output_message "ln#{force ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if verbose
+ return if noop
+ fu_each_src_dest0(src, dest) do |s,d|
+ remove_file d, true if force
+ File.link s, d
+ end
+ end
+ module_function :ln
+
+ alias link ln
+ module_function :link
+
+ #
+ # :call-seq:
+ # Bundler::FileUtils.ln_s(target, link, force: nil, noop: nil, verbose: nil)
+ # Bundler::FileUtils.ln_s(target, dir, force: nil, noop: nil, verbose: nil)
+ # Bundler::FileUtils.ln_s(targets, dir, force: nil, noop: nil, verbose: nil)
+ #
+ # In the first form, creates a symbolic link +link+ which points to +target+.
+ # If +link+ already exists, raises Errno::EEXIST.
+ # But if the :force option is set, overwrites +link+.
+ #
+ # Bundler::FileUtils.ln_s '/usr/bin/ruby', '/usr/local/bin/ruby'
+ # Bundler::FileUtils.ln_s 'verylongsourcefilename.c', 'c', force: true
+ #
+ # In the second form, creates a link +dir/target+ pointing to +target+.
+ # In the third form, creates several symbolic links in the directory +dir+,
+ # pointing to each item in +targets+.
+ # If +dir+ is not a directory, raises Errno::ENOTDIR.
+ #
+ # Bundler::FileUtils.ln_s Dir.glob('/bin/*.rb'), '/home/foo/bin'
+ #
+ def ln_s(src, dest, force: nil, noop: nil, verbose: nil)
+ fu_output_message "ln -s#{force ? 'f' : ''} #{[src,dest].flatten.join ' '}" if verbose
+ return if noop
+ fu_each_src_dest0(src, dest) do |s,d|
+ remove_file d, true if force
+ File.symlink s, d
+ end
+ end
+ module_function :ln_s
+
+ alias symlink ln_s
+ module_function :symlink
+
+ #
+ # :call-seq:
+ # Bundler::FileUtils.ln_sf(*args)
+ #
+ # Same as
+ #
+ # Bundler::FileUtils.ln_s(*args, force: true)
+ #
+ def ln_sf(src, dest, noop: nil, verbose: nil)
+ ln_s src, dest, force: true, noop: noop, verbose: verbose
+ end
+ module_function :ln_sf
+
+ #
+ # Copies a file content +src+ to +dest+. If +dest+ is a directory,
+ # copies +src+ to +dest/src+.
+ #
+ # If +src+ is a list of files, then +dest+ must be a directory.
+ #
+ # Bundler::FileUtils.cp 'eval.c', 'eval.c.org'
+ # Bundler::FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6'
+ # Bundler::FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6', :verbose => true
+ # Bundler::FileUtils.cp 'symlink', 'dest' # copy content, "dest" is not a symlink
+ #
+ def cp(src, dest, preserve: nil, noop: nil, verbose: nil)
+ fu_output_message "cp#{preserve ? ' -p' : ''} #{[src,dest].flatten.join ' '}" if verbose
+ return if noop
+ fu_each_src_dest(src, dest) do |s, d|
+ copy_file s, d, preserve
+ end
+ end
+ module_function :cp
+
+ alias copy cp
+ module_function :copy
+
+ #
+ # Copies +src+ to +dest+. If +src+ is a directory, this method copies
+ # all its contents recursively. If +dest+ is a directory, copies
+ # +src+ to +dest/src+.
+ #
+ # +src+ can be a list of files.
+ #
+ # # Installing Ruby library "mylib" under the site_ruby
+ # Bundler::FileUtils.rm_r site_ruby + '/mylib', :force
+ # Bundler::FileUtils.cp_r 'lib/', site_ruby + '/mylib'
+ #
+ # # Examples of copying several files to target directory.
+ # Bundler::FileUtils.cp_r %w(mail.rb field.rb debug/), site_ruby + '/tmail'
+ # Bundler::FileUtils.cp_r Dir.glob('*.rb'), '/home/foo/lib/ruby', :noop => true, :verbose => true
+ #
+ # # If you want to copy all contents of a directory instead of the
+ # # directory itself, c.f. src/x -> dest/x, src/y -> dest/y,
+ # # use following code.
+ # Bundler::FileUtils.cp_r 'src/.', 'dest' # cp_r('src', 'dest') makes dest/src,
+ # # but this doesn't.
+ #
+ def cp_r(src, dest, preserve: nil, noop: nil, verbose: nil,
+ dereference_root: true, remove_destination: nil)
+ fu_output_message "cp -r#{preserve ? 'p' : ''}#{remove_destination ? ' --remove-destination' : ''} #{[src,dest].flatten.join ' '}" if verbose
+ return if noop
+ fu_each_src_dest(src, dest) do |s, d|
+ copy_entry s, d, preserve, dereference_root, remove_destination
+ end
+ end
+ module_function :cp_r
+
+ #
+ # Copies a file system entry +src+ to +dest+.
+ # If +src+ is a directory, this method copies its contents recursively.
+ # This method preserves file types, c.f. symlink, directory...
+ # (FIFO, device files and etc. are not supported yet)
+ #
+ # Both of +src+ and +dest+ must be a path name.
+ # +src+ must exist, +dest+ must not exist.
+ #
+ # If +preserve+ is true, this method preserves owner, group, and
+ # modified time. Permissions are copied regardless +preserve+.
+ #
+ # If +dereference_root+ is true, this method dereference tree root.
+ #
+ # If +remove_destination+ is true, this method removes each destination file before copy.
+ #
+ def copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false)
+ Entry_.new(src, nil, dereference_root).wrap_traverse(proc do |ent|
+ destent = Entry_.new(dest, ent.rel, false)
+ File.unlink destent.path if remove_destination && File.file?(destent.path)
+ ent.copy destent.path
+ end, proc do |ent|
+ destent = Entry_.new(dest, ent.rel, false)
+ ent.copy_metadata destent.path if preserve
+ end)
+ end
+ module_function :copy_entry
+
+ #
+ # Copies file contents of +src+ to +dest+.
+ # Both of +src+ and +dest+ must be a path name.
+ #
+ def copy_file(src, dest, preserve = false, dereference = true)
+ ent = Entry_.new(src, nil, dereference)
+ ent.copy_file dest
+ ent.copy_metadata dest if preserve
+ end
+ module_function :copy_file
+
+ #
+ # Copies stream +src+ to +dest+.
+ # +src+ must respond to #read(n) and
+ # +dest+ must respond to #write(str).
+ #
+ def copy_stream(src, dest)
+ IO.copy_stream(src, dest)
+ end
+ module_function :copy_stream
+
+ #
+ # Moves file(s) +src+ to +dest+. If +file+ and +dest+ exist on the different
+ # disk partition, the file is copied then the original file is removed.
+ #
+ # Bundler::FileUtils.mv 'badname.rb', 'goodname.rb'
+ # Bundler::FileUtils.mv 'stuff.rb', '/notexist/lib/ruby', :force => true # no error
+ #
+ # Bundler::FileUtils.mv %w(junk.txt dust.txt), '/home/foo/.trash/'
+ # Bundler::FileUtils.mv Dir.glob('test*.rb'), 'test', :noop => true, :verbose => true
+ #
+ def mv(src, dest, force: nil, noop: nil, verbose: nil, secure: nil)
+ fu_output_message "mv#{force ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if verbose
+ return if noop
+ fu_each_src_dest(src, dest) do |s, d|
+ destent = Entry_.new(d, nil, true)
+ begin
+ if destent.exist?
+ if destent.directory?
+ raise Errno::EEXIST, d
+ else
+ destent.remove_file if rename_cannot_overwrite_file?
+ end
+ end
+ begin
+ File.rename s, d
+ rescue Errno::EXDEV
+ copy_entry s, d, true
+ if secure
+ remove_entry_secure s, force
+ else
+ remove_entry s, force
+ end
+ end
+ rescue SystemCallError
+ raise unless force
+ end
+ end
+ end
+ module_function :mv
+
+ alias move mv
+ module_function :move
+
+ def rename_cannot_overwrite_file? #:nodoc:
+ /emx/ =~ RUBY_PLATFORM
+ end
+ private_module_function :rename_cannot_overwrite_file?
+
+ #
+ # Remove file(s) specified in +list+. This method cannot remove directories.
+ # All StandardErrors are ignored when the :force option is set.
+ #
+ # Bundler::FileUtils.rm %w( junk.txt dust.txt )
+ # Bundler::FileUtils.rm Dir.glob('*.so')
+ # Bundler::FileUtils.rm 'NotExistFile', :force => true # never raises exception
+ #
+ def rm(list, force: nil, noop: nil, verbose: nil)
+ list = fu_list(list)
+ fu_output_message "rm#{force ? ' -f' : ''} #{list.join ' '}" if verbose
+ return if noop
+
+ list.each do |path|
+ remove_file path, force
+ end
+ end
+ module_function :rm
+
+ alias remove rm
+ module_function :remove
+
+ #
+ # Equivalent to
+ #
+ # Bundler::FileUtils.rm(list, :force => true)
+ #
+ def rm_f(list, noop: nil, verbose: nil)
+ rm list, force: true, noop: noop, verbose: verbose
+ end
+ module_function :rm_f
+
+ alias safe_unlink rm_f
+ module_function :safe_unlink
+
+ #
+ # remove files +list+[0] +list+[1]... If +list+[n] is a directory,
+ # removes its all contents recursively. This method ignores
+ # StandardError when :force option is set.
+ #
+ # Bundler::FileUtils.rm_r Dir.glob('/tmp/*')
+ # Bundler::FileUtils.rm_r 'some_dir', :force => true
+ #
+ # WARNING: This method causes local vulnerability
+ # if one of parent directories or removing directory tree are world
+ # writable (including /tmp, whose permission is 1777), and the current
+ # process has strong privilege such as Unix super user (root), and the
+ # system has symbolic link. For secure removing, read the documentation
+ # of #remove_entry_secure carefully, and set :secure option to true.
+ # Default is :secure=>false.
+ #
+ # NOTE: This method calls #remove_entry_secure if :secure option is set.
+ # See also #remove_entry_secure.
+ #
+ def rm_r(list, force: nil, noop: nil, verbose: nil, secure: nil)
+ list = fu_list(list)
+ fu_output_message "rm -r#{force ? 'f' : ''} #{list.join ' '}" if verbose
+ return if noop
+ list.each do |path|
+ if secure
+ remove_entry_secure path, force
+ else
+ remove_entry path, force
+ end
+ end
+ end
+ module_function :rm_r
+
+ #
+ # Equivalent to
+ #
+ # Bundler::FileUtils.rm_r(list, :force => true)
+ #
+ # WARNING: This method causes local vulnerability.
+ # Read the documentation of #rm_r first.
+ #
+ def rm_rf(list, noop: nil, verbose: nil, secure: nil)
+ rm_r list, force: true, noop: noop, verbose: verbose, secure: secure
+ end
+ module_function :rm_rf
+
+ alias rmtree rm_rf
+ module_function :rmtree
+
+ #
+ # This method removes a file system entry +path+. +path+ shall be a
+ # regular file, a directory, or something. If +path+ is a directory,
+ # remove it recursively. This method is required to avoid TOCTTOU
+ # (time-of-check-to-time-of-use) local security vulnerability of #rm_r.
+ # #rm_r causes security hole when:
+ #
+ # * Parent directory is world writable (including /tmp).
+ # * Removing directory tree includes world writable directory.
+ # * The system has symbolic link.
+ #
+ # To avoid this security hole, this method applies special preprocess.
+ # If +path+ is a directory, this method chown(2) and chmod(2) all
+ # removing directories. This requires the current process is the
+ # owner of the removing whole directory tree, or is the super user (root).
+ #
+ # WARNING: You must ensure that *ALL* parent directories cannot be
+ # moved by other untrusted users. For example, parent directories
+ # should not be owned by untrusted users, and should not be world
+ # writable except when the sticky bit set.
+ #
+ # WARNING: Only the owner of the removing directory tree, or Unix super
+ # user (root) should invoke this method. Otherwise this method does not
+ # work.
+ #
+ # For details of this security vulnerability, see Perl's case:
+ #
+ # * http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2005-0448
+ # * http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2004-0452
+ #
+ # For fileutils.rb, this vulnerability is reported in [ruby-dev:26100].
+ #
+ def remove_entry_secure(path, force = false)
+ unless fu_have_symlink?
+ remove_entry path, force
+ return
+ end
+ fullpath = File.expand_path(path)
+ st = File.lstat(fullpath)
+ unless st.directory?
+ File.unlink fullpath
+ return
+ end
+ # is a directory.
+ parent_st = File.stat(File.dirname(fullpath))
+ unless parent_st.world_writable?
+ remove_entry path, force
+ return
+ end
+ unless parent_st.sticky?
+ raise ArgumentError, "parent directory is world writable, Bundler::FileUtils#remove_entry_secure does not work; abort: #{path.inspect} (parent directory mode #{'%o' % parent_st.mode})"
+ end
+ # freeze tree root
+ euid = Process.euid
+ File.open(fullpath + '/.') {|f|
+ unless fu_stat_identical_entry?(st, f.stat)
+ # symlink (TOC-to-TOU attack?)
+ File.unlink fullpath
+ return
+ end
+ f.chown euid, -1
+ f.chmod 0700
+ unless fu_stat_identical_entry?(st, File.lstat(fullpath))
+ # TOC-to-TOU attack?
+ File.unlink fullpath
+ return
+ end
+ }
+ # ---- tree root is frozen ----
+ root = Entry_.new(path)
+ root.preorder_traverse do |ent|
+ if ent.directory?
+ ent.chown euid, -1
+ ent.chmod 0700
+ end
+ end
+ root.postorder_traverse do |ent|
+ begin
+ ent.remove
+ rescue
+ raise unless force
+ end
+ end
+ rescue
+ raise unless force
+ end
+ module_function :remove_entry_secure
+
+ def fu_have_symlink? #:nodoc:
+ File.symlink nil, nil
+ rescue NotImplementedError
+ return false
+ rescue TypeError
+ return true
+ end
+ private_module_function :fu_have_symlink?
+
+ def fu_stat_identical_entry?(a, b) #:nodoc:
+ a.dev == b.dev and a.ino == b.ino
+ end
+ private_module_function :fu_stat_identical_entry?
+
+ #
+ # This method removes a file system entry +path+.
+ # +path+ might be a regular file, a directory, or something.
+ # If +path+ is a directory, remove it recursively.
+ #
+ # See also #remove_entry_secure.
+ #
+ def remove_entry(path, force = false)
+ Entry_.new(path).postorder_traverse do |ent|
+ begin
+ ent.remove
+ rescue
+ raise unless force
+ end
+ end
+ rescue
+ raise unless force
+ end
+ module_function :remove_entry
+
+ #
+ # Removes a file +path+.
+ # This method ignores StandardError if +force+ is true.
+ #
+ def remove_file(path, force = false)
+ Entry_.new(path).remove_file
+ rescue
+ raise unless force
+ end
+ module_function :remove_file
+
+ #
+ # Removes a directory +dir+ and its contents recursively.
+ # This method ignores StandardError if +force+ is true.
+ #
+ def remove_dir(path, force = false)
+ remove_entry path, force # FIXME?? check if it is a directory
+ end
+ module_function :remove_dir
+
+ #
+ # Returns true if the contents of a file +a+ and a file +b+ are identical.
+ #
+ # Bundler::FileUtils.compare_file('somefile', 'somefile') #=> true
+ # Bundler::FileUtils.compare_file('/dev/null', '/dev/urandom') #=> false
+ #
+ def compare_file(a, b)
+ return false unless File.size(a) == File.size(b)
+ File.open(a, 'rb') {|fa|
+ File.open(b, 'rb') {|fb|
+ return compare_stream(fa, fb)
+ }
+ }
+ end
+ module_function :compare_file
+
+ alias identical? compare_file
+ alias cmp compare_file
+ module_function :identical?
+ module_function :cmp
+
+ #
+ # Returns true if the contents of a stream +a+ and +b+ are identical.
+ #
+ def compare_stream(a, b)
+ bsize = fu_stream_blksize(a, b)
+ sa = String.new(capacity: bsize)
+ sb = String.new(capacity: bsize)
+ begin
+ a.read(bsize, sa)
+ b.read(bsize, sb)
+ return true if sa.empty? && sb.empty?
+ end while sa == sb
+ false
+ end
+ module_function :compare_stream
+
+ #
+ # If +src+ is not same as +dest+, copies it and changes the permission
+ # mode to +mode+. If +dest+ is a directory, destination is +dest+/+src+.
+ # This method removes destination before copy.
+ #
+ # Bundler::FileUtils.install 'ruby', '/usr/local/bin/ruby', :mode => 0755, :verbose => true
+ # Bundler::FileUtils.install 'lib.rb', '/usr/local/lib/ruby/site_ruby', :verbose => true
+ #
+ def install(src, dest, mode: nil, owner: nil, group: nil, preserve: nil,
+ noop: nil, verbose: nil)
+ if verbose
+ msg = +"install -c"
+ msg << ' -p' if preserve
+ msg << ' -m ' << mode_to_s(mode) if mode
+ msg << " -o #{owner}" if owner
+ msg << " -g #{group}" if group
+ msg << ' ' << [src,dest].flatten.join(' ')
+ fu_output_message msg
+ end
+ return if noop
+ uid = fu_get_uid(owner)
+ gid = fu_get_gid(group)
+ fu_each_src_dest(src, dest) do |s, d|
+ st = File.stat(s)
+ unless File.exist?(d) and compare_file(s, d)
+ remove_file d, true
+ copy_file s, d
+ File.utime st.atime, st.mtime, d if preserve
+ File.chmod fu_mode(mode, st), d if mode
+ File.chown uid, gid, d if uid or gid
+ end
+ end
+ end
+ module_function :install
+
+ def user_mask(target) #:nodoc:
+ target.each_char.inject(0) do |mask, chr|
+ case chr
+ when "u"
+ mask | 04700
+ when "g"
+ mask | 02070
+ when "o"
+ mask | 01007
+ when "a"
+ mask | 07777
+ else
+ raise ArgumentError, "invalid `who' symbol in file mode: #{chr}"
+ end
+ end
+ end
+ private_module_function :user_mask
+
+ def apply_mask(mode, user_mask, op, mode_mask) #:nodoc:
+ case op
+ when '='
+ (mode & ~user_mask) | (user_mask & mode_mask)
+ when '+'
+ mode | (user_mask & mode_mask)
+ when '-'
+ mode & ~(user_mask & mode_mask)
+ end
+ end
+ private_module_function :apply_mask
+
+ def symbolic_modes_to_i(mode_sym, path) #:nodoc:
+ mode = if File::Stat === path
+ path.mode
+ else
+ File.stat(path).mode
+ end
+ mode_sym.split(/,/).inject(mode & 07777) do |current_mode, clause|
+ target, *actions = clause.split(/([=+-])/)
+ raise ArgumentError, "invalid file mode: #{mode_sym}" if actions.empty?
+ target = 'a' if target.empty?
+ user_mask = user_mask(target)
+ actions.each_slice(2) do |op, perm|
+ need_apply = op == '='
+ mode_mask = (perm || '').each_char.inject(0) do |mask, chr|
+ case chr
+ when "r"
+ mask | 0444
+ when "w"
+ mask | 0222
+ when "x"
+ mask | 0111
+ when "X"
+ if FileTest.directory? path
+ mask | 0111
+ else
+ mask
+ end
+ when "s"
+ mask | 06000
+ when "t"
+ mask | 01000
+ when "u", "g", "o"
+ if mask.nonzero?
+ current_mode = apply_mask(current_mode, user_mask, op, mask)
+ end
+ need_apply = false
+ copy_mask = user_mask(chr)
+ (current_mode & copy_mask) / (copy_mask & 0111) * (user_mask & 0111)
+ else
+ raise ArgumentError, "invalid `perm' symbol in file mode: #{chr}"
+ end
+ end
+
+ if mode_mask.nonzero? || need_apply
+ current_mode = apply_mask(current_mode, user_mask, op, mode_mask)
+ end
+ end
+ current_mode
+ end
+ end
+ private_module_function :symbolic_modes_to_i
+
+ def fu_mode(mode, path) #:nodoc:
+ mode.is_a?(String) ? symbolic_modes_to_i(mode, path) : mode
+ end
+ private_module_function :fu_mode
+
+ def mode_to_s(mode) #:nodoc:
+ mode.is_a?(String) ? mode : "%o" % mode
+ end
+ private_module_function :mode_to_s
+
+ #
+ # Changes permission bits on the named files (in +list+) to the bit pattern
+ # represented by +mode+.
+ #
+ # +mode+ is the symbolic and absolute mode can be used.
+ #
+ # Absolute mode is
+ # Bundler::FileUtils.chmod 0755, 'somecommand'
+ # Bundler::FileUtils.chmod 0644, %w(my.rb your.rb his.rb her.rb)
+ # Bundler::FileUtils.chmod 0755, '/usr/bin/ruby', :verbose => true
+ #
+ # Symbolic mode is
+ # Bundler::FileUtils.chmod "u=wrx,go=rx", 'somecommand'
+ # Bundler::FileUtils.chmod "u=wr,go=rr", %w(my.rb your.rb his.rb her.rb)
+ # Bundler::FileUtils.chmod "u=wrx,go=rx", '/usr/bin/ruby', :verbose => true
+ #
+ # "a" :: is user, group, other mask.
+ # "u" :: is user's mask.
+ # "g" :: is group's mask.
+ # "o" :: is other's mask.
+ # "w" :: is write permission.
+ # "r" :: is read permission.
+ # "x" :: is execute permission.
+ # "X" ::
+ # is execute permission for directories only, must be used in conjunction with "+"
+ # "s" :: is uid, gid.
+ # "t" :: is sticky bit.
+ # "+" :: is added to a class given the specified mode.
+ # "-" :: Is removed from a given class given mode.
+ # "=" :: Is the exact nature of the class will be given a specified mode.
+
+ def chmod(mode, list, noop: nil, verbose: nil)
+ list = fu_list(list)
+ fu_output_message sprintf('chmod %s %s', mode_to_s(mode), list.join(' ')) if verbose
+ return if noop
+ list.each do |path|
+ Entry_.new(path).chmod(fu_mode(mode, path))
+ end
+ end
+ module_function :chmod
+
+ #
+ # Changes permission bits on the named files (in +list+)
+ # to the bit pattern represented by +mode+.
+ #
+ # Bundler::FileUtils.chmod_R 0700, "/tmp/app.#{$$}"
+ # Bundler::FileUtils.chmod_R "u=wrx", "/tmp/app.#{$$}"
+ #
+ def chmod_R(mode, list, noop: nil, verbose: nil, force: nil)
+ list = fu_list(list)
+ fu_output_message sprintf('chmod -R%s %s %s',
+ (force ? 'f' : ''),
+ mode_to_s(mode), list.join(' ')) if verbose
+ return if noop
+ list.each do |root|
+ Entry_.new(root).traverse do |ent|
+ begin
+ ent.chmod(fu_mode(mode, ent.path))
+ rescue
+ raise unless force
+ end
+ end
+ end
+ end
+ module_function :chmod_R
+
+ #
+ # Changes owner and group on the named files (in +list+)
+ # to the user +user+ and the group +group+. +user+ and +group+
+ # may be an ID (Integer/String) or a name (String).
+ # If +user+ or +group+ is nil, this method does not change
+ # the attribute.
+ #
+ # Bundler::FileUtils.chown 'root', 'staff', '/usr/local/bin/ruby'
+ # Bundler::FileUtils.chown nil, 'bin', Dir.glob('/usr/bin/*'), :verbose => true
+ #
+ def chown(user, group, list, noop: nil, verbose: nil)
+ list = fu_list(list)
+ fu_output_message sprintf('chown %s %s',
+ (group ? "#{user}:#{group}" : user || ':'),
+ list.join(' ')) if verbose
+ return if noop
+ uid = fu_get_uid(user)
+ gid = fu_get_gid(group)
+ list.each do |path|
+ Entry_.new(path).chown uid, gid
+ end
+ end
+ module_function :chown
+
+ #
+ # Changes owner and group on the named files (in +list+)
+ # to the user +user+ and the group +group+ recursively.
+ # +user+ and +group+ may be an ID (Integer/String) or
+ # a name (String). If +user+ or +group+ is nil, this
+ # method does not change the attribute.
+ #
+ # Bundler::FileUtils.chown_R 'www', 'www', '/var/www/htdocs'
+ # Bundler::FileUtils.chown_R 'cvs', 'cvs', '/var/cvs', :verbose => true
+ #
+ def chown_R(user, group, list, noop: nil, verbose: nil, force: nil)
+ list = fu_list(list)
+ fu_output_message sprintf('chown -R%s %s %s',
+ (force ? 'f' : ''),
+ (group ? "#{user}:#{group}" : user || ':'),
+ list.join(' ')) if verbose
+ return if noop
+ uid = fu_get_uid(user)
+ gid = fu_get_gid(group)
+ list.each do |root|
+ Entry_.new(root).traverse do |ent|
+ begin
+ ent.chown uid, gid
+ rescue
+ raise unless force
+ end
+ end
+ end
+ end
+ module_function :chown_R
+
+ begin
+ require 'etc'
+ rescue LoadError # rescue LoadError for miniruby
+ end
+
+ def fu_get_uid(user) #:nodoc:
+ return nil unless user
+ case user
+ when Integer
+ user
+ when /\A\d+\z/
+ user.to_i
+ else
+ Etc.getpwnam(user) ? Etc.getpwnam(user).uid : nil
+ end
+ end
+ private_module_function :fu_get_uid
+
+ def fu_get_gid(group) #:nodoc:
+ return nil unless group
+ case group
+ when Integer
+ group
+ when /\A\d+\z/
+ group.to_i
+ else
+ Etc.getgrnam(group) ? Etc.getgrnam(group).gid : nil
+ end
+ end
+ private_module_function :fu_get_gid
+
+ #
+ # Updates modification time (mtime) and access time (atime) of file(s) in
+ # +list+. Files are created if they don't exist.
+ #
+ # Bundler::FileUtils.touch 'timestamp'
+ # Bundler::FileUtils.touch Dir.glob('*.c'); system 'make'
+ #
+ def touch(list, noop: nil, verbose: nil, mtime: nil, nocreate: nil)
+ list = fu_list(list)
+ t = mtime
+ if verbose
+ fu_output_message "touch #{nocreate ? '-c ' : ''}#{t ? t.strftime('-t %Y%m%d%H%M.%S ') : ''}#{list.join ' '}"
+ end
+ return if noop
+ list.each do |path|
+ created = nocreate
+ begin
+ File.utime(t, t, path)
+ rescue Errno::ENOENT
+ raise if created
+ File.open(path, 'a') {
+ ;
+ }
+ created = true
+ retry if t
+ end
+ end
+ end
+ module_function :touch
+
+ private
+
+ module StreamUtils_
+ private
+
+ def fu_windows?
+ /mswin|mingw|bccwin|emx/ =~ RUBY_PLATFORM
+ end
+
+ def fu_copy_stream0(src, dest, blksize = nil) #:nodoc:
+ IO.copy_stream(src, dest)
+ end
+
+ def fu_stream_blksize(*streams)
+ streams.each do |s|
+ next unless s.respond_to?(:stat)
+ size = fu_blksize(s.stat)
+ return size if size
+ end
+ fu_default_blksize()
+ end
+
+ def fu_blksize(st)
+ s = st.blksize
+ return nil unless s
+ return nil if s == 0
+ s
+ end
+
+ def fu_default_blksize
+ 1024
+ end
+ end
+
+ include StreamUtils_
+ extend StreamUtils_
+
+ class Entry_ #:nodoc: internal use only
+ include StreamUtils_
+
+ def initialize(a, b = nil, deref = false)
+ @prefix = @rel = @path = nil
+ if b
+ @prefix = a
+ @rel = b
+ else
+ @path = a
+ end
+ @deref = deref
+ @stat = nil
+ @lstat = nil
+ end
+
+ def inspect
+ "\#<#{self.class} #{path()}>"
+ end
+
+ def path
+ if @path
+ File.path(@path)
+ else
+ join(@prefix, @rel)
+ end
+ end
+
+ def prefix
+ @prefix || @path
+ end
+
+ def rel
+ @rel
+ end
+
+ def dereference?
+ @deref
+ end
+
+ def exist?
+ begin
+ lstat
+ true
+ rescue Errno::ENOENT
+ false
+ end
+ end
+
+ def file?
+ s = lstat!
+ s and s.file?
+ end
+
+ def directory?
+ s = lstat!
+ s and s.directory?
+ end
+
+ def symlink?
+ s = lstat!
+ s and s.symlink?
+ end
+
+ def chardev?
+ s = lstat!
+ s and s.chardev?
+ end
+
+ def blockdev?
+ s = lstat!
+ s and s.blockdev?
+ end
+
+ def socket?
+ s = lstat!
+ s and s.socket?
+ end
+
+ def pipe?
+ s = lstat!
+ s and s.pipe?
+ end
+
+ S_IF_DOOR = 0xD000
+
+ def door?
+ s = lstat!
+ s and (s.mode & 0xF000 == S_IF_DOOR)
+ end
+
+ def entries
+ opts = {}
+ opts[:encoding] = ::Encoding::UTF_8 if fu_windows?
+ Dir.entries(path(), opts)\
+ .reject {|n| n == '.' or n == '..' }\
+ .map {|n| Entry_.new(prefix(), join(rel(), n.untaint)) }
+ end
+
+ def stat
+ return @stat if @stat
+ if lstat() and lstat().symlink?
+ @stat = File.stat(path())
+ else
+ @stat = lstat()
+ end
+ @stat
+ end
+
+ def stat!
+ return @stat if @stat
+ if lstat! and lstat!.symlink?
+ @stat = File.stat(path())
+ else
+ @stat = lstat!
+ end
+ @stat
+ rescue SystemCallError
+ nil
+ end
+
+ def lstat
+ if dereference?
+ @lstat ||= File.stat(path())
+ else
+ @lstat ||= File.lstat(path())
+ end
+ end
+
+ def lstat!
+ lstat()
+ rescue SystemCallError
+ nil
+ end
+
+ def chmod(mode)
+ if symlink?
+ File.lchmod mode, path() if have_lchmod?
+ else
+ File.chmod mode, path()
+ end
+ end
+
+ def chown(uid, gid)
+ if symlink?
+ File.lchown uid, gid, path() if have_lchown?
+ else
+ File.chown uid, gid, path()
+ end
+ end
+
+ def copy(dest)
+ lstat
+ case
+ when file?
+ copy_file dest
+ when directory?
+ if !File.exist?(dest) and descendant_directory?(dest, path)
+ raise ArgumentError, "cannot copy directory %s to itself %s" % [path, dest]
+ end
+ begin
+ Dir.mkdir dest
+ rescue
+ raise unless File.directory?(dest)
+ end
+ when symlink?
+ File.symlink File.readlink(path()), dest
+ when chardev?
+ raise "cannot handle device file" unless File.respond_to?(:mknod)
+ mknod dest, ?c, 0666, lstat().rdev
+ when blockdev?
+ raise "cannot handle device file" unless File.respond_to?(:mknod)
+ mknod dest, ?b, 0666, lstat().rdev
+ when socket?
+ raise "cannot handle socket" unless File.respond_to?(:mknod)
+ mknod dest, nil, lstat().mode, 0
+ when pipe?
+ raise "cannot handle FIFO" unless File.respond_to?(:mkfifo)
+ mkfifo dest, 0666
+ when door?
+ raise "cannot handle door: #{path()}"
+ else
+ raise "unknown file type: #{path()}"
+ end
+ end
+
+ def copy_file(dest)
+ File.open(path()) do |s|
+ File.open(dest, 'wb', s.stat.mode) do |f|
+ IO.copy_stream(s, f)
+ end
+ end
+ end
+
+ def copy_metadata(path)
+ st = lstat()
+ if !st.symlink?
+ File.utime st.atime, st.mtime, path
+ end
+ mode = st.mode
+ begin
+ if st.symlink?
+ begin
+ File.lchown st.uid, st.gid, path
+ rescue NotImplementedError
+ end
+ else
+ File.chown st.uid, st.gid, path
+ end
+ rescue Errno::EPERM, Errno::EACCES
+ # clear setuid/setgid
+ mode &= 01777
+ end
+ if st.symlink?
+ begin
+ File.lchmod mode, path
+ rescue NotImplementedError
+ end
+ else
+ File.chmod mode, path
+ end
+ end
+
+ def remove
+ if directory?
+ remove_dir1
+ else
+ remove_file
+ end
+ end
+
+ def remove_dir1
+ platform_support {
+ Dir.rmdir path().chomp(?/)
+ }
+ end
+
+ def remove_file
+ platform_support {
+ File.unlink path
+ }
+ end
+
+ def platform_support
+ return yield unless fu_windows?
+ first_time_p = true
+ begin
+ yield
+ rescue Errno::ENOENT
+ raise
+ rescue => err
+ if first_time_p
+ first_time_p = false
+ begin
+ File.chmod 0700, path() # Windows does not have symlink
+ retry
+ rescue SystemCallError
+ end
+ end
+ raise err
+ end
+ end
+
+ def preorder_traverse
+ stack = [self]
+ while ent = stack.pop
+ yield ent
+ stack.concat ent.entries.reverse if ent.directory?
+ end
+ end
+
+ alias traverse preorder_traverse
+
+ def postorder_traverse
+ if directory?
+ entries().each do |ent|
+ ent.postorder_traverse do |e|
+ yield e
+ end
+ end
+ end
+ ensure
+ yield self
+ end
+
+ def wrap_traverse(pre, post)
+ pre.call self
+ if directory?
+ entries.each do |ent|
+ ent.wrap_traverse pre, post
+ end
+ end
+ post.call self
+ end
+
+ private
+
+ $fileutils_rb_have_lchmod = nil
+
+ def have_lchmod?
+ # This is not MT-safe, but it does not matter.
+ if $fileutils_rb_have_lchmod == nil
+ $fileutils_rb_have_lchmod = check_have_lchmod?
+ end
+ $fileutils_rb_have_lchmod
+ end
+
+ def check_have_lchmod?
+ return false unless File.respond_to?(:lchmod)
+ File.lchmod 0
+ return true
+ rescue NotImplementedError
+ return false
+ end
+
+ $fileutils_rb_have_lchown = nil
+
+ def have_lchown?
+ # This is not MT-safe, but it does not matter.
+ if $fileutils_rb_have_lchown == nil
+ $fileutils_rb_have_lchown = check_have_lchown?
+ end
+ $fileutils_rb_have_lchown
+ end
+
+ def check_have_lchown?
+ return false unless File.respond_to?(:lchown)
+ File.lchown nil, nil
+ return true
+ rescue NotImplementedError
+ return false
+ end
+
+ def join(dir, base)
+ return File.path(dir) if not base or base == '.'
+ return File.path(base) if not dir or dir == '.'
+ File.join(dir, base)
+ end
+
+ if File::ALT_SEPARATOR
+ DIRECTORY_TERM = "(?=[/#{Regexp.quote(File::ALT_SEPARATOR)}]|\\z)"
+ else
+ DIRECTORY_TERM = "(?=/|\\z)"
+ end
+ SYSCASE = File::FNM_SYSCASE.nonzero? ? "-i" : ""
+
+ def descendant_directory?(descendant, ascendant)
+ /\A(?#{SYSCASE}:#{Regexp.quote(ascendant)})#{DIRECTORY_TERM}/ =~ File.dirname(descendant)
+ end
+ end # class Entry_
+
+ def fu_list(arg) #:nodoc:
+ [arg].flatten.map {|path| File.path(path) }
+ end
+ private_module_function :fu_list
+
+ def fu_each_src_dest(src, dest) #:nodoc:
+ fu_each_src_dest0(src, dest) do |s, d|
+ raise ArgumentError, "same file: #{s} and #{d}" if fu_same?(s, d)
+ yield s, d
+ end
+ end
+ private_module_function :fu_each_src_dest
+
+ def fu_each_src_dest0(src, dest) #:nodoc:
+ if tmp = Array.try_convert(src)
+ tmp.each do |s|
+ s = File.path(s)
+ yield s, File.join(dest, File.basename(s))
+ end
+ else
+ src = File.path(src)
+ if File.directory?(dest)
+ yield src, File.join(dest, File.basename(src))
+ else
+ yield src, File.path(dest)
+ end
+ end
+ end
+ private_module_function :fu_each_src_dest0
+
+ def fu_same?(a, b) #:nodoc:
+ File.identical?(a, b)
+ end
+ private_module_function :fu_same?
+
+ @fileutils_output = $stderr
+ @fileutils_label = ''
+
+ def fu_output_message(msg) #:nodoc:
+ @fileutils_output ||= $stderr
+ @fileutils_label ||= ''
+ @fileutils_output.puts @fileutils_label + msg
+ end
+ private_module_function :fu_output_message
+
+ # This hash table holds command options.
+ OPT_TABLE = {} #:nodoc: internal use only
+ (private_instance_methods & methods(false)).inject(OPT_TABLE) {|tbl, name|
+ (tbl[name.to_s] = instance_method(name).parameters).map! {|t, n| n if t == :key}.compact!
+ tbl
+ }
+
+ #
+ # Returns an Array of method names which have any options.
+ #
+ # p Bundler::FileUtils.commands #=> ["chmod", "cp", "cp_r", "install", ...]
+ #
+ def self.commands
+ OPT_TABLE.keys
+ end
+
+ #
+ # Returns an Array of option names.
+ #
+ # p Bundler::FileUtils.options #=> ["noop", "force", "verbose", "preserve", "mode"]
+ #
+ def self.options
+ OPT_TABLE.values.flatten.uniq.map {|sym| sym.to_s }
+ end
+
+ #
+ # Returns true if the method +mid+ have an option +opt+.
+ #
+ # p Bundler::FileUtils.have_option?(:cp, :noop) #=> true
+ # p Bundler::FileUtils.have_option?(:rm, :force) #=> true
+ # p Bundler::FileUtils.have_option?(:rm, :preserve) #=> false
+ #
+ def self.have_option?(mid, opt)
+ li = OPT_TABLE[mid.to_s] or raise ArgumentError, "no such method: #{mid}"
+ li.include?(opt)
+ end
+
+ #
+ # Returns an Array of option names of the method +mid+.
+ #
+ # p Bundler::FileUtils.options_of(:rm) #=> ["noop", "verbose", "force"]
+ #
+ def self.options_of(mid)
+ OPT_TABLE[mid.to_s].map {|sym| sym.to_s }
+ end
+
+ #
+ # Returns an Array of method names which have the option +opt+.
+ #
+ # p Bundler::FileUtils.collect_method(:preserve) #=> ["cp", "cp_r", "copy", "install"]
+ #
+ def self.collect_method(opt)
+ OPT_TABLE.keys.select {|m| OPT_TABLE[m].include?(opt) }
+ end
+
+ LOW_METHODS = singleton_methods(false) - collect_method(:noop).map(&:intern)
+ module LowMethods
+ private
+ def _do_nothing(*)end
+ ::Bundler::FileUtils::LOW_METHODS.map {|name| alias_method name, :_do_nothing}
+ end
+
+ METHODS = singleton_methods() - [:private_module_function,
+ :commands, :options, :have_option?, :options_of, :collect_method]
+
+ #
+ # This module has all methods of Bundler::FileUtils module, but it outputs messages
+ # before acting. This equates to passing the <tt>:verbose</tt> flag to
+ # methods in Bundler::FileUtils.
+ #
+ module Verbose
+ include Bundler::FileUtils
+ @fileutils_output = $stderr
+ @fileutils_label = ''
+ names = ::Bundler::FileUtils.collect_method(:verbose)
+ names.each do |name|
+ module_eval(<<-EOS, __FILE__, __LINE__ + 1)
+ def #{name}(*args, **options)
+ super(*args, **options, verbose: true)
+ end
+ EOS
+ end
+ private(*names)
+ extend self
+ class << self
+ public(*::Bundler::FileUtils::METHODS)
+ end
+ end
+
+ #
+ # This module has all methods of Bundler::FileUtils module, but never changes
+ # files/directories. This equates to passing the <tt>:noop</tt> flag
+ # to methods in Bundler::FileUtils.
+ #
+ module NoWrite
+ include Bundler::FileUtils
+ include LowMethods
+ @fileutils_output = $stderr
+ @fileutils_label = ''
+ names = ::Bundler::FileUtils.collect_method(:noop)
+ names.each do |name|
+ module_eval(<<-EOS, __FILE__, __LINE__ + 1)
+ def #{name}(*args, **options)
+ super(*args, **options, noop: true)
+ end
+ EOS
+ end
+ private(*names)
+ extend self
+ class << self
+ public(*::Bundler::FileUtils::METHODS)
+ end
+ end
+
+ #
+ # This module has all methods of Bundler::FileUtils module, but never changes
+ # files/directories, with printing message before acting.
+ # This equates to passing the <tt>:noop</tt> and <tt>:verbose</tt> flag
+ # to methods in Bundler::FileUtils.
+ #
+ module DryRun
+ include Bundler::FileUtils
+ include LowMethods
+ @fileutils_output = $stderr
+ @fileutils_label = ''
+ names = ::Bundler::FileUtils.collect_method(:noop)
+ names.each do |name|
+ module_eval(<<-EOS, __FILE__, __LINE__ + 1)
+ def #{name}(*args, **options)
+ super(*args, **options, noop: true, verbose: true)
+ end
+ EOS
+ end
+ private(*names)
+ extend self
+ class << self
+ public(*::Bundler::FileUtils::METHODS)
+ end
+ end
+
+end
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo.rb b/lib/bundler/vendor/molinillo/lib/molinillo.rb
index 134bf1d720..9e2867144f 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo.rb
@@ -1,4 +1,6 @@
# frozen_string_literal: true
+
+require 'bundler/vendor/molinillo/lib/molinillo/compatibility'
require 'bundler/vendor/molinillo/lib/molinillo/gem_metadata'
require 'bundler/vendor/molinillo/lib/molinillo/errors'
require 'bundler/vendor/molinillo/lib/molinillo/resolver'
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/compatibility.rb b/lib/bundler/vendor/molinillo/lib/molinillo/compatibility.rb
new file mode 100644
index 0000000000..3eba8e4083
--- /dev/null
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/compatibility.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module Bundler::Molinillo
+ # Hacks needed for old Ruby versions.
+ module Compatibility
+ module_function
+
+ if [].respond_to?(:flat_map)
+ # Flat map
+ # @param [Enumerable] enum an enumerable object
+ # @block the block to flat-map with
+ # @return The enum, flat-mapped
+ def flat_map(enum, &blk)
+ enum.flat_map(&blk)
+ end
+ else
+ # Flat map
+ # @param [Enumerable] enum an enumerable object
+ # @block the block to flat-map with
+ # @return The enum, flat-mapped
+ def flat_map(enum, &blk)
+ enum.map(&blk).flatten(1)
+ end
+ end
+ end
+end
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/delegates/resolution_state.rb b/lib/bundler/vendor/molinillo/lib/molinillo/delegates/resolution_state.rb
index 253c18764f..bcacf35243 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo/delegates/resolution_state.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/delegates/resolution_state.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler::Molinillo
# @!visibility private
module Delegates
@@ -45,6 +46,12 @@ module Bundler::Molinillo
current_state = state || Bundler::Molinillo::ResolutionState.empty
current_state.conflicts
end
+
+ # (see Bundler::Molinillo::ResolutionState#unused_unwind_options)
+ def unused_unwind_options
+ current_state = state || Bundler::Molinillo::ResolutionState.empty
+ current_state.unused_unwind_options
+ end
end
end
end
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/delegates/specification_provider.rb b/lib/bundler/vendor/molinillo/lib/molinillo/delegates/specification_provider.rb
index 29f48d5b3c..ec9c770a28 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo/delegates/specification_provider.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/delegates/specification_provider.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler::Molinillo
module Delegates
# Delegates all {Bundler::Molinillo::SpecificationProvider} methods to a
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb
index 76e84ab7e6..677a8bd916 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require 'set'
require 'tsort'
@@ -147,8 +148,8 @@ module Bundler::Molinillo
vertex = add_vertex(name, payload, root)
vertex.explicit_requirements << requirement if root
parent_names.each do |parent_name|
- parent_node = vertex_named(parent_name)
- add_edge(parent_node, vertex, requirement)
+ parent_vertex = vertex_named(parent_name)
+ add_edge(parent_vertex, vertex, requirement)
end
vertex
end
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/action.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/action.rb
index e0dfe6cbbd..c04c7eec9c 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/action.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/action.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler::Molinillo
class DependencyGraph
# An action that modifies a {DependencyGraph} that is reversible.
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb
index 9092e4d546..9849aea2fe 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/action'
module Bundler::Molinillo
class DependencyGraph
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rb
index eda4251801..0a1e08255b 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/action'
module Bundler::Molinillo
class DependencyGraph
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rb
index e9125a59c6..1d9f4b327d 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/action'
module Bundler::Molinillo
class DependencyGraph
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb
index d20b2cb0e0..385dcbdd06 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/action'
module Bundler::Molinillo
class DependencyGraph
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/log.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/log.rb
index 72a705e023..8582dd19c1 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/log.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/log.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular'
require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex'
require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge'
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rb
index 8d8e10fedf..37286d104a 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/action'
module Bundler::Molinillo
class DependencyGraph
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/tag.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/tag.rb
index 53524d36ad..d6ad16e07a 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/tag.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/tag.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/action'
module Bundler::Molinillo
class DependencyGraph
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/vertex.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/vertex.rb
index eab989e7bc..e4d016de24 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/vertex.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/vertex.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler::Molinillo
class DependencyGraph
# A vertex in a {DependencyGraph} that encapsulates a {#name} and a
@@ -32,7 +33,7 @@ module Bundler::Molinillo
# @return [Array<Object>] all of the requirements that required
# this vertex
def requirements
- incoming_edges.map(&:requirement) + explicit_requirements
+ (incoming_edges.map(&:requirement) + explicit_requirements).uniq
end
# @return [Array<Edge>] the edges of {#graph} that have `self` as their
@@ -53,7 +54,7 @@ module Bundler::Molinillo
# {#descendent?}
def recursive_predecessors
vertices = predecessors
- vertices += vertices.map(&:recursive_predecessors).flatten(1)
+ vertices += Compatibility.flat_map(vertices, &:recursive_predecessors)
vertices.uniq!
vertices
end
@@ -68,7 +69,7 @@ module Bundler::Molinillo
# {#ancestor?}
def recursive_successors
vertices = successors
- vertices += vertices.map(&:recursive_successors).flatten(1)
+ vertices += Compatibility.flat_map(vertices, &:recursive_successors)
vertices.uniq!
vertices
end
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/errors.rb b/lib/bundler/vendor/molinillo/lib/molinillo/errors.rb
index f904bd0814..fb343250b1 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo/errors.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/errors.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler::Molinillo
# An error that occurred during the resolution process
class ResolverError < StandardError; end
@@ -41,11 +42,11 @@ module Bundler::Molinillo
attr_reader :dependencies
# Initializes a new error with the given circular vertices.
- # @param [Array<DependencyGraph::Vertex>] nodes the nodes in the dependency
+ # @param [Array<DependencyGraph::Vertex>] vertices the vertices in the dependency
# that caused the error
- def initialize(nodes)
- super "There is a circular dependency between #{nodes.map(&:name).join(' and ')}"
- @dependencies = nodes.map(&:payload).to_set
+ def initialize(vertices)
+ super "There is a circular dependency between #{vertices.map(&:name).join(' and ')}"
+ @dependencies = vertices.map { |vertex| vertex.payload.possibilities.last }.to_set
end
end
@@ -55,11 +56,16 @@ module Bundler::Molinillo
# resolution to fail
attr_reader :conflicts
+ # @return [SpecificationProvider] the specification provider used during
+ # resolution
+ attr_reader :specification_provider
+
# Initializes a new error with the given version conflicts.
# @param [{String => Resolution::Conflict}] conflicts see {#conflicts}
- def initialize(conflicts)
+ # @param [SpecificationProvider] specification_provider see {#specification_provider}
+ def initialize(conflicts, specification_provider)
pairs = []
- conflicts.values.flatten.map(&:requirements).flatten.each do |conflicting|
+ Compatibility.flat_map(conflicts.values.flatten, &:requirements).each do |conflicting|
conflicting.each do |source, conflict_requirements|
conflict_requirements.each do |c|
pairs << [c, source]
@@ -69,7 +75,64 @@ module Bundler::Molinillo
super "Unable to satisfy the following requirements:\n\n" \
"#{pairs.map { |r, d| "- `#{r}` required by `#{d}`" }.join("\n")}"
+
@conflicts = conflicts
+ @specification_provider = specification_provider
+ end
+
+ require 'bundler/vendor/molinillo/lib/molinillo/delegates/specification_provider'
+ include Delegates::SpecificationProvider
+
+ # @return [String] An error message that includes requirement trees,
+ # which is much more detailed & customizable than the default message
+ # @param [Hash] opts the options to create a message with.
+ # @option opts [String] :solver_name The user-facing name of the solver
+ # @option opts [String] :possibility_type The generic name of a possibility
+ # @option opts [Proc] :reduce_trees A proc that reduced the list of requirement trees
+ # @option opts [Proc] :printable_requirement A proc that pretty-prints requirements
+ # @option opts [Proc] :additional_message_for_conflict A proc that appends additional
+ # messages for each conflict
+ # @option opts [Proc] :version_for_spec A proc that returns the version number for a
+ # possibility
+ def message_with_trees(opts = {})
+ solver_name = opts.delete(:solver_name) { self.class.name.split('::').first }
+ possibility_type = opts.delete(:possibility_type) { 'possibility named' }
+ reduce_trees = opts.delete(:reduce_trees) { proc { |trees| trees.uniq.sort_by(&:to_s) } }
+ printable_requirement = opts.delete(:printable_requirement) { proc { |req| req.to_s } }
+ additional_message_for_conflict = opts.delete(:additional_message_for_conflict) { proc {} }
+ version_for_spec = opts.delete(:version_for_spec) { proc(&:to_s) }
+
+ conflicts.sort.reduce(''.dup) do |o, (name, conflict)|
+ o << %(\n#{solver_name} could not find compatible versions for #{possibility_type} "#{name}":\n)
+ if conflict.locked_requirement
+ o << %( In snapshot (#{name_for_locking_dependency_source}):\n)
+ o << %( #{printable_requirement.call(conflict.locked_requirement)}\n)
+ o << %(\n)
+ end
+ o << %( In #{name_for_explicit_dependency_source}:\n)
+ trees = reduce_trees.call(conflict.requirement_trees)
+
+ o << trees.map do |tree|
+ t = ''.dup
+ depth = 2
+ tree.each do |req|
+ t << ' ' * depth << req.to_s
+ unless tree.last == req
+ if spec = conflict.activated_by_name[name_for(req)]
+ t << %( was resolved to #{version_for_spec.call(spec)}, which)
+ end
+ t << %( depends on)
+ end
+ t << %(\n)
+ depth += 1
+ end
+ t
+ end.join("\n")
+
+ additional_message_for_conflict.call(o, name, conflict)
+
+ o
+ end.strip
end
end
end
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb b/lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb
index a4fb6dd68e..3feb7be9b5 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: true
+
module Bundler::Molinillo
# The version of Bundler::Molinillo.
- VERSION = '0.5.7'.freeze
+ VERSION = '0.6.4'.freeze
end
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/modules/specification_provider.rb b/lib/bundler/vendor/molinillo/lib/molinillo/modules/specification_provider.rb
index 0f1ad195f2..fa094c1981 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo/modules/specification_provider.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/modules/specification_provider.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler::Molinillo
# Provides information about specifcations and dependencies to the resolver,
# allowing the {Resolver} class to remain generic while still providing power
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/modules/ui.rb b/lib/bundler/vendor/molinillo/lib/molinillo/modules/ui.rb
index d47cfa2928..a166bc6991 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo/modules/ui.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/modules/ui.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler::Molinillo
# Conveys information about the resolution process to a user.
module UI
@@ -48,7 +49,8 @@ module Bundler::Molinillo
if debug?
debug_info = yield
debug_info = debug_info.inspect unless debug_info.is_a?(String)
- output.puts debug_info.split("\n").map { |s| ' ' * depth + s }
+ debug_info = debug_info.split("\n").map { |s| ":#{depth.to_s.rjust 4}: #{s}" }
+ output.puts debug_info
end
end
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/resolution.rb b/lib/bundler/vendor/molinillo/lib/molinillo/resolution.rb
index 1845966a75..0eb665d17a 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo/resolution.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/resolution.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler::Molinillo
class Resolver
# A specific resolution from a given {Resolver}
@@ -8,22 +9,125 @@ module Bundler::Molinillo
# @attr [{String,Nil=>[Object]}] requirements the requirements that caused the conflict
# @attr [Object, nil] existing the existing spec that was in conflict with
# the {#possibility}
- # @attr [Object] possibility the spec that was unable to be activated due
- # to a conflict
+ # @attr [Object] possibility_set the set of specs that was unable to be
+ # activated due to a conflict.
# @attr [Object] locked_requirement the relevant locking requirement.
# @attr [Array<Array<Object>>] requirement_trees the different requirement
# trees that led to every requirement for the conflicting name.
# @attr [{String=>Object}] activated_by_name the already-activated specs.
+ # @attr [Object] underlying_error an error that has occurred during resolution, and
+ # will be raised at the end of it if no resolution is found.
Conflict = Struct.new(
:requirement,
:requirements,
:existing,
- :possibility,
+ :possibility_set,
:locked_requirement,
:requirement_trees,
- :activated_by_name
+ :activated_by_name,
+ :underlying_error
)
+ class Conflict
+ # @return [Object] a spec that was unable to be activated due to a conflict
+ def possibility
+ possibility_set && possibility_set.latest_version
+ end
+ end
+
+ # A collection of possibility states that share the same dependencies
+ # @attr [Array] dependencies the dependencies for this set of possibilities
+ # @attr [Array] possibilities the possibilities
+ PossibilitySet = Struct.new(:dependencies, :possibilities)
+
+ class PossibilitySet
+ # String representation of the possibility set, for debugging
+ def to_s
+ "[#{possibilities.join(', ')}]"
+ end
+
+ # @return [Object] most up-to-date dependency in the possibility set
+ def latest_version
+ possibilities.last
+ end
+ end
+
+ # Details of the state to unwind to when a conflict occurs, and the cause of the unwind
+ # @attr [Integer] state_index the index of the state to unwind to
+ # @attr [Object] state_requirement the requirement of the state we're unwinding to
+ # @attr [Array] requirement_tree for the requirement we're relaxing
+ # @attr [Array] conflicting_requirements the requirements that combined to cause the conflict
+ # @attr [Array] requirement_trees for the conflict
+ # @attr [Array] requirements_unwound_to_instead array of unwind requirements that were chosen over this unwind
+ UnwindDetails = Struct.new(
+ :state_index,
+ :state_requirement,
+ :requirement_tree,
+ :conflicting_requirements,
+ :requirement_trees,
+ :requirements_unwound_to_instead
+ )
+
+ class UnwindDetails
+ include Comparable
+
+ # We compare UnwindDetails when choosing which state to unwind to. If
+ # two options have the same state_index we prefer the one most
+ # removed from a requirement that caused the conflict. Both options
+ # would unwind to the same state, but a `grandparent` option will
+ # filter out fewer of its possibilities after doing so - where a state
+ # is both a `parent` and a `grandparent` to requirements that have
+ # caused a conflict this is the correct behaviour.
+ # @param [UnwindDetail] other UnwindDetail to be compared
+ # @return [Integer] integer specifying ordering
+ def <=>(other)
+ if state_index > other.state_index
+ 1
+ elsif state_index == other.state_index
+ reversed_requirement_tree_index <=> other.reversed_requirement_tree_index
+ else
+ -1
+ end
+ end
+
+ # @return [Integer] index of state requirement in reversed requirement tree
+ # (the conflicting requirement itself will be at position 0)
+ def reversed_requirement_tree_index
+ @reversed_requirement_tree_index ||=
+ if state_requirement
+ requirement_tree.reverse.index(state_requirement)
+ else
+ 999_999
+ end
+ end
+
+ # @return [Boolean] where the requirement of the state we're unwinding
+ # to directly caused the conflict. Note: in this case, it is
+ # impossible for the state we're unwinding to to be a parent of
+ # any of the other conflicting requirements (or we would have
+ # circularity)
+ def unwinding_to_primary_requirement?
+ requirement_tree.last == state_requirement
+ end
+
+ # @return [Array] array of sub-dependencies to avoid when choosing a
+ # new possibility for the state we've unwound to. Only relevant for
+ # non-primary unwinds
+ def sub_dependencies_to_avoid
+ @requirements_to_avoid ||=
+ requirement_trees.map do |tree|
+ index = tree.index(state_requirement)
+ tree[index + 1] if index
+ end.compact
+ end
+
+ # @return [Array] array of all the requirements that led to the need for
+ # this unwind
+ def all_requirements
+ @all_requirements ||= requirement_trees.flatten(1)
+ end
+ end
+
# @return [SpecificationProvider] the provider that knows about
# dependencies, requirements, specifications, versions, etc.
attr_reader :specification_provider
@@ -64,7 +168,7 @@ module Bundler::Molinillo
start_resolution
while state
- break unless state.requirements.any? || state.requirement
+ break if !state.requirement && state.requirements.empty?
indicate_progress
if state.respond_to?(:pop_possibility_state) # DependencyState
debug(depth) { "Creating possibility state for #{requirement} (#{possibilities.count} remaining)" }
@@ -78,7 +182,7 @@ module Bundler::Molinillo
process_topmost_state
end
- activated.freeze
+ resolve_activated_specs
ensure
end_resolution
end
@@ -109,6 +213,19 @@ module Bundler::Molinillo
resolver_ui.before_resolution
end
+ def resolve_activated_specs
+ activated.vertices.each do |_, vertex|
+ next unless vertex.payload
+
+ latest_version = vertex.payload.possibilities.reverse_each.find do |possibility|
+ vertex.requirements.all? { |req| requirement_satisfied_by?(req, activated, possibility) }
+ end
+
+ activated.set_payload(vertex.name, latest_version)
+ end
+ activated.freeze
+ end
+
# Ends the resolution process
# @return [void]
def end_resolution
@@ -136,9 +253,12 @@ module Bundler::Molinillo
if possibility
attempt_to_activate
else
- create_conflict if state.is_a? PossibilityState
- unwind_for_conflict until possibility && state.is_a?(DependencyState)
+ create_conflict
+ unwind_for_conflict
end
+ rescue CircularDependencyError => underlying_error
+ create_conflict(underlying_error)
+ unwind_for_conflict
end
# @return [Object] the current possibility that the resolution is trying
@@ -158,7 +278,10 @@ module Bundler::Molinillo
# @return [DependencyState] the initial state for the resolution
def initial_state
graph = DependencyGraph.new.tap do |dg|
- original_requested.each { |r| dg.add_vertex(name_for(r), nil, true).tap { |v| v.explicit_requirements << r } }
+ original_requested.each do |requested|
+ vertex = dg.add_vertex(name_for(requested), nil, true)
+ vertex.explicit_requirements << requested
+ end
dg.tag(:initial_state)
end
@@ -169,45 +292,280 @@ module Bundler::Molinillo
requirements,
graph,
initial_requirement,
- initial_requirement && search_for(initial_requirement),
+ possibilities_for_requirement(initial_requirement, graph),
0,
- {}
+ {},
+ []
)
end
# Unwinds the states stack because a conflict has been encountered
# @return [void]
def unwind_for_conflict
- debug(depth) { "Unwinding for conflict: #{requirement} to #{state_index_for_unwind / 2}" }
+ details_for_unwind = build_details_for_unwind
+ unwind_options = unused_unwind_options
+ debug(depth) { "Unwinding for conflict: #{requirement} to #{details_for_unwind.state_index / 2}" }
conflicts.tap do |c|
- sliced_states = states.slice!((state_index_for_unwind + 1)..-1)
- raise VersionConflict.new(c) unless state
+ sliced_states = states.slice!((details_for_unwind.state_index + 1)..-1)
+ raise_error_unless_state(c)
activated.rewind_to(sliced_states.first || :initial_state) if sliced_states
state.conflicts = c
+ state.unused_unwind_options = unwind_options
+ filter_possibilities_after_unwind(details_for_unwind)
index = states.size - 1
@parents_of.each { |_, a| a.reject! { |i| i >= index } }
+ state.unused_unwind_options.reject! { |uw| uw.state_index >= index }
+ end
+ end
+
+ # Raises a VersionConflict error, or any underlying error, if there is no
+ # current state
+ # @return [void]
+ def raise_error_unless_state(conflicts)
+ return if state
+
+ error = conflicts.values.map(&:underlying_error).compact.first
+ raise error || VersionConflict.new(conflicts, specification_provider)
+ end
+
+ # @return [UnwindDetails] Details of the nearest index to which we could unwind
+ def build_details_for_unwind
+ # Get the possible unwinds for the current conflict
+ current_conflict = conflicts[name]
+ binding_requirements = binding_requirements_for_conflict(current_conflict)
+ unwind_details = unwind_options_for_requirements(binding_requirements)
+
+ last_detail_for_current_unwind = unwind_details.sort.last
+ current_detail = last_detail_for_current_unwind
+
+ # Look for past conflicts that could be unwound to affect the
+ # requirement tree for the current conflict
+ relevant_unused_unwinds = unused_unwind_options.select do |alternative|
+ intersecting_requirements =
+ last_detail_for_current_unwind.all_requirements &
+ alternative.requirements_unwound_to_instead
+ next if intersecting_requirements.empty?
+ # Find the highest index unwind whilst looping through
+ current_detail = alternative if alternative > current_detail
+ alternative
+ end
+
+ # Add the current unwind options to the `unused_unwind_options` array.
+ # The "used" option will be filtered out during `unwind_for_conflict`.
+ state.unused_unwind_options += unwind_details.reject { |detail| detail.state_index == -1 }
+
+ # Update the requirements_unwound_to_instead on any relevant unused unwinds
+ relevant_unused_unwinds.each { |d| d.requirements_unwound_to_instead << current_detail.state_requirement }
+ unwind_details.each { |d| d.requirements_unwound_to_instead << current_detail.state_requirement }
+
+ current_detail
+ end
+
+ # @param [Array<Object>] array of requirements that combine to create a conflict
+ # @return [Array<UnwindDetails>] array of UnwindDetails that have a chance
+ # of resolving the passed requirements
+ def unwind_options_for_requirements(binding_requirements)
+ unwind_details = []
+
+ trees = []
+ binding_requirements.reverse_each do |r|
+ partial_tree = [r]
+ trees << partial_tree
+ unwind_details << UnwindDetails.new(-1, nil, partial_tree, binding_requirements, trees, [])
+
+ # If this requirement has alternative possibilities, check if any would
+ # satisfy the other requirements that created this conflict
+ requirement_state = find_state_for(r)
+ if conflict_fixing_possibilities?(requirement_state, binding_requirements)
+ unwind_details << UnwindDetails.new(
+ states.index(requirement_state),
+ r,
+ partial_tree,
+ binding_requirements,
+ trees,
+ []
+ )
+ end
+
+ # Next, look at the parent of this requirement, and check if the requirement
+ # could have been avoided if an alternative PossibilitySet had been chosen
+ parent_r = parent_of(r)
+ next if parent_r.nil?
+ partial_tree.unshift(parent_r)
+ requirement_state = find_state_for(parent_r)
+ if requirement_state.possibilities.any? { |set| !set.dependencies.include?(r) }
+ unwind_details << UnwindDetails.new(
+ states.index(requirement_state),
+ parent_r,
+ partial_tree,
+ binding_requirements,
+ trees,
+ []
+ )
+ end
+
+ # Finally, look at the grandparent and up of this requirement, looking
+ # for any possibilities that wouldn't create their parent requirement
+ grandparent_r = parent_of(parent_r)
+ until grandparent_r.nil?
+ partial_tree.unshift(grandparent_r)
+ requirement_state = find_state_for(grandparent_r)
+ if requirement_state.possibilities.any? { |set| !set.dependencies.include?(parent_r) }
+ unwind_details << UnwindDetails.new(
+ states.index(requirement_state),
+ grandparent_r,
+ partial_tree,
+ binding_requirements,
+ trees,
+ []
+ )
+ end
+ parent_r = grandparent_r
+ grandparent_r = parent_of(parent_r)
+ end
+ end
+
+ unwind_details
+ end
+
+ # @param [DependencyState] state
+ # @param [Array] array of requirements
+ # @return [Boolean] whether or not the given state has any possibilities
+ # that could satisfy the given requirements
+ def conflict_fixing_possibilities?(state, binding_requirements)
+ return false unless state
+
+ state.possibilities.any? do |possibility_set|
+ possibility_set.possibilities.any? do |poss|
+ possibility_satisfies_requirements?(poss, binding_requirements)
+ end
+ end
+ end
+
+ # Filter's a state's possibilities to remove any that would not fix the
+ # conflict we've just rewound from
+ # @param [UnwindDetails] details of the conflict just unwound from
+ # @return [void]
+ def filter_possibilities_after_unwind(unwind_details)
+ return unless state && !state.possibilities.empty?
+
+ if unwind_details.unwinding_to_primary_requirement?
+ filter_possibilities_for_primary_unwind(unwind_details)
+ else
+ filter_possibilities_for_parent_unwind(unwind_details)
+ end
+ end
+
+ # Filter's a state's possibilities to remove any that would not satisfy
+ # the requirements in the conflict we've just rewound from
+ # @param [UnwindDetails] details of the conflict just unwound from
+ # @return [void]
+ def filter_possibilities_for_primary_unwind(unwind_details)
+ unwinds_to_state = unused_unwind_options.select { |uw| uw.state_index == unwind_details.state_index }
+ unwinds_to_state << unwind_details
+ unwind_requirement_sets = unwinds_to_state.map(&:conflicting_requirements)
+
+ state.possibilities.reject! do |possibility_set|
+ possibility_set.possibilities.none? do |poss|
+ unwind_requirement_sets.any? do |requirements|
+ possibility_satisfies_requirements?(poss, requirements)
+ end
+ end
end
end
- # @return [Integer] The index to which the resolution should unwind in the
- # case of conflict.
- def state_index_for_unwind
- current_requirement = requirement
- existing_requirement = requirement_for_existing_name(name)
- index = -1
- [current_requirement, existing_requirement].each do |r|
- until r.nil?
- current_state = find_state_for(r)
- if state_any?(current_state)
- current_index = states.index(current_state)
- index = current_index if current_index > index
- break
+ # @param [Object] possibility a single possibility
+ # @param [Array] requirements an array of requirements
+ # @return [Boolean] whether the possibility satisfies all of the
+ # given requirements
+ def possibility_satisfies_requirements?(possibility, requirements)
+ name = name_for(possibility)
+
+ activated.tag(:swap)
+ activated.set_payload(name, possibility) if activated.vertex_named(name)
+ satisfied = requirements.all? { |r| requirement_satisfied_by?(r, activated, possibility) }
+ activated.rewind_to(:swap)
+
+ satisfied
+ end
+
+ # Filter's a state's possibilities to remove any that would (eventually)
+ # create a requirement in the conflict we've just rewound from
+ # @param [UnwindDetails] details of the conflict just unwound from
+ # @return [void]
+ def filter_possibilities_for_parent_unwind(unwind_details)
+ unwinds_to_state = unused_unwind_options.select { |uw| uw.state_index == unwind_details.state_index }
+ unwinds_to_state << unwind_details
+
+ primary_unwinds = unwinds_to_state.select(&:unwinding_to_primary_requirement?).uniq
+ parent_unwinds = unwinds_to_state.uniq - primary_unwinds
+
+ allowed_possibility_sets = Compatibility.flat_map(primary_unwinds) do |unwind|
+ states[unwind.state_index].possibilities.select do |possibility_set|
+ possibility_set.possibilities.any? do |poss|
+ possibility_satisfies_requirements?(poss, unwind.conflicting_requirements)
end
- r = parent_of(r)
end
end
- index
+ requirements_to_avoid = Compatibility.flat_map(parent_unwinds, &:sub_dependencies_to_avoid)
+
+ state.possibilities.reject! do |possibility_set|
+ !allowed_possibility_sets.include?(possibility_set) &&
+ (requirements_to_avoid - possibility_set.dependencies).empty?
+ end
+ end
+
+ # @param [Conflict] conflict
+ # @return [Array] minimal array of requirements that would cause the passed
+ # conflict to occur.
+ def binding_requirements_for_conflict(conflict)
+ return [conflict.requirement] if conflict.possibility.nil?
+
+ possible_binding_requirements = conflict.requirements.values.flatten(1).uniq
+
+ # When there’s a `CircularDependency` error the conflicting requirement
+ # (the one causing the circular) won’t be `conflict.requirement`
+ # (which won’t be for the right state, because we won’t have created it,
+ # because it’s circular).
+ # We need to make sure we have that requirement in the conflict’s list,
+ # otherwise we won’t be able to unwind properly, so we just return all
+ # the requirements for the conflict.
+ return possible_binding_requirements if conflict.underlying_error
+
+ possibilities = search_for(conflict.requirement)
+
+ # If all the requirements together don't filter out all possibilities,
+ # then the only two requirements we need to consider are the initial one
+ # (where the dependency's version was first chosen) and the last
+ if binding_requirement_in_set?(nil, possible_binding_requirements, possibilities)
+ return [conflict.requirement, requirement_for_existing_name(name_for(conflict.requirement))].compact
+ end
+
+ # Loop through the possible binding requirements, removing each one
+ # that doesn't bind. Use a `reverse_each` as we want the earliest set of
+ # binding requirements, and don't use `reject!` as we wish to refine the
+ # array *on each iteration*.
+ binding_requirements = possible_binding_requirements.dup
+ possible_binding_requirements.reverse_each do |req|
+ next if req == conflict.requirement
+ unless binding_requirement_in_set?(req, binding_requirements, possibilities)
+ binding_requirements -= [req]
+ end
+ end
+
+ binding_requirements
+ end
+
+ # @param [Object] requirement we wish to check
+ # @param [Array] array of requirements
+ # @param [Array] array of possibilities the requirements will be used to filter
+ # @return [Boolean] whether or not the given requirement is required to filter
+ # out all elements of the array of possibilities.
+ def binding_requirement_in_set?(requirement, possible_binding_requirements, possibilities)
+ possibilities.any? do |poss|
+ possibility_satisfies_requirements?(poss, possible_binding_requirements - [requirement])
+ end
end
# @return [Object] the requirement that led to `requirement` being added
@@ -222,7 +580,8 @@ module Bundler::Molinillo
# @return [Object] the requirement that led to a version of a possibility
# with the given name being activated.
def requirement_for_existing_name(name)
- return nil unless activated.vertex_named(name).payload
+ return nil unless vertex = activated.vertex_named(name)
+ return nil unless vertex.payload
states.find { |s| s.name == name }.requirement
end
@@ -230,18 +589,12 @@ module Bundler::Molinillo
# `requirement`.
def find_state_for(requirement)
return nil unless requirement
- states.reverse_each.find { |i| requirement == i.requirement && i.is_a?(DependencyState) }
- end
-
- # @return [Boolean] whether or not the given state has any possibilities
- # left.
- def state_any?(state)
- state && state.possibilities.any?
+ states.find { |i| requirement == i.requirement }
end
# @return [Conflict] a {Conflict} that reflects the failure to activate
# the {#possibility} in conjunction with the current {#state}
- def create_conflict
+ def create_conflict(underlying_error = nil)
vertex = activated.vertex_named(name)
locked_requirement = locked_requirement_named(name)
@@ -250,18 +603,21 @@ module Bundler::Molinillo
requirements[name_for_explicit_dependency_source] = vertex.explicit_requirements
end
requirements[name_for_locking_dependency_source] = [locked_requirement] if locked_requirement
- vertex.incoming_edges.each { |edge| (requirements[edge.origin.payload] ||= []).unshift(edge.requirement) }
+ vertex.incoming_edges.each do |edge|
+ (requirements[edge.origin.payload.latest_version] ||= []).unshift(edge.requirement)
+ end
activated_by_name = {}
- activated.each { |v| activated_by_name[v.name] = v.payload if v.payload }
+ activated.each { |v| activated_by_name[v.name] = v.payload.latest_version if v.payload }
conflicts[name] = Conflict.new(
requirement,
requirements,
- vertex.payload,
+ vertex.payload && vertex.payload.latest_version,
possibility,
locked_requirement,
requirement_trees,
- activated_by_name
+ activated_by_name,
+ underlying_error
)
end
@@ -311,116 +667,48 @@ module Bundler::Molinillo
# @return [void]
def attempt_to_activate
debug(depth) { 'Attempting to activate ' + possibility.to_s }
- existing_node = activated.vertex_named(name)
- if existing_node.payload
- debug(depth) { "Found existing spec (#{existing_node.payload})" }
- attempt_to_activate_existing_spec(existing_node)
- else
- attempt_to_activate_new_spec
- end
- end
-
- # Attempts to activate the current {#possibility} (given that it has
- # already been activated)
- # @return [void]
- def attempt_to_activate_existing_spec(existing_node)
- existing_spec = existing_node.payload
- if requirement_satisfied_by?(requirement, activated, existing_spec)
- new_requirements = requirements.dup
- push_state_for_requirements(new_requirements, false)
+ existing_vertex = activated.vertex_named(name)
+ if existing_vertex.payload
+ debug(depth) { "Found existing spec (#{existing_vertex.payload})" }
+ attempt_to_filter_existing_spec(existing_vertex)
else
- return if attempt_to_swap_possibility
- create_conflict
- debug(depth) { "Unsatisfied by existing spec (#{existing_node.payload})" }
- unwind_for_conflict
- end
- end
-
- # Attempts to swp the current {#possibility} with the already-activated
- # spec with the given name
- # @return [Boolean] Whether the possibility was swapped into {#activated}
- def attempt_to_swap_possibility
- activated.tag(:swap)
- vertex = activated.vertex_named(name)
- activated.set_payload(name, possibility)
- if !vertex.requirements.
- all? { |r| requirement_satisfied_by?(r, activated, possibility) } ||
- !new_spec_satisfied?
- activated.rewind_to(:swap)
- return
- end
- fixup_swapped_children(vertex)
- activate_spec
- end
-
- # Ensures there are no orphaned successors to the given {vertex}.
- # @param [DependencyGraph::Vertex] vertex the vertex to fix up.
- # @return [void]
- def fixup_swapped_children(vertex) # rubocop:disable Metrics/CyclomaticComplexity
- payload = vertex.payload
- deps = dependencies_for(payload).group_by(&method(:name_for))
- vertex.outgoing_edges.each do |outgoing_edge|
- requirement = outgoing_edge.requirement
- parent_index = @parents_of[requirement].last
- succ = outgoing_edge.destination
- matching_deps = Array(deps[succ.name])
- dep_matched = matching_deps.include?(requirement)
-
- # only push the current index when it was originally required by the
- # same named spec
- if parent_index && states[parent_index].name == name
- @parents_of[requirement].push(states.size - 1)
+ latest = possibility.latest_version
+ # use reject!(!satisfied) for 1.8.7 compatibility
+ possibility.possibilities.reject! do |possibility|
+ !requirement_satisfied_by?(requirement, activated, possibility)
end
-
- if matching_deps.empty? && !succ.root? && succ.predecessors.to_a == [vertex]
- debug(depth) { "Removing orphaned spec #{succ.name} after swapping #{name}" }
- succ.requirements.each { |r| @parents_of.delete(r) }
-
- removed_names = activated.detach_vertex_named(succ.name).map(&:name)
- requirements.delete_if do |r|
- # the only removed vertices are those with no other requirements,
- # so it's safe to delete only based upon name here
- removed_names.include?(name_for(r))
- end
- elsif !dep_matched
- debug(depth) { "Removing orphaned dependency #{requirement} after swapping #{name}" }
- # also reset if we're removing the edge, but only if its parent has
- # already been fixed up
- @parents_of[requirement].push(states.size - 1) if @parents_of[requirement].empty?
-
- activated.delete_edge(outgoing_edge)
- requirements.delete(requirement)
+ if possibility.latest_version.nil?
+ # ensure there's a possibility for better error messages
+ possibility.possibilities << latest if latest
+ create_conflict
+ unwind_for_conflict
+ else
+ activate_new_spec
end
end
end
- # Attempts to activate the current {#possibility} (given that it hasn't
- # already been activated)
+ # Attempts to update the existing vertex's `PossibilitySet` with a filtered version
# @return [void]
- def attempt_to_activate_new_spec
- if new_spec_satisfied?
- activate_spec
+ def attempt_to_filter_existing_spec(vertex)
+ filtered_set = filtered_possibility_set(vertex)
+ if !filtered_set.possibilities.empty?
+ activated.set_payload(name, filtered_set)
+ new_requirements = requirements.dup
+ push_state_for_requirements(new_requirements, false)
else
create_conflict
+ debug(depth) { "Unsatisfied by existing spec (#{vertex.payload})" }
unwind_for_conflict
end
end
- # @return [Boolean] whether the current spec is satisfied as a new
- # possibility.
- def new_spec_satisfied?
- unless requirement_satisfied_by?(requirement, activated, possibility)
- debug(depth) { 'Unsatisfied by requested spec' }
- return false
- end
-
- locked_requirement = locked_requirement_named(name)
-
- locked_spec_satisfied = !locked_requirement ||
- requirement_satisfied_by?(locked_requirement, activated, possibility)
- debug(depth) { 'Unsatisfied by locked spec' } unless locked_spec_satisfied
-
- locked_spec_satisfied
+ # Generates a filtered version of the existing vertex's `PossibilitySet` using the
+ # current state's `requirement`
+ # @param [Object] existing vertex
+ # @return [PossibilitySet] filtered possibility set
+ def filtered_possibility_set(vertex)
+ PossibilitySet.new(vertex.payload.dependencies, vertex.payload.possibilities & possibility.possibilities)
end
# @param [String] requirement_name the spec name to search for
@@ -434,7 +722,7 @@ module Bundler::Molinillo
# Add the current {#possibility} to the dependency graph of the current
# {#state}
# @return [void]
- def activate_spec
+ def activate_new_spec
conflicts.delete(name)
debug(depth) { "Activated #{name} at #{possibility}" }
activated.set_payload(name, possibility)
@@ -442,14 +730,14 @@ module Bundler::Molinillo
end
# Requires the dependencies that the recently activated spec has
- # @param [Object] activated_spec the specification that has just been
+ # @param [Object] activated_possibility the PossibilitySet that has just been
# activated
# @return [void]
- def require_nested_dependencies_for(activated_spec)
- nested_dependencies = dependencies_for(activated_spec)
+ def require_nested_dependencies_for(possibility_set)
+ nested_dependencies = dependencies_for(possibility_set.latest_version)
debug(depth) { "Requiring nested dependencies (#{nested_dependencies.join(', ')})" }
nested_dependencies.each do |d|
- activated.add_child_vertex(name_for(d), nil, [name_for(activated_spec)], d)
+ activated.add_child_vertex(name_for(d), nil, [name_for(possibility_set.latest_version)], d)
parent_index = states.size - 1
parents = @parents_of[d]
parents << parent_index if parents.empty?
@@ -464,20 +752,75 @@ module Bundler::Molinillo
# @return [void]
def push_state_for_requirements(new_requirements, requires_sort = true, new_activated = activated)
new_requirements = sort_dependencies(new_requirements.uniq, new_activated, conflicts) if requires_sort
- new_requirement = new_requirements.shift
+ new_requirement = nil
+ loop do
+ new_requirement = new_requirements.shift
+ break if new_requirement.nil? || states.none? { |s| s.requirement == new_requirement }
+ end
new_name = new_requirement ? name_for(new_requirement) : ''.freeze
- possibilities = new_requirement ? search_for(new_requirement) : []
+ possibilities = possibilities_for_requirement(new_requirement)
handle_missing_or_push_dependency_state DependencyState.new(
new_name, new_requirements, new_activated,
- new_requirement, possibilities, depth, conflicts.dup
+ new_requirement, possibilities, depth, conflicts.dup, unused_unwind_options.dup
)
end
+ # Checks a proposed requirement with any existing locked requirement
+ # before generating an array of possibilities for it.
+ # @param [Object] the proposed requirement
+ # @return [Array] possibilities
+ def possibilities_for_requirement(requirement, activated = self.activated)
+ return [] unless requirement
+ if locked_requirement_named(name_for(requirement))
+ return locked_requirement_possibility_set(requirement, activated)
+ end
+
+ group_possibilities(search_for(requirement))
+ end
+
+ # @param [Object] the proposed requirement
+ # @return [Array] possibility set containing only the locked requirement, if any
+ def locked_requirement_possibility_set(requirement, activated = self.activated)
+ all_possibilities = search_for(requirement)
+ locked_requirement = locked_requirement_named(name_for(requirement))
+
+ # Longwinded way to build a possibilities array with either the locked
+ # requirement or nothing in it. Required, since the API for
+ # locked_requirement isn't guaranteed.
+ locked_possibilities = all_possibilities.select do |possibility|
+ requirement_satisfied_by?(locked_requirement, activated, possibility)
+ end
+
+ group_possibilities(locked_possibilities)
+ end
+
+ # Build an array of PossibilitySets, with each element representing a group of
+ # dependency versions that all have the same sub-dependency version constraints
+ # and are contiguous.
+ # @param [Array] an array of possibilities
+ # @return [Array] an array of possibility sets
+ def group_possibilities(possibilities)
+ possibility_sets = []
+ current_possibility_set = nil
+
+ possibilities.reverse_each do |possibility|
+ dependencies = dependencies_for(possibility)
+ if current_possibility_set && current_possibility_set.dependencies == dependencies
+ current_possibility_set.possibilities.unshift(possibility)
+ else
+ possibility_sets.unshift(PossibilitySet.new(dependencies, [possibility]))
+ current_possibility_set = possibility_sets.first
+ end
+ end
+
+ possibility_sets
+ end
+
# Pushes a new {DependencyState}.
# If the {#specification_provider} says to
# {SpecificationProvider#allow_missing?} that particular requirement, and
# there are no possibilities for that requirement, then `state` is not
- # pushed, and the node in {#activated} is removed, and we continue
+ # pushed, and the vertex in {#activated} is removed, and we continue
# resolving the remaining requirements.
# @param [DependencyState] state
# @return [void]
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/resolver.rb b/lib/bundler/vendor/molinillo/lib/molinillo/resolver.rb
index 50d853b146..7d36858778 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo/resolver.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/resolver.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph'
module Bundler::Molinillo
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/state.rb b/lib/bundler/vendor/molinillo/lib/molinillo/state.rb
index 3a8107cf1a..68fa1f54e3 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo/state.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/state.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler::Molinillo
# A state that a {Resolution} can be in
# @attr [String] name the name of the current requirement
@@ -7,7 +8,8 @@ module Bundler::Molinillo
# @attr [Object] requirement the current requirement
# @attr [Object] possibilities the possibilities to satisfy the current requirement
# @attr [Integer] depth the depth of the resolution
- # @attr [Set<Object>] conflicts unresolved conflicts
+ # @attr [Hash] conflicts unresolved conflicts, indexed by dependency name
+ # @attr [Array<UnwindDetails>] unused_unwind_options unwinds for previous conflicts that weren't explored
ResolutionState = Struct.new(
:name,
:requirements,
@@ -15,14 +17,15 @@ module Bundler::Molinillo
:requirement,
:possibilities,
:depth,
- :conflicts
+ :conflicts,
+ :unused_unwind_options
)
class ResolutionState
# Returns an empty resolution state
# @return [ResolutionState] an empty state
def self.empty
- new(nil, [], DependencyGraph.new, nil, nil, 0, Set.new)
+ new(nil, [], DependencyGraph.new, nil, nil, 0, {}, [])
end
end
@@ -40,7 +43,8 @@ module Bundler::Molinillo
requirement,
[possibilities.pop],
depth + 1,
- conflicts.dup
+ conflicts.dup,
+ unused_unwind_options.dup
).tap do |state|
state.activated.tag(state)
end
diff --git a/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb b/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb
index c872a79c13..7cbca5bc06 100644
--- a/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb
+++ b/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb
@@ -814,7 +814,7 @@ class Bundler::Persistent::Net::HTTP::Persistent
##
# Pipelines +requests+ to the HTTP server at +uri+ yielding responses if a
- # block is given. Returns all responses recieved.
+ # block is given. Returns all responses received.
#
# See
# Net::HTTP::Pipeline[http://docs.seattlerb.org/net-http-pipeline/Net/HTTP/Pipeline.html]
diff --git a/lib/bundler/vendor/thor/lib/thor/runner.rb b/lib/bundler/vendor/thor/lib/thor/runner.rb
index 65ae422d7f..b110b8d478 100644
--- a/lib/bundler/vendor/thor/lib/thor/runner.rb
+++ b/lib/bundler/vendor/thor/lib/thor/runner.rb
@@ -3,7 +3,7 @@ require "bundler/vendor/thor/lib/thor/group"
require "bundler/vendor/thor/lib/thor/core_ext/io_binary_read"
require "yaml"
-require "digest/md5"
+require "digest"
require "pathname"
class Bundler::Thor::Runner < Bundler::Thor #:nodoc: # rubocop:disable ClassLength
@@ -90,7 +90,7 @@ class Bundler::Thor::Runner < Bundler::Thor #:nodoc: # rubocop:disable ClassLeng
end
thor_yaml[as] = {
- :filename => Digest::MD5.hexdigest(name + as),
+ :filename => Digest(:MD5).hexdigest(name + as),
:location => location,
:namespaces => Bundler::Thor::Util.namespaces_in_content(contents, base)
}
diff --git a/lib/bundler/vendored_fileutils.rb b/lib/bundler/vendored_fileutils.rb
new file mode 100644
index 0000000000..d14e98baf7
--- /dev/null
+++ b/lib/bundler/vendored_fileutils.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module Bundler; end
+if RUBY_VERSION >= "2.4"
+ require "bundler/vendor/fileutils/lib/fileutils"
+else
+ # the version we vendor is 2.4+
+ require "fileutils"
+end
diff --git a/lib/bundler/vendored_molinillo.rb b/lib/bundler/vendored_molinillo.rb
index 7b231263cb..061b634f72 100644
--- a/lib/bundler/vendored_molinillo.rb
+++ b/lib/bundler/vendored_molinillo.rb
@@ -1,3 +1,4 @@
# frozen_string_literal: true
+
module Bundler; end
require "bundler/vendor/molinillo/lib/molinillo"
diff --git a/lib/bundler/vendored_persistent.rb b/lib/bundler/vendored_persistent.rb
index 729ac6b6f5..de9c42fcc1 100644
--- a/lib/bundler/vendored_persistent.rb
+++ b/lib/bundler/vendored_persistent.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
# We forcibly require OpenSSL, because net/http/persistent will only autoload
# it. On some Rubies, autoload fails but explicit require succeeds.
begin
@@ -15,3 +16,37 @@ module Bundler
end
end
require "bundler/vendor/net-http-persistent/lib/net/http/persistent"
+
+module Bundler
+ class PersistentHTTP < Persistent::Net::HTTP::Persistent
+ def connection_for(uri)
+ connection = super
+ warn_old_tls_version_rubygems_connection(uri, connection)
+ connection
+ end
+
+ def warn_old_tls_version_rubygems_connection(uri, connection)
+ return unless connection.use_ssl?
+ return unless (uri.host || "").end_with?("rubygems.org")
+
+ socket = connection.instance_variable_get(:@socket)
+ return unless socket
+ socket_io = socket.io
+ return unless socket_io.respond_to?(:ssl_version)
+ ssl_version = socket_io.ssl_version
+
+ case ssl_version
+ when /TLSv([\d\.]+)/
+ version = Gem::Version.new($1)
+ if version < Gem::Version.new("1.2")
+ Bundler.ui.warn \
+ "Warning: Your Ruby version is compiled against a copy of OpenSSL that is very old. " \
+ "Starting in January 2018, RubyGems.org will refuse connection requests from these " \
+ "very old versions of OpenSSL. If you will need to continue installing gems after " \
+ "January 2018, please follow this guide to upgrade: http://ruby.to/tls-outdated.",
+ :wrap => true
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bundler/vendored_thor.rb b/lib/bundler/vendored_thor.rb
index 4a5d0cf6bb..8cca090f55 100644
--- a/lib/bundler/vendored_thor.rb
+++ b/lib/bundler/vendored_thor.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler
def self.require_thor_actions
Kernel.send(:require, "bundler/vendor/thor/lib/thor/actions")
diff --git a/lib/bundler/version.rb b/lib/bundler/version.rb
index b2dad6dfb6..c2355ff2c8 100644
--- a/lib/bundler/version.rb
+++ b/lib/bundler/version.rb
@@ -1,4 +1,4 @@
-# frozen_string_literal: true
+# frozen_string_literal: false
# Ruby 1.9.3 and old RubyGems don't play nice with frozen version strings
# rubocop:disable MutableConstant
@@ -7,7 +7,7 @@ module Bundler
# We're doing this because we might write tests that deal
# with other versions of bundler and we are unsure how to
# handle this better.
- VERSION = "1.15.4" unless defined?(::Bundler::VERSION)
+ VERSION = "1.16.0" unless defined?(::Bundler::VERSION)
def self.overwrite_loaded_gem_version
begin
@@ -21,4 +21,8 @@ module Bundler
end
private_class_method :overwrite_loaded_gem_version
overwrite_loaded_gem_version
+
+ def self.bundler_major_version
+ @bundler_major_version ||= VERSION.split(".").first.to_i
+ end
end
diff --git a/lib/bundler/version_ranges.rb b/lib/bundler/version_ranges.rb
index 1ee8440edd..ec25716cde 100644
--- a/lib/bundler/version_ranges.rb
+++ b/lib/bundler/version_ranges.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler
module VersionRanges
NEq = Struct.new(:version)
diff --git a/lib/bundler/vlad.rb b/lib/bundler/vlad.rb
index db78f84baa..68181e7db8 100644
--- a/lib/bundler/vlad.rb
+++ b/lib/bundler/vlad.rb
@@ -1,4 +1,9 @@
# frozen_string_literal: true
+
+require "bundler/shared_helpers"
+Bundler::SharedHelpers.major_deprecation 2,
+ "The Bundler task for Vlad"
+
# Vlad task for Bundler.
#
# Add "require 'bundler/vlad'" in your Vlad deploy.rb, and
diff --git a/lib/bundler/worker.rb b/lib/bundler/worker.rb
index b73a7ed04a..e91cfa7805 100644
--- a/lib/bundler/worker.rb
+++ b/lib/bundler/worker.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require "thread"
module Bundler
diff --git a/lib/bundler/yaml_serializer.rb b/lib/bundler/yaml_serializer.rb
index 3c9eccafc2..0fd81c40ef 100644
--- a/lib/bundler/yaml_serializer.rb
+++ b/lib/bundler/yaml_serializer.rb
@@ -37,7 +37,7 @@ module Bundler
HASH_REGEX = /
^
([ ]*) # indentations
- (.*) # key
+ (.+) # key
(?::(?=(?:\s|$))) # : (without the lookahead the #key includes this when : is present in value)
[ ]?
(?: !\s)? # optional exclamation mark found with ruby 1.9.3
@@ -54,10 +54,10 @@ module Bundler
last_empty_key = nil
str.split(/\r?\n/).each do |line|
if match = HASH_REGEX.match(line)
- indent, key, _, val = match.captures
+ indent, key, quote, val = match.captures
key = convert_to_backward_compatible_key(key)
depth = indent.scan(/ /).length
- if val.empty?
+ if quote.empty? && val.empty?
new_hash = {}
stack[depth][key] = new_hash
stack[depth + 1] = new_hash