use std::{
env,
path::{Path, PathBuf},
};
use crate::error::Error;
pub(crate) fn derive_object_and_bitcode_filepath<P, Q>(
src_filepath: P,
output_dir: Q,
is_compile_only: bool,
) -> Result<(PathBuf, PathBuf), Error>
where
P: AsRef<Path>,
Q: AsRef<Path>,
{
let src_filepath = src_filepath.as_ref();
if !src_filepath.is_absolute() {
return Err(Error::InvalidArguments(format!(
"'src_filepath' must be absolute: {:?}",
src_filepath
)));
}
let output_dir = output_dir.as_ref();
let file_stem = src_filepath
.file_stem()
.ok_or_else(|| {
Error::InvalidArguments(format!(
"Failed to obtain the file stem: {:?}",
src_filepath
))
})?
.to_str()
.ok_or_else(|| {
Error::InvalidArguments(format!(
"Failed to convert OsStr to str: {:?}",
src_filepath
))
})?;
let src_filepath_hash = calculate_filepath_hash(src_filepath);
let artifact_stem = format!("{file_stem}_{src_filepath_hash:016x}");
let bitcode_filepath = output_dir.join(format!(".{artifact_stem}.o.bc"));
let object_filepath = if is_compile_only {
env::current_dir()?.join(format!("{file_stem}.o"))
} else {
output_dir.join(format!(".{artifact_stem}.o"))
};
Ok((object_filepath, bitcode_filepath))
}
const FNV_OFFSET_BASIS: u64 = 0xcbf2_9ce4_8422_2325;
const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
pub fn calculate_filepath_hash<P>(filepath: P) -> u64
where
P: AsRef<Path>,
{
let filepath = filepath.as_ref();
let mut hash = FNV_OFFSET_BASIS;
for byte in filepath.to_string_lossy().as_bytes() {
hash ^= u64::from(*byte);
hash = hash.wrapping_mul(FNV_PRIME);
}
hash
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_derive_object_and_bitcode_filepath() {
let src_dir = env::temp_dir();
let src_filepath = src_dir.join("foo.c");
let src_filepath = src_filepath.as_path();
let output_dir = env::temp_dir().join("rllvm-output");
let hash = calculate_filepath_hash(src_filepath);
let stem = format!("foo_{hash:016x}");
let (object_filepath, bitcode_filepath) =
derive_object_and_bitcode_filepath(src_filepath, &output_dir, false)
.expect("Failed to derive filepaths");
assert_eq!(object_filepath, output_dir.join(format!(".{stem}.o")));
assert_eq!(bitcode_filepath, output_dir.join(format!(".{stem}.o.bc")));
let (object_filepath, bitcode_filepath) =
derive_object_and_bitcode_filepath(src_filepath, &output_dir, true)
.expect("Failed to derive filepaths");
assert_eq!(
object_filepath,
env::current_dir().unwrap().join("foo.o"),
"compile-only object file belongs in the working directory"
);
assert_eq!(bitcode_filepath, output_dir.join(format!(".{stem}.o.bc")));
}
#[test]
fn test_same_stem_in_different_directories_does_not_collide() {
let output_dir = env::temp_dir().join("rllvm-output");
let first = env::temp_dir().join("a").join("util.c");
let second = env::temp_dir().join("b").join("util.c");
let (first_object, first_bitcode) =
derive_object_and_bitcode_filepath(&first, &output_dir, false)
.expect("Failed to derive filepaths");
let (second_object, second_bitcode) =
derive_object_and_bitcode_filepath(&second, &output_dir, false)
.expect("Failed to derive filepaths");
assert_ne!(first_object, second_object);
assert_ne!(first_bitcode, second_bitcode);
}
#[test]
fn test_calculate_filepath_hash_is_stable() {
assert_eq!(
calculate_filepath_hash(Path::new("")),
0xcbf2_9ce4_8422_2325
);
assert_eq!(
calculate_filepath_hash(Path::new("/tmp/foo.c")),
6720249941370504407
);
assert_eq!(
calculate_filepath_hash(Path::new(
"/home/user/projects/very/deeply/nested/directory/structure/with/many/components/source_file.c"
)),
16351161328938945821
);
}
#[test]
fn test_calculate_filepath_hash_distinguishes_paths() {
let foo = calculate_filepath_hash(Path::new("/tmp/foo.c"));
let bar = calculate_filepath_hash(Path::new("/tmp/bar.c"));
let nested = calculate_filepath_hash(Path::new("/tmp/sub/foo.c"));
assert_ne!(foo, bar, "different file stems must hash differently");
assert_ne!(foo, nested, "different directories must hash differently");
assert_eq!(foo, calculate_filepath_hash(PathBuf::from("/tmp/foo.c")));
}
}