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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: edfc3d6e8c62e95f5a008e6625b5b7f2ff7e0067e3d311ec352e1bf6c6922c27
4
- data.tar.gz: 9087c6cc6524a8fd2b6843c283496e29cc5d9157421f6ba7994feb362f383333
3
+ metadata.gz: d7c15f85768f681d78dd0a96b23e5bb92fe8955fe74d9ae0cefeb1b0a55b76f1
4
+ data.tar.gz: 212a4b236b7d67c9413bd583bef540c0c9c090f5d0457af38b0681405bba3244
5
5
  SHA512:
6
- metadata.gz: 4d31bf02d352d61024f553f21b14093bc4d8cfc9276c106e8a67b499044aaf12bdd076288c89828b342a1980f163706b62c82d9bd21c15cdc4cadacf117e5299
7
- data.tar.gz: 89cc85a2efeb95760de195fc2acf854256dba89cdee2208992c33ba5d8cb78ad71adaddc757026660dbc4a2d562e1efa623572b91d2107e9cf30020ea83524ab
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 autovivified automatically by Zeitwerk. For example, in
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
- `Admin` is autovivified as a module on demand, you do not need to define an `Admin` class or module in an `admin.rb` file explicitly.
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 layour compliance](#testing-compliance).
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
- In order for reloading to be thread-safe, you need to implement some coordination. For example, a web framework that serves each request with its own thread may have a globally accessible RW lock. When a request comes in, the framework acquires the lock for reading at the beginning, and the code in the framework that calls `loader.reload` needs to acquire the lock for writing.
505
+ Reloading is not thread-safe:
484
506
 
485
- 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 controller class objects or instances in internal structures, on reload it has to refresh them somehow, possibly reevaluating routes.
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
 
@@ -5,6 +5,9 @@ module Zeitwerk
5
5
  end
6
6
 
7
7
  class ReloadingDisabledError < Error
8
+ def initialize
9
+ super("can't reload, please call loader.enable_reloading before setup")
10
+ end
8
11
  end
9
12
 
10
13
  class NameError < ::NameError
@@ -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
@@ -3,11 +3,11 @@
3
3
  module Kernel
4
4
  module_function
5
5
 
6
- # We are going to decorate Kernel#require with two goals.
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
- # First, by intercepting Kernel#require calls, we are able to autovivify
9
- # modules on required directories, and also do internal housekeeping when
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
@@ -131,7 +131,6 @@ module Zeitwerk::Loader::Config
131
131
 
132
132
  # Sets a tag for the loader, useful for logging.
133
133
  #
134
- # @param tag [#to_s]
135
134
  # @sig (#to_s) -> void
136
135
  def tag=(tag)
137
136
  @tag = tag.to_s
@@ -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.each_child(dir) do |basename|
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
@@ -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
- abspaths_of_unloaded_crefs = Set.new
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
- abspaths_of_unloaded_crefs.add(abspath)
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
- value = parent.const_get(cname)
143
- run_on_unload_callbacks(cpath, value, abspath)
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
- abspaths_of_unloaded_crefs.add(abspath)
163
+ unloaded_files.add(abspath) if ruby?(abspath)
148
164
  end
149
165
 
150
- unless abspaths_of_unloaded_crefs.empty?
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| abspaths_of_unloaded_crefs.member?(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
- if reloading_enabled?
192
- unload
193
- recompute_ignored_paths
194
- recompute_collapse_dirs
195
- setup
196
- else
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 dir?(abspath) && !root_dirs.key?(abspath)
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
- # @sig () -> Zeitwerk::Loader
300
- def for_gem
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
- elsif dir?(abspath)
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
- self.class.mutex.synchronize do
468
+ MUTEX.synchronize do
462
469
  Registry.loaders.each do |loader|
463
470
  next if loader == self
464
471
  next if loader.ignores?(dir)
@@ -10,12 +10,11 @@ module Zeitwerk
10
10
  # @sig Array[Zeitwerk::Loader]
11
11
  attr_reader :loaders
12
12
 
13
- # Registers loaders created with `for_gem` to make the method idempotent
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 :loaders_managing_gems
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
- loaders_managing_gems.delete_if { |_, l| l == loader }
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
- loaders_managing_gems[root_file] ||= begin
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
- @loaders_managing_gems = {}
142
- @autoloads = {}
143
- @inceptions = {}
133
+ @loaders = []
134
+ @gem_loaders_by_root_file = {}
135
+ @autoloads = {}
136
+ @inceptions = {}
144
137
  end
145
138
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zeitwerk
4
- VERSION = "2.5.2"
4
+ VERSION = "2.6.0"
5
5
  end
data/lib/zeitwerk.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  module Zeitwerk
4
4
  require_relative "zeitwerk/real_mod_name"
5
5
  require_relative "zeitwerk/loader"
6
+ require_relative "zeitwerk/gem_loader"
6
7
  require_relative "zeitwerk/registry"
7
8
  require_relative "zeitwerk/explicit_namespace"
8
9
  require_relative "zeitwerk/inflector"
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.5.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: 2021-12-27 00:00:00.000000000 Z
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.2.22
62
+ rubygems_version: 3.3.3
62
63
  signing_key:
63
64
  specification_version: 4
64
65
  summary: Efficient and thread-safe constant autoloader