diff options
Diffstat (limited to 'spec/bundler/install')
34 files changed, 4692 insertions, 1464 deletions
diff --git a/spec/bundler/install/allow_offline_install_spec.rb b/spec/bundler/install/allow_offline_install_spec.rb index 524363fde5..8da94718e0 100644 --- a/spec/bundler/install/allow_offline_install_spec.rb +++ b/spec/bundler/install/allow_offline_install_spec.rb @@ -7,7 +7,7 @@ RSpec.describe "bundle install with :allow_offline_install" do context "with no cached data locally" do it "still installs" do - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "http://testgemserver.local" gem "rack-obama" G @@ -15,7 +15,7 @@ RSpec.describe "bundle install with :allow_offline_install" do end it "still fails when the network is down" do - install_gemfile <<-G, :artifice => "fail", :raise_on_error => false + install_gemfile <<-G, artifice: "fail", raise_on_error: false source "http://testgemserver.local" gem "rack-obama" G @@ -26,10 +26,10 @@ RSpec.describe "bundle install with :allow_offline_install" do context "with cached data locally" do it "will install from the compact index" do - system_gems ["rack-1.0.0"], :path => default_bundle_path + system_gems ["rack-1.0.0"], path: default_bundle_path bundle "config set clean false" - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "http://testgemserver.local" gem "rack-obama" gem "rack", "< 1.0" @@ -42,7 +42,7 @@ RSpec.describe "bundle install with :allow_offline_install" do gem "rack-obama" G - bundle :update, :artifice => "fail", :all => true + bundle :update, artifice: "fail", all: true expect(last_command.stdboth).to include "Using the cached data for the new index because of a network error" expect(the_bundle).to include_gems("rack-obama 1.0", "rack 1.0.0") @@ -51,9 +51,12 @@ RSpec.describe "bundle install with :allow_offline_install" do def break_git_remote_ops! FileUtils.mkdir_p(tmp("broken_path")) File.open(tmp("broken_path/git"), "w", 0o755) do |f| - f.puts strip_whitespace(<<-RUBY) + f.puts <<~RUBY #!/usr/bin/env ruby - if %w(fetch --force --quiet --tags refs/heads/*:refs/heads/*).-(ARGV).empty? || %w(clone --bare --no-hardlinks --quiet).-(ARGV).empty? + fetch_args = %w(fetch --force --quiet) + clone_args = %w(clone --bare --no-hardlinks --quiet) + + if (fetch_args.-(ARGV).empty? || clone_args.-(ARGV).empty?) && ARGV.any? {|arg| arg.start_with?("file://") } warn "git remote ops have been disabled" exit 1 end @@ -72,14 +75,14 @@ RSpec.describe "bundle install with :allow_offline_install" do it "will install from a cached git repo" do skip "doesn't print errors" if Gem.win_platform? - git = build_git "a", "1.0.0", :path => lib_path("a") - update_git("a", :path => git.path, :branch => "new_branch") + git = build_git "a", "1.0.0", path: lib_path("a") + update_git("a", path: git.path, branch: "new_branch") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "a", :git => #{git.path.to_s.dump} G - break_git_remote_ops! { bundle :update, :all => true } + break_git_remote_ops! { bundle :update, all: true } expect(err).to include("Using cached git data because of network errors") expect(the_bundle).to be_locked diff --git a/spec/bundler/install/binstubs_spec.rb b/spec/bundler/install/binstubs_spec.rb index 6961171f4f..928ba80b15 100644 --- a/spec/bundler/install/binstubs_spec.rb +++ b/spec/bundler/install/binstubs_spec.rb @@ -2,10 +2,8 @@ RSpec.describe "bundle install" do describe "when system_bindir is set" do - # On OS X, Gem.bindir defaults to /usr/bin, so system_bindir is useful if - # you want to avoid sudo installs for system gems with OS X's default ruby it "overrides Gem.bindir" do - expect(Pathname.new("/usr/bin")).not_to be_writable unless Process.euid == 0 + expect(Pathname.new("/usr/bin")).not_to be_writable gemfile <<-G def Gem.bindir; "/usr/bin"; end source "#{file_uri_for(gem_repo1)}" diff --git a/spec/bundler/install/bundler_spec.rb b/spec/bundler/install/bundler_spec.rb index 963ce82db8..19911f1154 100644 --- a/spec/bundler/install/bundler_spec.rb +++ b/spec/bundler/install/bundler_spec.rb @@ -30,19 +30,18 @@ RSpec.describe "bundle install" do end it "causes a conflict if explicitly requesting a different version of bundler" do - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo2)}" gem "rails", "3.0" gem "bundler", "0.9.1" G - nice_error = <<-E.strip.gsub(/^ {8}/, "") - Bundler could not find compatible versions for gem "bundler": - In Gemfile: - bundler (= 0.9.1) + nice_error = <<~E.strip + Could not find compatible versions - Current Bundler version: - bundler (#{Bundler::VERSION}) + Because the current Bundler version (#{Bundler::VERSION}) does not satisfy bundler = 0.9.1 + and Gemfile depends on bundler = 0.9.1, + version solving has failed. Your bundle requires a different version of Bundler than the one you're running. Install the necessary version with `gem install bundler:0.9.1` and rerun bundler using `bundle _0.9.1_ install` @@ -51,19 +50,21 @@ RSpec.describe "bundle install" do end it "causes a conflict if explicitly requesting a non matching requirement on bundler" do - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo2)}" gem "rails", "3.0" gem "bundler", "~> 0.8" G - nice_error = <<-E.strip.gsub(/^ {8}/, "") - Bundler could not find compatible versions for gem "bundler": - In Gemfile: - bundler (~> 0.8) + nice_error = <<~E.strip + Could not find compatible versions - Current Bundler version: - bundler (#{Bundler::VERSION}) + Because rails >= 3.0 depends on bundler >= 0.9.0.pre + and the current Bundler version (#{Bundler::VERSION}) does not satisfy bundler >= 0.9.0.pre, < 1.A, + rails >= 3.0 requires bundler >= 1.A. + So, because Gemfile depends on rails = 3.0 + and Gemfile depends on bundler ~> 0.8, + version solving has failed. Your bundle requires a different version of Bundler than the one you're running. Install the necessary version with `gem install bundler:0.9.1` and rerun bundler using `bundle _0.9.1_ install` @@ -72,19 +73,18 @@ RSpec.describe "bundle install" do end it "causes a conflict if explicitly requesting a version of bundler that doesn't exist" do - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo2)}" gem "rails", "3.0" gem "bundler", "0.9.2" G - nice_error = <<-E.strip.gsub(/^ {8}/, "") - Bundler could not find compatible versions for gem "bundler": - In Gemfile: - bundler (= 0.9.2) + nice_error = <<~E.strip + Could not find compatible versions - Current Bundler version: - bundler (#{Bundler::VERSION}) + Because the current Bundler version (#{Bundler::VERSION}) does not satisfy bundler = 0.9.2 + and Gemfile depends on bundler = 0.9.2, + version solving has failed. Your bundle requires a different version of Bundler than the one you're running, and that version could not be found. E @@ -143,20 +143,21 @@ RSpec.describe "bundle install" do end end - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo2)}" gem "activemerchant" gem "rails_pinned_to_old_activesupport" G - nice_error = <<-E.strip.gsub(/^ {8}/, "") - Bundler could not find compatible versions for gem "activesupport": - In Gemfile: - activemerchant was resolved to 1.0, which depends on - activesupport (>= 2.0.0) + nice_error = <<~E.strip + Could not find compatible versions - rails_pinned_to_old_activesupport was resolved to 1.0, which depends on - activesupport (= 1.2.3) + Because every version of rails_pinned_to_old_activesupport depends on activesupport = 1.2.3 + and every version of activemerchant depends on activesupport >= 2.0.0, + every version of rails_pinned_to_old_activesupport is incompatible with activemerchant >= 0. + So, because Gemfile depends on activemerchant >= 0 + and Gemfile depends on rails_pinned_to_old_activesupport >= 0, + version solving has failed. E expect(err).to include(nice_error) end @@ -170,19 +171,20 @@ RSpec.describe "bundle install" do end end - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo2)}" gem "rails_pinned_to_old_activesupport" gem "activesupport", "2.3.5" G - nice_error = <<-E.strip.gsub(/^ {8}/, "") - Bundler could not find compatible versions for gem "activesupport": - In Gemfile: - activesupport (= 2.3.5) + nice_error = <<~E.strip + Could not find compatible versions - rails_pinned_to_old_activesupport was resolved to 1.0, which depends on - activesupport (= 1.2.3) + Because every version of rails_pinned_to_old_activesupport depends on activesupport = 1.2.3 + and Gemfile depends on rails_pinned_to_old_activesupport >= 0, + activesupport = 1.2.3 is required. + So, because Gemfile depends on activesupport = 2.3.5, + version solving has failed. E expect(err).to include(nice_error) end @@ -208,6 +210,33 @@ RSpec.describe "bundle install" do expect(err).to be_empty end + it "prints the previous version when switching to a previously downloaded gem" do + build_repo4 do + build_gem "rails", "7.0.3" + build_gem "rails", "7.0.4" + end + + bundle "config set path.system true" + + install_gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem 'rails', "7.0.4" + G + + install_gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem 'rails', "7.0.3" + G + + install_gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem 'rails', "7.0.4" + G + + expect(out).to include("Using rails 7.0.4 (was 7.0.3)") + expect(err).to be_empty + end + it "can install dependencies with newer bundler version with system gems" do bundle "config set path.system true" diff --git a/spec/bundler/install/deploy_spec.rb b/spec/bundler/install/deploy_spec.rb index 8f2650a932..d89fdea6f1 100644 --- a/spec/bundler/install/deploy_spec.rb +++ b/spec/bundler/install/deploy_spec.rb @@ -8,26 +8,26 @@ RSpec.describe "install in deployment or frozen mode" do G end - context "with CLI flags", :bundler => "< 3" do + context "with CLI flags", bundler: "< 3" do it "fails without a lockfile and says that --deployment requires a lock" do - bundle "install --deployment", :raise_on_error => false - expect(err).to include("The --deployment flag requires a Gemfile.lock") + bundle "install --deployment", raise_on_error: false + expect(err).to include("The --deployment flag requires a lockfile") end it "fails without a lockfile and says that --frozen requires a lock" do - bundle "install --frozen", :raise_on_error => false - expect(err).to include("The --frozen flag requires a Gemfile.lock") + bundle "install --frozen", raise_on_error: false + expect(err).to include("The --frozen flag requires a lockfile") end it "disallows --deployment --system" do - bundle "install --deployment --system", :raise_on_error => false + bundle "install --deployment --system", raise_on_error: false expect(err).to include("You have specified both --deployment") expect(err).to include("Please choose only one option") expect(exitstatus).to eq(15) end it "disallows --deployment --path --system" do - bundle "install --deployment --path . --system", :raise_on_error => false + bundle "install --deployment --path . --system", raise_on_error: false expect(err).to include("You have specified both --path") expect(err).to include("as well as --system") expect(err).to include("Please choose only one option") @@ -35,10 +35,43 @@ RSpec.describe "install in deployment or frozen mode" do end it "doesn't mess up a subsequent `bundle install` after you try to deploy without a lock" do - bundle "install --deployment", :raise_on_error => false + bundle "install --deployment", raise_on_error: false bundle :install expect(the_bundle).to include_gems "rack 1.0" end + + it "installs gems by default to vendor/bundle" do + bundle :lock + bundle "install --deployment" + expect(out).to include("vendor/bundle") + end + + it "installs gems to custom path if specified" do + bundle :lock + bundle "install --path vendor/bundle2 --deployment" + expect(out).to include("vendor/bundle2") + end + + it "works with the --frozen flag" do + bundle :lock + bundle "install --frozen" + end + + it "explodes with the --deployment flag if you make a change and don't check in the lockfile" do + bundle :lock + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem "rack" + gem "rack-obama" + G + + bundle "install --deployment", raise_on_error: false + expect(err).to include("frozen mode") + expect(err).to include("You have added to the Gemfile") + expect(err).to include("* rack-obama") + expect(err).not_to include("You have deleted from the Gemfile") + expect(err).not_to include("You have changed in the Gemfile") + end end it "still works if you are not in the app directory and specify --gemfile" do @@ -46,7 +79,7 @@ RSpec.describe "install in deployment or frozen mode" do simulate_new_machine bundle "config set --local deployment true" bundle "config set --local path vendor/bundle" - bundle "install --gemfile #{tmp}/bundled_app/Gemfile", :dir => tmp + bundle "install --gemfile #{tmp}/bundled_app/Gemfile", dir: tmp expect(the_bundle).to include_gems "rack 1.0" end @@ -70,12 +103,12 @@ RSpec.describe "install in deployment or frozen mode" do bundle :install bundle "config set --local deployment true" bundle :install - bundle "exec bundle check", :env => { "PATH" => path } + bundle "exec bundle check", env: { "PATH" => path } end it "works when using path gems from the same path and the version is specified" do - build_lib "foo", :path => lib_path("nested/foo") - build_lib "bar", :path => lib_path("nested/bar") + build_lib "foo", path: lib_path("nested/foo") + build_lib "bar", path: lib_path("nested/bar") gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "foo", "1.0", :path => "#{lib_path("nested")}" @@ -88,7 +121,7 @@ RSpec.describe "install in deployment or frozen mode" do end it "works when path gems are specified twice" do - build_lib "foo", :path => lib_path("nested/foo") + build_lib "foo", path: lib_path("nested/foo") gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "foo", :path => "#{lib_path("nested/foo")}" @@ -101,14 +134,14 @@ RSpec.describe "install in deployment or frozen mode" do end it "works when there are credentials in the source URL" do - install_gemfile(<<-G, :artifice => "endpoint_strict_basic_authentication", :quiet => true) + install_gemfile(<<-G, artifice: "endpoint_strict_basic_authentication", quiet: true) source "http://user:pass@localgemserver.test/" gem "rack-obama", ">= 1.0" G bundle "config set --local deployment true" - bundle :install, :artifice => "endpoint_strict_basic_authentication" + bundle :install, artifice: "endpoint_strict_basic_authentication" end it "works with sources given by a block" do @@ -141,7 +174,7 @@ RSpec.describe "install in deployment or frozen mode" do rack (1.0.0) PLATFORMS - #{local} + #{generic_local_platform} DEPENDENCIES rack @@ -150,50 +183,10 @@ RSpec.describe "install in deployment or frozen mode" do bundle "config set --local deployment true" end - it "prevents the replace by default" do - bundle :install, :raise_on_error => false - - expect(err).to match(/The list of sources changed/) - end - - context "when allow_deployment_source_credential_changes is true" do - before { bundle "config set allow_deployment_source_credential_changes true" } - - it "allows the replace" do - bundle :install - - expect(out).to match(/Bundle complete!/) - end - end - - context "when allow_deployment_source_credential_changes is false" do - before { bundle "config set allow_deployment_source_credential_changes false" } - - it "prevents the replace" do - bundle :install, :raise_on_error => false - - expect(err).to match(/The list of sources changed/) - end - end - - context "when BUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES env var is true" do - before { ENV["BUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES"] = "true" } - - it "allows the replace" do - bundle :install + it "allows the replace" do + bundle :install - expect(out).to match(/Bundle complete!/) - end - end - - context "when BUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES env var is false" do - before { ENV["BUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES"] = "false" } - - it "prevents the replace" do - bundle :install, :raise_on_error => false - - expect(err).to match(/The list of sources changed/) - end + expect(out).to match(/Bundle complete!/) end end @@ -202,29 +195,41 @@ RSpec.describe "install in deployment or frozen mode" do bundle "install" end - it "installs gems by default to vendor/bundle", :bundler => "< 3" do - bundle "install --deployment" + it "installs gems by default to vendor/bundle" do + bundle "config set deployment true" + expect do + bundle "install" + end.not_to change { bundled_app_lock.mtime } expect(out).to include("vendor/bundle") end - it "installs gems to custom path if specified", :bundler => "< 3" do - bundle "install --path vendor/bundle2 --deployment" + it "installs gems to custom path if specified" do + bundle "config set path vendor/bundle2" + bundle "config set deployment true" + bundle "install" expect(out).to include("vendor/bundle2") end - it "works with the --deployment flag if you didn't change anything", :bundler => "< 3" do - bundle "install --deployment" + it "installs gems to custom path if specified, even when configured through ENV" do + bundle "config set deployment true" + bundle "install", env: { "BUNDLE_PATH" => "vendor/bundle2" } + expect(out).to include("vendor/bundle2") end - it "works with the --frozen flag if you didn't change anything", :bundler => "< 3" do - bundle "install --frozen" + it "works with the `frozen` setting" do + bundle "config set frozen true" + expect do + bundle "install" + end.not_to change { bundled_app_lock.mtime } end it "works with BUNDLE_FROZEN if you didn't change anything" do - bundle :install, :env => { "BUNDLE_FROZEN" => "true" } + expect do + bundle :install, env: { "BUNDLE_FROZEN" => "true" } + end.not_to change { bundled_app_lock.mtime } end - it "explodes with the --deployment flag if you make a change and don't check in the lockfile" do + it "explodes with the `deployment` setting if you make a change and don't check in the lockfile" do gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rack" @@ -232,8 +237,8 @@ RSpec.describe "install in deployment or frozen mode" do G bundle "config set --local deployment true" - bundle :install, :raise_on_error => false - expect(err).to include("deployment mode") + bundle :install, raise_on_error: false + expect(err).to include("frozen mode") expect(err).to include("You have added to the Gemfile") expect(err).to include("* rack-obama") expect(err).not_to include("You have deleted from the Gemfile") @@ -253,11 +258,60 @@ RSpec.describe "install in deployment or frozen mode" do bundle "config set --local path .bundle" bundle "config set --local without development" bundle "config set --local deployment true" - bundle :install, :env => { "DEBUG" => "1" } + bundle :install, env: { "DEBUG" => "1" } run "puts :WIN" expect(out).to eq("WIN") end + it "works if a gem is missing, but it's on a different platform" do + build_repo2 + + install_gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + + source "#{file_uri_for(gem_repo1)}" do + gem "rake", platform: :#{not_local_tag} + end + G + + bundle :install, env: { "BUNDLE_FROZEN" => "true" } + expect(last_command).to be_success + end + + it "shows a good error if a gem is missing from the lockfile" do + build_repo4 do + build_gem "foo" + build_gem "bar" + end + + gemfile <<-G + source "https://gem.repo4" + + gem "foo" + gem "bar" + G + + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + foo (1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo + bar + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle :install, env: { "BUNDLE_FROZEN" => "true" }, raise_on_error: false, artifice: "compact_index" + expect(err).to include("Your lock file is missing \"bar\", but the lockfile can't be updated because frozen mode is set") + end + it "explodes if a path gem is missing" do build_lib "path_gem" install_gemfile <<-G @@ -270,11 +324,11 @@ RSpec.describe "install in deployment or frozen mode" do bundle "config set --local path .bundle" bundle "config set --local deployment true" - bundle :install, :raise_on_error => false + bundle :install, raise_on_error: false expect(err).to include("The path `#{lib_path("path_gem-1.0")}` does not exist.") end - it "can have --frozen set via an environment variable", :bundler => "< 3" do + it "can have --frozen set via an environment variable" do gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rack" @@ -282,8 +336,8 @@ RSpec.describe "install in deployment or frozen mode" do G ENV["BUNDLE_FROZEN"] = "1" - bundle "install", :raise_on_error => false - expect(err).to include("deployment mode") + bundle "install", raise_on_error: false + expect(err).to include("frozen mode") expect(err).to include("You have added to the Gemfile") expect(err).to include("* rack-obama") expect(err).not_to include("You have deleted from the Gemfile") @@ -298,21 +352,21 @@ RSpec.describe "install in deployment or frozen mode" do G ENV["BUNDLE_DEPLOYMENT"] = "true" - bundle "install", :raise_on_error => false - expect(err).to include("deployment mode") + bundle "install", raise_on_error: false + expect(err).to include("frozen mode") expect(err).to include("You have added to the Gemfile") expect(err).to include("* rack-obama") expect(err).not_to include("You have deleted from the Gemfile") expect(err).not_to include("You have changed in the Gemfile") end - it "installs gems by default to vendor/bundle when deployment mode is set via an environment variable", :bundler => "< 3" do + it "installs gems by default to vendor/bundle when deployment mode is set via an environment variable" do ENV["BUNDLE_DEPLOYMENT"] = "true" bundle "install" expect(out).to include("vendor/bundle") end - it "installs gems to custom path when deployment mode is set via an environment variable ", :bundler => "< 3" do + it "installs gems to custom path when deployment mode is set via an environment variable " do ENV["BUNDLE_DEPLOYMENT"] = "true" ENV["BUNDLE_PATH"] = "vendor/bundle2" bundle "install" @@ -329,7 +383,7 @@ RSpec.describe "install in deployment or frozen mode" do ENV["BUNDLE_FROZEN"] = "false" ENV["BUNDLE_DEPLOYMENT"] = "false" bundle "install" - expect(out).not_to include("deployment mode") + expect(out).not_to include("frozen mode") expect(out).not_to include("You have added to the Gemfile") expect(out).not_to include("* rack-obama") end @@ -341,8 +395,8 @@ RSpec.describe "install in deployment or frozen mode" do G bundle "config set --local deployment true" - bundle :install, :raise_on_error => false - expect(err).to include("deployment mode") + bundle :install, raise_on_error: false + expect(err).to include("frozen mode") expect(err).to include("You have added to the Gemfile:\n* activesupport\n\n") expect(err).to include("You have deleted from the Gemfile:\n* rack") expect(err).not_to include("You have changed in the Gemfile") @@ -355,13 +409,13 @@ RSpec.describe "install in deployment or frozen mode" do G bundle "config set --local deployment true" - bundle :install, :raise_on_error => false - expect(err).to include("deployment mode") - expect(err).to include("You have added to the Gemfile:\n* source: git://hubz.com") - expect(err).not_to include("You have changed in the Gemfile") + bundle :install, raise_on_error: false + expect(err).to include("frozen mode") + expect(err).not_to include("You have added to the Gemfile") + expect(err).to include("You have changed in the Gemfile:\n* rack from `no specified source` to `git://hubz.com`") end - it "explodes if you unpin a source" do + it "explodes if you change a source" do build_git "rack" install_gemfile <<-G @@ -375,16 +429,16 @@ RSpec.describe "install in deployment or frozen mode" do G bundle "config set --local deployment true" - bundle :install, :raise_on_error => false - expect(err).to include("deployment mode") - expect(err).to include("You have deleted from the Gemfile:\n* source: #{lib_path("rack-1.0")}") + bundle :install, raise_on_error: false + expect(err).to include("frozen mode") + expect(err).not_to include("You have deleted from the Gemfile") expect(err).not_to include("You have added to the Gemfile") - expect(err).not_to include("You have changed in the Gemfile") + expect(err).to include("You have changed in the Gemfile:\n* rack from `#{lib_path("rack-1.0")}` to `no specified source`") end - it "explodes if you unpin a source, leaving it pinned somewhere else" do - build_lib "foo", :path => lib_path("rack/foo") - build_git "rack", :path => lib_path("rack") + it "explodes if you change a source" do + build_lib "foo", path: lib_path("rack/foo") + build_git "rack", path: lib_path("rack") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -399,9 +453,9 @@ RSpec.describe "install in deployment or frozen mode" do G bundle "config set --local deployment true" - bundle :install, :raise_on_error => false - expect(err).to include("deployment mode") - expect(err).to include("You have changed in the Gemfile:\n* rack from `no specified source` to `#{lib_path("rack")}`") + bundle :install, raise_on_error: false + expect(err).to include("frozen mode") + expect(err).to include("You have changed in the Gemfile:\n* rack from `#{lib_path("rack")}` to `no specified source`") expect(err).not_to include("You have added to the Gemfile") expect(err).not_to include("You have deleted from the Gemfile") end @@ -417,23 +471,23 @@ RSpec.describe "install in deployment or frozen mode" do gem "rack-obama" G - run "require 'rack'", :raise_on_error => false - expect(err).to include strip_whitespace(<<-E).strip -The dependencies in your gemfile changed + run "require 'rack'", raise_on_error: false + expect(err).to include <<~E.strip + The dependencies in your gemfile changed, but the lockfile can't be updated because frozen mode is set (Bundler::ProductionError) -You have added to the Gemfile: -* rack (= 1.0.0) -* rack-obama + You have added to the Gemfile: + * rack (= 1.0.0) + * rack-obama -You have deleted from the Gemfile: -* rack + You have deleted from the Gemfile: + * rack E end end context "with path in Gemfile and packed" do it "works fine after bundle package and bundle install --local" do - build_lib "foo", :path => lib_path("foo") + build_lib "foo", path: lib_path("foo") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "foo", :path => "#{lib_path("foo")}" @@ -452,7 +506,7 @@ You have deleted from the Gemfile: simulate_new_machine bundle "config set --local deployment true" bundle "install --verbose" - expect(out).not_to include("You are trying to install in deployment mode after changing your Gemfile") + expect(out).not_to include("but the lockfile can't be updated because frozen mode is set") expect(out).not_to include("You have added to the Gemfile") expect(out).not_to include("You have deleted from the Gemfile") expect(out).to include("vendor/cache/foo") diff --git a/spec/bundler/install/failure_spec.rb b/spec/bundler/install/failure_spec.rb index 4a9c33754f..f972a37bf6 100644 --- a/spec/bundler/install/failure_spec.rb +++ b/spec/bundler/install/failure_spec.rb @@ -14,7 +14,7 @@ RSpec.describe "bundle install" do end end - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo2)}" gem "rails" G @@ -39,7 +39,7 @@ In Gemfile: end it "removes the downloaded .gem" do - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo4)}" gem "a" G diff --git a/spec/bundler/install/gemfile/eval_gemfile_spec.rb b/spec/bundler/install/gemfile/eval_gemfile_spec.rb index 02283291b4..cfa66e5986 100644 --- a/spec/bundler/install/gemfile/eval_gemfile_spec.rb +++ b/spec/bundler/install/gemfile/eval_gemfile_spec.rb @@ -2,7 +2,7 @@ RSpec.describe "bundle install with gemfile that uses eval_gemfile" do before do - build_lib("gunks", :path => bundled_app.join("gems/gunks")) do |s| + build_lib("gunks", path: bundled_app.join("gems/gunks")) do |s| s.name = "gunks" s.version = "0.0.1" end @@ -24,7 +24,7 @@ RSpec.describe "bundle install with gemfile that uses eval_gemfile" do expect(out).to include("Resolving dependencies") expect(out).to include("Bundle complete") - expect(the_bundle).to include_gem "gunks 0.0.1", :source => "path@#{bundled_app("gems", "gunks")}" + expect(the_bundle).to include_gem "gunks 0.0.1", source: "path@#{bundled_app("gems", "gunks")}" end end @@ -64,7 +64,7 @@ RSpec.describe "bundle install with gemfile that uses eval_gemfile" do context "eval-ed Gemfile has relative-path gems" do before do - build_lib("a", :path => bundled_app("gems/a")) + build_lib("a", path: bundled_app("gems/a")) create_file bundled_app("nested/Gemfile-nested"), <<-G source "#{file_uri_for(gem_repo1)}" gem "a", :path => "../gems/a" @@ -102,7 +102,7 @@ RSpec.describe "bundle install with gemfile that uses eval_gemfile" do expect(out).to include("Resolving dependencies") expect(out).to include("Bundle complete") - expect(the_bundle).to include_gem "gunks 0.0.1", :source => "path@#{bundled_app("gems", "gunks")}" + expect(the_bundle).to include_gem "gunks 0.0.1", source: "path@#{bundled_app("gems", "gunks")}" end end diff --git a/spec/bundler/install/gemfile/force_ruby_platform_spec.rb b/spec/bundler/install/gemfile/force_ruby_platform_spec.rb new file mode 100644 index 0000000000..a29b79ad62 --- /dev/null +++ b/spec/bundler/install/gemfile/force_ruby_platform_spec.rb @@ -0,0 +1,146 @@ +# frozen_string_literal: true + +RSpec.describe "bundle install with force_ruby_platform DSL option", :jruby do + context "when no transitive deps" do + before do + build_repo4 do + # Build a gem with platform specific versions + build_gem("platform_specific") do |s| + s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 RUBY'" + end + + build_gem("platform_specific") do |s| + s.platform = Bundler.local_platform + s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 #{Bundler.local_platform}'" + end + + # Build the exact same gem with a different name to compare using vs not using the option + build_gem("platform_specific_forced") do |s| + s.write "lib/platform_specific_forced.rb", "PLATFORM_SPECIFIC_FORCED = '1.0.0 RUBY'" + end + + build_gem("platform_specific_forced") do |s| + s.platform = Bundler.local_platform + s.write "lib/platform_specific_forced.rb", "PLATFORM_SPECIFIC_FORCED = '1.0.0 #{Bundler.local_platform}'" + end + end + end + + it "pulls the pure ruby variant of the given gem" do + install_gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + + gem "platform_specific_forced", :force_ruby_platform => true + gem "platform_specific" + G + + expect(the_bundle).to include_gems "platform_specific_forced 1.0.0 RUBY" + expect(the_bundle).to include_gems "platform_specific 1.0.0 #{Bundler.local_platform}" + end + + it "still respects a global `force_ruby_platform` config" do + install_gemfile <<-G, env: { "BUNDLE_FORCE_RUBY_PLATFORM" => "true" } + source "#{file_uri_for(gem_repo4)}" + + gem "platform_specific_forced", :force_ruby_platform => true + gem "platform_specific" + G + + expect(the_bundle).to include_gems "platform_specific_forced 1.0.0 RUBY" + expect(the_bundle).to include_gems "platform_specific 1.0.0 RUBY" + end + end + + context "when also a transitive dependency" do + before do + build_repo4 do + build_gem("depends_on_platform_specific") {|s| s.add_runtime_dependency "platform_specific" } + + build_gem("platform_specific") do |s| + s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 RUBY'" + end + + build_gem("platform_specific") do |s| + s.platform = Bundler.local_platform + s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 #{Bundler.local_platform}'" + end + end + end + + it "still pulls the ruby variant" do + install_gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + + gem "depends_on_platform_specific" + gem "platform_specific", :force_ruby_platform => true + G + + expect(the_bundle).to include_gems "platform_specific 1.0.0 RUBY" + end + end + + context "with transitive dependencies with platform specific versions" do + before do + build_repo4 do + build_gem("depends_on_platform_specific") do |s| + s.add_runtime_dependency "platform_specific" + s.write "lib/depends_on_platform_specific.rb", "DEPENDS_ON_PLATFORM_SPECIFIC = '1.0.0 RUBY'" + end + + build_gem("depends_on_platform_specific") do |s| + s.add_runtime_dependency "platform_specific" + s.platform = Bundler.local_platform + s.write "lib/depends_on_platform_specific.rb", "DEPENDS_ON_PLATFORM_SPECIFIC = '1.0.0 #{Bundler.local_platform}'" + end + + build_gem("platform_specific") do |s| + s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 RUBY'" + end + + build_gem("platform_specific") do |s| + s.platform = Bundler.local_platform + s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 #{Bundler.local_platform}'" + end + end + end + + it "ignores ruby variants for the transitive dependencies" do + install_gemfile <<-G, env: { "DEBUG_RESOLVER" => "true" } + source "#{file_uri_for(gem_repo4)}" + + gem "depends_on_platform_specific", :force_ruby_platform => true + G + + expect(the_bundle).to include_gems "depends_on_platform_specific 1.0.0 RUBY" + expect(the_bundle).to include_gems "platform_specific 1.0.0 #{Bundler.local_platform}" + end + + it "reinstalls the ruby variant when a platform specific variant is already installed, the lockile has only RUBY platform, and :force_ruby_platform is used in the Gemfile" do + lockfile <<-L + GEM + remote: #{file_uri_for(gem_repo4)} + specs: + platform_specific (1.0) + + PLATFORMS + ruby + + DEPENDENCIES + platform_specific + + BUNDLED WITH + #{Bundler::VERSION} + L + + system_gems "platform_specific-1.0-#{Gem::Platform.local}", path: default_bundle_path + + install_gemfile <<-G, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }, artifice: "compact_index" + source "#{file_uri_for(gem_repo4)}" + + gem "platform_specific", :force_ruby_platform => true + G + + expect(the_bundle).to include_gems "platform_specific 1.0.0 RUBY" + end + end +end diff --git a/spec/bundler/install/gemfile/gemspec_spec.rb b/spec/bundler/install/gemfile/gemspec_spec.rb index 6d9cd2daff..63778567cf 100644 --- a/spec/bundler/install/gemfile/gemspec_spec.rb +++ b/spec/bundler/install/gemfile/gemspec_spec.rb @@ -8,8 +8,38 @@ RSpec.describe "bundle install from an existing gemspec" do end end + let(:x64_mingw_archs) do + if RUBY_PLATFORM == "x64-mingw-ucrt" + if Gem.rubygems_version >= Gem::Version.new("3.2.28") + ["x64-mingw-ucrt", "x64-mingw32"] + else + ["x64-mingw32", "x64-unknown"] + end + else + ["x64-mingw32"] + end + end + + let(:x64_mingw_gems) do + x64_mingw_archs.map {|p| "platform_specific (1.0-#{p})" }.join("\n ") + end + + let(:x64_mingw_platforms) do + x64_mingw_archs.join("\n ") + end + + def x64_mingw_checksums(checksums) + x64_mingw_archs.each do |arch| + if arch == "x64-mingw-ucrt" + checksums.no_checksum "platform_specific", "1.0", arch + else + checksums.checksum gem_repo2, "platform_specific", "1.0", arch + end + end + end + it "should install runtime and development dependencies" do - build_lib("foo", :path => tmp.join("foo")) do |s| + build_lib("foo", path: tmp.join("foo")) do |s| s.write("Gemfile", "source :rubygems\ngemspec") s.add_dependency "bar", "=1.0.0" s.add_development_dependency "bar-dev", "=1.0.0" @@ -20,11 +50,11 @@ RSpec.describe "bundle install from an existing gemspec" do G expect(the_bundle).to include_gems "bar 1.0.0" - expect(the_bundle).to include_gems "bar-dev 1.0.0", :groups => :development + expect(the_bundle).to include_gems "bar-dev 1.0.0", groups: :development end it "that is hidden should install runtime and development dependencies" do - build_lib("foo", :path => tmp.join("foo")) do |s| + build_lib("foo", path: tmp.join("foo")) do |s| s.write("Gemfile", "source :rubygems\ngemspec") s.add_dependency "bar", "=1.0.0" s.add_development_dependency "bar-dev", "=1.0.0" @@ -37,7 +67,7 @@ RSpec.describe "bundle install from an existing gemspec" do G expect(the_bundle).to include_gems "bar 1.0.0" - expect(the_bundle).to include_gems "bar-dev 1.0.0", :groups => :development + expect(the_bundle).to include_gems "bar-dev 1.0.0", groups: :development end it "should handle a list of requirements" do @@ -46,7 +76,7 @@ RSpec.describe "bundle install from an existing gemspec" do build_gem "baz", "1.1" end - build_lib("foo", :path => tmp.join("foo")) do |s| + build_lib("foo", path: tmp.join("foo")) do |s| s.write("Gemfile", "source :rubygems\ngemspec") s.add_dependency "baz", ">= 1.0", "< 1.1" end @@ -59,29 +89,29 @@ RSpec.describe "bundle install from an existing gemspec" do end it "should raise if there are no gemspecs available" do - build_lib("foo", :path => tmp.join("foo"), :gemspec => false) + build_lib("foo", path: tmp.join("foo"), gemspec: false) - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo2)}" gemspec :path => '#{tmp.join("foo")}' G - expect(err).to match(/There are no gemspecs at #{tmp.join('foo')}/) + expect(err).to match(/There are no gemspecs at #{tmp.join("foo")}/) end it "should raise if there are too many gemspecs available" do - build_lib("foo", :path => tmp.join("foo")) do |s| + build_lib("foo", path: tmp.join("foo")) do |s| s.write("foo2.gemspec", build_spec("foo", "4.0").first.to_ruby) end - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo2)}" gemspec :path => '#{tmp.join("foo")}' G - expect(err).to match(/There are multiple gemspecs at #{tmp.join('foo')}/) + expect(err).to match(/There are multiple gemspecs at #{tmp.join("foo")}/) end it "should pick a specific gemspec" do - build_lib("foo", :path => tmp.join("foo")) do |s| + build_lib("foo", path: tmp.join("foo")) do |s| s.write("foo2.gemspec", "") s.add_dependency "bar", "=1.0.0" s.add_development_dependency "bar-dev", "=1.0.0" @@ -93,11 +123,11 @@ RSpec.describe "bundle install from an existing gemspec" do G expect(the_bundle).to include_gems "bar 1.0.0" - expect(the_bundle).to include_gems "bar-dev 1.0.0", :groups => :development + expect(the_bundle).to include_gems "bar-dev 1.0.0", groups: :development end it "should use a specific group for development dependencies" do - build_lib("foo", :path => tmp.join("foo")) do |s| + build_lib("foo", path: tmp.join("foo")) do |s| s.write("foo2.gemspec", "") s.add_dependency "bar", "=1.0.0" s.add_development_dependency "bar-dev", "=1.0.0" @@ -109,32 +139,32 @@ RSpec.describe "bundle install from an existing gemspec" do G expect(the_bundle).to include_gems "bar 1.0.0" - expect(the_bundle).not_to include_gems "bar-dev 1.0.0", :groups => :development - expect(the_bundle).to include_gems "bar-dev 1.0.0", :groups => :dev + expect(the_bundle).not_to include_gems "bar-dev 1.0.0", groups: :development + expect(the_bundle).to include_gems "bar-dev 1.0.0", groups: :dev end it "should match a lockfile even if the gemspec defines development dependencies" do - build_lib("foo", :path => tmp.join("foo")) do |s| + build_lib("foo", path: tmp.join("foo")) do |s| s.write("Gemfile", "source '#{file_uri_for(gem_repo1)}'\ngemspec") s.add_dependency "actionpack", "=2.3.2" - s.add_development_dependency "rake", "=13.0.1" + s.add_development_dependency "rake", rake_version end - bundle "install", :dir => tmp.join("foo") + bundle "install", dir: tmp.join("foo") # This should really be able to rely on $stderr, but, it's not written # right, so we can't. In fact, this is a bug negation test, and so it'll # ghost pass in future, and will only catch a regression if the message # doesn't change. Exit codes should be used correctly (they can be more # than just 0 and 1). bundle "config set --local deployment true" - output = bundle("install", :dir => tmp.join("foo")) + output = bundle("install", dir: tmp.join("foo")) expect(output).not_to match(/You have added to the Gemfile/) expect(output).not_to match(/You have deleted from the Gemfile/) - expect(output).not_to match(/install in deployment mode after changing/) + expect(output).not_to match(/the lockfile can't be updated because frozen mode is set/) end it "should match a lockfile without needing to re-resolve" do - build_lib("foo", :path => tmp.join("foo")) do |s| + build_lib("foo", path: tmp.join("foo")) do |s| s.add_dependency "rack" end @@ -143,7 +173,7 @@ RSpec.describe "bundle install from an existing gemspec" do gemspec :path => '#{tmp.join("foo")}' G - bundle "install", :verbose => true + bundle "install", verbose: true message = "Found no changes, using resolution from the lockfile" expect(out.scan(message).size).to eq(1) @@ -152,7 +182,7 @@ RSpec.describe "bundle install from an existing gemspec" do it "should match a lockfile without needing to re-resolve with development dependencies" do simulate_platform java - build_lib("foo", :path => tmp.join("foo")) do |s| + build_lib("foo", path: tmp.join("foo")) do |s| s.add_dependency "rack" s.add_development_dependency "thin" end @@ -162,34 +192,34 @@ RSpec.describe "bundle install from an existing gemspec" do gemspec :path => '#{tmp.join("foo")}' G - bundle "install", :verbose => true + bundle "install", verbose: true message = "Found no changes, using resolution from the lockfile" expect(out.scan(message).size).to eq(1) end - it "should match a lockfile on non-ruby platforms with a transitive platform dependency", :jruby do - build_lib("foo", :path => tmp.join("foo")) do |s| + it "should match a lockfile on non-ruby platforms with a transitive platform dependency", :jruby_only do + build_lib("foo", path: tmp.join("foo")) do |s| s.add_dependency "platform_specific" end - system_gems "platform_specific-1.0-java", :path => default_bundle_path + system_gems "platform_specific-1.0-java", path: default_bundle_path install_gemfile <<-G gemspec :path => '#{tmp.join("foo")}' G - bundle "update --bundler", :verbose => true + bundle "update --bundler", artifice: "compact_index", verbose: true expect(the_bundle).to include_gems "foo 1.0", "platform_specific 1.0 JAVA" end it "should evaluate the gemspec in its directory" do - build_lib("foo", :path => tmp.join("foo")) + build_lib("foo", path: tmp.join("foo")) File.open(tmp.join("foo/foo.gemspec"), "w") do |s| s.write "raise 'ahh' unless Dir.pwd == '#{tmp.join("foo")}'" end - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false gemspec :path => '#{tmp.join("foo")}' G expect(last_command.stdboth).not_to include("ahh") @@ -203,7 +233,7 @@ RSpec.describe "bundle install from an existing gemspec" do # so emulate that system_gems %w[rack-1.0.0 rack-0.9.1 rack-obama-1.0] - build_lib("foo", :path => bundled_app) + build_lib("foo", path: bundled_app) gemspec = bundled_app("foo.gemspec").read bundled_app("foo.gemspec").open("w") do |f| f.write "#{gemspec.strip}.tap { gem 'rack-obama'; require 'rack/obama' }" @@ -218,14 +248,14 @@ RSpec.describe "bundle install from an existing gemspec" do end it "allows conflicts" do - build_lib("foo", :path => tmp.join("foo")) do |s| + build_lib("foo", path: tmp.join("foo")) do |s| s.version = "1.0.0" s.add_dependency "bar", "= 1.0.0" end - build_gem "deps", :to_bundle => true do |s| + build_gem "deps", to_bundle: true do |s| s.add_dependency "foo", "= 0.0.1" end - build_gem "foo", "0.0.1", :to_bundle => true + build_gem "foo", "0.0.1", to_bundle: true install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" @@ -237,7 +267,7 @@ RSpec.describe "bundle install from an existing gemspec" do end it "does not break Gem.finish_resolve with conflicts" do - build_lib("foo", :path => tmp.join("foo")) do |s| + build_lib("foo", path: tmp.join("foo")) do |s| s.version = "1.0.0" s.add_dependency "bar", "= 1.0.0" end @@ -261,14 +291,14 @@ RSpec.describe "bundle install from an existing gemspec" do end it "handles downgrades" do - build_lib "omg", "2.0", :path => lib_path("omg") + build_lib "omg", "2.0", path: lib_path("omg") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gemspec :path => "#{lib_path("omg")}" G - build_lib "omg", "1.0", :path => lib_path("omg") + build_lib "omg", "1.0", path: lib_path("omg") bundle :install @@ -278,7 +308,7 @@ RSpec.describe "bundle install from an existing gemspec" do context "in deployment mode" do context "when the lockfile was not updated after a change to the gemspec's dependencies" do it "reports that installation failed" do - build_lib "cocoapods", :path => bundled_app do |s| + build_lib "cocoapods", path: bundled_app do |s| s.add_dependency "activesupport", ">= 1" end @@ -289,12 +319,12 @@ RSpec.describe "bundle install from an existing gemspec" do expect(the_bundle).to include_gems("cocoapods 1.0", "activesupport 2.3.5") - build_lib "cocoapods", :path => bundled_app do |s| + build_lib "cocoapods", path: bundled_app do |s| s.add_dependency "activesupport", ">= 1.0.1" end bundle "config set --local deployment true" - bundle :install, :raise_on_error => false + bundle :install, raise_on_error: false expect(err).to include("changed") end @@ -304,13 +334,13 @@ RSpec.describe "bundle install from an existing gemspec" do context "when child gemspecs conflict with a released gemspec" do before do # build the "parent" gem that depends on another gem in the same repo - build_lib "source_conflict", :path => bundled_app do |s| + build_lib "source_conflict", path: bundled_app do |s| s.add_dependency "rack_middleware" end # build the "child" gem that is the same version as a released gem, but # has completely different and conflicting dependency requirements - build_lib "rack_middleware", "1.0", :path => bundled_app("rack_middleware") do |s| + build_lib "rack_middleware", "1.0", path: bundled_app("rack_middleware") do |s| s.add_dependency "rack", "1.0" # anything other than 0.9.1 end end @@ -328,83 +358,69 @@ RSpec.describe "bundle install from an existing gemspec" do context "with a lockfile and some missing dependencies" do let(:source_uri) { "http://localgemserver.test" } - context "previously bundled for Ruby" do - let(:platform) { "ruby" } - - before do - skip "not installing for some reason" if Gem.win_platform? - - build_lib("foo", :path => tmp.join("foo")) do |s| - s.add_dependency "rack", "=1.0.0" - end - - gemfile <<-G - source "#{source_uri}" - gemspec :path => "../foo" - G + before do + build_lib("foo", path: tmp.join("foo")) do |s| + s.add_dependency "rack", "=1.0.0" + end - lockfile <<-L - PATH - remote: ../foo - specs: - foo (1.0) - rack (= 1.0.0) + gemfile <<-G + source "#{source_uri}" + gemspec :path => "../foo" + G - GEM - remote: #{source_uri} - specs: - rack (1.0.0) + checksums = checksums_section_when_existing do |c| + c.no_checksum "foo", "1.0" + end - PLATFORMS - #{generic_local_platform} + lockfile <<-L + PATH + remote: ../foo + specs: + foo (1.0) + rack (= 1.0.0) - DEPENDENCIES - foo! + GEM + remote: #{source_uri} + specs: + rack (1.0.0) - BUNDLED WITH - #{Bundler::VERSION} - L - end + PLATFORMS + #{generic_local_platform} - context "using JRuby with explicit platform", :jruby do - before do - create_file( - tmp.join("foo", "foo-java.gemspec"), - build_spec("foo", "1.0", "java") do - dep "rack", "=1.0.0" - @spec.authors = "authors" - @spec.summary = "summary" - end.first.to_ruby - ) - end + DEPENDENCIES + foo! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end - it "should install" do - results = bundle "install", :artifice => "endpoint" - expect(results).to include("Installing rack 1.0.0") - expect(the_bundle).to include_gems "rack 1.0.0" - end + context "using JRuby with explicit platform", :jruby_only do + before do + create_file( + tmp.join("foo", "foo-java.gemspec"), + build_spec("foo", "1.0", "java") do + dep "rack", "=1.0.0" + @spec.authors = "authors" + @spec.summary = "summary" + end.first.to_ruby + ) end - context "using JRuby", :jruby do - it "should install" do - results = bundle "install", :artifice => "endpoint" - expect(results).to include("Installing rack 1.0.0") - expect(the_bundle).to include_gems "rack 1.0.0" - end + it "should install" do + results = bundle "install", artifice: "endpoint" + expect(results).to include("Installing rack 1.0.0") + expect(the_bundle).to include_gems "rack 1.0.0" end + end - context "using Windows" do - it "should install" do - simulate_windows do - results = bundle "install", :artifice => "endpoint" - expect(results).to include("Installing rack 1.0.0") - expect(the_bundle).to include_gems "rack 1.0.0" - end - end - end + it "should install", :jruby do + results = bundle "install", artifice: "endpoint" + expect(results).to include("Installing rack 1.0.0") + expect(the_bundle).to include_gems "rack 1.0.0" end - context "bundled for ruby and jruby" do + context "bundled for multiple platforms" do let(:platform_specific_type) { :runtime } let(:dependency) { "platform_specific" } before do @@ -414,7 +430,7 @@ RSpec.describe "bundle install from an existing gemspec" do end end - build_lib "foo", :path => bundled_app do |s| + build_lib "foo", path: bundled_app do |s| if platform_specific_type == :runtime s.add_runtime_dependency dependency elsif platform_specific_type == :development @@ -429,20 +445,32 @@ RSpec.describe "bundle install from an existing gemspec" do gemspec G - simulate_platform("ruby") { bundle "install" } + bundle "config set --local force_ruby_platform true" + bundle "install" + + simulate_new_machine simulate_platform("jruby") { bundle "install" } + simulate_platform(x64_mingw32) { bundle "install" } end context "on ruby" do before do - simulate_platform("ruby") + bundle "config set --local force_ruby_platform true" bundle :install end context "as a runtime dependency" do - it "keeps java dependencies in the lockfile" do + it "keeps all platform dependencies in the lockfile" do expect(the_bundle).to include_gems "foo 1.0", "platform_specific 1.0 RUBY" - expect(lockfile).to eq strip_whitespace(<<-L) + + checksums = checksums_section_when_existing do |c| + c.no_checksum "foo", "1.0" + c.checksum gem_repo2, "platform_specific", "1.0" + c.checksum gem_repo2, "platform_specific", "1.0", "java" + x64_mingw_checksums(c) + end + + expect(lockfile).to eq <<~L PATH remote: . specs: @@ -454,14 +482,16 @@ RSpec.describe "bundle install from an existing gemspec" do specs: platform_specific (1.0) platform_specific (1.0-java) + #{x64_mingw_gems} PLATFORMS java ruby + #{x64_mingw_platforms} DEPENDENCIES foo! - + #{checksums} BUNDLED WITH #{Bundler::VERSION} L @@ -471,9 +501,17 @@ RSpec.describe "bundle install from an existing gemspec" do context "as a development dependency" do let(:platform_specific_type) { :development } - it "keeps java dependencies in the lockfile" do + it "keeps all platform dependencies in the lockfile" do expect(the_bundle).to include_gems "foo 1.0", "platform_specific 1.0 RUBY" - expect(lockfile).to eq strip_whitespace(<<-L) + + checksums = checksums_section_when_existing do |c| + c.no_checksum "foo", "1.0" + c.checksum gem_repo2, "platform_specific", "1.0" + c.checksum gem_repo2, "platform_specific", "1.0", "java" + x64_mingw_checksums(c) + end + + expect(lockfile).to eq <<~L PATH remote: . specs: @@ -484,15 +522,17 @@ RSpec.describe "bundle install from an existing gemspec" do specs: platform_specific (1.0) platform_specific (1.0-java) + #{x64_mingw_gems} PLATFORMS java ruby + #{x64_mingw_platforms} DEPENDENCIES foo! platform_specific - + #{checksums} BUNDLED WITH #{Bundler::VERSION} L @@ -503,9 +543,18 @@ RSpec.describe "bundle install from an existing gemspec" do let(:platform_specific_type) { :development } let(:dependency) { "indirect_platform_specific" } - it "keeps java dependencies in the lockfile" do + it "keeps all platform dependencies in the lockfile" do expect(the_bundle).to include_gems "foo 1.0", "indirect_platform_specific 1.0", "platform_specific 1.0 RUBY" - expect(lockfile).to eq strip_whitespace(<<-L) + + checksums = checksums_section_when_existing do |c| + c.no_checksum "foo", "1.0" + c.checksum gem_repo2, "indirect_platform_specific", "1.0" + c.checksum gem_repo2, "platform_specific", "1.0" + c.checksum gem_repo2, "platform_specific", "1.0", "java" + x64_mingw_checksums(c) + end + + expect(lockfile).to eq <<~L PATH remote: . specs: @@ -518,15 +567,17 @@ RSpec.describe "bundle install from an existing gemspec" do platform_specific platform_specific (1.0) platform_specific (1.0-java) + #{x64_mingw_gems} PLATFORMS java ruby + #{x64_mingw_platforms} DEPENDENCIES foo! indirect_platform_specific - + #{checksums} BUNDLED WITH #{Bundler::VERSION} L @@ -538,7 +589,7 @@ RSpec.describe "bundle install from an existing gemspec" do context "with multiple platforms" do before do - build_lib("foo", :path => tmp.join("foo")) do |s| + build_lib("foo", path: tmp.join("foo")) do |s| s.version = "1.0.0" s.add_development_dependency "rack" s.write "foo-universal-java.gemspec", build_spec("foo", "1.0.0", "universal-java") {|sj| sj.runtime "rack", "1.0.0" }.first.to_ruby @@ -546,7 +597,7 @@ RSpec.describe "bundle install from an existing gemspec" do end it "installs the ruby platform gemspec" do - simulate_platform "ruby" + bundle "config set --local force_ruby_platform true" install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -557,7 +608,7 @@ RSpec.describe "bundle install from an existing gemspec" do end it "installs the ruby platform gemspec and skips dev deps with `without development` configured" do - simulate_platform "ruby" + bundle "config set --local force_ruby_platform true" bundle "config set --local without development" install_gemfile <<-G @@ -572,7 +623,7 @@ RSpec.describe "bundle install from an existing gemspec" do context "with multiple platforms and resolving for more specific platforms" do before do - build_lib("chef", :path => tmp.join("chef")) do |s| + build_lib("chef", path: tmp.join("chef")) do |s| s.version = "17.1.17" s.write "chef-universal-mingw32.gemspec", build_spec("chef", "17.1.17", "universal-mingw32") {|sw| sw.runtime "win32-api", "~> 1.5.3" }.first.to_ruby end @@ -590,6 +641,12 @@ RSpec.describe "bundle install from an existing gemspec" do gemspec :path => "../chef" G + checksums = checksums_section_when_existing do |c| + c.no_checksum "chef", "17.1.17" + c.no_checksum "chef", "17.1.17", "universal-mingw32" + c.checksum gem_repo4, "win32-api", "1.5.3", "universal-mingw32" + end + initial_lockfile = <<~L PATH remote: ../chef @@ -605,12 +662,12 @@ RSpec.describe "bundle install from an existing gemspec" do PLATFORMS ruby - x64-mingw32 + #{x64_mingw_platforms} x86-mingw32 DEPENDENCIES chef! - + #{checksums} BUNDLED WITH #{Bundler::VERSION} L @@ -622,4 +679,70 @@ RSpec.describe "bundle install from an existing gemspec" do expect(lockfile).to eq initial_lockfile end end + + context "with multiple locked platforms" do + before do + build_lib("activeadmin", path: tmp.join("activeadmin")) do |s| + s.version = "2.9.0" + s.add_dependency "railties", ">= 5.2", "< 6.2" + end + + build_repo4 do + build_gem "railties", "6.1.4" + + build_gem "jruby-openssl", "0.10.7" do |s| + s.platform = "java" + end + end + + install_gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gemspec :path => "../activeadmin" + gem "jruby-openssl", :platform => :jruby + G + + bundle "lock --add-platform java" + end + + it "does not remove the platform specific specs from the lockfile when re-resolving due to gemspec changes" do + checksums = checksums_section_when_existing do |c| + c.no_checksum "activeadmin", "2.9.0" + c.no_checksum "jruby-openssl", "0.10.7", "java" + c.checksum gem_repo4, "railties", "6.1.4" + end + + expect(lockfile).to eq <<~L + PATH + remote: ../activeadmin + specs: + activeadmin (2.9.0) + railties (>= 5.2, < 6.2) + + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + jruby-openssl (0.10.7-java) + railties (6.1.4) + + PLATFORMS + #{lockfile_platforms("java")} + + DEPENDENCIES + activeadmin! + jruby-openssl + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + + gemspec = tmp.join("activeadmin/activeadmin.gemspec") + File.write(gemspec, File.read(gemspec).sub(">= 5.2", ">= 6.0")) + + previous_lockfile = lockfile + + bundle "install --local" + + expect(lockfile).to eq(previous_lockfile.sub(">= 5.2", ">= 6.0")) + end + end end diff --git a/spec/bundler/install/gemfile/git_spec.rb b/spec/bundler/install/gemfile/git_spec.rb index 5ea9eee878..45ee7b44d1 100644 --- a/spec/bundler/install/gemfile/git_spec.rb +++ b/spec/bundler/install/gemfile/git_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true RSpec.describe "bundle install with git sources" do - describe "when floating on master" do + describe "when floating on main" do before :each do build_git "foo" do |s| s.executables = "foobar" @@ -26,15 +26,22 @@ RSpec.describe "bundle install with git sources" do expect(out).to eq("WIN") end - it "caches the git repo", :bundler => "< 3" do - expect(Dir["#{default_bundle_path}/cache/bundler/git/foo-1.0-*"]).to have_attributes :size => 1 + it "caches the git repo", bundler: "< 3" do + expect(Dir["#{default_bundle_path}/cache/bundler/git/foo-1.0-*"]).to have_attributes size: 1 + end + + it "does not write to cache on bundler/setup" do + cache_path = default_bundle_path.join("cache") + FileUtils.rm_rf(cache_path) + ruby "require 'bundler/setup'" + expect(cache_path).not_to exist end it "caches the git repo globally and properly uses the cached repo on the next invocation" do simulate_new_machine bundle "config set global_gem_cache true" bundle :install - expect(Dir["#{home}/.bundle/cache/git/foo-1.0-*"]).to have_attributes :size => 1 + expect(Dir["#{home}/.bundle/cache/git/foo-1.0-*"]).to have_attributes size: 1 bundle "install --verbose" expect(err).to be_empty @@ -51,9 +58,10 @@ RSpec.describe "bundle install with git sources" do bundle "update foo" - sha = git.ref_for("master", 11) - spec_file = default_bundle_path.join("bundler/gems/foo-1.0-#{sha}/foo.gemspec").to_s - ruby_code = Gem::Specification.load(spec_file).to_ruby + sha = git.ref_for("main", 11) + spec_file = default_bundle_path.join("bundler/gems/foo-1.0-#{sha}/foo.gemspec") + expect(spec_file).to exist + ruby_code = Gem::Specification.load(spec_file.to_s).to_ruby file_code = File.read(spec_file) expect(file_code).to eq(ruby_code) end @@ -61,7 +69,7 @@ RSpec.describe "bundle install with git sources" do it "does not update the git source implicitly" do update_git "foo" - install_gemfile bundled_app2("Gemfile"), <<-G, :dir => bundled_app2 + install_gemfile bundled_app2("Gemfile"), <<-G, dir: bundled_app2 source "#{file_uri_for(gem_repo1)}" git "#{lib_path("foo-1.0")}" do gem 'foo' @@ -84,34 +92,30 @@ RSpec.describe "bundle install with git sources" do it "complains if pinned specs don't exist in the git repo" do build_git "foo" - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" gem "foo", "1.1", :git => "#{lib_path("foo-1.0")}" G - expect(err).to include("The source contains the following versions of 'foo': 1.0") + expect(err).to include("The source contains the following gems matching 'foo':\n * foo-1.0") end - it "complains with version and platform if pinned specs don't exist in the git repo" do - simulate_platform "java" - + it "complains with version and platform if pinned specs don't exist in the git repo", :jruby_only do build_git "only_java" do |s| s.platform = "java" end - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" platforms :jruby do gem "only_java", "1.2", :git => "#{lib_path("only_java-1.0-java")}" end G - expect(err).to include("The source contains the following versions of 'only_java': 1.0 java") + expect(err).to include("The source contains the following gems matching 'only_java':\n * only_java-1.0-java") end - it "complains with multiple versions and platforms if pinned specs don't exist in the git repo" do - simulate_platform "java" - + it "complains with multiple versions and platforms if pinned specs don't exist in the git repo", :jruby_only do build_git "only_java", "1.0" do |s| s.platform = "java" end @@ -121,14 +125,14 @@ RSpec.describe "bundle install with git sources" do s.write "only_java1-0.gemspec", File.read("#{lib_path("only_java-1.0-java")}/only_java.gemspec") end - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" platforms :jruby do gem "only_java", "1.2", :git => "#{lib_path("only_java-1.1-java")}" end G - expect(err).to include("The source contains the following versions of 'only_java': 1.0 java, 1.1 java") + expect(err).to include("The source contains the following gems matching 'only_java':\n * only_java-1.0-java\n * only_java-1.1-java") end it "still works after moving the application directory" do @@ -137,7 +141,7 @@ RSpec.describe "bundle install with git sources" do FileUtils.mv bundled_app, tmp("bundled_app.bck") - expect(the_bundle).to include_gems "foo 1.0", :dir => tmp("bundled_app.bck") + expect(the_bundle).to include_gems "foo 1.0", dir: tmp("bundled_app.bck") end it "can still install after moving the application directory" do @@ -146,7 +150,7 @@ RSpec.describe "bundle install with git sources" do FileUtils.mv bundled_app, tmp("bundled_app.bck") - update_git "foo", "1.1", :path => lib_path("foo-1.0") + update_git "foo", "1.1", path: lib_path("foo-1.0") gemfile tmp("bundled_app.bck/Gemfile"), <<-G source "#{file_uri_for(gem_repo1)}" @@ -157,9 +161,9 @@ RSpec.describe "bundle install with git sources" do gem "rack", "1.0" G - bundle "update foo", :dir => tmp("bundled_app.bck") + bundle "update foo", dir: tmp("bundled_app.bck") - expect(the_bundle).to include_gems "foo 1.1", "rack 1.0", :dir => tmp("bundled_app.bck") + expect(the_bundle).to include_gems "foo 1.1", "rack 1.0", dir: tmp("bundled_app.bck") end end @@ -196,6 +200,7 @@ RSpec.describe "bundle install with git sources" do gem "foo" end G + expect(err).to be_empty run <<-RUBY require 'foo' @@ -222,17 +227,56 @@ RSpec.describe "bundle install with git sources" do expect(out).to eq("WIN") end + it "works when an abbreviated revision is added after an initial, potentially shallow clone" do + install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + git "#{lib_path("foo-1.0")}" do + gem "foo" + end + G + + install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + git "#{lib_path("foo-1.0")}", :ref => #{@revision[0..7].inspect} do + gem "foo" + end + G + end + + it "works when a tag that does not look like a commit hash is used as the value of :ref" do + build_git "foo" + @remote = build_git("bar", bare: true) + update_git "foo", remote: file_uri_for(@remote.path) + update_git "foo", push: "main" + + install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem 'foo', :git => "#{@remote.path}" + G + + # Create a new tag on the remote that needs fetching + update_git "foo", tag: "v1.0.0" + update_git "foo", push: "v1.0.0" + + install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem 'foo', :git => "#{@remote.path}", :ref => "v1.0.0" + G + + expect(err).to be_empty + end + it "works when the revision is a non-head ref" do - # want to ensure we don't fallback to master - update_git "foo", :path => lib_path("foo-1.0") do |s| + # want to ensure we don't fallback to main + update_git "foo", path: lib_path("foo-1.0") do |s| s.write("lib/foo.rb", "raise 'FAIL'") end - sys_exec("git update-ref -m \"Bundler Spec!\" refs/bundler/1 master~1", :dir => lib_path("foo-1.0")) + sys_exec("git update-ref -m \"Bundler Spec!\" refs/bundler/1 main~1", dir: lib_path("foo-1.0")) # want to ensure we don't fallback to HEAD - update_git "foo", :path => lib_path("foo-1.0"), :branch => "rando" do |s| - s.write("lib/foo.rb", "raise 'FAIL'") + update_git "foo", path: lib_path("foo-1.0"), branch: "rando" do |s| + s.write("lib/foo.rb", "raise 'FAIL_FROM_RANDO'") end install_gemfile <<-G @@ -259,16 +303,16 @@ RSpec.describe "bundle install with git sources" do end G - # want to ensure we don't fallback to master - update_git "foo", :path => lib_path("foo-1.0") do |s| + # want to ensure we don't fallback to main + update_git "foo", path: lib_path("foo-1.0") do |s| s.write("lib/foo.rb", "raise 'FAIL'") end - sys_exec("git update-ref -m \"Bundler Spec!\" refs/bundler/1 master~1", :dir => lib_path("foo-1.0")) + sys_exec("git update-ref -m \"Bundler Spec!\" refs/bundler/1 main~1", dir: lib_path("foo-1.0")) # want to ensure we don't fallback to HEAD - update_git "foo", :path => lib_path("foo-1.0"), :branch => "rando" do |s| - s.write("lib/foo.rb", "raise 'FAIL'") + update_git "foo", path: lib_path("foo-1.0"), branch: "rando" do |s| + s.write("lib/foo.rb", "raise 'FAIL_FROM_RANDO'") end install_gemfile <<-G @@ -288,7 +332,7 @@ RSpec.describe "bundle install with git sources" do end it "does not download random non-head refs" do - sys_exec("git update-ref -m \"Bundler Spec!\" refs/bundler/1 master~1", :dir => lib_path("foo-1.0")) + sys_exec("git update-ref -m \"Bundler Spec!\" refs/bundler/1 main~1", dir: lib_path("foo-1.0")) bundle "config set global_gem_cache true" @@ -300,9 +344,9 @@ RSpec.describe "bundle install with git sources" do G # ensure we also git fetch after cloning - bundle :update, :all => true + bundle :update, all: true - sys_exec("git ls-remote .", :dir => Dir[home(".bundle/cache/git/foo-*")].first) + sys_exec("git ls-remote .", dir: Dir[home(".bundle/cache/git/foo-*")].first) expect(out).not_to include("refs/bundler/1") end @@ -313,7 +357,7 @@ RSpec.describe "bundle install with git sources" do let(:repo) { build_git("foo").path } it "works" do - update_git("foo", :path => repo, :branch => branch) + update_git("foo", path: repo, branch: branch) install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -330,7 +374,7 @@ RSpec.describe "bundle install with git sources" do it "works" do skip "git does not accept this" if Gem.win_platform? - update_git("foo", :path => repo, :branch => branch) + update_git("foo", path: repo, branch: branch) install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -348,7 +392,7 @@ RSpec.describe "bundle install with git sources" do it "works" do skip "git does not accept this" if Gem.win_platform? - update_git("foo", :path => repo, :branch => branch) + update_git("foo", path: repo, branch: branch) install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -367,7 +411,7 @@ RSpec.describe "bundle install with git sources" do let(:repo) { build_git("foo").path } it "works" do - update_git("foo", :path => repo, :tag => tag) + update_git("foo", path: repo, tag: tag) install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -384,7 +428,7 @@ RSpec.describe "bundle install with git sources" do it "works" do skip "git does not accept this" if Gem.win_platform? - update_git("foo", :path => repo, :tag => tag) + update_git("foo", path: repo, tag: tag) install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -402,7 +446,7 @@ RSpec.describe "bundle install with git sources" do it "works" do skip "git does not accept this" if Gem.win_platform? - update_git("foo", :path => repo, :tag => tag) + update_git("foo", path: repo, tag: tag) install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -418,16 +462,13 @@ RSpec.describe "bundle install with git sources" do describe "when specifying local override" do it "uses the local repository instead of checking a new one out" do - # We don't generate it because we actually don't need it - # build_git "rack", "0.8" - - build_git "rack", "0.8", :path => lib_path("local-rack") do |s| + build_git "rack", "0.8", path: lib_path("local-rack") do |s| s.write "lib/rack.rb", "puts :LOCAL" end gemfile <<-G source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master" + gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main" G bundle %(config set local.rack #{lib_path("local-rack")}) @@ -442,13 +483,13 @@ RSpec.describe "bundle install with git sources" do FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack")) - update_git "rack", "0.8", :path => lib_path("local-rack") do |s| + update_git "rack", "0.8", path: lib_path("local-rack") do |s| s.write "lib/rack.rb", "puts :LOCAL" end install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master" + gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main" G bundle %(config set local.rack #{lib_path("local-rack")}) @@ -461,14 +502,14 @@ RSpec.describe "bundle install with git sources" do FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack")) - update_git "rack", "0.8", :path => lib_path("local-rack") do |s| + update_git "rack", "0.8", path: lib_path("local-rack") do |s| s.write "rack.gemspec", build_spec("rack", "0.8") { runtime "rspec", "> 0" }.first.to_ruby s.write "lib/rack.rb", "puts :LOCAL" end install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master" + gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main" G bundle %(config set local.rack #{lib_path("local-rack")}) @@ -484,13 +525,13 @@ RSpec.describe "bundle install with git sources" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master" + gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main" G lockfile0 = File.read(bundled_app_lock) FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack")) - update_git "rack", "0.8", :path => lib_path("local-rack") do |s| + update_git "rack", "0.8", path: lib_path("local-rack") do |s| s.add_dependency "nokogiri", "1.4.2" end @@ -506,13 +547,13 @@ RSpec.describe "bundle install with git sources" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master" + gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main" G lockfile0 = File.read(bundled_app_lock) FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack")) - update_git "rack", "0.8", :path => lib_path("local-rack") + update_git "rack", "0.8", path: lib_path("local-rack") bundle %(config set local.rack #{lib_path("local-rack")}) bundle :install @@ -526,12 +567,12 @@ RSpec.describe "bundle install with git sources" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master" + gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main" G bundle %(config set local.rack #{lib_path("local-rack")}) - bundle :install, :raise_on_error => false - expect(err).to match(/Cannot use local override for rack-0.8 because #{Regexp.escape(lib_path('local-rack').to_s)} does not exist/) + bundle :install, raise_on_error: false + expect(err).to match(/Cannot use local override for rack-0.8 because #{Regexp.escape(lib_path("local-rack").to_s)} does not exist/) solution = "config unset local.rack" expect(err).to match(/Run `bundle #{solution}` to remove the local override/) @@ -552,8 +593,8 @@ RSpec.describe "bundle install with git sources" do G bundle %(config set local.rack #{lib_path("local-rack")}) - bundle :install, :raise_on_error => false - expect(err).to match(/Cannot use local override for rack-0.8 at #{Regexp.escape(lib_path('local-rack').to_s)} because :branch is not specified in Gemfile/) + bundle :install, raise_on_error: false + expect(err).to match(/Cannot use local override for rack-0.8 at #{Regexp.escape(lib_path("local-rack").to_s)} because :branch is not specified in Gemfile/) solution = "config unset local.rack" expect(err).to match(/Specify a branch or run `bundle #{solution}` to remove the local override/) @@ -584,47 +625,47 @@ RSpec.describe "bundle install with git sources" do FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack")) - update_git "rack", "0.8", :path => lib_path("local-rack"), :branch => "another" do |s| + update_git "rack", "0.8", path: lib_path("local-rack"), branch: "another" do |s| s.write "lib/rack.rb", "puts :LOCAL" end install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master" + gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main" G bundle %(config set local.rack #{lib_path("local-rack")}) - bundle :install, :raise_on_error => false - expect(err).to match(/is using branch another but Gemfile specifies master/) + bundle :install, raise_on_error: false + expect(err).to match(/is using branch another but Gemfile specifies main/) end it "explodes on invalid revision on install" do build_git "rack", "0.8" - build_git "rack", "0.8", :path => lib_path("local-rack") do |s| + build_git "rack", "0.8", path: lib_path("local-rack") do |s| s.write "lib/rack.rb", "puts :LOCAL" end install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master" + gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main" G bundle %(config set local.rack #{lib_path("local-rack")}) - bundle :install, :raise_on_error => false + bundle :install, raise_on_error: false expect(err).to match(/The Gemfile lock is pointing to revision \w+/) end it "does not explode on invalid revision on install" do build_git "rack", "0.8" - build_git "rack", "0.8", :path => lib_path("local-rack") do |s| + build_git "rack", "0.8", path: lib_path("local-rack") do |s| s.write "lib/rack.rb", "puts :LOCAL" end install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master" + gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main" G bundle %(config set local.rack #{lib_path("local-rack")}) @@ -665,11 +706,11 @@ RSpec.describe "bundle install with git sources" do it "installs dependencies from git even if a newer gem is available elsewhere" do system_gems "rack-1.0.0" - build_lib "rack", "1.0", :path => lib_path("nested/bar") do |s| + build_lib "rack", "1.0", path: lib_path("nested/bar") do |s| s.write "lib/rack.rb", "puts 'WIN OVERRIDE'" end - build_git "foo", :path => lib_path("nested") do |s| + build_git "foo", path: lib_path("nested") do |s| s.add_dependency "rack", "= 1.0" end @@ -688,7 +729,7 @@ RSpec.describe "bundle install with git sources" do gem "rack", "0.9.1" G - build_git "rack", :path => lib_path("rack") + build_git "rack", path: lib_path("rack") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -704,7 +745,7 @@ RSpec.describe "bundle install with git sources" do gem "rack" G - build_git "rack", "1.2", :path => lib_path("rack") + build_git "rack", "1.2", path: lib_path("rack") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -717,8 +758,8 @@ RSpec.describe "bundle install with git sources" do describe "block syntax" do it "pulls all gems from a git block" do - build_lib "omg", :path => lib_path("hi2u/omg") - build_lib "hi2u", :path => lib_path("hi2u") + build_lib "omg", path: lib_path("hi2u/omg") + build_lib "hi2u", path: lib_path("hi2u") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -766,7 +807,7 @@ RSpec.describe "bundle install with git sources" do end it "runs the gemspec in the context of its parent directory" do - build_lib "bar", :path => lib_path("foo/bar"), :gemspec => false do |s| + build_lib "bar", path: lib_path("foo/bar"), gemspec: false do |s| s.write lib_path("foo/bar/lib/version.rb"), %(BAR_VERSION = '1.0') s.write "bar.gemspec", <<-G $:.unshift Dir.pwd @@ -781,7 +822,7 @@ RSpec.describe "bundle install with git sources" do G end - build_git "foo", :path => lib_path("foo") do |s| + build_git "foo", path: lib_path("foo") do |s| s.write "bin/foo", "" end @@ -796,7 +837,7 @@ RSpec.describe "bundle install with git sources" do end it "installs from git even if a rubygems gem is present" do - build_gem "foo", "1.0", :path => lib_path("fake_foo"), :to_system => true do |s| + build_gem "foo", "1.0", path: lib_path("fake_foo"), to_system: true do |s| s.write "lib/foo.rb", "raise 'FAIL'" end @@ -811,7 +852,7 @@ RSpec.describe "bundle install with git sources" do end it "fakes the gem out if there is no gemspec" do - build_git "foo", :gemspec => false + build_git "foo", gemspec: false install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -829,7 +870,7 @@ RSpec.describe "bundle install with git sources" do gem "foo", "1.0", :git => "omgomg" G - bundle :install, :raise_on_error => false + bundle :install, raise_on_error: false expect(err).to include("Git error:") expect(err).to include("fatal") @@ -837,7 +878,7 @@ RSpec.describe "bundle install with git sources" do end it "works when the gem path has spaces in it" do - build_git "foo", :path => lib_path("foo space-1.0") + build_git "foo", path: lib_path("foo space-1.0") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -862,41 +903,47 @@ RSpec.describe "bundle install with git sources" do s.write "lib/forced.rb", "FORCED = '1.1'" end - bundle "update", :all => true + bundle "update", all: true expect(the_bundle).to include_gems "forced 1.1" - sys_exec("git reset --hard HEAD^", :dir => lib_path("forced-1.0")) + sys_exec("git reset --hard HEAD^", dir: lib_path("forced-1.0")) - bundle "update", :all => true + bundle "update", all: true expect(the_bundle).to include_gems "forced 1.0" end it "ignores submodules if :submodule is not passed" do + # CVE-2022-39253: https://lore.kernel.org/lkml/xmqq4jw1uku5.fsf@gitster.g/ + system(*%W[git config --global protocol.file.allow always]) + build_git "submodule", "1.0" build_git "has_submodule", "1.0" do |s| s.add_dependency "submodule" end - sys_exec "git submodule add #{lib_path("submodule-1.0")} submodule-1.0", :dir => lib_path("has_submodule-1.0") - sys_exec "git commit -m \"submodulator\"", :dir => lib_path("has_submodule-1.0") + sys_exec "git submodule add #{lib_path("submodule-1.0")} submodule-1.0", dir: lib_path("has_submodule-1.0") + sys_exec "git commit -m \"submodulator\"", dir: lib_path("has_submodule-1.0") - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" git "#{lib_path("has_submodule-1.0")}" do gem "has_submodule" end G - expect(err).to match(/could not find gem 'submodule/i) + expect(err).to match(%r{submodule >= 0 could not be found in rubygems repository #{file_uri_for(gem_repo1)}/, cached gems or installed locally}) expect(the_bundle).not_to include_gems "has_submodule 1.0" end it "handles repos with submodules" do + # CVE-2022-39253: https://lore.kernel.org/lkml/xmqq4jw1uku5.fsf@gitster.g/ + system(*%W[git config --global protocol.file.allow always]) + build_git "submodule", "1.0" build_git "has_submodule", "1.0" do |s| s.add_dependency "submodule" end - sys_exec "git submodule add #{lib_path("submodule-1.0")} submodule-1.0", :dir => lib_path("has_submodule-1.0") - sys_exec "git commit -m \"submodulator\"", :dir => lib_path("has_submodule-1.0") + sys_exec "git submodule add #{lib_path("submodule-1.0")} submodule-1.0", dir: lib_path("has_submodule-1.0") + sys_exec "git commit -m \"submodulator\"", dir: lib_path("has_submodule-1.0") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -909,11 +956,14 @@ RSpec.describe "bundle install with git sources" do end it "does not warn when deiniting submodules" do + # CVE-2022-39253: https://lore.kernel.org/lkml/xmqq4jw1uku5.fsf@gitster.g/ + system(*%W[git config --global protocol.file.allow always]) + build_git "submodule", "1.0" build_git "has_submodule", "1.0" - sys_exec "git submodule add #{lib_path("submodule-1.0")} submodule-1.0", :dir => lib_path("has_submodule-1.0") - sys_exec "git commit -m \"submodulator\"", :dir => lib_path("has_submodule-1.0") + sys_exec "git submodule add #{lib_path("submodule-1.0")} submodule-1.0", dir: lib_path("has_submodule-1.0") + sys_exec "git commit -m \"submodulator\"", dir: lib_path("has_submodule-1.0") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -987,7 +1037,7 @@ RSpec.describe "bundle install with git sources" do FileUtils.mkdir_p(default_bundle_path) FileUtils.touch(default_bundle_path("bundler")) - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{lib_path("foo-1.0")}" G @@ -999,11 +1049,11 @@ RSpec.describe "bundle install with git sources" do end it "does not duplicate git gem sources" do - build_lib "foo", :path => lib_path("nested/foo") - build_lib "bar", :path => lib_path("nested/bar") + build_lib "foo", path: lib_path("nested/foo") + build_lib "bar", path: lib_path("nested/bar") - build_git "foo", :path => lib_path("nested") - build_git "bar", :path => lib_path("nested") + build_git "foo", path: lib_path("nested") + build_git "bar", path: lib_path("nested") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -1016,11 +1066,11 @@ RSpec.describe "bundle install with git sources" do describe "switching sources" do it "doesn't explode when switching Path to Git sources" do - build_gem "foo", "1.0", :to_system => true do |s| + build_gem "foo", "1.0", to_system: true do |s| s.write "lib/foo.rb", "raise 'fail'" end - build_lib "foo", "1.0", :path => lib_path("bar/foo") - build_git "bar", "1.0", :path => lib_path("bar") do |s| + build_lib "foo", "1.0", path: lib_path("bar/foo") + build_git "bar", "1.0", path: lib_path("bar") do |s| s.add_dependency "foo" end @@ -1095,17 +1145,28 @@ RSpec.describe "bundle install with git sources" do G expect(out).to_not match(/Revision.*does not exist/) - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{file_uri_for(lib_path("foo-1.0"))}", :ref => "deadbeef" G expect(err).to include("Revision deadbeef does not exist in the repository") end + + it "gives a helpful error message when the remote branch no longer exists" do + build_git "foo" + + install_gemfile <<-G, env: { "LANG" => "en" }, raise_on_error: false + source "#{file_uri_for(gem_repo1)}" + gem "foo", :git => "#{file_uri_for(lib_path("foo-1.0"))}", :branch => "deadbeef" + G + + expect(err).to include("Revision deadbeef does not exist in the repository") + end end describe "bundle install with deployment mode configured and git sources" do it "works" do - build_git "valim", :path => lib_path("valim") + build_git "valim", path: lib_path("valim") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -1136,7 +1197,7 @@ RSpec.describe "bundle install with git sources" do end bundle :install, - :requires => [lib_path("install_hooks.rb")] + requires: [lib_path("install_hooks.rb")] expect(err_without_deprecations).to eq("Ran pre-install hook: foo-1.0") end @@ -1156,7 +1217,7 @@ RSpec.describe "bundle install with git sources" do end bundle :install, - :requires => [lib_path("install_hooks.rb")] + requires: [lib_path("install_hooks.rb")] expect(err_without_deprecations).to eq("Ran post-install hook: foo-1.0") end @@ -1175,7 +1236,7 @@ RSpec.describe "bundle install with git sources" do H end - bundle :install, :requires => [lib_path("install_hooks.rb")], :raise_on_error => false + bundle :install, requires: [lib_path("install_hooks.rb")], raise_on_error: false expect(err).to include("failed for foo-1.0") end end @@ -1187,7 +1248,7 @@ RSpec.describe "bundle install with git sources" do s.extensions << "Rakefile" s.write "Rakefile", <<-RUBY task :default do - path = File.expand_path("../lib", __FILE__) + path = File.expand_path("lib", __dir__) FileUtils.mkdir_p(path) File.open("\#{path}/foo.rb", "w") do |f| f.puts "FOO = 'YES'" @@ -1213,8 +1274,8 @@ RSpec.describe "bundle install with git sources" do expect(out).to include(Pathname.glob(default_bundle_path("bundler/gems/extensions/**/foo-1.0-*")).first.to_s) end - it "does not use old extension after ref changes", :ruby_repo do - git_reader = build_git "foo", :no_default => true do |s| + it "does not use old extension after ref changes" do + git_reader = build_git "foo", no_default: true do |s| s.extensions = ["ext/extconf.rb"] s.write "ext/extconf.rb", <<-RUBY require "mkmf" @@ -1231,7 +1292,7 @@ RSpec.describe "bundle install with git sources" do void Init_foo() { rb_define_global_function("foo", &foo, 0); } C end - sys_exec("git commit -m \"commit for iteration #{i}\" ext/foo.c", :dir => git_reader.path) + sys_exec("git commit -m \"commit for iteration #{i}\" ext/foo.c", dir: git_reader.path) git_commit_sha = git_reader.ref_for("HEAD") @@ -1260,7 +1321,7 @@ RSpec.describe "bundle install with git sources" do RUBY end - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{lib_path("foo-1.0")}" G @@ -1280,7 +1341,7 @@ In Gemfile: s.extensions << "Rakefile" s.write "Rakefile", <<-RUBY task :default do - path = File.expand_path("../lib", __FILE__) + path = File.expand_path("lib", __dir__) FileUtils.mkdir_p(path) cur_time = Time.now.to_f.to_s File.open("\#{path}/foo.rb", "w") do |f| @@ -1321,7 +1382,7 @@ In Gemfile: s.extensions << "Rakefile" s.write "Rakefile", <<-RUBY task :default do - path = File.expand_path("../lib", __FILE__) + path = File.expand_path("lib", __dir__) FileUtils.mkdir_p(path) cur_time = Time.now.to_f.to_s File.open("\#{path}/foo.rb", "w") do |f| @@ -1364,7 +1425,7 @@ In Gemfile: s.extensions << "Rakefile" s.write "Rakefile", <<-RUBY task :default do - path = File.expand_path("../lib", __FILE__) + path = File.expand_path("lib", __dir__) FileUtils.mkdir_p(path) cur_time = Time.now.to_f.to_s File.open("\#{path}/foo.rb", "w") do |f| @@ -1386,7 +1447,7 @@ In Gemfile: installed_time = out - update_git("foo", :branch => "branch2") + update_git("foo", branch: "branch2") expect(installed_time).to match(/\A\d+\.\d+\z/) @@ -1436,7 +1497,42 @@ In Gemfile: end describe "without git installed" do - it "prints a better error message" do + it "prints a better error message when installing" do + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + + gem "rake", git: "https://github.com/ruby/rake" + G + + lockfile <<-L + GIT + remote: https://github.com/ruby/rake + revision: 5c60da8644a9e4f655e819252e3b6ca77f42b7af + specs: + rake (13.0.6) + + GEM + remote: https://rubygems.org/ + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + rake! + + BUNDLED WITH + #{Bundler::VERSION} + L + + with_path_as("") do + bundle "install", raise_on_error: false + end + expect(err). + to include("You need to install git to be able to use gems from git repositories. For help installing git, please refer to GitHub's tutorial at https://help.github.com/articles/set-up-git") + end + + it "prints a better error message when updating" do build_git "foo" install_gemfile <<-G @@ -1447,7 +1543,7 @@ In Gemfile: G with_path_as("") do - bundle "update", :all => true, :raise_on_error => false + bundle "update", all: true, raise_on_error: false end expect(err). to include("You need to install git to be able to use gems from git repositories. For help installing git, please refer to GitHub's tutorial at https://help.github.com/articles/set-up-git") @@ -1466,7 +1562,7 @@ In Gemfile: bundle :cache simulate_new_machine - bundle "install", :env => { "PATH" => "" } + bundle "install", env: { "PATH" => "" } expect(out).to_not include("You need to install git to be able to use gems from git repositories.") end end @@ -1484,11 +1580,11 @@ In Gemfile: end it "installs successfully" do - build_git "foo", "1.0", :path => lib_path("foo") + build_git "foo", "1.0", path: lib_path("foo") gemfile <<-G source "#{file_uri_for(gem_repo1)}" - gem "foo", :git => "#{lib_path("foo")}", :branch => "master" + gem "foo", :git => "#{lib_path("foo")}", :branch => "main" G bundle :install @@ -1502,7 +1598,7 @@ In Gemfile: let(:credentials) { "user1:password1" } it "does not display the password" do - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" git "https://#{credentials}@github.com/company/private-repo" do gem "foo" @@ -1518,7 +1614,7 @@ In Gemfile: let(:credentials) { "oauth_token" } it "displays the oauth scheme but not the oauth token" do - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" git "https://#{credentials}:x-oauth-basic@github.com/company/private-repo" do gem "foo" diff --git a/spec/bundler/install/gemfile/groups_spec.rb b/spec/bundler/install/gemfile/groups_spec.rb index c92b5dcc57..f7907a9cad 100644 --- a/spec/bundler/install/gemfile/groups_spec.rb +++ b/spec/bundler/install/gemfile/groups_spec.rb @@ -88,41 +88,32 @@ RSpec.describe "bundle install with groups" do it "installs gems in the default group" do bundle "config set --local without emo" bundle :install - expect(the_bundle).to include_gems "rack 1.0.0", :groups => [:default] + expect(the_bundle).to include_gems "rack 1.0.0", groups: [:default] end - it "respects global `without` configuration, and saves it locally", :bundler => "< 3" do - bundle "config set without emo" - bundle :install - expect(the_bundle).to include_gems "rack 1.0.0", :groups => [:default] - bundle "config list" - expect(out).to include("Set for your local app (#{bundled_app(".bundle/config")}): [:emo]") - expect(out).to include("Set for the current user (#{home(".bundle/config")}): [:emo]") - end - - it "respects global `without` configuration, but does not save it locally", :bundler => "3" do - bundle "config set without emo" + it "respects global `without` configuration, but does not save it locally" do + bundle "config set --global without emo" bundle :install - expect(the_bundle).to include_gems "rack 1.0.0", :groups => [:default] + expect(the_bundle).to include_gems "rack 1.0.0", groups: [:default] bundle "config list" expect(out).not_to include("Set for your local app (#{bundled_app(".bundle/config")}): [:emo]") expect(out).to include("Set for the current user (#{home(".bundle/config")}): [:emo]") end - it "allows running application where groups where configured by a different user", :bundler => "< 3" do + it "allows running application where groups where configured by a different user", bundler: "< 3" do bundle "config set without emo" bundle :install - bundle "exec ruby -e 'puts 42'", :env => { "BUNDLE_USER_HOME" => tmp("new_home").to_s } + bundle "exec ruby -e 'puts 42'", env: { "BUNDLE_USER_HOME" => tmp("new_home").to_s } expect(out).to include("42") end it "does not install gems from the excluded group" do bundle "config set --local without emo" bundle :install - expect(the_bundle).not_to include_gems "activesupport 2.3.5", :groups => [:default] + expect(the_bundle).not_to include_gems "activesupport 2.3.5", groups: [:default] end - it "remembers previous exclusion with `--without`", :bundler => "< 3" do + it "remembers previous exclusion with `--without`", bundler: "< 3" do bundle "install --without emo" expect(the_bundle).not_to include_gems "activesupport 2.3.5" bundle :install @@ -153,7 +144,7 @@ RSpec.describe "bundle install with groups" do bundle "config set --local without emo" bundle :install - expect(the_bundle).to include_gems "activesupport 2.3.2", :groups => [:default] + expect(the_bundle).to include_gems "activesupport 2.3.2", groups: [:default] end it "still works when BUNDLE_WITHOUT is set" do @@ -162,20 +153,20 @@ RSpec.describe "bundle install with groups" do bundle :install expect(out).not_to include("activesupport") - expect(the_bundle).to include_gems "rack 1.0.0", :groups => [:default] - expect(the_bundle).not_to include_gems "activesupport 2.3.5", :groups => [:default] + expect(the_bundle).to include_gems "rack 1.0.0", groups: [:default] + expect(the_bundle).not_to include_gems "activesupport 2.3.5", groups: [:default] ENV["BUNDLE_WITHOUT"] = nil end - it "clears --without when passed an empty list", :bundler => "< 3" do + it "clears --without when passed an empty list", bundler: "< 3" do bundle "install --without emo" bundle "install --without ''" expect(the_bundle).to include_gems "activesupport 2.3.5" end - it "doesn't clear without when nothing is passed", :bundler => "< 3" do + it "doesn't clear without when nothing is passed", bundler: "< 3" do bundle "install --without emo" bundle :install @@ -193,7 +184,7 @@ RSpec.describe "bundle install with groups" do expect(the_bundle).to include_gems "thin 1.0" end - it "installs gems from the previously requested group", :bundler => "< 3" do + it "installs gems from the previously requested group", bundler: "< 3" do bundle "install --with debugging" expect(the_bundle).to include_gems "thin 1.0" bundle :install @@ -207,26 +198,26 @@ RSpec.describe "bundle install with groups" do ENV["BUNDLE_WITH"] = nil end - it "clears --with when passed an empty list", :bundler => "< 3" do + it "clears --with when passed an empty list", bundler: "< 3" do bundle "install --with debugging" bundle "install --with ''" expect(the_bundle).not_to include_gems "thin 1.0" end - it "removes groups from without when passed at --with", :bundler => "< 3" do + it "removes groups from without when passed at --with", bundler: "< 3" do bundle "config set --local without emo" bundle "install --with emo" expect(the_bundle).to include_gems "activesupport 2.3.5" end - it "removes groups from with when passed at --without", :bundler => "< 3" do + it "removes groups from with when passed at --without", bundler: "< 3" do bundle "config set --local with debugging" - bundle "install --without debugging", :raise_on_error => false + bundle "install --without debugging", raise_on_error: false expect(the_bundle).not_to include_gem "thin 1.0" end - it "errors out when passing a group to with and without via CLI flags", :bundler => "< 3" do - bundle "install --with emo debugging --without emo", :raise_on_error => false + it "errors out when passing a group to with and without via CLI flags", bundler: "< 3" do + bundle "install --with emo debugging --without emo", raise_on_error: false expect(last_command).to be_failure expect(err).to include("The offending groups are: emo") end @@ -244,7 +235,7 @@ RSpec.describe "bundle install with groups" do expect(the_bundle).to include_gem "thin 1.0" end - it "can add and remove a group at the same time", :bundler => "< 3" do + it "can add and remove a group at the same time", bundler: "< 3" do bundle "install --with debugging --without emo" expect(the_bundle).to include_gems "thin 1.0" expect(the_bundle).not_to include_gems "activesupport 2.3.5" @@ -358,7 +349,7 @@ RSpec.describe "bundle install with groups" do G ruby <<-R - require "#{entrypoint}" + require "bundler" Bundler.setup :default Bundler.require :default puts RACK @@ -405,7 +396,7 @@ RSpec.describe "bundle install with groups" do it "does not hit the remote a second time" do FileUtils.rm_rf gem_repo2 bundle "config set --local without rack" - bundle :install, :verbose => true + bundle :install, verbose: true expect(last_command.stdboth).not_to match(/fetching/i) end end diff --git a/spec/bundler/install/gemfile/install_if_spec.rb b/spec/bundler/install/gemfile/install_if_spec.rb index 786e0e9258..c7640d07e1 100644 --- a/spec/bundler/install/gemfile/install_if_spec.rb +++ b/spec/bundler/install/gemfile/install_if_spec.rb @@ -18,7 +18,14 @@ RSpec.describe "bundle install with install_if conditionals" do expect(the_bundle).not_to include_gems("thin") expect(the_bundle).not_to include_gems("foo") - lockfile_should_be <<-L + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo1, "activesupport", "2.3.5" + c.no_checksum "foo", "1.0" + c.checksum gem_repo1, "rack", "1.0.0" + c.no_checksum "thin", "1.0" + end + + expect(lockfile).to eq <<~L GEM remote: #{file_uri_for(gem_repo1)}/ specs: @@ -36,7 +43,7 @@ RSpec.describe "bundle install with install_if conditionals" do foo rack thin - + #{checksums} BUNDLED WITH #{Bundler::VERSION} L diff --git a/spec/bundler/install/gemfile/lockfile_spec.rb b/spec/bundler/install/gemfile/lockfile_spec.rb index 313e99d0b8..4601d3e2a8 100644 --- a/spec/bundler/install/gemfile/lockfile_spec.rb +++ b/spec/bundler/install/gemfile/lockfile_spec.rb @@ -11,6 +11,11 @@ RSpec.describe "bundle install with a lockfile present" do install_gemfile(gf) end + it "touches the lockfile on install even when nothing has changed" do + subject + expect { bundle :install }.to change { bundled_app_lock.mtime } + end + context "gemfile evaluation" do let(:gf) { super() + "\n\n File.open('evals', 'a') {|f| f << %(1\n) } unless ENV['BUNDLER_SPEC_NO_APPEND']" } diff --git a/spec/bundler/install/gemfile/path_spec.rb b/spec/bundler/install/gemfile/path_spec.rb index 566fdcf65a..a57b7ee560 100644 --- a/spec/bundler/install/gemfile/path_spec.rb +++ b/spec/bundler/install/gemfile/path_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true RSpec.describe "bundle install with explicit source paths" do - it "fetches gems with a global path source", :bundler => "< 3" do + it "fetches gems with a global path source", bundler: "< 3" do build_lib "foo" install_gemfile <<-G @@ -69,7 +69,7 @@ RSpec.describe "bundle install with explicit source paths" do username = "some_unexisting_user" relative_path = lib_path("foo-1.0").relative_path_from(Pathname.new("/home/#{username}").expand_path) - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" gem 'foo', :path => "~#{username}/#{relative_path}" G @@ -78,27 +78,30 @@ RSpec.describe "bundle install with explicit source paths" do end it "expands paths relative to Bundler.root" do - build_lib "foo", :path => bundled_app("foo-1.0") + build_lib "foo", path: bundled_app("foo-1.0") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem 'foo', :path => "./foo-1.0" G - expect(the_bundle).to include_gems("foo 1.0", :dir => bundled_app("subdir").mkpath) + expect(the_bundle).to include_gems("foo 1.0", dir: bundled_app("subdir").mkpath) end it "sorts paths consistently on install and update when they start with ./" do - build_lib "demo", :path => lib_path("demo") - build_lib "aaa", :path => lib_path("demo/aaa") + build_lib "demo", path: lib_path("demo") + build_lib "aaa", path: lib_path("demo/aaa") - gemfile = <<-G + gemfile lib_path("demo/Gemfile"), <<-G source "#{file_uri_for(gem_repo1)}" gemspec gem "aaa", :path => "./aaa" G - File.open(lib_path("demo/Gemfile"), "w") {|f| f.puts gemfile } + checksums = checksums_section_when_existing do |c| + c.no_checksum "aaa", "1.0" + c.no_checksum "demo", "1.0" + end lockfile = <<~L PATH @@ -121,23 +124,23 @@ RSpec.describe "bundle install with explicit source paths" do DEPENDENCIES aaa! demo! - + #{checksums} BUNDLED WITH #{Bundler::VERSION} L - bundle :install, :dir => lib_path("demo") - expect(lib_path("demo/Gemfile.lock")).to have_lockfile(lockfile) - bundle :update, :all => true, :dir => lib_path("demo") - expect(lib_path("demo/Gemfile.lock")).to have_lockfile(lockfile) + bundle :install, dir: lib_path("demo") + expect(lib_path("demo/Gemfile.lock")).to read_as(lockfile) + bundle :update, all: true, dir: lib_path("demo") + expect(lib_path("demo/Gemfile.lock")).to read_as(lockfile) end it "expands paths when comparing locked paths to Gemfile paths" do - build_lib "foo", :path => bundled_app("foo-1.0") + build_lib "foo", path: bundled_app("foo-1.0") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" - gem 'foo', :path => File.expand_path("../foo-1.0", __FILE__) + gem 'foo', :path => File.expand_path("foo-1.0", __dir__) G bundle "config set --local frozen true" @@ -147,11 +150,11 @@ RSpec.describe "bundle install with explicit source paths" do it "installs dependencies from the path even if a newer gem is available elsewhere" do system_gems "rack-1.0.0" - build_lib "rack", "1.0", :path => lib_path("nested/bar") do |s| + build_lib "rack", "1.0", path: lib_path("nested/bar") do |s| s.write "lib/rack.rb", "puts 'WIN OVERRIDE'" end - build_lib "foo", :path => lib_path("nested") do |s| + build_lib "foo", path: lib_path("nested") do |s| s.add_dependency "rack", "= 1.0" end @@ -165,15 +168,15 @@ RSpec.describe "bundle install with explicit source paths" do end it "works" do - build_gem "foo", "1.0.0", :to_system => true do |s| + build_gem "foo", "1.0.0", to_system: true do |s| s.write "lib/foo.rb", "puts 'FAIL'" end - build_lib "omg", "1.0", :path => lib_path("omg") do |s| + build_lib "omg", "1.0", path: lib_path("omg") do |s| s.add_dependency "foo" end - build_lib "foo", "1.0.0", :path => lib_path("omg/foo") + build_lib "foo", "1.0.0", path: lib_path("omg/foo") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -183,15 +186,81 @@ RSpec.describe "bundle install with explicit source paths" do expect(the_bundle).to include_gems "foo 1.0" end + it "works when using prereleases of 0.0.0" do + build_lib "foo", "0.0.0.dev", path: lib_path("foo") + + gemfile <<~G + source "#{file_uri_for(gem_repo1)}" + gem "foo", :path => "#{lib_path("foo")}" + G + + lockfile <<~L + PATH + remote: #{lib_path("foo")} + specs: + foo (0.0.0.dev) + + GEM + remote: #{file_uri_for(gem_repo1)}/ + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo! + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle :install + + expect(the_bundle).to include_gems "foo 0.0.0.dev" + end + + it "works when using uppercase prereleases of 0.0.0" do + build_lib "foo", "0.0.0.SNAPSHOT", path: lib_path("foo") + + gemfile <<~G + source "#{file_uri_for(gem_repo1)}" + gem "foo", :path => "#{lib_path("foo")}" + G + + lockfile <<~L + PATH + remote: #{lib_path("foo")} + specs: + foo (0.0.0.SNAPSHOT) + + GEM + remote: #{file_uri_for(gem_repo1)}/ + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo! + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle :install + + expect(the_bundle).to include_gems "foo 0.0.0.SNAPSHOT" + end + it "handles downgrades" do - build_lib "omg", "2.0", :path => lib_path("omg") + build_lib "omg", "2.0", path: lib_path("omg") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "omg", :path => "#{lib_path("omg")}" G - build_lib "omg", "1.0", :path => lib_path("omg") + build_lib "omg", "1.0", path: lib_path("omg") bundle :install @@ -199,7 +268,7 @@ RSpec.describe "bundle install with explicit source paths" do end it "prefers gemspecs closer to the path root" do - build_lib "premailer", "1.0.0", :path => lib_path("premailer") do |s| + build_lib "premailer", "1.0.0", path: lib_path("premailer") do |s| s.write "gemfiles/ruby187.gemspec", <<-G Gem::Specification.new do |s| s.name = 'premailer' @@ -232,7 +301,7 @@ RSpec.describe "bundle install with explicit source paths" do G end - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" gem "foo", :path => "#{lib_path("foo-1.0")}" G @@ -244,24 +313,78 @@ RSpec.describe "bundle install with explicit source paths" do end it "supports gemspec syntax" do - build_lib "foo", "1.0", :path => lib_path("foo") do |s| + build_lib "foo", "1.0", path: lib_path("foo") do |s| s.add_dependency "rack", "1.0" end - gemfile = <<-G + gemfile lib_path("foo/Gemfile"), <<-G source "#{file_uri_for(gem_repo1)}" gemspec G - File.open(lib_path("foo/Gemfile"), "w") {|f| f.puts gemfile } + bundle "install", dir: lib_path("foo") + expect(the_bundle).to include_gems "foo 1.0", dir: lib_path("foo") + expect(the_bundle).to include_gems "rack 1.0", dir: lib_path("foo") + end + + it "does not unlock dependencies of path sources" do + build_repo4 do + build_gem "graphql", "2.0.15" + build_gem "graphql", "2.0.16" + end + + build_lib "foo", "0.1.0", path: lib_path("foo") do |s| + s.add_dependency "graphql", "~> 2.0" + end + + gemfile_path = lib_path("foo/Gemfile") + + gemfile gemfile_path, <<-G + source "#{file_uri_for(gem_repo4)}" + gemspec + G + + lockfile_path = lib_path("foo/Gemfile.lock") - bundle "install", :dir => lib_path("foo") - expect(the_bundle).to include_gems "foo 1.0", :dir => lib_path("foo") - expect(the_bundle).to include_gems "rack 1.0", :dir => lib_path("foo") + checksums = checksums_section_when_existing do |c| + c.no_checksum "foo", "0.1.0" + c.checksum gem_repo4, "graphql", "2.0.15" + end + + original_lockfile = <<~L + PATH + remote: . + specs: + foo (0.1.0) + graphql (~> 2.0) + + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + graphql (2.0.15) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + + lockfile lockfile_path, original_lockfile + + build_lib "foo", "0.1.1", path: lib_path("foo") do |s| + s.add_dependency "graphql", "~> 2.0" + end + + bundle "install", dir: lib_path("foo") + expect(lockfile_path).to read_as(original_lockfile.gsub("foo (0.1.0)", "foo (0.1.1)")) end it "supports gemspec syntax with an alternative path" do - build_lib "foo", "1.0", :path => lib_path("foo") do |s| + build_lib "foo", "1.0", path: lib_path("foo") do |s| s.add_dependency "rack", "1.0" end @@ -275,48 +398,48 @@ RSpec.describe "bundle install with explicit source paths" do end it "doesn't automatically unlock dependencies when using the gemspec syntax" do - build_lib "foo", "1.0", :path => lib_path("foo") do |s| + build_lib "foo", "1.0", path: lib_path("foo") do |s| s.add_dependency "rack", ">= 1.0" end - install_gemfile lib_path("foo/Gemfile"), <<-G, :dir => lib_path("foo") + install_gemfile lib_path("foo/Gemfile"), <<-G, dir: lib_path("foo") source "#{file_uri_for(gem_repo1)}" gemspec G - build_gem "rack", "1.0.1", :to_system => true + build_gem "rack", "1.0.1", to_system: true - bundle "install", :dir => lib_path("foo") + bundle "install", dir: lib_path("foo") - expect(the_bundle).to include_gems "foo 1.0", :dir => lib_path("foo") - expect(the_bundle).to include_gems "rack 1.0", :dir => lib_path("foo") + expect(the_bundle).to include_gems "foo 1.0", dir: lib_path("foo") + expect(the_bundle).to include_gems "rack 1.0", dir: lib_path("foo") end it "doesn't automatically unlock dependencies when using the gemspec syntax and the gem has development dependencies" do - build_lib "foo", "1.0", :path => lib_path("foo") do |s| + build_lib "foo", "1.0", path: lib_path("foo") do |s| s.add_dependency "rack", ">= 1.0" s.add_development_dependency "activesupport" end - install_gemfile lib_path("foo/Gemfile"), <<-G, :dir => lib_path("foo") + install_gemfile lib_path("foo/Gemfile"), <<-G, dir: lib_path("foo") source "#{file_uri_for(gem_repo1)}" gemspec G - build_gem "rack", "1.0.1", :to_system => true + build_gem "rack", "1.0.1", to_system: true - bundle "install", :dir => lib_path("foo") + bundle "install", dir: lib_path("foo") - expect(the_bundle).to include_gems "foo 1.0", :dir => lib_path("foo") - expect(the_bundle).to include_gems "rack 1.0", :dir => lib_path("foo") + expect(the_bundle).to include_gems "foo 1.0", dir: lib_path("foo") + expect(the_bundle).to include_gems "rack 1.0", dir: lib_path("foo") end it "raises if there are multiple gemspecs" do - build_lib "foo", "1.0", :path => lib_path("foo") do |s| + build_lib "foo", "1.0", path: lib_path("foo") do |s| s.write "bar.gemspec", build_spec("bar", "1.0").first.to_ruby end - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" gemspec :path => "#{lib_path("foo")}" G @@ -326,7 +449,7 @@ RSpec.describe "bundle install with explicit source paths" do end it "allows :name to be specified to resolve ambiguity" do - build_lib "foo", "1.0", :path => lib_path("foo") do |s| + build_lib "foo", "1.0", path: lib_path("foo") do |s| s.write "bar.gemspec" end @@ -343,7 +466,7 @@ RSpec.describe "bundle install with explicit source paths" do s.executables = "foobar" end - install_gemfile <<-G, :verbose => true + install_gemfile <<-G, verbose: true source "#{file_uri_for(gem_repo1)}" path "#{lib_path("foo-1.0")}" do gem 'foo' @@ -397,9 +520,9 @@ RSpec.describe "bundle install with explicit source paths" do end it "keeps source pinning" do - build_lib "foo", "1.0", :path => lib_path("foo") - build_lib "omg", "1.0", :path => lib_path("omg") - build_lib "foo", "1.0", :path => lib_path("omg/foo") do |s| + build_lib "foo", "1.0", path: lib_path("foo") + build_lib "omg", "1.0", path: lib_path("omg") + build_lib "foo", "1.0", path: lib_path("omg/foo") do |s| s.write "lib/foo.rb", "puts 'FAIL'" end @@ -413,7 +536,7 @@ RSpec.describe "bundle install with explicit source paths" do end it "works when the path does not have a gemspec" do - build_lib "foo", :gemspec => false + build_lib "foo", gemspec: false gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -451,17 +574,17 @@ RSpec.describe "bundle install with explicit source paths" do gem 'net-ssh', '1.0' G - bundle :check, :env => { "DEBUG" => "1" } + bundle :check, env: { "DEBUG" => "1" } expect(out).to match(/using resolution from the lockfile/) expect(the_bundle).to include_gems "rack-obama 1.0", "net-ssh 1.0" end it "source path gems w/deps don't re-resolve without changes" do - build_lib "rack-obama", "1.0", :path => lib_path("omg") do |s| + build_lib "rack-obama", "1.0", path: lib_path("omg") do |s| s.add_dependency "yard" end - build_lib "net-ssh", "1.0", :path => lib_path("omg") do |s| + build_lib "net-ssh", "1.0", path: lib_path("omg") do |s| s.add_dependency "yard" end @@ -471,7 +594,7 @@ RSpec.describe "bundle install with explicit source paths" do gem 'net-ssh', :path => "#{lib_path("omg")}" G - bundle :check, :env => { "DEBUG" => "1" } + bundle :check, env: { "DEBUG" => "1" } expect(out).to match(/using resolution from the lockfile/) expect(the_bundle).to include_gems "rack-obama 1.0", "net-ssh 1.0" end @@ -493,10 +616,10 @@ RSpec.describe "bundle install with explicit source paths" do describe "when the gem version in the path is updated" do before :each do - build_lib "foo", "1.0", :path => lib_path("foo") do |s| + build_lib "foo", "1.0", path: lib_path("foo") do |s| s.add_dependency "bar" end - build_lib "bar", "1.0", :path => lib_path("foo/bar") + build_lib "bar", "1.0", path: lib_path("foo/bar") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -505,7 +628,7 @@ RSpec.describe "bundle install with explicit source paths" do end it "unlocks all gems when the top level gem is updated" do - build_lib "foo", "2.0", :path => lib_path("foo") do |s| + build_lib "foo", "2.0", path: lib_path("foo") do |s| s.add_dependency "bar" end @@ -515,7 +638,7 @@ RSpec.describe "bundle install with explicit source paths" do end it "unlocks all gems when a child dependency gem is updated" do - build_lib "bar", "2.0", :path => lib_path("foo/bar") + build_lib "bar", "2.0", path: lib_path("foo/bar") bundle "install" @@ -525,7 +648,7 @@ RSpec.describe "bundle install with explicit source paths" do describe "when dependencies in the path are updated" do before :each do - build_lib "foo", "1.0", :path => lib_path("foo") + build_lib "foo", "1.0", path: lib_path("foo") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -534,7 +657,7 @@ RSpec.describe "bundle install with explicit source paths" do end it "gets dependencies that are updated in the path" do - build_lib "foo", "1.0", :path => lib_path("foo") do |s| + build_lib "foo", "1.0", path: lib_path("foo") do |s| s.add_dependency "rack" end @@ -544,7 +667,7 @@ RSpec.describe "bundle install with explicit source paths" do end it "keeps using the same version if it's compatible" do - build_lib "foo", "1.0", :path => lib_path("foo") do |s| + build_lib "foo", "1.0", path: lib_path("foo") do |s| s.add_dependency "rack", "0.9.1" end @@ -552,7 +675,12 @@ RSpec.describe "bundle install with explicit source paths" do expect(the_bundle).to include_gems "rack 0.9.1" - lockfile_should_be <<-G + checksums = checksums_section_when_existing do |c| + c.no_checksum "foo", "1.0" + c.checksum gem_repo1, "rack", "0.9.1" + end + + expect(lockfile).to eq <<~G PATH remote: #{lib_path("foo")} specs: @@ -569,18 +697,18 @@ RSpec.describe "bundle install with explicit source paths" do DEPENDENCIES foo! - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G - build_lib "foo", "1.0", :path => lib_path("foo") do |s| + build_lib "foo", "1.0", path: lib_path("foo") do |s| s.add_dependency "rack" end bundle "install" - lockfile_should_be <<-G + expect(lockfile).to eq <<~G PATH remote: #{lib_path("foo")} specs: @@ -597,7 +725,7 @@ RSpec.describe "bundle install with explicit source paths" do DEPENDENCIES foo! - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G @@ -606,7 +734,7 @@ RSpec.describe "bundle install with explicit source paths" do end it "keeps using the same version even when another dependency is added" do - build_lib "foo", "1.0", :path => lib_path("foo") do |s| + build_lib "foo", "1.0", path: lib_path("foo") do |s| s.add_dependency "rack", "0.9.1" end @@ -614,7 +742,12 @@ RSpec.describe "bundle install with explicit source paths" do expect(the_bundle).to include_gems "rack 0.9.1" - lockfile_should_be <<-G + checksums = checksums_section_when_existing do |c| + c.no_checksum "foo", "1.0" + c.checksum gem_repo1, "rack", "0.9.1" + end + + expect(lockfile).to eq <<~G PATH remote: #{lib_path("foo")} specs: @@ -631,53 +764,107 @@ RSpec.describe "bundle install with explicit source paths" do DEPENDENCIES foo! - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G - build_lib "foo", "1.0", :path => lib_path("foo") do |s| + build_lib "foo", "1.0", path: lib_path("foo") do |s| s.add_dependency "rack" - s.add_dependency "rake", "13.0.1" + s.add_dependency "rake", rake_version end bundle "install" - lockfile_should_be <<-G + checksums.checksum gem_repo1, "rake", rake_version + + expect(lockfile).to eq <<~G PATH remote: #{lib_path("foo")} specs: foo (1.0) rack - rake (= 13.0.1) + rake (= #{rake_version}) GEM remote: #{file_uri_for(gem_repo1)}/ specs: rack (0.9.1) - rake (13.0.1) + rake (#{rake_version}) PLATFORMS #{lockfile_platforms} DEPENDENCIES foo! - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G expect(the_bundle).to include_gems "rack 0.9.1" end + + it "does not remove existing ruby platform" do + build_lib "foo", "1.0", path: lib_path("foo") do |s| + s.add_dependency "rack", "0.9.1" + end + + checksums = checksums_section_when_existing do |c| + c.no_checksum "foo", "1.0" + end + + lockfile <<~L + PATH + remote: #{lib_path("foo")} + specs: + foo (1.0) + + PLATFORMS + #{lockfile_platforms("ruby")} + + DEPENDENCIES + foo! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "lock" + + checksums.no_checksum "rack", "0.9.1" + + expect(lockfile).to eq <<~G + PATH + remote: #{lib_path("foo")} + specs: + foo (1.0) + rack (= 0.9.1) + + GEM + remote: #{file_uri_for(gem_repo1)}/ + specs: + rack (0.9.1) + + PLATFORMS + #{lockfile_platforms("ruby")} + + DEPENDENCIES + foo! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + G + end end describe "switching sources" do it "doesn't switch pinned git sources to rubygems when pinning the parent gem to a path source" do - build_gem "foo", "1.0", :to_system => true do |s| + build_gem "foo", "1.0", to_system: true do |s| s.write "lib/foo.rb", "raise 'fail'" end - build_lib "foo", "1.0", :path => lib_path("bar/foo") - build_git "bar", "1.0", :path => lib_path("bar") do |s| + build_lib "foo", "1.0", path: lib_path("bar/foo") + build_git "bar", "1.0", path: lib_path("bar") do |s| s.add_dependency "foo" end @@ -695,8 +882,8 @@ RSpec.describe "bundle install with explicit source paths" do end it "switches the source when the gem existed in rubygems and the path was already being used for another gem" do - build_lib "foo", "1.0", :path => lib_path("foo") - build_gem "bar", "1.0", :to_bundle => true do |s| + build_lib "foo", "1.0", path: lib_path("foo") + build_gem "bar", "1.0", to_bundle: true do |s| s.write "lib/bar.rb", "raise 'fail'" end @@ -708,7 +895,7 @@ RSpec.describe "bundle install with explicit source paths" do end G - build_lib "bar", "1.0", :path => lib_path("foo/bar") + build_lib "bar", "1.0", path: lib_path("foo/bar") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -724,19 +911,17 @@ RSpec.describe "bundle install with explicit source paths" do describe "when there are both a gemspec and remote gems" do it "doesn't query rubygems for local gemspec name" do - build_lib "private_lib", "2.2", :path => lib_path("private_lib") - gemfile = <<-G + build_lib "private_lib", "2.2", path: lib_path("private_lib") + gemfile lib_path("private_lib/Gemfile"), <<-G source "http://localgemserver.test" gemspec gem 'rack' G - File.open(lib_path("private_lib/Gemfile"), "w") {|f| f.puts gemfile } - - bundle :install, :env => { "DEBUG" => "1" }, :artifice => "endpoint", :dir => lib_path("private_lib") + bundle :install, env: { "DEBUG" => "1" }, artifice: "endpoint", dir: lib_path("private_lib") expect(out).to match(%r{^HTTP GET http://localgemserver\.test/api/v1/dependencies\?gems=rack$}) expect(out).not_to match(/^HTTP GET.*private_lib/) - expect(the_bundle).to include_gems "private_lib 2.2", :dir => lib_path("private_lib") - expect(the_bundle).to include_gems "rack 1.0", :dir => lib_path("private_lib") + expect(the_bundle).to include_gems "private_lib 2.2", dir: lib_path("private_lib") + expect(the_bundle).to include_gems "rack 1.0", dir: lib_path("private_lib") end end @@ -757,7 +942,7 @@ RSpec.describe "bundle install with explicit source paths" do end bundle :install, - :requires => [lib_path("install_hooks.rb")] + requires: [lib_path("install_hooks.rb")] expect(err_without_deprecations).to eq("Ran pre-install hook: foo-1.0") end @@ -777,7 +962,7 @@ RSpec.describe "bundle install with explicit source paths" do end bundle :install, - :requires => [lib_path("install_hooks.rb")] + requires: [lib_path("install_hooks.rb")] expect(err_without_deprecations).to eq("Ran post-install hook: foo-1.0") end @@ -796,7 +981,7 @@ RSpec.describe "bundle install with explicit source paths" do H end - bundle :install, :requires => [lib_path("install_hooks.rb")], :raise_on_error => false + bundle :install, requires: [lib_path("install_hooks.rb")], raise_on_error: false expect(err).to include("failed for foo-1.0") end diff --git a/spec/bundler/install/gemfile/platform_spec.rb b/spec/bundler/install/gemfile/platform_spec.rb index 7cd06b7e08..d90dacdc02 100644 --- a/spec/bundler/install/gemfile/platform_spec.rb +++ b/spec/bundler/install/gemfile/platform_spec.rb @@ -50,7 +50,7 @@ RSpec.describe "bundle install across platforms" do expect(the_bundle).to include_gems "platform_specific 1.0 JAVA" end - it "pulls the pure ruby version on jruby if the java platform is not present in the lockfile and bundler is run in frozen mode", :jruby do + it "pulls the pure ruby version on jruby if the java platform is not present in the lockfile and bundler is run in frozen mode", :jruby_only do lockfile <<-G GEM remote: #{file_uri_for(gem_repo1)} @@ -75,6 +75,81 @@ RSpec.describe "bundle install across platforms" do expect(the_bundle).to include_gems "platform_specific 1.0 RUBY" end + context "on universal Rubies" do + before do + build_repo4 do + build_gem "darwin_single_arch" do |s| + s.platform = "ruby" + s.write "lib/darwin_single_arch.rb", "DARWIN_SINGLE_ARCH = '1.0 RUBY'" + end + build_gem "darwin_single_arch" do |s| + s.platform = "arm64-darwin" + s.write "lib/darwin_single_arch.rb", "DARWIN_SINGLE_ARCH = '1.0 arm64-darwin'" + end + build_gem "darwin_single_arch" do |s| + s.platform = "x86_64-darwin" + s.write "lib/darwin_single_arch.rb", "DARWIN_SINGLE_ARCH = '1.0 x86_64-darwin'" + end + end + end + + it "pulls in the correct architecture gem" do + lockfile <<-G + GEM + remote: #{file_uri_for(gem_repo4)} + specs: + darwin_single_arch (1.0) + darwin_single_arch (1.0-arm64-darwin) + darwin_single_arch (1.0-x86_64-darwin) + + PLATFORMS + ruby + + DEPENDENCIES + darwin_single_arch + G + + simulate_platform "universal-darwin-21" + simulate_ruby_platform "universal.x86_64-darwin21" do + install_gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + + gem "darwin_single_arch" + G + + expect(the_bundle).to include_gems "darwin_single_arch 1.0 x86_64-darwin" + end + end + + it "pulls in the correct architecture gem on arm64e macOS Ruby" do + lockfile <<-G + GEM + remote: #{file_uri_for(gem_repo4)} + specs: + darwin_single_arch (1.0) + darwin_single_arch (1.0-arm64-darwin) + darwin_single_arch (1.0-x86_64-darwin) + + PLATFORMS + ruby + + DEPENDENCIES + darwin_single_arch + G + + simulate_platform "universal-darwin-21" + simulate_ruby_platform "universal.arm64e-darwin21" do + install_gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + + gem "darwin_single_arch" + G + + expect(the_bundle).to include_gems "darwin_single_arch 1.0 arm64-darwin" + end + end + end + it "works with gems that have different dependencies" do simulate_platform "java" install_gemfile <<-G @@ -86,13 +161,13 @@ RSpec.describe "bundle install across platforms" do expect(the_bundle).to include_gems "nokogiri 1.4.2 JAVA", "weakling 0.0.3" simulate_new_machine - - simulate_platform "ruby" + bundle "config set --local force_ruby_platform true" bundle "install" expect(the_bundle).to include_gems "nokogiri 1.4.2" expect(the_bundle).not_to include_gems "weakling" + simulate_new_machine simulate_platform "java" bundle "install" @@ -128,7 +203,16 @@ RSpec.describe "bundle install across platforms" do gem "pry" G - lockfile_should_be <<-L + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo4, "coderay", "1.1.2" + c.checksum gem_repo4, "empyrean", "0.1.0" + c.checksum gem_repo4, "ffi", "1.9.23", "java" + c.checksum gem_repo4, "method_source", "0.9.0" + c.checksum gem_repo4, "pry", "0.11.3", "java" + c.checksum gem_repo4, "spoon", "0.0.6" + end + + expect(lockfile).to eq <<~L GEM remote: #{file_uri_for(gem_repo4)}/ specs: @@ -149,14 +233,14 @@ RSpec.describe "bundle install across platforms" do DEPENDENCIES empyrean (= 0.1.0) pry - + #{checksums} BUNDLED WITH #{Bundler::VERSION} L bundle "lock --add-platform ruby" - good_lockfile = strip_whitespace(<<-L) + good_lockfile = <<~L GEM remote: #{file_uri_for(gem_repo4)}/ specs: @@ -181,14 +265,14 @@ RSpec.describe "bundle install across platforms" do DEPENDENCIES empyrean (= 0.1.0) pry - + #{checksums} BUNDLED WITH #{Bundler::VERSION} L - lockfile_should_be good_lockfile + expect(lockfile).to eq good_lockfile - bad_lockfile = strip_whitespace <<-L + bad_lockfile = <<~L GEM remote: #{file_uri_for(gem_repo4)}/ specs: @@ -214,31 +298,31 @@ RSpec.describe "bundle install across platforms" do DEPENDENCIES empyrean (= 0.1.0) pry - + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + 1.16.1 L aggregate_failures do lockfile bad_lockfile - bundle :install - lockfile_should_be good_lockfile + bundle :install, env: { "BUNDLER_VERSION" => Bundler::VERSION } + expect(lockfile).to eq good_lockfile lockfile bad_lockfile - bundle :update, :all => true - lockfile_should_be good_lockfile + bundle :update, all: true, env: { "BUNDLER_VERSION" => Bundler::VERSION } + expect(lockfile).to eq good_lockfile lockfile bad_lockfile - bundle "update ffi" - lockfile_should_be good_lockfile + bundle "update ffi", env: { "BUNDLER_VERSION" => Bundler::VERSION } + expect(lockfile).to eq good_lockfile lockfile bad_lockfile - bundle "update empyrean" - lockfile_should_be good_lockfile + bundle "update empyrean", env: { "BUNDLER_VERSION" => Bundler::VERSION } + expect(lockfile).to eq good_lockfile lockfile bad_lockfile - bundle :lock - lockfile_should_be good_lockfile + bundle :lock, env: { "BUNDLER_VERSION" => Bundler::VERSION } + expect(lockfile).to eq good_lockfile end end @@ -288,6 +372,11 @@ RSpec.describe "bundle install across platforms" do end it "keeps existing platforms when installing with force_ruby_platform" do + checksums = checksums_section do |c| + c.no_checksum "platform_specific", "1.0" + c.no_checksum "platform_specific", "1.0", "java" + end + lockfile <<-G GEM remote: #{file_uri_for(gem_repo1)}/ @@ -299,6 +388,7 @@ RSpec.describe "bundle install across platforms" do DEPENDENCIES platform_specific + #{checksums} G bundle "config set --local force_ruby_platform true" @@ -308,9 +398,11 @@ RSpec.describe "bundle install across platforms" do gem "platform_specific" G + checksums.checksum gem_repo1, "platform_specific", "1.0" + expect(the_bundle).to include_gem "platform_specific 1.0 RUBY" - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo1)}/ specs: @@ -323,7 +415,7 @@ RSpec.describe "bundle install across platforms" do DEPENDENCIES platform_specific - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G @@ -332,8 +424,6 @@ end RSpec.describe "bundle install with platform conditionals" do it "installs gems tagged w/ the current platforms" do - skip "platform issues" if Gem.win_platform? - install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -358,9 +448,50 @@ RSpec.describe "bundle install with platform conditionals" do expect(the_bundle).not_to include_gems "nokogiri 1.4.2" end - it "installs gems tagged w/ the current platforms inline" do - skip "platform issues" if Gem.win_platform? + it "installs gems tagged w/ another platform but also dependent on the current one transitively" do + build_repo4 do + build_gem "activesupport", "6.1.4.1" do |s| + s.add_dependency "tzinfo", "~> 2.0" + end + + build_gem "tzinfo", "2.0.4" + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + gem "activesupport" + + platforms :#{not_local_tag} do + gem "tzinfo", "~> 1.2" + end + G + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + activesupport (6.1.4.1) + tzinfo (~> 2.0) + tzinfo (2.0.4) + + PLATFORMS + #{local_platform} + + DEPENDENCIES + activesupport + tzinfo (~> 1.2) + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install --verbose" + + expect(the_bundle).to include_gems "tzinfo 2.0.4" + end + + it "installs gems tagged w/ the current platforms inline" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "nokogiri", :platforms => :#{local_tag} @@ -379,8 +510,6 @@ RSpec.describe "bundle install with platform conditionals" do end it "installs gems tagged w/ the current platform inline" do - skip "platform issues" if Gem.win_platform? - install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "nokogiri", :platform => :#{local_tag} @@ -410,7 +539,7 @@ RSpec.describe "bundle install with platform conditionals" do end it "does not attempt to install gems from :rbx when using --local" do - simulate_platform "ruby" + bundle "config set --local force_ruby_platform true" gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -422,12 +551,10 @@ RSpec.describe "bundle install with platform conditionals" do end it "does not attempt to install gems from other rubies when using --local" do - simulate_platform "ruby" - other_ruby_version_tag = RUBY_VERSION =~ /^1\.8/ ? :ruby_19 : :ruby_18 - + bundle "config set --local force_ruby_platform true" gemfile <<-G source "#{file_uri_for(gem_repo1)}" - gem "some_gem", platform: :#{other_ruby_version_tag} + gem "some_gem", platform: :ruby_22 G bundle "install --local" @@ -435,19 +562,19 @@ RSpec.describe "bundle install with platform conditionals" do end it "does not print a warning when a dependency is unused on a platform different from the current one" do - simulate_platform "ruby" + bundle "config set --local force_ruby_platform true" gemfile <<-G source "#{file_uri_for(gem_repo1)}" - gem "rack", :platform => [:mingw, :mswin, :x64_mingw, :jruby] + gem "rack", :platform => [:windows, :mswin, :mswin64, :mingw, :x64_mingw, :jruby] G bundle "install" expect(err).to be_empty - lockfile_should_be <<-L + expect(lockfile).to eq <<~L GEM remote: #{file_uri_for(gem_repo1)}/ specs: @@ -457,16 +584,36 @@ RSpec.describe "bundle install with platform conditionals" do DEPENDENCIES rack - + #{checksums_section_when_existing} BUNDLED WITH #{Bundler::VERSION} L end + + it "resolves fine when a dependency is unused on a platform different from the current one, but reintroduced transitively" do + bundle "config set --local force_ruby_platform true" + + build_repo4 do + build_gem "listen", "3.7.1" do |s| + s.add_dependency "ffi" + end + + build_gem "ffi", "1.15.5" + end + + install_gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "listen" + gem "ffi", :platform => :windows + G + expect(err).to be_empty + end end RSpec.describe "when a gem has no architecture" do it "still installs correctly" do - simulate_platform mswin + simulate_platform x86_mswin32 build_repo2 do # The rcov gem is platform mswin32, but has no arch @@ -482,7 +629,7 @@ RSpec.describe "when a gem has no architecture" do gem "rcov" G - bundle :install, :artifice => "windows", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } + bundle :install, artifice: "windows", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } expect(the_bundle).to include_gems "rcov 1.0.0" end end diff --git a/spec/bundler/install/gemfile/ruby_spec.rb b/spec/bundler/install/gemfile/ruby_spec.rb index fd4300c042..b64d633fd3 100644 --- a/spec/bundler/install/gemfile/ruby_spec.rb +++ b/spec/bundler/install/gemfile/ruby_spec.rb @@ -11,13 +11,13 @@ RSpec.describe "ruby requirement" do it "allows adding gems" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" - ruby "#{RUBY_VERSION}" + ruby "#{Gem.ruby_version}" gem "rack" G install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" - ruby "#{RUBY_VERSION}" + ruby "#{Gem.ruby_version}" gem "rack" gem "rack-obama" G @@ -28,7 +28,7 @@ RSpec.describe "ruby requirement" do it "allows removing the ruby version requirement" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" - ruby "~> #{RUBY_VERSION}" + ruby "~> #{Gem.ruby_version}" gem "rack" G @@ -46,18 +46,16 @@ RSpec.describe "ruby requirement" do it "allows changing the ruby version requirement to something compatible" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" - ruby ">= 1.0.0" + ruby ">= #{current_ruby_minor}" gem "rack" G allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) expect(locked_ruby_version).to eq(Bundler::RubyVersion.system) - simulate_ruby_version "5100" - install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" - ruby ">= 1.0.1" + ruby ">= #{Gem.ruby_version}" gem "rack" G @@ -72,25 +70,41 @@ RSpec.describe "ruby requirement" do gem "rack" G - allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) - expect(locked_ruby_version).to eq(Bundler::RubyVersion.system) + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo1)}/ + specs: + rack (1.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + rack + + RUBY VERSION + ruby 2.1.4p422 + + BUNDLED WITH + #{Bundler::VERSION} + L - simulate_ruby_version "5100" + allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" - ruby ">= 5000.0" + ruby ">= #{current_ruby_minor}" gem "rack" G expect(the_bundle).to include_gems "rack 1.0.0" - expect(locked_ruby_version.versions).to eq(["5100"]) + expect(locked_ruby_version).to eq(Bundler::RubyVersion.system) end it "allows requirements with trailing whitespace" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" - ruby "#{RUBY_VERSION}\\n \t\\n" + ruby "#{Gem.ruby_version}\\n \t\\n" gem "rack" G @@ -98,7 +112,7 @@ RSpec.describe "ruby requirement" do end it "fails gracefully with malformed requirements" do - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" ruby ">= 0", "-.\\0" gem "rack" @@ -106,4 +120,38 @@ RSpec.describe "ruby requirement" do expect(err).to include("There was an error parsing") # i.e. DSL error, not error template end + + it "allows picking up ruby version from a file" do + create_file ".ruby-version", Gem.ruby_version.to_s + + install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + ruby file: ".ruby-version" + gem "rack" + G + + expect(lockfile).to include("RUBY VERSION") + end + + it "reads the ruby version file from the right folder when nested Gemfiles are involved" do + create_file ".ruby-version", Gem.ruby_version.to_s + + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + ruby file: ".ruby-version" + gem "rack" + G + + nested_dir = bundled_app(".ruby-lsp") + + FileUtils.mkdir nested_dir + + create_file ".ruby-lsp/Gemfile", <<-G + eval_gemfile(File.expand_path("../Gemfile", __dir__)) + G + + bundle "install", dir: nested_dir + + expect(bundled_app(".ruby-lsp/Gemfile.lock").read).to include("RUBY VERSION") + end end diff --git a/spec/bundler/install/gemfile/sources_spec.rb b/spec/bundler/install/gemfile/sources_spec.rb index 8c225afb11..a5ba76f4d9 100644 --- a/spec/bundler/install/gemfile/sources_spec.rb +++ b/spec/bundler/install/gemfile/sources_spec.rb @@ -27,18 +27,72 @@ RSpec.describe "bundle install with gems on multiple sources" do G end - it "warns about ambiguous gems, but installs anyway, prioritizing sources last to first", :bundler => "< 3" do - bundle :install, :artifice => "compact_index" + it "refuses to install mismatched checksum because one gem has been tampered with", bundler: "< 3" do + lockfile <<~L + GEM + remote: https://gem.repo3/ + remote: https://gem.repo1/ + specs: + rack (1.0.0) - expect(err).to include("Warning: the gem 'rack' was found in multiple sources.") - expect(err).to include("Installed from: https://gem.repo1") - expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0", :source => "remote1") + PLATFORMS + #{local_platform} + + DEPENDENCIES + depends_on_rack! + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle :install, artifice: "compact_index", raise_on_error: false + + expect(exitstatus).to eq(37) + expect(err).to eq <<~E.strip + [DEPRECATED] Your Gemfile contains multiple global sources. Using `source` more than once without a block is a security risk, and may result in installing unexpected gems. To resolve this warning, use a block to indicate which gems should come from the secondary source. + Bundler found mismatched checksums. This is a potential security risk. + #{checksum_to_lock(gem_repo1, "rack", "1.0.0")} + from the API at https://gem.repo1/ + #{checksum_to_lock(gem_repo3, "rack", "1.0.0")} + from the API at https://gem.repo3/ + + Mismatched checksums each have an authoritative source: + 1. the API at https://gem.repo1/ + 2. the API at https://gem.repo3/ + You may need to alter your Gemfile sources to resolve this issue. + + To ignore checksum security warnings, disable checksum validation with + `bundle config set --local disable_checksum_validation true` + E end - it "fails", :bundler => "3" do - bundle :instal, :artifice => "compact_index", :raise_on_error => false - expect(err).to include("Each source after the first must include a block") - expect(exitstatus).to eq(4) + context "when checksum validation is disabled" do + before do + bundle "config set --local disable_checksum_validation true" + end + + it "warns about ambiguous gems, but installs anyway, prioritizing sources last to first", bundler: "< 3" do + bundle :install, artifice: "compact_index" + + expect(err).to include("Warning: the gem 'rack' was found in multiple sources.") + expect(err).to include("Installed from: https://gem.repo1") + expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0", source: "remote1") + end + + it "does not use the full index unnecessarily", bundler: "< 3" do + bundle :install, artifice: "compact_index", verbose: true + + expect(out).to include("https://gem.repo1/versions") + expect(out).to include("https://gem.repo3/versions") + expect(out).not_to include("https://gem.repo1/quick/Marshal.4.8/") + expect(out).not_to include("https://gem.repo3/quick/Marshal.4.8/") + end + + it "fails", bundler: "3" do + bundle :install, artifice: "compact_index", raise_on_error: false + expect(err).to include("Each source after the first must include a block") + expect(exitstatus).to eq(4) + end end end @@ -54,21 +108,49 @@ RSpec.describe "bundle install with gems on multiple sources" do G end - it "warns about ambiguous gems, but installs anyway", :bundler => "< 3" do - bundle :install, :artifice => "compact_index" + it "warns about ambiguous gems, but installs anyway", bundler: "< 3" do + bundle :install, artifice: "compact_index" expect(err).to include("Warning: the gem 'rack' was found in multiple sources.") expect(err).to include("Installed from: https://gem.repo1") - expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0", :source => "remote1") + expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0", source: "remote1") end - it "fails", :bundler => "3" do - bundle :install, :artifice => "compact_index", :raise_on_error => false + it "fails", bundler: "3" do + bundle :install, artifice: "compact_index", raise_on_error: false expect(err).to include("Each source after the first must include a block") expect(exitstatus).to eq(4) end end end + context "without source affinity, and a stdlib gem present in one of the sources", :ruby_repo do + let(:default_json_version) { ruby "gem 'json'; require 'json'; puts JSON::VERSION" } + + before do + build_repo2 do + build_gem "json", default_json_version + end + + build_repo4 do + build_gem "foo" do |s| + s.add_dependency "json", default_json_version + end + end + + gemfile <<-G + source "https://gem.repo2" + source "https://gem.repo4" + + gem "foo" + G + end + + it "works in standalone mode", bundler: "< 3" do + gem_checksum = checksum_digest(gem_repo4, "foo", "1.0") + bundle "install --standalone", artifice: "compact_index", env: { "BUNDLER_SPEC_FOO_CHECKSUM" => gem_checksum } + end + end + context "with source affinity" do context "with sources given by a block" do before do @@ -95,20 +177,20 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "installs the gems without any warning" do - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(err).not_to include("Warning") expect(the_bundle).to include_gems("rack-obama 1.0.0") - expect(the_bundle).to include_gems("rack 1.0.0", :source => "remote1") + expect(the_bundle).to include_gems("rack 1.0.0", source: "remote1") end it "can cache and deploy" do - bundle :cache, :artifice => "compact_index" + bundle :cache, artifice: "compact_index" expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist expect(bundled_app("vendor/cache/rack-obama-1.0.gem")).to exist bundle "config set --local deployment true" - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0") end @@ -128,7 +210,7 @@ RSpec.describe "bundle install with gems on multiple sources" do end end - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "https://gem.repo3" gem "rack-obama" # should come from repo3! gem "rack", :source => "https://gem.repo1" @@ -168,9 +250,9 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "installs from the same source without any warning" do - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(err).not_to include("Warning") - expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote3") + expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", source: "remote3") end end @@ -185,18 +267,18 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "installs from the same source without any warning" do - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(err).not_to include("Warning: the gem 'rack' was found in multiple sources.") - expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote3") + expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", source: "remote3") # In https://github.com/bundler/bundler/issues/3585 this failed # when there is already a lock file, and the gems are missing, so try again system_gems [] - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(err).not_to include("Warning: the gem 'rack' was found in multiple sources.") - expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote3") + expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", source: "remote3") end end end @@ -218,7 +300,7 @@ RSpec.describe "bundle install with gems on multiple sources" do context "and not in any other sources" do before do - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "https://gem.repo2" source "https://gem.repo3" do gem "depends_on_rack" @@ -243,11 +325,63 @@ RSpec.describe "bundle install with gems on multiple sources" do G end - it "installs from the other source and warns about ambiguous gems", :bundler => "< 3" do - bundle :install, :artifice => "compact_index" + it "fails when the two sources don't have the same checksum", bundler: "< 3" do + bundle :install, artifice: "compact_index", raise_on_error: false + + expect(err).to eq(<<~E.strip) + [DEPRECATED] Your Gemfile contains multiple global sources. Using `source` more than once without a block is a security risk, and may result in installing unexpected gems. To resolve this warning, use a block to indicate which gems should come from the secondary source. + Bundler found mismatched checksums. This is a potential security risk. + #{checksum_to_lock(gem_repo2, "rack", "1.0.0")} + from the API at https://gem.repo2/ + #{checksum_to_lock(gem_repo1, "rack", "1.0.0")} + from the API at https://gem.repo1/ + + Mismatched checksums each have an authoritative source: + 1. the API at https://gem.repo2/ + 2. the API at https://gem.repo1/ + You may need to alter your Gemfile sources to resolve this issue. + + To ignore checksum security warnings, disable checksum validation with + `bundle config set --local disable_checksum_validation true` + E + expect(exitstatus).to eq(37) + end + + it "fails when the two sources agree, but the local gem calculates a different checksum", bundler: "< 3" do + rack_checksum = "c0ffee11" * 8 + bundle :install, artifice: "compact_index", env: { "BUNDLER_SPEC_RACK_CHECKSUM" => rack_checksum }, raise_on_error: false + + expect(err).to eq(<<~E.strip) + [DEPRECATED] Your Gemfile contains multiple global sources. Using `source` more than once without a block is a security risk, and may result in installing unexpected gems. To resolve this warning, use a block to indicate which gems should come from the secondary source. + Bundler found mismatched checksums. This is a potential security risk. + rack (1.0.0) sha256=#{rack_checksum} + from the API at https://gem.repo2/ + and the API at https://gem.repo1/ + #{checksum_to_lock(gem_repo2, "rack", "1.0.0")} + from the gem at #{default_bundle_path("cache", "rack-1.0.0.gem")} + + If you trust the API at https://gem.repo2/, to resolve this issue you can: + 1. remove the gem at #{default_bundle_path("cache", "rack-1.0.0.gem")} + 2. run `bundle install` + + To ignore checksum security warnings, disable checksum validation with + `bundle config set --local disable_checksum_validation true` + E + expect(exitstatus).to eq(37) + end + + it "installs from the other source and warns about ambiguous gems when the sources have the same checksum", bundler: "< 3" do + gem_checksum = checksum_digest(gem_repo2, "rack", "1.0.0") + bundle :install, artifice: "compact_index", env: { "BUNDLER_SPEC_RACK_CHECKSUM" => gem_checksum, "DEBUG" => "1" } + expect(err).to include("Warning: the gem 'rack' was found in multiple sources.") expect(err).to include("Installed from: https://gem.repo2") + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo3, "depends_on_rack", "1.0.1" + c.checksum gem_repo2, "rack", "1.0.0" + end + expect(lockfile).to eq <<~L GEM remote: https://gem.repo1/ @@ -262,11 +396,51 @@ RSpec.describe "bundle install with gems on multiple sources" do rack PLATFORMS - #{specific_local_platform} + #{lockfile_platforms} DEPENDENCIES depends_on_rack! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + + previous_lockfile = lockfile + expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0") + expect(lockfile).to eq(previous_lockfile) + end + + it "installs from the other source and warns about ambiguous gems when checksum validation is disabled", bundler: "< 3" do + bundle "config set --local disable_checksum_validation true" + bundle :install, artifice: "compact_index" + expect(err).to include("Warning: the gem 'rack' was found in multiple sources.") + expect(err).to include("Installed from: https://gem.repo2") + + checksums = checksums_section_when_existing do |c| + c.no_checksum "depends_on_rack", "1.0.1" + c.no_checksum "rack", "1.0.0" + end + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo1/ + remote: https://gem.repo2/ + specs: + rack (1.0.0) + + GEM + remote: https://gem.repo3/ + specs: + depends_on_rack (1.0.1) + rack + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + depends_on_rack! + #{checksums} BUNDLED WITH #{Bundler::VERSION} L @@ -276,8 +450,8 @@ RSpec.describe "bundle install with gems on multiple sources" do expect(lockfile).to eq(previous_lockfile) end - it "fails", :bundler => "3" do - bundle :install, :artifice => "compact_index", :raise_on_error => false + it "fails", bundler: "3" do + bundle :install, artifice: "compact_index", raise_on_error: false expect(err).to include("Each source after the first must include a block") expect(exitstatus).to eq(4) end @@ -301,8 +475,8 @@ RSpec.describe "bundle install with gems on multiple sources" do G end - it "installs the dependency from the pinned source without warning", :bundler => "< 3" do - bundle :install, :artifice => "compact_index" + it "installs the dependency from the pinned source without warning", bundler: "< 3" do + bundle :install, artifice: "compact_index" expect(err).not_to include("Warning: the gem 'rack' was found in multiple sources.") expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0") @@ -310,14 +484,14 @@ RSpec.describe "bundle install with gems on multiple sources" do # In https://github.com/rubygems/bundler/issues/3585 this failed # when there is already a lock file, and the gems are missing, so try again system_gems [] - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(err).not_to include("Warning: the gem 'rack' was found in multiple sources.") expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0") end - it "fails", :bundler => "3" do - bundle :install, :artifice => "compact_index", :raise_on_error => false + it "fails", bundler: "3" do + bundle :install, artifice: "compact_index", raise_on_error: false expect(err).to include("Each source after the first must include a block") expect(exitstatus).to eq(4) end @@ -345,13 +519,12 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "fails" do - bundle :install, :artifice => "compact_index", :raise_on_error => false - expect(err).to include("Could not find gem 'private_gem_1' in rubygems repository https://gem.repo2/ or installed locally.") - expect(err).to include("The source does not contain any versions of 'private_gem_1'") + bundle :install, artifice: "compact_index", raise_on_error: false + expect(err).to include("Could not find gem 'private_gem_1' in rubygems repository https://gem.repo2/, cached gems or installed locally.") end end - context "when an indirect dependency can't be found in the aggregate rubygems source", :bundler => "< 3" do + context "when an indirect dependency can't be found in the aggregate rubygems source", bundler: "< 3" do before do build_repo2 @@ -371,8 +544,16 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "fails" do - bundle :install, :artifice => "compact_index", :raise_on_error => false - expect(err).to include("Could not find gem 'missing', which is required by gem 'depends_on_missing', in any of the sources.") + bundle :install, artifice: "compact_index", raise_on_error: false + expect(err).to end_with <<~E.strip + Could not find compatible versions + + Because every version of depends_on_missing depends on missing >= 0 + and missing >= 0 could not be found in any of the sources, + depends_on_missing cannot be used. + So, because Gemfile depends on depends_on_missing >= 0, + version solving has failed. + E end end @@ -407,11 +588,11 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "installs the dependency from the top-level source without warning" do - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(err).not_to include("Warning") expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", "unrelated_gem 1.0.0") - expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote2") - expect(the_bundle).to include_gems("unrelated_gem 1.0.0", :source => "remote3") + expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", source: "remote2") + expect(the_bundle).to include_gems("unrelated_gem 1.0.0", source: "remote3") end end @@ -425,10 +606,16 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "does not find the dependency" do - bundle :install, :artifice => "compact_index", :raise_on_error => false - expect(err).to include( - "Could not find gem 'rack', which is required by gem 'depends_on_rack', in rubygems repository https://gem.repo2/ or installed locally." - ) + bundle :install, artifice: "compact_index", raise_on_error: false + expect(err).to end_with <<~E.strip + Could not find compatible versions + + Because every version of depends_on_rack depends on rack >= 0 + and rack >= 0 could not be found in rubygems repository https://gem.repo2/, cached gems or installed locally, + depends_on_rack cannot be used. + So, because Gemfile depends on depends_on_rack >= 0, + version solving has failed. + E end end @@ -446,12 +633,12 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "installs the dependency from the top-level source without warning" do - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(err).not_to include("Warning") expect(run("require 'rack'; puts RACK")).to eq("1.0.0") expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", "unrelated_gem 1.0.0") - expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote2") - expect(the_bundle).to include_gems("unrelated_gem 1.0.0", :source => "remote3") + expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", source: "remote2") + expect(the_bundle).to include_gems("unrelated_gem 1.0.0", source: "remote3") end end end @@ -485,10 +672,10 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "installs the dependency from the top-level source" do - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(the_bundle).to include_gems("depends_on_depends_on_rack 1.0.1", "depends_on_rack 1.0.1", "rack 1.0.0") - expect(the_bundle).to include_gems("rack 1.0.0", :source => "remote2") - expect(the_bundle).to include_gems("depends_on_depends_on_rack 1.0.1", "depends_on_rack 1.0.1", :source => "remote3") + expect(the_bundle).to include_gems("rack 1.0.0", source: "remote2") + expect(the_bundle).to include_gems("depends_on_depends_on_rack 1.0.1", "depends_on_rack 1.0.1", source: "remote3") end end @@ -502,8 +689,8 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "installs the dependency from the pinned source" do - bundle :install, :artifice => "compact_index" - expect(the_bundle).to include_gems("depends_on_depends_on_rack 1.0.1", "depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote3") + bundle :install, artifice: "compact_index" + expect(the_bundle).to include_gems("depends_on_depends_on_rack 1.0.1", "depends_on_rack 1.0.1", "rack 1.0.0", source: "remote3") end end @@ -521,8 +708,8 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "installs the dependency from the pinned source without warning" do - bundle :install, :artifice => "compact_index" - expect(the_bundle).to include_gems("depends_on_depends_on_rack 1.0.1", "depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote3") + bundle :install, artifice: "compact_index" + expect(the_bundle).to include_gems("depends_on_depends_on_rack 1.0.1", "depends_on_rack 1.0.1", "rack 1.0.0", source: "remote3") end end end @@ -596,6 +783,21 @@ RSpec.describe "bundle install with gems on multiple sources" do end G + @locked_checksums = checksums_section_when_existing do |c| + c.checksum gem_repo2, "activesupport", "6.0.3.4" + c.checksum gem_repo2, "concurrent-ruby", "1.1.8" + c.checksum gem_repo2, "connection_pool", "2.2.3" + c.checksum gem_repo2, "i18n", "1.8.9" + c.checksum gem_repo2, "minitest", "5.14.3" + c.checksum gem_repo2, "rack", "2.2.3" + c.checksum gem_repo2, "redis", "4.2.5" + c.checksum gem_repo2, "sidekiq", "6.1.3" + c.checksum gem_repo3, "sidekiq-pro", "5.2.1" + c.checksum gem_repo2, "thread_safe", "0.3.6" + c.checksum gem_repo2, "tzinfo", "1.2.9" + c.checksum gem_repo2, "zeitwerk", "2.4.2" + end + lockfile <<~L GEM remote: https://gem.repo2/ @@ -627,19 +829,19 @@ RSpec.describe "bundle install with gems on multiple sources" do zeitwerk (2.4.2) PLATFORMS - #{specific_local_platform} + #{lockfile_platforms} DEPENDENCIES activesupport sidekiq-pro! - + #{@locked_checksums} BUNDLED WITH #{Bundler::VERSION} L end it "does not install newer versions but updates the lockfile format when running bundle install in non frozen mode, and doesn't warn" do - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(err).to be_empty expect(the_bundle).to include_gems("activesupport 6.0.3.4") @@ -683,22 +885,22 @@ RSpec.describe "bundle install with gems on multiple sources" do sidekiq (>= 6.1.0) PLATFORMS - #{specific_local_platform} + #{lockfile_platforms} DEPENDENCIES activesupport sidekiq-pro! - + #{@locked_checksums} BUNDLED WITH #{Bundler::VERSION} L end - it "does not install newer versions or generate lockfile changes when running bundle install in frozen mode, and warns", :bundler => "< 3" do + it "does not install newer versions or generate lockfile changes when running bundle install in frozen mode, and warns", bundler: "< 3" do initial_lockfile = lockfile bundle "config set --local frozen true" - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.") @@ -712,11 +914,11 @@ RSpec.describe "bundle install with gems on multiple sources" do expect(lockfile).to eq(initial_lockfile) end - it "fails when running bundle install in frozen mode", :bundler => "3" do + it "fails when running bundle install in frozen mode", bundler: "3" do initial_lockfile = lockfile bundle "config set --local frozen true" - bundle :install, :artifice => "compact_index", :raise_on_error => false + bundle :install, artifice: "compact_index", raise_on_error: false expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.") @@ -724,15 +926,21 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "splits sections and upgrades gems when running bundle update, and doesn't warn" do - bundle "update --all", :artifice => "compact_index" + bundle "update --all", artifice: "compact_index" expect(err).to be_empty expect(the_bundle).not_to include_gems("activesupport 6.0.3.4") expect(the_bundle).to include_gems("activesupport 6.1.2.1") + @locked_checksums.checksum gem_repo2, "activesupport", "6.1.2.1" + expect(the_bundle).not_to include_gems("tzinfo 1.2.9") expect(the_bundle).to include_gems("tzinfo 2.0.4") + @locked_checksums.checksum gem_repo2, "tzinfo", "2.0.4" + @locked_checksums.delete "thread_safe" + expect(the_bundle).not_to include_gems("concurrent-ruby 1.1.8") expect(the_bundle).to include_gems("concurrent-ruby 1.1.9") + @locked_checksums.checksum gem_repo2, "concurrent-ruby", "1.1.9" expect(lockfile).to eq <<~L GEM @@ -767,19 +975,19 @@ RSpec.describe "bundle install with gems on multiple sources" do sidekiq (>= 6.1.0) PLATFORMS - #{specific_local_platform} + #{lockfile_platforms} DEPENDENCIES activesupport sidekiq-pro! - + #{@locked_checksums} BUNDLED WITH #{Bundler::VERSION} L end it "upgrades the lockfile format and upgrades the requested gem when running bundle update with an argument" do - bundle "update concurrent-ruby", :artifice => "compact_index" + bundle "update concurrent-ruby", artifice: "compact_index" expect(err).to be_empty expect(the_bundle).to include_gems("activesupport 6.0.3.4") @@ -789,6 +997,8 @@ RSpec.describe "bundle install with gems on multiple sources" do expect(the_bundle).to include_gems("concurrent-ruby 1.1.9") expect(the_bundle).not_to include_gems("concurrent-ruby 1.1.8") + @locked_checksums.checksum gem_repo2, "concurrent-ruby", "1.1.9" + expect(lockfile).to eq <<~L GEM remote: https://gem.repo2/ @@ -823,12 +1033,12 @@ RSpec.describe "bundle install with gems on multiple sources" do sidekiq (>= 6.1.0) PLATFORMS - #{specific_local_platform} + #{lockfile_platforms} DEPENDENCIES activesupport sidekiq-pro! - + #{@locked_checksums} BUNDLED WITH #{Bundler::VERSION} L @@ -837,8 +1047,8 @@ RSpec.describe "bundle install with gems on multiple sources" do context "when a top-level gem has an indirect dependency present in the default source, but with a different version from the one resolved" do before do - build_lib "activesupport", "7.0.0.alpha", :path => lib_path("rails/activesupport") - build_lib "rails", "7.0.0.alpha", :path => lib_path("rails") do |s| + build_lib "activesupport", "7.0.0.alpha", path: lib_path("rails/activesupport") + build_lib "rails", "7.0.0.alpha", path: lib_path("rails") do |s| s.add_dependency "activesupport", "= 7.0.0.alpha" end @@ -860,11 +1070,11 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "installs all gems without warning" do - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(err).not_to include("Warning") expect(the_bundle).to include_gems("activesupport 7.0.0.alpha", "rails 7.0.0.alpha") - expect(the_bundle).to include_gems("activesupport 7.0.0.alpha", :source => "path@#{lib_path("rails/activesupport")}") - expect(the_bundle).to include_gems("rails 7.0.0.alpha", :source => "path@#{lib_path("rails")}") + expect(the_bundle).to include_gems("activesupport 7.0.0.alpha", source: "path@#{lib_path("rails/activesupport")}") + expect(the_bundle).to include_gems("rails 7.0.0.alpha", source: "path@#{lib_path("rails")}") end end @@ -896,6 +1106,12 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "installs from the default source without any warnings or errors and generates a proper lockfile" do + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo3, "handsoap", "0.2.5.5" + c.checksum gem_repo2, "nokogiri", "1.11.1" + c.checksum gem_repo2, "racca", "1.5.2" + end + expected_lockfile = <<~L GEM remote: https://gem.repo2/ @@ -911,30 +1127,30 @@ RSpec.describe "bundle install with gems on multiple sources" do nokogiri (>= 1.2.3) PLATFORMS - #{specific_local_platform} + #{lockfile_platforms} DEPENDENCIES handsoap! nokogiri - + #{checksums} BUNDLED WITH #{Bundler::VERSION} L - bundle "install --verbose", :artifice => "compact_index" + bundle "install --verbose", artifice: "compact_index" expect(err).not_to include("Warning") expect(the_bundle).to include_gems("handsoap 0.2.5.5", "nokogiri 1.11.1", "racca 1.5.2") - expect(the_bundle).to include_gems("handsoap 0.2.5.5", :source => "remote3") - expect(the_bundle).to include_gems("nokogiri 1.11.1", "racca 1.5.2", :source => "remote2") + expect(the_bundle).to include_gems("handsoap 0.2.5.5", source: "remote3") + expect(the_bundle).to include_gems("nokogiri 1.11.1", "racca 1.5.2", source: "remote2") expect(lockfile).to eq(expected_lockfile) # Even if the gems are already installed FileUtils.rm bundled_app_lock - bundle "install --verbose", :artifice => "compact_index" + bundle "install --verbose", artifice: "compact_index" expect(err).not_to include("Warning") expect(the_bundle).to include_gems("handsoap 0.2.5.5", "nokogiri 1.11.1", "racca 1.5.2") - expect(the_bundle).to include_gems("handsoap 0.2.5.5", :source => "remote3") - expect(the_bundle).to include_gems("nokogiri 1.11.1", "racca 1.5.2", :source => "remote2") + expect(the_bundle).to include_gems("handsoap 0.2.5.5", source: "remote3") + expect(the_bundle).to include_gems("nokogiri 1.11.1", "racca 1.5.2", source: "remote2") expect(lockfile).to eq(expected_lockfile) end end @@ -945,7 +1161,7 @@ RSpec.describe "bundle install with gems on multiple sources" do build_gem "not_in_repo1", "1.0.0" end - install_gemfile <<-G, :artifice => "compact_index", :raise_on_error => false + install_gemfile <<-G, artifice: "compact_index", raise_on_error: false source "https://gem.repo3" gem "not_in_repo1", :source => "https://gem.repo1" G @@ -958,7 +1174,7 @@ RSpec.describe "bundle install with gems on multiple sources" do context "with an existing lockfile" do before do - system_gems "rack-0.9.1", "rack-1.0.0", :path => default_bundle_path + system_gems "rack-0.9.1", "rack-1.0.0", path: default_bundle_path lockfile <<-L GEM @@ -971,7 +1187,7 @@ RSpec.describe "bundle install with gems on multiple sources" do rack (0.9.1) PLATFORMS - #{specific_local_platform} + #{lockfile_platforms} DEPENDENCIES rack! @@ -1001,11 +1217,11 @@ RSpec.describe "bundle install with gems on multiple sources" do rack (0.9.1) PLATFORMS - #{specific_local_platform} + #{lockfile_platforms} DEPENDENCIES rack! - + #{checksums_section} BUNDLED WITH #{Bundler::VERSION} L @@ -1023,7 +1239,7 @@ RSpec.describe "bundle install with gems on multiple sources" do rack (0.9.1) PLATFORMS - #{specific_local_platform} + #{lockfile_platforms} DEPENDENCIES rack! @@ -1048,20 +1264,48 @@ RSpec.describe "bundle install with gems on multiple sources" do lockfile aggregate_gem_section_lockfile end - it "installs the existing lockfile but prints a warning", :bundler => "< 3" do + it "installs the existing lockfile but prints a warning when checksum validation is disabled", bundler: "< 3" do bundle "config set --local deployment true" + bundle "config set --local disable_checksum_validation true" - bundle "install", :artifice => "compact_index" + bundle "install", artifice: "compact_index" expect(lockfile).to eq(aggregate_gem_section_lockfile) expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.") - expect(the_bundle).to include_gems("rack 0.9.1", :source => "remote3") + expect(the_bundle).to include_gems("rack 0.9.1", source: "remote3") end - it "refuses to install the existing lockfile and prints an error", :bundler => "3" do + it "prints a checksum warning when the checksums from both sources do not match", bundler: "< 3" do bundle "config set --local deployment true" - bundle "install", :artifice => "compact_index", :raise_on_error =>false + bundle "install", artifice: "compact_index", raise_on_error: false + + api_checksum1 = checksum_digest(gem_repo1, "rack", "0.9.1") + api_checksum3 = checksum_digest(gem_repo3, "rack", "0.9.1") + + expect(exitstatus).to eq(37) + expect(err).to eq(<<~E.strip) + [DEPRECATED] Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. Make sure you run `bundle install` in non frozen mode and commit the result to make your lockfile secure. + Bundler found mismatched checksums. This is a potential security risk. + rack (0.9.1) sha256=#{api_checksum3} + from the API at https://gem.repo3/ + rack (0.9.1) sha256=#{api_checksum1} + from the API at https://gem.repo1/ + + Mismatched checksums each have an authoritative source: + 1. the API at https://gem.repo3/ + 2. the API at https://gem.repo1/ + You may need to alter your Gemfile sources to resolve this issue. + + To ignore checksum security warnings, disable checksum validation with + `bundle config set --local disable_checksum_validation true` + E + end + + it "refuses to install the existing lockfile and prints an error", bundler: "3" do + bundle "config set --local deployment true" + + bundle "install", artifice: "compact_index", raise_on_error: false expect(lockfile).to eq(aggregate_gem_section_lockfile) expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.") @@ -1081,7 +1325,7 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "does not unlock the non-path gem after install" do - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" bundle %(exec ruby -e 'puts "OK"') @@ -1094,7 +1338,7 @@ RSpec.describe "bundle install with gems on multiple sources" do before do system_gems "rack-0.9.1" - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "https://gem.repo1" gem "rack" # should come from repo1! G @@ -1123,7 +1367,7 @@ RSpec.describe "bundle install with gems on multiple sources" do G bundle "config set --local path ../gems/system" - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" # And then we add some new versions... update_repo4 do @@ -1134,7 +1378,7 @@ RSpec.describe "bundle install with gems on multiple sources" do it "allows them to be unlocked separately" do # And install this gemfile, updating only foo. - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source 'https://gem.repo1' gem 'rack' gem 'foo', '~> 0.2', :source => 'https://gem.repo4' @@ -1158,7 +1402,7 @@ RSpec.describe "bundle install with gems on multiple sources" do build_git "git1" build_git "git2" - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "https://gem.repo1" gem "rails" @@ -1174,7 +1418,7 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "does not re-resolve" do - bundle :install, :artifice => "compact_index", :verbose => true + bundle :install, artifice: "compact_index", verbose: true expect(out).to include("using resolution from the lockfile") expect(out).not_to include("re-resolving dependencies") end @@ -1183,7 +1427,7 @@ RSpec.describe "bundle install with gems on multiple sources" do context "when a gem is installed to system gems" do before do - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "https://gem.repo1" gem "rack" G @@ -1197,7 +1441,7 @@ RSpec.describe "bundle install with gems on multiple sources" do end # When this gemfile is installed... - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "https://gem.repo1" source "https://gem.repo4" do @@ -1219,14 +1463,14 @@ RSpec.describe "bundle install with gems on multiple sources" do G # But we should still be able to find rack 2.0.1.1.forked and install it - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" end end end describe "source changed to one containing a higher version of a dependency" do before do - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "https://gem.repo1" gem "rack" @@ -1240,19 +1484,45 @@ RSpec.describe "bundle install with gems on multiple sources" do build_gem "bar" end - build_lib("gemspec_test", :path => tmp.join("gemspec_test")) do |s| + build_lib("gemspec_test", path: tmp.join("gemspec_test")) do |s| s.add_dependency "bar", "=1.0.0" end - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "https://gem.repo2" gem "rack" gemspec :path => "#{tmp.join("gemspec_test")}" G end - it "installs the higher version in the new repo" do - expect(the_bundle).to include_gems("rack 1.2") + it "conservatively installs the existing locked version" do + expect(the_bundle).to include_gems("rack 1.0.0") + end + end + + context "when Gemfile overrides a gemspec development dependency to change the default source" do + before do + build_repo4 do + build_gem "bar" + end + + build_lib("gemspec_test", path: tmp.join("gemspec_test")) do |s| + s.add_development_dependency "bar" + end + + install_gemfile <<-G, artifice: "compact_index" + source "https://gem.repo1" + + source "https://gem.repo4" do + gem "bar" + end + + gemspec :path => "#{tmp.join("gemspec_test")}" + G + end + + it "does not print warnings" do + expect(err).to be_empty end end @@ -1265,7 +1535,7 @@ RSpec.describe "bundle install with gems on multiple sources" do build_gem "example", "1.0.2" end - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "https://gem.repo4" gem "example", :source => "https://gem.repo2" @@ -1274,14 +1544,14 @@ RSpec.describe "bundle install with gems on multiple sources" do bundle "info example" expect(out).to include("example (0.1.0)") - system_gems "example-1.0.2", :path => default_bundle_path, :gem_repo => gem_repo4 + system_gems "example-1.0.2", path: default_bundle_path, gem_repo: gem_repo4 - bundle "update example --verbose", :artifice => "compact_index" + bundle "update example --verbose", artifice: "compact_index" expect(out).not_to include("Using example 1.0.2") expect(out).to include("Using example 0.1.0") end - it "fails inmmediately with a helpful error when a non retriable network error happens while resolving sources" do + it "fails immediately with a helpful error when a rubygems source does not exist and bundler/setup is required" do gemfile <<-G source "https://gem.repo1" @@ -1290,17 +1560,30 @@ RSpec.describe "bundle install with gems on multiple sources" do end G - simulate_bundler_version_when_missing_prerelease_default_gem_activation do - ruby <<~R, :raise_on_error => false - require 'bundler/setup' - R - end + ruby <<~R, raise_on_error: false + require 'bundler/setup' + R + + expect(last_command).to be_failure + expect(err).to include("Could not find gem 'example' in locally installed gems.") + end + + it "fails immediately with a helpful error when a non retriable network error happens while resolving sources" do + gemfile <<-G + source "https://gem.repo1" + + source "https://gem.repo4" do + gem "example" + end + G + + bundle "install", artifice: nil, raise_on_error: false expect(last_command).to be_failure expect(err).to include("Could not reach host gem.repo4. Check your network connection and try again.") end - context "when an indirect dependency is available from multiple ambiguous sources", :bundler => "< 3" do + context "when an indirect dependency is available from multiple ambiguous sources", bundler: "< 3" do it "succeeds but warns, suggesting a source block" do build_repo4 do build_gem "depends_on_rack" do |s| @@ -1309,7 +1592,7 @@ RSpec.describe "bundle install with gems on multiple sources" do build_gem "rack" end - install_gemfile <<-G, :artifice => "compact_index", :raise_on_error => false + install_gemfile <<-G, artifice: "compact_index", raise_on_error: false source "#{file_uri_for(gem_repo1)}" source "https://gem.repo4" do @@ -1320,10 +1603,10 @@ RSpec.describe "bundle install with gems on multiple sources" do gem "thin" end G - expect(err).to eq strip_whitespace(<<-EOS).strip + expect(err).to eq <<~EOS.strip Warning: The gem 'rack' was found in multiple relevant sources. - * rubygems repository https://gem.repo1/ or installed locally - * rubygems repository https://gem.repo4/ or installed locally + * rubygems repository https://gem.repo1/ + * rubygems repository https://gem.repo4/ You should add this gem to the source block for the source you wish it to be installed from. EOS expect(last_command).to be_success @@ -1331,7 +1614,7 @@ RSpec.describe "bundle install with gems on multiple sources" do end end - context "when an indirect dependency is available from multiple ambiguous sources", :bundler => "3" do + context "when an indirect dependency is available from multiple ambiguous sources", bundler: "3" do it "raises, suggesting a source block" do build_repo4 do build_gem "depends_on_rack" do |s| @@ -1340,7 +1623,7 @@ RSpec.describe "bundle install with gems on multiple sources" do build_gem "rack" end - install_gemfile <<-G, :artifice => "compact_index", :raise_on_error => false + install_gemfile <<-G, artifice: "compact_index", raise_on_error: false source "#{file_uri_for(gem_repo1)}" source "https://gem.repo4" do gem "depends_on_rack" @@ -1350,10 +1633,10 @@ RSpec.describe "bundle install with gems on multiple sources" do end G expect(last_command).to be_failure - expect(err).to eq strip_whitespace(<<-EOS).strip + expect(err).to eq <<~EOS.strip The gem 'rack' was found in multiple relevant sources. - * rubygems repository https://gem.repo1/ or installed locally - * rubygems repository https://gem.repo4/ or installed locally + * rubygems repository https://gem.repo1/ + * rubygems repository https://gem.repo4/ You must add this gem to the source block for the source you wish it to be installed from. EOS expect(the_bundle).not_to be_locked @@ -1394,16 +1677,23 @@ RSpec.describe "bundle install with gems on multiple sources" do mime-types (3.3.1) PLATFORMS - #{specific_local_platform} + #{lockfile_platforms} DEPENDENCIES capybara (~> 2.5.0) mime-types (~> 3.0)! + + CHECKSUMS L end it "upgrades the lockfile correctly" do - bundle "lock --update", :artifice => "compact_index" + bundle "lock --update", artifice: "compact_index" + + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo2, "capybara", "2.5.0" + c.checksum gem_repo4, "mime-types", "3.0.0" + end expect(lockfile).to eq <<~L GEM @@ -1418,15 +1708,215 @@ RSpec.describe "bundle install with gems on multiple sources" do mime-types (3.0.0) PLATFORMS - #{specific_local_platform} + #{lockfile_platforms} DEPENDENCIES capybara (~> 2.5.0) mime-types (~> 3.0)! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + context "when default source includes old gems with nil required_ruby_version" do + before do + build_repo2 do + build_gem "ruport", "1.7.0.3" do |s| + s.add_dependency "pdf-writer", "1.1.8" + end + end + + build_repo gem_repo4 do + build_gem "pdf-writer", "1.1.8" + end + + path = "#{gem_repo4}/#{Gem::MARSHAL_SPEC_DIR}/pdf-writer-1.1.8.gemspec.rz" + spec = Marshal.load(Bundler.rubygems.inflate(File.binread(path))) + spec.instance_variable_set(:@required_ruby_version, nil) + File.open(path, "wb") do |f| + f.write Gem.deflate(Marshal.dump(spec)) + end + + gemfile <<~G + source "https://localgemserver.test" + + gem "ruport", "= 1.7.0.3", :source => "https://localgemserver.test/extra" + G + end + + it "handles that fine" do + bundle "install", artifice: "compact_index_extra", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo4, "pdf-writer", "1.1.8" + c.checksum gem_repo2, "ruport", "1.7.0.3" + end + + expect(lockfile).to eq <<~L + GEM + remote: https://localgemserver.test/ + specs: + pdf-writer (1.1.8) + + GEM + remote: https://localgemserver.test/extra/ + specs: + ruport (1.7.0.3) + pdf-writer (= 1.1.8) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + ruport (= 1.7.0.3)! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + context "when default source includes old gems with nil required_rubygems_version" do + before do + build_repo2 do + build_gem "ruport", "1.7.0.3" do |s| + s.add_dependency "pdf-writer", "1.1.8" + end + end + + build_repo gem_repo4 do + build_gem "pdf-writer", "1.1.8" + end + + path = "#{gem_repo4}/#{Gem::MARSHAL_SPEC_DIR}/pdf-writer-1.1.8.gemspec.rz" + spec = Marshal.load(Bundler.rubygems.inflate(File.binread(path))) + spec.instance_variable_set(:@required_rubygems_version, nil) + File.open(path, "wb") do |f| + f.write Gem.deflate(Marshal.dump(spec)) + end + + gemfile <<~G + source "https://localgemserver.test" + + gem "ruport", "= 1.7.0.3", :source => "https://localgemserver.test/extra" + G + end + it "handles that fine" do + bundle "install", artifice: "compact_index_extra", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo4, "pdf-writer", "1.1.8" + c.checksum gem_repo2, "ruport", "1.7.0.3" + end + + expect(lockfile).to eq <<~L + GEM + remote: https://localgemserver.test/ + specs: + pdf-writer (1.1.8) + + GEM + remote: https://localgemserver.test/extra/ + specs: + ruport (1.7.0.3) + pdf-writer (= 1.1.8) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + ruport (= 1.7.0.3)! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + context "when default source uses the old API and includes old gems with nil required_rubygems_version" do + before do + build_repo4 do + build_gem "pdf-writer", "1.1.8" + end + + path = "#{gem_repo4}/#{Gem::MARSHAL_SPEC_DIR}/pdf-writer-1.1.8.gemspec.rz" + spec = Marshal.load(Bundler.rubygems.inflate(File.binread(path))) + spec.instance_variable_set(:@required_rubygems_version, nil) + File.open(path, "wb") do |f| + f.write Gem.deflate(Marshal.dump(spec)) + end + + gemfile <<~G + source "https://localgemserver.test" + + gem "pdf-writer", "= 1.1.8" + G + end + + it "handles that fine" do + bundle "install --verbose", artifice: "endpoint", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo4, "pdf-writer", "1.1.8" + end + + expect(lockfile).to eq <<~L + GEM + remote: https://localgemserver.test/ + specs: + pdf-writer (1.1.8) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + pdf-writer (= 1.1.8) + #{checksums} BUNDLED WITH #{Bundler::VERSION} L end end + + context "when mistakenly adding a top level gem already depended on and cached under the wrong source" do + before do + build_repo4 do + build_gem "some_private_gem", "0.1.0" do |s| + s.add_dependency "example", "~> 1.0" + end + end + + build_repo2 do + build_gem "example", "1.0.0" + end + + install_gemfile <<~G, artifice: "compact_index" + source "https://gem.repo2" + + source "https://gem.repo4" do + gem "some_private_gem" + end + G + + gemfile <<~G + source "https://gem.repo2" + + source "https://gem.repo4" do + gem "some_private_gem" + gem "example" # MISTAKE, example is not available at gem.repo4 + end + G + end + + it "shows a proper error message and does not generate a corrupted lockfile" do + expect do + bundle :install, artifice: "compact_index", raise_on_error: false, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + end.not_to change { lockfile } + + expect(err).to include("Could not find gem 'example' in rubygems repository https://gem.repo4/") + end + end end diff --git a/spec/bundler/install/gemfile/specific_platform_spec.rb b/spec/bundler/install/gemfile/specific_platform_spec.rb index 4c24f09368..5f1b034bfc 100644 --- a/spec/bundler/install/gemfile/specific_platform_spec.rb +++ b/spec/bundler/install/gemfile/specific_platform_spec.rb @@ -6,29 +6,29 @@ RSpec.describe "bundle install with specific platforms" do gem "google-protobuf" G - context "when on a darwin machine" do - before { simulate_platform "x86_64-darwin-15" } - - it "locks to the specific darwin platform" do + it "locks to the specific darwin platform" do + simulate_platform "x86_64-darwin-15" do setup_multiplatform_gem install_gemfile(google_protobuf) allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) - expect(the_bundle.locked_gems.platforms).to eq([pl("x86_64-darwin-15")]) + expect(the_bundle.locked_gems.platforms).to include(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-universal-darwin - ]) + expect(the_bundle.locked_gems.specs.map(&:full_name)).to include( + "google-protobuf-3.0.0.alpha.5.0.5.1-universal-darwin" + ) end + end - it "understands that a non-plaform specific gem in a old lockfile doesn't necessarily mean installing the non-specific variant" do + it "understands that a non-platform specific gem in a old lockfile doesn't necessarily mean installing the non-specific variant" do + simulate_platform "x86_64-darwin-15" do setup_multiplatform_gem system_gems "bundler-2.1.4" # Consistent location to install and look for gems - bundle "config set --local path vendor/bundle", :env => { "BUNDLER_VERSION" => "2.1.4" } + bundle "config set --local path vendor/bundle", env: { "BUNDLER_VERSION" => "2.1.4" } - install_gemfile(google_protobuf, :env => { "BUNDLER_VERSION" => "2.1.4" }) + install_gemfile(google_protobuf, env: { "BUNDLER_VERSION" => "2.1.4" }) # simulate lockfile created with old bundler, which only locks for ruby platform lockfile <<-L @@ -48,22 +48,28 @@ RSpec.describe "bundle install with specific platforms" do L # force strict usage of the lock file by setting frozen mode - bundle "config set --local frozen true", :env => { "BUNDLER_VERSION" => "2.1.4" } + bundle "config set --local frozen true", env: { "BUNDLER_VERSION" => "2.1.4" } # make sure the platform that got actually installed with the old bundler is used expect(the_bundle).to include_gem("google-protobuf 3.0.0.alpha.5.0.5.1 universal-darwin") end + end - it "understands that a non-plaform specific gem in a new lockfile locked only to RUBY doesn't necessarily mean installing the non-specific variant" do + it "understands that a non-platform specific gem in a new lockfile locked only to RUBY doesn't necessarily mean installing the non-specific variant" do + simulate_platform "x86_64-darwin-15" do setup_multiplatform_gem system_gems "bundler-2.1.4" # Consistent location to install and look for gems - bundle "config set --local path vendor/bundle", :env => { "BUNDLER_VERSION" => "2.1.4" } + bundle "config set --local path vendor/bundle", env: { "BUNDLER_VERSION" => "2.1.4" } gemfile google_protobuf + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo2, "google-protobuf", "3.0.0.alpha.4.0" + end + # simulate lockfile created with old bundler, which only locks for ruby platform lockfile <<-L GEM @@ -76,18 +82,20 @@ RSpec.describe "bundle install with specific platforms" do DEPENDENCIES google-protobuf - + #{checksums} BUNDLED WITH 2.1.4 L - bundle "update", :env => { "BUNDLER_VERSION" => Bundler::VERSION } + bundle "update", env: { "BUNDLER_VERSION" => Bundler::VERSION } + + checksums.checksum gem_repo2, "google-protobuf", "3.0.0.alpha.5.0.5.1" # make sure the platform that the platform specific dependency is used, since we're only locked to ruby expect(the_bundle).to include_gem("google-protobuf 3.0.0.alpha.5.0.5.1 universal-darwin") # make sure we're still only locked to ruby - lockfile_should_be <<-L + expect(lockfile).to eq <<~L GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -98,13 +106,61 @@ RSpec.describe "bundle install with specific platforms" do DEPENDENCIES google-protobuf + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + context "when running on a legacy lockfile locked only to RUBY" do + around do |example| + build_repo4 do + build_gem "nokogiri", "1.3.10" + build_gem "nokogiri", "1.3.10" do |s| + s.platform = "arm64-darwin" + s.required_ruby_version = "< #{Gem.ruby_version}" + end + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "nokogiri" + G + + lockfile <<-L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + nokogiri (1.3.10) + + PLATFORMS + ruby + + DEPENDENCIES + nokogiri BUNDLED WITH #{Bundler::VERSION} L + + simulate_platform "arm64-darwin-22", &example end - it "doesn't discard previously installed platform specific gem and fall back to ruby on subsequent bundles" do + it "still installs the generic RUBY variant if necessary" do + bundle "install --verbose", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + expect(out).to include("Installing nokogiri 1.3.10") + end + + it "still installs the generic RUBY variant if necessary, even in frozen mode" do + bundle "install --verbose", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s, "BUNDLE_FROZEN" => "true" } + expect(out).to include("Installing nokogiri 1.3.10") + end + end + + it "doesn't discard previously installed platform specific gem and fall back to ruby on subsequent bundles" do + simulate_platform "x86_64-darwin-15" do build_repo2 do build_gem("libv8", "8.4.255.0") build_gem("libv8", "8.4.255.0") {|s| s.platform = "universal-darwin" } @@ -117,7 +173,7 @@ RSpec.describe "bundle install with specific platforms" do system_gems "bundler-2.1.4" # Consistent location to install and look for gems - bundle "config set --local path vendor/bundle", :env => { "BUNDLER_VERSION" => "2.1.4" } + bundle "config set --local path vendor/bundle", env: { "BUNDLER_VERSION" => "2.1.4" } gemfile <<-G source "https://localgemserver.test" @@ -141,14 +197,50 @@ RSpec.describe "bundle install with specific platforms" do 2.1.4 L - bundle "install --verbose", :artifice => "compact_index", :env => { "BUNDLER_VERSION" => "2.1.4", "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } + bundle "install --verbose", artifice: "compact_index", env: { "BUNDLER_VERSION" => "2.1.4", "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } expect(out).to include("Installing libv8 8.4.255.0 (universal-darwin)") - bundle "add mini_racer --verbose", :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } + bundle "add mini_racer --verbose", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } expect(out).to include("Using libv8 8.4.255.0 (universal-darwin)") end + end - it "caches the universal-darwin gem when --all-platforms is passed and properly picks it up on further bundler invocations" do + it "chooses platform specific gems even when resolving upon materialization and the API returns more specific platforms first" do + simulate_platform "x86_64-darwin-15" do + build_repo4 do + build_gem("grpc", "1.50.0") + build_gem("grpc", "1.50.0") {|s| s.platform = "universal-darwin" } + end + + gemfile <<-G + source "https://localgemserver.test" + gem "grpc" + G + + # simulate lockfile created with old bundler, which only locks for ruby platform + lockfile <<-L + GEM + remote: https://localgemserver.test/ + specs: + grpc (1.50.0) + + PLATFORMS + ruby + + DEPENDENCIES + grpc + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install --verbose", artifice: "compact_index_precompiled_before", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + expect(out).to include("Installing grpc 1.50.0 (universal-darwin)") + end + end + + it "caches the universal-darwin gem when --all-platforms is passed and properly picks it up on further bundler invocations" do + simulate_platform "x86_64-darwin-15" do setup_multiplatform_gem gemfile(google_protobuf) bundle "cache --all-platforms" @@ -157,8 +249,10 @@ RSpec.describe "bundle install with specific platforms" do bundle "install --verbose" expect(err).to be_empty end + end - it "caches the universal-darwin gem when cache_all_platforms is configured and properly picks it up on further bundler invocations" do + it "caches the universal-darwin gem when cache_all_platforms is configured and properly picks it up on further bundler invocations" do + simulate_platform "x86_64-darwin-15" do setup_multiplatform_gem gemfile(google_protobuf) bundle "config set --local cache_all_platforms true" @@ -168,44 +262,46 @@ RSpec.describe "bundle install with specific platforms" do bundle "install --verbose" expect(err).to be_empty end + end - it "caches multiplatform git gems with a single gemspec when --all-platforms is passed" do - git = build_git "pg_array_parser", "1.0" + it "caches multiplatform git gems with a single gemspec when --all-platforms is passed" do + git = build_git "pg_array_parser", "1.0" - gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "pg_array_parser", :git => "#{lib_path("pg_array_parser-1.0")}" - G + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem "pg_array_parser", :git => "#{lib_path("pg_array_parser-1.0")}" + G - lockfile <<-L - GIT - remote: #{lib_path("pg_array_parser-1.0")} - revision: #{git.ref_for("master")} - specs: - pg_array_parser (1.0-java) - pg_array_parser (1.0) + lockfile <<-L + GIT + remote: #{lib_path("pg_array_parser-1.0")} + revision: #{git.ref_for("main")} + specs: + pg_array_parser (1.0-java) + pg_array_parser (1.0) - GEM - specs: + GEM + specs: - PLATFORMS - java - #{lockfile_platforms} + PLATFORMS + java + #{lockfile_platforms} - DEPENDENCIES - pg_array_parser! + DEPENDENCIES + pg_array_parser! - BUNDLED WITH - #{Bundler::VERSION} - L + BUNDLED WITH + #{Bundler::VERSION} + L - bundle "config set --local cache_all true" - bundle "cache --all-platforms" + bundle "config set --local cache_all true" + bundle "cache --all-platforms" - expect(err).to be_empty - end + expect(err).to be_empty + end - it "uses the platform-specific gem with extra dependencies" do + it "uses the platform-specific gem with extra dependencies" do + simulate_platform "x86_64-darwin-15" do setup_multiplatform_gem_with_different_dependencies_per_platform install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" @@ -213,47 +309,52 @@ RSpec.describe "bundle install with specific platforms" do G allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) - expect(the_bundle.locked_gems.platforms).to eq([pl("x86_64-darwin-15")]) + expect(the_bundle.locked_gems.platforms).to include(pl("x86_64-darwin-15")) expect(the_bundle).to include_gems("facter 2.4.6 universal-darwin", "CFPropertyList 1.0") - expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(["CFPropertyList-1.0", - "facter-2.4.6-universal-darwin"]) + expect(the_bundle.locked_gems.specs.map(&:full_name)).to include("CFPropertyList-1.0", + "facter-2.4.6-universal-darwin") end + end - context "when adding a platform via lock --add_platform" do - before do - allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) - end + context "when adding a platform via lock --add_platform" do + before do + allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) + end - it "adds the foreign platform" do + it "adds the foreign platform" do + simulate_platform "x86_64-darwin-15" do setup_multiplatform_gem install_gemfile(google_protobuf) - bundle "lock --add-platform=#{x64_mingw}" + bundle "lock --add-platform=#{x64_mingw32}" - expect(the_bundle.locked_gems.platforms).to eq([x64_mingw, pl("x86_64-darwin-15")]) - expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w[ + expect(the_bundle.locked_gems.platforms).to include(x64_mingw32, pl("x86_64-darwin-15")) + expect(the_bundle.locked_gems.specs.map(&:full_name)).to include(*%w[ 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 + end - it "falls back on plain ruby when that version doesnt have a platform-specific gem" do + it "falls back on plain ruby when that version doesn't have a platform-specific gem" do + simulate_platform "x86_64-darwin-15" do setup_multiplatform_gem install_gemfile(google_protobuf) bundle "lock --add-platform=#{java}" - expect(the_bundle.locked_gems.platforms).to eq([java, 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 - ]) + expect(the_bundle.locked_gems.platforms).to include(java, pl("x86_64-darwin-15")) + expect(the_bundle.locked_gems.specs.map(&:full_name)).to include( + "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 - it "installs sorbet-static, which does not provide a pure ruby variant, just fine on truffleruby", :truffleruby do + it "installs sorbet-static, which does not provide a pure ruby variant, just fine", :truffleruby do + skip "does not apply to Windows" if Gem.win_platform? + build_repo2 do - build_gem("sorbet-static", "0.5.6403") {|s| s.platform = "x86_64-linux" } - build_gem("sorbet-static", "0.5.6403") {|s| s.platform = "universal-darwin-20" } + build_gem("sorbet-static", "0.5.6403") {|s| s.platform = Bundler.local_platform } end gemfile <<~G @@ -266,8 +367,7 @@ RSpec.describe "bundle install with specific platforms" do GEM remote: #{file_uri_for(gem_repo2)}/ specs: - sorbet-static (0.5.6403-universal-darwin-20) - sorbet-static (0.5.6403-x86_64-linux) + sorbet-static (0.5.6403-#{Bundler.local_platform}) PLATFORMS ruby @@ -282,6 +382,1004 @@ RSpec.describe "bundle install with specific platforms" do bundle "install --verbose" end + it "does not resolve if the current platform does not match any of available platform specific variants for a top level dependency" do + build_repo4 do + build_gem("sorbet-static", "0.5.6433") {|s| s.platform = "x86_64-linux" } + build_gem("sorbet-static", "0.5.6433") {|s| s.platform = "universal-darwin-20" } + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "sorbet-static", "0.5.6433" + G + + error_message = <<~ERROR.strip + Could not find gem 'sorbet-static (= 0.5.6433)' with platform 'arm64-darwin-21' in rubygems repository #{file_uri_for(gem_repo4)}/, cached gems or installed locally. + + The source contains the following gems matching 'sorbet-static (= 0.5.6433)': + * sorbet-static-0.5.6433-universal-darwin-20 + * sorbet-static-0.5.6433-x86_64-linux + ERROR + + simulate_platform "arm64-darwin-21" do + bundle "lock", raise_on_error: false + end + + expect(err).to include(error_message).once + + # Make sure it doesn't print error twice in verbose mode + + simulate_platform "arm64-darwin-21" do + bundle "lock --verbose", raise_on_error: false + end + + expect(err).to include(error_message).once + end + + it "does not resolve if the current platform does not match any of available platform specific variants for a transitive dependency" do + build_repo4 do + build_gem("sorbet", "0.5.6433") {|s| s.add_dependency "sorbet-static", "= 0.5.6433" } + build_gem("sorbet-static", "0.5.6433") {|s| s.platform = "x86_64-linux" } + build_gem("sorbet-static", "0.5.6433") {|s| s.platform = "universal-darwin-20" } + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "sorbet", "0.5.6433" + G + + error_message = <<~ERROR.strip + Could not find compatible versions + + Because every version of sorbet depends on sorbet-static = 0.5.6433 + and sorbet-static = 0.5.6433 could not be found in rubygems repository #{file_uri_for(gem_repo4)}/, cached gems or installed locally for any resolution platforms (arm64-darwin-21), + sorbet cannot be used. + So, because Gemfile depends on sorbet = 0.5.6433, + version solving has failed. + + The source contains the following gems matching 'sorbet-static (= 0.5.6433)': + * sorbet-static-0.5.6433-universal-darwin-20 + * sorbet-static-0.5.6433-x86_64-linux + ERROR + + simulate_platform "arm64-darwin-21" do + bundle "lock", raise_on_error: false + end + + expect(err).to include(error_message).once + + # Make sure it doesn't print error twice in verbose mode + + simulate_platform "arm64-darwin-21" do + bundle "lock --verbose", raise_on_error: false + end + + expect(err).to include(error_message).once + end + + it "does not generate a lockfile if RUBY platform is forced and some gem has no RUBY variant available" do + build_repo4 do + build_gem("sorbet-static", "0.5.9889") {|s| s.platform = Gem::Platform.local } + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "sorbet-static", "0.5.9889" + G + + bundle "lock", raise_on_error: false, env: { "BUNDLE_FORCE_RUBY_PLATFORM" => "true" } + + expect(err).to include <<~ERROR.rstrip + Could not find gem 'sorbet-static (= 0.5.9889)' with platform 'ruby' in rubygems repository #{file_uri_for(gem_repo4)}/, cached gems or installed locally. + + The source contains the following gems matching 'sorbet-static (= 0.5.9889)': + * sorbet-static-0.5.9889-#{Gem::Platform.local} + ERROR + end + + it "automatically fixes the lockfile if RUBY platform is locked and some gem has no RUBY variant available" do + build_repo4 do + build_gem("sorbet-static-and-runtime", "0.5.10160") do |s| + s.add_runtime_dependency "sorbet", "= 0.5.10160" + s.add_runtime_dependency "sorbet-runtime", "= 0.5.10160" + end + + build_gem("sorbet", "0.5.10160") do |s| + s.add_runtime_dependency "sorbet-static", "= 0.5.10160" + end + + build_gem("sorbet-runtime", "0.5.10160") + + build_gem("sorbet-static", "0.5.10160") do |s| + s.platform = Gem::Platform.local + end + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "sorbet-static-and-runtime" + G + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + sorbet (0.5.10160) + sorbet-static (= 0.5.10160) + sorbet-runtime (0.5.10160) + sorbet-static (0.5.10160-#{Gem::Platform.local}) + sorbet-static-and-runtime (0.5.10160) + sorbet (= 0.5.10160) + sorbet-runtime (= 0.5.10160) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + sorbet-static-and-runtime + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "update" + + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo4, "sorbet", "0.5.10160" + c.checksum gem_repo4, "sorbet-runtime", "0.5.10160" + c.checksum gem_repo4, "sorbet-static", "0.5.10160", Gem::Platform.local + c.checksum gem_repo4, "sorbet-static-and-runtime", "0.5.10160" + end + + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + sorbet (0.5.10160) + sorbet-static (= 0.5.10160) + sorbet-runtime (0.5.10160) + sorbet-static (0.5.10160-#{Gem::Platform.local}) + sorbet-static-and-runtime (0.5.10160) + sorbet (= 0.5.10160) + sorbet-runtime (= 0.5.10160) + + PLATFORMS + #{local_platform} + + DEPENDENCIES + sorbet-static-and-runtime + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "automatically fixes the lockfile if both RUBY platform and a more specific platform are locked, and some gem has no RUBY variant available" do + build_repo4 do + build_gem "nokogiri", "1.12.0" + build_gem "nokogiri", "1.12.0" do |s| + s.platform = "x86_64-darwin" + end + + build_gem "nokogiri", "1.13.0" + build_gem "nokogiri", "1.13.0" do |s| + s.platform = "x86_64-darwin" + end + + build_gem("sorbet-static", "0.5.10601") do |s| + s.platform = "x86_64-darwin" + end + end + + simulate_platform "x86_64-darwin-22" do + install_gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "nokogiri" + gem "sorbet-static" + G + end + + checksums = checksums_section_when_existing do |c| + c.no_checksum "nokogiri", "1.13.0", "x86_64-darwin" + c.no_checksum "sorbet-static", "0.5.10601", "x86_64-darwin" + end + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + nokogiri (1.12.0) + nokogiri (1.12.0-x86_64-darwin) + sorbet-static (0.5.10601-x86_64-darwin) + + PLATFORMS + ruby + x86_64-darwin + + DEPENDENCIES + nokogiri + sorbet-static + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + + simulate_platform "x86_64-darwin-22" do + bundle "update --conservative nokogiri" + end + + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + nokogiri (1.13.0-x86_64-darwin) + sorbet-static (0.5.10601-x86_64-darwin) + + PLATFORMS + x86_64-darwin + + DEPENDENCIES + nokogiri + sorbet-static + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "automatically fixes the lockfile if only RUBY platform is locked and some gem has no RUBY variant available" do + build_repo4 do + build_gem("sorbet-static-and-runtime", "0.5.10160") do |s| + s.add_runtime_dependency "sorbet", "= 0.5.10160" + s.add_runtime_dependency "sorbet-runtime", "= 0.5.10160" + end + + build_gem("sorbet", "0.5.10160") do |s| + s.add_runtime_dependency "sorbet-static", "= 0.5.10160" + end + + build_gem("sorbet-runtime", "0.5.10160") + + build_gem("sorbet-static", "0.5.10160") do |s| + s.platform = Gem::Platform.local + end + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "sorbet-static-and-runtime" + G + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + sorbet (0.5.10160) + sorbet-static (= 0.5.10160) + sorbet-runtime (0.5.10160) + sorbet-static (0.5.10160-#{Gem::Platform.local}) + sorbet-static-and-runtime (0.5.10160) + sorbet (= 0.5.10160) + sorbet-runtime (= 0.5.10160) + + PLATFORMS + ruby + + DEPENDENCIES + sorbet-static-and-runtime + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "update" + + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo4, "sorbet", "0.5.10160" + c.checksum gem_repo4, "sorbet-runtime", "0.5.10160" + c.checksum gem_repo4, "sorbet-static", "0.5.10160", Gem::Platform.local + c.checksum gem_repo4, "sorbet-static-and-runtime", "0.5.10160" + end + + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + sorbet (0.5.10160) + sorbet-static (= 0.5.10160) + sorbet-runtime (0.5.10160) + sorbet-static (0.5.10160-#{Gem::Platform.local}) + sorbet-static-and-runtime (0.5.10160) + sorbet (= 0.5.10160) + sorbet-runtime (= 0.5.10160) + + PLATFORMS + #{local_platform} + + DEPENDENCIES + sorbet-static-and-runtime + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "automatically fixes the lockfile if multiple platforms locked, but no valid versions of direct dependencies for all of them" do + simulate_platform "x86_64-linux" do + build_repo4 do + build_gem "nokogiri", "1.14.0" do |s| + s.platform = "x86_64-linux" + end + build_gem "nokogiri", "1.14.0" do |s| + s.platform = "arm-linux" + end + + build_gem "sorbet-static", "0.5.10696" do |s| + s.platform = "x86_64-linux" + end + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "nokogiri" + gem "sorbet-static" + G + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + nokogiri (1.14.0-arm-linux) + nokogiri (1.14.0-x86_64-linux) + sorbet-static (0.5.10696-x86_64-linux) + + PLATFORMS + aarch64-linux + arm-linux + x86_64-linux + + DEPENDENCIES + nokogiri + sorbet-static + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "update" + + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo4, "nokogiri", "1.14.0", "x86_64-linux" + c.checksum gem_repo4, "sorbet-static", "0.5.10696", "x86_64-linux" + end + + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + nokogiri (1.14.0-x86_64-linux) + sorbet-static (0.5.10696-x86_64-linux) + + PLATFORMS + x86_64-linux + + DEPENDENCIES + nokogiri + sorbet-static + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + it "automatically fixes the lockfile without removing other variants if it's missing platform gems, but they are installed locally" do + simulate_platform "x86_64-darwin-21" do + build_repo4 do + build_gem("sorbet-static", "0.5.10549") do |s| + s.platform = "universal-darwin-20" + end + + build_gem("sorbet-static", "0.5.10549") do |s| + s.platform = "universal-darwin-21" + end + end + + # Make sure sorbet-static-0.5.10549-universal-darwin-21 is installed + install_gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "sorbet-static", "= 0.5.10549" + G + + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo4, "sorbet-static", "0.5.10549", "universal-darwin-20" + c.checksum gem_repo4, "sorbet-static", "0.5.10549", "universal-darwin-21" + end + + # Make sure the lockfile is missing sorbet-static-0.5.10549-universal-darwin-21 + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + sorbet-static (0.5.10549-universal-darwin-20) + + PLATFORMS + x86_64-darwin + + DEPENDENCIES + sorbet-static (= 0.5.10549) + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install" + + checksums.no_checksum "sorbet-static", "0.5.10549", "universal-darwin-21" + + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + sorbet-static (0.5.10549-universal-darwin-20) + sorbet-static (0.5.10549-universal-darwin-21) + + PLATFORMS + x86_64-darwin + + DEPENDENCIES + sorbet-static (= 0.5.10549) + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + it "does not remove ruby if gems for other platforms, and not present in the lockfile, exist in the Gemfile" do + build_repo4 do + build_gem "nokogiri", "1.13.8" + build_gem "nokogiri", "1.13.8" do |s| + s.platform = Gem::Platform.local + end + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "nokogiri" + + gem "tzinfo", "~> 1.2", platform: :#{not_local_tag} + G + + original_lockfile = <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + nokogiri (1.13.8) + nokogiri (1.13.8-#{Gem::Platform.local}) + + PLATFORMS + #{lockfile_platforms("ruby")} + + DEPENDENCIES + nokogiri + tzinfo (~> 1.2) + + CHECKSUMS + + BUNDLED WITH + #{Bundler::VERSION} + L + + lockfile original_lockfile + + bundle "lock --update" + + checksums = checksums_section_when_existing do |c| + c.no_checksum "nokogiri", "1.13.8" + c.no_checksum "nokogiri", "1.13.8", Gem::Platform.local + end + + updated_lockfile = <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + nokogiri (1.13.8) + nokogiri (1.13.8-#{Gem::Platform.local}) + + PLATFORMS + #{lockfile_platforms("ruby")} + + DEPENDENCIES + nokogiri + tzinfo (~> 1.2) + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + + expect(lockfile).to eq(updated_lockfile) + end + + it "does not remove ruby when adding a new gem to the Gemfile" do + build_repo4 do + build_gem "concurrent-ruby", "1.2.2" + build_gem "rack", "3.0.7" + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "concurrent-ruby" + gem "rack" + G + + checksums = checksums_section_when_existing do |c| + c.no_checksum "concurrent-ruby", "1.2.2" + c.no_checksum "rack", "3.0.7" + end + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + concurrent-ruby (1.2.2) + + PLATFORMS + ruby + + DEPENDENCIES + concurrent-ruby + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "lock" + + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + concurrent-ruby (1.2.2) + rack (3.0.7) + + PLATFORMS + #{lockfile_platforms("ruby", generic_local_platform, defaults: [])} + + DEPENDENCIES + concurrent-ruby + rack + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "can fallback to a source gem when platform gems are incompatible with current ruby version" do + setup_multiplatform_gem_with_source_gem + + source = file_uri_for(gem_repo2) + + gemfile <<~G + source "#{source}" + + gem "my-precompiled-gem" + G + + # simulate lockfile which includes both a precompiled gem with: + # - Gem the current platform (with incompatible ruby version) + # - A source gem with compatible ruby version + lockfile <<-L + GEM + remote: #{source}/ + specs: + my-precompiled-gem (3.0.0) + my-precompiled-gem (3.0.0-#{Bundler.local_platform}) + + PLATFORMS + ruby + #{Bundler.local_platform} + + DEPENDENCIES + my-precompiled-gem + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle :install + end + + it "automatically fixes the lockfile if the specific platform is locked and we move to a newer ruby version for which a native package is not available" do + # + # Given an existing application using native gems (e.g., nokogiri) + # And a lockfile generated with a stable ruby version + # When want test the application against ruby-head and `bundle install` + # Then bundler should fall back to the generic ruby platform gem + # + simulate_platform "x86_64-linux" do + build_repo4 do + build_gem "nokogiri", "1.14.0" + build_gem "nokogiri", "1.14.0" do |s| + s.platform = "x86_64-linux" + s.required_ruby_version = "< #{Gem.ruby_version}" + end + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "nokogiri", "1.14.0" + G + + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo4, "nokogiri", "1.14.0", "x86_64-linux" + end + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + nokogiri (1.14.0-x86_64-linux) + + PLATFORMS + x86_64-linux + + DEPENDENCIES + nokogiri (= 1.14.0) + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle :install + + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo4, "nokogiri", "1.14.0" + end + + expect(lockfile).to eq(<<~L) + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + nokogiri (1.14.0) + + PLATFORMS + x86_64-linux + + DEPENDENCIES + nokogiri (= 1.14.0) + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + it "locks specific platforms automatically" do + simulate_platform "x86_64-linux" do + build_repo4 do + build_gem "nokogiri", "1.14.0" + build_gem "nokogiri", "1.14.0" do |s| + s.platform = "x86_64-linux" + end + build_gem "nokogiri", "1.14.0" do |s| + s.platform = "arm-linux" + end + build_gem "nokogiri", "1.14.0" do |s| + s.platform = "x64-mingw32" + end + build_gem "nokogiri", "1.14.0" do |s| + s.platform = "java" + end + + build_gem "sorbet-static", "0.5.10696" do |s| + s.platform = "x86_64-linux" + end + build_gem "sorbet-static", "0.5.10696" do |s| + s.platform = "universal-darwin-22" + end + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "nokogiri" + G + + bundle "lock" + + checksums = checksums_section_when_existing do |c| + c.no_checksum "nokogiri", "1.14.0" + c.no_checksum "nokogiri", "1.14.0", "arm-linux" + c.no_checksum "nokogiri", "1.14.0", "x86_64-linux" + end + + # locks all compatible platforms, excluding Java and Windows + expect(lockfile).to eq(<<~L) + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + nokogiri (1.14.0) + nokogiri (1.14.0-arm-linux) + nokogiri (1.14.0-x86_64-linux) + + PLATFORMS + arm-linux + ruby + x86_64-linux + + DEPENDENCIES + nokogiri + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "nokogiri" + gem "sorbet-static" + G + + FileUtils.rm bundled_app_lock + + bundle "lock" + + checksums.delete "nokogiri", "arm-linux" + checksums.no_checksum "sorbet-static", "0.5.10696", "universal-darwin-22" + checksums.no_checksum "sorbet-static", "0.5.10696", "x86_64-linux" + + # locks only platforms compatible with all gems in the bundle + expect(lockfile).to eq(<<~L) + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + nokogiri (1.14.0) + nokogiri (1.14.0-x86_64-linux) + sorbet-static (0.5.10696-universal-darwin-22) + sorbet-static (0.5.10696-x86_64-linux) + + PLATFORMS + universal-darwin-22 + x86_64-linux + + DEPENDENCIES + nokogiri + sorbet-static + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + it "does not fail when a platform variant is incompatible with the current ruby and another equivalent platform specific variant is part of the resolution", rubygems: ">= 3.3.21" do + build_repo4 do + build_gem "nokogiri", "1.15.5" + + build_gem "nokogiri", "1.15.5" do |s| + s.platform = "x86_64-linux" + s.required_ruby_version = "< #{current_ruby_minor}.dev" + end + + build_gem "sass-embedded", "1.69.5" + + build_gem "sass-embedded", "1.69.5" do |s| + s.platform = "x86_64-linux-gnu" + end + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "nokogiri" + gem "sass-embedded" + G + + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo4, "nokogiri", "1.15.5" + c.no_checksum "sass-embedded", "1.69.5" + c.checksum gem_repo4, "sass-embedded", "1.69.5", "x86_64-linux-gnu" + end + + simulate_platform "x86_64-linux" do + bundle "install --verbose", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + + # locks all compatible platforms, excluding Java and Windows + expect(lockfile).to eq(<<~L) + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + nokogiri (1.15.5) + sass-embedded (1.69.5) + sass-embedded (1.69.5-x86_64-linux-gnu) + + PLATFORMS + ruby + x86_64-linux + + DEPENDENCIES + nokogiri + sass-embedded + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + it "does not add ruby platform gem if it brings extra dependencies not resolved originally" do + build_repo4 do + build_gem "nokogiri", "1.15.5" do |s| + s.add_dependency "mini_portile2", "~> 2.8.2" + end + + build_gem "nokogiri", "1.15.5" do |s| + s.platform = "x86_64-linux" + end + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "nokogiri" + G + + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo4, "nokogiri", "1.15.5", "x86_64-linux" + end + + simulate_platform "x86_64-linux" do + bundle "install --verbose", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + + expect(lockfile).to eq(<<~L) + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + nokogiri (1.15.5-x86_64-linux) + + PLATFORMS + x86_64-linux + + DEPENDENCIES + nokogiri + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + ["x86_64-linux", "x86_64-linux-musl"].each do |host_platform| + describe "on host platform #{host_platform}" do + it "adds current musl platform" do + build_repo4 do + build_gem "rcee_precompiled", "0.5.0" do |s| + s.platform = "x86_64-linux" + end + + build_gem "rcee_precompiled", "0.5.0" do |s| + s.platform = "x86_64-linux-musl" + end + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "rcee_precompiled", "0.5.0" + G + + simulate_platform host_platform do + bundle "lock", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + + expect(lockfile).to eq(<<~L) + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + rcee_precompiled (0.5.0-x86_64-linux) + rcee_precompiled (0.5.0-x86_64-linux-musl) + + PLATFORMS + x86_64-linux + x86_64-linux-musl + + DEPENDENCIES + rcee_precompiled (= 0.5.0) + + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + end + end + + it "adds current musl platform, when there are also gnu variants", rubygems: ">= 3.3.21" do + build_repo4 do + build_gem "rcee_precompiled", "0.5.0" do |s| + s.platform = "x86_64-linux-gnu" + end + + build_gem "rcee_precompiled", "0.5.0" do |s| + s.platform = "x86_64-linux-musl" + end + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "rcee_precompiled", "0.5.0" + G + + simulate_platform "x86_64-linux-musl" do + bundle "lock", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + + expect(lockfile).to eq(<<~L) + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + rcee_precompiled (0.5.0-x86_64-linux-gnu) + rcee_precompiled (0.5.0-x86_64-linux-musl) + + PLATFORMS + x86_64-linux-gnu + x86_64-linux-musl + + DEPENDENCIES + rcee_precompiled (= 0.5.0) + + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + it "does not add current platform if there's an equivalent less specific platform among the ones resolved" do + build_repo4 do + build_gem "rcee_precompiled", "0.5.0" do |s| + s.platform = "universal-darwin" + end + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "rcee_precompiled", "0.5.0" + G + + simulate_platform "x86_64-darwin-15" do + bundle "lock", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + + expect(lockfile).to eq(<<~L) + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + rcee_precompiled (0.5.0-universal-darwin) + + PLATFORMS + universal-darwin + + DEPENDENCIES + rcee_precompiled (= 0.5.0) + + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + private def setup_multiplatform_gem @@ -312,4 +1410,16 @@ RSpec.describe "bundle install with specific platforms" do build_gem("CFPropertyList") end end + + def setup_multiplatform_gem_with_source_gem + build_repo2 do + build_gem("my-precompiled-gem", "3.0.0") + build_gem("my-precompiled-gem", "3.0.0") do |s| + s.platform = Bundler.local_platform + + # purposely unresolvable + s.required_ruby_version = ">= 1000.0.0" + end + end + end end diff --git a/spec/bundler/install/gemfile_spec.rb b/spec/bundler/install/gemfile_spec.rb index 0f8f1ecfa8..158645d3eb 100644 --- a/spec/bundler/install/gemfile_spec.rb +++ b/spec/bundler/install/gemfile_spec.rb @@ -3,7 +3,7 @@ RSpec.describe "bundle install" do context "with duplicated gems" do it "will display a warning" do - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" gem 'rails', '~> 4.0.0' @@ -20,7 +20,7 @@ RSpec.describe "bundle install" do gem 'rack' G - bundle :install, :gemfile => bundled_app("NotGemfile") + bundle :install, gemfile: bundled_app("NotGemfile") # Specify BUNDLE_GEMFILE for `the_bundle` # to retrieve the proper Gemfile @@ -46,8 +46,8 @@ RSpec.describe "bundle install" do end it "uses the gemfile while in a subdirectory" do bundled_app("subdir").mkpath - bundle "install", :dir => bundled_app("subdir") - bundle "list", :dir => bundled_app("subdir") + bundle "install", dir: bundled_app("subdir") + bundle "list", dir: bundled_app("subdir") expect(out).to include("rack (1.0.0)") end @@ -61,12 +61,12 @@ RSpec.describe "bundle install" do gem "rack", :lib => "rack" G - bundle :install, :raise_on_error => false + bundle :install, raise_on_error: false expect(err).to match(/You passed :lib as an option for gem 'rack', but it is invalid/) end end - context "with engine specified in symbol", :jruby do + context "with engine specified in symbol", :jruby_only do it "does not raise any error parsing Gemfile" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" diff --git a/spec/bundler/install/gems/compact_index_spec.rb b/spec/bundler/install/gems/compact_index_spec.rb index 0cee69f702..50add8743b 100644 --- a/spec/bundler/install/gems/compact_index_spec.rb +++ b/spec/bundler/install/gems/compact_index_spec.rb @@ -10,7 +10,7 @@ RSpec.describe "compact index api" do gem "rack" G - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(out).to include("Fetching gem metadata from #{source_uri}") expect(the_bundle).to include_gems "rack 1.0.0" end @@ -21,7 +21,7 @@ RSpec.describe "compact index api" do gem " sinatra" G - bundle :install, :artifice => "compact_index", :raise_on_error => false + bundle :install, artifice: "compact_index", raise_on_error: false expect(err).to include("' sinatra' is not a valid gem name because it contains whitespace.") end @@ -31,7 +31,7 @@ RSpec.describe "compact index api" do gem "rails" G - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(out).to include("Fetching gem metadata from #{source_uri}") expect(the_bundle).to include_gems( "rails 2.3.2", @@ -44,14 +44,14 @@ RSpec.describe "compact index api" do end it "should handle case sensitivity conflicts" do - build_repo4 do + build_repo4(build_compact_index: false) do build_gem "rack", "1.0" do |s| s.add_runtime_dependency("Rack", "0.1") end build_gem "Rack", "0.1" end - install_gemfile <<-G, :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } source "#{source_uri}" gem "rack", "1.0" gem "Rack", "0.1" @@ -69,7 +69,7 @@ RSpec.describe "compact index api" do gem "net-sftp" G - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(the_bundle).to include_gems "net-sftp 1.1.1" end @@ -78,11 +78,11 @@ RSpec.describe "compact index api" do source "#{source_uri}" gem "rack" G - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" bundle "config set --local deployment true" bundle "config set --local path vendor/bundle" - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(out).to include("Fetching gem metadata from #{source_uri}") expect(the_bundle).to include_gems "rack 1.0.0" end @@ -100,7 +100,7 @@ RSpec.describe "compact index api" do end G - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(the_bundle).to include_gems("rails 2.3.2") end @@ -116,10 +116,10 @@ RSpec.describe "compact index api" do gem 'foo', :git => "#{file_uri_for(lib_path("foo-1.0"))}" G - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" bundle "config set --local deployment true" - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(the_bundle).to include_gems("rails 2.3.2") end @@ -131,9 +131,9 @@ RSpec.describe "compact index api" do gem 'foo', :git => "#{file_uri_for(lib_path("foo-1.0"))}" G - bundle "install", :artifice => "compact_index" + bundle "install", artifice: "compact_index" bundle "config set --local deployment true" - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(the_bundle).to include_gems("foo 1.0") end @@ -144,7 +144,7 @@ RSpec.describe "compact index api" do gem "rack" G - bundle :install, :verbose => true, :artifice => "compact_index_forbidden" + bundle :install, verbose: true, artifice: "compact_index_forbidden" expect(out).to include("Fetching gem metadata from #{source_uri}") expect(the_bundle).to include_gems "rack 1.0.0" end @@ -155,14 +155,34 @@ RSpec.describe "compact index api" do gem "rack" G - bundle :install, :verbose => true, :artifice => "compact_index_checksum_mismatch" + bundle :install, verbose: true, artifice: "compact_index_checksum_mismatch" expect(out).to include("Fetching gem metadata from #{source_uri}") - expect(out).to include <<-'WARN' -The checksum of /versions does not match the checksum provided by the server! Something is wrong (local checksum is "\"d41d8cd98f00b204e9800998ecf8427e\"", was expecting "\"123\""). - WARN + expect(out).to include("The checksum of /versions does not match the checksum provided by the server!") + expect(out).to include('Calculated checksums {"sha-256"=>"8KfZiM/fszVkqhP/m5s9lvE6M9xKu4I1bU4Izddp5Ms="} did not match expected {"sha-256"=>"ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD/YfIAFa0="}') expect(the_bundle).to include_gems "rack 1.0.0" end + it "shows proper path when permission errors happen", :permissions do + gemfile <<-G + source "#{source_uri}" + gem "rack" + G + + versions = Pathname.new(Bundler.rubygems.user_home).join( + ".bundle", "cache", "compact_index", + "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "versions" + ) + versions.dirname.mkpath + versions.write("created_at") + FileUtils.chmod("-r", versions) + + bundle :install, artifice: "compact_index", raise_on_error: false + + expect(err).to include( + "There was an error while trying to read from `#{versions}`. It is likely that you need to grant read permissions for that path." + ) + end + it "falls back when the user's home directory does not exist or is not writable" do ENV["HOME"] = tmp("missing_home").to_s @@ -171,7 +191,7 @@ The checksum of /versions does not match the checksum provided by the server! So gem "rack" G - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(out).to include("Fetching gem metadata from #{source_uri}") expect(the_bundle).to include_gems "rack 1.0.0" end @@ -182,11 +202,11 @@ The checksum of /versions does not match the checksum provided by the server! So gem "rack" G - bundle :install, :artifice => "compact_index_host_redirect" + bundle :install, artifice: "compact_index_host_redirect" expect(the_bundle).to include_gems "rack 1.0.0" end - it "handles host redirects without Net::HTTP::Persistent" do + it "handles host redirects without Gem::Net::HTTP::Persistent" do gemfile <<-G source "#{source_uri}" gem "rack" @@ -205,7 +225,7 @@ The checksum of /versions does not match the checksum provided by the server! So H end - bundle :install, :artifice => "compact_index_host_redirect", :requires => [lib_path("disable_net_http_persistent.rb")] + bundle :install, artifice: "compact_index_host_redirect", requires: [lib_path("disable_net_http_persistent.rb")] expect(out).to_not match(/Too many redirects/) expect(the_bundle).to include_gems "rack 1.0.0" end @@ -216,7 +236,7 @@ The checksum of /versions does not match the checksum provided by the server! So gem "rack" G - bundle :install, :artifice => "compact_index_redirects", :raise_on_error => false + bundle :install, artifice: "compact_index_redirects", raise_on_error: false expect(err).to match(/Too many redirects/) end @@ -227,7 +247,7 @@ The checksum of /versions does not match the checksum provided by the server! So gem "rack" G - bundle "install --full-index", :artifice => "compact_index" + bundle "install --full-index", artifice: "compact_index" expect(out).to include("Fetching source index from #{source_uri}") expect(the_bundle).to include_gems "rack 1.0.0" end @@ -238,7 +258,7 @@ The checksum of /versions does not match the checksum provided by the server! So gem "rack" G - bundle "update --full-index", :artifice => "compact_index", :all => true + bundle "update --full-index", artifice: "compact_index", all: true expect(out).to include("Fetching source index from #{source_uri}") expect(the_bundle).to include_gems "rack 1.0.0" end @@ -258,7 +278,7 @@ The checksum of /versions does not match the checksum provided by the server! So s.extensions << "Rakefile" s.write "Rakefile", <<-RUBY task :default do - path = File.expand_path("../lib", __FILE__) + path = File.expand_path("lib", __dir__) FileUtils.mkdir_p(path) File.open("\#{path}/net_build_extensions.rb", "w") do |f| f.puts "NET_BUILD_EXTENSIONS = 'YES'" @@ -268,14 +288,14 @@ The checksum of /versions does not match the checksum provided by the server! So end end - system_gems %w[rack-1.0.0 thin-1.0 net_a-1.0], :gem_repo => gem_repo2 + system_gems %w[rack-1.0.0 thin-1.0 net_a-1.0], gem_repo: gem_repo2 bundle "config set --local path.system true" - ENV["BUNDLER_SPEC_ALL_REQUESTS"] = strip_whitespace(<<-EOS).strip + ENV["BUNDLER_SPEC_ALL_REQUESTS"] = <<~EOS.strip #{source_uri}/versions #{source_uri}/info/rack EOS - install_gemfile <<-G, :artifice => "compact_index", :verbose => true, :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } + install_gemfile <<-G, artifice: "compact_index", verbose: true, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } source "#{source_uri}" gem "rack" G @@ -283,7 +303,7 @@ The checksum of /versions does not match the checksum provided by the server! So expect(last_command.stdboth).not_to include "Double checking" end - it "fetches again when more dependencies are found in subsequent sources", :bundler => "< 3" do + it "fetches again when more dependencies are found in subsequent sources", bundler: "< 3" do build_repo2 do build_gem "back_deps" do |s| s.add_dependency "foo" @@ -297,7 +317,7 @@ The checksum of /versions does not match the checksum provided by the server! So gem "back_deps" G - bundle :install, :artifice => "compact_index_extra" + bundle :install, artifice: "compact_index_extra" expect(the_bundle).to include_gems "back_deps 1.0", "foo 1.0" end @@ -309,7 +329,7 @@ The checksum of /versions does not match the checksum provided by the server! So FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")] end - install_gemfile <<-G, :artifice => "compact_index_extra", :verbose => true + install_gemfile <<-G, artifice: "compact_index_extra", verbose: true source "#{source_uri}" source "#{source_uri}/extra" do gem "back_deps" @@ -324,7 +344,7 @@ The checksum of /versions does not match the checksum provided by the server! So source "#{source_uri}" gem "rack", "1.0.0" G - bundle :install, :artifice => "compact_index_extra_api" + bundle :install, artifice: "compact_index_extra_api" expect(the_bundle).to include_gems "rack 1.0.0" build_repo4 do @@ -338,11 +358,11 @@ The checksum of /versions does not match the checksum provided by the server! So source "#{source_uri}/extra" gem "rack", "1.2" G - bundle :install, :artifice => "compact_index_extra_api" + bundle :install, artifice: "compact_index_extra_api" expect(the_bundle).to include_gems "rack 1.2" end - it "considers all possible versions of dependencies from all api gem sources", :bundler => "< 3" do + it "considers all possible versions of dependencies from all api gem sources", bundler: "< 3" do # In this scenario, the gem "somegem" only exists in repo4. It depends on specific version of activesupport that # exists only in repo1. There happens also be a version of activesupport in repo4, but not the one that version 1.0.0 # of somegem wants. This test makes sure that bundler actually finds version 1.2.3 of active support in the other @@ -360,7 +380,7 @@ The checksum of /versions does not match the checksum provided by the server! So gem 'somegem', '1.0.0' G - bundle :install, :artifice => "compact_index_extra_api" + bundle :install, artifice: "compact_index_extra_api" expect(the_bundle).to include_gems "somegem 1.0.0" expect(the_bundle).to include_gems "activesupport 1.2.3" @@ -381,13 +401,13 @@ The checksum of /versions does not match the checksum provided by the server! So end G - bundle :install, :artifice => "compact_index_extra" + bundle :install, artifice: "compact_index_extra" expect(out).to include("Fetching gem metadata from http://localgemserver.test/") expect(out).to include("Fetching source index from http://localgemserver.test/extra") end - it "does not fetch every spec if the index of gems is large when doing back deps" do + it "does not fetch every spec when doing back deps" do build_repo2 do build_gem "back_deps" do |s| s.add_dependency "foo" @@ -397,9 +417,7 @@ The checksum of /versions does not match the checksum provided by the server! So FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")] end - api_request_limit = low_api_request_limit_for(gem_repo2) - - install_gemfile <<-G, :artifice => "compact_index_extra_missing", :requires => [api_request_limit_hack_file], :env => { "BUNDLER_SPEC_API_REQUEST_LIMIT" => api_request_limit.to_s }.merge(env_for_missing_prerelease_default_gem_activation) + install_gemfile <<-G, artifice: "compact_index_extra_missing" source "#{source_uri}" source "#{source_uri}/extra" do gem "back_deps" @@ -409,7 +427,7 @@ The checksum of /versions does not match the checksum provided by the server! So expect(the_bundle).to include_gems "back_deps 1.0" end - it "does not fetch every spec if the index of gems is large when doing back deps & everything is the compact index" do + it "does not fetch every spec when doing back deps & everything is the compact index" do build_repo4 do build_gem "back_deps" do |s| s.add_dependency "foo" @@ -419,9 +437,7 @@ The checksum of /versions does not match the checksum provided by the server! So FileUtils.rm_rf Dir[gem_repo4("gems/foo-*.gem")] end - api_request_limit = low_api_request_limit_for(gem_repo4) - - install_gemfile <<-G, :artifice => "compact_index_extra_api_missing", :requires => [api_request_limit_hack_file], :env => { "BUNDLER_SPEC_API_REQUEST_LIMIT" => api_request_limit.to_s }.merge(env_for_missing_prerelease_default_gem_activation) + install_gemfile <<-G, artifice: "compact_index_extra_api_missing" source "#{source_uri}" source "#{source_uri}/extra" do gem "back_deps" @@ -438,11 +454,11 @@ The checksum of /versions does not match the checksum provided by the server! So gem 'foo' G - bundle :install, :artifice => "compact_index_api_missing" + bundle :install, artifice: "compact_index_api_missing" expect(the_bundle).to include_gems "foo 1.0" end - it "fetches again when more dependencies are found in subsequent sources using deployment mode", :bundler => "< 3" do + it "fetches again when more dependencies are found in subsequent sources using deployment mode", bundler: "< 3" do build_repo2 do build_gem "back_deps" do |s| s.add_dependency "foo" @@ -456,9 +472,9 @@ The checksum of /versions does not match the checksum provided by the server! So gem "back_deps" G - bundle :install, :artifice => "compact_index_extra" + bundle :install, artifice: "compact_index_extra" bundle "config --set local deployment true" - bundle :install, :artifice => "compact_index_extra" + bundle :install, artifice: "compact_index_extra" expect(the_bundle).to include_gems "back_deps 1.0" end @@ -477,9 +493,9 @@ The checksum of /versions does not match the checksum provided by the server! So end G - bundle :install, :artifice => "compact_index_extra" + bundle :install, artifice: "compact_index_extra" bundle "config set --local deployment true" - bundle :install, :artifice => "compact_index_extra" + bundle :install, artifice: "compact_index_extra" expect(the_bundle).to include_gems "back_deps 1.0" end @@ -496,52 +512,40 @@ The checksum of /versions does not match the checksum provided by the server! So gem "bundler_dep" G - bundle :install, :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } + bundle :install, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } expect(out).to include("Fetching gem metadata from #{source_uri}") end - it "should install when EndpointSpecification has a bin dir owned by root", :sudo => true do - sudo "mkdir -p #{system_gem_path("bin")}" - sudo "chown -R root #{system_gem_path("bin")}" - - gemfile <<-G - source "#{source_uri}" - gem "rails" - G - bundle :install, :artifice => "compact_index" - expect(the_bundle).to include_gems "rails 2.3.2" - end - - it "installs the binstubs", :bundler => "< 3" do + it "installs the binstubs", bundler: "< 3" do gemfile <<-G source "#{source_uri}" gem "rack" G - bundle "install --binstubs", :artifice => "compact_index" + bundle "install --binstubs", artifice: "compact_index" gembin "rackup" expect(out).to eq("1.0.0") end - it "installs the bins when using --path and uses autoclean", :bundler => "< 3" do + it "installs the bins when using --path and uses autoclean", bundler: "< 3" do gemfile <<-G source "#{source_uri}" gem "rack" G - bundle "install --path vendor/bundle", :artifice => "compact_index" + bundle "install --path vendor/bundle", artifice: "compact_index" expect(vendored_gems("bin/rackup")).to exist end - it "installs the bins when using --path and uses bundle clean", :bundler => "< 3" do + it "installs the bins when using --path and uses bundle clean", bundler: "< 3" do gemfile <<-G source "#{source_uri}" gem "rack" G - bundle "install --path vendor/bundle --no-clean", :artifice => "compact_index" + bundle "install --path vendor/bundle --no-clean", artifice: "compact_index" expect(vendored_gems("bin/rackup")).to exist end @@ -552,7 +556,7 @@ The checksum of /versions does not match the checksum provided by the server! So gem 'rack-obama' G - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(out).to include("Post-install message from rack:") end @@ -562,7 +566,7 @@ The checksum of /versions does not match the checksum provided by the server! So gem 'rack_middleware' G - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(out).to include("Post-install message from rack:") expect(out).to include("Rack's post install message") end @@ -571,7 +575,7 @@ The checksum of /versions does not match the checksum provided by the server! So let(:user) { "user" } let(:password) { "pass" } let(:basic_auth_source_uri) do - uri = Bundler::URI.parse(source_uri) + uri = Gem::URI.parse(source_uri) uri.user = user uri.password = password @@ -584,7 +588,7 @@ The checksum of /versions does not match the checksum provided by the server! So gem "rack" G - bundle :install, :artifice => "compact_index_basic_authentication" + bundle :install, artifice: "compact_index_basic_authentication" expect(out).not_to include("#{user}:#{password}") expect(the_bundle).to include_gems "rack 1.0.0" end @@ -595,19 +599,19 @@ The checksum of /versions does not match the checksum provided by the server! So gem "rack" G - bundle :install, :verbose => true, :artifice => "compact_index_basic_authentication" + bundle :install, verbose: true, artifice: "compact_index_basic_authentication" expect(out).not_to include("#{user}:#{password}") expect(the_bundle).to include_gems "rack 1.0.0" end - it "strips http basic auth creds when warning about ambiguous sources", :bundler => "< 3" do + it "strips http basic auth creds when warning about ambiguous sources", bundler: "< 3" do gemfile <<-G source "#{basic_auth_source_uri}" source "#{file_uri_for(gem_repo1)}" gem "rack" G - bundle :install, :artifice => "compact_index_basic_authentication" + bundle :install, artifice: "compact_index_basic_authentication" expect(err).to include("Warning: the gem 'rack' was found in multiple sources.") expect(err).not_to include("#{user}:#{password}") expect(the_bundle).to include_gems "rack 1.0.0" @@ -619,7 +623,7 @@ The checksum of /versions does not match the checksum provided by the server! So gem "rack" G - bundle :install, :artifice => "compact_index_creds_diff_host" + bundle :install, artifice: "compact_index_creds_diff_host" expect(the_bundle).to include_gems "rack 1.0.0" end @@ -634,7 +638,7 @@ The checksum of /versions does not match the checksum provided by the server! So it "reads authentication details by host name from bundle config" do bundle "config set #{source_hostname} #{user}:#{password}" - bundle :install, :artifice => "compact_index_strict_basic_authentication" + bundle :install, artifice: "compact_index_strict_basic_authentication" expect(out).to include("Fetching gem metadata from #{source_uri}") expect(the_bundle).to include_gems "rack 1.0.0" @@ -644,7 +648,7 @@ The checksum of /versions does not match the checksum provided by the server! So # The trailing slash is necessary here; Fetcher canonicalizes the URI. bundle "config set #{source_uri}/ #{user}:#{password}" - bundle :install, :artifice => "compact_index_strict_basic_authentication" + bundle :install, artifice: "compact_index_strict_basic_authentication" expect(out).to include("Fetching gem metadata from #{source_uri}") expect(the_bundle).to include_gems "rack 1.0.0" @@ -652,7 +656,7 @@ The checksum of /versions does not match the checksum provided by the server! So it "should use the API" do bundle "config set #{source_hostname} #{user}:#{password}" - bundle :install, :artifice => "compact_index_strict_basic_authentication" + bundle :install, artifice: "compact_index_strict_basic_authentication" expect(out).to include("Fetching gem metadata from #{source_uri}") expect(the_bundle).to include_gems "rack 1.0.0" end @@ -665,20 +669,29 @@ The checksum of /versions does not match the checksum provided by the server! So bundle "config set #{source_hostname} otheruser:wrong" - bundle :install, :artifice => "compact_index_strict_basic_authentication" + bundle :install, artifice: "compact_index_strict_basic_authentication" expect(the_bundle).to include_gems "rack 1.0.0" end it "shows instructions if auth is not provided for the source" do - bundle :install, :artifice => "compact_index_strict_basic_authentication", :raise_on_error => false + bundle :install, artifice: "compact_index_strict_basic_authentication", raise_on_error: false expect(err).to include("bundle config set --global #{source_hostname} username:password") end it "fails if authentication has already been provided, but failed" do bundle "config set #{source_hostname} #{user}:wrong" - bundle :install, :artifice => "compact_index_strict_basic_authentication", :raise_on_error => false + bundle :install, artifice: "compact_index_strict_basic_authentication", raise_on_error: false + expect(err).to include("Bad username or password") + end + + it "does not fallback to old dependency API if bad authentication is provided" do + bundle "config set #{source_hostname} #{user}:wrong" + + bundle :install, artifice: "compact_index_strict_basic_authentication", raise_on_error: false, verbose: true expect(err).to include("Bad username or password") + expect(out).to include("HTTP 401 Unauthorized http://user@localgemserver.test/versions") + expect(out).not_to include("HTTP 401 Unauthorized http://user@localgemserver.test/api/v1/dependencies") end end @@ -691,7 +704,7 @@ The checksum of /versions does not match the checksum provided by the server! So gem "rack" G - bundle :install, :artifice => "compact_index_basic_authentication" + bundle :install, artifice: "compact_index_basic_authentication" expect(the_bundle).to include_gems "rack 1.0.0" end end @@ -716,7 +729,7 @@ The checksum of /versions does not match the checksum provided by the server! So gem "rack" G - bundle :install, :env => { "RUBYOPT" => opt_add("-I#{bundled_app("broken_ssl")}", ENV["RUBYOPT"]) }, :raise_on_error => false + bundle :install, env: { "RUBYOPT" => opt_add("-I#{bundled_app("broken_ssl")}", ENV["RUBYOPT"]) }, raise_on_error: false expect(err).to include("OpenSSL") end end @@ -726,7 +739,7 @@ The checksum of /versions does not match the checksum provided by the server! So # Install a monkeypatch that reproduces the effects of openssl raising # a certificate validation error when RubyGems tries to connect. gemfile <<-G - class Net::HTTP + class Gem::Net::HTTP def start raise OpenSSL::SSL::SSLError, "certificate verify failed" end @@ -736,7 +749,7 @@ The checksum of /versions does not match the checksum provided by the server! So gem "rack" G - bundle :install, :raise_on_error => false + bundle :install, raise_on_error: false expect(err).to match(/could not verify the SSL certificate/i) end end @@ -744,7 +757,7 @@ The checksum of /versions does not match the checksum provided by the server! So context ".gemrc with sources is present" do it "uses other sources declared in the Gemfile" do File.open(home(".gemrc"), "w") do |file| - file.puts({ :sources => ["https://rubygems.org"] }.to_yaml) + file.puts({ sources: ["https://rubygems.org"] }.to_yaml) end begin @@ -753,23 +766,30 @@ The checksum of /versions does not match the checksum provided by the server! So gem 'rack' G - bundle :install, :artifice => "compact_index_forbidden" + bundle :install, artifice: "compact_index_forbidden" ensure home(".gemrc").rmtree end end end - it "performs partial update with a non-empty range" do - skip "HTTP_RANGE not set" if Gem.win_platform? + it "performs update with etag not-modified" do + versions_etag = Pathname.new(Bundler.rubygems.user_home).join( + ".bundle", "cache", "compact_index", + "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "versions.etag" + ) + expect(versions_etag.file?).to eq(false) gemfile <<-G source "#{source_uri}" gem 'rack', '0.9.1' G - # Initial install creates the cached versions file - bundle :install, :artifice => "compact_index" + # Initial install creates the cached versions file and etag file + bundle :install, artifice: "compact_index" + + expect(versions_etag.file?).to eq(true) + previous_content = versions_etag.binread # Update the Gemfile so we can check subsequent install was successful gemfile <<-G @@ -777,8 +797,59 @@ The checksum of /versions does not match the checksum provided by the server! So gem 'rack', '1.0.0' G - # Second install should make only a partial request to /versions - bundle :install, :artifice => "compact_index_partial_update" + # Second install should match etag + bundle :install, artifice: "compact_index_etag_match" + + expect(versions_etag.binread).to eq(previous_content) + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "performs full update when range is ignored" do + gemfile <<-G + source "#{source_uri}" + gem 'rack', '0.9.1' + G + + # Initial install creates the cached versions file and etag file + bundle :install, artifice: "compact_index" + + gemfile <<-G + source "#{source_uri}" + gem 'rack', '1.0.0' + G + + versions = Pathname.new(Bundler.rubygems.user_home).join( + ".bundle", "cache", "compact_index", + "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "versions" + ) + # Modify the cached file. The ranged request will be based on this but, + # in this test, the range is ignored so this gets overwritten, allowing install. + versions.write "ruining this file" + + bundle :install, artifice: "compact_index_range_ignored" + + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "performs partial update with a non-empty range" do + build_repo4 do + build_gem "rack", "0.9.1" + end + + # Initial install creates the cached versions file + install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + source "#{source_uri}" + gem 'rack', '0.9.1' + G + + update_repo4 do + build_gem "rack", "1.0.0" + end + + install_gemfile <<-G, artifice: "compact_index_partial_update", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + source "#{source_uri}" + gem 'rack', '1.0.0' + G expect(the_bundle).to include_gems "rack 1.0.0" end @@ -789,24 +860,26 @@ The checksum of /versions does not match the checksum provided by the server! So gem 'rack' G - # Create an empty file to trigger a partial download - versions = File.join(Bundler.rubygems.user_home, ".bundle", "cache", "compact_index", - "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "versions") - FileUtils.mkdir_p(File.dirname(versions)) - FileUtils.touch(versions) + # Create a partial cache versions file + versions = Pathname.new(Bundler.rubygems.user_home).join( + ".bundle", "cache", "compact_index", + "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "versions" + ) + versions.dirname.mkpath + versions.write("created_at") - bundle :install, :artifice => "compact_index_concurrent_download" + bundle :install, artifice: "compact_index_concurrent_download" - expect(File.read(versions)).to start_with("created_at") + expect(versions.read).to start_with("created_at") expect(the_bundle).to include_gems "rack 1.0.0" end - it "performs full update if server endpoints serve partial content responses but don't have incremental content and provide no Etag" do + it "performs a partial update that fails digest check, then a full update" do build_repo4 do build_gem "rack", "0.9.1" end - install_gemfile <<-G, :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } source "#{source_uri}" gem 'rack', '0.9.1' G @@ -815,7 +888,29 @@ The checksum of /versions does not match the checksum provided by the server! So build_gem "rack", "1.0.0" end - install_gemfile <<-G, :artifice => "compact_index_partial_update_no_etag_not_incremental", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + install_gemfile <<-G, artifice: "compact_index_partial_update_bad_digest", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + source "#{source_uri}" + gem 'rack', '1.0.0' + G + + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "performs full update if server endpoints serve partial content responses but don't have incremental content and provide no digest" do + build_repo4 do + build_gem "rack", "0.9.1" + end + + install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + source "#{source_uri}" + gem 'rack', '0.9.1' + G + + update_repo4 do + build_gem "rack", "1.0.0" + end + + install_gemfile <<-G, artifice: "compact_index_partial_update_no_digest_not_incremental", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } source "#{source_uri}" gem 'rack', '1.0.0' G @@ -829,15 +924,19 @@ The checksum of /versions does not match the checksum provided by the server! So gem 'rack', '0.9.1' G - rake_info_path = File.join(Bundler.rubygems.user_home, ".bundle", "cache", "compact_index", - "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "info", "rack") + bundle :install, artifice: "compact_index" - bundle :install, :artifice => "compact_index" + # We must remove the etag so that we don't ignore the range and get a 304 Not Modified. + rake_info_etag_path = File.join(Bundler.rubygems.user_home, ".bundle", "cache", "compact_index", + "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "info-etags", "rack-11690b09f16021ff06a6857d784a1870") + File.unlink(rake_info_etag_path) if File.exist?(rake_info_etag_path) + rake_info_path = File.join(Bundler.rubygems.user_home, ".bundle", "cache", "compact_index", + "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "info", "rack") expected_rack_info_content = File.read(rake_info_path) - # Modify the cache files. We expect them to be reset to the normal ones when we re-run :install - File.open(rake_info_path, "w") {|f| f << (expected_rack_info_content + "this is different") } + # Modify the cache files to make the range not satisfiable + File.open(rake_info_path, "a") {|f| f << "0.9.2 |checksum:c55b525b421fd833a93171ad3d7f04528ca8e87d99ac273f8933038942a5888c" } # Update the Gemfile so the next install does its normal things gemfile <<-G @@ -847,7 +946,7 @@ The checksum of /versions does not match the checksum provided by the server! So # The cache files now being longer means the requested range is going to be not satisfiable # Bundler must end up requesting the whole file to fix things up. - bundle :install, :artifice => "compact_index_range_not_satisfiable" + bundle :install, artifice: "compact_index_range_not_satisfiable" resulting_rack_info_content = File.read(rake_info_path) @@ -855,7 +954,7 @@ The checksum of /versions does not match the checksum provided by the server! So end it "fails gracefully when the source URI has an invalid scheme" do - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "htps://rubygems.org" gem "rack" G @@ -866,38 +965,76 @@ The checksum of /versions does not match the checksum provided by the server! So end describe "checksum validation" do + before do + lockfile <<-L + GEM + remote: #{source_uri} + specs: + rack (1.0.0) + + PLATFORMS + ruby + + DEPENDENCIES + #{checksums_section} + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "handles checksums from the server in base64" do + api_checksum = checksum_digest(gem_repo1, "rack", "1.0.0") + rack_checksum = [[api_checksum].pack("H*")].pack("m0") + install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_RACK_CHECKSUM" => rack_checksum } + source "#{source_uri}" + gem "rack" + G + + expect(out).to include("Fetching gem metadata from #{source_uri}") + expect(the_bundle).to include_gems("rack 1.0.0") + end + it "raises when the checksum does not match" do - install_gemfile <<-G, :artifice => "compact_index_wrong_gem_checksum", :raise_on_error => false + install_gemfile <<-G, artifice: "compact_index_wrong_gem_checksum", raise_on_error: false source "#{source_uri}" gem "rack" G - expect(exitstatus).to eq(19) - expect(err). - to include("Bundler cannot continue installing rack (1.0.0)."). - and include("The checksum for the downloaded `rack-1.0.0.gem` does not match the checksum given by the server."). - and include("This means the contents of the downloaded gem is different from what was uploaded to the server, and could be a potential security issue."). - and include("To resolve this issue:"). - and include("1. delete the downloaded gem located at: `#{default_bundle_path}/gems/rack-1.0.0/rack-1.0.0.gem`"). - and include("2. run `bundle install`"). - and include("If you wish to continue installing the downloaded gem, and are certain it does not pose a security issue despite the mismatching checksum, do the following:"). - and include("1. run `bundle config set --local disable_checksum_validation true` to turn off checksum verification"). - and include("2. run `bundle install`"). - and match(/\(More info: The expected SHA256 checksum was "#{"ab" * 22}", but the checksum for the downloaded gem was ".+?"\.\)/) + gem_path = if Bundler.feature_flag.global_gem_cache? + default_cache_path.dirname.join("cache", "gems", "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "rack-1.0.0.gem") + else + default_cache_path.dirname.join("rack-1.0.0.gem") + end + + expect(exitstatus).to eq(37) + expect(err).to eq <<~E.strip + Bundler found mismatched checksums. This is a potential security risk. + rack (1.0.0) sha256=2222222222222222222222222222222222222222222222222222222222222222 + from the API at http://localgemserver.test/ + #{checksum_to_lock(gem_repo1, "rack", "1.0.0")} + from the gem at #{gem_path} + + If you trust the API at http://localgemserver.test/, to resolve this issue you can: + 1. remove the gem at #{gem_path} + 2. run `bundle install` + + To ignore checksum security warnings, disable checksum validation with + `bundle config set --local disable_checksum_validation true` + E 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!", "DEBUG" => "1" }, :verbose => true, :raise_on_error => false + install_gemfile <<-G, artifice: "compact_index_wrong_gem_checksum", env: { "BUNDLER_SPEC_RACK_CHECKSUM" => "checksum!", "DEBUG" => "1" }, verbose: true, raise_on_error: false source "#{source_uri}" gem "rack" G - expect(exitstatus).to eq(5) - expect(err).to include("The given checksum for rack-1.0.0 (\"checksum!\") is not a valid SHA256 hexdigest nor base64digest") + expect(exitstatus).to eq(14) + expect(err).to include('Invalid checksum for rack-0.9.1: "checksum!" is not a valid SHA256 hex or base64 digest') end it "does not raise when disable_checksum_validation is set" do bundle "config set disable_checksum_validation true" - install_gemfile <<-G, :artifice => "compact_index_wrong_gem_checksum" + install_gemfile <<-G, artifice: "compact_index_wrong_gem_checksum" source "#{source_uri}" gem "rack" G @@ -905,7 +1042,7 @@ The checksum of /versions does not match the checksum provided by the server! So end it "works when cache dir is world-writable" do - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" File.umask(0000) source "#{source_uri}" gem "rack" @@ -913,23 +1050,24 @@ The checksum of /versions does not match the checksum provided by the server! So end it "doesn't explode when the API dependencies are wrong" do - install_gemfile <<-G, :artifice => "compact_index_wrong_dependencies", :env => { "DEBUG" => "true" }, :raise_on_error => false + install_gemfile <<-G, artifice: "compact_index_wrong_dependencies", env: { "DEBUG" => "true" }, raise_on_error: false source "#{source_uri}" gem "rails" G - deps = [Gem::Dependency.new("rake", "= 13.0.1"), + deps = [Gem::Dependency.new("rake", "= #{rake_version}"), Gem::Dependency.new("actionpack", "= 2.3.2"), Gem::Dependency.new("activerecord", "= 2.3.2"), Gem::Dependency.new("actionmailer", "= 2.3.2"), Gem::Dependency.new("activeresource", "= 2.3.2")] - expect(out).to include(<<-E.strip).and include("rails-2.3.2 from rubygems remote at #{source_uri}/ has either corrupted API or lockfile dependencies") + expect(out).to include("rails-2.3.2 from rubygems remote at #{source_uri}/ has either corrupted API or lockfile dependencies") + expect(err).to include(<<-E.strip) Bundler::APIResponseMismatchError: Downloading rails-2.3.2 revealed dependencies not in the API or the lockfile (#{deps.map(&:to_s).join(", ")}). -Either installing with `--full-index` or running `bundle update rails` should fix the problem. +Running `bundle update rails` should fix the problem. E end it "does not duplicate specs in the lockfile when updating and a dependency is not installed" do - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "#{file_uri_for(gem_repo1)}" source "#{source_uri}" do gem "rails" @@ -937,7 +1075,8 @@ Either installing with `--full-index` or running `bundle update rails` should fi end G gem_command "uninstall activemerchant" - bundle "update rails", :artifice => "compact_index" - expect(lockfile.scan(/activemerchant \(/).size).to eq(1) + bundle "update rails", artifice: "compact_index" + count = lockfile.match?("CHECKSUMS") ? 2 : 1 # Once in the specs, and once in CHECKSUMS + expect(lockfile.scan(/activemerchant \(/).size).to eq(count) end end diff --git a/spec/bundler/install/gems/dependency_api_spec.rb b/spec/bundler/install/gems/dependency_api_spec.rb index 9738a75474..35468b3a1c 100644 --- a/spec/bundler/install/gems/dependency_api_spec.rb +++ b/spec/bundler/install/gems/dependency_api_spec.rb @@ -10,7 +10,7 @@ RSpec.describe "gemcutter's dependency API" do gem "rack" G - bundle :install, :artifice => "endpoint" + bundle :install, artifice: "endpoint" expect(out).to include("Fetching gem metadata from #{source_uri}") expect(the_bundle).to include_gems "rack 1.0.0" end @@ -21,7 +21,7 @@ RSpec.describe "gemcutter's dependency API" do gem " sinatra" G - bundle :install, :artifice => "endpoint", :raise_on_error => false + bundle :install, artifice: "endpoint", raise_on_error: false expect(err).to include("' sinatra' is not a valid gem name because it contains whitespace.") end @@ -31,7 +31,7 @@ RSpec.describe "gemcutter's dependency API" do gem "rails" G - bundle :install, :artifice => "endpoint" + bundle :install, artifice: "endpoint" expect(out).to include("Fetching gem metadata from #{source_uri}/...") expect(the_bundle).to include_gems( "rails 2.3.2", @@ -49,7 +49,7 @@ RSpec.describe "gemcutter's dependency API" do gem "net-sftp" G - bundle :install, :artifice => "endpoint" + bundle :install, artifice: "endpoint" expect(the_bundle).to include_gems "net-sftp 1.1.1" end @@ -58,11 +58,11 @@ RSpec.describe "gemcutter's dependency API" do source "#{source_uri}" gem "rack" G - bundle :install, :artifice => "endpoint" + bundle :install, artifice: "endpoint" bundle "config set --local deployment true" bundle "config set --local path vendor/bundle" - bundle :install, :artifice => "endpoint" + bundle :install, artifice: "endpoint" expect(out).to include("Fetching gem metadata from #{source_uri}") expect(the_bundle).to include_gems "rack 1.0.0" end @@ -80,7 +80,7 @@ RSpec.describe "gemcutter's dependency API" do end G - bundle :install, :artifice => "endpoint" + bundle :install, artifice: "endpoint" expect(the_bundle).to include_gems("rails 2.3.2") end @@ -96,10 +96,10 @@ RSpec.describe "gemcutter's dependency API" do gem 'foo', :git => "#{file_uri_for(lib_path("foo-1.0"))}" G - bundle :install, :artifice => "endpoint" + bundle :install, artifice: "endpoint" bundle "config set --local deployment true" - bundle :install, :artifice => "endpoint" + bundle :install, artifice: "endpoint" expect(the_bundle).to include_gems("rails 2.3.2") end @@ -111,15 +111,15 @@ RSpec.describe "gemcutter's dependency API" do gem 'foo', :git => "#{file_uri_for(lib_path("foo-1.0"))}" G - bundle "install", :artifice => "endpoint" + bundle "install", artifice: "endpoint" bundle "config set --local deployment true" - bundle :install, :artifice => "endpoint" + bundle :install, artifice: "endpoint" expect(the_bundle).to include_gems("foo 1.0") end it "falls back when the API errors out" do - simulate_platform mswin + simulate_platform x86_mswin32 build_repo2 do # The rcov gem is platform mswin32, but has no arch @@ -134,7 +134,7 @@ RSpec.describe "gemcutter's dependency API" do gem "rcov" G - bundle :install, :artifice => "windows", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } + bundle :install, artifice: "windows", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } expect(out).to include("Fetching source index from #{source_uri}") expect(the_bundle).to include_gems "rcov 1.0.0" end @@ -150,7 +150,7 @@ RSpec.describe "gemcutter's dependency API" do gem "rack" gem "rails" G - bundle :install, :artifice => "endpoint_fallback" + bundle :install, artifice: "endpoint_fallback" expect(out).to include("Fetching source index from #{source_uri}") expect(the_bundle).to include_gems( @@ -171,7 +171,7 @@ RSpec.describe "gemcutter's dependency API" do gem "rack" G - bundle :install, :verbose => true, :artifice => "endpoint_marshal_fail" + bundle :install, verbose: true, artifice: "endpoint_marshal_fail" expect(out).to include("could not fetch from the dependency API, trying the full index") expect(the_bundle).to include_gems "rack 1.0.0" end @@ -182,7 +182,7 @@ RSpec.describe "gemcutter's dependency API" do gem "rack" G - bundle :install, :verbose => true, :artifice => "endpoint_api_forbidden" + bundle :install, verbose: true, artifice: "endpoint_api_forbidden" expect(out).to include("Fetching source index from #{source_uri}") expect(the_bundle).to include_gems "rack 1.0.0" end @@ -193,11 +193,11 @@ RSpec.describe "gemcutter's dependency API" do gem "rack" G - bundle :install, :artifice => "endpoint_host_redirect" + bundle :install, artifice: "endpoint_host_redirect" expect(the_bundle).to include_gems "rack 1.0.0" end - it "handles host redirects without Net::HTTP::Persistent" do + it "handles host redirects without Gem::Net::HTTP::Persistent" do gemfile <<-G source "#{source_uri}" gem "rack" @@ -216,7 +216,7 @@ RSpec.describe "gemcutter's dependency API" do H end - bundle :install, :artifice => "endpoint_host_redirect", :requires => [lib_path("disable_net_http_persistent.rb")] + bundle :install, artifice: "endpoint_host_redirect", requires: [lib_path("disable_net_http_persistent.rb")] expect(out).to_not match(/Too many redirects/) expect(the_bundle).to include_gems "rack 1.0.0" end @@ -227,7 +227,7 @@ RSpec.describe "gemcutter's dependency API" do gem "rack" G - bundle :install, :artifice => "endpoint_redirect", :raise_on_error => false + bundle :install, artifice: "endpoint_redirect", raise_on_error: false expect(err).to match(/Too many redirects/) end @@ -238,7 +238,7 @@ RSpec.describe "gemcutter's dependency API" do gem "rack" G - bundle "install --full-index", :artifice => "endpoint" + bundle "install --full-index", artifice: "endpoint" expect(out).to include("Fetching source index from #{source_uri}") expect(the_bundle).to include_gems "rack 1.0.0" end @@ -249,13 +249,13 @@ RSpec.describe "gemcutter's dependency API" do gem "rack" G - bundle "update --full-index", :artifice => "endpoint", :all => true + bundle "update --full-index", artifice: "endpoint", all: true expect(out).to include("Fetching source index from #{source_uri}") expect(the_bundle).to include_gems "rack 1.0.0" end end - it "fetches again when more dependencies are found in subsequent sources", :bundler => "< 3" do + it "fetches again when more dependencies are found in subsequent sources", bundler: "< 3" do build_repo2 do build_gem "back_deps" do |s| s.add_dependency "foo" @@ -269,7 +269,7 @@ RSpec.describe "gemcutter's dependency API" do gem "back_deps" G - bundle :install, :artifice => "endpoint_extra" + bundle :install, artifice: "endpoint_extra" expect(the_bundle).to include_gems "back_deps 1.0", "foo 1.0" end @@ -288,7 +288,7 @@ RSpec.describe "gemcutter's dependency API" do end G - bundle :install, :artifice => "endpoint_extra" + bundle :install, artifice: "endpoint_extra" expect(the_bundle).to include_gems "back_deps 1.0", "foo 1.0" end @@ -297,7 +297,7 @@ RSpec.describe "gemcutter's dependency API" do source "#{source_uri}" gem "rack", "1.0.0" G - bundle :install, :artifice => "endpoint_extra_api" + bundle :install, artifice: "endpoint_extra_api" build_repo4 do build_gem "rack", "1.2" do |s| @@ -310,11 +310,11 @@ RSpec.describe "gemcutter's dependency API" do source "#{source_uri}/extra" gem "rack", "1.2" G - bundle :install, :artifice => "endpoint_extra_api" + bundle :install, artifice: "endpoint_extra_api" expect(the_bundle).to include_gems "rack 1.2" end - it "considers all possible versions of dependencies from all api gem sources", :bundler => "< 3" do + it "considers all possible versions of dependencies from all api gem sources", bundler: "< 3" do # In this scenario, the gem "somegem" only exists in repo4. It depends on specific version of activesupport that # exists only in repo1. There happens also be a version of activesupport in repo4, but not the one that version 1.0.0 # of somegem wants. This test makes sure that bundler actually finds version 1.2.3 of active support in the other @@ -332,7 +332,7 @@ RSpec.describe "gemcutter's dependency API" do gem 'somegem', '1.0.0' G - bundle :install, :artifice => "endpoint_extra_api" + bundle :install, artifice: "endpoint_extra_api" expect(the_bundle).to include_gems "somegem 1.0.0" expect(the_bundle).to include_gems "activesupport 1.2.3" @@ -353,13 +353,13 @@ RSpec.describe "gemcutter's dependency API" do end G - bundle :install, :artifice => "endpoint_extra" + bundle :install, artifice: "endpoint_extra" expect(out).to include("Fetching gem metadata from http://localgemserver.test/.") expect(out).to include("Fetching source index from http://localgemserver.test/extra") end - it "does not fetch every spec if the index of gems is large when doing back deps", :bundler => "< 3" do + it "does not fetch every spec when doing back deps", bundler: "< 3" do build_repo2 do build_gem "back_deps" do |s| s.add_dependency "foo" @@ -369,9 +369,7 @@ RSpec.describe "gemcutter's dependency API" do FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")] end - api_request_limit = low_api_request_limit_for(gem_repo2) - - install_gemfile <<-G, :artifice => "endpoint_extra_missing", :requires => [api_request_limit_hack_file], :env => { "BUNDLER_SPEC_API_REQUEST_LIMIT" => api_request_limit.to_s }.merge(env_for_missing_prerelease_default_gem_activation) + install_gemfile <<-G, artifice: "endpoint_extra_missing" source "#{source_uri}" source "#{source_uri}/extra" gem "back_deps" @@ -380,7 +378,7 @@ RSpec.describe "gemcutter's dependency API" do expect(the_bundle).to include_gems "back_deps 1.0" end - it "does not fetch every spec if the index of gems is large when doing back deps using blocks" do + it "does not fetch every spec when doing back deps using blocks" do build_repo2 do build_gem "back_deps" do |s| s.add_dependency "foo" @@ -390,9 +388,7 @@ RSpec.describe "gemcutter's dependency API" do FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")] end - api_request_limit = low_api_request_limit_for(gem_repo2) - - install_gemfile <<-G, :artifice => "endpoint_extra_missing", :requires => [api_request_limit_hack_file], :env => { "BUNDLER_SPEC_API_REQUEST_LIMIT" => api_request_limit.to_s }.merge(env_for_missing_prerelease_default_gem_activation) + install_gemfile <<-G, artifice: "endpoint_extra_missing" source "#{source_uri}" source "#{source_uri}/extra" do gem "back_deps" @@ -402,18 +398,7 @@ RSpec.describe "gemcutter's dependency API" do expect(the_bundle).to include_gems "back_deps 1.0" end - it "uses the endpoint if all sources support it" do - gemfile <<-G - source "#{source_uri}" - - gem 'foo' - G - - bundle :install, :artifice => "endpoint_api_missing" - expect(the_bundle).to include_gems "foo 1.0" - end - - it "fetches again when more dependencies are found in subsequent sources using deployment mode", :bundler => "< 3" do + it "fetches again when more dependencies are found in subsequent sources using deployment mode", bundler: "< 3" do build_repo2 do build_gem "back_deps" do |s| s.add_dependency "foo" @@ -427,9 +412,9 @@ RSpec.describe "gemcutter's dependency API" do gem "back_deps" G - bundle :install, :artifice => "endpoint_extra" + bundle :install, artifice: "endpoint_extra" bundle "config set --local deployment true" - bundle :install, :artifice => "endpoint_extra" + bundle :install, artifice: "endpoint_extra" expect(the_bundle).to include_gems "back_deps 1.0" end @@ -448,12 +433,28 @@ RSpec.describe "gemcutter's dependency API" do end G - bundle :install, :artifice => "endpoint_extra" + bundle :install, artifice: "endpoint_extra" bundle "config set --local deployment true" - bundle "install", :artifice => "endpoint_extra" + bundle "install", artifice: "endpoint_extra" expect(the_bundle).to include_gems "back_deps 1.0" end + it "does not fetch all marshaled specs" do + build_repo2 do + build_gem "foo", "1.0" + build_gem "foo", "2.0" + end + + install_gemfile <<-G, artifice: "endpoint", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s }, verbose: true + source "#{source_uri}" + + gem "foo" + G + + expect(out).to include("foo-2.0.gemspec.rz") + expect(out).not_to include("foo-1.0.gemspec.rz") + end + it "does not refetch if the only unmet dependency is bundler" do build_repo2 do build_gem "bundler_dep" do |s| @@ -467,52 +468,40 @@ RSpec.describe "gemcutter's dependency API" do gem "bundler_dep" G - bundle :install, :artifice => "endpoint", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } + bundle :install, artifice: "endpoint", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } expect(out).to include("Fetching gem metadata from #{source_uri}") end - it "should install when EndpointSpecification has a bin dir owned by root", :sudo => true do - sudo "mkdir -p #{system_gem_path("bin")}" - sudo "chown -R root #{system_gem_path("bin")}" - - gemfile <<-G - source "#{source_uri}" - gem "rails" - G - bundle :install, :artifice => "endpoint" - expect(the_bundle).to include_gems "rails 2.3.2" - end - - it "installs the binstubs", :bundler => "< 3" do + it "installs the binstubs", bundler: "< 3" do gemfile <<-G source "#{source_uri}" gem "rack" G - bundle "install --binstubs", :artifice => "endpoint" + bundle "install --binstubs", artifice: "endpoint" gembin "rackup" expect(out).to eq("1.0.0") end - it "installs the bins when using --path and uses autoclean", :bundler => "< 3" do + it "installs the bins when using --path and uses autoclean", bundler: "< 3" do gemfile <<-G source "#{source_uri}" gem "rack" G - bundle "install --path vendor/bundle", :artifice => "endpoint" + bundle "install --path vendor/bundle", artifice: "endpoint" expect(vendored_gems("bin/rackup")).to exist end - it "installs the bins when using --path and uses bundle clean", :bundler => "< 3" do + it "installs the bins when using --path and uses bundle clean", bundler: "< 3" do gemfile <<-G source "#{source_uri}" gem "rack" G - bundle "install --path vendor/bundle --no-clean", :artifice => "endpoint" + bundle "install --path vendor/bundle --no-clean", artifice: "endpoint" expect(vendored_gems("bin/rackup")).to exist end @@ -523,7 +512,7 @@ RSpec.describe "gemcutter's dependency API" do gem 'rack-obama' G - bundle :install, :artifice => "endpoint" + bundle :install, artifice: "endpoint" expect(out).to include("Post-install message from rack:") end @@ -533,7 +522,7 @@ RSpec.describe "gemcutter's dependency API" do gem 'rack_middleware' G - bundle :install, :artifice => "endpoint" + bundle :install, artifice: "endpoint" expect(out).to include("Post-install message from rack:") expect(out).to include("Rack's post install message") end @@ -542,7 +531,7 @@ RSpec.describe "gemcutter's dependency API" do let(:user) { "user" } let(:password) { "pass" } let(:basic_auth_source_uri) do - uri = Bundler::URI.parse(source_uri) + uri = Gem::URI.parse(source_uri) uri.user = user uri.password = password @@ -555,7 +544,7 @@ RSpec.describe "gemcutter's dependency API" do gem "rack" G - bundle :install, :artifice => "endpoint_basic_authentication" + bundle :install, artifice: "endpoint_basic_authentication" expect(out).not_to include("#{user}:#{password}") expect(the_bundle).to include_gems "rack 1.0.0" end @@ -566,7 +555,7 @@ RSpec.describe "gemcutter's dependency API" do gem "rack" G - bundle :install, :verbose => true, :artifice => "endpoint_basic_authentication" + bundle :install, verbose: true, artifice: "endpoint_basic_authentication" expect(out).not_to include("#{user}:#{password}") expect(the_bundle).to include_gems "rack 1.0.0" end @@ -577,7 +566,7 @@ RSpec.describe "gemcutter's dependency API" do gem "rack" G - bundle :install, :artifice => "endpoint_marshal_fail_basic_authentication" + bundle :install, artifice: "endpoint_marshal_fail_basic_authentication" expect(out).not_to include("#{user}:#{password}") expect(the_bundle).to include_gems "rack 1.0.0" end @@ -588,18 +577,18 @@ RSpec.describe "gemcutter's dependency API" do gem "rack" G - bundle :install, :artifice => "endpoint_500", :raise_on_error => false + bundle :install, artifice: "endpoint_500", raise_on_error: false expect(out).not_to include("#{user}:#{password}") end - it "strips http basic auth creds when warning about ambiguous sources", :bundler => "< 3" do + it "strips http basic auth creds when warning about ambiguous sources", bundler: "< 3" do gemfile <<-G source "#{basic_auth_source_uri}" source "#{file_uri_for(gem_repo1)}" gem "rack" G - bundle :install, :artifice => "endpoint_basic_authentication" + bundle :install, artifice: "endpoint_basic_authentication" expect(err).to include("Warning: the gem 'rack' was found in multiple sources.") expect(err).not_to include("#{user}:#{password}") expect(the_bundle).to include_gems "rack 1.0.0" @@ -611,7 +600,7 @@ RSpec.describe "gemcutter's dependency API" do gem "rack" G - bundle :install, :artifice => "endpoint_creds_diff_host" + bundle :install, artifice: "endpoint_creds_diff_host" expect(the_bundle).to include_gems "rack 1.0.0" end @@ -624,7 +613,7 @@ RSpec.describe "gemcutter's dependency API" do end it "reads authentication details from a valid ENV variable" do - bundle :install, :artifice => "endpoint_strict_basic_authentication", :env => { "BUNDLE_LOCAL___GEMSERVER__TEST" => "#{user}:#{password}" } + bundle :install, artifice: "endpoint_strict_basic_authentication", env: { "BUNDLE_LOCAL___GEMSERVER__TEST" => "#{user}:#{password}" } expect(out).to include("Fetching gem metadata from http://local-gemserver.test") expect(the_bundle).to include_gems "rack 1.0.0" @@ -642,7 +631,7 @@ RSpec.describe "gemcutter's dependency API" do it "reads authentication details by host name from bundle config" do bundle "config set #{source_hostname} #{user}:#{password}" - bundle :install, :artifice => "endpoint_strict_basic_authentication" + bundle :install, artifice: "endpoint_strict_basic_authentication" expect(out).to include("Fetching gem metadata from #{source_uri}") expect(the_bundle).to include_gems "rack 1.0.0" @@ -652,7 +641,7 @@ RSpec.describe "gemcutter's dependency API" do # The trailing slash is necessary here; Fetcher canonicalizes the URI. bundle "config set #{source_uri}/ #{user}:#{password}" - bundle :install, :artifice => "endpoint_strict_basic_authentication" + bundle :install, artifice: "endpoint_strict_basic_authentication" expect(out).to include("Fetching gem metadata from #{source_uri}") expect(the_bundle).to include_gems "rack 1.0.0" @@ -660,7 +649,7 @@ RSpec.describe "gemcutter's dependency API" do it "should use the API" do bundle "config set #{source_hostname} #{user}:#{password}" - bundle :install, :artifice => "endpoint_strict_basic_authentication" + bundle :install, artifice: "endpoint_strict_basic_authentication" expect(out).to include("Fetching gem metadata from #{source_uri}") expect(the_bundle).to include_gems "rack 1.0.0" end @@ -673,19 +662,19 @@ RSpec.describe "gemcutter's dependency API" do bundle "config set #{source_hostname} otheruser:wrong" - bundle :install, :artifice => "endpoint_strict_basic_authentication" + bundle :install, artifice: "endpoint_strict_basic_authentication" expect(the_bundle).to include_gems "rack 1.0.0" end it "shows instructions if auth is not provided for the source" do - bundle :install, :artifice => "endpoint_strict_basic_authentication", :raise_on_error => false + bundle :install, artifice: "endpoint_strict_basic_authentication", raise_on_error: false expect(err).to include("bundle config set --global #{source_hostname} username:password") end it "fails if authentication has already been provided, but failed" do bundle "config set #{source_hostname} #{user}:wrong" - bundle :install, :artifice => "endpoint_strict_basic_authentication", :raise_on_error => false + bundle :install, artifice: "endpoint_strict_basic_authentication", raise_on_error: false expect(err).to include("Bad username or password") end end @@ -699,7 +688,7 @@ RSpec.describe "gemcutter's dependency API" do gem "rack" G - bundle :install, :artifice => "endpoint_basic_authentication" + bundle :install, artifice: "endpoint_basic_authentication" expect(the_bundle).to include_gems "rack 1.0.0" end end @@ -724,7 +713,7 @@ RSpec.describe "gemcutter's dependency API" do gem "rack" G - bundle :install, :env => { "RUBYOPT" => opt_add("-I#{bundled_app("broken_ssl")}", ENV["RUBYOPT"]) }, :raise_on_error => false + bundle :install, env: { "RUBYOPT" => opt_add("-I#{bundled_app("broken_ssl")}", ENV["RUBYOPT"]) }, raise_on_error: false expect(err).to include("OpenSSL") end end @@ -734,7 +723,7 @@ RSpec.describe "gemcutter's dependency API" do # Install a monkeypatch that reproduces the effects of openssl raising # a certificate validation error when RubyGems tries to connect. gemfile <<-G - class Net::HTTP + class Gem::Net::HTTP def start raise OpenSSL::SSL::SSLError, "certificate verify failed" end @@ -744,7 +733,7 @@ RSpec.describe "gemcutter's dependency API" do gem "rack" G - bundle :install, :raise_on_error => false + bundle :install, raise_on_error: false expect(err).to match(/could not verify the SSL certificate/i) end end @@ -752,7 +741,7 @@ RSpec.describe "gemcutter's dependency API" do context ".gemrc with sources is present" do it "uses other sources declared in the Gemfile" do File.open(home(".gemrc"), "w") do |file| - file.puts({ :sources => ["https://rubygems.org"] }.to_yaml) + file.puts({ sources: ["https://rubygems.org"] }.to_yaml) end begin @@ -761,7 +750,7 @@ RSpec.describe "gemcutter's dependency API" do gem 'rack' G - bundle "install", :artifice => "endpoint_marshal_fail" + bundle "install", artifice: "endpoint_marshal_fail" ensure home(".gemrc").rmtree end diff --git a/spec/bundler/install/gems/flex_spec.rb b/spec/bundler/install/gems/flex_spec.rb index 326ec51214..8ef3984975 100644 --- a/spec/bundler/install/gems/flex_spec.rb +++ b/spec/bundler/install/gems/flex_spec.rb @@ -156,7 +156,7 @@ RSpec.describe "bundle flex_install" do end end - describe "when Gemfile conflicts with lockfile" do + describe "when running bundle install and Gemfile conflicts with lockfile" do before(:each) do build_repo2 install_gemfile <<-G @@ -183,34 +183,70 @@ RSpec.describe "bundle flex_install" do end it "does not install gems whose dependencies are not met" do - bundle :install, :raise_on_error => false - ruby <<-RUBY, :raise_on_error => false + bundle :install, raise_on_error: false + ruby <<-RUBY, raise_on_error: false require 'bundler/setup' RUBY expect(err).to match(/could not find gem 'rack-obama/i) end - it "suggests bundle update when the Gemfile requires different versions than the lock" do + it "discards the locked gems when the Gemfile requires different versions than the lock" do bundle "config set force_ruby_platform true" - nice_error = <<-E.strip.gsub(/^ {8}/, "") - Bundler could not find compatible versions for gem "rack": - In snapshot (Gemfile.lock): - rack (= 0.9.1) + nice_error = <<~E.strip + Could not find compatible versions - In Gemfile: - rack-obama (= 2.0) was resolved to 2.0, which depends on - rack (= 1.2) + Because rack-obama >= 2.0 depends on rack = 1.2 + and rack = 1.2 could not be found in rubygems repository #{file_uri_for(gem_repo2)}/, cached gems or installed locally, + rack-obama >= 2.0 cannot be used. + So, because Gemfile depends on rack-obama = 2.0, + version solving has failed. + E + + bundle :install, retry: 0, raise_on_error: false + expect(err).to end_with(nice_error) + end - rack_middleware was resolved to 1.0, which depends on - rack (= 0.9.1) + it "does not include conflicts with a single requirement tree, because that can't possibly be a conflict" do + bundle "config set force_ruby_platform true" - Running `bundle update` will rebuild your snapshot from scratch, using only - the gems in your Gemfile, which may resolve the conflict. + bad_error = <<~E.strip + Bundler could not find compatible versions for gem "rack-obama": + In Gemfile: + rack-obama (= 2.0) E - bundle :install, :retry => 0, :raise_on_error => false - expect(err).to end_with(nice_error) + bundle "update rack_middleware", retry: 0, raise_on_error: false + expect(err).not_to end_with(bad_error) + end + end + + describe "when running bundle update and Gemfile conflicts with lockfile" do + before(:each) do + build_repo4 do + build_gem "jekyll-feed", "0.16.0" + build_gem "jekyll-feed", "0.15.1" + + build_gem "github-pages", "226" do |s| + s.add_dependency "jekyll-feed", "0.15.1" + end + end + + install_gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem "jekyll-feed", "~> 0.12" + G + + gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem "github-pages", "~> 226" + gem "jekyll-feed", "~> 0.12" + G + end + + it "discards the conflicting lockfile information and resolves properly" do + bundle :update, raise_on_error: false, all: true + expect(err).to be_empty end end @@ -229,14 +265,32 @@ RSpec.describe "bundle flex_install" do G end - it "does something" do - expect do - bundle "install", :raise_on_error => false - end.not_to change { File.read(bundled_app_lock) } + it "should work when you install" do + bundle "install" + + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo1, "rack", "0.9.1" + c.checksum gem_repo1, "rack-obama", "1.0" + end - expect(err).to include("rack = 0.9.1") - expect(err).to include("locked at 1.0.0") - expect(err).to include("bundle update rack") + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo1)}/ + specs: + rack (0.9.1) + rack-obama (1.0) + rack + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + rack (= 0.9.1) + rack-obama + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L end it "should work when you update" do @@ -259,24 +313,28 @@ RSpec.describe "bundle flex_install" do gem "rack" G - lockfile_should_be <<-L - GEM - remote: #{file_uri_for(gem_repo1)}/ - specs: - rack (1.0.0) + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo1, "rack", "1.0.0" + end - GEM - remote: #{file_uri_for(gem_repo2)}/ - specs: + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo1)}/ + specs: + rack (1.0.0) - PLATFORMS - #{lockfile_platforms} + GEM + remote: #{file_uri_for(gem_repo2)}/ + specs: - DEPENDENCIES - rack + PLATFORMS + #{lockfile_platforms} - BUNDLED WITH - #{Bundler::VERSION} + DEPENDENCIES + rack + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} L end end @@ -301,7 +359,7 @@ RSpec.describe "bundle flex_install" do end end - it "prints the correct error message" do + it "resolves them" do # install Rails 3.0.0.rc install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" @@ -310,13 +368,12 @@ RSpec.describe "bundle flex_install" do G # upgrade Rails to 3.0.0 and then install again - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" gem "rails", "3.0.0" gem "capybara", "0.3.9" G - - expect(err).to include("Gemfile.lock") + expect(err).to be_empty end end end diff --git a/spec/bundler/install/gems/fund_spec.rb b/spec/bundler/install/gems/fund_spec.rb index f521b0296f..9aadc9ed25 100644 --- a/spec/bundler/install/gems/fund_spec.rb +++ b/spec/bundler/install/gems/fund_spec.rb @@ -6,20 +6,20 @@ RSpec.describe "bundle install" do build_repo2 do build_gem "has_funding_and_other_metadata" do |s| s.metadata = { - "bug_tracker_uri" => "https://example.com/user/bestgemever/issues", - "changelog_uri" => "https://example.com/user/bestgemever/CHANGELOG.md", + "bug_tracker_uri" => "https://example.com/user/bestgemever/issues", + "changelog_uri" => "https://example.com/user/bestgemever/CHANGELOG.md", "documentation_uri" => "https://www.example.info/gems/bestgemever/0.0.1", - "homepage_uri" => "https://bestgemever.example.io", - "mailing_list_uri" => "https://groups.example.com/bestgemever", - "funding_uri" => "https://example.com/has_funding_and_other_metadata/funding", - "source_code_uri" => "https://example.com/user/bestgemever", - "wiki_uri" => "https://example.com/user/bestgemever/wiki", + "homepage_uri" => "https://bestgemever.example.io", + "mailing_list_uri" => "https://groups.example.com/bestgemever", + "funding_uri" => "https://example.com/has_funding_and_other_metadata/funding", + "source_code_uri" => "https://example.com/user/bestgemever", + "wiki_uri" => "https://example.com/user/bestgemever/wiki", } end build_gem "has_funding", "1.2.3" do |s| s.metadata = { - "funding_uri" => "https://example.com/has_funding/funding", + "funding_uri" => "https://example.com/has_funding/funding", } end @@ -52,6 +52,33 @@ RSpec.describe "bundle install" do end end + context "when gems include a fund URI but `ignore_funding_requests` is configured" do + before do + bundle "config set ignore_funding_requests true" + end + + it "does not display the plural fund message after installing" do + install_gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + gem 'has_funding_and_other_metadata' + gem 'has_funding' + gem 'rack-obama' + G + + expect(out).not_to include("2 installed gems you directly depend on are looking for funding.") + end + + it "does not display the singular fund message after installing" do + install_gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + gem 'has_funding' + gem 'rack-obama' + G + + expect(out).not_to include("1 installed gem you directly depend on is looking for funding.") + end + end + context "when gems do not include fund messages" do it "does not display any fund messages" do install_gemfile <<-G diff --git a/spec/bundler/install/gems/native_extensions_spec.rb b/spec/bundler/install/gems/native_extensions_spec.rb index d5cafcfc2c..907778a384 100644 --- a/spec/bundler/install/gems/native_extensions_spec.rb +++ b/spec/bundler/install/gems/native_extensions_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe "installing a gem with native extensions", :ruby_repo do +RSpec.describe "installing a gem with native extensions" do it "installs" do build_repo2 do build_gem "c_extension" do |s| @@ -91,7 +91,7 @@ RSpec.describe "installing a gem with native extensions", :ruby_repo do it "installs correctly from git when multiple gems with extensions share one repository" do build_repo2 do ["one", "two"].each do |n| - build_lib "c_extension_#{n}", "1.0", :path => lib_path("gems/c_extension_#{n}") do |s| + build_lib "c_extension_#{n}", "1.0", path: lib_path("gems/c_extension_#{n}") do |s| s.extensions = ["ext/extconf.rb"] s.write "ext/extconf.rb", <<-E require "mkmf" @@ -119,7 +119,7 @@ RSpec.describe "installing a gem with native extensions", :ruby_repo do C end end - build_git "gems", :path => lib_path("gems"), :gemspec => false + build_git "gems", path: lib_path("gems"), gemspec: false end bundle "config set build.c_extension_one --with-c_extension_one=one" diff --git a/spec/bundler/install/gems/resolving_spec.rb b/spec/bundler/install/gems/resolving_spec.rb index 94fac0052c..c5f9c4a3d3 100644 --- a/spec/bundler/install/gems/resolving_spec.rb +++ b/spec/bundler/install/gems/resolving_spec.rb @@ -7,7 +7,7 @@ RSpec.describe "bundle install with install-time dependencies" do s.extensions << "Rakefile" s.write "Rakefile", <<-RUBY task :default do - path = File.expand_path("../lib", __FILE__) + path = File.expand_path("lib", __dir__) FileUtils.mkdir_p(path) File.open("\#{path}/implicit_rake_dep.rb", "w") do |f| f.puts "IMPLICIT_RAKE_DEP = 'YES'" @@ -20,7 +20,7 @@ RSpec.describe "bundle install with install-time dependencies" do s.extensions << "Rakefile" s.write "Rakefile", <<-RUBY task :default do - path = File.expand_path("../lib", __FILE__) + path = File.expand_path("lib", __dir__) FileUtils.mkdir_p(path) File.open("\#{path}/another_implicit_rake_dep.rb", "w") do |f| f.puts "ANOTHER_IMPLICIT_RAKE_DEP = 'YES'" @@ -42,7 +42,7 @@ RSpec.describe "bundle install with install-time dependencies" do s.extensions << "Rakefile" s.write "Rakefile", <<-RUBY task :default do - path = File.expand_path("../lib", __FILE__) + path = File.expand_path("lib", __dir__) FileUtils.mkdir_p(path) File.open("\#{path}/net_build_extensions.rb", "w") do |f| f.puts "NET_BUILD_EXTENSIONS = 'YES'" @@ -101,16 +101,14 @@ RSpec.describe "bundle install with install-time dependencies" do end it "installs gems with a dependency with no type" do - skip "incorrect data check error" if Gem.win_platform? - build_repo2 path = "#{gem_repo2}/#{Gem::MARSHAL_SPEC_DIR}/actionpack-2.3.2.gemspec.rz" spec = Marshal.load(Bundler.rubygems.inflate(File.binread(path))) spec.dependencies.each do |d| - d.instance_variable_set(:@type, :fail) + d.instance_variable_set(:@type, "fail") end - File.open(path, "w") do |f| + File.open(path, "wb") do |f| f.write Gem.deflate(Marshal.dump(spec)) end @@ -133,7 +131,7 @@ RSpec.describe "bundle install with install-time dependencies" do end it "installs plugins depended on by other plugins" do - install_gemfile <<-G, :env => { "DEBUG" => "1" } + install_gemfile <<-G, env: { "DEBUG" => "1" } source "#{file_uri_for(gem_repo2)}" gem "net_a" G @@ -142,7 +140,7 @@ RSpec.describe "bundle install with install-time dependencies" do end it "installs multiple levels of dependencies" do - install_gemfile <<-G, :env => { "DEBUG" => "1" } + install_gemfile <<-G, env: { "DEBUG" => "1" } source "#{file_uri_for(gem_repo2)}" gem "net_c" gem "net_e" @@ -159,9 +157,9 @@ RSpec.describe "bundle install with install-time dependencies" do gem "net_e" G - bundle :install, :env => { "BUNDLER_DEBUG_RESOLVER" => "1", "DEBUG" => "1" } + bundle :install, env: { "BUNDLER_DEBUG_RESOLVER" => "1", "DEBUG" => "1" } - expect(out).to include("BUNDLER: Starting resolution") + expect(out).to include("Resolving dependencies...") end end @@ -173,9 +171,9 @@ RSpec.describe "bundle install with install-time dependencies" do gem "net_e" G - bundle :install, :env => { "DEBUG_RESOLVER" => "1", "DEBUG" => "1" } + bundle :install, env: { "DEBUG_RESOLVER" => "1", "DEBUG" => "1" } - expect(out).to include("BUNDLER: Starting resolution") + expect(out).to include("Resolving dependencies...") end end @@ -187,18 +185,12 @@ RSpec.describe "bundle install with install-time dependencies" do gem "net_e" G - bundle :install, :env => { "DEBUG_RESOLVER_TREE" => "1", "DEBUG" => "1" } - - activated_groups = if local_platforms.any? - "net_b (1.0) (ruby), net_b (1.0) (#{local_platforms.join(", ")})" - else - "net_b (1.0) (ruby)" - end + bundle :install, env: { "DEBUG_RESOLVER_TREE" => "1", "DEBUG" => "1" } expect(out).to include(" net_b"). - and include("BUNDLER: Starting resolution"). - and include("BUNDLER: Finished resolution"). - and include("Attempting to activate [#{activated_groups}]") + and include("Resolving dependencies..."). + and include("Solution found after 1 attempts:"). + and include("selected net_b 1.0") end end end @@ -216,16 +208,327 @@ RSpec.describe "bundle install with install-time dependencies" do end end - install_gemfile <<-G, :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } - ruby "#{RUBY_VERSION}" + install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } + ruby "#{Gem.ruby_version}" source "http://localgemserver.test/" gem 'rack' G - expect(out).to_not include("rack-9001.0.0 requires ruby version > 9000") + expect(err).to_not include("rack-9001.0.0 requires ruby version > 9000") expect(the_bundle).to include_gems("rack 1.2") end + it "installs the older version when using servers not implementing the compact index API" do + build_repo2 do + build_gem "rack", "1.2" do |s| + s.executables = "rackup" + end + + build_gem "rack", "9001.0.0" do |s| + s.required_ruby_version = "> 9000" + end + end + + install_gemfile <<-G, artifice: "endpoint", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } + ruby "#{Gem.ruby_version}" + source "http://localgemserver.test/" + gem 'rack' + G + + expect(err).to_not include("rack-9001.0.0 requires ruby version > 9000") + expect(the_bundle).to include_gems("rack 1.2") + end + + context "when there is a lockfile using the newer incompatible version" do + before do + build_repo2 do + build_gem "parallel_tests", "3.7.0" do |s| + s.required_ruby_version = ">= #{current_ruby_minor}" + end + + build_gem "parallel_tests", "3.8.0" do |s| + s.required_ruby_version = ">= #{next_ruby_minor}" + end + end + + gemfile <<-G + source "http://localgemserver.test/" + gem 'parallel_tests' + G + + checksums = checksums_section do |c| + c.checksum gem_repo2, "parallel_tests", "3.8.0" + end + + lockfile <<~L + GEM + remote: http://localgemserver.test/ + specs: + parallel_tests (3.8.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + parallel_tests + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "automatically updates lockfile to use the older version" do + bundle "install --verbose", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } + + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo2, "parallel_tests", "3.7.0" + end + + expect(lockfile).to eq <<~L + GEM + remote: http://localgemserver.test/ + specs: + parallel_tests (3.7.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + parallel_tests + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "gives a meaningful error if we're in frozen mode" do + expect do + bundle "install --verbose", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s, "BUNDLE_FROZEN" => "true" }, raise_on_error: false + end.not_to change { lockfile } + + expect(err).to include("parallel_tests-3.8.0 requires ruby version >= #{next_ruby_minor}") + expect(err).not_to include("That means the author of parallel_tests (3.8.0) has removed it.") + end + end + + context "with transitive dependencies in a lockfile" do + before do + build_repo2 do + build_gem "rubocop", "1.28.2" do |s| + s.required_ruby_version = ">= #{current_ruby_minor}" + + s.add_dependency "rubocop-ast", ">= 1.17.0", "< 2.0" + end + + build_gem "rubocop", "1.35.0" do |s| + s.required_ruby_version = ">= #{next_ruby_minor}" + + s.add_dependency "rubocop-ast", ">= 1.20.1", "< 2.0" + end + + build_gem "rubocop-ast", "1.17.0" do |s| + s.required_ruby_version = ">= #{current_ruby_minor}" + end + + build_gem "rubocop-ast", "1.21.0" do |s| + s.required_ruby_version = ">= #{next_ruby_minor}" + end + end + + gemfile <<-G + source "http://localgemserver.test/" + gem 'rubocop' + G + + checksums = checksums_section do |c| + c.checksum gem_repo2, "rubocop", "1.35.0" + c.checksum gem_repo2, "rubocop-ast", "1.21.0" + end + + lockfile <<~L + GEM + remote: http://localgemserver.test/ + specs: + rubocop (1.35.0) + rubocop-ast (>= 1.20.1, < 2.0) + rubocop-ast (1.21.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + parallel_tests + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "automatically updates lockfile to use the older compatible versions" do + bundle "install --verbose", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } + + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo2, "rubocop", "1.28.2" + c.checksum gem_repo2, "rubocop-ast", "1.17.0" + end + + expect(lockfile).to eq <<~L + GEM + remote: http://localgemserver.test/ + specs: + rubocop (1.28.2) + rubocop-ast (>= 1.17.0, < 2.0) + rubocop-ast (1.17.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + rubocop + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + context "with a Gemfile and lock file that don't resolve under the current platform" do + before do + build_repo4 do + build_gem "sorbet", "0.5.10554" do |s| + s.add_dependency "sorbet-static", "0.5.10554" + end + + build_gem "sorbet-static", "0.5.10554" do |s| + s.platform = "universal-darwin-21" + end + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + gem 'sorbet', '= 0.5.10554' + G + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + sorbet (0.5.10554) + sorbet-static (= 0.5.10554) + sorbet-static (0.5.10554-universal-darwin-21) + + PLATFORMS + arm64-darwin-21 + + DEPENDENCIES + sorbet (= 0.5.10554) + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "raises a proper error" do + simulate_platform "aarch64-linux" do + bundle "install", raise_on_error: false + end + + nice_error = <<~E.strip + Could not find gems matching 'sorbet-static (= 0.5.10554)' valid for all resolution platforms (arm64-darwin-21, aarch64-linux) in rubygems repository #{file_uri_for(gem_repo4)}/, cached gems or installed locally. + + The source contains the following gems matching 'sorbet-static (= 0.5.10554)': + * sorbet-static-0.5.10554-universal-darwin-21 + E + expect(err).to end_with(nice_error) + end + end + + context "when adding a new gem that does not resolve under all locked platforms" do + before do + simulate_platform "x86_64-linux" do + build_repo4 do + build_gem "nokogiri", "1.14.0" do |s| + s.platform = "x86_64-linux" + end + build_gem "nokogiri", "1.14.0" do |s| + s.platform = "arm-linux" + end + + build_gem "sorbet-static", "0.5.10696" do |s| + s.platform = "x86_64-linux" + end + end + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + nokogiri (1.14.0-arm-linux) + nokogiri (1.14.0-x86_64-linux) + + PLATFORMS + arm-linux + x86_64-linux + + DEPENDENCIES + nokogiri + + BUNDLED WITH + #{Bundler::VERSION} + L + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "nokogiri" + gem "sorbet-static" + G + + bundle "lock", raise_on_error: false + end + end + + it "raises a proper error" do + nice_error = <<~E.strip + Could not find gems matching 'sorbet-static' valid for all resolution platforms (arm-linux, x86_64-linux) in rubygems repository #{file_uri_for(gem_repo4)}/, cached gems or installed locally. + + The source contains the following gems matching 'sorbet-static': + * sorbet-static-0.5.10696-x86_64-linux + E + expect(err).to end_with(nice_error) + end + end + + it "gives a meaningful error on ruby version mismatches between dependencies" do + build_repo4 do + build_gem "requires-old-ruby" do |s| + s.required_ruby_version = "< #{Gem.ruby_version}" + end + end + + build_lib("foo", path: bundled_app) do |s| + s.required_ruby_version = ">= #{Gem.ruby_version}" + + s.add_dependency "requires-old-ruby" + end + + install_gemfile <<-G, raise_on_error: false + source "#{file_uri_for(gem_repo4)}" + gemspec + G + + expect(err).to end_with <<~E.strip + Could not find compatible versions + + Because every version of foo depends on requires-old-ruby >= 0 + and every version of requires-old-ruby depends on Ruby < #{Gem.ruby_version}, + every version of foo requires Ruby < #{Gem.ruby_version}. + So, because Gemfile depends on foo >= 0 + and current Ruby version is = #{Gem.ruby_version}, + version solving has failed. + E + end + it "installs the older version under rate limiting conditions" do build_repo4 do build_gem "rack", "9001.0.0" do |s| @@ -235,14 +538,14 @@ RSpec.describe "bundle install with install-time dependencies" do build_gem "foo1", "1.0" end - install_gemfile <<-G, :artifice => "compact_index_rate_limited", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } - ruby "#{RUBY_VERSION}" + install_gemfile <<-G, artifice: "compact_index_rate_limited", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + ruby "#{Gem.ruby_version}" source "http://localgemserver.test/" gem 'rack' gem 'foo1' G - expect(out).to_not include("rack-9001.0.0 requires ruby version > 9000") + expect(err).to_not include("rack-9001.0.0 requires ruby version > 9000") expect(the_bundle).to include_gems("rack 1.2") end @@ -252,22 +555,22 @@ RSpec.describe "bundle install with install-time dependencies" do s.required_ruby_version = "> 9000" end build_gem "rack", "1.2" do |s| - s.platform = mingw + s.platform = x86_mingw32 s.required_ruby_version = "> 9000" end build_gem "rack", "1.2" end - simulate_platform mingw do - install_gemfile <<-G, :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } - ruby "#{RUBY_VERSION}" + simulate_platform x86_mingw32 do + install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + ruby "#{Gem.ruby_version}" source "http://localgemserver.test/" gem 'rack' G end - expect(out).to_not include("rack-9001.0.0 requires ruby version > 9000") - expect(out).to_not include("rack-1.2-#{Bundler.local_platform} requires ruby version > 9000") + expect(err).to_not include("rack-9001.0.0 requires ruby version > 9000") + expect(err).to_not include("rack-1.2-#{Bundler.local_platform} requires ruby version > 9000") expect(the_bundle).to include_gems("rack 1.2") end end @@ -281,14 +584,32 @@ RSpec.describe "bundle install with install-time dependencies" do end end - let(:ruby_requirement) { %("#{RUBY_VERSION}") } - let(:error_message_requirement) { "~> #{RUBY_VERSION}.0" } + let(:ruby_requirement) { %("#{Gem.ruby_version}") } + let(:error_message_requirement) { "= #{Gem.ruby_version}" } + + it "raises a proper error that mentions the current Ruby version during resolution" do + install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s }, raise_on_error: false + source "http://localgemserver.test/" + gem 'require_ruby' + G + + expect(out).to_not include("Gem::InstallError: require_ruby requires Ruby version > 9000") + + nice_error = <<~E.strip + Could not find compatible versions + + Because every version of require_ruby depends on Ruby > 9000 + and Gemfile depends on require_ruby >= 0, + Ruby > 9000 is required. + So, because current Ruby version is #{error_message_requirement}, + version solving has failed. + E + expect(err).to end_with(nice_error) + end shared_examples_for "ruby version conflicts" do it "raises an error during resolution" do - skip "ruby requirement includes platform and it shouldn't" if Gem.win_platform? - - install_gemfile <<-G, :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s }, :raise_on_error => false + install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s }, raise_on_error: false source "http://localgemserver.test/" ruby #{ruby_requirement} gem 'require_ruby' @@ -296,15 +617,14 @@ RSpec.describe "bundle install with install-time dependencies" do expect(out).to_not include("Gem::InstallError: require_ruby requires Ruby version > 9000") - nice_error = strip_whitespace(<<-E).strip - Bundler found conflicting requirements for the Ruby\0 version: - In Gemfile: - Ruby\0 (#{error_message_requirement}) - - require_ruby was resolved to 1.0, which depends on - Ruby\0 (> 9000) + nice_error = <<~E.strip + Could not find compatible versions - Ruby\0 (> 9000), which is required by gem 'require_ruby', is not available in the local ruby installation + Because every version of require_ruby depends on Ruby > 9000 + and Gemfile depends on require_ruby >= 0, + Ruby > 9000 is required. + So, because current Ruby version is #{error_message_requirement}, + version solving has failed. E expect(err).to end_with(nice_error) end @@ -314,7 +634,6 @@ RSpec.describe "bundle install with install-time dependencies" do describe "with a < requirement" do let(:ruby_requirement) { %("< 5000") } - let(:error_message_requirement) { "< 5000" } it_behaves_like "ruby version conflicts" end @@ -322,7 +641,6 @@ RSpec.describe "bundle install with install-time dependencies" do describe "with a compound requirement" do let(:reqs) { ["> 0.1", "< 5000"] } let(:ruby_requirement) { reqs.map(&:dump).join(", ") } - let(:error_message_requirement) { Gem::Requirement.new(reqs).to_s } it_behaves_like "ruby version conflicts" end @@ -337,13 +655,20 @@ RSpec.describe "bundle install with install-time dependencies" do end end - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo2)}" gem 'require_rubygems' G expect(err).to_not include("Gem::InstallError: require_rubygems requires RubyGems version > 9000") - expect(err).to include("require_rubygems-1.0 requires rubygems version > 9000, which is incompatible with the current version, #{Gem::VERSION}") + nice_error = <<~E.strip + Because every version of require_rubygems depends on RubyGems > 9000 + and Gemfile depends on require_rubygems >= 0, + RubyGems > 9000 is required. + So, because current RubyGems version is = #{Gem::VERSION}, + version solving has failed. + E + expect(err).to end_with(nice_error) end end end diff --git a/spec/bundler/install/gems/standalone_spec.rb b/spec/bundler/install/gems/standalone_spec.rb index b3b9d4d46b..46cab2dfeb 100644 --- a/spec/bundler/install/gems/standalone_spec.rb +++ b/spec/bundler/install/gems/standalone_spec.rb @@ -32,6 +32,21 @@ RSpec.shared_examples "bundle install --standalone" do expect(out).to eq(expected_gems.values.join("\n")) end + it "makes the gems available without bundler nor rubygems" do + testrb = String.new <<-RUBY + $:.unshift File.expand_path("bundle") + require "bundler/setup" + + RUBY + expected_gems.each do |k, _| + testrb << "\nrequire \"#{k}\"" + testrb << "\nputs #{k.upcase}" + end + sys_exec %(#{Gem.ruby} --disable-gems -w -e #{testrb.shellescape}) + + expect(out).to eq(expected_gems.values.join("\n")) + end + it "makes the gems available without bundler via Kernel.require" do testrb = String.new <<-RUBY $:.unshift File.expand_path("bundle") @@ -82,7 +97,23 @@ RSpec.shared_examples "bundle install --standalone" do testrb << "\nrequire \"#{k}\"" testrb << "\nputs #{k.upcase}" end - ruby testrb, :dir => "#{bundled_app}2" + ruby testrb, dir: "#{bundled_app}2" + + expect(out).to eq(expected_gems.values.join("\n")) + end + + it "skips activating gems" do + testrb = String.new <<-RUBY + $:.unshift File.expand_path("bundle") + require "bundler/setup" + + gem "do_not_activate_me" + RUBY + expected_gems.each do |k, _| + testrb << "\nrequire \"#{k}\"" + testrb << "\nputs #{k.upcase}" + end + sys_exec %(#{Gem.ruby} -w -e #{testrb.shellescape}) expect(out).to eq(expected_gems.values.join("\n")) end @@ -95,7 +126,7 @@ RSpec.shared_examples "bundle install --standalone" do gem "rails" G bundle "config set --local path #{bundled_app("bundle")}" - bundle :install, :standalone => true, :dir => cwd + bundle :install, standalone: true, dir: cwd end let(:expected_gems) do @@ -108,10 +139,113 @@ RSpec.shared_examples "bundle install --standalone" do include_examples "common functionality" end - describe "with gems with native extension", :ruby_repo do + describe "with default gems and a lockfile", :ruby_repo do before do + realworld_system_gems "tsort --version 0.1.0" + + necessary_system_gems = ["optparse --version 0.1.1", "psych --version 3.3.2", "logger --version 1.4.3", "etc --version 1.2.0", "stringio --version 3.1.0"] + necessary_system_gems += ["shellwords --version 0.1.0", "base64 --version 0.1.0", "resolv --version 0.2.1"] if Gem.rubygems_version < Gem::Version.new("3.3.a") + necessary_system_gems += ["yaml --version 0.1.1"] if Gem.rubygems_version < Gem::Version.new("3.4.a") + realworld_system_gems(*necessary_system_gems, path: scoped_gem_path(bundled_app("bundle"))) + + build_gem "foo", "1.0.0", to_system: true, default: true do |s| + s.add_dependency "bar" + end + + build_gem "bar", "1.0.0", to_system: true, default: true + + build_repo4 do + build_gem "foo", "1.0.0" do |s| + s.add_dependency "bar" + end + + build_gem "bar", "1.0.0" + end + + gemfile <<-G + source "https://gem.repo4" + gem "foo" + G + + bundle "lock", dir: cwd, artifice: "compact_index" + end + + it "works and points to the vendored copies, not to the default copies", :realworld do bundle "config set --local path #{bundled_app("bundle")}" - install_gemfile <<-G, :standalone => true, :dir => cwd + bundle :install, standalone: true, dir: cwd, artifice: "compact_index", env: { "BUNDLER_GEM_DEFAULT_DIR" => system_gem_path.to_s } + + load_path_lines = bundled_app("bundle/bundler/setup.rb").read.split("\n").select {|line| line.start_with?("$:.unshift") } + + expect(load_path_lines).to eq [ + '$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/bar-1.0.0/lib")', + '$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/foo-1.0.0/lib")', + ] + end + end + + describe "with Gemfiles using absolute path sources and resulting bundle moved to a folder hierarchy with different nesting" do + before do + build_lib "minitest", "1.0.0", path: lib_path("minitest") + + Dir.mkdir bundled_app("app") + + gemfile bundled_app("app/Gemfile"), <<-G + source "#{file_uri_for(gem_repo1)}" + gem "minitest", :path => "#{lib_path("minitest")}" + G + + bundle "install", standalone: true, dir: bundled_app("app") + + Dir.mkdir tmp("one_more_level") + FileUtils.mv bundled_app, tmp("one_more_level") + end + + it "also works" do + ruby <<-RUBY, dir: tmp("one_more_level/bundled_app/app") + require "./bundle/bundler/setup" + + require "minitest" + puts MINITEST + RUBY + + expect(out).to eq("1.0.0") + expect(err).to be_empty + end + end + + describe "with Gemfiles using relative path sources and app moved to a different root" do + before do + FileUtils.mkdir_p bundled_app("app/vendor") + + build_lib "minitest", "1.0.0", path: bundled_app("app/vendor/minitest") + + gemfile bundled_app("app/Gemfile"), <<-G + source "#{file_uri_for(gem_repo1)}" + gem "minitest", :path => "vendor/minitest" + G + + bundle "install", standalone: true, dir: bundled_app("app") + + FileUtils.mv(bundled_app("app"), bundled_app2("app")) + end + + it "also works" do + ruby <<-RUBY, dir: bundled_app2("app") + require "./bundle/bundler/setup" + + require "minitest" + puts MINITEST + RUBY + + expect(out).to eq("1.0.0") + expect(err).to be_empty + end + end + + describe "with gems with native extension" do + before do + bundle "config set --local path #{bundled_app("bundle")}" + install_gemfile <<-G, standalone: true, dir: cwd source "#{file_uri_for(gem_repo1)}" gem "very_simple_binary" G @@ -119,18 +253,22 @@ RSpec.shared_examples "bundle install --standalone" do it "generates a bundle/bundler/setup.rb with the proper paths" do expected_path = bundled_app("bundle/bundler/setup.rb") - extension_line = File.read(expected_path).each_line.find {|line| line.include? "/extensions/" }.strip - expect(extension_line).to start_with '$:.unshift File.expand_path("#{path}/../#{ruby_engine}/#{ruby_version}/extensions/' - expect(extension_line).to end_with '/very_simple_binary-1.0")' + script_content = File.read(expected_path) + expect(script_content).to include("def self.ruby_api_version") + expect(script_content).to include("def self.extension_api_version") + extension_line = script_content.each_line.find {|line| line.include? "/extensions/" }.strip + platform = Gem::Platform.local + expect(extension_line).to start_with '$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/extensions/' + expect(extension_line).to end_with platform.to_s + '/#{Gem.extension_api_version}/very_simple_binary-1.0")' end end describe "with gem that has an invalid gemspec" do before do - build_git "bar", :gemspec => false do |s| + build_git "bar", gemspec: false do |s| s.write "lib/bar/version.rb", %(BAR_VERSION = '1.0') s.write "bar.gemspec", <<-G - lib = File.expand_path('../lib/', __FILE__) + lib = File.expand_path('lib/', __dir__) $:.unshift lib unless $:.include?(lib) require 'bar/version' @@ -145,7 +283,7 @@ RSpec.shared_examples "bundle install --standalone" do G end bundle "config set --local path #{bundled_app("bundle")}" - install_gemfile <<-G, :standalone => true, :dir => cwd, :raise_on_error => false + install_gemfile <<-G, standalone: true, dir: cwd, raise_on_error: false source "#{file_uri_for(gem_repo1)}" gem "bar", :git => "#{lib_path("bar-1.0")}" G @@ -167,7 +305,7 @@ RSpec.shared_examples "bundle install --standalone" do gem "devise", :git => "#{lib_path("devise-1.0")}" G bundle "config set --local path #{bundled_app("bundle")}" - bundle :install, :standalone => true, :dir => cwd + bundle :install, standalone: true, dir: cwd end let(:expected_gems) do @@ -195,7 +333,7 @@ RSpec.shared_examples "bundle install --standalone" do end G bundle "config set --local path #{bundled_app("bundle")}" - bundle :install, :standalone => true, :dir => cwd + bundle :install, standalone: true, dir: cwd end let(:expected_gems) do @@ -209,7 +347,7 @@ RSpec.shared_examples "bundle install --standalone" do it "allows creating a standalone file with limited groups" do bundle "config set --local path #{bundled_app("bundle")}" - bundle :install, :standalone => "default", :dir => cwd + bundle :install, standalone: "default", dir: cwd load_error_ruby <<-RUBY, "spec" $:.unshift File.expand_path("bundle") @@ -227,7 +365,7 @@ RSpec.shared_examples "bundle install --standalone" do it "allows `without` configuration to limit the groups used in a standalone" do bundle "config set --local path #{bundled_app("bundle")}" bundle "config set --local without test" - bundle :install, :standalone => true, :dir => cwd + bundle :install, standalone: true, dir: cwd load_error_ruby <<-RUBY, "spec" $:.unshift File.expand_path("bundle") @@ -244,7 +382,7 @@ RSpec.shared_examples "bundle install --standalone" do it "allows `path` configuration to change the location of the standalone bundle" do bundle "config set --local path path/to/bundle" - bundle "install", :standalone => true, :dir => cwd + bundle "install", standalone: true, dir: cwd ruby <<-RUBY $:.unshift File.expand_path("path/to/bundle") @@ -259,9 +397,9 @@ RSpec.shared_examples "bundle install --standalone" do it "allows `without` to limit the groups used in a standalone" do bundle "config set --local without test" - bundle :install, :dir => cwd + bundle :install, dir: cwd bundle "config set --local path #{bundled_app("bundle")}" - bundle :install, :standalone => true, :dir => cwd + bundle :install, standalone: true, dir: cwd load_error_ruby <<-RUBY, "spec" $:.unshift File.expand_path("bundle") @@ -287,7 +425,7 @@ RSpec.shared_examples "bundle install --standalone" do gem "rails" G bundle "config set --local path #{bundled_app("bundle")}" - bundle :install, :standalone => true, :artifice => "endpoint", :dir => cwd + bundle :install, standalone: true, artifice: "endpoint", dir: cwd end let(:expected_gems) do @@ -301,14 +439,14 @@ RSpec.shared_examples "bundle install --standalone" do end end - describe "with --binstubs", :bundler => "< 3" do + describe "with --binstubs", bundler: "< 3" do before do gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rails" G bundle "config set --local path #{bundled_app("bundle")}" - bundle :install, :standalone => true, :binstubs => true, :dir => cwd + bundle :install, standalone: true, binstubs: true, dir: cwd end let(:expected_gems) do @@ -326,7 +464,7 @@ RSpec.shared_examples "bundle install --standalone" do it "creates stubs that can be executed from anywhere" do require "tmpdir" - sys_exec(%(#{bundled_app("bin/rails")} -v), :dir => Dir.tmpdir) + sys_exec(%(#{bundled_app("bin/rails")} -v), dir: Dir.tmpdir) expect(out).to eq("2.3.2") end @@ -344,7 +482,7 @@ RSpec.shared_examples "bundle install --standalone" do it "creates stubs with the correct load path" do extension_line = File.read(bundled_app("bin/rails")).each_line.find {|line| line.include? "$:.unshift" }.strip - expect(extension_line).to eq %($:.unshift File.expand_path "../../bundle", path.realpath) + expect(extension_line).to eq %($:.unshift File.expand_path "../bundle", __dir__) end end end @@ -360,3 +498,31 @@ RSpec.describe "bundle install --standalone run in a subdirectory" do include_examples("bundle install --standalone") end + +RSpec.describe "bundle install --standalone --local" do + before do + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem "rack" + G + + system_gems "rack-1.0.0", path: default_bundle_path + end + + it "generates script pointing to system gems" do + bundle "install --standalone --local --verbose" + + expect(out).to include("Using rack 1.0.0") + + load_error_ruby <<-RUBY, "spec" + require "./bundler/setup" + + require "rack" + puts RACK + require "spec" + RUBY + + expect(out).to eq("1.0.0") + expect(err).to eq("ZOMG LOAD ERROR") + end +end diff --git a/spec/bundler/install/gems/sudo_spec.rb b/spec/bundler/install/gems/sudo_spec.rb deleted file mode 100644 index 3e5d38ea4c..0000000000 --- a/spec/bundler/install/gems/sudo_spec.rb +++ /dev/null @@ -1,205 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe "when using sudo", :sudo => true do - describe "and BUNDLE_PATH is writable" do - context "but BUNDLE_PATH/build_info is not writable" do - let(:subdir) do - system_gem_path("cache") - end - - before do - bundle "config set path.system true" - subdir.mkpath - sudo "chmod u-w #{subdir}" - end - - after do - sudo "chmod u+w #{subdir}" - end - - it "installs" do - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" - G - - expect(out).to_not match(/an error occurred/i) - expect(system_gem_path("cache/rack-1.0.0.gem")).to exist - expect(the_bundle).to include_gems "rack 1.0" - end - end - end - - describe "and GEM_HOME is owned by root" do - before :each do - bundle "config set path.system true" - chown_system_gems_to_root - end - - it "installs" do - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", '1.0' - gem "thin" - G - - expect(system_gem_path("gems/rack-1.0.0")).to exist - expect(system_gem_path("gems/rack-1.0.0").stat.uid).to eq(0) - expect(the_bundle).to include_gems "rack 1.0" - end - - it "installs rake and a gem dependent on rake in the same session" do - build_repo2 do - build_gem "another_implicit_rake_dep" do |s| - s.extensions << "Rakefile" - s.write "Rakefile", <<-RUBY - task :default do - path = File.expand_path("../lib", __FILE__) - FileUtils.mkdir_p(path) - File.open("\#{path}/another_implicit_rake_dep.rb", "w") do |f| - f.puts "ANOTHER_IMPLICIT_RAKE_DEP = 'YES'" - end - end - RUBY - end - end - - gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem "rake" - gem "another_implicit_rake_dep" - G - bundle "install" - expect(system_gem_path("gems/another_implicit_rake_dep-1.0")).to exist - end - - it "installs when BUNDLE_PATH is owned by root" do - bundle_path = tmp("owned_by_root") - FileUtils.mkdir_p bundle_path - sudo "chown -R root #{bundle_path}" - - ENV["BUNDLE_PATH"] = bundle_path.to_s - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", '1.0' - G - - expect(bundle_path.join(Bundler.ruby_scope, "gems/rack-1.0.0")).to exist - expect(bundle_path.join(Bundler.ruby_scope, "gems/rack-1.0.0").stat.uid).to eq(0) - expect(the_bundle).to include_gems "rack 1.0" - end - - it "installs when BUNDLE_PATH does not exist" do - root_path = tmp("owned_by_root") - FileUtils.mkdir_p root_path - sudo "chown -R root #{root_path}" - bundle_path = root_path.join("does_not_exist") - - ENV["BUNDLE_PATH"] = bundle_path.to_s - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", '1.0' - G - - expect(bundle_path.join(Bundler.ruby_scope, "gems/rack-1.0.0")).to exist - expect(bundle_path.join(Bundler.ruby_scope, "gems/rack-1.0.0").stat.uid).to eq(0) - expect(the_bundle).to include_gems "rack 1.0" - end - - it "installs extensions/" do - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "very_simple_binary" - G - - expect(system_gem_path("gems/very_simple_binary-1.0")).to exist - binary_glob = system_gem_path("extensions/*/*/very_simple_binary-1.0") - expect(Dir.glob(binary_glob).first).to be - end - end - - describe "and BUNDLE_PATH is not writable" do - before do - bundle "config set --local path .bundle" - sudo "chmod ugo-w .bundle" - end - - after do - sudo "chmod ugo+w .bundle" - end - - it "installs" do - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", '1.0' - G - - expect(local_gem_path("gems/rack-1.0.0")).to exist - expect(the_bundle).to include_gems "rack 1.0" - end - - it "cleans up the tmpdirs generated" do - require "tmpdir" - Dir.glob("#{Dir.tmpdir}/bundler*").each do |tmpdir| - FileUtils.remove_entry_secure(tmpdir) - end - - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" - G - tmpdirs = Dir.glob("#{Dir.tmpdir}/bundler*") - - expect(tmpdirs).to be_empty - end - end - - describe "and GEM_HOME is not writable" do - it "installs" do - bundle "config set path.system true" - gem_home = tmp("sudo_gem_home") - sudo "mkdir -p #{gem_home}" - sudo "chmod ugo-w #{gem_home}" - - system_gems :bundler, :path => gem_home - - gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", '1.0' - G - - bundle :install, :env => { "GEM_HOME" => gem_home.to_s, "GEM_PATH" => nil } - expect(gem_home.join("bin/rackup")).to exist - expect(the_bundle).to include_gems "rack 1.0", :env => { "GEM_HOME" => gem_home.to_s, "GEM_PATH" => nil } - - sudo "rm -rf #{tmp("sudo_gem_home")}" - end - end - - describe "and root runs install" do - let(:warning) { "Don't run Bundler as root." } - - before do - gemfile %(source "#{file_uri_for(gem_repo1)}") - end - - it "warns against that" do - bundle :install, :sudo => :preserve_env - expect(err).to include(warning) - end - - context "when ENV['BUNDLE_SILENCE_ROOT_WARNING'] is set" do - it "skips the warning" do - bundle :install, :sudo => :preserve_env, :env => { "BUNDLE_SILENCE_ROOT_WARNING" => "true" } - expect(err).to_not include(warning) - end - end - - context "when silence_root_warning = false" do - it "warns against that" do - bundle :install, :sudo => :preserve_env, :env => { "BUNDLE_SILENCE_ROOT_WARNING" => "false" } - expect(err).to include(warning) - end - end - end -end diff --git a/spec/bundler/install/gemspecs_spec.rb b/spec/bundler/install/gemspecs_spec.rb index 0c4518fe2b..51aa0ed14f 100644 --- a/spec/bundler/install/gemspecs_spec.rb +++ b/spec/bundler/install/gemspecs_spec.rb @@ -4,7 +4,7 @@ RSpec.describe "bundle install" do describe "when a gem has a YAML gemspec" do before :each do build_repo2 do - build_gem "yaml_spec", :gemspec => :yaml + build_gem "yaml_spec", gemspec: :yaml end end @@ -18,7 +18,7 @@ RSpec.describe "bundle install" do end it "still installs correctly when using path" do - build_lib "yaml_spec", :gemspec => :yaml + build_lib "yaml_spec", gemspec: :yaml install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -34,6 +34,8 @@ RSpec.describe "bundle install" do gem 'rack' G + system_gems "rack-1.0.0", path: default_bundle_path + FileUtils.mkdir_p "#{default_bundle_path}/specifications" File.open("#{default_bundle_path}/specifications/rack-1.0.0.gemspec", "w+") do |f| spec = Gem::Specification.new do |s| @@ -43,8 +45,8 @@ RSpec.describe "bundle install" do end f.write spec.to_ruby end - bundle :install, :artifice => "endpoint_marshal_fail" # force gemspec load - expect(the_bundle).to include_gems "activesupport 2.3.2" + bundle :install, artifice: "endpoint_marshal_fail" # force gemspec load + expect(the_bundle).to include_gems "rack 1.0.0", "activesupport 2.3.2" end it "does not hang when gemspec has incompatible encoding" do @@ -57,7 +59,7 @@ RSpec.describe "bundle install" do end G - install_gemfile <<-G, :env => { "LANG" => "C" } + install_gemfile <<-G, env: { "LANG" => "C" } source "#{file_uri_for(gem_repo1)}" gemspec G @@ -92,8 +94,9 @@ RSpec.describe "bundle install" do end 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| + it "installs when patch level is not specified and the version matches", + if: RUBY_PATCHLEVEL >= 0 do + build_lib("foo", path: bundled_app) do |s| s.required_ruby_version = "~> #{RUBY_VERSION}.0" end @@ -106,12 +109,12 @@ RSpec.describe "bundle install" do end it "installs when patch level is specified and the version still matches the current version", - :if => RUBY_PATCHLEVEL >= 0 do - build_lib("foo", :path => bundled_app) do |s| + if: RUBY_PATCHLEVEL >= 0 do + build_lib("foo", path: bundled_app) do |s| s.required_ruby_version = "#{RUBY_VERSION}.#{RUBY_PATCHLEVEL}" end - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false ruby '#{RUBY_VERSION}', :engine_version => '#{RUBY_VERSION}', :engine => 'ruby', :patchlevel => '#{RUBY_PATCHLEVEL}' source "#{file_uri_for(gem_repo1)}" gemspec @@ -120,13 +123,13 @@ RSpec.describe "bundle install" do end it "fails and complains about patchlevel on patchlevel mismatch", - :if => RUBY_PATCHLEVEL >= 0 do + if: RUBY_PATCHLEVEL >= 0 do patchlevel = RUBY_PATCHLEVEL.to_i + 1 - build_lib("foo", :path => bundled_app) do |s| + build_lib("foo", path: bundled_app) do |s| s.required_ruby_version = "#{RUBY_VERSION}.#{patchlevel}" end - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false ruby '#{RUBY_VERSION}', :engine_version => '#{RUBY_VERSION}', :engine => 'ruby', :patchlevel => '#{patchlevel}' source "#{file_uri_for(gem_repo1)}" gemspec @@ -140,11 +143,11 @@ RSpec.describe "bundle install" do it "fails and complains about version on version mismatch" do version = Gem::Requirement.create(RUBY_VERSION).requirements.first.last.bump.version - build_lib("foo", :path => bundled_app) do |s| + build_lib("foo", path: bundled_app) do |s| s.required_ruby_version = version end - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false ruby '#{version}', :engine_version => '#{version}', :engine => 'ruby' source "#{file_uri_for(gem_repo1)}" gemspec diff --git a/spec/bundler/install/git_spec.rb b/spec/bundler/install/git_spec.rb index d43aacee7e..c8d574baf3 100644 --- a/spec/bundler/install/git_spec.rb +++ b/spec/bundler/install/git_spec.rb @@ -3,51 +3,51 @@ RSpec.describe "bundle install" do context "git sources" do it "displays the revision hash of the gem repository" do - build_git "foo", "1.0", :path => lib_path("foo") + build_git "foo", "1.0", path: lib_path("foo") - install_gemfile <<-G, :verbose => true + install_gemfile <<-G, verbose: true source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{file_uri_for(lib_path("foo"))}" G - expect(out).to include("Using foo 1.0 from #{file_uri_for(lib_path("foo"))} (at master@#{revision_for(lib_path("foo"))[0..6]})") - expect(the_bundle).to include_gems "foo 1.0", :source => "git@#{lib_path("foo")}" + expect(out).to include("Using foo 1.0 from #{file_uri_for(lib_path("foo"))} (at main@#{revision_for(lib_path("foo"))[0..6]})") + expect(the_bundle).to include_gems "foo 1.0", source: "git@#{lib_path("foo")}" end - it "displays the correct default branch", :git => ">= 2.28.0" do - build_git "foo", "1.0", :path => lib_path("foo"), :default_branch => "main" + it "displays the correct default branch", git: ">= 2.28.0" do + build_git "foo", "1.0", path: lib_path("foo"), default_branch: "main" - install_gemfile <<-G, :verbose => true + install_gemfile <<-G, verbose: true source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{file_uri_for(lib_path("foo"))}" G expect(out).to include("Using foo 1.0 from #{file_uri_for(lib_path("foo"))} (at main@#{revision_for(lib_path("foo"))[0..6]})") - expect(the_bundle).to include_gems "foo 1.0", :source => "git@#{lib_path("foo")}" + expect(the_bundle).to include_gems "foo 1.0", source: "git@#{lib_path("foo")}" end it "displays the ref of the gem repository when using branch~num as a ref" do skip "maybe branch~num notation doesn't work on Windows' git" if Gem.win_platform? - build_git "foo", "1.0", :path => lib_path("foo") + build_git "foo", "1.0", path: lib_path("foo") rev = revision_for(lib_path("foo"))[0..6] - update_git "foo", "2.0", :path => lib_path("foo"), :gemspec => true + update_git "foo", "2.0", path: lib_path("foo"), gemspec: true rev2 = revision_for(lib_path("foo"))[0..6] - update_git "foo", "3.0", :path => lib_path("foo"), :gemspec => true + update_git "foo", "3.0", path: lib_path("foo"), gemspec: true - install_gemfile <<-G, :verbose => true + install_gemfile <<-G, verbose: true source "#{file_uri_for(gem_repo1)}" - gem "foo", :git => "#{file_uri_for(lib_path("foo"))}", :ref => "master~2" + gem "foo", :git => "#{file_uri_for(lib_path("foo"))}", :ref => "main~2" G - expect(out).to include("Using foo 1.0 from #{file_uri_for(lib_path("foo"))} (at master~2@#{rev})") - expect(the_bundle).to include_gems "foo 1.0", :source => "git@#{lib_path("foo")}" + expect(out).to include("Using foo 1.0 from #{file_uri_for(lib_path("foo"))} (at main~2@#{rev})") + expect(the_bundle).to include_gems "foo 1.0", source: "git@#{lib_path("foo")}" - update_git "foo", "4.0", :path => lib_path("foo"), :gemspec => true + update_git "foo", "4.0", path: lib_path("foo"), gemspec: true - bundle :update, :all => true, :verbose => true - expect(out).to include("Using foo 2.0 (was 1.0) from #{file_uri_for(lib_path("foo"))} (at master~2@#{rev2})") - expect(the_bundle).to include_gems "foo 2.0", :source => "git@#{lib_path("foo")}" + bundle :update, all: true, verbose: true + expect(out).to include("Using foo 2.0 (was 1.0) from #{file_uri_for(lib_path("foo"))} (at main~2@#{rev2})") + expect(the_bundle).to include_gems "foo 2.0", source: "git@#{lib_path("foo")}" end it "should allows git repos that are missing but not being installed" do @@ -81,9 +81,9 @@ RSpec.describe "bundle install" do it "allows multiple gems from the same git source" do build_repo2 do - build_lib "foo", "1.0", :path => lib_path("gems/foo") - build_lib "zebra", "2.0", :path => lib_path("gems/zebra") - build_git "gems", :path => lib_path("gems"), :gemspec => false + build_lib "foo", "1.0", path: lib_path("gems/foo") + build_lib "zebra", "2.0", path: lib_path("gems/zebra") + build_git "gems", path: lib_path("gems"), gemspec: false end install_gemfile <<-G @@ -98,5 +98,108 @@ RSpec.describe "bundle install" do bundle "info zebra" expect(out).to include("* zebra (2.0 #{revision_for(lib_path("gems"))[0..6]})") end + + it "should always sort dependencies in the same order" do + # This Gemfile + lockfile had a problem where the first + # `bundle install` would change the order, but the second would + # change it back. + + # NOTE: both gems MUST have the same path! It has to be two gems in one repo. + + test = build_git "test", "1.0.0", path: lib_path("test-and-other") + other = build_git "other", "1.0.0", path: lib_path("test-and-other") + test_ref = test.ref_for("HEAD") + other_ref = other.ref_for("HEAD") + + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + + gem "test", git: #{test.path.to_s.inspect} + gem "other", ref: #{other_ref.inspect}, git: #{other.path.to_s.inspect} + G + + lockfile <<-L + GIT + remote: #{test.path} + revision: #{test_ref} + specs: + test (1.0.0) + + GIT + remote: #{other.path} + revision: #{other_ref} + ref: #{other_ref} + specs: + other (1.0.0) + + GEM + remote: #{file_uri_for(gem_repo1)}/ + specs: + + PLATFORMS + ruby + + DEPENDENCIES + other! + test! + + BUNDLED WITH + #{Bundler::VERSION} + L + + # If GH#6743 is present, the first `bundle install` will change the + # lockfile, by flipping the order (`other` would be moved to the top). + # + # The second `bundle install` would then change the lockfile back + # to the original. + # + # The fix makes it so it may change it once, but it will not change + # it a second time. + # + # So, we run `bundle install` once, and store the value of the + # modified lockfile. + bundle :install + modified_lockfile = lockfile + + # If GH#6743 is present, the second `bundle install` would change the + # lockfile back to what it was originally. + # + # This `expect` makes sure it doesn't change a second time. + bundle :install + expect(lockfile).to eq(modified_lockfile) + + expect(out).to include("Bundle complete!") + end + + it "allows older revisions of git source when clean true" do + build_git "foo", "1.0", path: lib_path("foo") + rev = revision_for(lib_path("foo")) + + bundle "config set path vendor/bundle" + bundle "config set clean true" + install_gemfile <<-G, verbose: true + source "#{file_uri_for(gem_repo1)}" + gem "foo", :git => "#{file_uri_for(lib_path("foo"))}" + G + + expect(out).to include("Using foo 1.0 from #{file_uri_for(lib_path("foo"))} (at main@#{rev[0..6]})") + expect(the_bundle).to include_gems "foo 1.0", source: "git@#{lib_path("foo")}" + + old_lockfile = lockfile + + update_git "foo", "2.0", path: lib_path("foo"), gemspec: true + rev2 = revision_for(lib_path("foo")) + + bundle :update, all: true, verbose: true + expect(out).to include("Using foo 2.0 (was 1.0) from #{file_uri_for(lib_path("foo"))} (at main@#{rev2[0..6]})") + expect(out).to include("Removing foo (#{rev[0..11]})") + expect(the_bundle).to include_gems "foo 2.0", source: "git@#{lib_path("foo")}" + + lockfile(old_lockfile) + + bundle :install, verbose: true + expect(out).to include("Using foo 1.0 from #{file_uri_for(lib_path("foo"))} (at main@#{rev[0..6]})") + expect(the_bundle).to include_gems "foo 1.0", source: "git@#{lib_path("foo")}" + end end end diff --git a/spec/bundler/install/global_cache_spec.rb b/spec/bundler/install/global_cache_spec.rb index 9bc243e7cf..0da4de05b2 100644 --- a/spec/bundler/install/global_cache_spec.rb +++ b/spec/bundler/install/global_cache_spec.rb @@ -16,7 +16,7 @@ RSpec.describe "global gem caching" do end it "caches gems into the global cache on download" do - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "#{source}" gem "rack" G @@ -29,7 +29,7 @@ RSpec.describe "global gem caching" do source_global_cache.mkpath FileUtils.cp(gem_repo1("gems/rack-1.0.0.gem"), source_global_cache("rack-1.0.0.gem")) - install_gemfile <<-G, :artifice => "compact_index_no_gem" + install_gemfile <<-G, artifice: "compact_index_no_gem" source "#{source}" gem "rack" G @@ -37,9 +37,21 @@ RSpec.describe "global gem caching" do expect(the_bundle).to include_gems "rack 1.0.0" end + it "shows a proper error message if a cached gem is corrupted" do + source_global_cache.mkpath + FileUtils.touch(source_global_cache("rack-1.0.0.gem")) + + install_gemfile <<-G, artifice: "compact_index_no_gem", raise_on_error: false + source "#{source}" + gem "rack" + G + + expect(err).to include("Gem::Package::FormatError: package metadata is missing in #{source_global_cache("rack-1.0.0.gem")}") + end + describe "when the same gem from different sources is installed" do it "should use the appropriate one from the global cache" do - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "#{source}" gem "rack" G @@ -49,7 +61,7 @@ RSpec.describe "global gem caching" do expect(source_global_cache("rack-1.0.0.gem")).to exist # rack 1.0.0 is not installed and it is in the global cache - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "#{source2}" gem "rack", "0.9.1" G @@ -64,7 +76,7 @@ RSpec.describe "global gem caching" do gem "rack", "1.0.0" G - bundle :install, :artifice => "compact_index_no_gem" + bundle :install, artifice: "compact_index_no_gem" # rack 1.0.0 is installed and rack 0.9.1 is not expect(the_bundle).to include_gems "rack 1.0.0" expect(the_bundle).not_to include_gems "rack 0.9.1" @@ -75,7 +87,7 @@ RSpec.describe "global gem caching" do gem "rack", "0.9.1" G - bundle :install, :artifice => "compact_index_no_gem" + bundle :install, artifice: "compact_index_no_gem" # rack 0.9.1 is installed and rack 1.0.0 is not expect(the_bundle).to include_gems "rack 0.9.1" expect(the_bundle).not_to include_gems "rack 1.0.0" @@ -87,7 +99,7 @@ RSpec.describe "global gem caching" do gem "rack" G - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" simulate_new_machine expect(the_bundle).not_to include_gems "rack 1.0.0" expect(source_global_cache("rack-1.0.0.gem")).to exist @@ -98,7 +110,7 @@ RSpec.describe "global gem caching" do gem "rack", "0.9.1" G - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" simulate_new_machine expect(the_bundle).not_to include_gems "rack 0.9.1" expect(source2_global_cache("rack-0.9.1.gem")).to exist @@ -111,9 +123,9 @@ RSpec.describe "global gem caching" do expect(source_global_cache("rack-1.0.0.gem")).to exist expect(source2_global_cache("rack-0.9.1.gem")).to exist - bundle :install, :artifice => "compact_index_no_gem", :raise_on_error => false + bundle :install, artifice: "compact_index_no_gem", raise_on_error: false expect(err).to include("Internal Server Error 500") - expect(err).not_to include("please copy and paste the report template above into a new issue") + expect(err).not_to include("ERROR REPORT TEMPLATE") # rack 1.0.0 is not installed and rack 0.9.1 is not expect(the_bundle).not_to include_gems "rack 1.0.0" @@ -126,9 +138,9 @@ RSpec.describe "global gem caching" do expect(source_global_cache("rack-1.0.0.gem")).to exist expect(source2_global_cache("rack-0.9.1.gem")).to exist - bundle :install, :artifice => "compact_index_no_gem", :raise_on_error => false + bundle :install, artifice: "compact_index_no_gem", raise_on_error: false expect(err).to include("Internal Server Error 500") - expect(err).not_to include("please copy and paste the report template above into a new issue") + expect(err).not_to include("ERROR REPORT TEMPLATE") # rack 0.9.1 is not installed and rack 1.0.0 is not expect(the_bundle).not_to include_gems "rack 0.9.1" @@ -138,7 +150,7 @@ RSpec.describe "global gem caching" do describe "when installing gems from a different directory" do it "uses the global cache as a source" do - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "#{source}" gem "rack" gem "activesupport" @@ -154,7 +166,7 @@ RSpec.describe "global gem caching" do expect(the_bundle).not_to include_gems "rack 1.0.0" expect(the_bundle).not_to include_gems "activesupport 2.3.5" - install_gemfile <<-G, :artifice => "compact_index_no_gem" + install_gemfile <<-G, artifice: "compact_index_no_gem" source "#{source}" gem "rack" G @@ -171,20 +183,18 @@ RSpec.describe "global gem caching" do G # Neither gem is installed and both are in the global cache - expect(the_bundle).not_to include_gems "rack 1.0.0", :dir => bundled_app2 - expect(the_bundle).not_to include_gems "activesupport 2.3.5", :dir => bundled_app2 + expect(the_bundle).not_to include_gems "rack 1.0.0", dir: bundled_app2 + expect(the_bundle).not_to include_gems "activesupport 2.3.5", dir: bundled_app2 expect(source_global_cache("rack-1.0.0.gem")).to exist expect(source_global_cache("activesupport-2.3.5.gem")).to exist # Install using the global cache instead of by downloading the .gem # from the server - bundle :install, :artifice => "compact_index_no_gem", :dir => bundled_app2 + bundle :install, artifice: "compact_index_no_gem", dir: bundled_app2 # activesupport is installed and both are in the global cache - simulate_bundler_version_when_missing_prerelease_default_gem_activation do - expect(the_bundle).not_to include_gems "rack 1.0.0", :dir => bundled_app2 - expect(the_bundle).to include_gems "activesupport 2.3.5", :dir => bundled_app2 - end + expect(the_bundle).not_to include_gems "rack 1.0.0", dir: bundled_app2 + expect(the_bundle).to include_gems "activesupport 2.3.5", dir: bundled_app2 expect(source_global_cache("rack-1.0.0.gem")).to exist expect(source_global_cache("activesupport-2.3.5.gem")).to exist @@ -193,7 +203,7 @@ RSpec.describe "global gem caching" do end describe "extension caching" do - it "works", :ruby_repo do + it "works" do skip "gets incorrect ref in path" if Gem.win_platform? build_git "very_simple_git_binary", &:add_c_extension @@ -208,9 +218,9 @@ RSpec.describe "global gem caching" do gem "very_simple_path_binary", :path => "#{lib_path("very_simple_path_binary-1.0")}" G - gem_binary_cache = home(".bundle", "cache", "extensions", specific_local_platform.to_s, Bundler.ruby_scope, + gem_binary_cache = home(".bundle", "cache", "extensions", local_platform.to_s, Bundler.ruby_scope, Digest(:MD5).hexdigest("#{gem_repo1}/"), "very_simple_binary-1.0") - git_binary_cache = home(".bundle", "cache", "extensions", specific_local_platform.to_s, Bundler.ruby_scope, + git_binary_cache = home(".bundle", "cache", "extensions", local_platform.to_s, Bundler.ruby_scope, "very_simple_git_binary-1.0-#{revision}", "very_simple_git_binary-1.0") cached_extensions = Pathname.glob(home(".bundle", "cache", "extensions", "*", "*", "*", "*", "*")).sort @@ -222,7 +232,7 @@ RSpec.describe "global gem caching" do R expect(out).to eq "VERY_SIMPLE_BINARY_IN_C\nVERY_SIMPLE_GIT_BINARY_IN_C" - FileUtils.rm Dir[home(".bundle", "cache", "extensions", "**", "*binary_c*")] + FileUtils.rm_rf Dir[home(".bundle", "cache", "extensions", "**", "*binary_c*")] gem_binary_cache.join("very_simple_binary_c.rb").open("w") {|f| f << "puts File.basename(__FILE__)" } git_binary_cache.join("very_simple_git_binary_c.rb").open("w") {|f| f << "puts File.basename(__FILE__)" } diff --git a/spec/bundler/install/path_spec.rb b/spec/bundler/install/path_spec.rb index b0392c4ed2..0a30e402b7 100644 --- a/spec/bundler/install/path_spec.rb +++ b/spec/bundler/install/path_spec.rb @@ -3,7 +3,7 @@ RSpec.describe "bundle install" do describe "with path configured" do before :each do - build_gem "rack", "1.0.0", :to_system => true do |s| + build_gem "rack", "1.0.0", to_system: true do |s| s.write "lib/rack.rb", "puts 'FAIL'" end @@ -23,7 +23,7 @@ RSpec.describe "bundle install" do bundle "config set --local path.system true" bundle "config set --global path vendor/bundle" bundle :install - run "require 'rack'", :raise_on_error => false + run "require 'rack'", raise_on_error: false expect(out).to include("FAIL") end @@ -32,7 +32,7 @@ RSpec.describe "bundle install" do dir.mkpath bundle "config set --local path #{dir.join("vendor/bundle")}" - bundle :install, :dir => dir + bundle :install, dir: dir expect(out).to include("installed into `./vendor/bundle`") dir.rmtree @@ -44,13 +44,13 @@ RSpec.describe "bundle install" do expect(out).to include("gems are installed into `./vendor/bundle`") end - it "disallows --path vendor/bundle --system", :bundler => "< 3" do - bundle "install --path vendor/bundle --system", :raise_on_error => false + it "disallows --path vendor/bundle --system", bundler: "< 3" do + bundle "install --path vendor/bundle --system", raise_on_error: false expect(err).to include("Please choose only one option.") expect(exitstatus).to eq(15) end - it "remembers to disable system gems after the first time with bundle --path vendor/bundle", :bundler => "< 3" do + it "remembers to disable system gems after the first time with bundle --path vendor/bundle", bundler: "< 3" do bundle "install --path vendor/bundle" FileUtils.rm_rf bundled_app("vendor") bundle "install" @@ -62,22 +62,22 @@ RSpec.describe "bundle install" do context "with path_relative_to_cwd set to true" do before { bundle "config set path_relative_to_cwd true" } - it "installs the bundle relatively to current working directory", :bundler => "< 3" do - bundle "install --gemfile='#{bundled_app}/Gemfile' --path vendor/bundle", :dir => bundled_app.parent + it "installs the bundle relatively to current working directory", bundler: "< 3" do + bundle "install --gemfile='#{bundled_app}/Gemfile' --path vendor/bundle", dir: bundled_app.parent expect(out).to include("installed into `./vendor/bundle`") expect(bundled_app("../vendor/bundle")).to be_directory expect(the_bundle).to include_gems "rack 1.0.0" end it "installs the standalone bundle relative to the cwd" do - bundle :install, :gemfile => bundled_app_gemfile, :standalone => true, :dir => bundled_app.parent + bundle :install, gemfile: bundled_app_gemfile, standalone: true, dir: bundled_app.parent expect(out).to include("installed into `./bundled_app/bundle`") expect(bundled_app("bundle")).to be_directory expect(bundled_app("bundle/ruby")).to be_directory bundle "config unset path" - bundle :install, :gemfile => bundled_app_gemfile, :standalone => true, :dir => bundled_app("subdir").tap(&:mkpath) + bundle :install, gemfile: bundled_app_gemfile, standalone: true, dir: bundled_app("subdir").tap(&:mkpath) expect(out).to include("installed into `../bundle`") expect(bundled_app("bundle")).to be_directory expect(bundled_app("bundle/ruby")).to be_directory @@ -87,7 +87,7 @@ RSpec.describe "bundle install" do describe "when BUNDLE_PATH or the global path config is set" do before :each do - build_lib "rack", "1.0.0", :to_system => true do |s| + build_lib "rack", "1.0.0", to_system: true do |s| s.write "lib/rack.rb", "raise 'FAIL'" end @@ -141,7 +141,7 @@ RSpec.describe "bundle install" do set_bundle_path(type, "vendor") FileUtils.mkdir_p bundled_app("lol") - bundle :install, :dir => bundled_app("lol") + bundle :install, dir: bundled_app("lol") expect(bundled_app("vendor", Bundler.ruby_scope, "gems/rack-1.0.0")).to be_directory expect(the_bundle).to include_gems "rack 1.0.0" @@ -168,7 +168,7 @@ RSpec.describe "bundle install" do it "disables system gems when passing a path to install" do # This is so that vendored gems can be distributed to others - build_gem "rack", "1.1.0", :to_system => true + build_gem "rack", "1.1.0", to_system: true bundle "config set --local path ./vendor/bundle" bundle :install @@ -176,8 +176,8 @@ RSpec.describe "bundle install" do expect(the_bundle).to include_gems "rack 1.0.0" end - it "re-installs gems whose extensions have been deleted", :ruby_repo do - build_lib "very_simple_binary", "1.0.0", :to_system => true do |s| + it "re-installs gems whose extensions have been deleted" do + build_lib "very_simple_binary", "1.0.0", to_system: true do |s| s.write "lib/very_simple_binary.rb", "raise 'FAIL'" end @@ -191,11 +191,11 @@ RSpec.describe "bundle install" do expect(vendored_gems("gems/very_simple_binary-1.0")).to be_directory expect(vendored_gems("extensions")).to be_directory - expect(the_bundle).to include_gems "very_simple_binary 1.0", :source => "remote1" + expect(the_bundle).to include_gems "very_simple_binary 1.0", source: "remote1" vendored_gems("extensions").rmtree - run "require 'very_simple_binary_c'", :raise_on_error => false + run "require 'very_simple_binary_c'", raise_on_error: false expect(err).to include("Bundler::GemNotFound") bundle "config set --local path ./vendor/bundle" @@ -203,7 +203,7 @@ RSpec.describe "bundle install" do expect(vendored_gems("gems/very_simple_binary-1.0")).to be_directory expect(vendored_gems("extensions")).to be_directory - expect(the_bundle).to include_gems "very_simple_binary 1.0", :source => "remote1" + expect(the_bundle).to include_gems "very_simple_binary 1.0", source: "remote1" end end @@ -219,7 +219,7 @@ RSpec.describe "bundle install" do G bundle "config set --local path bundle" - bundle :install, :raise_on_error => false + bundle :install, raise_on_error: false expect(err).to include("file already exists") end end diff --git a/spec/bundler/install/process_lock_spec.rb b/spec/bundler/install/process_lock_spec.rb index dac0d34bc4..1f8c62f26e 100644 --- a/spec/bundler/install/process_lock_spec.rb +++ b/spec/bundler/install/process_lock_spec.rb @@ -31,5 +31,27 @@ RSpec.describe "process lock spec" do expect(processed).to eq true end end + + context "when creating a lock raises Errno::EPERM" do + before { allow(File).to receive(:open).and_raise(Errno::EPERM) } + + it "skips creating the lock file and yields" do + processed = false + Bundler::ProcessLock.lock(default_bundle_path) { processed = true } + + expect(processed).to eq true + end + end + + context "when creating a lock raises Errno::EROFS" do + before { allow(File).to receive(:open).and_raise(Errno::EROFS) } + + it "skips creating the lock file and yields" do + processed = false + Bundler::ProcessLock.lock(default_bundle_path) { processed = true } + + expect(processed).to eq true + end + end end end diff --git a/spec/bundler/install/redownload_spec.rb b/spec/bundler/install/redownload_spec.rb index a936b2b536..3a72c356d9 100644 --- a/spec/bundler/install/redownload_spec.rb +++ b/spec/bundler/install/redownload_spec.rb @@ -57,7 +57,7 @@ RSpec.describe "bundle install" do end end - describe "with --force", :bundler => 2 do + describe "with --force", bundler: 2 do it_behaves_like "an option to force redownloading gems" do let(:flag) { "force" } end diff --git a/spec/bundler/install/security_policy_spec.rb b/spec/bundler/install/security_policy_spec.rb index 43c3069c4e..befeb81da5 100644 --- a/spec/bundler/install/security_policy_spec.rb +++ b/spec/bundler/install/security_policy_spec.rb @@ -16,23 +16,23 @@ RSpec.describe "policies with unsigned gems" do end it "will work after you try to deploy without a lock" do - bundle "install --deployment", :raise_on_error => false + bundle "install --deployment", raise_on_error: false bundle :install expect(the_bundle).to include_gems "rack 1.0", "signed_gem 1.0" end it "will fail when given invalid security policy" do - bundle "install --trust-policy=InvalidPolicyName", :raise_on_error => false + bundle "install --trust-policy=InvalidPolicyName", raise_on_error: false expect(err).to include("RubyGems doesn't know about trust policy") end it "will fail with High Security setting due to presence of unsigned gem" do - bundle "install --trust-policy=HighSecurity", :raise_on_error => false + bundle "install --trust-policy=HighSecurity", raise_on_error: false expect(err).to include("security policy didn't allow") end it "will fail with Medium Security setting due to presence of unsigned gem" do - bundle "install --trust-policy=MediumSecurity", :raise_on_error => false + bundle "install --trust-policy=MediumSecurity", raise_on_error: false expect(err).to include("security policy didn't allow") end @@ -51,12 +51,12 @@ RSpec.describe "policies with signed gems and no CA" do end it "will fail with High Security setting, gem is self-signed" do - bundle "install --trust-policy=HighSecurity", :raise_on_error => false + bundle "install --trust-policy=HighSecurity", raise_on_error: false expect(err).to include("security policy didn't allow") end it "will fail with Medium Security setting, gem is self-signed" do - bundle "install --trust-policy=MediumSecurity", :raise_on_error => false + bundle "install --trust-policy=MediumSecurity", raise_on_error: false expect(err).to include("security policy didn't allow") end diff --git a/spec/bundler/install/yanked_spec.rb b/spec/bundler/install/yanked_spec.rb index 6d3065a836..7408c24327 100644 --- a/spec/bundler/install/yanked_spec.rb +++ b/spec/bundler/install/yanked_spec.rb @@ -15,14 +15,14 @@ RSpec.context "when installing a bundle that includes yanked gems" do foo (10.0.0) PLATFORMS - ruby + #{lockfile_platforms} DEPENDENCIES foo (= 10.0.0) L - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo4)}" gem "foo", "10.0.0" G @@ -30,10 +30,76 @@ RSpec.context "when installing a bundle that includes yanked gems" do expect(err).to include("Your bundle is locked to foo (10.0.0)") end + context "when a re-resolve is necessary, and a yanked version is considered by the resolver" do + before do + skip "Materialization on Windows is not yet strict, so the example does not detect the gem has been yanked" if Gem.win_platform? + + build_repo4 do + build_gem "foo", "1.0.0", "1.0.1" + build_gem "actiontext", "6.1.7" do |s| + s.add_dependency "nokogiri", ">= 1.8" + end + build_gem "actiontext", "6.1.6" do |s| + s.add_dependency "nokogiri", ">= 1.8" + end + build_gem "actiontext", "6.1.7" do |s| + s.add_dependency "nokogiri", ">= 1.8" + end + build_gem "nokogiri", "1.13.8" + end + + gemfile <<~G + source "#{source_uri}" + gem "foo", "1.0.1" + gem "actiontext", "6.1.6" + G + + lockfile <<~L + GEM + remote: #{source_uri}/ + specs: + actiontext (6.1.6) + nokogiri (>= 1.8) + foo (1.0.0) + nokogiri (1.13.8-#{Bundler.local_platform}) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + actiontext (= 6.1.6) + foo (= 1.0.0) + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + context "and the old index is used" do + let(:source_uri) { file_uri_for(gem_repo4) } + + it "reports the yanked gem properly" do + bundle "install", raise_on_error: false + + expect(err).to include("Your bundle is locked to nokogiri (1.13.8-#{Bundler.local_platform})") + end + end + + context "and the compact index API is used" do + let(:source_uri) { "https://gem.repo4" } + + it "reports the yanked gem properly" do + bundle "install", artifice: "compact_index", raise_on_error: false + + expect(err).to include("Your bundle is locked to nokogiri (1.13.8-#{Bundler.local_platform})") + end + end + end + it "throws the original error when only the Gemfile specifies a gem version that doesn't exist" do bundle "config set force_ruby_platform true" - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo4)}" gem "foo", "10.0.0" G @@ -43,6 +109,63 @@ RSpec.context "when installing a bundle that includes yanked gems" do end end +RSpec.context "when resolving a bundle that includes yanked gems, but unlocking an unrelated gem" do + before(:each) do + build_repo4 do + build_gem "foo", "10.0.0" + + build_gem "bar", "1.0.0" + build_gem "bar", "2.0.0" + end + + lockfile <<-L + GEM + remote: #{file_uri_for(gem_repo4)} + specs: + foo (9.0.0) + bar (1.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo + bar + + BUNDLED WITH + #{Bundler::VERSION} + L + + gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem "foo" + gem "bar" + G + end + + it "does not update the yanked gem" do + bundle "lock --update bar" + + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + bar (2.0.0) + foo (9.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + bar + foo + + BUNDLED WITH + #{Bundler::VERSION} + L + end +end + RSpec.context "when using gem before installing" do it "does not suggest the author has yanked the gem" do gemfile <<-G @@ -57,16 +180,53 @@ RSpec.context "when using gem before installing" do rack (0.9.1) PLATFORMS - ruby + #{lockfile_platforms} + + DEPENDENCIES + rack (= 0.9.1) + L + + bundle :list, raise_on_error: false + + expect(err).to include("Could not find rack-0.9.1 in cached gems or installed locally") + expect(err).to_not include("Your bundle is locked to rack (0.9.1) from") + expect(err).to_not include("If you haven't changed sources, that means the author of rack (0.9.1) has removed it.") + expect(err).to_not include("You'll need to update your bundle to a different version of rack (0.9.1) that hasn't been removed in order to install.") + + # Check error message is still correct when multiple platforms are locked + lockfile lockfile.gsub(/PLATFORMS\n #{lockfile_platforms}/m, "PLATFORMS\n #{lockfile_platforms("ruby")}") + + bundle :list, raise_on_error: false + expect(err).to include("Could not find rack-0.9.1 in cached gems or installed locally") + end + + it "does not suggest the author has yanked the gem when using more than one gem, but shows all gems that couldn't be found in the source" do + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem "rack", "0.9.1" + gem "rack_middleware", "1.0" + G + + lockfile <<-L + GEM + remote: #{file_uri_for(gem_repo1)} + specs: + rack (0.9.1) + rack_middleware (1.0) + + PLATFORMS + #{lockfile_platforms} DEPENDENCIES rack (= 0.9.1) + rack_middleware (1.0) L - bundle :list, :raise_on_error => false + bundle :list, raise_on_error: false - expect(err).to include("Could not find rack-0.9.1 in any of the sources") - expect(err).to_not include("Your bundle is locked to rack (0.9.1), but that version could not be found in any of the sources listed in your Gemfile.") + expect(err).to include("Could not find rack-0.9.1, rack_middleware-1.0 in cached gems or installed locally") + expect(err).to include("Install missing gems with `bundle install`.") + expect(err).to_not include("Your bundle is locked to rack (0.9.1) from") expect(err).to_not include("If you haven't changed sources, that means the author of rack (0.9.1) has removed it.") expect(err).to_not include("You'll need to update your bundle to a different version of rack (0.9.1) that hasn't been removed in order to install.") end |