From e10ab533adee8270423b35af4e2a8e4f7b73ff60 Mon Sep 17 00:00:00 2001 From: itowlson Date: Thu, 18 May 2023 13:51:07 +1200 Subject: [PATCH] Support building individual components (for `spin doctor`) Signed-off-by: itowlson --- crates/build/src/lib.rs | 37 +++++++++++++++++++++++++++++++------ src/commands/build.rs | 8 ++++---- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/crates/build/src/lib.rs b/crates/build/src/lib.rs index 2a2a268234..db1475e0db 100644 --- a/crates/build/src/lib.rs +++ b/crates/build/src/lib.rs @@ -6,24 +6,49 @@ mod manifest; use anyhow::{anyhow, bail, Context, Result}; use spin_loader::local::parent_dir; -use std::path::{Path, PathBuf}; +use std::{ + collections::HashSet, + path::{Path, PathBuf}, +}; use subprocess::{Exec, Redirection}; use crate::manifest::{BuildAppInfoAnyVersion, RawComponentManifest}; /// If present, run the build command of each component. -pub async fn build(manifest_file: &Path) -> Result<()> { +pub async fn build(manifest_file: &Path, component_ids: &[String]) -> Result<()> { let manifest_text = tokio::fs::read_to_string(manifest_file) .await .with_context(|| format!("Cannot read manifest file from {}", manifest_file.display()))?; let app = toml::from_str(&manifest_text).map(BuildAppInfoAnyVersion::into_v1)?; let app_dir = parent_dir(manifest_file)?; - if app.components.iter().all(|c| c.build.is_none()) { - println!("No build command found!"); + let components_to_build = if component_ids.is_empty() { + app.components + } else { + let all_ids: HashSet<_> = app.components.iter().map(|c| &c.id).collect(); + let unknown_component_ids: Vec<_> = component_ids + .iter() + .filter(|id| !all_ids.contains(id)) + .map(|s| s.as_str()) + .collect(); + + if !unknown_component_ids.is_empty() { + bail!("Unknown component(s) {}", unknown_component_ids.join(", ")); + } + + app.components + .into_iter() + .filter(|c| component_ids.contains(&c.id)) + .collect() + }; + + if components_to_build.iter().all(|c| c.build.is_none()) { + println!("None of the components have a build command."); + println!("For information on specifying a build command, see https://developer.fermyon.com/spin/build#setting-up-for-spin-build."); return Ok(()); } - app.components + + components_to_build .into_iter() .map(|c| build_component(c, &app_dir)) .collect::, _>>()?; @@ -101,6 +126,6 @@ mod tests { #[tokio::test] async fn can_load_even_if_trigger_invalid() { let bad_trigger_file = test_data_root().join("bad_trigger.toml"); - build(&bad_trigger_file).await.unwrap(); + build(&bad_trigger_file, &[]).await.unwrap(); } } diff --git a/src/commands/build.rs b/src/commands/build.rs index 71f3f9c51b..2d7517a30a 100644 --- a/src/commands/build.rs +++ b/src/commands/build.rs @@ -23,9 +23,9 @@ pub struct BuildCommand { )] pub app_source: PathBuf, - /// Component ID to build. The default is all components. - #[clap(short = 'c', long)] - pub component_id: Option, + /// Component ID to build. This can be specified multiple times. The default is all components. + #[clap(short = 'c', long, multiple = true)] + pub component_id: Vec, /// Run the application after building. #[clap(name = BUILD_UP_OPT, short = 'u', long = "up")] @@ -38,7 +38,7 @@ pub struct BuildCommand { impl BuildCommand { pub async fn run(self) -> Result<()> { let manifest_file = crate::manifest::resolve_file_path(&self.app_source)?; - spin_build::build(&manifest_file).await?; + spin_build::build(&manifest_file, &self.component_id).await?; if self.up { let mut cmd = UpCommand::parse_from(