diff options
author | Diego Steiner <diego.steiner@renuo.ch> | 2016-09-27 14:07:35 +0200 |
---|---|---|
committer | Diego Steiner <diego.steiner@renuo.ch> | 2016-09-27 14:07:35 +0200 |
commit | f809209b0c13a48a6da75692483bb51cf3ac0342 (patch) | |
tree | 37ca2dd297079ef5b91faefc423cd1b997d3991f | |
parent | 14c1a12b711878c7d7d7f360cb70c9398496b018 (diff) | |
parent | 121ada2a1842ebe9989f35567e2dd829ec06b7a1 (diff) | |
download | bundler-f809209b0c13a48a6da75692483bb51cf3ac0342.tar.gz |
Merge remote-tracking branch 'origin/master' into 4854-gemnotfound-error
101 files changed, 1394 insertions, 333 deletions
diff --git a/.rubocop.yml b/.rubocop.yml index cbce695f..b027d4cf 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -119,6 +119,9 @@ Metrics/AbcSize: Metrics/CyclomaticComplexity: Enabled: false +Metrics/ParameterLists: + Enabled: false + # It will be obvious which code is complex, Rubocop should only lint simple # rules for us. Metrics/PerceivedComplexity: diff --git a/.travis.yml b/.travis.yml index f8dd2dd7..04a0edc2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,11 +13,6 @@ branches: - /.+-stable$/ notifications: - email: - # andre - - secure: "bCcvqJT7YrBawtkXXwHhT+jOFth7r2Qv/30PkkbhQxk6Jb3xambjCOJ3U6vJ\ngYmiL50exi5lUp3oc3SEbHN5t2CrZqOZDQ6o7P8EAmB5c0oH2RrYaFOkI5Gt\nul/jGH/96A9sj0aMwG7JfdMSfhqj1DUKAm2PnnbXPL853VfmT24=" - # terence - - secure: "MQ8eA5Jb8YzEpAo58DRGfVJklAPcEbAulpBZnTxp0am6ldneDtJHbQk21w6R\nj5GsDHlzr/lMp/GHIimtUZ7rLohfND8fj/W7fs1Dkd4eN02/ERt98x3pHlqv\nvZgSnZ39uVYv+OcphraE24QaRaGWLhWZAMYQTVe/Yz50NyG8g1U=" slack: on_success: change on_failure: always @@ -43,12 +38,16 @@ env: # We need to know if changes to rubygems will break bundler on release - RGV=master # Test the latest rubygems release with all of our supported rubies - - RGV=v2.6.4 - - RGV=v2.4.8 + - RGV=v2.6.6 matrix: include: - # Ruby 2.2, Rubygems 2.4.5 and up (included by RGV above) + # Ruby 2.3, Rubygems 2.5.1 and up + - rvm: 2.2 + env: RGV=v2.5.2 + # Ruby 2.2, Rubygems 2.4.5 and up + - rvm: 2.2 + env: RGV=v2.4.8 # Ruby 2.1, Rubygems 2.2.2 and up - rvm: 2.1 env: RGV=v2.2.5 @@ -74,9 +73,12 @@ matrix: env: RGV=v1.6.2 - rvm: 1.9.3 env: RGV=v1.5.3 + # Ruby 1.8.7, Rubygems 1.3.6 and up - rvm: 1.8.7 env: RGV=v2.2.5 + # ALLOWED FAILURES + # since the great Travis image outage, frequent random segfaults :'( - rvm: 1.8.7 env: RGV=v2.0.14 - rvm: 1.8.7 @@ -93,8 +95,6 @@ matrix: env: RGV=v1.3.7 - rvm: 1.8.7 env: RGV=v1.3.6 - - # ALLOWED FAILURES # For no apparent reason, this often goes over the Travis limit - rvm: 1.8.7 env: RGV=v2.1.11 @@ -104,5 +104,21 @@ matrix: allow_failures: - rvm: 1.8.7 + env: RGV=v2.0.14 + - rvm: 1.8.7 + env: RGV=v1.8.29 + - rvm: 1.8.7 + env: RGV=v1.7.2 + - rvm: 1.8.7 + env: RGV=v1.6.2 + - rvm: 1.8.7 + env: RGV=v1.5.3 + - rvm: 1.8.7 + env: RGV=v1.4.2 + - rvm: 1.8.7 + env: RGV=v1.3.7 + - rvm: 1.8.7 + env: RGV=v1.3.6 + - rvm: 1.8.7 env: RGV=v2.1.11 - rvm: ruby-head diff --git a/CHANGELOG.md b/CHANGELOG.md index 22d1f1fb..6961b524 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,46 @@ +## 1.13.1 (2016-09-13) + +Bugfixes: + + - ensure that `Gem::Source` is available, fixing several exceptions (#4944, @dekellum) + - ensure that dependency resolution works when multiple gems have the same dependency (#4961, @segiddins) + +## 1.13.0 (2016-09-05) + +This space deliberately left blank. + +## 1.13.0.rc.2 (2016-08-21) + +Features: + + - add setting `exec_disable_load` to force `exec` to spawn a new Ruby process (@segiddins) + - add `doctor` command to help with issues like unlinked compiled gems (#4765, @mistydemeo) + - rework the `update` command, providing fine-grained control via flags (#4676, @chrismo) + - add URI to http response output in debug mode (#4808, @NickLaMuro) + - add manpage for `binstubs` command (#4847, @Zorbash) + - support `mirror` settings for sources by hostname, not only full URL (@opiethehokie) + - print gem installation errors after other install output (#4834, @segiddins) + - add `lock --remove-platform` flag to remove platforms from the lock (#4877, @segiddins) + - add `only_update_to_newer_versions` setting to prevent downgrades during `update` (@segiddins) + - expanded experimental plugin support to include hooks and sources (@asutoshpalai) + +Bugfixes: + + - retry gem downloads (#4846, @jkeiser) + - improve the CompactIndex to handle capitalized legacy gems (#4867, @segiddins) + - re-use persistent HTTP connections for CompactIndex (@NickLaMuro) + - respect `required_ruby_version` when Gemfile contains `ruby` version (@indirect) + - allow `rake release` to sign git tags (#4743, @eagletmt) + - set process titles when using `#load` during `exec` (@yob) + - recognize JRuby shebangs for using `#load` during `exec` (@ojab) + - handle world-writable home directories (#4726, @allenzhao) + - support multi-platform gems via the `gemspec` Gemfile method (#4798, @segiddins) + - print dots correctly for CompactIndex fetcher (@NickLaMuro) + - set an `open_timeout` when requesting gem data via HTTP (@NickLaMuro) + - rename the BUNDLE\_ORIG\_ENV variable so it no longer shows up in `config` (@indirect) + - show help only when `-h` or `--help` is passed to Bundler, not to `exec` (#4801, @segiddins) + - handle symlinks to binstubs created by `--standalone` (#4782, @terinjokes) + ## 1.13.0.rc.1 (2016-06-27) Features: @@ -5,7 +48,7 @@ Features: - when `bundle config major_deprecations` or `BUNDLE_MAJOR_DEPRECATIONS` is set, deprecation warnings for bundler 2 will be printed (@segiddins) - when running with `--verbose`, bundler will print the reason it is re-resolving a gemfile (@segiddins) -Bug fixes: +Bugfixes: - fix support for running RubyGems 1.x on Ruby 2.3 (#4698, @segiddins) - fix bundle exec'ing to a ruby file when gems are installed into a path (#4592, @chrismo) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 8964b75c..ab95db36 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -40,6 +40,8 @@ Bundler doesn't use a Gemfile to list development dependencies, because when we The `BUNDLE_DISABLE_POSTIT` environment variable ensures that the version of Bundler in `/path/to/bundler/lib` will be used. Without that environment setting, Bundler will automatically download, install, and run the version of Bundler listed in `Gemfile.lock`. With that set up, you can test changes you've made to Bundler by running `dbundle`, without interfering with the regular `bundle` command. +To dive into the code with Pry: `RUBYOPT=-rpry dbundle` to require pry and then run commands. + # Submitting Pull Requests Before you submit a pull request, please remember to do the following: @@ -127,7 +127,7 @@ begin rubyopt = ENV["RUBYOPT"] # When editing this list, also edit .travis.yml! branches = %w(master) - releases = %w(v1.3.6 v1.3.7 v1.4.2 v1.5.3 v1.6.2 v1.7.2 v1.8.29 v2.0.14 v2.1.11 v2.2.5 v2.4.8 v2.6.4) + releases = %w(v1.3.6 v1.3.7 v1.4.2 v1.5.3 v1.6.2 v1.7.2 v1.8.29 v2.0.14 v2.1.11 v2.2.5 v2.4.8 v2.5.2 v2.6.6) (branches + releases).each do |rg| desc "Run specs with Rubygems #{rg}" RSpec::Core::RakeTask.new(rg) do |t| @@ -4,12 +4,10 @@ # Exit cleanly from an early interrupt Signal.trap("INT") { exit 1 } -unless ENV["BUNDLE_DISABLE_POSTIT"] - update = "update".start_with?(ARGV.first || " ") && ARGV.find {|a| a.start_with?("--bundler") } - update &&= update =~ /--bundler(?:=(.+))?/ && $1 || "> 0.a" - ENV["BUNDLER_VERSION"] = update if update - require "bundler/postit_trampoline" -end +update = "update".start_with?(ARGV.first || " ") && ARGV.find {|a| a.start_with?("--bundler") } +update &&= update =~ /--bundler(?:=(.+))?/ && $1 || "> 0.a" +ENV["BUNDLER_VERSION"] = update if update +require "bundler/postit_trampoline" require "bundler" # Check if an older version of bundler is installed diff --git a/lib/bundler.rb b/lib/bundler.rb index 8b3f60d3..f5bbd61f 100644 --- a/lib/bundler.rb +++ b/lib/bundler.rb @@ -27,6 +27,7 @@ module Bundler autoload :EndpointSpecification, "bundler/endpoint_specification" autoload :Env, "bundler/env" autoload :Fetcher, "bundler/fetcher" + autoload :FeatureFlag, "bundler/feature_flag" autoload :GemHelper, "bundler/gem_helper" autoload :GemHelpers, "bundler/gem_helpers" autoload :GemVersionPromoter, "bundler/gem_version_promoter" @@ -90,7 +91,7 @@ module Bundler # Return if all groups are already loaded return @setup if defined?(@setup) && @setup - definition.validate_ruby! + definition.validate_runtime! SharedHelpers.print_major_deprecations! @@ -328,17 +329,23 @@ EOF def sudo(str) SUDO_MUTEX.synchronize do prompt = "\n\n" + <<-PROMPT.gsub(/^ {6}/, "").strip + " " - Your user account isn't allowed to install to the system Rubygems. + Your user account isn't allowed to install to the system RubyGems. You can cancel this installation and run: bundle install --path vendor/bundle to install the gems into ./vendor/bundle/, or you can enter your password - and install the bundled gems to Rubygems using sudo. + and install the bundled gems to RubyGems using sudo. Password: PROMPT + unless @prompted_for_sudo ||= system(%(sudo -k -p "#{prompt}" true)) + raise SudoNotPermittedError, + "Bundler requires sudo access to install at the moment. " \ + "Try installing again, granting Bundler sudo access when prompted, or installing into a different path." + end + `sudo -p "#{prompt}" #{str}` end end @@ -389,6 +396,10 @@ EOF @git_present = Bundler.which("git") || Bundler.which("git.exe") end + def feature_flag + @feature_flag ||= FeatureFlag.new(VERSION) + end + def reset! @root = nil @settings = nil diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index 75cceee8..ee18767a 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -92,7 +92,7 @@ module Bundler end def self.handle_no_command_error(command, has_namespace = $thor_runner) - if Bundler.settings[:plugins] && Bundler::Plugin.command?(command) + if Bundler.feature_flag.plugins? && Bundler::Plugin.command?(command) return Bundler::Plugin.exec_command(command, ARGV[1..-1]) end @@ -184,11 +184,9 @@ module Bundler map "i" => "install" def install require "bundler/cli/install" - no_install = Bundler.settings[:no_install] - Bundler.settings[:no_install] = false if no_install == true - Install.new(options.dup).run - ensure - Bundler.settings[:no_install] = no_install unless no_install.nil? + Bundler.settings.temporary(:no_install => false) do + Install.new(options.dup).run + end end desc "update [OPTIONS]", "update the current environment" @@ -255,7 +253,7 @@ module Bundler "Overwrite existing binstubs if they exist" method_option "path", :type => :string, :lazy_default => "bin", :banner => "Binstub destination directory (default bin)" - method_option "standalone", :type => :array, :lazy_default => [], :banner => + method_option "standalone", :type => :boolean, :banner => "Make binstubs that can work without the Bundler runtime" def binstubs(*gems) require "bundler/cli/binstubs" @@ -437,12 +435,13 @@ module Bundler desc "inject GEM VERSION ...", "Add the named gem(s), with version requirements, to the resolved Gemfile" def inject(name, version, *gems) + SharedHelpers.major_deprecation "The `inject` command has been replaced by the `add` command" require "bundler/cli/inject" Inject.new(options, name, version, gems).run end desc "lock", "Creates a lockfile without installing" - method_option "update", :type => :array, :lazy_default => [], :banner => + method_option "update", :type => :array, :lazy_default => true, :banner => "ignore the existing lockfile, update all gems by default, or update list of given gems" method_option "local", :type => :boolean, :default => false, :banner => "do not attempt to fetch remote gemspecs and use the local gem cache only" @@ -466,16 +465,6 @@ module Bundler Env.new.write($stdout) end - desc "add GEM [VERSION]", "Add the specified gem to the bottom of Gemfile" - method_option "group", :type => :array, :aliases => "-g", :desc => "Specify groups to add the gem in" - method_option "source", :type => :string, :aliases => "-s", :desc => "Specify the gem's source" - method_option "pre", :type => :boolean, :aliases => "-p", :default => false, :desc => "Check for newer pre-release gems" - method_option "timestamp", :type => :boolean, :aliases => "-t", :default => false, :desc => "Append timestamp to Gemfile" - def add(name, version = nil, *gems) - require "bundler/cli/inject" - Inject.new(options, name, version, gems).run - end - desc "doctor [OPTIONS]", "Checks the bundle for common problems" long_desc <<-D Doctor scans the OS dependencies of each of the gems requested in the Gemfile. If @@ -491,7 +480,7 @@ module Bundler Doctor.new(options).run end - if Bundler.settings[:plugins] + if Bundler.feature_flag.plugins? require "bundler/cli/plugin" desc "plugin SUBCOMMAND ...ARGS", "manage the bundler plugins" subcommand "plugin", Plugin diff --git a/lib/bundler/cli/binstubs.rb b/lib/bundler/cli/binstubs.rb index f7a27b01..95103b7d 100644 --- a/lib/bundler/cli/binstubs.rb +++ b/lib/bundler/cli/binstubs.rb @@ -10,7 +10,7 @@ module Bundler end def run - Bundler.definition.validate_ruby! + Bundler.definition.validate_runtime! Bundler.settings[:bin] = options["path"] if options["path"] Bundler.settings[:bin] = nil if options["path"] && options["path"].empty? installer = Installer.new(Bundler.root, Bundler.definition) diff --git a/lib/bundler/cli/cache.rb b/lib/bundler/cli/cache.rb index c8c63e92..5ba105a3 100644 --- a/lib/bundler/cli/cache.rb +++ b/lib/bundler/cli/cache.rb @@ -7,7 +7,7 @@ module Bundler end def run - Bundler.definition.validate_ruby! + 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") diff --git a/lib/bundler/cli/check.rb b/lib/bundler/cli/check.rb index 738d40b6..3f504ff6 100644 --- a/lib/bundler/cli/check.rb +++ b/lib/bundler/cli/check.rb @@ -15,7 +15,7 @@ module Bundler begin definition = Bundler.definition - definition.validate_ruby! + definition.validate_runtime! not_installed = definition.missing_specs rescue GemNotFound, VersionConflict Bundler.ui.error "Bundler can't satisfy your Gemfile's dependencies." diff --git a/lib/bundler/cli/doctor.rb b/lib/bundler/cli/doctor.rb index 8fd862a1..72866202 100644 --- a/lib/bundler/cli/doctor.rb +++ b/lib/bundler/cli/doctor.rb @@ -14,11 +14,11 @@ module Bundler end def otool_available? - system("otool --version 2>&1 >#{Bundler::NULL}") + system("otool --version 2>#{Bundler::NULL} >#{Bundler::NULL}") end def ldd_available? - !system("ldd --help 2>&1 >#{Bundler::NULL}").nil? + !system("ldd --help 2>#{Bundler::NULL} >#{Bundler::NULL}").nil? end def dylibs_darwin(path) @@ -55,22 +55,18 @@ module Bundler Dir.glob("#{spec.full_gem_path}/**/*.bundle") end + def check! + require "bundler/cli/check" + Bundler::CLI::Check.new({}).run + end + def run Bundler.ui.level = "error" if options[:quiet] + check! + definition = Bundler.definition broken_links = {} - begin - definition = Bundler.definition - definition.validate_ruby! - not_installed = definition.missing_specs - raise GemNotFound if not_installed.any? - rescue GemNotFound - Bundler.ui.warn "This bundle's gems must be installed to run this command." - Bundler.ui.warn "Install missing gems with `bundle install`." - exit 0 - end - definition.specs.each do |spec| bundles_for_gem(spec).each do |bundle| bad_paths = dylibs(bundle).select {|f| !File.exist?(f) } @@ -82,13 +78,15 @@ module Bundler end if broken_links.any? - Bundler.ui.error "The following gems are missing OS dependencies" - broken_links.each do |spec, paths| - paths.uniq.each do |path| - Bundler.ui.error " * #{spec.name}: #{path}" + message = "The following gems are missing OS dependencies:" + broken_links.map do |spec, paths| + paths.uniq.map do |path| + "\n * #{spec.name}: #{path}" end - end - exit 1 + end.flatten.sort.each {|m| message += m } + raise ProductionError, message + else + Bundler.ui.info "No issues found with the installed bundle" end end end diff --git a/lib/bundler/cli/gem.rb b/lib/bundler/cli/gem.rb index 27f4262e..4dc0dbdb 100644 --- a/lib/bundler/cli/gem.rb +++ b/lib/bundler/cli/gem.rb @@ -44,7 +44,8 @@ module Bundler :test => options[:test], :ext => options[:ext], :exe => options[:exe], - :bundler_version => bundler_dependency_version + :bundler_version => bundler_dependency_version, + :git_user_name => git_user_name.empty? ? "[USERNAME]" : git_user_name } ensure_safe_gem_name(name, constant_array) diff --git a/lib/bundler/cli/inject.rb b/lib/bundler/cli/inject.rb index 262514d5..9d1d0812 100644 --- a/lib/bundler/cli/inject.rb +++ b/lib/bundler/cli/inject.rb @@ -31,7 +31,7 @@ module Bundler Bundler.ui.confirm "Added to Gemfile:" Bundler.ui.confirm added.map {|g| " #{g}" }.join("\n") else - Bundler.ui.confirm "All injected gems were already present in the Gemfile" + Bundler.ui.confirm "All gems were already present in the Gemfile" end end diff --git a/lib/bundler/cli/install.rb b/lib/bundler/cli/install.rb index 5c7b8c5b..f1632c9e 100644 --- a/lib/bundler/cli/install.rb +++ b/lib/bundler/cli/install.rb @@ -63,10 +63,10 @@ module Bundler # rubygems plugins sometimes hook into the gem install process Gem.load_env_plugins if Gem.respond_to?(:load_env_plugins) - Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.settings[:plugins] + Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.feature_flag.plugins? definition = Bundler.definition - definition.validate_ruby! + definition.validate_runtime! installer = Installer.install(Bundler.root, definition, options) Bundler.load.cache if Bundler.app_cache.exist? && !options["no-cache"] && !Bundler.settings[:frozen] diff --git a/lib/bundler/cli/lock.rb b/lib/bundler/cli/lock.rb index a6a95f89..d4256ad4 100644 --- a/lib/bundler/cli/lock.rb +++ b/lib/bundler/cli/lock.rb @@ -17,14 +17,11 @@ module Bundler ui = Bundler.ui Bundler.ui = UI::Silent.new if print - gems = options[:update] Bundler::Fetcher.disable_endpoint = options["full-index"] - if gems && !gems.empty? - definition = Bundler.definition(:gems => gems) - else - definition = Bundler.definition(true) - end + update = options[:update] + update = { :gems => update } if update.is_a?(Array) + definition = Bundler.definition(update) options["remove-platform"].each do |platform| definition.remove_platform(platform) diff --git a/lib/bundler/cli/outdated.rb b/lib/bundler/cli/outdated.rb index 09b2d714..de710755 100644 --- a/lib/bundler/cli/outdated.rb +++ b/lib/bundler/cli/outdated.rb @@ -18,7 +18,7 @@ module Bundler Bundler::CLI::Common.select_spec(gem_name) end - Bundler.definition.validate_ruby! + Bundler.definition.validate_runtime! current_specs = Bundler.ui.silence { Bundler.load.specs } current_dependencies = {} Bundler.ui.silence { Bundler.load.dependencies.each {|dep| current_dependencies[dep.name] = dep } } diff --git a/lib/bundler/cli/platform.rb b/lib/bundler/cli/platform.rb index b5f906bf..9fdab0a5 100644 --- a/lib/bundler/cli/platform.rb +++ b/lib/bundler/cli/platform.rb @@ -29,7 +29,7 @@ module Bundler output << "Your Gemfile specifies a Ruby version requirement:\n* #{ruby_version}" begin - Bundler.definition.validate_ruby! + Bundler.definition.validate_runtime! output << "Your current platform satisfies the Ruby version requirement." rescue RubyVersionMismatch => e output << e.message diff --git a/lib/bundler/cli/show.rb b/lib/bundler/cli/show.rb index d67b086d..77e845a6 100644 --- a/lib/bundler/cli/show.rb +++ b/lib/bundler/cli/show.rb @@ -13,7 +13,7 @@ module Bundler def run Bundler.ui.silence do - Bundler.definition.validate_ruby! + Bundler.definition.validate_runtime! Bundler.load.lock end diff --git a/lib/bundler/cli/update.rb b/lib/bundler/cli/update.rb index 1c35659e..5aac47bd 100644 --- a/lib/bundler/cli/update.rb +++ b/lib/bundler/cli/update.rb @@ -10,7 +10,7 @@ module Bundler def run Bundler.ui.level = "error" if options[:quiet] - Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.settings[:plugins] + Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.feature_flag.plugins? sources = Array(options[:source]) groups = Array(options[:group]).map(&:to_sym) @@ -40,7 +40,7 @@ module Bundler end patch_level = [:major, :minor, :patch].select {|v| options.keys.include?(v.to_s) } - raise ProductionError, "Provide only one of the following options: #{patch_level.join(", ")}" unless patch_level.length <= 1 + raise InvalidOption, "Provide only one of the following options: #{patch_level.join(", ")}" unless patch_level.length <= 1 Bundler.definition.gem_version_promoter.tap do |gvp| gvp.level = patch_level.first || :major gvp.strict = options[:strict] @@ -57,7 +57,7 @@ module Bundler # rubygems plugins sometimes hook into the gem install process Gem.load_env_plugins if Gem.respond_to?(:load_env_plugins) - Bundler.definition.validate_ruby! + Bundler.definition.validate_runtime! Installer.install Bundler.root, Bundler.definition, opts Bundler.load.cache if Bundler.app_cache.exist? diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index ee966e71..12951cdf 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -62,6 +62,7 @@ module Bundler @specs = nil @ruby_version = ruby_version + @lockfile = lockfile @lockfile_contents = String.new @locked_bundler_version = nil @locked_ruby_version = nil @@ -69,7 +70,8 @@ module Bundler if lockfile && File.exist?(lockfile) @lockfile_contents = Bundler.read_file(lockfile) @locked_gems = LockfileParser.new(@lockfile_contents) - @platforms = @locked_gems.platforms + @locked_platforms = @locked_gems.platforms + @platforms = @locked_platforms.dup @locked_bundler_version = @locked_gems.bundler_version @locked_ruby_version = @locked_gems.ruby_version @@ -90,28 +92,24 @@ module Bundler @locked_deps = [] @locked_specs = SpecSet.new([]) @locked_sources = [] + @locked_platforms = [] end @unlock[:gems] ||= [] @unlock[:sources] ||= [] - @unlock[:ruby] ||= if @ruby_version && @locked_ruby_version - unless locked_ruby_version_object = RubyVersion.from_string(@locked_ruby_version) - raise LockfileError, "Failed to create a `RubyVersion` object from " \ - "`#{@locked_ruby_version}` found in #{lockfile} -- try running `bundle update --ruby`." - end + @unlock[:ruby] ||= if @ruby_version && locked_ruby_version_object @ruby_version.diff(locked_ruby_version_object) end @unlocking ||= @unlock[:ruby] ||= (!@locked_ruby_version ^ !@ruby_version) - @gem_version_promoter = create_gem_version_promoter - - current_platform = Bundler.rubygems.platforms.map {|p| generic(p) }.compact.last - add_platform(current_platform) + add_current_platform unless Bundler.settings[:frozen] @path_changes = converge_paths eager_unlock = expand_dependencies(@unlock[:gems]) @unlock[:gems] = @locked_specs.for(eager_unlock).map(&:name) + @gem_version_promoter = create_gem_version_promoter + @source_changes = converge_sources @dependency_changes = converge_dependencies @local_changes = converge_locals @@ -137,17 +135,15 @@ module Bundler end def create_gem_version_promoter - locked_specs = begin + locked_specs = if @unlocking && @locked_specs.empty? && !@lockfile_contents.empty? # Definition uses an empty set of locked_specs to indicate all gems # are unlocked, but GemVersionPromoter needs the locked_specs # for conservative comparison. - locked = Bundler::LockfileParser.new(@lockfile_contents) - Bundler::SpecSet.new(locked.specs) + Bundler::SpecSet.new(@locked_gems.specs) else @locked_specs end - end GemVersionPromoter.new(locked_specs, @unlock[:gems]) end @@ -249,7 +245,7 @@ module Bundler else # Run a resolve against the locally available gems Bundler.ui.debug("Found changes from the lockfile, re-resolving dependencies because #{change_reason}") - last_resolve.merge Resolver.resolve(expanded_dependencies, index, source_requirements, last_resolve, ruby_version, gem_version_promoter) + last_resolve.merge Resolver.resolve(expanded_dependencies, index, source_requirements, last_resolve, gem_version_promoter, additional_base_requirements_for_resolve) end end end @@ -264,6 +260,8 @@ module Bundler 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 @@ -340,6 +338,18 @@ module Bundler end end + def locked_ruby_version_object + return unless @locked_ruby_version + @locked_ruby_version_object ||= begin + unless version = RubyVersion.from_string(@locked_ruby_version) + raise LockfileError, "The Ruby version #{@locked_ruby_version} from " \ + "#{@lockfile} could not be parsed. " \ + "Try running bundle update --ruby to resolve this." + end + version + end + end + def to_lock out = String.new @@ -403,6 +413,11 @@ module Bundler deleted = [] changed = [] + new_platforms = @platforms - @locked_platforms + deleted_platforms = @locked_platforms - @platforms + added.concat new_platforms.map {|p| "* platform: #{p}" } + deleted.concat deleted_platforms.map {|p| "* platform: #{p}" } + gemfile_sources = sources.lock_sources new_sources = gemfile_sources - @locked_sources @@ -451,6 +466,11 @@ module Bundler raise ProductionError, msg if added.any? || deleted.any? || changed.any? end + def validate_runtime! + validate_ruby! + validate_platforms! + end + def validate_ruby! return unless ruby_version @@ -476,6 +496,22 @@ module Bundler end end + # TODO: refactor this so that `match_platform` can be called with two platforms + DummyPlatform = Struct.new(:platform) + class DummyPlatform; include MatchPlatform; end + def validate_platforms! + return if @platforms.any? do |bundle_platform| + bundle_platform = DummyPlatform.new(bundle_platform) + Bundler.rubygems.platforms.any? do |local_platform| + bundle_platform.match_platform(local_platform) + end + end + + raise ProductionError, "Your bundle only supports platforms #{@platforms.map(&:to_s)} " \ + "but your local platforms are #{Bundler.rubygems.platforms.map(&:to_s)}, and " \ + "there's no compatible match between those two lists." + end + def add_platform(platform) @new_platform ||= !@platforms.include?(platform) @platforms |= [platform] @@ -486,6 +522,12 @@ module Bundler raise InvalidOption, "Unable to remove the platform `#{platform}` since the only platforms are #{@platforms.join ", "}" end + def add_current_platform + current_platform = Bundler.rubygems.platforms.last + add_platform(current_platform) if Bundler.settings[:specific_platform] + add_platform(generic(current_platform)) + end + attr_reader :sources private :sources @@ -728,8 +770,38 @@ module Bundler @locked_specs.any? {|s| s.satisfies?(dep) && (!dep.source || s.source.include?(dep.source)) } end + # This list of dependencies is only used in #resolve, so it's OK to add + # the metadata dependencies here def expanded_dependencies - @expanded_dependencies ||= expand_dependencies(dependencies, @remote) + @expanded_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 + + def concat_ruby_version_requirements(ruby_version, ruby_versions = []) + return ruby_versions unless ruby_version + if ruby_version.patchlevel + ruby_versions << ruby_version.to_gem_version_with_patchlevel + else + ruby_versions.concat(ruby_version.versions.map do |version| + requirement = Gem::Requirement.new(version) + if requirement.exact? + "~> #{version}.0" + else + requirement + end + end) + end end def expand_dependencies(dependencies, remote = false) @@ -812,5 +884,14 @@ module Bundler requires end end + + def additional_base_requirements_for_resolve + return [] unless @locked_gems && Bundler.settings[:only_update_to_newer_versions] + @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) + requirements + end.values + end end end diff --git a/lib/bundler/dependency.rb b/lib/bundler/dependency.rb index 1b1808b4..66162d74 100644 --- a/lib/bundler/dependency.rb +++ b/lib/bundler/dependency.rb @@ -52,7 +52,7 @@ module Bundler :x64_mingw_20 => Gem::Platform::X64_MINGW, :x64_mingw_21 => Gem::Platform::X64_MINGW, :x64_mingw_22 => Gem::Platform::X64_MINGW, - :x64_mingw_23 => Gem::Platform::X64_MINGW + :x64_mingw_23 => Gem::Platform::X64_MINGW, }.freeze REVERSE_PLATFORM_MAP = {}.tap do |reverse_platform_map| diff --git a/lib/bundler/deployment.rb b/lib/bundler/deployment.rb index 94f2fac6..a62fa232 100644 --- a/lib/bundler/deployment.rb +++ b/lib/bundler/deployment.rb @@ -15,7 +15,7 @@ module Bundler else context_name = "vlad" role_default = "[:app]" - error_type = ::Rake::CommandFailedError + error_type = ::Vlad::CommandFailedError end roles = context.fetch(:bundle_roles, false) diff --git a/lib/bundler/dsl.rb b/lib/bundler/dsl.rb index b064c80d..428ccd4c 100644 --- a/lib/bundler/dsl.rb +++ b/lib/bundler/dsl.rb @@ -393,7 +393,8 @@ module Bundler "as an option for #{command}, but it is invalid." end - message << " Valid options are: #{valid_keys.join(", ")}" + 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 end diff --git a/lib/bundler/endpoint_specification.rb b/lib/bundler/endpoint_specification.rb index 69d05167..4f5377d3 100644 --- a/lib/bundler/endpoint_specification.rb +++ b/lib/bundler/endpoint_specification.rb @@ -113,6 +113,8 @@ module Bundler @required_ruby_version = Gem::Requirement.new(v) end end + rescue => e + raise GemspecError, "There was an error parsing the metadata for the gem #{name} (#{version}): #{e.class}\n#{e}\nThe metadata was #{data.inspect}" end def build_dependency(name, requirements) diff --git a/lib/bundler/env.rb b/lib/bundler/env.rb index 75edbf2b..74541681 100644 --- a/lib/bundler/env.rb +++ b/lib/bundler/env.rb @@ -20,6 +20,7 @@ module Bundler 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 << " 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? @@ -33,6 +34,8 @@ module Bundler end end + return out unless SharedHelpers.in_bundle? + if print_gemfile out << "\n#{Bundler.default_gemfile.relative_path_from(SharedHelpers.pwd)}\n\n" out << " " << read_file(Bundler.default_gemfile).gsub(/\n/, "\n ") << "\n" diff --git a/lib/bundler/errors.rb b/lib/bundler/errors.rb index 7681ea73..dd5782fb 100644 --- a/lib/bundler/errors.rb +++ b/lib/bundler/errors.rb @@ -52,6 +52,7 @@ module Bundler class CyclicDependencyError < BundlerError; status_code(21); end class GemfileLockNotFound < BundlerError; status_code(22); end class PluginError < BundlerError; status_code(29); end + class SudoNotPermittedError < BundlerError; status_code(30); end class GemfileEvalError < GemfileError; end class MarshalError < StandardError; end diff --git a/lib/bundler/feature_flag.rb b/lib/bundler/feature_flag.rb new file mode 100644 index 00000000..80bf2a51 --- /dev/null +++ b/lib/bundler/feature_flag.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true +module Bundler + class FeatureFlag + def self.settings_flag(flag) + 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}?") { Bundler.settings[flag] } + end + + (1..10).each {|v| define_method("bundler_#{v}_mode?") { major_version >= v } } + + settings_flag :allow_offline_install + settings_flag :plugins + + def initialize(bundler_version) + @bundler_version = Gem::Version.create(bundler_version) + end + + def major_version + @bundler_version.segments.first + end + private :major_version + + class << self; private :settings_flag; end + end +end diff --git a/lib/bundler/fetcher/compact_index.rb b/lib/bundler/fetcher/compact_index.rb index fb774518..9461368d 100644 --- a/lib/bundler/fetcher/compact_index.rb +++ b/lib/bundler/fetcher/compact_index.rb @@ -110,7 +110,7 @@ module Bundler begin downloader.fetch(fetch_uri + path, headers) rescue NetworkDownError => e - raise unless Bundler.settings[:allow_offline_install] && headers["If-None-Match"] + raise unless Bundler.feature_flag.allow_offline_install? && headers["If-None-Match"] Bundler.ui.warn "Using the cached data for the new index because of a network error: #{e}" Net::HTTPNotModified.new(nil, nil, nil) end diff --git a/lib/bundler/fetcher/downloader.rb b/lib/bundler/fetcher/downloader.rb index c8d714c0..ee1aa1a9 100644 --- a/lib/bundler/fetcher/downloader.rb +++ b/lib/bundler/fetcher/downloader.rb @@ -38,6 +38,8 @@ module Bundler end def request(uri, options) + validate_uri_scheme!(uri) + Bundler.ui.debug "HTTP GET #{uri}" req = Net::HTTP::Get.new uri.request_uri, options if uri.user @@ -61,6 +63,15 @@ module Bundler raise HTTPError, "Network error while fetching #{URICredentialsFilter.credential_filtered_uri(uri)}" end end + + private + + def validate_uri_scheme!(uri) + return if uri.scheme =~ /\Ahttps?\z/ + raise InvalidOption, + "The request uri `#{uri}` has an invalid scheme (`#{uri.scheme}`). " \ + "Did you mean `http` or `https`?" + end end end end diff --git a/lib/bundler/gem_helpers.rb b/lib/bundler/gem_helpers.rb index 5c824ffe..6d926ce8 100644 --- a/lib/bundler/gem_helpers.rb +++ b/lib/bundler/gem_helpers.rb @@ -28,5 +28,73 @@ module Bundler generic(Gem::Platform.local) end module_function :generic_local_platform + + def platform_specificity_match(spec_platform, user_platform) + spec_platform = Gem::Platform.new(spec_platform) + return PlatformMatch::EXACT_MATCH if spec_platform == user_platform + return PlatformMatch::WORST_MATCH if spec_platform.nil? || spec_platform == Gem::Platform::RUBY || user_platform == Gem::Platform::RUBY + + PlatformMatch.new( + PlatformMatch.os_match(spec_platform, user_platform), + PlatformMatch.cpu_match(spec_platform, user_platform), + PlatformMatch.platform_version_match(spec_platform, user_platform) + ) + end + module_function :platform_specificity_match + + def select_best_platform_match(specs, platform) + specs.select {|spec| spec.match_platform(platform) }. + min_by {|spec| platform_specificity_match(spec.platform, platform) } + end + module_function :select_best_platform_match + + PlatformMatch = Struct.new(:os_match, :cpu_match, :platform_version_match) + class PlatformMatch + def <=>(other) + return nil unless other.is_a?(PlatformMatch) + + m = os_match <=> other.os_match + return m unless m.zero? + + m = cpu_match <=> other.cpu_match + return m unless m.zero? + + m = platform_version_match <=> other.platform_version_match + m + end + + EXACT_MATCH = new(-1, -1, -1).freeze + WORST_MATCH = new(1_000_000, 1_000_000, 1_000_000).freeze + + def self.os_match(spec_platform, user_platform) + if spec_platform.os == user_platform.os + 0 + else + 1 + end + end + + def self.cpu_match(spec_platform, user_platform) + if spec_platform.cpu == user_platform.cpu + 0 + elsif spec_platform.cpu == "arm" && user_platform.cpu.to_s.start_with?("arm") + 0 + elsif spec_platform.cpu.nil? || spec_platform.cpu == "universal" + 1 + else + 2 + end + end + + def self.platform_version_match(spec_platform, user_platform) + if spec_platform.version == user_platform.version + 0 + elsif spec_platform.version.nil? + 1 + else + 2 + end + end + end end end diff --git a/lib/bundler/inline.rb b/lib/bundler/inline.rb index dcaf2294..dec3be3e 100644 --- a/lib/bundler/inline.rb +++ b/lib/bundler/inline.rb @@ -41,13 +41,13 @@ def gemfile(install = false, options = {}, &gemfile) end ENV["BUNDLE_GEMFILE"] ||= "Gemfile" - Bundler::Plugin.gemfile_install(&gemfile) if Bundler.settings[:plugins] + Bundler::Plugin.gemfile_install(&gemfile) if Bundler.feature_flag.plugins? builder = Bundler::Dsl.new builder.instance_eval(&gemfile) definition = builder.to_definition(nil, true) def definition.lock(*); end - definition.validate_ruby! + definition.validate_runtime! missing_specs = proc do begin diff --git a/lib/bundler/installer.rb b/lib/bundler/installer.rb index 528dee17..824b1a45 100644 --- a/lib/bundler/installer.rb +++ b/lib/bundler/installer.rb @@ -207,7 +207,7 @@ module Bundler end unless Bundler.bundle_path.exist? rescue Errno::EEXIST raise PathError, "Could not install to path `#{Bundler.settings[:path]}` " \ - "because of an invalid symlink. Remove the symlink so the directory can be created." + "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) diff --git a/lib/bundler/lazy_specification.rb b/lib/bundler/lazy_specification.rb index 36ff5c59..0b667f7d 100644 --- a/lib/bundler/lazy_specification.rb +++ b/lib/bundler/lazy_specification.rb @@ -6,6 +6,20 @@ require "bundler/match_platform" module Bundler class LazySpecification Identifier = Struct.new(:name, :version, :source, :platform, :dependencies) + class Identifier + include Comparable + def <=>(other) + return unless other.is_a?(Identifier) + [name, version, platform_string] <=> [other.name, other.version, other.platform_string] + end + + protected + + def platform_string + platform_string = platform.to_s + platform_string == Index::RUBY ? Index::NULL : platform_string + end + end include MatchPlatform @@ -55,7 +69,8 @@ module Bundler end def __materialize__ - @specification = source.specs.search(Gem::Dependency.new(name, version)).last + search_object = Bundler.settings[:specific_platform] ? self : Dependency.new(name, version) + @specification = source.specs.search(search_object).last end def respond_to?(*args) diff --git a/lib/bundler/lockfile_parser.rb b/lib/bundler/lockfile_parser.rb index 063a1887..51148ab6 100644 --- a/lib/bundler/lockfile_parser.rb +++ b/lib/bundler/lockfile_parser.rb @@ -92,7 +92,7 @@ module Bundler end end @sources << @rubygems_aggregate - @specs = @specs.values + @specs = @specs.values.sort_by(&:identifier) warn_for_outdated_bundler_version rescue ArgumentError => e Bundler.ui.debug(e) diff --git a/lib/bundler/match_platform.rb b/lib/bundler/match_platform.rb index fed418b5..0a4e4c7e 100644 --- a/lib/bundler/match_platform.rb +++ b/lib/bundler/match_platform.rb @@ -8,7 +8,8 @@ module Bundler def match_platform(p) Gem::Platform::RUBY == platform || platform.nil? || p == platform || - generic(Gem::Platform.new(platform)) === p + generic(Gem::Platform.new(platform)) === p || + Gem::Platform.new(platform) === p end end end diff --git a/lib/bundler/plugin.rb b/lib/bundler/plugin.rb index 1f0297f2..8fb41193 100644 --- a/lib/bundler/plugin.rb +++ b/lib/bundler/plugin.rb @@ -62,7 +62,9 @@ module Bundler save_plugins plugins, installed_specs, builder.inferred_plugins rescue => e - Bundler.ui.error "Failed to install plugin: #{e.message}\n #{e.backtrace[0]}" + unless e.is_a?(GemfileError) + Bundler.ui.error "Failed to install plugin: #{e.message}\n #{e.backtrace[0]}" + end raise end @@ -158,7 +160,7 @@ module Bundler # # @param [String] event def hook(event, *args, &arg_blk) - return unless Bundler.settings[:plugins] + return unless Bundler.feature_flag.plugins? plugins = index.hook_plugins(event) return unless plugins.any? diff --git a/lib/bundler/postit_trampoline.rb b/lib/bundler/postit_trampoline.rb index dbb23aa4..2a224899 100644 --- a/lib/bundler/postit_trampoline.rb +++ b/lib/bundler/postit_trampoline.rb @@ -1,13 +1,18 @@ # frozen_string_literal: true -if ENV["BUNDLE_ENABLE_TRAMPOLINE"] - module BundlerVendoredPostIt; end - require "bundler/vendor/postit/lib/postit" - require "rubygems" +module BundlerVendoredPostIt; end +require "bundler/vendor/postit/lib/postit" +require "rubygems" - environment = BundlerVendoredPostIt::PostIt::Environment.new([]) - version = Gem::Requirement.new(environment.bundler_version) +environment = BundlerVendoredPostIt::PostIt::Environment.new([]) +version = Gem::Requirement.new(environment.bundler_version) +if version.requirements.size == 1 && version.requirements.first.first == "=" # version.exact? + if version.requirements.first.last.segments.first >= 2 + ENV["BUNDLE_ENABLE_TRAMPOLINE"] = "true" + end +end +if ENV["BUNDLE_ENABLE_TRAMPOLINE"] && !ENV["BUNDLE_DISABLE_POSTIT"] installed_version = if defined?(Bundler::VERSION) Bundler::VERSION @@ -65,4 +70,4 @@ You're running Bundler #{installed_version} but this project uses #{running_vers abort "The running bundler (#{running_version}) does not match the required `#{version}`" end -end # unless ENV["BUNDLE_ENABLE_TRAMPOLINE"] +end # if ENV["BUNDLE_ENABLE_TRAMPOLINE"] && !ENV["BUNDLE_DISABLE_POSTIT"] diff --git a/lib/bundler/remote_specification.rb b/lib/bundler/remote_specification.rb index 6a02897c..112c7f97 100644 --- a/lib/bundler/remote_specification.rb +++ b/lib/bundler/remote_specification.rb @@ -81,5 +81,10 @@ module Bundler def method_missing(method, *args, &blk) _remote_specification.send(method, *args, &blk) end + + def respond_to?(method, include_all = false) + super || _remote_specification.respond_to?(method, include_all) + end + public :respond_to? end end diff --git a/lib/bundler/resolver.rb b/lib/bundler/resolver.rb index 81537baf..e636e1f8 100644 --- a/lib/bundler/resolver.rb +++ b/lib/bundler/resolver.rb @@ -66,48 +66,39 @@ module Bundler end end - ALL = Bundler::Dependency::PLATFORM_MAP.values.uniq.freeze - class SpecGroup < Array include GemHelpers - attr_reader :activated, :required_by + attr_reader :activated def initialize(a) super - @required_by = [] - @activated = [] + @required_by = [] + @activated_platforms = [] @dependencies = nil - @specs = {} - - ALL.each do |p| - @specs[p] = reverse.find {|s| s.match_platform(p) } + @specs = Hash.new do |specs, platform| + specs[platform] = select_best_platform_match(self, platform) end end def initialize_copy(o) super - @required_by = o.required_by.dup - @activated = o.activated.dup + @activated_platforms = o.activated.dup end def to_specs - specs = {} - - @activated.each do |p| + @activated_platforms.map do |p| next unless s = @specs[p] - platform = generic(Gem::Platform.new(s.platform)) - next if specs[platform] - - lazy_spec = LazySpecification.new(name, version, platform, source) + lazy_spec = LazySpecification.new(name, version, s.platform, source) lazy_spec.dependencies.replace s.dependencies - specs[platform] = lazy_spec - end - specs.values + lazy_spec + end.compact end def activate_platform!(platform) - @activated << platform if !@activated.include?(platform) && for?(platform, nil) + return unless for?(platform) + return if @activated_platforms.include?(platform) + @activated_platforms << platform end def name @@ -122,17 +113,9 @@ module Bundler @source ||= first.source end - def for?(platform, ruby_version) + def for?(platform) spec = @specs[platform] - return false unless spec - - return true if ruby_version.nil? - # Only allow endpoint specifications since they won't hit the network to - # fetch the full gemspec when calling required_ruby_version - return true if !spec.is_a?(EndpointSpecification) && !spec.is_a?(Gem::Specification) - return true if spec.required_ruby_version.nil? - - spec.required_ruby_version.satisfied_by?(ruby_version.to_gem_version_with_patchlevel) + !spec.nil? end def to_s @@ -140,7 +123,11 @@ module Bundler end def dependencies_for_activated_platforms - @activated.map {|p| __dependencies[p] }.flatten + 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) @@ -150,18 +137,31 @@ module Bundler private def __dependencies - @dependencies ||= begin - dependencies = {} - ALL.each do |p| - next unless spec = @specs[p] - dependencies[p] = [] + @dependencies = Hash.new do |dependencies, platform| + dependencies[platform] = [] + if spec = @specs[platform] spec.dependencies.each do |dep| next if dep.type == :development - dependencies[p] << DepProxy.new(dep, p) + dependencies[platform] << DepProxy.new(dep, platform) end end - dependencies + 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 @@ -175,29 +175,34 @@ module Bundler # ==== Returns # <GemBundle>,nil:: If the list of dependencies can be resolved, a # collection of gemspecs is returned. Otherwise, nil is returned. - def self.resolve(requirements, index, source_requirements = {}, base = [], ruby_version = nil, gem_version_promoter = GemVersionPromoter.new) + def self.resolve(requirements, index, source_requirements = {}, base = [], gem_version_promoter = GemVersionPromoter.new, additional_base_requirements = []) base = SpecSet.new(base) unless base.is_a?(SpecSet) - resolver = new(index, source_requirements, base, ruby_version, gem_version_promoter) + resolver = new(index, source_requirements, base, gem_version_promoter, additional_base_requirements) result = resolver.start(requirements) SpecSet.new(result) end - def initialize(index, source_requirements, base, ruby_version, gem_version_promoter) + def initialize(index, source_requirements, base, gem_version_promoter, additional_base_requirements) @index = index @source_requirements = source_requirements @base = base @resolver = Molinillo::Resolver.new(self, self) @search_for = {} @base_dg = Molinillo::DependencyGraph.new - @base.each {|ls| @base_dg.add_vertex(ls.name, Dependency.new(ls.name, ls.version), true) } - @ruby_version = ruby_version + @base.each do |ls| + dep = Dependency.new(ls.name, ls.version) + @base_dg.add_vertex(ls.name, DepProxy.new(dep, ls.platform), true) + end + additional_base_requirements.each {|d| @base_dg.add_vertex(d.name, d) } @gem_version_promoter = gem_version_promoter end def start(requirements) verify_gemfile_dependencies_are_found!(requirements) dg = @resolver.resolve(requirements, @base_dg) - dg.map(&:payload).map(&:to_specs).flatten + 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) rescue Molinillo::CircularDependencyError => e @@ -278,7 +283,7 @@ module Bundler @gem_version_promoter.sort_versions(dependency, spec_groups) end end - search.select {|sg| sg.for?(platform, @ruby_version) }.each {|sg| sg.activate_platform!(platform) } + search.select {|sg| sg.for?(platform) }.each {|sg| sg.activate_platform!(platform) } end def index_for(dependency) @@ -302,7 +307,8 @@ module Bundler end def requirement_satisfied_by?(requirement, activated, spec) - requirement.matches_spec?(spec) || spec.source.is_a?(Source::Gemspec) + return false unless requirement.matches_spec?(spec) || spec.source.is_a?(Source::Gemspec) + spec.activate_platform!(requirement.__platform) || spec.for?(requirement.__platform) end def sort_dependencies(dependencies, activated, conflicts) diff --git a/lib/bundler/ruby_version.rb b/lib/bundler/ruby_version.rb index 9321f94c..ebdefe63 100644 --- a/lib/bundler/ruby_version.rb +++ b/lib/bundler/ruby_version.rb @@ -23,8 +23,8 @@ module Bundler @versions = Array(versions) @gem_version = Gem::Requirement.create(@versions.first).requirements.first.last - @input_engine = engine - @engine = engine || "ruby" + @input_engine = engine && engine.to_s + @engine = engine && engine.to_s || "ruby" @engine_versions = (engine_version && Array(engine_version)) || @versions @engine_gem_version = Gem::Requirement.create(@engine_versions.first).requirements.first.last @patchlevel = patchlevel @@ -128,6 +128,11 @@ module Bundler end end + def exact? + return @exact if defined?(@exact) + @exact = versions.all? {|v| Gem::Requirement.create(v).exact? } + end + private def matches?(requirements, version) diff --git a/lib/bundler/rubygems_ext.rb b/lib/bundler/rubygems_ext.rb index fc8eadd1..53a153e5 100644 --- a/lib/bundler/rubygems_ext.rb +++ b/lib/bundler/rubygems_ext.rb @@ -8,6 +8,16 @@ end require "rubygems" require "rubygems/specification" + +begin + # Possible use in Gem::Specification#source below and require + # shouldn't be deferred. + require "rubygems/source" +rescue LoadError + # Not available before Rubygems 2.0.0, ignore + nil +end + require "bundler/match_platform" module Gem @@ -159,6 +169,11 @@ module Gem def none? @none ||= (to_s == ">= 0") end unless allocate.respond_to?(:none?) + + def exact? + return false unless @requirements.size == 1 + @requirements[0][0] == "=" + end unless allocate.respond_to?(:exact?) end class Platform diff --git a/lib/bundler/rubygems_gem_installer.rb b/lib/bundler/rubygems_gem_installer.rb index e18f4626..28ad988b 100644 --- a/lib/bundler/rubygems_gem_installer.rb +++ b/lib/bundler/rubygems_gem_installer.rb @@ -12,5 +12,53 @@ module Bundler def check_executable_overwrite(filename) # Bundler needs to install gems regardless of binstub overwriting end + + def pre_install_checks + super && validate_bundler_checksum(options[:bundler_expected_checksum]) + end + + private + + def validate_bundler_checksum(checksum) + return true if Bundler.settings[:disable_checksum_validation] + return true unless checksum + 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 << io.read(16_384) until io.eof? + io.rewind + send(checksum_type(checksum), digest) + end + unless digest == checksum + raise SecurityError, + "The checksum for the downloaded `#{spec.full_name}.gem` did not match " \ + "the checksum given by the API. This means that the contents of the " \ + "gem appear to be different from what was uploaded, and could be an indicator of a security issue.\n" \ + "(The expected SHA256 checksum was #{checksum.inspect}, but the checksum for the downloaded gem was #{digest.inspect}.)\n" \ + "Bundler cannot continue installing #{spec.name} (#{spec.version})." + end + true + end + + def checksum_type(checksum) + case checksum.length + when 64 then :hexdigest! + when 44 then :base64digest! + else raise InstallError, "The given checksum for #{spec.full_name} (#{checksum.inspect}) is not a valid SHA256 hexdigest nor base64digest" + end + end + + def hexdigest!(digest) + digest.hexdigest! + end + + def base64digest!(digest) + if digest.respond_to?(:base64digest!) + digest.base64digest! + else + [digest.digest!].pack("m0") + end + end end end diff --git a/lib/bundler/rubygems_integration.rb b/lib/bundler/rubygems_integration.rb index c1bb6c7a..23ae95aa 100644 --- a/lib/bundler/rubygems_integration.rb +++ b/lib/bundler/rubygems_integration.rb @@ -489,6 +489,7 @@ module Bundler end def redefine_method(klass, method, unbound_method = nil, &block) + visibility = method_visibility(klass, method) begin if (instance_method = klass.instance_method(method)) && method != :initialize # doing this to ensure we also get private methods @@ -501,8 +502,20 @@ module Bundler @replaced_methods[[method, klass]] = instance_method if unbound_method klass.send(:define_method, method, unbound_method) + klass.send(visibility, method) elsif block klass.send(:define_method, method, &block) + klass.send(visibility, method) + end + end + + def method_visibility(klass, method) + if klass.private_method_defined?(method) + :private + elsif klass.protected_method_defined?(method) + :protected + else + :public end end diff --git a/lib/bundler/runtime.rb b/lib/bundler/runtime.rb index fda499cf..45f445ae 100644 --- a/lib/bundler/runtime.rb +++ b/lib/bundler/runtime.rb @@ -140,7 +140,8 @@ module Bundler Bundler.ui.info "Updating files in #{Bundler.settings.app_cache_path}" - specs.each do |spec| + specs_to_cache = Bundler.settings[:cache_all_platforms] ? @definition.resolve.materialized_for_all_platforms : specs + specs_to_cache.each do |spec| next if spec.name == "bundler" next if spec.source.is_a?(Source::Gemspec) spec.source.send(:fetch_gem, spec) if Bundler.settings[:cache_all_platforms] && spec.source.respond_to?(:fetch_gem, true) diff --git a/lib/bundler/settings.rb b/lib/bundler/settings.rb index 2a04805f..db6a4ab6 100644 --- a/lib/bundler/settings.rb +++ b/lib/bundler/settings.rb @@ -6,6 +6,7 @@ module Bundler BOOL_KEYS = %w( allow_offline_install cache_all + disable_checksum_validation disable_exec_load disable_local_branch_check disable_shared_gems @@ -16,6 +17,7 @@ module Bundler major_deprecations no_install no_prune + only_update_to_newer_versions plugins silence_root_warning ).freeze @@ -40,11 +42,18 @@ module Bundler @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 = (@local_config[key] || ENV[key] || @global_config[key] || DEFAULT_CONFIG[name]) + value = @temporary.fetch(name) do + @local_config.fetch(key) do + ENV.fetch(key) do + @global_config.fetch(key) do + DEFAULT_CONFIG.fetch(name) do + nil + end end end end end if value.nil? nil @@ -74,9 +83,19 @@ module Bundler 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) + return unless block_given? + begin + yield + ensure + existing.each {|k, v| v.nil? ? @temporary.delete(k) : @temporary[k] = v } + end + end + def delete(key) @local_config.delete(key_for(key)) end @@ -277,7 +296,7 @@ module Bundler }xo def load_config(config_file) - return unless config_file + return {} unless config_file SharedHelpers.filesystem_access(config_file, :read) do |file| valid_file = file.exist? && !file.size.zero? return {} if ignore_config? || !valid_file diff --git a/lib/bundler/setup.rb b/lib/bundler/setup.rb index 8b4b4797..4e213bee 100644 --- a/lib/bundler/setup.rb +++ b/lib/bundler/setup.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require "bundler/postit_trampoline" unless ENV["BUNDLE_DISABLE_POSTIT"] +require "bundler/postit_trampoline" require "bundler/shared_helpers" if Bundler::SharedHelpers.in_bundle? diff --git a/lib/bundler/shared_helpers.rb b/lib/bundler/shared_helpers.rb index 69543356..ca4eafd6 100644 --- a/lib/bundler/shared_helpers.rb +++ b/lib/bundler/shared_helpers.rb @@ -224,7 +224,8 @@ module Bundler def prints_major_deprecations? require "bundler" - return false unless Bundler.settings[:major_deprecations] + deprecation_release = Bundler::VERSION.split(".").drop(1).include?("99") + return false if !deprecation_release && !Bundler.settings[:major_deprecations] require "bundler/deprecate" return false if Bundler::Deprecate.skip true diff --git a/lib/bundler/source.rb b/lib/bundler/source.rb index afa7d918..9d65d461 100644 --- a/lib/bundler/source.rb +++ b/lib/bundler/source.rb @@ -14,12 +14,13 @@ module Bundler def version_message(spec) message = "#{spec.name} #{spec.version}" + message += " (#{spec.platform})" if spec.platform != Gem::Platform::RUBY && !spec.platform.nil? if Bundler.locked_gems locked_spec = Bundler.locked_gems.specs.find {|s| s.name == spec.name } locked_spec_version = locked_spec.version if locked_spec if locked_spec_version && spec.version != locked_spec_version - message += " (#{Bundler.ui.add_color("was #{locked_spec_version}", :green)})" + message += Bundler.ui.add_color(" (was #{locked_spec_version})", :green) end end diff --git a/lib/bundler/source/git.rb b/lib/bundler/source/git.rb index 60fb555f..d1757a4a 100644 --- a/lib/bundler/source/git.rb +++ b/lib/bundler/source/git.rb @@ -292,7 +292,7 @@ module Bundler def fetch git_proxy.checkout rescue GitError - raise unless Bundler.settings[:allow_offline_install] + raise unless Bundler.feature_flag.allow_offline_install? Bundler.ui.warn "Using cached git data because of network errors" end end diff --git a/lib/bundler/source/git/git_proxy.rb b/lib/bundler/source/git/git_proxy.rb index 4b76d187..c44f00d7 100644 --- a/lib/bundler/source/git/git_proxy.rb +++ b/lib/bundler/source/git/git_proxy.rb @@ -224,6 +224,11 @@ module Bundler raise GitError, "The git source #{uri} is not yet checked out. Please run `bundle install` before trying to start your application" end + # TODO: Replace this with Open3 when upgrading to bundler 2 + # Similar to #git_null, as Open3 is not cross-platform, + # a temporary way is to use Tempfile to capture the stderr. + # When replacing this using Open3, make sure git_null is + # also replaced by Open3, so stdout and stderr all got handled properly. def capture_and_filter_stderr(uri) return_value, captured_err = "" backup_stderr = STDERR.dup diff --git a/lib/bundler/source/path.rb b/lib/bundler/source/path.rb index 69bb0c1a..87a49044 100644 --- a/lib/bundler/source/path.rb +++ b/lib/bundler/source/path.rb @@ -46,7 +46,7 @@ module Bundler def to_lock out = String.new("PATH\n") - out << " remote: #{relative_path}\n" + out << " remote: #{lockfile_path}\n" out << " glob: #{@glob}\n" unless @glob == DEFAULT_GLOB out << " specs:\n" end @@ -129,6 +129,11 @@ module Bundler "`#{somepath}`.\nThe error message was: #{e.message}." end + def lockfile_path + return relative_path if path.absolute? + expand(path).relative_path_from(Bundler.root) + end + def app_cache_path(custom_path = nil) @app_cache_path ||= Bundler.app_cache(custom_path).join(app_cache_dirname) end diff --git a/lib/bundler/source/rubygems.rb b/lib/bundler/source/rubygems.rb index aedad708..89f7673e 100644 --- a/lib/bundler/source/rubygems.rb +++ b/lib/bundler/source/rubygems.rb @@ -140,7 +140,8 @@ module Bundler :bin_dir => bin_path.to_s, :ignore_dependencies => true, :wrappers => true, - :env_shebang => true + :env_shebang => true, + :bundler_expected_checksum => spec.respond_to?(:checksum) && spec.checksum ).install end spec.full_gem_path = installed_spec.full_gem_path diff --git a/lib/bundler/spec_set.rb b/lib/bundler/spec_set.rb index f2ccac47..fe31b17f 100644 --- a/lib/bundler/spec_set.rb +++ b/lib/bundler/spec_set.rb @@ -24,17 +24,9 @@ module Bundler dep = deps.shift next if handled[dep] || skip.include?(dep.name) - spec = lookup[dep.name].find do |s| - if match_current_platform - Gem::Platform.match(s.platform) - else - s.match_platform(dep.__platform) - end - end - handled[dep] = true - if spec + if spec = spec_for_dependency(dep, match_current_platform) specs << spec spec.dependencies.each do |d| @@ -99,6 +91,20 @@ module Bundler SpecSet.new(materialized.compact) end + # Materialize for all the specs in the spec set, regardless of what platform they're for + # This is in contrast to how for does platform filtering (and specifically different from how `materialize` calls `for` only for the current platform) + # @return [Array<Gem::Specification>] + def materialized_for_all_platforms + names = @specs.map(&:name).uniq + @specs.map do |s| + next s unless s.is_a?(LazySpecification) + s.source.dependency_names = names if s.source.respond_to?(:dependency_names=) + spec = s.__materialize__ + raise GemNotFound, "Could not find #{s.full_name} in any of the sources" unless spec + spec + end + end + def merge(set) arr = sorted.dup set.each do |s| @@ -133,10 +139,7 @@ module Bundler def lookup @lookup ||= begin lookup = Hash.new {|h, k| h[k] = [] } - specs = @specs.sort_by do |s| - s.platform.to_s == "ruby" ? "\0" : s.platform.to_s - end - specs.reverse_each do |s| + Index.sort_specs(@specs).reverse_each do |s| lookup[s.name] << s end lookup @@ -147,6 +150,18 @@ module Bundler @specs.each {|s| yield s } end + def spec_for_dependency(dep, match_current_platform) + if match_current_platform + Bundler.rubygems.platforms.reverse_each do |pl| + match = GemHelpers.select_best_platform_match(lookup[dep.name], pl) + return match if match + end + nil + else + GemHelpers.select_best_platform_match(lookup[dep.name], dep.__platform) + end + end + def tsort_each_child(s) s.dependencies.sort_by(&:name).each do |d| next if d.type == :development diff --git a/lib/bundler/templates/newgem/README.md.tt b/lib/bundler/templates/newgem/README.md.tt index 30c7b936..ad8d88b6 100644 --- a/lib/bundler/templates/newgem/README.md.tt +++ b/lib/bundler/templates/newgem/README.md.tt @@ -32,7 +32,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To ## Contributing -Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/<%= config[:name] %>.<% if config[:coc] %> This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.<% end %> +Bug reports and pull requests are welcome on GitHub at https://github.com/<%= config[:git_user_name] %>/<%= config[:name] %>.<% if config[:coc] %> This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.<% end %> <% if config[:mit] %> ## License diff --git a/lib/bundler/vendor/compact_index_client/lib/compact_index_client.rb b/lib/bundler/vendor/compact_index_client/lib/compact_index_client.rb index 9ab2722f..c063c6b4 100644 --- a/lib/bundler/vendor/compact_index_client/lib/compact_index_client.rb +++ b/lib/bundler/vendor/compact_index_client/lib/compact_index_client.rb @@ -3,6 +3,12 @@ require "pathname" require "set" class Bundler::CompactIndexClient + DEBUG_MUTEX = Mutex.new + def self.debug + return unless ENV["DEBUG_COMPACT_INDEX"] + DEBUG_MUTEX.synchronize { warn("[#{self}] #{yield}") } + end + class Error < StandardError; end require "bundler/vendor/compact_index_client/lib/compact_index_client/cache" @@ -28,17 +34,20 @@ class Bundler::CompactIndexClient end def names + Bundler::CompactIndexClient.debug { "/names" } update(@cache.names_path, "names") @cache.names end def versions + Bundler::CompactIndexClient.debug { "/versions" } update(@cache.versions_path, "versions") versions, @info_checksums_by_name = @cache.versions versions end def dependencies(names) + Bundler::CompactIndexClient.debug { "dependencies(#{names})" } in_parallel.call(names) do |name| update_info(name) @cache.dependencies(name).map {|d| d.unshift(name) } @@ -46,11 +55,13 @@ class Bundler::CompactIndexClient end def spec(name, version, platform = nil) + Bundler::CompactIndexClient.debug { "spec(name = #{name}, version = #{version}, platform = #{platform})" } update_info(name) @cache.specific_dependency(name, version, platform) end def update_and_parse_checksums! + Bundler::CompactIndexClient.debug { "update_and_parse_checksums!" } return @info_checksums_by_name if @parsed_checksums update(@cache.versions_path, "versions") @info_checksums_by_name = @cache.checksums @@ -60,15 +71,27 @@ class Bundler::CompactIndexClient private def update(local_path, remote_path) - return unless @endpoints.add?(remote_path) + Bundler::CompactIndexClient.debug { "update(#{local_path}, #{remote_path})" } + unless @endpoints.add?(remote_path) + Bundler::CompactIndexClient.debug { "already fetched #{remote_path}" } + return + end @updater.update(local_path, url(remote_path)) end def update_info(name) + Bundler::CompactIndexClient.debug { "update_info(#{name})" } path = @cache.info_path(name) checksum = @updater.checksum_for_file(path) - return unless existing = @info_checksums_by_name[name] - return if checksum == existing + unless existing = @info_checksums_by_name[name] + Bundler::CompactIndexClient.debug { "skipping updating info for #{name} since it is missing from versions" } + return + end + if checksum == existing + Bundler::CompactIndexClient.debug { "skipping updating info for #{name} since the versions checksum matches the local checksum" } + return + end + Bundler::CompactIndexClient.debug { "updating info for #{name} since the versions checksum #{existing} != the local checksum #{checksum}" } update(path, "info/#{name}") end diff --git a/lib/bundler/vendor/compact_index_client/lib/compact_index_client/updater.rb b/lib/bundler/vendor/compact_index_client/lib/compact_index_client/updater.rb index 5c5ba414..a410dd42 100644 --- a/lib/bundler/vendor/compact_index_client/lib/compact_index_client/updater.rb +++ b/lib/bundler/vendor/compact_index_client/lib/compact_index_client/updater.rb @@ -50,7 +50,7 @@ class Bundler::CompactIndexClient mode = response.is_a?(Net::HTTPPartialContent) ? "a" : "w" local_temp_path.open(mode) {|f| f << content } - response_etag = response["ETag"] + response_etag = response["ETag"].gsub(%r{\AW/}, "") if etag_for(local_temp_path) == response_etag FileUtils.mv(local_temp_path, local_path) return diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb b/lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb index c44da1ce..f840e7ea 100644 --- a/lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb +++ b/lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Bundler::Molinillo # The version of Bundler::Molinillo. - VERSION = '0.5.0'.freeze + VERSION = '0.5.1'.freeze end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/resolution.rb b/lib/bundler/vendor/molinillo/lib/molinillo/resolution.rb index 5707e890..1890d95a 100644 --- a/lib/bundler/vendor/molinillo/lib/molinillo/resolution.rb +++ b/lib/bundler/vendor/molinillo/lib/molinillo/resolution.rb @@ -184,6 +184,8 @@ module Bundler::Molinillo raise VersionConflict.new(c) unless state activated.rewind_to(sliced_states.first || :initial_state) if sliced_states state.conflicts = c + index = states.size - 1 + @parent_of.reject! { |_, i| i >= index } end end @@ -209,7 +211,10 @@ module Bundler::Molinillo # @return [Object] the requirement that led to `requirement` being added # to the list of requirements. def parent_of(requirement) - @parent_of[requirement] + return unless requirement + return unless index = @parent_of[requirement] + return unless parent_state = @states[index] + parent_state.requirement end # @return [Object] the requirement that led to a version of a possibility @@ -418,7 +423,8 @@ module Bundler::Molinillo 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) - @parent_of[d] = requirement + parent_index = states.size - 1 + @parent_of[d] ||= parent_index end push_state_for_requirements(requirements + nested_dependencies, !nested_dependencies.empty?) diff --git a/lib/bundler/version.rb b/lib/bundler/version.rb index bcee8f72..2fcacc0f 100644 --- a/lib/bundler/version.rb +++ b/lib/bundler/version.rb @@ -7,5 +7,5 @@ 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.13.0.rc.1" unless defined?(::Bundler::VERSION) + VERSION = "1.13.1" unless defined?(::Bundler::VERSION) end diff --git a/lib/bundler/yaml_serializer.rb b/lib/bundler/yaml_serializer.rb index dede8fd5..3c9eccaf 100644 --- a/lib/bundler/yaml_serializer.rb +++ b/lib/bundler/yaml_serializer.rb @@ -52,7 +52,7 @@ module Bundler stack = [res] last_hash = nil last_empty_key = nil - str.split("\n").each do |line| + str.split(/\r?\n/).each do |line| if match = HASH_REGEX.match(line) indent, key, _, val = match.captures key = convert_to_backward_compatible_key(key) diff --git a/man/bundle-config.ronn b/man/bundle-config.ronn index 3ea6d109..dc701c58 100644 --- a/man/bundle-config.ronn +++ b/man/bundle-config.ronn @@ -142,7 +142,7 @@ learn more about their operation in [bundle install(1)][bundle-install]. and key in PEM format. * `cache_path` (`BUNDLE_CACHE_PATH`): The directory that bundler will place cached gems in when running <code>bundle package</code>, and that bundler - will look in when installing gems. + will look in when installing gems. Defaults to `vendor/bundle`. * `disable_multisource` (`BUNDLE_DISABLE_MULTISOURCE`): When set, Gemfiles containing multiple sources will produce errors instead of warnings. Use `bundle config --delete disable_multisource` to unset. diff --git a/man/bundle-exec.ronn b/man/bundle-exec.ronn index ba6844c5..c9ab2309 100644 --- a/man/bundle-exec.ronn +++ b/man/bundle-exec.ronn @@ -63,6 +63,15 @@ It also modifies Rubygems: making system executables work * Add all gems in the bundle into Gem.loaded_specs +### Loading + +By default, when attempting to `bundle exec` to a file with a ruby shebang, +Bundler will `Kernel.load` that file instead of using `Kernel.exec`. For the +vast majority of cases, this is a performance improvement. In a rare few cases, +this could cause some subtle side-effects (such as dependence on the exact +contents of `$0` or `__FILE__`) and the optimization can be disabled by enabling +the `disable_exec_load` setting. + ### Shelling out Any Ruby code that opens a subshell (like `system`, backticks, or `%x{}`) will diff --git a/spec/bundler/definition_spec.rb b/spec/bundler/definition_spec.rb index c72f50f0..71e5bb00 100644 --- a/spec/bundler/definition_spec.rb +++ b/spec/bundler/definition_spec.rb @@ -137,7 +137,7 @@ describe Bundler::Definition do describe "initialize" do context "gem version promoter" do context "with lockfile" do - before :each do + before do install_gemfile <<-G source "file://#{gem_repo1}" gem "foo" @@ -159,6 +159,73 @@ describe Bundler::Definition do end end + context "shared dependent gems" do + before do + build_repo4 do + build_gem "isolated_owner", %w(1.0.1 1.0.2) do |s| + s.add_dependency "isolated_dep", "~> 2.0" + end + build_gem "isolated_dep", %w(2.0.1 2.0.2) + + build_gem "shared_owner_a", %w(3.0.1 3.0.2) do |s| + s.add_dependency "shared_dep", "~> 5.0" + end + build_gem "shared_owner_b", %w(4.0.1 4.0.2) do |s| + s.add_dependency "shared_dep", "~> 5.0" + end + build_gem "shared_dep", %w(5.0.1 5.0.2) + end + + gemfile <<-G + source "file://#{gem_repo4}" + gem 'isolated_owner' + + gem 'shared_owner_a' + gem 'shared_owner_b' + G + + lockfile <<-L + GEM + remote: file://#{gem_repo4} + specs: + isolated_dep (2.0.1) + isolated_owner (1.0.1) + isolated_dep (~> 2.0) + shared_dep (5.0.1) + shared_owner_a (3.0.1) + shared_dep (~> 5.0) + shared_owner_b (4.0.1) + shared_dep (~> 5.0) + + PLATFORMS + ruby + + DEPENDENCIES + shared_owner_a + shared_owner_b + isolated_owner + + BUNDLED WITH + 1.13.0 + L + end + + it "should unlock isolated and shared dependencies equally" do + # setup for these test costs about 3/4 of a second, much faster to just jam them all in here. + # the global before :each defeats any ability to have re-usable setup for many examples in a + # single context by wiping out the tmp dir and contents. + + unlock_deps_test(%w(isolated_owner), %w(isolated_dep isolated_owner)) + unlock_deps_test(%w(isolated_owner shared_owner_a), %w(isolated_dep isolated_owner shared_dep shared_owner_a)) + end + + def unlock_deps_test(passed_unlocked, expected_calculated) + definition = Bundler::Definition.new(bundled_app("Gemfile.lock"), [], Bundler::SourceList.new, :gems => passed_unlocked) + unlock_gems = definition.gem_version_promoter.unlock_gems + expect(unlock_gems.sort).to eq expected_calculated + end + end + def mock_source_list Class.new do def all_sources diff --git a/spec/bundler/endpoint_specification_spec.rb b/spec/bundler/endpoint_specification_spec.rb index 6718b249..b1e71df3 100644 --- a/spec/bundler/endpoint_specification_spec.rb +++ b/spec/bundler/endpoint_specification_spec.rb @@ -50,4 +50,17 @@ describe Bundler::EndpointSpecification do end end end + + describe "#parse_metadata" do + context "when the metadata has malformed requirements" do + let(:metadata) { { "rubygems" => ">\n" } } + it "raises a helpful error message" do + expect { subject }.to raise_error( + Bundler::GemspecError, + a_string_including("There was an error parsing the metadata for the gem foo (1.0.0)"). + and(a_string_including('The metadata was {"rubygems"=>">\n"}')) + ) + end + end + end end diff --git a/spec/bundler/env_spec.rb b/spec/bundler/env_spec.rb index 73d1f1d7..245073d9 100644 --- a/spec/bundler/env_spec.rb +++ b/spec/bundler/env_spec.rb @@ -15,6 +15,7 @@ describe Bundler::Env do expect(out).to include(Gem::VERSION) expect(out).to include(env.send(:ruby_version)) expect(out).to include(env.send(:git_version)) + expect(out).to include(OpenSSL::OPENSSL_VERSION) end context "when there is a Gemfile and a lockfile and print_gemfile is true" do @@ -48,6 +49,14 @@ describe Bundler::Env do end end + context "when there no Gemfile and print_gemfile is true" do + let(:output) { env.report(:print_gemfile => true) } + + it "prints the environment" do + expect(output).to start_with("Environment") + end + end + context "when Gemfile contains a gemspec and print_gemspecs is true" do let(:gemspec) do <<-GEMSPEC.gsub(/^\s+/, "") diff --git a/spec/bundler/mirror_spec.rb b/spec/bundler/mirror_spec.rb index eb0ccf0b..6a81ef2a 100644 --- a/spec/bundler/mirror_spec.rb +++ b/spec/bundler/mirror_spec.rb @@ -131,6 +131,16 @@ describe Bundler::Settings::Mirror do end end end + + describe "#==" do + it "returns true if uri and fallback timeout are the same" do + uri = "https://ruby.taobao.org" + mirror = Bundler::Settings::Mirror.new(uri, 1) + another_mirror = Bundler::Settings::Mirror.new(uri, 1) + + expect(mirror == another_mirror).to be true + end + end end end diff --git a/spec/bundler/remote_specification_spec.rb b/spec/bundler/remote_specification_spec.rb index 6a8e9a64..d958ca85 100644 --- a/spec/bundler/remote_specification_spec.rb +++ b/spec/bundler/remote_specification_spec.rb @@ -158,16 +158,30 @@ describe Bundler::RemoteSpecification do describe "method missing" do context "and is present in Gem::Specification" do - let(:remote_spec) { double(:remote_spec) } + let(:remote_spec) { double(:remote_spec, :authors => "abcd") } before do - allow_any_instance_of(Gem::Specification).to receive(:respond_to?).and_return(true) allow(subject).to receive(:_remote_specification).and_return(remote_spec) + expect(subject.methods.map(&:to_sym)).not_to include(:authors) end it "should send through to Gem::Specification" do - expect(remote_spec).to receive(:send).with(:missing_method_call).once - subject.missing_method_call + expect(subject.authors).to eq("abcd") + end + end + end + + describe "respond to missing?" do + context "and is present in Gem::Specification" do + let(:remote_spec) { double(:remote_spec, :authors => "abcd") } + + before do + allow(subject).to receive(:_remote_specification).and_return(remote_spec) + expect(subject.methods.map(&:to_sym)).not_to include(:authors) + end + + it "should send through to Gem::Specification" do + expect(subject.respond_to?(:authors)).to be_truthy end end end diff --git a/spec/bundler/ruby_version_spec.rb b/spec/bundler/ruby_version_spec.rb index abcd0303..e983c184 100644 --- a/spec/bundler/ruby_version_spec.rb +++ b/spec/bundler/ruby_version_spec.rb @@ -35,6 +35,14 @@ describe "Bundler::RubyVersion and its subclasses" do end end + context "with engine in symbol" do + let(:engine) { :jruby } + + it "should coerce engine to string" do + expect(subject.engine).to eq("jruby") + end + end + context "is called with multiple requirements" do let(:version) { ["<= 2.0.0", "> 1.9.3"] } let(:engine_version) { nil } diff --git a/spec/bundler/settings_spec.rb b/spec/bundler/settings_spec.rb index 0f7d2a01..5a9d0cb0 100644 --- a/spec/bundler/settings_spec.rb +++ b/spec/bundler/settings_spec.rb @@ -54,6 +54,16 @@ that would suck --ehhh=oh geez it looks like i might have broken bundler somehow end describe "#[]" do + context "when the local config file is not found" do + subject(:settings) { described_class.new } + + it "does not raise" do + expect do + subject["foo"] + end.not_to raise_error + end + end + context "when not set" do context "when default value present" do it "retrieves value" do @@ -96,6 +106,18 @@ that would suck --ehhh=oh geez it looks like i might have broken bundler somehow end end + describe "#temporary" do + it "reset after used" do + Bundler.settings[:no_install] = true + + Bundler.settings.temporary(:no_install => false) do + expect(Bundler.settings[:no_install]).to eq false + end + + expect(Bundler.settings[:no_install]).to eq true + end + end + describe "#set_global" do context "when it's not possible to write to the file" do it "raises an PermissionError with explanation" do diff --git a/spec/bundler/source_spec.rb b/spec/bundler/source_spec.rb index 25abd90c..ea171d38 100644 --- a/spec/bundler/source_spec.rb +++ b/spec/bundler/source_spec.rb @@ -22,7 +22,7 @@ describe Bundler::Source do end describe "#version_message" do - let(:spec) { double(:spec, :name => "nokogiri", :version => ">= 1.6") } + let(:spec) { double(:spec, :name => "nokogiri", :version => ">= 1.6", :platform => rb) } shared_examples_for "the lockfile specs are not relevant" do it "should return a string with the spec name and version" do @@ -61,7 +61,7 @@ describe Bundler::Source do before { Bundler.ui = Bundler::UI::Shell.new } it "should return a string with the spec name and version and locked spec version" do - expect(subject.version_message(spec)).to eq("nokogiri >= 1.6 (\e[32mwas < 1.5\e[0m)") + expect(subject.version_message(spec)).to eq("nokogiri >= 1.6\e[32m (was < 1.5)\e[0m") end end diff --git a/spec/bundler/yaml_serializer_spec.rb b/spec/bundler/yaml_serializer_spec.rb index 0b326133..bf86d2a0 100644 --- a/spec/bundler/yaml_serializer_spec.rb +++ b/spec/bundler/yaml_serializer_spec.rb @@ -125,6 +125,29 @@ describe Bundler::YAMLSerializer do expect(serializer.load(yaml)).to eq(hash) end + + it "handles windows-style CRLF line endings" do + yaml = strip_whitespace(<<-YAML).gsub("\n", "\r\n") + --- + nested_hash: + contains_array: + - "Why shouldn't you write with a broken pencil?" + - "Because it's pointless!" + - oh so silly + YAML + + hash = { + "nested_hash" => { + "contains_array" => [ + "Why shouldn't you write with a broken pencil?", + "Because it's pointless!", + "oh so silly", + ], + }, + } + + expect(serializer.load(yaml)).to eq(hash) + end end describe "against yaml lib" do diff --git a/spec/commands/add_spec.rb b/spec/commands/add_spec.rb deleted file mode 100644 index d6b9b46f..00000000 --- a/spec/commands/add_spec.rb +++ /dev/null @@ -1,86 +0,0 @@ -# frozen_string_literal: true -require "spec_helper" - -describe "bundle add" do - before :each do - build_repo2 - - gemfile <<-G - source "file://#{gem_repo2}" - G - end - - context "when version number is set" do - it "adds gem with provided version" do - bundle "add activesupport 2.3.5" - expect(bundled_app("Gemfile").read).to include("gem 'activesupport', '~> 2.3.5'") - end - - it "adds gem with provided version and version operator" do - update_repo2 do - build_gem "activesupport", "3.0.0" - end - - bundle "add activesupport '> 2.3.5'" - expect(bundled_app("Gemfile").read).to include("gem 'activesupport', '> 2.3.5'") - end - end - - context "when version number is not set" do - it "adds gem with last stable version" do - bundle "add activesupport" - expect(bundled_app("Gemfile").read).to include("gem 'activesupport', '~> 2.3.5'") - end - - it "`--pre` flag adds the gem with the latest prerelease version" do - update_repo2 do - build_gem "activesupport", "3.0.0.beta" - end - - bundle "add activesupport --pre" - expect(bundled_app("Gemfile").read).to include("gem 'activesupport', '~> 3.0.0.beta'") - end - - it "`--pre` flag adds the gem with the latest non-prerelease version if it is available" do - update_repo2 do - build_gem "activesupport", "3.0.0.beta" - build_gem "activesupport", "3.0.0" - end - - bundle "add activesupport --pre" - expect(bundled_app("Gemfile").read).to include("gem 'activesupport', '~> 3.0.0'") - end - end - - context "when group is set" do - it "adds the gem with the specified groups" do - bundle "add activesupport --group development test" - expect(bundled_app("Gemfile").read).to include("gem 'activesupport', '~> 2.3.5', :group => [:development, :test]") - end - end - - context "when source is set" do - it "adds the gem with a specified source" do - bundle "add activesupport --source file://#{gem_repo2}" - expect(bundled_app("Gemfile").read).to include("gem 'activesupport', '~> 2.3.5', :source => 'file:\/\/#{gem_repo2}'") - end - end - - context "when multiple options are set" do - before :each do - update_repo2 do - build_gem "activesupport", "3.0.0" - end - end - - it "adds the gem with a specified group and source" do - bundle "add activesupport --group test --source file://#{gem_repo2}" - expect(bundled_app("Gemfile").read).to include("gem 'activesupport', '~> 3.0.0', :group => [:test], :source => 'file:\/\/#{gem_repo2}'") - end - - it "adds the gem with a specified version, group, and source" do - bundle "add activesupport 2.3.5 --group development --source file://#{gem_repo2}" - expect(bundled_app("Gemfile").read).to include("gem 'activesupport', '~> 2.3.5', :group => [:development], :source => 'file:\/\/#{gem_repo2}'") - end - end -end diff --git a/spec/commands/doctor_spec.rb b/spec/commands/doctor_spec.rb index 236138a6..8debeb55 100644 --- a/spec/commands/doctor_spec.rb +++ b/spec/commands/doctor_spec.rb @@ -55,7 +55,10 @@ describe "bundle doctor" do expect(doctor).to receive(:dylibs).exactly(2).times.and_return ["/usr/local/opt/icu4c/lib/libicui18n.57.1.dylib"] allow(File).to receive(:exist?).and_call_original allow(File).to receive(:exist?).with("/usr/local/opt/icu4c/lib/libicui18n.57.1.dylib").and_return(false) - expect { doctor.run }.to raise_error SystemExit - expect(@stdout.string).to include("libicui18n.57.1.dylib") + expect { doctor.run }.to raise_error Bundler::ProductionError, strip_whitespace(<<-E).strip + The following gems are missing OS dependencies: + * bundler: /usr/local/opt/icu4c/lib/libicui18n.57.1.dylib + * rack: /usr/local/opt/icu4c/lib/libicui18n.57.1.dylib + E end end diff --git a/spec/commands/exec_spec.rb b/spec/commands/exec_spec.rb index 3d861c07..4d374ceb 100644 --- a/spec/commands/exec_spec.rb +++ b/spec/commands/exec_spec.rb @@ -564,6 +564,42 @@ describe "bundle exec" do it_behaves_like "it runs" end + context "regarding $0 and __FILE__" do + let(:executable) { super() + <<-'RUBY' } + + puts "$0: #{$0.inspect}" + puts "__FILE__: #{__FILE__.inspect}" + RUBY + + let(:expected) { super() + <<-EOS.chomp } + +$0: #{path.to_s.inspect} +__FILE__: #{path.to_s.inspect} + EOS + + it_behaves_like "it runs" + + context "when the path is relative" do + let(:path) { super().relative_path_from(bundled_app) } + + if LessThanProc.with(RUBY_VERSION).call("1.9") + pending "relative paths have ./ __FILE__" + else + it_behaves_like "it runs" + end + end + + context "when the path is relative with a leading ./" do + let(:path) { Pathname.new("./#{super().relative_path_from(Pathname.pwd)}") } + + if LessThanProc.with(RUBY_VERSION).call("< 1.9") + pending "relative paths with ./ have absolute __FILE__" + else + it_behaves_like "it runs" + end + end + end + context "signals being trapped by bundler" do let(:executable) { strip_whitespace <<-RUBY } #{shebang} diff --git a/spec/commands/lock_spec.rb b/spec/commands/lock_spec.rb index 693c1a6f..56004346 100644 --- a/spec/commands/lock_spec.rb +++ b/spec/commands/lock_spec.rb @@ -10,9 +10,11 @@ describe "bundle lock" do strip_lockfile bundled_app(file).read end + let(:repo) { gem_repo1 } + before :each do gemfile <<-G - source "file://#{gem_repo1}" + source "file://#{repo}" gem "rails" gem "with_license" gem "foo" @@ -20,7 +22,7 @@ describe "bundle lock" do @lockfile = strip_lockfile <<-L GEM - remote: file:#{gem_repo1}/ + remote: file:#{repo}/ specs: actionmailer (2.3.2) activesupport (= 2.3.2) @@ -77,7 +79,7 @@ describe "bundle lock" do it "writes a lockfile when there is an outdated lockfile using --update" do lockfile @lockfile.gsub("2.3.2", "2.3.1") - bundle "lock --update" + bundle! "lock --update" expect(read_lockfile).to eq(@lockfile) end @@ -132,4 +134,119 @@ describe "bundle lock" do bundle "lock --remove-platform #{local}" expect(out).to include("Removing all platforms from the bundle is not allowed") end + + # from https://github.com/bundler/bundler/issues/4896 + it "properly adds platforms when platform requirements come from different dependencies" do + build_repo4 do + build_gem "ffi", "1.9.14" + build_gem "ffi", "1.9.14" do |s| + s.platform = mingw + end + + build_gem "gssapi", "0.1" + build_gem "gssapi", "0.2" + build_gem "gssapi", "0.3" + build_gem "gssapi", "1.2.0" do |s| + s.add_dependency "ffi", ">= 1.0.1" + end + + build_gem "mixlib-shellout", "2.2.6" + build_gem "mixlib-shellout", "2.2.6" do |s| + s.platform = "universal-mingw32" + s.add_dependency "win32-process", "~> 0.8.2" + end + + # we need all these versions to get the sorting the same as it would be + # pulling from rubygems.org + %w(0.8.3 0.8.2 0.8.1 0.8.0).each do |v| + build_gem "win32-process", v do |s| + s.add_dependency "ffi", ">= 1.0.0" + end + end + end + + gemfile <<-G + source "file:#{gem_repo4}" + + gem "mixlib-shellout" + gem "gssapi" + G + + simulate_platform(mingw) { bundle! :lock } + + expect(the_bundle.lockfile).to read_as(strip_whitespace(<<-G)) + GEM + remote: file:#{gem_repo4}/ + specs: + ffi (1.9.14-x86-mingw32) + gssapi (1.2.0) + ffi (>= 1.0.1) + mixlib-shellout (2.2.6-universal-mingw32) + win32-process (~> 0.8.2) + win32-process (0.8.3) + ffi (>= 1.0.0) + + PLATFORMS + x86-mingw32 + + DEPENDENCIES + gssapi + mixlib-shellout + + BUNDLED WITH + #{Bundler::VERSION} + G + + simulate_platform(rb) { bundle! :lock } + + expect(the_bundle.lockfile).to read_as(strip_whitespace(<<-G)) + GEM + remote: file:#{gem_repo4}/ + specs: + ffi (1.9.14) + ffi (1.9.14-x86-mingw32) + gssapi (1.2.0) + ffi (>= 1.0.1) + mixlib-shellout (2.2.6) + mixlib-shellout (2.2.6-universal-mingw32) + win32-process (~> 0.8.2) + win32-process (0.8.3) + ffi (>= 1.0.0) + + PLATFORMS + ruby + x86-mingw32 + + DEPENDENCIES + gssapi + mixlib-shellout + + BUNDLED WITH + #{Bundler::VERSION} + G + end + + context "when an update is available" do + let(:repo) { gem_repo2 } + + before do + lockfile(@lockfile) + build_repo2 do + build_gem "foo", "2.0" + end + end + + it "does not implicitly update" do + bundle! "lock" + + expect(read_lockfile).to eq(@lockfile) + end + + it "accounts for changes in the gemfile" do + gemfile gemfile.gsub('"foo"', '"foo", "2.0"') + bundle! "lock" + + expect(read_lockfile).to eq(@lockfile.sub("foo (1.0)", "foo (2.0)").sub(/foo$/, "foo (= 2.0)")) + end + end end diff --git a/spec/commands/newgem_spec.rb b/spec/commands/newgem_spec.rb index cb3c4813..6e80aa7a 100644 --- a/spec/commands/newgem_spec.rb +++ b/spec/commands/newgem_spec.rb @@ -112,6 +112,35 @@ describe "bundle gem" do end end + context "README.md" do + let(:gem_name) { "test_gem" } + let(:generated_gem) { Bundler::GemHelper.new(bundled_app(gem_name).to_s) } + + context "git config user.name present" do + before do + execute_bundle_gem(gem_name) + end + + it "contribute URL set to git username" do + expect(bundled_app("test_gem/README.md").read).not_to include("[USERNAME]") + end + end + + context "git config user.name is absent" do + before do + `git config --unset user.name` + reset! + in_app_root + bundle "gem #{gem_name}" + remove_push_guard(gem_name) + end + + it "contribute URL set to [USERNAME]" do + expect(bundled_app("test_gem/README.md").read).to include("[USERNAME]") + end + end + end + it "generates a valid gemspec" do system_gems ["rake-10.0.2"] diff --git a/spec/commands/update_spec.rb b/spec/commands/update_spec.rb index 19fbb838..8a9867d1 100644 --- a/spec/commands/update_spec.rb +++ b/spec/commands/update_spec.rb @@ -72,6 +72,42 @@ describe "bundle update" do end end + describe "when a possible resolve requires an older version of a locked gem" do + context "and only_update_to_newer_versions is set" do + before do + bundle! "config only_update_to_newer_versions true" + end + it "does not go to an older version" do + build_repo4 do + build_gem "a" do |s| + s.add_dependency "b" + s.add_dependency "c" + end + build_gem "b" + build_gem "c" + build_gem "c", "2.0" + end + + install_gemfile! <<-G + source "file:#{gem_repo4}" + gem "a" + G + + expect(the_bundle).to include_gems("a 1.0", "b 1.0", "c 2.0") + + update_repo4 do + build_gem "b", "2.0" do |s| + s.add_dependency "c", "< 2" + end + end + + bundle! "update" + + expect(the_bundle).to include_gems("a 1.0", "b 1.0", "c 2.0") + end + end + end + describe "with --local option" do it "doesn't hit repo2" do FileUtils.rm_rf(gem_repo2) diff --git a/spec/install/gemfile/eval_gemfile_spec.rb b/spec/install/gemfile/eval_gemfile_spec.rb index 2660ac98..29f27550 100644 --- a/spec/install/gemfile/eval_gemfile_spec.rb +++ b/spec/install/gemfile/eval_gemfile_spec.rb @@ -26,6 +26,24 @@ describe "bundle install with gemfile that uses eval_gemfile" do end end + context "eval-ed Gemfile has relative-path gems" do + before do + build_lib("a", :path => "gems/a") + create_file "nested/Gemfile-nested", <<-G + gem "a", :path => "../gems/a" + G + + gemfile <<-G + eval_gemfile "nested/Gemfile-nested" + G + end + + it "installs the path gem" do + bundle! :install + expect(the_bundle).to include_gem("a 1.0") + end + end + context "Gemfile uses gemspec paths after eval-ing a Gemfile" do before { create_file "other/Gemfile-other" } diff --git a/spec/install/gemfile/specific_platform_spec.rb b/spec/install/gemfile/specific_platform_spec.rb new file mode 100644 index 00000000..3e12f94c --- /dev/null +++ b/spec/install/gemfile/specific_platform_spec.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true +require "spec_helper" + +describe "bundle install with specific_platform enabled" do + before do + bundle "config specific_platform true" + + build_repo2 do + build_gem("google-protobuf", "3.0.0.alpha.5.0.5.1") + build_gem("google-protobuf", "3.0.0.alpha.5.0.5.1") {|s| s.platform = "x86_64-linux" } + build_gem("google-protobuf", "3.0.0.alpha.5.0.5.1") {|s| s.platform = "x86-mingw32" } + build_gem("google-protobuf", "3.0.0.alpha.5.0.5.1") {|s| s.platform = "x86-linux" } + build_gem("google-protobuf", "3.0.0.alpha.5.0.5.1") {|s| s.platform = "x64-mingw32" } + build_gem("google-protobuf", "3.0.0.alpha.5.0.5.1") {|s| s.platform = "universal-darwin" } + + build_gem("google-protobuf", "3.0.0.alpha.5.0.5") {|s| s.platform = "x86_64-linux" } + build_gem("google-protobuf", "3.0.0.alpha.5.0.5") {|s| s.platform = "x86-linux" } + build_gem("google-protobuf", "3.0.0.alpha.5.0.5") {|s| s.platform = "x64-mingw32" } + build_gem("google-protobuf", "3.0.0.alpha.5.0.5") {|s| s.platform = "x86-mingw32" } + build_gem("google-protobuf", "3.0.0.alpha.5.0.5") + + build_gem("google-protobuf", "3.0.0.alpha.5.0.4") {|s| s.platform = "universal-darwin" } + build_gem("google-protobuf", "3.0.0.alpha.5.0.4") {|s| s.platform = "x86_64-linux" } + build_gem("google-protobuf", "3.0.0.alpha.5.0.4") {|s| s.platform = "x86-mingw32" } + build_gem("google-protobuf", "3.0.0.alpha.5.0.4") {|s| s.platform = "x86-linux" } + build_gem("google-protobuf", "3.0.0.alpha.5.0.4") {|s| s.platform = "x64-mingw32" } + build_gem("google-protobuf", "3.0.0.alpha.5.0.4") + + build_gem("google-protobuf", "3.0.0.alpha.5.0.3") + build_gem("google-protobuf", "3.0.0.alpha.5.0.3") {|s| s.platform = "x86_64-linux" } + build_gem("google-protobuf", "3.0.0.alpha.5.0.3") {|s| s.platform = "x86-mingw32" } + build_gem("google-protobuf", "3.0.0.alpha.5.0.3") {|s| s.platform = "x86-linux" } + build_gem("google-protobuf", "3.0.0.alpha.5.0.3") {|s| s.platform = "x64-mingw32" } + build_gem("google-protobuf", "3.0.0.alpha.5.0.3") {|s| s.platform = "universal-darwin" } + + build_gem("google-protobuf", "3.0.0.alpha.4.0") + build_gem("google-protobuf", "3.0.0.alpha.3.1.pre") + build_gem("google-protobuf", "3.0.0.alpha.3") + build_gem("google-protobuf", "3.0.0.alpha.2.0") + build_gem("google-protobuf", "3.0.0.alpha.1.1") + build_gem("google-protobuf", "3.0.0.alpha.1.0") + end + end + + let(:google_protobuf) { <<-G } + source "file:#{gem_repo2}" + gem "google-protobuf" + G + + context "when on a darwin machine" do + before { simulate_platform "x86_64-darwin-15" } + + it "locks to both the specific darwin platform and ruby" do + install_gemfile!(google_protobuf) + expect(the_bundle.locked_gems.platforms).to eq([pl("ruby"), pl("x86_64-darwin-15")]) + expect(the_bundle).to include_gem("google-protobuf 3.0.0.alpha.5.0.5.1 universal-darwin") + expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w( + google-protobuf-3.0.0.alpha.5.0.5.1 + google-protobuf-3.0.0.alpha.5.0.5.1-universal-darwin + )) + end + + it "caches both the universal-darwin and ruby gems when --all-platforms is passed" do + gemfile(google_protobuf) + bundle! "package --all-platforms" + expect([cached_gem("google-protobuf-3.0.0.alpha.5.0.5.1"), cached_gem("google-protobuf-3.0.0.alpha.5.0.5.1-universal-darwin")]). + to all(exist) + end + + context "when adding a platform via lock --add_platform" do + it "adds the foreign platform" do + install_gemfile!(google_protobuf) + bundle! "lock --add-platform=#{x64_mingw}" + + expect(the_bundle.locked_gems.platforms).to eq([rb, x64_mingw, pl("x86_64-darwin-15")]) + expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w( + google-protobuf-3.0.0.alpha.5.0.5.1 + google-protobuf-3.0.0.alpha.5.0.5.1-universal-darwin + google-protobuf-3.0.0.alpha.5.0.5.1-x64-mingw32 + )) + end + + it "falls back on plain ruby when that version doesnt have a platform-specific gem" do + install_gemfile!(google_protobuf) + bundle! "lock --add-platform=#{java}" + + expect(the_bundle.locked_gems.platforms).to eq([java, rb, pl("x86_64-darwin-15")]) + expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w( + google-protobuf-3.0.0.alpha.5.0.5.1 + google-protobuf-3.0.0.alpha.5.0.5.1-universal-darwin + )) + end + end + end +end diff --git a/spec/install/gemfile_spec.rb b/spec/install/gemfile_spec.rb index 98abc30c..03ae3449 100644 --- a/spec/install/gemfile_spec.rb +++ b/spec/install/gemfile_spec.rb @@ -66,4 +66,33 @@ describe "bundle install" do expect(out).to match(/You passed :lib as an option for gem 'rack', but it is invalid/) end end + + context "with engine specified in symbol" do + it "does not raise any error parsing Gemfile" do + simulate_ruby_version "2.3.0" do + simulate_ruby_engine "jruby", "9.1.2.0" do + install_gemfile! <<-G + source "https://rubygems.org" + ruby "2.3.0", :engine => :jruby, :engine_version => "9.1.2.0" + G + + expect(out).to match(/Bundle complete!/) + end + end + end + + it "installation succeeds" do + simulate_ruby_version "2.3.0" do + simulate_ruby_engine "jruby", "9.1.2.0" do + install_gemfile! <<-G + source "file://#{gem_repo1}" + ruby "2.3.0", :engine => :jruby, :engine_version => "9.1.2.0" + gem "rack" + G + + expect(the_bundle).to include_gems "rack 1.0.0" + end + end + end + end end diff --git a/spec/install/gems/compact_index_spec.rb b/spec/install/gems/compact_index_spec.rb index 0edd1d20..228d8ddc 100644 --- a/spec/install/gems/compact_index_spec.rb +++ b/spec/install/gems/compact_index_spec.rb @@ -695,4 +695,47 @@ The checksum of /versions does not match the checksum provided by the server! So expect(File.read(versions)).to start_with("created_at") expect(the_bundle).to include_gems "rack 1.0.0" end + + it "fails gracefully when the source URI has an invalid scheme" do + install_gemfile <<-G + source "htps://rubygems.org" + gem "rack" + G + expect(exitstatus).to eq(15) if exitstatus + expect(out).to end_with(<<-E.strip) + The request uri `htps://index.rubygems.org/versions` has an invalid scheme (`htps`). Did you mean `http` or `https`? + E + end + + describe "checksum validation", :rubygems => ">= 2.3.0" do + it "raises when the checksum does not match" do + install_gemfile <<-G, :artifice => "compact_index_wrong_gem_checksum" + source "#{source_uri}" + gem "rack" + G + expect(exitstatus).to eq(19) if exitstatus + expect(out). + to include("The checksum for the downloaded `rack-1.0.0.gem` did not match the checksum given by the API."). + and include("This means that the contents of the gem appear to be different from what was uploaded, and could be an indicator of a security issue."). + and match(/\(The expected SHA256 checksum was "#{"ab" * 22}", but the checksum for the downloaded gem was ".+?"\.\)/). + and include("Bundler cannot continue installing rack (1.0.0).") + end + + it "raises when the checksum is the wrong length" do + install_gemfile <<-G, :artifice => "compact_index_wrong_gem_checksum", :env => { "BUNDLER_SPEC_RACK_CHECKSUM" => "checksum!" } + source "#{source_uri}" + gem "rack" + G + expect(exitstatus).to eq(5) if exitstatus + expect(out).to include("The given checksum for rack-1.0.0 (\"checksum!\") is not a valid SHA256 hexdigest nor base64digest") + end + + it "does not raise when disable_checksum_validation is set" do + bundle! "config disable_checksum_validation true" + install_gemfile! <<-G, :artifice => "compact_index_wrong_gem_checksum" + source "#{source_uri}" + gem "rack" + G + end + end end diff --git a/spec/install/gems/resolving_spec.rb b/spec/install/gems/resolving_spec.rb index 816799c0..0204a222 100644 --- a/spec/install/gems/resolving_spec.rb +++ b/spec/install/gems/resolving_spec.rb @@ -119,20 +119,58 @@ describe "bundle install with install-time dependencies" do end context "allows no gems" do - it "does not try to install those gems" do + before do build_repo2 do build_gem "require_ruby" do |s| s.required_ruby_version = "> 9000" end end + end - install_gemfile <<-G - source "file://#{gem_repo2}" - gem 'require_ruby' - G + let(:ruby_requirement) { %("#{RUBY_VERSION}") } + let(:error_message_requirement) { "~> #{RUBY_VERSION}.0" } + + shared_examples_for "ruby version conflicts" do + it "raises an error during resolution" do + install_gemfile <<-G, :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2 } + source "http://localgemserver.test/" + ruby #{ruby_requirement} + gem 'require_ruby' + G + + expect(out).to_not include("Gem::InstallError: require_ruby requires Ruby version > 9000") + + nice_error = strip_whitespace(<<-E).strip + Fetching gem metadata from http://localgemserver.test/. + Fetching version metadata from http://localgemserver.test/ + Resolving dependencies... + Bundler could not find compatible versions for gem "ruby\0": + In Gemfile: + ruby\0 (#{error_message_requirement}) + + require_ruby was resolved to 1.0, which depends on + ruby\0 (> 9000) + + Could not find gem 'ruby\0 (> 9000)', which is required by gem 'require_ruby', in any of the sources. + E + expect(out).to eq(nice_error) + end + end + + it_behaves_like "ruby version conflicts" + + describe "with a < requirement" do + let(:ruby_requirement) { %("< 5000") } + let(:error_message_requirement) { "< 5000" } + + it_behaves_like "ruby version conflicts" + end + + describe "with a compound requirement" do + let(:ruby_requirement) { %("< 5000", "> 0.1") } + let(:error_message_requirement) { "< 5000, > 0.1" } - expect(out).to_not include("Gem::InstallError: require_ruby requires Ruby version > 9000") - expect(out).to include("require_ruby-1.0 requires ruby version > 9000, which is incompatible with the current version, #{Bundler::RubyVersion.system}") + it_behaves_like "ruby version conflicts" end end end diff --git a/spec/install/gemspecs_spec.rb b/spec/install/gemspecs_spec.rb index 3e6021b7..8f719bf6 100644 --- a/spec/install/gemspecs_spec.rb +++ b/spec/install/gemspecs_spec.rb @@ -50,7 +50,7 @@ describe "bundle install" do context "when ruby version is specified in gemspec and gemfile" do it "installs when patch level is not specified and the version matches" do build_lib("foo", :path => bundled_app) do |s| - s.required_ruby_version = RUBY_VERSION + s.required_ruby_version = "~> #{RUBY_VERSION}.0" end install_gemfile <<-G diff --git a/spec/install/path_spec.rb b/spec/install/path_spec.rb index 3d84fffd..03c42f00 100644 --- a/spec/install/path_spec.rb +++ b/spec/install/path_spec.rb @@ -130,21 +130,21 @@ describe "bundle install" do end end - describe "to a dead symlink" do + describe "to a file" do before do in_app_root do - `ln -s /tmp/idontexist bundle` + `touch /tmp/idontexist bundle` end end - it "reports the symlink is dead" do + it "reports the file exists" do gemfile <<-G source "file://#{gem_repo1}" gem "rack" G bundle "install --path bundle" - expect(out).to match(/invalid symlink/) + expect(out).to match(/file already exists/) end end end diff --git a/spec/other/major_deprecation_spec.rb b/spec/other/major_deprecation_spec.rb index c8a26332..6505023d 100644 --- a/spec/other/major_deprecation_spec.rb +++ b/spec/other/major_deprecation_spec.rb @@ -4,6 +4,22 @@ require "spec_helper" describe "major deprecations" do let(:warnings) { out } # change to err in 2.0 + context "in a .99 version" do + before do + simulate_bundler_version "1.99.1" + bundle "config --delete major_deprecations" + end + + it "prints major deprecations without being configured" do + ruby <<-R + require "bundler" + Bundler::SharedHelpers.major_deprecation(Bundler::VERSION) + R + + expect(warnings).to have_major_deprecation("1.99.1") + end + end + before do bundle "config major_deprecations true" diff --git a/spec/other/trampoline_spec.rb b/spec/other/trampoline_spec.rb index 2aac0a2c..9a8e0a4a 100644 --- a/spec/other/trampoline_spec.rb +++ b/spec/other/trampoline_spec.rb @@ -59,6 +59,24 @@ describe "bundler version trampolining" do end end + context "without BUNDLE_ENABLE_TRAMPOLINE" do + before { ENV["BUNDLE_ENABLE_TRAMPOLINE"] = nil } + + context "when the version is >= 2" do + let(:version) { "2.7182818285" } + before do + simulate_bundler_version version do + install_gemfile! "" + end + end + + it "trampolines automatically", :realworld => true do + bundle "--version" + expect(err).to include("Installing locked Bundler version #{version}...") + end + end + end + context "installing missing bundler versions", :realworld => true do before do ENV["BUNDLER_VERSION"] = "1.12.3" diff --git a/spec/realworld/edgecases_spec.rb b/spec/realworld/edgecases_spec.rb index 7a78a114..06e58804 100644 --- a/spec/realworld/edgecases_spec.rb +++ b/spec/realworld/edgecases_spec.rb @@ -48,8 +48,16 @@ describe "real world edgecases", :realworld => true, :sometimes => true do gem 'capybara', '~> 2.2.0' gem 'rack-cache', '1.2.0' # last version that works on Ruby 1.9 G - bundle :lock - expect(lockfile).to include("rails (3.2.22.4)") + bundle! :lock + rails_version = ruby(<<-R) + require 'rubygems' + require 'bundler' + fetcher = Bundler::Fetcher.new(Bundler::Source::Rubygems::Remote.new(URI('https://rubygems.org'))) + index = fetcher.specs(%w(rails), nil) + rails = index.search(Gem::Dependency.new("rails", "~> 3.0")).last + puts rails.version + R + expect(lockfile).to include("rails (#{rails_version})") expect(lockfile).to include("capybara (2.2.1)") end diff --git a/spec/resolver/basic_spec.rb b/spec/resolver/basic_spec.rb index b7b8b4c3..3e8883d1 100644 --- a/spec/resolver/basic_spec.rb +++ b/spec/resolver/basic_spec.rb @@ -92,15 +92,18 @@ describe "Resolving" do gem "bar", "2.0.0" do |s| s.required_ruby_version = "~> 2.0.0" end + + gem "ruby\0", "1.8.7" end dep "foo" + dep "ruby\0", "1.8.7" deps = [] @deps.each do |d| deps << Bundler::DepProxy.new(d, "ruby") end - should_resolve_and_include %w(foo-1.0.0 bar-1.0.0), [{}, [], Bundler::RubyVersion.new("1.8.7", nil, nil, nil)] + should_resolve_and_include %w(foo-1.0.0 bar-1.0.0), [{}, []] end context "conservative" do diff --git a/spec/resolver/platform_spec.rb b/spec/resolver/platform_spec.rb index d5f21768..fa91eab9 100644 --- a/spec/resolver/platform_spec.rb +++ b/spec/resolver/platform_spec.rb @@ -50,7 +50,7 @@ describe "Resolving platform craziness" do # mingw is _not_ hardcoded to add CPU x86 in rubygems platforms "x86-mingw32" dep "thin" - should_resolve_as %w(thin-1.2.7-x86-mingw32) + should_resolve_as %w(thin-1.2.7-mingw32) end it "finds x64-mingw gems" do diff --git a/spec/support/artifice/compact_index.rb b/spec/support/artifice/compact_index.rb index 233c192a..0afd7fc5 100644 --- a/spec/support/artifice/compact_index.rb +++ b/spec/support/artifice/compact_index.rb @@ -78,7 +78,12 @@ class CompactIndexAPI < Endpoint reqs = d.requirement.requirements.map {|r| r.join(" ") }.join(", ") CompactIndex::Dependency.new(d.name, reqs) end - CompactIndex::GemVersion.new(spec.version.version, spec.platform.to_s, nil, nil, + checksum = begin + Digest::SHA256.file("#{GEM_REPO}/gems/#{spec.original_name}.gem").base64digest + rescue + nil + end + CompactIndex::GemVersion.new(spec.version.version, spec.platform.to_s, checksum, nil, deps, spec.required_ruby_version, spec.required_rubygems_version) end CompactIndex::Gem.new(name, gem_versions) @@ -98,8 +103,8 @@ class CompactIndexAPI < Endpoint file = tmp("versions.list") file.delete if file.file? file = CompactIndex::VersionsFile.new(file.to_s) - file.update_with(gems) - CompactIndex.versions(file, nil, {}) + file.create(gems) + file.contents end end diff --git a/spec/support/artifice/compact_index_concurrent_download.rb b/spec/support/artifice/compact_index_concurrent_download.rb index 30a2171a..b788a852 100644 --- a/spec/support/artifice/compact_index_concurrent_download.rb +++ b/spec/support/artifice/compact_index_concurrent_download.rb @@ -22,8 +22,8 @@ class CompactIndexConcurrentDownload < CompactIndexAPI file = tmp("versions.list") file.delete if file.file? file = CompactIndex::VersionsFile.new(file.to_s) - file.update_with(gems) - CompactIndex.versions(file, nil, {}) + file.create(gems) + file.contents end end end diff --git a/spec/support/artifice/compact_index_extra_api.rb b/spec/support/artifice/compact_index_extra_api.rb index 063e5589..844a9ca9 100644 --- a/spec/support/artifice/compact_index_extra_api.rb +++ b/spec/support/artifice/compact_index_extra_api.rb @@ -15,8 +15,8 @@ class CompactIndexExtraApi < CompactIndexAPI file = tmp("versions.list") file.delete if file.file? file = CompactIndex::VersionsFile.new(file.to_s) - file.update_with(gems(gem_repo4)) - CompactIndex.versions(file, nil, {}) + file.create(gems(gem_repo4)) + file.contents end end diff --git a/spec/support/artifice/compact_index_wrong_gem_checksum.rb b/spec/support/artifice/compact_index_wrong_gem_checksum.rb new file mode 100644 index 00000000..3a12a59a --- /dev/null +++ b/spec/support/artifice/compact_index_wrong_gem_checksum.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true +require File.expand_path("../compact_index", __FILE__) + +Artifice.deactivate + +class CompactIndexWrongGemChecksum < CompactIndexAPI + get "/info/:name" do + etag_response do + name = params[:name] + gem = gems.find {|g| g.name == name } + checksum = ENV.fetch("BUNDLER_SPEC_#{name.upcase}_CHECKSUM") { "ab" * 22 } + versions = gem ? gem.versions : [] + versions.each {|v| v.checksum = checksum } + CompactIndex.info(versions) + end + end +end + +Artifice.activate_with(CompactIndexWrongGemChecksum) diff --git a/spec/support/builders.rb b/spec/support/builders.rb index 337234f1..7436779d 100644 --- a/spec/support/builders.rb +++ b/spec/support/builders.rb @@ -610,7 +610,10 @@ module Spec end def _default_files - @_default_files ||= { "lib/#{name}.rb" => "#{Builders.constantize(name)} = '#{version}'" } + @_default_files ||= begin + platform_string = " #{@spec.platform}" unless @spec.platform == Gem::Platform::RUBY + { "lib/#{name}.rb" => "#{Builders.constantize(name)} = '#{version}#{platform_string}'" } + end end def _default_path diff --git a/spec/support/helpers.rb b/spec/support/helpers.rb index fe79604f..b30c44d1 100644 --- a/spec/support/helpers.rb +++ b/spec/support/helpers.rb @@ -208,7 +208,11 @@ module Spec end def gemfile(*args) - create_file("Gemfile", *args) + if args.empty? + File.open("Gemfile", "r", &:read) + else + create_file("Gemfile", *args) + end end def lockfile(*args) diff --git a/spec/support/indexes.rb b/spec/support/indexes.rb index 9a7879bc..29780014 100644 --- a/spec/support/indexes.rb +++ b/spec/support/indexes.rb @@ -62,7 +62,7 @@ module Spec s.level = opts.first s.strict = opts.include?(:strict) end - should_resolve_and_include specs, [{}, @base, nil, search] + should_resolve_and_include specs, [{}, @base, search] end def an_awesome_index diff --git a/spec/support/matchers.rb b/spec/support/matchers.rb index 9476f189..92483606 100644 --- a/spec/support/matchers.rb +++ b/spec/support/matchers.rb @@ -110,15 +110,9 @@ module Spec define_compound_matcher :read_as, [exist] do |file_contents| diffable - attr_reader :strip_whitespace - - chain :stripping_whitespace do - @strip_whitespace = true - end match do |actual| @actual = Bundler.read_file(actual) - file_contents = strip_whitespace(file_contents) if strip_whitespace values_match?(file_contents, @actual) end end diff --git a/spec/support/platforms.rb b/spec/support/platforms.rb index b1dedb05..a2a3afba 100644 --- a/spec/support/platforms.rb +++ b/spec/support/platforms.rb @@ -11,6 +11,10 @@ module Spec Gem::Platform.new("x86-darwin-10") end + def x64_mac + Gem::Platform.new("x86_64-darwin-15") + end + def java Gem::Platform.new([nil, "java", nil]) end diff --git a/spec/support/rubygems_ext.rb b/spec/support/rubygems_ext.rb index 4ddbc331..e2b8c6a4 100644 --- a/spec/support/rubygems_ext.rb +++ b/spec/support/rubygems_ext.rb @@ -9,7 +9,8 @@ module Spec # rack 2.x requires Ruby version >= 2.2.2. # artifice doesn't support rack 2.x now. "rack" => "< 2", - "fakeweb artifice compact_index" => nil, + "fakeweb artifice" => nil, + "compact_index" => "~> 0.11.0", "sinatra" => "1.2.7", # Rake version has to be consistent for tests to pass "rake" => "10.0.2", @@ -36,7 +37,7 @@ module Spec FileUtils.rm_rf(Path.base_system_gems) FileUtils.mkdir_p(Path.base_system_gems) puts "installing gems for the tests to use..." - DEPS.sort {|a, _| a[1].nil? ? 1 : -1 }.each {|n, v| install_gem(n, v) } + install_gems(DEPS) File.open(manifest_path, "w") {|f| f << manifest.join } end @@ -45,10 +46,14 @@ module Spec Gem::DefaultUserInteraction.ui = Gem::SilentUI.new end - def self.install_gem(name, version = nil) - cmd = "gem install #{name} --no-rdoc --no-ri" - cmd += " --version '#{version}'" if version - system(cmd) || raise("Installing gem #{name} for the tests to use failed!") + def self.install_gems(gems) + reqs, no_reqs = gems.partition {|_, req| !req.nil? && !req.split(" ").empty? } + no_reqs.map!(&:first) + reqs.map! {|name, req| "'#{name}:#{req}'" } + deps = reqs.concat(no_reqs).join(" ") + cmd = "gem install #{deps} --no-rdoc --no-ri" + puts cmd + system(cmd) || raise("Installing gems #{deps} for the tests to use failed!") end end end diff --git a/spec/support/the_bundle.rb b/spec/support/the_bundle.rb index 86df9cd9..742d3934 100644 --- a/spec/support/the_bundle.rb +++ b/spec/support/the_bundle.rb @@ -27,5 +27,10 @@ module Spec def lockfile bundle_dir.join("Gemfile.lock") end + + def locked_gems + raise "Cannot read lockfile if it doesn't exist" unless locked? + Bundler::LockfileParser.new(lockfile.read) + end end end |