# frozen_string_literal: true RSpec.describe "bundle install with install-time dependencies" do before do build_repo2 do build_gem "with_implicit_rake_dep" do |s| s.extensions << "Rakefile" s.write "Rakefile", <<-RUBY task :default do path = File.expand_path("lib", __dir__) FileUtils.mkdir_p(path) File.open("\#{path}/implicit_rake_dep.rb", "w") do |f| f.puts "IMPLICIT_RAKE_DEP = 'YES'" end end RUBY end build_gem "another_implicit_rake_dep" do |s| s.extensions << "Rakefile" s.write "Rakefile", <<-RUBY task :default do path = File.expand_path("lib", __dir__) FileUtils.mkdir_p(path) File.open("\#{path}/another_implicit_rake_dep.rb", "w") do |f| f.puts "ANOTHER_IMPLICIT_RAKE_DEP = 'YES'" end end RUBY end # Test complicated gem dependencies for install build_gem "net_a" do |s| s.add_dependency "net_b" s.add_dependency "net_build_extensions" end build_gem "net_b" build_gem "net_build_extensions" do |s| s.add_dependency "rake" s.extensions << "Rakefile" s.write "Rakefile", <<-RUBY task :default do path = File.expand_path("lib", __dir__) FileUtils.mkdir_p(path) File.open("\#{path}/net_build_extensions.rb", "w") do |f| f.puts "NET_BUILD_EXTENSIONS = 'YES'" end end RUBY end build_gem "net_c" do |s| s.add_dependency "net_a" s.add_dependency "net_d" end build_gem "net_d" build_gem "net_e" do |s| s.add_dependency "net_d" end end end it "installs gems with implicit rake dependencies" do install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" gem "with_implicit_rake_dep" gem "another_implicit_rake_dep" gem "rake" G run <<-R require 'implicit_rake_dep' require 'another_implicit_rake_dep' puts IMPLICIT_RAKE_DEP puts ANOTHER_IMPLICIT_RAKE_DEP R expect(out).to eq("YES\nYES") end it "installs gems with implicit rake dependencies without rake previously installed" do with_path_as("") do install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" gem "with_implicit_rake_dep" gem "another_implicit_rake_dep" gem "rake" G end run <<-R require 'implicit_rake_dep' require 'another_implicit_rake_dep' puts IMPLICIT_RAKE_DEP puts ANOTHER_IMPLICIT_RAKE_DEP R expect(out).to eq("YES\nYES") end it "installs gems with a dependency with no type" do build_repo2 path = "#{gem_repo2}/#{Gem::MARSHAL_SPEC_DIR}/actionpack-2.3.2.gemspec.rz" spec = Marshal.load(Bundler.rubygems.inflate(File.binread(path))) spec.dependencies.each do |d| d.instance_variable_set(:@type, "fail") end File.open(path, "wb") do |f| f.write Gem.deflate(Marshal.dump(spec)) end install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" gem "actionpack", "2.3.2" G expect(the_bundle).to include_gems "actionpack 2.3.2", "activesupport 2.3.2" end describe "with crazy rubygem plugin stuff" do it "installs plugins" do install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" gem "net_b" G expect(the_bundle).to include_gems "net_b 1.0" end it "installs plugins depended on by other plugins" do install_gemfile <<-G, env: { "DEBUG" => "1" } source "#{file_uri_for(gem_repo2)}" gem "net_a" G expect(the_bundle).to include_gems "net_a 1.0", "net_b 1.0" end it "installs multiple levels of dependencies" do install_gemfile <<-G, env: { "DEBUG" => "1" } source "#{file_uri_for(gem_repo2)}" gem "net_c" gem "net_e" G expect(the_bundle).to include_gems "net_a 1.0", "net_b 1.0", "net_c 1.0", "net_d 1.0", "net_e 1.0" end context "with ENV['BUNDLER_DEBUG_RESOLVER'] set" do it "produces debug output" do gemfile <<-G source "#{file_uri_for(gem_repo2)}" gem "net_c" gem "net_e" G bundle :install, env: { "BUNDLER_DEBUG_RESOLVER" => "1", "DEBUG" => "1" } expect(out).to include("Resolving dependencies...") end end context "with ENV['DEBUG_RESOLVER'] set" do it "produces debug output" do gemfile <<-G source "#{file_uri_for(gem_repo2)}" gem "net_c" gem "net_e" G bundle :install, env: { "DEBUG_RESOLVER" => "1", "DEBUG" => "1" } expect(out).to include("Resolving dependencies...") end end context "with ENV['DEBUG_RESOLVER_TREE'] set" do it "produces debug output" do gemfile <<-G source "#{file_uri_for(gem_repo2)}" gem "net_c" gem "net_e" G bundle :install, env: { "DEBUG_RESOLVER_TREE" => "1", "DEBUG" => "1" } expect(out).to include(" net_b"). and include("Resolving dependencies..."). and include("Solution found after 1 attempts:"). and include("selected net_b 1.0") end end end describe "when a required ruby version" do context "allows only an older version" do it "installs the older version" do build_repo2 do build_gem "rack", "1.2" do |s| s.executables = "rackup" end build_gem "rack", "9001.0.0" do |s| s.required_ruby_version = "> 9000" end end install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } ruby "#{Gem.ruby_version}" source "http://localgemserver.test/" gem 'rack' G expect(err).to_not include("rack-9001.0.0 requires ruby version > 9000") expect(the_bundle).to include_gems("rack 1.2") end it "installs the older version when using servers not implementing the compact index API" do build_repo2 do build_gem "rack", "1.2" do |s| s.executables = "rackup" end build_gem "rack", "9001.0.0" do |s| s.required_ruby_version = "> 9000" end end install_gemfile <<-G, artifice: "endpoint", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } ruby "#{Gem.ruby_version}" source "http://localgemserver.test/" gem 'rack' G expect(err).to_not include("rack-9001.0.0 requires ruby version > 9000") expect(the_bundle).to include_gems("rack 1.2") end context "when there is a lockfile using the newer incompatible version" do before do build_repo2 do build_gem "parallel_tests", "3.7.0" do |s| s.required_ruby_version = ">= #{current_ruby_minor}" end build_gem "parallel_tests", "3.8.0" do |s| s.required_ruby_version = ">= #{next_ruby_minor}" end end gemfile <<-G source "http://localgemserver.test/" gem 'parallel_tests' G checksums = checksums_section do |c| c.checksum gem_repo2, "parallel_tests", "3.8.0" end lockfile <<~L GEM remote: http://localgemserver.test/ specs: parallel_tests (3.8.0) PLATFORMS #{lockfile_platforms} DEPENDENCIES parallel_tests #{checksums} BUNDLED WITH #{Bundler::VERSION} L end it "automatically updates lockfile to use the older version" do bundle "install --verbose", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } checksums = checksums_section_when_existing do |c| c.checksum gem_repo2, "parallel_tests", "3.7.0" end expect(lockfile).to eq <<~L GEM remote: http://localgemserver.test/ specs: parallel_tests (3.7.0) PLATFORMS #{lockfile_platforms} DEPENDENCIES parallel_tests #{checksums} BUNDLED WITH #{Bundler::VERSION} L end it "gives a meaningful error if we're in frozen mode" do expect do bundle "install --verbose", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s, "BUNDLE_FROZEN" => "true" }, raise_on_error: false end.not_to change { lockfile } expect(err).to include("parallel_tests-3.8.0 requires ruby version >= #{next_ruby_minor}") expect(err).not_to include("That means the author of parallel_tests (3.8.0) has removed it.") end end context "with transitive dependencies in a lockfile" do before do build_repo2 do build_gem "rubocop", "1.28.2" do |s| s.required_ruby_version = ">= #{current_ruby_minor}" s.add_dependency "rubocop-ast", ">= 1.17.0", "< 2.0" end build_gem "rubocop", "1.35.0" do |s| s.required_ruby_version = ">= #{next_ruby_minor}" s.add_dependency "rubocop-ast", ">= 1.20.1", "< 2.0" end build_gem "rubocop-ast", "1.17.0" do |s| s.required_ruby_version = ">= #{current_ruby_minor}" end build_gem "rubocop-ast", "1.21.0" do |s| s.required_ruby_version = ">= #{next_ruby_minor}" end end gemfile <<-G source "http://localgemserver.test/" gem 'rubocop' G checksums = checksums_section do |c| c.checksum gem_repo2, "rubocop", "1.35.0" c.checksum gem_repo2, "rubocop-ast", "1.21.0" end lockfile <<~L GEM remote: http://localgemserver.test/ specs: rubocop (1.35.0) rubocop-ast (>= 1.20.1, < 2.0) rubocop-ast (1.21.0) PLATFORMS #{lockfile_platforms} DEPENDENCIES parallel_tests #{checksums} BUNDLED WITH #{Bundler::VERSION} L end it "automatically updates lockfile to use the older compatible versions" do bundle "install --verbose", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } checksums = checksums_section_when_existing do |c| c.checksum gem_repo2, "rubocop", "1.28.2" c.checksum gem_repo2, "rubocop-ast", "1.17.0" end expect(lockfile).to eq <<~L GEM remote: http://localgemserver.test/ specs: rubocop (1.28.2) rubocop-ast (>= 1.17.0, < 2.0) rubocop-ast (1.17.0) PLATFORMS #{lockfile_platforms} DEPENDENCIES rubocop #{checksums} BUNDLED WITH #{Bundler::VERSION} L end end context "with a Gemfile and lock file that don't resolve under the current platform" do before do build_repo4 do build_gem "sorbet", "0.5.10554" do |s| s.add_dependency "sorbet-static", "0.5.10554" end build_gem "sorbet-static", "0.5.10554" do |s| s.platform = "universal-darwin-21" end end gemfile <<~G source "#{file_uri_for(gem_repo4)}" gem 'sorbet', '= 0.5.10554' G lockfile <<~L GEM remote: #{file_uri_for(gem_repo4)}/ specs: sorbet (0.5.10554) sorbet-static (= 0.5.10554) sorbet-static (0.5.10554-universal-darwin-21) PLATFORMS arm64-darwin-21 DEPENDENCIES sorbet (= 0.5.10554) BUNDLED WITH #{Bundler::VERSION} L end it "raises a proper error" do simulate_platform "aarch64-linux" do bundle "install", raise_on_error: false end nice_error = <<~E.strip Could not find gems matching 'sorbet-static (= 0.5.10554)' valid for all resolution platforms (arm64-darwin-21, aarch64-linux) in rubygems repository #{file_uri_for(gem_repo4)}/, cached gems or installed locally. The source contains the following gems matching 'sorbet-static (= 0.5.10554)': * sorbet-static-0.5.10554-universal-darwin-21 E expect(err).to end_with(nice_error) end end context "when adding a new gem that does not resolve under all locked platforms" do before do simulate_platform "x86_64-linux" do build_repo4 do build_gem "nokogiri", "1.14.0" do |s| s.platform = "x86_64-linux" end build_gem "nokogiri", "1.14.0" do |s| s.platform = "arm-linux" end build_gem "sorbet-static", "0.5.10696" do |s| s.platform = "x86_64-linux" end end lockfile <<~L GEM remote: #{file_uri_for(gem_repo4)}/ specs: nokogiri (1.14.0-arm-linux) nokogiri (1.14.0-x86_64-linux) PLATFORMS arm-linux x86_64-linux DEPENDENCIES nokogiri BUNDLED WITH #{Bundler::VERSION} L gemfile <<~G source "#{file_uri_for(gem_repo4)}" gem "nokogiri" gem "sorbet-static" G bundle "lock", raise_on_error: false end end it "raises a proper error" do nice_error = <<~E.strip Could not find gems matching 'sorbet-static' valid for all resolution platforms (arm-linux, x86_64-linux) in rubygems repository #{file_uri_for(gem_repo4)}/, cached gems or installed locally. The source contains the following gems matching 'sorbet-static': * sorbet-static-0.5.10696-x86_64-linux E expect(err).to end_with(nice_error) end end it "gives a meaningful error on ruby version mismatches between dependencies" do build_repo4 do build_gem "requires-old-ruby" do |s| s.required_ruby_version = "< #{Gem.ruby_version}" end end build_lib("foo", path: bundled_app) do |s| s.required_ruby_version = ">= #{Gem.ruby_version}" s.add_dependency "requires-old-ruby" end install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo4)}" gemspec G expect(err).to end_with <<~E.strip Could not find compatible versions Because every version of foo depends on requires-old-ruby >= 0 and every version of requires-old-ruby depends on Ruby < #{Gem.ruby_version}, every version of foo requires Ruby < #{Gem.ruby_version}. So, because Gemfile depends on foo >= 0 and current Ruby version is = #{Gem.ruby_version}, version solving has failed. E end it "installs the older version under rate limiting conditions" do build_repo4 do build_gem "rack", "9001.0.0" do |s| s.required_ruby_version = "> 9000" end build_gem "rack", "1.2" build_gem "foo1", "1.0" end install_gemfile <<-G, artifice: "compact_index_rate_limited", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } ruby "#{Gem.ruby_version}" source "http://localgemserver.test/" gem 'rack' gem 'foo1' G expect(err).to_not include("rack-9001.0.0 requires ruby version > 9000") expect(the_bundle).to include_gems("rack 1.2") end it "installs the older not platform specific version" do build_repo4 do build_gem "rack", "9001.0.0" do |s| s.required_ruby_version = "> 9000" end build_gem "rack", "1.2" do |s| s.platform = x86_mingw32 s.required_ruby_version = "> 9000" end build_gem "rack", "1.2" end simulate_platform x86_mingw32 do install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } ruby "#{Gem.ruby_version}" source "http://localgemserver.test/" gem 'rack' G end expect(err).to_not include("rack-9001.0.0 requires ruby version > 9000") expect(err).to_not include("rack-1.2-#{Bundler.local_platform} requires ruby version > 9000") expect(the_bundle).to include_gems("rack 1.2") end end context "allows no gems" do before do build_repo2 do build_gem "require_ruby" do |s| s.required_ruby_version = "> 9000" end end end let(:ruby_requirement) { %("#{Gem.ruby_version}") } let(:error_message_requirement) { "= #{Gem.ruby_version}" } it "raises a proper error that mentions the current Ruby version during resolution" do install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s }, raise_on_error: false source "http://localgemserver.test/" gem 'require_ruby' G expect(out).to_not include("Gem::InstallError: require_ruby requires Ruby version > 9000") nice_error = <<~E.strip Could not find compatible versions Because every version of require_ruby depends on Ruby > 9000 and Gemfile depends on require_ruby >= 0, Ruby > 9000 is required. So, because current Ruby version is #{error_message_requirement}, version solving has failed. E expect(err).to end_with(nice_error) end shared_examples_for "ruby version conflicts" do it "raises an error during resolution" do install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s }, raise_on_error: false source "http://localgemserver.test/" ruby #{ruby_requirement} gem 'require_ruby' G expect(out).to_not include("Gem::InstallError: require_ruby requires Ruby version > 9000") nice_error = <<~E.strip Could not find compatible versions Because every version of require_ruby depends on Ruby > 9000 and Gemfile depends on require_ruby >= 0, Ruby > 9000 is required. So, because current Ruby version is #{error_message_requirement}, version solving has failed. E expect(err).to end_with(nice_error) end end it_behaves_like "ruby version conflicts" describe "with a < requirement" do let(:ruby_requirement) { %("< 5000") } it_behaves_like "ruby version conflicts" end describe "with a compound requirement" do let(:reqs) { ["> 0.1", "< 5000"] } let(:ruby_requirement) { reqs.map(&:dump).join(", ") } it_behaves_like "ruby version conflicts" end end end describe "when a required rubygems version disallows a gem" do it "does not try to install those gems" do build_repo2 do build_gem "require_rubygems" do |s| s.required_rubygems_version = "> 9000" end end install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo2)}" gem 'require_rubygems' G expect(err).to_not include("Gem::InstallError: require_rubygems requires RubyGems version > 9000") nice_error = <<~E.strip Because every version of require_rubygems depends on RubyGems > 9000 and Gemfile depends on require_rubygems >= 0, RubyGems > 9000 is required. So, because current RubyGems version is = #{Gem::VERSION}, version solving has failed. E expect(err).to end_with(nice_error) end end end