From d52a81f59478113cbfee5e6971550b0bfb6e5a7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20Istv=C3=A1n=20B=C3=ADr=C3=B3?= Date: Thu, 16 Jan 2025 09:39:12 +0100 Subject: [PATCH] composable fixes --- examples/c/c-app-common/metadata.json | 3 +- .../components-cpp/component-name/golem.yaml | 2 +- src/cli.rs | 2 +- src/lib.rs | 143 ++++++++++-------- src/model.rs | 6 +- src/test/main.rs | 28 +++- 6 files changed, 112 insertions(+), 72 deletions(-) diff --git a/examples/c/c-app-common/metadata.json b/examples/c/c-app-common/metadata.json index 5a9636d..dff7635 100644 --- a/examples/c/c-app-common/metadata.json +++ b/examples/c/c-app-common/metadata.json @@ -6,5 +6,6 @@ "requiresWASI": true, "witDepsPaths": [ "common-wit-deps" - ] + ], + "transform": false } \ No newline at end of file diff --git a/examples/c/c-app-component/components-cpp/component-name/golem.yaml b/examples/c/c-app-component/components-cpp/component-name/golem.yaml index 0f5a2d6..61320a6 100644 --- a/examples/c/c-app-component/components-cpp/component-name/golem.yaml +++ b/examples/c/c-app-component/components-cpp/component-name/golem.yaml @@ -4,5 +4,5 @@ # yaml-language-server: $schema=https://schema.golem.cloud/app/golem/1.1.0/golem.schema.json components: - app:component-e: + pack:name: template: cpp diff --git a/src/cli.rs b/src/cli.rs index ea351f7..e956ea9 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -24,7 +24,7 @@ impl NameOrLanguage { .clone() .unwrap_or(ExampleName::from_string(format!( "{}-default", - self.language.clone().unwrap_or(GuestLanguage::Rust).id() + self.language.unwrap_or(GuestLanguage::Rust).id() ))) } } diff --git a/src/lib.rs b/src/lib.rs index cf0e16e..4a17afd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ use crate::model::{ }; use include_dir::{include_dir, Dir, DirEntry}; use itertools::Itertools; +use std::borrow::Cow; use std::collections::{BTreeMap, BTreeSet}; use std::path::{Path, PathBuf}; use std::{fs, io}; @@ -126,11 +127,13 @@ pub fn instantiate_example( )?; if let Some(adapter_path) = &example.adapter_source { let adapter_dir = { - match &example.adapter_target { - Some(target) => target.clone(), - None => parameters.target_path.join("adapters"), - } - .join(example.language.tier().name()) + parameters + .target_path + .join(match &example.adapter_target { + Some(target) => target.clone(), + None => parameters.target_path.join("adapters"), + }) + .join(example.language.tier().name()) }; fs::create_dir_all(&adapter_dir)?; @@ -245,14 +248,25 @@ fn instantiate_file( resolve_mode: TargetExistsResolveMode, ) -> io::Result<()> { match get_resolved_contents(catalog, source, target, resolve_mode)? { - Some(contents) => fs::write( - target, + Some(contents) => { if transform_contents { - transform(&contents, parameters) + fs::write( + target, + transform( + std::str::from_utf8(contents.as_ref()).map_err(|err| { + io::Error::other(format!( + "Failed to decode as utf8, source: {}, err: {}", + source.display(), + err + )) + })?, + parameters, + ), + ) } else { - contents - }, - ), + fs::write(target, contents) + } + } None => Ok(()), } } @@ -346,15 +360,25 @@ fn check_target( match file_name { ".gitignore" => { - let current_content = fs::read_to_string(target)?; + let target = target.to_path_buf(); + let current_content = fs::read_to_string(&target)?; Ok(Some(TargetExistsResolveDecision::Merge(Box::new( - move |new_content: &str| -> String { - current_content + move |new_content: &[u8]| -> io::Result> { + Ok(current_content .lines() - .chain(new_content.lines()) + .chain( + std::str::from_utf8(new_content).map_err(|err| { + io::Error::other(format!( + "Failed to decode new content for merge as utf8, target: {}, err: {}", + target.display(), + err + )) + })?.lines(), + ) .collect::>() .iter() .join("\n") + .into_bytes()) }, )))) } @@ -383,34 +407,24 @@ fn check_target( } } -fn get_contents(catalog: &Dir<'_>, source: &Path) -> io::Result { - String::from_utf8( - catalog - .get_file(source) - .ok_or_else(|| io::Error::other(format!("Could not find entry {}", source.display())))? - .contents() - .to_vec(), - ) - .map_err(|err| { - io::Error::other(format!( - "Could not parse utf8 contents for {}: {:?}", - source.display(), - err - )) - }) +fn get_contents<'a>(catalog: &Dir<'a>, source: &'a Path) -> io::Result<&'a [u8]> { + Ok(catalog + .get_file(source) + .ok_or_else(|| io::Error::other(format!("Could not find entry {}", source.display())))? + .contents()) } -fn get_resolved_contents( - catalog: &Dir<'_>, - source: &Path, - target: &Path, +fn get_resolved_contents<'a>( + catalog: &Dir<'a>, + source: &'a Path, + target: &'a Path, resolve_mode: TargetExistsResolveMode, -) -> io::Result> { +) -> io::Result>> { match check_target(target, resolve_mode)? { - None => Ok(Some(get_contents(catalog, source)?)), + None => Ok(Some(Cow::Borrowed(get_contents(catalog, source)?))), Some(TargetExistsResolveDecision::Skip) => Ok(None), Some(TargetExistsResolveDecision::Merge(merge)) => { - Ok(Some(merge(&get_contents(catalog, source)?))) + Ok(Some(Cow::Owned(merge(get_contents(catalog, source)?)?))) } } } @@ -428,16 +442,39 @@ fn parse_example( .contents(); let metadata = serde_json::from_slice::(raw_metadata) .expect("Failed to parse metadata JSON"); - let instructions_path = match metadata.instructions { - Some(instructions_file_name) => lang_path.join(instructions_file_name), - None => lang_path.join(default_instructions_file_name), + + let kind = match (metadata.app_common_group, metadata.app_component_group) { + (None, None) => ExampleKind::Standalone, + (Some(group), None) => ExampleKind::ComposableAppCommon { + group: ComposableAppGroupName::from_string(group), + }, + (None, Some(group)) => ExampleKind::ComposableAppComponent { + group: ComposableAppGroupName::from_string(group), + }, + (Some(_), Some(_)) => panic!( + "Only one of appCommonGroup and appComponentGroup can be specified, example root: {}", + example_root.display() + ), }; - let raw_instructions = EXAMPLES - .get_file(instructions_path) - .expect("Failed to read instructions") - .contents(); - let instructions = - String::from_utf8(raw_instructions.to_vec()).expect("Failed to decode instructions"); + + let instructions = match &kind { + ExampleKind::Standalone => { + let instructions_path = match metadata.instructions { + Some(instructions_file_name) => lang_path.join(instructions_file_name), + None => lang_path.join(default_instructions_file_name), + }; + + let raw_instructions = EXAMPLES + .get_file(instructions_path) + .expect("Failed to read instructions") + .contents(); + + String::from_utf8(raw_instructions.to_vec()).expect("Failed to decode instructions") + } + ExampleKind::ComposableAppCommon { .. } => "".to_string(), + ExampleKind::ComposableAppComponent { .. } => "".to_string(), + }; + let name = ExampleName::from_string(example_root.file_name().unwrap().to_str().unwrap()); let mut wit_deps: Vec = vec![]; @@ -459,20 +496,6 @@ fn parse_example( wit_deps.push(Path::new("sockets").to_path_buf()); } - let kind = match (metadata.app_common_group, metadata.app_component_group) { - (None, None) => ExampleKind::Standalone, - (Some(group), None) => ExampleKind::ComposableAppCommon { - group: ComposableAppGroupName::from_string(group), - }, - (None, Some(group)) => ExampleKind::ComposableAppComponent { - group: ComposableAppGroupName::from_string(group), - }, - (Some(_), Some(_)) => panic!( - "Only one of appCommonGroup and appComponentGroup can be specified, example root: {}", - example_root.display() - ), - }; - let requires_adapter = metadata .requires_adapter .unwrap_or(metadata.adapter_target.is_some()); diff --git a/src/model.rs b/src/model.rs index d3ba0dc..9fc46b0 100644 --- a/src/model.rs +++ b/src/model.rs @@ -3,10 +3,10 @@ use inflector::Inflector; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; use std::collections::HashSet; -use std::fmt; use std::fmt::Formatter; use std::path::PathBuf; use std::str::FromStr; +use std::{fmt, io}; use strum::IntoEnumIterator; use strum_macros::EnumIter; @@ -349,9 +349,11 @@ pub enum TargetExistsResolveMode { MergeOrFail, } +pub type MergeContents = Box io::Result>>; + pub enum TargetExistsResolveDecision { Skip, - Merge(Box String>), + Merge(MergeContents), } #[derive(Debug, Clone)] diff --git a/src/test/main.rs b/src/test/main.rs index 565140d..87aa579 100644 --- a/src/test/main.rs +++ b/src/test/main.rs @@ -1,8 +1,3 @@ -use std::io; -use std::path::PathBuf; -use std::process::exit; -use std::str::FromStr; - use clap::Parser; use colored::{ColoredString, Colorize}; use golem_examples::model::{ @@ -15,6 +10,10 @@ use golem_examples::{ }; use nanoid::nanoid; use regex::Regex; +use std::io; +use std::path::PathBuf; +use std::process::exit; +use std::str::FromStr; #[derive(Parser, Debug)] #[command()] @@ -94,10 +93,15 @@ pub fn main() -> io::Result<()> { let target_path = PathBuf::from(target_path.unwrap_or_else(|| "examples".to_string())) .join("app-default"); + if target_path.exists() { + println!("Deleting {}", target_path.display().to_string().blue()); + std::fs::remove_dir_all(&target_path)?; + } + let app_examples = all_composable_app_examples(); for (language, examples) in app_examples { - println!("Testing language {}", language); + println!("Adding components for language {}", language.name().blue()); let default_examples = examples.get(&ComposableAppGroupName::default()).unwrap(); assert_eq!(default_examples.components.len(), 1); @@ -105,7 +109,11 @@ pub fn main() -> io::Result<()> { for _ in 1..=4 { let component_name = format!("app:comp-{}", nanoid!(10, &alphabet)); - println!("Adding component {} ({})", component_name, language); + println!( + "Adding component {} ({})", + component_name.bright_blue(), + language.name().blue() + ); let package_name = PackageName::from_string(component_name).unwrap(); add_component_by_example( default_examples.common.as_ref(), @@ -116,6 +124,12 @@ pub fn main() -> io::Result<()> { } } + println!("Building with default profile"); + std::process::Command::new("golem-cli") + .args(["app", "build"]) + .current_dir(target_path) + .status()?; + Ok(()) } }