From 385b43f72fbe492de4799218092d5f493754bdeb Mon Sep 17 00:00:00 2001 From: Robin Lambertz Date: Thu, 25 Jan 2024 00:00:50 +0100 Subject: [PATCH] Add new compile_intermediates function. (#914) * Add new compile_intermediates function. This new function can be used to just compile the files to a bunch of .o files. * Pre-allocate objects vec in objects_from_files * Remove some dead code in objects_from_file Since https://github.com/rust-lang/cc-rs/pull/684 was merged, it is impossible for `obj` to not start with dst - if `obj` is absolute or contains `../` in its Path, then the path will be hashed, and the file will still be placed under dst. As such, this obj.starts_with(&dst) check is always true. * Always hash the file prefix * Fix error handling of objects_from_files * Fix nightly warning --- src/lib.rs | 118 ++++++++++++++++++++++++++++++++------------------ tests/test.rs | 24 +++++++++- 2 files changed, 98 insertions(+), 44 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0a5b2ae35..ca0f444dd 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 { + 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, 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], dst: &Path) -> Result, 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, diff --git a/tests/test.rs b/tests/test.rs index 36cdd8410..1983fd321 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")); +}