Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Curious error with 'bundler/inline': Failed loading plugin `standard-custom' because we couldn't determine (RuntimeError) the corresponding plugin class to instantiate. #591

Open
pblzd opened this issue Nov 20, 2023 · 1 comment

Comments

@pblzd
Copy link

pblzd commented Nov 20, 2023

I'm trying to create a self-contained script to run standard using bundler/inline like this:

#!/usr/bin/env ruby

Gem.paths = {"GEM_HOME" => "./.bundle"}

require 'bundler/inline'

gemfile(true) do
  source "https://rubygems.org"
  gem 'standard'
end

load Gem.bin_path("standard", "standardrb")

Whenever I call this script and standard-custom is not already installed, it raises the strange error from this issue's title. If I re-run the script it works as expected. If I remove standard-custom from ./.bundle and re-run the script, the error occurs again.

If I use a standalone Gemfile, the error does not occur, even if installing gems and running the code happens in one process.

Details:

user@host standardrb-test % ruby -v
ruby 3.2.2 (2023-03-30 revision e51014f9c0) [arm64-darwin22]
user@host standardrb-test % ruby test.rb
Fetching gem metadata from https://rubygems.org/..........
Resolving dependencies...
Using ast 2.4.2
Fetching regexp_parser 2.8.2
Using json 2.6.3
Fetching lint_roller 1.1.0
Fetching rexml 3.2.6
Fetching racc 1.7.3
Fetching parallel 1.23.0
Using rainbow 3.1.1
Fetching ruby-progressbar 1.13.0
Using bundler 2.4.10
Using unicode-display_width 2.5.0
Fetching language_server-protocol 3.17.0.3
Installing lint_roller 1.1.0
Installing parallel 1.23.0
Installing language_server-protocol 3.17.0.3
Installing ruby-progressbar 1.13.0
Installing regexp_parser 2.8.2
Installing racc 1.7.3 with native extensions
Installing rexml 3.2.6
Using parser 3.2.2.4
Using rubocop-ast 1.30.0
Using rubocop 1.57.2
Using standard-custom 1.0.2
Fetching rubocop-performance 1.19.1
Installing rubocop-performance 1.19.1
Fetching standard-performance 1.2.1
Installing standard-performance 1.2.1
Fetching standard 1.32.0
Installing standard 1.32.0
/Users/user/projects/standardrb-test/.bundle/gems/standard-1.32.0/lib/standard/plugin/determines_class_constant.rb:17:in `rescue in call': Failed loading plugin `standard-performance' because we couldn't determine (RuntimeError)
the corresponding plugin class to instantiate.

Standard plugin class names must either be:

  - If the plugin is a gem, defined in the gemspec as `default_lint_roller_plugin'

    spec.metadata["default_lint_roller_plugin"] = "MyModule::Plugin"

  - Set in YAML as `plugin_class_name'; example:

    plugins:
      - incomplete:
          require_path: my_module/plugin
          plugin_class_name: "MyModule::Plugin"

	from /Users/user/projects/standardrb-test/.bundle/gems/standard-1.32.0/lib/standard/plugin/determines_class_constant.rb:14:in `call'
	from /Users/user/projects/standardrb-test/.bundle/gems/standard-1.32.0/lib/standard/plugin/initializes_plugins.rb:14:in `block in call'
	from /Users/user/projects/standardrb-test/.bundle/gems/standard-1.32.0/lib/standard/plugin/initializes_plugins.rb:11:in `each'
	from /Users/user/projects/standardrb-test/.bundle/gems/standard-1.32.0/lib/standard/plugin/initializes_plugins.rb:11:in `map'
	from /Users/user/projects/standardrb-test/.bundle/gems/standard-1.32.0/lib/standard/plugin/initializes_plugins.rb:11:in `call'
	from /Users/user/projects/standardrb-test/.bundle/gems/standard-1.32.0/lib/standard/plugin/combines_plugin_configs.rb:10:in `call'
	from /Users/user/projects/standardrb-test/.bundle/gems/standard-1.32.0/lib/standard/creates_config_store.rb:22:in `block in call'
	from <internal:kernel>:90:in `tap'
	from /Users/user/projects/standardrb-test/.bundle/gems/standard-1.32.0/lib/standard/creates_config_store.rb:19:in `call'
	from /Users/user/projects/standardrb-test/.bundle/gems/standard-1.32.0/lib/standard/builds_config.rb:26:in `call'
	from /Users/user/projects/standardrb-test/.bundle/gems/standard-1.32.0/lib/standard/cli.rb:13:in `run'
	from /Users/user/projects/standardrb-test/.bundle/gems/standard-1.32.0/exe/standardrb:7:in `<top (required)>'
	from test.rb:12:in `load'
	from test.rb:12:in `<main>'
/Users/user/projects/standardrb-test/.bundle/gems/standard-1.32.0/lib/standard/plugin/determines_class_constant.rb:15:in `const_get': no implicit conversion of nil into String (TypeError)

            Kernel.const_get(Gem.loaded_specs[plugin_name].metadata["default_lint_roller_plugin"])
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
	from /Users/user/projects/standardrb-test/.bundle/gems/standard-1.32.0/lib/standard/plugin/determines_class_constant.rb:15:in `call'
	from /Users/user/projects/standardrb-test/.bundle/gems/standard-1.32.0/lib/standard/plugin/initializes_plugins.rb:14:in `block in call'
	from /Users/user/projects/standardrb-test/.bundle/gems/standard-1.32.0/lib/standard/plugin/initializes_plugins.rb:11:in `each'
	from /Users/user/projects/standardrb-test/.bundle/gems/standard-1.32.0/lib/standard/plugin/initializes_plugins.rb:11:in `map'
	from /Users/user/projects/standardrb-test/.bundle/gems/standard-1.32.0/lib/standard/plugin/initializes_plugins.rb:11:in `call'
	from /Users/user/projects/standardrb-test/.bundle/gems/standard-1.32.0/lib/standard/plugin/combines_plugin_configs.rb:10:in `call'
	from /Users/user/projects/standardrb-test/.bundle/gems/standard-1.32.0/lib/standard/creates_config_store.rb:22:in `block in call'
	from <internal:kernel>:90:in `tap'
	from /Users/user/projects/standardrb-test/.bundle/gems/standard-1.32.0/lib/standard/creates_config_store.rb:19:in `call'
	from /Users/user/projects/standardrb-test/.bundle/gems/standard-1.32.0/lib/standard/builds_config.rb:26:in `call'
	from /Users/user/projects/standardrb-test/.bundle/gems/standard-1.32.0/lib/standard/cli.rb:13:in `run'
	from /Users/user/projects/standardrb-test/.bundle/gems/standard-1.32.0/exe/standardrb:7:in `<top (required)>'
	from test.rb:12:in `load'
	from test.rb:12:in `<main>'

This is on MacOS 14.1. ("Sonoma"), in case that matters.

I'd love to spare myself a Gemfile, so I'd be happy for any ideas or fixes.

If I can help with further information, please let me know.

@searls
Copy link
Contributor

searls commented Nov 20, 2023

Okay, I'll be honest, this one sucks.

First, I was able to reproduce this repeatedly by uninstalling standard-custom before each run (and nuking the local bundle folder):

#!/usr/bin/env ruby

system "gem uninstall --ignore-dependencies --force standard-custom"

require 'bundler/inline'

gemfile(true) do
  source "https://rubygems.org"
  gem 'standard', path: '../standard'
end

load Gem.bin_path("standard", "standardrb")

Given that the class raising this error also does some clever stuff when requiring each plugin, I expected that this line would be the culprit, which in this situation resolves to require "standard-custom", however that works fine and returns true. Additionally, Standard::Custom is defined, indicating that it is successfully requiring the Ruby.

The issue appears to be at the line raising the error:

Kernel.const_get(Gem.loaded_specs[plugin_name].metadata["default_lint_roller_plugin"])

Where Gem.loaded_specs[plugin_name] is returning this Bundler::EndpointSpecification:

Gem::Specification.new do |s|                                                                                                                    
  s.name = "standard-custom"                                                                                                                     
  s.version = Gem::Version.new("1.0.2")                                                                                                          
  s.installed_by_version = Gem::Version.new("0")                                                                                                 
  s.bindir = "exe"                                                                                                                               
  s.date = Time.utc(2023, 11, 20)                                                                                                                
  s.dependencies = [Gem::Dependency.new("lint_roller", Gem::Requirement.new(["~> 1.0"]), :runtime), Gem::Dependency.new("rubocop", Gem::Requirement.new(["~> 1.50"]), :runtime)]
  s.require_paths = ["lib"]                                                                                                                      
  s.required_ruby_version = Gem::Requirement.new([">= 2.6.0"])                                                                                   
  s.rubygems_version = "3.4.17"                                                                                                                  
  s.specification_version = 4
  s.summary = nil
end

Compare that to an installed plugin like Gem.loaded_specs["standard-performance"], which returns a more complete Bundler::StubSpecification:

#<Bundler::StubSpecification:0x000000010fc548a0
 @_remote_specification=
  Gem::Specification.new do |s|
    s.name = "standard-performance"
    s.version = Gem::Version.new("1.2.1")
    s.installed_by_version = Gem::Version.new("3.4.17")
    s.authors = ["Justin Searls"]
    s.bindir = "exe"
    s.date = Time.utc(2023, 10, 10)
    s.dependencies = [Gem::Dependency.new("lint_roller", Gem::Requirement.new(["~> 1.1"]), :runtime), Gem::Dependency.new("rubocop-performance", Gem::Requirement.new(["~> 1.19.1"]), :runtime)]
    s.email = ["[email protected]"]
    s.homepage = "https://github.com/standardrb/standard-performance"
    s.licenses = ["MIT"]
    s.metadata = {"changelog_uri"=>"https://github.com/standardrb/standard-performance/blob/main/CHANGELOG.md",
     "default_lint_roller_plugin"=>"Standard::Performance::Plugin",
     "homepage_uri"=>"https://github.com/standardrb/standard-performance",
     "source_code_uri"=>"https://github.com/standardrb/standard-performance"}
    s.require_paths = ["lib"]
    s.required_ruby_version = Gem::Requirement.new([">= 2.7.0"])
    s.rubygems_version = "3.4.17"
    s.specification_version = 4
    s.summary = "Standard Ruby Plugin providing configuration for rubocop-performance"
    end,
 @dependencies=[Gem::Dependency.new("lint_roller", Gem::Requirement.new(["~> 1.1"]), :runtime), Gem::Dependency.new("rubocop-performance", Gem::Requirement.new(["~> 1.19.1"]), :runtime)],
 @full_name="standard-performance-1.2.1",
 @name="standard-performance",
 @original_platform="ruby",
 @platform="ruby",
 @required_ruby_version=Gem::Requirement.new([">= 2.7.0"]),
 @required_rubygems_version=Gem::Requirement.new([">= 0"]),
 @source=#<Bundler::Source::Rubygems:0x234620 rubygems repository https://rubygems.org/ or installed locally>,
 @spec_fetcher=nil,
 @stub=
  #<Gem::StubSpecification:0x00000001046130c8
   @activated=true,
   @base_dir="/Users/justin/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0",
   @data=#<Gem::StubSpecification::StubLine:0x0000000104654208 @extensions=[], @full_name="standard-performance-1.2.1", @name="standard-performance", @platform="ruby", @require_paths=["lib"], @version=Gem::Version.new("1.2.1")>,
   @default_gem=false,
   @extension_dir=nil,
   @full_gem_path="/Users/justin/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/standard-performance-1.2.1",
   @full_require_paths=["/Users/justin/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/standard-performance-1.2.1/lib"],
   @gem_dir=nil,
   @gems_dir="/Users/justin/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems",
   @ignored=nil,
   @loaded_from="/Users/justin/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/specifications/standard-performance-1.2.1.gemspec",
   @name=nil,
   @spec=
    Gem::Specification.new do |s|
      s.name = "standard-performance"
      s.version = Gem::Version.new("1.2.1")
      s.installed_by_version = Gem::Version.new("3.4.17")
      s.authors = ["Justin Searls"]
      s.bindir = "exe"
      s.date = Time.utc(2023, 10, 10)
      s.dependencies = [Gem::Dependency.new("lint_roller", Gem::Requirement.new(["~> 1.1"]), :runtime), Gem::Dependency.new("rubocop-performance", Gem::Requirement.new(["~> 1.19.1"]), :runtime)]
      s.email = ["[email protected]"]
      s.homepage = "https://github.com/standardrb/standard-performance"
      s.licenses = ["MIT"]
      s.metadata = {"changelog_uri"=>"https://github.com/standardrb/standard-performance/blob/main/CHANGELOG.md",
       "default_lint_roller_plugin"=>"Standard::Performance::Plugin",
       "homepage_uri"=>"https://github.com/standardrb/standard-performance",
       "source_code_uri"=>"https://github.com/standardrb/standard-performance"}
      s.require_paths = ["lib"]
      s.required_ruby_version = Gem::Requirement.new([">= 2.7.0"])
      s.rubygems_version = "3.4.17"
      s.specification_version = 4
      s.summary = "Standard Ruby Plugin providing configuration for rubocop-performance"
      end>,
 @version=Gem::Version.new("1.2.1")>

Because lint_roller depends on standard-custom's gemspec to contain this line:

spec.metadata["default_lint_roller_plugin"] = "Standard::Custom::Plugin"

The fact that the Bundler::EndpointSpecification being returned above does not actually load the real gemspec means that it won't know what class to load, which is causing the error.

From the perspective of me as a gem author relying on spec.metadata, this seems to be a bug in Bundler (or maybe rubygems, depending on whether Bundler is just wrapping an incomplete spec from an underlying gems API), since I can't find any documentation stating that it's not safe to rely on the metadata API after the gem is loaded and required. I think that should be our next step.

Paging @deivid-rodriguez and @hsbt in case they might be able to offer advice here

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

No branches or pull requests

2 participants