require-hooks 0.4.1 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5179f6010729ac30b40c0040f2d00956164c8f765d41e2d095eeefa9b96da842
4
- data.tar.gz: f836cd71ec64084e4145ac572a555b7a32b7abdacd7d7d25965766bca2d264bc
3
+ metadata.gz: 9357c7e14301ad7ab541c2e2fabbda1f03df56e748aee55245dfc44ef98519f4
4
+ data.tar.gz: 87b05cf3ec16a3c9c4e9c1a46a3ec267ca03717923fe8abdccd4e46deb212791
5
5
  SHA512:
6
- metadata.gz: 93559e1cc1b84d23e17b60abb8f92e47c67ea6f66f1dcbc4243b34f3d68345674c96da73aa890ba8c79e226209877a48aa4438343a51ba3ce736160403597abb
7
- data.tar.gz: 4c36e011286728aee0f43a81411875c38fee956e34105c4dcba87bc4cf52aaee6a2d4e97d332bd2727551c85bc009ddba646b3a9764ca19c3521b7687bf43a1c
6
+ metadata.gz: a9e828449e13517a11c03d9521c4170507f08862e6a4e75fa01360a2c17191dbecfa3a745a1433bd7546f190c1599f17be7a4dc76869bbd798fd1e47e859a130
7
+ data.tar.gz: e1e80635ac8c7ceb90c1580797145c3c15636d463635f3ccf57578024619af150f4dbe94eb279aff7dc62c231ed1de3733b5db3e7a1b38a21002e8ef0ca8a063
data/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
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
+
5
9
  ## 0.4.1 (2026-07-22)
6
10
 
7
11
  - Bring back Ruby 2.3 compatibility.
data/README.md CHANGED
@@ -124,7 +124,7 @@ 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
- Thus, if you introduce new source transformers or hijackers, you must invalidate the cache. (We plan to implement automatic invalidation in future versions.)
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
 
@@ -28,75 +28,109 @@ module RequireHooks
28
28
  end
29
29
  end
30
30
 
31
- # For new Bootsnap
32
- module CompilerExt
33
- def input_to_storage(source, path, *)
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
- return super if ctx.empty?
35
+ cache_dir = File.join(cache_dir, RequireHooks::Bootsnap.version_hash) unless ctx.empty?
36
+
37
+ super
38
+ end
39
+ end
36
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
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}: #{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
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
- super
48
- rescue SyntaxError, TypeError
49
- ::Bootsnap::CompileCache::UNCOMPILABLE
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
- # Early-return for non-trackable paths
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
- begin
73
- iseq = super
94
+ iseq = super
74
95
 
75
- ::Bootsnap::CompileCache::ISeq.cache_dir = LoadIseqExt.orig_cache_dir
96
+ # Bootsnap returns nil when the coverage is on,
97
+ # we fallback to our custom #compile_with_coverage
98
+ unless iseq
99
+ next unless defined?(Coverage) && Coverage.running?
76
100
 
77
- # Bootsnap returns nil when the coverage is on,
78
- # we fallback to our custom #compile_with_coverage
79
- unless iseq
80
- next unless defined?(Coverage) && Coverage.running?
81
-
82
- iseq = RequireHooks::Iseq.compile_with_coverage(ctx, path)
83
- end
84
-
85
- iseq.eval
86
- EMPTY_ISEQ
87
- ensure
88
- ::Bootsnap::CompileCache::ISeq.cache_dir = LoadIseqExt.orig_cache_dir
101
+ iseq = RequireHooks::Iseq.compile_with_coverage(ctx, path)
89
102
  end
103
+
104
+ iseq.eval
105
+ EMPTY_ISEQ
90
106
  end
91
107
  end
92
108
  end
93
109
 
94
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
+
95
126
  def version_hash
96
- @version_key ||= RequireHooks.contexts.values.map(&:to_cache_key).join("-")
127
+ @version_hash ||= RequireHooks.contexts.values.map(&:to_cache_key).join("-")
97
128
  end
98
129
 
99
- attr_writer :version_hash
130
+ def version_hash=(version_hash)
131
+ @version_hash = version_hash
132
+ @compilers&.clear
133
+ end
100
134
  end
101
135
  end
102
136
 
@@ -111,6 +145,10 @@ module RequireHooks
111
145
  end
112
146
  end
113
147
 
114
- Bootsnap::CompileCache::ISeq.singleton_class.prepend(RequireHooks::Bootsnap::CompileCacheExt)
115
- Bootsnap::CompileCache::ISeq::Compiler.prepend(RequireHooks::Bootsnap::CompilerExt) if defined?(Bootsnap::CompileCache::ISeq::Compiler)
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
116
154
  RubyVM::InstructionSequence.singleton_class.prepend(RequireHooks::Bootsnap::LoadIseqExt)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RequireHooks
4
- VERSION = "0.4.1"
4
+ VERSION = "0.5.0"
5
5
  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.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vladimir Dementyev