Skip to content

Commit c01b01d

Browse files
committed
support combining SDKs with multiple WIT worlds
This adds two new features: - Multiple `--world` options are accepted, in which case the worlds will be merged into a single world for building, testing, or bindings generation. - Zero or more `--wit-module` options are accepted, which allow the user to specify a WIT path by way of a Go module whose source code contains a subdirectory called "wit". In combination, these options make it easier to use multiple SDKs or other libraries which contain `wit-bindgen-go`-generated code. For example say module `example.com/foo` contains code that targets the `com:example/foo` world, while `example.com/bar` contains code that targets the `com:example/bar` world, and each of them has a `wit` directory in root of their source tree containing the WIT files describing their respective worlds. In that case, we can use `componentize-go -w com:example/foo -w com:example/bar -m example.com/foo -m example.com/bar build` to build our app, and `componentize-go` will use `go list` to find the WIT files, resolve the worlds, and union them together to produce the final component type. Note that I've also removed the `wat` dependency because `go build` always generates a binary Wasm file, so no need to check for or parse WAT. Fixes #28
1 parent 9441333 commit c01b01d

4 files changed

Lines changed: 106 additions & 21 deletions

File tree

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ allow_attributes_without_reason = 'warn'
3737
anyhow = { workspace = true}
3838
clap = { version = "4.5.60", features = ["derive"] }
3939
regex = "1.12.3"
40-
wat = { version = "1.245.1"}
4140
wit-bindgen-go = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "3ee9fe20a5bce398360d5d291e81a4224a6d7c76" }
4241
wit-component = "0.245.1"
4342
wit-parser = "0.245.1"

src/cmd_bindings.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,17 @@ use std::path::{Path, PathBuf};
44

55
#[allow(clippy::too_many_arguments)]
66
pub fn generate_bindings(
7-
wit_path: &[impl AsRef<Path>],
8-
world: Option<&str>,
7+
paths: &[impl AsRef<Path>],
8+
modules: &[String],
9+
worlds: &[String],
910
features: &[String],
1011
all_features: bool,
1112
generate_stubs: bool,
1213
should_format: bool,
1314
output: Option<&Path>,
1415
pkg_name: Option<String>,
1516
) -> Result<()> {
16-
let (mut resolve, world) = parse_wit(wit_path, world, features, all_features)?;
17+
let (mut resolve, world) = parse_wit(paths, modules, worlds, features, all_features)?;
1718
let mut files = Default::default();
1819

1920
let format = if should_format {

src/command.rs

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,24 @@ pub struct WitOpts {
3131
#[arg(long, short = 'd')]
3232
pub wit_path: Vec<PathBuf>,
3333

34-
/// Name of world to target (or default world if `None`).
34+
/// The name of a Go module whose source contains WIT document(s).
35+
///
36+
/// This may be specified more than once, for example:
37+
/// `-m github.com/example/foo -m github.com/example/bar`.
38+
///
39+
/// The module must have already been added to the project using `go get`,
40+
/// and its source directory must be discoverable via `go list`. That
41+
/// directory should have a subdirectory named "wit", which will be added to
42+
/// the set of WIT paths to search.
43+
#[arg(long, short = 'm')]
44+
pub wit_module: Vec<String>,
45+
46+
/// Name of world to target (or default world if not specified).
47+
///
48+
/// This may be specified more than once, in which case the worlds will be
49+
/// merged.
3550
#[arg(long, short = 'w')]
36-
pub world: Option<String>,
51+
pub world: Vec<String>,
3752

3853
/// Whether or not to activate all WIT features when processing WIT files.
3954
///
@@ -156,7 +171,8 @@ fn build(wit_opts: WitOpts, build: Build) -> Result<()> {
156171
embed_wit(
157172
&module,
158173
&wit_opts.wit_path,
159-
wit_opts.world.as_deref(),
174+
&wit_opts.wit_module,
175+
&wit_opts.world,
160176
&wit_opts.features,
161177
wit_opts.all_features,
162178
)?;
@@ -182,7 +198,8 @@ fn test(wit_opts: WitOpts, test: Test) -> Result<()> {
182198
embed_wit(
183199
&module,
184200
&wit_opts.wit_path,
185-
wit_opts.world.as_deref(),
201+
&wit_opts.wit_module,
202+
&wit_opts.world,
186203
&wit_opts.features,
187204
wit_opts.all_features,
188205
)?;
@@ -197,8 +214,9 @@ fn test(wit_opts: WitOpts, test: Test) -> Result<()> {
197214

198215
fn bindings(wit_opts: WitOpts, bindings: Bindings) -> Result<()> {
199216
generate_bindings(
200-
wit_opts.wit_path.as_ref(),
201-
wit_opts.world.as_deref(),
217+
&wit_opts.wit_path,
218+
&wit_opts.wit_module,
219+
&wit_opts.world,
202220
&wit_opts.features,
203221
wit_opts.all_features,
204222
bindings.generate_stubs,

src/utils.rs

Lines changed: 78 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
1-
use anyhow::{Context, Result, anyhow};
1+
use anyhow::{Context, Result, anyhow, bail};
22
use std::{
33
path::{Path, PathBuf},
44
process::Command,
55
};
6-
use wit_parser::{PackageId, Resolve, WorldId};
6+
use wit_parser::{CloneMaps, Package, PackageId, PackageName, Resolve, Stability, World, WorldId};
77

88
pub fn parse_wit(
99
paths: &[impl AsRef<Path>],
10-
world: Option<&str>,
10+
modules: &[String],
11+
worlds: &[String],
1112
features: &[String],
1213
all_features: bool,
1314
) -> Result<(Resolve, WorldId)> {
1415
// If no WIT directory was provided as a parameter and none were referenced
1516
// by Go packages, use ./wit by default.
16-
if paths.is_empty() {
17+
if paths.is_empty() && modules.is_empty() {
1718
let paths = &[Path::new("wit")];
18-
return parse_wit(paths, world, features, all_features);
19+
return parse_wit(paths, modules, worlds, features, all_features);
1920
}
2021
debug_assert!(!paths.is_empty(), "The paths should not be empty");
2122

@@ -39,7 +40,72 @@ pub fn parse_wit(
3940
main_packages.push(pkg);
4041
}
4142

42-
let world = resolve.select_world(&main_packages, world)?;
43+
for module in modules {
44+
let mut command = std::process::Command::new("go");
45+
let output = command
46+
.args([
47+
"list",
48+
"-mod=readonly",
49+
"-m",
50+
"-f",
51+
"{{.Dir}}",
52+
module.as_str(),
53+
])
54+
.output()?;
55+
if !output.status.success() {
56+
bail!(
57+
"`go list` failed: {}",
58+
String::from_utf8_lossy(&output.stderr)
59+
);
60+
}
61+
let (pkg, _files) = resolve
62+
.push_path(PathBuf::from(String::from_utf8(output.stdout)?.trim()).join("wit"))?;
63+
main_packages.push(pkg);
64+
}
65+
66+
let world = match worlds {
67+
[] => resolve.select_world(&main_packages, None)?,
68+
[world] => resolve.select_world(&main_packages, Some(world))?,
69+
worlds => {
70+
let worlds = worlds
71+
.iter()
72+
.map(|world| resolve.select_world(&main_packages, Some(world)))
73+
.collect::<Result<Vec<_>>>()?;
74+
75+
let union_package = resolve.packages.alloc(Package {
76+
name: PackageName {
77+
namespace: "componentize-go".into(),
78+
name: "union".into(),
79+
version: None,
80+
},
81+
docs: Default::default(),
82+
interfaces: Default::default(),
83+
worlds: Default::default(),
84+
});
85+
86+
let union_world = resolve.worlds.alloc(World {
87+
name: "union".into(),
88+
imports: Default::default(),
89+
exports: Default::default(),
90+
package: Some(union_package),
91+
docs: Default::default(),
92+
stability: Stability::Unknown,
93+
includes: Default::default(),
94+
span: Default::default(),
95+
});
96+
97+
resolve.packages[union_package]
98+
.worlds
99+
.insert("union".into(), union_world);
100+
101+
for &world in &worlds {
102+
resolve.merge_worlds(world, union_world, &mut CloneMaps::default())?;
103+
}
104+
105+
union_world
106+
}
107+
};
108+
43109
Ok((resolve, world))
44110
}
45111

@@ -53,14 +119,15 @@ pub fn make_path_absolute(p: &PathBuf) -> Result<PathBuf> {
53119
}
54120

55121
pub fn embed_wit(
56-
wasm_file: &PathBuf,
57-
wit_path: &[PathBuf],
58-
world: Option<&str>,
122+
wasm_file: &Path,
123+
paths: &[PathBuf],
124+
modules: &[String],
125+
worlds: &[String],
59126
features: &[String],
60127
all_features: bool,
61128
) -> Result<()> {
62-
let mut wasm = wat::Parser::new().parse_file(wasm_file)?;
63-
let (resolve, world_id) = parse_wit(wit_path, world, features, all_features)?;
129+
let mut wasm = std::fs::read(wasm_file)?;
130+
let (resolve, world_id) = parse_wit(paths, modules, worlds, features, all_features)?;
64131
wit_component::embed_component_metadata(
65132
&mut wasm,
66133
&resolve,

0 commit comments

Comments
 (0)