-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Allow "mix format" to read exported configuration from dependencies #7108
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
Conversation
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.
Do we really need a manifest for this? Is Code.eval_file that much slower than File.read + binary_to_term?
lib/mix/lib/mix/tasks/format.ex
Outdated
# This function reads exported configuration from the imported dependencies and deals with | ||
# caching the result of reading such configuration in a manifest file. | ||
defp fetch_deps_opts(formatter_opts) do | ||
case formatter_opts[:import_deps] do |
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.
Looks like this should be a cond
.
Could you do the same as the other format tests but also write to |
@ericmj that doesn't work because we read dependencies, but I was able to add a test using a fixture project. |
test "can read exported configuration from dependencies" do | ||
Mix.Project.push(__MODULE__.FormatWithDepsApp) | ||
|
||
in_fixture "format_with_deps", fn -> |
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.
If the fixture is empty you can use in_tmp
instead.
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.
You're right, I didn't realize we were recreating the dependency directory structure in the test as well. Updated to use in_tmp
:)
lib/mix/lib/mix/tasks/format.ex
Outdated
[ | ||
inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"] | ||
] | ||
* `:input` (a list of paths and patterns) - specifies the default inputs |
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.
It should be :inputs
.
lib/mix/lib/mix/tasks/format.ex
Outdated
See the "Importing dependencies configuration" section below for more | ||
information. | ||
|
||
* `:export_locals_without_parens` (a list of function names and arities) - |
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.
I was thinking an :export
keys with a :locals_without_parens
inside. But this way also works. What do you prefer?
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.
I like them both. Maybe :export
with subkeys is more flexible for the future. Shall we go with that?
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.
👍.
lib/mix/lib/mix/tasks/format.ex
Outdated
# Regular formatter configuration for my_dep | ||
# ... | ||
|
||
export_locals_without_parens: [some_dsl_call: :*] |
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.
It is better to give examples without :*
so developers don't overuse it.
lib/mix/lib/mix/tasks/format.ex
Outdated
# This function reads exported configuration from the imported dependencies and deals with | ||
# caching the result of reading such configuration in a manifest file. | ||
defp fetch_deps_opts(formatter_opts) do | ||
deps = formatter_opts[:import_deps] |
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.
Keyword.get(formatter_opts, :import_deps, [])
so we can remove the nil check?
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.
I used this initially but removed it because I thought it made sense. Not sure anymore, so I'll just reintroduce this 😄
lib/mix/lib/mix/tasks/format.ex
Outdated
end | ||
|
||
defp deps_manifest() do | ||
Path.join(Mix.Project.build_path(), ".cached_deps_formatter.exs") |
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.
Mix.Project.manifest_path()
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.
It is also best to remove the .exs extension to avoid issues.
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.
.exs
was a residue of initially dumping the Elixir term, good catch. Pushed a fix that uses manifest_path/0
instead of build_path/0
as well.
lib/mix/lib/mix/tasks/format.ex
Outdated
for dep <- deps, | ||
dep_dot_formatter = Path.join(Map.fetch!(deps_paths, dep), ".formatter.exs"), | ||
File.regular?(dep_dot_formatter), | ||
{dep_opts, _} = Code.eval_file(dep_dot_formatter), |
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.
What if dep_opts is not a keyword list?
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.
Pushed a fix that extracts reading a kw list from a file (using this when reading .formatter.exs
as well).
lib/mix/lib/mix/tasks/format.ex
Outdated
dep_parenless_calls = eval_deps_opts(deps) | ||
|
||
if dep_parenless_calls != [] do | ||
write_deps_manifest(dep_parenless_calls) |
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.
If we don't write the file, the manifest is always out of date and we will execute this every time. It is probably best to write the file even if it will be an empty list.
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.
That makes sense, but do we want to write it even if folks don't provide :export_locals_without_parens
? That might be why I wasn't using Keyword.get(..., [])
.
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.
This is the case where the user specifies some deps but they don't export anything.
lib/mix/lib/mix/tasks/format.ex
Outdated
deps_paths = Mix.Project.deps_paths() | ||
|
||
for dep <- deps, | ||
dep_dot_formatter = Path.join(Map.fetch!(deps_paths, dep), ".formatter.exs"), |
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.
What happens if an invalid dep is given? Should we have a better error message?
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.
Pushed a fix that validates dependencies with a nice error message if something goes wrong.
lib/mix/lib/mix/tasks/format.ex
Outdated
deps_paths = Mix.Project.deps_paths() | ||
|
||
for dep <- deps, | ||
ensure_valid_dependency!(dep, deps_paths), |
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.
path = fetch_dependency_path!(dep, deps_paths)
?
File.touch!(manifest_path, {{1970, 1, 1}, {0, 0, 0}}) | ||
%File.Stat{mtime: mtime} = File.stat!(manifest_path) | ||
|
||
File.touch!(".formatter.exs") |
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.
I don't think this is necessary?
|
||
# Let's check that the manifest gets updated if .formatter.exs changes. | ||
File.touch!(manifest_path, {{1970, 1, 1}, {0, 0, 0}}) | ||
%File.Stat{mtime: mtime} = File.stat!(manifest_path) |
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 know the mtime, there is no reason to read it back!
Mix.Tasks.Format.run(["a.ex"]) | ||
%File.Stat{mtime: new_mtime} = File.stat!(manifest_path) | ||
|
||
assert new_mtime > mtime |
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.
Other places in the suite we would do:
assert File.stat!(manifest_path).mtime > {{1970, 1, 1}, {0, 0, 0}}
66b55f5
to
5b9c8fb
Compare
5b9c8fb
to
cd95dcc
Compare
❤️ 💚 💙 💛 💜 |
🎉 |
|
||
This task supports importing formatter configuration from dependencies. | ||
|
||
A dependency that wants to export formatter configuration needs to have a `.formatter.exs` file |
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.
strictly speaking, for Hex dependencies the .formatter.exs
file needs to be in the package and by default it won't be [1]. I think we could add it by default, or make a note here, or hope maintainers will remember to set it. WDYT?
[1] https://github.com/hexpm/hex/blob/v0.17.1/lib/mix/tasks/hex.build.ex#L4
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.
@wojtekmach let's update hex?
I am not sure yet on how to go about testing this, any suggestions are appreciated :).Testing is done.The names of the options have been just decided by me for now so feedback is welcome.
Closes #6644.