diff --git a/rust/boil/src/cmd/build.rs b/rust/boil/src/cmd/build.rs index b2c844e04..4b2e082a7 100644 --- a/rust/boil/src/cmd/build.rs +++ b/rust/boil/src/cmd/build.rs @@ -50,7 +50,9 @@ pub fn run_command(args: Box, config: Config) -> Result<(), Erro // TODO (@Techassi): Parse Dockerfile instead to build the target graph let bakefile = Bakefile::from_cli_args(&args, config).context(CreateBakefileSnafu)?; let image_manifest_uris = bakefile.image_manifest_uris(); - let count = image_manifest_uris.len(); + let image_count = image_manifest_uris + .iter() + .fold(0, |acc, (_, tag_sets)| acc + tag_sets.len()); // Write the image manifest URIs to file if requested if let Some(path) = args.write_image_manifest_uris { @@ -109,15 +111,32 @@ pub fn run_command(args: Box, config: Config) -> Result<(), Erro // Take care of formatting output let mut built_images = String::new(); - for (name, tags) in image_manifest_uris { - built_images.push_str(&format!("{name}:\n")); - built_images.push_str(&format!(" {tags}", tags = tags.join("\n "))); + for (name, tag_sets) in image_manifest_uris { + let tag_count = tag_sets.iter().fold(0, |acc, tag_set| acc + tag_set.len()); + + // Add the image name line first. It also includes the number of tags for that particular + // image. + built_images.push_str(&format!( + "{name} ({tag_count} tag{plural}):\n", + plural = if tag_count > 1 { "s" } else { "" } + )); + + // Add an indented list of tags, one tag set per line. + built_images.push_str(&format!( + " {tags}", + tags = tag_sets + .iter() + .map(|tag_set| tag_set.to_string()) + .collect::>() + .join("\n ") + )); + built_images.push('\n'); } print!( - "Successfully built {count} image{plural}:\n{built_images}", - plural = if count > 1 { "s" } else { "" }, + "Successfully built {image_count} image{plural}:\n{built_images}", + plural = if image_count > 1 { "s" } else { "" }, ); Ok(()) diff --git a/rust/boil/src/core/bakefile.rs b/rust/boil/src/core/bakefile.rs index 9d7588cd0..a47b59182 100644 --- a/rust/boil/src/core/bakefile.rs +++ b/rust/boil/src/core/bakefile.rs @@ -1,6 +1,6 @@ use std::{ collections::{BTreeMap, btree_map::Entry}, - fmt::Debug, + fmt::{Debug, Display}, ops::{Deref, DerefMut}, path::PathBuf, }; @@ -334,16 +334,20 @@ impl Bakefile { } /// Returns all image manifest URIs for entry images. - pub fn image_manifest_uris(&self) -> BTreeMap<&str, &TagSet> { + pub fn image_manifest_uris(&self) -> BTreeMap<&str, Vec<&TagSet>> { self.targets .iter() // We only care about the entry targets, because those are the primary images boil // builds. .filter(|(target_name, _)| target_name.starts_with(ENTRY_TARGET_NAME_PREFIX)) // The image manifest URIs file only contains the image tags - .map(|(_, target)| (target.image_name.as_str(), &target.tags)) - // Group tags by image name and collect them into a map - .collect() + // Group tags by image name and collect them into a map of tag sets + .fold(BTreeMap::new(), |mut acc, (_, target)| { + acc.entry(target.image_name.as_str()) + .and_modify(|tags| tags.push(&target.tags)) + .or_insert(vec![&target.tags]); + acc + }) } /// Creates the common target, containing shared data, which will be inherited by other targets. @@ -796,6 +800,21 @@ impl Default for TagSet { } } +impl Display for TagSet { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if self.others.is_empty() { + write!(f, "{canonical}", canonical = self.canonical) + } else { + write!( + f, + "{canonical} ({others})", + canonical = self.canonical, + others = self.others.join(",") + ) + } + } +} + impl TagSet { /// Creates and returns a new [`TagSet`] which only contains the canonical tag. pub fn new(canonical: String) -> Self { @@ -813,6 +832,18 @@ impl TagSet { tag_set } + /// Returns the length of the [`TagSet`] (number of tags). + /// + /// If the [`TagSet`] is marked as empty, it will return 0. Otherwise, the count is the sum of + /// the length of other tags and 1 (one) for the canonical tag. + pub fn len(&self) -> usize { + if self.is_empty { + 0 + } else { + self.others.len() + 1 + } + } + /// Create an empty [`TagSet`]. /// /// See the `is_empty` field for more details on this. @@ -824,20 +855,6 @@ impl TagSet { } } - /// Joins the tags using `separator` into a [`String`]. - pub fn join(&self, separator: &str) -> String { - let mut joined = String::new(); - - joined.push_str(&self.canonical); - - if !self.others.is_empty() { - joined.push_str(separator); - } - - joined.push_str(&self.others.join(separator)); - joined - } - /// Returns if the [`TagSet`] is empty. /// /// Used for skipping serialization in the common target of the [`Bakefile`].