zeitwerk 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,71 @@
1
+ module Zeitwerk::Loader::Callbacks
2
+ include Zeitwerk::RealModName
3
+
4
+ # Invoked from our decorated Kernel#require when a managed file is autoloaded.
5
+ #
6
+ # @private
7
+ # @param file [String]
8
+ # @return [void]
9
+ def on_file_autoloaded(file)
10
+ cref = autoloads.delete(file)
11
+ to_unload[cpath(*cref)] = [file, cref] if reloading_enabled?
12
+ Zeitwerk::Registry.unregister_autoload(file)
13
+
14
+ if logger && cdef?(*cref)
15
+ log("constant #{cpath(*cref)} loaded from file #{file}")
16
+ elsif !cdef?(*cref)
17
+ raise Zeitwerk::NameError.new("expected file #{file} to define constant #{cpath(*cref)}, but didn't", cref.last)
18
+ end
19
+ end
20
+
21
+ # Invoked from our decorated Kernel#require when a managed directory is
22
+ # autoloaded.
23
+ #
24
+ # @private
25
+ # @param dir [String]
26
+ # @return [void]
27
+ def on_dir_autoloaded(dir)
28
+ # Module#autoload does not serialize concurrent requires, and we handle
29
+ # directories ourselves, so the callback needs to account for concurrency.
30
+ #
31
+ # Multi-threading would introduce a race condition here in which thread t1
32
+ # autovivifies the module, and while autoloads for its children are being
33
+ # set, thread t2 autoloads the same namespace.
34
+ #
35
+ # Without the mutex and subsequent delete call, t2 would reset the module.
36
+ # That not only would reassign the constant (undesirable per se) but, worse,
37
+ # the module object created by t2 wouldn't have any of the autoloads for its
38
+ # children, since t1 would have correctly deleted its lazy_subdirs entry.
39
+ mutex2.synchronize do
40
+ if cref = autoloads.delete(dir)
41
+ autovivified_module = cref[0].const_set(cref[1], Module.new)
42
+ log("module #{autovivified_module.name} autovivified from directory #{dir}") if logger
43
+
44
+ to_unload[autovivified_module.name] = [dir, cref] if reloading_enabled?
45
+
46
+ # We don't unregister `dir` in the registry because concurrent threads
47
+ # wouldn't find a loader associated to it in Kernel#require and would
48
+ # try to require the directory. Instead, we are going to keep track of
49
+ # these to be able to unregister later if eager loading.
50
+ autoloaded_dirs << dir
51
+
52
+ on_namespace_loaded(autovivified_module)
53
+ end
54
+ end
55
+ end
56
+
57
+ # Invoked when a class or module is created or reopened, either from the
58
+ # tracer or from module autovivification. If the namespace has matching
59
+ # subdirectories, we descend into them now.
60
+ #
61
+ # @private
62
+ # @param namespace [Module]
63
+ # @return [void]
64
+ def on_namespace_loaded(namespace)
65
+ if subdirs = lazy_subdirs.delete(real_mod_name(namespace))
66
+ subdirs.each do |subdir|
67
+ set_autoloads_in_dir(subdir, namespace)
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,21 @@
1
+ module Zeitwerk::RealModName
2
+ UNBOUND_METHOD_MODULE_NAME = Module.instance_method(:name)
3
+ private_constant :UNBOUND_METHOD_MODULE_NAME
4
+
5
+ # Returns the real name of the class or module, as set after the first
6
+ # constant to which it was assigned (or nil).
7
+ #
8
+ # The name method can be overridden, hence the indirection in this method.
9
+ #
10
+ # @param mod [Class, Module]
11
+ # @return [String, nil]
12
+ if UnboundMethod.method_defined?(:bind_call)
13
+ def real_mod_name(mod)
14
+ UNBOUND_METHOD_MODULE_NAME.bind_call(mod)
15
+ end
16
+ else
17
+ def real_mod_name(mod)
18
+ UNBOUND_METHOD_MODULE_NAME.bind(mod).call
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zeitwerk
4
+ module Registry # :nodoc: all
5
+ class << self
6
+ # Keeps track of all loaders. Useful to broadcast messages and to prevent
7
+ # them from being garbage collected.
8
+ #
9
+ # @private
10
+ # @return [<Zeitwerk::Loader>]
11
+ attr_reader :loaders
12
+
13
+ # Registers loaders created with `for_gem` to make the method idempotent
14
+ # in case of reload.
15
+ #
16
+ # @private
17
+ # @return [{String => Zeitwerk::Loader}]
18
+ attr_reader :loaders_managing_gems
19
+
20
+ # Maps real paths to the loaders responsible for them.
21
+ #
22
+ # This information is used by our decorated `Kernel#require` to be able to
23
+ # invoke callbacks and autovivify modules.
24
+ #
25
+ # @private
26
+ # @return [{String => Zeitwerk::Loader}]
27
+ attr_reader :autoloads
28
+
29
+ # This hash table addresses an edge case in which an autoload is ignored.
30
+ #
31
+ # For example, let's suppose we want to autoload in a gem like this:
32
+ #
33
+ # # lib/my_gem.rb
34
+ # loader = Zeitwerk::Loader.new
35
+ # loader.push_dir(__dir__)
36
+ # loader.setup
37
+ #
38
+ # module MyGem
39
+ # end
40
+ #
41
+ # if you require "my_gem", as Bundler would do, this happens while setting
42
+ # up autoloads:
43
+ #
44
+ # 1. Object.autoload?(:MyGem) returns `nil` because the autoload for
45
+ # the constant is issued by Zeitwerk while the same file is being
46
+ # required.
47
+ # 2. The constant `MyGem` is undefined while setup runs.
48
+ #
49
+ # Therefore, a directory `lib/my_gem` would autovivify a module according to
50
+ # the existing information. But that would be wrong.
51
+ #
52
+ # To overcome this fundamental limitation, we keep track of the constant
53
+ # paths that are in this situation ---in the example above, "MyGem"--- and
54
+ # take this collection into account for the autovivification logic.
55
+ #
56
+ # Note that you cannot generally address this by moving the setup code
57
+ # below the constant definition, because we want libraries to be able to
58
+ # use managed constants in the module body:
59
+ #
60
+ # module MyGem
61
+ # include MyConcern
62
+ # end
63
+ #
64
+ # @private
65
+ # @return [{String => (String, Zeitwerk::Loader)}]
66
+ attr_reader :inceptions
67
+
68
+ # Registers a loader.
69
+ #
70
+ # @private
71
+ # @param loader [Zeitwerk::Loader]
72
+ # @return [void]
73
+ def register_loader(loader)
74
+ loaders << loader
75
+ end
76
+
77
+ # This method returns always a loader, the same instance for the same root
78
+ # file. That is how Zeitwerk::Loader.for_gem is idempotent.
79
+ #
80
+ # @private
81
+ # @param root_file [String]
82
+ # @return [Zeitwerk::Loader]
83
+ def loader_for_gem(root_file)
84
+ loaders_managing_gems[root_file] ||= begin
85
+ Loader.new.tap do |loader|
86
+ loader.tag = File.basename(root_file, ".rb")
87
+ loader.inflector = GemInflector.new(root_file)
88
+ loader.push_dir(File.dirname(root_file))
89
+ end
90
+ end
91
+ end
92
+
93
+ # @private
94
+ # @param loader [Zeitwerk::Loader]
95
+ # @param realpath [String]
96
+ # @return [void]
97
+ def register_autoload(loader, realpath)
98
+ autoloads[realpath] = loader
99
+ end
100
+
101
+ # @private
102
+ # @param realpath [String]
103
+ # @return [void]
104
+ def unregister_autoload(realpath)
105
+ autoloads.delete(realpath)
106
+ end
107
+
108
+ # @private
109
+ # @param cpath [String]
110
+ # @param realpath [String]
111
+ # @param loader [Zeitwerk::Loader]
112
+ # @return [void]
113
+ def register_inception(cpath, realpath, loader)
114
+ inceptions[cpath] = [realpath, loader]
115
+ end
116
+
117
+ # @private
118
+ # @param cpath [String]
119
+ # @return [String, nil]
120
+ def inception?(cpath)
121
+ if pair = inceptions[cpath]
122
+ pair.first
123
+ end
124
+ end
125
+
126
+ # @private
127
+ # @param path [String]
128
+ # @return [Zeitwerk::Loader, nil]
129
+ def loader_for(path)
130
+ autoloads[path]
131
+ end
132
+
133
+ # @private
134
+ # @param loader [Zeitwerk::Loader]
135
+ # @return [void]
136
+ def on_unload(loader)
137
+ autoloads.delete_if { |_path, object| object == loader }
138
+ inceptions.delete_if { |_cpath, (_path, object)| object == loader }
139
+ end
140
+ end
141
+
142
+ @loaders = []
143
+ @loaders_managing_gems = {}
144
+ @autoloads = {}
145
+ @inceptions = {}
146
+ end
147
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zeitwerk
4
+ VERSION = "2.3.0"
5
+ end
metadata ADDED
@@ -0,0 +1,59 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: zeitwerk
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Xavier Noria
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-03-03 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: |2
14
+ Zeitwerk implements constant autoloading with Ruby semantics. Each gem
15
+ and application may have their own independent autoloader, with its own
16
+ configuration, inflector, and logger. Supports autoloading, preloading,
17
+ reloading, and eager loading.
18
+ email: fxn@hashref.com
19
+ executables: []
20
+ extensions: []
21
+ extra_rdoc_files: []
22
+ files:
23
+ - MIT-LICENSE
24
+ - README.md
25
+ - lib/zeitwerk.rb
26
+ - lib/zeitwerk/error.rb
27
+ - lib/zeitwerk/explicit_namespace.rb
28
+ - lib/zeitwerk/gem_inflector.rb
29
+ - lib/zeitwerk/inflector.rb
30
+ - lib/zeitwerk/kernel.rb
31
+ - lib/zeitwerk/loader.rb
32
+ - lib/zeitwerk/loader/callbacks.rb
33
+ - lib/zeitwerk/real_mod_name.rb
34
+ - lib/zeitwerk/registry.rb
35
+ - lib/zeitwerk/version.rb
36
+ homepage: https://github.com/fxn/zeitwerk
37
+ licenses:
38
+ - MIT
39
+ metadata: {}
40
+ post_install_message:
41
+ rdoc_options: []
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: 2.4.4
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ requirements: []
55
+ rubygems_version: 3.0.3
56
+ signing_key:
57
+ specification_version: 4
58
+ summary: Efficient and thread-safe constant autoloader
59
+ test_files: []