lib/tapioca/loaders/loader.rb in tapioca-0.10.5 vs lib/tapioca/loaders/loader.rb in tapioca-0.11.0
- old
+ new
@@ -56,40 +56,106 @@
say("Continuing RBI generation without loading the Rails application.")
end
sig { void }
def load_rails_engines
- rails_engines.each do |engine|
- errored_files = []
+ return if engines.empty?
+ with_rails_application do
+ run_initializers
+
+ if zeitwerk_mode?
+ load_engines_in_zeitwerk_mode
+ else
+ load_engines_in_classic_mode
+ end
+ end
+ end
+
+ def run_initializers
+ engines.each do |engine|
+ engine.instance.initializers.tsort_each do |initializer|
+ initializer.run(Rails.application)
+ rescue ScriptError, StandardError
+ nil
+ end
+ end
+ end
+
+ sig { void }
+ def load_engines_in_zeitwerk_mode
+ # Collect all the directories that are already managed by all existing Zeitwerk loaders.
+ managed_dirs = Zeitwerk::Registry.loaders.flat_map(&:dirs).to_set
+ # We use a fresh loader to load the engine directories, so that we don't interfere with
+ # any of the existing loaders.
+ autoloader = Zeitwerk::Loader.new
+
+ engines.each do |engine|
+ engine.config.eager_load_paths.each do |path|
+ # Zeitwerk only accepts existing directories in `push_dir`.
+ next unless File.directory?(path)
+ # We should not add directories that are already managed by a Zeitwerk loader.
+ next if managed_dirs.member?(path)
+
+ autoloader.push_dir(path)
+ end
+ end
+
+ autoloader.setup
+ end
+
+ sig { void }
+ def load_engines_in_classic_mode
+ # This is code adapted from `Rails::Engine#eager_load!` in
+ # https://github.com/rails/rails/blob/d9e188dbab81b412f73dfb7763318d52f360af49/railties/lib/rails/engine.rb#L489-L495
+ #
+ # We can't use `Rails::Engine#eager_load!` directly because it will raise as soon as it encounters
+ # an error, which is not what we want. We want to try to load as much as we can.
+ engines.each do |engine|
engine.config.eager_load_paths.each do |load_path|
Dir.glob("#{load_path}/**/*.rb").sort.each do |file|
- require(file)
- rescue LoadError, StandardError
- errored_files << file
+ require_dependency file
end
- end
-
- # Try files that have errored one more time
- # It might have been a load order problem
- errored_files.each do |file|
- require(file)
- rescue LoadError, StandardError
+ rescue ScriptError, StandardError
nil
end
end
end
- sig { returns(T::Array[T.untyped]) }
- def rails_engines
- return [] unless Object.const_defined?("Rails::Engine")
+ sig { returns(T::Boolean) }
+ def zeitwerk_mode?
+ Rails.respond_to?(:autoloaders) &&
+ Rails.autoloaders.respond_to?(:zeitwerk_enabled?) &&
+ Rails.autoloaders.zeitwerk_enabled?
+ end
+ sig { params(blk: T.proc.void).void }
+ def with_rails_application(&blk)
+ # Store the current Rails.application object so that we can restore it
+ rails_application = T.unsafe(Rails.application)
+
+ # Create a new Rails::Application object, so that we can load the engines.
+ # Some engines and the `Rails.autoloaders` call might expect `Rails.application`
+ # to be set, so we need to create one here.
+ unless rails_application
+ Rails.application = Class.new(Rails::Application)
+ end
+
+ blk.call
+ ensure
+ Rails.app_class = Rails.application = rails_application
+ end
+
+ T::Sig::WithoutRuntime.sig { returns(T::Array[T.class_of(Rails::Engine)]) }
+ def engines
+ return [] unless defined?(Rails::Engine)
+
safe_require("active_support/core_ext/class/subclasses")
project_path = Bundler.default_gemfile.parent.expand_path
# We can use `Class#descendants` here, since we know Rails is loaded
- Object.const_get("Rails::Engine")
+ Rails::Engine
.descendants
.reject(&:abstract_railtie?)
.reject { |engine| gem_in_app_dir?(project_path, engine.config.root.to_path) }
end
@@ -101,33 +167,28 @@
end
sig { void }
def silence_deprecations
# Stop any ActiveSupport Deprecations from being reported
- Object.const_get("ActiveSupport::Deprecation").silenced = true
- rescue NameError
- nil
+ if defined?(ActiveSupport::Deprecation)
+ ActiveSupport::Deprecation.silenced = true
+ end
end
sig { void }
def eager_load_rails_app
- rails = Object.const_get("Rails")
- application = rails.application
+ application = Rails.application
- if Object.const_defined?("ActiveSupport")
- Object.const_get("ActiveSupport").run_load_hooks(
- :before_eager_load,
- application,
- )
+ if defined?(ActiveSupport)
+ ActiveSupport.run_load_hooks(:before_eager_load, application)
end
- if Object.const_defined?("Zeitwerk::Loader")
- zeitwerk_loader = Object.const_get("Zeitwerk::Loader")
- zeitwerk_loader.eager_load_all
+ if defined?(Zeitwerk::Loader)
+ Zeitwerk::Loader.eager_load_all
end
- if rails.respond_to?(:autoloaders) && rails.autoloaders.zeitwerk_enabled?
- rails.autoloaders.each(&:eager_load)
+ if Rails.respond_to?(:autoloaders)
+ Rails.autoloaders.each(&:eager_load)
end
if application.config.respond_to?(:eager_load_namespaces)
application.config.eager_load_namespaces.each(&:eager_load!)
end