Skip to content

Commit 5913e2f

Browse files
committed
Support Ruby 3.1
## How does require_relative work The `require_relative` method is different from other monkey patches because the code uses the caller location to change behavior. That means if you monkey patch that method and alias it back to the original `require_relative` it will not work as expected. Here is the c-code for `require_relative` https://github.com/ruby/ruby/blob/b35b7a1ef25347735a6bb7c28ab7e77afea1d856/load.c#L907-L924. The way that bootsnap (and others) work around this limitation is by grabbing the second to last caller location and requiring relative to that file instead. This method uses the `Thread::Backtrace::Location` class: ``` caller_locations(1..1).first.class => Thread::Backtrace::Location ``` ## What changed in 3.1 This change in ruby ruby/ruby#4519 changed the behavior of that class when being returned against code with an `eval`. It appears that previously the behavior was to return the path of the file where the eval was defined. The new behavior is to return a nil, and then that would be used to implement the behavior described: ``` if (!eval_default_path) { eval_default_path = rb_fstring_lit("(eval)"); rb_gc_register_mark_object(eval_default_path); } fname = eval_default_path; ``` That behavior had the side effect of breaking gems that were using `Thread::Backtrace::Location#absolute_path` to implement `require_relative` in their monkey patch (such as `bootsnap` and `derailed_benchmarks`). The fix is easy enough, while `absolute_path` returns a nil the `path` still returns a value for eval code. ## This commit Basically checks if `Thread::Backtrace::Location#absolute_path` is nil and if so, falls back to `Thread::Backtrace::Location#path`. The previous online was a little incomprehensible, so I refactored it a bit to highlight what's going on. I found out about this fix from rails/bootsnap@0d64e7e after realizing that the test suite was failing on Ruby 3.1: ``` /home/circleci/.rvm/gems/ruby-3.1.0-preview1/gems/bundler-2.2.30/lib/bundler.rb:663:in `rescue in eval_gemspec': (Bundler::Dsl::DSLError) [!] There was an error parsing `Gemfile`: [!] There was an error while loading `dead_end.gemspec`: cannot load such file -- /home/circleci/lib/dead_end/version. Bundler cannot continue. # from /home/circleci/project/dead_end.gemspec:3 # ------------------------------------------- # > require_relative "lib/dead_end/version" # # ------------------------------------------- . Bundler cannot continue. # from /home/circleci/project/Gemfile:6 # ------------------------------------------- # # Specify your gem's dependencies in dead_end.gemspec > gemspec # # ------------------------------------------- from /home/circleci/.rvm/gems/ruby-3.1.0-preview1/gems/bundler-2.2.30/lib/bundler.rb:658:in `eval_gemspec' from /home/circleci/.rvm/gems/ruby-3.1.0-preview1/gems/bundler-2.2.30/lib/bundler.rb:585:in `block in load_gemspec_uncached' from /home/circleci/.rvm/gems/ruby-3.1.0-preview1/gems/bundler-2.2.30/lib/bundler/shared_helpers.rb:52:in `chdir' from /home/circleci/.rvm/gems/ruby-3.1.0-preview1/gems/bundler-2.2.30/lib/bundler/shared_helpers.rb:52:in `block in chdir' from /home/circleci/.rvm/gems/ruby-3.1.0-preview1/gems/bundler-2.2.30/lib/bundler/shared_helpers.rb:51:in `synchronize' from /home/circleci/.rvm/gems/ruby-3.1.0-preview1/gems/bundler-2.2.30/lib/bundler/shared_helpers.rb:51:in `chdir' from /home/circleci/.rvm/gems/ruby-3.1.0-preview1/gems/bundler-2.2.30/lib/bundler.rb:584:in `load_gemspec_uncached' from /home/circleci/.rvm/gems/ruby-3.1.0-preview1/gems/bundler-2.2.30/lib/bundler.rb:570:in `load_gemspec' from /home/circleci/.rvm/gems/ruby-3.1.0-preview1/gems/bundler-2.2.30/lib/bundler/dsl.rb:66:in `block in gemspec' from /home/circleci/.rvm/gems/ruby-3.1.0-preview1/gems/bundler-2.2.30/lib/bundler/dsl.rb:66:in `map' from /home/circleci/.rvm/gems/ruby-3.1.0-preview1/gems/bundler-2.2.30/lib/bundler/dsl.rb:66:in `gemspec' from /home/circleci/project/Gemfile:6:in `eval_gemfile' from /home/circleci/.rvm/gems/ruby-3.1.0-preview1/gems/bundler-2.2.30/lib/bundler/dsl.rb:47:in `instance_eval' from /home/circleci/.rvm/gems/ruby-3.1.0-preview1/gems/bundler-2.2.30/lib/bundler/dsl.rb:47:in `eval_gemfile' from /home/circleci/.rvm/gems/ruby-3.1.0-preview1/gems/bundler-2.2.30/lib/bundler/dsl.rb:12:in `evaluate' from /home/circleci/.rvm/gems/ruby-3.1.0-preview1/gems/bundler-2.2.30/lib/bundler/definition.rb:33:in `build' from /home/circleci/.rvm/gems/ruby-3.1.0-preview1/gems/bundler-2.2.30/lib/bundler.rb:196:in `definition' from /home/circleci/.rvm/gems/ruby-3.1.0-preview1/gems/bundler-2.2.30/lib/bundler.rb:144:in `setup' from /home/circleci/.rvm/gems/ruby-3.1.0-preview1/gems/bundler-2.2.30/lib/bundler/setup.rb:20:in `block in <top (required)>' from /home/circleci/.rvm/gems/ruby-3.1.0-preview1/gems/bundler-2.2.30/lib/bundler/ui/shell.rb:136:in `with_level' from /home/circleci/.rvm/gems/ruby-3.1.0-preview1/gems/bundler-2.2.30/lib/bundler/ui/shell.rb:88:in `silence' from /home/circleci/.rvm/gems/ruby-3.1.0-preview1/gems/bundler-2.2.30/lib/bundler/setup.rb:20:in `<top (required)>' from <internal:/home/circleci/.rvm/rubies/ruby-3.1.0-preview1/lib/ruby/3.1.0/rubygems/core_ext/kernel_require.rb>:85:in `require' from <internal:/home/circleci/.rvm/rubies/ruby-3.1.0-preview1/lib/ruby/3.1.0/rubygems/core_ext/kernel_require.rb>:85:in `require' from /home/circleci/project/lib/dead_end/core_ext.rb:19:in `require' ```
1 parent c4ce262 commit 5913e2f

File tree

4 files changed

+27
-5
lines changed

4 files changed

+27
-5
lines changed

.circleci/config.yml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
version: 2.1
22
orbs:
3-
ruby: circleci/ruby@1.1.2
3+
ruby: circleci/ruby@1.2.0
44
references:
55
unit: &unit
66
run:
@@ -45,6 +45,17 @@ jobs:
4545
- ruby/install-deps
4646
- <<: *unit
4747

48+
"ruby-3-1":
49+
docker:
50+
- image: 'cimg/base:stable'
51+
steps:
52+
- checkout
53+
- ruby/install:
54+
version: '3.1.0-preview1'
55+
- run: ruby -v
56+
- ruby/install-deps
57+
- <<: *unit
58+
4859
"lint":
4960
docker:
5061
- image: circleci/ruby:3.0
@@ -61,4 +72,5 @@ workflows:
6172
- "ruby-2-6"
6273
- "ruby-2-7"
6374
- "ruby-3-0"
75+
- "ruby-3-1"
6476
- "lint"

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
## HEAD (unreleased)
22

3+
- Add support for Ruby 3.1 by updating `require_relative` logic (https://github.com/zombocom/dead_end/pull/120)
34
- Requiring `dead_end/auto` is now deprecated please require `dead_end` instead (https://github.com/zombocom/dead_end/pull/119)
45
- Requiring `dead_end/api` now loads code without monkeypatching core extensions (https://github.com/zombocom/dead_end/pull/119)
56
- The interface `DeadEnd.handle_error` is declared public and stable (https://github.com/zombocom/dead_end/pull/119)

lib/dead_end/core_ext.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ def require_relative(file)
2525
if Pathname.new(file).absolute?
2626
dead_end_original_require file
2727
else
28-
dead_end_original_require File.expand_path("../#{file}", Kernel.caller_locations(1, 1)[0].absolute_path)
28+
relative_from = caller_locations(1..1).first
29+
relative_from_path = relative_from.absolute_path || relative_from.path
30+
dead_end_original_require File.expand_path("../#{file}", relative_from_path)
2931
end
3032
rescue SyntaxError => e
3133
DeadEnd.handle_error(e)

spec/integration/ruby_command_line_spec.rb

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,22 @@ module DeadEnd
2424
Process.wait(d_pid)
2525
Process.wait(r_pid)
2626

27-
dead_end_methods_array = dead_end_methods_file.read.strip.lines.map(&:strip)
2827
kernel_methods_array = kernel_methods_file.read.strip.lines.map(&:strip)
28+
dead_end_methods_array = dead_end_methods_file.read.strip.lines.map(&:strip)
2929
api_only_methods_array = api_only_methods_file.read.strip.lines.map(&:strip)
3030

31+
# In ruby 3.1.0-preview1 the `timeout` file is already required
32+
# we can remove it if it exists to normalize the output for
33+
# all ruby versions
34+
[dead_end_methods_array, kernel_methods_array, api_only_methods_array].each do |array|
35+
array.delete("timeout")
36+
end
37+
3138
methods = (dead_end_methods_array - kernel_methods_array).sort
32-
expect(methods).to eq(["dead_end_original_load", "dead_end_original_require", "dead_end_original_require_relative", "timeout"])
39+
expect(methods).to eq(["dead_end_original_load", "dead_end_original_require", "dead_end_original_require_relative"])
3340

3441
methods = (api_only_methods_array - kernel_methods_array).sort
35-
expect(methods).to eq(["timeout"])
42+
expect(methods).to eq([])
3643
end
3744
end
3845

0 commit comments

Comments
 (0)