Add WASI platform support (#106)

This commit is contained in:
Sylvestre Ledru
2026-03-30 15:38:27 +02:00
committed by GitHub
parent bca451510e
commit af7e14e094
5 changed files with 205 additions and 4 deletions
+27
View File
@@ -56,6 +56,11 @@ jobs:
docs/src/**/*.md
src/**/*.md
- run: cargo clippy --all-targets -- -D warnings
- name: Clippy (WASI)
if: matrix.os == 'ubuntu-latest'
run: |
rustup target add wasm32-wasip1
cargo clippy --target wasm32-wasip1 -- -D warnings
style_spellcheck:
name: Style/spellcheck
@@ -108,6 +113,28 @@ jobs:
env:
RUST_BACKTRACE: '1'
test_wasi:
name: Build/Test (WASI)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-wasip1
- uses: Swatinem/rust-cache@v2
- uses: mozilla-actions/sccache-action@v0.0.9
- name: Install wasmtime
run: |
curl https://wasmtime.dev/install.sh -sSf | bash
echo "$HOME/.wasmtime/bin" >> $GITHUB_PATH
- name: Build for WASI
run: cargo build --target wasm32-wasip1
- name: Test with wasmtime
env:
CARGO_TARGET_WASM32_WASIP1_RUNNER: wasmtime
# WASI does not support threads, so tests must run sequentially
run: cargo test --target wasm32-wasip1 -- --test-threads 1
coverage:
name: Code Coverage
runs-on: ${{ matrix.job.os }}
+29
View File
@@ -1,6 +1,7 @@
# platform-info
<!-- spell-checker:ignore (API) nodename osname sysname (rust) println -->
<!-- spell-checker:ignore (wasm) wasip wasmtime -->
[![Crates.io](https://img.shields.io/crates/v/platform-info.svg)](https://crates.io/crates/platform-info)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
@@ -48,6 +49,34 @@ GNU/Linux
> Using `cargo run --example ex` will build and execute this [example code](examples/ex.rs).
### WASI
To cross-compile and run the example under [WASI](https://wasi.dev/), first install
[wasmtime](https://wasmtime.dev/):
```shell
curl https://wasmtime.dev/install.sh -sSf | bash
```
Then build and run:
```shell
cargo build --target wasm32-wasip1 --example ex
wasmtime target/wasm32-wasip1/debug/examples/ex.wasm
```
This should display:
```text
wasi
localhost
0.0.0
0.0.0
wasm32
wasm32
WASI
```
Other examples can be found in the [examples](examples) directory.
## License
+7 -2
View File
@@ -99,19 +99,24 @@ const HOST_OS_NAME: &str = if cfg!(all(
"solaris"
} else if cfg!(target_os = "cygwin") {
"Cygwin"
} else if cfg!(target_os = "wasi") {
"WASI"
} else {
"unknown"
};
//=== platform-specific module code
#[cfg(unix)]
#[cfg(all(unix, not(target_os = "wasi")))]
#[path = "platform/unix.rs"]
mod target;
#[cfg(windows)]
#[path = "platform/windows.rs"]
mod target;
#[cfg(not(any(unix, windows)))]
#[cfg(target_os = "wasi")]
#[path = "platform/wasi.rs"]
mod target;
#[cfg(not(any(unix, windows, target_os = "wasi")))]
#[path = "platform/unknown.rs"]
mod target;
+2 -2
View File
@@ -123,7 +123,7 @@ impl Debug for UTSName {
.field("release", &oss_from_cstr(&self.0.release))
.field("version", &oss_from_cstr(&self.0.version))
.field("machine", &oss_from_cstr(&self.0.machine));
// The domainname field is not part of the POSIX standard but a GNU extension. Therefor
// The domainname field is not part of the POSIX standard but a GNU extension. Therefore
// BSD-like platforms and solaris/illumos are missing the domainname field.
#[cfg(not(any(
target_os = "aix",
@@ -161,7 +161,7 @@ impl PartialEq for UTSName {
other.0.version,
other.0.machine,
);
// The domainname field is not part of the POSIX standard but a GNU extension. Therefor
// The domainname field is not part of the POSIX standard but a GNU extension. Therefore
// BSD-like platforms and solaris/illumos are missing the domainname field.
#[cfg(not(any(
target_os = "aix",
+140
View File
@@ -0,0 +1,140 @@
// This file is part of the platform-info package.
//
// For the full copyright and license information, please view the LICENSE file
// that was distributed with this source code.
// spell-checker:ignore (API) nodename osname sysname gethostname
// spell-checker:ignore (runtimes) wasmtime wasmer wazero
#![warn(unused_results)] // enable warnings for unused results
use std::ffi::{OsStr, OsString};
use crate::{PlatformInfoAPI, PlatformInfoError, UNameAPI};
/// Platform information for WASI (WebAssembly System Interface).
///
/// WASI does not provide a `uname()` syscall, so we return values
/// that describe the WebAssembly platform.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PlatformInfo {
sysname: OsString,
nodename: OsString,
release: OsString,
version: OsString,
machine: OsString,
processor: OsString,
osname: OsString,
}
/// Try to detect the hostname from environment variables.
///
/// WASI (neither preview 1 nor preview 2) provides a `gethostname` syscall or
/// equivalent WIT interface. The `wasi-sockets` proposal lists hostname as a
/// future goal. Until then, the best we can do is check environment variables
/// that the host runtime may pass (e.g. `wasmtime --env HOSTNAME=$(hostname)`).
fn detect_nodename() -> OsString {
for var in &["HOSTNAME", "COMPUTERNAME"] {
if let Ok(val) = std::env::var(var) {
if !val.is_empty() {
return OsString::from(val);
}
}
}
OsString::from("localhost")
}
impl PlatformInfoAPI for PlatformInfo {
/// Create a new `PlatformInfo` for WASI.
///
/// WASI does not provide a `uname()` syscall, and no WASI runtime (wasmtime,
/// wasmer, WasmEdge, wazero) exposes its identity or version to guest modules.
/// There is no standard or convention for runtime self-identification.
///
/// We follow the same approach as wasi-libc's `uname()` implementation, which
/// returns hardcoded values: `sysname="wasi"`, `release="0.0.0"`,
/// `version="0.0.0"`, `machine="wasm32"`.
///
/// ref: <https://github.com/WebAssembly/wasi-libc/blob/main/libc-top-half/musl/src/misc/uname.c>
fn new() -> Result<Self, PlatformInfoError> {
Ok(Self {
sysname: OsString::from("wasi"),
nodename: detect_nodename(),
release: OsString::from("0.0.0"),
version: OsString::from("0.0.0"),
machine: OsString::from(std::env::consts::ARCH),
processor: OsString::from(crate::lib_impl::map_processor(std::env::consts::ARCH)),
osname: OsString::from(crate::lib_impl::HOST_OS_NAME),
})
}
}
impl UNameAPI for PlatformInfo {
fn sysname(&self) -> &OsStr {
&self.sysname
}
fn nodename(&self) -> &OsStr {
&self.nodename
}
fn release(&self) -> &OsStr {
&self.release
}
fn version(&self) -> &OsStr {
&self.version
}
fn machine(&self) -> &OsStr {
&self.machine
}
fn processor(&self) -> &OsStr {
&self.processor
}
fn osname(&self) -> &OsStr {
&self.osname
}
}
#[test]
fn test_wasi() {
let info = PlatformInfo::new().unwrap();
assert_eq!(info.sysname().to_string_lossy(), "wasi");
// nodename comes from env or defaults to "localhost"
assert!(!info.nodename().to_string_lossy().is_empty());
assert_eq!(info.release().to_string_lossy(), "0.0.0");
assert_eq!(info.version().to_string_lossy(), "0.0.0");
assert_eq!(info.machine().to_string_lossy(), std::env::consts::ARCH);
}
#[test]
fn test_wasi_nodename_env() {
let orig = std::env::var("HOSTNAME").ok();
std::env::set_var("HOSTNAME", "my-wasi-host");
let info = PlatformInfo::new().unwrap();
assert_eq!(info.nodename().to_string_lossy(), "my-wasi-host");
std::env::remove_var("HOSTNAME");
std::env::remove_var("COMPUTERNAME");
let info = PlatformInfo::new().unwrap();
assert_eq!(info.nodename().to_string_lossy(), "localhost");
// Restore
match orig {
Some(v) => std::env::set_var("HOSTNAME", v),
None => std::env::remove_var("HOSTNAME"),
}
}
#[test]
fn structure_clone() {
let info = PlatformInfo::new().unwrap();
println!("{:?}", info);
let info_copy = info.clone();
assert_eq!(info_copy, info);
}