Skip to content

Dont require inside install#13

Open
phorsuedzie wants to merge 4 commits intotarnowsc:mainfrom
phorsuedzie:dont-require-inside-install
Open

Dont require inside install#13
phorsuedzie wants to merge 4 commits intotarnowsc:mainfrom
phorsuedzie:dont-require-inside-install

Conversation

@phorsuedzie
Copy link

@phorsuedzie phorsuedzie commented Mar 2, 2026

Looks like to eat own dogfood while producing it is a bit questionable. The following Gemfile

# frozen_string_literal: true

source "https://rubygems.org"

plugin "bundler-override", "= 0.4.0"
if Bundler::Plugin.installed?("bundler-override")
  require(File.join(Bundler::Plugin.index.load_paths("bundler-override")[0], "bundler-override"))

  override "resque", drop: ["sinatra"]
end

# gem specifications below

creates a NoMethodError for Bundler::Override.override? (for a bundle exec rspec call):

'Gem::Specification#override_dependencies': undefined method 'override?' for module Bundler::Override (NoMethodError)
...
    # require "bundler/friendly_errors.rb"
    from /usr/local/bundle/plugin/gems/bundler-override-0.4.0/lib/bundler-override.rb:5:in '<top (required)>'
...
    # require(File.join(Bundler::Plugin.index.load_paths("bundler-override")[0], "bundler-override"))
    from /codebuild/output/src0000000000/src/Gemfile:7:in 'block in Bundler::Dsl#eval_gemfile'
...

This PR moves the "responsible" require out of the way. Plus it restructures the code to separate "compile code" from "wire into Gem and Bundler".

Full backtrace

/usr/local/bundle/plugin/gems/bundler-override-0.4.0/lib/bundler/override/dependency_patch.rb:18:in 'Gem::Specification#override_dependencies': undefined method 'override?' for module Bundler::Override (NoMethodError)
    from /usr/local/bundle/plugin/gems/bundler-override-0.4.0/lib/bundler/override/dependency_patch.rb:10:in 'Gem::Specification#dependencies'
    from /usr/local/lib/ruby/3.4.0/rubygems/specification.rb:2315:in 'Gem::Specification#runtime_dependencies'
    from /usr/local/bundle/plugin/gems/bundler-override-0.4.0/lib/bundler/override/dependency_patch.rb:14:in 'Gem::Specification#runtime_dependencies'
    from /usr/local/lib/ruby/3.4.0/rubygems/specification.rb:1664:in 'Gem::Specification#has_conflicts?'
    from /usr/local/lib/ruby/3.4.0/rubygems/specification.rb:2241:in 'Gem::Specification#raise_if_conflicts'
    from /usr/local/lib/ruby/3.4.0/rubygems/specification.rb:1387:in 'Gem::Specification#activate'
    from /usr/local/lib/ruby/3.4.0/rubygems/core_ext/kernel_gem.rb:62:in 'block in Kernel#gem'
    from /usr/local/lib/ruby/3.4.0/rubygems/core_ext/kernel_gem.rb:62:in 'Thread::Mutex#synchronize'
    from /usr/local/lib/ruby/3.4.0/rubygems/core_ext/kernel_gem.rb:62:in 'Kernel#gem'
    from <internal:/usr/local/lib/ruby/3.4.0/rubygems/core_ext/kernel_require.rb>:67:in 'block in Kernel#require'
    from <internal:/usr/local/lib/ruby/3.4.0/rubygems/core_ext/kernel_require.rb>:39:in 'Monitor#synchronize'
    from <internal:/usr/local/lib/ruby/3.4.0/rubygems/core_ext/kernel_require.rb>:39:in 'Kernel#require'
    from /usr/local/bundle/plugin/gems/bundler-override-0.4.0/lib/bundler-override.rb:5:in '<top (required)>'
    from <internal:/usr/local/lib/ruby/3.4.0/rubygems/core_ext/kernel_require.rb>:136:in 'Kernel#require'
    from <internal:/usr/local/lib/ruby/3.4.0/rubygems/core_ext/kernel_require.rb>:136:in 'Kernel#require'
    from /codebuild/output/src0000000000/src/Gemfile:7:in 'block in Bundler::Dsl#eval_gemfile'
    from /usr/local/lib/ruby/3.4.0/bundler/dsl.rb:47:in 'BasicObject#instance_eval'
    from /usr/local/lib/ruby/3.4.0/bundler/dsl.rb:47:in 'block in Bundler::Dsl#eval_gemfile'
    from /usr/local/lib/ruby/3.4.0/bundler/dsl.rb:315:in 'Bundler::Dsl#with_gemfile'
    from /usr/local/lib/ruby/3.4.0/bundler/dsl.rb:45:in 'Bundler::Dsl#eval_gemfile'
    from /usr/local/lib/ruby/3.4.0/bundler/dsl.rb:12:in 'Bundler::Dsl.evaluate'
    from /usr/local/lib/ruby/3.4.0/bundler/definition.rb:39:in 'Bundler::Definition.build'
    from /usr/local/lib/ruby/3.4.0/bundler.rb:237:in 'Bundler.definition'
    from /usr/local/lib/ruby/3.4.0/bundler.rb:161:in 'Bundler.setup'
    from /usr/local/lib/ruby/3.4.0/bundler/setup.rb:32:in 'block in <top (required)>'
    from /usr/local/lib/ruby/3.4.0/bundler/ui/shell.rb:173:in 'Bundler::UI::Shell#with_level'
    from /usr/local/lib/ruby/3.4.0/bundler/ui/shell.rb:119:in 'Bundler::UI::Shell#silence'
    from /usr/local/lib/ruby/3.4.0/bundler/setup.rb:32:in '<top (required)>'
    from <internal:/usr/local/lib/ruby/3.4.0/rubygems/core_ext/kernel_require.rb>:136:in 'Kernel#require'
    from <internal:/usr/local/lib/ruby/3.4.0/rubygems/core_ext/kernel_require.rb>:136:in 'Kernel#require'
    from /usr/local/lib/ruby/3.4.0/rubygems.rb:1417:in '<top (required)>'
    from <internal:gem_prelude>:2:in 'Kernel#require'
    from <internal:gem_prelude>:2:in '<internal:gem_prelude>'

This Gemfile code already helps to load the plugin successfully (predefines the method accessed by require):

-  require(File.join(Bundler::Plugin.index.load_paths("bundler-override")[0], "bundler-override"))
+  unless ::Bundler.const_defined?(:Override)
+    ::Bundler.const_set(:Override, Module.new do
+      def self.override?(*)
+        false
+      end
+    end)
+
+    require(File.join(::Bundler::Plugin.index.load_paths("bundler-override")[0], "bundler-override"))
+  end

Bundler override hooks into methods which are called by `Kernel#require`
(or more precisely: all the magic build on top of it) sooner or later.

To install the plugin "atomically", i.e. without any call to `require`,
avoids to run into only half-installed code (see below for details). To
require the friendly error earlier keeps it out of "install".

Details:
`dependency_patch.rb` already auto-installs itself into the modules
`Bundler` and `Gem` (at the end of the source file). Which activates
the code already for whatever `Bundler` and `Gem` already use these
methods for. Unfortunately, the implementation of `DependencyPatch`
accesses methods of `Bundler::Override` (e.g., `.override?`), but these
methods are NOT (yet) defined.

Alternatives:
A) Don't require `bundler/friendly_errors`. This is not actually plugin's
business.

B) Don't (auto-)install too early. To define all code first, and only
afterwards wire the modules into `Bundler` and `Gem`, keeps trouble
away.

C) Define all of `Bundler::Override` first, to have it there "if needed",
and only afterwards run `require_relative` of `bundler/override/*`.*).
I.e., move all `require_relative` down (see next commit, doesn't hurt).

*) Where `Dsl` is likely not a troublemaker, because DSL methods are
hopefully not involved into code loading via `require`.

Piggy-backed: removes `.rb` from the argument of `require`.
See alternative C) of the parent commit. It is now impossible to have
(own plugin) code code that calls from `Bundler::Override` "too early".
Instead, the code is wired into `Bundler` and `Gem` only after all code
of `Bundler::Override` has been sourced.
Keeps "load all lib" more simple.

Note: Actually, the two workhorses of the plugin are `DslPatch` and
`DependencyPatch`. `Bundler::Override` itself is only a registry shared
by both.

To `require_relative "../override"` from each `bundler/override/*.rb`
would express the actual dependency more precisely. But to require the
shared code early and once is also good.
@phorsuedzie phorsuedzie force-pushed the dont-require-inside-install branch from 90248f0 to a7634eb Compare March 2, 2026 17:21
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.

1 participant