class Zeitwerk::Loader
def all_dirs
registered loaders. This is a read-only collection.
Returns an array with the absolute paths of the root directories of all
def all_dirs Registry.loaders.flat_map(&:dirs).freeze end
def autoload_file(parent, cname, file)
def autoload_file(parent, cname, file) oload_path = strict_autoload_path(parent, cname) || Registry.inception?(cpath(parent, cname)) rst autoload for a Ruby file wins, just ignore subsequent ones. uby?(autoload_path) adowed_files << file g("file #{file} is ignored because #{autoload_path} has precedence") if logger omote_namespace_from_implicit_to_explicit( dir: autoload_path, file: file, parent: parent, cname: cname cdef?(parent, cname) owed_files << file "file #{file} is ignored because #{cpath(parent, cname)} is already defined") if logger ne_autoload(parent, cname, file)
def autoload_path_set_by_me_for?(parent, cname)
def autoload_path_set_by_me_for?(parent, cname) oload_path = strict_autoload_path(parent, cname) load_path if autoloads.key?(autoload_path) stry.inception?(cpath(parent, cname))
def autoload_subdir(parent, cname, subdir)
def autoload_subdir(parent, cname, subdir) oload_path = autoload_path_set_by_me_for?(parent, cname) h = cpath(parent, cname) uby?(autoload_path) Scanning visited a Ruby file first, and now a directory for the same constant has been found. This means we are dealing with an explicit namespace whose definition was seen first. Registering is idempotent, and we have to keep the autoload pointing to the file. This may run again if more directories are found later on, no big deal. gister_explicit_namespace(cpath) the existing autoload points to a file, it has to be preserved, if t, it is fine as it is. In either case, we do not need to override. st remember the subdirectory conforms this namespace. space_dirs[cpath] << subdir !cdef?(parent, cname) rst time we find this namespace, set an autoload for it. space_dirs[cpath(parent, cname)] << subdir ne_autoload(parent, cname, subdir) r whatever reason the constant that corresponds to this namespace has ready been defined, we have to recurse. "the namespace #{cpath(parent, cname)} already exists, descending into #{subdir}") if logger ne_autoloads_for_dir(subdir, cget(parent, cname))
def cpath_expected_at(path)
def cpath_expected_at(path) abspath = File.expand_path(path) raise Zeitwerk::Error.new("#{abspath} does not exist") unless File.exist?(abspath) return unless dir?(abspath) || ruby?(abspath) return if ignored_path?(abspath) paths = [] if ruby?(abspath) basename = File.basename(abspath, ".rb") return if hidden?(basename) paths << [basename, abspath] walk_up_from = File.dirname(abspath) else walk_up_from = abspath end root_namespace = nil walk_up(walk_up_from) do |dir| break if root_namespace = roots[dir] return if ignored_path?(dir) basename = File.basename(dir) return if hidden?(basename) paths << [basename, abspath] unless collapse?(dir) end return unless root_namespace if paths.empty? real_mod_name(root_namespace) else cnames = paths.reverse_each.map { |b, a| cname_for(b, a) } if root_namespace == Object cnames.join("::") else "#{real_mod_name(root_namespace)}::#{cnames.join("::")}" end end end
def define_autoload(parent, cname, abspath)
def define_autoload(parent, cname, abspath) .autoload(cname, abspath) ger uby?(abspath) g("autoload set for #{cpath(parent, cname)}, to be loaded from #{abspath}") g("autoload set for #{cpath(parent, cname)}, to be autovivified from #{abspath}") ads[abspath] = [parent, cname] ry.register_autoload(self, abspath) why in the documentation of Zeitwerk::Registry.inceptions. parent.autoload?(cname) stry.register_inception(cpath(parent, cname), abspath, self)
def define_autoloads_for_dir(dir, parent)
def define_autoloads_for_dir(dir, parent) ) do |basename, abspath| uby?(basename) sename.delete_suffix!(".rb") toload_file(parent, cname_for(basename, abspath), abspath) collapse?(abspath) define_autoloads_for_dir(abspath, parent) se autoload_subdir(parent, cname_for(basename, abspath), abspath) d
def eager_load_all
are skipped.
Broadcasts `eager_load` to all loaders. Those that have not been setup
def eager_load_all Registry.loaders.each do |loader| begin loader.eager_load rescue SetupRequired # This is fine, we eager load what can be eager loaded. end end end
def eager_load_namespace(mod)
been setup are skipped.
Broadcasts `eager_load_namespace` to all loaders. Those that have not
def eager_load_namespace(mod) Registry.loaders.each do |loader| begin loader.eager_load_namespace(mod) rescue SetupRequired # This is fine, we eager load what can be eager loaded. end end end
def for_gem(warn_on_extra_files: true)
is private, client code can only rely on the interface.
This method returns a subclass of Zeitwerk::Loader, but the exact type
the same file, in the unlikely case the gem wants to be able to reload.
except that this method returns the same object in subsequent calls from
loader.push_dir(__dir__)
loader.inflector = Zeitwerk::GemInflector.new(__FILE__)
loader.tag = File.basename(__FILE__, ".rb")
loader = Zeitwerk::Loader.new
require "zeitwerk"
This is a shortcut for
def for_gem(warn_on_extra_files: true) called_from = caller_locations(1, 1).first.path Registry.loader_for_gem(called_from, namespace: Object, warn_on_extra_files: warn_on_extra_files) end
def for_gem_extension(namespace)
is private, client code can only rely on the interface.
This method returns a subclass of Zeitwerk::Loader, but the exact type
the same file, in the unlikely case the gem wants to be able to reload.
except that this method returns the same object in subsequent calls from
loader.push_dir(__dir__, namespace: namespace)
loader.inflector = Zeitwerk::GemInflector.new(__FILE__)
loader.tag = namespace.name + "-" + File.basename(__FILE__, ".rb")
loader = Zeitwerk::Loader.new
require "zeitwerk"
This is a shortcut for
def for_gem_extension(namespace) unless namespace.is_a?(Module) # Note that Class < Module. raise Zeitwerk::Error, "#{namespace.inspect} is not a class or module object, should be" end unless real_mod_name(namespace) raise Zeitwerk::Error, "extending anonymous namespaces is unsupported" end called_from = caller_locations(1, 1).first.path Registry.loader_for_gem(called_from, namespace: namespace, warn_on_extra_files: false) end
def initialize
def initialize super @autoloads = {} @autoloaded_dirs = [] @to_unload = {} @namespace_dirs = Hash.new { |h, cpath| h[cpath] = [] } @shadowed_files = Set.new @setup = false @eager_loaded = false @mutex = Mutex.new @dirs_autoload_monitor = Monitor.new Registry.register_loader(self) end
def promote_namespace_from_implicit_to_explicit(dir:, file:, parent:, cname:)
the file where we've found the namespace is explicitly defined.
`dir` is the directory that would have autovivified a namespace. `file` is
def promote_namespace_from_implicit_to_explicit(dir:, file:, parent:, cname:) ads.delete(dir) ry.unregister_autoload(dir) arlier autoload for #{cpath(parent, cname)} discarded, it is actually an explicit namespace defined in #{file}") if logger _autoload(parent, cname, file) er_explicit_namespace(cpath(parent, cname))
def raise_if_conflicting_directory(dir)
def raise_if_conflicting_directory(dir) synchronize do slash = dir + "/" stry.loaders.each do |loader| xt if loader == self xt if loader.__ignores?(dir) ader.__roots.each_key do |root_dir| next if ignores?(root_dir) root_dir_slash = root_dir + "/" if dir_slash.start_with?(root_dir_slash) || root_dir_slash.start_with?(dir_slash) require "pp" # Needed for pretty_inspect, even in Ruby 2.5. raise Error, "loader\n\n#{pretty_inspect}\n\nwants to manage directory #{dir}," \ " which is already managed by\n\n#{loader.pretty_inspect}\n" end d
def register_explicit_namespace(cpath)
def register_explicit_namespace(cpath) itNamespace.__register(cpath, self)
def reload
-
(Zeitwerk::Error)
-
def reload raise ReloadingDisabledError unless reloading_enabled? raise SetupRequired unless @setup unload recompute_ignored_paths recompute_collapse_dirs setup end
def run_on_unload_callbacks(cpath, value, abspath)
def run_on_unload_callbacks(cpath, value, abspath) r matters. If present, run the most specific one. oad_callbacks[cpath]&.each { |c| c.call(value, abspath) } oad_callbacks[:ANY]&.each { |c| c.call(cpath, value, abspath) }
def setup
Sets autoloads in the root namespaces.
def setup mutex.synchronize do break if @setup actual_roots.each do |root_dir, root_namespace| define_autoloads_for_dir(root_dir, root_namespace) end on_setup_callbacks.each(&:call) @setup = true end end
def shadowed_file?(file)
scanned the file. This is the case in the spots where we use it.
The return value of this predicate is only meaningful if the loader has
def shadowed_file?(file) d_files.member?(file)
def unload
`unregister`, which is undocumented too.
means `unload` + `setup`. This one is available to be used together with
This method is public but undocumented. Main interface is `reload`, which
unload them.
else, they are eligible for garbage collection, which would effectively
addition, since said objects are normally not referenced from anywhere
The objects the constants stored are no longer reachable through them. In
Removes loaded constants and configured autoloads.
def unload mutex.synchronize do raise SetupRequired unless @setup # We are going to keep track of the files that were required by our # autoloads to later remove them from $LOADED_FEATURES, thus making them # loadable by Kernel#require again. # # Directories are not stored in $LOADED_FEATURES, keeping track of files # is enough. unloaded_files = Set.new autoloads.each do |abspath, (parent, cname)| if parent.autoload?(cname) unload_autoload(parent, cname) else # Could happen if loaded with require_relative. That is unsupported, # and the constant path would escape unloadable_cpath? This is just # defensive code to clean things up as much as we are able to. unload_cref(parent, cname) unloaded_files.add(abspath) if ruby?(abspath) end end to_unload.each do |cpath, (abspath, (parent, cname))| unless on_unload_callbacks.empty? begin value = cget(parent, cname) rescue ::NameError # Perhaps the user deleted the constant by hand, or perhaps an # autoload failed to define the expected constant but the user # rescued the exception. else run_on_unload_callbacks(cpath, value, abspath) end end unload_cref(parent, cname) unloaded_files.add(abspath) if ruby?(abspath) end unless unloaded_files.empty? # Bootsnap decorates Kernel#require to speed it up using a cache and # this optimization does not check if $LOADED_FEATURES has the file. # # To make it aware of changes, the gem defines singleton methods in # $LOADED_FEATURES: # # https://github.com/Shopify/bootsnap/blob/master/lib/bootsnap/load_path_cache/core_ext/loaded_features.rb # # Rails applications may depend on bootsnap, so for unloading to work # in that setting it is preferable that we restrict our API choice to # one of those methods. $LOADED_FEATURES.reject! { |file| unloaded_files.member?(file) } end autoloads.clear autoloaded_dirs.clear to_unload.clear namespace_dirs.clear shadowed_files.clear Registry.on_unload(self) ExplicitNamespace.__unregister_loader(self) @setup = false @eager_loaded = false end end
def unload_autoload(parent, cname)
def unload_autoload(parent, cname) arent, cname) utoload for #{cpath(parent, cname)} removed") if logger
def unload_cref(parent, cname)
def unload_cref(parent, cname) s optimistically remove_const. The way we use it, this is going to eed always if all is good. arent, cname) :NameError e are a few edge scenarios in which this may happen. If the constant one, that is OK, anyway. {cpath(parent, cname)} unloaded") if logger
def unloadable_cpath?(cpath)
predicate returns `false` if reloading is disabled.
Says if the given constant path would be unloaded on reload. This
def unloadable_cpath?(cpath) to_unload.key?(cpath) end
def unloadable_cpaths
This predicate returns an empty array if reloading is disabled.
Returns an array with the constant paths that would be unloaded on reload.
def unloadable_cpaths to_unload.keys.freeze end
def unregister
@experimental
This is a dangerous method.
def unregister Registry.unregister_loader(self) ExplicitNamespace.__unregister_loader(self) end