Skip to content
This repository has been archived by the owner on Oct 31, 2023. It is now read-only.

TINYGO + WASI + wash build = YES PLEASE #758

Merged
merged 5 commits into from
Oct 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 42 additions & 14 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -150,11 +150,14 @@ toml = "0.7.8"
wadm = "0.6.0"
walkdir = "2.4"
wascap = "0.11.2"
wasm-encoder = "0.33.2"
wash-lib = { version = "0.11", path = "./crates/wash-lib" }
wasmbus-rpc = "0.14.0"
wasmbus-rpc = "0.14"
wasmcloud-component-adapters = { version = "0.2.4" }
wasmcloud-control-interface = "0.27"
wasmcloud-test-util = "0.10.0"
weld-codegen = "0.7.0"
which = "4.4.0"
wit-component = "0.13.2"
wit-component = "0.14.4"
wit-parser = "0.11.3"
wat = "1.0.74"
9 changes: 6 additions & 3 deletions crates/wash-lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ reqwest = { workspace = true, features = ["json", "rustls-tls", "stream"] }
rmp-serde = "1"
semver = { workspace = true, features = ["serde"], optional = true }
serde = { workspace = true, features = ["derive"], optional = true }
serde-transcode = "1"
serde_cbor = "0.11"
serde_json = { workspace = true, optional = true }
serde-transcode = "1"
serde_with = { workspace = true }
tempfile = { workspace = true }
term-table = { workspace = true, optional = true }
Expand All @@ -76,11 +76,14 @@ toml = { workspace = true }
wadm = { workspace = true, optional = true }
walkdir = { workspace = true }
wascap = { workspace = true }
wasm-encoder = { workspace = true }
wasmbus-rpc = { workspace = true }
wasmcloud-component-adapters = { workspace = true }
wasmcloud-control-interface = { workspace = true }
wat = { workspace = true }
weld-codegen = { workspace = true }
wasmbus-rpc = { workspace = true }
wit-component = { workspace = true }
wasmcloud-component-adapters = { workspace = true }
wit-parser = { workspace = true }

[dev-dependencies]
claims = { workspace = true }
Expand Down
92 changes: 88 additions & 4 deletions crates/wash-lib/src/build.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Build (and sign) a wasmCloud actor, provider, or interface. Depends on the "cli" feature

use std::{
borrow::Cow,
fs,
io::ErrorKind,
path::{Path, PathBuf},
Expand All @@ -9,7 +10,8 @@ use std::{
};

use anyhow::{anyhow, bail, Context, Result};
use wit_component::ComponentEncoder;
use wasm_encoder::{Encode, Section};
use wit_component::{ComponentEncoder, StringEncoding};

use crate::{
cli::{
Expand Down Expand Up @@ -90,10 +92,25 @@ pub fn build_actor(
LanguageConfig::Rust(rust_config) => {
build_rust_actor(common_config, rust_config, actor_config)
}
LanguageConfig::TinyGo(tinygo_config) => build_tinygo_actor(common_config, tinygo_config),
LanguageConfig::TinyGo(tinygo_config) => {
build_tinygo_actor(common_config, tinygo_config, actor_config)
}
}?;

// If the actor has been configured as WASI Preview2, adapt it
// Perform embedding, if necessary
if let WasmTarget::WasiPreview1 | WasmTarget::WasiPreview2 = &actor_config.wasm_target {
embed_wasm_component_metadata(
&common_config.path,
actor_config
.wit_world
.as_ref()
.context("`wit_world` must be specified in wasmcloud.toml for creating preview1 or preview2 components")?,
&actor_wasm_path,
&actor_wasm_path,
)?;
};

// If the actor has been configured as WASI Preview2, adapt it from preview1
if actor_config.wasm_target == WasmTarget::WasiPreview2 {
let adapter_wasm_bytes = get_wasi_preview2_adapter_bytes(actor_config)?;
// Adapt the component, using the adapter that is available locally
Expand Down Expand Up @@ -219,6 +236,7 @@ fn build_rust_actor(
fn build_tinygo_actor(
common_config: &CommonConfig,
tinygo_config: &TinyGoConfig,
actor_config: &ActorConfig,
) -> Result<PathBuf> {
let filename = format!("build/{}.wasm", common_config.name);

Expand All @@ -240,7 +258,7 @@ fn build_tinygo_actor(
"-o",
filename.as_str(),
"-target",
"wasm",
tinygo_config.build_target(&actor_config.wasm_target),
"-scheduler",
"none",
"-no-debug",
Expand Down Expand Up @@ -321,6 +339,72 @@ pub(crate) fn get_wasi_preview2_adapter_bytes(config: &ActorConfig) -> Result<Ve
Ok(wasmcloud_component_adapters::WASI_PREVIEW1_REACTOR_COMPONENT_ADAPTER.into())
}

/// Embed required component metadata to a given WebAssembly binary
fn embed_wasm_component_metadata(
project_path: impl AsRef<Path>,
wit_world: impl AsRef<str>,
input_wasm: impl AsRef<Path>,
output_wasm: impl AsRef<Path>,
) -> Result<()> {
// Find the the WIT directory for the project
let wit_dir = project_path.as_ref().join("wit");
if !wit_dir.is_dir() {
bail!(
"expected 'wit' directory under project path at [{}] is missing",
wit_dir.display()
);
};

// Resolve the WIT directory packages & worlds
let mut resolve = wit_parser::Resolve::default();
let (package_id, _paths) = resolve
.push_dir(&wit_dir)
.context("failed to add WIT deps directory")?;
log::info!("successfully loaded WIT @ [{}]", wit_dir.display());

// Select the target world that was specified by the user
let world = resolve
.select_world(package_id, wit_world.as_ref().into())
.context("failed to select world from built resolver")?;

// Encode the metadata
let encoded_metadata =
wit_component::metadata::encode(&resolve, world, StringEncoding::UTF8, None)
.context("failed to encode WIT metadata for component")?;

// Load the wasm binary
let mut wasm_bytes = wat::parse_file(input_wasm.as_ref()).with_context(|| {
format!(
"failed to read wasm bytes from [{}]",
input_wasm.as_ref().display()
)
})?;

// Build & encode a new custom section at the end of the wasm
let section = wasm_encoder::CustomSection {
name: "component-type".into(),
data: Cow::Borrowed(&encoded_metadata),
};
wasm_bytes.push(section.id());
section.encode(&mut wasm_bytes);
log::info!("successfully embedded component metadata in WASM");

// Output the WASM to disk (possibly overwriting the original path)
std::fs::write(output_wasm.as_ref(), wasm_bytes).with_context(|| {
format!(
"failed to write updated wasm to disk at [{}]",
output_wasm.as_ref().display()
)
})?;

log::info!(
"successfully wrote component w/ metadata to [{}]",
output_wasm.as_ref().display()
);

Ok(())
}

/// Placeholder for future functionality for building providers
#[allow(unused)]
fn build_provider(
Expand Down
Loading