mod envelope;
mod path;
mod publish;
mod warning;
use anyhow::{anyhow, Result};
use crate::rules_list::RulesEntry;
use crate::rules_snapshot::RulesSnapshot;
use crate::runtime_matcher::RuntimeRules;
use envelope::{decode, encode, MAX_ARTIFACT_BYTES};
use path::{
cache_location, current_environment, current_platform, resolve_cache_root, source_digest,
CacheRootError, SourceDigest,
};
use publish::{publish_artifact, read_artifact, ArtifactReadError};
pub use warning::CacheWarning;
use warning::CacheWarningReason;
pub(crate) struct CacheLoad {
pub(crate) compiled: RuntimeRules,
pub(crate) warnings: Vec<CacheWarning>,
}
pub(crate) fn load_or_compile(snapshot: &RulesSnapshot) -> Result<CacheLoad> {
let digest = source_digest(snapshot.text().as_bytes())
.map_err(|error| return anyhow!("rules {}: {}", snapshot.label(), error))?;
let environment = current_environment();
let root = match resolve_cache_root(&environment, current_platform()) {
Ok(root) => root,
Err(CacheRootError::InvalidOverride) => {
return Err(anyhow!("rules {}: {}", snapshot.label(), CacheRootError::InvalidOverride));
}
Err(CacheRootError::Unavailable) => {
let compiled = snapshot.compile()?;
return Ok(CacheLoad {
compiled,
warnings: vec![CacheWarning::compile_from_text(
CacheWarningReason::CacheRootUnavailable,
)],
});
}
};
let location = cache_location(&root, digest);
match read_artifact(&location.artifact_path, MAX_ARTIFACT_BYTES) {
Ok(bytes) => match decode(&bytes, digest) {
Ok(compiled) => return Ok(CacheLoad { compiled, warnings: Vec::new() }),
Err(error) => {
return compile_and_repair(snapshot, digest, &location, error.warning_reason());
}
},
Err(ArtifactReadError::Missing) => {
return compile_and_repair(snapshot, digest, &location, CacheWarningReason::Missing);
}
Err(ArtifactReadError::Unreadable) => {
return compile_and_repair(snapshot, digest, &location, CacheWarningReason::Unreadable);
}
}
}
fn compile_and_repair(
snapshot: &RulesSnapshot,
digest: SourceDigest,
location: &path::CacheLocation,
reason: CacheWarningReason,
) -> Result<CacheLoad> {
let compiled = snapshot.compile()?;
let mut warnings = vec![CacheWarning::compile_from_text(reason)];
if !snapshot.still_current() || !publish_compiled_snapshot(&compiled, digest, location) {
warnings.push(CacheWarning::write_failed());
}
return Ok(CacheLoad { compiled, warnings })
}
fn publish_compiled_snapshot(
compiled: &RuntimeRules,
digest: SourceDigest,
location: &path::CacheLocation,
) -> bool {
let Ok(bytes) = encode(compiled, digest) else {
return false;
};
return publish_artifact(location, &bytes).is_ok()
}
pub(crate) fn compile_rules_to_cache(entries: &[RulesEntry]) -> Result<Vec<std::path::PathBuf>> {
let snapshot = RulesSnapshot::read(entries)?;
snapshot.require_present()?;
let label = snapshot.label();
let digest = source_digest(snapshot.text().as_bytes())
.map_err(|error| return anyhow!("rules {}: {}", label, error))?;
let root = resolve_cache_root(¤t_environment(), current_platform())
.map_err(|error| return anyhow!(error))?;
let location = cache_location(&root, digest);
if read_artifact(&location.artifact_path, MAX_ARTIFACT_BYTES)
.is_ok_and(|bytes| return decode(&bytes, digest).is_ok())
{
if snapshot.still_current() {
return Ok(snapshot.skipped());
}
return Err(anyhow!("rules {} changed while validating cache", label));
}
let compiled = snapshot.compile()?;
if !snapshot.still_current() {
return Err(anyhow!("rules {} changed during compilation", label));
}
let artifact = encode(&compiled, digest)
.map_err(|_| return anyhow!("runtime rules cache artifact could not be encoded"))?;
publish_artifact(&location, &artifact)
.map_err(|_| return anyhow!("runtime rules cache artifact could not be published"))?;
return Ok(snapshot.skipped());
}
#[cfg(test)]
#[path = "verification_tests.rs"]
mod verification_tests;
#[cfg(feature = "fuzzing")]
pub fn decode_artifact_for_fuzzing(
bytes: &[u8],
source: &[u8],
) -> bool {
let Ok(digest) = source_digest(source) else {
return false;
};
return decode(bytes, digest).is_ok()
}