1use anyhow::Result;
2use serde::Deserialize;
3use std::{collections::BTreeMap, path::Path};
4
5use spin_manifest::{schema::v2, ManifestVersion};
6
7pub async fn component_build_configs(
12 manifest_file: impl AsRef<Path>,
13) -> Result<(Vec<ComponentBuildInfo>, Option<spin_manifest::Error>)> {
14 let manifest = spin_manifest::manifest_from_file(&manifest_file);
15 match manifest {
16 Ok(manifest) => Ok((build_configs_from_manifest(manifest), None)),
17 Err(e) => fallback_load_build_configs(&manifest_file)
18 .await
19 .map(|bc| (bc, Some(e))),
20 }
21}
22
23fn build_configs_from_manifest(
24 mut manifest: spin_manifest::schema::v2::AppManifest,
25) -> Vec<ComponentBuildInfo> {
26 spin_manifest::normalize::normalize_manifest(&mut manifest);
27
28 manifest
29 .components
30 .into_iter()
31 .map(|(id, c)| ComponentBuildInfo {
32 id: id.to_string(),
33 build: c.build,
34 })
35 .collect()
36}
37
38async fn fallback_load_build_configs(
39 manifest_file: impl AsRef<Path>,
40) -> Result<Vec<ComponentBuildInfo>> {
41 let manifest_text = tokio::fs::read_to_string(manifest_file).await?;
42 Ok(match ManifestVersion::detect(&manifest_text)? {
43 ManifestVersion::V1 => {
44 let v1: ManifestV1BuildInfo = toml::from_str(&manifest_text)?;
45 v1.components
46 }
47 ManifestVersion::V2 => {
48 let v2: ManifestV2BuildInfo = toml::from_str(&manifest_text)?;
49 v2.components
50 .into_iter()
51 .map(|(id, mut c)| {
52 c.id = id;
53 c
54 })
55 .collect()
56 }
57 })
58}
59
60#[derive(Deserialize)]
61pub struct ComponentBuildInfo {
62 #[serde(default)]
63 pub id: String,
64 pub build: Option<v2::ComponentBuildConfig>,
65}
66
67#[derive(Deserialize)]
68struct ManifestV1BuildInfo {
69 #[serde(rename = "component")]
70 components: Vec<ComponentBuildInfo>,
71}
72
73#[derive(Deserialize)]
74struct ManifestV2BuildInfo {
75 #[serde(rename = "component")]
76 components: BTreeMap<String, ComponentBuildInfo>,
77}