zeitwerk 2.6.0 → 2.6.6

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.
@@ -7,11 +7,13 @@ module Zeitwerk
7
7
  require_relative "loader/helpers"
8
8
  require_relative "loader/callbacks"
9
9
  require_relative "loader/config"
10
+ require_relative "loader/eager_load"
10
11
 
11
12
  include RealModName
12
13
  include Callbacks
13
14
  include Helpers
14
15
  include Config
16
+ include EagerLoad
15
17
 
16
18
  MUTEX = Mutex.new
17
19
  private_constant :MUTEX
@@ -54,7 +56,7 @@ module Zeitwerk
54
56
  # @sig Hash[String, [String, [Module, Symbol]]]
55
57
  attr_reader :to_unload
56
58
 
57
- # Maps constant paths of namespaces to arrays of corresponding directories.
59
+ # Maps namespace constant paths to their respective directories.
58
60
  #
59
61
  # For example, given this mapping:
60
62
  #
@@ -64,13 +66,24 @@ module Zeitwerk
64
66
  # ...
65
67
  # ]
66
68
  #
67
- # when `Admin` gets defined we know that it plays the role of a namespace and
68
- # that its children are spread over those directories. We'll visit them to set
69
- # up the corresponding autoloads.
69
+ # when `Admin` gets defined we know that it plays the role of a namespace
70
+ # and that its children are spread over those directories. We'll visit them
71
+ # to set up the corresponding autoloads.
70
72
  #
71
73
  # @private
72
74
  # @sig Hash[String, Array[String]]
73
- attr_reader :lazy_subdirs
75
+ attr_reader :namespace_dirs
76
+
77
+ # A shadowed file is a file managed by this loader that is ignored when
78
+ # setting autoloads because its matching constant is already taken.
79
+ #
80
+ # This private set is populated as we descend. For example, if the loader
81
+ # has only scanned the top-level, `shadowed_files` does not have shadowed
82
+ # files that may exist deep in the project tree yet.
83
+ #
84
+ # @private
85
+ # @sig Set[String]
86
+ attr_reader :shadowed_files
74
87
 
75
88
  # @private
76
89
  # @sig Mutex
@@ -86,7 +99,8 @@ module Zeitwerk
86
99
  @autoloads = {}
87
100
  @autoloaded_dirs = []
88
101
  @to_unload = {}
89
- @lazy_subdirs = Hash.new { |h, cpath| h[cpath] = [] }
102
+ @namespace_dirs = Hash.new { |h, cpath| h[cpath] = [] }
103
+ @shadowed_files = Set.new
90
104
  @mutex = Mutex.new
91
105
  @mutex2 = Mutex.new
92
106
  @setup = false
@@ -95,15 +109,15 @@ module Zeitwerk
95
109
  Registry.register_loader(self)
96
110
  end
97
111
 
98
- # Sets autoloads in the root namespace.
112
+ # Sets autoloads in the root namespaces.
99
113
  #
100
114
  # @sig () -> void
101
115
  def setup
102
116
  mutex.synchronize do
103
117
  break if @setup
104
118
 
105
- actual_root_dirs.each do |root_dir, namespace|
106
- set_autoloads_in_dir(root_dir, namespace)
119
+ actual_roots.each do |root_dir, root_namespace|
120
+ set_autoloads_in_dir(root_dir, root_namespace)
107
121
  end
108
122
 
109
123
  on_setup_callbacks.each(&:call)
@@ -126,6 +140,8 @@ module Zeitwerk
126
140
  # @sig () -> void
127
141
  def unload
128
142
  mutex.synchronize do
143
+ raise SetupRequired unless @setup
144
+
129
145
  # We are going to keep track of the files that were required by our
130
146
  # autoloads to later remove them from $LOADED_FEATURES, thus making them
131
147
  # loadable by Kernel#require again.
@@ -181,10 +197,11 @@ module Zeitwerk
181
197
  autoloads.clear
182
198
  autoloaded_dirs.clear
183
199
  to_unload.clear
184
- lazy_subdirs.clear
200
+ namespace_dirs.clear
201
+ shadowed_files.clear
185
202
 
186
203
  Registry.on_unload(self)
187
- ExplicitNamespace.unregister_loader(self)
204
+ ExplicitNamespace.__unregister_loader(self)
188
205
 
189
206
  @setup = false
190
207
  @eager_loaded = false
@@ -201,6 +218,7 @@ module Zeitwerk
201
218
  # @sig () -> void
202
219
  def reload
203
220
  raise ReloadingDisabledError unless reloading_enabled?
221
+ raise SetupRequired unless @setup
204
222
 
205
223
  unload
206
224
  recompute_ignored_paths
@@ -208,58 +226,6 @@ module Zeitwerk
208
226
  setup
209
227
  end
210
228
 
211
- # Eager loads all files in the root directories, recursively. Files do not
212
- # need to be in `$LOAD_PATH`, absolute file names are used. Ignored files
213
- # are not eager loaded. You can opt-out specifically in specific files and
214
- # directories with `do_not_eager_load`, and that can be overridden passing
215
- # `force: true`.
216
- #
217
- # @sig (true | false) -> void
218
- def eager_load(force: false)
219
- mutex.synchronize do
220
- break if @eager_loaded
221
-
222
- log("eager load start") if logger
223
-
224
- honour_exclusions = !force
225
-
226
- queue = []
227
- actual_root_dirs.each do |root_dir, namespace|
228
- queue << [namespace, root_dir] unless honour_exclusions && excluded_from_eager_load?(root_dir)
229
- end
230
-
231
- while to_eager_load = queue.shift
232
- namespace, dir = to_eager_load
233
-
234
- ls(dir) do |basename, abspath|
235
- next if honour_exclusions && excluded_from_eager_load?(abspath)
236
-
237
- if ruby?(abspath)
238
- if cref = autoloads[abspath]
239
- cget(*cref)
240
- end
241
- elsif !root_dirs.key?(abspath)
242
- if collapse?(abspath)
243
- queue << [namespace, abspath]
244
- else
245
- cname = inflector.camelize(basename, abspath)
246
- queue << [cget(namespace, cname), abspath]
247
- end
248
- end
249
- end
250
- end
251
-
252
- autoloaded_dirs.each do |autoloaded_dir|
253
- Registry.unregister_autoload(autoloaded_dir)
254
- end
255
- autoloaded_dirs.clear
256
-
257
- @eager_loaded = true
258
-
259
- log("eager load end") if logger
260
- end
261
- end
262
-
263
229
  # Says if the given constant path would be unloaded on reload. This
264
230
  # predicate returns `false` if reloading is disabled.
265
231
  #
@@ -282,7 +248,16 @@ module Zeitwerk
282
248
  # @sig () -> void
283
249
  def unregister
284
250
  Registry.unregister_loader(self)
285
- ExplicitNamespace.unregister_loader(self)
251
+ ExplicitNamespace.__unregister_loader(self)
252
+ end
253
+
254
+ # The return value of this predicate is only meaningful if the loader has
255
+ # scanned the file. This is the case in the spots where we use it.
256
+ #
257
+ # @private
258
+ # @sig (String) -> Boolean
259
+ def shadowed_file?(file)
260
+ shadowed_files.member?(file)
286
261
  end
287
262
 
288
263
  # --- Class methods ---------------------------------------------------------------------------
@@ -311,11 +286,32 @@ module Zeitwerk
311
286
  Registry.loader_for_gem(called_from, warn_on_extra_files: warn_on_extra_files)
312
287
  end
313
288
 
314
- # Broadcasts `eager_load` to all loaders.
289
+ # Broadcasts `eager_load` to all loaders. Those that have not been setup
290
+ # are skipped.
315
291
  #
316
292
  # @sig () -> void
317
293
  def eager_load_all
318
- Registry.loaders.each(&:eager_load)
294
+ Registry.loaders.each do |loader|
295
+ begin
296
+ loader.eager_load
297
+ rescue SetupRequired
298
+ # This is fine, we eager load what can be eager loaded.
299
+ end
300
+ end
301
+ end
302
+
303
+ # Broadcasts `eager_load_namespace` to all loaders. Those that have not
304
+ # been setup are skipped.
305
+ #
306
+ # @sig (Module) -> void
307
+ def eager_load_namespace(mod)
308
+ Registry.loaders.each do |loader|
309
+ begin
310
+ loader.eager_load_namespace(mod)
311
+ rescue SetupRequired
312
+ # This is fine, we eager load what can be eager loaded.
313
+ end
314
+ end
319
315
  end
320
316
 
321
317
  # Returns an array with the absolute paths of the root directories of all
@@ -338,19 +334,11 @@ module Zeitwerk
338
334
  cname = inflector.camelize(basename, abspath).to_sym
339
335
  autoload_file(parent, cname, abspath)
340
336
  else
341
- # In a Rails application, `app/models/concerns` is a subdirectory of
342
- # `app/models`, but both of them are root directories.
343
- #
344
- # To resolve the ambiguity file name -> constant path this introduces,
345
- # the `app/models/concerns` directory is totally ignored as a namespace,
346
- # it counts only as root. The guard checks that.
347
- unless root_dir?(abspath)
337
+ if collapse?(abspath)
338
+ set_autoloads_in_dir(abspath, parent)
339
+ else
348
340
  cname = inflector.camelize(basename, abspath).to_sym
349
- if collapse?(abspath)
350
- set_autoloads_in_dir(abspath, parent)
351
- else
352
- autoload_subdir(parent, cname, abspath)
353
- end
341
+ autoload_subdir(parent, cname, abspath)
354
342
  end
355
343
  end
356
344
  rescue ::NameError => error
@@ -380,10 +368,10 @@ module Zeitwerk
380
368
  # We do not need to issue another autoload, the existing one is enough
381
369
  # no matter if it is for a file or a directory. Just remember the
382
370
  # subdirectory has to be visited if the namespace is used.
383
- lazy_subdirs[cpath] << subdir
371
+ namespace_dirs[cpath] << subdir
384
372
  elsif !cdef?(parent, cname)
385
373
  # First time we find this namespace, set an autoload for it.
386
- lazy_subdirs[cpath(parent, cname)] << subdir
374
+ namespace_dirs[cpath(parent, cname)] << subdir
387
375
  set_autoload(parent, cname, subdir)
388
376
  else
389
377
  # For whatever reason the constant that corresponds to this namespace has
@@ -398,6 +386,7 @@ module Zeitwerk
398
386
  if autoload_path = strict_autoload_path(parent, cname) || Registry.inception?(cpath(parent, cname))
399
387
  # First autoload for a Ruby file wins, just ignore subsequent ones.
400
388
  if ruby?(autoload_path)
389
+ shadowed_files << file
401
390
  log("file #{file} is ignored because #{autoload_path} has precedence") if logger
402
391
  else
403
392
  promote_namespace_from_implicit_to_explicit(
@@ -408,6 +397,7 @@ module Zeitwerk
408
397
  )
409
398
  end
410
399
  elsif cdef?(parent, cname)
400
+ shadowed_files << file
411
401
  log("file #{file} is ignored because #{cpath(parent, cname)} is already defined") if logger
412
402
  else
413
403
  set_autoload(parent, cname, file)
@@ -460,25 +450,26 @@ module Zeitwerk
460
450
 
461
451
  # @sig (String) -> void
462
452
  def register_explicit_namespace(cpath)
463
- ExplicitNamespace.register(cpath, self)
453
+ ExplicitNamespace.__register(cpath, self)
464
454
  end
465
455
 
466
456
  # @sig (String) -> void
467
457
  def raise_if_conflicting_directory(dir)
468
458
  MUTEX.synchronize do
459
+ dir_slash = dir + "/"
460
+
469
461
  Registry.loaders.each do |loader|
470
462
  next if loader == self
471
- next if loader.ignores?(dir)
463
+ next if loader.__ignores?(dir)
472
464
 
473
- dir = dir + "/"
474
- loader.root_dirs.each do |root_dir, _namespace|
465
+ loader.__roots.each_key do |root_dir|
475
466
  next if ignores?(root_dir)
476
467
 
477
- root_dir = root_dir + "/"
478
- if dir.start_with?(root_dir) || root_dir.start_with?(dir)
468
+ root_dir_slash = root_dir + "/"
469
+ if dir_slash.start_with?(root_dir_slash) || root_dir_slash.start_with?(dir_slash)
479
470
  require "pp" # Needed for pretty_inspect, even in Ruby 2.5.
480
471
  raise Error,
481
- "loader\n\n#{pretty_inspect}\n\nwants to manage directory #{dir.chop}," \
472
+ "loader\n\n#{pretty_inspect}\n\nwants to manage directory #{dir}," \
482
473
  " which is already managed by\n\n#{loader.pretty_inspect}\n"
483
474
  EOS
484
475
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zeitwerk
4
- VERSION = "2.6.0"
4
+ VERSION = "2.6.6"
5
5
  end
data/lib/zeitwerk.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Zeitwerk
4
4
  require_relative "zeitwerk/real_mod_name"
5
+ require_relative "zeitwerk/internal"
5
6
  require_relative "zeitwerk/loader"
6
7
  require_relative "zeitwerk/gem_loader"
7
8
  require_relative "zeitwerk/registry"
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.6.0
4
+ version: 2.6.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Xavier Noria
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-06-13 00:00:00.000000000 Z
11
+ date: 2022-11-08 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |2
14
14
  Zeitwerk implements constant autoloading with Ruby semantics. Each gem
@@ -28,10 +28,12 @@ files:
28
28
  - lib/zeitwerk/gem_inflector.rb
29
29
  - lib/zeitwerk/gem_loader.rb
30
30
  - lib/zeitwerk/inflector.rb
31
+ - lib/zeitwerk/internal.rb
31
32
  - lib/zeitwerk/kernel.rb
32
33
  - lib/zeitwerk/loader.rb
33
34
  - lib/zeitwerk/loader/callbacks.rb
34
35
  - lib/zeitwerk/loader/config.rb
36
+ - lib/zeitwerk/loader/eager_load.rb
35
37
  - lib/zeitwerk/loader/helpers.rb
36
38
  - lib/zeitwerk/real_mod_name.rb
37
39
  - lib/zeitwerk/registry.rb