Skip to content

Commit

Permalink
generator support for multi-component examples
Browse files Browse the repository at this point in the history
  • Loading branch information
noise64 committed Aug 27, 2024
1 parent f483888 commit e731306
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 14 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,19 @@ The following fields are required:
The following fields are optional:

- `requiresAdapter` is a boolean, defaults to **true**. If true, the appropriate version of the WASI Preview2 to Preview1 adapter is copied into the generated project (based on the guest language) to an `adapters` directory.
- `requiresGolemHostWIT` is a boolean, defaults to **false**. If true, the Golem specific WIT interface gets copied into `wit/deps`.
- `requiresGolemHostWIT` is a boolean, defaults to **false**. If true, the Golem specific WIT interface gets copied into `wit/deps`.
- `requiresWASI` is a boolean, defaults to **false**. If true, the WASI Preview2 WIT interfaces which are compatible with Golem Cloud get copied into `wit/deps`.
- `witDepsPaths` is an array of directory paths, defaults to **null**. When set, overrides the `wit/deps` directory for the above options and allows to use multiple target dirs for supporting multi-component examples.
- `exclude` is a list of sub-paths and works as a simplified `.gitignore` file. It's primary purpose is to help the development loop of working on examples and in the future it will likely be dropped in favor of just using `.gitignore` files.
- `instructions` is an optional filename, defaults to **null**. When set, overrides the __INSTRUCTIONS__ file used for the example, the file needs to be placed to same directory as the default instructions file.

### Template rules

Golem examples are currently simple and not using any known template language, in order to keep the examples **compilable** as they are - this makes it very convenient to work on existing ones and add new examples as you can immediately verify that it can be compiled into a _Golem template_.

When calling `golem-new` the user specifies a **template name**. The provided component name must use either `PascalCase`, `snake_case` or `kebab-case`.

There is an optional parameter for defining a **package name**, which defaults to `golem:component`. It has to be in the `pack:name` format.
There is an optional parameter for defining a **package name**, which defaults to `golem:component`. It has to be in the `pack:name` format. The first part of the package name is called **package namespace**.

The following occurrences get replaced to the provided component name, applying the casing used in the template:
- `component-name`
Expand All @@ -46,6 +48,8 @@ The following occurrences get replaced to the provided component name, applying
- `pack-name`
- `pack/name`
- `PackName`
- `pack-ns`
- `PackNs`

### Testing the examples
The example generation and instructions can be tested with a test [cli app](/src/test/main.rs).
Expand Down
44 changes: 33 additions & 11 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ impl Examples for GolemExamples {
if let Some(lang_dir) = entry.as_dir() {
let lang_dir_name = lang_dir.path().file_name().unwrap().to_str().unwrap();
if let Some(lang) = GuestLanguage::from_string(lang_dir_name) {
let instructions_path = lang_dir.path().join("INSTRUCTIONS");
let adapters_path =
Path::new(lang.tier().name()).join("wasi_snapshot_preview1.wasm");

Expand All @@ -42,7 +41,8 @@ impl Examples for GolemExamples {
{
let example = parse_example(
&lang,
&instructions_path,
&lang_dir.path(),
Path::new("INSTRUCTIONS"),
&adapters_path,
example_dir.path(),
);
Expand Down Expand Up @@ -81,17 +81,29 @@ impl Examples for GolemExamples {
.join(adapter_path.file_name().unwrap().to_str().unwrap()),
)?;
}
for wit_dep in &example.wit_deps {
copy_all(
&WIT,
wit_dep,
&parameters
let wit_deps_targets = {
match &example.wit_deps_targets {
Some(paths) => paths
.iter()
.map(|path| {
parameters
.target_path
.join(parameters.component_name.as_string())
.join(path)
})
.collect(),
None => vec![parameters
.target_path
.join(parameters.component_name.as_string())
.join("wit")
.join("deps")
.join(wit_dep.file_name().unwrap().to_str().unwrap()),
)?;
.join("deps")],
}
};
for wit_dep in &example.wit_deps {
for target_wit_deps in &wit_deps_targets {
let target = target_wit_deps.join(wit_dep.file_name().unwrap().to_str().unwrap());
copy_all(&WIT, wit_dep, &target)?;
}
}
Ok(Self::instructions(example, parameters))
}
Expand Down Expand Up @@ -205,6 +217,8 @@ fn transform(str: impl AsRef<str>, parameters: &ExampleParameters) -> String {
.replace("pack-name", &parameters.package_name.to_kebab_case())
.replace("pack/name", &parameters.package_name.to_string_with_slash())
.replace("PackName", &parameters.package_name.to_pascal_case())
.replace("pack-ns", &parameters.package_name.namespace())
.replace("PackNs", &parameters.package_name.namespace_title_case())
}

fn file_name_transform(str: impl AsRef<str>, parameters: &ExampleParameters) -> String {
Expand All @@ -213,7 +227,8 @@ fn file_name_transform(str: impl AsRef<str>, parameters: &ExampleParameters) ->

fn parse_example(
lang: &GuestLanguage,
instructions_path: &Path,
lang_path: &Path,
default_instructions_file_name: &Path,
adapters_path: &Path,
example_root: &Path,
) -> Example {
Expand All @@ -223,6 +238,10 @@ fn parse_example(
.contents();
let metadata = serde_json::from_slice::<ExampleMetadata>(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 raw_instructions = EXAMPLES
.get_file(instructions_path)
.expect("Failed to read instructions")
Expand Down Expand Up @@ -261,6 +280,9 @@ fn parse_example(
None
},
wit_deps,
wit_deps_targets: metadata
.wit_deps_paths
.map(|dirs| dirs.iter().map(|dir| PathBuf::from(dir)).collect()),
exclude: metadata.exclude.iter().cloned().collect(),
}
}
12 changes: 12 additions & 0 deletions src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,14 @@ impl PackageName {
pub fn to_kebab_case(&self) -> String {
format!("{}-{}", self.0 .0, self.0 .1)
}

pub fn namespace(&self) -> String {
self.0 .0.to_string()
}

pub fn namespace_title_case(&self) -> String {
self.0 .0.to_title_case()
}
}

impl fmt::Display for PackageName {
Expand Down Expand Up @@ -303,6 +311,7 @@ pub struct Example {
pub instructions: String,
pub adapter: Option<PathBuf>,
pub wit_deps: Vec<PathBuf>,
pub wit_deps_targets: Option<Vec<PathBuf>>,
pub exclude: HashSet<String>,
}

Expand All @@ -322,7 +331,10 @@ pub(crate) struct ExampleMetadata {
pub requires_golem_host_wit: Option<bool>,
#[serde(rename = "requiresWASI")]
pub requires_wasi: Option<bool>,
#[serde(rename = "witDepsPaths")]
pub wit_deps_paths: Option<Vec<String>>,
pub exclude: Vec<String>,
pub instructions: Option<String>,
}

#[cfg(test)]
Expand Down
2 changes: 1 addition & 1 deletion src/test/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ fn test_example(command: &Command, example: &Example) -> Result<(), String> {
);
let component_name = ComponentName::new(example.name.as_string().to_string() + "-comp");
let package_name =
PackageName::from_string("golem:component").ok_or("failed to create package name")?;
PackageName::from_string("golemx:componentx").ok_or("failed to create package name")?;
let component_path = target_path.join(component_name.as_string());

println!("Target path: {}", target_path.display().to_string().blue());
Expand Down

0 comments on commit e731306

Please sign in to comment.