Skip to content

Commit df0d2d3

Browse files
authored
Merge pull request #27 from asteurer/wasip1
feat: able to build modules
2 parents d3e475d + 1d7a178 commit df0d2d3

8 files changed

Lines changed: 157 additions & 55 deletions

File tree

examples/wasip1/Makefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
build:
2+
@componentize-go build --wasip1
3+
4+
.PHONY: run
5+
run: build
6+
@wasmtime run main.wasm

examples/wasip1/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# `wasip1` Example
2+
3+
## Usage
4+
5+
### Prerequisites
6+
7+
- [**componentize-go**](https://github.com/bytecodealliance/componentize-go) - Latest version
8+
- [**go**](https://go.dev/dl/) - v1.25+
9+
- [**wasmtime**](https://github.com/bytecodealliance/wasmtime) - v40.0.2
10+
11+
### Run
12+
13+
```sh
14+
make run

examples/wasip1/main.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package main
2+
3+
import "fmt"
4+
5+
func main() {
6+
fmt.Println("Hello, World!")
7+
}

examples/wasip2/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
- [**wasmtime**](https://github.com/bytecodealliance/wasmtime) - v40.0.2
1010

1111
### Run
12+
1213
```sh
1314
# Start the application
1415
make run
@@ -18,6 +19,7 @@ curl localhost:8080
1819
```
1920

2021
### Run unit tests
22+
2123
```sh
2224
make run-tests
2325
```

examples/wasip3/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# `wasip3` Example
2+
23
This demonstrates how to implement a `wasi:http@0.3.0-rc-2025-09-16` handler
34
using Go, based on a new `wit-bindgen-go` bindings generator which supports
45
idiomatic, goroutine-based concurrency on top of the [Component Model
@@ -11,7 +12,9 @@ Go](https://github.com/dicej/go/releases/tag/go1.25.5-wasi-on-idle). Once
1112
everything is merged, we'll be able to switch to the upstream releases.
1213

1314
## Building and Running
15+
1416
### Prerequisites
17+
1518
- [**componentize-go**](https://github.com/bytecodealliance/componentize-go) - Latest version
1619
- [**go**](https://github.com/dicej/go/releases/tag/go1.25.5-wasi-on-idle) - The [Makefile](./Makefile) installs the patched version of Go.
1720
- [**wasmtime**](https://github.com/bytecodealliance/wasmtime) - v40.0.2
@@ -56,4 +59,5 @@ curl -i \
5659
```
5760

5861
## Note
62+
5963
This was originally built by [@dicej](https://github.com/dicej) and has been adapted from the [original example](https://github.com/dicej/go-wasi-http-example/tree/main) to use componentize-go.

src/cmd_build.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,12 @@ pub fn build_module(
4444
None => ".",
4545
};
4646

47-
// TODO: for when/if we decide to allow users to build wasm modules without componentizing them
48-
#[allow(unused_variables)]
47+
// The -buildmode flag mutes the module's output, so it is ommitted
4948
let module_args = [
5049
"build",
5150
"-C",
5251
module_path,
53-
"-buildmode=c-shared",
52+
"-ldflags=-checklinkname=0",
5453
"-o",
5554
out_path,
5655
];
@@ -66,9 +65,6 @@ pub fn build_module(
6665
];
6766

6867
let output = if only_wasip1 {
69-
unimplemented!("Building wasip1 Go apps isn't supported quite yet.");
70-
// TODO: for when/if we decide to allow users to build wasm modules without componentizing them
71-
#[allow(unreachable_code)]
7268
Command::new(&go)
7369
.args(module_args)
7470
.env("GOOS", "wasip1")

src/cmd_test.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,9 @@ pub fn build_test_module(
7474
.env("GOARCH", "wasm")
7575
.output()?
7676
} else {
77-
unimplemented!("Please use the --wasip1 flag when building unit tests");
77+
unimplemented!(
78+
"Building Go test components is not yet supported. Please use the --wasip1 flag when building unit tests."
79+
);
7880

7981
// TODO: for when we figure out how wasip2 tests are to be run
8082
#[allow(unreachable_code)]

tests/src/lib.rs

Lines changed: 119 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -92,76 +92,88 @@ mod tests {
9292
go_bin
9393
}
9494

95-
struct App<'a> {
95+
struct App {
9696
/// The path to the example application
9797
path: String,
9898
/// The WIT world to target
99-
world: String,
99+
world: Option<String>,
100100
/// The output path of the wasm file
101101
wasm_path: String,
102102
/// The path to the directory containing the WIT files
103-
wit_path: String,
103+
wit_path: Option<String>,
104104
/// The child process ID of a running wasm app
105105
process: Option<Child>,
106106
/// Any tests that need to be compiled and run as such
107-
tests: Option<&'a [Test<'a>]>,
107+
tests: Option<Vec<Test>>,
108108
}
109109

110-
struct Test<'a> {
110+
#[derive(Clone)]
111+
struct Test {
111112
should_fail: bool,
112-
pkg_path: &'a str,
113+
pkg_path: String,
113114
}
114115

115-
impl<'a> App<'a> {
116+
impl App {
116117
/// Create a new app runner.
117-
fn new(path: &'a str, world: &'a str, tests: Option<&'a [Test<'a>]>) -> Self {
118+
fn new(
119+
path: &str,
120+
world: Option<&str>,
121+
tests: Option<Vec<Test>>,
122+
is_component: bool,
123+
) -> Self {
118124
let path = componentize_go::utils::make_path_absolute(&PathBuf::from(path))
119125
.expect("failed to make app path absolute");
120126

127+
let world = if is_component {
128+
Some(world.expect("WIT world must be specified").to_string())
129+
} else {
130+
None
131+
};
132+
133+
let wit_path = if is_component {
134+
Some(
135+
path.join("wit")
136+
.to_str()
137+
.expect("wit_path is not valid unicode")
138+
.to_string(),
139+
)
140+
} else {
141+
None
142+
};
143+
121144
App {
122145
path: path
123146
.clone()
124147
.to_str()
125148
.expect("app path is not valid unicode")
126149
.to_string(),
127-
world: world.to_string(),
150+
world,
128151
wasm_path: path
129152
.join("main.wasm")
130153
.to_str()
131154
.expect("wasm_path is not valid unicode")
132155
.to_string(),
133-
wit_path: path
134-
.join("wit")
135-
.to_str()
136-
.expect("wit_path is not valid unicode")
137-
.to_string(),
156+
wit_path,
138157
process: None,
139158
tests,
140159
}
141160
}
142161

143-
// Build unit tests with componentize-go
144-
fn build_tests(&self, go: Option<&PathBuf>) -> Result<()> {
145-
let test_pkgs = if let Some(pkgs) = self.tests {
146-
pkgs
147-
} else {
148-
return Err(anyhow!(
149-
"Please include the test_pkg_paths when creating App::new()"
150-
));
151-
};
162+
fn build_test_modules(&self, go: Option<&PathBuf>) -> Result<()> {
163+
let test_pkgs = self.tests.as_ref().expect("missing test_pkg_paths");
152164

153165
self.generate_bindings(go)?;
154166

155167
let mut test_cmd = Command::new(COMPONENTIZE_GO_PATH.as_path());
156168
test_cmd
157-
.args(["-w", &self.world])
158-
.args(["-d", &self.wit_path])
169+
.args(["-w", self.world.as_ref().expect("missing WIT world")])
170+
.args(["-d", self.wit_path.as_ref().expect("missing WIT path")])
159171
.arg("test")
160172
.arg("--wasip1");
161173

162174
// Add all the paths to the packages that have unit tests to compile
163175
for test in test_pkgs.iter() {
164-
test_cmd.args(["--pkg", test.pkg_path]);
176+
test_cmd.args(["--pkg", &test.pkg_path]);
165177
}
166178

167179
// `go test -c` needs to be in the same path as the go.mod file.
@@ -183,13 +195,30 @@ mod tests {
183195
Ok(())
184196
}
185197

186-
fn run_tests(&self) -> Result<()> {
198+
fn run_module(&self) -> Result<()> {
199+
let output = Command::new("wasmtime")
200+
.arg("run")
201+
.arg(&self.wasm_path)
202+
.output()?;
203+
204+
if !output.status.success() {
205+
return Err(anyhow!(
206+
"Failed to run wasm module for application at '{}':\n{} ",
207+
&self.wasm_path,
208+
String::from_utf8_lossy(&output.stdout)
209+
));
210+
}
211+
212+
Ok(())
213+
}
214+
215+
fn run_test_modules(&self) -> Result<()> {
187216
let example_dir = PathBuf::from(&self.path);
188-
if let Some(tests) = self.tests {
217+
if let Some(tests) = &self.tests {
189218
let mut test_errors: Vec<String> = vec![];
190219
for test in tests.iter() {
191220
let wasm_file = example_dir.join(componentize_go::cmd_test::get_test_filename(
192-
&PathBuf::from(test.pkg_path),
221+
&PathBuf::from(&test.pkg_path),
193222
));
194223
match Command::new("wasmtime")
195224
.args(["run", wasm_file.to_str().unwrap()])
@@ -233,15 +262,45 @@ mod tests {
233262
Ok(())
234263
}
235264

236-
/// Build the app with componentize-go.
237-
fn build(&self, go: Option<&PathBuf>) -> Result<()> {
265+
fn build_module(&self, go: Option<&PathBuf>) -> Result<()> {
266+
// Build component
267+
let mut build_cmd = Command::new(COMPONENTIZE_GO_PATH.as_path());
268+
build_cmd
269+
.arg("build")
270+
.arg("--wasip1")
271+
.args(["-o", &self.wasm_path]);
272+
273+
if let Some(go_path) = go.as_ref() {
274+
build_cmd.args(["--go", go_path.to_str().unwrap()]);
275+
}
276+
277+
// Run `go build` in the same directory as the go.mod file.
278+
build_cmd.current_dir(&self.path);
279+
280+
let build_output = build_cmd.output().expect(&format!(
281+
"failed to execute componentize-go for \"{}\"",
282+
self.path
283+
));
284+
285+
if !build_output.status.success() {
286+
return Err(anyhow!(
287+
"failed to build application \"{}\": {}",
288+
self.path,
289+
String::from_utf8_lossy(&build_output.stderr)
290+
));
291+
}
292+
293+
Ok(())
294+
}
295+
296+
fn build_component(&self, go: Option<&PathBuf>) -> Result<()> {
238297
self.generate_bindings(go)?;
239298

240299
// Build component
241300
let mut build_cmd = Command::new(COMPONENTIZE_GO_PATH.as_path());
242301
build_cmd
243-
.args(["-w", &self.world])
244-
.args(["-d", &self.wit_path])
302+
.args(["-w", self.world.as_ref().expect("missing WIT world")])
303+
.args(["-d", self.wit_path.as_ref().expect("missing WIT path")])
245304
.arg("build")
246305
.args(["-o", &self.wasm_path]);
247306

@@ -270,8 +329,8 @@ mod tests {
270329

271330
fn generate_bindings(&self, go: Option<&PathBuf>) -> Result<()> {
272331
let bindings_output = Command::new(COMPONENTIZE_GO_PATH.as_path())
273-
.args(["-w", &self.world])
274-
.args(["-d", &self.wit_path])
332+
.args(["-w", self.world.as_ref().expect("missing WIT world")])
333+
.args(["-d", self.wit_path.as_ref().expect("missing WIT path")])
275334
.arg("bindings")
276335
.args(["-o", &self.path])
277336
.current_dir(&self.path)
@@ -307,7 +366,7 @@ mod tests {
307366
}
308367

309368
/// Run the app and check the output.
310-
async fn run(&mut self, route: &str, expected_response: &str) -> Result<()> {
369+
async fn run_component(&mut self, route: &str, expected_response: &str) -> Result<()> {
311370
let listener = TcpListener::bind("127.0.0.1:0").expect("Failed to bind to a free port");
312371
let addr = listener.local_addr().expect("Failed to get local address");
313372
let port = addr.port();
@@ -345,48 +404,60 @@ mod tests {
345404
}
346405
}
347406

348-
impl<'a> Drop for App<'a> {
407+
impl Drop for App {
349408
fn drop(&mut self) {
350409
if let Some(child) = &mut self.process {
351410
_ = child.kill()
352411
}
353412
}
354413
}
355414

415+
#[test]
416+
fn example_wasip1() {
417+
let app = App::new("../examples/wasip1", None, None, false);
418+
app.build_module(None).expect("failed to build app module");
419+
app.run_module().expect("failed to run app module");
420+
}
421+
356422
#[tokio::test]
357423
async fn example_wasip2() {
358-
let unit_tests = [
424+
let unit_tests = vec![
359425
Test {
360426
should_fail: false,
361-
pkg_path: "./unit_tests_should_pass",
427+
pkg_path: String::from("./unit_tests_should_pass"),
362428
},
363429
Test {
364430
should_fail: true,
365-
pkg_path: "./unit_tests_should_fail",
431+
pkg_path: String::from("./unit_tests_should_fail"),
366432
},
367433
];
368434

369-
let mut app = App::new("../examples/wasip2", "wasip2-example", Some(&unit_tests));
435+
let mut app = App::new(
436+
"../examples/wasip2",
437+
Some("wasip2-example"),
438+
Some(unit_tests),
439+
true,
440+
);
370441

371-
app.build(None).expect("failed to build app");
442+
app.build_component(None).expect("failed to build app");
372443

373-
app.run("/", "Hello, world!")
444+
app.run_component("/", "Hello, world!")
374445
.await
375446
.expect("app failed to run");
376447

377-
app.build_tests(None)
448+
app.build_test_modules(None)
378449
.expect("failed to build app unit tests");
379450

380-
app.run_tests()
451+
app.run_test_modules()
381452
.expect("tests succeeded/failed when they should not have");
382453
}
383454

384455
#[tokio::test]
385456
async fn example_wasip3() {
386-
let mut app = App::new("../examples/wasip3", "wasip3-example", None);
387-
app.build(Some(&patched_go_path().await))
457+
let mut app = App::new("../examples/wasip3", Some("wasip3-example"), None, true);
458+
app.build_component(Some(&patched_go_path().await))
388459
.expect("failed to build app");
389-
app.run("/hello", "Hello, world!")
460+
app.run_component("/hello", "Hello, world!")
390461
.await
391462
.expect("app failed to run");
392463
}

0 commit comments

Comments
 (0)