diff --git a/.changelog.yml b/.changelog.yml index d0f94e223b88..1ebb5678aa58 100644 --- a/.changelog.yml +++ b/.changelog.yml @@ -13,21 +13,19 @@ release_date_format: "%Y-%m-%d" entry_wrapping: 74 changelog_label_mapping: - "rubygems: security": "### Security:" - "rubygems: breaking change": "### Breaking changes:" - "rubygems: deprecation": "### Deprecations:" "rubygems: feature": "### Features:" "rubygems: performance": "### Performance:" "rubygems: enhancement": "### Enhancements:" "rubygems: bug fix": "### Bug fixes:" + "rubygems: security": "### Security:" + "rubygems: breaking change": "### Breaking changes:" + "rubygems: deprecation": "### Deprecations:" "rubygems: documentation": "### Documentation:" "rubygems: skip changelog": null patch_level_labels: - - "rubygems: security" - - "rubygems: deprecation" - "rubygems: enhancement" - "rubygems: bug fix" - - "rubygems: performance" + - "rubygems: security" - "rubygems: documentation" - "rubygems: skip changelog" diff --git a/.github/workflows/bundler.yml b/.github/workflows/bundler.yml index 2a11dbeb55cb..223316c9d47e 100644 --- a/.github/workflows/bundler.yml +++ b/.github/workflows/bundler.yml @@ -30,17 +30,29 @@ jobs: ruby: - { name: ruby-3.2, value: 3.2.9 } - - { name: ruby-3.3, value: 3.3.9 } - - { name: ruby-3.4, value: 3.4.5 } + - { name: ruby-3.3, value: 3.3.10 } + - { name: ruby-3.4, value: 3.4.8 } + - { name: ruby-4.0, value: 4.0.0 } include: - { os: { name: macOS, value: macos-15 }, ruby: { name: ruby-3.2, value: 3.2.9 }, timeout: 90 } - - { os: { name: macOS, value: macos-15 }, ruby: { name: ruby-3.3, value: 3.3.9 }, timeout: 90 } - - { os: { name: macOS, value: macos-15 }, ruby: { name: ruby-3.4, value: 3.4.5 }, timeout: 90 } + - { os: { name: macOS, value: macos-15 }, ruby: { name: ruby-3.3, value: 3.3.10 }, timeout: 90 } + - { os: { name: macOS, value: macos-15 }, ruby: { name: ruby-3.4, value: 3.4.8 }, timeout: 90 } + - { os: { name: macOS, value: macos-15 }, ruby: { name: ruby-4.0, value: 4.0.0 }, timeout: 90 } # Ruby 3.2 is about 20 minutes slower than 3.3/3.4, so it will be excluded from testing. - - { os: { name: Windows, value: windows-2025 }, ruby: { name: ruby-3.3, value: 3.3.9 }, timeout: 150 } - - { os: { name: Windows, value: windows-2025 }, ruby: { name: ruby-3.4, value: 3.4.5 }, timeout: 150 } + - { os: { name: Windows, value: windows-2025 }, ruby: { name: ruby-3.3, value: 3.3.10 }, timeout: 150, group: a } + - { os: { name: Windows, value: windows-2025 }, ruby: { name: ruby-3.3, value: 3.3.10 }, timeout: 150, group: b } + - { os: { name: Windows, value: windows-2025 }, ruby: { name: ruby-3.3, value: 3.3.10 }, timeout: 150, group: c } + - { os: { name: Windows, value: windows-2025 }, ruby: { name: ruby-3.3, value: 3.3.10 }, timeout: 150, group: d } + - { os: { name: Windows, value: windows-2025 }, ruby: { name: ruby-3.4, value: 3.4.8 }, timeout: 150, group: a } + - { os: { name: Windows, value: windows-2025 }, ruby: { name: ruby-3.4, value: 3.4.8 }, timeout: 150, group: b } + - { os: { name: Windows, value: windows-2025 }, ruby: { name: ruby-3.4, value: 3.4.8 }, timeout: 150, group: c } + - { os: { name: Windows, value: windows-2025 }, ruby: { name: ruby-3.4, value: 3.4.8 }, timeout: 150, group: d } + # - { os: { name: Windows, value: windows-2025 }, ruby: { name: ruby-4.0, value: 4.0.0 }, timeout: 150, group: a } + # - { os: { name: Windows, value: windows-2025 }, ruby: { name: ruby-4.0, value: 4.0.0 }, timeout: 150, group: b } + # - { os: { name: Windows, value: windows-2025 }, ruby: { name: ruby-4.0, value: 4.0.0 }, timeout: 150, group: c } + # - { os: { name: Windows, value: windows-2025 }, ruby: { name: ruby-4.0, value: 4.0.0 }, timeout: 150, group: d } - { os: { name: Ubuntu, value: ubuntu-24.04 }, ruby: { name: jruby, value: jruby-10.0.2.0 } } - { os: { name: Windows, value: windows-2025 }, ruby: { name: jruby, value: jruby-10.0.2.0 } } @@ -53,7 +65,7 @@ jobs: with: persist-credentials: false - name: Setup ruby - uses: ruby/setup-ruby@8aeb6ff8030dd539317f8e1769a044873b56ea71 # v1.268.0 + uses: ruby/setup-ruby@b90be12699fdfcbee4440c2bba85f6f460446bb0 # v1.279.0 with: ruby-version: ${{ matrix.ruby.value }} bundler: none @@ -70,7 +82,12 @@ jobs: run: | bin/parallel_rspec working-directory: ./bundler - if: matrix.ruby.name != 'jruby' + if: matrix.ruby.name != 'jruby' && matrix.os.name != 'Windows' + - name: Run Test (CRuby on Windows - Group ${{ matrix.group }}) + run: | + bin/parallel_rspec --tag windows_${{ matrix.group }} + working-directory: ./bundler + if: matrix.ruby.name != 'jruby' && matrix.os.name == 'Windows' - name: Run Test (JRuby) run: | bin/parallel_rspec --tag jruby_only --tag jruby diff --git a/.github/workflows/daily-bundler.yml b/.github/workflows/daily-bundler.yml index 14f78a83c661..a45e980f666d 100644 --- a/.github/workflows/daily-bundler.yml +++ b/.github/workflows/daily-bundler.yml @@ -25,7 +25,7 @@ jobs: persist-credentials: false - name: Set up Ruby - uses: ruby/setup-ruby@8aeb6ff8030dd539317f8e1769a044873b56ea71 # v1.268.0 + uses: ruby/setup-ruby@b90be12699fdfcbee4440c2bba85f6f460446bb0 # v1.279.0 with: ruby-version: ruby-head bundler: none diff --git a/.github/workflows/daily-rubygems.yml b/.github/workflows/daily-rubygems.yml index e7512f869f03..cbc16ec76665 100644 --- a/.github/workflows/daily-rubygems.yml +++ b/.github/workflows/daily-rubygems.yml @@ -28,7 +28,7 @@ jobs: persist-credentials: false - name: Set up Ruby - uses: ruby/setup-ruby@8aeb6ff8030dd539317f8e1769a044873b56ea71 # v1.268.0 + uses: ruby/setup-ruby@b90be12699fdfcbee4440c2bba85f6f460446bb0 # v1.279.0 with: ruby-version: ${{ matrix.ruby }} bundler: none diff --git a/.github/workflows/install-rubygems.yml b/.github/workflows/install-rubygems.yml index fee2d0f0d247..d5a46f230deb 100644 --- a/.github/workflows/install-rubygems.yml +++ b/.github/workflows/install-rubygems.yml @@ -35,7 +35,7 @@ jobs: with: persist-credentials: false - name: Setup ruby - uses: ruby/setup-ruby@8aeb6ff8030dd539317f8e1769a044873b56ea71 # v1.268.0 + uses: ruby/setup-ruby@b90be12699fdfcbee4440c2bba85f6f460446bb0 # v1.279.0 with: ruby-version: ${{ matrix.ruby.value }} bundler: none @@ -128,7 +128,7 @@ jobs: with: persist-credentials: false - name: Setup ruby - uses: ruby/setup-ruby@8aeb6ff8030dd539317f8e1769a044873b56ea71 # v1.268.0 + uses: ruby/setup-ruby@b90be12699fdfcbee4440c2bba85f6f460446bb0 # v1.279.0 with: ruby-version: ${{ matrix.ruby.value }} bundler: none @@ -170,7 +170,7 @@ jobs: with: persist-credentials: false - name: Setup original ruby - uses: ruby/setup-ruby@8aeb6ff8030dd539317f8e1769a044873b56ea71 # v1.268.0 + uses: ruby/setup-ruby@b90be12699fdfcbee4440c2bba85f6f460446bb0 # v1.279.0 with: ruby-version: 3.2 bundler: none @@ -191,7 +191,7 @@ jobs: GEM_HOME: bar GEM_PATH: bar - name: Setup final ruby - uses: ruby/setup-ruby@8aeb6ff8030dd539317f8e1769a044873b56ea71 # v1.268.0 + uses: ruby/setup-ruby@b90be12699fdfcbee4440c2bba85f6f460446bb0 # v1.279.0 with: ruby-version: 3.3 bundler: none @@ -220,7 +220,7 @@ jobs: with: persist-credentials: false - name: Setup ruby - uses: ruby/setup-ruby@8aeb6ff8030dd539317f8e1769a044873b56ea71 # v1.268.0 + uses: ruby/setup-ruby@b90be12699fdfcbee4440c2bba85f6f460446bb0 # v1.279.0 with: ruby-version: ${{ matrix.ruby.value }} bundler: none diff --git a/.github/workflows/realworld-bundler.yml b/.github/workflows/realworld-bundler.yml index 7447bf59c3c6..39627df46a58 100644 --- a/.github/workflows/realworld-bundler.yml +++ b/.github/workflows/realworld-bundler.yml @@ -26,13 +26,15 @@ jobs: ruby: - { name: ruby-3.2, value: 3.2.9 } - - { name: ruby-3.3, value: 3.3.9 } - - { name: ruby-3.4, value: 3.4.5 } + - { name: ruby-3.3, value: 3.3.10 } + - { name: ruby-3.4, value: 3.4.8 } + - { name: ruby-4.0, value: 4.0.0 } include: - { os: { name: macOS, value: macos-15 }, ruby: { name: ruby-3.2, value: 3.2.9 } } - - { os: { name: macOS, value: macos-15 }, ruby: { name: ruby-3.3, value: 3.3.9 } } - - { os: { name: macOS, value: macos-15 }, ruby: { name: ruby-3.4, value: 3.4.5 } } + - { os: { name: macOS, value: macos-15 }, ruby: { name: ruby-3.3, value: 3.3.10 } } + - { os: { name: macOS, value: macos-15 }, ruby: { name: ruby-3.4, value: 3.4.8 } } + - { os: { name: macOS, value: macos-15 }, ruby: { name: ruby-4.0, value: 4.0.0 } } env: RGV: .. RUBYOPT: --disable-gems @@ -41,7 +43,7 @@ jobs: with: persist-credentials: false - name: Setup ruby - uses: ruby/setup-ruby@8aeb6ff8030dd539317f8e1769a044873b56ea71 # v1.268.0 + uses: ruby/setup-ruby@b90be12699fdfcbee4440c2bba85f6f460446bb0 # v1.279.0 with: ruby-version: ${{ matrix.ruby.value }} bundler: none @@ -64,9 +66,9 @@ jobs: with: persist-credentials: false - name: Setup ruby - uses: ruby/setup-ruby@8aeb6ff8030dd539317f8e1769a044873b56ea71 # v1.268.0 + uses: ruby/setup-ruby@b90be12699fdfcbee4440c2bba85f6f460446bb0 # v1.279.0 with: - ruby-version: 3.4.5 + ruby-version: 3.4.8 bundler: none - name: Prepare tapioca run: ../../../../bin/bundle install @@ -91,7 +93,7 @@ jobs: with: persist-credentials: false - name: Setup ruby - uses: ruby/setup-ruby@8aeb6ff8030dd539317f8e1769a044873b56ea71 # v1.268.0 + uses: ruby/setup-ruby@b90be12699fdfcbee4440c2bba85f6f460446bb0 # v1.279.0 with: ruby-version: ${{ matrix.ruby.value }} bundler: none @@ -115,9 +117,9 @@ jobs: with: persist-credentials: false - name: Setup ruby - uses: ruby/setup-ruby@8aeb6ff8030dd539317f8e1769a044873b56ea71 # v1.268.0 + uses: ruby/setup-ruby@b90be12699fdfcbee4440c2bba85f6f460446bb0 # v1.279.0 with: - ruby-version: 3.4.5 + ruby-version: 3.4.8 bundler: none - name: Prepare dependencies run: bin/rake setup diff --git a/.github/workflows/rubygems.yml b/.github/workflows/rubygems.yml index 90a920bc3cb8..f37377877d3e 100644 --- a/.github/workflows/rubygems.yml +++ b/.github/workflows/rubygems.yml @@ -28,8 +28,9 @@ jobs: ruby: - { name: "3.2", value: 3.2.9 } - - { name: "3.3", value: 3.3.9 } - - { name: "3.4", value: 3.4.5 } + - { name: "3.3", value: 3.3.10 } + - { name: "3.4", value: 3.4.8 } + - { name: "4.0", value: 4.0.0 } include: - ruby: { name: jruby, value: jruby-10.0.2.0 } @@ -43,7 +44,7 @@ jobs: with: persist-credentials: false - name: Setup ruby - uses: ruby/setup-ruby@8aeb6ff8030dd539317f8e1769a044873b56ea71 # v1.268.0 + uses: ruby/setup-ruby@b90be12699fdfcbee4440c2bba85f6f460446bb0 # v1.279.0 with: ruby-version: ${{ matrix.ruby.value }} bundler: none diff --git a/.github/workflows/system-rubygems-bundler.yml b/.github/workflows/system-rubygems-bundler.yml index 50b717431324..a6c7f77e8a1e 100644 --- a/.github/workflows/system-rubygems-bundler.yml +++ b/.github/workflows/system-rubygems-bundler.yml @@ -37,7 +37,7 @@ jobs: with: persist-credentials: false - name: Setup ruby - uses: ruby/setup-ruby@8aeb6ff8030dd539317f8e1769a044873b56ea71 # v1.268.0 + uses: ruby/setup-ruby@b90be12699fdfcbee4440c2bba85f6f460446bb0 # v1.279.0 with: ruby-version: ${{ matrix.ruby.value }} bundler: none diff --git a/.github/workflows/truffleruby-bundler.yml b/.github/workflows/truffleruby-bundler.yml index c7715071150d..3f7bb03c96da 100644 --- a/.github/workflows/truffleruby-bundler.yml +++ b/.github/workflows/truffleruby-bundler.yml @@ -28,7 +28,7 @@ jobs: with: persist-credentials: false - name: Setup ruby - uses: ruby/setup-ruby@8aeb6ff8030dd539317f8e1769a044873b56ea71 # v1.268.0 + uses: ruby/setup-ruby@b90be12699fdfcbee4440c2bba85f6f460446bb0 # v1.279.0 with: ruby-version: truffleruby-24.2.1 bundler: none diff --git a/.github/workflows/ubuntu-lint.yml b/.github/workflows/ubuntu-lint.yml index 0dda6a140eeb..01bf7721ec13 100644 --- a/.github/workflows/ubuntu-lint.yml +++ b/.github/workflows/ubuntu-lint.yml @@ -75,7 +75,7 @@ jobs: with: persist-credentials: false - name: Setup ruby - uses: ruby/setup-ruby@8aeb6ff8030dd539317f8e1769a044873b56ea71 # v1.268.0 + uses: ruby/setup-ruby@b90be12699fdfcbee4440c2bba85f6f460446bb0 # v1.279.0 with: ruby-version: ${{ matrix.ruby.value }} bundler: none diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a76e0332164..b620d2ecde44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,54 @@ # Changelog +## 4.0.4 / 2026-01-15 + +### Enhancements: + +* Remove date require from rebuild command. Pull request + [#9232](https://github.com/ruby/rubygems/pull/9232) by jeremyevans +* Installs bundler 4.0.4 as a default gem. + +### Bug fixes: + +* Add a missing "require 'etc'" statement:. Pull request + [#9242](https://github.com/ruby/rubygems/pull/9242) by Edouard-chin + +## 4.0.3 / 2025-12-23 + +### Enhancements: + +* Installs bundler 4.0.3 as a default gem. + +### Documentation: + +* Fix broken documentation links. Pull request + [#9208](https://github.com/ruby/rubygems/pull/9208) by eileencodes + +## 4.0.2 / 2025-12-17 + +### Enhancements: + +* Pass down value of `BUNDLE_JOBS` to RubyGems before compiling & + introduce a new `gem install -j` flag. Pull request + [#9171](https://github.com/ruby/rubygems/pull/9171) by Edouard-chin +* Installs bundler 4.0.2 as a default gem. + +## 4.0.1 / 2025-12-09 + +### Enhancements: + +* Installs bundler 4.0.1 as a default gem. + +### Bug fixes: + +* Fixed unexpected default bundler installation. Pull request + [#9167](https://github.com/ruby/rubygems/pull/9167) by hsbt + +### Documentation: + +* Update contributing docs with `RGV`. Pull request + [#9155](https://github.com/ruby/rubygems/pull/9155) by eileencodes + ## 4.0.0 / 2025-12-03 ### Features: diff --git a/Rakefile b/Rakefile index 66ea0a31e446..d65d1fd50d67 100644 --- a/Rakefile +++ b/Rakefile @@ -254,10 +254,16 @@ desc "Upload release to S3" task :upload_to_s3 do require "aws-sdk-s3" - s3 = Aws::S3::Resource.new(region:"us-west-2") + client = Aws::S3::Client.new(region: "us-west-2") + transfer_manager = Aws::S3::TransferManager.new(client: client) + %w[zip tgz].each do |ext| - obj = s3.bucket("oregon.production.s3.rubygems.org").object("rubygems/rubygems-#{v}.#{ext}") - obj.upload_file("pkg/rubygems-#{v}.#{ext}", acl: "public-read") + transfer_manager.upload_file( + "pkg/rubygems-#{v}.#{ext}", + bucket: "oregon.production.s3.rubygems.org", + key: "rubygems/rubygems-#{v}.#{ext}", + acl: "public-read" + ) end end @@ -706,6 +712,8 @@ namespace :bundler do Rake::Task["bundler:build_metadata:clean"].tap(&:reenable).invoke end + task "build" => ["bundler:release:check_ruby_version"] + desc "Push to rubygems.org" task "release:rubygem_push" => ["bundler:release:setup", "man:check", "bundler:build_metadata", "check_release_preparations", "bundler:release:github"] @@ -726,5 +734,9 @@ namespace :bundler do Release.for_bundler(gemspec_version).create_for_github! end + + task :check_ruby_version do + raise "bundler:build need to released Ruby for using nokogiri" if RUBY_PATCHLEVEL.to_i < 0 + end end end diff --git a/bundler/.changelog.yml b/bundler/.changelog.yml index da930c8a2cd6..e43698b1fa25 100644 --- a/bundler/.changelog.yml +++ b/bundler/.changelog.yml @@ -7,21 +7,19 @@ entry_template: " - %title [#%pull_request_number](%pull_request_url)" release_date_format: "%Y-%m-%d" changelog_label_mapping: - "bundler: security": "### Security:" - "bundler: breaking change": "### Breaking changes:" - "bundler: deprecation": "### Deprecations:" "bundler: feature": "### Features:" "bundler: performance": "### Performance:" "bundler: enhancement": "### Enhancements:" "bundler: bug fix": "### Bug fixes:" + "bundler: security": "### Security:" + "bundler: breaking change": "### Breaking changes:" + "bundler: deprecation": "### Deprecations:" "bundler: documentation": "### Documentation:" "bundler: skip changelog": null patch_level_labels: - - "bundler: security" - - "bundler: deprecation" - "bundler: enhancement" - "bundler: bug fix" - - "bundler: performance" + - "bundler: security" - "bundler: documentation" - "bundler: skip changelog" diff --git a/bundler/CHANGELOG.md b/bundler/CHANGELOG.md index 744345a61987..fee83430ea53 100644 --- a/bundler/CHANGELOG.md +++ b/bundler/CHANGELOG.md @@ -1,5 +1,50 @@ # Changelog +## 4.0.4 (2026-01-15) + +### Enhancements: + + - Validate more options for add sub-command [#5905](https://github.com/ruby/rubygems/pull/5905) + - Support Ruby 4.1 [#9219](https://github.com/ruby/rubygems/pull/9219) + +### Bug fixes: + + - Fix dependency source bug in bundler [#9213](https://github.com/ruby/rubygems/pull/9213) + - Retain current bundler version on `bundle clean` [#9221](https://github.com/ruby/rubygems/pull/9221) + +## 4.0.3 (2025-12-23) + +### Enhancements: + + - Fall back to ruby platform gem when precompiled variant is incompatible [#9211](https://github.com/ruby/rubygems/pull/9211) + +## 4.0.2 (2025-12-17) + +### Enhancements: + + - Support single quotes in mise format ruby version [#9183](https://github.com/ruby/rubygems/pull/9183) + - Tweak the Bundler's "X gems now installed message": [#9194](https://github.com/ruby/rubygems/pull/9194) + +### Bug fixes: + + - Allow to show cli_help with `bundler` executable [#9198](https://github.com/ruby/rubygems/pull/9198) + - Allow bundle pristine to work for git gems in the same repo [#9196](https://github.com/ruby/rubygems/pull/9196) + +## 4.0.1 (2025-12-09) + +### Performance: + + - Increase connection pool to allow for up to 70% speed increase on `bundle install` [#9087](https://github.com/ruby/rubygems/pull/9087) + +### Enhancements: + + - Fix the config suggestion in the warning for `$ bundle` [#9164](https://github.com/ruby/rubygems/pull/9164) + - Fix native extension loading in newgem template for RHEL-based systems [#9156](https://github.com/ruby/rubygems/pull/9156) + +### Bug fixes: + + - Fix Bundler removing executables after creating them [#9169](https://github.com/ruby/rubygems/pull/9169) + ## 4.0.0 (2025-12-03) ### Features: diff --git a/bundler/lib/bundler/cli.rb b/bundler/lib/bundler/cli.rb index 36ce04eb2a5d..1f6a65ca57ae 100644 --- a/bundler/lib/bundler/cli.rb +++ b/bundler/lib/bundler/cli.rb @@ -104,7 +104,7 @@ def cli_help primary_commands = ["install", "update", "cache", "exec", "config", "help"] list = self.class.printable_commands(true) - by_name = list.group_by {|name, _message| name.match(/^bundle (\w+)/)[1] } + by_name = list.group_by {|name, _message| name.match(/^bundler? (\w+)/)[1] } utilities = by_name.keys.sort - primary_commands primary_commands.map! {|name| (by_name[name] || raise("no primary command #{name}")).first } utilities.map! {|name| by_name[name].first } @@ -120,28 +120,31 @@ def cli_help self.class.send(:class_options_help, shell) end - desc "install_or_cli_help", "Tries to run bundle install but prints a summary of bundler commands if there is no Gemfile", hide: true + desc "install_or_cli_help", "Deprecated alias of install", hide: true def install_or_cli_help + Bundler.ui.warn <<~MSG + `bundle install_or_cli_help` is a deprecated alias of `bundle install`. + It might be called due to the 'default_cli_command' being set to 'install_or_cli_help', + if so fix that by running `bundle config set default_cli_command install --global`. + MSG invoke_other_command("install") - rescue GemfileNotFound => error - Bundler.ui.error error.message, wrap: true - invoke_other_command("cli_help") end def self.default_command(meth = nil) return super if meth unless Bundler.settings[:default_cli_command] - Bundler.ui.info <<-MSG + Bundler.ui.info <<~MSG In a future version of Bundler, running `bundle` without argument will no longer run `bundle install`. Instead, the `cli_help` command will be displayed. Please use `bundle install` explicitly for scripts like CI/CD. You can use the future behavior now with `bundle config set default_cli_command cli_help --global`, - or you can continue to use the current behavior with `bundle config set default_cli_command install_or_cli_help --global`. + or you can continue to use the current behavior with `bundle config set default_cli_command install --global`. This message will be removed after a default_cli_command value is set. + MSG end - Bundler.settings[:default_cli_command] || "install_or_cli_help" + Bundler.settings[:default_cli_command] || "install" end class_option "no-color", type: :boolean, desc: "Disable colorization in output" @@ -287,6 +290,9 @@ def install Bundler.settings.temporary(no_install: false) do Install.new(options).run end + rescue GemfileNotFound => error + invoke_other_command("cli_help") + raise error # re-raise to show the error and get a failing exit status end map aliases_for("install") diff --git a/bundler/lib/bundler/cli/add.rb b/bundler/lib/bundler/cli/add.rb index 12a681a81688..9f1760409617 100644 --- a/bundler/lib/bundler/cli/add.rb +++ b/bundler/lib/bundler/cli/add.rb @@ -36,6 +36,16 @@ def inject_dependencies end def validate_options! + raise InvalidOption, "You cannot specify `--git` and `--github` at the same time." if options["git"] && options["github"] + + unless options["git"] || options["github"] + raise InvalidOption, "You cannot specify `--branch` unless `--git` or `--github` is specified." if options["branch"] + + raise InvalidOption, "You cannot specify `--ref` unless `--git` or `--github` is specified." if options["ref"] + end + + raise InvalidOption, "You cannot specify `--branch` and `--ref` at the same time." if options["branch"] && options["ref"] + raise InvalidOption, "You cannot specify `--strict` and `--optimistic` at the same time." if options[:strict] && options[:optimistic] # raise error when no gems are specified diff --git a/bundler/lib/bundler/cli/install.rb b/bundler/lib/bundler/cli/install.rb index ba1cef8434e6..67feba84bdb0 100644 --- a/bundler/lib/bundler/cli/install.rb +++ b/bundler/lib/bundler/cli/install.rb @@ -90,7 +90,7 @@ def dependencies_count_for(definition) end def gems_installed_for(definition) - count = definition.specs.count + count = definition.specs.count {|spec| spec.name != "bundler" } "#{count} #{count == 1 ? "gem" : "gems"} now installed" end diff --git a/bundler/lib/bundler/cli/pristine.rb b/bundler/lib/bundler/cli/pristine.rb index cfd4a995a31f..b8545fe4c9c3 100644 --- a/bundler/lib/bundler/cli/pristine.rb +++ b/bundler/lib/bundler/cli/pristine.rb @@ -11,6 +11,7 @@ def run definition = Bundler.definition definition.validate_runtime! installer = Bundler::Installer.new(Bundler.root, definition) + git_sources = [] ProcessLock.lock do installed_specs = definition.specs.reject do |spec| @@ -41,6 +42,9 @@ def run end FileUtils.rm_rf spec.extension_dir FileUtils.rm_rf spec.full_gem_path + + next if git_sources.include?(source) + git_sources << source else Bundler.ui.warn("Cannot pristine #{gem_name}. Gem is sourced from local path.") next diff --git a/bundler/lib/bundler/current_ruby.rb b/bundler/lib/bundler/current_ruby.rb index ab6520a106a4..17c7655adb58 100644 --- a/bundler/lib/bundler/current_ruby.rb +++ b/bundler/lib/bundler/current_ruby.rb @@ -11,7 +11,7 @@ def self.current_ruby end class CurrentRuby - ALL_RUBY_VERSIONS = [*18..27, *30..34, 40].freeze + ALL_RUBY_VERSIONS = [*18..27, *30..34, *40..41].freeze KNOWN_MINOR_VERSIONS = ALL_RUBY_VERSIONS.map {|v| v.digits.reverse.join(".") }.freeze KNOWN_MAJOR_VERSIONS = ALL_RUBY_VERSIONS.map {|v| v.digits.last.to_s }.uniq.freeze PLATFORM_MAP = { diff --git a/bundler/lib/bundler/definition.rb b/bundler/lib/bundler/definition.rb index 2fa7d0d27794..5ab577f504c3 100644 --- a/bundler/lib/bundler/definition.rb +++ b/bundler/lib/bundler/definition.rb @@ -1066,7 +1066,22 @@ def converge_specs(specs) deps << dep if !replacement_source || lockfile_source.include?(replacement_source) || new_deps.include?(dep) else - replacement_source = sources.get(lockfile_source) + parent_dep = @dependencies.find do |d| + next unless d.source && d.source != lockfile_source + next if d.source.is_a?(Source::Gemspec) + + parent_locked_specs = @originally_locked_specs[d.name] + + parent_locked_specs.any? do |parent_spec| + parent_spec.runtime_dependencies.any? {|rd| rd.name == s.name } + end + end + + if parent_dep + replacement_source = parent_dep.source + else + replacement_source = sources.get(lockfile_source) + end end # Replace the locked dependency's source with the equivalent source from the Gemfile diff --git a/bundler/lib/bundler/endpoint_specification.rb b/bundler/lib/bundler/endpoint_specification.rb index 95c6deea26bf..c06684657d59 100644 --- a/bundler/lib/bundler/endpoint_specification.rb +++ b/bundler/lib/bundler/endpoint_specification.rb @@ -60,6 +60,28 @@ def load_paths end end + # needed for binstubs + def executables + if @remote_specification + @remote_specification.executables + elsif _local_specification + _local_specification.executables + else + super + end + end + + # needed for bundle clean + def bindir + if @remote_specification + @remote_specification.bindir + elsif _local_specification + _local_specification.bindir + else + super + end + end + # needed for post_install_messages during install def post_install_message if @remote_specification diff --git a/bundler/lib/bundler/fetcher/gem_remote_fetcher.rb b/bundler/lib/bundler/fetcher/gem_remote_fetcher.rb index 3fc7b682632b..3c3c1826a1b1 100644 --- a/bundler/lib/bundler/fetcher/gem_remote_fetcher.rb +++ b/bundler/lib/bundler/fetcher/gem_remote_fetcher.rb @@ -5,6 +5,12 @@ module Bundler class Fetcher class GemRemoteFetcher < Gem::RemoteFetcher + def initialize(*) + super + + @pool_size = 5 + end + def request(*args) super do |req| req.delete("User-Agent") if headers["User-Agent"] diff --git a/bundler/lib/bundler/lazy_specification.rb b/bundler/lib/bundler/lazy_specification.rb index 81ded5479779..786dbcae6586 100644 --- a/bundler/lib/bundler/lazy_specification.rb +++ b/bundler/lib/bundler/lazy_specification.rb @@ -138,24 +138,16 @@ def materialize_for_installation source.local! if use_exact_resolved_specifications? - materialize(self) do |matching_specs| - choose_compatible(matching_specs) - end - else - materialize([name, version]) do |matching_specs| - target_platform = source.is_a?(Source::Path) ? platform : Bundler.local_platform - - installable_candidates = MatchPlatform.select_best_platform_match(matching_specs, target_platform) - - specification = choose_compatible(installable_candidates, fallback_to_non_installable: false) - return specification unless specification.nil? + spec = materialize(self) {|specs| choose_compatible(specs, fallback_to_non_installable: false) } + return spec if spec - if target_platform != platform - installable_candidates = MatchPlatform.select_best_platform_match(matching_specs, platform) - end - - choose_compatible(installable_candidates) + # Exact spec is incompatible; in frozen mode, try to find a compatible platform variant + # In non-frozen mode, return nil to trigger re-resolution and lockfile update + if Bundler.frozen_bundle? + materialize([name, version]) {|specs| resolve_best_platform(specs) } end + else + materialize([name, version]) {|specs| resolve_best_platform(specs) } end end @@ -190,6 +182,39 @@ def use_exact_resolved_specifications? !source.is_a?(Source::Path) && ruby_platform_materializes_to_ruby_platform? end + # Try platforms in order of preference until finding a compatible spec. + # Used for legacy lockfiles and as a fallback when the exact locked spec + # is incompatible. Falls back to frozen bundle behavior if none match. + def resolve_best_platform(specs) + find_compatible_platform_spec(specs) || frozen_bundle_fallback(specs) + end + + def find_compatible_platform_spec(specs) + candidate_platforms.each do |plat| + candidates = MatchPlatform.select_best_platform_match(specs, plat) + spec = choose_compatible(candidates, fallback_to_non_installable: false) + return spec if spec + end + nil + end + + # Platforms to try in order of preference. Ruby platform is last since it + # requires compilation, but works when precompiled gems are incompatible. + def candidate_platforms + target = source.is_a?(Source::Path) ? platform : Bundler.local_platform + [target, platform, Gem::Platform::RUBY].uniq + end + + # In frozen mode, accept any candidate. Will error at install time. + # When target differs from locked platform, prefer locked platform's candidates + # to preserve lockfile integrity. + def frozen_bundle_fallback(specs) + target = source.is_a?(Source::Path) ? platform : Bundler.local_platform + fallback_platform = target == platform ? target : platform + candidates = MatchPlatform.select_best_platform_match(specs, fallback_platform) + choose_compatible(candidates) + end + def ruby_platform_materializes_to_ruby_platform? generic_platform = Bundler.generic_local_platform == Gem::Platform::JAVA ? Gem::Platform::JAVA : Gem::Platform::RUBY diff --git a/bundler/lib/bundler/ruby_dsl.rb b/bundler/lib/bundler/ruby_dsl.rb index db4d5521e549..5e52f38c8fd7 100644 --- a/bundler/lib/bundler/ruby_dsl.rb +++ b/bundler/lib/bundler/ruby_dsl.rb @@ -42,18 +42,21 @@ def ruby(*ruby_version) # Loads the file relative to the dirname of the Gemfile itself. def normalize_ruby_file(filename) file_content = Bundler.read_file(gemfile.dirname.join(filename)) - # match "ruby-3.2.2", ruby = "3.2.2" or "ruby 3.2.2" capturing version string up to the first space or comment - if /^ # Start of line - ruby # Literal "ruby" - [\s-]* # Optional whitespace or hyphens (for "ruby-3.2.2" format) - (?:=\s*)? # Optional equals sign with whitespace (for ruby = "3.2.2" format) - "? # Optional opening quote - ( # Start capturing group - [^\s#"]+ # One or more chars that aren't spaces, #, or quotes - ) # End capturing group - "? # Optional closing quote - /x.match(file_content) - $1 + # match "ruby-3.2.2", ruby = "3.2.2", ruby = '3.2.2' or "ruby 3.2.2" capturing version string up to the first space or comment + version_match = /^ # Start of line + ruby # Literal "ruby" + [\s-]* # Optional whitespace or hyphens (for "ruby-3.2.2" format) + (?:=\s*)? # Optional equals sign with whitespace (for ruby = "3.2.2" format) + (?: + "([^"]+)" # Double quoted version + | + '([^']+)' # Single quoted version + | + ([^\s#"']+) # Unquoted version + ) + /x.match(file_content) + if version_match + version_match[1] || version_match[2] || version_match[3] else file_content.strip end diff --git a/bundler/lib/bundler/rubygems_gem_installer.rb b/bundler/lib/bundler/rubygems_gem_installer.rb index 0da5ed236b2b..64ce6193d3d1 100644 --- a/bundler/lib/bundler/rubygems_gem_installer.rb +++ b/bundler/lib/bundler/rubygems_gem_installer.rb @@ -103,6 +103,10 @@ def generate_bin_script(filename, bindir) end end + def build_jobs + Bundler.settings[:jobs] || super + end + def build_extensions extension_cache_path = options[:bundler_extension_cache_path] extension_dir = spec.extension_dir diff --git a/bundler/lib/bundler/rubygems_integration.rb b/bundler/lib/bundler/rubygems_integration.rb index d8f95cffb8fc..e04ef232592a 100644 --- a/bundler/lib/bundler/rubygems_integration.rb +++ b/bundler/lib/bundler/rubygems_integration.rb @@ -432,7 +432,7 @@ def default_specs end def find_bundler(version) - find_name("bundler").find {|s| s.version.to_s == version } + find_name("bundler").find {|s| s.version.to_s == version.to_s } end def find_name(name) diff --git a/bundler/lib/bundler/runtime.rb b/bundler/lib/bundler/runtime.rb index 5eb827dcb2a8..5280e72aa24b 100644 --- a/bundler/lib/bundler/runtime.rb +++ b/bundler/lib/bundler/runtime.rb @@ -174,7 +174,14 @@ def clean(dry_run = false) spec_cache_paths = [] spec_gemspec_paths = [] spec_extension_paths = [] - Bundler.rubygems.add_default_gems_to(specs).values.each do |spec| + specs_to_keep = Bundler.rubygems.add_default_gems_to(specs).values + + current_bundler = Bundler.rubygems.find_bundler(Bundler.gem_version) + if current_bundler + specs_to_keep << current_bundler + end + + specs_to_keep.each do |spec| spec_gem_paths << spec.full_gem_path # need to check here in case gems are nested like for the rails git repo md = %r{(.+bundler/gems/.+-[a-f0-9]{7,12})}.match(spec.full_gem_path) diff --git a/bundler/lib/bundler/templates/newgem/lib/newgem.rb.tt b/bundler/lib/bundler/templates/newgem/lib/newgem.rb.tt index caf6e32f4abf..3aedee0d25e9 100644 --- a/bundler/lib/bundler/templates/newgem/lib/newgem.rb.tt +++ b/bundler/lib/bundler/templates/newgem/lib/newgem.rb.tt @@ -2,7 +2,7 @@ require_relative "<%= File.basename(config[:namespaced_path]) %>/version" <%- if config[:ext] -%> -require_relative "<%= File.basename(config[:namespaced_path]) %>/<%= config[:underscored_name] %>" +require "<%= File.basename(config[:namespaced_path]) %>/<%= config[:underscored_name] %>" <%- end -%> <%- config[:constant_array].each_with_index do |c, i| -%> diff --git a/bundler/lib/bundler/version.rb b/bundler/lib/bundler/version.rb index 8cdc3067366a..8c039d16a06c 100644 --- a/bundler/lib/bundler/version.rb +++ b/bundler/lib/bundler/version.rb @@ -1,7 +1,7 @@ # frozen_string_literal: false module Bundler - VERSION = "4.0.0".freeze + VERSION = "4.0.4".freeze def self.bundler_major_version @bundler_major_version ||= gem_version.segments.first diff --git a/bundler/spec/bundler/cli_spec.rb b/bundler/spec/bundler/cli_spec.rb index 4503cea6a005..e2c64b939401 100644 --- a/bundler/spec/bundler/cli_spec.rb +++ b/bundler/spec/bundler/cli_spec.rb @@ -100,10 +100,11 @@ def out_with_macos_man_workaround end it "runs bundle install when default_cli_command set to install" do - bundle "config set default_cli_command install_or_cli_help" + bundle "config set default_cli_command install" bundle "", raise_on_error: false expect(out).to_not include("In a future version of Bundler") expect(err).to include("Could not locate Gemfile") + expect(exitstatus).to_not be_zero end end @@ -281,4 +282,15 @@ def out_with_macos_man_workaround bundler "--version" expect(out).to eq("#{Bundler::VERSION} (simulating Bundler 5)") end + + it "shows cli_help when bundler install and no Gemfile is found" do + bundler "install", raise_on_error: false + expect(err).to include("Could not locate Gemfile") + + expect(out).to include("Bundler version #{Bundler::VERSION}"). + and include("\n\nBundler commands:\n\n"). + and include("\n\n Primary commands:\n"). + and include("\n\n Utilities:\n"). + and include("\n\nOptions:\n") + end end diff --git a/bundler/spec/bundler/current_ruby_spec.rb b/bundler/spec/bundler/current_ruby_spec.rb index aa19a4140765..79eb802aa522 100644 --- a/bundler/spec/bundler/current_ruby_spec.rb +++ b/bundler/spec/bundler/current_ruby_spec.rb @@ -23,6 +23,7 @@ ruby_33: Gem::Platform::RUBY, ruby_34: Gem::Platform::RUBY, ruby_40: Gem::Platform::RUBY, + ruby_41: Gem::Platform::RUBY, mri: Gem::Platform::RUBY, mri_18: Gem::Platform::RUBY, mri_19: Gem::Platform::RUBY, @@ -40,6 +41,7 @@ mri_33: Gem::Platform::RUBY, mri_34: Gem::Platform::RUBY, mri_40: Gem::Platform::RUBY, + mri_41: Gem::Platform::RUBY, rbx: Gem::Platform::RUBY, truffleruby: Gem::Platform::RUBY, jruby: Gem::Platform::JAVA, @@ -61,7 +63,8 @@ windows_32: Gem::Platform::WINDOWS, windows_33: Gem::Platform::WINDOWS, windows_34: Gem::Platform::WINDOWS, - windows_40: Gem::Platform::WINDOWS } + windows_40: Gem::Platform::WINDOWS, + windows_41: Gem::Platform::WINDOWS } end let(:deprecated) do @@ -82,6 +85,7 @@ mswin_33: Gem::Platform::MSWIN, mswin_34: Gem::Platform::MSWIN, mswin_40: Gem::Platform::MSWIN, + mswin_41: Gem::Platform::MSWIN, mswin64: Gem::Platform::MSWIN64, mswin64_19: Gem::Platform::MSWIN64, mswin64_20: Gem::Platform::MSWIN64, @@ -98,6 +102,7 @@ mswin64_33: Gem::Platform::MSWIN64, mswin64_34: Gem::Platform::MSWIN64, mswin64_40: Gem::Platform::MSWIN64, + mswin64_41: Gem::Platform::MSWIN64, mingw: Gem::Platform::UNIVERSAL_MINGW, mingw_18: Gem::Platform::UNIVERSAL_MINGW, mingw_19: Gem::Platform::UNIVERSAL_MINGW, @@ -115,6 +120,7 @@ mingw_33: Gem::Platform::UNIVERSAL_MINGW, mingw_34: Gem::Platform::UNIVERSAL_MINGW, mingw_40: Gem::Platform::UNIVERSAL_MINGW, + mingw_41: Gem::Platform::UNIVERSAL_MINGW, x64_mingw: Gem::Platform::UNIVERSAL_MINGW, x64_mingw_20: Gem::Platform::UNIVERSAL_MINGW, x64_mingw_21: Gem::Platform::UNIVERSAL_MINGW, @@ -129,7 +135,8 @@ x64_mingw_32: Gem::Platform::UNIVERSAL_MINGW, x64_mingw_33: Gem::Platform::UNIVERSAL_MINGW, x64_mingw_34: Gem::Platform::UNIVERSAL_MINGW, - x64_mingw_40: Gem::Platform::UNIVERSAL_MINGW } + x64_mingw_40: Gem::Platform::UNIVERSAL_MINGW, + x64_mingw_41: Gem::Platform::UNIVERSAL_MINGW } end # rubocop:enable Naming/VariableNumber diff --git a/bundler/spec/bundler/fetcher/gem_remote_fetcher_spec.rb b/bundler/spec/bundler/fetcher/gem_remote_fetcher_spec.rb new file mode 100644 index 000000000000..df1a58d84362 --- /dev/null +++ b/bundler/spec/bundler/fetcher/gem_remote_fetcher_spec.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require "rubygems/remote_fetcher" +require "bundler/fetcher/gem_remote_fetcher" +require_relative "../../support/artifice/helpers/artifice" +require "bundler/vendored_persistent.rb" + +RSpec.describe Bundler::Fetcher::GemRemoteFetcher do + describe "Parallel download" do + it "download using multiple connections from the pool" do + unless Bundler.rubygems.provides?(">= 4.0.0.dev") + skip "This example can only run when RubyGems supports multiple http connection pool" + end + + require_relative "../../support/artifice/helpers/endpoint" + concurrent_ruby_path = Dir[scoped_base_system_gem_path.join("gems/concurrent-ruby-*/lib/concurrent-ruby")].first + $LOAD_PATH.unshift(concurrent_ruby_path) + require "concurrent-ruby" + + require_rack_test + responses = [] + + latch1 = Concurrent::CountDownLatch.new + latch2 = Concurrent::CountDownLatch.new + previous_client = Gem::Request::ConnectionPools.client + dummy_endpoint = Class.new(Endpoint) do + get "/foo" do + latch2.count_down + latch1.wait + + responses << "foo" + end + + get "/bar" do + responses << "bar" + + latch1.count_down + end + end + + Artifice.activate_with(dummy_endpoint) + Gem::Request::ConnectionPools.client = Gem::Net::HTTP + + first_request = Thread.new do + subject.fetch_path("https://example.org/foo") + end + second_request = Thread.new do + latch2.wait + subject.fetch_path("https://example.org/bar") + end + + [first_request, second_request].each(&:join) + + expect(responses).to eq(["bar", "foo"]) + ensure + Artifice.deactivate + Gem::Request::ConnectionPools.client = previous_client + end + end +end diff --git a/bundler/spec/bundler/plugin/events_spec.rb b/bundler/spec/bundler/plugin/events_spec.rb index 28d70c6fdddd..77e5fdb74cb5 100644 --- a/bundler/spec/bundler/plugin/events_spec.rb +++ b/bundler/spec/bundler/plugin/events_spec.rb @@ -2,7 +2,17 @@ RSpec.describe Bundler::Plugin::Events do context "plugin events" do - before { Bundler::Plugin::Events.send :reset } + before do + @old_constants = Bundler::Plugin::Events.constants.map {|name| [name, Bundler::Plugin::Events.const_get(name)] } + Bundler::Plugin::Events.send :reset + end + + after do + Bundler::Plugin::Events.send(:reset) + Hash[@old_constants].each do |name, value| + Bundler::Plugin::Events.send(:define, name, value) + end + end describe "#define" do it "raises when redefining a constant" do diff --git a/bundler/spec/bundler/plugin/installer_spec.rb b/bundler/spec/bundler/plugin/installer_spec.rb index 8e1879395a63..c200a98afaf2 100644 --- a/bundler/spec/bundler/plugin/installer_spec.rb +++ b/bundler/spec/bundler/plugin/installer_spec.rb @@ -47,6 +47,13 @@ build_plugin "re-plugin" build_plugin "ma-plugin" end + + @previous_ui = Bundler.ui + Bundler.ui = Bundler::UI::Silent.new + end + + after do + Bundler.ui = @previous_ui end context "git plugins" do diff --git a/bundler/spec/bundler/plugin_spec.rb b/bundler/spec/bundler/plugin_spec.rb index fea392500067..e416772a3672 100644 --- a/bundler/spec/bundler/plugin_spec.rb +++ b/bundler/spec/bundler/plugin_spec.rb @@ -279,6 +279,7 @@ s.write "plugins.rb", code end + @old_constants = Bundler::Plugin::Events.constants.map {|name| [name, Bundler::Plugin::Events.const_get(name)] } Bundler::Plugin::Events.send(:reset) Bundler::Plugin::Events.send(:define, :EVENT1, "event-1") Bundler::Plugin::Events.send(:define, :EVENT2, "event-2") @@ -291,6 +292,13 @@ allow(index).to receive(:load_paths).with("foo-plugin").and_return([]) end + after do + Bundler::Plugin::Events.send(:reset) + Hash[@old_constants].each do |name, value| + Bundler::Plugin::Events.send(:define, name, value) + end + end + let(:code) { <<-RUBY } Bundler::Plugin::API.hook("event-1") { puts "hook for event 1" } RUBY diff --git a/bundler/spec/bundler/ruby_dsl_spec.rb b/bundler/spec/bundler/ruby_dsl_spec.rb index 0d02542fb595..45a37c5795c8 100644 --- a/bundler/spec/bundler/ruby_dsl_spec.rb +++ b/bundler/spec/bundler/ruby_dsl_spec.rb @@ -178,11 +178,34 @@ class MockDSL let(:file_content) do <<~TOML [tools] - ruby = "#{version}" + ruby = #{quote}#{version}#{quote} TOML end - it_behaves_like "it stores the ruby version" + context "with double quotes" do + let(:quote) { '"' } + + it_behaves_like "it stores the ruby version" + end + + context "with single quotes" do + let(:quote) { "'" } + + it_behaves_like "it stores the ruby version" + end + + context "with mismatched quotes" do + let(:file_content) do + <<~TOML + [tools] + ruby = "#{version}' + TOML + end + + it "raises an error" do + expect { subject }.to raise_error(Bundler::InvalidArgumentError, "= is not a valid requirement on the Ruby version") + end + end end context "with a .tool-versions file format" do diff --git a/bundler/spec/bundler/uri_normalizer_spec.rb b/bundler/spec/bundler/uri_normalizer_spec.rb new file mode 100644 index 000000000000..1308e8601459 --- /dev/null +++ b/bundler/spec/bundler/uri_normalizer_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +RSpec.describe Bundler::URINormalizer do + describe ".normalize_suffix" do + context "when trailing_slash is true" do + it "adds a trailing slash when missing" do + expect(described_class.normalize_suffix("https://example.com", trailing_slash: true)).to eq("https://example.com/") + end + + it "keeps the trailing slash when present" do + expect(described_class.normalize_suffix("https://example.com/", trailing_slash: true)).to eq("https://example.com/") + end + end + + context "when trailing_slash is false" do + it "removes a trailing slash when present" do + expect(described_class.normalize_suffix("https://example.com/", trailing_slash: false)).to eq("https://example.com") + end + + it "keeps the value unchanged when no trailing slash exists" do + expect(described_class.normalize_suffix("https://example.com", trailing_slash: false)).to eq("https://example.com") + end + end + end +end diff --git a/bundler/spec/commands/add_spec.rb b/bundler/spec/commands/add_spec.rb index 00aa6415e11c..ed98a914f326 100644 --- a/bundler/spec/commands/add_spec.rb +++ b/bundler/spec/commands/add_spec.rb @@ -236,6 +236,40 @@ end end + describe "with mismatched pair in --git/--github, --branch/--ref" do + describe "with --git and --github" do + it "throws error" do + bundle "add 'foo' --git x --github y", raise_on_error: false + + expect(err).to include("You cannot specify `--git` and `--github` at the same time.") + end + end + + describe "with --branch and --ref with --git" do + it "throws error" do + bundle "add 'foo' --branch x --ref y --git file://git", raise_on_error: false + + expect(err).to include("You cannot specify `--branch` and `--ref` at the same time.") + end + end + + describe "with --branch but without --git or --github" do + it "throws error" do + bundle "add 'foo' --branch x", raise_on_error: false + + expect(err).to include("You cannot specify `--branch` unless `--git` or `--github` is specified.") + end + end + + describe "with --ref but without --git or --github" do + it "throws error" do + bundle "add 'foo' --ref y", raise_on_error: false + + expect(err).to include("You cannot specify `--ref` unless `--git` or `--github` is specified.") + end + end + end + describe "with --skip-install" do it "adds gem to Gemfile but is not installed" do bundle "add foo --skip-install --version=2.0" diff --git a/bundler/spec/commands/clean_spec.rb b/bundler/spec/commands/clean_spec.rb index 6b678d0aa545..793aacf5c2b5 100644 --- a/bundler/spec/commands/clean_spec.rb +++ b/bundler/spec/commands/clean_spec.rb @@ -898,4 +898,41 @@ def should_not_have_gems(*gems) expect(very_simple_binary_extensions_dir).to be_nil end + + it "does not remove the bundler version currently running" do + gemfile <<-G + source "https://gem.repo1" + + gem "myrack" + G + + bundle "config set path vendor/bundle" + bundle "install" + + version = Bundler.gem_version.to_s + # Simulate that the locked bundler version is installed in the bundle path + # by creating the gem directory and gemspec (as would happen after bundle install with that version) + Pathname(vendored_gems("cache/bundler-#{version}.gem")).tap do |path| + path.basename.mkpath + FileUtils.touch(path) + end + FileUtils.touch(vendored_gems("gems/bundler-#{version}")) + Pathname(vendored_gems("specifications/bundler-#{version}.gemspec")).tap do |path| + path.basename.mkpath + path.write(<<~GEMSPEC) + Gem::Specification.new do |s| + s.name = "bundler" + s.version = "#{version}" + s.authors = ["bundler team"] + s.summary = "The best way to manage your application's dependencies" + end + GEMSPEC + end + + should_have_gems "bundler-#{version}" + + bundle :clean + + should_have_gems "bundler-#{version}" + end end diff --git a/bundler/spec/commands/install_spec.rb b/bundler/spec/commands/install_spec.rb index 3dc8aa0dc017..ae651bf981c7 100644 --- a/bundler/spec/commands/install_spec.rb +++ b/bundler/spec/commands/install_spec.rb @@ -1306,6 +1306,79 @@ def run end end + describe "parallel make" do + before do + unless Gem::Installer.private_method_defined?(:build_jobs) + skip "This example is runnable when RubyGems::Installer implements `build_jobs`" + end + + @old_makeflags = ENV["MAKEFLAGS"] + @gemspec = nil + + extconf_code = <<~CODE + require "mkmf" + create_makefile("foo") + CODE + + build_repo4 do + build_gem "mypsych", "4.0.6" do |s| + @gemspec = s + extension = "ext/mypsych/extconf.rb" + s.extensions = extension + + s.write(extension, extconf_code) + end + end + end + + after do + if @old_makeflags + ENV["MAKEFLAGS"] = @old_makeflags + else + ENV.delete("MAKEFLAGS") + end + end + + it "doesn't pass down -j to make when MAKEFLAGS is set" do + ENV["MAKEFLAGS"] = "-j1" + + install_gemfile(<<~G, env: { "BUNDLE_JOBS" => "8" }) + source "https://gem.repo4" + gem "mypsych" + G + + gem_make_out = File.read(File.join(@gemspec.extension_dir, "gem_make.out")) + + expect(gem_make_out).not_to include("make -j8") + end + + it "pass down the BUNDLE_JOBS to RubyGems when running the compilation of an extension" do + ENV.delete("MAKEFLAGS") + + install_gemfile(<<~G, env: { "BUNDLE_JOBS" => "8" }) + source "https://gem.repo4" + gem "mypsych" + G + + gem_make_out = File.read(File.join(@gemspec.extension_dir, "gem_make.out")) + + expect(gem_make_out).to include("make -j8") + end + + it "uses nprocessors by default" do + ENV.delete("MAKEFLAGS") + + install_gemfile(<<~G) + source "https://gem.repo4" + gem "mypsych" + G + + gem_make_out = File.read(File.join(@gemspec.extension_dir, "gem_make.out")) + + expect(gem_make_out).to include("make -j#{Etc.nprocessors + 1}") + end + end + describe "when configured path is UTF-8 and a file inside a gem package too" do let(:app_path) do path = tmp("♥") @@ -1885,6 +1958,25 @@ def run expect(Dir.glob(vendored_gems("bin/*"))).to eq(expected_executables) end + it "prevents removing binstubs when BUNDLE_CLEAN is set" do + build_repo4 do + build_gem "kamal", "4.0.6" do |s| + s.executables = ["kamal"] + end + end + + gemfile = <<~G + source "https://gem.repo4" + gem "kamal" + G + + install_gemfile(gemfile, env: { "BUNDLE_CLEAN" => "true", "BUNDLE_PATH" => "vendor/bundle" }) + + expected_executables = [vendored_gems("bin/kamal").to_s] + expected_executables << vendored_gems("bin/kamal.bat").to_s if Gem.win_platform? + expect(Dir.glob(vendored_gems("bin/*"))).to eq(expected_executables) + end + it "preserves lockfile versions conservatively" do build_repo4 do build_gem "mypsych", "4.0.6" do |s| diff --git a/bundler/spec/commands/newgem_spec.rb b/bundler/spec/commands/newgem_spec.rb index 1d158726bed6..06c226f9e56a 100644 --- a/bundler/spec/commands/newgem_spec.rb +++ b/bundler/spec/commands/newgem_spec.rb @@ -1681,6 +1681,13 @@ def create_temporary_dir(dir) expect(bundled_app("#{gem_name}/ext/#{gem_name}/#{gem_name}.c")).to exist end + it "generates native extension loading code" do + expect(bundled_app("#{gem_name}/lib/#{gem_name}.rb").read).to include(<<~RUBY) + require_relative "test_gem/version" + require "#{gem_name}/#{gem_name}" + RUBY + end + it "includes rake-compiler, but no Rust related changes" do expect(bundled_app("#{gem_name}/Gemfile").read).to include('gem "rake-compiler"') diff --git a/bundler/spec/commands/post_bundle_message_spec.rb b/bundler/spec/commands/post_bundle_message_spec.rb index 257443526089..9eecff593f7e 100644 --- a/bundler/spec/commands/post_bundle_message_spec.rb +++ b/bundler/spec/commands/post_bundle_message_spec.rb @@ -18,7 +18,7 @@ let(:bundle_show_path_message) { "Bundled gems are installed into `#{bundle_path}`" } let(:bundle_complete_message) { "Bundle complete!" } let(:bundle_updated_message) { "Bundle updated!" } - let(:installed_gems_stats) { "4 Gemfile dependencies, 5 gems now installed." } + let(:installed_gems_stats) { "4 Gemfile dependencies, 4 gems now installed." } describe "when installing to system gems" do before do @@ -44,14 +44,14 @@ expect(out).to include(bundle_show_system_message) expect(out).to include("Gems in the groups 'emo' and 'test' were not installed") expect(out).to include(bundle_complete_message) - expect(out).to include("4 Gemfile dependencies, 3 gems now installed.") + expect(out).to include("4 Gemfile dependencies, 2 gems now installed.") bundle "config set --local without emo obama test" bundle :install expect(out).to include(bundle_show_system_message) expect(out).to include("Gems in the groups 'emo', 'obama' and 'test' were not installed") expect(out).to include(bundle_complete_message) - expect(out).to include("4 Gemfile dependencies, 2 gems now installed.") + expect(out).to include("4 Gemfile dependencies, 1 gem now installed.") end describe "for second bundle install run" do diff --git a/bundler/spec/commands/pristine_spec.rb b/bundler/spec/commands/pristine_spec.rb index da61dc819920..daeafea43bf8 100644 --- a/bundler/spec/commands/pristine_spec.rb +++ b/bundler/spec/commands/pristine_spec.rb @@ -89,6 +89,66 @@ expect(changes_txt).to be_file expect(err).to include("Cannot pristine #{spec.name} (#{spec.version}#{spec.git_version}). Gem is locally overridden.") end + + it "doesn't run multiple git processes for the same repository" do + nested_gems = [ + "actioncable", + "actionmailer", + "actionpack", + "actionview", + "activejob", + "activemodel", + "activerecord", + "activestorage", + "activesupport", + "railties", + ] + + build_repo2 do + nested_gems.each do |gem| + build_lib gem, path: lib_path("rails/#{gem}") + end + + build_git "rails", path: lib_path("rails") do |s| + nested_gems.each do |gem| + s.add_dependency gem + end + end + end + + install_gemfile <<-G + source 'https://rubygems.org' + + git "#{lib_path("rails")}" do + gem "rails" + gem "actioncable" + gem "actionmailer" + gem "actionpack" + gem "actionview" + gem "activejob" + gem "activemodel" + gem "activerecord" + gem "activestorage" + gem "activesupport" + gem "railties" + end + G + + changed_files = [] + diff = "#Pristine spec changes" + + nested_gems.each do |gem| + spec = find_spec(gem) + changed_files << Pathname.new(spec.full_gem_path).join("lib/#{gem}.rb") + File.open(changed_files.last, "a") {|f| f.puts diff } + end + + bundle "pristine" + + changed_files.each do |changed_file| + expect(File.read(changed_file)).to_not include(diff) + end + end end context "when sourced from gemspec" do diff --git a/bundler/spec/install/gemfile/sources_spec.rb b/bundler/spec/install/gemfile/sources_spec.rb index c0b4d98f1c5c..90f87ed0c5da 100644 --- a/bundler/spec/install/gemfile/sources_spec.rb +++ b/bundler/spec/install/gemfile/sources_spec.rb @@ -1079,4 +1079,120 @@ expect(lockfile).to eq original_lockfile.gsub("bigdecimal (1.0.0)", "bigdecimal (3.3.1)") end end + + context "when switching a gem with components from rubygems to git source" do + before do + build_repo2 do + build_gem "rails", "7.0.0" do |s| + s.add_dependency "actionpack", "7.0.0" + s.add_dependency "activerecord", "7.0.0" + end + build_gem "actionpack", "7.0.0" + build_gem "activerecord", "7.0.0" + # propshaft also depends on actionpack, creating the conflict + build_gem "propshaft", "1.0.0" do |s| + s.add_dependency "actionpack", ">= 7.0.0" + end + end + + build_git "rails", "7.0.0", path: lib_path("rails") do |s| + s.add_dependency "actionpack", "7.0.0" + s.add_dependency "activerecord", "7.0.0" + end + + build_git "actionpack", "7.0.0", path: lib_path("rails") + build_git "activerecord", "7.0.0", path: lib_path("rails") + + install_gemfile <<-G + source "https://gem.repo2" + gem "rails", "7.0.0" + gem "propshaft" + G + end + + it "moves component gems to the git source in the lockfile" do + expect(lockfile).to include("remote: https://gem.repo2") + expect(lockfile).to include("rails (7.0.0)") + expect(lockfile).to include("actionpack (7.0.0)") + expect(lockfile).to include("activerecord (7.0.0)") + expect(lockfile).to include("propshaft (1.0.0)") + + gemfile <<-G + source "https://gem.repo2" + gem "rails", git: "#{lib_path("rails")}" + gem "propshaft" + G + + bundle "install" + + expect(lockfile).to include("remote: #{lib_path("rails")}") + expect(lockfile).to include("rails (7.0.0)") + expect(lockfile).to include("actionpack (7.0.0)") + expect(lockfile).to include("activerecord (7.0.0)") + + # Component gems should NOT remain in the GEM section + # Extract just the GEM section by splitting on GIT first, then GEM + gem_section = lockfile.split("GEM\n").last.split(/\n(PLATFORMS|DEPENDENCIES)/)[0] + expect(gem_section).not_to include("actionpack (7.0.0)") + expect(gem_section).not_to include("activerecord (7.0.0)") + end + end + + context "when switching a gem with components from rubygems to path source" do + before do + build_repo2 do + build_gem "rails", "7.0.0" do |s| + s.add_dependency "actionpack", "7.0.0" + s.add_dependency "activerecord", "7.0.0" + end + build_gem "actionpack", "7.0.0" + build_gem "activerecord", "7.0.0" + # propshaft also depends on actionpack, creating the conflict + build_gem "propshaft", "1.0.0" do |s| + s.add_dependency "actionpack", ">= 7.0.0" + end + end + + build_lib "rails", "7.0.0", path: lib_path("rails") do |s| + s.add_dependency "actionpack", "7.0.0" + s.add_dependency "activerecord", "7.0.0" + end + + build_lib "actionpack", "7.0.0", path: lib_path("rails") + build_lib "activerecord", "7.0.0", path: lib_path("rails") + + install_gemfile <<-G + source "https://gem.repo2" + gem "rails", "7.0.0" + gem "propshaft" + G + end + + it "moves component gems to the path source in the lockfile" do + expect(lockfile).to include("remote: https://gem.repo2") + expect(lockfile).to include("rails (7.0.0)") + expect(lockfile).to include("actionpack (7.0.0)") + expect(lockfile).to include("activerecord (7.0.0)") + expect(lockfile).to include("propshaft (1.0.0)") + + gemfile <<-G + source "https://gem.repo2" + gem "rails", path: "#{lib_path("rails")}" + gem "propshaft" + G + + bundle "install" + + expect(lockfile).to include("remote: #{lib_path("rails")}") + expect(lockfile).to include("rails (7.0.0)") + expect(lockfile).to include("actionpack (7.0.0)") + expect(lockfile).to include("activerecord (7.0.0)") + + # Component gems should NOT remain in the GEM section + # Extract just the GEM section by splitting appropriately + gem_section = lockfile.split("GEM\n").last.split(/\n(PLATFORMS|DEPENDENCIES)/)[0] + expect(gem_section).not_to include("actionpack (7.0.0)") + expect(gem_section).not_to include("activerecord (7.0.0)") + end + end end diff --git a/bundler/spec/install/gemfile/specific_platform_spec.rb b/bundler/spec/install/gemfile/specific_platform_spec.rb index 39d2700474a7..448800d57817 100644 --- a/bundler/spec/install/gemfile/specific_platform_spec.rb +++ b/bundler/spec/install/gemfile/specific_platform_spec.rb @@ -157,6 +157,11 @@ end context "when running on a legacy lockfile locked only to ruby" do + # Exercises the legacy lockfile path (use_exact_resolved_specifications? = false) + # because most_specific_locked_platform is ruby, matching the generic platform. + # Key insight: when target (arm64-darwin-22) != platform (ruby), the code tries + # both platforms before falling back, preserving lockfile integrity. + around do |example| build_repo4 do build_gem "nokogiri", "1.3.10" @@ -192,13 +197,69 @@ end it "still installs the generic ruby variant if necessary" do - bundle "install --verbose" - expect(out).to include("Installing nokogiri 1.3.10") + bundle "install" + expect(the_bundle).to include_gem("nokogiri 1.3.10") + expect(the_bundle).not_to include_gem("nokogiri 1.3.10 arm64-darwin") end it "still installs the generic ruby variant if necessary, even in frozen mode" do - bundle "install --verbose", env: { "BUNDLE_FROZEN" => "true" } - expect(out).to include("Installing nokogiri 1.3.10") + bundle "install", env: { "BUNDLE_FROZEN" => "true" } + expect(the_bundle).to include_gem("nokogiri 1.3.10") + expect(the_bundle).not_to include_gem("nokogiri 1.3.10 arm64-darwin") + end + end + + context "when platform-specific gem has incompatible required_ruby_version" do + # Key insight: candidate_platforms tries [target, platform, ruby] in order. + # Ruby platform is last since it requires compilation, but works when + # precompiled gems are incompatible with the current Ruby version. + # + # Note: This fix requires the lockfile to include both ruby and platform- + # specific variants (typical after `bundle lock --add-platform`). If the + # lockfile only has platform-specific gems, frozen mode cannot help because + # Bundler.setup would still expect the locked (incompatible) gem. + + # Exercises the exact spec path (use_exact_resolved_specifications? = true) + # because lockfile has platform-specific entry as most_specific_locked_platform + it "falls back to ruby platform in frozen mode when lockfile includes both variants" do + build_repo4 do + build_gem "nokogiri", "1.18.10" + build_gem "nokogiri", "1.18.10" do |s| + s.platform = "x86_64-linux" + s.required_ruby_version = "< #{Gem.ruby_version}" + end + end + + gemfile <<~G + source "https://gem.repo4" + + gem "nokogiri" + G + + # Lockfile has both ruby and platform-specific gem (typical after `bundle lock --add-platform`) + lockfile <<-L + GEM + remote: https://gem.repo4/ + specs: + nokogiri (1.18.10) + nokogiri (1.18.10-x86_64-linux) + + PLATFORMS + ruby + x86_64-linux + + DEPENDENCIES + nokogiri + + BUNDLED WITH + #{Bundler::VERSION} + L + + simulate_platform "x86_64-linux" do + bundle "install", env: { "BUNDLE_FROZEN" => "true" } + expect(the_bundle).to include_gem("nokogiri 1.18.10") + expect(the_bundle).not_to include_gem("nokogiri 1.18.10 x86_64-linux") + end end end diff --git a/bundler/spec/realworld/fixtures/tapioca/Gemfile.lock b/bundler/spec/realworld/fixtures/tapioca/Gemfile.lock index 92b16c760644..1d4eae8ffe0b 100644 --- a/bundler/spec/realworld/fixtures/tapioca/Gemfile.lock +++ b/bundler/spec/realworld/fixtures/tapioca/Gemfile.lock @@ -46,4 +46,4 @@ DEPENDENCIES tapioca BUNDLED WITH - 4.0.0 + 4.0.4 diff --git a/bundler/spec/realworld/fixtures/warbler/Gemfile.lock b/bundler/spec/realworld/fixtures/warbler/Gemfile.lock index 90090e5fbfdc..6eb02db3db73 100644 --- a/bundler/spec/realworld/fixtures/warbler/Gemfile.lock +++ b/bundler/spec/realworld/fixtures/warbler/Gemfile.lock @@ -36,4 +36,4 @@ DEPENDENCIES warbler! BUNDLED WITH - 4.0.0 + 4.0.4 diff --git a/bundler/spec/spec_helper.rb b/bundler/spec/spec_helper.rb index 96cd7be4720a..a79e33fbb016 100644 --- a/bundler/spec/spec_helper.rb +++ b/bundler/spec/spec_helper.rb @@ -38,6 +38,7 @@ require_relative "support/matchers" require_relative "support/permissions" require_relative "support/platforms" +require_relative "support/windows_tag_group" $debug = false @@ -56,6 +57,7 @@ def self.ruby=(ruby) config.include Spec::Path config.include Spec::Platforms config.include Spec::Permissions + config.include Spec::WindowsTagGroup # Enable flags like --only-failures and --next-failure config.example_status_persistence_file_path = ".rspec_status" @@ -129,4 +131,19 @@ def self.ruby=(ruby) ensure reset! end + + Spec::WindowsTagGroup::EXAMPLE_MAPPINGS.each do |tag, file_paths| + file_pattern = Regexp.union(file_paths.map {|path| Regexp.new(Regexp.escape(path) + "$") }) + + config.define_derived_metadata(file_path: file_pattern) do |metadata| + metadata[tag] = true + end + end + + config.before(:context) do |example| + metadata = example.class.metadata + if metadata[:type] != :aruba && metadata.keys.none? {|k| Spec::WindowsTagGroup::EXAMPLE_MAPPINGS.keys.include?(k) } + warn "#{metadata[:file_path]} is not assigned to any Windows runner group. see spec/support/windows_tag_group.rb for details." + end + end unless Spec::Path.ruby_core? end diff --git a/bundler/spec/support/windows_tag_group.rb b/bundler/spec/support/windows_tag_group.rb new file mode 100644 index 000000000000..c41c446462c4 --- /dev/null +++ b/bundler/spec/support/windows_tag_group.rb @@ -0,0 +1,191 @@ +# frozen_string_literal: true + +# This group classifies test files into 4 groups by running `bin/rspec --profile 10000` +# to ensure balanced execution times. When adding new test files, it is recommended to +# re-aggregate and adjust the groups to keep them balanced. +# For now, please add new files to group 'windows_d'. + +module Spec + module WindowsTagGroup + EXAMPLE_MAPPINGS = { + windows_a: [ + "spec/runtime/setup_spec.rb", + "spec/commands/install_spec.rb", + "spec/commands/add_spec.rb", + "spec/install/gems/compact_index_spec.rb", + "spec/commands/config_spec.rb", + "spec/commands/pristine_spec.rb", + "spec/install/gemfile/path_spec.rb", + "spec/update/git_spec.rb", + "spec/commands/open_spec.rb", + "spec/commands/remove_spec.rb", + "spec/commands/show_spec.rb", + "spec/plugins/source/example_spec.rb", + "spec/commands/console_spec.rb", + "spec/runtime/require_spec.rb", + "spec/runtime/env_helpers_spec.rb", + "spec/runtime/gem_tasks_spec.rb", + "spec/install/gemfile_spec.rb", + "spec/commands/fund_spec.rb", + "spec/commands/init_spec.rb", + "spec/bundler/ruby_dsl_spec.rb", + "spec/bundler/mirror_spec.rb", + "spec/bundler/source/git/git_proxy_spec.rb", + "spec/bundler/source_list_spec.rb", + "spec/bundler/plugin/installer_spec.rb", + "spec/bundler/friendly_errors_spec.rb", + "spec/resolver/platform_spec.rb", + "spec/bundler/fetcher/downloader_spec.rb", + "spec/update/force_spec.rb", + "spec/bundler/env_spec.rb", + "spec/install/gems/mirror_spec.rb", + "spec/install/failure_spec.rb", + "spec/bundler/yaml_serializer_spec.rb", + "spec/bundler/environment_preserver_spec.rb", + "spec/install/gemfile/install_if_spec.rb", + "spec/install/gems/gemfile_source_header_spec.rb", + "spec/bundler/fetcher/base_spec.rb", + "spec/bundler/rubygems_integration_spec.rb", + "spec/bundler/worker_spec.rb", + "spec/bundler/dependency_spec.rb", + "spec/bundler/ui_spec.rb", + "spec/bundler/plugin/source_list_spec.rb", + "spec/bundler/source/path_spec.rb", + ], + windows_b: [ + "spec/install/gemfile/git_spec.rb", + "spec/install/gems/standalone_spec.rb", + "spec/commands/lock_spec.rb", + "spec/cache/gems_spec.rb", + "spec/other/major_deprecation_spec.rb", + "spec/install/gems/dependency_api_spec.rb", + "spec/install/gemfile/gemspec_spec.rb", + "spec/plugins/install_spec.rb", + "spec/commands/binstubs_spec.rb", + "spec/install/gems/flex_spec.rb", + "spec/runtime/inline_spec.rb", + "spec/commands/post_bundle_message_spec.rb", + "spec/runtime/executable_spec.rb", + "spec/lock/git_spec.rb", + "spec/plugins/hook_spec.rb", + "spec/install/allow_offline_install_spec.rb", + "spec/install/gems/post_install_spec.rb", + "spec/install/gemfile/ruby_spec.rb", + "spec/install/security_policy_spec.rb", + "spec/install/yanked_spec.rb", + "spec/update/gemfile_spec.rb", + "spec/runtime/load_spec.rb", + "spec/plugins/command_spec.rb", + "spec/commands/version_spec.rb", + "spec/install/prereleases_spec.rb", + "spec/bundler/uri_credentials_filter_spec.rb", + "spec/bundler/plugin_spec.rb", + "spec/install/gems/mirror_probe_spec.rb", + "spec/plugins/list_spec.rb", + "spec/bundler/compact_index_client/parser_spec.rb", + "spec/bundler/gem_version_promoter_spec.rb", + "spec/other/cli_dispatch_spec.rb", + "spec/bundler/source/rubygems_spec.rb", + "spec/cache/platform_spec.rb", + "spec/update/gems/fund_spec.rb", + "spec/bundler/stub_specification_spec.rb", + "spec/bundler/retry_spec.rb", + "spec/bundler/installer/spec_installation_spec.rb", + "spec/bundler/spec_set_spec.rb", + "spec/quality_es_spec.rb", + "spec/bundler/index_spec.rb", + "spec/other/cli_man_pages_spec.rb", + ], + windows_c: [ + "spec/commands/newgem_spec.rb", + "spec/commands/exec_spec.rb", + "spec/commands/clean_spec.rb", + "spec/commands/platform_spec.rb", + "spec/cache/git_spec.rb", + "spec/install/gemfile/groups_spec.rb", + "spec/commands/cache_spec.rb", + "spec/commands/check_spec.rb", + "spec/commands/list_spec.rb", + "spec/install/path_spec.rb", + "spec/bundler/cli_spec.rb", + "spec/install/bundler_spec.rb", + "spec/install/git_spec.rb", + "spec/commands/doctor_spec.rb", + "spec/bundler/dsl_spec.rb", + "spec/install/gems/fund_spec.rb", + "spec/install/gems/env_spec.rb", + "spec/bundler/ruby_version_spec.rb", + "spec/bundler/definition_spec.rb", + "spec/install/gemfile/eval_gemfile_spec.rb", + "spec/plugins/source_spec.rb", + "spec/install/gems/dependency_api_fallback_spec.rb", + "spec/plugins/uninstall_spec.rb", + "spec/bundler/plugin/index_spec.rb", + "spec/bundler/bundler_spec.rb", + "spec/bundler/fetcher_spec.rb", + "spec/bundler/source/rubygems/remote_spec.rb", + "spec/bundler/lockfile_parser_spec.rb", + "spec/cache/cache_path_spec.rb", + "spec/bundler/source/git_spec.rb", + "spec/bundler/source_spec.rb", + "spec/commands/ssl_spec.rb", + "spec/bundler/fetcher/compact_index_spec.rb", + "spec/bundler/plugin/api_spec.rb", + "spec/bundler/endpoint_specification_spec.rb", + "spec/bundler/fetcher/index_spec.rb", + "spec/bundler/settings/validator_spec.rb", + "spec/bundler/build_metadata_spec.rb", + "spec/bundler/current_ruby_spec.rb", + "spec/bundler/installer/gem_installer_spec.rb", + "spec/bundler/cli_common_spec.rb", + "spec/bundler/ci_detector_spec.rb", + ], + windows_d: [ + "spec/commands/outdated_spec.rb", + "spec/commands/update_spec.rb", + "spec/lock/lockfile_spec.rb", + "spec/install/deploy_spec.rb", + "spec/install/gemfile/sources_spec.rb", + "spec/runtime/self_management_spec.rb", + "spec/install/gemfile/specific_platform_spec.rb", + "spec/commands/info_spec.rb", + "spec/install/gems/resolving_spec.rb", + "spec/install/gemfile/platform_spec.rb", + "spec/bundler/gem_helper_spec.rb", + "spec/install/global_cache_spec.rb", + "spec/runtime/platform_spec.rb", + "spec/update/gems/post_install_spec.rb", + "spec/install/gems/native_extensions_spec.rb", + "spec/install/force_spec.rb", + "spec/cache/path_spec.rb", + "spec/install/gemspecs_spec.rb", + "spec/commands/help_spec.rb", + "spec/bundler/shared_helpers_spec.rb", + "spec/bundler/settings_spec.rb", + "spec/resolver/basic_spec.rb", + "spec/install/gemfile/force_ruby_platform_spec.rb", + "spec/commands/licenses_spec.rb", + "spec/install/gemfile/lockfile_spec.rb", + "spec/bundler/fetcher/dependency_spec.rb", + "spec/quality_spec.rb", + "spec/bundler/remote_specification_spec.rb", + "spec/install/process_lock_spec.rb", + "spec/install/binstubs_spec.rb", + "spec/bundler/compact_index_client/updater_spec.rb", + "spec/bundler/ui/shell_spec.rb", + "spec/other/ext_spec.rb", + "spec/commands/issue_spec.rb", + "spec/update/path_spec.rb", + "spec/bundler/plugin/api/source_spec.rb", + "spec/install/gems/win32_spec.rb", + "spec/bundler/plugin/dsl_spec.rb", + "spec/runtime/requiring_spec.rb", + "spec/bundler/plugin/events_spec.rb", + "spec/bundler/resolver/candidate_spec.rb", + "spec/bundler/digest_spec.rb", + "spec/bundler/fetcher/gem_remote_fetcher_spec.rb", + "spec/bundler/uri_normalizer_spec.rb", + ], + }.freeze + end +end diff --git a/doc/RELEASE.md b/doc/RELEASE.md index f6314927dab7..cbe92b026046 100644 --- a/doc/RELEASE.md +++ b/doc/RELEASE.md @@ -84,11 +84,25 @@ We only release major breaking changes when incrementing the _major_ version of ### Steps for patch releases +#### Preparation + * Confirm all PRs that you want backported are properly tagged with `rubygems: ` or `bundler: ` labels at GitHub. -* Run `bin/rake prepare_release[]`. This will create a PR to the stable branch with the backports included in the release, and proper changelogs and version bumps. It will also create a PR to merge release changelogs into master. +* Confirm to set `AWS_PROFILE` and `GITHUB_RELEASE_PAT` environment variables. + +#### Release + +* Run `DRYRUN=1 bin/rake prepare_release[]` to verify that everything is working as expected. +* Run `bin/rake prepare_release[]`, this will: + * Create a PR to the stable branch with the backports included in the release. + * Generate proper changelogs and version bumps. It will also create a PR to merge release changelogs into master. +* If you need to make any manual changes, do so in the release PR created above and re-run `rake generate_changelog[]` to update changelogs and run `git rebase -i` as needed. Finally, force push the release PR branch. * Once CI passes, merge the release PR, switch to the stable branch and pull the PR just merged. -* Release `bundler` with `bin/rake bundler:release`. * Release `rubygems` with `bin/rake release`. +* Release `bundler` with `bin/rake bundler:release`. + +#### Post-release + +* Merge the changelog PR created above into master. ### Steps for minor and major releases @@ -119,6 +133,10 @@ We only release major breaking changes when incrementing the _major_ version of #### Post-release +* Merge the changelog PR created above into master. + +The following changes are needed to prepare for the next development cycle: + * Replace version numbers with the next ".dev" version. * Run `rake version:update_locked_bundler`, and push that change to the master PR. * Once CI passes, merge the release PR, switch to the stable branch and pull the PR just merged. diff --git a/doc/rubygems/CONTRIBUTING.md b/doc/rubygems/CONTRIBUTING.md index b6b0a2013005..35db373939fe 100644 --- a/doc/rubygems/CONTRIBUTING.md +++ b/doc/rubygems/CONTRIBUTING.md @@ -82,6 +82,10 @@ To run an individual test file location for example in `spec/install/gems/standa bin/rspec spec/install/gems/standalone_spec.rb +To test Rubygems changes in bundler, set the path to your local Rubygems copy using the `RGV` environment variable: + + RGV=.. bin/rspec spec/install/gems/standalone_spec.rb + ### Checking code style You can check compliance with our code style with diff --git a/lib/rubygems.rb b/lib/rubygems.rb index eaeffc7a54d0..9e90e608a50d 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -9,7 +9,7 @@ require "rbconfig" module Gem - VERSION = "4.0.0" + VERSION = "4.0.4" end require_relative "rubygems/defaults" diff --git a/lib/rubygems/commands/rebuild_command.rb b/lib/rubygems/commands/rebuild_command.rb index 77a474ef1ddf..23b9d7b3ba92 100644 --- a/lib/rubygems/commands/rebuild_command.rb +++ b/lib/rubygems/commands/rebuild_command.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require "date" require "digest" require "fileutils" require "tmpdir" diff --git a/lib/rubygems/commands/setup_command.rb b/lib/rubygems/commands/setup_command.rb index 8fcece5d5ce2..175599967cf6 100644 --- a/lib/rubygems/commands/setup_command.rb +++ b/lib/rubygems/commands/setup_command.rb @@ -402,8 +402,10 @@ def install_default_bundler_gem(bin_dir) install_dir: default_dir, wrappers: true ) - installer.install - File.delete installer.spec_file + # We need to install only executable and default spec files. + # lib/bundler.rb and lib/bundler/* are available under the site_ruby directory. + installer.extract_bin + installer.generate_bin installer.write_default_spec ensure FileUtils.rm_f built_gem diff --git a/lib/rubygems/dependency_installer.rb b/lib/rubygems/dependency_installer.rb index a6cfc3c07af7..6a6dfa5c2031 100644 --- a/lib/rubygems/dependency_installer.rb +++ b/lib/rubygems/dependency_installer.rb @@ -83,6 +83,7 @@ def initialize(options = {}) @user_install = options[:user_install] @wrappers = options[:wrappers] @build_args = options[:build_args] + @build_jobs = options[:build_jobs] @build_docs_in_background = options[:build_docs_in_background] @dir_mode = options[:dir_mode] @data_mode = options[:data_mode] @@ -154,6 +155,7 @@ def install(dep_or_name, version = Gem::Requirement.default) options = { bin_dir: @bin_dir, build_args: @build_args, + build_jobs: @build_jobs, document: @document, env_shebang: @env_shebang, force: @force, diff --git a/lib/rubygems/ext/builder.rb b/lib/rubygems/ext/builder.rb index 600a6a5ff675..350daf1e16d7 100644 --- a/lib/rubygems/ext/builder.rb +++ b/lib/rubygems/ext/builder.rb @@ -22,7 +22,7 @@ def self.class_name end def self.make(dest_path, results, make_dir = Dir.pwd, sitedir = nil, targets = ["clean", "", "install"], - target_rbconfig: Gem.target_rbconfig) + target_rbconfig: Gem.target_rbconfig, n_jobs: nil) unless File.exist? File.join(make_dir, "Makefile") # No makefile exists, nothing to do. raise NoMakefileError, "No Makefile found in #{make_dir}" @@ -34,8 +34,18 @@ def self.make(dest_path, results, make_dir = Dir.pwd, sitedir = nil, targets = [ make_program_name ||= RUBY_PLATFORM.include?("mswin") ? "nmake" : "make" make_program = shellsplit(make_program_name) + is_nmake = /\bnmake/i.match?(make_program_name) # The installation of the bundled gems is failed when DESTDIR is empty in mswin platform. - destdir = /\bnmake/i !~ make_program_name || ENV["DESTDIR"] && ENV["DESTDIR"] != "" ? format("DESTDIR=%s", ENV["DESTDIR"]) : "" + destdir = !is_nmake || ENV["DESTDIR"] && ENV["DESTDIR"] != "" ? format("DESTDIR=%s", ENV["DESTDIR"]) : "" + + # nmake doesn't support parallel build + unless is_nmake + have_make_arguments = make_program.size > 1 + + if !have_make_arguments && !ENV["MAKEFLAGS"] && n_jobs + make_program << "-j#{n_jobs}" + end + end env = [destdir] @@ -147,11 +157,12 @@ def self.shelljoin(command) # have build arguments, saved, set +build_args+ which is an ARGV-style # array. - def initialize(spec, build_args = spec.build_args, target_rbconfig = Gem.target_rbconfig) + def initialize(spec, build_args = spec.build_args, target_rbconfig = Gem.target_rbconfig, build_jobs = nil) @spec = spec @build_args = build_args @gem_dir = spec.full_gem_path @target_rbconfig = target_rbconfig + @build_jobs = build_jobs @ran_rake = false end @@ -208,7 +219,7 @@ def build_extension(extension, dest_path) # :nodoc: FileUtils.mkdir_p dest_path results = builder.build(extension, dest_path, - results, @build_args, lib_dir, extension_dir, @target_rbconfig) + results, @build_args, lib_dir, extension_dir, @target_rbconfig, n_jobs: @build_jobs) verbose { results.join("\n") } diff --git a/lib/rubygems/ext/cargo_builder.rb b/lib/rubygems/ext/cargo_builder.rb index 6bf3b405ad7c..42dca3b10254 100644 --- a/lib/rubygems/ext/cargo_builder.rb +++ b/lib/rubygems/ext/cargo_builder.rb @@ -15,7 +15,7 @@ def initialize end def build(extension, dest_path, results, args = [], lib_dir = nil, cargo_dir = Dir.pwd, - target_rbconfig = Gem.target_rbconfig) + target_rbconfig = Gem.target_rbconfig, n_jobs: nil) require "tempfile" require "fileutils" diff --git a/lib/rubygems/ext/cmake_builder.rb b/lib/rubygems/ext/cmake_builder.rb index 2915568b39d0..e660ed558b7e 100644 --- a/lib/rubygems/ext/cmake_builder.rb +++ b/lib/rubygems/ext/cmake_builder.rb @@ -37,7 +37,7 @@ def initialize end def build(extension, dest_path, results, args = [], lib_dir = nil, cmake_dir = Dir.pwd, - target_rbconfig = Gem.target_rbconfig) + target_rbconfig = Gem.target_rbconfig, n_jobs: nil) if target_rbconfig.path warn "--target-rbconfig is not yet supported for CMake extensions. Ignoring" end diff --git a/lib/rubygems/ext/configure_builder.rb b/lib/rubygems/ext/configure_builder.rb index 76c1cd8b1975..230b214b3cb5 100644 --- a/lib/rubygems/ext/configure_builder.rb +++ b/lib/rubygems/ext/configure_builder.rb @@ -8,7 +8,7 @@ class Gem::Ext::ConfigureBuilder < Gem::Ext::Builder def self.build(extension, dest_path, results, args = [], lib_dir = nil, configure_dir = Dir.pwd, - target_rbconfig = Gem.target_rbconfig) + target_rbconfig = Gem.target_rbconfig, n_jobs: nil) if target_rbconfig.path warn "--target-rbconfig is not yet supported for configure-based extensions. Ignoring" end @@ -19,7 +19,7 @@ def self.build(extension, dest_path, results, args = [], lib_dir = nil, configur run cmd, results, class_name, configure_dir end - make dest_path, results, configure_dir, target_rbconfig: target_rbconfig + make dest_path, results, configure_dir, target_rbconfig: target_rbconfig, n_jobs: n_jobs results end diff --git a/lib/rubygems/ext/ext_conf_builder.rb b/lib/rubygems/ext/ext_conf_builder.rb index 81491eac79ab..822454355d10 100644 --- a/lib/rubygems/ext/ext_conf_builder.rb +++ b/lib/rubygems/ext/ext_conf_builder.rb @@ -8,7 +8,7 @@ class Gem::Ext::ExtConfBuilder < Gem::Ext::Builder def self.build(extension, dest_path, results, args = [], lib_dir = nil, extension_dir = Dir.pwd, - target_rbconfig = Gem.target_rbconfig) + target_rbconfig = Gem.target_rbconfig, n_jobs: nil) require "fileutils" require "tempfile" @@ -40,11 +40,8 @@ def self.build(extension, dest_path, results, args = [], lib_dir = nil, extensio end ENV["DESTDIR"] = nil - unless RUBY_PLATFORM.include?("mswin") && RbConfig::CONFIG["configure_args"]&.include?("nmake") - ENV["MAKEFLAGS"] ||= "-j#{Etc.nprocessors + 1}" - end - make dest_path, results, extension_dir, tmp_dest_relative, target_rbconfig: target_rbconfig + make dest_path, results, extension_dir, tmp_dest_relative, target_rbconfig: target_rbconfig, n_jobs: n_jobs full_tmp_dest = File.join(extension_dir, tmp_dest_relative) diff --git a/lib/rubygems/ext/rake_builder.rb b/lib/rubygems/ext/rake_builder.rb index 0eac5a180ca3..d702d7f33937 100644 --- a/lib/rubygems/ext/rake_builder.rb +++ b/lib/rubygems/ext/rake_builder.rb @@ -8,7 +8,7 @@ class Gem::Ext::RakeBuilder < Gem::Ext::Builder def self.build(extension, dest_path, results, args = [], lib_dir = nil, extension_dir = Dir.pwd, - target_rbconfig = Gem.target_rbconfig) + target_rbconfig = Gem.target_rbconfig, n_jobs: nil) if target_rbconfig.path warn "--target-rbconfig is not yet supported for Rake extensions. Ignoring" end diff --git a/lib/rubygems/install_update_options.rb b/lib/rubygems/install_update_options.rb index 2d80e9978797..66cb5c049bcc 100644 --- a/lib/rubygems/install_update_options.rb +++ b/lib/rubygems/install_update_options.rb @@ -31,6 +31,15 @@ def add_install_update_options options[:bin_dir] = File.expand_path(value) end + add_option(:"Install/Update", "-j", "--build-jobs VALUE", Integer, + "Specify the number of jobs to pass to `make` when installing", + "gems with native extensions.", + "Defaults to the number of processors.", + "This option is ignored on the mswin platform or", + "if the MAKEFLAGS environment variable is set.") do |value, options| + options[:build_jobs] = value + end + add_option(:"Install/Update", "--document [TYPES]", Array, "Generate documentation for installed gems", "List the documentation types you wish to", diff --git a/lib/rubygems/installer.rb b/lib/rubygems/installer.rb index 4c3038770d48..914e41367731 100644 --- a/lib/rubygems/installer.rb +++ b/lib/rubygems/installer.rb @@ -635,6 +635,7 @@ def process_options # :nodoc: @build_root = options[:build_root] @build_args = options[:build_args] + @build_jobs = options[:build_jobs] @gem_home = @install_dir || user_install_dir || Gem.dir @@ -803,7 +804,7 @@ def windows_stub_script(bindir, bin_file_name) # configure scripts and rakefiles or mkrf_conf files. def build_extensions - builder = Gem::Ext::Builder.new spec, build_args, Gem.target_rbconfig + builder = Gem::Ext::Builder.new spec, build_args, Gem.target_rbconfig, build_jobs builder.build_extensions end @@ -941,6 +942,15 @@ def build_args end end + def build_jobs + @build_jobs ||= begin + require "etc" + Etc.nprocessors + 1 + rescue LoadError + 1 + end + end + def rb_config Gem.target_rbconfig end diff --git a/lib/rubygems/psych_tree.rb b/lib/rubygems/psych_tree.rb index 24857adb9d3c..8b4c425a3340 100644 --- a/lib/rubygems/psych_tree.rb +++ b/lib/rubygems/psych_tree.rb @@ -22,7 +22,7 @@ def visit_Hash(o) def register(target, obj) end - # This is ported over from the yaml_tree in 1.9.3 + # This is ported over from the YAMLTree implementation in Ruby 1.9.3 def format_time(time) if time.utc? time.strftime("%Y-%m-%d %H:%M:%S.%9N Z") diff --git a/lib/rubygems/remote_fetcher.rb b/lib/rubygems/remote_fetcher.rb index 01788a6a5f17..805f7aaf82ed 100644 --- a/lib/rubygems/remote_fetcher.rb +++ b/lib/rubygems/remote_fetcher.rb @@ -82,6 +82,7 @@ def initialize(proxy = nil, dns = nil, headers = {}) @proxy = proxy @pools = {} @pool_lock = Thread::Mutex.new + @pool_size = 1 @cert_files = Gem::Request.get_cert_files @headers = headers @@ -338,7 +339,7 @@ def proxy_for(proxy, uri) def pools_for(proxy) @pool_lock.synchronize do - @pools[proxy] ||= Gem::Request::ConnectionPools.new proxy, @cert_files + @pools[proxy] ||= Gem::Request::ConnectionPools.new proxy, @cert_files, @pool_size end end end diff --git a/lib/rubygems/request/connection_pools.rb b/lib/rubygems/request/connection_pools.rb index 6c1b04ab6567..01e7e0629a90 100644 --- a/lib/rubygems/request/connection_pools.rb +++ b/lib/rubygems/request/connection_pools.rb @@ -7,11 +7,12 @@ class << self attr_accessor :client end - def initialize(proxy_uri, cert_files) + def initialize(proxy_uri, cert_files, pool_size = 1) @proxy_uri = proxy_uri @cert_files = cert_files @pools = {} @pool_mutex = Thread::Mutex.new + @pool_size = pool_size end def pool_for(uri) @@ -20,9 +21,9 @@ def pool_for(uri) @pool_mutex.synchronize do @pools[key] ||= if https? uri - Gem::Request::HTTPSPool.new(http_args, @cert_files, @proxy_uri) + Gem::Request::HTTPSPool.new(http_args, @cert_files, @proxy_uri, @pool_size) else - Gem::Request::HTTPPool.new(http_args, @cert_files, @proxy_uri) + Gem::Request::HTTPPool.new(http_args, @cert_files, @proxy_uri, @pool_size) end end end diff --git a/lib/rubygems/request/http_pool.rb b/lib/rubygems/request/http_pool.rb index 52543de41fde..468502ca6b64 100644 --- a/lib/rubygems/request/http_pool.rb +++ b/lib/rubygems/request/http_pool.rb @@ -9,12 +9,14 @@ class Gem::Request::HTTPPool # :nodoc: attr_reader :cert_files, :proxy_uri - def initialize(http_args, cert_files, proxy_uri) + def initialize(http_args, cert_files, proxy_uri, pool_size) @http_args = http_args @cert_files = cert_files @proxy_uri = proxy_uri - @queue = Thread::SizedQueue.new 1 - @queue << nil + @pool_size = pool_size + + @queue = Thread::SizedQueue.new @pool_size + setup_queue end def checkout @@ -31,7 +33,8 @@ def close_all connection.finish end end - @queue.push(nil) + + setup_queue end private @@ -44,4 +47,8 @@ def setup_connection(connection) connection.start connection end + + def setup_queue + @pool_size.times { @queue.push(nil) } + end end diff --git a/lib/rubygems/request_set/lockfile.rb b/lib/rubygems/request_set/lockfile.rb index c446b3ae51ab..da6dd329bc07 100644 --- a/lib/rubygems/request_set/lockfile.rb +++ b/lib/rubygems/request_set/lockfile.rb @@ -38,7 +38,7 @@ def initialize(message, column, line, path) end ## - # Creates a new Lockfile for the given +request_set+ and +gem_deps_file+ + # Creates a new Lockfile for the given Gem::RequestSet and +gem_deps_file+ # location. def self.build(request_set, gem_deps_file, dependencies = nil) diff --git a/lib/rubygems/security/policy.rb b/lib/rubygems/security/policy.rb index 7b86ac57639e..128958ab802a 100644 --- a/lib/rubygems/security/policy.rb +++ b/lib/rubygems/security/policy.rb @@ -143,7 +143,7 @@ def check_root(chain, time) end ## - # Ensures the root of +chain+ has a trusted certificate in +trust_dir+ and + # Ensures the root of +chain+ has a trusted certificate in Gem::Security.trust_dir and # the digests of the two certificates match according to +digester+ def check_trust(chain, digester, trust_dir) diff --git a/lib/rubygems/source.rb b/lib/rubygems/source.rb index 8b031e27a8d6..f203b7312b11 100644 --- a/lib/rubygems/source.rb +++ b/lib/rubygems/source.rb @@ -102,7 +102,7 @@ def update_cache? end ## - # Fetches a specification for the given +name_tuple+. + # Fetches a specification for the given Gem::NameTuple. def fetch_spec(name_tuple) fetcher = Gem::RemoteFetcher.fetcher diff --git a/test/rubygems/helper.rb b/test/rubygems/helper.rb index 6e0be10ef530..dc40f4ecb1f8 100644 --- a/test/rubygems/helper.rb +++ b/test/rubygems/helper.rb @@ -333,6 +333,7 @@ def setup ENV["XDG_CONFIG_HOME"] = nil ENV["XDG_DATA_HOME"] = nil ENV["XDG_STATE_HOME"] = nil + ENV["MAKEFLAGS"] = nil ENV["SOURCE_DATE_EPOCH"] = nil ENV["BUNDLER_VERSION"] = nil ENV["BUNDLE_CONFIG"] = nil diff --git a/test/rubygems/test_gem_commands_install_command.rb b/test/rubygems/test_gem_commands_install_command.rb index 92933bfb77d0..d2ca933a632c 100644 --- a/test/rubygems/test_gem_commands_install_command.rb +++ b/test/rubygems/test_gem_commands_install_command.rb @@ -1584,6 +1584,38 @@ def test_suggest_update_if_enabled end end + def test_pass_down_the_job_option_to_make + gemspec = nil + + spec_fetcher do |fetcher| + fetcher.gem "a", 2 do |spec| + gemspec = spec + + extconf_path = "#{spec.gem_dir}/extconf.rb" + + write_file(extconf_path) do |io| + io.puts "require 'mkmf'" + io.puts "create_makefile '#{spec.name}'" + end + + spec.extensions = "extconf.rb" + end + end + + use_ui @ui do + assert_raise Gem::MockGemUi::SystemExitException, @ui.error do + @cmd.invoke "a", "-j4" + end + end + + gem_make_out = File.read(File.join(gemspec.extension_dir, "gem_make.out")) + if vc_windows? && nmake_found? + refute_includes(gem_make_out, " -j4") + else + assert_includes(gem_make_out, "make -j4") + end + end + def test_execute_bindir_with_nonexistent_parent_dirs spec_fetcher do |fetcher| fetcher.gem "a", 2 do |s| diff --git a/test/rubygems/test_gem_commands_setup_command.rb b/test/rubygems/test_gem_commands_setup_command.rb index dfd951268dc9..b33e05ab28dc 100644 --- a/test/rubygems/test_gem_commands_setup_command.rb +++ b/test/rubygems/test_gem_commands_setup_command.rb @@ -31,6 +31,7 @@ def setup gemspec = util_spec "bundler", "9.9.9" do |s| s.bindir = "exe" s.executables = ["bundle", "bundler"] + s.files = ["lib/bundler.rb"] end File.open "bundler/bundler.gemspec", "w" do |io| @@ -222,6 +223,9 @@ def test_install_default_bundler_gem assert_path_exist "#{Gem.dir}/gems/bundler-#{bundler_version}" assert_path_exist "#{Gem.dir}/gems/bundler-audit-1.0.0" + + assert_path_exist "#{Gem.dir}/gems/bundler-#{bundler_version}/exe/bundle" + assert_path_not_exist "#{Gem.dir}/gems/bundler-#{bundler_version}/lib/bundler.rb" end def test_install_default_bundler_gem_with_default_gems_not_installed_at_default_dir diff --git a/test/rubygems/test_gem_commands_update_command.rb b/test/rubygems/test_gem_commands_update_command.rb index 3b106e458100..5ed12ad48140 100644 --- a/test/rubygems/test_gem_commands_update_command.rb +++ b/test/rubygems/test_gem_commands_update_command.rb @@ -696,6 +696,38 @@ def test_fetch_remote_gems_prerelease assert_equal expected, @cmd.fetch_remote_gems(specs["a-1"]) end + def test_pass_down_the_job_option_to_make + gemspec = nil + + spec_fetcher do |fetcher| + fetcher.download "a", 3 do |spec| + gemspec = spec + + extconf_path = "#{spec.gem_dir}/extconf.rb" + + write_file(extconf_path) do |io| + io.puts "require 'mkmf'" + io.puts "create_makefile '#{spec.name}'" + end + + spec.extensions = "extconf.rb" + end + + fetcher.gem "a", 2 + end + + use_ui @ui do + @cmd.invoke("a", "-j2") + end + + gem_make_out = File.read(File.join(gemspec.extension_dir, "gem_make.out")) + if vc_windows? && nmake_found? + refute_includes(gem_make_out, " -j2") + else + assert_includes(gem_make_out, "make -j2") + end + end + def test_handle_options_system @cmd.handle_options %w[--system] diff --git a/test/rubygems/test_gem_install_update_options.rb b/test/rubygems/test_gem_install_update_options.rb index 8fd5d9c543ba..1e451dcb050f 100644 --- a/test/rubygems/test_gem_install_update_options.rb +++ b/test/rubygems/test_gem_install_update_options.rb @@ -202,4 +202,16 @@ def test_minimal_deps assert_equal true, @cmd.options[:minimal_deps] end + + def test_build_jobs_short_version + @cmd.handle_options %w[-j 4] + + assert_equal 4, @cmd.options[:build_jobs] + end + + def test_build_jobs_long_version + @cmd.handle_options %w[--build-jobs 4] + + assert_equal 4, @cmd.options[:build_jobs] + end end diff --git a/test/rubygems/test_gem_request_connection_pools.rb b/test/rubygems/test_gem_request_connection_pools.rb index 966447bff641..2860deabf713 100644 --- a/test/rubygems/test_gem_request_connection_pools.rb +++ b/test/rubygems/test_gem_request_connection_pools.rb @@ -148,4 +148,16 @@ def test_thread_waits_for_connection end end.join end + + def test_checkouts_multiple_connections_from_the_pool + uri = Gem::URI.parse("http://example/some_endpoint") + pools = Gem::Request::ConnectionPools.new nil, [], 2 + pool = pools.pool_for uri + + pool.checkout + + Thread.new do + assert_not_nil(pool.checkout) + end.join + end end diff --git a/tool/bundler/dev_gems.rb.lock b/tool/bundler/dev_gems.rb.lock index 4cc6655e43d6..01317be72d11 100644 --- a/tool/bundler/dev_gems.rb.lock +++ b/tool/bundler/dev_gems.rb.lock @@ -129,4 +129,4 @@ CHECKSUMS turbo_tests (2.2.5) sha256=3fa31497d12976d11ccc298add29107b92bda94a90d8a0a5783f06f05102509f BUNDLED WITH - 4.0.0 + 4.0.4 diff --git a/tool/bundler/lint_gems.rb.lock b/tool/bundler/lint_gems.rb.lock index 5e0164cc7b70..7607c762d29d 100644 --- a/tool/bundler/lint_gems.rb.lock +++ b/tool/bundler/lint_gems.rb.lock @@ -119,4 +119,4 @@ CHECKSUMS wmi-lite (1.0.7) sha256=116ef5bb470dbe60f58c2db9047af3064c16245d6562c646bc0d90877e27ddda BUNDLED WITH - 4.0.0 + 4.0.4 diff --git a/tool/bundler/release_gems.rb.lock b/tool/bundler/release_gems.rb.lock index 928a535f479a..130079b4a621 100644 --- a/tool/bundler/release_gems.rb.lock +++ b/tool/bundler/release_gems.rb.lock @@ -87,4 +87,4 @@ CHECKSUMS uri (1.1.1) sha256=379fa58d27ffb1387eaada68c749d1426738bd0f654d812fcc07e7568f5c57c6 BUNDLED WITH - 4.0.0 + 4.0.4 diff --git a/tool/bundler/rubocop_gems.rb.lock b/tool/bundler/rubocop_gems.rb.lock index 77bdc4b6f90a..a6d9fb125a5e 100644 --- a/tool/bundler/rubocop_gems.rb.lock +++ b/tool/bundler/rubocop_gems.rb.lock @@ -156,4 +156,4 @@ CHECKSUMS unicode-emoji (4.1.0) sha256=4997d2d5df1ed4252f4830a9b6e86f932e2013fbff2182a9ce9ccabda4f325a5 BUNDLED WITH - 4.0.0 + 4.0.4 diff --git a/tool/bundler/standard_gems.rb.lock b/tool/bundler/standard_gems.rb.lock index 354a57d660e9..c4e2f1e78485 100644 --- a/tool/bundler/standard_gems.rb.lock +++ b/tool/bundler/standard_gems.rb.lock @@ -176,4 +176,4 @@ CHECKSUMS unicode-emoji (4.1.0) sha256=4997d2d5df1ed4252f4830a9b6e86f932e2013fbff2182a9ce9ccabda4f325a5 BUNDLED WITH - 4.0.0 + 4.0.4 diff --git a/tool/bundler/test_gems.rb b/tool/bundler/test_gems.rb index 9776a96b90ed..ddc19e2939d4 100644 --- a/tool/bundler/test_gems.rb +++ b/tool/bundler/test_gems.rb @@ -11,6 +11,7 @@ gem "rb_sys" gem "fiddle" gem "rubygems-generate_index", "~> 1.1" +gem "concurrent-ruby" gem "psych" gem "etc", platforms: [:ruby, :windows] gem "open3" diff --git a/tool/bundler/test_gems.rb.lock b/tool/bundler/test_gems.rb.lock index bc103af86052..46c12cfb6022 100644 --- a/tool/bundler/test_gems.rb.lock +++ b/tool/bundler/test_gems.rb.lock @@ -4,6 +4,7 @@ GEM base64 (0.3.0) builder (3.3.0) compact_index (0.15.0) + concurrent-ruby (1.3.5) date (3.5.0) date (3.5.0-java) etc (1.4.6) @@ -59,6 +60,7 @@ PLATFORMS DEPENDENCIES builder (~> 3.2) compact_index (~> 0.15.0) + concurrent-ruby etc fiddle open3 @@ -75,6 +77,7 @@ CHECKSUMS base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b builder (3.3.0) sha256=497918d2f9dca528fdca4b88d84e4ef4387256d984b8154e9d5d3fe5a9c8835f compact_index (0.15.0) sha256=5c6c404afca8928a7d9f4dde9524f6e1610db17e675330803055db282da84a8b + concurrent-ruby (1.3.5) sha256=813b3e37aca6df2a21a3b9f1d497f8cbab24a2b94cab325bffe65ee0f6cbebc6 date (3.5.0) sha256=5e74fd6c04b0e65d97ad4f3bb5cb2d8efb37f386cc848f46310b4593ffc46ee5 date (3.5.0-java) sha256=d6876651299185b935e1b834a353e3a1d1db054be478967e8104e30a9a8f1127 etc (1.4.6) sha256=0f7e9e7842ea5e3c3bd9bc81746ebb8c65ea29e4c42a93520a0d638129c7de01 @@ -100,4 +103,4 @@ CHECKSUMS tilt (2.6.1) sha256=35a99bba2adf7c1e362f5b48f9b581cce4edfba98117e34696dde6d308d84770 BUNDLED WITH - 4.0.0 + 4.0.4 diff --git a/tool/bundler/vendor_gems.rb.lock b/tool/bundler/vendor_gems.rb.lock index 9c8c5217640d..8aef85935918 100644 --- a/tool/bundler/vendor_gems.rb.lock +++ b/tool/bundler/vendor_gems.rb.lock @@ -72,4 +72,4 @@ CHECKSUMS uri (1.1.1) sha256=379fa58d27ffb1387eaada68c749d1426738bd0f654d812fcc07e7568f5c57c6 BUNDLED WITH - 4.0.0 + 4.0.4 diff --git a/tool/release.rb b/tool/release.rb index c1422ccd19ad..467266afb5b0 100644 --- a/tool/release.rb +++ b/tool/release.rb @@ -144,10 +144,14 @@ def initialize(version) @previous_stable_branch = @level == :minor_or_major ? "#{segments[0]}.#{segments[1] - 1}" : @stable_branch @previous_stable_branch = "3.7" if @stable_branch == "4.0" - @previous_release_tag = if @level == :minor_or_major && !@prerelease - "v#{@previous_stable_branch}.0" + @previous_release_tag = if @level == :minor_or_major + if @prerelease + `git describe --tags --abbrev=0`.strip + else + "v#{@previous_stable_branch}.0" + end else - `git describe --tags --abbrev=0`.strip + "v#{@stable_branch}.0" end rubygems_version = segments.join(".").gsub(/([a-z])\.(\d)/i, '\1\2') @@ -299,6 +303,14 @@ def unreleased_pull_requests @unreleased_pull_requests ||= scan_unreleased_pull_requests(unreleased_pr_ids) end + def released_pull_requests + commits = `git log --oneline --grep "^Merge pull request #" #{@previous_release_tag}..#{@stable_branch}`.split("\n") + commits.filter_map do |commit| + match = commit.match(/Merge pull request #(\d+) from /) + match[1].to_i if match + end + end + def scan_unreleased_pull_requests(ids) pulls = [] ids.each do |id| @@ -309,13 +321,8 @@ def scan_unreleased_pull_requests(ids) end def unreleased_pr_ids - commits = if @level == :minor_or_major - `git log --format=%h #{@previous_release_tag}..HEAD`.split("\n") - else - `git log --format=%B #{@previous_release_tag}..HEAD`.split("\n").filter_map do |line| - line[/\(cherry picked from commit ([0-9a-f]+)\)/, 1]&.slice(0, 12) - end - end + head = @level == :minor_or_major ? "HEAD" : "master" + commits = `git log --format=%h #{@previous_release_tag}..#{head}`.split("\n") # GitHub search API has a rate limit of 30 requests per minute for authenticated users rate_limit = 28 @@ -329,7 +336,11 @@ def unreleased_pr_ids puts "Processing batch #{index + 1}/#{(commits.size / batch_size.to_f).ceil}" result = `gh search prs --repo ruby/rubygems #{batch.join(",")} --json number --jq '.[].number'`.strip unless result.empty? - result.split("\n").each {|pr_number| pr_ids.add(pr_number.to_i) } + result.split("\n").each do |pr_number| + pr_id = pr_number.to_i + next if @level == :patch && released_pull_requests.include?(pr_id) + pr_ids.add(pr_id) + end end if index != 0 && index % rate_limit == 0