Skip to content
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
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,3 @@ examples/*/go.mod
examples/*/go.sum
examples/*/wasi_*
examples/*/wit_*
examples/*/export_wasi*
1 change: 1 addition & 0 deletions Cargo.lock

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

4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,7 @@ You can download a specific release from the [release page](https://github.com/b
```sh
cargo install --git https://github.com/bytecodealliance/componentize-go
```

## Usage

Please reference the `README.md` and `Makefile` files in each of the directories in [examples](./examples/).
20 changes: 16 additions & 4 deletions examples/wasip2/Makefile
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
generate-bindings:
componentize-go --world wasip2-example bindings --format
go mod tidy
@componentize-go --world wasip2-example bindings --format
@go mod tidy

build-component: generate-bindings
componentize-go --world wasip2-example componentize
@componentize-go --world wasip2-example build

.PHONY: run
run: build-component
wasmtime serve -Sp3,cli main.wasm
@wasmtime serve -Sp3,cli main.wasm

build-tests: generate-bindings
@componentize-go test --wasip1 --pkg ./unit_tests_should_pass --pkg ./unit_tests_should_fail

run-tests: build-tests
@echo "===== Running tests that will pass ====="
@wasmtime run test_unit_tests_should_pass.wasm
@echo ""

@echo "===== Running tests that will fail ====="
@wasmtime run test_unit_tests_should_fail.wasm || true
@echo ""
7 changes: 7 additions & 0 deletions examples/wasip2/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# `wasip2` Example

## Usage

### Prerequisites

- [**componentize-go**](https://github.com/bytecodealliance/componentize-go) - Latest version
- [**go**](https://go.dev/dl/) - v1.25+
- [**wasmtime**](https://github.com/bytecodealliance/wasmtime) - v40.0.2
Expand All @@ -14,3 +17,7 @@ make run
curl localhost:8080
```

### Run unit tests
```sh
make run-tests
```
30 changes: 30 additions & 0 deletions examples/wasip2/unit_tests_should_fail/err_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package unit_tests_should_fail

import (
"testing"
)

func test_sum(a, b int) int {
return a + b
}

func TestSum(t *testing.T) {
tests := []struct {
name string
a, b int
expected int
}{
{"positive numbers", 2, 3, -1},
{"negative numbers", -1, -2, 10},
{"zeros", 0, 0, 5},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := test_sum(tt.a, tt.b)
if got != tt.expected {
t.Errorf("test_sum(%d, %d) = %d, expected %d", tt.a, tt.b, got, tt.expected)
}
})
}
}
30 changes: 30 additions & 0 deletions examples/wasip2/unit_tests_should_pass/ok_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package unit_tests_should_pass

import (
"testing"
)

func test_sum(a, b int) int {
return a + b
}

func TestSum(t *testing.T) {
tests := []struct {
name string
a, b int
expected int
}{
{"positive numbers", 2, 3, 5},
{"negative numbers", -1, -2, -3},
{"zeros", 0, 0, 0},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := test_sum(tt.a, tt.b)
if got != tt.expected {
t.Errorf("test_sum(%d, %d) = %d, expected %d", tt.a, tt.b, got, tt.expected)
}
})
}
}
2 changes: 1 addition & 1 deletion src/bindings.rs → src/cmd_bindings.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::common::{make_path_absolute, parse_wit};
use crate::utils::{make_path_absolute, parse_wit};
use anyhow::Result;
use std::path::{Path, PathBuf};

Expand Down
93 changes: 93 additions & 0 deletions src/cmd_build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
use crate::utils::{check_go_version, make_path_absolute};
use anyhow::{Result, anyhow};
use std::{path::PathBuf, process::Command};

/// Compiles a Go application to a wasm module with `go build`.
///
/// If the module is not going to be adapted to the component model,
/// set the `only_wasip1` arg to true.
pub fn build_module(
go_module: Option<&PathBuf>,
out: Option<&PathBuf>,
go_path: Option<&PathBuf>,
only_wasip1: bool,
) -> Result<PathBuf> {
let go = match &go_path {
Some(p) => make_path_absolute(p)?,
None => PathBuf::from("go"),
};

check_go_version(&go)?;

let out_path_buf = match &out {
Some(p) => make_path_absolute(p)?,
None => std::env::current_dir()?.join("main.wasm"),
};

// Ensuring the newly compiled wasm file overwrites any previously-existing wasm file
if out_path_buf.exists() {
std::fs::remove_file(&out_path_buf)?;
}

let out_path = out_path_buf
.to_str()
.ok_or_else(|| anyhow!("Output path is not valid unicode"))?;

let module_path = match &go_module {
Some(p) => {
if !p.is_dir() {
return Err(anyhow!("Module path '{}' is not a directory", p.display()));
}
p.to_str()
.ok_or_else(|| anyhow!("Module path is not valid unicode"))?
}
None => ".",
};

// TODO: for when/if we decide to allow users to build wasm modules without componentizing them
#[allow(unused_variables)]
let module_args = [
"build",
"-C",
module_path,
"-buildmode=c-shared",
"-o",
out_path,
];

let component_args = [
"build",
"-C",
module_path,
"-buildmode=c-shared",
"-ldflags=-checklinkname=0",
"-o",
out_path,
];

let output = if only_wasip1 {
unimplemented!();
// TODO: for when/if we decide to allow users to build wasm modules without componentizing them
#[allow(unreachable_code)]
Command::new(&go)
.args(module_args)
.env("GOOS", "wasip1")
.env("GOARCH", "wasm")
.output()?
} else {
Command::new(&go)
.args(component_args)
.env("GOOS", "wasip1")
.env("GOARCH", "wasm")
.output()?
};

if !output.status.success() {
return Err(anyhow!(
"'go build' command failed: {}",
String::from_utf8_lossy(&output.stderr)
));
}

Ok(PathBuf::from(out_path))
}
137 changes: 137 additions & 0 deletions src/cmd_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
use crate::utils::{check_go_version, make_path_absolute};
use anyhow::{Result, anyhow};
use std::{
path::{Path, PathBuf},
process::Command,
};

/// Compiles a Go application to a wasm module with `go test -c`.
///
/// If the module is not going to be adapted to the component model,
/// set the `only_wasip1` arg to true.
pub fn build_test_module(
path: &Path,
output_dir: Option<&PathBuf>,
go_path: Option<&PathBuf>,
only_wasip1: bool,
) -> Result<PathBuf> {
let go = match &go_path {
Some(p) => make_path_absolute(p)?,
None => PathBuf::from("go"),
};

check_go_version(&go)?;

let test_wasm_path = {
// The directory in which the test component will be placed
let test_dir = match output_dir {
Some(p) => make_path_absolute(p)?,
None => std::env::current_dir()?,
};

test_dir.join(get_test_filename(path))
};

// Ensuring the newly compiled wasm file overwrites any previously-existing wasm file
if test_wasm_path.exists() {
std::fs::remove_file(&test_wasm_path)?;
}

if let Some(dir) = output_dir {
std::fs::create_dir_all(dir)?;
}

// The -buildmode flag mutes the unit test output, so it is ommitted
let module_args = [
"test",
"-c",
"-ldflags=-checklinkname=0",
"-o",
test_wasm_path
.to_str()
.expect("the combined paths of 'output-dir' and 'pkg' are not valid unicode"),
path.to_str().expect("pkg path is not valid unicode"),
];

// TODO: for when we figure out how wasip2 tests are to be run
#[allow(unused_variables)]
let component_args = [
"test",
"-c",
"-buildmode=c-shared",
"-ldflags=-checklinkname=0",
"-o",
test_wasm_path
.to_str()
.expect("the combined paths of 'output-dir' and 'pkg' are not valid unicode"),
path.to_str().expect("pkg path is not valid unicode"),
];

let output = if only_wasip1 {
Command::new(&go)
.args(module_args)
.env("GOOS", "wasip1")
.env("GOARCH", "wasm")
.output()?
} else {
unimplemented!();

// TODO: for when we figure out how wasip2 tests are to be run
#[allow(unreachable_code)]
Command::new(&go)
.args(component_args)
.env("GOOS", "wasip1")
.env("GOARCH", "wasm")
.output()?
};

if !output.status.success() {
return Err(anyhow!(
"'go test -c' command failed: {}",
String::from_utf8_lossy(&output.stderr)
));
}

Ok(test_wasm_path)
}

// Format the test filename based on the package path (see unit tests for more details).
pub fn get_test_filename(path: &Path) -> String {
let components: Vec<&str> = path
.components()
.filter_map(|c| match c {
// Filter out the `/` and `.`
std::path::Component::Normal(s) => s.to_str(),
_ => None,
})
.collect();

let tail = if components.len() >= 2 {
&components[components.len() - 2..]
} else {
&components[..]
};

format!("test_{}.wasm", tail.join("_"))
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_get_test_filename() {
let tests = [
("./foo/bar/baz", "test_bar_baz.wasm"),
("./foo/bar", "test_foo_bar.wasm"),
("./bar", "test_bar.wasm"),
("/usr/bin/foo/bar/baz", "test_bar_baz.wasm"),
];

for (input, expected) in tests.iter() {
let input_string = input.to_string();
let actual = get_test_filename(&PathBuf::from(input_string));
assert_eq!(actual, expected.to_string());
}
}
}
Loading
Loading