Skip to content

Commit e1ba867

Browse files
committed
Normalize -f*-prefix-map args via SCCACHE_BASEDIRS
These compound flags embed absolute paths that `strip_basedirs` cannot reach via preprocessor-output normalization, causing cache misses when the same sources are built from different directories. Extract the `OLD` path component and strip basedir prefixes from it before hashing.
1 parent 619c72c commit e1ba867

2 files changed

Lines changed: 77 additions & 2 deletions

File tree

src/compiler/c.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use crate::dist::pkg;
2727
use crate::mock_command::CommandCreatorSync;
2828
use crate::util::{
2929
Digest, HashToDigest, MetadataCtimeExt, TimeMacroFinder, Timestamp, decode_path, encode_path,
30-
hash_all, strip_basedirs,
30+
hash_all, normalize_prefix_map_arg, strip_basedirs,
3131
};
3232
use async_trait::async_trait;
3333
use fs_err as fs;
@@ -1566,7 +1566,7 @@ impl<'a> HashKeyParams<'a> {
15661566
m.update(CACHE_VERSION);
15671567
m.update(self.language.as_str().as_bytes());
15681568
for arg in self.arguments {
1569-
arg.hash(&mut HashToDigest { digest: &mut m });
1569+
normalize_prefix_map_arg(arg, self.basedirs).hash(&mut HashToDigest { digest: &mut m });
15701570
}
15711571
for hash in self.extra_hashes {
15721572
m.update(hash.as_bytes());

src/util.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1109,6 +1109,45 @@ pub fn strip_basedirs<'a>(preprocessor_output: &'a [u8], basedirs: &[Vec<u8>]) -
11091109
Cow::Owned(result)
11101110
}
11111111

1112+
const PREFIX_MAP_FLAGS: &[&[u8]] = &[
1113+
b"-fdebug-prefix-map=",
1114+
b"-fmacro-prefix-map=",
1115+
b"-ffile-prefix-map=",
1116+
];
1117+
1118+
/// Normalize a `-f{debug,macro,file}-prefix-map=OLD=NEW` compiler argument for cache key
1119+
/// computation by stripping basedir prefixes from the OLD path component.
1120+
///
1121+
/// These flags embed absolute source or object directory paths. Because they are compound
1122+
/// arguments (not standalone paths), `strip_basedirs` cannot reach them via preprocessor-output
1123+
/// normalization. This function handles them explicitly so builds from different directories
1124+
/// can share cache entries when `SCCACHE_BASEDIRS` is configured.
1125+
///
1126+
/// Returns `Cow::Owned` with the normalized argument if a basedir was stripped, or
1127+
/// `Cow::Borrowed` of the original if the argument was not a prefix-map flag or no basedir matched.
1128+
pub fn normalize_prefix_map_arg<'a>(arg: &'a OsStr, basedirs: &[Vec<u8>]) -> Cow<'a, OsStr> {
1129+
fn try_normalize(bytes: &[u8], basedirs: &[Vec<u8>]) -> Option<Vec<u8>> {
1130+
let (flag, rest) = PREFIX_MAP_FLAGS
1131+
.iter()
1132+
.find_map(|&flag| Some((flag, bytes.strip_prefix(flag)?)))?;
1133+
// Format is OLD=NEW; split on the first '='
1134+
let sep = rest.iter().position(|&b| b == b'=')?;
1135+
let old = &rest[..sep];
1136+
// Strip the longest matching basedir prefix from OLD
1137+
let stripped_old = basedirs
1138+
.iter()
1139+
.filter(|dir| old.starts_with(dir.as_slice()))
1140+
.max_by_key(|dir| dir.len())
1141+
.map(|dir| &old[dir.len()..])?;
1142+
Some([flag, stripped_old, &rest[sep..]].concat())
1143+
}
1144+
1145+
// SAFETY: result bytes originate from `arg.as_encoded_bytes()`, preserving its encoding
1146+
try_normalize(arg.as_encoded_bytes(), basedirs)
1147+
.map(|b| Cow::Owned(unsafe { OsString::from_encoded_bytes_unchecked(b) }))
1148+
.unwrap_or(Cow::Borrowed(arg))
1149+
}
1150+
11121151
/// Normalize path for case-insensitive comparison.
11131152
/// On Windows: converts all backslashes to forward slashes;
11141153
/// lowercases characters for consistency.
@@ -1249,6 +1288,7 @@ pub fn resolve_compiler_avoiding_ccache(
12491288
#[cfg(test)]
12501289
mod tests {
12511290
use super::{OsStrExt, TimeMacroFinder, resolve_compiler_avoiding_ccache};
1291+
use std::borrow::Cow;
12521292
use std::ffi::{OsStr, OsString};
12531293
use std::path::Path;
12541294

@@ -1625,6 +1665,41 @@ mod tests {
16251665
);
16261666
}
16271667

1668+
#[test]
1669+
fn test_normalize_prefix_map_arg() {
1670+
let basedirs = vec![b"/home/user/project/".to_vec()];
1671+
1672+
// Each flag variant with matching basedir — OLD is fully stripped, leaving empty OLD
1673+
for flag in &[
1674+
"-fdebug-prefix-map",
1675+
"-fmacro-prefix-map",
1676+
"-ffile-prefix-map",
1677+
] {
1678+
let arg = OsString::from(format!("{flag}=/home/user/project/=/topsrcdir/"));
1679+
let result = super::normalize_prefix_map_arg(&arg, &basedirs);
1680+
assert_eq!(
1681+
&*result,
1682+
OsStr::new(&format!("{flag}==/topsrcdir/")),
1683+
"{flag}"
1684+
);
1685+
}
1686+
1687+
// Non-matching basedir returns the original (Borrowed)
1688+
let arg = OsString::from("-fdebug-prefix-map=/other/path/=/topsrcdir/");
1689+
let result = super::normalize_prefix_map_arg(&arg, &basedirs);
1690+
assert!(matches!(result, Cow::Borrowed(_)));
1691+
1692+
// Non-prefix-map arg returns the original (Borrowed)
1693+
let arg = OsString::from("-I/home/user/project/include");
1694+
let result = super::normalize_prefix_map_arg(&arg, &basedirs);
1695+
assert!(matches!(result, Cow::Borrowed(_)));
1696+
1697+
// Empty basedirs returns the original (Borrowed)
1698+
let arg = OsString::from("-fdebug-prefix-map=/home/user/project/=/topsrcdir/");
1699+
let result = super::normalize_prefix_map_arg(&arg, &[]);
1700+
assert!(matches!(result, Cow::Borrowed(_)));
1701+
}
1702+
16281703
#[test]
16291704
fn test_normalize_win_path_ascii() {
16301705
// Test basic ASCII normalization

0 commit comments

Comments
 (0)