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(
105 plans: &[PlannedArtifact],
106 published_by_reference: &BTreeMap<String, PublishedArtifact>,
107 min_glibc_by_reference: &BTreeMap<String, Option<crate::glibc::GlibcVersion>>,
108) -> crate::error::Result<Vec<ArtifactRecord>> {
109 let mut records = Vec::with_capacity(plans.len());
110
111 for plan in plans {
112 let Some(published) = published_by_reference.get(&plan.oci_reference) else {
113 return Err(crate::stow_error!(
114 "missing published coordinates for reference {}",
115 plan.oci_reference
116 ));
117 };
118 let Some(min_glibc) = min_glibc_by_reference.get(&plan.oci_reference) else {
119 return Err(crate::stow_error!(
120 "missing measured glibc floor for reference {}",
121 plan.oci_reference
122 ));
123 };
124
125 records.push(ArtifactRecord {
126 compile_key: plan.compile_key.clone(),
127 c_metadata: plan.c_metadata.clone(),
128 extra_filename: plan.extra_filename.clone(),
129 target: plan.target.clone(),
130 rustc_version: plan.rustc_version.clone(),
131 profile: plan.profile.clone(),
132 emit: plan.emit.clone(),
133 crate_name: plan.crate_name.clone(),
134 version: plan.crate_version.clone(),
135 features_json: plan.features_json.clone(),
136 dependency_c_metadata_json: plan.dependency_c_metadata_json.clone(),
137 oci_reference: plan.oci_reference.clone(),
138 oci_digest: published.oci_digest.clone(),
139 has_native: plan.native.is_some(),
140 artifact_kind: plan.kind.clone(),
141 crate_types: plan.crate_types.clone(),
142 artifact_size: plan.artifact_size,
143 bundle_digest: published.bundle_digest.clone(),
144 bundle_size: published.bundle_size,
145 compile_millis: plan.compile_millis,
146 min_glibc: *min_glibc,
147 });
148 }
149
150 Ok(records)
151}
152
153#[derive(Debug)]
160pub struct CompileKeyInputs<'a> {
161 pub crate_name: &'a str,
163 pub crate_version: &'a str,
165 pub target: &'a str,
167 pub rustc_version: &'a str,
169 pub profile: &'a Profile,
171 pub crate_types: &'a [RustCrateType],
173 pub emit: &'a [String],
175 pub features_json: &'a str,
177 pub dependency_c_metadata_json: &'a str,
179 pub kind: &'a ArtifactKind,
181 pub embed_metadata: Option<bool>,
185 pub cfgs: &'a [String],
189 pub embed_bitcode: bool,
194 pub link_options: &'a [String],
200}
201
202pub fn compute_compile_key(inputs: &CompileKeyInputs<'_>) -> crate::error::Result<String> {
208 let mut hasher = Hasher::new();
209 hasher.update(b"stow-compile-key-v1");
210 update_str(&mut hasher, inputs.crate_name);
211 update_str(&mut hasher, inputs.crate_version);
212 update_str(&mut hasher, inputs.target);
213 update_str(&mut hasher, inputs.rustc_version);
214 update_str(&mut hasher, inputs.features_json);
215 update_str(&mut hasher, inputs.dependency_c_metadata_json);
216 update_str(&mut hasher, inputs.kind.as_str());
217 update_str(
218 &mut hasher,
219 &serde_json::to_string(inputs.profile).map_err(|error| {
220 crate::stow_error!(
221 "serialize compile profile for {} {}: {error}",
222 inputs.crate_name,
223 inputs.crate_version
224 )
225 })?,
226 );
227 update_str(
228 &mut hasher,
229 &serde_json::to_string(inputs.crate_types).map_err(|error| {
230 crate::stow_error!(
231 "serialize crate types for {} {}: {error}",
232 inputs.crate_name,
233 inputs.crate_version
234 )
235 })?,
236 );
237 update_str(
238 &mut hasher,
239 &serde_json::to_string(inputs.emit).map_err(|error| {
240 crate::stow_error!(
241 "serialize emit kinds for {} {}: {error}",
242 inputs.crate_name,
243 inputs.crate_version
244 )
245 })?,
246 );
247 if let Some(embed_metadata) = inputs.embed_metadata {
248 update_str(&mut hasher, if embed_metadata { "yes" } else { "no" });
249 }
250 if !inputs.link_options.is_empty() {
251 update_str(&mut hasher, "link-options");
252 update_str(
253 &mut hasher,
254 &serde_json::to_string(inputs.link_options).map_err(|error| {
255 crate::stow_error!(
256 "serialize link options for {} {}: {error}",
257 inputs.crate_name,
258 inputs.crate_version
259 )
260 })?,
261 );
262 }
263 if !inputs.cfgs.is_empty() {
264 update_str(&mut hasher, "cfgs");
265 update_str(
266 &mut hasher,
267 &serde_json::to_string(inputs.cfgs).map_err(|error| {
268 crate::stow_error!(
269 "serialize cfgs for {} {}: {error}",
270 inputs.crate_name,
271 inputs.crate_version
272 )
273 })?,
274 );
275 }
276 if inputs.embed_bitcode {
277 update_str(&mut hasher, "embed-bitcode=yes");
278 }
279 Ok(hasher.finalize().to_hex().to_string())
280}
281
282fn update_str(hasher: &mut Hasher, value: &str) {
283 let len = u32::try_from(value.len()).expect("hash input string length exceeds u32 range");
284 hasher.update(&len.to_le_bytes());
285 hasher.update(value.as_bytes());
286}