]> git.proxmox.com Git - cargo.git/commitdiff
Retry with argfile for cargo in rustc fix-proxy-mode
authorWeihang Lo <me@weihanglo.tw>
Fri, 8 Apr 2022 09:00:00 +0000 (17:00 +0800)
committerWeihang Lo <me@weihanglo.tw>
Fri, 8 Apr 2022 07:39:37 +0000 (15:39 +0800)
src/cargo/ops/fix.rs

index 8c970ef171dbbc3f2741aac9180c9fe3a937e712..67e742360f9e6fe7fc443ddc309cbfecbdaab4d9 100644 (file)
 //!   show them to the user.
 
 use std::collections::{BTreeSet, HashMap, HashSet};
-use std::env;
 use std::ffi::OsString;
 use std::path::{Path, PathBuf};
 use std::process::{self, ExitStatus};
-use std::str;
+use std::{env, fs, str};
 
 use anyhow::{bail, Context, Error};
 use cargo_util::{exit_status_to_string, is_simple_exit_code, paths, ProcessBuilder};
@@ -122,6 +121,9 @@ pub fn fix(ws: &Workspace<'_>, opts: &mut FixOptions) -> CargoResult<()> {
 
     let rustc = ws.config().load_global_rustc(Some(ws))?;
     wrapper.arg(&rustc.path);
+    // This is calling rustc in cargo fix-proxy-mode, so it also need to retry.
+    // The argfile handling are located at `FixArgs::from_args`.
+    wrapper.retry_with_argfile(true);
 
     // primary crates are compiled using a cargo subprocess to do extra work of applying fixes and
     // repeating build until there are no more changes to be applied
@@ -352,6 +354,7 @@ pub fn fix_maybe_exec_rustc(config: &Config) -> CargoResult<bool> {
         .map(PathBuf::from)
         .ok();
     let mut rustc = ProcessBuilder::new(&args.rustc).wrapped(workspace_rustc.as_ref());
+    rustc.retry_with_argfile(true);
     rustc.env_remove(FIX_ENV);
     args.apply(&mut rustc);
 
@@ -774,35 +777,65 @@ struct FixArgs {
 
 impl FixArgs {
     fn get() -> Result<FixArgs, Error> {
-        let rustc = env::args_os()
+        Self::from_args(env::args_os())
+    }
+
+    // This is a separate function so that we can use it in tests.
+    fn from_args(argv: impl IntoIterator<Item = OsString>) -> Result<Self, Error> {
+        let mut argv = argv.into_iter();
+        let mut rustc = argv
             .nth(1)
             .map(PathBuf::from)
-            .ok_or_else(|| anyhow::anyhow!("expected rustc as first argument"))?;
+            .ok_or_else(|| anyhow::anyhow!("expected rustc or `@path` as first argument"))?;
         let mut file = None;
         let mut enabled_edition = None;
         let mut other = Vec::new();
         let mut format_args = Vec::new();
 
-        for arg in env::args_os().skip(2) {
+        let mut handle_arg = |arg: OsString| -> Result<(), Error> {
             let path = PathBuf::from(arg);
             if path.extension().and_then(|s| s.to_str()) == Some("rs") && path.exists() {
                 file = Some(path);
-                continue;
+                return Ok(());
             }
             if let Some(s) = path.to_str() {
                 if let Some(edition) = s.strip_prefix("--edition=") {
                     enabled_edition = Some(edition.parse()?);
-                    continue;
+                    return Ok(());
                 }
                 if s.starts_with("--error-format=") || s.starts_with("--json=") {
                     // Cargo may add error-format in some cases, but `cargo
                     // fix` wants to add its own.
                     format_args.push(s.to_string());
-                    continue;
+                    return Ok(());
                 }
             }
             other.push(path.into());
+            Ok(())
+        };
+
+        if let Some(argfile_path) = rustc.to_str().unwrap_or_default().strip_prefix("@") {
+            // Because cargo in fix-proxy-mode might hit the command line size limit,
+            // cargo fix need handle `@path` argfile for this special case.
+            if argv.next().is_some() {
+                bail!("argfile `@path` cannot be combined with other arguments");
+            }
+            let contents = fs::read_to_string(argfile_path)
+                .with_context(|| format!("failed to read argfile at `{argfile_path}`"))?;
+            let mut iter = contents.lines().map(OsString::from);
+            rustc = iter
+                .next()
+                .map(PathBuf::from)
+                .ok_or_else(|| anyhow::anyhow!("expected rustc as first argument"))?;
+            for arg in iter {
+                handle_arg(arg)?;
+            }
+        } else {
+            for arg in argv {
+                handle_arg(arg)?;
+            }
         }
+
         let file = file.ok_or_else(|| anyhow::anyhow!("could not find .rs file in rustc args"))?;
         let idioms = env::var(IDIOMS_ENV).is_ok();
 
@@ -914,3 +947,46 @@ impl FixArgs {
         .and(Ok(true))
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::FixArgs;
+    use std::ffi::OsString;
+    use std::io::Write as _;
+    use std::path::PathBuf;
+
+    #[test]
+    fn get_fix_args_from_argfile() {
+        let mut temp = tempfile::Builder::new().tempfile().unwrap();
+        let main_rs = tempfile::Builder::new().suffix(".rs").tempfile().unwrap();
+
+        let content = format!("/path/to/rustc\n{}\nfoobar\n", main_rs.path().display());
+        temp.write_all(content.as_bytes()).unwrap();
+
+        let argfile = format!("@{}", temp.path().display());
+        let args = ["cargo", &argfile];
+        let fix_args = FixArgs::from_args(args.map(|x| x.into())).unwrap();
+        assert_eq!(fix_args.rustc, PathBuf::from("/path/to/rustc"));
+        assert_eq!(fix_args.file, main_rs.path());
+        assert_eq!(fix_args.other, vec![OsString::from("foobar")]);
+    }
+
+    #[test]
+    fn get_fix_args_from_argfile_with_extra_arg() {
+        let mut temp = tempfile::Builder::new().tempfile().unwrap();
+        let main_rs = tempfile::Builder::new().suffix(".rs").tempfile().unwrap();
+
+        let content = format!("/path/to/rustc\n{}\nfoobar\n", main_rs.path().display());
+        temp.write_all(content.as_bytes()).unwrap();
+
+        let argfile = format!("@{}", temp.path().display());
+        let args = ["cargo", &argfile, "boo!"];
+        match FixArgs::from_args(args.map(|x| x.into())) {
+            Err(e) => assert_eq!(
+                e.to_string(),
+                "argfile `@path` cannot be combined with other arguments"
+            ),
+            Ok(_) => panic!("should fail"),
+        }
+    }
+}