Repository: rust-lang/cc-rs (rust)

Issue:
Expose function compile to an object file
Currently, rustc's bootstrap uses `cc` to generate object file by asking it to generate a `.a` archive, discard the archive, and "guessing" where the temporary object files are to use them. Things like [this](https://github.com/rust-lang/rust/blob/c5208518faa423b96e6d1fecaa6428a2fa4eb9d1/src/bootstrap/src/core/build_steps/llvm.rs#L1214). This, however, has been broken since either #684 or #786, as bootstrap does not understand the hashing mechanism used by cc-rs. This prevents upgrading cc in rustc's bootstrap, which is not ideal as `cc` has since included some bugfixes that are necessary to properly handle some situations (such as [this](https://github.com/rust-lang/cc-rs/pull/676)).

I wonder if cc would accept a PR adding a public `Build::compile_objects()` function that would return a `Vec<Path>` of all the object files?

Pull request description:
Add new compile_intermediates function.
This new function can be used to just compile the files to a bunch of .o files. It won't create an archive file, nor print any of the cargo link directives. This is meant to be used by rustc to:

- Compile the MUSL `crtbegin.c` [here](https://github.com/rust-lang/rust/blob/c5208518faa423b96e6d1fecaa6428a2fa4eb9d1/src/bootstrap/src/core/build_steps/llvm.rs#L1214)
- Use different set of arguments when building the C and C++ parts of libunwind [here](https://github.com/rust-lang/rust/blob/c5208518faa423b96e6d1fecaa6428a2fa4eb9d1/src/bootstrap/src/core/build_steps/llvm.rs#L1358)

Currently, rustc guesses the output path of the object files, even though it's an unexposed implementation detail of `cc`. The latest version of `cc` added some hash to the generated object file name, which broke rustc's assumptions.

By providing a proper way to generate an object file using `cc`, this should hopefully make the system more robust.

Fixes #912 

Patch:
diff --git a/src/lib.rs b/src/lib.rs
index 0a5b2ae..ca0f444 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1131,48 +1131,7 @@ impl Build {
         };
         let dst = self.get_out_dir()?;
 
-        let mut objects = Vec::new();
-        for file in self.files.iter() {
-            let obj = if file.has_root() || file.components().any(|x| x == Component::ParentDir) {
-                // If `file` is an absolute path or might not be usable directly as a suffix due to
-                // using "..", use the `basename` prefixed with the `dirname`'s hash to ensure name
-                // uniqueness.
-                let basename = file
-                    .file_name()
-                    .ok_or_else(|| Error::new(ErrorKind::InvalidArgument, "file_name() failure"))?
-                    .to_string_lossy();
-                let dirname = file
-                    .parent()
-                    .ok_or_else(|| Error::new(ErrorKind::InvalidArgument, "parent() failure"))?
-                    .to_string_lossy();
-                let mut hasher = hash_map::DefaultHasher::new();
-                hasher.write(dirname.to_string().as_bytes());
-                dst.join(format!("{:016x}-{}", hasher.finish(), basename))
-                    .with_extension("o")
-            } else {
-                dst.join(file).with_extension("o")
-            };
-            let obj = if !obj.starts_with(&dst) {
-                dst.join(obj.file_name().ok_or_else(|| {
-                    Error::new(ErrorKind::IOError, "Getting object file details failed.")
-                })?)
-            } else {
-                obj
-            };
-
-            match obj.parent() {
-                Some(s) => fs::create_dir_all(s)?,
-                None => {
-                    return Err(Error::new(
-                        ErrorKind::IOError,
-                        "Getting object file details failed.",
-                    ));
-                }
-            };
-
-            objects.push(Object::new(file.to_path_buf(), obj));
-        }
-
+        let objects = objects_from_files(&self.files, &dst)?;
         let print = PrintThread::new()?;
 
         self.compile_objects(&objects, &print)?;
@@ -1316,6 +1275,32 @@ impl Build {
         }
     }
 
+    /// Run the compiler, generating intermediate files, but without linking
+    /// them into an archive file.
+    ///
+    /// This will return a list of compiled object files, in the same order
+    /// as they were passed in as `file`/`files` methods.
+    pub fn compile_intermediates(&self) -> Vec<PathBuf> {
+        match self.try_compile_intermediates() {
+            Ok(v) => v,
+            Err(e) => fail(&e.message),
+        }
+    }
+
+    /// Run the compiler, generating intermediate files, but without linking
+    /// them into an archive file.
+    ///
+    /// This will return a result instead of panicing; see `compile_intermediates()` for the complete description.
+    pub fn try_compile_intermediates(&self) -> Result<Vec<PathBuf>, Error> {
+        let dst = self.get_out_dir()?;
+        let objects = objects_from_files(&self.files, &dst)?;
+        let print = PrintThread::new()?;
+
+        self.compile_objects(&objects, &print)?;
+
+        Ok(objects.into_iter().map(|v| v.dst).collect())
+    }
+
     #[cfg(feature = "parallel")]
     fn compile_objects(&self, objs: &[Object], print: &PrintThread) -> Result<(), Error> {
         use std::cell::Cell;
@@ -2379,6 +2364,7 @@ impl Build {
     }
 
     fn apple_flags(&self, cmd: &mut Tool) -> Result<(), Error> {
+        #[allow(dead_code)]
         enum ArchSpec {
             Device(&'static str),
             Simulator(&'static str),
@@ -3837,6 +3823,54 @@ fn wait_on_child(cmd: &Command, program: &str, child: &mut Child) -> Result<(),
     }
 }
 
+/// Find the destination object path for each file in the input source files,
+/// and store them in the output Object.
+fn objects_from_files(files: &[Arc<Path>], dst: &Path) -> Result<Vec<Object>, Error> {
+    let mut objects = Vec::with_capacity(files.len());
+    for file in files {
+        let basename = file
+            .file_name()
+            .ok_or_else(|| {
+                Error::new(
+                    ErrorKind::InvalidArgument,
+                    "No file_name for object file path!",
+                )
+            })?
+            .to_string_lossy();
+        let dirname = file
+            .parent()
+            .ok_or_else(|| {
+                Error::new(
+                    ErrorKind::InvalidArgument,
+                    "No parent for object file path!",
+                )
+            })?
+            .to_string_lossy();
+
+        // Hash the dirname. This should prevent conflicts if we have multiple
+        // object files with the same filename in different subfolders.
+        let mut hasher = hash_map::DefaultHasher::new();
+        hasher.write(dirname.to_string().as_bytes());
+        let obj = dst
+            .join(format!("{:016x}-{}", hasher.finish(), basename))
+            .with_extension("o");
+
+        match obj.parent() {
+            Some(s) => fs::create_dir_all(s)?,
+            None => {
+                return Err(Error::new(
+                    ErrorKind::InvalidArgument,
+                    "dst is an invalid path with no parent",
+                ));
+            }
+        };
+
+        objects.push(Object::new(file.to_path_buf(), obj));
+    }
+
+    Ok(objects)
+}
+
 #[cfg(feature = "parallel")]
 fn try_wait_on_child(
     cmd: &Command,


Test patch:
diff --git a/tests/test.rs b/tests/test.rs
index 36cdd84..1983fd3 100644
--- a/tests/test.rs
+++ b/tests/test.rs
@@ -24,7 +24,8 @@ fn gnu_smoke() {
         .must_have("-c")
         .must_have("-ffunction-sections")
         .must_have("-fdata-sections");
-    test.cmd(1).must_have(test.td.path().join("foo.o"));
+    test.cmd(1)
+        .must_have(test.td.path().join("d1fba762150c532c-foo.o"));
 }
 
 #[test]
@@ -399,7 +400,8 @@ fn msvc_smoke() {
         .must_not_have("-Z7")
         .must_have("-c")
         .must_have("-MD");
-    test.cmd(1).must_have(test.td.path().join("foo.o"));
+    test.cmd(1)
+        .must_have(test.td.path().join("d1fba762150c532c-foo.o"));
 }
 
 #[test]
@@ -576,3 +578,21 @@ fn clang_apple_tvsimulator() {
         test.cmd(0).must_have("-mappletvsimulator-version-min=9.0");
     }
 }
+
+#[test]
+fn compile_intermediates() {
+    let test = Test::gnu();
+    let intermediates = test
+        .gcc()
+        .file("foo.c")
+        .file("x86_64.asm")
+        .file("x86_64.S")
+        .asm_flag("--abc")
+        .compile_intermediates();
+
+    assert_eq!(intermediates.len(), 3);
+
+    assert!(intermediates[0].display().to_string().contains("foo"));
+    assert!(intermediates[1].display().to_string().contains("x86_64"));
+    assert!(intermediates[2].display().to_string().contains("x86_64"));
+}