Skip to content
This repository has been archived by the owner on Apr 14, 2021. It is now read-only.

Bundler 1.10.1 breaks Rails 2.3.x plugin-loading #3762

Closed
ghost opened this issue Jun 18, 2015 · 12 comments · Fixed by #3815
Closed

Bundler 1.10.1 breaks Rails 2.3.x plugin-loading #3762

ghost opened this issue Jun 18, 2015 · 12 comments · Fixed by #3815

Comments

@ghost
Copy link

ghost commented Jun 18, 2015

I appreciate that this might not be a bug in Bundler per se, but it's exasperating for those of us who still maintain Rails 2 apps, so I thought I'd report it and see if there's any advice on a workaround.

It seems that Rails 2 calls Gem::DependencyList#dependency_order while locating gems that host a plugin. This method does not appear to be deprecated, or at least the stdlib docs for Ruby 2.1.6 don't mention it. However, it results in an exception (see backtrace below).

Any advice on how to overcome this would be greatly appreciated; I'd hate to be locked into using an older Bundler version. If there's a more modern, supported way to sort specs in dependency order, then I'll be happy to monkey-patch that into my app.

    $ bundle exec script/console
    Loading development environment (Rails 2.3.18)
    /Users/tony/.rbenv/versions/2.1.6/lib/ruby/2.1.0/rubygems/dependency_list.rb:214:in `sort': comparison of Bundler::StubSpecification with Gem::Specification failed (ArgumentError)
        from /Users/tony/.rbenv/versions/2.1.6/lib/ruby/2.1.0/rubygems/dependency_list.rb:214:in `tsort_each_child'
        from /Users/tony/.rbenv/versions/2.1.6/lib/ruby/2.1.0/tsort.rb:411:in `call'
        from /Users/tony/.rbenv/versions/2.1.6/lib/ruby/2.1.0/tsort.rb:411:in `each_strongly_connected_component_from'
        from /Users/tony/.rbenv/versions/2.1.6/lib/ruby/2.1.0/tsort.rb:347:in `block in each_strongly_connected_component'
        from /Users/tony/.rbenv/versions/2.1.6/lib/ruby/2.1.0/rubygems/dependency_list.rb:210:in `each'
        from /Users/tony/.rbenv/versions/2.1.6/lib/ruby/2.1.0/rubygems/dependency_list.rb:210:in `tsort_each_node'
        from /Users/tony/.rbenv/versions/2.1.6/lib/ruby/2.1.0/tsort.rb:345:in `call'
        from /Users/tony/.rbenv/versions/2.1.6/lib/ruby/2.1.0/tsort.rb:345:in `each_strongly_connected_component'
        from /Users/tony/.rbenv/versions/2.1.6/lib/ruby/2.1.0/tsort.rb:280:in `strongly_connected_components'
        from /Users/tony/.rbenv/versions/2.1.6/lib/ruby/2.1.0/tsort.rb:255:in `strongly_connected_components'
        from /Users/tony/.rbenv/versions/2.1.6/lib/ruby/2.1.0/rubygems/dependency_list.rb:75:in `dependency_order'
        from /Users/tony/.rbenv/versions/2.1.6/lib/ruby/gems/2.1.0/gems/rails-2.3.18/lib/rails/plugin/locator.rb:94:in `plugins'
        from /Users/tony/.rbenv/versions/2.1.6/lib/ruby/gems/2.1.0/gems/rails-2.3.18/lib/rails/plugin/loader.rb:109:in `block in locate_plugins'
        from /Users/tony/.rbenv/versions/2.1.6/lib/ruby/gems/2.1.0/gems/rails-2.3.18/lib/rails/plugin/loader.rb:108:in `map'
        from /Users/tony/.rbenv/versions/2.1.6/lib/ruby/gems/2.1.0/gems/rails-2.3.18/lib/rails/plugin/loader.rb:108:in `locate_plugins'
        from /Users/tony/.rbenv/versions/2.1.6/lib/ruby/gems/2.1.0/gems/rails-2.3.18/lib/rails/plugin/loader.rb:32:in `all_plugins'
        from /Users/tony/.rbenv/versions/2.1.6/lib/ruby/gems/2.1.0/gems/rails-2.3.18/lib/rails/plugin/loader.rb:22:in `plugins'
        from /Users/tony/.rbenv/versions/2.1.6/lib/ruby/gems/2.1.0/gems/rails-2.3.18/lib/rails/plugin/loader.rb:53:in `add_plugin_load_paths'
        from /Users/tony/.rbenv/versions/2.1.6/lib/ruby/gems/2.1.0/gems/rails-2.3.18/lib/initializer.rb:294:in `add_plugin_load_paths'
        from /Users/tony/.rbenv/versions/2.1.6/lib/ruby/gems/2.1.0/gems/rails-2.3.18/lib/initializer.rb:136:in `process'
        from /Users/tony/.rbenv/versions/2.1.6/lib/ruby/gems/2.1.0/gems/rails-2.3.18/lib/initializer.rb:113:in `run'
        from /Users/tony/Code/rails23-bundler-bug/config/environment.rb:100:in `<top (required)>'
        from /Users/tony/.rbenv/versions/2.1.6/lib/ruby/2.1.0/irb/init.rb:286:in `require'
        from /Users/tony/.rbenv/versions/2.1.6/lib/ruby/2.1.0/irb/init.rb:286:in `block in load_modules'
        from /Users/tony/.rbenv/versions/2.1.6/lib/ruby/2.1.0/irb/init.rb:284:in `each'
        from /Users/tony/.rbenv/versions/2.1.6/lib/ruby/2.1.0/irb/init.rb:284:in `load_modules'
        from /Users/tony/.rbenv/versions/2.1.6/lib/ruby/2.1.0/irb/init.rb:20:in `setup'
        from /Users/tony/.rbenv/versions/2.1.6/lib/ruby/2.1.0/irb.rb:380:in `start'
        from /Users/tony/.rbenv/versions/2.1.6/bin/irb:11:in `<main>'
@segiddins
Copy link
Member

@tony-spataro-rs do you have a project I can investigate this on?

@ghost
Copy link
Author

ghost commented Jun 18, 2015

I'm unable to reproduce outside of my closed-source project, even after duplicating Gemfile and Gemfile.lock exactly in freshly-created Rails 2.3.18 scaffold app. This indicates that something within my project might be at fault; will update as I investigate.

@ghost
Copy link
Author

ghost commented Jun 18, 2015

Still unclear as to why this is happening only in my project, but after researching #3278 and #3279, I've made StubSpecification comparable to Gem::Specification by adding a flying-saucer instance method.

I don't understand the problem domain well enough to advocate this as a viable fix -- but if the purpose of a Bundler::StubSpecification is to stand in place of a Gem::Specification, I imagine that comparability is an important part of being a stub...

  module Bundler
    class StubSpecification
      def <=>(other)
        p0 = name <=> other.name
        p1 = version <=> other.version
        p2 = platform <=> other.platform

        (p0 != 0 && p0) || (p1 != 0 && p1) || (p2 != 0 && p2) || 0
      end
    end
  end

@segiddins
Copy link
Member

Can you check if this patch also fixes the issue?

diff --git a/lib/bundler/stub_specification.rb b/lib/bundler/stub_specification.rb
index be73c90..1562612 100644
--- a/lib/bundler/stub_specification.rb
+++ b/lib/bundler/stub_specification.rb
@@ -14,10 +14,22 @@ module Bundler
       _remote_specification.to_yaml
     end

+    def respond_to_missing?(name, *args)
+      Gem::Specification.instance_methods.include?(name) || super
+    end
+
+    def method_missing(name, *args, &blk)
+      if respond_to_missing?(name)
+        stub.to_spec.send(name, *args, &blk)
+      else
+        super
+      end
+    end
+
   private

     def _remote_specification
       stub.to_spec
     end
   end
-end
\ No newline at end of file
+end

@ghost
Copy link
Author

ghost commented Jun 19, 2015

Interestingly, it does not. It seems that Array#sort is not fooled by respond_to_missing?

# this is debug output that I added to RubyGems#dependency_list to show the contents of the objectionable array
["Bundler::StubSpecification sass 3.4.14",
 "Bundler::StubSpecification global_session 3.0.5",
 "Gem::Specification right_session 2.1.0",
 "Gem::Specification rightscale_core_ui 1.4.0",
 "Gem::Specification s3_uploader 1.0.1",
 "Gem::Specification tr8n 0.1.0"]
# this is the error still happening after switching to the `r_t_m` patch
/Users/tony/.rbenv/versions/2.1.6/lib/ruby/2.1.0/rubygems/dependency_list.rb:216:in `sort': comparison of Bundler::StubSpecification with Gem::Specification failed (ArgumentError)

@ghost
Copy link
Author

ghost commented Jun 19, 2015

Indulging myself in a bit of shotgun debugging ... getting rid of the metaprogramming in your patch, we seem to make some progress.

diff --git a/lib/bundler/stub_specification.rb b/lib/bundler/stub_specification.rb
index be73c90..0fbde3e 100644
--- a/lib/bundler/stub_specification.rb
+++ b/lib/bundler/stub_specification.rb
@@ -14,10 +14,19 @@ module Bundler
       _remote_specification.to_yaml
     end

+    def <=>(other)
+      case other
+      when Gem::Specification
+        self.to_spec <=> other
+      else
+        super
+      end
+    end
+
   private

     def _remote_specification
       stub.to_spec
     end
   end
-end
\ No newline at end of file
+end

This results in a failure to compare StubSpecification with itself -- better than comparing two dislike classes!

/Users/tony/.rbenv/versions/2.1.6/lib/ruby/2.1.0/rubygems/dependency_list.rb:216:in `sort': comparison of Bundler::StubSpecification with Bundler::StubSpecification failed (ArgumentError)

It seems that neither StubSpecification nor RemoteSpecification concern themselves with equality or comparison, so they inherit defaults from Object

irb> a = Bundler::RemoteSpecification.new('a', '1.1', 'ruby', nil)
irb> b = Bundler::RemoteSpecification.new('a', '1.1', 'ruby', nil)

irb> a <=> b
=> nil

irb> a == b
=> false

@ghost
Copy link
Author

ghost commented Jun 19, 2015

That observation about comparability led me to try the following solution, which does fix the issue in my Rails app (and is a bit cleaner than my first attempt at comparability).

diff --git a/lib/bundler/remote_specification.rb b/lib/bundler/remote_specification.rb
index c80eaa4..c2c29bd 100644
--- a/lib/bundler/remote_specification.rb
+++ b/lib/bundler/remote_specification.rb
@@ -8,6 +8,7 @@ module Bundler
   # full specification will only be fetched when necessary.
   class RemoteSpecification
     include MatchPlatform
+    include Comparable

     attr_reader :name, :version, :platform
     attr_accessor :source, :remote
@@ -33,6 +34,14 @@ module Bundler
       end
     end

+    def <=>(other)
+      if other.respond_to?(:full_name)
+        full_name <=> other.full_name
+      else
+        super
+      end
+    end
+
     # Because Rubyforge cannot be trusted to provide valid specifications
     # once the remote gem is downloaded, the backend specification will
     # be swapped out.

@indirect
Copy link
Member

sounds good to me 👍

@ghost
Copy link
Author

ghost commented Jun 19, 2015

@indirect and @segiddins, thank you both for your input. I've skimmed DEVELOPMENT.md and opened #3767 to fix this issue; let me know if you need anything to change.

Note that I pursued a slightly different fix than my last proposal; I reviewed the source of Gem::Specification and realized that it was best to exactly duplicate its comparison semantics (including the use of #sort_obj). Verified that this still fixed my issue.

indirect pushed a commit that referenced this issue Jul 10, 2015
The RemoteSpecification#sort_obj method was added in e5d936e (which
was cherry-picked from #3767) to fix #3762, but it still fails when
comparing two instances of RemoteSpecification.

As #sort_obj is private, the check for other.respond_to?(:sort_obj)
returns false, which means the <=> check falls back to the default
(from Object) which returns nil if the objects being compared don't
match. This then results in an ArgumentError when (e.g.) sorting an
array containing multiple instances of RemoteSpecification.

The fix is simple: make RemoteSpecification#sort_obj public.
@sobrinho
Copy link

I'm seeing a similar message on a rails 2.3 application with bundler 1.10.5:

rake aborted!
comparison of Bundler::StubSpecification with Bundler::StubSpecification failed
/home/runner/secret/vendor/bundle/ruby/2.1.0/bundler/gems/rails-133bbcb301a2/railties/lib/rails/plugin/locator.rb:94:in `plugins'
/home/runner/secret/vendor/bundle/ruby/2.1.0/bundler/gems/rails-133bbcb301a2/railties/lib/rails/plugin/loader.rb:109:in `block in locate_plugins'
/home/runner/secret/vendor/bundle/ruby/2.1.0/bundler/gems/rails-133bbcb301a2/railties/lib/rails/plugin/loader.rb:108:in `map'
/home/runner/secret/vendor/bundle/ruby/2.1.0/bundler/gems/rails-133bbcb301a2/railties/lib/rails/plugin/loader.rb:108:in `locate_plugins'
/home/runner/secret/vendor/bundle/ruby/2.1.0/bundler/gems/rails-133bbcb301a2/railties/lib/rails/plugin/loader.rb:32:in `all_plugins'
/home/runner/secret/vendor/bundle/ruby/2.1.0/bundler/gems/rails-133bbcb301a2/railties/lib/rails/plugin/loader.rb:22:in `plugins'
/home/runner/secret/vendor/bundle/ruby/2.1.0/bundler/gems/rails-133bbcb301a2/railties/lib/rails/plugin/loader.rb:53:in `add_plugin_load_paths'
/home/runner/secret/vendor/bundle/ruby/2.1.0/bundler/gems/rails-133bbcb301a2/railties/lib/initializer.rb:299:in `add_plugin_load_paths'
/home/runner/secret/vendor/bundle/ruby/2.1.0/bundler/gems/rails-133bbcb301a2/railties/lib/initializer.rb:139:in `process'
/home/runner/secret/vendor/bundle/ruby/2.1.0/bundler/gems/rails-133bbcb301a2/railties/lib/initializer.rb:114:in `run'
/home/runner/secret/config/environment.rb:36:in `<top (required)>'
/home/runner/secret/vendor/bundle/ruby/2.1.0/bundler/gems/rails-133bbcb301a2/activesupport/lib/active_support/dependencies.rb:182:in `require'
/home/runner/secret/vendor/bundle/ruby/2.1.0/bundler/gems/rails-133bbcb301a2/activesupport/lib/active_support/dependencies.rb:182:in `block in require'
/home/runner/secret/vendor/bundle/ruby/2.1.0/bundler/gems/rails-133bbcb301a2/activesupport/lib/active_support/dependencies.rb:547:in `new_constants_in'
/home/runner/secret/vendor/bundle/ruby/2.1.0/bundler/gems/rails-133bbcb301a2/activesupport/lib/active_support/dependencies.rb:182:in `require'
/home/runner/secret/vendor/bundle/ruby/2.1.0/bundler/gems/rails-133bbcb301a2/railties/lib/tasks/misc.rake:4:in `block in <top (required)>'
Tasks: TOP => db:migrate => environment
(See full trace by running task with --trace)

@ghost
Copy link
Author

ghost commented Jul 21, 2015

My fix had an issue (comparison isn't symmetrical); see #3815 which is pending release.

@sobrinho
Copy link

Thanks @tony-spataro-rs!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants