diff --git a/src/config.rs b/src/config.rs index 48e61c811..feda3da23 100644 --- a/src/config.rs +++ b/src/config.rs @@ -828,10 +828,52 @@ pub fn try_read_config_file(path: &Path) -> Result>, client_side_mode: Option, } +#[derive(Debug, Default)] +struct DiskCacheConfigOverrides { + dir: Option, + size: Option, + use_preprocessor_cache_mode: Option, + rw_mode: Option, +} + +impl DiskCacheConfigOverrides { + fn apply_to(self, disk: &mut Option) { + let Self { + dir, + size, + use_preprocessor_cache_mode, + rw_mode, + } = self; + + if dir.is_none() + && size.is_none() + && use_preprocessor_cache_mode.is_none() + && rw_mode.is_none() + { + return; + } + + let disk = disk.get_or_insert_default(); + if let Some(dir) = dir { + disk.dir = dir; + } + if let Some(size) = size { + disk.size = size; + } + if let Some(enabled) = use_preprocessor_cache_mode { + disk.preprocessor_cache_mode.use_preprocessor_cache_mode = enabled; + } + if let Some(rw_mode) = rw_mode { + disk.rw_mode = rw_mode; + } + } +} + fn string_from_env_var(env_var_name: &str) -> Option { env::var(env_var_name).ok().filter(|s| !s.is_empty()) } @@ -1155,36 +1197,11 @@ fn config_from_env() -> Result { }; // ======= Local ======= - let disk_dir = env::var_os("SCCACHE_DIR").map(PathBuf::from); - let disk_sz = string_from_env_var("SCCACHE_CACHE_SIZE").and_then(|v| parse_size(&v)); - - let mut preprocessor_mode_config = PreprocessorCacheModeConfig::activated(); - let preprocessor_mode_overridden = if let Some(value) = bool_from_env_var("SCCACHE_DIRECT")? { - preprocessor_mode_config.use_preprocessor_cache_mode = value; - true - } else { - false - }; - - let (disk_rw_mode, disk_rw_mode_overridden) = - match cache_mode_from_env_var("SCCACHE_LOCAL_RW_MODE") { - Some(mode) => (mode, true), - _ => (CacheModeConfig::ReadWrite, false), - }; - - let any_overridden = disk_dir.is_some() - || disk_sz.is_some() - || preprocessor_mode_overridden - || disk_rw_mode_overridden; - let disk = if any_overridden { - Some(DiskCacheConfig { - dir: disk_dir.unwrap_or_else(default_disk_cache_dir), - size: disk_sz.unwrap_or_else(default_disk_cache_size), - preprocessor_cache_mode: preprocessor_mode_config, - rw_mode: disk_rw_mode, - }) - } else { - None + let disk_overrides = DiskCacheConfigOverrides { + dir: env::var_os("SCCACHE_DIR").map(PathBuf::from), + size: string_from_env_var("SCCACHE_CACHE_SIZE").and_then(|v| parse_size(&v)), + use_preprocessor_cache_mode: bool_from_env_var("SCCACHE_DIRECT")?, + rw_mode: cache_mode_from_env_var("SCCACHE_LOCAL_RW_MODE"), }; // Parse multi-level cache configuration @@ -1205,7 +1222,7 @@ fn config_from_env() -> Result { let cache = CacheConfigs { azure, - disk, + disk: None, gcs, gha, memcached, @@ -1236,6 +1253,7 @@ fn config_from_env() -> Result { Ok(EnvConfig { cache, + disk_overrides, basedirs, client_side_mode, }) @@ -1306,10 +1324,12 @@ impl Config { let EnvConfig { cache, + disk_overrides, basedirs: env_basedirs, client_side_mode: env_client_side_mode, } = env_conf; conf_caches.merge(cache); + disk_overrides.apply_to(&mut conf_caches.disk); // Environment variable takes precedence over file config if it is set let basedirs_raw = if let Some(basedirs) = env_basedirs { @@ -1679,6 +1699,7 @@ fn config_overrides() { }), ..Default::default() }, + disk_overrides: Default::default(), basedirs: None, client_side_mode: None, }; @@ -1767,12 +1788,143 @@ fn config_overrides() { ); } +#[test] +#[serial(config_from_env)] +fn config_disk_env_dir_preserves_file_size() { + const FILE_CACHE_SIZE: u64 = 20 * 1024 * 1024 * 1024; + + let file_conf: FileConfig = toml::from_str( + r#" +[cache.disk] +size = 21474836480 +"#, + ) + .unwrap(); + + temp_env::with_vars( + [ + ("SCCACHE_DIR", Some("/env-cache")), + ("SCCACHE_CACHE_SIZE", None), + ("SCCACHE_DIRECT", None), + ("SCCACHE_LOCAL_RW_MODE", None), + ], + || { + let env_conf = config_from_env().unwrap(); + let config = Config::from_env_and_file_configs(env_conf, file_conf).unwrap(); + + assert_eq!(config.fallback_cache.dir, PathBuf::from("/env-cache")); + assert_eq!(config.fallback_cache.size, FILE_CACHE_SIZE); + }, + ); +} + +#[test] +#[serial(config_from_env)] +fn config_disk_env_overrides_only_explicit_fields() { + let file_preprocessor_config = PreprocessorCacheModeConfig { + use_preprocessor_cache_mode: true, + file_stat_matches: true, + use_ctime_for_stat: false, + ignore_time_macros: true, + skip_system_headers: true, + hash_working_directory: false, + }; + let file_conf = FileConfig { + cache: CacheConfigs { + disk: Some(DiskCacheConfig { + dir: "/file-cache".into(), + size: 20 * 1024 * 1024 * 1024, + preprocessor_cache_mode: file_preprocessor_config, + rw_mode: CacheModeConfig::ReadOnly, + }), + ..Default::default() + }, + ..Default::default() + }; + + temp_env::with_vars( + [ + ("SCCACHE_DIR", None), + ("SCCACHE_CACHE_SIZE", None), + ("SCCACHE_DIRECT", Some("false")), + ("SCCACHE_LOCAL_RW_MODE", Some("READ_WRITE")), + ], + || { + let env_conf = config_from_env().unwrap(); + let config = Config::from_env_and_file_configs(env_conf, file_conf).unwrap(); + let expected = DiskCacheConfig { + dir: "/file-cache".into(), + size: 20 * 1024 * 1024 * 1024, + preprocessor_cache_mode: PreprocessorCacheModeConfig { + use_preprocessor_cache_mode: false, + file_stat_matches: true, + use_ctime_for_stat: false, + ignore_time_macros: true, + skip_system_headers: true, + hash_working_directory: false, + }, + rw_mode: CacheModeConfig::ReadWrite, + }; + + assert_eq!(config.fallback_cache, expected); + assert_eq!(config.cache_configs.disk.as_ref(), Some(&expected)); + }, + ); +} + +#[test] +#[serial(config_from_env)] +fn config_disk_env_size_creates_default_disk() { + temp_env::with_vars( + [ + ("SCCACHE_DIR", None), + ("SCCACHE_CACHE_SIZE", Some("1")), + ("SCCACHE_DIRECT", None), + ("SCCACHE_LOCAL_RW_MODE", None), + ], + || { + let env_conf = config_from_env().unwrap(); + let config = + Config::from_env_and_file_configs(env_conf, FileConfig::default()).unwrap(); + let expected = DiskCacheConfig { + size: 1, + ..Default::default() + }; + + assert_eq!(config.fallback_cache, expected); + assert_eq!(config.cache_configs.disk.as_ref(), Some(&expected)); + }, + ); +} + +#[test] +#[serial(config_from_env)] +fn config_without_disk_sources_keeps_disk_unset() { + temp_env::with_vars( + [ + ("SCCACHE_DIR", None::<&str>), + ("SCCACHE_CACHE_SIZE", None), + ("SCCACHE_DIRECT", None), + ("SCCACHE_LOCAL_RW_MODE", None), + ], + || { + let env_conf = config_from_env().unwrap(); + let config = + Config::from_env_and_file_configs(env_conf, FileConfig::default()).unwrap(); + + assert_eq!(config.fallback_cache, DiskCacheConfig::default()); + assert!(config.cache_configs.disk.is_none()); + }, + ); +} + #[test] #[cfg(target_os = "windows")] fn config_basedirs_overrides() { // Test that env variable takes precedence over file config let env_conf = EnvConfig { cache: Default::default(), + disk_overrides: Default::default(), basedirs: vec!["C:/env/basedir".to_string()].into(), client_side_mode: None, }; @@ -1791,6 +1943,7 @@ fn config_basedirs_overrides() { // Test that file config is used when env is None let env_conf = EnvConfig { cache: Default::default(), + disk_overrides: Default::default(), basedirs: None, client_side_mode: None, }; @@ -1809,6 +1962,7 @@ fn config_basedirs_overrides() { // Test that env config is used when env is set but empty let env_conf = EnvConfig { cache: Default::default(), + disk_overrides: Default::default(), basedirs: vec![].into(), client_side_mode: None, }; @@ -1827,6 +1981,7 @@ fn config_basedirs_overrides() { // Test that both empty results in empty let env_conf = EnvConfig { cache: Default::default(), + disk_overrides: Default::default(), basedirs: vec![].into(), client_side_mode: None, }; @@ -1849,6 +2004,7 @@ fn config_basedirs_overrides() { // Test that env variable takes precedence over file config let env_conf = EnvConfig { cache: Default::default(), + disk_overrides: Default::default(), basedirs: vec!["/env/basedir".to_string()].into(), client_side_mode: None, }; @@ -1867,6 +2023,7 @@ fn config_basedirs_overrides() { // Test that file config is used when env is None let env_conf = EnvConfig { cache: Default::default(), + disk_overrides: Default::default(), basedirs: None, client_side_mode: None, }; @@ -1885,6 +2042,7 @@ fn config_basedirs_overrides() { // Test that env config is used when env is set but empty let env_conf = EnvConfig { cache: Default::default(), + disk_overrides: Default::default(), basedirs: vec![].into(), client_side_mode: None, }; @@ -1903,6 +2061,7 @@ fn config_basedirs_overrides() { // Test that both empty results in empty let env_conf = EnvConfig { cache: Default::default(), + disk_overrides: Default::default(), basedirs: vec![].into(), client_side_mode: None, }; @@ -1919,6 +2078,7 @@ fn config_basedirs_overrides() { assert!(config.basedirs.is_empty()); let env_conf = EnvConfig { cache: Default::default(), + disk_overrides: Default::default(), basedirs: None, client_side_mode: None, }; @@ -2731,6 +2891,7 @@ fn test_integration_config_normalizes_and_strips() { let env_conf = EnvConfig { cache: Default::default(), + disk_overrides: Default::default(), basedirs: None, client_side_mode: None, }; @@ -2766,6 +2927,7 @@ fn test_integration_normalized_path_with_double_slashes() { let env_conf = EnvConfig { cache: Default::default(), + disk_overrides: Default::default(), basedirs: None, client_side_mode: None, }; @@ -2797,6 +2959,7 @@ fn test_integration_windows_path_normalization() { let env_conf = EnvConfig { cache: Default::default(), + disk_overrides: Default::default(), basedirs: None, client_side_mode: None, }; @@ -2829,6 +2992,7 @@ fn test_integration_cow_borrowed_when_no_match() { let env_conf = EnvConfig { cache: Default::default(), + disk_overrides: Default::default(), basedirs: None, client_side_mode: None, }; @@ -2861,6 +3025,7 @@ fn test_integration_cow_borrowed_when_empty_basedirs() { let env_conf = EnvConfig { cache: Default::default(), + disk_overrides: Default::default(), basedirs: None, client_side_mode: None, }; @@ -2892,6 +3057,7 @@ fn test_integration_multiple_basedirs_longest_match() { let env_conf = EnvConfig { cache: Default::default(), + disk_overrides: Default::default(), basedirs: None, client_side_mode: None, }; @@ -2928,6 +3094,7 @@ fn test_integration_paths_with_dots_normalized() { let env_conf = EnvConfig { cache: Default::default(), + disk_overrides: Default::default(), basedirs: None, client_side_mode: None, }; @@ -2960,6 +3127,7 @@ fn test_integration_windows_mixed_slashes() { let env_conf = EnvConfig { cache: Default::default(), + disk_overrides: Default::default(), basedirs: None, client_side_mode: None, };