Skip to content

Commit cf0b871

Browse files
committed
Make basedirs match case insensitive on Windows
1 parent 6f47f36 commit cf0b871

File tree

3 files changed

+50
-17
lines changed

3 files changed

+50
-17
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,8 @@ You can also specify multiple base directories by separating them with `|` (pipe
293293
export SCCACHE_BASEDIRS="/home/user/project|/home/user/workspace"
294294
```
295295

296+
Path matching is **case-insensitive** on Windows and **case-sensitive** on other operating systems.
297+
296298
This is similar to ccache's `CCACHE_BASEDIR` and helps when:
297299
* Building the same project from different directories
298300
* Sharing cache between CI jobs with different checkout paths

docs/Configuration.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ server_startup_timeout_ms = 10000
1111
# different absolute paths when compiling the same source code.
1212
# Can be an array of paths. When multiple paths are provided,
1313
# the longest matching prefix is used.
14+
# Path matching is case-insensitive on Windows and case-sensitive on other OSes.
1415
# For example, if basedir is "/home/user/project", then paths like
1516
# "/home/user/project/src/main.c" will be normalized to "./src/main.c"
1617
# for caching purposes.
@@ -146,7 +147,7 @@ Note that some env variables may need sccache server restart to take effect.
146147

147148
* `SCCACHE_ALLOW_CORE_DUMPS` to enable core dumps by the server
148149
* `SCCACHE_CONF` configuration file path
149-
* `SCCACHE_BASEDIRS` base directory (or directories) to strip from paths for cache key computation. This is similar to ccache's `CCACHE_BASEDIR` and enables cache hits across different absolute paths when compiling the same source code. Multiple directories can be separated by `|` (pipe character). When multiple directories are specified, the longest matching prefix is used. Environment variable takes precedence over file configuration. Only absolute paths are supported; relative paths will be ignored with a warning.
150+
* `SCCACHE_BASEDIRS` base directory (or directories) to strip from paths for cache key computation. This is similar to ccache's `CCACHE_BASEDIR` and enables cache hits across different absolute paths when compiling the same source code. Multiple directories can be separated by `|` (pipe character). When multiple directories are specified, the longest matching prefix is used. Path matching is **case-insensitive** on Windows and **case-sensitive** on other operating systems. Environment variable takes precedence over file configuration. Only absolute paths are supported; relative paths will be ignored with a warning.
150151
* `SCCACHE_CACHED_CONF`
151152
* `SCCACHE_IDLE_TIMEOUT` how long the local daemon process waits for more client requests before exiting, in seconds. Set to `0` to run sccache permanently
152153
* `SCCACHE_STARTUP_NOTIFY` specify a path to a socket which will be used for server completion notification

src/util.rs

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,8 +1021,10 @@ pub fn num_cpus() -> usize {
10211021
/// replaces them with relative path markers. When multiple basedirs are provided,
10221022
/// the longest matching prefix is used. This is similar to ccache's CCACHE_BASEDIR.
10231023
///
1024-
/// On Windows, this function handles paths with mixed forward and backward slashes,
1025-
/// which can occur when different build tools produce preprocessor output.
1024+
/// Path matching is case-insensitive to handle various filesystem behaviors and build system
1025+
/// configurations uniformly across all operating systems. On Windows, this function also handles
1026+
/// paths with mixed forward and backward slashes, which can occur when different build tools
1027+
/// produce preprocessor output.
10261028
///
10271029
/// Only paths that start with one of the basedirs are modified. The paths are expected to be
10281030
/// in the format found in preprocessor output (e.g., `# 1 "/path/to/file"`).
@@ -1059,19 +1061,19 @@ pub fn strip_basedirs(preprocessor_output: &[u8], basedirs: &[PathBuf]) -> Vec<u
10591061
for (basedir_bytes, basedir_len) in &basedirs_data {
10601062
// Check if we have a match for this basedir at current position
10611063
if i + basedir_len <= preprocessor_output.len() {
1062-
// On Windows, we need to handle mixed forward and backward slashes
1063-
// Check for exact match first
1064-
let exact_match = preprocessor_output[i..i + basedir_len] == basedir_bytes[..];
1065-
1066-
let slash_normalized_match = if cfg!(target_os = "windows") && !exact_match {
1067-
// Try matching with normalized slashes (convert all backslashes to forward slashes)
1068-
normalize_path_slashes(&preprocessor_output[i..i + basedir_len])
1069-
== normalize_path_slashes(basedir_bytes)
1064+
let candidate = &preprocessor_output[i..i + basedir_len];
1065+
1066+
// Try exact match first
1067+
let exact_match = candidate == basedir_bytes;
1068+
1069+
// Try case-insensitive match
1070+
let normalized_match = if !exact_match {
1071+
normalize_path(candidate) == normalize_path(basedir_bytes)
10701072
} else {
10711073
false
10721074
};
10731075

1074-
if exact_match || slash_normalized_match {
1076+
if exact_match || normalized_match {
10751077
// Check if this is actually a path boundary (preceded by whitespace, quote, or start)
10761078
let is_boundary = i == 0
10771079
|| preprocessor_output[i - 1].is_ascii_whitespace()
@@ -1098,17 +1100,22 @@ pub fn strip_basedirs(preprocessor_output: &[u8], basedirs: &[PathBuf]) -> Vec<u
10981100
result
10991101
}
11001102

1101-
/// Normalize path slashes for comparison (Windows only).
1102-
/// Converts all backslashes to forward slashes for consistent comparison.
1103+
/// Normalize path for case-insensitive comparison.
1104+
/// On Windows: converts all backslashes to forward slashes;
1105+
/// lowercases ASCII characters for consistency.
11031106
#[cfg(target_os = "windows")]
1104-
fn normalize_path_slashes(path: &[u8]) -> Vec<u8> {
1107+
fn normalize_path(path: &[u8]) -> Vec<u8> {
11051108
path.iter()
1106-
.map(|&b| if b == b'\\' { b'/' } else { b })
1109+
.map(|&b| match b {
1110+
b'A'..=b'Z' => b + 32,
1111+
b'\\' => b'/',
1112+
_ => b,
1113+
})
11071114
.collect()
11081115
}
11091116

11101117
#[cfg(not(target_os = "windows"))]
1111-
fn normalize_path_slashes(path: &[u8]) -> Vec<u8> {
1118+
fn normalize_path(path: &[u8]) -> Vec<u8> {
11121119
path.to_vec()
11131120
}
11141121

@@ -1370,6 +1377,29 @@ mod tests {
13701377
assert_eq!(output, expected);
13711378
}
13721379

1380+
#[test]
1381+
fn test_strip_basedir_case_insensitive() {
1382+
use std::path::PathBuf;
1383+
1384+
// Case insensitive matching - basedir in lowercase, input in uppercase
1385+
let basedir = PathBuf::from("/home/user/project");
1386+
let input = b"# 1 \"/HOME/USER/PROJECT/src/main.c\"";
1387+
let output = super::strip_basedirs(input, std::slice::from_ref(&basedir));
1388+
let expected = b"# 1 \"./src/main.c\"";
1389+
assert_eq!(output, expected);
1390+
1391+
// Mixed case in both
1392+
let input = b"# 1 \"/Home/User/Project/src/main.c\"";
1393+
let output = super::strip_basedirs(input, std::slice::from_ref(&basedir));
1394+
assert_eq!(output, expected);
1395+
1396+
// Basedir in uppercase, input in lowercase
1397+
let basedir = PathBuf::from("/HOME/USER/PROJECT");
1398+
let input = b"# 1 \"/home/user/project/src/main.c\"";
1399+
let output = super::strip_basedirs(input, std::slice::from_ref(&basedir));
1400+
assert_eq!(output, expected);
1401+
}
1402+
13731403
#[cfg(target_os = "windows")]
13741404
#[test]
13751405
fn test_strip_basedir_windows_backslashes() {

0 commit comments

Comments
 (0)