diff options
author | The Bundler Bot <bot@bundler.io> | 2017-05-08 02:39:02 +0000 |
---|---|---|
committer | The Bundler Bot <bot@bundler.io> | 2017-05-08 02:39:02 +0000 |
commit | 47e7dd0e93a11b40fa982aaef5eb7d35cb7c2717 (patch) | |
tree | 0a9cb512601c5561e4c567254429d89746606fd6 | |
parent | b6f5408e58b6ec6c24e004053ead6875d7a3f028 (diff) | |
parent | a542e2b5f982ee423ce6dad8d0e99815c1d0db07 (diff) | |
download | bundler-47e7dd0e93a11b40fa982aaef5eb7d35cb7c2717.tar.gz |
Auto merge of #5630 - bundler:jules2689-bundler-stub-spec, r=segiddins
Return Bundler::StubSpec if stub is a Bundler::StubSpec
Supersedes https://github.com/bundler/bundler/pull/5593
Fixes https://github.com/bundler/bundler/issues/5592
Explanation
---
In some cases the `Gem::Specification.stubs` call in [this method](https://github.com/bundler/bundler/blob/master/lib/bundler/rubygems_integration.rb#L773-L778) in the rubygems integration returns a mixed bag of `Bundler::StubSpecification` and `Gem::StubSpecification` objects. We then instantiate `Bundler::StubSpecification` objects and set the `stub` to be both `Gem::StubSpecification` and `Bundler::StubSpecification` objects.
This happens after we tell rubygems to use our overrides [here](https://github.com/bundler/bundler/blob/master/lib/bundler/runtime.rb#L21-L24).
A `Bundler::StubSpecification` does not define `to_spec` where `Gem::StubSpecification` does. In `Bundler::StubSpecification` we assume the `stub` to be a `Gem::StubSpecification` rather than the `to_spec`-less `Bundler::StubSpecification`. This means that in `_remote_specification`, the call to `to_spec` [here](https://github.com/bundler/bundler/blob/master/lib/bundler/stub_specification.rb#L88) fails. This falls back to `method_missing` [here](https://github.com/bundler/bundler/blob/master/lib/bundler/remote_specification.rb#L96-L98) which, of course, calls `_remote_specification` (and thus an infinite failing loops occurs).
### Why did this happen in such a weird way?
We needed to use a combination of `foreman`, `unicorn`, and a call to `Gem::Specification.find_by_name(*args)` to replicate.
I suspect this was required because Bundler doesn't call these methods as much. The last call in a doubly nested `bundle exec` resulted in the issue being exasperated.
You can however replicate with this:
```ruby
gem_stub = Gem::Specification.stubs.first
bundler_stub = Bundler::StubSpecification.from_stub(gem_stub)
bundler_stub = Bundler::StubSpecification.from_stub(bundler_stub)
bundler_stub.to_spec
```
We basically got to a point where we tried calling a method that doesn't exist on a `Bundler::StubSpecification`, so `_remote_specification` was called, but that had a method which didn't exist since we had the weirdness going on described here.
It was just a very specific sequence of events that is hard to replicate.
Options
---
1. We implement `to_spec` on `Bundler::StubSpecification`, as is done in https://github.com/bundler/bundler/pull/5593
2. We assume that `stub` is a `Gem::Specification`. Therefore if we try to create a `Bundler::StubSpecification` with the stub being a `Bundler::StubSpecification`, we simply return that stub we already made instead.
Thoughts
---
1. This basically ends up making a linked list of `Bundler::StubSpecifications` where you can follow `stub` all the way up until it's no longer a `Bundler::StubSpecification`. This means that the implementation is an accidental fix as `to_spec` in #5593 actually just calls `stub.to_spec` - which, if the stub is a `Bundler:StubSpecification`, would call that `Bundler::StubSpecification`, following the list up until we find a `Gem::StubSpecification`.
2. This is the right solution IMO. This breaks the weird linked list we made by mistake and just returns the object as we'd expect. Then, when `stub.to_spec` is called in `_remote_specification`, we always know it is a `Gem::StubSpecification` which has it defined.
cc @segiddins
-rw-r--r-- | lib/bundler/stub_specification.rb | 1 | ||||
-rw-r--r-- | spec/bundler/stub_specification_spec.rb | 25 |
2 files changed, 26 insertions, 0 deletions
diff --git a/lib/bundler/stub_specification.rb b/lib/bundler/stub_specification.rb index d5f6a883f9..aeacf245a3 100644 --- a/lib/bundler/stub_specification.rb +++ b/lib/bundler/stub_specification.rb @@ -4,6 +4,7 @@ require "bundler/remote_specification" module Bundler class StubSpecification < RemoteSpecification def self.from_stub(stub) + return stub if stub.is_a?(Bundler::StubSpecification) spec = new(stub.name, stub.version, stub.platform, nil) spec.stub = stub spec diff --git a/spec/bundler/stub_specification_spec.rb b/spec/bundler/stub_specification_spec.rb new file mode 100644 index 0000000000..f1ddf43bb4 --- /dev/null +++ b/spec/bundler/stub_specification_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true +require "spec_helper" + +RSpec.describe Bundler::StubSpecification do + let(:gemspec) do + Gem::Specification.new do |s| + s.name = "gemname" + s.version = "1.0.0" + s.loaded_from = __FILE__ + end + end + + let(:with_bundler_stub_spec) do + described_class.from_stub(gemspec) + end + + if Bundler.rubygems.provides?(">= 2.1") + describe "#from_stub" do + it "returns the same stub if already a Bundler::StubSpecification" do + stub = described_class.from_stub(with_bundler_stub_spec) + expect(stub).to be(with_bundler_stub_spec) + end + end + end +end |