require-hooks 0.4.0 → 0.5.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 +2 -2
- data/lib/require-hooks/mode/bootsnap.rb +71 -31
- data/lib/require-hooks/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9357c7e14301ad7ab541c2e2fabbda1f03df56e748aee55245dfc44ef98519f4
|
|
4
|
+
data.tar.gz: 87b05cf3ec16a3c9c4e9c1a46a3ec267ca03717923fe8abdccd4e46deb212791
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a9e828449e13517a11c03d9521c4170507f08862e6a4e75fa01360a2c17191dbecfa3a745a1433bd7546f190c1599f17be7a4dc76869bbd798fd1e47e859a130
|
|
7
|
+
data.tar.gz: e1e80635ac8c7ceb90c1580797145c3c15636d463635f3ccf57578024619af150f4dbe94eb279aff7dc62c231ed1de3733b5db3e7a1b38a21002e8ef0ca8a063
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## master
|
|
4
4
|
|
|
5
|
+
## 0.5.0 (2026-08-26)
|
|
6
|
+
|
|
7
|
+
- Use Bootsnap compiler namespaces for cache invalidation without changing Bootsnap's cache directory.
|
|
8
|
+
|
|
9
|
+
## 0.4.1 (2026-07-22)
|
|
10
|
+
|
|
11
|
+
- Bring back Ruby 2.3 compatibility.
|
|
12
|
+
|
|
5
13
|
## 0.4.0 (2026-04-29)
|
|
6
14
|
|
|
7
15
|
- Improved Bootsnap cache invalidation logic on hooks configuration changes.
|
data/README.md
CHANGED
|
@@ -124,11 +124,11 @@ require "require-hooks/setup"
|
|
|
124
124
|
|
|
125
125
|
The _around load_ hooks are executed for all files independently of whether they are cached or not. Source transformation and hijacking is only done for non-cached files.
|
|
126
126
|
|
|
127
|
-
|
|
127
|
+
Require Hooks separates cached instructions by hook configuration. New source transformers and hijackers invalidate affected cache entries automatically.
|
|
128
128
|
|
|
129
129
|
## Limitations
|
|
130
130
|
|
|
131
|
-
- Coverage tracking is only supported
|
|
131
|
+
- Coverage tracking is only supported in Ruby 4.0.4+ (or 3.4.10+ for 3.4.x series); for older versions in 3.4 and 4.0 series, you can enable `eval` coverage tracking to make it work with Require Hooks (`Coverage.start(eval: true, ...)` or `SimpleCov.enable_coverage :eval`). For Ruby 3.2.x and 3.3.x, only around hooks are supported.
|
|
132
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.
|
|
133
133
|
- Some very edgy symlinking scenarios are not supported (unlikely to affect real-world projects).
|
|
134
134
|
|
|
@@ -28,51 +28,71 @@ module RequireHooks
|
|
|
28
28
|
end
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
#
|
|
32
|
-
module
|
|
33
|
-
def
|
|
31
|
+
# Bootsnap before 1.24 does not support compiler namespaces.
|
|
32
|
+
module LegacyCacheExt
|
|
33
|
+
def fetch(path, cache_dir: self.cache_dir)
|
|
34
34
|
ctx = RequireHooks.context_for(path)
|
|
35
|
-
|
|
35
|
+
cache_dir = File.join(cache_dir, RequireHooks::Bootsnap.version_hash) unless ctx.empty?
|
|
36
|
+
|
|
37
|
+
super
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Bootsnap 1.24+ compiler wrapper. Its namespace isolates transformed ISeqs
|
|
42
|
+
# without changing Bootsnap's process-wide cache directory.
|
|
43
|
+
class VersionedCompiler
|
|
44
|
+
attr_reader :namespace
|
|
45
|
+
|
|
46
|
+
def initialize(compiler)
|
|
47
|
+
@compiler = compiler
|
|
48
|
+
@namespace = "#{compiler.namespace}-require-hooks-#{RequireHooks::Bootsnap.version_hash}"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def input_to_storage(source, path, *args)
|
|
52
|
+
iseq = compile(RequireHooks.context_for(path), path, nil)
|
|
53
|
+
return @compiler.input_to_storage(source, path, *args) unless iseq
|
|
36
54
|
|
|
55
|
+
iseq.to_binary
|
|
56
|
+
rescue SyntaxError, TypeError
|
|
57
|
+
::Bootsnap::CompileCache::UNCOMPILABLE
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def storage_to_output(*args)
|
|
61
|
+
@compiler.storage_to_output(*args)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def input_to_output(source, path, args)
|
|
65
|
+
compile(RequireHooks.context_for(path), path, args) || @compiler.input_to_output(source, path, args)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def compile(ctx, path, args)
|
|
37
71
|
new_contents = ctx.perform_source_transform(path)
|
|
38
72
|
hijacked = ctx.try_hijack_load(path, new_contents)
|
|
39
73
|
|
|
40
74
|
if hijacked
|
|
41
|
-
raise TypeError, "Unsupported bytecode format for #{path}: #{
|
|
42
|
-
return hijacked
|
|
43
|
-
elsif new_contents
|
|
44
|
-
return RubyVM::InstructionSequence.compile(new_contents, path, path, 1, @compile_options).to_binary
|
|
75
|
+
raise TypeError, "Unsupported bytecode format for #{path}: #{hijacked.class}" unless hijacked.is_a?(::RubyVM::InstructionSequence)
|
|
76
|
+
return hijacked
|
|
45
77
|
end
|
|
46
78
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
79
|
+
return unless new_contents
|
|
80
|
+
|
|
81
|
+
@compiler.input_to_output(new_contents, path, args) ||
|
|
82
|
+
RubyVM::InstructionSequence.compile(new_contents, path, path, 1)
|
|
50
83
|
end
|
|
51
84
|
end
|
|
52
85
|
|
|
53
86
|
module LoadIseqExt
|
|
54
|
-
class << self
|
|
55
|
-
attr_accessor :orig_cache_dir
|
|
56
|
-
end
|
|
57
|
-
|
|
58
87
|
# Around hooks must be performed every time we trigger a file load, even if
|
|
59
88
|
# the file is already cached.
|
|
60
89
|
def load_iseq(path)
|
|
61
90
|
ctx = RequireHooks.context_for(path)
|
|
62
|
-
|
|
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)
|
|
91
|
+
return super if ctx.empty?
|
|
70
92
|
|
|
71
93
|
ctx.run_around_load_callbacks(path) do
|
|
72
94
|
iseq = super
|
|
73
95
|
|
|
74
|
-
::Bootsnap::CompileCache::ISeq.cache_dir = LoadIseqExt.orig_cache_dir
|
|
75
|
-
|
|
76
96
|
# Bootsnap returns nil when the coverage is on,
|
|
77
97
|
# we fallback to our custom #compile_with_coverage
|
|
78
98
|
unless iseq
|
|
@@ -83,18 +103,34 @@ module RequireHooks
|
|
|
83
103
|
|
|
84
104
|
iseq.eval
|
|
85
105
|
EMPTY_ISEQ
|
|
86
|
-
ensure
|
|
87
|
-
::Bootsnap::CompileCache::ISeq.cache_dir = LoadIseqExt.orig_cache_dir
|
|
88
106
|
end
|
|
89
107
|
end
|
|
90
108
|
end
|
|
91
109
|
|
|
92
110
|
class << self
|
|
111
|
+
def install_compiler_selector
|
|
112
|
+
iseq = ::Bootsnap::CompileCache::ISeq
|
|
113
|
+
@original_compiler_selector = iseq.compiler_selector
|
|
114
|
+
@compilers = {}.compare_by_identity
|
|
115
|
+
iseq.compiler_selector = method(:compiler_for)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def compiler_for(path)
|
|
119
|
+
iseq = ::Bootsnap::CompileCache::ISeq
|
|
120
|
+
compiler = @original_compiler_selector&.call(path) || iseq.default_compiler
|
|
121
|
+
return compiler if RequireHooks.context_for(path).empty?
|
|
122
|
+
|
|
123
|
+
@compilers[compiler] ||= VersionedCompiler.new(compiler)
|
|
124
|
+
end
|
|
125
|
+
|
|
93
126
|
def version_hash
|
|
94
|
-
@
|
|
127
|
+
@version_hash ||= RequireHooks.contexts.values.map(&:to_cache_key).join("-")
|
|
95
128
|
end
|
|
96
129
|
|
|
97
|
-
|
|
130
|
+
def version_hash=(version_hash)
|
|
131
|
+
@version_hash = version_hash
|
|
132
|
+
@compilers&.clear
|
|
133
|
+
end
|
|
98
134
|
end
|
|
99
135
|
end
|
|
100
136
|
|
|
@@ -109,6 +145,10 @@ module RequireHooks
|
|
|
109
145
|
end
|
|
110
146
|
end
|
|
111
147
|
|
|
112
|
-
Bootsnap::CompileCache::ISeq
|
|
113
|
-
|
|
148
|
+
if defined?(Bootsnap::CompileCache::ISeq::Compiler)
|
|
149
|
+
RequireHooks::Bootsnap.install_compiler_selector
|
|
150
|
+
else
|
|
151
|
+
Bootsnap::CompileCache::ISeq.singleton_class.prepend(RequireHooks::Bootsnap::CompileCacheExt)
|
|
152
|
+
Bootsnap::CompileCache::ISeq.singleton_class.prepend(RequireHooks::Bootsnap::LegacyCacheExt)
|
|
153
|
+
end
|
|
114
154
|
RubyVM::InstructionSequence.singleton_class.prepend(RequireHooks::Bootsnap::LoadIseqExt)
|