use std::collections::BTreeMap;
use std::path::PathBuf;
use blake3::Hasher;
use serde::{Deserialize, Serialize};
use crate::api::ArtifactRecord;
use crate::artifact::{ArtifactKind, NativeArtifacts, RustCrateType};
use crate::bundle::ArtifactBundleFile;
use crate::identity::{
CMetadata, CrateName, CrateVersion, DependencyCMetadataJson, FeaturesJson, TargetTriple,
WireRustcVersion,
};
use crate::platform::Profile;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlannedArtifact {
pub compile_key: String,
pub crate_name: CrateName,
pub crate_version: CrateVersion,
pub c_metadata: CMetadata,
pub extra_filename: String,
pub features_json: FeaturesJson,
pub dependency_c_metadata_json: DependencyCMetadataJson,
pub dependency_compile_keys_json: String,
pub target: TargetTriple,
pub rustc_version: WireRustcVersion,
pub profile: Profile,
pub emit: Vec<String>,
pub oci_reference: String,
pub kind: ArtifactKind,
pub crate_types: Vec<RustCrateType>,
pub artifact_size: u64,
pub compile_millis: u64,
pub outputs: Vec<PlannedArtifactOutput>,
pub native: Option<NativeArtifacts>,
#[serde(default)]
pub native_archive: Option<PlannedArtifactOutput>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlannedArtifactOutput {
pub path: PathBuf,
pub bundle_file: ArtifactBundleFile,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PublishedArtifact {
pub oci_digest: String,
pub bundle_digest: String,
pub bundle_size: u64,
}
pub fn build_artifact_records(
plans: &[PlannedArtifact],
published_by_reference: &BTreeMap<String, PublishedArtifact>,
min_glibc_by_reference: &BTreeMap<String, Option<crate::glibc::GlibcVersion>>,
) -> crate::error::Result<Vec<ArtifactRecord>> {
let mut records = Vec::with_capacity(plans.len());
for plan in plans {
let Some(published) = published_by_reference.get(&plan.oci_reference) else {
return Err(crate::stow_error!(
"missing published coordinates for reference {}",
plan.oci_reference
));
};
let Some(min_glibc) = min_glibc_by_reference.get(&plan.oci_reference) else {
return Err(crate::stow_error!(
"missing measured glibc floor for reference {}",
plan.oci_reference
));
};
records.push(ArtifactRecord {
compile_key: plan.compile_key.clone(),
c_metadata: plan.c_metadata.clone(),
extra_filename: plan.extra_filename.clone(),
target: plan.target.clone(),
rustc_version: plan.rustc_version.clone(),
profile: plan.profile.clone(),
emit: plan.emit.clone(),
crate_name: plan.crate_name.clone(),
version: plan.crate_version.clone(),
features_json: plan.features_json.clone(),
dependency_c_metadata_json: plan.dependency_c_metadata_json.clone(),
oci_reference: plan.oci_reference.clone(),
oci_digest: published.oci_digest.clone(),
has_native: plan.native.is_some(),
artifact_kind: plan.kind.clone(),
crate_types: plan.crate_types.clone(),
artifact_size: plan.artifact_size,
bundle_digest: published.bundle_digest.clone(),
bundle_size: published.bundle_size,
compile_millis: plan.compile_millis,
min_glibc: *min_glibc,
});
}
Ok(records)
}
#[derive(Debug)]
pub struct CompileKeyInputs<'a> {
pub crate_name: &'a str,
pub crate_version: &'a str,
pub target: &'a str,
pub rustc_version: &'a str,
pub profile: &'a Profile,
pub crate_types: &'a [RustCrateType],
pub emit: &'a [String],
pub features_json: &'a str,
pub dependency_c_metadata_json: &'a str,
pub kind: &'a ArtifactKind,
pub embed_metadata: Option<bool>,
pub cfgs: &'a [String],
pub embed_bitcode: bool,
pub link_options: &'a [String],
}
pub fn compute_compile_key(inputs: &CompileKeyInputs<'_>) -> crate::error::Result<String> {
let mut hasher = Hasher::new();
hasher.update(b"stow-compile-key-v1");
update_str(&mut hasher, inputs.crate_name);
update_str(&mut hasher, inputs.crate_version);
update_str(&mut hasher, inputs.target);
update_str(&mut hasher, inputs.rustc_version);
update_str(&mut hasher, inputs.features_json);
update_str(&mut hasher, inputs.dependency_c_metadata_json);
update_str(&mut hasher, inputs.kind.as_str());
update_str(
&mut hasher,
&serde_json::to_string(inputs.profile).map_err(|error| {
crate::stow_error!(
"serialize compile profile for {} {}: {error}",
inputs.crate_name,
inputs.crate_version
)
})?,
);
update_str(
&mut hasher,
&serde_json::to_string(inputs.crate_types).map_err(|error| {
crate::stow_error!(
"serialize crate types for {} {}: {error}",
inputs.crate_name,
inputs.crate_version
)
})?,
);
update_str(
&mut hasher,
&serde_json::to_string(inputs.emit).map_err(|error| {
crate::stow_error!(
"serialize emit kinds for {} {}: {error}",
inputs.crate_name,
inputs.crate_version
)
})?,
);
if let Some(embed_metadata) = inputs.embed_metadata {
update_str(&mut hasher, if embed_metadata { "yes" } else { "no" });
}
if !inputs.link_options.is_empty() {
update_str(&mut hasher, "link-options");
update_str(
&mut hasher,
&serde_json::to_string(inputs.link_options).map_err(|error| {
crate::stow_error!(
"serialize link options for {} {}: {error}",
inputs.crate_name,
inputs.crate_version
)
})?,
);
}
if !inputs.cfgs.is_empty() {
update_str(&mut hasher, "cfgs");
update_str(
&mut hasher,
&serde_json::to_string(inputs.cfgs).map_err(|error| {
crate::stow_error!(
"serialize cfgs for {} {}: {error}",
inputs.crate_name,
inputs.crate_version
)
})?,
);
}
if inputs.embed_bitcode {
update_str(&mut hasher, "embed-bitcode=yes");
}
Ok(hasher.finalize().to_hex().to_string())
}
fn update_str(hasher: &mut Hasher, value: &str) {
let len = u32::try_from(value.len()).expect("hash input string length exceeds u32 range");
hasher.update(&len.to_le_bytes());
hasher.update(value.as_bytes());
}