Skip to content

Allow reading args from @path - #7220

Merged
ytmimi merged 1 commit into
rust-lang:mainfrom
matthewhughes934:args-from-path
Nov 5, 2026
Merged

ytmimi merged 1 commit into
rust-lang:mainfrom
matthewhughes934:args-from-path

Conversation

@matthewhughes934

@matthewhughes934 matthewhughes934 commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

That is, something like

$ printf "--config path/to/config.toml\n--check\n" > @args-file
$ rustfmt @args-file path/to/format.rs
# same as: rustfmt --config path/to/config.toml --check path/to/format.rs

The motivation for this is that cargo fmt collects the list of files to be formatted and passes them to rustfmt as arguments. But this list can get so long that it reaches OS limits, see #6934.

The process for making this change was:

  1. Go and look for inspiration from other tools
  2. Find out the rustdoc supports this pattern[1] so just re-use its approach[2]

Conveniently, that approach uses helpers from internal compiler crates that are also available to us. Note, those helpers expect UTF-8 everywhere, which is what the current code also does, but this will be an additional place to consider in the future if we want to move away from that (see #7210).

Link: https://doc.rust-lang.org/rustdoc/command-line-arguments.html#path-load-command-line-flags-from-a-path [1]
Link: https://github.com/rust-lang/rust/blob/ab019e71a1a1f4688bcc4d5bf06d2c5a424a31a5/src/librustdoc/lib.rs#L827 [2]

  • I did not use an LLM to create a change in this PR.
  • I used an LLM to create a change in this PR, and I have explained below how it was used.

@rustbot rustbot added the S-waiting-on-author Status: awaiting some action (such as code changes or more information) from the author. label Oct 29, 2026
Comment thread tests/rustfmt/main.rs Resolved
Comment thread tests/rustfmt/main.rs Resolved
Comment thread src/bin/main.rs
let cmdline_args = env::args().skip(1).collect::<Vec<String>>();
// if we saw a `@path` arg, read arguments from that file and merge them with the other
// arguments.
let args = rustc_driver::args::arg_expand_all(&early_dcx, &cmdline_args);

@ytmimi ytmimi Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's nice that we can leverage existing code from rustc_driver. Will this enforce that options are newline separated? For example, can we pass more than one argument on a single line? I think we should add some tests for this.

I see that your tests below dynamically create files with build_args_file. We can probably create some sample @path files and store them in the tests/ directory.

// Is this okay?
--config=wrap_comments=true --config=comment_width=80

How is whitespace handled?

// is this okay?
                 --config=wrap_comments=true
--config=comment_width=80                                                 
// there's trailing whitespace here ^^^^^^^^^^^

View changes since the review

@matthewhughes934 matthewhughes934 Oct 29, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Will this enforce that options are newline separated?

Yep, it just reads lines from the file and adds them as args, so

// Is this okay?
--config=wrap_comments=true --config=comment_width=80

This will add a single arg like: "--config=wrap_comments=true --config=comment_width=80"

How is whitespace handled?

// is this okay?
                 --config=wrap_comments=true
--config=comment_width=80                                                 
// there's trailing whitespace here ^^^^^^^^^^^

It's left untouched, so that would result in two args:

[" --config=wrap_comments=true"., "--config=comment_width=80"]

Additionally it means it can't accepts any arguments with newlines in it e.g.

path/to/my/file_that_has_a_newline
in_its_name.rs

Would present as two arguments

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cool. I wouldn't expect us to handle newlines in arguments so that seems fine to me.

@matthewhughes934
matthewhughes934 marked this pull request as ready for review October 30, 2026 19:21
@rustbot rustbot added S-waiting-on-review Status: awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: awaiting some action (such as code changes or more information) from the author. labels Oct 30, 2026

@ytmimi ytmimi Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is this necessary?

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Is this necessary?

From experience, yes: otherwise git on Windows will try and convert these to plain \n (IIRC, that happens under default config, but I can't remember all the details, see the eol attribute if you want to go down a documentation rabbit hole

@ytmimi ytmimi Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Which files are we worried about having their line endings changed? I guess I'm just confused because we don't have this setup anywhere else.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Which files are we worried about having their line endings changed?

args_full_carriage_return uses \r\n for its line endings, so I want to preserve those

I guess I'm just confused because we don't have this setup anywhere else.

We have an equivalent setup for some files in the top level .gitattributes, e.g. tests/source/issue-3494/crlf.rs -text

Which disables the text attribute, and since that attribute means:

This attribute marks the path as a text file, which enables end-of-line conversion

Disabling it means we don't do any eol conversion. I prefer setting the eol attribute as it's more explicit plus it means if e.g. your editor decides to write the file with just \n instead, then git will convert them back.

Poking around, I'm wondering if these tests are silently doing nothing:

$ git grep --files-with-matches $'\r' | git check-attr --stdin text | awk '$NF!="unset"'
tests/source/preserves_carriage_return_for_unix.rs: text: auto
tests/source/preserves_carriage_return_for_windows.rs: text: auto
tests/target/preserves_carriage_return_for_unix.rs: text: auto
tests/target/preserves_carriage_return_for_windows.rs: text: auto
tests/writemode/target/stdin.xml: text: auto

I.e. with text=auto I suspect git will convert all those \r\n to \n (in both source and target files) so the tests pass but we don't actually test anything

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for elaborating. Let's leave a comment somewhere to make it clear that this is for args_full_carriage_return

We can look into those other tests later.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added comment to the attributes: f169a87

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

After more experimentation: -text is indeed the right choice:

Unsetting the text attribute on a path tells Git not to attempt any end-of-line conversion upon checkin or checkout.

I.e. tell git to just leave the file alone cb331d2

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We can look into those other tests later.

Turns out those tests just ignored line endings: #7233

@ytmimi ytmimi Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do we need a whole args_file cargo project? Is it enough to just have the src dir with .rs files?

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Do we need a whole args_file cargo project? Is it enough to just have the src dir with .rs files?

Nope, plain .rs should be fine: c70847a

@matthewhughes934

Copy link
Copy Markdown
Contributor Author

@rustbot ready

@ytmimi ytmimi left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@matthewhughes934 things are looking good. I also wanted to double double check if this supports multiple @path declarations? Maybe arg_expand_all already handles that?

If that's the case maybe we need to update the help text to make it clear that more than one @path can be passed.

View changes since this review

@ytmimi ytmimi added S-waiting-on-author Status: awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: awaiting review from the assignee but also interested parties. labels Nov 4, 2026
@jieyouxu jieyouxu added A-CLI Area: command-line interface A-io Area: related to I/O by rustfmt (fs, stdin/stdout, etc.) labels Nov 5, 2026
@matthewhughes934

matthewhughes934 commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor Author

@matthewhughes934 things are looking good. I also wanted to double double check if this supports multiple @path declarations? Maybe arg_expand_all already handles that?

Yep, the added test test_args_file_multiple_files_merged covers this behaviour

If that's the case maybe we need to update the help text to make it clear that more than one @path can be passed.

Done a7b1781, I also added a full example in the README since I don't think the short help alone is fully clear (at least for me, since I wasn't familiar with args-file via the @ syntax before this)

@matthewhughes934

Copy link
Copy Markdown
Contributor Author

@rustbot ready

@rustbot rustbot added S-waiting-on-review Status: awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: awaiting some action (such as code changes or more information) from the author. labels Nov 5, 2026
@ytmimi

ytmimi commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

Yep, the added test test_args_file_multiple_files_merged covers this behaviour

Sorry, a lot going on and I missed that.

Done a7b1781, I also added a full example in the README since I don't think the short help alone is fully clear (at least for me, since I wasn't familiar with args-file via the @ syntax before this)

Agreed. This is also something I didn't know what possible. Thank you for updating the README.

That is, something like

    $ printf "--config path/to/config.toml\n--check\n" > @args-file
    $ rustfmt @args-file path/to/format.rs
    # same as: rustfmt --config path/to/config.toml --check path/to/format.rs

The motivation for this is that `cargo fmt` collects the list of files
to be formatted and passes them to `rustfmt` as arguments. But this list
can get so long that it reaches OS limits, see rust-lang#6934.

The process for making this change was:

1. Go and look for inspiration from other tools
2. Find out the `rustdoc` supports this pattern[1] so just re-use its
   approach[2]

Conveniently, that approach uses helpers from internal compiler crates
that are also available to us. Note, those helpers expect UTF-8
everywhere, which is what the current code also does, but this will be
an additional place to consider in the future if we want to move away
from that (see rustfmt/issues#7210).

Link: https://doc.rust-lang.org/rustdoc/command-line-arguments.html#path-load-command-line-flags-from-a-path [1]
Link: https://github.com/rust-lang/rust/blob/ab019e71a1a1f4688bcc4d5bf06d2c5a424a31a5/src/librustdoc/lib.rs#L827 [2]
@rustbot

rustbot commented Nov 5, 2026

Copy link
Copy Markdown
Collaborator

This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

@ytmimi

ytmimi commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

@matthewhughes934 thanks for rebasing! was just about to say that I saw some fixup! commits

@matthewhughes934

Copy link
Copy Markdown
Contributor Author

Just noting, I commented on the original issue about this being a breaking change for anyone running rustfmt over a file starting with @, e.g. rustfmt @some_file.rs would break. But since rustdoc and rustc both support this I'm leaning towards this being acceptable. rustfmt ./@some_file.rs will still work fine too.

@matthewhughes934
matthewhughes934 added this pull request to the merge queue Nov 5, 2026
@matthewhughes934
matthewhughes934 removed this pull request from the merge queue due to a manual request Nov 5, 2026
@ytmimi
ytmimi added this pull request to the merge queue Nov 5, 2026
@matthewhughes934

Copy link
Copy Markdown
Contributor Author

ah, thanks @ytmimi I removed it from queue because I wasn't sure if there was subtree syncing going at the moment (so I should avoid merges to keep things simple)

Merged via the queue into rust-lang:main with commit 059b793 Nov 5, 2026
33 checks passed
@rustbot rustbot added release-notes Needs an associated changelog entry and removed S-waiting-on-review Status: awaiting review from the assignee but also interested parties. labels Nov 5, 2026
@matthewhughes934
matthewhughes934 deleted the args-from-path branch November 5, 2026 21:02
matthewhughes934 added a commit to matthewhughes934/rustfmt that referenced this pull request Nov 5, 2026
While working on rust-lang#7220 I got curious about how the
integration tests handle line endings, specifically CRLF, and it turns
out they don't. Given we have features that explicitly add/remove these
line endings we should ensure the tests correctly cover this case to
avoid regressions.

We use `diff::lines` to generate diffs, and this calls `str::lines`
under the hood, which strip any trailing `\r\n` or `\n`. So the only way
we can tell that line-endings differ is by comparing the two contents,
previously we performed this comparison in a way that would ignore if
one file had `\r\n` endings and another had `\n` endings. This meant we
never actually asserted that we saw the expected line endings.

Note: we can only tell the files have differing line endings, not the
lines where this occurs. It _could_ be a future improvement to make this
more specific, but I think the approach is reasonable since there aren't
any tests (or really any use case) for mixed line-endings in a single
file. This reflects the behaviour of how we report such errors with e.g.
`emitter::diff::DiffEmitter::emit_formatted_file`

This required updating some test files that used `newline_style=Windows`
but never actually added those line endings in the corresponding
`target`. In these cases carriage returns are added to the `target`
files, but not the `source` ones so these tests also assert that we do
indeed convert the line endings.

The files
`tests/{target,source}/configs/enum_discrim_align_threshold/40.rs` had
CRLF line endings, but not corresponding `newline_style` option and the
feature does not depend on line endings, so these were converted back to
used Unix line endings
matthewhughes934 added a commit to matthewhughes934/rustfmt that referenced this pull request Nov 5, 2026
While working on rust-lang#7220 I got curious about how the
integration tests handle line endings, specifically CRLF, and it turns
out they don't. Given we have features that explicitly add/remove these
line endings we should ensure the tests correctly cover this case to
avoid regressions.

We use `diff::lines` to generate diffs, and this calls `str::lines`
under the hood, which strip any trailing `\r\n` or `\n`. So the only way
we can tell that line-endings differ is by comparing the two contents,
previously we performed this comparison in a way that would ignore if
one file had `\r\n` endings and another had `\n` endings. This meant we
never actually asserted that we saw the expected line endings.

Note: we can only tell the files have differing line endings, not the
lines where this occurs. It _could_ be a future improvement to make this
more specific, but I think the approach is reasonable since there aren't
any tests (or really any use case) for mixed line-endings in a single
file. This reflects the behaviour of how we report such errors with e.g.
`emitter::diff::DiffEmitter::emit_formatted_file`

This required updating some test files that used `newline_style=Windows`
but never actually added those line endings in the corresponding
`target`. In these cases carriage returns are added to the `target`
files, but not the `source` ones so these tests also assert that we do
indeed convert the line endings.

The files
`tests/{target,source}/configs/enum_discrim_align_threshold/40.rs` had
CRLF line endings, but not corresponding `newline_style` option and the
feature does not depend on line endings, so these were converted back to
used Unix line endings
matthewhughes934 added a commit to matthewhughes934/rustfmt that referenced this pull request Nov 5, 2026
While working on rust-lang#7220 I got curious about how the
integration tests handle line endings, specifically CRLF, and it turns
out they don't. Given we have features that explicitly add/remove these
line endings we should ensure the tests correctly cover this case to
avoid regressions.

We use `diff::lines` to generate diffs, and this calls `str::lines`
under the hood, which strip any trailing `\r\n` or `\n`. So the only way
we can tell that line-endings differ is by comparing the two contents,
previously we performed this comparison in a way that would ignore if
one file had `\r\n` endings and another had `\n` endings. This meant we
never actually asserted that we saw the expected line endings.

Note: we can only tell the files have differing line endings, not the
lines where this occurs. It _could_ be a future improvement to make this
more specific, but I think the approach is reasonable since there aren't
any tests (or really any use case) for mixed line-endings in a single
file. This reflects the behaviour of how we report such errors with e.g.
`emitter::diff::DiffEmitter::emit_formatted_file`

This required updating some test files that used `newline_style=Windows`
but never actually added those line endings in the corresponding
`target`. In these cases carriage returns are added to the `target`
files, but not the `source` ones so these tests also assert that we do
indeed convert the line endings.

The files
`tests/{target,source}/configs/enum_discrim_align_threshold/40.rs` had
CRLF line endings, but not corresponding `newline_style` option and the
feature does not depend on line endings, so these were converted back to
used Unix line endings
matthewhughes934 added a commit to matthewhughes934/rustfmt that referenced this pull request Nov 7, 2026
While working on rust-lang#7220 I got curious about how the
integration tests handle line endings, specifically CRLF, and it turns
out they don't. Given we have features that explicitly add/remove these
line endings we should ensure the tests correctly cover this case to
avoid regressions.

We use `diff::lines` to generate diffs, and this calls `str::lines`
under the hood, which strip any trailing `\r\n` or `\n`. So the only way
we can tell that line-endings differ is by comparing the two contents,
previously we performed this comparison in a way that would ignore if
one file had `\r\n` endings and another had `\n` endings. This meant we
never actually asserted that we saw the expected line endings.

Note: we can only tell the files have differing line endings, not the
lines where this occurs. It _could_ be a future improvement to make this
more specific, but I think the approach is reasonable since there aren't
any tests (or really any use case) for mixed line-endings in a single
file. This reflects the behaviour of how we report such errors with e.g.
`emitter::diff::DiffEmitter::emit_formatted_file`
matthewhughes934 added a commit to matthewhughes934/rustfmt that referenced this pull request Nov 7, 2026
While working on rust-lang#7220 I got curious about how the
integration tests handle line endings, specifically CRLF, and it turns
out they don't. Given we have features that explicitly add/remove these
line endings we should ensure the tests correctly cover this case to
avoid regressions.

We use `diff::lines` to generate diffs, and this calls `str::lines`
under the hood, which strip any trailing `\r\n` or `\n`. So the only way
we can tell that line-endings differ is by comparing the two contents,
previously we performed this comparison in a way that would ignore if
one file had `\r\n` endings and another had `\n` endings. This meant we
never actually asserted that we saw the expected line endings.

Note: we can only tell the files have differing line endings, not the
lines where this occurs. It _could_ be a future improvement to make this
more specific, but I think the approach is reasonable since there aren't
any tests (or really any use case) for mixed line-endings in a single
file. This reflects the behaviour of how we report such errors with e.g.
`emitter::diff::DiffEmitter::emit_formatted_file`
matthewhughes934 added a commit to matthewhughes934/rustfmt that referenced this pull request Nov 7, 2026
While working on rust-lang#7220 I got curious about how the
integration tests handle line endings, specifically CRLF, and it turns
out they don't. Given we have features that explicitly add/remove these
line endings we should ensure the tests correctly cover this case to
avoid regressions.

We use `diff::lines` to generate diffs, and this calls `str::lines`
under the hood, which strip any trailing `\r\n` or `\n`. So the only way
we can tell that line-endings differ is by comparing the two contents,
previously we performed this comparison in a way that would ignore if
one file had `\r\n` endings and another had `\n` endings. This meant we
never actually asserted that we saw the expected line endings.

Note: we can only tell the files have differing line endings, not the
lines where this occurs. It _could_ be a future improvement to make this
more specific, but I think the approach is reasonable since there aren't
any tests (or really any use case) for mixed line-endings in a single
file. This reflects the behaviour of how we report such errors with e.g.
`emitter::diff::DiffEmitter::emit_formatted_file`
matthewhughes934 added a commit to matthewhughes934/rustfmt that referenced this pull request Nov 7, 2026
While working on rust-lang#7220 I got curious about how the
integration tests handle line endings, specifically CRLF, and it turns
out they don't. Given we have features that explicitly add/remove these
line endings we should ensure the tests correctly cover this case to
avoid regressions.

We use `diff::lines` to generate diffs, and this calls `str::lines`
under the hood, which strip any trailing `\r\n` or `\n`. So the only way
we can tell that line-endings differ is by comparing the two contents,
previously we performed this comparison in a way that would ignore if
one file had `\r\n` endings and another had `\n` endings. This meant we
never actually asserted that we saw the expected line endings.

Note: we can only tell the files have differing line endings, not the
lines where this occurs. It _could_ be a future improvement to make this
more specific, but I think the approach is reasonable since there aren't
any tests (or really any use case) for mixed line-endings in a single
file. This reflects the behaviour of how we report such errors with e.g.
`emitter::diff::DiffEmitter::emit_formatted_file`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-CLI Area: command-line interface A-io Area: related to I/O by rustfmt (fs, stdin/stdout, etc.) release-notes Needs an associated changelog entry

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants