Bundler standalone setup assumes `defined?(Gem)` implies `Gem.ruby_api_version` is available
Bundler
### Describe the problem as clearly as you can
Bundler standalone setup assumes that if the `Gem` constant is defined, RubyGems API helpers such as `Gem.ruby_api_version` and `Gem.extension_api_version` are also available.
This assumption can fail in ruby.wasm.
In ruby.wasm, after Ruby VM initialization, we can observe a state where `Gem` is already defined, but RubyGems has not been fully loaded yet. In that state:
```ruby
defined?(Gem) #=> "constant"
Gem.respond_to?(:ruby_api_version) #=> false
Gem.respond_to?(:extension_api_version) #=> false
```
Bundler standalone setup generated by `bundle install --standalone` only checks `unless defined?(Gem)` before defining fallback helpers:
https://github.com/ruby/rubygems/blob/884d80fd158fead18bb4f021796a2af062a45ac9/bundler/lib/bundler/installer/standalone.rb#L76
So, when `Gem` is partially defined, the fallback helpers are not defined, but the generated load paths still use `Gem.ruby_api_version` and `Gem.extension_api_version`. This can fail with `NoMethodError` before application code starts.
This is currently known as a ruby.wasm-specific issue. I do not think this affects normal CRuby usage, where VM initialization appears to leave `Gem` and RubyGems in a consistent state.
ruby.wasm has worked around this by requiring RubyGems before loading the generated standalone setup when `Gem` is defined but `Gem.ruby_api_version` is missing:
https://github.com/ruby/ruby.wasm/pull/622
I would like to ask whether Bundler standalone setup should be more defensive here, for example by checking whether the specific methods it needs are available, instead of checking only `defined?(Gem)`.
### Did you try upgrading rubygems & bundler?
Yes.
I checked the current Bundler standalone implementation on the `rubygems` main branch, and the generated fallback still appears to use only `unless defined?(Gem)`:
https://github.com/ruby/rubygems/blob/master/bundler/lib/bundler/installer/standalone.rb
So I believe this behavior is still present.
### Post steps to reproduce the problem
This is not known to reproduce on normal CRuby. It is observed in ruby.wasm, where the Ruby VM can be initialized with `Gem` defined but RubyGems not fully loaded.
### Which command did you run?
In ruby.wasm packaging, Bundler is invoked with standalone mode, roughly:
```sh
bundle install --standalone --target-rbconfig path/to/target/rbconfig.rb
```
Then, at runtime, ruby.wasm loads the generated setup file:
```ruby
require "/bundle/bundler/setup.rb"
```
### What were you expecting to happen?
I expected the generated standalone `setup.rb` not to fail just because the `Gem` constant already exists.
More specifically, if `setup.rb` needs `Gem.ruby_api_version` and `Gem.extension_api_version`, I expected it to either:
- define fallback versions of those methods when they are missing, or
- clearly rely on RubyGems being fully loaded before `setup.rb` is required.
If Bundler expects the second behavior, then ruby.wasm should probably require RubyGems before loading the generated standalone setup file. I would like to confirm which behavior Bundler expects.
### What happened instead?
The generated standalone setup can fail with `NoMethodError` because `Gem.ruby_api_version` or `Gem.extension_api_version` is missing.
```text
NoMethodError: undefined method `ruby_api_version' for module Gem
```
### If not included with the output of your command, run `bundle env` and paste the output below
This is observed in ruby.wasm rather than a normal local CRuby environment, so `bundle env` from the host environment may not represent the runtime where the failure occurs.
Relevant context:
- Runtime: ruby.wasm
- Mode: WASI component model flow
- Bundler feature involved: `bundle install --standalone`
- ruby.wasm workaround: https://github.com/ruby/ruby.wasm/pull/622
This may be related to #7545, which also describes a standalone setup case where `Gem` exists but RubyGems is not fully available. The missing method is different there (`Gem.try_activate`), while this issue is about `Gem.ruby_api_version` and `Gem.extension_api_version` used by the generated standalone load paths.
0 条评论