use std::path::Path;
use crate::gha::{GhaCache, GhaError};
use super::local::{
restore_rust_plan_local, rust_plan_bundle_dir, rust_plan_cache_key, save_rust_plan_local,
};
use super::schema::RustArtifactPlanV1;
use super::summary::RustPlanSummary;
use super::targz::{tar_gz_decode, tar_gz_encode};
#[derive(Debug)]
pub enum RustPlanGhaError {
Unavailable(String),
Failure(String),
}
impl RustPlanGhaError {
#[must_use]
pub fn message(&self) -> &str {
match self {
Self::Unavailable(message) | Self::Failure(message) => message,
}
}
#[must_use]
pub fn is_unavailable(&self) -> bool {
matches!(self, Self::Unavailable(_))
}
}
impl std::fmt::Display for RustPlanGhaError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Unavailable(message) => write!(f, "GHA cache backend unavailable: {message}"),
Self::Failure(message) => write!(f, "GHA cache backend failure: {message}"),
}
}
}
impl std::error::Error for RustPlanGhaError {}
fn gha_error(err: GhaError) -> RustPlanGhaError {
let message = err.to_string();
if matches!(err, GhaError::NotAvailable) {
RustPlanGhaError::Unavailable(message)
} else {
RustPlanGhaError::Failure(message)
}
}
async fn run_rust_plan_backend_blocking<F, T>(work: F) -> Result<T, RustPlanGhaError>
where
F: FnOnce() -> Result<T, String> + Send + 'static,
T: Send + 'static,
{
tokio::task::spawn_blocking(work)
.await
.map_err(|err| RustPlanGhaError::Failure(format!("blocking rust-plan task failed: {err}")))?
.map_err(RustPlanGhaError::Failure)
}
#[must_use]
pub fn rust_plan_gha_version(cache_key: &str) -> String {
GhaCache::version_hash(&["zccache-rust-plan-v2-protobuf", cache_key])
}
pub async fn restore_rust_plan_gha(
plan: &RustArtifactPlanV1,
cache_dir: &Path,
) -> Result<RustPlanSummary, RustPlanGhaError> {
let cache_key = rust_plan_cache_key(plan);
let version = rust_plan_gha_version(&cache_key);
let cache = GhaCache::from_env().map_err(gha_error)?;
let Some(data) = cache
.restore(&cache_key, &version)
.await
.map_err(gha_error)?
else {
let plan = plan.clone();
let cache_dir = cache_dir.to_path_buf();
let mut summary = run_rust_plan_backend_blocking(move || {
restore_rust_plan_local(&plan, &cache_dir).map_err(|err| err.to_string())
})
.await?;
summary.set_backend("gha", Some(cache_key), Some(version));
summary.record_skip("<gha-cache>", "backend_cache_miss");
return Ok(summary);
};
let plan = plan.clone();
let cache_dir = cache_dir.to_path_buf();
run_rust_plan_backend_blocking(move || {
let bundle_dir = rust_plan_bundle_dir(&cache_dir, &cache_key);
if bundle_dir.exists() {
std::fs::remove_dir_all(&bundle_dir).map_err(|err| err.to_string())?;
}
let bundle_parent = bundle_dir
.parent()
.ok_or_else(|| "invalid rust-plan bundle path".to_string())?;
std::fs::create_dir_all(bundle_parent).map_err(|err| err.to_string())?;
tar_gz_decode(&data, bundle_parent).map_err(|err| err.to_string())?;
let mut summary =
restore_rust_plan_local(&plan, &cache_dir).map_err(|err| err.to_string())?;
summary.set_backend("gha", Some(cache_key), Some(version));
Ok(summary)
})
.await
}
pub async fn save_rust_plan_gha(
plan: &RustArtifactPlanV1,
cache_dir: &Path,
) -> Result<RustPlanSummary, RustPlanGhaError> {
let plan = plan.clone();
let cache_dir = cache_dir.to_path_buf();
let (summary, data) = run_rust_plan_backend_blocking(move || {
let summary = save_rust_plan_local(&plan, &cache_dir).map_err(|err| err.to_string())?;
let cache_key = summary.cache_key.clone();
let bundle_dir = rust_plan_bundle_dir(&cache_dir, &cache_key);
let data = tar_gz_encode(&bundle_dir).map_err(|err| err.to_string())?;
Ok((summary, data))
})
.await?;
let cache_key = summary.cache_key.clone();
let version = rust_plan_gha_version(&cache_key);
let cache = GhaCache::from_env().map_err(gha_error)?;
cache
.save(&cache_key, &version, &data)
.await
.map_err(gha_error)?;
let mut summary = summary;
summary.set_backend("gha", Some(cache_key), Some(version));
Ok(summary)
}