1use std::collections::BTreeMap;
6use std::path::PathBuf;
7
8use blake3::Hasher;
9use serde::{Deserialize, Serialize};
10
11use crate::api::ArtifactRecord;
12use crate::artifact::{ArtifactKind, NativeArtifacts, RustCrateType};
13use crate::bundle::ArtifactBundleFile;
14use crate::identity::{
15 CMetadata, CrateName, CrateVersion, DependencyCMetadataJson, FeaturesJson, TargetTriple,
16 WireRustcVersion,
17};
18use crate::platform::Profile;
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct PlannedArtifact {
23 pub compile_key: String,
25 pub crate_name: CrateName,
27 pub crate_version: CrateVersion,
29 pub c_metadata: CMetadata,
31 pub extra_filename: String,
33 pub features_json: FeaturesJson,
35 pub dependency_c_metadata_json: DependencyCMetadataJson,
37 pub dependency_compile_keys_json: String,
39 pub target: TargetTriple,
41 pub rustc_version: WireRustcVersion,
43 pub profile: Profile,
45 pub emit: Vec<String>,
47 pub oci_reference: String,
49 pub kind: ArtifactKind,
51 pub crate_types: Vec<RustCrateType>,
53 pub artifact_size: u64,
55 pub compile_millis: u64,
57 pub outputs: Vec<PlannedArtifactOutput>,
59 pub native: Option<NativeArtifacts>,
61 #[serde(default)]
64 pub native_archive: Option<PlannedArtifactOutput>,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct PlannedArtifactOutput {
70 pub path: PathBuf,
72 pub bundle_file: ArtifactBundleFile,
74}
75
76#[derive(Debug, Clone, PartialEq, Eq)]
85pub struct PublishedArtifact {
86 pub oci_digest: String,
88 pub bundle_digest: String,
90 pub bundle_size: u64,
92}
93
94pub fn build_artifact_records(
100 plans: &[PlannedArtifact],
101 published_by_reference: &BTreeMap<String, PublishedArtifact>,
102) -> crate::error::Result<Vec<ArtifactRecord>> {
103 let mut records = Vec::with_capacity(plans.len());
104
105 for plan in plans {
106 let Some(published) = published_by_reference.get(&plan.oci_reference) else {
107 return Err(crate::stow_error!(
108 "missing published coordinates for reference {}",
109 plan.oci_reference
110 ));
111 };
112
113 records.push(ArtifactRecord {
114 compile_key: plan.compile_key.clone(),
115 c_metadata: plan.c_metadata.clone(),
116 extra_filename: plan.extra_filename.clone(),
117 target: plan.target.clone(),
118 rustc_version: plan.rustc_version.clone(),
119 profile: plan.profile.clone(),
120 emit: plan.emit.clone(),
121 crate_name: plan.crate_name.clone(),
122 version: plan.crate_version.clone(),
123 features_json: plan.features_json.clone(),
124 dependency_c_metadata_json: plan.dependency_c_metadata_json.clone(),
125 oci_reference: plan.oci_reference.clone(),
126 oci_digest: published.oci_digest.clone(),
127 has_native: plan.native.is_some(),
128 artifact_kind: plan.kind.clone(),
129 crate_types: plan.crate_types.clone(),
130 artifact_size: plan.artifact_size,
131 bundle_digest: published.bundle_digest.clone(),
132 bundle_size: published.bundle_size,
133 compile_millis: plan.compile_millis,
134 });
135 }
136
137 Ok(records)
138}
139
140#[derive(Debug)]
147pub struct CompileKeyInputs<'a> {
148 pub crate_name: &'a str,
150 pub crate_version: &'a str,
152 pub target: &'a str,
154 pub rustc_version: &'a str,
156 pub profile: &'a Profile,
158 pub crate_types: &'a [RustCrateType],
160 pub emit: &'a [String],
162 pub features_json: &'a str,
164 pub dependency_c_metadata_json: &'a str,
166 pub kind: &'a ArtifactKind,
168 pub embed_metadata: Option<bool>,
172 pub cfgs: &'a [String],
176 pub embed_bitcode: bool,
181}
182
183pub fn compute_compile_key(inputs: &CompileKeyInputs<'_>) -> crate::error::Result<String> {
189 let mut hasher = Hasher::new();
190 hasher.update(b"stow-compile-key-v1");
191 update_str(&mut hasher, inputs.crate_name);
192 update_str(&mut hasher, inputs.crate_version);
193 update_str(&mut hasher, inputs.target);
194 update_str(&mut hasher, inputs.rustc_version);
195 update_str(&mut hasher, inputs.features_json);
196 update_str(&mut hasher, inputs.dependency_c_metadata_json);
197 update_str(&mut hasher, inputs.kind.as_str());
198 update_str(
199 &mut hasher,
200 &serde_json::to_string(inputs.profile).map_err(|error| {
201 crate::stow_error!(
202 "serialize compile profile for {} {}: {error}",
203 inputs.crate_name,
204 inputs.crate_version
205 )
206 })?,
207 );
208 update_str(
209 &mut hasher,
210 &serde_json::to_string(inputs.crate_types).map_err(|error| {
211 crate::stow_error!(
212 "serialize crate types for {} {}: {error}",
213 inputs.crate_name,
214 inputs.crate_version
215 )
216 })?,
217 );
218 update_str(
219 &mut hasher,
220 &serde_json::to_string(inputs.emit).map_err(|error| {
221 crate::stow_error!(
222 "serialize emit kinds for {} {}: {error}",
223 inputs.crate_name,
224 inputs.crate_version
225 )
226 })?,
227 );
228 if let Some(embed_metadata) = inputs.embed_metadata {
229 update_str(&mut hasher, if embed_metadata { "yes" } else { "no" });
230 }
231 if !inputs.cfgs.is_empty() {
232 update_str(&mut hasher, "cfgs");
233 update_str(
234 &mut hasher,
235 &serde_json::to_string(inputs.cfgs).map_err(|error| {
236 crate::stow_error!(
237 "serialize cfgs for {} {}: {error}",
238 inputs.crate_name,
239 inputs.crate_version
240 )
241 })?,
242 );
243 }
244 if inputs.embed_bitcode {
245 update_str(&mut hasher, "embed-bitcode=yes");
246 }
247 Ok(hasher.finalize().to_hex().to_string())
248}
249
250fn update_str(hasher: &mut Hasher, value: &str) {
251 let len = u32::try_from(value.len()).expect("hash input string length exceeds u32 range");
252 hasher.update(&len.to_le_bytes());
253 hasher.update(value.as_bytes());
254}