When converging locked specifications to select the ones that should be
preserved while resolving, we can avoid having to do a second pass to
ignore the ones that have been explicitly unlocked.
411742703e
When resolving on truffleruby, and multiple platforms are included in
the lockfile, Bundler will not respect existing platforms, but always
force ruby variants. That means removal of existing version specific
variants, introducing lockfile churn between implementations.
To prevent this, we introduce the distinction between
`Dependency#force_ruby_platform`, only settable via Gemfile, and
`Dependency#default_force_ruby_platform`, which is always true on
truffleruby for certain dependency names. This way, when resolving
lockfile gems for other platforms on truffleruby, we keep platform
specific variants in the lockfile.
However, that introduces the problem that if only platform specific
variants are locked in the lockfile, Bundler won't be able to
materialize on truffleruby because the generic variant will be missing.
To fix this additional problem, we make sure the generic "ruby" platform
is always added when resolving on truffleruby.
* `bundle exec` assigns `Gem::Specification.all` to the set of specs
known to Bundler (a `Bundler::SpecSet`).
* `gem uninstall` recently started calling `#delete` on the set of specs
stored in `Gem::Specification#all`. This, in RubyGems, is just an
array of specs, so has a `#delete` method that receives a single
element.
* However, at some point I added a `SpecSet#delete` method that takes an
array of specs, breaking the "Array-like" contract and making `gem
uninstall` break when run in a `bundle exec` context.
The fix is to make `Bundler::SpecSet#delete` handle being given a single
spec.
e3acb7b01d
Instead of having to do a complete pass after resolve.
To do this, we add to the ruby group all the platform specs with the
same dependencies as the ruby specs.
e50415f2a6
Even if all gems are properly installed and no resolve is needed, we
recently started always reading all packages in `vendor/cache` and
extracting specifications from them.
This commit fixes the problem by longer making considering cached specs
the default and only enable them when a resolve is actually needed.
edeb2c42bf
If `cache_all_platforms` setting is enabled, the secondary source was
no longer considering cached gems.
That means that if the remote secondary source has removed its gems,
then this was now resulting in an error while before the previously
cached gem from the source would still be used.
This commit restores previous behavior.
2d2cd00255
This case is for not locking things like `arm-darwin-23` when the
lockfile already includes `arm-darwin`, so that we don't infinitely keep
redundant versioned platforms in the lockfile when not necessary.
We detect this with `Gem::Platform#===`. For example,
`Gem::Platform.new("arm-darwin-23") === Gem::Platform.new("arm-darwin")`
but they're not `==`.
However, in the case of `-musl` vs `-gnu`, those act as the platform
"version", but `===` is not commutative for them. This is explained in
`===` docs.
We only want to exclude the local platform in situations when
`Gem::Platform#===` is actually commutative.
8099c4face
If two platform specific variants have different dependencies, then
resolution may fallback to the non platform specific variant. However,
the platform specific variants that have the same dependencies as the
non specific one can still be kept.
Do a pass to complete those after resolution.
If a platform specific variant would not match the current Ruby, we would still be
considering it compatible with the initial resolution and adding its
platform to the lockfile, but we would later fail to materialize it for
installation due to not really being compatible.
Fix is to only add platforms for variants that are also compatible with
current Ruby and RubyGems versions.
75d1290843
Since we started locking the specific platform in the lockfile, that has
created an annoying situation for users that don't develop on Linux.
They will create a lockfile on their machines, locking their local
platform, for example, darwin. But then that lockfile won't work
automatically when deploying to Heroku for example, because the lockfile
is frozen and the Linux platform is not included.
There's the chance though that resolving against two platforms (Linux +
the local platform) won't succeed while resolving for just the current
platform will. So, instead, we check other platform specific variants
available for the resolution we initially found, and lock those
platforms and specs too if they satisfy the resolution.
This is only done when generating new lockfiles from scratch, existing
lockfiles should keep working as before, and it's only done for "ruby
platforms", i.e., not Java or Windows which have their own complexities,
and so are excluded.
With this change, we expect that MacOS users can bundle locally and
deploy to Heroku without needing to do anything special.
5f24f06bc5
While working on locking multiple platforms by default, I got an
infinite resolution loop in one of our resolver specs.
The culprit ended up being that when dealing with lockfile specs with
incomplete dependencies (spec appears in lockfile, but its dependencies
don't), those specs were not being properly expired and that tripped up
resolution.
The issue for some reason only manifests when dealing with multiple
lockfile platforms, that's why it only manifested when working on
locking multiple platforms by default.
4ca72913bb
Recent bugs fixed made me realize we were relying on state too much
here. We only need to keep incomplete specs to be able to expire them
and retry resolution without them locked. If we use a separate class, we
can do that more transparently and handle them just like we handle
"missing specs".
Following up on https://github.com/rubygems/rubygems/pull/6355, which
turned a crash into a nicer error message, this commit auto-heals the
corrupt lockfile instead.
In this particular case (a corrupt Gemfile.lock with missing
dependencies) the LazySpecification will not have accurate dependency
information, we have to materialize the SpecSet to determine there are
missing dependencies. We've already got a way to handle this, via
`SpecSet#incomplete_specs`, but it wasn't quite working for this case
because we'd get to `@incomplete_specs += lookup[name]` and
`lookup[name]` would be empty for the dependency.
With this commit we catch it a bit earlier, marking the parent spec
containing the missing dependency as incomplete.
486ecb8f20