feat: woc <dir> builds from wo.toml
- a directory carrying wo.toml is a PROJECT: `woc .` inside it (or `woc path/to/project` from anywhere) reads the manifest and produces <target>/<name>, exactly what `woc build <dir> -o ...` produces - schema is the one the sample already carried -- top-level name/ version/description, [runtime] wo (accepted, not yet enforced) -- plus a new [build] section: runtime (wovm to prepend) and target (output dir, default "target"), both relative to the manifest's own directory so the build is invocation-point independent - unknown keys and sections are hard errors: a typo'd key silently ignored would build the wrong thing - directories WITHOUT wo.toml keep check-only semantics -- the corpus is full of those; oop-e2e 71/0, woc-test 565/0, log-watcher 7/0 - sample's wo.toml gains the [build] section; target/ gitignored Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e1f31cf75d
commit
348564d620
3 changed files with 114 additions and 2 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -1,6 +1,9 @@
|
||||||
# Cargo build artifacts
|
# Cargo build artifacts
|
||||||
/target
|
/target
|
||||||
|
|
||||||
|
# `woc .` manifest builds (wo.toml [build] target)
|
||||||
|
/docs/examples/log-watcher/target
|
||||||
|
|
||||||
# Rust runtime (crates/rt/): compiled binary + build artifacts
|
# Rust runtime (crates/rt/): compiled binary + build artifacts
|
||||||
/crates/rt/target
|
/crates/rt/target
|
||||||
/crates/rt/Cargo.lock
|
/crates/rt/Cargo.lock
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@
|
||||||
|
|
||||||
let usage_msg =
|
let usage_msg =
|
||||||
"usage: woc <path>\n\
|
"usage: woc <path>\n\
|
||||||
|
usage: woc <dir> (builds when <dir>/wo.toml exists)\n\
|
||||||
usage: woc --emit <path> -o <out.wob>\n\
|
usage: woc --emit <path> -o <out.wob>\n\
|
||||||
usage: woc build <dir> -o <app> [--runtime <path>]\n\
|
usage: woc build <dir> -o <app> [--runtime <path>]\n\
|
||||||
usage: woc --dump-tokens <path>\n\
|
usage: woc --dump-tokens <path>\n\
|
||||||
|
|
@ -56,7 +57,11 @@ let usage_msg =
|
||||||
\n\
|
\n\
|
||||||
With no flag, <path> is fully compiled (lexed, parsed, typechecked,\n\
|
With no flag, <path> is fully compiled (lexed, parsed, typechecked,\n\
|
||||||
ownership-checked) and nothing is printed on success; diagnostics,\n\
|
ownership-checked) and nothing is printed on success; diagnostics,\n\
|
||||||
if any, print to stderr.\n\
|
if any, print to stderr. One exception: a directory containing a\n\
|
||||||
|
wo.toml manifest is BUILT instead — `woc .` inside a project (or\n\
|
||||||
|
`woc path/to/project` from anywhere) reads the manifest's `name` plus\n\
|
||||||
|
the optional [build] runtime/target keys (paths relative to the\n\
|
||||||
|
manifest) and produces <target>/<name> exactly as `woc build` would.\n\
|
||||||
\n\
|
\n\
|
||||||
--dump-tokens prints one line per lexed token to stdout, in source\n\
|
--dump-tokens prints one line per lexed token to stdout, in source\n\
|
||||||
order (\"LINE:COL KIND\" or \"LINE:COL KIND(payload)\"), ending with\n\
|
order (\"LINE:COL KIND\" or \"LINE:COL KIND(payload)\"), ending with\n\
|
||||||
|
|
@ -559,6 +564,100 @@ let build_mode ~(runtime : string option) (path : string) (out : string) : unit
|
||||||
finish collector lookup
|
finish collector lookup
|
||||||
end
|
end
|
||||||
|
|
||||||
|
(* ---- manifest build ---------------------------------------------------
|
||||||
|
`woc <dir>` where <dir>/wo.toml exists is a BUILD, not a check: the
|
||||||
|
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:
|
||||||
|
|
||||||
|
[build]
|
||||||
|
runtime = "../runtime/wovm" # optional: wovm to prepend, relative to
|
||||||
|
# the manifest's own directory (default:
|
||||||
|
# `runtime/wovm` relative to the CWD, the
|
||||||
|
# same as `woc build` with no --runtime)
|
||||||
|
target = "target" # optional: output directory, relative to
|
||||||
|
# the manifest's directory
|
||||||
|
|
||||||
|
Anything else is an error: a typo'd key silently ignored would build the
|
||||||
|
wrong thing. A directory WITHOUT wo.toml keeps today's meaning (check
|
||||||
|
only), and the corpus is full of those. *)
|
||||||
|
let manifest_parse (path : string) : (string * string) list =
|
||||||
|
let fail line msg =
|
||||||
|
Printf.eprintf "woc: %s:%d: %s\n" path line msg;
|
||||||
|
exit 2
|
||||||
|
in
|
||||||
|
let ic = try open_in path with Sys_error m -> Printf.eprintf "woc: %s\n" m; exit 2 in
|
||||||
|
let kvs = ref [] in
|
||||||
|
let section = ref "" in
|
||||||
|
let lineno = ref 0 in
|
||||||
|
(try
|
||||||
|
while true do
|
||||||
|
let raw = input_line ic in
|
||||||
|
incr lineno;
|
||||||
|
let line = String.trim raw in
|
||||||
|
if line = "" || (String.length line >= 1 && line.[0] = '#') then ()
|
||||||
|
else if line.[0] = '[' then begin
|
||||||
|
if line.[String.length line - 1] <> ']' then fail !lineno "malformed section header";
|
||||||
|
section := String.sub line 1 (String.length line - 2);
|
||||||
|
if !section <> "runtime" && !section <> "build" then
|
||||||
|
fail !lineno (Printf.sprintf "unknown section [%s] (runtime and build exist)" !section)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
match String.index_opt line '=' with
|
||||||
|
| None -> fail !lineno "expected `key = \"value\"`"
|
||||||
|
| Some eq ->
|
||||||
|
let key = String.trim (String.sub line 0 eq) in
|
||||||
|
let v = String.trim (String.sub line (eq + 1) (String.length line - eq - 1)) in
|
||||||
|
if String.length v < 2 || v.[0] <> '"' || v.[String.length v - 1] <> '"' then
|
||||||
|
fail !lineno (Printf.sprintf "`%s`: only quoted string values are supported" key);
|
||||||
|
let v = String.sub v 1 (String.length v - 2) in
|
||||||
|
let known =
|
||||||
|
match (!section, key) with
|
||||||
|
| "", ("name" | "version" | "description") -> true
|
||||||
|
| "runtime", "wo" -> true (* accepted, not yet enforced *)
|
||||||
|
| "build", ("runtime" | "target") -> true
|
||||||
|
| _ -> false
|
||||||
|
in
|
||||||
|
if not known then
|
||||||
|
fail !lineno
|
||||||
|
(if !section = "" then
|
||||||
|
Printf.sprintf "unknown key `%s` (name, version, description exist)" key
|
||||||
|
else
|
||||||
|
Printf.sprintf "unknown key `%s` in [%s]" key !section);
|
||||||
|
kvs := ((if !section = "" then key else !section ^ "." ^ key), v) :: !kvs
|
||||||
|
done
|
||||||
|
with End_of_file -> close_in ic);
|
||||||
|
!kvs
|
||||||
|
|
||||||
|
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
|
||||||
|
let name =
|
||||||
|
match get "name" with
|
||||||
|
| Some n when n <> "" && not (String.contains n '/') -> n
|
||||||
|
| Some _ ->
|
||||||
|
Printf.eprintf "woc: %s: `name` must be a bare file name\n" mf;
|
||||||
|
exit 2
|
||||||
|
| None ->
|
||||||
|
Printf.eprintf "woc: %s: `name` is required\n" mf;
|
||||||
|
exit 2
|
||||||
|
in
|
||||||
|
(* paths in the manifest are the PROJECT's, so they resolve against the
|
||||||
|
manifest's directory — `woc .` inside the project and `woc path/to/it`
|
||||||
|
from anywhere must build the same thing *)
|
||||||
|
let resolve rel = if Filename.is_relative rel then Filename.concat dir rel else rel in
|
||||||
|
let runtime = Option.map resolve (get "build.runtime") in
|
||||||
|
let target = resolve (match get "build.target" with Some t when t <> "" -> t | _ -> "target") in
|
||||||
|
(if not (Sys.file_exists target) then
|
||||||
|
try Sys.mkdir target 0o755
|
||||||
|
with Sys_error m ->
|
||||||
|
Printf.eprintf "woc: %s\n" m;
|
||||||
|
exit 2);
|
||||||
|
build_mode ~runtime dir (Filename.concat target name)
|
||||||
|
|
||||||
let () =
|
let () =
|
||||||
match Sys.argv with
|
match Sys.argv with
|
||||||
| [| _; "--dump-tokens"; path |] -> dump_tokens path
|
| [| _; "--dump-tokens"; path |] -> dump_tokens path
|
||||||
|
|
@ -569,7 +668,11 @@ let () =
|
||||||
| [| _; "build"; path; "-o"; out |] -> build_mode ~runtime:None path out
|
| [| _; "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; "-o"; out; "--runtime"; rt |] -> build_mode ~runtime:(Some rt) path out
|
||||||
| [| _; "build"; path; "--runtime"; rt; "-o"; out |] -> build_mode ~runtime:(Some rt) path out
|
| [| _; "build"; path; "--runtime"; rt; "-o"; out |] -> build_mode ~runtime:(Some rt) path out
|
||||||
| [| _; path |] -> check_only path
|
| [| _; path |] ->
|
||||||
|
if Sys.file_exists path && Sys.is_directory path
|
||||||
|
&& Sys.file_exists (Filename.concat path "wo.toml")
|
||||||
|
then manifest_build path
|
||||||
|
else check_only path
|
||||||
| _ ->
|
| _ ->
|
||||||
prerr_string usage_msg;
|
prerr_string usage_msg;
|
||||||
exit 2
|
exit 2
|
||||||
|
|
|
||||||
|
|
@ -9,3 +9,9 @@ description = "The Haxe log-watcher ported file-for-file: the systems-track samp
|
||||||
|
|
||||||
[runtime]
|
[runtime]
|
||||||
wo = ">= 0.1"
|
wo = ">= 0.1"
|
||||||
|
|
||||||
|
# `woc .` in this directory (or `woc docs/examples/log-watcher` from the
|
||||||
|
# repo root) builds target/log-watcher from this manifest. Paths resolve
|
||||||
|
# against this file's directory.
|
||||||
|
[build]
|
||||||
|
runtime = "../../../runtime/wovm"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue