Skip to content

Commit 4bb5579

Browse files
avikivitysylvestre
authored andcommitted
Avoid double-caching when ccache is installed in PATH
On Linux, ccache is typically installed in $PATH as /usr/lib64/ccache/g++ or similar. If we keep it there, all compilations will be cached by both ccache and sccache. While the user could easily disable ccache with CCACHE_DISABLE, it's reasonable to assume many will forget and will have their disk space doubly consumed by both caches. Better to recognize this and disable ccache under sccache. This patch does this by removing the ccache binary paths from $PATH. Fixes #2519
1 parent 2b92721 commit 4bb5579

2 files changed

Lines changed: 101 additions & 3 deletions

File tree

src/compiler/compiler.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ use crate::dist::pkg;
3535
use crate::lru_disk_cache;
3636
use crate::mock_command::{CommandChild, CommandCreatorSync, RunCommand, exit_status};
3737
use crate::server;
38-
use crate::util::{fmt_duration_as_secs, run_input_output};
38+
use crate::util::{filter_ccache_from_path, fmt_duration_as_secs, run_input_output};
3939
use crate::{counted_array, dist};
4040
use async_trait::async_trait;
4141
use filetime::FileTime;
@@ -194,10 +194,13 @@ impl CompileCommandImpl for SingleCompileCommand {
194194
env_vars,
195195
cwd,
196196
} = self;
197+
// Filter out ccache directories from PATH to avoid double-caching
198+
// when ccache is also installed on the system.
199+
let env_vars = filter_ccache_from_path(env_vars.to_vec());
197200
let mut cmd = creator.clone().new_command_sync(executable);
198201
cmd.args(arguments)
199202
.env_clear()
200-
.envs(env_vars.to_vec())
203+
.envs(env_vars)
201204
.current_dir(cwd);
202205
run_input_output(cmd, None).await
203206
}

src/util.rs

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1015,11 +1015,106 @@ pub fn num_cpus() -> usize {
10151015
std::thread::available_parallelism().map_or(1, std::num::NonZeroUsize::get)
10161016
}
10171017

1018+
/// Filter out ccache directories from PATH environment variable.
1019+
///
1020+
/// This prevents double-caching when ccache is installed and has
1021+
/// /usr/lib/ccache or /usr/lib64/ccache in PATH, since those directories
1022+
/// contain wrapper scripts that would call ccache.
1023+
pub fn filter_ccache_from_path(env_vars: Vec<(OsString, OsString)>) -> Vec<(OsString, OsString)> {
1024+
use std::env;
1025+
use std::path::Path;
1026+
1027+
const CCACHE_DIRS: &[&str] = &["/usr/lib/ccache", "/usr/lib64/ccache"];
1028+
1029+
env_vars
1030+
.into_iter()
1031+
.map(|(key, value)| {
1032+
if key == "PATH" {
1033+
let filtered_path = env::split_paths(&value)
1034+
.filter(|p| !CCACHE_DIRS.iter().any(|dir| p == Path::new(dir)))
1035+
.collect::<Vec<_>>();
1036+
let new_value = env::join_paths(filtered_path).unwrap_or(value.clone());
1037+
(key, new_value)
1038+
} else {
1039+
(key, value)
1040+
}
1041+
})
1042+
.collect()
1043+
}
1044+
10181045
#[cfg(test)]
10191046
mod tests {
1020-
use super::{OsStrExt, TimeMacroFinder};
1047+
use super::{OsStrExt, TimeMacroFinder, filter_ccache_from_path};
10211048
use std::ffi::{OsStr, OsString};
10221049

1050+
#[test]
1051+
fn test_filter_ccache_from_path() {
1052+
use std::env;
1053+
1054+
// Create a PATH with ccache directories
1055+
let path_with_ccache = env::join_paths([
1056+
"/usr/bin",
1057+
"/usr/lib64/ccache",
1058+
"/usr/local/bin",
1059+
"/usr/lib/ccache",
1060+
"/home/user/bin",
1061+
])
1062+
.unwrap();
1063+
1064+
let env_vars = vec![
1065+
(OsString::from("HOME"), OsString::from("/home/user")),
1066+
(OsString::from("PATH"), path_with_ccache),
1067+
(OsString::from("LANG"), OsString::from("en_US.UTF-8")),
1068+
];
1069+
1070+
let filtered = filter_ccache_from_path(env_vars);
1071+
1072+
// Other env vars should be unchanged
1073+
assert_eq!(filtered.len(), 3);
1074+
assert_eq!(
1075+
filtered.iter().find(|(k, _)| k == "HOME").unwrap().1,
1076+
OsString::from("/home/user")
1077+
);
1078+
assert_eq!(
1079+
filtered.iter().find(|(k, _)| k == "LANG").unwrap().1,
1080+
OsString::from("en_US.UTF-8")
1081+
);
1082+
1083+
// PATH should have ccache directories removed
1084+
let new_path = &filtered.iter().find(|(k, _)| k == "PATH").unwrap().1;
1085+
let expected_path =
1086+
env::join_paths(["/usr/bin", "/usr/local/bin", "/home/user/bin"]).unwrap();
1087+
assert_eq!(new_path, &expected_path);
1088+
}
1089+
1090+
#[test]
1091+
fn test_filter_ccache_from_path_no_ccache() {
1092+
use std::env;
1093+
1094+
// Create a PATH without ccache directories
1095+
let path_without_ccache = env::join_paths(["/usr/bin", "/usr/local/bin"]).unwrap();
1096+
1097+
let env_vars = vec![(OsString::from("PATH"), path_without_ccache.clone())];
1098+
1099+
let filtered = filter_ccache_from_path(env_vars);
1100+
1101+
assert_eq!(filtered.len(), 1);
1102+
assert_eq!(filtered[0].1, path_without_ccache);
1103+
}
1104+
1105+
#[test]
1106+
fn test_filter_ccache_from_path_no_path() {
1107+
// No PATH variable at all
1108+
let env_vars = vec![
1109+
(OsString::from("HOME"), OsString::from("/home/user")),
1110+
(OsString::from("LANG"), OsString::from("en_US.UTF-8")),
1111+
];
1112+
1113+
let filtered = filter_ccache_from_path(env_vars.clone());
1114+
1115+
assert_eq!(filtered, env_vars);
1116+
}
1117+
10231118
#[test]
10241119
fn simple_starts_with() {
10251120
let a: &OsStr = "foo".as_ref();

0 commit comments

Comments
 (0)