wcc/ww: ww build --sep separate-compilation driver (M3-tail c3, #46)
build_one_sep (both stages + ww/main.combined.ww regen): discover_deps (transitive directory-package set, dotted-path identity), tri-color reverse-topo (cycle bails; loud reject is commit 4), the transitive producer loop (one w6c -c -I pass per package, dep-first, each both consumer and producer of its .wwi), and a flat w6l of the .o set. combined.ww stays the DEFAULT live path; --sep is additive. Every dep is tagged by its full dotted import path on prepend (//ww:module <path>), so the definer's qualified symbol (#53) == the consumer's qualified reference (#40) and the sep .o set links. The prepend is the TRANSITIVE closure (lead-ratified, superseding rob-c3-spec §1.3 direct-deps): a dep's interface can name a transitive dep's type (os exposes time.instant), so the consuming unit needs the whole closure for resolution — direct-deps-only does not type-check. Consistent with the current flat-unit transitive-namespace model (the visibility tighten is #45, post-M4). Gate 989_sepbuild_run drives ww + ww_ww --sep on the real chain root->os->{rt,time}: build+run (exit 7) + cs==ww per-pkg .s/.wwi/.unit + final binary + transitive-topo discovery + keystone bodies==.wwi (os,root) through the real driver. Cold scratch; -o-redirected.
This commit is contained in:
470
cmd/ww/main.c
470
cmd/ww/main.c
@@ -570,6 +570,451 @@ build_one(const char *src, int entry_is_dir, const char *out,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ====================================================================
|
||||
* ww build --sep — M3-tail separate-compilation driver (task #46/c3).
|
||||
* ====================================================================
|
||||
* The `--sep` path materializes each imported package's `.wwi`
|
||||
* interface and compiles every package on its own (`w6c -c`), then
|
||||
* flat-links the `.o` set. combined.ww stays the DEFAULT live path;
|
||||
* --sep is purely additive (no existing invocation reaches it).
|
||||
*
|
||||
* Each w6c pass is BOTH consumer (reads dep `.wwi` as import scope) AND
|
||||
* producer (writes this package's `.wwi` for its importers via -I), so
|
||||
* a package's interface is materialized as a side effect of compiling
|
||||
* it. Reverse-topo order guarantees a package's deps' `.wwi` exist
|
||||
* before it compiles.
|
||||
*
|
||||
* The load-bearing rule (ken #56, rob-resolved): every dep is tagged by
|
||||
* its FULL DOTTED import path on prepend (`//ww:module <path>`), so the
|
||||
* definer's qualified symbol (#53) equals the consumer's qualified
|
||||
* reference (#40) and the sep `.o`s link. A dep is NEVER bare-embedded.
|
||||
*
|
||||
* The prepend is the TRANSITIVE closure of a package's deps (lead-
|
||||
* ratified, superseding rob-c3-spec §1.3 "direct deps"): a dep's public
|
||||
* interface can name a transitive dep's type (os exposes time.instant),
|
||||
* so the consuming unit needs the whole closure for name RESOLUTION —
|
||||
* direct-deps-only does not type-check. This mirrors harec reading the
|
||||
* transitive `.td` closure and is consistent with the current flat-unit
|
||||
* transitive-namespace model (the visibility tighten is task #45,
|
||||
* deferred post-M4).
|
||||
*/
|
||||
#define SEP_MAXPKG 256
|
||||
|
||||
struct seppkg {
|
||||
char path[256]; /* dotted import path; "" == root/primary */
|
||||
char entry[1024]; /* resolved package dir (or file, for a file root) */
|
||||
int is_dir;
|
||||
int deps[SEP_MAXPKG]; /* direct-dep indices into sepgraph.pkg */
|
||||
int ndeps;
|
||||
int color; /* tri-color DFS: 0 white, 1 gray, 2 black */
|
||||
};
|
||||
|
||||
struct sepgraph {
|
||||
struct seppkg pkg[SEP_MAXPKG];
|
||||
int n;
|
||||
};
|
||||
|
||||
/* Find a package by dotted path, or add it. Returns its index, -1 full. */
|
||||
static int
|
||||
sep_find_or_add(struct sepgraph *g, const char *path, const char *entry,
|
||||
int is_dir)
|
||||
{
|
||||
for (int i = 0; i < g->n; i++)
|
||||
if (strcmp(g->pkg[i].path, path) == 0) return i;
|
||||
if (g->n >= SEP_MAXPKG) {
|
||||
fprintf(stderr, "ww --sep: too many packages (limit %d)\n",
|
||||
SEP_MAXPKG);
|
||||
return -1;
|
||||
}
|
||||
struct seppkg *p = &g->pkg[g->n];
|
||||
snprintf(p->path, sizeof p->path, "%s", path);
|
||||
snprintf(p->entry, sizeof p->entry, "%s", entry);
|
||||
p->is_dir = is_dir;
|
||||
p->ndeps = 0;
|
||||
p->color = 0;
|
||||
return g->n++;
|
||||
}
|
||||
|
||||
/* Sanitize a package's dotted path into a scratch-file basename. Dots
|
||||
* stay (legal in filenames); the root's empty path becomes "__root". */
|
||||
static void
|
||||
sep_fname(const struct sepgraph *g, int pi, const char *scratch,
|
||||
const char *suffix, char *out, size_t outsz)
|
||||
{
|
||||
const char *base = g->pkg[pi].path[0] ? g->pkg[pi].path : "__root";
|
||||
snprintf(out, outsz, "%s/%s%s", scratch, base, suffix);
|
||||
}
|
||||
|
||||
/* Scan one source file for top-level `import IDENT;`, resolving each. A
|
||||
* DIRECTORY import is a package boundary: add it as a direct dep of pkg
|
||||
* `pi`. A FILE import is an intra-package split — fold its imports into
|
||||
* `pi` (its bytes join pi's body at emit time). Mirrors expand's scan
|
||||
* but collects package PATHS instead of concatenating bytes (§1.1). */
|
||||
static int
|
||||
sep_scan_file(struct sepgraph *g, int pi, const char *file,
|
||||
const char *searchpath, struct ImportSet *filevisit)
|
||||
{
|
||||
if (import_seen(filevisit, file)) return 0;
|
||||
import_add(filevisit, file);
|
||||
FILE *in = fopen(file, "rb");
|
||||
if (in == NULL) {
|
||||
fprintf(stderr, "ww --sep: cannot read %s\n", file);
|
||||
return -1;
|
||||
}
|
||||
char line[2048];
|
||||
int rc = 0;
|
||||
while (fgets(line, sizeof line, in)) {
|
||||
const char *p = line;
|
||||
while (*p == ' ' || *p == '\t') p++;
|
||||
if (strncmp(p, "import ", 7) != 0 && strncmp(p, "import\t", 7) != 0)
|
||||
continue;
|
||||
p += 7;
|
||||
while (*p == ' ' || *p == '\t') p++;
|
||||
char name[256] = {0};
|
||||
int j = 0;
|
||||
while ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z')
|
||||
|| *p == '_' || *p == '.' || (*p >= '0' && *p <= '9'))
|
||||
if (j + 1 < (int)sizeof name) name[j++] = *p++;
|
||||
if (j == 0) continue;
|
||||
char path_form[256];
|
||||
import_path_form(name, path_form, sizeof path_form);
|
||||
char ipath[1024];
|
||||
int is_dir = 0;
|
||||
/* a locate-miss is an inline-satisfied (single-file multi-
|
||||
* package) import; --sep targets directory packages, so the
|
||||
* combined path owns that case. Skip, mirroring expand. */
|
||||
if (!locate_import(searchpath, path_form, ipath, sizeof ipath,
|
||||
&is_dir))
|
||||
continue;
|
||||
if (is_dir) {
|
||||
int di = sep_find_or_add(g, name, ipath, 1);
|
||||
if (di < 0) { rc = -1; break; }
|
||||
int seen = 0;
|
||||
for (int k = 0; k < g->pkg[pi].ndeps; k++)
|
||||
if (g->pkg[pi].deps[k] == di) { seen = 1; break; }
|
||||
if (!seen) {
|
||||
if (g->pkg[pi].ndeps >= SEP_MAXPKG) { rc = -1; break; }
|
||||
g->pkg[pi].deps[g->pkg[pi].ndeps++] = di;
|
||||
}
|
||||
} else if (sep_scan_file(g, pi, ipath, searchpath, filevisit) < 0) {
|
||||
rc = -1; break;
|
||||
}
|
||||
}
|
||||
fclose(in);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Discover pkg pi's direct deps + recurse. Enumerate the package's own
|
||||
* source files (dir → *.ww less *test.ww; file → the file) and scan
|
||||
* each. `color` doubles as a scanned-marker here (2 == scanned); it is
|
||||
* reset to white before the topo pass. */
|
||||
static int
|
||||
sep_scan_pkg(struct sepgraph *g, int pi, const char *searchpath)
|
||||
{
|
||||
if (g->pkg[pi].color == 2) return 0;
|
||||
g->pkg[pi].color = 2;
|
||||
struct ImportSet fv = {0};
|
||||
int rc = 0;
|
||||
if (g->pkg[pi].is_dir) {
|
||||
char **files = NULL;
|
||||
int n = enumerate_dir_ww(g->pkg[pi].entry, &files);
|
||||
for (int i = 0; i < n && rc == 0; i++) {
|
||||
char fp[1024];
|
||||
snprintf(fp, sizeof fp, "%s/%s", g->pkg[pi].entry, files[i]);
|
||||
rc = sep_scan_file(g, pi, fp, searchpath, &fv);
|
||||
}
|
||||
for (int i = 0; i < n; i++) free(files[i]);
|
||||
free(files);
|
||||
} else {
|
||||
rc = sep_scan_file(g, pi, g->pkg[pi].entry, searchpath, &fv);
|
||||
}
|
||||
for (int i = 0; i < fv.n; i++) free(fv.paths[i]);
|
||||
free(fv.paths);
|
||||
if (rc < 0) return rc;
|
||||
/* recurse into freshly-added deps (sep_find_or_add may have grown
|
||||
* g->n during the scan; iterate by index). */
|
||||
for (int k = 0; k < g->pkg[pi].ndeps; k++)
|
||||
if (sep_scan_pkg(g, g->pkg[pi].deps[k], searchpath) < 0)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* DFS post-order over the dep DAG → reverse-topo (deps before importer),
|
||||
* cite Hare gather (deps.ha:123). Tri-color: a back-edge BAILS rather
|
||||
* than spinning. Commit 3 assumes acyclic; the LOUD cycle reject is
|
||||
* commit 4 (this just must not infinite-loop). */
|
||||
static int
|
||||
sep_topo_visit(struct sepgraph *g, int pi, int *order, int *no)
|
||||
{
|
||||
if (g->pkg[pi].color == 2) return 0;
|
||||
if (g->pkg[pi].color == 1) {
|
||||
fprintf(stderr,
|
||||
"ww --sep: import cycle (loud reject lands commit 4)\n");
|
||||
return -1;
|
||||
}
|
||||
g->pkg[pi].color = 1;
|
||||
for (int k = 0; k < g->pkg[pi].ndeps; k++)
|
||||
if (sep_topo_visit(g, g->pkg[pi].deps[k], order, no) < 0)
|
||||
return -1;
|
||||
g->pkg[pi].color = 2;
|
||||
order[(*no)++] = pi;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Mark pi's transitive deps (excluding pi) in inset[]. */
|
||||
static void
|
||||
sep_mark_deps(struct sepgraph *g, int pi, char *inset)
|
||||
{
|
||||
for (int k = 0; k < g->pkg[pi].ndeps; k++) {
|
||||
int di = g->pkg[pi].deps[k];
|
||||
if (!inset[di]) { inset[di] = 1; sep_mark_deps(g, di, inset); }
|
||||
}
|
||||
}
|
||||
|
||||
/* Emit one of pi's own source files into the sep-unit under the
|
||||
* //ww:module-reset primary boundary (so -c emits its decls, imported
|
||||
* ==0). DIRECTORY imports are skipped (provided as `.wwi` ahead of the
|
||||
* body); FILE imports fold in (intra-package split). */
|
||||
static void
|
||||
sep_emit_body(FILE *out, const char *path, struct ImportSet *visited,
|
||||
const char *searchpath)
|
||||
{
|
||||
if (import_seen(visited, path)) return;
|
||||
import_add(visited, path);
|
||||
FILE *in = fopen(path, "rb");
|
||||
if (in == NULL) {
|
||||
fprintf(stderr, "ww --sep: cannot read %s\n", path);
|
||||
return;
|
||||
}
|
||||
char line[2048];
|
||||
while (fgets(line, sizeof line, in)) {
|
||||
const char *p = line;
|
||||
while (*p == ' ' || *p == '\t') p++;
|
||||
if (strncmp(p, "import ", 7) != 0 && strncmp(p, "import\t", 7) != 0)
|
||||
continue;
|
||||
p += 7;
|
||||
while (*p == ' ' || *p == '\t') p++;
|
||||
char name[256] = {0};
|
||||
int j = 0;
|
||||
while ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z')
|
||||
|| *p == '_' || *p == '.' || (*p >= '0' && *p <= '9'))
|
||||
if (j + 1 < (int)sizeof name) name[j++] = *p++;
|
||||
if (j == 0) continue;
|
||||
char path_form[256];
|
||||
import_path_form(name, path_form, sizeof path_form);
|
||||
char ipath[1024];
|
||||
int is_dir = 0;
|
||||
if (!locate_import(searchpath, path_form, ipath, sizeof ipath,
|
||||
&is_dir))
|
||||
continue;
|
||||
if (!is_dir)
|
||||
sep_emit_body(out, ipath, visited, searchpath);
|
||||
}
|
||||
fputs("//ww:module-reset\n", out);
|
||||
rewind(in);
|
||||
int ch;
|
||||
while ((ch = fgetc(in)) != EOF) fputc(ch, out);
|
||||
fputc('\n', out);
|
||||
fclose(in);
|
||||
}
|
||||
|
||||
static void
|
||||
sep_emit_dir_body(FILE *out, const char *dir, struct ImportSet *visited,
|
||||
const char *searchpath)
|
||||
{
|
||||
char **files = NULL;
|
||||
int n = enumerate_dir_ww(dir, &files);
|
||||
for (int i = 0; i < n; i++) {
|
||||
char fp[1024];
|
||||
snprintf(fp, sizeof fp, "%s/%s", dir, files[i]);
|
||||
sep_emit_body(out, fp, visited, searchpath);
|
||||
free(files[i]);
|
||||
}
|
||||
free(files);
|
||||
}
|
||||
|
||||
/* Compose pi's sep-unit at `unitf`: the transitive-closure `.wwi`s
|
||||
* (reverse-topo order, each tagged by its dotted path), then pi's own
|
||||
* body under //ww:module-reset. */
|
||||
static int
|
||||
sep_compose_unit(struct sepgraph *g, int pi, const char *scratch,
|
||||
const int *order, int norder, const char *searchpath, const char *unitf)
|
||||
{
|
||||
FILE *u = fopen(unitf, "wb");
|
||||
if (u == NULL) {
|
||||
fprintf(stderr, "ww --sep: cannot open %s\n", unitf);
|
||||
return -1;
|
||||
}
|
||||
char inset[SEP_MAXPKG] = {0};
|
||||
sep_mark_deps(g, pi, inset);
|
||||
for (int oi = 0; oi < norder; oi++) {
|
||||
int dj = order[oi];
|
||||
if (dj == pi || !inset[dj]) continue;
|
||||
char wwi[1024];
|
||||
sep_fname(g, dj, scratch, ".wwi", wwi, sizeof wwi);
|
||||
FILE *wf = fopen(wwi, "rb");
|
||||
if (wf == NULL) {
|
||||
fprintf(stderr, "ww --sep: missing %s\n", wwi);
|
||||
fclose(u);
|
||||
return -1;
|
||||
}
|
||||
fprintf(u, "//ww:module %s\n", g->pkg[dj].path);
|
||||
int ch;
|
||||
while ((ch = fgetc(wf)) != EOF) fputc(ch, u);
|
||||
fputc('\n', u);
|
||||
fclose(wf);
|
||||
}
|
||||
struct ImportSet bodyvisit = {0};
|
||||
if (g->pkg[pi].is_dir)
|
||||
sep_emit_dir_body(u, g->pkg[pi].entry, &bodyvisit, searchpath);
|
||||
else
|
||||
sep_emit_body(u, g->pkg[pi].entry, &bodyvisit, searchpath);
|
||||
for (int i = 0; i < bodyvisit.n; i++) free(bodyvisit.paths[i]);
|
||||
free(bodyvisit.paths);
|
||||
fclose(u);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* build_one_sep — the --sep orchestration: discover_deps, reverse_topo,
|
||||
* the transitive producer loop (one `w6c -c -I` per package, dep-first),
|
||||
* then a flat `w6l` of the `.o` set (per-pkg `.a` + multi-archive link
|
||||
* is commit 4). Side files land in a cold `<stem>.sepwork` scratch dir
|
||||
* (the structured cache is commit 5). */
|
||||
static int
|
||||
build_one_sep(const char *src, int entry_is_dir, const char *out,
|
||||
const char *objstem, const char *extra_includes, const char *extra_libs,
|
||||
const char *extra_libdirs)
|
||||
{
|
||||
const char *c6 = toolpath("WW_W6C", "w6c");
|
||||
const char *a6 = toolpath("WW_W6A", "w6a");
|
||||
const char *l6 = toolpath("WW_W6L", "w6l");
|
||||
const char *libdir = getenv("WW_LIB");
|
||||
if (libdir == NULL || libdir[0] == 0) {
|
||||
static char libbuf[1024];
|
||||
snprintf(libbuf, sizeof libbuf, "%s/../lib", self_dir);
|
||||
libdir = libbuf;
|
||||
}
|
||||
const char *srcdir = getenv("WW_SRCLIB");
|
||||
static char srcbuf[1024];
|
||||
if (srcdir == NULL || srcdir[0] == 0) {
|
||||
snprintf(srcbuf, sizeof srcbuf, "%s/../../lib", self_dir);
|
||||
if (access(srcbuf, 0) == 0) srcdir = srcbuf;
|
||||
else if (access("lib", 0) == 0) srcdir = "lib";
|
||||
else srcdir = libdir;
|
||||
}
|
||||
/* search path: source-dir, then -I dirs, then srcdir (mirrors
|
||||
* build_one). */
|
||||
char srcd[1024];
|
||||
if (entry_is_dir) {
|
||||
snprintf(srcd, sizeof srcd, "%s", src);
|
||||
size_t n = strlen(srcd);
|
||||
while (n > 1 && srcd[n-1] == '/') srcd[--n] = '\0';
|
||||
} else {
|
||||
const char *slash = strrchr(src, '/');
|
||||
if (slash) {
|
||||
size_t n = (size_t)(slash - src);
|
||||
if (n >= sizeof srcd) n = sizeof srcd - 1;
|
||||
memcpy(srcd, src, n);
|
||||
srcd[n] = '\0';
|
||||
} else { srcd[0] = '.'; srcd[1] = '\0'; }
|
||||
}
|
||||
static char searchpath[4096];
|
||||
if (extra_includes && extra_includes[0])
|
||||
snprintf(searchpath, sizeof searchpath, "%s:%s:%s",
|
||||
srcd, extra_includes, srcdir);
|
||||
else
|
||||
snprintf(searchpath, sizeof searchpath, "%s:%s", srcd, srcdir);
|
||||
srcdir = searchpath;
|
||||
|
||||
char stem[1024];
|
||||
if (entry_is_dir) {
|
||||
const char *b = strrchr(srcd, '/');
|
||||
const char *base = b ? b + 1 : srcd;
|
||||
snprintf(stem, sizeof stem, "%s/%s", srcd, base);
|
||||
} else {
|
||||
snprintf(stem, sizeof stem, "%s", src);
|
||||
char *dot = strrchr(stem, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
}
|
||||
const char *ostem = (objstem && objstem[0]) ? objstem : stem;
|
||||
char scratch[1100];
|
||||
snprintf(scratch, sizeof scratch, "%s.sepwork", ostem);
|
||||
{ char m[1200]; snprintf(m, sizeof m, "rm -rf %s", scratch); run(m); }
|
||||
if (mkdir(scratch, 0755) != 0) {
|
||||
fprintf(stderr, "ww --sep: cannot create scratch %s\n", scratch);
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct sepgraph *g = calloc(1, sizeof *g);
|
||||
if (g == NULL) return 1;
|
||||
int root = sep_find_or_add(g, "", src, entry_is_dir);
|
||||
if (root < 0 || sep_scan_pkg(g, root, srcdir) < 0) { free(g); return 1; }
|
||||
for (int i = 0; i < g->n; i++) g->pkg[i].color = 0;
|
||||
int *order = calloc((size_t)g->n, sizeof *order);
|
||||
int norder = 0;
|
||||
if (order == NULL || sep_topo_visit(g, root, order, &norder) < 0) {
|
||||
free(order); free(g); return 1;
|
||||
}
|
||||
|
||||
/* producer loop — dep-first, one `w6c -c -I` pass per package. */
|
||||
for (int oi = 0; oi < norder; oi++) {
|
||||
int pi = order[oi];
|
||||
char unitf[1024], wwi[1024], asmf[1024], obj[1024], cmd[8192];
|
||||
sep_fname(g, pi, scratch, ".unit.ww", unitf, sizeof unitf);
|
||||
sep_fname(g, pi, scratch, ".wwi", wwi, sizeof wwi);
|
||||
sep_fname(g, pi, scratch, ".s", asmf, sizeof asmf);
|
||||
sep_fname(g, pi, scratch, ".o", obj, sizeof obj);
|
||||
if (sep_compose_unit(g, pi, scratch, order, norder, srcdir,
|
||||
unitf) < 0) { free(order); free(g); return 1; }
|
||||
snprintf(cmd, sizeof cmd, "%s -c -I %s -o %s %s",
|
||||
c6, wwi, asmf, unitf);
|
||||
if (run(cmd) != 0) {
|
||||
fprintf(stderr, "ww --sep: w6c failed for %s\n",
|
||||
g->pkg[pi].path[0] ? g->pkg[pi].path : "(root)");
|
||||
free(order); free(g); return 1;
|
||||
}
|
||||
snprintf(cmd, sizeof cmd, "%s -o %s %s", a6, obj, asmf);
|
||||
if (run(cmd) != 0) {
|
||||
fprintf(stderr, "ww --sep: w6a failed for %s\n",
|
||||
g->pkg[pi].path[0] ? g->pkg[pi].path : "(root)");
|
||||
free(order); free(g); return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* flat link: root.o first (order[norder-1]), deps after; runtime
|
||||
* archive selectively pulls only undefined runtime symbols (commit 4
|
||||
* adds per-pkg `.a` + multi-archive reverse-topo link). */
|
||||
char rtargs[2048] = {0};
|
||||
char rtpath[1024];
|
||||
snprintf(rtpath, sizeof rtpath, "%s/libwwrt.a", libdir);
|
||||
if (access(rtpath, 0) == 0) {
|
||||
snprintf(rtargs, sizeof rtargs, "%s", rtpath);
|
||||
} else {
|
||||
char a1[1024], a2[1024];
|
||||
snprintf(a1, sizeof a1, "%s/../obj/rt/start.o", self_dir);
|
||||
snprintf(a2, sizeof a2, "%s/../obj/rt/syscall.o", self_dir);
|
||||
snprintf(rtargs, sizeof rtargs, "%s %s", a1, a2);
|
||||
}
|
||||
char objs[8192] = {0};
|
||||
for (int oi = norder - 1; oi >= 0; oi--) {
|
||||
char obj[1024];
|
||||
sep_fname(g, order[oi], scratch, ".o", obj, sizeof obj);
|
||||
size_t n = strlen(objs);
|
||||
snprintf(objs + n, sizeof objs - n, "%s%s", n ? " " : "", obj);
|
||||
}
|
||||
const char *libargs = (extra_libs && extra_libs[0]) ? extra_libs : "";
|
||||
const char *libdirset = (extra_libdirs && extra_libdirs[0]) ? extra_libdirs : "";
|
||||
char cmd[16384];
|
||||
snprintf(cmd, sizeof cmd, "%s -o %s %s %s%s%s%s%s",
|
||||
l6, out, objs, rtargs,
|
||||
libdirset[0] ? " " : "", libdirset,
|
||||
libargs[0] ? " " : "", libargs);
|
||||
int rc = run(cmd);
|
||||
free(order); free(g);
|
||||
if (rc != 0) { fprintf(stderr, "ww --sep: w6l failed\n"); return 1; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
do_version(void)
|
||||
{
|
||||
@@ -694,12 +1139,25 @@ parse_build_flags(const char *cmd, int argc, char **argv,
|
||||
char *libdirs, size_t libdirsz,
|
||||
char *libs, size_t libsz,
|
||||
char *outpath, size_t outsz,
|
||||
const char **src_out)
|
||||
const char **src_out, int *want_sep)
|
||||
{
|
||||
*src_out = NULL;
|
||||
int i = 0;
|
||||
for (; i < argc; i++) {
|
||||
if (strncmp(argv[i], "-l", 2) == 0 && argv[i][2]) {
|
||||
if (strcmp(argv[i], "--sep") == 0) {
|
||||
/* M3-tail c3: separate-compilation path, build-only.
|
||||
* `run` has no sep-compile-then-run path (out of #46
|
||||
* commit-3 scope), so it LOUD-REJECTS rather than
|
||||
* silently swallowing a typed flag — both stages reject
|
||||
* identically (rule 10; want_sep==NULL marks the run
|
||||
* caller). */
|
||||
if (want_sep == NULL) {
|
||||
fprintf(stderr,
|
||||
"ww %s: --sep is only valid with build\n", cmd);
|
||||
return -1;
|
||||
}
|
||||
*want_sep = 1;
|
||||
} else if (strncmp(argv[i], "-l", 2) == 0 && argv[i][2]) {
|
||||
size_t n = strlen(libs);
|
||||
snprintf(libs + n, libsz - n,
|
||||
"%s%s", n ? " " : "", argv[i]);
|
||||
@@ -764,9 +1222,10 @@ do_build(int argc, char **argv)
|
||||
char libdirs[2048] = {0};
|
||||
char incs[2048] = {0};
|
||||
char outflag[1024] = {0};
|
||||
int want_sep = 0;
|
||||
if (parse_build_flags("build", argc, argv, incs, sizeof incs,
|
||||
libdirs, sizeof libdirs, libs, sizeof libs,
|
||||
outflag, sizeof outflag, &src) < 0)
|
||||
outflag, sizeof outflag, &src, &want_sep) < 0)
|
||||
return 2;
|
||||
if (src == NULL) src = "."; /* default: build cwd */
|
||||
char resolved[1024];
|
||||
@@ -792,6 +1251,9 @@ do_build(int argc, char **argv)
|
||||
} else {
|
||||
basename_no_ext(resolved, out, sizeof out);
|
||||
}
|
||||
if (want_sep)
|
||||
return build_one_sep(resolved, is_dir, out, objstem, incs,
|
||||
libs, libdirs);
|
||||
return build_one(resolved, is_dir, out, objstem, incs, libs, libdirs, 0);
|
||||
}
|
||||
|
||||
@@ -805,7 +1267,7 @@ do_run(int argc, char **argv)
|
||||
char outflag[1024] = {0}; /* -o accepted+ignored: run always uses the temp */
|
||||
int next = parse_build_flags("run", argc, argv, incs, sizeof incs,
|
||||
libdirs, sizeof libdirs, libs, sizeof libs,
|
||||
outflag, sizeof outflag, &src);
|
||||
outflag, sizeof outflag, &src, NULL);
|
||||
if (next < 0) return 2;
|
||||
if (src == NULL) src = ".";
|
||||
char resolved[1024];
|
||||
|
||||
Reference in New Issue
Block a user