Skip to content
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

Load paths hash #190

Merged
merged 2 commits into from
Apr 13, 2022
Merged

Load paths hash #190

merged 2 commits into from
Apr 13, 2022

Conversation

mclark
Copy link
Contributor

@mclark mclark commented Apr 1, 2022

What are you trying to accomplish?

As described in #186, Zeitwerk supports the notion of a "default" namespace for each root directory. This is represented by a Hash of root directory path strings to constants representing the default module in Zeitwerk::Loader#root_dirs. This pull request updates Packwerk so that it treats the group of load paths as a hash rather than an Array and uses the #root_dirs of each autoloader in Rails.autoloaders instead.

What approach did you choose and why?

As Packwerk relies on Rails' config.autoload_paths and friends lists to get the list of load paths to draw on for its constant discovery process, and these are just lists of strings, they needed to be abandoned in favour of relying on the autoloaders instead which are hashes of root dir paths to default modules. This triggered a number of changes, mostly to method signatures as the new hash worked its way through the call stacks to eventually arrive in the ConstantResolver gem (already updated in Shopify/constant_resolver#27).

After that, most of the effort just went into updating the tests to conform to the new expectations:

  • Removed RailsPaths in favour of using plain old Mocha mocks
  • Removed uniqueness and method signature tests that were impossible to fail thanks to the use of Hashes and Sorbet
  • Initialized the dummy application so Rails.autoloaders would be populated
  • Changed two tests from ActiveSupport::TestCase to Minitest::Test to be consistent with all other tests and to suppress log output caused by initializing the dummy application.

What should reviewers focus on?

I believe @exterm is bumping constant_resolver as I write this so expect the git dependency for constant_resolver to go away and be replaced once that is done.

I am not an expert on rails or load paths. Is eager loading still supported properly? Can we suppress the logging in a better way than outputting "nothing" to standard out?

Type of Change

  • Bugfix
  • New feature
  • Non-breaking change (a change that doesn't alter functionality - i.e., code refactor, configs, etc.)

Additional Release Notes

  • Breaking change (fix or feature that would cause existing functionality to change)

Include any notes here to include in the release description. For example, if you selected "breaking change" above, leave notes on how users can transition to this version.

If no additional notes are necessary, delete this section or leave it unchanged.

Checklist

  • I have updated the documentation accordingly.
  • I have added tests to cover my changes.
  • It is safe to rollback this change.

Gemfile Outdated Show resolved Hide resolved
end
Rails.autoloaders.inject({}) do |h, loader|
h.merge(loader.root_dirs)
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

@mclark mclark Apr 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it turns out this doesn't work in Rails 6.0 unless Rails.autoloaders.zeitwerk_enabled? returns true. If it is false, Rails.autoloaders will be an empty enumerable.

So now we have to choose one of:

  • For all the places that we stub out config.autoload_paths and friends we add matching tests to cover when zeitwerk is both enabled and disabled.
  • Explicitly add the requirement that packwerk is only compatible with Rails applications which are zeitwerk enabled. This is the case for Rails 6.x codebases that have Rails.autoloaders.zeitwerk_enabled? == true or Rails 7.0+ codebases.

Personally, based on our discussions about adding Zeitwerk as a dev dependency in the test in this diff I feel we could pretty safely say that Packwerk requires zeitwerk_enabled? to be true. This would retain Rails 6.x support without requiring us to duplicate a whole bunch of test.

As "Classic mode" isn't coming back, and Packwerk is based on the conventions of Zeitwerk, unless this causes problems for any known users, I'd suggest we go with the addition of this "soft" requirement.

Edit: I've edited the rails_test_helper Dummy application to explicitly have config.autoloader = :zeitwerk for now to get tests passing.

Copy link
Contributor

@exterm exterm Apr 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should make it more obvious, but packwerk already officially requires zeitwerk to be enabled https://github.com/Shopify/packwerk#prerequisites

If you put up a PR to make it more obvious I'll accept it

@mclark
Copy link
Contributor Author

mclark commented Apr 1, 2022

Anyone with more Sorbet knowledge than I know what to do with this error?

test/rails_test_helper.rb:23: Method `initialize!` does not exist on `T.class_of(Dummy)` https://srb.help/7003
    23 |Dummy.initialize!
              ^^^^^^^^^^^

@exterm
Copy link
Contributor

exterm commented Apr 4, 2022

Anyone with more Sorbet knowledge

Looks like we don't have a type spec for Rails::Application... maybe tapioca gem rails can fix it?

test/rails_test_helper.rb Outdated Show resolved Hide resolved
@mclark
Copy link
Contributor Author

mclark commented Apr 4, 2022

Looks like we don't have a type spec for Rails::Application... maybe tapioca gem rails can fix it?

I couldn't really get it working, probably my lack of experience with tapioca.. Anyway, using Rails.application.initialize! instead seems to have done the trick 👍

@mclark mclark marked this pull request as ready for review April 5, 2022 14:08
@mclark mclark requested a review from a team as a code owner April 5, 2022 14:08
@exterm
Copy link
Contributor

exterm commented Apr 7, 2022

This should be a non-breaking change right? I'll try to find some time to run it against our monolith.

@exterm
Copy link
Contributor

exterm commented Apr 12, 2022

our main monolith CI is green against this branch. I've also done some manual testing and it's looking great.

@exterm
Copy link
Contributor

exterm commented Apr 12, 2022

@mclark do you think it'd make ense to reduce the number of commits (squash some together) before we merge?

This changes a couple things when running Packwerk against a Rails app.
First of all, instead of getting our load paths via
`config.autoload_paths` and related methods, we use Rails' zeitwerk
autoloaders. Secondly, instead of representing the load paths as a list
of root directory paths, we instead use a hashmap of root directory
paths to module instances which are the root modules for the path.
This allows users to override the default module for any root directory.
@exterm exterm merged commit 8f274b1 into Shopify:main Apr 13, 2022
@shopify-shipit shopify-shipit bot temporarily deployed to rubygems April 27, 2022 15:37 Inactive
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants