From edc0b9284f59480ca1d1daddc730556650698c6e Mon Sep 17 00:00:00 2001 From: Kaan Ozkan Date: Fri, 6 Dec 2024 14:24:43 -0500 Subject: [PATCH 1/3] Lazily boot the rails app during `tapioca gem` --- lib/tapioca/loaders/loader.rb | 116 +--------------------------------- spec/tapioca/cli/gem_spec.rb | 23 +++++++ 2 files changed, 25 insertions(+), 114 deletions(-) diff --git a/lib/tapioca/loaders/loader.rb b/lib/tapioca/loaders/loader.rb index a1242aefb..7766eb913 100644 --- a/lib/tapioca/loaders/loader.rb +++ b/lib/tapioca/loaders/loader.rb @@ -29,13 +29,11 @@ def load; end def load_bundle(gemfile, initialize_file, require_file, halt_upon_load_error) require_helper(initialize_file) - load_rails_application(halt_upon_load_error: halt_upon_load_error) - gemfile.require_bundle - require_helper(require_file) + load_rails_application(environment_load: true, halt_upon_load_error: halt_upon_load_error) - load_rails_engines + require_helper(require_file) end sig do @@ -85,116 +83,6 @@ def load_rails_application(environment_load: false, eager_load: false, app_root: say("Continuing RBI generation without loading the Rails application.") end - sig { void } - def load_rails_engines - 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| - eager_load_paths(engine).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| - eager_load_paths(engine).each do |load_path| - Dir.glob("#{load_path}/**/*.rb").sort.each do |file| - require_dependency file - end - rescue ScriptError, StandardError - nil - end - end - end - - 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 - Rails::Engine - .descendants - .reject(&:abstract_railtie?) - .reject { |engine| gem_in_app_dir?(project_path, engine.config.root.to_path) } - end - - sig { params(path: String).void } - def safe_require(path) - require path - rescue LoadError - nil - end - sig { void } def eager_load_rails_app application = Rails.application diff --git a/spec/tapioca/cli/gem_spec.rb b/spec/tapioca/cli/gem_spec.rb index 76a62c515..f8031807d 100644 --- a/spec/tapioca/cli/gem_spec.rb +++ b/spec/tapioca/cli/gem_spec.rb @@ -1286,6 +1286,19 @@ class Post RB end + @project.write!("config/application.rb", <<~RB) + module Tapioca + class Application < Rails::Application + config.load_defaults(#{ActiveSupport.gem_version.to_s[0..2]}) + end + end + RB + + @project.write!("config/environment.rb", <<~RB) + require_relative "application" + Rails.application.initialize! + RB + @project.require_real_gem("rails", ActiveSupport.gem_version.to_s) @project.require_mock_gem(foo) @project.bundle_install! @@ -1343,6 +1356,11 @@ class Application < Rails::Application end RB + @project.write!("config/environment.rb", <<~RB) + require_relative "application" + Rails.application.initialize! + RB + response = @project.tapioca("gem turbo-rails") assert_includes(response.out, "Compiled turbo-rails") @@ -1374,6 +1392,11 @@ class Application < Rails::Application end RB + @project.write!("config/environment.rb", <<~RB) + require_relative "application" + Rails.application.initialize! + RB + response = @project.tapioca("gem turbo-rails") assert_includes(response.out, "Compiled turbo-rails") From 204c579f699a50e90492beeb4ec590508e35ab18 Mon Sep 17 00:00:00 2001 From: Kaan Ozkan Date: Wed, 15 Jan 2025 10:36:21 -0500 Subject: [PATCH 2/3] wip --- lib/tapioca/sorbet_ext/name_patch.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/tapioca/sorbet_ext/name_patch.rb b/lib/tapioca/sorbet_ext/name_patch.rb index fab77a821..6875bfc6f 100644 --- a/lib/tapioca/sorbet_ext/name_patch.rb +++ b/lib/tapioca/sorbet_ext/name_patch.rb @@ -1,6 +1,8 @@ # typed: true # frozen_string_literal: true +Module.include(T::Sig) # TODO + module T module Types class Simple From 5d63387b3f1cefe9908a49d0062eeb9ee1794295 Mon Sep 17 00:00:00 2001 From: Kaan Ozkan Date: Tue, 18 Feb 2025 21:02:47 -0500 Subject: [PATCH 3/3] Require bundle after loading rails app --- lib/tapioca/loaders/loader.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/tapioca/loaders/loader.rb b/lib/tapioca/loaders/loader.rb index 7766eb913..754b52516 100644 --- a/lib/tapioca/loaders/loader.rb +++ b/lib/tapioca/loaders/loader.rb @@ -29,10 +29,10 @@ def load; end def load_bundle(gemfile, initialize_file, require_file, halt_upon_load_error) require_helper(initialize_file) - gemfile.require_bundle - load_rails_application(environment_load: true, halt_upon_load_error: halt_upon_load_error) + gemfile.require_bundle + require_helper(require_file) end