ruby/spec/bundler/install/gemfile/gemspec_spec.rb
David Rodríguez af0b184e83
[rubygems/rubygems] Never ignore gems from path sources during activation
The "ignore" attribute is a RubyGems thing to mark when a installed gem
should be ignored for activation because its extensions are not properly
compiled.

In the case of gems from path sources, the warning is not accurate
because extensions are compiled into the local lib path, which is not
where RubyGems leaves its sentinel `gem.build_complete` file.

Also, there's a single version of each gem in the path source available
to Bundler, so we always certainly want to consider that for activation
and never makes sense to ignore it.

ec5d33695e
2025-06-19 10:23:36 +09:00

736 lines
22 KiB
Ruby

# frozen_string_literal: true
RSpec.describe "bundle install from an existing gemspec" do
before(:each) do
build_repo2 do
build_gem "bar"
build_gem "bar-dev"
end
end
it "should install runtime and development dependencies" do
build_lib("foo", path: tmp("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"
end
install_gemfile <<-G
source "https://gem.repo2"
gemspec :path => '#{tmp("foo")}'
G
expect(the_bundle).to include_gems "bar 1.0.0"
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("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"
end
FileUtils.mv tmp("foo", "foo.gemspec"), tmp("foo", ".gemspec")
install_gemfile <<-G
source "https://gem.repo2"
gemspec :path => '#{tmp("foo")}'
G
expect(the_bundle).to include_gems "bar 1.0.0"
expect(the_bundle).to include_gems "bar-dev 1.0.0", groups: :development
end
it "should handle a list of requirements" do
update_repo2 do
build_gem "baz", "1.0"
build_gem "baz", "1.1"
end
build_lib("foo", path: tmp("foo")) do |s|
s.write("Gemfile", "source :rubygems\ngemspec")
s.add_dependency "baz", ">= 1.0", "< 1.1"
end
install_gemfile <<-G
source "https://gem.repo2"
gemspec :path => '#{tmp("foo")}'
G
expect(the_bundle).to include_gems "baz 1.0"
end
it "should raise if there are no gemspecs available" do
build_lib("foo", path: tmp("foo"), gemspec: false)
install_gemfile <<-G, raise_on_error: false
source "https://gem.repo2"
gemspec :path => '#{tmp("foo")}'
G
expect(err).to match(/There are no gemspecs at #{tmp("foo")}/)
end
it "should raise if there are too many gemspecs available" do
build_lib("foo", path: tmp("foo")) do |s|
s.write("foo2.gemspec", build_spec("foo", "4.0").first.to_ruby)
end
install_gemfile <<-G, raise_on_error: false
source "https://gem.repo2"
gemspec :path => '#{tmp("foo")}'
G
expect(err).to match(/There are multiple gemspecs at #{tmp("foo")}/)
end
it "should pick a specific gemspec" do
build_lib("foo", path: tmp("foo")) do |s|
s.write("foo2.gemspec", "")
s.add_dependency "bar", "=1.0.0"
s.add_development_dependency "bar-dev", "=1.0.0"
end
install_gemfile(<<-G)
source "https://gem.repo2"
gemspec :path => '#{tmp("foo")}', :name => 'foo'
G
expect(the_bundle).to include_gems "bar 1.0.0"
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("foo")) do |s|
s.write("foo2.gemspec", "")
s.add_dependency "bar", "=1.0.0"
s.add_development_dependency "bar-dev", "=1.0.0"
end
install_gemfile(<<-G)
source "https://gem.repo2"
gemspec :path => '#{tmp("foo")}', :name => 'foo', :development_group => :dev
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
end
it "should match a lockfile even if the gemspec defines development dependencies" do
build_lib("foo", path: tmp("foo")) do |s|
s.write("Gemfile", "source 'https://gem.repo1'\ngemspec")
s.add_dependency "actionpack", "=2.3.2"
s.add_development_dependency "rake", rake_version
end
bundle "install", dir: tmp("foo"), artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo1.to_s }
# 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("foo"), artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo1.to_s })
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(/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("foo")) do |s|
s.add_dependency "myrack"
end
install_gemfile <<-G
source "https://gem.repo1"
gemspec :path => '#{tmp("foo")}'
G
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 without needing to re-resolve with development dependencies" do
simulate_platform "java" do
build_lib("foo", path: tmp("foo")) do |s|
s.add_dependency "myrack"
s.add_development_dependency "thin"
end
install_gemfile <<-G
source "https://gem.repo1"
gemspec :path => '#{tmp("foo")}'
G
bundle "install", verbose: true
message = "Found no changes, using resolution from the lockfile"
expect(out.scan(message).size).to eq(1)
end
end
it "should match a lockfile on non-ruby platforms with a transitive platform dependency", :jruby_only do
build_lib("foo", path: tmp("foo")) do |s|
s.add_dependency "platform_specific"
end
system_gems "platform_specific-1.0-java", path: default_bundle_path
install_gemfile <<-G
gemspec :path => '#{tmp("foo")}'
G
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("foo"))
File.open(tmp("foo/foo.gemspec"), "w") do |s|
s.write "raise 'ahh' unless Dir.pwd == '#{tmp("foo")}'"
end
install_gemfile <<-G, raise_on_error: false
gemspec :path => '#{tmp("foo")}'
G
expect(stdboth).not_to include("ahh")
end
it "allows the gemspec to activate other gems" do
ENV["BUNDLE_PATH__SYSTEM"] = "true"
# see https://github.com/rubygems/bundler/issues/5409
#
# issue was caused by rubygems having an unresolved gem during a require,
# so emulate that
system_gems %w[myrack-1.0.0 myrack-0.9.1 myrack-obama-1.0]
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 'myrack-obama'; require 'myrack/obama' }"
end
install_gemfile <<-G
source "https://gem.repo1"
gemspec
G
expect(the_bundle).to include_gem "foo 1.0"
end
it "allows conflicts" do
build_lib("foo", path: tmp("foo")) do |s|
s.version = "1.0.0"
s.add_dependency "bar", "= 1.0.0"
end
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
install_gemfile <<-G
source "https://gem.repo2"
gem "deps"
gemspec :path => '#{tmp("foo")}', :name => 'foo'
G
expect(the_bundle).to include_gems "foo 1.0.0"
end
it "does not break Gem.finish_resolve with conflicts" do
build_lib("foo", path: tmp("foo")) do |s|
s.version = "1.0.0"
s.add_dependency "bar", "= 1.0.0"
end
update_repo2 do
build_gem "deps" do |s|
s.add_dependency "foo", "= 0.0.1"
end
build_gem "foo", "0.0.1"
end
install_gemfile <<-G
source "https://gem.repo2"
gem "deps"
gemspec :path => '#{tmp("foo")}', :name => 'foo'
G
expect(the_bundle).to include_gems "foo 1.0.0"
run "Gem.finish_resolve; puts 'WIN'"
expect(out).to eq("WIN")
end
it "does not make Gem.try_activate warn when local gem has extensions" do
build_lib("foo", path: tmp("foo")) do |s|
s.version = "1.0.0"
s.add_c_extension
end
build_repo2
install_gemfile <<-G
source "https://gem.repo2"
gemspec :path => '#{tmp("foo")}'
G
expect(the_bundle).to include_gems "foo 1.0.0"
run "Gem.try_activate('irb/lc/es/error.rb'); puts 'WIN'"
expect(out).to eq("WIN")
expect(err).to be_empty
end
it "handles downgrades" do
build_lib "omg", "2.0", path: lib_path("omg")
install_gemfile <<-G
source "https://gem.repo1"
gemspec :path => "#{lib_path("omg")}"
G
build_lib "omg", "1.0", path: lib_path("omg")
bundle :install
expect(the_bundle).to include_gems "omg 1.0"
end
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|
s.add_dependency "activesupport", ">= 1"
end
install_gemfile <<-G
source "https://gem.repo1"
gemspec
G
expect(the_bundle).to include_gems("cocoapods 1.0", "activesupport 2.3.5")
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
expect(err).to include("changed")
end
end
end
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|
s.add_dependency "myrack_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 "myrack_middleware", "1.0", path: bundled_app("myrack_middleware") do |s|
s.add_dependency "myrack", "1.0" # anything other than 0.9.1
end
end
it "should install the child gemspec's deps" do
install_gemfile <<-G
source "https://gem.repo1"
gemspec
G
expect(the_bundle).to include_gems "myrack 1.0"
end
end
context "with a lockfile and some missing dependencies" do
let(:source_uri) { "http://localgemserver.test" }
before do
build_lib("foo", path: tmp("foo")) do |s|
s.add_dependency "myrack", "=1.0.0"
end
gemfile <<-G
source "#{source_uri}"
gemspec :path => "../foo"
G
checksums = checksums_section_when_enabled do |c|
c.no_checksum "foo", "1.0"
end
lockfile <<-L
PATH
remote: ../foo
specs:
foo (1.0)
myrack (= 1.0.0)
GEM
remote: #{source_uri}
specs:
myrack (1.0.0)
PLATFORMS
ruby
DEPENDENCIES
foo!
#{checksums}
BUNDLED WITH
#{Bundler::VERSION}
L
end
context "using JRuby with explicit platform", :jruby_only do
before do
create_file(
tmp("foo", "foo-java.gemspec"),
build_spec("foo", "1.0", "java") do
dep "myrack", "=1.0.0"
@spec.authors = "authors"
@spec.summary = "summary"
end.first.to_ruby
)
end
it "should install" do
results = bundle "install", artifice: "endpoint"
expect(results).to include("Installing myrack 1.0.0")
expect(the_bundle).to include_gems "myrack 1.0.0"
end
end
it "should install", :jruby do
results = bundle "install", artifice: "endpoint"
expect(results).to include("Installing myrack 1.0.0")
expect(the_bundle).to include_gems "myrack 1.0.0"
end
context "bundled for multiple platforms" do
let(:platform_specific_type) { :runtime }
let(:dependency) { "platform_specific" }
before do
build_repo2 do
build_gem "indirect_platform_specific" do |s|
s.add_runtime_dependency "platform_specific"
end
end
build_lib "foo", path: bundled_app do |s|
if platform_specific_type == :runtime
s.add_runtime_dependency dependency
elsif platform_specific_type == :development
s.add_development_dependency dependency
else
raise "wrong dependency type #{platform_specific_type}, can only be :development or :runtime"
end
end
gemfile <<-G
source "https://gem.repo2"
gemspec
G
bundle "config set --local force_ruby_platform true"
bundle "install"
simulate_new_machine
simulate_platform("jruby") { bundle "install" }
expect(lockfile).to include("platform_specific (1.0-java)")
simulate_platform("x64-mingw-ucrt") { bundle "install" }
end
context "on ruby" do
before do
bundle "config set --local force_ruby_platform true"
bundle :install
end
context "as a runtime dependency" do
it "keeps all platform dependencies in the lockfile" do
expect(the_bundle).to include_gems "foo 1.0", "platform_specific 1.0 ruby"
checksums = checksums_section_when_enabled 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"
c.checksum gem_repo2, "platform_specific", "1.0", "x64-mingw-ucrt"
end
expect(lockfile).to eq <<~L
PATH
remote: .
specs:
foo (1.0)
platform_specific
GEM
remote: https://gem.repo2/
specs:
platform_specific (1.0)
platform_specific (1.0-java)
platform_specific (1.0-x64-mingw-ucrt)
PLATFORMS
java
ruby
x64-mingw-ucrt
DEPENDENCIES
foo!
#{checksums}
BUNDLED WITH
#{Bundler::VERSION}
L
end
end
context "as a development dependency" do
let(:platform_specific_type) { :development }
it "keeps all platform dependencies in the lockfile" do
expect(the_bundle).to include_gems "foo 1.0", "platform_specific 1.0 ruby"
checksums = checksums_section_when_enabled 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"
c.checksum gem_repo2, "platform_specific", "1.0", "x64-mingw-ucrt"
end
expect(lockfile).to eq <<~L
PATH
remote: .
specs:
foo (1.0)
GEM
remote: https://gem.repo2/
specs:
platform_specific (1.0)
platform_specific (1.0-java)
platform_specific (1.0-x64-mingw-ucrt)
PLATFORMS
java
ruby
x64-mingw-ucrt
DEPENDENCIES
foo!
platform_specific
#{checksums}
BUNDLED WITH
#{Bundler::VERSION}
L
end
end
context "with an indirect platform-specific development dependency" do
let(:platform_specific_type) { :development }
let(:dependency) { "indirect_platform_specific" }
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"
checksums = checksums_section_when_enabled 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"
c.checksum gem_repo2, "platform_specific", "1.0", "x64-mingw-ucrt"
end
expect(lockfile).to eq <<~L
PATH
remote: .
specs:
foo (1.0)
GEM
remote: https://gem.repo2/
specs:
indirect_platform_specific (1.0)
platform_specific
platform_specific (1.0)
platform_specific (1.0-java)
platform_specific (1.0-x64-mingw-ucrt)
PLATFORMS
java
ruby
x64-mingw-ucrt
DEPENDENCIES
foo!
indirect_platform_specific
#{checksums}
BUNDLED WITH
#{Bundler::VERSION}
L
end
end
end
end
end
context "with multiple platforms" do
before do
build_lib("foo", path: tmp("foo")) do |s|
s.version = "1.0.0"
s.add_development_dependency "myrack"
s.write "foo-universal-java.gemspec", build_spec("foo", "1.0.0", "universal-java") {|sj| sj.runtime "myrack", "1.0.0" }.first.to_ruby
end
end
it "installs the ruby platform gemspec" do
bundle "config set --local force_ruby_platform true"
install_gemfile <<-G
source "https://gem.repo1"
gemspec :path => '#{tmp("foo")}', :name => 'foo'
G
expect(the_bundle).to include_gems "foo 1.0.0", "myrack 1.0.0"
end
it "installs the ruby platform gemspec and skips dev deps with `without development` configured" do
bundle "config set --local force_ruby_platform true"
bundle "config set --local without development"
install_gemfile <<-G
source "https://gem.repo1"
gemspec :path => '#{tmp("foo")}', :name => 'foo'
G
expect(the_bundle).to include_gem "foo 1.0.0"
expect(the_bundle).not_to include_gem "myrack"
end
end
context "with multiple platforms and resolving for more specific platforms" do
before do
build_lib("chef", path: tmp("chef")) do |s|
s.version = "17.1.17"
s.write "chef-universal-mingw-ucrt.gemspec", build_spec("chef", "17.1.17", "universal-mingw-ucrt") {|sw| sw.runtime "win32-api", "~> 1.5.3" }.first.to_ruby
end
end
it "does not remove the platform specific specs from the lockfile when updating" do
build_repo4 do
build_gem "win32-api", "1.5.3" do |s|
s.platform = "universal-mingw-ucrt"
end
end
gemfile <<-G
source "https://gem.repo4"
gemspec :path => "../chef"
G
checksums = checksums_section_when_enabled do |c|
c.no_checksum "chef", "17.1.17"
c.no_checksum "chef", "17.1.17", "universal-mingw-ucrt"
c.checksum gem_repo4, "win32-api", "1.5.3", "universal-mingw-ucrt"
end
initial_lockfile = <<~L
PATH
remote: ../chef
specs:
chef (17.1.17)
chef (17.1.17-universal-mingw-ucrt)
win32-api (~> 1.5.3)
GEM
remote: https://gem.repo4/
specs:
win32-api (1.5.3-universal-mingw-ucrt)
PLATFORMS
#{lockfile_platforms("ruby", "x64-mingw-ucrt", "x86-mingw32")}
DEPENDENCIES
chef!
#{checksums}
BUNDLED WITH
#{Bundler::VERSION}
L
lockfile initial_lockfile
bundle "update"
expect(lockfile).to eq initial_lockfile
end
end
context "with multiple locked platforms" do
before do
build_lib("activeadmin", path: tmp("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 "https://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_enabled do |c|
c.no_checksum "activeadmin", "2.9.0"
c.checksum gem_repo4, "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: https://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("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