Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
679219f
Merge pull request #4892 from godfat/fix-setup-custom-gemfile
hsbt Mar 25, 2026
8d16266
Print a warning for a potential confusion from the indirect dependenc…
junaruga Oct 26, 2021
09a31b3
Remove extra guard conditions to preserve existing behavior
hsbt Apr 15, 2026
41d9869
Fix Style/HashSyntax offenses in definition_spec.rb
hsbt Apr 15, 2026
0b2a6e0
Remove cygwin from WIN_PATTERNS
fd00 May 4, 2026
558d922
Deprecate parsing non-lockfile content in LockfileParser
kurotaky Apr 23, 2026
9ffc075
Fix RuboCop Layout/MultilineOperationIndentation offense
kurotaky May 1, 2026
008b410
Use Pathname#absolute?
nobu May 7, 2026
e7d1e02
Fix bundle config gemfile unset behavior
afurm Apr 30, 2026
19ecdfe
Make `bundle config get` return status 1 when the value is not set
willnet Apr 29, 2026
b80e141
Simplify the code
willnet May 5, 2026
43fab51
Return exit status 1 only when the config value is nil
willnet May 7, 2026
82a2d46
[ruby/rubygems] Allow non-zero exit status in bundle config gemfile u…
hsbt May 8, 2026
3aedfc9
Merge pull request #9538 from ruby/bundle-version-env-system
hsbt May 14, 2026
7c94b94
Merge pull request #9545 from ruby/bundle-version-lockfile-fallback
hsbt May 14, 2026
b7c7af2
Merge pull request #9544 from yahonda/fix-gh-9536
hsbt May 14, 2026
0d42fe5
Merge pull request #9492 from jneen/bugfix.error-on-missing-checksum
hsbt May 14, 2026
91cf33a
Changelog for Bundler version 4.0.12
hsbt May 20, 2026
c2e83ba
Bump Bundler version to 4.0.12
hsbt May 20, 2026
ee9e760
Changelog for Rubygems version 4.0.12
hsbt May 20, 2026
665f998
Bump Rubygems version to 4.0.12
hsbt May 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Changelog

## 4.0.12 / 2026-05-20

### Enhancements:

* Remove cygwin from WIN_PATTERNS. Pull request [#9527](https://github.com/ruby/rubygems/pull/9527) by fd00
* Installs bundler 4.0.12 as a default gem.

### Bug fixes:

* Fall back to lockfile version when `BUNDLE_VERSION` is "lockfile". Pull request [#9545](https://github.com/ruby/rubygems/pull/9545) by hsbt
* Read `BUNDLE_VERSION` env var in `BundlerVersionFinder`. Pull request [#9538](https://github.com/ruby/rubygems/pull/9538) by hsbt

## 4.0.11 / 2026-04-30

### Enhancements:
Expand Down
16 changes: 16 additions & 0 deletions bundler/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# Changelog

## 4.0.12 / 2026-05-20

### Enhancements:

* Make `bundle config get` return status 1 when the value is not set. Pull request [#9505](https://github.com/ruby/rubygems/pull/9505) by willnet
* Use Pathname#absolute?. Pull request [#9529](https://github.com/ruby/rubygems/pull/9529) by nobu
* Deprecate parsing non-lockfile content in LockfileParser. Pull request [#9502](https://github.com/ruby/rubygems/pull/9502) by kurotaky
* Print a warning for a potential confusion from the indirect dependencies. Pull request [#5029](https://github.com/ruby/rubygems/pull/5029) by junaruga
* Respect Gemfile bundler setting in `Bundler.setup`. Pull request [#4892](https://github.com/ruby/rubygems/pull/4892) by godfat

### Bug fixes:

* Gracefully handle missing checksums in Compact Index. Pull request [#9492](https://github.com/ruby/rubygems/pull/9492) by jneen
* Skip git source exclusion when lockfile cannot backfill. Pull request [#9544](https://github.com/ruby/rubygems/pull/9544) by yahonda
* Fix bundle config gemfile unset behavior. Pull request [#9514](https://github.com/ruby/rubygems/pull/9514) by afurm

## 4.0.11 / 2026-04-30

### Enhancements:
Expand Down
10 changes: 10 additions & 0 deletions bundler/lib/bundler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ def setup(*groups)
# Return if all groups are already loaded
return @setup if defined?(@setup) && @setup

configure_custom_gemfile
definition.validate_runtime!

SharedHelpers.print_major_deprecations!
Expand Down Expand Up @@ -586,6 +587,15 @@ def configure_gem_home_and_path(path = bundle_path)
Bundler.rubygems.clear_paths
end

def configure_custom_gemfile(custom_gemfile = nil)
custom_gemfile ||= Bundler.settings[:gemfile]

if custom_gemfile && !custom_gemfile.empty?
Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", File.expand_path(custom_gemfile)
reset_settings_and_root!
end
end

def self_manager
@self_manager ||= begin
require_relative "bundler/self_manager"
Expand Down
24 changes: 12 additions & 12 deletions bundler/lib/bundler/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,18 +61,18 @@ def initialize(*args)

current_cmd = args.last[:current_command].name

custom_gemfile = options[:gemfile] || Bundler.settings[:gemfile]
if custom_gemfile && !custom_gemfile.empty?
Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", File.expand_path(custom_gemfile)
reset_settings = true
end

# lock --lockfile works differently than install --lockfile
unless current_cmd == "lock"
custom_lockfile = options[:lockfile] || ENV["BUNDLE_LOCKFILE"] || Bundler.settings[:lockfile]
if custom_lockfile && !custom_lockfile.empty?
Bundler::SharedHelpers.set_env "BUNDLE_LOCKFILE", File.expand_path(custom_lockfile)
reset_settings = true
# `bundle config` manages stored settings, so avoid promoting settings
# like `gemfile` or `lockfile` to environment variables before it runs.
unless current_cmd == "config"
Bundler.configure_custom_gemfile(options[:gemfile])

# lock --lockfile works differently than install --lockfile
unless current_cmd == "lock"
custom_lockfile = options[:lockfile] || ENV["BUNDLE_LOCKFILE"] || Bundler.settings[:lockfile]
if custom_lockfile && !custom_lockfile.empty?
Bundler::SharedHelpers.set_env "BUNDLE_LOCKFILE", File.expand_path(custom_lockfile)
reset_settings = true
end
end
end

Expand Down
11 changes: 8 additions & 3 deletions bundler/lib/bundler/cli/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -87,16 +87,21 @@ def run

if value.nil?
warn_unused_scope "Ignoring --#{scope} since no value to set was given"
current_value = Bundler.settings[name]

if options[:parseable]
if value = Bundler.settings[name]
Bundler.ui.info("#{name}=#{value}")
end
return
else
confirm(name)
end

confirm(name)
return
if current_value.nil?
exit 1
else
return
end
end

Bundler.ui.info(message) if message
Expand Down
5 changes: 4 additions & 1 deletion bundler/lib/bundler/compact_index_client/parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,10 @@ def gem_parser
# This method gets called at least once for every gem when parsing versions.
def parse_version_checksum(line, checksums)
return unless (name_end = line.index(" ")) # Artifactory bug causes blank lines in artifactor index files
return unless (checksum_start = line.index(" ", name_end + 1) + 1)
checksum_start = line.index(" ", name_end + 1)
return unless checksum_start
checksum_start += 1
Comment on lines 72 to +76

checksum_end = line.size - checksum_start

line.freeze # allows slicing into the string to not allocate a copy of the line
Expand Down
30 changes: 26 additions & 4 deletions bundler/lib/bundler/definition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -783,7 +783,25 @@ def start_resolution
end

def precompute_source_requirements_for_indirect_dependencies?
sources.non_global_rubygems_sources.all?(&:dependency_api_available?)
if sources.non_global_rubygems_sources.all?(&:dependency_api_available?)
true
else
non_dependency_api_warning
false
end
end

def non_dependency_api_warning
non_api_sources = sources.non_global_rubygems_sources.reject(&:dependency_api_available?)
non_api_source_names = non_api_sources.map {|d| " * #{d}" }.join("\n")

msg = String.new
msg << "Your Gemfile contains scoped sources that don't implement a dependency API, namely:\n\n"
msg << non_api_source_names
msg << "\n\nUsing the above gem servers may result in installing unexpected gems. " \
"To resolve this warning, make sure you use gem servers that implement dependency APIs, " \
"such as gemstash or geminabox gem servers."
Bundler.ui.warn msg
end

def current_platform_locked?
Expand Down Expand Up @@ -1159,16 +1177,20 @@ def excluded_git_sources
def find_source_requirements
preload_git_sources

# Only safe to exclude when locked_requirements (merged below) backfills the gap.
nothing_changed = nothing_changed?
excluded = nothing_changed ? excluded_git_sources : []

# Record the specs available in each gem's source, so that those
# specs will be available later when the resolver knows where to
# look for that gemspec (or its dependencies)
source_requirements = if precompute_source_requirements_for_indirect_dependencies?
all_requirements = source_map.all_requirements(excluded_git_sources)
all_requirements = source_map.all_requirements(excluded)
{ default: default_source }.merge(all_requirements)
else
{ default: Source::RubygemsAggregate.new(sources, source_map, excluded_git_sources) }.merge(source_map.direct_requirements)
{ default: Source::RubygemsAggregate.new(sources, source_map, excluded) }.merge(source_map.direct_requirements)
end
source_requirements.merge!(source_map.locked_requirements) if nothing_changed?
source_requirements.merge!(source_map.locked_requirements) if nothing_changed
metadata_dependencies.each do |dep|
source_requirements[dep.name] = sources.metadata_source
end
Expand Down
15 changes: 15 additions & 0 deletions bundler/lib/bundler/lockfile_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,17 @@ def initialize(lockfile, strict: false)
"Run `git checkout HEAD -- #{@lockfile_path}` first to get a clean lock."
end

@valid = lockfile.strip.empty? ||
lockfile.split(/(?:\r?\n)+/).any? {|l| KNOWN_SECTIONS.include?(l) }

unless @valid
SharedHelpers.feature_deprecated!(
"Your #{@lockfile_path} does not appear to be a valid lockfile. " \
"Run `rm #{@lockfile_path}` and then `bundle install` to generate a new lockfile. " \
"This will raise a LockfileError in a future version of Bundler."
)
end

lockfile.split(/((?:\r?\n)+)/) do |line|
# split alternates between the line and the following whitespace
next @pos.advance!(line) if line.match?(/^\s*$/)
Expand Down Expand Up @@ -164,6 +175,10 @@ def may_include_redundant_platform_specific_gems?
bundler_version.nil? || bundler_version < Gem::Version.new("1.16.2")
end

def valid?
@valid
end

private

TYPES = {
Expand Down
5 changes: 3 additions & 2 deletions bundler/lib/bundler/source/path.rb
Original file line number Diff line number Diff line change
Expand Up @@ -220,10 +220,11 @@ def generate_bin(spec, options = {})
# Some gem authors put absolute paths in their gemspec
# and we have to save them from themselves
spec.files = spec.files.filter_map do |path|
next path unless /\A#{Pathname::SEPARATOR_PAT}/o.match?(path)
pathname = Pathname.new(path)
next path unless pathname.absolute?
next if File.directory?(path)
begin
Pathname.new(path).relative_path_from(gem_dir).to_s
pathname.relative_path_from(gem_dir).to_s
rescue ArgumentError
path
end
Expand Down
2 changes: 1 addition & 1 deletion bundler/lib/bundler/version.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: false

module Bundler
VERSION = "4.0.11".freeze
VERSION = "4.0.12".freeze

def self.bundler_major_version
@bundler_major_version ||= gem_version.segments.first
Expand Down
4 changes: 3 additions & 1 deletion bundler/spec/bundler/cli_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -251,8 +251,10 @@ def out_with_macos_man_workaround

context "running a parseable command" do
it "prints no warning" do
bundle "config set foo value", env: { "BUNDLER_VERSION" => bundler_version }
bundle "config get --parseable foo", env: { "BUNDLER_VERSION" => bundler_version }
expect(stdboth).to eq ""
expect(out).to eq "foo=value"
expect(err).to eq ""

bundle "platform --ruby", env: { "BUNDLER_VERSION" => bundler_version }, raise_on_error: false
expect(stdboth).to eq "Could not locate Gemfile"
Expand Down
12 changes: 12 additions & 0 deletions bundler/spec/bundler/compact_index_client/parser_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -233,5 +233,17 @@ def set_info_data(name, value)
VERSIONS
expect(parser.info("a")).to eq(a_result)
end

it "handles lines without a checksum" do
compact_index.versions = <<~VERSIONS
created_at: 2024-05-01T00:00:04Z
---
a 1.0.0,1.0.1,1.1.0 aaa111
b 2.0.0,2.0.0-java
c 3.0.0,3.0.3,3.3.3 ccc333
VERSIONS

expect(parser.info("a")).to eq(a_result)
end
end
end
51 changes: 51 additions & 0 deletions bundler/spec/bundler/definition_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,57 @@
end
end

describe "#precompute_source_requirements_for_indirect_dependencies?" do
before do
allow(Bundler::SharedHelpers).to receive(:find_gemfile) { Pathname.new("Gemfile") }
end

let(:sources) { Bundler::SourceList.new }
subject { Bundler::Definition.new(nil, [], sources, []) }

before do
allow(sources).to receive(:non_global_rubygems_sources).and_return(non_global_rubygems_sources)
end

context "when all the scoped sources implement a dependency API" do
let(:non_global_rubygems_sources) do
[
double("non-global-source-0", "dependency_api_available?":true, to_s:"a"),
double("non-global-source-1", "dependency_api_available?":true, to_s:"b"),
]
end

it "returns true without warning" do
expect(subject).not_to receive(:non_dependency_api_warning)

expect(subject.send(:precompute_source_requirements_for_indirect_dependencies?)).to be_truthy
end
end

context "when some scoped sources do not implement a dependency API" do
let(:non_global_rubygems_sources) do
[
double("non-global-source-0", "dependency_api_available?":true, to_s:"a"),
double("non-global-source-1", "dependency_api_available?":false, to_s:"b"),
double("non-global-source-2", "dependency_api_available?":false, to_s:"c"),
]
end

it "returns false and warns about the non-API sources" do
expect(Bundler.ui).to receive(:warn).with(<<-W.strip)
Your Gemfile contains scoped sources that don't implement a dependency API, namely:

* b
* c

Using the above gem servers may result in installing unexpected gems. To resolve this warning, make sure you use gem servers that implement dependency APIs, such as gemstash or geminabox gem servers.
W

expect(subject.send(:precompute_source_requirements_for_indirect_dependencies?)).to be_falsy
end
end
end

def mock_source_list
Class.new do
def all_sources
Expand Down
61 changes: 61 additions & 0 deletions bundler/spec/bundler/lockfile_parser_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@

shared_examples_for "parsing" do
it "parses correctly" do
expect(subject.valid?).to be(true)
expect(subject.sources).to eq sources
expect(subject.dependencies).to eq dependencies
expect(subject.specs).to eq specs
Expand Down Expand Up @@ -191,6 +192,66 @@
include_examples "parsing"
end

context "when the content does not contain any recognized lockfile sections" do
let(:lockfile_contents) { "hello world\nlorem ipsum\n" }

it "does not raise, is not valid, and deprecates" do
expect(Bundler::SharedHelpers).to receive(:feature_deprecated!).with(
/does not appear to be a valid lockfile.*future version of Bundler/m
)
parser = described_class.new(lockfile_contents)
expect(parser.valid?).to be(false)
expect(parser.specs).to eq([])
expect(parser.dependencies).to eq({})
end

it "does not raise when strict: true, and still deprecates" do
expect(Bundler::SharedHelpers).to receive(:feature_deprecated!).with(
/does not appear to be a valid lockfile.*future version of Bundler/m
)
parser = described_class.new(lockfile_contents, strict: true)
expect(parser.valid?).to be(false)
expect(parser.specs).to eq([])
expect(parser.dependencies).to eq({})
end
end

context "when the content looks like a Gemfile DSL" do
let(:lockfile_contents) { <<~G }
source "https://rubygems.org"
gem "rake"
G

it "does not raise, is not valid, and deprecates" do
expect(Bundler::SharedHelpers).to receive(:feature_deprecated!).with(
/does not appear to be a valid lockfile.*future version of Bundler/m
)
parser = described_class.new(lockfile_contents)
expect(parser.valid?).to be(false)
expect(parser.specs).to eq([])
expect(parser.dependencies).to eq({})
end

it "does not raise when strict: true, and still deprecates" do
expect(Bundler::SharedHelpers).to receive(:feature_deprecated!).with(
/does not appear to be a valid lockfile.*future version of Bundler/m
)
parser = described_class.new(lockfile_contents, strict: true)
expect(parser.valid?).to be(false)
expect(parser.specs).to eq([])
expect(parser.dependencies).to eq({})
end
end

context "when the content is empty" do
let(:lockfile_contents) { "" }

it "does not raise and is valid" do
expect { subject }.not_to raise_error
expect(subject.valid?).to be(true)
end
end

context "when CHECKSUMS has duplicate checksums in the lockfile that don't match" do
let(:bad_checksum) { "sha256=c0ffee11c0ffee11c0ffee11c0ffee11c0ffee11c0ffee11c0ffee11c0ffee11" }
let(:lockfile_contents) { super().split(/(?<=CHECKSUMS\n)/m).insert(1, " rake (10.3.2) #{bad_checksum}\n").join }
Expand Down
Loading
Loading