feat: installable toolchain — version, wovm self-locate, dist tarball
Close the 3 gaps between "builds in the repo" and "installs from a tarball
like Go", so writeonce can ship to other developers.
- VERSION file at repo root single-sources the toolchain version (0.1.0).
- `woc version` -> "writeonce 0.1.0 linux/amd64"; `wovm --version` -> "wovm
0.1.0" (stamped by the Makefile from VERSION; --version handled only in the
plain-wovm path so a built app never shadows its own `version` arg).
- wo.toml `[runtime] wo = ">= X.Y"` is now ENFORCED: woc refuses a project
requiring a newer toolchain than itself (>= and bare version parsed;
unknown operators accepted forward-compatibly). Was parsed-and-ignored.
- woc self-locates wovm: --runtime > [build] runtime > $WO_RUNTIME > a `wovm`
beside the woc binary (Sys.executable_name) > runtime/wovm rel CWD. An
installed woc in <prefix>/bin finds its sibling wovm from any cwd.
- `just dist` (scripts/mkdist.sh) packages writeonce-<ver>-linux-amd64.tar.gz,
Go-shaped (archive root writeonce/, bin/{woc,wovm}, README, VERSION), with a
drift guard asserting VERSION == woc == wovm. dist/ gitignored.
- `just install-accept` (scripts/install-accept.sh) is the gate: extract, PATH,
version, build+run a project from an unrelated cwd, constraint refusal — 6/0.
Verified: install-accept 6/0; woc-test 565/0; wovm suites + cli_smoke;
log-watcher 7/0. Linux-amd64 only (a cross matrix is future work).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2c2a3bb7cd
commit
f0971e1481
9 changed files with 248 additions and 9 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -100,3 +100,4 @@ prototypes/wo-db/
|
|||
# un-ignoring their directories above exposed these.
|
||||
__pycache__/
|
||||
*.pyc
|
||||
dist/
|
||||
|
|
|
|||
1
VERSION
Normal file
1
VERSION
Normal file
|
|
@ -0,0 +1 @@
|
|||
0.1.0
|
||||
|
|
@ -43,6 +43,7 @@ let usage_msg =
|
|||
usage: woc <dir> (builds when <dir>/wo.toml exists)\n\
|
||||
usage: woc --emit <path> -o <out.wob>\n\
|
||||
usage: woc build <dir> -o <app> [--runtime <path>]\n\
|
||||
usage: woc version\n\
|
||||
usage: woc --dump-tokens <path>\n\
|
||||
usage: woc --dump-ast <path>\n\
|
||||
usage: woc --dump-owner <path>\n\
|
||||
|
|
@ -91,8 +92,9 @@ let usage_msg =
|
|||
to compile would be describing bytecode nobody may run.\n\
|
||||
\n\
|
||||
build compiles <dir> like --emit, then produces one self-contained\n\
|
||||
executable at -o: the wovm runtime binary (--runtime <path>, or\n\
|
||||
runtime/wovm relative to the current directory when omitted) with the\n\
|
||||
executable at -o: the wovm runtime binary (--runtime <path>; else\n\
|
||||
$WO_RUNTIME, a `wovm` beside this `woc`, or runtime/wovm relative to\n\
|
||||
the current directory) with the\n\
|
||||
compiled .wob image and a fixed-size trailer appended, so the result\n\
|
||||
runs standalone with no separate .wob file or argument (wovm finds the\n\
|
||||
embedded image via /proc/self/exe -- see docs/plan/oop-vm/00-wob-format.md's\n\
|
||||
|
|
@ -515,7 +517,23 @@ let strip_existing_trailer (rt : string) : string =
|
|||
then String.sub rt 0 payload_off
|
||||
else rt
|
||||
|
||||
let default_runtime_path = "runtime/wovm"
|
||||
(* keep in sync with the repo-root VERSION file; `just dist` asserts that
|
||||
`woc version`, `wovm --version`, and VERSION all agree before packaging. *)
|
||||
let toolchain_version = "0.1.0"
|
||||
|
||||
(* the wovm the standalone build embeds into. When neither --runtime nor the
|
||||
manifest's [build] runtime is given, resolve the default in order:
|
||||
1. $WO_RUNTIME -- explicit override
|
||||
2. a `wovm` beside this `woc` -- the tarball layout <prefix>/bin/{woc,wovm},
|
||||
located via Sys.executable_name
|
||||
3. `runtime/wovm` relative to CWD -- the repo/dev fallback *)
|
||||
let default_runtime_path () : string =
|
||||
match Sys.getenv_opt "WO_RUNTIME" with
|
||||
| Some p when p <> "" -> p
|
||||
| _ ->
|
||||
let sibling = Filename.concat (Filename.dirname Sys.executable_name) "wovm" in
|
||||
if Sys.file_exists sibling && not (Sys.is_directory sibling) then sibling
|
||||
else "runtime/wovm"
|
||||
|
||||
let build_mode ~(runtime : string option) (path : string) (out : string) : unit =
|
||||
let collector, lookup, image = compile_image path in
|
||||
|
|
@ -528,7 +546,7 @@ let build_mode ~(runtime : string option) (path : string) (out : string) : unit
|
|||
path;
|
||||
exit 2
|
||||
end;
|
||||
let rt_path = match runtime with Some p -> p | None -> default_runtime_path in
|
||||
let rt_path = match runtime with Some p -> p | None -> default_runtime_path () in
|
||||
if (not (Sys.file_exists rt_path)) || Sys.is_directory rt_path then begin
|
||||
Printf.eprintf "woc: runtime binary not found at '%s' -- build it with: make -C runtime wovm\n"
|
||||
rt_path;
|
||||
|
|
@ -569,8 +587,9 @@ let build_mode ~(runtime : string option) (path : string) (out : string) : unit
|
|||
manifest names the application, so pointing woc at the project is enough
|
||||
(`woc .` inside it). The schema is the one the sample already carried —
|
||||
top-level `name` (the executable's basename), `version`, `description`,
|
||||
a `[runtime]` section whose `wo` constraint is accepted and not yet
|
||||
enforced — plus one new section this feature adds:
|
||||
a `[runtime]` section whose `wo` constraint is a minimum toolchain
|
||||
version, enforced against `woc`'s own version — plus one new section
|
||||
this feature adds:
|
||||
|
||||
[build]
|
||||
runtime = "../runtime/wovm" # optional: wovm to prepend, relative to
|
||||
|
|
@ -629,7 +648,7 @@ let manifest_parse (path : string) : (string * string) list =
|
|||
let known =
|
||||
match (!section, key) with
|
||||
| "", ("name" | "version" | "description") -> true
|
||||
| "runtime", "wo" -> true (* accepted, not yet enforced *)
|
||||
| "runtime", "wo" -> true (* minimum toolchain version; enforced in manifest_build *)
|
||||
| "build", ("runtime" | "target") -> true
|
||||
| _ -> false
|
||||
in
|
||||
|
|
@ -644,10 +663,38 @@ let manifest_parse (path : string) : (string * string) list =
|
|||
with End_of_file -> close_in ic);
|
||||
!kvs
|
||||
|
||||
(* [runtime] wo = ">= X.Y" — a minimum-toolchain-version constraint. Only `>=`
|
||||
and a bare version are interpreted; any other operator is accepted untouched
|
||||
(forward-compatible — don't hard-fail on a constraint syntax not grokked
|
||||
yet). Compared as a (major, minor, patch) triple. *)
|
||||
let ver_triple (s : string) : int * int * int =
|
||||
match String.split_on_char '.' s |> List.filter_map int_of_string_opt with
|
||||
| [ a ] -> (a, 0, 0)
|
||||
| [ a; b ] -> (a, b, 0)
|
||||
| a :: b :: c :: _ -> (a, b, c)
|
||||
| [] -> (0, 0, 0)
|
||||
|
||||
let check_runtime_constraint (mf : string) (c : string) : unit =
|
||||
let c = String.trim c in
|
||||
let min_req =
|
||||
if String.length c >= 2 && String.sub c 0 2 = ">=" then
|
||||
Some (String.trim (String.sub c 2 (String.length c - 2)))
|
||||
else if String.length c > 0 && c.[0] >= '0' && c.[0] <= '9' then Some c
|
||||
else None
|
||||
in
|
||||
match min_req with
|
||||
| Some req when compare (ver_triple toolchain_version) (ver_triple req) < 0 ->
|
||||
Printf.eprintf
|
||||
"woc: %s: project requires writeonce %s, but this toolchain is %s -- upgrade the toolchain\n"
|
||||
mf c toolchain_version;
|
||||
exit 2
|
||||
| _ -> ()
|
||||
|
||||
let manifest_build (dir : string) : unit =
|
||||
let mf = Filename.concat dir "wo.toml" in
|
||||
let kvs = manifest_parse mf in
|
||||
let get k = List.assoc_opt k kvs in
|
||||
(match get "runtime.wo" with Some c -> check_runtime_constraint mf c | None -> ());
|
||||
let name =
|
||||
match get "name" with
|
||||
| Some n when n <> "" && not (String.contains n '/') -> n
|
||||
|
|
@ -681,6 +728,9 @@ let () =
|
|||
| [| _; "build"; path; "-o"; out |] -> build_mode ~runtime:None path out
|
||||
| [| _; "build"; path; "-o"; out; "--runtime"; rt |] -> build_mode ~runtime:(Some rt) path out
|
||||
| [| _; "build"; path; "--runtime"; rt; "-o"; out |] -> build_mode ~runtime:(Some rt) path out
|
||||
| [| _; ("version" | "--version") |] ->
|
||||
(* Go-style: `writeonce <ver> <os>/<arch>`. linux/amd64 is the only target. *)
|
||||
Printf.printf "writeonce %s linux/amd64\n" toolchain_version
|
||||
| [| _; path |] ->
|
||||
if Sys.file_exists path && Sys.is_directory path
|
||||
&& Sys.file_exists (Filename.concat path "wo.toml")
|
||||
|
|
|
|||
13
justfile
13
justfile
|
|
@ -43,6 +43,19 @@ wovm-test:
|
|||
make -C runtime test-iso
|
||||
bash runtime/test/cli_smoke.sh
|
||||
|
||||
# dist: package the toolchain as an installable, Go-style tarball —
|
||||
# writeonce-<ver>-linux-amd64.tar.gz whose `writeonce/bin/` holds woc + wovm.
|
||||
# The version is single-sourced from ./VERSION and asserted against both
|
||||
# binaries (drift guard). See scripts/mkdist.sh.
|
||||
dist:
|
||||
./scripts/mkdist.sh
|
||||
|
||||
# install-accept: extract the dist tarball to a temp prefix, PATH it, and prove
|
||||
# `woc version` + a from-scratch project build+run (self-located wovm) + the
|
||||
# wo-constraint refusal all work — the "tarball install actually works" gate.
|
||||
install-accept:
|
||||
./scripts/install-accept.sh
|
||||
|
||||
# the sample workload's own recipes live beside it (build from wo.toml,
|
||||
# the acceptance test, the soak): `just log-watcher` runs the acceptance,
|
||||
# `just log-watcher::build` / `::soak 60` the rest — see the module's
|
||||
|
|
|
|||
|
|
@ -2,6 +2,11 @@ CC ?= cc
|
|||
CFLAGS ?= -O2 -Wall -Wextra -std=c11
|
||||
LDFLAGS ?= -pthread
|
||||
|
||||
# toolchain version, single-sourced from the repo-root VERSION file; stamped
|
||||
# into wovm's `--version`. `just dist` asserts woc and wovm agree with it.
|
||||
WO_VERSION := $(shell cat ../VERSION 2>/dev/null || echo 0.0.0-dev)
|
||||
VERFLAG := -DWO_VERSION='"$(WO_VERSION)"'
|
||||
|
||||
wo-rt: wo-rt.c
|
||||
$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
|
||||
|
||||
|
|
@ -43,7 +48,7 @@ test-iso: $(ISOBIN)
|
|||
|
||||
# the wovm binary (plain optimized build; the test suite is the ASan gate)
|
||||
wovm: src/main.c $(VMSRC) $(VMHDR)
|
||||
$(CC) $(CFLAGS) -Isrc -I../database/src -o $@ src/main.c $(VMSRC)
|
||||
$(CC) $(CFLAGS) $(VERFLAG) -Isrc -I../database/src -o $@ src/main.c $(VMSRC)
|
||||
|
||||
# ASan+UBSan wovm, same flags as the unit tests, for corpus fixtures that
|
||||
# need a sanitizer to prove a free actually happened (gc/ cycle fixtures) —
|
||||
|
|
|
|||
|
|
@ -17,6 +17,12 @@
|
|||
#include "vm.h"
|
||||
#include "wal.h"
|
||||
|
||||
/* stamped by the Makefile from the repo-root VERSION file; the fallback keeps
|
||||
* a hand-compiled main.c building. `just dist` asserts it matches `woc`. */
|
||||
#ifndef WO_VERSION
|
||||
#define WO_VERSION "0.0.0-dev"
|
||||
#endif
|
||||
|
||||
static wo_vm VM; /* 32K value stack: keep it off the C stack */
|
||||
static wo_db DB; /* the per-shard engine (one shard until iteration 8) */
|
||||
static wo_wal WAL;
|
||||
|
|
@ -134,7 +140,13 @@ int main(int argc, char **argv) {
|
|||
}
|
||||
if (self_rc == 0) {
|
||||
/* no embedded image: the image path is argv[1], and program mode
|
||||
passes everything after it to the program itself */
|
||||
passes everything after it to the program itself. `--version` is
|
||||
handled ONLY here (plain wovm) so a built app never shadows its
|
||||
own `version` argument. */
|
||||
if (argc >= 2 && (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "version") == 0)) {
|
||||
printf("wovm %s\n", WO_VERSION);
|
||||
return 0;
|
||||
}
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "usage: wovm <file.wob> [args...]\n");
|
||||
return 2;
|
||||
|
|
|
|||
72
scripts/install-accept.sh
Executable file
72
scripts/install-accept.sh
Executable file
|
|
@ -0,0 +1,72 @@
|
|||
#!/usr/bin/env bash
|
||||
# scripts/install-accept.sh — proves the dist tarball installs and works the way
|
||||
# Go's does: extract to a prefix, put its bin on PATH, `woc version`, then build
|
||||
# AND run a throwaway project from an unrelated cwd (so only woc's self-location
|
||||
# can find wovm), and confirm the wo-version constraint refuses a newer toolchain.
|
||||
# Run `just dist` first to produce the tarball.
|
||||
set -uo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
ver="$(cat "$ROOT/VERSION")"
|
||||
tarball="$ROOT/dist/writeonce-${ver}-linux-amd64.tar.gz"
|
||||
|
||||
pass=0
|
||||
fail=0
|
||||
ok() { echo "ok $1"; pass=$((pass + 1)); }
|
||||
bad() { echo "FAIL $1 -- $2"; fail=$((fail + 1)); }
|
||||
|
||||
if [[ ! -f "$tarball" ]]; then
|
||||
echo "install-accept: $tarball missing -- build it first: just dist" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
W="$(mktemp -d "${TMPDIR:-/tmp}/wo-install.XXXXXX")"
|
||||
trap 'rm -rf "$W"' EXIT
|
||||
|
||||
# 1. extract exactly like `tar -C /usr/local` — archive root is `writeonce/`
|
||||
tar -C "$W" -xzf "$tarball"
|
||||
prefix="$W/writeonce"
|
||||
if [[ -x "$prefix/bin/woc" && -x "$prefix/bin/wovm" ]]; then
|
||||
ok "tarball extracts writeonce/bin/{woc,wovm}"
|
||||
else
|
||||
bad "extract" "binaries missing under $prefix/bin"
|
||||
echo; printf 'install-accept: %d checks, %d failures\n' "$((pass + fail))" "$fail"; exit 1
|
||||
fi
|
||||
|
||||
export PATH="$prefix/bin:$PATH"
|
||||
|
||||
# 2. version reporting (the `go version` equivalent)
|
||||
got="$(woc version)"
|
||||
[[ "$got" == "writeonce $ver linux/amd64" ]] && ok "woc version" || bad "woc version" "$got"
|
||||
got="$(wovm --version)"
|
||||
[[ "$got" == "wovm $ver" ]] && ok "wovm --version" || bad "wovm --version" "$got"
|
||||
|
||||
# 3. build + run a project from an UNRELATED cwd (only self-location finds wovm)
|
||||
proj="$W/proj"
|
||||
mkdir -p "$proj"
|
||||
cat > "$proj/wo.toml" <<EOF
|
||||
name = "greet"
|
||||
version = "0.1.0"
|
||||
|
||||
[runtime]
|
||||
wo = ">= 0.1"
|
||||
EOF
|
||||
cat > "$proj/main.wo" <<'EOF'
|
||||
fn main(args: multi Text) -> Int { print("installed writeonce works"); return 0; }
|
||||
EOF
|
||||
if ( cd /tmp && woc "$proj" ) >/dev/null 2>&1 && [[ -x "$proj/target/greet" ]]; then
|
||||
ok "woc builds a project (self-located wovm from an unrelated cwd)"
|
||||
else
|
||||
bad "build" "no target/greet produced"
|
||||
fi
|
||||
out="$("$proj/target/greet" 2>&1)"
|
||||
[[ "$out" == "installed writeonce works" ]] && ok "standalone binary runs" || bad "run" "$out"
|
||||
|
||||
# 4. the wo constraint refuses a toolchain older than required
|
||||
sed -i 's/>= 0.1/>= 99.0/' "$proj/wo.toml"
|
||||
( cd /tmp && woc "$proj" ) >/dev/null 2>&1
|
||||
[[ $? -eq 2 ]] && ok "wo constraint refuses a newer requirement" || bad "constraint" "did not refuse >= 99.0"
|
||||
|
||||
echo
|
||||
printf 'install-accept: %d checks, %d failures\n' "$((pass + fail))" "$fail"
|
||||
[[ $fail -eq 0 ]]
|
||||
42
scripts/install-readme.tmpl.md
Normal file
42
scripts/install-readme.tmpl.md
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# writeonce @VER@ (@TRIPLE@)
|
||||
|
||||
A single-binary compiled language with an embedded, WAL-durable database. This
|
||||
archive is the toolchain: `woc` (the compiler) and `wovm` (the runtime VM).
|
||||
Both are native binaries that depend only on the system C library — nothing
|
||||
else to install.
|
||||
|
||||
## Install (Linux)
|
||||
|
||||
Remove any previous install and extract this archive into `/usr/local`,
|
||||
creating a fresh `/usr/local/writeonce`:
|
||||
|
||||
rm -rf /usr/local/writeonce && tar -C /usr/local -xzf writeonce-@VER@-@TRIPLE@.tar.gz
|
||||
|
||||
(Run as root, or through `sudo`.)
|
||||
|
||||
Add `/usr/local/writeonce/bin` to your `PATH` by adding this line to your
|
||||
`$HOME/.profile` (or `/etc/profile` for a system-wide install):
|
||||
|
||||
export PATH=$PATH:/usr/local/writeonce/bin
|
||||
|
||||
Restart your shell, or `source $HOME/.profile` to apply it now. Then verify:
|
||||
|
||||
woc version # writeonce @VER@ linux/amd64
|
||||
wovm --version # wovm @VER@
|
||||
|
||||
## Use
|
||||
|
||||
woc <project-dir> # builds <project>/target/<name>, one standalone binary
|
||||
./<project>/target/<name> # run it — no runtime to install on the target host
|
||||
|
||||
A writeonce project is a directory with a `wo.toml` manifest and one or more
|
||||
`.wo` files. `woc` locates `wovm` beside itself, so a tarball install needs no
|
||||
extra configuration; override with the `$WO_RUNTIME` environment variable or a
|
||||
`[build] runtime = "..."` key in `wo.toml` if you ever need to.
|
||||
|
||||
A `wo.toml` may declare a minimum toolchain version:
|
||||
|
||||
[runtime]
|
||||
wo = ">= @VER@"
|
||||
|
||||
`woc` refuses to build a project that requires a newer toolchain than itself.
|
||||
43
scripts/mkdist.sh
Executable file
43
scripts/mkdist.sh
Executable file
|
|
@ -0,0 +1,43 @@
|
|||
#!/usr/bin/env bash
|
||||
# scripts/mkdist.sh — package the writeonce toolchain as an installable tarball,
|
||||
# Go-style. Archive root is `writeonce/` (version-less, like Go's `go/`), so
|
||||
# `tar -C /usr/local -xzf ...` drops it at /usr/local/writeonce. Builds both
|
||||
# release binaries, asserts VERSION == `woc version` == `wovm --version` (drift
|
||||
# guard), stages an install README, and emits dist/<name>.tar.gz + a sha256.
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
ver="$(cat VERSION)"
|
||||
triple="linux-amd64" # the only target today; a cross matrix is future work
|
||||
name="writeonce-${ver}-${triple}"
|
||||
|
||||
# release binaries
|
||||
dune build --root compiler
|
||||
make -C runtime wovm >/dev/null
|
||||
|
||||
woc="compiler/_build/default/bin/woc"
|
||||
wovm="runtime/wovm"
|
||||
|
||||
# drift guard: the three version sources must agree, else the tarball would ship
|
||||
# a `wo >= X` constraint nobody can honor
|
||||
got_woc="$("$woc" version | awk '{print $2}')"
|
||||
got_vm="$("$wovm" --version | awk '{print $2}')"
|
||||
[[ "$got_woc" == "$ver" ]] || { echo "mkdist: woc reports $got_woc but VERSION says $ver" >&2; exit 1; }
|
||||
[[ "$got_vm" == "$ver" ]] || { echo "mkdist: wovm reports $got_vm but VERSION says $ver" >&2; exit 1; }
|
||||
|
||||
stage="dist/writeonce"
|
||||
rm -rf "$stage"
|
||||
mkdir -p "$stage/bin"
|
||||
install -m 0755 "$woc" "$stage/bin/woc"
|
||||
install -m 0755 "$wovm" "$stage/bin/wovm"
|
||||
cp VERSION "$stage/VERSION"
|
||||
sed "s/@VER@/$ver/g; s/@TRIPLE@/$triple/g" scripts/install-readme.tmpl.md > "$stage/README.md"
|
||||
|
||||
tar -C dist -czf "dist/${name}.tar.gz" writeonce
|
||||
rm -rf "$stage"
|
||||
( cd dist && sha256sum "${name}.tar.gz" > "${name}.tar.gz.sha256" )
|
||||
|
||||
echo "built dist/${name}.tar.gz"
|
||||
cat "dist/${name}.tar.gz.sha256"
|
||||
Loading…
Reference in a new issue