-
Notifications
You must be signed in to change notification settings - Fork 113
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Switch application load path finding to zeitwerk #218
Changes from all commits
d99f33b
9c2b13e
c8afec5
a0ffece
db2bfd0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,7 +9,7 @@ module ApplicationLoadPaths | |
class << self | ||
extend T::Sig | ||
|
||
sig { params(root: String, environment: String).returns(T::Hash[String, Module]) } | ||
sig { params(root: String, environment: String).returns(T::Array[String]) } | ||
def extract_relevant_paths(root, environment) | ||
require_application(root, environment) | ||
all_paths = extract_application_autoload_paths | ||
|
@@ -18,30 +18,28 @@ def extract_relevant_paths(root, environment) | |
relative_path_strings(relevant_paths) | ||
end | ||
|
||
sig { returns(T::Hash[String, Module]) } | ||
sig { returns(T::Array[String]) } | ||
def extract_application_autoload_paths | ||
Rails.autoloaders.inject({}) do |h, loader| | ||
h.merge(loader.root_dirs) | ||
end | ||
Zeitwerk::Loader.all_dirs | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmmm this isn't going to work for us. Having the default namespace specified for each root dir is a feature GitHub depends on quite a bit. I think using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm willing to help if new use cases arise. For example, if a read-only collection of root directories with their associated namespaces would be a good solution for what this is trying to solve, I'd be open to implement it. However, I don't know if this is a good solution to the root problem. If a gem is not an engine, and does not use Zeitwerk either, what? Take also into account that Zeitwerk is used by gems. How are you going to distinguish the loaders that matter among them? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mclark it feels like we should have used packwerk to define which parts of packwerk are public API and which are not ;) jkjk Do you mind elaborating as to what you do with the default namespace? Thanks for offering to support this, Xavier! To answer your questions:
Such gems would currently not be included. And I think that is fine because...
... currently packwerk only operates on constants defined with the Rails application. I.e., packwerk is focussed on helping engineers understand and clean up the dependency structures within their own applications, so there is already a boundary built in. #216 is looking to change this and add more options, but the fundamental fact that packwerk has controls for this won't change. How is any of this useful? One example of an app that uses gems and engines as part of the codebase is https://github.com/instructure/canvas-lms/tree/master/gems, which defines a bunch of gems inline and uses the directly from the Gemfile (https://github.com/instructure/canvas-lms/blob/master/Gemfile.d/app.rb#L160) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @shageman sorry I missed your comment somehow!
Some legacy directories in GitHub do not fully conform to the patterns established by Zeitwerk and have an implicit "root" namespace. When we migrated to Zeitwerk we leveraged the ability to set a default namespace for each root directory in order to override the default For Packwerk to resolve constants properly, it too needs to know what the default root namespaces are otherwise the expected and actual namespaces for each file will not match. This isn't an ideal situation, but for the foreseeable future we will be stuck with this pattern unfortunately. Thanks for taking the time to understand the problem. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let me say that I believe root namespaces are as good practice as I have been consulting for many years, and it is rare the project that does not have |
||
end | ||
|
||
sig do | ||
params(all_paths: T::Hash[String, Module], bundle_path: Pathname, rails_root: Pathname) | ||
.returns(T::Hash[Pathname, Module]) | ||
params(all_paths: T::Array[String], bundle_path: Pathname, rails_root: Pathname) | ||
.returns(T::Array[Pathname]) | ||
end | ||
def filter_relevant_paths(all_paths, bundle_path: Bundler.bundle_path, rails_root: Rails.root) | ||
bundle_path_match = bundle_path.join("**") | ||
rails_root_match = rails_root.join("**") | ||
|
||
all_paths | ||
.transform_keys { |path| Pathname.new(path).expand_path } | ||
.map { |path| Pathname.new(path).expand_path } | ||
.select { |path| path.fnmatch(rails_root_match.to_s) } # path needs to be in application directory | ||
.reject { |path| path.fnmatch(bundle_path_match.to_s) } # reject paths from vendored gems | ||
end | ||
|
||
sig { params(load_paths: T::Hash[Pathname, Module], rails_root: Pathname).returns(T::Hash[String, Module]) } | ||
sig { params(load_paths: T::Array[Pathname], rails_root: Pathname).returns(T::Array[String]) } | ||
def relative_path_strings(load_paths, rails_root: Rails.root) | ||
load_paths.transform_keys { |path| Pathname.new(path).relative_path_from(rails_root).to_s } | ||
load_paths.map { |path| Pathname.new(path).relative_path_from(rails_root).to_s } | ||
end | ||
|
||
private | ||
|
@@ -59,7 +57,7 @@ def require_application(root, environment) | |
end | ||
end | ||
|
||
sig { params(paths: T::Hash[T.untyped, Module]).void } | ||
sig { params(paths: T::Array[Pathname]).void } | ||
def assert_load_paths_present(paths) | ||
if paths.empty? | ||
raise <<~EOS | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have no interest of entering the business of modularize arbitrary Ruby applications. I'm ok to allow load path discovery using Zeitwerk given that makes sense in the context of a Rails application, but if people start to ask for "feature X to support Hanami apps" or "Feature Y to support Sinatra apps" the answer at the moment will be an automatic "sorry, you are on your own". So, it is better to not claim in the README that we Packwerk is something it isn't.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense! I know a lot happened today and this PR is no longer relevant for the moment, but I will make sure to have this fixed when we have an acceptable solution in place.