Skip to content

tegmentum/wsvm

Repository files navigation

WSVM — wasi-sdk Version Manager

WSVM is a lightweight toolchain manager for the WebAssembly wasi-sdk. It installs, selects, discovers, and validates versioned wasi-sdk toolchains and exposes the active one to your build tooling via WASI_SDK_PATH, so that wasi-sdk becomes an implementation detail rather than a prerequisite.

wsvm's logic runs as a WebAssembly component, which needs a wasm runtime to execute. Rather than bundling its own, wsvm borrows one — from wvm (its sibling, the Wasmtime version manager), from a user-supplied path, or from PATH — so wsvm and its runtime stay decoupled. See docs/design.md for the full design.

Install

curl -fsSL https://raw.githubusercontent.com/tegmentum/wsvm/main/install.sh | sh

The installer places the wsvm binary and, unless you pass --no-wvm (or set WSVM_NO_WVM=1), chain-installs wvm plus a default Wasmtime so nothing extra is needed to run the app component. To bring your own runtime, set WSVM_WASMTIME=/path/to/wasmtime or add wasmtime to PATH.

Quickstart

wsvm list                # all available versions (installed ones marked)
wsvm install latest      # download (over wasi:http) + verify + install
wsvm default latest      # default toolchain for new shells
eval "$(wsvm env)"       # export WASI_SDK_PATH and add bin/ to PATH

clang --target=wasm32-wasip1 \
  --sysroot="$WASI_SDK_PATH/share/wasi-sysroot" \
  hello.c -o hello.wasm

Or run a tool directly through wsvm:

wsvm exec clang --version

Long operations show a progress bar / spinner on stderr when attached to a terminal, and fall back to plain milestone lines when output is piped.

Version specifiers

wasi-sdk publishes bare-major releases (33, 32, 27, …); no minor or patch levels have ever shipped, so install, default, use, path, and upgrade accept exactly two spec forms:

Spec Meaning Example resolution
latest newest available 33
<major> that specific major 33

Both forms are stored as the spec (not a frozen version), so wsvm default latest keeps tracking new releases as they land. wsvm default 27 sticks with the 27 line — if wasi-sdk ever adds a 27.1, it would pick that up. wsvm upgrade [spec] [--all] refreshes a spec now rather than waiting for activation-time auto-install.

33.0 / 33.0.0 normalize to 33 (for users coming from nvm/rustup conventions). Anything with a non-zero minor or patch is rejected with a clear "wasi-sdk uses major-only versions" error.

Default vs. per-shell version

  • wsvm default <spec> sets the persistent default used by new shells.
  • wsvm use <spec> switches the toolchain for the current shell only (reverting when you open a new one), via a WSVM_VERSION environment variable.

The installer wires a shell hook into your rc file that auto-manages WASI_SDK_PATH and its bin/ on PATH: it applies the active toolchain once at shell startup and re-applies it after any wsvm use, wsvm deactivate, or wsvm default. Tools like clang, wasm-ld, and ar just work — no manual eval "$(wsvm env)" needed. The hook is idempotent, so re-evaluating it never accumulates duplicate PATH entries.

If you skipped the installer, wire the hook yourself:

wsvm shell-init >> ~/.zshrc   # then restart your shell

For one-shot shells that did not source the hook (e.g. a scratch container), eval "$(wsvm env)" still works.

Commands

Command Description
wsvm install <spec> Install a toolchain (spec: latest, 27, 27.0, 27.0.1). --default to set it as default.
wsvm install <ver> --from <archive> Install offline from a local .tar.gz (exact version required).
wsvm list [--all] List all available versions (most recent first); installed/default marked. --all includes prereleases.
wsvm uninstall <version> Remove an installed toolchain.
wsvm default <spec> Set the persistent default (floats: latest, 27, 27.0).
wsvm use <spec> Switch the toolchain for the current shell (needs shell-init).
wsvm upgrade [spec] [--all] Pull the newest match for a floating line now.
wsvm deactivate Clear the per-shell override, reverting to the default.
wsvm shell-init Print the shell hook that enables per-shell use.
wsvm current Print the effective version (session override, else default).
wsvm path [spec] Print a toolchain's filesystem path (its WASI_SDK_PATH).
wsvm env Print shell exports for the active toolchain.
wsvm exec <tool> [args] Run a tool from the active toolchain's bin/.
wsvm verify [version] Validate installation integrity against manifests.
wsvm gc [--prune] Report (or delete) unreferenced store objects.
wsvm objects List stored objects with sizes and the versions referencing them.
wsvm doctor Diagnose install, shell integration, runtime discovery, and detect external wasi-sdks.
wsvm completions <shell> Print a completion script (bash, zsh, or fish).
wsvm --version Print the wsvm version.
wsvm --upgrade [--check] Upgrade the wsvm binary itself to the latest release. --check reports without installing.

Self-management

wsvm --upgrade replaces the running wsvm binary with the newest release from GitHub. It downloads the wsvm-<arch>-<os> asset for this host, verifies the published .sha256 sidecar, and atomically renames the new binary over the old one. Add --check to report whether a newer release exists without installing it. This is distinct from wsvm upgrade <spec>, which upgrades the managed wasi-sdk toolchains.

On ordinary management commands, wsvm prints a one-line "a newer version is available" notice at most once per WSVM_REFRESH_INTERVAL seconds (default 3600). Set WSVM_NO_UPDATE_NOTIFIER=1 to disable the notice entirely.

Architecture

wsvm (native bootstrapper, on PATH)
  ├─ resolves a wasm runtime  (WSVM_WASMTIME → config → wvm → PATH wasmtime)
  ├─ handles `wsvm exec` natively (resolve toolchain, run bin/<tool>)
  ├─ finalizes unix file modes after each install
  └─ runs the app on that runtime:  <runtime> run -S http --dir WSVM_HOME wsvm-app.wasm -- <args>

wsvm-app (wasm32-wasip2 component) — all other commands
  ├─ explicit wasi:cli command: include wasi:cli/imports + export wasi:cli/run@0.2.6
  ├─ imports wasi:http               (downloads, via waki)
  ├─ imports wasi:filesystem         (internal toolchain symlinks, via symlink-at)
  └─ imports sqlite:wasm/high-level  (the index; composed in via `wac`)

wsvm-app is a standard wasi:cli command (built as a cdylib that owns its wasi:cli/run@0.2.6 export), so it also runs directly under any wasi:cli host:

wasmtime run -S http --dir "$WSVM_HOME::$WSVM_HOME" --env WSVM_HOME="$WSVM_HOME" \
  --env WSVM_HOST_ARCH="$(uname -m)" --env WSVM_HOST_OS=macos \
  target/wsvm-app.composed.wasm -- list

The native binary embeds the composed app component; the wasm runtime is supplied by the environment (wvm, WSVM_WASMTIME, or PATH).

Runtime discovery

wsvm picks the first runtime in this order:

  1. WSVM_WASMTIME=/path/to/wasmtime — one-off override.
  2. ~/.tegmentum/wsvm/config.toml:
    [runtime]
    path = "/opt/wasmtime/bin/wasmtime"
  3. wvm on PATH — delegated as wvm exec -- run ..., so wsvm picks up whatever runtime wvm has active. This is the installer's default.
  4. wasmtime on PATH.

wsvm doctor reports which of these won. Missing a runtime is a hard error with an actionable hint.

Storage layout

WSVM stores everything under ~/.tegmentum/wsvm (override with WSVM_HOME). Files are kept once in a content-addressable store and referenced per version, so multiple toolchains share identical files:

~/.tegmentum/wsvm/
  store/sha256/<ab>/<cd>/<digest>      # deduplicated file objects
  33/                                  # a materialized toolchain (per version)
    bin/clang                          # hardlinked from the store (default)
    bin/clang++ -> clang               # archive symlink, reproduced
    lib/ … share/wasi-sysroot/ …
    manifest.json
  32/                                  # another installed version
  default                              # persistent default spec (plain text)
  downloads/
  cache/releases.json                  # bounded remote-release list for spec resolution
  index.db                             # SQLite backlink/metadata index (rebuildable)
  wsvm-app.wasm                        # the app component
  config.toml

Version directories live directly at the root — wsvm manages exactly one toolchain (wasi-sdk), so the earlier toolchains/wasi-sdk/versions/ was three layers of redundant nesting. Version names are recognized by the presence of a manifest.json, so they never collide with the fixed siblings above.

The index.db SQLite database tracks object backlinks and version metadata; it is a derived cache that wsvm gc rebuilds from disk, so a missing or stale index is never fatal.

Regular files are materialized with the hardlink strategy: the store object and the version path share an inode, so each unique byte lives on disk once — du counts the store or the version dirs, not both. wasi-sdk's rpath-relative library loading (@loader_path/../lib, $ORIGIN/../lib) works because hardlinks look like ordinary files to the loader (no path canonicalization, unlike symlinks). On filesystems that don't support hardlinks (cross-device store, some wasm hosts), each file falls back to copy. Internal toolchain symlinks (e.g. clang++ -> clang) are reproduced as real symlinks regardless of strategy. Override in config.toml: [wsvm] materialization = "copy".

Build from source

Requires the Rust wasm32-wasip2 target and wac (cargo install wac-cli).

rustup target add wasm32-wasip2
make            # builds the app, composes it with the SQLite component,
                # then builds the native binary (target/release/wsvm)

The vendored SQLite component (vendor/sqlite-core.wasm) provides sqlite:wasm/high-level. The WASI WIT for the app's wasi:cli command world is vendored under crates/wsvm-app/wit/deps, so a normal build needs no network for WIT.

License

Apache-2.0.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors