Skip to content

Replace Molinillo with PubGrub for dependency resolution#9402

Open
mlarraz wants to merge 26 commits intoruby:masterfrom
mlarraz:replace-molinillo-with-pubgrub
Open

Replace Molinillo with PubGrub for dependency resolution#9402
mlarraz wants to merge 26 commits intoruby:masterfrom
mlarraz:replace-molinillo-with-pubgrub

Conversation

@mlarraz
Copy link
Copy Markdown
Contributor

@mlarraz mlarraz commented Mar 16, 2026

What was the end-user or developer problem that led to this PR?

My attempt at fixing #9365.

What is your fix for the problem, implemented in this PR?

  • Vendor PubGrub from Bundler's existing copy
  • Re-namespace it under Gem::PubGrub
  • Rewrite Gem::Resolver to implement PubGrub's source interface
  • Delete Molinillo

We should probably to de-duplicate the copies at some point but I figured that can wait.

Gem::Resolver now implements PubGrub's source interface (all_versions_for, versions_for, incompatibilities_for, no_versions_incompatibility_for) instead of Molinillo's interfaces.

Some notes:

  • Version preference: all_versions_for returns highest-first, with already-installed versions preferred to avoid unnecessary upgrades. The skip_gems mechanism continues to work for conservative updates.
  • Prereleases: Excluded from the preference index by default, but still available to the solver when a constraint requires them (e.g., = 2.a).
  • Platform specificity: spec_for prefers InstalledSpecification when available, then selects by
    Gem::Platform.platform_specificity_match
  • Error handling: A pre-check still raises UnsatisfiableDependencyError for root deps with zero matches. Everything else flows through PubGrub's SolveFailure -> DependencyResolutionError with its full explanation chain.
  • Compatibility fix for InstallerSet: PubGrub needs all available versions upfront, but Source::Local#find_gem only returns the highest matching version. Added Source::Local#find_all_gems (returns all matches) and updated InstallerSet#find_all to use it.

Make sure the following tasks are checked

@mlarraz mlarraz force-pushed the replace-molinillo-with-pubgrub branch from e8b7fb1 to 32a966d Compare March 16, 2026 23:30
@colby-swandale
Copy link
Copy Markdown
Member

I appreciate the effort here, but I should flag that I was already actively working on this. I opened the issue, I'm assigned to it, and I've had a WIP implementation going for several weeks now.

That said, I don't want to be a gatekeeper, but I'm not exactly sure what the path forward here is.

@mlarraz
Copy link
Copy Markdown
Contributor Author

mlarraz commented Mar 17, 2026

I appreciate the effort here, but I should flag that I was already actively working on this. I opened the issue, I'm assigned to it, and I've had a WIP implementation going for several weeks now.

That said, I don't want to be a gatekeeper, but I'm not exactly sure what the path forward here is.

Makes sense. Not sure what your version looks like but feel free to use any ideas from mine if it helps. I would just like the change to get done.

@hsbt
Copy link
Copy Markdown
Member

hsbt commented Apr 1, 2026

@colby-swandale Could you open a pull request with your current WIP branch? Even if it's still in progress, having it visible will help us coordinate.

Once your PR is up, we can compare both implementations and incorporate any useful differences from this PR into yours. The goal is to combine the best of both approaches into the final PubGrub integration.

mlarraz and others added 7 commits April 12, 2026 12:57
Molinillo (a backtracking resolver) is replaced by PubGrub (a CDCL
SAT-based solver) which provides better conflict explanations, smarter
backjumping, and provable completeness. PubGrub is already used by
Bundler; this vendors the same library re-namespaced under Gem::PubGrub.

Key changes:
- Vendor PubGrub from bundler/lib/bundler/vendor/pub_grub/, re-namespaced
  Bundler::PubGrub -> Gem::PubGrub
- Rewrite Gem::Resolver to implement PubGrub's source interface
  (all_versions_for, versions_for, incompatibilities_for, etc.)
- Add Gem::Resolver::PubGrubFailure for error reporting via PubGrub's
  superior conflict explanation format
- Add Source::Local#find_all_gems to expose all local gem versions
  (PubGrub needs complete version information upfront)
- Prefer installed specs in version ordering to avoid unnecessary upgrades
- Delete Molinillo (21 files, ~2400 lines) and resolver/stats.rb

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Preserve the original dependency requirement from root deps when
building ActivationRequests, so lockfiles correctly record constraints
like "a (>= 1)" instead of bare "a".

Update the orphaned dependencies test: PubGrub correctly backtracks
from b-2 (missing c-2) to b-1 (has c-1), finding a valid solution
that Molinillo's simpler backtracking missed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Create pub_grub-rubygems.patch for custom Logger and Strategy changes
- Remove molinillo from vendor_gems.rb and its lockfile
- Remove molinillo-master.patch

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@colby-swandale colby-swandale force-pushed the replace-molinillo-with-pubgrub branch from 666a38a to 38b0aa6 Compare April 12, 2026 02:57
@colby-swandale colby-swandale marked this pull request as draft April 13, 2026 02:54
@colby-swandale
Copy link
Copy Markdown
Member

colby-swandale commented Apr 13, 2026

Lots of progress over the weekend. Performance is noticeably much much improved, which is encouraging.

That said, there's still a fair amount of resolver behaviour to verify and work through before this is ready. Will block out some more time this week before RubyKaigi to get this ready for review.

@colby-swandale colby-swandale marked this pull request as ready for review April 20, 2026 13:55
Copilot AI review requested due to automatic review settings April 20, 2026 13:55
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR migrates RubyGems’ dependency resolution from Molinillo to PubGrub to align with Bundler’s solver and support the ongoing unification work discussed in #9365.

Changes:

  • Vendors PubGrub under Gem::PubGrub, wires Gem::Resolver to PubGrub’s source interface, and removes vendored Molinillo.
  • Updates local gem sourcing to support returning all matching local gems (find_all_gems) so PubGrub can see the full version universe.
  • Adjusts RubyGems error handling/tests to reflect PubGrub’s failure explanations (SolveFailureDependencyResolutionError).

Reviewed changes

Copilot reviewed 27 out of 67 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tool/bundler/vendor_gems.rb.lock Removes Molinillo from the vendoring lockfile.
tool/bundler/vendor_gems.rb Removes Molinillo from Bundler’s vendoring Gemfile.
tool/automatiek/vendor.rb Switches automatiek vendoring config from Molinillo to PubGrub.
tool/automatiek/pub_grub-rubygems.patch Applies RubyGems-specific PubGrub tweaks (logger + strategy nil-index handling).
tool/automatiek/molinillo-master.patch Removes now-obsolete Molinillo patch.
test/rubygems/test_gem_source_local.rb Adds tests for Source::Local#find_all_gems and prerelease behavior.
test/rubygems/test_gem_resolver_strategy.rb Adds focused tests for strategy selection + caching behavior.
test/rubygems/test_gem_resolver_conflict.rb Removes Molinillo-era conflict explanation tests.
test/rubygems/test_gem_resolver.rb Updates resolver tests for new error shapes/messages; adds PubGrub-related scenarios.
test/rubygems/test_gem_impossible_dependencies_error.rb Removes tests for removed ImpossibleDependenciesError.
test/rubygems/test_gem_dependency_resolution_error.rb Updates DependencyResolutionError expectations for PubGrub explanation-based errors.
test/rubygems/test_gem_dependency_installer.rb Updates installer error expectations and adds --force/unsatisfiable dep test.
test/rubygems/test_gem_commands_install_command.rb Loosens error matching to account for new error text.
test/rubygems/test_gem_commands_exec_command.rb Adds exec-command test for dependency resolution failures.
test/rubygems/test_gem.rb Updates activation test to reflect PubGrub backtracking behavior.
lib/rubygems/vendored_pub_grub.rb Adds a new vendored entrypoint for PubGrub.
lib/rubygems/vendored_molinillo.rb Removes Molinillo vendored entrypoint.
lib/rubygems/vendor/pub_grub/lib/pub_grub.rb Adds vendored PubGrub entrypoint (namespaced as Gem::PubGrub).
lib/rubygems/vendor/pub_grub/lib/pub_grub/assignment.rb Vendored PubGrub solver support code.
lib/rubygems/vendor/pub_grub/lib/pub_grub/basic_package_source.rb Vendored PubGrub package source base implementation.
lib/rubygems/vendor/pub_grub/lib/pub_grub/failure_writer.rb Vendored PubGrub failure explanation writer.
lib/rubygems/vendor/pub_grub/lib/pub_grub/incompatibility.rb Vendored PubGrub incompatibility representation.
lib/rubygems/vendor/pub_grub/lib/pub_grub/package.rb Vendored PubGrub package abstraction.
lib/rubygems/vendor/pub_grub/lib/pub_grub/partial_solution.rb Vendored PubGrub partial solution state.
lib/rubygems/vendor/pub_grub/lib/pub_grub/rubygems.rb RubyGems requirement → PubGrub range/constraint conversion helpers.
lib/rubygems/vendor/pub_grub/lib/pub_grub/solve_failure.rb Vendored PubGrub SolveFailure error type.
lib/rubygems/vendor/pub_grub/lib/pub_grub/static_package_source.rb Vendored PubGrub static source (mostly for tests/examples).
lib/rubygems/vendor/pub_grub/lib/pub_grub/strategy.rb Vendored PubGrub default strategy implementation.
lib/rubygems/vendor/pub_grub/lib/pub_grub/term.rb Vendored PubGrub term implementation.
lib/rubygems/vendor/pub_grub/lib/pub_grub/version.rb Vendored PubGrub version constant.
lib/rubygems/vendor/pub_grub/lib/pub_grub/version_constraint.rb Vendored PubGrub version constraint implementation.
lib/rubygems/vendor/pub_grub/lib/pub_grub/version_range.rb Vendored PubGrub version range implementation.
lib/rubygems/vendor/pub_grub/lib/pub_grub/version_solver.rb Vendored PubGrub version solver implementation.
lib/rubygems/vendor/pub_grub/lib/pub_grub/version_union.rb Vendored PubGrub union-of-ranges implementation.
lib/rubygems/vendor/pub_grub/LICENSE.txt Adds PubGrub license.
lib/rubygems/source/local.rb Adds find_all_gems; rewires find_gem to use it.
lib/rubygems/resolver/strategy.rb Adds a RubyGems-specific PubGrub strategy with caching.
lib/rubygems/resolver/stats.rb Removes Molinillo-era resolver stats.
lib/rubygems/resolver/installer_set.rb Switches local lookup to find_all_gems so all versions are visible.
lib/rubygems/resolver/incompatibility.rb Adds RubyGems wrapper for PubGrub incompatibilities with extended explanation.
lib/rubygems/resolver/conflict.rb Removes Molinillo-era conflict type.
lib/rubygems/resolver.rb Replaces Molinillo resolver with PubGrub integration and error translation.
lib/rubygems/exceptions.rb Reworks DependencyResolutionError; removes ImpossibleDependenciesError.
lib/rubygems/commands/install_command.rb Adds handling for DependencyResolutionError exit code behavior.
lib/rubygems/commands/exec_command.rb Adds handling for DependencyResolutionError exit code behavior.
Manifest.txt Updates shipped file list: removes Molinillo, adds PubGrub + new strategy/incompatibility files.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/rubygems/resolver.rb Outdated
Comment thread lib/rubygems/resolver/strategy.rb
Comment thread lib/rubygems/resolver.rb Outdated
@colby-swandale
Copy link
Copy Markdown
Member

I ran the top 1000 gems through both Molinillo & PubGrub. 996 of 1000 resolved identically.

The 3 edge cases

Both resolvers produce resolutions that satisfy the declared constraints; they just pick different valid solutions in their graphs.

Gem Molinillo picks PubGrub picks
redis-rails Rails 7.0 Rails 7.2
webdrivers selenium-webdriver 4.10.0, rubyzip 2.4.1 selenium-webdriver 4.1.0, rubyzip 3.2.2
ohai chef-config 19.2.12 chef-config 18.9.4

The one that errors

ruby_dep fails to resolve on both parsers due to ruby ~> 2.x requirement. This is essentially a broken gem.

Performance

Molinillo PubGrub Ratio
p50 1.14s 1.10s 0.97x
p95 5.15s 6.86s 1.33x
Total 1772s 2372s 1.34x

PubGrub is at parity at the median and ~30% slower at the tail and in aggregate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants