diff options
Diffstat (limited to 'spec/bundler/install/gemfile')
-rw-r--r-- | spec/bundler/install/gemfile/eval_gemfile_spec.rb | 47 | ||||
-rw-r--r-- | spec/bundler/install/gemfile/force_ruby_platform_spec.rb | 146 | ||||
-rw-r--r-- | spec/bundler/install/gemfile/gemspec_spec.rb | 375 | ||||
-rw-r--r-- | spec/bundler/install/gemfile/git_spec.rb | 382 | ||||
-rw-r--r-- | spec/bundler/install/gemfile/groups_spec.rb | 55 | ||||
-rw-r--r-- | spec/bundler/install/gemfile/install_if_spec.rb | 11 | ||||
-rw-r--r-- | spec/bundler/install/gemfile/lockfile_spec.rb | 5 | ||||
-rw-r--r-- | spec/bundler/install/gemfile/path_spec.rb | 399 | ||||
-rw-r--r-- | spec/bundler/install/gemfile/platform_spec.rb | 228 | ||||
-rw-r--r-- | spec/bundler/install/gemfile/ruby_spec.rb | 76 | ||||
-rw-r--r-- | spec/bundler/install/gemfile/sources_spec.rb | 1594 | ||||
-rw-r--r-- | spec/bundler/install/gemfile/specific_platform_spec.rb | 1277 |
12 files changed, 3691 insertions, 904 deletions
diff --git a/spec/bundler/install/gemfile/eval_gemfile_spec.rb b/spec/bundler/install/gemfile/eval_gemfile_spec.rb index 102f61dc12..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 @@ -11,29 +11,67 @@ RSpec.describe "bundle install with gemfile that uses eval_gemfile" do context "eval-ed Gemfile points to an internal gemspec" do before do create_file "Gemfile-other", <<-G + source "#{file_uri_for(gem_repo1)}" gemspec :path => 'gems/gunks' G end it "installs the gemspec specified gem" do install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" eval_gemfile 'Gemfile-other' G 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 + + context "eval-ed Gemfile points to an internal gemspec and uses a scoped source that duplicates the main Gemfile global source" do + before do + build_repo2 do + build_gem "rails", "6.1.3.2" + + build_gem "zip-zip", "0.3" + end + + create_file bundled_app("gems/Gemfile"), <<-G + source "#{file_uri_for(gem_repo2)}" + + gemspec :path => "\#{__dir__}/gunks" + + source "#{file_uri_for(gem_repo2)}" do + gem "zip-zip" + end + G + end + + it "installs and finds gems correctly" do + install_gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + + gem "rails" + + eval_gemfile File.join(__dir__, "gems/Gemfile") + G + expect(out).to include("Resolving dependencies") + expect(out).to include("Bundle complete") + + expect(the_bundle).to include_gem "rails 6.1.3.2" end end 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" G gemfile <<-G + source "#{file_uri_for(gem_repo1)}" eval_gemfile "nested/Gemfile-nested" G end @@ -57,13 +95,14 @@ RSpec.describe "bundle install with gemfile that uses eval_gemfile" do it "installs the gemspec specified gem" do install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" eval_gemfile 'other/Gemfile-other' gemspec :path => 'gems/gunks' G 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 32dd7d24b8..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,13 +233,14 @@ 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' }" end install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gemspec G @@ -217,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)}" @@ -236,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 @@ -260,13 +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 @@ -276,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 @@ -287,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 @@ -302,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 @@ -326,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 @@ -412,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 @@ -427,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: @@ -452,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 @@ -469,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: @@ -482,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 @@ -501,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: @@ -516,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 @@ -536,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 @@ -544,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)}" @@ -555,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 @@ -570,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 @@ -588,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 @@ -603,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 @@ -620,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 ba8f253b0e..24cf30eadb 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,8 @@ 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' end @@ -83,32 +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 @@ -118,13 +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 @@ -133,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 @@ -142,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)}" @@ -153,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 @@ -187,10 +195,12 @@ RSpec.describe "bundle install with git sources" do it "works" do install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" git "#{lib_path("foo-1.0")}", :ref => "#{@revision}" do gem "foo" end G + expect(err).to be_empty run <<-RUBY require 'foo' @@ -202,6 +212,7 @@ RSpec.describe "bundle install with git sources" do it "works when the revision is a symbol" do install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" git "#{lib_path("foo-1.0")}", :ref => #{@revision.to_sym.inspect} do gem "foo" end @@ -216,20 +227,60 @@ 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 + source "#{file_uri_for(gem_repo1)}" git "#{lib_path("foo-1.0")}", :ref => "refs/bundler/1" do gem "foo" end @@ -246,24 +297,26 @@ RSpec.describe "bundle install with git sources" do it "works when the revision is a non-head ref and it was previously downloaded" do install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" git "#{lib_path("foo-1.0")}" do gem "foo" 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 + source "#{file_uri_for(gem_repo1)}" git "#{lib_path("foo-1.0")}", :ref => "refs/bundler/1" do gem "foo" end @@ -279,20 +332,21 @@ 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" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" git "#{lib_path("foo-1.0")}" do gem "foo" end 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 @@ -303,9 +357,10 @@ 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)}" git "#{repo}", :branch => #{branch.dump} do gem "foo" end @@ -319,9 +374,10 @@ 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)}" git "#{repo}", :branch => #{branch.dump} do gem "foo" end @@ -336,9 +392,10 @@ 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)}" git "#{repo}", :branch => #{branch.dump} do gem "foo" end @@ -354,9 +411,10 @@ 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)}" git "#{repo}", :tag => #{tag.dump} do gem "foo" end @@ -370,9 +428,10 @@ 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)}" git "#{repo}", :tag => #{tag.dump} do gem "foo" end @@ -387,9 +446,10 @@ 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)}" git "#{repo}", :tag => #{tag.dump} do gem "foo" end @@ -402,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")}) @@ -426,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")}) @@ -445,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")}) @@ -468,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 @@ -490,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 @@ -510,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/) @@ -536,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/) @@ -568,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")}) @@ -649,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 @@ -672,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)}" @@ -688,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)}" @@ -701,10 +758,11 @@ 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)}" path "#{lib_path("hi2u")}" do gem "omg" gem "hi2u" @@ -721,6 +779,7 @@ RSpec.describe "bundle install with git sources" do update_git "foo" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{lib_path("foo-1.0")}", :ref => "#{@revision}" G @@ -748,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 @@ -763,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 @@ -778,13 +837,14 @@ 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 build_git "foo", "1.0" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", "1.0", :git => "#{lib_path("foo-1.0")}" G @@ -792,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)}" @@ -806,10 +866,11 @@ RSpec.describe "bundle install with git sources" do it "catches git errors and spits out useful output" do gemfile <<-G + source "#{file_uri_for(gem_repo1)}" 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") @@ -817,9 +878,10 @@ 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)}" gem "foo", :git => "#{lib_path("foo space-1.0")}" G @@ -830,6 +892,7 @@ RSpec.describe "bundle install with git sources" do build_git "forced", "1.0" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" git "#{lib_path("forced-1.0")}" do gem 'forced' end @@ -840,42 +903,50 @@ 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)}/ 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)}" git "#{lib_path("has_submodule-1.0")}", :submodules => true do gem "has_submodule" end @@ -885,13 +956,17 @@ 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)}" git "#{lib_path("has_submodule-1.0")}" do gem "has_submodule" end @@ -906,6 +981,7 @@ RSpec.describe "bundle install with git sources" do git = build_git "foo" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" git "#{lib_path("foo-1.0")}" do gem "foo" end @@ -915,6 +991,7 @@ RSpec.describe "bundle install with git sources" do update_git "foo" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" git "#{lib_path("foo-1.0")}", :ref => "#{git.ref_for("HEAD^")}" do gem "foo" end @@ -932,6 +1009,7 @@ RSpec.describe "bundle install with git sources" do build_git "foo" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{lib_path("foo-1.0")}" G @@ -945,6 +1023,7 @@ RSpec.describe "bundle install with git sources" do build_git "foo" gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{lib_path("foo-1.0")}" G @@ -958,7 +1037,8 @@ 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 @@ -969,13 +1049,14 @@ 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)}" gem "foo", :git => "#{lib_path("nested")}" gem "bar", :git => "#{lib_path("nested")}" G @@ -985,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 @@ -1033,6 +1114,7 @@ RSpec.describe "bundle install with git sources" do build_git "valim" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "valim", :git => "#{file_uri_for(lib_path("valim-1.0"))}" G @@ -1058,20 +1140,33 @@ RSpec.describe "bundle install with git sources" do revision = revision_for(lib_path("foo-1.0")) install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{file_uri_for(lib_path("foo-1.0"))}", :ref => "#{revision}" 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)}" @@ -1089,6 +1184,7 @@ RSpec.describe "bundle install with git sources" do it "runs pre-install hooks" do build_git "foo" gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{lib_path("foo-1.0")}" G @@ -1101,13 +1197,14 @@ 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 it "runs post-install hooks" do build_git "foo" gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{lib_path("foo-1.0")}" G @@ -1120,13 +1217,14 @@ 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 it "complains if the install hook fails" do build_git "foo" gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{lib_path("foo-1.0")}" G @@ -1138,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 @@ -1150,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'" @@ -1176,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" @@ -1194,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") @@ -1223,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 @@ -1243,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| @@ -1284,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| @@ -1327,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| @@ -1349,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/) @@ -1399,17 +1497,53 @@ 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 + source "#{file_uri_for(gem_repo1)}" git "#{lib_path("foo-1.0")}" do gem 'foo' end 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") @@ -1419,6 +1553,7 @@ In Gemfile: build_git "foo" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" git "#{lib_path("foo-1.0")}" do gem 'foo' end @@ -1427,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 @@ -1445,10 +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 - gem "foo", :git => "#{lib_path("foo")}", :branch => "master" + source "#{file_uri_for(gem_repo1)}" + gem "foo", :git => "#{lib_path("foo")}", :branch => "main" G bundle :install @@ -1462,7 +1598,8 @@ 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" end @@ -1477,7 +1614,8 @@ 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" end diff --git a/spec/bundler/install/gemfile/groups_spec.rb b/spec/bundler/install/gemfile/groups_spec.rb index f53315e766..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 "#{lib_dir}/bundler" + 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 1c77b3a37e..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 @@ -16,6 +16,7 @@ RSpec.describe "bundle install with explicit source paths" do build_lib "foo" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" path "#{lib_path("foo-1.0")}" do gem 'foo' end @@ -28,6 +29,7 @@ RSpec.describe "bundle install with explicit source paths" do build_lib "foo" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem 'foo', :path => "#{lib_path("foo-1.0")}" G @@ -40,6 +42,7 @@ RSpec.describe "bundle install with explicit source paths" do relative_path = lib_path("foo-1.0").relative_path_from(bundled_app) install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem 'foo', :path => "#{relative_path}" G @@ -52,6 +55,7 @@ RSpec.describe "bundle install with explicit source paths" do relative_path = lib_path("foo-1.0").relative_path_from(Pathname.new("~").expand_path) install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem 'foo', :path => "~/#{relative_path}" G @@ -65,7 +69,8 @@ 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 expect(err).to match("There was an error while trying to use the path `~#{username}/#{relative_path}`.") @@ -73,25 +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 @@ -105,6 +115,7 @@ RSpec.describe "bundle install with explicit source paths" do aaa (1.0) GEM + remote: #{file_uri_for(gem_repo1)}/ specs: PLATFORMS @@ -113,22 +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 - gem 'foo', :path => File.expand_path("../foo-1.0", __FILE__) + source "#{file_uri_for(gem_repo1)}" + gem 'foo', :path => File.expand_path("foo-1.0", __dir__) G bundle "config set --local frozen true" @@ -138,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 @@ -156,31 +168,99 @@ 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)}" gem "omg", :path => "#{lib_path("omg")}" G 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 @@ -188,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' @@ -200,6 +280,7 @@ RSpec.describe "bundle install with explicit source paths" do end install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "premailer", :path => "#{lib_path("premailer")}" G @@ -220,7 +301,8 @@ 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 @@ -231,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") + + 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) - 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") + 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 @@ -262,48 +398,49 @@ 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 @@ -312,11 +449,12 @@ 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 install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gemspec :path => "#{lib_path("foo")}", :name => "foo" G @@ -328,7 +466,8 @@ 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' end @@ -346,6 +485,7 @@ RSpec.describe "bundle install with explicit source paths" do lib_path("foo-1.0").join("bin/performance").mkpath install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem 'foo', '1.0', :path => "#{lib_path("foo-1.0")}" G expect(err).to be_empty @@ -355,6 +495,7 @@ RSpec.describe "bundle install with explicit source paths" do build_lib "foo" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem 'foo', :path => "#{lib_path("foo-1.0")}" G @@ -367,6 +508,7 @@ RSpec.describe "bundle install with explicit source paths" do build_lib "hi2u" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" path "#{lib_path}" do gem "omg" gem "hi2u" @@ -378,13 +520,14 @@ 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 install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :path => "#{lib_path("foo")}" gem "omg", :path => "#{lib_path("omg")}" G @@ -393,9 +536,10 @@ 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)}" gem "foo", "1.0", :path => "#{lib_path("foo-1.0")}" G @@ -411,12 +555,13 @@ RSpec.describe "bundle install with explicit source paths" do specs: GEM - remote: http://rubygems.org + remote: http://rubygems.org/ L FileUtils.mkdir_p(bundled_app("vendor/bar")) install_gemfile <<-G + source "http://rubygems.org" gem "bar", "1.0.0", path: "vendor/bar", require: "bar/nyard" G end @@ -429,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 @@ -449,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 @@ -461,6 +606,7 @@ RSpec.describe "bundle install with explicit source paths" do end install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :path => "#{lib_path("foo-1.0")}" G @@ -470,18 +616,19 @@ 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)}" gem "foo", :path => "#{lib_path("foo")}" G 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 @@ -491,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" @@ -501,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)}" @@ -510,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 @@ -520,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 @@ -528,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: @@ -545,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: @@ -573,7 +725,7 @@ RSpec.describe "bundle install with explicit source paths" do DEPENDENCIES foo! - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G @@ -582,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 @@ -590,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: @@ -607,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 @@ -671,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 @@ -684,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)}" @@ -700,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 @@ -720,6 +929,7 @@ RSpec.describe "bundle install with explicit source paths" do it "runs pre-install hooks" do build_git "foo" gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{lib_path("foo-1.0")}" G @@ -732,13 +942,14 @@ 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 it "runs post-install hooks" do build_git "foo" gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{lib_path("foo-1.0")}" G @@ -751,13 +962,14 @@ 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 it "complains if the install hook fails" do build_git "foo" gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{lib_path("foo-1.0")}" G @@ -769,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 @@ -788,6 +1000,7 @@ RSpec.describe "bundle install with explicit source paths" do end install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :path => "#{lib_path("foo-1.0")}" gem "bar", :path => "#{lib_path("bar-1.0")}" G diff --git a/spec/bundler/install/gemfile/platform_spec.rb b/spec/bundler/install/gemfile/platform_spec.rb index c49594183e..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} @@ -400,6 +529,7 @@ RSpec.describe "bundle install with platform conditionals" do build_git "foo" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" platform :#{not_local_tag} do gem "foo", :git => "#{lib_path("foo-1.0")}" end @@ -409,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)}" @@ -421,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" @@ -434,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: @@ -456,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 @@ -481,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 4f931a493b..daee8a2744 100644 --- a/spec/bundler/install/gemfile/sources_spec.rb +++ b/spec/bundler/install/gemfile/sources_spec.rb @@ -20,25 +20,79 @@ RSpec.describe "bundle install with gems on multiple sources" do before do gemfile <<-G - source "#{file_uri_for(gem_repo3)}" - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo3" + source "https://gem.repo1" gem "rack-obama" gem "rack" G end - it "warns about ambiguous gems, but installs anyway, prioritizing sources last to first", :bundler => "< 3" do - bundle :install + 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: #{file_uri_for(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, :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 @@ -47,28 +101,56 @@ RSpec.describe "bundle install with gems on multiple sources" do before do gemfile <<-G - source "#{file_uri_for(gem_repo3)}" - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo3" + source "https://gem.repo1" gem "rack-obama" gem "rack", "1.0.0" # force it to install the working version in repo1 G end - it "warns about ambiguous gems, but installs anyway", :bundler => "< 3" do - bundle :install + 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: #{file_uri_for(gem_repo1)}") - expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0", :source => "remote1") + 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 "fails", :bundler => "3" do - bundle :install, :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 @@ -85,8 +167,8 @@ RSpec.describe "bundle install with gems on multiple sources" do end gemfile <<-G - source "#{file_uri_for(gem_repo3)}" - source "#{file_uri_for(gem_repo1)}" do + source "https://gem.repo3" + source "https://gem.repo1" do gem "thin" # comes first to test name sorting gem "rack" end @@ -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 + 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 + 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 + bundle :install, artifice: "compact_index" expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0") end @@ -128,10 +210,10 @@ RSpec.describe "bundle install with gems on multiple sources" do end end - install_gemfile <<-G - source "#{file_uri_for(gem_repo3)}" + install_gemfile <<-G, artifice: "compact_index" + source "https://gem.repo3" gem "rack-obama" # should come from repo3! - gem "rack", :source => "#{file_uri_for(gem_repo1)}" + gem "rack", :source => "https://gem.repo1" G end @@ -155,8 +237,8 @@ RSpec.describe "bundle install with gems on multiple sources" do end gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - source "#{file_uri_for(gem_repo3)}" do + source "https://gem.repo2" + source "https://gem.repo3" do gem "depends_on_rack" end G @@ -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 + 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 + 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 + 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,9 +300,9 @@ RSpec.describe "bundle install with gems on multiple sources" do context "and not in any other sources" do before do - install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - source "#{file_uri_for(gem_repo3)}" do + install_gemfile <<-G, artifice: "compact_index" + source "https://gem.repo2" + source "https://gem.repo3" do gem "depends_on_rack" end G @@ -235,23 +317,141 @@ RSpec.describe "bundle install with gems on multiple sources" do context "and in yet another source" do before do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - source "#{file_uri_for(gem_repo2)}" - source "#{file_uri_for(gem_repo3)}" do + source "https://gem.repo1" + source "https://gem.repo2" + source "https://gem.repo3" do gem "depends_on_rack" end G end - it "installs from the other source and warns about ambiguous gems", :bundler => "< 3" do - bundle :install + 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: #{file_uri_for(gem_repo2)}") + 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/ + 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 + + 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 + + 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 "fails", :bundler => "3" do - bundle :install, :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 @@ -267,16 +467,16 @@ RSpec.describe "bundle install with gems on multiple sources" do end gemfile <<-G - source "#{file_uri_for(gem_repo3)}" # contains depends_on_rack - source "#{file_uri_for(gem_repo2)}" # contains broken rack + source "https://gem.repo3" # contains depends_on_rack + source "https://gem.repo2" # contains broken rack gem "depends_on_rack" # installed from gem_repo3 - gem "rack", :source => "#{file_uri_for(gem_repo1)}" + gem "rack", :source => "https://gem.repo1" G end - it "installs the dependency from the pinned source without warning", :bundler => "< 3" do - bundle :install + 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") @@ -284,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 + 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, :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 @@ -308,363 +508,547 @@ RSpec.describe "bundle install with gems on multiple sources" do end gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "private_gem_1" - source "#{file_uri_for(gem_repo3)}" do + source "https://gem.repo3" do gem "private_gem_2" end G end it "fails" do - bundle :install, :raise_on_error => false - expect(err).to include("Could not find gem 'private_gem_1' in rubygems repository #{file_uri_for(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/ or installed locally.") end end - context "when a top-level gem has an indirect dependency" do - context "when disable_multisource is set" do - before do - bundle "config set disable_multisource true" + context "when an indirect dependency can't be found in the aggregate rubygems source", bundler: "< 3" do + before do + build_repo2 + + build_repo gem_repo3 do + build_gem "depends_on_missing", "1.0.1" do |s| + s.add_dependency "missing" + end end - before do - build_repo gem_repo2 do - build_gem "depends_on_rack", "1.0.1" do |s| - s.add_dependency "rack" - end + gemfile <<-G + source "https://gem.repo2" + + source "https://gem.repo3" + + gem "depends_on_missing" + G + end + + it "fails" do + 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 + + context "when a top-level gem has an indirect dependency" do + before do + build_repo gem_repo2 do + build_gem "depends_on_rack", "1.0.1" do |s| + s.add_dependency "rack" end + end + + build_repo gem_repo3 do + build_gem "unrelated_gem", "1.0.0" + end + + gemfile <<-G + source "https://gem.repo2" + + gem "depends_on_rack" - build_repo gem_repo3 do - build_gem "unrelated_gem", "1.0.0" + source "https://gem.repo3" do + gem "unrelated_gem" end + G + end - gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + context "and the dependency is only in the top-level source" do + before do + update_repo gem_repo2 do + build_gem "rack", "1.0.0" + end + end - gem "depends_on_rack" + it "installs the dependency from the top-level source without warning" do + 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") + end + end - source "#{file_uri_for(gem_repo3)}" do - gem "unrelated_gem" + context "and the dependency is only in a pinned source" do + before do + update_repo gem_repo3 do + build_gem "rack", "1.0.0" do |s| + s.write "lib/rack.rb", "RACK = 'FAIL'" end - G + end end - context "and the dependency is only in the top-level source" do - before do - update_repo gem_repo2 do - build_gem "rack", "1.0.0" - end + it "does not find the dependency" do + 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/ 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 + + context "and the dependency is in both the top-level and a pinned source" do + before do + update_repo gem_repo2 do + build_gem "rack", "1.0.0" end - it "installs all gems without warning" do - bundle :install - 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") + update_repo gem_repo3 do + build_gem "rack", "1.0.0" do |s| + s.write "lib/rack.rb", "RACK = 'FAIL'" + end end end - context "and the dependency is only in a pinned source" do - before do - update_repo gem_repo3 do - build_gem "rack", "1.0.0" do |s| - s.write "lib/rack.rb", "RACK = 'FAIL'" - end - end + it "installs the dependency from the top-level source without warning" do + 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") + end + end + end + + context "when a scoped gem has a deeply nested indirect dependency" do + before do + build_repo gem_repo3 do + build_gem "depends_on_depends_on_rack", "1.0.1" do |s| + s.add_dependency "depends_on_rack" end - it "does not find the dependency" do - bundle :install, :raise_on_error => false - expect(err).to include("Could not find gem 'rack', which is required by gem 'depends_on_rack', in any of the relevant sources") + build_gem "depends_on_rack", "1.0.1" do |s| + s.add_dependency "rack" end end - context "and the dependency is in both the top-level and a pinned source" do - before do - update_repo gem_repo2 do - build_gem "rack", "1.0.0" - end + gemfile <<-G + source "https://gem.repo2" - update_repo gem_repo3 do - build_gem "rack", "1.0.0" do |s| - s.write "lib/rack.rb", "RACK = 'FAIL'" - end - end + source "https://gem.repo3" do + gem "depends_on_depends_on_rack" end + G + end - it "installs the dependency from the top-level source without warning" do - bundle :install - 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") + context "and the dependency is only in the top-level source" do + before do + update_repo gem_repo2 do + build_gem "rack", "1.0.0" end end + + it "installs the dependency from the top-level 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") + 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 - context "when the lockfile has aggregated rubygems sources and newer versions of dependencies are available" do + context "and the dependency is only in a pinned source" do before do - build_repo gem_repo2 do - build_gem "activesupport", "6.0.3.4" do |s| - s.add_dependency "concurrent-ruby", "~> 1.0", ">= 1.0.2" - s.add_dependency "i18n", ">= 0.7", "< 2" - s.add_dependency "minitest", "~> 5.1" - s.add_dependency "tzinfo", "~> 1.1" - s.add_dependency "zeitwerk", "~> 2.2", ">= 2.2.2" - end + build_repo2 - build_gem "activesupport", "6.1.2.1" do |s| - s.add_dependency "concurrent-ruby", "~> 1.0", ">= 1.0.2" - s.add_dependency "i18n", ">= 1.6", "< 2" - s.add_dependency "minitest", ">= 5.1" - s.add_dependency "tzinfo", "~> 2.0" - s.add_dependency "zeitwerk", "~> 2.3" - end + update_repo gem_repo3 do + build_gem "rack", "1.0.0" + end + end - build_gem "concurrent-ruby", "1.1.8" - build_gem "concurrent-ruby", "1.1.9" - build_gem "connection_pool", "2.2.3" + 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") + end + end - build_gem "i18n", "1.8.9" do |s| - s.add_dependency "concurrent-ruby", "~> 1.0" + context "and the dependency is in both the top-level and a pinned source" do + before do + update_repo gem_repo2 do + build_gem "rack", "1.0.0" do |s| + s.write "lib/rack.rb", "RACK = 'FAIL'" end + end - build_gem "minitest", "5.14.3" - build_gem "rack", "2.2.3" - build_gem "redis", "4.2.5" + update_repo gem_repo3 do + build_gem "rack", "1.0.0" + end + end - build_gem "sidekiq", "6.1.3" do |s| - s.add_dependency "connection_pool", ">= 2.2.2" - s.add_dependency "rack", "~> 2.0" - s.add_dependency "redis", ">= 4.2.0" - 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") + end + end + end - build_gem "thread_safe", "0.3.6" + context "when the lockfile has aggregated rubygems sources and newer versions of dependencies are available" do + before do + build_repo gem_repo2 do + build_gem "activesupport", "6.0.3.4" do |s| + s.add_dependency "concurrent-ruby", "~> 1.0", ">= 1.0.2" + s.add_dependency "i18n", ">= 0.7", "< 2" + s.add_dependency "minitest", "~> 5.1" + s.add_dependency "tzinfo", "~> 1.1" + s.add_dependency "zeitwerk", "~> 2.2", ">= 2.2.2" + end - build_gem "tzinfo", "1.2.9" do |s| - s.add_dependency "thread_safe", "~> 0.1" - end + build_gem "activesupport", "6.1.2.1" do |s| + s.add_dependency "concurrent-ruby", "~> 1.0", ">= 1.0.2" + s.add_dependency "i18n", ">= 1.6", "< 2" + s.add_dependency "minitest", ">= 5.1" + s.add_dependency "tzinfo", "~> 2.0" + s.add_dependency "zeitwerk", "~> 2.3" + end - build_gem "tzinfo", "2.0.4" do |s| - s.add_dependency "concurrent-ruby", "~> 1.0" - end + build_gem "concurrent-ruby", "1.1.8" + build_gem "concurrent-ruby", "1.1.9" + build_gem "connection_pool", "2.2.3" - build_gem "zeitwerk", "2.4.2" + build_gem "i18n", "1.8.9" do |s| + s.add_dependency "concurrent-ruby", "~> 1.0" end - build_repo gem_repo3 do - build_gem "sidekiq-pro", "5.2.1" do |s| - s.add_dependency "connection_pool", ">= 2.2.3" - s.add_dependency "sidekiq", ">= 6.1.0" - end + build_gem "minitest", "5.14.3" + build_gem "rack", "2.2.3" + build_gem "redis", "4.2.5" + + build_gem "sidekiq", "6.1.3" do |s| + s.add_dependency "connection_pool", ">= 2.2.2" + s.add_dependency "rack", "~> 2.0" + s.add_dependency "redis", ">= 4.2.0" end - gemfile <<-G - # frozen_string_literal: true + build_gem "thread_safe", "0.3.6" - source "#{file_uri_for(gem_repo2)}" + build_gem "tzinfo", "1.2.9" do |s| + s.add_dependency "thread_safe", "~> 0.1" + end - gem "activesupport" + build_gem "tzinfo", "2.0.4" do |s| + s.add_dependency "concurrent-ruby", "~> 1.0" + end - source "#{file_uri_for(gem_repo3)}" do - gem "sidekiq-pro" - end - G + build_gem "zeitwerk", "2.4.2" + end - lockfile <<~L - GEM - remote: #{file_uri_for(gem_repo2)}/ - remote: #{file_uri_for(gem_repo3)}/ - specs: - activesupport (6.0.3.4) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - zeitwerk (~> 2.2, >= 2.2.2) - concurrent-ruby (1.1.8) - connection_pool (2.2.3) - i18n (1.8.9) - concurrent-ruby (~> 1.0) - minitest (5.14.3) - rack (2.2.3) - redis (4.2.5) - sidekiq (6.1.3) - connection_pool (>= 2.2.2) - rack (~> 2.0) - redis (>= 4.2.0) - sidekiq-pro (5.2.1) - connection_pool (>= 2.2.3) - sidekiq (>= 6.1.0) - thread_safe (0.3.6) - tzinfo (1.2.9) - thread_safe (~> 0.1) - zeitwerk (2.4.2) + build_repo gem_repo3 do + build_gem "sidekiq-pro", "5.2.1" do |s| + s.add_dependency "connection_pool", ">= 2.2.3" + s.add_dependency "sidekiq", ">= 6.1.0" + end + end - PLATFORMS - #{specific_local_platform} + gemfile <<-G + # frozen_string_literal: true - DEPENDENCIES - activesupport - sidekiq-pro! + source "https://gem.repo2" - BUNDLED WITH - #{Bundler::VERSION} - L + gem "activesupport" + + source "https://gem.repo3" do + gem "sidekiq-pro" + 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 - it "does not install newer versions or generate lockfile changes when running bundle install, and warns", :bundler => "< 3" do - initial_lockfile = lockfile + lockfile <<~L + GEM + remote: https://gem.repo2/ + remote: https://gem.repo3/ + specs: + activesupport (6.0.3.4) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) + minitest (~> 5.1) + tzinfo (~> 1.1) + zeitwerk (~> 2.2, >= 2.2.2) + concurrent-ruby (1.1.8) + connection_pool (2.2.3) + i18n (1.8.9) + concurrent-ruby (~> 1.0) + minitest (5.14.3) + rack (2.2.3) + redis (4.2.5) + sidekiq (6.1.3) + connection_pool (>= 2.2.2) + rack (~> 2.0) + redis (>= 4.2.0) + sidekiq-pro (5.2.1) + connection_pool (>= 2.2.3) + sidekiq (>= 6.1.0) + thread_safe (0.3.6) + tzinfo (1.2.9) + thread_safe (~> 0.1) + zeitwerk (2.4.2) - bundle :install + PLATFORMS + #{lockfile_platforms} - expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.") + DEPENDENCIES + activesupport + sidekiq-pro! + #{@locked_checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end - expect(the_bundle).to include_gems("activesupport 6.0.3.4") - expect(the_bundle).not_to include_gems("activesupport 6.1.2.1") - expect(the_bundle).to include_gems("tzinfo 1.2.9") - expect(the_bundle).not_to include_gems("tzinfo 2.0.4") - expect(the_bundle).to include_gems("concurrent-ruby 1.1.8") - expect(the_bundle).not_to include_gems("concurrent-ruby 1.1.9") + 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" + expect(err).to be_empty - expect(lockfile).to eq(initial_lockfile) - end + expect(the_bundle).to include_gems("activesupport 6.0.3.4") + expect(the_bundle).not_to include_gems("activesupport 6.1.2.1") + expect(the_bundle).to include_gems("tzinfo 1.2.9") + expect(the_bundle).not_to include_gems("tzinfo 2.0.4") + expect(the_bundle).to include_gems("concurrent-ruby 1.1.8") + expect(the_bundle).not_to include_gems("concurrent-ruby 1.1.9") - it "fails when running bundle install", :bundler => "3" do - initial_lockfile = lockfile + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo2/ + specs: + activesupport (6.0.3.4) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) + minitest (~> 5.1) + tzinfo (~> 1.1) + zeitwerk (~> 2.2, >= 2.2.2) + concurrent-ruby (1.1.8) + connection_pool (2.2.3) + i18n (1.8.9) + concurrent-ruby (~> 1.0) + minitest (5.14.3) + rack (2.2.3) + redis (4.2.5) + sidekiq (6.1.3) + connection_pool (>= 2.2.2) + rack (~> 2.0) + redis (>= 4.2.0) + thread_safe (0.3.6) + tzinfo (1.2.9) + thread_safe (~> 0.1) + zeitwerk (2.4.2) - bundle :install, :raise_on_error => false + GEM + remote: https://gem.repo3/ + specs: + sidekiq-pro (5.2.1) + connection_pool (>= 2.2.3) + sidekiq (>= 6.1.0) - expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.") + PLATFORMS + #{lockfile_platforms} - expect(lockfile).to eq(initial_lockfile) - end + DEPENDENCIES + activesupport + sidekiq-pro! + #{@locked_checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end - it "splits sections and upgrades gems when running bundle update, and doesn't warn" do - bundle "update --all" - expect(err).to be_empty + 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 - expect(the_bundle).not_to include_gems("activesupport 6.0.3.4") - expect(the_bundle).to include_gems("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") - expect(the_bundle).not_to include_gems("concurrent-ruby 1.1.8") - expect(the_bundle).to include_gems("concurrent-ruby 1.1.9") + bundle "config set --local frozen true" + bundle :install, artifice: "compact_index" - expect(lockfile).to eq <<~L - GEM - remote: #{file_uri_for(gem_repo2)}/ - specs: - activesupport (6.1.2.1) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 1.6, < 2) - minitest (>= 5.1) - tzinfo (~> 2.0) - zeitwerk (~> 2.3) - concurrent-ruby (1.1.9) - connection_pool (2.2.3) - i18n (1.8.9) - concurrent-ruby (~> 1.0) - minitest (5.14.3) - rack (2.2.3) - redis (4.2.5) - sidekiq (6.1.3) - connection_pool (>= 2.2.2) - rack (~> 2.0) - redis (>= 4.2.0) - tzinfo (2.0.4) - concurrent-ruby (~> 1.0) - zeitwerk (2.4.2) + expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.") - GEM - remote: #{file_uri_for(gem_repo3)}/ - specs: - sidekiq-pro (5.2.1) - connection_pool (>= 2.2.3) - sidekiq (>= 6.1.0) + expect(the_bundle).to include_gems("activesupport 6.0.3.4") + expect(the_bundle).not_to include_gems("activesupport 6.1.2.1") + expect(the_bundle).to include_gems("tzinfo 1.2.9") + expect(the_bundle).not_to include_gems("tzinfo 2.0.4") + expect(the_bundle).to include_gems("concurrent-ruby 1.1.8") + expect(the_bundle).not_to include_gems("concurrent-ruby 1.1.9") - PLATFORMS - #{specific_local_platform} + expect(lockfile).to eq(initial_lockfile) + end - DEPENDENCIES - activesupport - sidekiq-pro! + it "fails when running bundle install in frozen mode", bundler: "3" do + initial_lockfile = lockfile - BUNDLED WITH - #{Bundler::VERSION} - L - end + bundle "config set --local frozen true" + bundle :install, artifice: "compact_index", raise_on_error: false - it "it keeps the current lockfile format and upgrades the requested gem when running bundle update with an argument, and warns", :bundler => "< 3" do - bundle "update concurrent-ruby" - expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.") + expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.") - expect(the_bundle).to include_gems("activesupport 6.0.3.4") - expect(the_bundle).not_to include_gems("activesupport 6.1.2.1") - expect(the_bundle).to include_gems("tzinfo 1.2.9") - expect(the_bundle).not_to include_gems("tzinfo 2.0.4") - expect(the_bundle).to include_gems("concurrent-ruby 1.1.9") - expect(the_bundle).not_to include_gems("concurrent-ruby 1.1.8") + expect(lockfile).to eq(initial_lockfile) + end - expect(lockfile).to eq <<~L - GEM - remote: #{file_uri_for(gem_repo2)}/ - remote: #{file_uri_for(gem_repo3)}/ - specs: - activesupport (6.0.3.4) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - zeitwerk (~> 2.2, >= 2.2.2) - concurrent-ruby (1.1.9) - connection_pool (2.2.3) - i18n (1.8.9) - concurrent-ruby (~> 1.0) - minitest (5.14.3) - rack (2.2.3) - redis (4.2.5) - sidekiq (6.1.3) - connection_pool (>= 2.2.2) - rack (~> 2.0) - redis (>= 4.2.0) - sidekiq-pro (5.2.1) - connection_pool (>= 2.2.3) - sidekiq (>= 6.1.0) - thread_safe (0.3.6) - tzinfo (1.2.9) - thread_safe (~> 0.1) - zeitwerk (2.4.2) + it "splits sections and upgrades gems when running bundle update, and doesn't warn" do + bundle "update --all", artifice: "compact_index" + expect(err).to be_empty - PLATFORMS - #{specific_local_platform} + 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" - DEPENDENCIES - activesupport - sidekiq-pro! + 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" - BUNDLED WITH - #{Bundler::VERSION} - L - end + 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" - it "fails when running bundle update with an argument", :bundler => "3" do - initial_lockfile = lockfile + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo2/ + specs: + activesupport (6.1.2.1) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 1.6, < 2) + minitest (>= 5.1) + tzinfo (~> 2.0) + zeitwerk (~> 2.3) + concurrent-ruby (1.1.9) + connection_pool (2.2.3) + i18n (1.8.9) + concurrent-ruby (~> 1.0) + minitest (5.14.3) + rack (2.2.3) + redis (4.2.5) + sidekiq (6.1.3) + connection_pool (>= 2.2.2) + rack (~> 2.0) + redis (>= 4.2.0) + tzinfo (2.0.4) + concurrent-ruby (~> 1.0) + zeitwerk (2.4.2) - bundle "update concurrent-ruby", :raise_on_error => false + GEM + remote: https://gem.repo3/ + specs: + sidekiq-pro (5.2.1) + connection_pool (>= 2.2.3) + sidekiq (>= 6.1.0) - expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.") + PLATFORMS + #{lockfile_platforms} - expect(lockfile).to eq(initial_lockfile) - end + 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" + expect(err).to be_empty + + expect(the_bundle).to include_gems("activesupport 6.0.3.4") + expect(the_bundle).not_to include_gems("activesupport 6.1.2.1") + expect(the_bundle).to include_gems("tzinfo 1.2.9") + expect(the_bundle).not_to include_gems("tzinfo 2.0.4") + 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/ + specs: + activesupport (6.0.3.4) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) + minitest (~> 5.1) + tzinfo (~> 1.1) + zeitwerk (~> 2.2, >= 2.2.2) + concurrent-ruby (1.1.9) + connection_pool (2.2.3) + i18n (1.8.9) + concurrent-ruby (~> 1.0) + minitest (5.14.3) + rack (2.2.3) + redis (4.2.5) + sidekiq (6.1.3) + connection_pool (>= 2.2.2) + rack (~> 2.0) + redis (>= 4.2.0) + thread_safe (0.3.6) + tzinfo (1.2.9) + thread_safe (~> 0.1) + zeitwerk (2.4.2) + + GEM + remote: https://gem.repo3/ + specs: + sidekiq-pro (5.2.1) + connection_pool (>= 2.2.3) + sidekiq (>= 6.1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + activesupport + sidekiq-pro! + #{@locked_checksums} + BUNDLED WITH + #{Bundler::VERSION} + L end end - context "when a top-level gem has an indirect dependency present in the default source, but with a different version from the one resolved", :bundler => "< 3" 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 @@ -677,7 +1061,7 @@ RSpec.describe "bundle install with gems on multiple sources" do end gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gemspec :path => "#{lib_path("rails")}" @@ -686,11 +1070,11 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "installs all gems without warning" do - bundle :install + 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 @@ -711,9 +1095,9 @@ RSpec.describe "bundle install with gems on multiple sources" do end gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" - source "#{file_uri_for(gem_repo3)}" do + source "https://gem.repo3" do gem "handsoap" end @@ -722,45 +1106,51 @@ 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: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: nokogiri (1.11.1) racca (~> 1.4) racca (1.5.2) GEM - remote: #{file_uri_for(gem_repo3)}/ + remote: https://gem.repo3/ specs: handsoap (0.2.5.5) nokogiri (>= 1.2.3) PLATFORMS - #{specific_local_platform} + #{lockfile_platforms} DEPENDENCIES handsoap! nokogiri - + #{checksums} BUNDLED WITH #{Bundler::VERSION} L - bundle "install --verbose" + 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" + 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 @@ -771,9 +1161,9 @@ RSpec.describe "bundle install with gems on multiple sources" do build_gem "not_in_repo1", "1.0.0" end - install_gemfile <<-G, :raise_on_error => false - source "#{file_uri_for(gem_repo3)}" - gem "not_in_repo1", :source => "#{file_uri_for(gem_repo1)}" + install_gemfile <<-G, artifice: "compact_index", raise_on_error: false + source "https://gem.repo3" + gem "not_in_repo1", :source => "https://gem.repo1" G end @@ -784,28 +1174,28 @@ 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 - remote: #{file_uri_for(gem_repo1)} + remote: https://gem.repo1 specs: GEM - remote: #{file_uri_for(gem_repo3)} + remote: https://gem.repo3 specs: rack (0.9.1) PLATFORMS - ruby + #{lockfile_platforms} DEPENDENCIES rack! L gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - source "#{file_uri_for(gem_repo3)}" do + source "https://gem.repo1" + source "https://gem.repo3" do gem 'rack' end G @@ -821,17 +1211,17 @@ RSpec.describe "bundle install with gems on multiple sources" do let(:aggregate_gem_section_lockfile) do <<~L GEM - remote: #{file_uri_for(gem_repo1)}/ - remote: #{file_uri_for(gem_repo3)}/ + remote: https://gem.repo1/ + remote: https://gem.repo3/ specs: rack (0.9.1) PLATFORMS - #{specific_local_platform} + #{lockfile_platforms} DEPENDENCIES rack! - + #{checksums_section} BUNDLED WITH #{Bundler::VERSION} L @@ -840,16 +1230,16 @@ RSpec.describe "bundle install with gems on multiple sources" do let(:split_gem_section_lockfile) do <<~L GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: GEM - remote: #{file_uri_for(gem_repo3)}/ + remote: https://gem.repo3/ specs: rack (0.9.1) PLATFORMS - #{specific_local_platform} + #{lockfile_platforms} DEPENDENCIES rack! @@ -865,8 +1255,8 @@ RSpec.describe "bundle install with gems on multiple sources" do end gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - source "#{file_uri_for(gem_repo3)}" do + source "https://gem.repo1" + source "https://gem.repo3" do gem 'rack' end G @@ -874,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" + 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 "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 + + 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 + it "refuses to install the existing lockfile and prints an error", bundler: "3" do bundle "config set --local deployment true" - bundle "install", :raise_on_error =>false + 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.") @@ -900,13 +1318,14 @@ RSpec.describe "bundle install with gems on multiple sources" do build_lib "foo" gemfile <<-G - gem "rack", :source => "#{file_uri_for(gem_repo1)}" + source "#{file_uri_for(gem_repo1)}" + gem "rack", :source => "https://gem.repo1" gem "foo", :path => "#{lib_path("foo-1.0")}" G end it "does not unlock the non-path gem after install" do - bundle :install + bundle :install, artifice: "compact_index" bundle %(exec ruby -e 'puts "OK"') @@ -919,8 +1338,8 @@ RSpec.describe "bundle install with gems on multiple sources" do before do system_gems "rack-0.9.1" - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + install_gemfile <<-G, artifice: "compact_index" + source "https://gem.repo1" gem "rack" # should come from repo1! G end @@ -941,14 +1360,14 @@ RSpec.describe "bundle install with gems on multiple sources" do # Installing this gemfile... gemfile <<-G - source '#{file_uri_for(gem_repo1)}' + source 'https://gem.repo1' gem 'rack' - gem 'foo', '~> 0.1', :source => '#{file_uri_for(gem_repo4)}' - gem 'bar', '~> 0.1', :source => '#{file_uri_for(gem_repo4)}' + gem 'foo', '~> 0.1', :source => 'https://gem.repo4' + gem 'bar', '~> 0.1', :source => 'https://gem.repo4' G bundle "config set --local path ../gems/system" - bundle :install + bundle :install, artifice: "compact_index" # And then we add some new versions... update_repo4 do @@ -959,11 +1378,11 @@ 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 - source '#{file_uri_for(gem_repo1)}' + install_gemfile <<-G, artifice: "compact_index" + source 'https://gem.repo1' gem 'rack' - gem 'foo', '~> 0.2', :source => '#{file_uri_for(gem_repo4)}' - gem 'bar', '~> 0.1', :source => '#{file_uri_for(gem_repo4)}' + gem 'foo', '~> 0.2', :source => 'https://gem.repo4' + gem 'bar', '~> 0.1', :source => 'https://gem.repo4' G # It should update foo to 0.2, but not the (locked) bar 0.1 @@ -983,11 +1402,11 @@ RSpec.describe "bundle install with gems on multiple sources" do build_git "git1" build_git "git2" - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + install_gemfile <<-G, artifice: "compact_index" + source "https://gem.repo1" gem "rails" - source "#{file_uri_for(gem_repo3)}" do + source "https://gem.repo3" do gem "rack" end @@ -999,7 +1418,7 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "does not re-resolve" do - bundle :install, :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 @@ -1008,27 +1427,24 @@ 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 - source "#{file_uri_for(gem_repo1)}" + install_gemfile <<-G, artifice: "compact_index" + source "https://gem.repo1" gem "rack" G end context "and the gemfile changes" do it "is still able to find that gem from remote sources" do - source_uri = file_uri_for(gem_repo1) - second_uri = file_uri_for(gem_repo4) - build_repo4 do build_gem "rack", "2.0.1.1.forked" build_gem "thor", "0.19.1.1.forked" end # When this gemfile is installed... - install_gemfile <<-G - source "#{source_uri}" + install_gemfile <<-G, artifice: "compact_index" + source "https://gem.repo1" - source "#{second_uri}" do + source "https://gem.repo4" do gem "rack", "2.0.1.1.forked" gem "thor" end @@ -1037,9 +1453,9 @@ RSpec.describe "bundle install with gems on multiple sources" do # Then we change the Gemfile by adding a version to thor gemfile <<-G - source "#{source_uri}" + source "https://gem.repo1" - source "#{second_uri}" do + source "https://gem.repo4" do gem "rack", "2.0.1.1.forked" gem "thor", "0.19.1.1.forked" end @@ -1047,15 +1463,15 @@ 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 + 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 - source "#{file_uri_for(gem_repo1)}" + install_gemfile <<-G, artifice: "compact_index" + source "https://gem.repo1" gem "rack" G @@ -1068,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 - source "#{file_uri_for(gem_repo2)}" + 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 @@ -1093,23 +1535,86 @@ RSpec.describe "bundle install with gems on multiple sources" do build_gem "example", "1.0.2" end - install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + install_gemfile <<-G, artifice: "compact_index" + source "https://gem.repo4" - gem "example", :source => "#{file_uri_for(gem_repo2)}" + gem "example", :source => "https://gem.repo2" G 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" + 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 - context "when a gem is available from multiple ambiguous sources", :bundler => "3" 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" + + source "https://gem.repo4" do + gem "example" + end + G + + 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 + it "succeeds but warns, suggesting a source block" do + build_repo4 do + build_gem "depends_on_rack" do |s| + s.add_dependency "rack" + end + build_gem "rack" + end + + 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" + end + + source "https://gem.repo1" do + gem "thin" + end + G + expect(err).to eq <<~EOS.strip + Warning: The gem 'rack' was found in multiple relevant sources. + * 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 + expect(the_bundle).to be_locked + end + end + + 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| @@ -1118,21 +1623,300 @@ RSpec.describe "bundle install with gems on multiple sources" do build_gem "rack" end - install_gemfile <<-G, :raise_on_error => false - source "#{file_uri_for(gem_repo4)}" - source "#{file_uri_for(gem_repo1)}" do + 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" + end + source "https://gem.repo1" do gem "thin" end - gem "depends_on_rack" 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 #{file_uri_for(gem_repo1)}/ or installed locally - * rubygems repository #{file_uri_for(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 end end + + context "when upgrading a lockfile suffering from dependency confusion" do + before do + build_repo4 do + build_gem "mime-types", "3.0.0" + end + + build_repo2 do + build_gem "capybara", "2.5.0" do |s| + s.add_dependency "mime-types", ">= 1.16" + end + + build_gem "mime-types", "3.3.1" + end + + gemfile <<~G + source "https://gem.repo2" + + gem "capybara", "~> 2.5.0" + + source "https://gem.repo4" do + gem "mime-types", "~> 3.0" + end + G + + lockfile <<-L + GEM + remote: https://gem.repo2/ + remote: https://gem.repo4/ + specs: + capybara (2.5.0) + mime-types (>= 1.16) + mime-types (3.3.1) + + PLATFORMS + #{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" + + 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 + remote: https://gem.repo2/ + specs: + capybara (2.5.0) + mime-types (>= 1.16) + + GEM + remote: https://gem.repo4/ + specs: + mime-types (3.0.0) + + PLATFORMS + #{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 a5b78443b9..1a33053dc6 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 "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 + 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 "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 + 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,43 +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 - 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)}" @@ -212,41 +309,1075 @@ 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", :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 = Bundler.local_platform } + end + + gemfile <<~G + source "#{file_uri_for(gem_repo2)}" + + gem "sorbet-static", "0.5.6403" + G + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo2)}/ + specs: + sorbet-static (0.5.6403-#{Bundler.local_platform}) + + PLATFORMS + ruby + + DEPENDENCIES + sorbet-static (= 0.5.6403) + + BUNDLED WITH + #{Bundler::VERSION} + L + + 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)}/ 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)}/ 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)}/ 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 @@ -279,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 |