Skip to content

Commit f47732b

Browse files
committed
Auto merge of rust-lang#3330 - RossSmyth:win-fmt, r=RalfJung
Fix .\miri fmt on Windows This allows .\miri fmt to work on Windows. Closes rust-lang#3317. To reiterate, the problem with using `miri fmt` on Windows is that the CLI arguments to rustfmt are too long. Currently over 65,000 characters are used in the call to rustfmt, [which is incompatible with Windows](https://devblogs.microsoft.com/oldnewthing/20031210-00/?p=41553) as it is limited to (2^15 - 1) for all arguments plus all env vars. Two things are done do get around this limit: 1. Call out to cargo-fmt for the crates that exist. 2. Batch rustfmt calls by length Another alternative would be to just use rustfmt for everything and don't use cargo-fmt for the crates. I don't know how much you guys may care about `miri fmt` time to run. I don't know the diff as it did not work before on my computer. [I have another branch that solves this, but in a less permanent way](RossSmyth/miri/tree/windows-fmt). That was my initial attempt, and I learned that even with cargo-fmt and relative paths, the rustfmt call still has 27k characters. This is closer to the limit than I expected, so it would not be a permanent solution. So I went back to absolute paths & batching.
2 parents f51f923 + 83e2e2d commit f47732b

File tree

2 files changed

+64
-30
lines changed

2 files changed

+64
-30
lines changed

Diff for: src/tools/miri/miri-script/src/commands.rs

+18-28
Original file line numberDiff line numberDiff line change
@@ -526,37 +526,27 @@ impl Command {
526526
}
527527

528528
fn fmt(flags: Vec<OsString>) -> Result<()> {
529+
use itertools::Itertools;
530+
529531
let e = MiriEnv::new()?;
530-
let toolchain = &e.toolchain;
531532
let config_path = path!(e.miri_dir / "rustfmt.toml");
532533

533-
let mut cmd = cmd!(
534-
e.sh,
535-
"rustfmt +{toolchain} --edition=2021 --config-path {config_path} --unstable-features --skip-children {flags...}"
536-
);
537-
eprintln!("$ {cmd} ...");
538-
539-
// Add all the filenames to the command.
540-
// FIXME: `rustfmt` will follow the `mod` statements in these files, so we get a bunch of
541-
// duplicate diffs.
542-
for item in WalkDir::new(&e.miri_dir).into_iter().filter_entry(|entry| {
543-
let name = entry.file_name().to_string_lossy();
544-
let ty = entry.file_type();
545-
if ty.is_file() {
546-
name.ends_with(".rs")
547-
} else {
548-
// dir or symlink. skip `target` and `.git`.
549-
&name != "target" && &name != ".git"
550-
}
551-
}) {
552-
let item = item?;
553-
if item.file_type().is_file() {
554-
cmd = cmd.arg(item.into_path());
555-
}
556-
}
534+
// Collect each rust file in the miri repo.
535+
let files = WalkDir::new(&e.miri_dir)
536+
.into_iter()
537+
.filter_entry(|entry| {
538+
let name = entry.file_name().to_string_lossy();
539+
let ty = entry.file_type();
540+
if ty.is_file() {
541+
name.ends_with(".rs")
542+
} else {
543+
// dir or symlink. skip `target` and `.git`.
544+
&name != "target" && &name != ".git"
545+
}
546+
})
547+
.filter_ok(|item| item.file_type().is_file())
548+
.map_ok(|item| item.into_path());
557549

558-
// We want our own error message, repeating the command is too much.
559-
cmd.quiet().run().map_err(|_| anyhow!("`rustfmt` failed"))?;
560-
Ok(())
550+
e.format_files(files, &e.toolchain[..], &config_path, &flags[..])
561551
}
562552
}

Diff for: src/tools/miri/miri-script/src/util.rs

+46-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::ffi::{OsStr, OsString};
2-
use std::path::PathBuf;
2+
use std::path::{Path, PathBuf};
33

4-
use anyhow::{Context, Result};
4+
use anyhow::{anyhow, Context, Result};
55
use dunce::canonicalize;
66
use path_macro::path;
77
use xshell::{cmd, Shell};
@@ -145,4 +145,48 @@ impl MiriEnv {
145145
.run()?;
146146
Ok(())
147147
}
148+
149+
/// Receives an iterator of files.
150+
/// Will format each file with the miri rustfmt config.
151+
/// Does not recursively format modules.
152+
pub fn format_files(
153+
&self,
154+
files: impl Iterator<Item = Result<PathBuf, walkdir::Error>>,
155+
toolchain: &str,
156+
config_path: &Path,
157+
flags: &[OsString],
158+
) -> anyhow::Result<()> {
159+
use itertools::Itertools;
160+
161+
let mut first = true;
162+
163+
// Format in batches as not all our files fit into Windows' command argument limit.
164+
for batch in &files.chunks(256) {
165+
// Build base command.
166+
let mut cmd = cmd!(
167+
self.sh,
168+
"rustfmt +{toolchain} --edition=2021 --config-path {config_path} --unstable-features --skip-children {flags...}"
169+
);
170+
if first {
171+
// Log an abbreviating command, and only once.
172+
eprintln!("$ {cmd} ...");
173+
first = false;
174+
}
175+
// Add files.
176+
for file in batch {
177+
// Make it a relative path so that on platforms with extremely tight argument
178+
// limits (like Windows), we become immune to someone cloning the repo
179+
// 50 directories deep.
180+
let file = file?;
181+
let file = file.strip_prefix(&self.miri_dir)?;
182+
cmd = cmd.arg(file);
183+
}
184+
185+
// Run rustfmt.
186+
// We want our own error message, repeating the command is too much.
187+
cmd.quiet().run().map_err(|_| anyhow!("`rustfmt` failed"))?;
188+
}
189+
190+
Ok(())
191+
}
148192
}

0 commit comments

Comments
 (0)