diff --git a/Cargo.lock b/Cargo.lock index 1804202..6e7e749 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -342,6 +342,7 @@ dependencies = [ "anyhow", "bzip2", "clap", + "componentize-go-core", "dirs", "regex", "reqwest", @@ -349,6 +350,16 @@ dependencies = [ "tar", "toml 1.1.2+spec-1.1.0", "which", + "wit-parser 0.254.0", +] + +[[package]] +name = "componentize-go-core" +version = "0.4.1" +dependencies = [ + "anyhow", + "wasi-preview1-component-adapter-provider", + "wat", "wit-bindgen-go", "wit-component", "wit-parser 0.254.0", @@ -2580,6 +2591,12 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasi-preview1-component-adapter-provider" +version = "47.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffea5cc4926d42465ede05953cff22ad074e281709fc274033e7a09ff323c0f8" + [[package]] name = "wasip2" version = "1.0.2+wasi-0.2.9" @@ -2681,6 +2698,16 @@ dependencies = [ "wasmparser 0.254.0", ] +[[package]] +name = "wasm-encoder" +version = "0.255.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b524283fb5df62eec102ed0574838961bdd7ba5ac9c50d38e2756c51c971a42" +dependencies = [ + "leb128fmt", + "wasmparser 0.255.0", +] + [[package]] name = "wasm-metadata" version = "0.254.0" @@ -2718,6 +2745,17 @@ dependencies = [ "semver", ] +[[package]] +name = "wasmparser" +version = "0.255.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8e329ef4b5d46e73b91d3ac6924417cad55a8cbbf869c199283383427c3320b" +dependencies = [ + "bitflags", + "indexmap", + "semver", +] + [[package]] name = "wasmprinter" version = "0.252.0" @@ -3019,24 +3057,24 @@ dependencies = [ [[package]] name = "wast" -version = "252.0.0" +version = "255.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942a3449d6a593fccc111a6241c8df52bda168af30e40bf9580d4394d7374c65" +checksum = "55ffec530f199bd3d553ac442c13dd108353cad533cad8514bc41e1f1f0fe686" dependencies = [ "bumpalo", "leb128fmt", "memchr", "unicode-width", - "wasm-encoder 0.252.0", + "wasm-encoder 0.255.0", ] [[package]] name = "wat" -version = "1.252.0" +version = "1.255.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c72a4ba7088f7bac94cf516e49882bdf97068904a563768cf249efc839ec42cb" +checksum = "dda82c82e1486c7eed42a0465e544d80fff37abc3b39482e0d34dbaabe2fe5b1" dependencies = [ - "wast 252.0.0", + "wast 255.0.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 8b794c0..4672b51 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,14 +9,18 @@ name = "componentize_go" crate-type = ["cdylib", "rlib"] [workspace] -members = ["./tests"] +members = ["./tests", "crates/core"] [workspace.dependencies] anyhow = "1.0.103" bzip2 = "0.6.1" +componentize-go-core = { path = "crates/core" } once_cell = "1.21.3" reqwest = { version = "0.13.4", features = ["blocking"] } tar = "0.4.46" +wit-bindgen-go = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "d3ebba0957500bf3edb200a89e70555545d92f6c" } +wit-component = "0.254.0" +wit-parser = "0.254.0" [workspace.package] version = "0.4.1" @@ -40,14 +44,13 @@ allow_attributes_without_reason = 'warn' [dependencies] anyhow = { workspace = true } bzip2 = { workspace = true } +componentize-go-core = { workspace = true } reqwest = { workspace = true } tar = { workspace = true } clap = { version = "4.6.1", features = ["derive"] } regex = "1.12.4" serde = { version = "1.0.228", features = ["derive"] } toml = "1.1.0" -wit-bindgen-go = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "d3ebba0957500bf3edb200a89e70555545d92f6c" } -wit-component = "0.254.0" -wit-parser = "0.254.0" +wit-parser = { workspace = true } which = "8.0.4" dirs = "6.0.0" diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml new file mode 100644 index 0000000..590594a --- /dev/null +++ b/crates/core/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "componentize-go-core" +description = """ +The filesystem- and subprocess-free core of componentize-go: WIT resolution, Go +bindings generation, WIT metadata embedding, and component encoding. Everything +here is a pure byte transform, so it compiles to wasm as well as to native. +""" +version = { workspace = true } +edition = { workspace = true } + +[lints] +workspace = true + +[dependencies] +anyhow = { workspace = true } +# Versioned in lockstep with wasmtime; keep aligned with the wasmtime used in +# `tests/`. The adapter this replaces was checked in at 46 while wasmtime had +# already moved to 47, so sourcing it here also corrects that drift. +wasi-preview1-component-adapter-provider = "47" +wat = "1.253.0" +wit-bindgen-go = { workspace = true } +wit-component = { workspace = true } +wit-parser = { workspace = true } diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs new file mode 100644 index 0000000..9379060 --- /dev/null +++ b/crates/core/src/lib.rs @@ -0,0 +1,348 @@ +//! The filesystem- and subprocess-free core of componentize-go. +//! +//! Every operation here is a pure byte transform: WIT sources in, generated Go +//! or encoded wasm out. Nothing in this crate touches the filesystem, spawns a +//! process, or talks to the network, which is what lets it compile to +//! `wasm32-unknown-unknown` and run as a component with zero WASI imports. +//! +//! The native driver wraps these functions with filesystem I/O; the component +//! wrapper in `crates/component` exposes them over WIT. Both go through this +//! one implementation so the two paths cannot drift. + +use anyhow::{Context, Result, anyhow, bail}; +use wit_parser::{ + CloneMaps, Package, PackageId, PackageName, Resolve, Stability, UnresolvedPackageGroup, World, + WorldId, +}; + +pub use wit_bindgen_go::remote_pkg_version; +pub use wit_parser; + +/// The `wasi_snapshot_preview1` reactor adapter. +/// +/// Sourced from wasmtime's `wasi-preview1-component-adapter-provider` crate +/// rather than checked in, so updating it is a version bump. That crate is +/// versioned in lockstep with wasmtime, so keep it aligned with the wasmtime +/// used in `tests/`. +pub use wasi_preview1_component_adapter_provider::WASI_SNAPSHOT_PREVIEW1_REACTOR_ADAPTER as WASIP1_SNAPSHOT_ADAPT; + +/// A single file, identified by a path relative to the root of its +/// [`WitSource`] (for inputs) or to the output directory (for generated code). +#[derive(Clone, Debug)] +pub struct File { + pub path: String, + pub contents: Vec, +} + +/// One WIT input, corresponding to a single `-d`/`--wit-path` argument. +/// +/// Paths are relative to the root of that argument, and their structure carries +/// the same meaning the filesystem layout does for `wit-parser`'s `push_path`: +/// +/// * `*.wit` at the root — parsed together as this source's package. +/// * `deps//*.wit` — a dependency package. +/// * `deps/.wit` — a single-file dependency package. +/// * `deps/.{wasm,wat}` — a wasm-encoded dependency package. +/// +/// A source whose root holds a single `.wasm`/`.wat` and no `.wit` is itself a +/// wasm-encoded package. +#[derive(Clone, Debug, Default)] +pub struct WitSource { + /// Where this source came from — the `--wit-path` argument, natively. Used + /// only so that errors name the offending input, which matters when several + /// sources are in play and `componentize-go.toml` discovery can add more. + pub name: String, + pub files: Vec, +} + +impl WitSource { + pub fn new(name: impl Into, files: Vec) -> Self { + Self { + name: name.into(), + files, + } + } +} + +/// Options for Go bindings generation. +/// +/// Deliberately has no `format` field: `gofmt` is a subprocess and so is +/// unavailable in wasm. Formatting is the caller's job, applied to the returned +/// files, which keeps the two drivers producing identical bytes here. +#[derive(Clone, Debug, Default)] +pub struct BindingsOptions { + pub generate_stubs: bool, + pub pkg_name: Option, + pub export_pkg_name: Option, + pub include_versions: bool, +} + +fn is_wasm_encoded(path: &str) -> bool { + path.ends_with(".wasm") || path.ends_with(".wat") +} + +fn decode_package(resolve: &mut Resolve, name: &str, bytes: &[u8]) -> Result { + // `.wat` needs converting to binary first; `wit-component` only decodes wasm. + let owned; + let bytes = if name.ends_with(".wat") { + owned = wat::parse_bytes(bytes) + .with_context(|| format!("failed to parse `{name}` as WAT"))? + .into_owned(); + &owned + } else { + bytes + }; + + let decoded = wit_component::decode(bytes) + .with_context(|| format!("failed to decode `{name}` as a WIT package"))?; + let pkg = decoded.package(); + let remap = resolve.merge(decoded.resolve().clone())?; + Ok(remap.packages[pkg.index()]) +} + +/// Split a source's files into its main package and its `deps/` entries, then +/// push all of them into `resolve`, returning the id of the main package. +fn push_source(resolve: &mut Resolve, source: &WitSource) -> Result { + let mut root_wit: Vec<&File> = Vec::new(); + let mut root_encoded: Vec<&File> = Vec::new(); + // dep name -> files, preserving the `deps//...` grouping. + let mut dep_dirs: std::collections::BTreeMap> = Default::default(); + let mut dep_files: Vec<&File> = Vec::new(); + + for file in &source.files { + let path = file.path.replace('\\', "/"); + let rest = path.strip_prefix("deps/"); + match rest { + None => { + if path.contains('/') { + // Not at the root and not under `deps/`; wit-parser ignores + // these, so do the same rather than guessing. + continue; + } + if is_wasm_encoded(&path) { + root_encoded.push(file); + } else if path.ends_with(".wit") { + root_wit.push(file); + } + } + Some(rest) => match rest.split_once('/') { + // `deps//...` — a directory package. + Some((name, _)) => dep_dirs.entry(name.to_string()).or_default().push(file), + // `deps/.{wit,wasm,wat}` — a single-file package. + None => dep_files.push(file), + }, + } + } + + // Wasm-encoded packages must land first: text packages may reference them, + // and `push_groups` only resolves against what is already in `resolve`. + for file in dep_files.iter().filter(|f| is_wasm_encoded(&f.path)) { + decode_package(resolve, &file.path, &file.contents)?; + } + for files in dep_dirs.values() { + for file in files.iter().filter(|f| is_wasm_encoded(&f.path)) { + decode_package(resolve, &file.path, &file.contents)?; + } + } + + if root_wit.is_empty() { + // A source that is itself a wasm-encoded package. + return match root_encoded.as_slice() { + [file] => decode_package(resolve, &file.path, &file.contents), + [] => bail!("no WIT files found in source"), + _ => bail!("source root contains multiple wasm-encoded packages"), + }; + } + + let main = parse_group(&root_wit)?; + let mut deps = Vec::new(); + for files in dep_dirs.values() { + let text: Vec<&File> = files + .iter() + .copied() + .filter(|f| f.path.ends_with(".wit")) + .collect(); + if !text.is_empty() { + deps.push(parse_group(&text)?); + } + } + for file in dep_files.iter().filter(|f| f.path.ends_with(".wit")) { + deps.push(parse_group(&[file])?); + } + + // `push_groups` topologically sorts main against its dependencies, which is + // why the deps cannot simply be pushed one at a time. + Ok(resolve.push_groups(main, deps)?) +} + +fn parse_group(files: &[&File]) -> Result { + let mut map = wit_parser::SourceMap::default(); + for file in files { + let text = std::str::from_utf8(&file.contents) + .with_context(|| format!("`{}` is not valid UTF-8", file.path))?; + map.push(std::path::Path::new(&file.path), text); + } + map.parse().map_err(|(_, e)| e.into()) +} + +/// Resolve WIT sources into a [`Resolve`] and the world to target. +/// +/// Mirrors the CLI's semantics: each source is resolved independently and then +/// merged, so the same package appearing in several sources is consolidated +/// rather than duplicated. Naming more than one world merges them into a +/// synthetic `componentize-go:union/union` world. +pub fn resolve_wit( + sources: &[WitSource], + worlds: &[String], + features: &[String], + all_features: bool, +) -> Result<(Resolve, WorldId)> { + if sources.is_empty() { + bail!("no WIT sources provided"); + } + + let mut resolve = Resolve { + all_features, + ..Default::default() + }; + for features in features { + for feature in features + .split(',') + .flat_map(|s| s.split_whitespace()) + .filter(|f| !f.is_empty()) + { + resolve.features.insert(feature.to_string()); + } + } + + let packages = sources + .iter() + .map(|source| { + // Resolve into a scratch `Resolve` and merge, which consolidates a + // package referenced by more than one source. + let mut tmp = Resolve { + all_features, + features: resolve.features.clone(), + ..Default::default() + }; + let pkg = push_source(&mut tmp, source) + .with_context(|| format!("failed to parse WIT for path [{}]", source.name))?; + let consolidated = resolve.merge(tmp)?; + Ok(consolidated.packages[pkg.index()]) + }) + .collect::>>()?; + + let worlds = worlds + .iter() + .map(|world| { + packages + .iter() + .find_map(|&pkg| resolve.select_world(&[pkg], Some(world)).ok()) + .ok_or_else(|| { + anyhow!("no world named `{world}` found in any of the loaded WIT packages") + }) + }) + .collect::>>()?; + + let world = match &worlds[..] { + [] => packages + .iter() + .find_map(|&pkg| resolve.select_world(&[pkg], None).ok()) + .ok_or_else(|| anyhow!("no default world found in any of the loaded WIT packages"))?, + &[world] => world, + worlds => { + let union_package = resolve.packages.alloc(Package { + name: PackageName { + namespace: "componentize-go".into(), + name: "union".into(), + version: None, + }, + docs: Default::default(), + interfaces: Default::default(), + worlds: Default::default(), + }); + + let union_world = resolve.worlds.alloc(World { + name: "union".into(), + imports: Default::default(), + exports: Default::default(), + package: Some(union_package), + docs: Default::default(), + stability: Stability::Unknown, + includes: Default::default(), + span: Default::default(), + }); + + resolve.packages[union_package] + .worlds + .insert("union".into(), union_world); + + for &world in worlds { + resolve.merge_worlds(world, union_world, &mut CloneMaps::default())?; + } + + union_world + } + }; + + Ok((resolve, world)) +} + +/// Generate Go bindings for `world`, returning the files to write. +pub fn generate_bindings( + resolve: &mut Resolve, + world: WorldId, + opts: &BindingsOptions, +) -> Result> { + let mut files = Default::default(); + + wit_bindgen_go::Opts { + generate_stubs: opts.generate_stubs, + // Always off: `gofmt` is a subprocess. Callers format the result. + format: wit_bindgen_go::Format::False, + pkg_name: opts.pkg_name.clone(), + export_pkg_name: opts.export_pkg_name.clone(), + include_versions: opts.include_versions, + ..Default::default() + } + .build() + .generate(resolve, world, &mut files)?; + + Ok(files + .iter() + .map(|(path, contents)| File { + path: path.to_string(), + contents: contents.to_vec(), + }) + .collect()) +} + +/// Embed the component-type custom section describing `world` into `module`. +pub fn embed_wit(module: &[u8], resolve: &Resolve, world: WorldId) -> Result> { + let mut wasm = module.to_vec(); + wit_component::embed_component_metadata( + &mut wasm, + resolve, + world, + wit_component::StringEncoding::UTF8, + )?; + Ok(wasm) +} + +/// Encode a core module into a component. +/// +/// `adapter` overrides the bundled `wasi_snapshot_preview1` reactor adapter, +/// which is used when `None`. +pub fn module_to_component(module: &[u8], adapter: Option<&[u8]>) -> Result> { + let mut encoder = wit_component::ComponentEncoder::default() + .validate(true) + .module(module)? + .adapter( + wasi_preview1_component_adapter_provider::WASI_SNAPSHOT_PREVIEW1_ADAPTER_NAME, + adapter.unwrap_or(WASIP1_SNAPSHOT_ADAPT), + )?; + + encoder + .encode() + .context("failed to encode component from module") +} diff --git a/src/cmd_bindings.rs b/src/cmd_bindings.rs index 5a5560b..8f4fa0f 100644 --- a/src/cmd_bindings.rs +++ b/src/cmd_bindings.rs @@ -1,8 +1,38 @@ use crate::utils::make_path_absolute; -use anyhow::Result; -use std::path::{Path, PathBuf}; +use anyhow::{Context, Result}; +use componentize_go_core::BindingsOptions; +use std::{ + path::{Path, PathBuf}, + process::{Command, Stdio}, +}; use wit_parser::{Resolve, WorldId}; +/// Format Go source with `gofmt`, if it is on `PATH`. +/// +/// Generation itself happens in `componentize-go-core`, which cannot spawn a +/// process, so formatting is applied here to the emitted bytes instead. That +/// keeps the native and component code paths producing identical output. +fn gofmt(path: &Path) -> Result<()> { + let file = std::fs::File::open(path)?; + let output = match Command::new("gofmt") + .stdin(Stdio::from(file)) + .stderr(Stdio::inherit()) + .output() + { + Ok(output) => output, + // Not installed: generated code is still valid, just unformatted. + Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(()), + Err(e) => return Err(e).context("failed to run `gofmt`"), + }; + + if output.status.success() { + std::fs::write(path, output.stdout) + .with_context(|| format!("failed to write '{}'", path.display()))?; + } + + Ok(()) +} + #[allow(clippy::too_many_arguments)] pub fn generate_bindings( resolve: &mut Resolve, @@ -14,47 +44,43 @@ pub fn generate_bindings( export_pkg_name: Option, include_versions: bool, ) -> Result<()> { - let mut files = Default::default(); - - let format = if should_format { - wit_bindgen_go::Format::True - } else { - wit_bindgen_go::Format::False - }; - // If the user wants to create a package rather than a standalone binary, provide them with the // go.bytecodealliance.org/pkg version that needs to be placed in their go.mod file - let mut message: Option = None; - if pkg_name.is_some() { - message = Some(format!( + let message = pkg_name.as_ref().map(|_| { + format!( "Success! Please add the following line to your 'go.mod' file:\n\nrequire {}", - wit_bindgen_go::remote_pkg_version() - )); - } + componentize_go_core::remote_pkg_version() + ) + }); - wit_bindgen_go::Opts { - generate_stubs, - format, - pkg_name, - export_pkg_name, - include_versions, - ..Default::default() - } - .build() - .generate(resolve, world, &mut files)?; + let files = componentize_go_core::generate_bindings( + resolve, + world, + &BindingsOptions { + generate_stubs, + pkg_name, + export_pkg_name, + include_versions, + }, + )?; let output_path = match output { Some(p) => make_path_absolute(p)?, None => PathBuf::from("."), }; - for (name, contents) in files.iter() { - let file_path = output_path.join(name); + for file in &files { + let file_path = output_path.join(&file.path); if let Some(parent) = file_path.parent() { std::fs::create_dir_all(parent)?; } - std::fs::write(&file_path, contents)?; + std::fs::write(&file_path, &file.contents) + .with_context(|| format!("failed to write '{}'", file_path.display()))?; + + if should_format && file.path.ends_with(".go") { + gofmt(&file_path)?; + } } if let Some(msg) = message { diff --git a/src/utils.rs b/src/utils.rs index ec1d5d4..022785b 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,5 +1,6 @@ use anyhow::{Context, Result, anyhow, bail}; use bzip2::read::BzDecoder; +use componentize_go_core::{File as CoreFile, WitSource}; use serde::Deserialize; use std::{ collections::BTreeSet, @@ -11,8 +12,7 @@ use std::{ }; use tar::Archive; use wit_parser::{ - CloneMaps, Function, Interface, Package, PackageName, Resolve, Stability, Type, TypeDef, - TypeDefKind, World, WorldId, WorldItem, + Function, Interface, Resolve, Type, TypeDef, TypeDefKind, World, WorldId, WorldItem, }; pub fn dummy_wit() -> (Resolve, WorldId) { @@ -30,9 +30,62 @@ pub fn dummy_wit() -> (Resolve, WorldId) { (resolve, world) } -// In the rare case the snapshot needs to be updated, the latest version -// can be found here: https://github.com/bytecodealliance/wasmtime/releases -const WASIP1_SNAPSHOT_ADAPT: &[u8] = include_bytes!("wasi_snapshot_preview1.reactor.wasm"); +/// Read one `--wit-path` argument off the filesystem into a [`WitSource`]. +/// +/// Mirrors the layout `wit-parser`'s `push_path` expects: `*.wit` at the root of +/// a directory form its package, and a `deps` subdirectory holds dependency +/// packages as directories, single `.wit` files, or `.wasm`/`.wat` encoded +/// packages. A path to a single file becomes a one-file source. +fn read_wit_source(path: &Path) -> Result { + let mut files = Vec::new(); + + if path.is_file() { + files.push(CoreFile { + path: path + .file_name() + .map(|n| n.to_string_lossy().into_owned()) + .unwrap_or_else(|| path.display().to_string()), + contents: fs::read(path) + .with_context(|| format!("failed to read '{}'", path.display()))?, + }); + return Ok(WitSource::new(path.display().to_string(), files)); + } + + let read_into = |dir: &Path, prefix: &str, files: &mut Vec| -> Result<()> { + for entry in fs::read_dir(dir) + .with_context(|| format!("failed to read directory '{}'", dir.display()))? + { + let entry = entry?; + if !entry.file_type()?.is_file() { + continue; + } + let name = entry.file_name().to_string_lossy().into_owned(); + files.push(CoreFile { + path: format!("{prefix}{name}"), + contents: fs::read(entry.path()) + .with_context(|| format!("failed to read '{}'", entry.path().display()))?, + }); + } + Ok(()) + }; + + read_into(path, "", &mut files)?; + + let deps = path.join("deps"); + if deps.is_dir() { + read_into(&deps, "deps/", &mut files)?; + for entry in fs::read_dir(&deps)? { + let entry = entry?; + if !entry.file_type()?.is_dir() { + continue; + } + let name = entry.file_name().to_string_lossy().into_owned(); + read_into(&entry.path(), &format!("deps/{name}/"), &mut files)?; + } + } + + Ok(WitSource::new(path.display().to_string(), files)) +} pub fn parse_wit( paths: &[impl AsRef], @@ -51,89 +104,12 @@ pub fn parse_wit( } debug_assert!(!paths.is_empty(), "The paths should not be empty"); - let mut resolve = Resolve { - all_features, - ..Default::default() - }; - for features in features { - for feature in features - .split(',') - .flat_map(|s| s.split_whitespace()) - .filter(|f| !f.is_empty()) - { - resolve.features.insert(feature.to_string()); - } - } - - let packages = paths + let sources = paths .iter() - .map(|path| { - // Consolidates if the same package is referenced in multiple worlds - let mut tmp = Resolve { - all_features, - features: resolve.features.clone(), - ..Default::default() - }; - let (pkg, _files) = tmp.push_path(path)?; - let consolidated = resolve.merge(tmp)?; - Ok(consolidated.packages[pkg.index()]) - }) - .collect::>>()?; - - let worlds = worlds - .iter() - .map(|world| { - packages - .iter() - .find_map(|&pkg| resolve.select_world(&[pkg], Some(world)).ok()) - .ok_or_else(|| { - anyhow!("no world named `{world}` found in any of the loaded WIT packages") - }) - }) + .map(|path| read_wit_source(path.as_ref())) .collect::>>()?; - let world = match &worlds[..] { - [] => packages - .iter() - .find_map(|&pkg| resolve.select_world(&[pkg], None).ok()) - .ok_or_else(|| anyhow!("no default world found in any of the loaded WIT packages"))?, - &[world] => world, - worlds => { - let union_package = resolve.packages.alloc(Package { - name: PackageName { - namespace: "componentize-go".into(), - name: "union".into(), - version: None, - }, - docs: Default::default(), - interfaces: Default::default(), - worlds: Default::default(), - }); - - let union_world = resolve.worlds.alloc(World { - name: "union".into(), - imports: Default::default(), - exports: Default::default(), - package: Some(union_package), - docs: Default::default(), - stability: Stability::Unknown, - includes: Default::default(), - span: Default::default(), - }); - - resolve.packages[union_package] - .worlds - .insert("union".into(), union_world); - - for &world in worlds { - resolve.merge_worlds(world, union_world, &mut CloneMaps::default())?; - } - - union_world - } - }; - - Ok((resolve, world)) + componentize_go_core::resolve_wit(&sources, worlds, features, all_features) } /// Unless `ignore_toml_files` is `true`, use `go list` to search the current @@ -198,13 +174,8 @@ pub fn make_path_absolute(p: &Path) -> Result { } pub fn embed_wit(wasm_file: &Path, resolve: &Resolve, world: WorldId) -> Result<()> { - let mut wasm = fs::read(wasm_file)?; - wit_component::embed_component_metadata( - &mut wasm, - resolve, - world, - wit_component::StringEncoding::UTF8, - )?; + let wasm = fs::read(wasm_file)?; + let wasm = componentize_go_core::embed_wit(&wasm, resolve, world)?; fs::write(wasm_file, wasm).context(format!("failed to write '{}'", wasm_file.display()))?; Ok(()) } @@ -213,19 +184,14 @@ pub fn embed_wit(wasm_file: &Path, resolve: &Resolve, world: WorldId) -> Result< pub fn module_to_component(wasm_file: &Path, adapt_file: Option<&Path>) -> Result<()> { let wasm: Vec = fs::read(wasm_file)?; - let mut encoder = wit_component::ComponentEncoder::default().validate(true); - encoder = encoder.module(&wasm)?; - let adapt_bytes = if let Some(adapt) = adapt_file { - fs::read(adapt) - .with_context(|| format!("failed to read adapt file '{}'", adapt.display()))? - } else { - WASIP1_SNAPSHOT_ADAPT.to_vec() - }; - encoder = encoder.adapter("wasi_snapshot_preview1", &adapt_bytes)?; + let adapt_bytes = adapt_file + .map(|adapt| { + fs::read(adapt) + .with_context(|| format!("failed to read adapt file '{}'", adapt.display())) + }) + .transpose()?; - let bytes = encoder - .encode() - .context("failed to encode component from module")?; + let bytes = componentize_go_core::module_to_component(&wasm, adapt_bytes.as_deref())?; fs::write(wasm_file, bytes).context(format!("failed to write `{}`", wasm_file.display()))?; diff --git a/src/wasi_snapshot_preview1.reactor.wasm b/src/wasi_snapshot_preview1.reactor.wasm deleted file mode 100644 index 0207d75..0000000 Binary files a/src/wasi_snapshot_preview1.reactor.wasm and /dev/null differ diff --git a/tests/fixtures/named-implements/foo_baz_store/wit_bindings.go b/tests/fixtures/named-implements/foo_baz_store/wit_bindings.go index ac31a35..7d01c89 100644 --- a/tests/fixtures/named-implements/foo_baz_store/wit_bindings.go +++ b/tests/fixtures/named-implements/foo_baz_store/wit_bindings.go @@ -1,4 +1,4 @@ -// Generated by `wit-bindgen` 0.59.0. DO NOT EDIT! +// Generated by `wit-bindgen` 0.60.0. DO NOT EDIT! // // This code was generated from the following packages: // foo:baz@0.1.0 diff --git a/tests/fixtures/named-implements/primary/wit_bindings.go b/tests/fixtures/named-implements/primary/wit_bindings.go index e162485..8c3116c 100644 --- a/tests/fixtures/named-implements/primary/wit_bindings.go +++ b/tests/fixtures/named-implements/primary/wit_bindings.go @@ -1,4 +1,4 @@ -// Generated by `wit-bindgen` 0.59.0. DO NOT EDIT! +// Generated by `wit-bindgen` 0.60.0. DO NOT EDIT! // // This code was generated from the following packages: // foo:baz@0.1.0 diff --git a/tests/fixtures/named-implements/secondary/wit_bindings.go b/tests/fixtures/named-implements/secondary/wit_bindings.go index 83af910..f07b6de 100644 --- a/tests/fixtures/named-implements/secondary/wit_bindings.go +++ b/tests/fixtures/named-implements/secondary/wit_bindings.go @@ -1,4 +1,4 @@ -// Generated by `wit-bindgen` 0.59.0. DO NOT EDIT! +// Generated by `wit-bindgen` 0.60.0. DO NOT EDIT! // // This code was generated from the following packages: // foo:baz@0.1.0