Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ Note that some env variables may need sccache server restart to take effect.
* `SCCACHE_MAX_FRAME_LENGTH` how much data can be transferred between client and server
* `SCCACHE_NO_DAEMON` set to `1` to disable putting the server to the background
* `SCCACHE_CLIENT_SIDE` set to `1` to run the compile in the client process and use the daemon only as a gateway to the cache storage (see [the architecture doc](Architecture.md#client-side-mode-sccache_client_side)). This is the recommended mode and is expected to become the only supported configuration in the future. Ignored when `SCCACHE_ERROR_LOG` or distributed compilation is in use.
* `SCCACHE_SKIP_CACHE_CHECK` set to `true`, `on`, or `1` to skip remote cache capability checks. The configured backend `rw_mode` is used without reading or writing `.sccache_check`. The user is responsible for ensuring that the cache is reachable and grants the configured access.
* `SCCACHE_CACHE_MULTIARCH` to disable caching of multi architecture builds.
* `SCCACHE_CACHE_ZSTD_LEVEL` to set zstd compression level of cache. the range is `1-22` and default is `3`.
- For example, in `10`, it have about 0.9x size with about 1.6x time than default `3` (tested with compiling sccache code)
Expand Down
97 changes: 87 additions & 10 deletions src/cache/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ pub struct RemoteStorage {
operator: opendal::Operator,
basedirs: Vec<Vec<u8>>,
rw_mode: CacheMode,
skip_cache_check: bool,
}

#[cfg(any(
Expand All @@ -209,8 +210,14 @@ impl RemoteStorage {
operator,
basedirs,
rw_mode,
skip_cache_check: false,
}
}

pub fn with_skip_cache_check(mut self, skip_cache_check: bool) -> Self {
self.skip_cache_check = skip_cache_check;
self
}
}

/// Implement storage for operator.
Expand Down Expand Up @@ -251,6 +258,14 @@ impl Storage for RemoteStorage {
async fn check(&self) -> Result<CacheMode> {
use opendal::ErrorKind;

if self.skip_cache_check {
debug!(
"storage check skipped; using configured cache mode: {:?}",
self.rw_mode
);
return Ok(self.rw_mode);
}

let path = ".sccache_check";

// Read is required, return error directly if we can't read .
Expand Down Expand Up @@ -395,6 +410,7 @@ pub fn build_single_cache(
cache_type: &CacheType,
basedirs: &[Vec<u8>],
_pool: &tokio::runtime::Handle,
skip_cache_check: bool,
) -> Result<Arc<dyn Storage>> {
match cache_type {
#[cfg(feature = "azure")]
Expand All @@ -415,7 +431,8 @@ pub fn build_single_cache(
endpoint.as_deref(),
)
.map_err(|err| anyhow!("create azure cache failed: {err:?}"))?;
let storage = RemoteStorage::new(operator, basedirs.to_vec(), (*rw_mode).into());
let storage = RemoteStorage::new(operator, basedirs.to_vec(), (*rw_mode).into())
.with_skip_cache_check(skip_cache_check);
Ok(Arc::new(storage))
}
#[cfg(feature = "gcs")]
Expand All @@ -438,7 +455,8 @@ pub fn build_single_cache(
credential_url.as_deref(),
)
.map_err(|err| anyhow!("create gcs cache failed: {err:?}"))?;
let storage = RemoteStorage::new(operator, basedirs.to_vec(), (*rw_mode).into());
let storage = RemoteStorage::new(operator, basedirs.to_vec(), (*rw_mode).into())
.with_skip_cache_check(skip_cache_check);
Ok(Arc::new(storage))
}
#[cfg(feature = "gha")]
Expand All @@ -449,7 +467,8 @@ pub fn build_single_cache(

let operator = GHACache::build(version)
.map_err(|err| anyhow!("create gha cache failed: {err:?}"))?;
let storage = RemoteStorage::new(operator, basedirs.to_vec(), (*rw_mode).into());
let storage = RemoteStorage::new(operator, basedirs.to_vec(), (*rw_mode).into())
.with_skip_cache_check(skip_cache_check);
Ok(Arc::new(storage))
}
#[cfg(feature = "memcached")]
Expand All @@ -471,7 +490,8 @@ pub fn build_single_cache(
*expiration,
)
.map_err(|err| anyhow!("create memcached cache failed: {err:?}"))?;
let storage = RemoteStorage::new(operator, basedirs.to_vec(), (*rw_mode).into());
let storage = RemoteStorage::new(operator, basedirs.to_vec(), (*rw_mode).into())
.with_skip_cache_check(skip_cache_check);
Ok(Arc::new(storage))
}
#[cfg(feature = "redis")]
Expand Down Expand Up @@ -520,7 +540,8 @@ pub fn build_single_cache(
_ => bail!("Only one of `endpoint`, `cluster_endpoints`, `url` must be set"),
}
.map_err(|err| anyhow!("create redis cache failed: {err:?}"))?;
let storage = RemoteStorage::new(storage, basedirs.to_vec(), (*rw_mode).into());
let storage = RemoteStorage::new(storage, basedirs.to_vec(), (*rw_mode).into())
.with_skip_cache_check(skip_cache_check);
Ok(Arc::new(storage))
}
#[cfg(feature = "s3")]
Expand All @@ -542,7 +563,8 @@ pub fn build_single_cache(
.build()
.map_err(|err| anyhow!("create s3 cache failed: {err:?}"))?;

let storage = RemoteStorage::new(operator, basedirs.to_vec(), c.rw_mode.into());
let storage = RemoteStorage::new(operator, basedirs.to_vec(), c.rw_mode.into())
.with_skip_cache_check(skip_cache_check);
Ok(Arc::new(storage))
}
#[cfg(feature = "webdav")]
Expand All @@ -558,7 +580,8 @@ pub fn build_single_cache(
)
.map_err(|err| anyhow!("create webdav cache failed: {err:?}"))?;

let storage = RemoteStorage::new(operator, basedirs.to_vec(), c.rw_mode.into());
let storage = RemoteStorage::new(operator, basedirs.to_vec(), c.rw_mode.into())
.with_skip_cache_check(skip_cache_check);
Ok(Arc::new(storage))
}
#[cfg(feature = "oss")]
Expand All @@ -576,7 +599,8 @@ pub fn build_single_cache(
)
.map_err(|err| anyhow!("create oss cache failed: {err:?}"))?;

let storage = RemoteStorage::new(operator, basedirs.to_vec(), c.rw_mode.into());
let storage = RemoteStorage::new(operator, basedirs.to_vec(), c.rw_mode.into())
.with_skip_cache_check(skip_cache_check);
Ok(Arc::new(storage))
}
#[cfg(feature = "cos")]
Expand All @@ -589,7 +613,8 @@ pub fn build_single_cache(
let operator = COSCache::build(&c.bucket, &c.key_prefix, c.endpoint.as_deref())
.map_err(|err| anyhow!("create cos cache failed: {err:?}"))?;

let storage = RemoteStorage::new(operator, basedirs.to_vec(), c.rw_mode.into());
let storage = RemoteStorage::new(operator, basedirs.to_vec(), c.rw_mode.into())
.with_skip_cache_check(skip_cache_check);
Ok(Arc::new(storage))
}
#[allow(unreachable_patterns)]
Expand Down Expand Up @@ -624,7 +649,7 @@ pub fn storage_from_config(
))]
if let Some(cache_type) = &config.cache {
debug!("Configuring single cache from CacheType");
return build_single_cache(cache_type, &config.basedirs, pool);
return build_single_cache(cache_type, &config.basedirs, pool, config.skip_cache_check);
}

// No remote cache configured - use disk cache only
Expand Down Expand Up @@ -739,6 +764,58 @@ mod test {
assert_eq!(storage.basedirs()[1], b"/opt/build".to_vec());
}

#[test]
#[cfg(feature = "s3")]
fn test_skip_remote_cache_check_uses_configured_mode() {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();

for (configured_mode, expected_mode) in [
(CacheModeConfig::ReadOnly, CacheMode::ReadOnly),
(CacheModeConfig::ReadWrite, CacheMode::ReadWrite),
] {
let cache_config = config::S3CacheConfig {
bucket: "test-bucket".to_owned(),
region: Some("us-east-1".to_owned()),
key_prefix: String::new(),
no_credentials: true,
endpoint: Some("http://127.0.0.1:1".to_owned()),
use_ssl: Some(false),
server_side_encryption: None,
server_side_encryption_aws_kms: None,
server_side_encryption_kms_key_id: None,
enable_virtual_host_style: None,
rw_mode: configured_mode,
};
let single_cache_config = Config {
cache: Some(CacheType::S3(cache_config.clone())),
skip_cache_check: true,
..Default::default()
};

let storage = storage_from_config(&single_cache_config, runtime.handle()).unwrap();
assert_eq!(runtime.block_on(storage.check()).unwrap(), expected_mode);

let multilevel_config = Config {
cache_configs: config::CacheConfigs {
s3: Some(cache_config),
multilevel: Some(config::MultiLevelConfig {
chain: vec!["s3".to_owned()],
write_error_policy: config::WriteErrorPolicy::default(),
}),
..Default::default()
},
skip_cache_check: true,
..Default::default()
};

let storage = storage_from_config(&multilevel_config, runtime.handle()).unwrap();
assert_eq!(runtime.block_on(storage.check()).unwrap(), expected_mode);
}
}

#[test]
#[cfg(feature = "redis")]
fn test_operator_storage_redis_with_basedirs() {
Expand Down
13 changes: 9 additions & 4 deletions src/cache/multilevel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -528,10 +528,15 @@ impl MultiLevelStorage {
};

if let Some(cache_type) = cache_type {
let storage = build_single_cache(&cache_type, &config.basedirs, pool)
.with_context(|| {
format!("Failed to build cache for level '{}'", level_name)
})?;
let storage = build_single_cache(
&cache_type,
&config.basedirs,
pool,
config.skip_cache_check,
)
.with_context(|| {
format!("Failed to build cache for level '{}'", level_name)
})?;
storages.push(storage);
trace!("Added cache level: {}", level_name);
} else {
Expand Down
49 changes: 48 additions & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -890,6 +890,10 @@ fn bool_from_env_var(env_var_name: &str) -> Result<Option<bool>> {
.transpose()
}

fn skip_cache_check_from_env() -> Result<bool> {
Ok(bool_from_env_var("SCCACHE_SKIP_CACHE_CHECK")?.unwrap_or(false))
}

fn config_from_env() -> Result<EnvConfig> {
// ======= AWS =======
let s3 = if let Some(bucket) = string_from_env_var("SCCACHE_BUCKET") {
Expand Down Expand Up @@ -1311,6 +1315,7 @@ pub struct Config {
pub fallback_cache: DiskCacheConfig,
pub dist: DistConfig,
pub server_startup_timeout: Option<std::time::Duration>,
pub skip_cache_check: bool,
/// Base directory (or directories) to strip from paths for cache key computation.
/// Similar to ccache's CCACHE_BASEDIR.
pub basedirs: Vec<Vec<u8>>,
Expand All @@ -1320,13 +1325,16 @@ pub struct Config {
impl Config {
pub fn load() -> Result<Self> {
let env_conf = config_from_env()?;
let skip_cache_check = skip_cache_check_from_env()?;

let file_conf_path = config_file("SCCACHE_CONF", "config");
let file_conf = try_read_config_file(&file_conf_path)
.context("Failed to load config file")?
.unwrap_or_default();

Self::from_env_and_file_configs(env_conf, file_conf)
let mut config = Self::from_env_and_file_configs(env_conf, file_conf)?;
config.skip_cache_check = skip_cache_check;
Ok(config)
}

fn from_env_and_file_configs(env_conf: EnvConfig, file_conf: FileConfig) -> Result<Self> {
Expand Down Expand Up @@ -1417,6 +1425,7 @@ impl Config {
fallback_cache,
dist,
server_startup_timeout,
skip_cache_check: false,
basedirs,
client_side_mode,
})
Expand Down Expand Up @@ -1692,6 +1701,43 @@ fn test_string_from_env_var() {
}
}

#[test]
#[serial(config_from_env)]
fn test_skip_cache_check_from_env() {
const ENV_VAR: &str = "SCCACHE_SKIP_CACHE_CHECK";

unsafe {
std::env::remove_var(ENV_VAR);
}
assert!(!skip_cache_check_from_env().unwrap());

for (value, expected) in [
("true", true),
("on", true),
("1", true),
("false", false),
("off", false),
("0", false),
] {
unsafe {
std::env::set_var(ENV_VAR, value);
}
assert_eq!(skip_cache_check_from_env().unwrap(), expected);
}

unsafe {
std::env::set_var(ENV_VAR, "invalid");
}
assert_eq!(
skip_cache_check_from_env().unwrap_err().to_string(),
"SCCACHE_SKIP_CACHE_CHECK must be 'true', 'on', '1', 'false', 'off' or '0'."
);

unsafe {
std::env::remove_var(ENV_VAR);
}
}

#[test]
fn config_overrides() {
let env_conf = EnvConfig {
Expand Down Expand Up @@ -1805,6 +1851,7 @@ fn config_overrides() {
},
dist: Default::default(),
server_startup_timeout: None,
skip_cache_check: false,
basedirs: vec![],
client_side_mode: false,
}
Expand Down
Loading