From 00d0ed502d000063969047b051ceeed1aeebb7c4 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sun, 9 Aug 2026 03:56:37 +0900 Subject: [PATCH] ww: resolve root argv0 and retire stale help paths --- cmd/ww/main.c | 14 ++------ selfhost/cmd/ww/main.ww | 15 +++++---- test/tool/driver_test.ww | 72 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 18 deletions(-) diff --git a/cmd/ww/main.c b/cmd/ww/main.c index 51357e3b..f1a6f781 100644 --- a/cmd/ww/main.c +++ b/cmd/ww/main.c @@ -20,13 +20,12 @@ static const char *usage = " build [-S] [-w DIR] [-o FILE] [path] compile module; -S stops after package asm\n" " run [path] ... build then exec, passing extra args to the program\n" " test [-S -o STEM] [-w DIR] [options] [path] build/run tests; -S emits package asm\n" -" fmt reformat ww source\n" " version print version and exit\n" "\n" " path forms:\n" " foo.ww literal file\n" -" foo search cwd, -I dirs, then $WW_LIB for foo.ww or foo/foo.ww\n" -" lib/foo directory: build lib/foo/foo.ww\n" +" foo search cwd, -I dirs, then the source library for foo.ww or foo/\n" +" lib/foo directory: build its package sources\n" " lib/... every package under lib, recursively (test only)\n" " . build the cwd's .ww\n"; @@ -1865,14 +1864,6 @@ do_test(int argc, char **argv) return exec_package_tests(argc, argv, src, NULL, src == NULL); } -static int -do_fmt(int argc, char **argv) -{ - (void)argc; (void)argv; - fputs("ww: fmt: not implemented in this phase\n", stderr); - return 1; -} - int main(int argc, char **argv) { @@ -1891,7 +1882,6 @@ main(int argc, char **argv) if (strcmp(cmd, "build") == 0) return do_build(argc - 2, argv + 2); if (strcmp(cmd, "run") == 0) return do_run(argc - 2, argv + 2); if (strcmp(cmd, "test") == 0) return do_test(argc - 2, argv + 2); - if (strcmp(cmd, "fmt") == 0) return do_fmt(argc - 2, argv + 2); fprintf(stderr, "ww: unknown subcommand: %s\n", cmd); fputs(usage, stderr); return 2; diff --git a/selfhost/cmd/ww/main.ww b/selfhost/cmd/ww/main.ww index c4ca6de3..c1400d50 100644 --- a/selfhost/cmd/ww/main.ww +++ b/selfhost/cmd/ww/main.ww @@ -96,17 +96,18 @@ fn cstrseal(dst: *u8, off: u64) void = { fn selfdirinto(dst: *u8, dstsz: u64, argv0: *u8) void = { let n: u64 = cstrlen(argv0); - let cut: u64 = 0u64; + let cut: u64 = n; let i: u64 = 0u64; for (i < n) { - if (argv0[i] == 47u8) { cut = i; }; // '/' + if (argv0[i] == 47u8) { cut = i; }; i += 1u64; }; - if (cut == 0u64) { - dst[0u64] = 46u8; // '.' + if (cut == n) { + dst[0u64] = 46u8; dst[1u64] = 0u8; return; }; + if (cut == 0u64) { cut = 1u64; }; if (cut + 1u64 >= dstsz) { cut = dstsz - 2u64; }; bytecpy(dst, argv0, cut); dst[cut] = 0u8; @@ -2000,7 +2001,7 @@ fn resolvemodule(selfdir: *u8, name: *u8, incs: *u8, isdir: *i32) *u8 = { }; fn writeusage(fd: i32) void = { - let s: str = "usage: ww [-V] [args...]\n -V print version and exit\n build [-S] [-w DIR] [-o FILE] [path] compile module; -S stops after package asm\n run [path] ... build then exec, passing extra args to the program\n test [-S -o STEM] [-w DIR] [options] [path] build/run tests; -S emits package asm\n version print version and exit\n\n path forms:\n foo.ww literal file\n foo search cwd, -I dirs, then $WW_LIB-equiv for foo.ww or foo/foo.ww\n lib/foo directory: build lib/foo/foo.ww\n lib/... every package under lib, recursively (test only)\n . build the cwd's .ww\n"; + let s: str = "usage: ww [-V] [args...]\n -V print version and exit\n build [-S] [-w DIR] [-o FILE] [path] compile module; -S stops after package asm\n run [path] ... build then exec, passing extra args to the program\n test [-S -o STEM] [-w DIR] [options] [path] build/run tests; -S emits package asm\n version print version and exit\n\n path forms:\n foo.ww literal file\n foo search cwd, -I dirs, then the source library for foo.ww or foo/\n lib/foo directory: build its package sources\n lib/... every package under lib, recursively (test only)\n . build the cwd's .ww\n"; os.write(fd, s.ptr, s.len: u64); }; @@ -2733,7 +2734,9 @@ export fn main(argc: i32, argv: **u8) i32 = { if (cstreqlit(cmd, "test")) { return dotest(selfdir.ptr, argv, argc, 2); }; - cerr("ww: unknown subcommand\n"); + cerr("ww: unknown subcommand: "); + os.write(2, cmd, cstrlen(cmd)); + cerr("\n"); writeusage(2); return 2; }; diff --git a/test/tool/driver_test.ww b/test/tool/driver_test.ww index c593dfa0..3669d80d 100644 --- a/test/tool/driver_test.ww +++ b/test/tool/driver_test.ww @@ -73,6 +73,28 @@ fn rundrv(root: str, name: str, drv: str, argv: []str, assert(out.termination == exec.termination.EXIT); }; +fn runrootargv(dir: str, root: str, name: str, drv: str, + out: *testenv.commandout) void = { + let av: []str = ["/ww", "build", "rootprobe"]; + let env: []str = ["PATH=/usr/bin:/bin", "LC_ALL=C"]; + let c: exec.command; + c.path = testenv.driver(drv); + c.argv = av; + c.env = env; + c.dir = dir; + c.stdoutpath = strings.concat(root, "/", name, ".stdout"); + c.stderrpath = strings.concat(root, "/", name, ".stderr"); + c.deadline = time.add(time.now(time.clock.monotonic), tmo()); + c.grace = (100i64 * (time.millisecond: i64)): time.duration; + let r: exec.result; + exec.run(&c, &r); + assert(r.errno == 0 && r.cleanuperrno == 0); + out.termination = r.termination; + out.code = r.code; + out.stdout = testenv.readfile(c.stdoutpath); + out.stderr = testenv.readfile(c.stderrpath); +}; + @test fn version() void = { let td: str = testenv.fresh(); let want: str = strings.concat("ww ", wwversion(), "\n"); @@ -92,6 +114,56 @@ fn rundrv(root: str, name: str, drv: str, argv: []str, testenv.clean(td); }; +@test fn help() void = { + let td: str = testenv.fresh(); + let tail: []str = ["-h"]; + let co: testenv.commandout; + let wo: testenv.commandout; + rundrv(td, "help_c", "ww", tail, &co); + rundrv(td, "help_w", "ww_ww", tail, &wo); + if (co.code != 0 || wo.code != 0 + || !testenv.same(co.stdout, wo.stdout) + || co.stderr.len != 0 || wo.stderr.len != 0) { + fail("help", "driver help is not byte-identical on stdout"); + }; + if (testenv.has(co.stdout, "foo/foo.ww") + || testenv.has(co.stdout, "fmt ")) { + fail("help", "driver help advertises a retired path or command"); + }; + let fmttail: []str = ["fmt"]; + rundrv(td, "fmt_c", "ww", fmttail, &co); + rundrv(td, "fmt_w", "ww_ww", fmttail, &wo); + if (co.code != 2 || wo.code != co.code + || !testenv.same(co.stdout, wo.stdout) + || !testenv.same(co.stderr, wo.stderr) + || !testenv.has(co.stderr, "unknown subcommand: fmt")) { + fail("help", "retired fmt command is not an identical unknown command"); + }; + testenv.clean(td); +}; + +@test fn rootargv0() void = { + let td: str = testenv.fresh(); + let lib: str = strings.concat(td, "/lib"); + let a: str = strings.concat(td, "/a"); + let wd: str = strings.concat(a, "/b"); + assert(os.mkdir(lib, 448i32) == 0); + assert(os.mkdir(a, 448i32) == 0); + assert(os.mkdir(wd, 448i32) == 0); + testenv.writefile(strings.concat(lib, "/rootprobe.ww"), + "package main;\nexport fn main() i32 = { return 0; };\n"); + let co: testenv.commandout; + let wo: testenv.commandout; + runrootargv(wd, td, "rootargv_c", "ww", &co); + runrootargv(wd, td, "rootargv_w", "ww_ww", &wo); + if (co.code == 0 || wo.code == 0 + || !testenv.has(co.stderr, "cannot find module") + || !testenv.has(wo.stderr, "cannot find module")) { + fail("rootargv0", "argv0 /ww did not resolve selfdir as /"); + }; + testenv.clean(td); +}; + // a3 == "" means a two-token argv tail; no row passes a literal "". @test fn flagargs() void = { let a1: []str = ["build", "build", "build", "build", "build",