zeitwerk 2.5.2 → 2.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +53 -9
- data/lib/zeitwerk/error.rb +3 -0
- data/lib/zeitwerk/gem_loader.rb +65 -0
- data/lib/zeitwerk/kernel.rb +5 -6
- data/lib/zeitwerk/loader/config.rb +0 -1
- data/lib/zeitwerk/loader/helpers.rb +34 -2
- data/lib/zeitwerk/loader.rb +38 -31
- data/lib/zeitwerk/registry.rb +9 -16
- data/lib/zeitwerk/version.rb +1 -1
- data/lib/zeitwerk.rb +1 -0
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d7c15f85768f681d78dd0a96b23e5bb92fe8955fe74d9ae0cefeb1b0a55b76f1
|
4
|
+
data.tar.gz: 212a4b236b7d67c9413bd583bef540c0c9c090f5d0457af38b0681405bba3244
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4feeb02e4ec695895035a39d1a46d45bd8eb77a1d72e732c28387ff16f4c3e2ac10735195fa246179cfce2fe072ada05c0f661b599fd4bc44763d300e485b12d
|
7
|
+
data.tar.gz: 0bd284cdfd10b1f27c7ad31ecd79c7bce9d76be46d3644941259ac7ee22ed425baac0cbcc340653547c873aecca6d5833c908e3009f58c4f0376be5402c9645b
|
data/README.md
CHANGED
@@ -47,6 +47,7 @@
|
|
47
47
|
- [Edge cases](#edge-cases)
|
48
48
|
- [Beware of circular dependencies](#beware-of-circular-dependencies)
|
49
49
|
- [Reopening third-party namespaces](#reopening-third-party-namespaces)
|
50
|
+
- [Encodings](#encodings)
|
50
51
|
- [Rules of thumb](#rules-of-thumb)
|
51
52
|
- [Debuggers](#debuggers)
|
52
53
|
- [debug.rb](#debugrb)
|
@@ -58,6 +59,7 @@
|
|
58
59
|
- [Motivation](#motivation)
|
59
60
|
- [Kernel#require is brittle](#kernelrequire-is-brittle)
|
60
61
|
- [Rails autoloading was brittle](#rails-autoloading-was-brittle)
|
62
|
+
- [Awards](#awards)
|
61
63
|
- [Thanks](#thanks)
|
62
64
|
- [License](#license)
|
63
65
|
|
@@ -237,13 +239,17 @@ should define `Geolocatable`, not `Concerns::Geolocatable`.
|
|
237
239
|
<a id="markdown-implicit-namespaces" name="implicit-namespaces"></a>
|
238
240
|
### Implicit namespaces
|
239
241
|
|
240
|
-
Directories without a matching Ruby file get modules
|
242
|
+
If a namespace is just a simple module with no code, you do not need to define it in a file: Directories without a matching Ruby file get modules created automatically on your behalf.
|
243
|
+
|
244
|
+
For example, if a project has an `admin` directory:
|
241
245
|
|
242
246
|
```
|
243
247
|
app/controllers/admin/users_controller.rb -> Admin::UsersController
|
244
248
|
```
|
245
249
|
|
246
|
-
|
250
|
+
and does not have a file called `admin.rb`, Zeitwerk automatically creates an `Admin` module on your behalf the first time `Admin` is used.
|
251
|
+
|
252
|
+
For this to happen, the directory has to contain non-ignored Ruby files, directly or recursively, otherwise it is ignored. This condition is evaluated again on reloads.
|
247
253
|
|
248
254
|
<a id="markdown-explicit-namespaces" name="explicit-namespaces"></a>
|
249
255
|
### Explicit namespaces
|
@@ -349,8 +355,6 @@ lib/my_gem/foo.rb # MyGem::Foo
|
|
349
355
|
|
350
356
|
Neither a gemspec nor a version file are technically required, this helper works as long as the code is organized using that standard structure.
|
351
357
|
|
352
|
-
If the entry point of your gem lives in a subdirectory of `lib` because it is reopening a namespace defined somewhere else, please use the generic API to setup the loader, and make sure you check the section [_Reopening third-party namespaces_](https://github.com/fxn/zeitwerk#reopening-third-party-namespaces) down below.
|
353
|
-
|
354
358
|
Conceptually, `for_gem` translates to:
|
355
359
|
|
356
360
|
```ruby
|
@@ -363,8 +367,6 @@ loader.inflector = Zeitwerk::GemInflector.new(__FILE__)
|
|
363
367
|
loader.push_dir(__dir__)
|
364
368
|
```
|
365
369
|
|
366
|
-
except that this method returns the same object in subsequent calls from the same file, in the unlikely case the gem wants to be able to reload.
|
367
|
-
|
368
370
|
If the main module references project constants at the top-level, Zeitwerk has to be ready to load them. Their definitions, in turn, may reference other project constants. And this is recursive. Therefore, it is important that the `setup` call happens above the main module definition:
|
369
371
|
|
370
372
|
```ruby
|
@@ -381,6 +383,26 @@ module MyGem
|
|
381
383
|
end
|
382
384
|
```
|
383
385
|
|
386
|
+
Loaders returned by `Zeitwerk::Loader.for_gem` issue warnings if `lib` has extra Ruby files or directories.
|
387
|
+
|
388
|
+
For example, if the gem has Rails generators under `lib/generators`, by convention that directory defines a `Generators` Ruby module. If `generators` is just a container for non-autoloadable code and templates, not acting as a project namespace, you need to setup things accordingly.
|
389
|
+
|
390
|
+
If the warning is legit, just tell the loader to ignore the offending file or directory:
|
391
|
+
|
392
|
+
```ruby
|
393
|
+
loader.ignore("#{__dir__}/generators")
|
394
|
+
```
|
395
|
+
|
396
|
+
Otherwise, there's a flag to say the extra stuff is OK:
|
397
|
+
|
398
|
+
```ruby
|
399
|
+
Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
|
400
|
+
```
|
401
|
+
|
402
|
+
This method is idempotent when invoked from the same file, to support gems that want to reload (unlikely).
|
403
|
+
|
404
|
+
If the entry point of your gem lives in a subdirectory of `lib` because it is reopening a namespace defined somewhere else, please use the generic API to setup the loader, and make sure you check the section [_Reopening third-party namespaces_](https://github.com/fxn/zeitwerk#reopening-third-party-namespaces) down below.
|
405
|
+
|
384
406
|
<a id="markdown-autoloading" name="autoloading"></a>
|
385
407
|
### Autoloading
|
386
408
|
|
@@ -437,7 +459,7 @@ However, that can be overridden with `force`:
|
|
437
459
|
loader.eager_load(force: true) # database adapters are eager loaded
|
438
460
|
```
|
439
461
|
|
440
|
-
Which may be handy if the project eager loads in the test suite to [ensure project
|
462
|
+
Which may be handy if the project eager loads in the test suite to [ensure project layout compliance](#testing-compliance).
|
441
463
|
|
442
464
|
The `force` flag does not affect ignored files and directories, those are still ignored.
|
443
465
|
|
@@ -480,9 +502,14 @@ Reloading removes the currently loaded classes and modules and resets the loader
|
|
480
502
|
|
481
503
|
It is important to highlight that this is an instance method. Don't worry about project dependencies managed by Zeitwerk, their loaders are independent.
|
482
504
|
|
483
|
-
|
505
|
+
Reloading is not thread-safe:
|
484
506
|
|
485
|
-
|
507
|
+
* You should not reload while another thread is reloading.
|
508
|
+
* You should not autoload while another thread is reloading.
|
509
|
+
|
510
|
+
In order to reload in a thread-safe manner, frameworks need to implement some coordination. For example, a web framework that serves each request with its own thread may have a globally accessible read/write lock: When a request comes in, the framework acquires the lock for reading at the beginning, and releases it at the end. On the other hand, the code in the framework responsible for the call to `Zeitwerk::Loader#reload` needs to acquire the lock for writing.
|
511
|
+
|
512
|
+
On reloading, client code has to update anything that would otherwise be storing a stale object. For example, if the routing layer of a web framework stores reloadable controller class objects or instances in internal structures, on reload it has to refresh them somehow, possibly reevaluating routes.
|
486
513
|
|
487
514
|
<a id="markdown-inflection" name="inflection"></a>
|
488
515
|
### Inflection
|
@@ -962,6 +989,18 @@ loader.setup
|
|
962
989
|
|
963
990
|
With that, when Zeitwerk scans the file system and reaches the gem directories `lib/active_job` and `lib/active_job/queue_adapters`, it detects the corresponding modules already exist and therefore understands it does not have to manage them. The loader just descends into those directories. Eventually will reach `lib/active_job/queue_adapters/awesome_queue.rb`, and since `ActiveJob::QueueAdapters::AwesomeQueue` is unknown, Zeitwerk will manage it. Which is what happens regularly with the files in your gem. On reload, the namespaces are safe, won't be reloaded. The loader only reloads what it manages, which in this case is the adapter itself.
|
964
991
|
|
992
|
+
<a id="markdown-encodings" name="encodings"></a>
|
993
|
+
### Encodings
|
994
|
+
|
995
|
+
Zeitwerk supports projects whose files and file system are in UTF-8. The encoding of the file system can be checked this way:
|
996
|
+
|
997
|
+
```
|
998
|
+
% ruby -e "puts Encoding.find('filesystem')"
|
999
|
+
UTF-8
|
1000
|
+
```
|
1001
|
+
|
1002
|
+
The test suite passes on Windows with codepage `Windows-1252` if all the involved absolute paths are ASCII. Other supersets of ASCII may work too, but you have to try.
|
1003
|
+
|
965
1004
|
<a id="markdown-rules-of-thumb" name="rules-of-thumb"></a>
|
966
1005
|
### Rules of thumb
|
967
1006
|
|
@@ -1054,6 +1093,11 @@ With Zeitwerk, you just name things following conventions and done. Things are a
|
|
1054
1093
|
|
1055
1094
|
Autoloading in Rails was based on `const_missing` up to Rails 5. That callback lacks fundamental information like the nesting or the resolution algorithm being used. Because of that, Rails autoloading was not able to match Ruby's semantics, and that introduced a [series of issues](https://guides.rubyonrails.org/v5.2/autoloading_and_reloading_constants.html#common-gotchas). Zeitwerk is based on a different technique and fixed Rails autoloading starting with Rails 6.
|
1056
1095
|
|
1096
|
+
<a id="markdown-awards" name="awards"></a>
|
1097
|
+
## Awards
|
1098
|
+
|
1099
|
+
Zeitwerk has been awarded an "Outstanding Performance Award" Fukuoka Ruby Award 2022.
|
1100
|
+
|
1057
1101
|
<a id="markdown-thanks" name="thanks"></a>
|
1058
1102
|
## Thanks
|
1059
1103
|
|
data/lib/zeitwerk/error.rb
CHANGED
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Zeitwerk
|
4
|
+
# @private
|
5
|
+
class GemLoader < Loader
|
6
|
+
# Users should not create instances directly, the public interface is
|
7
|
+
# `Zeitwerk::Loader.for_gem`.
|
8
|
+
private_class_method :new
|
9
|
+
|
10
|
+
# @private
|
11
|
+
# @sig (String, bool) -> Zeitwerk::GemLoader
|
12
|
+
def self._new(root_file, warn_on_extra_files:)
|
13
|
+
new(root_file, warn_on_extra_files: warn_on_extra_files)
|
14
|
+
end
|
15
|
+
|
16
|
+
# @sig (String, bool) -> void
|
17
|
+
def initialize(root_file, warn_on_extra_files:)
|
18
|
+
super()
|
19
|
+
|
20
|
+
@tag = File.basename(root_file, ".rb")
|
21
|
+
@inflector = GemInflector.new(root_file)
|
22
|
+
@root_file = File.expand_path(root_file)
|
23
|
+
@lib = File.dirname(root_file)
|
24
|
+
@warn_on_extra_files = warn_on_extra_files
|
25
|
+
|
26
|
+
push_dir(@lib)
|
27
|
+
end
|
28
|
+
|
29
|
+
# @sig () -> void
|
30
|
+
def setup
|
31
|
+
warn_on_extra_files if @warn_on_extra_files
|
32
|
+
super
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
# @sig () -> void
|
38
|
+
def warn_on_extra_files
|
39
|
+
expected_namespace_dir = @root_file.delete_suffix(".rb")
|
40
|
+
|
41
|
+
ls(@lib) do |basename, abspath|
|
42
|
+
next if abspath == @root_file
|
43
|
+
next if abspath == expected_namespace_dir
|
44
|
+
|
45
|
+
basename_without_ext = basename.delete_suffix(".rb")
|
46
|
+
cname = inflector.camelize(basename_without_ext, abspath)
|
47
|
+
ftype = dir?(abspath) ? "directory" : "file"
|
48
|
+
|
49
|
+
warn(<<~EOS)
|
50
|
+
WARNING: Zeitwerk defines the constant #{cname} after the #{ftype}
|
51
|
+
|
52
|
+
#{abspath}
|
53
|
+
|
54
|
+
To prevent that, please configure the loader to ignore it:
|
55
|
+
|
56
|
+
loader.ignore("\#{__dir__}/#{basename}")
|
57
|
+
|
58
|
+
Otherwise, there is a flag to silence this warning:
|
59
|
+
|
60
|
+
Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
|
61
|
+
EOS
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/zeitwerk/kernel.rb
CHANGED
@@ -3,11 +3,11 @@
|
|
3
3
|
module Kernel
|
4
4
|
module_function
|
5
5
|
|
6
|
-
#
|
6
|
+
# Zeitwerk's main idea is to define autoloads for project constants, and then
|
7
|
+
# intercept them when triggered in this thin `Kernel#require` wrapper.
|
7
8
|
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
# managed files are loaded.
|
9
|
+
# That allows us to complete the circle, invoke callbacks, autovivify modules,
|
10
|
+
# define autoloads for just autoloaded namespaces, update internal state, etc.
|
11
11
|
#
|
12
12
|
# On the other hand, if you publish a new version of a gem that is now managed
|
13
13
|
# by Zeitwerk, client code can reference directly your classes and modules and
|
@@ -17,7 +17,7 @@ module Kernel
|
|
17
17
|
#
|
18
18
|
# We cannot decorate with prepend + super because Kernel has already been
|
19
19
|
# included in Object, and changes in ancestors don't get propagated into
|
20
|
-
# already existing ancestor chains.
|
20
|
+
# already existing ancestor chains on Ruby < 3.0.
|
21
21
|
alias_method :zeitwerk_original_require, :require
|
22
22
|
|
23
23
|
# @sig (String) -> true | false
|
@@ -29,7 +29,6 @@ module Kernel
|
|
29
29
|
required
|
30
30
|
else
|
31
31
|
loader.on_dir_autoloaded(path)
|
32
|
-
$LOADED_FEATURES << path
|
33
32
|
true
|
34
33
|
end
|
35
34
|
else
|
@@ -15,18 +15,50 @@ module Zeitwerk::Loader::Helpers
|
|
15
15
|
|
16
16
|
# @sig (String) { (String, String) -> void } -> void
|
17
17
|
def ls(dir)
|
18
|
-
Dir.
|
18
|
+
children = Dir.children(dir)
|
19
|
+
|
20
|
+
# The order in which a directory is listed depends on the file system.
|
21
|
+
#
|
22
|
+
# Since client code may run in different platforms, it seems convenient to
|
23
|
+
# order directory entries. This provides consistent eager loading across
|
24
|
+
# platforms, for example.
|
25
|
+
children.sort!
|
26
|
+
|
27
|
+
children.each do |basename|
|
19
28
|
next if hidden?(basename)
|
20
29
|
|
21
30
|
abspath = File.join(dir, basename)
|
22
31
|
next if ignored_paths.member?(abspath)
|
23
32
|
|
33
|
+
if dir?(abspath)
|
34
|
+
next unless has_at_least_one_ruby_file?(abspath)
|
35
|
+
else
|
36
|
+
next unless ruby?(abspath)
|
37
|
+
end
|
38
|
+
|
24
39
|
# We freeze abspath because that saves allocations when passed later to
|
25
40
|
# File methods. See #125.
|
26
41
|
yield basename, abspath.freeze
|
27
42
|
end
|
28
43
|
end
|
29
44
|
|
45
|
+
# @sig (String) -> bool
|
46
|
+
def has_at_least_one_ruby_file?(dir)
|
47
|
+
to_visit = [dir]
|
48
|
+
|
49
|
+
while dir = to_visit.shift
|
50
|
+
ls(dir) do |_basename, abspath|
|
51
|
+
if dir?(abspath)
|
52
|
+
to_visit << abspath
|
53
|
+
else
|
54
|
+
return true
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
false
|
60
|
+
end
|
61
|
+
|
30
62
|
# @sig (String) -> bool
|
31
63
|
def ruby?(path)
|
32
64
|
path.end_with?(".rb")
|
@@ -37,7 +69,7 @@ module Zeitwerk::Loader::Helpers
|
|
37
69
|
File.directory?(path)
|
38
70
|
end
|
39
71
|
|
40
|
-
# @sig String -> bool
|
72
|
+
# @sig (String) -> bool
|
41
73
|
def hidden?(basename)
|
42
74
|
basename.start_with?(".")
|
43
75
|
end
|
data/lib/zeitwerk/loader.rb
CHANGED
@@ -13,6 +13,9 @@ module Zeitwerk
|
|
13
13
|
include Helpers
|
14
14
|
include Config
|
15
15
|
|
16
|
+
MUTEX = Mutex.new
|
17
|
+
private_constant :MUTEX
|
18
|
+
|
16
19
|
# Maps absolute paths for which an autoload has been set ---and not
|
17
20
|
# executed--- to their corresponding parent class or module and constant
|
18
21
|
# name.
|
@@ -123,7 +126,13 @@ module Zeitwerk
|
|
123
126
|
# @sig () -> void
|
124
127
|
def unload
|
125
128
|
mutex.synchronize do
|
126
|
-
|
129
|
+
# We are going to keep track of the files that were required by our
|
130
|
+
# autoloads to later remove them from $LOADED_FEATURES, thus making them
|
131
|
+
# loadable by Kernel#require again.
|
132
|
+
#
|
133
|
+
# Directories are not stored in $LOADED_FEATURES, keeping track of files
|
134
|
+
# is enough.
|
135
|
+
unloaded_files = Set.new
|
127
136
|
|
128
137
|
autoloads.each do |abspath, (parent, cname)|
|
129
138
|
if parent.autoload?(cname)
|
@@ -133,25 +142,28 @@ module Zeitwerk
|
|
133
142
|
# and the constant path would escape unloadable_cpath? This is just
|
134
143
|
# defensive code to clean things up as much as we are able to.
|
135
144
|
unload_cref(parent, cname)
|
136
|
-
|
145
|
+
unloaded_files.add(abspath) if ruby?(abspath)
|
137
146
|
end
|
138
147
|
end
|
139
148
|
|
140
149
|
to_unload.each do |cpath, (abspath, (parent, cname))|
|
141
150
|
unless on_unload_callbacks.empty?
|
142
|
-
|
143
|
-
|
151
|
+
begin
|
152
|
+
value = cget(parent, cname)
|
153
|
+
rescue ::NameError
|
154
|
+
# Perhaps the user deleted the constant by hand, or perhaps an
|
155
|
+
# autoload failed to define the expected constant but the user
|
156
|
+
# rescued the exception.
|
157
|
+
else
|
158
|
+
run_on_unload_callbacks(cpath, value, abspath)
|
159
|
+
end
|
144
160
|
end
|
145
161
|
|
146
162
|
unload_cref(parent, cname)
|
147
|
-
|
163
|
+
unloaded_files.add(abspath) if ruby?(abspath)
|
148
164
|
end
|
149
165
|
|
150
|
-
unless
|
151
|
-
# We remove these abspaths from $LOADED_FEATURES because, otherwise,
|
152
|
-
# `require`'s idempotence would prevent newly defined autoloads from
|
153
|
-
# loading them again.
|
154
|
-
#
|
166
|
+
unless unloaded_files.empty?
|
155
167
|
# Bootsnap decorates Kernel#require to speed it up using a cache and
|
156
168
|
# this optimization does not check if $LOADED_FEATURES has the file.
|
157
169
|
#
|
@@ -163,7 +175,7 @@ module Zeitwerk
|
|
163
175
|
# Rails applications may depend on bootsnap, so for unloading to work
|
164
176
|
# in that setting it is preferable that we restrict our API choice to
|
165
177
|
# one of those methods.
|
166
|
-
$LOADED_FEATURES.reject! { |file|
|
178
|
+
$LOADED_FEATURES.reject! { |file| unloaded_files.member?(file) }
|
167
179
|
end
|
168
180
|
|
169
181
|
autoloads.clear
|
@@ -188,14 +200,12 @@ module Zeitwerk
|
|
188
200
|
# @raise [Zeitwerk::Error]
|
189
201
|
# @sig () -> void
|
190
202
|
def reload
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
raise ReloadingDisabledError, "can't reload, please call loader.enable_reloading before setup"
|
198
|
-
end
|
203
|
+
raise ReloadingDisabledError unless reloading_enabled?
|
204
|
+
|
205
|
+
unload
|
206
|
+
recompute_ignored_paths
|
207
|
+
recompute_collapse_dirs
|
208
|
+
setup
|
199
209
|
end
|
200
210
|
|
201
211
|
# Eager loads all files in the root directories, recursively. Files do not
|
@@ -228,7 +238,7 @@ module Zeitwerk
|
|
228
238
|
if cref = autoloads[abspath]
|
229
239
|
cget(*cref)
|
230
240
|
end
|
231
|
-
elsif
|
241
|
+
elsif !root_dirs.key?(abspath)
|
232
242
|
if collapse?(abspath)
|
233
243
|
queue << [namespace, abspath]
|
234
244
|
else
|
@@ -281,10 +291,6 @@ module Zeitwerk
|
|
281
291
|
# @sig #call | #debug | nil
|
282
292
|
attr_accessor :default_logger
|
283
293
|
|
284
|
-
# @private
|
285
|
-
# @sig Mutex
|
286
|
-
attr_accessor :mutex
|
287
|
-
|
288
294
|
# This is a shortcut for
|
289
295
|
#
|
290
296
|
# require "zeitwerk"
|
@@ -296,10 +302,13 @@ module Zeitwerk
|
|
296
302
|
# except that this method returns the same object in subsequent calls from
|
297
303
|
# the same file, in the unlikely case the gem wants to be able to reload.
|
298
304
|
#
|
299
|
-
#
|
300
|
-
|
305
|
+
# This method returns a subclass of Zeitwerk::Loader, but the exact type
|
306
|
+
# is private, client code can only rely on the interface.
|
307
|
+
#
|
308
|
+
# @sig (bool) -> Zeitwerk::GemLoader
|
309
|
+
def for_gem(warn_on_extra_files: true)
|
301
310
|
called_from = caller_locations(1, 1).first.path
|
302
|
-
Registry.loader_for_gem(called_from)
|
311
|
+
Registry.loader_for_gem(called_from, warn_on_extra_files: warn_on_extra_files)
|
303
312
|
end
|
304
313
|
|
305
314
|
# Broadcasts `eager_load` to all loaders.
|
@@ -318,8 +327,6 @@ module Zeitwerk
|
|
318
327
|
end
|
319
328
|
end
|
320
329
|
|
321
|
-
self.mutex = Mutex.new
|
322
|
-
|
323
330
|
private # -------------------------------------------------------------------------------------
|
324
331
|
|
325
332
|
# @sig (String, Module) -> void
|
@@ -330,7 +337,7 @@ module Zeitwerk
|
|
330
337
|
basename.delete_suffix!(".rb")
|
331
338
|
cname = inflector.camelize(basename, abspath).to_sym
|
332
339
|
autoload_file(parent, cname, abspath)
|
333
|
-
|
340
|
+
else
|
334
341
|
# In a Rails application, `app/models/concerns` is a subdirectory of
|
335
342
|
# `app/models`, but both of them are root directories.
|
336
343
|
#
|
@@ -458,7 +465,7 @@ module Zeitwerk
|
|
458
465
|
|
459
466
|
# @sig (String) -> void
|
460
467
|
def raise_if_conflicting_directory(dir)
|
461
|
-
|
468
|
+
MUTEX.synchronize do
|
462
469
|
Registry.loaders.each do |loader|
|
463
470
|
next if loader == self
|
464
471
|
next if loader.ignores?(dir)
|
data/lib/zeitwerk/registry.rb
CHANGED
@@ -10,12 +10,11 @@ module Zeitwerk
|
|
10
10
|
# @sig Array[Zeitwerk::Loader]
|
11
11
|
attr_reader :loaders
|
12
12
|
|
13
|
-
# Registers loaders
|
14
|
-
# in case of reload.
|
13
|
+
# Registers gem loaders to let `for_gem` be idempotent in case of reload.
|
15
14
|
#
|
16
15
|
# @private
|
17
16
|
# @sig Hash[String, Zeitwerk::Loader]
|
18
|
-
attr_reader :
|
17
|
+
attr_reader :gem_loaders_by_root_file
|
19
18
|
|
20
19
|
# Maps absolute paths to the loaders responsible for them.
|
21
20
|
#
|
@@ -77,7 +76,7 @@ module Zeitwerk
|
|
77
76
|
# @sig (Zeitwerk::Loader) -> void
|
78
77
|
def unregister_loader(loader)
|
79
78
|
loaders.delete(loader)
|
80
|
-
|
79
|
+
gem_loaders_by_root_file.delete_if { |_, l| l == loader }
|
81
80
|
autoloads.delete_if { |_, l| l == loader }
|
82
81
|
inceptions.delete_if { |_, (_, l)| l == loader }
|
83
82
|
end
|
@@ -87,14 +86,8 @@ module Zeitwerk
|
|
87
86
|
#
|
88
87
|
# @private
|
89
88
|
# @sig (String) -> Zeitwerk::Loader
|
90
|
-
def loader_for_gem(root_file)
|
91
|
-
|
92
|
-
Loader.new.tap do |loader|
|
93
|
-
loader.tag = File.basename(root_file, ".rb")
|
94
|
-
loader.inflector = GemInflector.new(root_file)
|
95
|
-
loader.push_dir(File.dirname(root_file))
|
96
|
-
end
|
97
|
-
end
|
89
|
+
def loader_for_gem(root_file, warn_on_extra_files:)
|
90
|
+
gem_loaders_by_root_file[root_file] ||= GemLoader._new(root_file, warn_on_extra_files: warn_on_extra_files)
|
98
91
|
end
|
99
92
|
|
100
93
|
# @private
|
@@ -137,9 +130,9 @@ module Zeitwerk
|
|
137
130
|
end
|
138
131
|
end
|
139
132
|
|
140
|
-
@loaders
|
141
|
-
@
|
142
|
-
@autoloads
|
143
|
-
@inceptions
|
133
|
+
@loaders = []
|
134
|
+
@gem_loaders_by_root_file = {}
|
135
|
+
@autoloads = {}
|
136
|
+
@inceptions = {}
|
144
137
|
end
|
145
138
|
end
|
data/lib/zeitwerk/version.rb
CHANGED
data/lib/zeitwerk.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zeitwerk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Xavier Noria
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-06-13 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: |2
|
14
14
|
Zeitwerk implements constant autoloading with Ruby semantics. Each gem
|
@@ -26,6 +26,7 @@ files:
|
|
26
26
|
- lib/zeitwerk/error.rb
|
27
27
|
- lib/zeitwerk/explicit_namespace.rb
|
28
28
|
- lib/zeitwerk/gem_inflector.rb
|
29
|
+
- lib/zeitwerk/gem_loader.rb
|
29
30
|
- lib/zeitwerk/inflector.rb
|
30
31
|
- lib/zeitwerk/kernel.rb
|
31
32
|
- lib/zeitwerk/loader.rb
|
@@ -58,7 +59,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
58
59
|
- !ruby/object:Gem::Version
|
59
60
|
version: '0'
|
60
61
|
requirements: []
|
61
|
-
rubygems_version: 3.
|
62
|
+
rubygems_version: 3.3.3
|
62
63
|
signing_key:
|
63
64
|
specification_version: 4
|
64
65
|
summary: Efficient and thread-safe constant autoloader
|