Bundler 2.4 learns from dependency conflicts
PubGrub changes how Bundler searches for compatible versions and explains failure, while Rust extension templates connect gem scaffolding to Cargo.
An unsatisfiable Gemfile needs an explanation that identifies which requirements cannot coexist. Repeating a list of dependency conflicts can leave the developer uncertain about which constraint to change. A resolver also wastes time if it repeatedly reaches the same incompatible choices through different search paths.
The January 31 Bundler 2.4 overview describes the switch from Molinillo to PubGrub. The new resolver learns from conflicts during version selection and produces explanations that connect incompatible requirements to the final failure. Natalie Weizenbaum designed PubGrub, and John Hawthorn ported it to Ruby.
I came to Ruby from Perl and still find its object model and ordinary code approachable. Dependency management should support that readability. A failed resolution that explains its reasoning is more useful than one that leaves the application author to reconstruct the entire graph.
Search needs to remember failed combinations
The overview describes a difficult case for the old resolver. Molinillo could try choices, backtrack, and reach the same conflicts repeatedly. Large search spaces made those repetitions expensive even though the individual version requirements were straightforward.
PubGrub uses conflict-driven clause learning to avoid redoing that work. A discovered conflict becomes information the solver can use in later decisions. The practical goal is to exclude incompatible combinations without revisiting them through every possible sequence of choices.
The Bundler team says the new resolver is usually faster. That is the team's assessment in the overview, not a universal timing guarantee for every Gemfile. A small graph with an obvious solution and a large graph with interacting constraints present different kinds of work.
The more broadly useful gain may be the failure explanation. When no combination satisfies all requirements, more search cannot produce a valid set. The resolver needs to explain why the requested solution does not exist.
A contradiction has a dependency path
The overview's test-suite example uses two packages with incompatible requirements on a third. Every version of one requires that shared dependency below version one. Every version of the other requires it at version two or above.
There is no shared version satisfying both ranges. Since the Gemfile requires both packages, the contradiction reaches the root requirements. The explanation follows that chain instead of presenting each conflict as though it were unrelated.
A simplified set of inequalities makes the problem visible.
c requires a < 1
b requires a >= 2
Gemfile requires b and c
The output matters because it identifies the joint requirement that must change. Updating the shared dependency alone cannot solve contradictory bounds. One of the requiring packages, its allowed version range, or the root selection has to change.
The overview contrasts this with a longer historical Rails-related failure containing multiple conflicts. A large amount of output can be accurate while still failing to isolate the decisive reason. PubGrub's explanation attempts to turn the learned incompatibilities into a readable argument.
Complex graphs can still require complex explanations. The announcement does not claim that every failed upgrade becomes trivial. It does claim that the messages should identify causes in a form users can follow.
Compatible solutions need not be identical
The Bundler team attempted to preserve behavior during the migration, but warns that a resolver can select a different valid solution. Dependency constraints often admit several combinations. Replacing the search algorithm can change which compatible set it finds.
That is a useful distinction from a compatibility failure. A changed answer is not necessarily an invalid answer when the problem permits multiple solutions. The relevant question is whether the selected versions satisfy the declared requirements and the application's expectations.
This makes the Gemfile's constraints consequential. A resolver operates on the permitted ranges, not on an unstated preference for one historical combination. Clear failure explanations improve the conversation when constraints conflict, but they cannot supply requirements the application never declared.
I prefer this explicitness in Ruby tooling. The language's readable syntax is less valuable if routine maintenance requires interpreting unexplained package-selection behavior. Showing the contradiction makes the dependency declaration itself something a developer can revise with a specific reason.
Rust extension scaffolding addresses a different boundary
Bundler 2.4 also adds a Rust extension option to the gem generator. The separate Rust gem skeleton guide documents the command and generated structure, recommending RubyGems 3.4.6 or newer for the best experience.
bundle gem --ext=rust hello_rust
The generated files include a top-level Cargo manifest and an extension-specific manifest under the ext directory. The top-level file points to the nested project, so Cargo commands and development tools can locate the Rust code from the gem's root.
An extconf.rb file configures compilation from Ruby's extension-building workflow using rb_sys. The generated Rust example uses Magnus bindings to expose a method through a Ruby module. The guide then builds the extension with the generated rake task after installing Ruby dependencies.
This changes the setup work required to start an extension. It does not remove the need for a Rust compiler or make the generated gemspec ready for publication without editing. The guide explicitly warns that TODO values may need replacement before bundle install succeeds.
Other maintenance changes reduce hidden work
Git-sourced dependencies receive a more limited cloning strategy. The overview uses the Rails repository to illustrate the cost of cloning an entire large history when Bundler needs less. Reducing the fetched material addresses disk usage and time independently of dependency solving.
The new prerelease option for bundle update and bundle lock also makes a selection policy explicit. A developer can opt into prereleases for selected gems without rewriting requirements merely to express that temporary intent.
Bundler 2.4 drops support for Ruby 2.3, 2.4, and 2.5, and RubyGems 2.5, 2.6, and 2.7. It also removes automatic acquisition of sudo privileges when installation permissions are insufficient. Older RubyGems versions may not automatically select a Bundler compatible with the installed Ruby, as the overview warns. Those changes affect the environment in which dependency installation runs. Together with the resolver work, they give a clearer account of what Bundler selects, what it needs to fetch, and which permissions the caller must already provide.