require-hooks 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +5 -2
- data/lib/require-hooks/api.rb +55 -29
- data/lib/require-hooks/iseq.rb +33 -0
- data/lib/require-hooks/mode/bootsnap.rb +67 -2
- data/lib/require-hooks/mode/kernel_patch.rb +22 -15
- data/lib/require-hooks/mode/load_iseq.rb +4 -14
- data/lib/require-hooks/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 046c644cbb446d1abd3263c3c1ee0c578cddac17693d102172e123f1a880966a
|
|
4
|
+
data.tar.gz: a102736a0665a9de69c9aba242033a479af9b090bf135fa729e74b9bd8f6e006
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 35e088739e021cc4e83827122ce2daa63934439f6e4d0c9597ccafdaf6f6a08b50eba9302cfed1ced10538a62b317406ded4da1882b06f4d04b527a5e4902b47
|
|
7
|
+
data.tar.gz: 801d491df8a08926067f47b44ee2372d73a53fada8cdfe36b62c0d66191f52f6900499121fc87a326125f00e443848734e5f7756df541d7e92f688ae293e5026
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## master
|
|
4
4
|
|
|
5
|
+
## 0.4.0 (2026-04-29)
|
|
6
|
+
|
|
7
|
+
- Improved Bootsnap cache invalidation logic on hooks configuration changes.
|
|
8
|
+
|
|
9
|
+
- Latest Bootsnap compatibility
|
|
10
|
+
|
|
11
|
+
- Coverage compatibility (w/ some limitations)
|
|
12
|
+
|
|
5
13
|
## 0.3.0 (2026-04-22)
|
|
6
14
|
|
|
7
15
|
- Fix the order of around hooks execution (after part) when using `#load_iseq` driven hooks.
|
data/README.md
CHANGED
|
@@ -9,6 +9,8 @@ Require Hooks is a library providing universal interface for injecting custom co
|
|
|
9
9
|
|
|
10
10
|
Require hooks allows you to interfere with `Kernel#require` (incl. `Kernel#require_relative`) and `Kernel#load`.
|
|
11
11
|
|
|
12
|
+
> Check the ["Require Hooks: Filling the Gap in Ruby's Extensibility"](https://evilmartians.com/events/require-hooks-rubykaigi) talk from RubyKaigi 2026 to learn more.
|
|
13
|
+
|
|
12
14
|
<a href="https://evilmartians.com/">
|
|
13
15
|
<img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Sponsored by Evil Martians" width="236" height="54"></a>
|
|
14
16
|
|
|
@@ -126,6 +128,7 @@ Thus, if you introduce new source transformers or hijackers, you must invalidate
|
|
|
126
128
|
|
|
127
129
|
## Limitations
|
|
128
130
|
|
|
131
|
+
- Coverage tracking is only supported if `eval` coverage tracking is enabled (`Coverage.start(eval: true, ...)` or `Simplecov.start { enable_coverage_for_eval; ... }`). Currently requires **Ruby 3.4+**.
|
|
129
132
|
- `Kernel#load` with a wrap argument (e.g., `load "some_path", true` or `load "some_path", MyModule)`) is not supported (fallbacked to the original implementation). The biggest challenge here is to support constants nesting.
|
|
130
133
|
- Some very edgy symlinking scenarios are not supported (unlikely to affect real-world projects).
|
|
131
134
|
|
|
@@ -175,9 +178,9 @@ Test script: `time bundle exec rails runner 'puts "done"'`.
|
|
|
175
178
|
| rhooks (patch) | **8m** |
|
|
176
179
|
| rhooks (bootsnap) | 12s |
|
|
177
180
|
|
|
178
|
-
You can see that requiring tons of files with Require Hooks in patch mode is very slow for now. Why?
|
|
181
|
+
You can see that requiring tons of files with Require Hooks in patch mode is very slow for now. Why? Manipulating the `$LOADED_FEATURES` index from Ruby triggers costly invalidation at the VM side when a regular `#require` occurs. We recommend activating Require Hooks after loading all the dependencies and limiting the scope of affected files (via the `patterns` option) on non-MRI platforms to avoid this overhead.
|
|
179
182
|
|
|
180
|
-
**NOTE:**
|
|
183
|
+
**NOTE:** This could be improved in the future if we get [optimized API](https://github.com/palkan/ruby/pull/1) for managing `$LOADED_FEATURES` from Ruby code.
|
|
181
184
|
|
|
182
185
|
Here are the numbers for the same project with scoped hooks (only some folders) activated after `Bundler.require(*)`:
|
|
183
186
|
|
data/lib/require-hooks/api.rb
CHANGED
|
@@ -14,6 +14,7 @@ module RequireHooks
|
|
|
14
14
|
@hijack_load = []
|
|
15
15
|
|
|
16
16
|
@empty = nil
|
|
17
|
+
@readonly = nil
|
|
17
18
|
end
|
|
18
19
|
|
|
19
20
|
def to_key
|
|
@@ -31,6 +32,12 @@ module RequireHooks
|
|
|
31
32
|
@empty = @around_load.empty? && @source_transform.empty? && @hijack_load.empty?
|
|
32
33
|
end
|
|
33
34
|
|
|
35
|
+
def readonly?
|
|
36
|
+
return @readonly unless @readonly.nil?
|
|
37
|
+
|
|
38
|
+
@readonly = @source_transform.empty? && @hijack_load.empty?
|
|
39
|
+
end
|
|
40
|
+
|
|
34
41
|
def source_transform?
|
|
35
42
|
@source_transform.any?
|
|
36
43
|
end
|
|
@@ -70,6 +77,12 @@ module RequireHooks
|
|
|
70
77
|
end
|
|
71
78
|
nil
|
|
72
79
|
end
|
|
80
|
+
|
|
81
|
+
def merge!(another_ctx)
|
|
82
|
+
around_load.concat(another_ctx.around_load)
|
|
83
|
+
source_transform.concat(another_ctx.source_transform)
|
|
84
|
+
hijack_load.concat(another_ctx.hijack_load)
|
|
85
|
+
end
|
|
73
86
|
end
|
|
74
87
|
|
|
75
88
|
@@default_context = Context.new
|
|
@@ -88,13 +101,7 @@ module RequireHooks
|
|
|
88
101
|
# block.call.tap { puts "Loaded #{path}" }
|
|
89
102
|
# end
|
|
90
103
|
def around_load(patterns: nil, exclude_patterns: nil, &block)
|
|
91
|
-
|
|
92
|
-
ctx = Context.new(patterns: patterns, exclude_patterns: exclude_patterns)
|
|
93
|
-
|
|
94
|
-
@@contexts[ctx.to_key] ||= ctx
|
|
95
|
-
@@contexts[ctx.to_key].around_load << block
|
|
96
|
-
|
|
97
|
-
@@default_context = @@contexts.values.first if @@contexts.size == 1
|
|
104
|
+
register_hook(:around_load, block, patterns: patterns, exclude_patterns: exclude_patterns)
|
|
98
105
|
end
|
|
99
106
|
|
|
100
107
|
# Define hooks to perform source-to-source transformations.
|
|
@@ -108,13 +115,7 @@ module RequireHooks
|
|
|
108
115
|
# "# frozen_string_literal: true\n#{source}"
|
|
109
116
|
# end
|
|
110
117
|
def source_transform(patterns: nil, exclude_patterns: nil, &block)
|
|
111
|
-
|
|
112
|
-
ctx = Context.new(patterns: patterns, exclude_patterns: exclude_patterns)
|
|
113
|
-
|
|
114
|
-
@@contexts[ctx.to_key] ||= ctx
|
|
115
|
-
@@contexts[ctx.to_key].source_transform << block
|
|
116
|
-
|
|
117
|
-
@@default_context = @@contexts.values.first if @@contexts.size == 1
|
|
118
|
+
register_hook(:source_transform, block, patterns: patterns, exclude_patterns: exclude_patterns)
|
|
118
119
|
end
|
|
119
120
|
|
|
120
121
|
# This hook should be used to manually compile byte code to be loaded by the VM.
|
|
@@ -132,21 +133,15 @@ module RequireHooks
|
|
|
132
133
|
# end
|
|
133
134
|
# end
|
|
134
135
|
def hijack_load(patterns: nil, exclude_patterns: nil, &block)
|
|
135
|
-
|
|
136
|
-
ctx = Context.new(patterns: patterns, exclude_patterns: exclude_patterns)
|
|
137
|
-
|
|
138
|
-
@@contexts[ctx.to_key] ||= ctx
|
|
139
|
-
@@contexts[ctx.to_key].hijack_load << block
|
|
140
|
-
|
|
141
|
-
@@default_context = @@contexts.values.first if @@contexts.size == 1
|
|
136
|
+
register_hook(:hijack_load, block, patterns: patterns, exclude_patterns: exclude_patterns)
|
|
142
137
|
end
|
|
143
138
|
|
|
144
139
|
def context_for(path)
|
|
145
|
-
# Fast-track in case we have just a single context defined
|
|
140
|
+
# Fast-track in case we have just a single non-global context defined
|
|
146
141
|
if @@default_context
|
|
147
|
-
return @@
|
|
142
|
+
return @@default_context if @@default_context.match?(path)
|
|
148
143
|
|
|
149
|
-
return @@
|
|
144
|
+
return @@noop_context
|
|
150
145
|
end
|
|
151
146
|
|
|
152
147
|
matching = @@contexts.values.select { |ctx| ctx.match?(path) }
|
|
@@ -154,13 +149,44 @@ module RequireHooks
|
|
|
154
149
|
return matching[0] || @@noop_context if matching.size < 2
|
|
155
150
|
|
|
156
151
|
ctx = Context.new
|
|
157
|
-
matching.each
|
|
158
|
-
ctx.around_load.concat(mctx.around_load)
|
|
159
|
-
ctx.source_transform.concat(mctx.source_transform)
|
|
160
|
-
ctx.hijack_load.concat(mctx.hijack_load)
|
|
161
|
-
end
|
|
152
|
+
matching.each { |mctx| ctx.merge!(mctx) }
|
|
162
153
|
|
|
163
154
|
ctx
|
|
164
155
|
end
|
|
156
|
+
|
|
157
|
+
# Hack to enable coverage for hooked files.
|
|
158
|
+
# Requires eval coverage to be on.
|
|
159
|
+
# See https://bugs.ruby-lang.org/issues/22018 (https://github.com/ruby/ruby/pull/16805)
|
|
160
|
+
def setup_path_coverage(path, contents = nil)
|
|
161
|
+
return unless defined?(Coverage) && Coverage.running?
|
|
162
|
+
|
|
163
|
+
return unless eval_coverage_enabled?
|
|
164
|
+
|
|
165
|
+
Kernel.eval("\n" * (contents || File.read(path)).lines.size, TOPLEVEL_BINDING, path, 1) # rubocop:disable Style/EvalWithLocation,Security/Eval
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def contexts
|
|
169
|
+
@@contexts
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
private
|
|
173
|
+
|
|
174
|
+
def register_hook(type, block, patterns: nil, exclude_patterns: nil)
|
|
175
|
+
@@default_context = nil
|
|
176
|
+
ctx = Context.new(patterns: patterns, exclude_patterns: exclude_patterns)
|
|
177
|
+
|
|
178
|
+
@@contexts[ctx.to_key] ||= ctx
|
|
179
|
+
@@contexts[ctx.to_key].public_send(type) << block
|
|
180
|
+
|
|
181
|
+
@@default_context = @@contexts.values.first if @@contexts.size == 1
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def eval_coverage_enabled?
|
|
185
|
+
return @eval_coverage_enabled if defined?(@eval_coverage_enabled)
|
|
186
|
+
probe_path = File.join(__dir__, "coverage_probe.rb")
|
|
187
|
+
Kernel.eval("proc { |val| val }", TOPLEVEL_BINDING, probe_path, 1) # rubocop:disable Style/EvalWithLocation
|
|
188
|
+
|
|
189
|
+
@eval_coverage_enabled = Coverage.peek_result.key?(probe_path)
|
|
190
|
+
end
|
|
165
191
|
end
|
|
166
192
|
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RequireHooks
|
|
4
|
+
module Iseq
|
|
5
|
+
class << self
|
|
6
|
+
def compile_with_coverage(ctx, path)
|
|
7
|
+
iseq =
|
|
8
|
+
if ctx.source_transform? || ctx.hijack?
|
|
9
|
+
new_contents = ctx.perform_source_transform(path)
|
|
10
|
+
|
|
11
|
+
RequireHooks.setup_path_coverage(path, new_contents)
|
|
12
|
+
|
|
13
|
+
hijacked = ctx.try_hijack_load(path, new_contents)
|
|
14
|
+
|
|
15
|
+
if hijacked
|
|
16
|
+
raise TypeError, "Unsupported bytecode format for #{path}: #{hijack.class}" unless hijacked.is_a?(::RubyVM::InstructionSequence)
|
|
17
|
+
hijacked
|
|
18
|
+
elsif new_contents
|
|
19
|
+
RubyVM::InstructionSequence.compile(new_contents, path, path, 1)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
RequireHooks.setup_path_coverage(path, new_contents)
|
|
24
|
+
|
|
25
|
+
iseq ||= yield if block_given?
|
|
26
|
+
|
|
27
|
+
iseq ||= RubyVM::InstructionSequence.compile_file(path)
|
|
28
|
+
|
|
29
|
+
iseq
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "require-hooks/iseq"
|
|
4
|
+
|
|
3
5
|
module RequireHooks
|
|
4
6
|
module Bootsnap
|
|
5
7
|
EMPTY_ISEQ = RubyVM::InstructionSequence.compile("").freeze
|
|
6
8
|
|
|
9
|
+
# For older Bootsnap
|
|
7
10
|
module CompileCacheExt
|
|
8
11
|
def input_to_storage(source, path, *)
|
|
9
12
|
ctx = RequireHooks.context_for(path)
|
|
@@ -25,25 +28,87 @@ module RequireHooks
|
|
|
25
28
|
end
|
|
26
29
|
end
|
|
27
30
|
|
|
31
|
+
# For new Bootsnap
|
|
32
|
+
module CompilerExt
|
|
33
|
+
def input_to_storage(source, path, *)
|
|
34
|
+
ctx = RequireHooks.context_for(path)
|
|
35
|
+
return super if ctx.empty?
|
|
36
|
+
|
|
37
|
+
new_contents = ctx.perform_source_transform(path)
|
|
38
|
+
hijacked = ctx.try_hijack_load(path, new_contents)
|
|
39
|
+
|
|
40
|
+
if hijacked
|
|
41
|
+
raise TypeError, "Unsupported bytecode format for #{path}: #{hijack.class}" unless hijacked.is_a?(::RubyVM::InstructionSequence)
|
|
42
|
+
return hijacked.to_binary
|
|
43
|
+
elsif new_contents
|
|
44
|
+
return RubyVM::InstructionSequence.compile(new_contents, path, path, 1, @compile_options).to_binary
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
super
|
|
48
|
+
rescue SyntaxError, TypeError
|
|
49
|
+
::Bootsnap::CompileCache::UNCOMPILABLE
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
28
53
|
module LoadIseqExt
|
|
54
|
+
class << self
|
|
55
|
+
attr_accessor :orig_cache_dir
|
|
56
|
+
end
|
|
57
|
+
|
|
29
58
|
# Around hooks must be performed every time we trigger a file load, even if
|
|
30
59
|
# the file is already cached.
|
|
31
60
|
def load_iseq(path)
|
|
32
61
|
ctx = RequireHooks.context_for(path)
|
|
33
62
|
# Early-return for non-trackable paths
|
|
34
|
-
|
|
63
|
+
if ctx.empty?
|
|
64
|
+
::Bootsnap::CompileCache::ISeq.cache_dir = LoadIseqExt.orig_cache_dir if LoadIseqExt.orig_cache_dir
|
|
65
|
+
return super
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
LoadIseqExt.orig_cache_dir ||= ::Bootsnap::CompileCache::ISeq.cache_dir
|
|
69
|
+
::Bootsnap::CompileCache::ISeq.cache_dir = File.join(LoadIseqExt.orig_cache_dir, RequireHooks::Bootsnap.version_hash)
|
|
35
70
|
|
|
36
71
|
ctx.run_around_load_callbacks(path) do
|
|
37
72
|
iseq = super
|
|
38
|
-
|
|
73
|
+
|
|
74
|
+
::Bootsnap::CompileCache::ISeq.cache_dir = LoadIseqExt.orig_cache_dir
|
|
75
|
+
|
|
76
|
+
# Bootsnap returns nil when the coverage is on,
|
|
77
|
+
# we fallback to our custom #compile_with_coverage
|
|
78
|
+
unless iseq
|
|
79
|
+
next unless defined?(Coverage) && Coverage.running?
|
|
80
|
+
|
|
81
|
+
iseq = RequireHooks::Iseq.compile_with_coverage(ctx, path)
|
|
82
|
+
end
|
|
39
83
|
|
|
40
84
|
iseq.eval
|
|
41
85
|
EMPTY_ISEQ
|
|
86
|
+
ensure
|
|
87
|
+
::Bootsnap::CompileCache::ISeq.cache_dir = LoadIseqExt.orig_cache_dir
|
|
42
88
|
end
|
|
43
89
|
end
|
|
44
90
|
end
|
|
91
|
+
|
|
92
|
+
class << self
|
|
93
|
+
def version_hash
|
|
94
|
+
@version_key ||= RequireHooks.contexts.values.map(&:to_cache_key).join("-")
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
attr_writer :version_hash
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
class Context
|
|
102
|
+
def to_cache_key
|
|
103
|
+
Zlib.crc32(
|
|
104
|
+
(around_load + source_transform + hijack_load).map do |pr|
|
|
105
|
+
RubyVM::InstructionSequence.disasm(pr)
|
|
106
|
+
end.join("\n")
|
|
107
|
+
).to_s
|
|
108
|
+
end
|
|
45
109
|
end
|
|
46
110
|
end
|
|
47
111
|
|
|
48
112
|
Bootsnap::CompileCache::ISeq.singleton_class.prepend(RequireHooks::Bootsnap::CompileCacheExt)
|
|
113
|
+
Bootsnap::CompileCache::ISeq::Compiler.prepend(RequireHooks::Bootsnap::CompilerExt) if defined?(Bootsnap::CompileCache::ISeq::Compiler)
|
|
49
114
|
RubyVM::InstructionSequence.singleton_class.prepend(RequireHooks::Bootsnap::LoadIseqExt)
|
|
@@ -14,7 +14,9 @@ module RequireHooks
|
|
|
14
14
|
new_contents = ctx.perform_source_transform(path)
|
|
15
15
|
hijacked = ctx.try_hijack_load(path, new_contents)
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
if hijacked
|
|
18
|
+
return try_evaluate(path, hijacked)
|
|
19
|
+
end
|
|
18
20
|
|
|
19
21
|
if new_contents
|
|
20
22
|
evaluate(new_contents, path)
|
|
@@ -29,6 +31,7 @@ module RequireHooks
|
|
|
29
31
|
|
|
30
32
|
def try_evaluate(path, bytecode)
|
|
31
33
|
if defined?(::RubyVM::InstructionSequence) && bytecode.is_a?(::RubyVM::InstructionSequence)
|
|
34
|
+
RequireHooks.setup_path_coverage(path)
|
|
32
35
|
bytecode.eval
|
|
33
36
|
else
|
|
34
37
|
raise TypeError, "Unknown bytecode format for #{path}: #{bytecode.inspect}"
|
|
@@ -49,6 +52,8 @@ module RequireHooks
|
|
|
49
52
|
end
|
|
50
53
|
else
|
|
51
54
|
def evaluate(code, filepath)
|
|
55
|
+
RequireHooks.setup_path_coverage(filepath, code)
|
|
56
|
+
|
|
52
57
|
# This is workaround to solve the "leaking refinements" problem in MRI
|
|
53
58
|
RubyVM::InstructionSequence.compile(code, filepath).tap do |iseq|
|
|
54
59
|
iseq.eval
|
|
@@ -226,22 +231,24 @@ module Kernel
|
|
|
226
231
|
|
|
227
232
|
return false if $LOADED_FEATURES.include?(realpath)
|
|
228
233
|
|
|
229
|
-
|
|
230
|
-
|
|
234
|
+
begin
|
|
235
|
+
RequireHooks::KernelPatch::Features::LOCK.lock_feature(feature) do |loaded|
|
|
236
|
+
return false if loaded
|
|
231
237
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
238
|
+
$LOADED_FEATURES << realpath
|
|
239
|
+
RequireHooks::KernelPatch.load(realpath, ctx: ctx)
|
|
240
|
+
true
|
|
241
|
+
end
|
|
242
|
+
rescue LoadError => e
|
|
243
|
+
$LOADED_FEATURES.delete(realpath) if realpath
|
|
244
|
+
warn "RequireHooks failed to require '#{path}' from '#{realpath}': #{e.message}" if RequireHooks.print_warnings
|
|
245
|
+
require_without_require_hooks(path)
|
|
246
|
+
rescue Errno::ENOENT, Errno::EACCES
|
|
247
|
+
raise LoadError, "cannot load such file -- #{path}"
|
|
248
|
+
rescue
|
|
249
|
+
$LOADED_FEATURES.delete(realpath) if realpath
|
|
250
|
+
raise
|
|
235
251
|
end
|
|
236
|
-
rescue LoadError => e
|
|
237
|
-
$LOADED_FEATURES.delete(realpath) if realpath
|
|
238
|
-
warn "RequireHooks failed to require '#{path}': #{e.message}" if RequireHooks.print_warnings
|
|
239
|
-
require_without_require_hooks(path)
|
|
240
|
-
rescue Errno::ENOENT, Errno::EACCES
|
|
241
|
-
raise LoadError, "cannot load such file -- #{path}"
|
|
242
|
-
rescue
|
|
243
|
-
$LOADED_FEATURES.delete(realpath) if realpath
|
|
244
|
-
raise
|
|
245
252
|
end
|
|
246
253
|
|
|
247
254
|
alias_method :require_relative_without_require_hooks, :require_relative
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "require-hooks/iseq"
|
|
4
|
+
|
|
3
5
|
module RequireHooks
|
|
4
6
|
EMPTY_ISEQ = RubyVM::InstructionSequence.compile("").freeze
|
|
5
7
|
|
|
@@ -11,20 +13,8 @@ module RequireHooks
|
|
|
11
13
|
return if ctx.empty?
|
|
12
14
|
|
|
13
15
|
ctx.run_around_load_callbacks(path) do
|
|
14
|
-
iseq =
|
|
15
|
-
|
|
16
|
-
new_contents = ctx.perform_source_transform(path)
|
|
17
|
-
hijacked = ctx.try_hijack_load(path, new_contents)
|
|
18
|
-
|
|
19
|
-
if hijacked
|
|
20
|
-
raise TypeError, "Unsupported bytecode format for #{path}: #{hijack.class}" unless hijacked.is_a?(::RubyVM::InstructionSequence)
|
|
21
|
-
hijacked
|
|
22
|
-
elsif new_contents
|
|
23
|
-
RubyVM::InstructionSequence.compile(new_contents, path, path, 1)
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
iseq ||= (defined?(super) ? super : RubyVM::InstructionSequence.compile_file(path))
|
|
16
|
+
iseq = RequireHooks::Iseq.compile_with_coverage(ctx, path) { defined?(super) && super }
|
|
17
|
+
|
|
28
18
|
iseq.eval
|
|
29
19
|
EMPTY_ISEQ
|
|
30
20
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: require-hooks
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Vladimir Dementyev
|
|
@@ -22,6 +22,7 @@ files:
|
|
|
22
22
|
- README.md
|
|
23
23
|
- lib/require-hooks.rb
|
|
24
24
|
- lib/require-hooks/api.rb
|
|
25
|
+
- lib/require-hooks/iseq.rb
|
|
25
26
|
- lib/require-hooks/mode/bootsnap.rb
|
|
26
27
|
- lib/require-hooks/mode/kernel_patch.rb
|
|
27
28
|
- lib/require-hooks/mode/load_iseq.rb
|