cmd: build local package graphs
This commit is contained in:
8
Makefile
8
Makefile
@@ -256,11 +256,16 @@ $(BIN)/ww_ww: selfhost/cmd/ww/main.ww lib/os/exec/exec.ww \
|
|||||||
lib/os/os.ww lib/rt/malloc.ww \
|
lib/os/os.ww lib/rt/malloc.ww \
|
||||||
lib/time/time.ww lib/types/types.ww \
|
lib/time/time.ww lib/types/types.ww \
|
||||||
lib/strings/strings.ww lib/bytes/bytes.ww lib/encoding/utf8/utf8.ww \
|
lib/strings/strings.ww lib/bytes/bytes.ww lib/encoding/utf8/utf8.ww \
|
||||||
|
lib/ww/syntax/lex.ww lib/ww/syntax/tok.ww lib/ww/syntax/ast.ww \
|
||||||
|
lib/ww/syntax/parse.ww lib/ww/syntax/expr.ww \
|
||||||
|
lib/ww/syntax/stmt.ww lib/ww/syntax/decl.ww \
|
||||||
|
lib/ww/syntax/typ.ww lib/ww/syntax/sym.ww \
|
||||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||||
$(LIB)/libwwrt.a | $(BIN)
|
$(LIB)/libwwrt.a | $(BIN)
|
||||||
@mkdir -p $(WWBUILD)/ww_ww
|
@mkdir -p $(WWBUILD)/ww_ww
|
||||||
@$(CURDIR)/$(BIN)/ww build -w $(WWBUILD)/ww_ww \
|
@$(CURDIR)/$(BIN)/ww build -w $(WWBUILD)/ww_ww \
|
||||||
-o $(WWBUILD)/ww_ww/main \
|
-o $(WWBUILD)/ww_ww/main \
|
||||||
|
-I $(CURDIR)/lib/ww \
|
||||||
$(CURDIR)/selfhost/cmd/ww/main.ww
|
$(CURDIR)/selfhost/cmd/ww/main.ww
|
||||||
@mv $(WWBUILD)/ww_ww/main $@
|
@mv $(WWBUILD)/ww_ww/main $@
|
||||||
|
|
||||||
@@ -366,7 +371,8 @@ BYTEID_WW_TARGETS = $(BYTEID_WW_TESTS:%=wwtest/%)
|
|||||||
# test-compiler beside the surviving residual carriers.
|
# test-compiler beside the surviving residual carriers.
|
||||||
SEP_WW_TESTS = test/sep/sepbuild_test.ww test/sep/sepimport_test.ww \
|
SEP_WW_TESTS = test/sep/sepbuild_test.ww test/sep/sepimport_test.ww \
|
||||||
test/sep/seplink_test.ww test/sep/sepscratch_test.ww \
|
test/sep/seplink_test.ww test/sep/sepscratch_test.ww \
|
||||||
test/sep/septest_test.ww test/sep/m3sep_test.ww
|
test/sep/septest_test.ww test/sep/m3sep_test.ww \
|
||||||
|
test/sep/localbuild_test.ww
|
||||||
SEP_WW_TARGETS = $(SEP_WW_TESTS:%=wwtest/%)
|
SEP_WW_TARGETS = $(SEP_WW_TESTS:%=wwtest/%)
|
||||||
# Ww-native lib env/OS arranger observers: the env contracts of the
|
# Ww-native lib env/OS arranger observers: the env contracts of the
|
||||||
# lib/os and lib/dirs suites (getenv cohorts, XDG cohorts, the dirs
|
# lib/os and lib/dirs suites (getenv cohorts, XDG cohorts, the dirs
|
||||||
|
|||||||
606
cmd/ww/main.c
606
cmd/ww/main.c
@@ -4,6 +4,7 @@
|
|||||||
* Tool paths default to siblings of $0 (so a fresh build runs out of
|
* Tool paths default to siblings of $0 (so a fresh build runs out of
|
||||||
* out/bin/), and can be overridden with WW_W6C / WW_W6A / WW_W6L.
|
* out/bin/), and can be overridden with WW_W6C / WW_W6A / WW_W6L.
|
||||||
*/
|
*/
|
||||||
|
#define _XOPEN_SOURCE 700
|
||||||
#include "ww.h"
|
#include "ww.h"
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@@ -17,7 +18,7 @@
|
|||||||
static const char *usage =
|
static const char *usage =
|
||||||
"usage: ww [-V] <subcommand> [args...]\n"
|
"usage: ww [-V] <subcommand> [args...]\n"
|
||||||
" -V print version and exit\n"
|
" -V print version and exit\n"
|
||||||
" build [-S] [-w DIR] [-o FILE] [path] compile module; -S stops after package asm\n"
|
" build [-p] [-S] [-w DIR] [-I DIR] [-o FILE] [path] build a local package graph\n"
|
||||||
" run [path] ... build then exec, passing extra args to the program\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"
|
" test [-S -o STEM] [-w DIR] [options] [path] build/run tests; -S emits package asm\n"
|
||||||
" version print version and exit\n"
|
" version print version and exit\n"
|
||||||
@@ -26,6 +27,7 @@ static const char *usage =
|
|||||||
" foo.ww literal file\n"
|
" foo.ww literal file\n"
|
||||||
" foo search cwd, -I dirs, then the source library for foo.ww or foo/\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/foo directory: build its package sources\n"
|
||||||
|
" -p emits a non-main archive FILE + FILE.wwi\n"
|
||||||
" lib/... every package under lib, recursively (test only)\n"
|
" lib/... every package under lib, recursively (test only)\n"
|
||||||
" . build the cwd's <basename>.ww\n";
|
" . build the cwd's <basename>.ww\n";
|
||||||
|
|
||||||
@@ -279,6 +281,16 @@ enumerate_dir_ww(const char *dirpath, char ***out_files)
|
|||||||
snprintf(path, sizeof path, "%s/%s", dirpath, nm);
|
snprintf(path, sizeof path, "%s/%s", dirpath, nm);
|
||||||
if (nl >= 8 && strcmp(nm + nl - 8, "_test.ww") == 0)
|
if (nl >= 8 && strcmp(nm + nl - 8, "_test.ww") == 0)
|
||||||
continue;
|
continue;
|
||||||
|
struct stat st;
|
||||||
|
if (lstat(path, &st) != 0 || !S_ISREG(st.st_mode)) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"ww: %s: package source is not a regular file\n", path);
|
||||||
|
for (int i = 0; i < n; i++) free(arr[i]);
|
||||||
|
free(arr);
|
||||||
|
closedir(d);
|
||||||
|
*out_files = NULL;
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
if (file_has_line_test(path)) {
|
if (file_has_line_test(path)) {
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"ww: %s: @test declaration outside *_test.ww\n",
|
"ww: %s: @test declaration outside *_test.ww\n",
|
||||||
@@ -324,15 +336,16 @@ enumerate_dir_ww(const char *dirpath, char ***out_files)
|
|||||||
* interface can name a transitive dep's type (os exposes time.instant),
|
* interface can name a transitive dep's type (os exposes time.instant),
|
||||||
* so the consuming unit needs the whole closure for name RESOLUTION —
|
* so the consuming unit needs the whole closure for name RESOLUTION —
|
||||||
* direct-deps-only does not type-check. This mirrors harec reading the
|
* direct-deps-only does not type-check. This mirrors harec reading the
|
||||||
* transitive `.td` closure and is consistent with the current flat-unit
|
* transitive `.td` closure. The checker still scopes qualifier lookup to
|
||||||
* transitive-namespace model (the visibility tighten is task #45,
|
* each source package's direct N_USE declarations, so carrying those type
|
||||||
* deferred post-M4).
|
* facts does not make a transitive package source-visible.
|
||||||
*/
|
*/
|
||||||
#define SEP_MAXPKG 256
|
#define SEP_MAXPKG 256
|
||||||
|
|
||||||
struct seppkg {
|
struct seppkg {
|
||||||
char path[256]; /* dotted import path; "" == root/primary */
|
char path[256]; /* dotted import path; "" == root/primary */
|
||||||
char entry[1024]; /* resolved package dir (or file, for a file root) */
|
char entry[1024]; /* resolved package dir (or file, for a file root) */
|
||||||
|
char canon[1024]; /* canonical location; never package identity */
|
||||||
char name[256]; /* validated declared name; directory packages only */
|
char name[256]; /* validated declared name; directory packages only */
|
||||||
char **sources; /* owned, byte-sorted production paths; dirs only */
|
char **sources; /* owned, byte-sorted production paths; dirs only */
|
||||||
int nsources;
|
int nsources;
|
||||||
@@ -351,16 +364,54 @@ static int
|
|||||||
sep_find_or_add(struct sepgraph *g, const char *path, const char *entry,
|
sep_find_or_add(struct sepgraph *g, const char *path, const char *entry,
|
||||||
int is_dir)
|
int is_dir)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < g->n; i++)
|
if (strlen(path) >= sizeof g->pkg[0].path) {
|
||||||
if (strcmp(g->pkg[i].path, path) == 0) return i;
|
fprintf(stderr, "ww: package path is too long (limit %zu bytes)\n",
|
||||||
|
sizeof g->pkg[0].path - 1);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
char *canon = realpath(entry, NULL);
|
||||||
|
if (canon == NULL) {
|
||||||
|
fprintf(stderr, "ww: cannot canonicalize package %s\n", entry);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (strlen(canon) >= sizeof g->pkg[0].canon) {
|
||||||
|
fprintf(stderr, "ww: canonical package path is too long\n");
|
||||||
|
free(canon);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < g->n; i++) {
|
||||||
|
if (strcmp(g->pkg[i].path, path) == 0) {
|
||||||
|
if (strcmp(g->pkg[i].canon, canon) != 0) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"ww: package %s resolves to both %s and %s\n",
|
||||||
|
path[0] ? path : "(root)", g->pkg[i].entry,
|
||||||
|
entry);
|
||||||
|
free(canon);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
free(canon);
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
if (strcmp(g->pkg[i].canon, canon) == 0) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"ww: package directory %s has identities %s and %s\n",
|
||||||
|
entry, g->pkg[i].path[0] ? g->pkg[i].path : "(root)",
|
||||||
|
path[0] ? path : "(root)");
|
||||||
|
free(canon);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (g->n >= SEP_MAXPKG) {
|
if (g->n >= SEP_MAXPKG) {
|
||||||
fprintf(stderr, "ww: too many packages (limit %d)\n",
|
fprintf(stderr, "ww: too many packages (limit %d)\n",
|
||||||
SEP_MAXPKG);
|
SEP_MAXPKG);
|
||||||
|
free(canon);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
struct seppkg *p = &g->pkg[g->n];
|
struct seppkg *p = &g->pkg[g->n];
|
||||||
snprintf(p->path, sizeof p->path, "%s", path);
|
snprintf(p->path, sizeof p->path, "%s", path);
|
||||||
snprintf(p->entry, sizeof p->entry, "%s", entry);
|
snprintf(p->entry, sizeof p->entry, "%s", entry);
|
||||||
|
snprintf(p->canon, sizeof p->canon, "%s", canon);
|
||||||
|
free(canon);
|
||||||
p->is_dir = is_dir;
|
p->is_dir = is_dir;
|
||||||
p->name[0] = '\0';
|
p->name[0] = '\0';
|
||||||
p->sources = NULL;
|
p->sources = NULL;
|
||||||
@@ -393,106 +444,45 @@ sep_fname(const struct sepgraph *g, int pi, const char *scratch,
|
|||||||
snprintf(out, outsz, "%s/%s%s", scratch, base, suffix);
|
snprintf(out, outsz, "%s/%s%s", scratch, base, suffix);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* unit_has_package — does `path` declare `package <leaf>;` ANYWHERE?
|
|
||||||
* #16 ENFORCE-driver (rob A): distinguishes a genuinely-missing import
|
|
||||||
* from one satisfied by an INLINE package in the same unit. Unlike a
|
|
||||||
* first-package-decl scan, this checks every line — single-file
|
|
||||||
* multi-package fixtures carry several `package` decls. The
|
|
||||||
* comment-skip line scan + name match are uncapped. The
|
|
||||||
* wwstage twin unithaspackage must stay byte-identical (rule 10). */
|
|
||||||
static int
|
static int
|
||||||
unit_has_package(const char *path, const char *leaf)
|
sep_slurp(const char *path, char **out, u64 *len)
|
||||||
{
|
{
|
||||||
FILE *in = fopen(path, "rb");
|
FILE *f = fopen(path, "rb");
|
||||||
if (in == NULL) return 0;
|
if (f == NULL) return -1;
|
||||||
char line[2048];
|
if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return -1; }
|
||||||
int found = 0;
|
long n = ftell(f);
|
||||||
while (fgets(line, sizeof line, in)) {
|
if (n < 0 || fseek(f, 0, SEEK_SET) != 0) {
|
||||||
const char *p = line;
|
fclose(f);
|
||||||
while (*p == ' ' || *p == '\t') p++;
|
return -1;
|
||||||
if (p[0] == '/' && p[1] == '/') continue;
|
|
||||||
if (strncmp(p, "package ", 8) != 0
|
|
||||||
&& strncmp(p, "package\t", 8) != 0) continue;
|
|
||||||
p += 8;
|
|
||||||
while (*p == ' ' || *p == '\t') p++;
|
|
||||||
size_t i = 0;
|
|
||||||
while (leaf[i] != '\0' && leaf[i] == p[i]) i++;
|
|
||||||
if (leaf[i] == '\0') {
|
|
||||||
char c = p[i];
|
|
||||||
if (c == ';' || c == ' ' || c == '\t'
|
|
||||||
|| c == '\n' || c == '\0') { found = 1; break; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
fclose(in);
|
char *buf = malloc((size_t)n + 1);
|
||||||
return found;
|
if (buf == NULL) { fclose(f); return -1; }
|
||||||
}
|
if (fread(buf, 1, (size_t)n, f) != (size_t)n) {
|
||||||
|
free(buf);
|
||||||
static int
|
fclose(f);
|
||||||
sep_ident_start(int c)
|
return -1;
|
||||||
{
|
|
||||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_';
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
sep_ident_continue(int c)
|
|
||||||
{
|
|
||||||
return sep_ident_start(c) || (c >= '0' && c <= '9');
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Deliberately only the loader's small header grammar for the leading
|
|
||||||
* package clause, not a second compiler lexer. */
|
|
||||||
static int
|
|
||||||
sep_skip_space(const char *src, size_t n, size_t *off)
|
|
||||||
{
|
|
||||||
size_t i = *off;
|
|
||||||
for (;;) {
|
|
||||||
while (i < n && (src[i] == ' ' || src[i] == '\t'
|
|
||||||
|| src[i] == '\r' || src[i] == '\n'))
|
|
||||||
i++;
|
|
||||||
if (i + 1 < n && src[i] == '/' && src[i + 1] == '/') {
|
|
||||||
i += 2;
|
|
||||||
while (i < n && src[i] != '\n') i++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (i + 1 < n && src[i] == '/' && src[i + 1] == '*') {
|
|
||||||
i += 2;
|
|
||||||
while (i + 1 < n && !(src[i] == '*' && src[i + 1] == '/'))
|
|
||||||
i++;
|
|
||||||
if (i + 1 >= n) return -1;
|
|
||||||
i += 2;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
*off = i;
|
buf[n] = '\0';
|
||||||
|
if (fclose(f) != 0) {
|
||||||
|
free(buf);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
*out = buf;
|
||||||
|
*len = (u64)n;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
sep_package_clause(const char *src, size_t n, char *name, size_t namesz)
|
use_node_cmp(const void *a, const void *b)
|
||||||
{
|
{
|
||||||
static const char kw[] = "package";
|
const Node *x = *(Node *const *)a;
|
||||||
size_t i = 0;
|
const Node *y = *(Node *const *)b;
|
||||||
if (sep_skip_space(src, n, &i) < 0
|
const char *xp = x->usepath ? x->usepath : x->str;
|
||||||
|| i + sizeof kw - 1 >= n
|
const char *yp = y->usepath ? y->usepath : y->str;
|
||||||
|| memcmp(src + i, kw, sizeof kw - 1) != 0)
|
int r = strcmp(xp, yp);
|
||||||
return -1;
|
if (r != 0) return r;
|
||||||
i += sizeof kw - 1;
|
if (x->pos.line != y->pos.line) return x->pos.line - y->pos.line;
|
||||||
if (i >= n || (src[i] != ' ' && src[i] != '\t'
|
return x->pos.col - y->pos.col;
|
||||||
&& src[i] != '\r' && src[i] != '\n'))
|
|
||||||
return -1;
|
|
||||||
if (sep_skip_space(src, n, &i) < 0 || i >= n
|
|
||||||
|| !sep_ident_start((unsigned char)src[i]))
|
|
||||||
return -1;
|
|
||||||
size_t begin = i++;
|
|
||||||
while (i < n && sep_ident_continue((unsigned char)src[i])) i++;
|
|
||||||
size_t len = i - begin;
|
|
||||||
if (len + 1 > namesz || sep_skip_space(src, n, &i) < 0
|
|
||||||
|| i >= n || src[i] != ';')
|
|
||||||
return -1;
|
|
||||||
memcpy(name, src + begin, len);
|
|
||||||
name[len] = '\0';
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* A DIRECTORY import is a package boundary: add it as a direct dep of pkg
|
/* A DIRECTORY import is a package boundary: add it as a direct dep of pkg
|
||||||
@@ -506,33 +496,37 @@ sep_scan_file(struct sepgraph *g, int pi, const char *file,
|
|||||||
{
|
{
|
||||||
if (import_seen(filevisit, file)) return 0;
|
if (import_seen(filevisit, file)) return 0;
|
||||||
import_add(filevisit, file);
|
import_add(filevisit, file);
|
||||||
FILE *in = fopen(file, "rb");
|
char *buf;
|
||||||
if (in == NULL) {
|
u64 len;
|
||||||
|
if (sep_slurp(file, &buf, &len) < 0) {
|
||||||
fprintf(stderr, "ww: cannot read %s\n", file);
|
fprintf(stderr, "ww: cannot read %s\n", file);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (fseek(in, 0, SEEK_END) != 0) { fclose(in); return -1; }
|
Arena *a = newarena();
|
||||||
long flen = ftell(in);
|
Lex l;
|
||||||
if (flen < 0 || fseek(in, 0, SEEK_SET) != 0) {
|
Parser p;
|
||||||
fclose(in);
|
lexinit(&l, a, file, buf, len);
|
||||||
return -1;
|
parserinit(&p, a, &l);
|
||||||
}
|
Node *imports = parseimports(&p);
|
||||||
char *buf = malloc((size_t)flen + 1);
|
if (l.errs || p.errs) {
|
||||||
if (buf == NULL) { fclose(in); return -1; }
|
freearena(a);
|
||||||
if (fread(buf, 1, (size_t)flen, in) != (size_t)flen) {
|
free(buf);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (imports->module == NULL && owned_source) {
|
||||||
|
Pos pp = { file, 1, 1 };
|
||||||
|
errorf(pp, "invalid or missing package clause");
|
||||||
|
freearena(a);
|
||||||
free(buf);
|
free(buf);
|
||||||
fclose(in);
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
buf[flen] = '\0';
|
|
||||||
fclose(in);
|
|
||||||
|
|
||||||
if (owned_source) {
|
if (imports->module != NULL
|
||||||
char declared[256];
|
&& (owned_source || g->pkg[pi].name[0] == '\0')) {
|
||||||
if (sep_package_clause(buf, (size_t)flen, declared,
|
const char *declared = imports->module;
|
||||||
sizeof declared) < 0) {
|
if (strlen(declared) >= sizeof g->pkg[pi].name) {
|
||||||
fprintf(stderr, "ww: %s: invalid or missing package clause\n",
|
errorf(imports->pos, "package name is too long");
|
||||||
file);
|
freearena(a);
|
||||||
free(buf);
|
free(buf);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -540,60 +534,97 @@ sep_scan_file(struct sepgraph *g, int pi, const char *file,
|
|||||||
if (pkg->name[0] == '\0')
|
if (pkg->name[0] == '\0')
|
||||||
snprintf(pkg->name, sizeof pkg->name, "%s", declared);
|
snprintf(pkg->name, sizeof pkg->name, "%s", declared);
|
||||||
else if (strcmp(pkg->name, declared) != 0) {
|
else if (strcmp(pkg->name, declared) != 0) {
|
||||||
fprintf(stderr,
|
errorf(imports->pos,
|
||||||
"ww: %s: conflicting package names %s and %s\n",
|
"conflicting package names %s and %s in %s",
|
||||||
pkg->entry, pkg->name, declared);
|
pkg->name, declared, pkg->entry);
|
||||||
|
freearena(a);
|
||||||
free(buf);
|
free(buf);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (owned_source && g->pkg[pi].is_dir) {
|
||||||
|
for (Node *package = imports->body; package; package = package->next) {
|
||||||
|
if (strcmp(package->module, g->pkg[pi].name) != 0) {
|
||||||
|
errorf(package->pos,
|
||||||
|
"conflicting package names %s and %s in %s",
|
||||||
|
g->pkg[pi].name, package->module, g->pkg[pi].entry);
|
||||||
|
freearena(a);
|
||||||
|
free(buf);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int nuse = 0;
|
||||||
|
for (Node *u = imports->list; u; u = u->next)
|
||||||
|
if (u->kind == N_USE) nuse++;
|
||||||
|
Node **uses = nuse ? malloc((size_t)nuse * sizeof *uses) : NULL;
|
||||||
|
if (nuse && uses == NULL) {
|
||||||
|
freearena(a);
|
||||||
|
free(buf);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int ui = 0;
|
||||||
|
for (Node *u = imports->list; u; u = u->next)
|
||||||
|
if (u->kind == N_USE) uses[ui++] = u;
|
||||||
|
if (nuse > 1) qsort(uses, (size_t)nuse, sizeof *uses, use_node_cmp);
|
||||||
|
|
||||||
int rc = 0;
|
int rc = 0;
|
||||||
for (size_t off = 0; off < (size_t)flen && rc == 0;) {
|
const char *previous = NULL;
|
||||||
size_t end = off;
|
for (int i = 0; i < nuse && rc == 0; i++) {
|
||||||
while (end < (size_t)flen && buf[end] != '\n') end++;
|
Node *u = uses[i];
|
||||||
const char *p = buf + off;
|
const char *name = u->usepath ? u->usepath : u->str;
|
||||||
const char *lineend = buf + end;
|
if (previous && strcmp(previous, name) == 0) continue;
|
||||||
while (p < lineend && (*p == ' ' || *p == '\t')) p++;
|
previous = name;
|
||||||
if ((size_t)(lineend - p) < 7
|
if (strlen(name) >= sizeof g->pkg[0].path) {
|
||||||
|| (memcmp(p, "import ", 7) != 0
|
errorf(u->pos, "import path is too long (limit %zu bytes)",
|
||||||
&& memcmp(p, "import\t", 7) != 0)) {
|
sizeof g->pkg[0].path - 1);
|
||||||
off = end < (size_t)flen ? end + 1 : end;
|
rc = -1;
|
||||||
continue;
|
break;
|
||||||
}
|
}
|
||||||
p += 7;
|
char path_form[1024];
|
||||||
while (p < lineend && (*p == ' ' || *p == '\t')) p++;
|
|
||||||
char name[256] = {0};
|
|
||||||
int j = 0;
|
|
||||||
while (p < lineend && ((*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) {
|
|
||||||
off = end < (size_t)flen ? end + 1 : end;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
char path_form[256];
|
|
||||||
import_path_form(name, path_form, sizeof path_form);
|
import_path_form(name, path_form, sizeof path_form);
|
||||||
|
if (strlen(name) + 1 > sizeof path_form) {
|
||||||
|
errorf(u->pos, "import path is too long");
|
||||||
|
rc = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
char ipath[1024];
|
char ipath[1024];
|
||||||
int is_dir = 0;
|
int is_dir = 0;
|
||||||
/* #16 ENFORCE-driver: an unresolvable import is a hard error,
|
|
||||||
* not a silent skip — EXCEPT when the package is defined INLINE
|
|
||||||
* in the same unit (single-file multi-package; leaf = the last
|
|
||||||
* dotted component). E3-C1 (#87): the legacy amalgamator that
|
|
||||||
* used to own the genuine-missing case is gone, so the sep
|
|
||||||
* producer enforces it here (INV-2, by construction). */
|
|
||||||
if (!locate_import(searchpath, path_form, ipath, sizeof ipath,
|
if (!locate_import(searchpath, path_form, ipath, sizeof ipath,
|
||||||
&is_dir)) {
|
&is_dir)) {
|
||||||
const char *dot = strrchr(name, '.');
|
const char *dot = strrchr(name, '.');
|
||||||
const char *leaf = dot ? dot + 1 : name;
|
const char *leaf = dot ? dot + 1 : name;
|
||||||
if (unit_has_package(file, leaf))
|
int inline_package = 0;
|
||||||
goto next_line; /* inline-satisfied */
|
for (Node *package = imports->body; package;
|
||||||
fprintf(stderr, "ww: cannot find package %s\n", name);
|
package = package->next)
|
||||||
|
if (strcmp(package->module, leaf) == 0) {
|
||||||
|
inline_package = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (inline_package)
|
||||||
|
continue;
|
||||||
|
errorf(u->pos, "cannot find package %s", name);
|
||||||
rc = -1;
|
rc = -1;
|
||||||
goto next_line;
|
break;
|
||||||
}
|
}
|
||||||
if (is_dir) {
|
if (is_dir) {
|
||||||
|
char *canon = realpath(ipath, NULL);
|
||||||
|
if (canon == NULL) {
|
||||||
|
errorf(u->pos, "cannot canonicalize package '%s'", name);
|
||||||
|
rc = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
int self = strcmp(canon, g->pkg[pi].canon) == 0;
|
||||||
|
free(canon);
|
||||||
|
if (self) {
|
||||||
|
const char *owner = g->pkg[pi].path[0]
|
||||||
|
? g->pkg[pi].path : g->pkg[pi].name;
|
||||||
|
errorf(u->pos, "self-import: package '%s' cannot import itself",
|
||||||
|
owner[0] ? owner : "(root)");
|
||||||
|
rc = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
int di = sep_find_or_add(g, name, ipath, 1);
|
int di = sep_find_or_add(g, name, ipath, 1);
|
||||||
if (di < 0) { rc = -1; break; }
|
if (di < 0) { rc = -1; break; }
|
||||||
int seen = 0;
|
int seen = 0;
|
||||||
@@ -606,9 +637,9 @@ sep_scan_file(struct sepgraph *g, int pi, const char *file,
|
|||||||
} else if (sep_scan_file(g, pi, ipath, searchpath, filevisit, 0) < 0) {
|
} else if (sep_scan_file(g, pi, ipath, searchpath, filevisit, 0) < 0) {
|
||||||
rc = -1; break;
|
rc = -1; break;
|
||||||
}
|
}
|
||||||
next_line:
|
|
||||||
off = end < (size_t)flen ? end + 1 : end;
|
|
||||||
}
|
}
|
||||||
|
free(uses);
|
||||||
|
freearena(a);
|
||||||
free(buf);
|
free(buf);
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
@@ -659,6 +690,25 @@ sep_load_pkg(struct sepgraph *g, int pi, const char *searchpath)
|
|||||||
for (int i = 0; i < fv.n; i++) free(fv.paths[i]);
|
for (int i = 0; i < fv.n; i++) free(fv.paths[i]);
|
||||||
free(fv.paths);
|
free(fv.paths);
|
||||||
if (rc < 0) return rc;
|
if (rc < 0) return rc;
|
||||||
|
/* Give a non-main root its declared identity before recursively loading
|
||||||
|
* dependencies. A back-edge can then reuse node 0 and reach the normal
|
||||||
|
* cycle detector instead of looking like a location alias. Executable
|
||||||
|
* roots are reset to the bare-root identity after loading. */
|
||||||
|
if (pi == 0 && g->pkg[pi].path[0] == '\0'
|
||||||
|
&& g->pkg[pi].name[0] != '\0') {
|
||||||
|
size_t n = strlen(g->pkg[pi].name);
|
||||||
|
memcpy(g->pkg[pi].path, g->pkg[pi].name, n + 1);
|
||||||
|
}
|
||||||
|
for (int i = 1; i < g->pkg[pi].ndeps; i++) {
|
||||||
|
int v = g->pkg[pi].deps[i];
|
||||||
|
int j = i;
|
||||||
|
while (j > 0 && strcmp(g->pkg[g->pkg[pi].deps[j - 1]].path,
|
||||||
|
g->pkg[v].path) > 0) {
|
||||||
|
g->pkg[pi].deps[j] = g->pkg[pi].deps[j - 1];
|
||||||
|
j--;
|
||||||
|
}
|
||||||
|
g->pkg[pi].deps[j] = v;
|
||||||
|
}
|
||||||
/* recurse into freshly-added deps (sep_find_or_add may have grown
|
/* recurse into freshly-added deps (sep_find_or_add may have grown
|
||||||
* g->n during the scan; iterate by index). */
|
* g->n during the scan; iterate by index). */
|
||||||
for (int k = 0; k < g->pkg[pi].ndeps; k++)
|
for (int k = 0; k < g->pkg[pi].ndeps; k++)
|
||||||
@@ -712,53 +762,81 @@ sep_mark_deps(struct sepgraph *g, int pi, char *inset)
|
|||||||
* //ww:module-reset primary boundary (so -c emits its decls, imported
|
* //ww:module-reset primary boundary (so -c emits its decls, imported
|
||||||
* ==0). DIRECTORY imports are skipped (provided as `.wwi` ahead of the
|
* ==0). DIRECTORY imports are skipped (provided as `.wwi` ahead of the
|
||||||
* body); FILE imports fold in (intra-package split). */
|
* body); FILE imports fold in (intra-package split). */
|
||||||
static void
|
static int
|
||||||
sep_emit_body(FILE *out, const char *path, struct ImportSet *visited,
|
sep_emit_body(FILE *out, const char *path, struct ImportSet *visited,
|
||||||
const char *searchpath, const char *modpath)
|
const char *searchpath, const char *modpath)
|
||||||
{
|
{
|
||||||
if (import_seen(visited, path)) return;
|
if (import_seen(visited, path)) return 0;
|
||||||
import_add(visited, path);
|
import_add(visited, path);
|
||||||
FILE *in = fopen(path, "rb");
|
char *buf;
|
||||||
if (in == NULL) {
|
u64 len;
|
||||||
|
if (sep_slurp(path, &buf, &len) < 0) {
|
||||||
fprintf(stderr, "ww: cannot read %s\n", path);
|
fprintf(stderr, "ww: cannot read %s\n", path);
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
char line[2048];
|
Arena *a = newarena();
|
||||||
while (fgets(line, sizeof line, in)) {
|
Lex l;
|
||||||
const char *p = line;
|
Parser p;
|
||||||
while (*p == ' ' || *p == '\t') p++;
|
lexinit(&l, a, path, buf, len);
|
||||||
if (strncmp(p, "import ", 7) != 0 && strncmp(p, "import\t", 7) != 0)
|
parserinit(&p, a, &l);
|
||||||
continue;
|
Node *imports = parseimports(&p);
|
||||||
p += 7;
|
if (l.errs || p.errs) {
|
||||||
while (*p == ' ' || *p == '\t') p++;
|
freearena(a);
|
||||||
char name[256] = {0};
|
free(buf);
|
||||||
int j = 0;
|
return -1;
|
||||||
while ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z')
|
}
|
||||||
|| *p == '_' || *p == '.' || (*p >= '0' && *p <= '9'))
|
int nuse = 0;
|
||||||
if (j + 1 < (int)sizeof name) name[j++] = *p++;
|
for (Node *u = imports->list; u; u = u->next)
|
||||||
if (j == 0) continue;
|
if (u->kind == N_USE) nuse++;
|
||||||
char path_form[256];
|
Node **uses = nuse ? malloc((size_t)nuse * sizeof *uses) : NULL;
|
||||||
|
if (nuse && uses == NULL) {
|
||||||
|
freearena(a);
|
||||||
|
free(buf);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int ui = 0;
|
||||||
|
for (Node *u = imports->list; u; u = u->next)
|
||||||
|
if (u->kind == N_USE) uses[ui++] = u;
|
||||||
|
if (nuse > 1) qsort(uses, (size_t)nuse, sizeof *uses, use_node_cmp);
|
||||||
|
for (int i = 0; i < nuse; i++) {
|
||||||
|
Node *u = uses[i];
|
||||||
|
const char *name = u->usepath ? u->usepath : u->str;
|
||||||
|
char path_form[1024];
|
||||||
import_path_form(name, path_form, sizeof path_form);
|
import_path_form(name, path_form, sizeof path_form);
|
||||||
char ipath[1024];
|
char ipath[1024];
|
||||||
int is_dir = 0;
|
int is_dir = 0;
|
||||||
if (!locate_import(searchpath, path_form, ipath, sizeof ipath,
|
if (!locate_import(searchpath, path_form, ipath, sizeof ipath,
|
||||||
&is_dir))
|
&is_dir))
|
||||||
continue;
|
continue;
|
||||||
if (!is_dir)
|
if (!is_dir && sep_emit_body(out, ipath, visited, searchpath,
|
||||||
sep_emit_body(out, ipath, visited, searchpath, modpath);
|
modpath) < 0) {
|
||||||
|
free(uses);
|
||||||
|
freearena(a);
|
||||||
|
free(buf);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/* #57: tag the primary body by its full dotted import path so the
|
/* #57: tag the primary body by its full dotted import path so the
|
||||||
* definer mangles == the importer reference; a root build (path "")
|
* definer mangles == the importer reference; a root build (path "")
|
||||||
* stays a bare reset (keeps bare main). */
|
* stays a bare reset (keeps bare main). */
|
||||||
if (modpath != NULL && modpath[0] != '\0')
|
int bad = 0;
|
||||||
fprintf(out, "//ww:module-reset %s\n", modpath);
|
if (modpath != NULL && modpath[0] != '\0') {
|
||||||
else
|
if (fprintf(out, "//ww:module-reset %s\n", modpath) < 0)
|
||||||
fputs("//ww:module-reset\n", out);
|
bad = 1;
|
||||||
rewind(in);
|
} else if (fputs("//ww:module-reset\n", out) == EOF) {
|
||||||
int ch;
|
bad = 1;
|
||||||
while ((ch = fgetc(in)) != EOF) fputc(ch, out);
|
}
|
||||||
fputc('\n', out);
|
if (fwrite(buf, 1, (size_t)len, out) != (size_t)len
|
||||||
fclose(in);
|
|| fputc('\n', out) == EOF)
|
||||||
|
bad = 1;
|
||||||
|
free(uses);
|
||||||
|
freearena(a);
|
||||||
|
free(buf);
|
||||||
|
if (bad) {
|
||||||
|
fprintf(stderr, "ww: cannot write package unit\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Compose pi's sep-unit at `unitf`: the transitive-closure `.wwi`s
|
/* Compose pi's sep-unit at `unitf`: the transitive-closure `.wwi`s
|
||||||
@@ -787,25 +865,35 @@ sep_compose_unit(struct sepgraph *g, int pi, const char *scratch,
|
|||||||
fclose(u);
|
fclose(u);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
fprintf(u, "//ww:module %s\n", g->pkg[dj].path);
|
int bad = fprintf(u, "//ww:module %s\n", g->pkg[dj].path) < 0;
|
||||||
int ch;
|
int ch;
|
||||||
while ((ch = fgetc(wf)) != EOF) fputc(ch, u);
|
while (!bad && (ch = fgetc(wf)) != EOF)
|
||||||
fputc('\n', u);
|
if (fputc(ch, u) == EOF) bad = 1;
|
||||||
fclose(wf);
|
if (ferror(wf) || fputc('\n', u) == EOF) bad = 1;
|
||||||
|
if (fclose(wf) != 0) bad = 1;
|
||||||
|
if (bad) {
|
||||||
|
fprintf(stderr, "ww: cannot compose package unit %s\n", unitf);
|
||||||
|
fclose(u);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
struct ImportSet bodyvisit = {0};
|
struct ImportSet bodyvisit = {0};
|
||||||
|
int bodyrc = 0;
|
||||||
if (g->pkg[pi].is_dir) {
|
if (g->pkg[pi].is_dir) {
|
||||||
for (int i = 0; i < g->pkg[pi].nsources; i++)
|
for (int i = 0; i < g->pkg[pi].nsources && bodyrc == 0; i++)
|
||||||
sep_emit_body(u, g->pkg[pi].sources[i], &bodyvisit,
|
bodyrc = sep_emit_body(u, g->pkg[pi].sources[i], &bodyvisit,
|
||||||
searchpath, g->pkg[pi].path);
|
searchpath, g->pkg[pi].path);
|
||||||
} else {
|
} else {
|
||||||
sep_emit_body(u, g->pkg[pi].entry, &bodyvisit, searchpath,
|
bodyrc = sep_emit_body(u, g->pkg[pi].entry, &bodyvisit, searchpath,
|
||||||
g->pkg[pi].path);
|
g->pkg[pi].path);
|
||||||
}
|
}
|
||||||
for (int i = 0; i < bodyvisit.n; i++) free(bodyvisit.paths[i]);
|
for (int i = 0; i < bodyvisit.n; i++) free(bodyvisit.paths[i]);
|
||||||
free(bodyvisit.paths);
|
free(bodyvisit.paths);
|
||||||
fclose(u);
|
if (fclose(u) != 0) {
|
||||||
return 0;
|
fprintf(stderr, "ww: cannot close package unit %s\n", unitf);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return bodyrc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* archive_o — write a deterministic single-member SysV ar archive at
|
/* archive_o — write a deterministic single-member SysV ar archive at
|
||||||
@@ -824,16 +912,18 @@ archive_o(const char *objpath, const char *apath)
|
|||||||
fprintf(stderr, "ww: cannot read %s\n", objpath);
|
fprintf(stderr, "ww: cannot read %s\n", objpath);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
fseek(in, 0, SEEK_END);
|
if (fseek(in, 0, SEEK_END) != 0) { fclose(in); return -1; }
|
||||||
long n = ftell(in);
|
long n = ftell(in);
|
||||||
fseek(in, 0, SEEK_SET);
|
if (n < 0 || fseek(in, 0, SEEK_SET) != 0) {
|
||||||
if (n < 0) { fclose(in); return -1; }
|
fclose(in);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
unsigned char *buf = malloc((size_t)n);
|
unsigned char *buf = malloc((size_t)n);
|
||||||
if (buf == NULL) { fclose(in); return -1; }
|
if (buf == NULL) { fclose(in); return -1; }
|
||||||
if (fread(buf, 1, (size_t)n, in) != (size_t)n) {
|
if (fread(buf, 1, (size_t)n, in) != (size_t)n) {
|
||||||
free(buf); fclose(in); return -1;
|
free(buf); fclose(in); return -1;
|
||||||
}
|
}
|
||||||
fclose(in);
|
if (fclose(in) != 0) { free(buf); return -1; }
|
||||||
|
|
||||||
FILE *out = fopen(apath, "wb");
|
FILE *out = fopen(apath, "wb");
|
||||||
if (out == NULL) {
|
if (out == NULL) {
|
||||||
@@ -841,7 +931,7 @@ archive_o(const char *objpath, const char *apath)
|
|||||||
free(buf);
|
free(buf);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
fwrite("!<arch>\n", 1, 8, out);
|
int bad = fwrite("!<arch>\n", 1, 8, out) != 8;
|
||||||
/* ar(5) fixes each member header at 60 bytes; the offsets below
|
/* ar(5) fixes each member header at 60 bytes; the offsets below
|
||||||
* address fields in that serialized header. */
|
* address fields in that serialized header. */
|
||||||
char hdr[60];
|
char hdr[60];
|
||||||
@@ -853,14 +943,20 @@ archive_o(const char *objpath, const char *apath)
|
|||||||
memcpy(hdr + 40, "100644", 6); /* mode (fixed octal) */
|
memcpy(hdr + 40, "100644", 6); /* mode (fixed octal) */
|
||||||
char sz[12];
|
char sz[12];
|
||||||
int szn = snprintf(sz, sizeof sz, "%lu", (unsigned long)n);
|
int szn = snprintf(sz, sizeof sz, "%lu", (unsigned long)n);
|
||||||
memcpy(hdr + 48, sz, (size_t)szn);
|
if (szn <= 0 || szn > 10) bad = 1;
|
||||||
|
else memcpy(hdr + 48, sz, (size_t)szn);
|
||||||
hdr[58] = 0x60; /* member-header magic byte */
|
hdr[58] = 0x60; /* member-header magic byte */
|
||||||
hdr[59] = 0x0a;
|
hdr[59] = 0x0a;
|
||||||
fwrite(hdr, 1, sizeof hdr, out);
|
if (fwrite(hdr, 1, sizeof hdr, out) != sizeof hdr
|
||||||
fwrite(buf, 1, (size_t)n, out);
|
|| fwrite(buf, 1, (size_t)n, out) != (size_t)n)
|
||||||
if (n & 1) fputc('\n', out); /* members are 2-byte aligned */
|
bad = 1;
|
||||||
fclose(out);
|
if ((n & 1) && fputc('\n', out) == EOF) bad = 1;
|
||||||
|
if (fclose(out) != 0) bad = 1;
|
||||||
free(buf);
|
free(buf);
|
||||||
|
if (bad) {
|
||||||
|
fprintf(stderr, "ww: cannot write archive %s\n", apath);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -958,10 +1054,12 @@ workdir_stamp_text(char *buf, size_t bufsz, int is_test, int emit_asm)
|
|||||||
* files land in a cold `<stem>.sepwork` dir, or under the persistent
|
* files land in a cold `<stem>.sepwork` dir, or under the persistent
|
||||||
* `-w` workdir with content-identity package reuse. */
|
* `-w` workdir with content-identity package reuse. */
|
||||||
static int
|
static int
|
||||||
build_one_sep_impl(const char *src, int entry_is_dir, const char *out,
|
build_one_sep_impl(const char *src, int entry_is_dir,
|
||||||
|
const char *root_identity, const char *out,
|
||||||
const char *objstem, const char *extra_includes, const char *extra_libs,
|
const char *objstem, const char *extra_includes, const char *extra_libs,
|
||||||
const char *extra_libdirs, int is_test, int emit_asm, const char *workdir,
|
const char *extra_libdirs, int package_only, int is_test, int emit_asm,
|
||||||
char *scratchout, size_t scratchoutsz, struct sepgraph **graphout)
|
const char *workdir, char *scratchout, size_t scratchoutsz,
|
||||||
|
struct sepgraph **graphout)
|
||||||
{
|
{
|
||||||
const char *c6 = toolpath("WW_W6C", "w6c");
|
const char *c6 = toolpath("WW_W6C", "w6c");
|
||||||
const char *a6 = toolpath("WW_W6A", "w6a");
|
const char *a6 = toolpath("WW_W6A", "w6a");
|
||||||
@@ -1061,7 +1159,9 @@ build_one_sep_impl(const char *src, int entry_is_dir, const char *out,
|
|||||||
struct sepgraph *g = calloc(1, sizeof *g);
|
struct sepgraph *g = calloc(1, sizeof *g);
|
||||||
if (g == NULL) return 1;
|
if (g == NULL) return 1;
|
||||||
if (graphout) *graphout = g;
|
if (graphout) *graphout = g;
|
||||||
int root = sep_find_or_add(g, "", src, entry_is_dir);
|
const char *rootpath = package_only && root_identity
|
||||||
|
? root_identity : "";
|
||||||
|
int root = sep_find_or_add(g, rootpath, src, entry_is_dir);
|
||||||
if (root < 0) return 1;
|
if (root < 0) return 1;
|
||||||
/* #79 (-T): lib/test is the synth main's `test.run` callee but @test
|
/* #79 (-T): lib/test is the synth main's `test.run` callee but @test
|
||||||
* files never `import test;`. Inject it as a direct dep of the root so
|
* files never `import test;`. Inject it as a direct dep of the root so
|
||||||
@@ -1082,6 +1182,11 @@ build_one_sep_impl(const char *src, int entry_is_dir, const char *out,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (sep_load_pkg(g, root, srcdir) < 0) return 1;
|
if (sep_load_pkg(g, root, srcdir) < 0) return 1;
|
||||||
|
int root_package = package_only;
|
||||||
|
if (root_package && strcmp(g->pkg[root].name, "main") == 0) {
|
||||||
|
fprintf(stderr, "ww: -p requires a non-main package\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
for (int i = 0; i < g->n; i++) g->pkg[i].color = 0;
|
for (int i = 0; i < g->n; i++) g->pkg[i].color = 0;
|
||||||
int *order = calloc((size_t)g->n, sizeof *order);
|
int *order = calloc((size_t)g->n, sizeof *order);
|
||||||
int *stack = calloc((size_t)g->n, sizeof *stack);
|
int *stack = calloc((size_t)g->n, sizeof *stack);
|
||||||
@@ -1091,6 +1196,7 @@ build_one_sep_impl(const char *src, int entry_is_dir, const char *out,
|
|||||||
free(stack); free(order); return 1;
|
free(stack); free(order); return 1;
|
||||||
}
|
}
|
||||||
free(stack);
|
free(stack);
|
||||||
|
if (!root_package) g->pkg[root].path[0] = '\0';
|
||||||
|
|
||||||
for (int oi = 0; oi < norder; oi++) {
|
for (int oi = 0; oi < norder; oi++) {
|
||||||
int pi = order[oi];
|
int pi = order[oi];
|
||||||
@@ -1114,13 +1220,15 @@ build_one_sep_impl(const char *src, int entry_is_dir, const char *out,
|
|||||||
const char *cs = warm ? asmnew : asmf;
|
const char *cs = warm ? asmnew : asmf;
|
||||||
const char *co = warm ? objnew : obj;
|
const char *co = warm ? objnew : obj;
|
||||||
const char *ca = warm ? anew : apath;
|
const char *ca = warm ? anew : apath;
|
||||||
|
int needs_export = pi != root || root_package;
|
||||||
|
int needs_archive = pi != root || root_package;
|
||||||
if (sep_compose_unit(g, pi, scratch, order, norder, srcdir,
|
if (sep_compose_unit(g, pi, scratch, order, norder, srcdir,
|
||||||
cu) < 0) { free(order); return 1; }
|
cu) < 0) { free(order); return 1; }
|
||||||
if (warm && !stale_all && file_equal(unitnew, unitf)
|
if (warm && !stale_all && file_equal(unitnew, unitf)
|
||||||
&& file_is_reg(asmf)
|
&& file_is_reg(asmf)
|
||||||
&& (pi == root || file_is_reg(wwi))
|
&& (!needs_export || file_is_reg(wwi))
|
||||||
&& (emit_asm || (file_size_nonzero(obj)
|
&& (emit_asm || (file_size_nonzero(obj)
|
||||||
&& (pi == root || file_size_nonzero(apath))))) {
|
&& (!needs_archive || file_size_nonzero(apath))))) {
|
||||||
if (unlink(unitnew) != 0) {
|
if (unlink(unitnew) != 0) {
|
||||||
fprintf(stderr, "ww: cannot remove %s\n",
|
fprintf(stderr, "ww: cannot remove %s\n",
|
||||||
unitnew);
|
unitnew);
|
||||||
@@ -1135,7 +1243,7 @@ build_one_sep_impl(const char *src, int entry_is_dir, const char *out,
|
|||||||
* LOCAL type (the root is never imported), which the
|
* LOCAL type (the root is never imported), which the
|
||||||
* export-check rejects. Skip -I for the root; its `.wwi`
|
* export-check rejects. Skip -I for the root; its `.wwi`
|
||||||
* is never consumed. */
|
* is never consumed. */
|
||||||
if (pi == root)
|
if (!needs_export)
|
||||||
/* #79: the root carries -T under `ww test`
|
/* #79: the root carries -T under `ww test`
|
||||||
* so w6c synthesizes the test main. Deps never
|
* so w6c synthesizes the test main. Deps never
|
||||||
* get -T. */
|
* get -T. */
|
||||||
@@ -1162,7 +1270,7 @@ build_one_sep_impl(const char *src, int entry_is_dir, const char *out,
|
|||||||
* the build target, always fully linked), so `main` is defined
|
* the build target, always fully linked), so `main` is defined
|
||||||
* before any archive is processed. The link consumes `.o`/`.a`,
|
* before any archive is processed. The link consumes `.o`/`.a`,
|
||||||
* never `.wwi`. */
|
* never `.wwi`. */
|
||||||
if (!emit_asm && pi != root) {
|
if (!emit_asm && needs_archive) {
|
||||||
if (archive_o(co, ca) != 0) {
|
if (archive_o(co, ca) != 0) {
|
||||||
fprintf(stderr, "ww: archive failed for %s\n",
|
fprintf(stderr, "ww: archive failed for %s\n",
|
||||||
g->pkg[pi].path[0] ? g->pkg[pi].path : "(root)");
|
g->pkg[pi].path[0] ? g->pkg[pi].path : "(root)");
|
||||||
@@ -1172,10 +1280,10 @@ build_one_sep_impl(const char *src, int entry_is_dir, const char *out,
|
|||||||
/* Commit order: artifacts before the unit that vouches for
|
/* Commit order: artifacts before the unit that vouches for
|
||||||
* them, unit strictly last. */
|
* them, unit strictly last. */
|
||||||
if (warm) {
|
if (warm) {
|
||||||
if ((pi != root && rename(wwinew, wwi) != 0)
|
if ((needs_export && rename(wwinew, wwi) != 0)
|
||||||
|| rename(asmnew, asmf) != 0
|
|| rename(asmnew, asmf) != 0
|
||||||
|| (!emit_asm && rename(objnew, obj) != 0)
|
|| (!emit_asm && rename(objnew, obj) != 0)
|
||||||
|| (!emit_asm && pi != root
|
|| (!emit_asm && needs_archive
|
||||||
&& rename(anew, apath) != 0)
|
&& rename(anew, apath) != 0)
|
||||||
|| rename(unitnew, unitf) != 0) {
|
|| rename(unitnew, unitf) != 0) {
|
||||||
fprintf(stderr, "ww: cannot commit %s\n",
|
fprintf(stderr, "ww: cannot commit %s\n",
|
||||||
@@ -1212,6 +1320,20 @@ build_one_sep_impl(const char *src, int entry_is_dir, const char *out,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (emit_asm) { free(order); return 0; }
|
if (emit_asm) { free(order); return 0; }
|
||||||
|
if (root_package) {
|
||||||
|
char archive[1024], iface[1024], outiface[1100];
|
||||||
|
sep_fname(g, root, scratch, ".a", archive, sizeof archive);
|
||||||
|
sep_fname(g, root, scratch, ".wwi", iface, sizeof iface);
|
||||||
|
snprintf(outiface, sizeof outiface, "%s.wwi", out);
|
||||||
|
if (copy_file_atomic(archive, out) != 0
|
||||||
|
|| copy_file_atomic(iface, outiface) != 0) {
|
||||||
|
fprintf(stderr, "ww: cannot write package artifact %s\n", out);
|
||||||
|
free(order);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
free(order);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* reverse-topo link: root `.o` first (order[norder-1], force-loaded),
|
/* reverse-topo link: root `.o` first (order[norder-1], force-loaded),
|
||||||
* then transitive dep `.a` in reverse-topo order, then libwwrt.a —
|
* then transitive dep `.a` in reverse-topo order, then libwwrt.a —
|
||||||
@@ -1256,15 +1378,16 @@ build_one_sep_impl(const char *src, int entry_is_dir, const char *out,
|
|||||||
* covers every internal-scratch impl return. The path is nonempty only after
|
* covers every internal-scratch impl return. The path is nonempty only after
|
||||||
* this invocation successfully created the exact `.sepwork` tree. */
|
* this invocation successfully created the exact `.sepwork` tree. */
|
||||||
static int
|
static int
|
||||||
build_one_sep(const char *src, int entry_is_dir, const char *out,
|
build_one_sep(const char *src, int entry_is_dir, const char *root_identity,
|
||||||
|
const char *out,
|
||||||
const char *objstem, const char *extra_includes, const char *extra_libs,
|
const char *objstem, const char *extra_includes, const char *extra_libs,
|
||||||
const char *extra_libdirs, int is_test, int emit_asm, int keepscratch,
|
const char *extra_libdirs, int package_only, int is_test, int emit_asm,
|
||||||
const char *workdir)
|
int keepscratch, const char *workdir)
|
||||||
{
|
{
|
||||||
char scratch[1100] = {0};
|
char scratch[1100] = {0};
|
||||||
struct sepgraph *g = NULL;
|
struct sepgraph *g = NULL;
|
||||||
int r = build_one_sep_impl(src, entry_is_dir, out, objstem,
|
int r = build_one_sep_impl(src, entry_is_dir, root_identity, out, objstem,
|
||||||
extra_includes, extra_libs, extra_libdirs, is_test, emit_asm,
|
extra_includes, extra_libs, extra_libdirs, package_only, is_test, emit_asm,
|
||||||
workdir, scratch, sizeof scratch, &g);
|
workdir, scratch, sizeof scratch, &g);
|
||||||
sep_graph_free(g);
|
sep_graph_free(g);
|
||||||
if (!keepscratch && scratch[0]) {
|
if (!keepscratch && scratch[0]) {
|
||||||
@@ -1371,10 +1494,11 @@ parse_build_flags(const char *cmd, int argc, char **argv,
|
|||||||
char *libs, size_t libsz,
|
char *libs, size_t libsz,
|
||||||
char *outpath, size_t outsz,
|
char *outpath, size_t outsz,
|
||||||
char *workdir, size_t workdirsz,
|
char *workdir, size_t workdirsz,
|
||||||
const char **src_out, int *emit_asm_out)
|
const char **src_out, int *emit_asm_out, int *package_out)
|
||||||
{
|
{
|
||||||
*src_out = NULL;
|
*src_out = NULL;
|
||||||
if (emit_asm_out) *emit_asm_out = 0;
|
if (emit_asm_out) *emit_asm_out = 0;
|
||||||
|
if (package_out) *package_out = 0;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (; i < argc; i++) {
|
for (; i < argc; i++) {
|
||||||
if (strcmp(argv[i], "-S") == 0) {
|
if (strcmp(argv[i], "-S") == 0) {
|
||||||
@@ -1383,6 +1507,12 @@ parse_build_flags(const char *cmd, int argc, char **argv,
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
*emit_asm_out = 1;
|
*emit_asm_out = 1;
|
||||||
|
} else if (strcmp(argv[i], "-p") == 0) {
|
||||||
|
if (package_out == NULL) {
|
||||||
|
fprintf(stderr, "ww %s: unknown flag\n", cmd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
*package_out = 1;
|
||||||
} else if (strcmp(argv[i], "-w") == 0) {
|
} else if (strcmp(argv[i], "-w") == 0) {
|
||||||
if (workdir == NULL) {
|
if (workdir == NULL) {
|
||||||
fprintf(stderr, "ww %s: unknown flag\n", cmd);
|
fprintf(stderr, "ww %s: unknown flag\n", cmd);
|
||||||
@@ -1470,18 +1600,29 @@ do_build(int argc, char **argv)
|
|||||||
char outflag[1024] = {0};
|
char outflag[1024] = {0};
|
||||||
char workdir[1024] = {0};
|
char workdir[1024] = {0};
|
||||||
int emit_asm = 0;
|
int emit_asm = 0;
|
||||||
|
int package_only = 0;
|
||||||
if (parse_build_flags("build", argc, argv, incs, sizeof incs,
|
if (parse_build_flags("build", argc, argv, incs, sizeof incs,
|
||||||
libdirs, sizeof libdirs, libs, sizeof libs,
|
libdirs, sizeof libdirs, libs, sizeof libs,
|
||||||
outflag, sizeof outflag, workdir, sizeof workdir,
|
outflag, sizeof outflag, workdir, sizeof workdir,
|
||||||
&src, &emit_asm) < 0)
|
&src, &emit_asm, &package_only) < 0)
|
||||||
return 2;
|
return 2;
|
||||||
if (src == NULL) src = ".";
|
if (src == NULL) src = ".";
|
||||||
|
if (package_only && emit_asm) {
|
||||||
|
fprintf(stderr, "ww build: -p and -S cannot be combined\n");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
struct stat requested;
|
||||||
|
int literal = stat(src, &requested) == 0;
|
||||||
char resolved[1024];
|
char resolved[1024];
|
||||||
int is_dir = 0;
|
int is_dir = 0;
|
||||||
if (!resolve_module(src, incs, resolved, sizeof resolved, &is_dir)) {
|
if (!resolve_module(src, incs, resolved, sizeof resolved, &is_dir)) {
|
||||||
fprintf(stderr, "ww build: cannot find module %s\n", src);
|
fprintf(stderr, "ww build: cannot find module %s\n", src);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
if (package_only && !is_dir) {
|
||||||
|
fprintf(stderr, "ww build: -p needs a package directory\n");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
char out[1024];
|
char out[1024];
|
||||||
const char *objstem = NULL;
|
const char *objstem = NULL;
|
||||||
if (outflag[0]) {
|
if (outflag[0]) {
|
||||||
@@ -1499,8 +1640,9 @@ do_build(int argc, char **argv)
|
|||||||
} else {
|
} else {
|
||||||
basename_no_ext(resolved, out, sizeof out);
|
basename_no_ext(resolved, out, sizeof out);
|
||||||
}
|
}
|
||||||
return build_one_sep(resolved, is_dir, out, objstem, incs, libs,
|
const char *root_identity = package_only && !literal ? src : NULL;
|
||||||
libdirs, 0, emit_asm, 1, workdir);
|
return build_one_sep(resolved, is_dir, root_identity, out, objstem, incs, libs,
|
||||||
|
libdirs, package_only, 0, emit_asm, 1, workdir);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@@ -1513,7 +1655,7 @@ do_run(int argc, char **argv)
|
|||||||
char outflag[1024] = {0}; /* -o accepted+ignored: run always uses the temp */
|
char outflag[1024] = {0}; /* -o accepted+ignored: run always uses the temp */
|
||||||
int next = parse_build_flags("run", argc, argv, incs, sizeof incs,
|
int next = parse_build_flags("run", argc, argv, incs, sizeof incs,
|
||||||
libdirs, sizeof libdirs, libs, sizeof libs,
|
libdirs, sizeof libdirs, libs, sizeof libs,
|
||||||
outflag, sizeof outflag, NULL, 0, &src, NULL);
|
outflag, sizeof outflag, NULL, 0, &src, NULL, NULL);
|
||||||
if (next < 0) return 2;
|
if (next < 0) return 2;
|
||||||
if (src == NULL) src = ".";
|
if (src == NULL) src = ".";
|
||||||
char resolved[1024];
|
char resolved[1024];
|
||||||
@@ -1531,8 +1673,8 @@ do_run(int argc, char **argv)
|
|||||||
snprintf(tmp, sizeof tmp, "%s/main", tmpdir);
|
snprintf(tmp, sizeof tmp, "%s/main", tmpdir);
|
||||||
/* The freshly acquired directory owns both the executable and the
|
/* The freshly acquired directory owns both the executable and the
|
||||||
* adjacent main.sepwork tree. Nothing outside it is adopted or removed. */
|
* adjacent main.sepwork tree. Nothing outside it is adopted or removed. */
|
||||||
if (build_one_sep(resolved, is_dir, tmp, tmp, incs, libs, libdirs, 0, 0,
|
if (build_one_sep(resolved, is_dir, NULL, tmp, tmp, incs, libs, libdirs,
|
||||||
0, NULL) != 0) {
|
0, 0, 0, 0, NULL) != 0) {
|
||||||
if (unlink(tmp) != 0 && errno != ENOENT)
|
if (unlink(tmp) != 0 && errno != ENOENT)
|
||||||
fputs("ww: cannot remove temporary output\n", stderr);
|
fputs("ww: cannot remove temporary output\n", stderr);
|
||||||
if (rmdir(tmpdir) != 0)
|
if (rmdir(tmpdir) != 0)
|
||||||
@@ -1754,8 +1896,8 @@ do_test(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
/* No-o redirects internal scratch to /tmp rather than beside the
|
/* No-o redirects internal scratch to /tmp rather than beside the
|
||||||
* source. An explicit -o names the caller-owned artifact stem. */
|
* source. An explicit -o names the caller-owned artifact stem. */
|
||||||
int br = build_one_sep(resolved, is_dir, outp,
|
int br = build_one_sep(resolved, is_dir, NULL, outp,
|
||||||
outstem[0] ? outstem : tmp, incs, "", "", 1,
|
outstem[0] ? outstem : tmp, incs, "", "", 0, 1,
|
||||||
emit_asm, outstem[0] ? 1 : 0, workdir);
|
emit_asm, outstem[0] ? 1 : 0, workdir);
|
||||||
if (br != 0) {
|
if (br != 0) {
|
||||||
if (owntmp && unlink(outp) != 0 && errno != ENOENT)
|
if (owntmp && unlink(outp) != 0 && errno != ENOENT)
|
||||||
@@ -1811,8 +1953,8 @@ do_test(int argc, char **argv)
|
|||||||
outp = tmp;
|
outp = tmp;
|
||||||
}
|
}
|
||||||
/* See module-mode note: no-o scratch is redirected to /tmp. */
|
/* See module-mode note: no-o scratch is redirected to /tmp. */
|
||||||
int br = build_one_sep(target, 0, outp, outstem[0] ? outstem : tmp,
|
int br = build_one_sep(target, 0, NULL, outp, outstem[0] ? outstem : tmp,
|
||||||
incs, "", "", 1, emit_asm, outstem[0] ? 1 : 0, workdir);
|
incs, "", "", 0, 1, emit_asm, outstem[0] ? 1 : 0, workdir);
|
||||||
if (br != 0) {
|
if (br != 0) {
|
||||||
if (owntmp && unlink(outp) != 0 && errno != ENOENT)
|
if (owntmp && unlink(outp) != 0 && errno != ENOENT)
|
||||||
fputs("ww: cannot remove temporary output\n", stderr);
|
fputs("ww: cannot remove temporary output\n", stderr);
|
||||||
|
|||||||
@@ -2770,6 +2770,59 @@ The replacement deletes these concepts rather than emulating them indefinitely:
|
|||||||
There will be no compatibility alias that silently translates an old import,
|
There will be no compatibility alias that silently translates an old import,
|
||||||
interface, workdir, or link search into the new model.
|
interface, workdir, or link search into the new model.
|
||||||
|
|
||||||
|
### 11.6 Implemented local package slice
|
||||||
|
|
||||||
|
The first executable package slice is intentionally smaller than the final
|
||||||
|
module design above. It is local, offline, and manifest-free. The supported
|
||||||
|
form is:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
out/bin/ww build -I /work/acme -o app /work/acme/cmd/app
|
||||||
|
```
|
||||||
|
|
||||||
|
Every selected source uses the existing syntax:
|
||||||
|
|
||||||
|
```ww
|
||||||
|
package main;
|
||||||
|
import lib.math;
|
||||||
|
```
|
||||||
|
|
||||||
|
An import is translated from dots to path separators and resolved, with
|
||||||
|
directory packages preferred, through the entry package's directory, explicit
|
||||||
|
`-I` roots in command order, and the toolchain source-library root. There is no
|
||||||
|
network or manifest fallback. The loader uses the compiler frontend's
|
||||||
|
imports-only parser, unions duplicate imports, byte-sorts direct edges, interns
|
||||||
|
resolved directories by filesystem identity, and reports self-imports and
|
||||||
|
stable cycle chains before compilation.
|
||||||
|
|
||||||
|
A directory package consists of its immediate regular non-symlink `.ww` files,
|
||||||
|
excluding `*_test.ww`, in byte-sorted filename order. Every selected file must
|
||||||
|
declare the same package. An imported directory's declared package must equal
|
||||||
|
the final component of its import path; two logical identities for one physical
|
||||||
|
directory are rejected rather than compiled twice.
|
||||||
|
|
||||||
|
Packages compile serially in dependency-first postorder. The compiler emits the
|
||||||
|
existing deterministic `.wwi` interface for every importable package and only
|
||||||
|
exported declarations enter that interface. A source qualifier is visible only
|
||||||
|
when its owning package directly imports it; private members and transitive-only
|
||||||
|
qualifiers are compiler errors. For compatibility with public signatures that
|
||||||
|
name deeper types, composed compiler units still carry transitive interface type
|
||||||
|
facts for internal resolution, but those facts do not create source-visible
|
||||||
|
bare names, package qualifiers, or value bindings. Replacing that
|
||||||
|
source-like closure with a self-contained typed export encoding remains part of
|
||||||
|
the later export-format work, not package-loader semantics.
|
||||||
|
|
||||||
|
An ordinary root is linked with the full reachable object closure into the
|
||||||
|
requested executable (legacy WW programs may use a package name other than
|
||||||
|
`main`). `ww build -p -o lib.a DIR` explicitly requests a non-main package
|
||||||
|
product: it emits a deterministic archive at `lib.a` and its compiler interface
|
||||||
|
at `lib.a.wwi`, without invoking the linker. A logical target retains its full
|
||||||
|
identity (`ww build -p -I ROOT -o bar.a foo.bar` emits `foo.bar.*` symbols),
|
||||||
|
while a literal directory uses its declared leaf package. Package output
|
||||||
|
requires a directory and `-p` cannot be combined with assembly-only `-S`. Two
|
||||||
|
cold builds with identical inputs are required to produce byte-identical
|
||||||
|
requested products.
|
||||||
|
|
||||||
## 12. Candidate architectures and hard-gate decision
|
## 12. Candidate architectures and hard-gate decision
|
||||||
|
|
||||||
Five candidates were developed as coherent systems, not as feature bins.
|
Five candidates were developed as coherent systems, not as feature bins.
|
||||||
|
|||||||
21
lib/os/os.ww
21
lib/os/os.ww
@@ -855,6 +855,27 @@ export fn stat(out: *filestat, path: str) (void | oserror) = {
|
|||||||
fillfilestat(out, &k);
|
fillfilestat(out, &k);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// samefile reports whether two existing paths resolve to the same kernel
|
||||||
|
// object. It follows terminal symlinks, like stat(2), and compares both the
|
||||||
|
// device and inode kept in the native stat record. Package loading uses this
|
||||||
|
// narrow primitive to intern directories by identity without exposing a
|
||||||
|
// platform-specific canonical pathname policy.
|
||||||
|
export fn samefile(a: str, b: str) bool = {
|
||||||
|
let ap: *u8 = kpath(a);
|
||||||
|
if (ap == nil: *u8) { return false; };
|
||||||
|
let ak: kstat;
|
||||||
|
let ar: i64 = syscall4(nr.NEWFSTATAT,
|
||||||
|
AT_FDCWD: i64, ap: i64, (&ak): i64, 0i64);
|
||||||
|
if (ar < 0) { return false; };
|
||||||
|
let bp: *u8 = kpath(b);
|
||||||
|
if (bp == nil: *u8) { return false; };
|
||||||
|
let bk: kstat;
|
||||||
|
let br: i64 = syscall4(nr.NEWFSTATAT,
|
||||||
|
AT_FDCWD: i64, bp: i64, (&bk): i64, 0i64);
|
||||||
|
if (br < 0) { return false; };
|
||||||
|
return ak.dev == bk.dev && ak.ino == bk.ino;
|
||||||
|
};
|
||||||
|
|
||||||
// lstat — like [[stat]] but does NOT follow a terminal symlink.
|
// lstat — like [[stat]] but does NOT follow a terminal symlink.
|
||||||
// Mirrors Hare's sys::lstat (ref/hare/sys/+linux/stat.ha:57).
|
// Mirrors Hare's sys::lstat (ref/hare/sys/+linux/stat.ha:57).
|
||||||
export fn lstat(out: *filestat, path: str) (void | oserror) = {
|
export fn lstat(out: *filestat, path: str) (void | oserror) = {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import os;
|
|||||||
import os.exec;
|
import os.exec;
|
||||||
import rt;
|
import rt;
|
||||||
import strings;
|
import strings;
|
||||||
|
import syntax;
|
||||||
|
|
||||||
// All path/string scratch buffers go on the runtime page allocator.
|
// All path/string scratch buffers go on the runtime page allocator.
|
||||||
// One page is plenty for any path we build. PATH_MAX lives in lib/os
|
// One page is plenty for any path we build. PATH_MAX lives in lib/os
|
||||||
@@ -33,6 +34,25 @@ fn cerr(m: str) void = {
|
|||||||
os.write(2, m.ptr, m.len: u64);
|
os.write(2, m.ptr, m.len: u64);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn cerrnum(v: i32) void = {
|
||||||
|
let digits: [16]u8;
|
||||||
|
let n: i32 = 0;
|
||||||
|
let x: i32 = v;
|
||||||
|
if (x <= 0) { digits[n] = '0'; n += 1; }
|
||||||
|
else {
|
||||||
|
for (x > 0) {
|
||||||
|
digits[n] = ((x % 10) + ('0': i32)): u8;
|
||||||
|
n += 1;
|
||||||
|
x = x / 10;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
for (n > 0) { n -= 1; os.write(2, &digits[n], 1u64); };
|
||||||
|
};
|
||||||
|
|
||||||
|
fn cerrpos(file: str, line: i32, col: i32) void = {
|
||||||
|
cerr(file); cerr(":"); cerrnum(line); cerr(":"); cerrnum(col);
|
||||||
|
};
|
||||||
|
|
||||||
fn cstrlen(p: *u8) u64 = {
|
fn cstrlen(p: *u8) u64 = {
|
||||||
let n: u64 = 0u64;
|
let n: u64 = 0u64;
|
||||||
for (p[n] != 0u8) { n += 1u64; };
|
for (p[n] != 0u8) { n += 1u64; };
|
||||||
@@ -386,6 +406,22 @@ fn dirfileclass(dirpath: *u8, name: *u8, nlen: u64) i32 = {
|
|||||||
s.len = nlen: i32;
|
s.len = nlen: i32;
|
||||||
if (!strings.hassuffix(s, ".ww")) { return 0; };
|
if (!strings.hassuffix(s, ".ww")) { return 0; };
|
||||||
if (strings.hassuffix(s, "_test.ww")) { return 0; };
|
if (strings.hassuffix(s, "_test.ww")) { return 0; };
|
||||||
|
let source: *u8 = joinpath(dirpath, name);
|
||||||
|
let fi: os.filestat;
|
||||||
|
let sr: (void | os.oserror) = os.lstat(&fi, pathstr(source));
|
||||||
|
let regular: bool = false;
|
||||||
|
match (sr) {
|
||||||
|
case void => {
|
||||||
|
let t: u32 = (fi.mode: u32) & 61440u32;
|
||||||
|
if (t == os.mode.REG: u32) { regular = true; };
|
||||||
|
};
|
||||||
|
case let e: os.oserror => void;
|
||||||
|
};
|
||||||
|
if (!regular) {
|
||||||
|
cerr("ww: "); cerr(pathstr(source));
|
||||||
|
cerr(": package source is not a regular file\n");
|
||||||
|
return -2;
|
||||||
|
};
|
||||||
if (dirfileattest(dirpath, name)) { return -1; };
|
if (dirfileattest(dirpath, name)) { return -1; };
|
||||||
return 1;
|
return 1;
|
||||||
};
|
};
|
||||||
@@ -438,13 +474,17 @@ fn enumeratedir(dirpath: *u8) (**u8, i32) = {
|
|||||||
let nm: *u8 = buf.ptr + off + 19u64;
|
let nm: *u8 = buf.ptr + off + 19u64;
|
||||||
let nl: u64 = cstrlen(nm);
|
let nl: u64 = cstrlen(nm);
|
||||||
let cls: i32 = dirfileclass(dirpath, nm, nl);
|
let cls: i32 = dirfileclass(dirpath, nm, nl);
|
||||||
if (cls < 0) {
|
if (cls == -1) {
|
||||||
cerr("ww: ");
|
cerr("ww: ");
|
||||||
cerr(pathstr(joinpath(dirpath, nm)));
|
cerr(pathstr(joinpath(dirpath, nm)));
|
||||||
cerr(": @test declaration outside *_test.ww\n");
|
cerr(": @test declaration outside *_test.ww\n");
|
||||||
os.close(fd);
|
os.close(fd);
|
||||||
return nil: **u8, -2;
|
return nil: **u8, -2;
|
||||||
};
|
};
|
||||||
|
if (cls == -2) {
|
||||||
|
os.close(fd);
|
||||||
|
return nil: **u8, -2;
|
||||||
|
};
|
||||||
if (cls > 0) {
|
if (cls > 0) {
|
||||||
if (n >= cap) {
|
if (n >= cap) {
|
||||||
let ncap: i32 = cap * 2;
|
let ncap: i32 = cap * 2;
|
||||||
@@ -527,59 +567,17 @@ fn slurp(pathcs: *u8) (*u8, u64) = {
|
|||||||
let buf: []u8 = alloc([], nu + 1u64)!;
|
let buf: []u8 = alloc([], nu + 1u64)!;
|
||||||
buf.len = (nu + 1u64): i32;
|
buf.len = (nu + 1u64): i32;
|
||||||
let rr: (i64 | os.oserror) = os.readall(fd, buf.ptr, nu);
|
let rr: (i64 | os.oserror) = os.readall(fd, buf.ptr, nu);
|
||||||
os.close(fd);
|
let closed: i32 = os.close(fd);
|
||||||
let got: i64 = 0i64;
|
let got: i64 = 0i64;
|
||||||
match (rr) {
|
match (rr) {
|
||||||
case let v: i64 => got = v;
|
case let v: i64 => got = v;
|
||||||
case let e: os.oserror => return nil, 0u64;
|
case let e: os.oserror => return nil, 0u64;
|
||||||
};
|
};
|
||||||
if (got != n) { return nil, 0u64; };
|
if (got != n || closed != 0) { return nil, 0u64; };
|
||||||
buf[nu] = 0u8;
|
buf[nu] = 0u8;
|
||||||
return buf.ptr, nu;
|
return buf.ptr, nu;
|
||||||
};
|
};
|
||||||
|
|
||||||
fn isidentbyte(c: u8) bool = {
|
|
||||||
if (c >= 'a' && c <= 'z') { return true; };
|
|
||||||
if (c >= 'A' && c <= 'Z') { return true; };
|
|
||||||
if (c >= '0' && c <= '9') { return true; };
|
|
||||||
if (c == '_') { return true; };
|
|
||||||
if (c == '.') { return true; };
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Scan one `import IDENT;` line out of [start, end). Returns the start
|
|
||||||
// of the ident and its length, or (nil, 0) if no `import` here. The
|
|
||||||
// caller passes a slice of the source: src points at the line start.
|
|
||||||
fn scanuse(src: *u8, len: u64) (*u8, u64) = {
|
|
||||||
let i: u64 = 0u64;
|
|
||||||
for (i < len) {
|
|
||||||
if (src[i] != 32u8) { if (src[i] != 9u8) { break; }; };
|
|
||||||
i += 1u64;
|
|
||||||
};
|
|
||||||
// i+7>len guard kept: hasprefix("import") covers the 6 spell-out
|
|
||||||
// bytes, but the sep read at src[i+6] still needs i+6 < len.
|
|
||||||
if (i + 7u64 > len) { return nil, 0u64; };
|
|
||||||
let rest: str;
|
|
||||||
rest.ptr = src + i;
|
|
||||||
rest.len = (len - i): i32;
|
|
||||||
if (!strings.hasprefix(rest, "import")) { return nil, 0u64; };
|
|
||||||
let sep: u8 = src[i + 6u64];
|
|
||||||
if (sep != 32u8) { if (sep != 9u8) { return nil, 0u64; }; };
|
|
||||||
i += 7u64;
|
|
||||||
for (i < len) {
|
|
||||||
if (src[i] != 32u8) { if (src[i] != 9u8) { break; }; };
|
|
||||||
i += 1u64;
|
|
||||||
};
|
|
||||||
let idstart: u64 = i;
|
|
||||||
for (i < len) {
|
|
||||||
if (!isidentbyte(src[i])) { break; };
|
|
||||||
i += 1u64;
|
|
||||||
};
|
|
||||||
let idlen: u64 = i - idstart;
|
|
||||||
if (idlen == 0u64) { return nil, 0u64; };
|
|
||||||
return src + idstart, idlen;
|
|
||||||
};
|
|
||||||
|
|
||||||
fn makestem(stem: *u8, src: *u8) void = {
|
fn makestem(stem: *u8, src: *u8) void = {
|
||||||
let n: u64 = cstrlen(src);
|
let n: u64 = cstrlen(src);
|
||||||
let stop: u64 = n;
|
let stop: u64 = n;
|
||||||
@@ -632,8 +630,10 @@ type lflags = struct {
|
|||||||
// reference (#40) and the sep `.o`s link. The prepend is the TRANSITIVE
|
// reference (#40) and the sep `.o`s link. The prepend is the TRANSITIVE
|
||||||
// closure of a package's deps (lead-ratified): a dep's interface can
|
// closure of a package's deps (lead-ratified): a dep's interface can
|
||||||
// name a transitive dep's type, so the consuming unit needs the whole
|
// name a transitive dep's type, so the consuming unit needs the whole
|
||||||
// closure for resolution. The unit composition is byte-identical to the
|
// closure for resolution. Qualifier lookup remains scoped to the direct
|
||||||
// cstage driver (rule 10) so w6c/w6c_ww emit identical `.s`.
|
// imports owned by each source package, so those type facts do not expose
|
||||||
|
// a transitive package. The unit composition is byte-identical to the cstage
|
||||||
|
// driver (rule 10) so w6c/w6c_ww emit identical `.s`.
|
||||||
|
|
||||||
def SEP_MAXPKG: i32 = 256;
|
def SEP_MAXPKG: i32 = 256;
|
||||||
|
|
||||||
@@ -655,9 +655,30 @@ type sepgraph = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
fn sepfindoradd(g: *sepgraph, path: *u8, entry: *u8, isdir: i32) i32 = {
|
fn sepfindoradd(g: *sepgraph, path: *u8, entry: *u8, isdir: i32) i32 = {
|
||||||
|
if (cstrlen(path) >= 256u64) {
|
||||||
|
cerr("ww: package path is too long (limit 255 bytes)\n");
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
for (i < g.n) {
|
for (i < g.n) {
|
||||||
if (cstreq(g.pkg[i].path, path)) { return i; };
|
if (cstreq(g.pkg[i].path, path)) {
|
||||||
|
if (!os.samefile(pathstr(g.pkg[i].entry), pathstr(entry))) {
|
||||||
|
cerr("ww: package "); cerr(pathstr(path));
|
||||||
|
cerr(" resolves to more than one location\n");
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
|
return i;
|
||||||
|
};
|
||||||
|
if (os.samefile(pathstr(g.pkg[i].entry), pathstr(entry))) {
|
||||||
|
cerr("ww: one package directory has identities ");
|
||||||
|
if (g.pkg[i].path[0u64] == 0u8) { cerr("(root)"); }
|
||||||
|
else { cerr(pathstr(g.pkg[i].path)); };
|
||||||
|
cerr(" and ");
|
||||||
|
if (path[0u64] == 0u8) { cerr("(root)"); }
|
||||||
|
else { cerr(pathstr(path)); };
|
||||||
|
cerr("\n");
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
i += 1;
|
i += 1;
|
||||||
};
|
};
|
||||||
if (g.n >= SEP_MAXPKG) {
|
if (g.n >= SEP_MAXPKG) {
|
||||||
@@ -723,140 +744,6 @@ fn sepfname(g: *sepgraph, pi: i32, scratch: *u8, suffix: str) *u8 = {
|
|||||||
return buf.ptr;
|
return buf.ptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
// unithaspackage — does the unit buffer declare `package <leaf>;` ANYWHERE?
|
|
||||||
// #16 ENFORCE-driver (rob A) cstage unit_has_package twin: distinguishes a
|
|
||||||
// genuinely-missing import from one satisfied by an INLINE package in the
|
|
||||||
// same single-file multi-package unit (`package aa; ... package main;
|
|
||||||
// import aa;`). Scans EVERY line (comment-skip) — not just the first
|
|
||||||
// package decl. Decision byte-identical to
|
|
||||||
// cstage so the skip/fatal choice + driver output match (rule 10).
|
|
||||||
fn unithaspackage(buf: *u8, buflen: u64, leafp: *u8, leafn: u64) bool = {
|
|
||||||
let p: u64 = 0u64;
|
|
||||||
for (p < buflen) {
|
|
||||||
let q: u64 = p;
|
|
||||||
for (q < buflen) { if (buf[q] == 10u8) { break; }; q += 1u64; };
|
|
||||||
let s: u64 = p;
|
|
||||||
for (s < q) {
|
|
||||||
if (buf[s] != 32u8) { if (buf[s] != 9u8) { break; }; };
|
|
||||||
s += 1u64;
|
|
||||||
};
|
|
||||||
if (s < q) {
|
|
||||||
let line: str;
|
|
||||||
line.ptr = buf + s;
|
|
||||||
line.len = (q - s): i32;
|
|
||||||
if (strings.hasprefix(line, "//")) { p = q + 1u64; continue; };
|
|
||||||
if (s + 8u64 <= q) {
|
|
||||||
if (strings.hasprefix(line, "package")) {
|
|
||||||
let sep: u8 = buf[s + 7u64];
|
|
||||||
let oksep: bool = false;
|
|
||||||
if (sep == 32u8) { oksep = true; }
|
|
||||||
else { if (sep == 9u8) { oksep = true; }; };
|
|
||||||
if (oksep) {
|
|
||||||
let t: u64 = s + 8u64;
|
|
||||||
for (t < q) {
|
|
||||||
if (buf[t] != 32u8) { if (buf[t] != 9u8) { break; }; };
|
|
||||||
t += 1u64;
|
|
||||||
};
|
|
||||||
let m: u64 = 0u64;
|
|
||||||
let eq: bool = true;
|
|
||||||
for (m < leafn) {
|
|
||||||
if (t + m >= q) { eq = false; break; };
|
|
||||||
if (buf[t + m] != leafp[m]) { eq = false; break; };
|
|
||||||
m += 1u64;
|
|
||||||
};
|
|
||||||
if (eq) {
|
|
||||||
let after: u64 = t + leafn;
|
|
||||||
let term: bool = false;
|
|
||||||
if (after >= q) { term = true; }
|
|
||||||
else {
|
|
||||||
let c: u8 = buf[after];
|
|
||||||
if (c == 59u8) { term = true; } // ';'
|
|
||||||
else { if (c == 32u8) { term = true; }
|
|
||||||
else { if (c == 9u8) { term = true; }; }; };
|
|
||||||
};
|
|
||||||
if (term) { return true; };
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
p = q + 1u64;
|
|
||||||
};
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
fn sepidentstart(c: u8) bool = {
|
|
||||||
if (c >= 'a' && c <= 'z') { return true; };
|
|
||||||
if (c >= 'A' && c <= 'Z') { return true; };
|
|
||||||
return c == '_';
|
|
||||||
};
|
|
||||||
|
|
||||||
fn sepidentcontinue(c: u8) bool = {
|
|
||||||
if (sepidentstart(c)) { return true; };
|
|
||||||
return c >= '0' && c <= '9';
|
|
||||||
};
|
|
||||||
|
|
||||||
// Skip the whitespace and comments accepted before and within the leading
|
|
||||||
// package clause. This is deliberately only the loader's small header
|
|
||||||
// grammar, not a second compiler lexer.
|
|
||||||
fn sepskipspace(src: *u8, n: u64, start: u64, ok: *bool) u64 = {
|
|
||||||
let i: u64 = start;
|
|
||||||
*ok = true;
|
|
||||||
for (true) {
|
|
||||||
for (i < n && (src[i] == ' ' || src[i] == '\t'
|
|
||||||
|| src[i] == '\r' || src[i] == '\n')) { i += 1u64; };
|
|
||||||
if (i + 1u64 < n && src[i] == '/' && src[i + 1u64] == '/') {
|
|
||||||
i += 2u64;
|
|
||||||
for (i < n && src[i] != '\n') { i += 1u64; };
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
if (i + 1u64 < n && src[i] == '/' && src[i + 1u64] == '*') {
|
|
||||||
i += 2u64;
|
|
||||||
let closed: bool = false;
|
|
||||||
for (i + 1u64 < n) {
|
|
||||||
if (src[i] == '*' && src[i + 1u64] == '/') {
|
|
||||||
i += 2u64;
|
|
||||||
closed = true;
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
i += 1u64;
|
|
||||||
};
|
|
||||||
if (!closed) { *ok = false; return i; };
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
return i;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Parse exactly the leading loader grammar `package ident;`.
|
|
||||||
fn seppackageclause(src: *u8, n: u64, outp: **u8, outn: *u64) bool = {
|
|
||||||
let ok: bool = true;
|
|
||||||
let i: u64 = sepskipspace(src, n, 0u64, &ok);
|
|
||||||
if (!ok || i + 7u64 >= n) { return false; };
|
|
||||||
let word: str = "package";
|
|
||||||
let j: i32 = 0;
|
|
||||||
for (j < word.len) {
|
|
||||||
let ju: u64 = j: u64;
|
|
||||||
if (src[i + ju] != word[j]) { return false; };
|
|
||||||
j += 1;
|
|
||||||
};
|
|
||||||
i += 7u64;
|
|
||||||
if (i >= n || !(src[i] == ' ' || src[i] == '\t'
|
|
||||||
|| src[i] == '\r' || src[i] == '\n')) { return false; };
|
|
||||||
i = sepskipspace(src, n, i, &ok);
|
|
||||||
if (!ok || i >= n || !sepidentstart(src[i])) { return false; };
|
|
||||||
let begin: u64 = i;
|
|
||||||
i += 1u64;
|
|
||||||
for (i < n && sepidentcontinue(src[i])) { i += 1u64; };
|
|
||||||
let end: u64 = i;
|
|
||||||
i = sepskipspace(src, n, i, &ok);
|
|
||||||
if (!ok || i >= n || src[i] != ';') { return false; };
|
|
||||||
*outp = src + begin;
|
|
||||||
*outn = end - begin;
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Scan one already-selected source file for its leading package clause
|
// Scan one already-selected source file for its leading package clause
|
||||||
// (when it is an owned directory source) and top-level imports. A DIRECTORY
|
// (when it is an owned directory source) and top-level imports. A DIRECTORY
|
||||||
// import is a package boundary: add as a direct dep of pi. A FILE import is an
|
// import is a package boundary: add as a direct dep of pi. A FILE import is an
|
||||||
@@ -877,44 +764,109 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, searchpath: *u8,
|
|||||||
cerr("ww: cannot read source\n");
|
cerr("ww: cannot read source\n");
|
||||||
return -1;
|
return -1;
|
||||||
};
|
};
|
||||||
if (ownedsource != 0) {
|
let l: syntax.lex;
|
||||||
let declared: *u8 = nil;
|
syntax.lexinit(&l, fdup, bufp, blen);
|
||||||
let declaredn: u64 = 0u64;
|
let ps: syntax.parser;
|
||||||
if (!seppackageclause(bufp, blen, &declared, &declaredn)) {
|
syntax.parserinit(&ps, &l);
|
||||||
cerr("ww: ");
|
let imports: *syntax.node = syntax.parseimports(&ps);
|
||||||
cerr(pathstr(file));
|
if (l.errs > 0 || ps.errs > 0) { return -1; };
|
||||||
cerr(": invalid or missing package clause\n");
|
if (imports.nmod.len == 0 && ownedsource != 0) {
|
||||||
return -1;
|
cerrpos(fdup, 1, 1);
|
||||||
};
|
cerr(": error: invalid or missing package clause\n");
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
|
if (imports.nmod.len != 0
|
||||||
|
&& (ownedsource != 0 || g.pkg[pi].name == nil)) {
|
||||||
|
let declared: *u8 = imports.nmod.ptr;
|
||||||
|
let declaredn: u64 = imports.nmod.len: u64;
|
||||||
if (g.pkg[pi].name == nil) {
|
if (g.pkg[pi].name == nil) {
|
||||||
g.pkg[pi].name = arenadupcstr(declared, declaredn);
|
g.pkg[pi].name = arenadupcstr(declared, declaredn);
|
||||||
} else { if (bytecmp(g.pkg[pi].name, cstrlen(g.pkg[pi].name),
|
} else { if (bytecmp(g.pkg[pi].name, cstrlen(g.pkg[pi].name),
|
||||||
declared, declaredn) != 0) {
|
declared, declaredn) != 0) {
|
||||||
cerr("ww: ");
|
cerrpos(imports.file, imports.line, imports.col);
|
||||||
cerr(pathstr(g.pkg[pi].entry));
|
cerr(": error: conflicting package names ");
|
||||||
cerr(": conflicting package names ");
|
|
||||||
cerr(pathstr(g.pkg[pi].name));
|
cerr(pathstr(g.pkg[pi].name));
|
||||||
cerr(" and ");
|
cerr(" and ");
|
||||||
os.write(2, declared, declaredn);
|
os.write(2, declared, declaredn);
|
||||||
cerr("\n");
|
cerr(" in "); cerr(pathstr(g.pkg[pi].entry)); cerr("\n");
|
||||||
return -1;
|
return -1;
|
||||||
}; };
|
}; };
|
||||||
};
|
};
|
||||||
let i: u64 = 0u64;
|
if (ownedsource != 0 && g.pkg[pi].isdir != 0) {
|
||||||
for (i < blen) {
|
let pm: *syntax.node = imports.body;
|
||||||
let j: u64 = i;
|
for (pm != nil) {
|
||||||
for (j < blen) {
|
if (!syntax.streq(pm.nmod, pathstr(g.pkg[pi].name))) {
|
||||||
if (bufp[j] == 10u8) { break; }; // '\n'
|
cerrpos(pm.file, pm.line, pm.col);
|
||||||
j += 1u64;
|
cerr(": error: conflicting package names ");
|
||||||
|
cerr(pathstr(g.pkg[pi].name)); cerr(" and ");
|
||||||
|
cerr(pm.nmod); cerr(" in ");
|
||||||
|
cerr(pathstr(g.pkg[pi].entry)); cerr("\n");
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
|
pm = pm.next;
|
||||||
};
|
};
|
||||||
let idp: *u8;
|
};
|
||||||
let idn: u64;
|
let nuse: i32 = 0;
|
||||||
idp, idn = scanuse(bufp + i, j - i);
|
let u: *syntax.node = imports.list;
|
||||||
if (idp != nil) {
|
for (u != nil) {
|
||||||
|
if (u.kind == syntax.nkind.N_USE) { nuse += 1; };
|
||||||
|
u = u.next;
|
||||||
|
};
|
||||||
|
let uses: []*syntax.node = [];
|
||||||
|
if (nuse > 0) {
|
||||||
|
let allocated: []*syntax.node = alloc([], nuse: u64)!;
|
||||||
|
uses = allocated;
|
||||||
|
uses.len = nuse;
|
||||||
|
};
|
||||||
|
let ui: i32 = 0;
|
||||||
|
u = imports.list;
|
||||||
|
for (u != nil) {
|
||||||
|
if (u.kind == syntax.nkind.N_USE) { uses[ui] = u; ui += 1; };
|
||||||
|
u = u.next;
|
||||||
|
};
|
||||||
|
let si: i32 = 1;
|
||||||
|
for (si < nuse) {
|
||||||
|
let sj: i32 = si;
|
||||||
|
for (sj > 0) {
|
||||||
|
if (strings.compare(uses[sj - 1].usepath,
|
||||||
|
uses[sj].usepath) <= 0) { sj = 0; }
|
||||||
|
else {
|
||||||
|
let t: *syntax.node = uses[sj];
|
||||||
|
uses[sj] = uses[sj - 1];
|
||||||
|
uses[sj - 1] = t;
|
||||||
|
sj -= 1;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
si += 1;
|
||||||
|
};
|
||||||
|
let previous: str = "";
|
||||||
|
ui = 0;
|
||||||
|
for (ui < nuse) {
|
||||||
|
u = uses[ui];
|
||||||
|
let duplicate: bool = previous.len > 0
|
||||||
|
&& syntax.streq(previous, u.usepath);
|
||||||
|
if (!duplicate) {
|
||||||
|
previous = u.usepath;
|
||||||
|
let idp: *u8 = u.usepath.ptr;
|
||||||
|
let idn: u64 = u.usepath.len: u64;
|
||||||
|
if (idn >= 256u64) {
|
||||||
|
cerrpos(u.file, u.line, u.col);
|
||||||
|
cerr(": error: import path is too long (limit 255 bytes)\n");
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
let isdir: i32 = 0;
|
let isdir: i32 = 0;
|
||||||
let ipath: *u8 = locateimport(searchpath, idp, idn, &isdir);
|
let ipath: *u8 = locateimport(searchpath, idp, idn, &isdir);
|
||||||
if (ipath != nil) {
|
if (ipath != nil) {
|
||||||
if (isdir != 0) {
|
if (isdir != 0) {
|
||||||
|
if (os.samefile(pathstr(ipath), pathstr(g.pkg[pi].entry))) {
|
||||||
|
cerrpos(u.file, u.line, u.col);
|
||||||
|
cerr(": error: self-import: package '");
|
||||||
|
if (g.pkg[pi].path[0u64] != 0u8) {
|
||||||
|
cerr(pathstr(g.pkg[pi].path));
|
||||||
|
} else { cerr(pathstr(g.pkg[pi].name)); };
|
||||||
|
cerr("' cannot import itself\n");
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
let nm: []u8 = alloc([], idn + 1u64)!;
|
let nm: []u8 = alloc([], idn + 1u64)!;
|
||||||
let k: u64 = 0u64;
|
let k: u64 = 0u64;
|
||||||
for (k < idn) { nm[k] = idp[k]; k += 1u64; };
|
for (k < idn) { nm[k] = idp[k]; k += 1u64; };
|
||||||
@@ -938,14 +890,6 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, searchpath: *u8,
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// #16 ENFORCE-driver (rob A): a locate-miss is legal
|
|
||||||
// when the package is defined INLINE in the same unit
|
|
||||||
// (single-file multi-package). leaf = last dotted
|
|
||||||
// component; inline `package <leaf>` -> skip (the
|
|
||||||
// checker binds it), else fatal. E3-C1 (#87): the
|
|
||||||
// legacy amalgamator that owned the genuine-missing
|
|
||||||
// case is gone, so the sep producer enforces it here
|
|
||||||
// (INV-2). cstage twin; fatal text identical.
|
|
||||||
let lstart: u64 = 0u64;
|
let lstart: u64 = 0u64;
|
||||||
let lk: u64 = 0u64;
|
let lk: u64 = 0u64;
|
||||||
for (lk < idn) {
|
for (lk < idn) {
|
||||||
@@ -954,15 +898,23 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, searchpath: *u8,
|
|||||||
};
|
};
|
||||||
let leafp: *u8 = idp + lstart;
|
let leafp: *u8 = idp + lstart;
|
||||||
let leafn: u64 = idn - lstart;
|
let leafn: u64 = idn - lstart;
|
||||||
if (!unithaspackage(bufp, blen, leafp, leafn)) {
|
let inlinepackage: bool = false;
|
||||||
cerr("ww: cannot find package ");
|
let pm: *syntax.node = imports.body;
|
||||||
|
for (pm != nil) {
|
||||||
|
if (bytecmp(pm.nmod.ptr, pm.nmod.len: u64,
|
||||||
|
leafp, leafn) == 0) { inlinepackage = true; };
|
||||||
|
pm = pm.next;
|
||||||
|
};
|
||||||
|
if (!inlinepackage) {
|
||||||
|
cerrpos(u.file, u.line, u.col);
|
||||||
|
cerr(": error: cannot find package ");
|
||||||
os.write(2, idp, idn);
|
os.write(2, idp, idn);
|
||||||
cerr("\n");
|
cerr("\n");
|
||||||
return -1;
|
return -1;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
i = j + 1u64;
|
ui += 1;
|
||||||
};
|
};
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
@@ -1028,6 +980,24 @@ fn seploadpkg(g: *sepgraph, pi: i32, searchpath: *u8) i32 = {
|
|||||||
rc = sepscanfile(g, pi, g.pkg[pi].entry, searchpath, &fv, 0);
|
rc = sepscanfile(g, pi, g.pkg[pi].entry, searchpath, &fv, 0);
|
||||||
};
|
};
|
||||||
if (rc < 0) { return rc; };
|
if (rc < 0) { return rc; };
|
||||||
|
if (pi == 0 && g.pkg[pi].path[0u64] == 0u8
|
||||||
|
&& g.pkg[pi].name != nil) {
|
||||||
|
g.pkg[pi].path = arenadupcstr(g.pkg[pi].name,
|
||||||
|
cstrlen(g.pkg[pi].name));
|
||||||
|
};
|
||||||
|
let si: i32 = 1;
|
||||||
|
for (si < g.pkg[pi].ndeps) {
|
||||||
|
let v: i32 = g.pkg[pi].deps[si];
|
||||||
|
let sj: i32 = si;
|
||||||
|
for (sj > 0 && strings.compare(
|
||||||
|
pathstr(g.pkg[g.pkg[pi].deps[sj - 1]].path),
|
||||||
|
pathstr(g.pkg[v].path)) > 0) {
|
||||||
|
g.pkg[pi].deps[sj] = g.pkg[pi].deps[sj - 1];
|
||||||
|
sj -= 1;
|
||||||
|
};
|
||||||
|
g.pkg[pi].deps[sj] = v;
|
||||||
|
si += 1;
|
||||||
|
};
|
||||||
let k: i32 = 0;
|
let k: i32 = 0;
|
||||||
for (k < g.pkg[pi].ndeps) {
|
for (k < g.pkg[pi].ndeps) {
|
||||||
if (seploadpkg(g, g.pkg[pi].deps[k], searchpath) < 0) { return -1; };
|
if (seploadpkg(g, g.pkg[pi].deps[k], searchpath) < 0) { return -1; };
|
||||||
@@ -1093,55 +1063,106 @@ fn sepmarkdeps(g: *sepgraph, pi: i32, inset: []u8) void = {
|
|||||||
// //ww:module-reset primary boundary (so -c emits its decls, imported
|
// //ww:module-reset primary boundary (so -c emits its decls, imported
|
||||||
// ==0). DIRECTORY imports are skipped (provided as `.wwi` ahead);
|
// ==0). DIRECTORY imports are skipped (provided as `.wwi` ahead);
|
||||||
// FILE imports fold in (intra-package split).
|
// FILE imports fold in (intra-package split).
|
||||||
fn sepemitbody(fd: i32, path: *u8, visit: *expctx, searchpath: *u8, modpath: *u8) void = {
|
fn sepwriteall(fd: i32, buf: *u8, n: u64) bool = {
|
||||||
|
match (os.writeall(fd, buf, n)) {
|
||||||
|
case let wrote: i64 => return wrote == n: i64;
|
||||||
|
case let e: os.oserror => return false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
fn sepemitbody(fd: i32, path: *u8, visit: *expctx, searchpath: *u8,
|
||||||
|
modpath: *u8) i32 = {
|
||||||
let pview: str;
|
let pview: str;
|
||||||
pview.ptr = path;
|
pview.ptr = path;
|
||||||
pview.len = cstrlen(path): i32;
|
pview.len = cstrlen(path): i32;
|
||||||
let pdup: str = strings.dup(pview);
|
let pdup: str = strings.dup(pview);
|
||||||
if (visitseen(visit, pdup)) { return; };
|
if (visitseen(visit, pdup)) { return 0; };
|
||||||
visitadd(visit, pdup);
|
visitadd(visit, pdup);
|
||||||
let bufp: *u8;
|
let bufp: *u8;
|
||||||
let blen: u64;
|
let blen: u64;
|
||||||
bufp, blen = slurp(path);
|
bufp, blen = slurp(path);
|
||||||
if (bufp == nil) {
|
if (bufp == nil) {
|
||||||
cerr("ww: cannot read source\n");
|
cerr("ww: cannot read source\n");
|
||||||
return;
|
return -1;
|
||||||
};
|
};
|
||||||
let i: u64 = 0u64;
|
let l: syntax.lex;
|
||||||
for (i < blen) {
|
syntax.lexinit(&l, pdup, bufp, blen);
|
||||||
let j: u64 = i;
|
let ps: syntax.parser;
|
||||||
for (j < blen) {
|
syntax.parserinit(&ps, &l);
|
||||||
if (bufp[j] == 10u8) { break; }; // '\n'
|
let imports: *syntax.node = syntax.parseimports(&ps);
|
||||||
j += 1u64;
|
if (l.errs > 0 || ps.errs > 0) { return -1; };
|
||||||
|
let nuse: i32 = 0;
|
||||||
|
let u: *syntax.node = imports.list;
|
||||||
|
for (u != nil) {
|
||||||
|
if (u.kind == syntax.nkind.N_USE) { nuse += 1; };
|
||||||
|
u = u.next;
|
||||||
|
};
|
||||||
|
let uses: []*syntax.node = [];
|
||||||
|
if (nuse > 0) {
|
||||||
|
let allocated: []*syntax.node = alloc([], nuse: u64)!;
|
||||||
|
uses = allocated;
|
||||||
|
uses.len = nuse;
|
||||||
|
};
|
||||||
|
let ui: i32 = 0;
|
||||||
|
u = imports.list;
|
||||||
|
for (u != nil) {
|
||||||
|
if (u.kind == syntax.nkind.N_USE) { uses[ui] = u; ui += 1; };
|
||||||
|
u = u.next;
|
||||||
|
};
|
||||||
|
let si: i32 = 1;
|
||||||
|
for (si < nuse) {
|
||||||
|
let sj: i32 = si;
|
||||||
|
for (sj > 0) {
|
||||||
|
if (strings.compare(uses[sj - 1].usepath,
|
||||||
|
uses[sj].usepath) <= 0) { sj = 0; }
|
||||||
|
else {
|
||||||
|
let t: *syntax.node = uses[sj];
|
||||||
|
uses[sj] = uses[sj - 1];
|
||||||
|
uses[sj - 1] = t;
|
||||||
|
sj -= 1;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
let idp: *u8;
|
si += 1;
|
||||||
let idn: u64;
|
};
|
||||||
idp, idn = scanuse(bufp + i, j - i);
|
ui = 0;
|
||||||
if (idp != nil) {
|
for (ui < nuse) {
|
||||||
|
u = uses[ui];
|
||||||
|
let idp: *u8 = u.usepath.ptr;
|
||||||
|
let idn: u64 = u.usepath.len: u64;
|
||||||
let isdir: i32 = 0;
|
let isdir: i32 = 0;
|
||||||
let ipath: *u8 = locateimport(searchpath, idp, idn, &isdir);
|
let ipath: *u8 = locateimport(searchpath, idp, idn, &isdir);
|
||||||
if (ipath != nil) {
|
if (ipath != nil) {
|
||||||
if (isdir == 0) {
|
if (isdir == 0) {
|
||||||
sepemitbody(fd, ipath, visit, searchpath, modpath);
|
if (sepemitbody(fd, ipath, visit, searchpath,
|
||||||
|
modpath) < 0) { return -1; };
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
ui += 1;
|
||||||
i = j + 1u64;
|
|
||||||
};
|
};
|
||||||
// #57: tag the primary body by its full dotted import path so the
|
// #57: tag the primary body by its full dotted import path so the
|
||||||
// definer mangles == the importer reference; a root build (path "")
|
// definer mangles == the importer reference; a root build (path "")
|
||||||
// stays a bare reset (keeps bare main).
|
// stays a bare reset (keeps bare main).
|
||||||
if (modpath != nil && modpath[0u64] != 0u8) {
|
if (modpath != nil && modpath[0u64] != 0u8) {
|
||||||
let dm: str = "//ww:module-reset ";
|
let dm: str = "//ww:module-reset ";
|
||||||
os.writeall(fd, dm.ptr, dm.len: u64);
|
if (!sepwriteall(fd, dm.ptr, dm.len: u64)
|
||||||
os.writeall(fd, modpath, cstrlen(modpath));
|
|| !sepwriteall(fd, modpath, cstrlen(modpath))
|
||||||
os.writeall(fd, "\n".ptr, 1u64);
|
|| !sepwriteall(fd, "\n".ptr, 1u64)) {
|
||||||
|
cerr("ww: cannot write package unit\n");
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
let d: str = "//ww:module-reset\n";
|
let d: str = "//ww:module-reset\n";
|
||||||
os.writeall(fd, d.ptr, d.len: u64);
|
if (!sepwriteall(fd, d.ptr, d.len: u64)) {
|
||||||
|
cerr("ww: cannot write package unit\n");
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
os.writeall(fd, bufp, blen);
|
if (!sepwriteall(fd, bufp, blen)
|
||||||
os.writeall(fd, "\n".ptr, 1u64);
|
|| !sepwriteall(fd, "\n".ptr, 1u64)) {
|
||||||
|
cerr("ww: cannot write package unit\n");
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Compose pi's sep-unit at `unitf`: the transitive-closure `.wwi`s
|
// Compose pi's sep-unit at `unitf`: the transitive-closure `.wwi`s
|
||||||
@@ -1175,11 +1196,16 @@ fn sepcomposeunit(g: *sepgraph, pi: i32, scratch: *u8, order: []i32,
|
|||||||
return -1;
|
return -1;
|
||||||
};
|
};
|
||||||
let dm: str = "//ww:module ";
|
let dm: str = "//ww:module ";
|
||||||
os.writeall(u, dm.ptr, dm.len: u64);
|
if (!sepwriteall(u, dm.ptr, dm.len: u64)
|
||||||
os.writeall(u, g.pkg[dj].path, cstrlen(g.pkg[dj].path));
|
|| !sepwriteall(u, g.pkg[dj].path,
|
||||||
os.writeall(u, "\n".ptr, 1u64);
|
cstrlen(g.pkg[dj].path))
|
||||||
os.writeall(u, wb, wn);
|
|| !sepwriteall(u, "\n".ptr, 1u64)
|
||||||
os.writeall(u, "\n".ptr, 1u64);
|
|| !sepwriteall(u, wb, wn)
|
||||||
|
|| !sepwriteall(u, "\n".ptr, 1u64)) {
|
||||||
|
cerr("ww: cannot compose package unit\n");
|
||||||
|
os.close(u);
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
oi += 1;
|
oi += 1;
|
||||||
@@ -1188,18 +1214,23 @@ fn sepcomposeunit(g: *sepgraph, pi: i32, scratch: *u8, order: []i32,
|
|||||||
bv.out = u;
|
bv.out = u;
|
||||||
bv.dirs = searchpath;
|
bv.dirs = searchpath;
|
||||||
bv.visit = nil;
|
bv.visit = nil;
|
||||||
|
let bodyrc: i32 = 0;
|
||||||
if (g.pkg[pi].isdir != 0) {
|
if (g.pkg[pi].isdir != 0) {
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
for (i < g.pkg[pi].nsources) {
|
for (i < g.pkg[pi].nsources && bodyrc == 0) {
|
||||||
sepemitbody(u, g.pkg[pi].sources[i], &bv, searchpath,
|
bodyrc = sepemitbody(u, g.pkg[pi].sources[i], &bv, searchpath,
|
||||||
g.pkg[pi].path);
|
g.pkg[pi].path);
|
||||||
i += 1;
|
i += 1;
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
sepemitbody(u, g.pkg[pi].entry, &bv, searchpath, g.pkg[pi].path);
|
bodyrc = sepemitbody(u, g.pkg[pi].entry, &bv, searchpath,
|
||||||
|
g.pkg[pi].path);
|
||||||
};
|
};
|
||||||
os.close(u);
|
if (os.close(u) != 0) {
|
||||||
return 0;
|
cerr("ww: cannot close package unit\n");
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
|
return bodyrc;
|
||||||
};
|
};
|
||||||
|
|
||||||
// archiveo — twin of cmd/ww/main.c archive_o. Writes a deterministic
|
// archiveo — twin of cmd/ww/main.c archive_o. Writes a deterministic
|
||||||
@@ -1268,8 +1299,12 @@ fn archiveo(objpath: *u8, apath: *u8) i32 = {
|
|||||||
cerr("ww: cannot open archive\n");
|
cerr("ww: cannot open archive\n");
|
||||||
return -1;
|
return -1;
|
||||||
};
|
};
|
||||||
os.writeall(fd, out, total);
|
let bad: bool = !sepwriteall(fd, out, total);
|
||||||
os.close(fd);
|
if (os.close(fd) != 0) { bad = true; };
|
||||||
|
if (bad) {
|
||||||
|
cerr("ww: cannot write archive\n");
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1375,12 +1410,14 @@ fn copyfileatomic(src: *u8, dst: *u8) i32 = {
|
|||||||
else { if (n == 0) { done = true; }
|
else { if (n == 0) { done = true; }
|
||||||
else {
|
else {
|
||||||
match (os.writeall(out, buf.ptr, n: u64)) {
|
match (os.writeall(out, buf.ptr, n: u64)) {
|
||||||
case let w: i64 => void;
|
case let w: i64 => {
|
||||||
|
if (w != n) { bad = true; done = true; };
|
||||||
|
};
|
||||||
case let e: os.oserror => { bad = true; done = true; };
|
case let e: os.oserror => { bad = true; done = true; };
|
||||||
};
|
};
|
||||||
}; };
|
}; };
|
||||||
};
|
};
|
||||||
os.close(in);
|
if (os.close(in) != 0) { bad = true; };
|
||||||
if (os.close(out) != 0) { bad = true; };
|
if (os.close(out) != 0) { bad = true; };
|
||||||
if (bad) { return -1; };
|
if (bad) { return -1; };
|
||||||
return os.rename(pathstr(tmpp), pathstr(dst));
|
return os.rename(pathstr(tmpp), pathstr(dst));
|
||||||
@@ -1427,7 +1464,7 @@ fn writestampatomic(path: *u8, want: str) i32 = {
|
|||||||
if (fd < 0) { return -1; };
|
if (fd < 0) { return -1; };
|
||||||
let bad: bool = false;
|
let bad: bool = false;
|
||||||
match (os.writeall(fd, want.ptr, want.len: u64)) {
|
match (os.writeall(fd, want.ptr, want.len: u64)) {
|
||||||
case let w: i64 => void;
|
case let w: i64 => { if (w != want.len: i64) { bad = true; }; };
|
||||||
case let e: os.oserror => { bad = true; };
|
case let e: os.oserror => { bad = true; };
|
||||||
};
|
};
|
||||||
if (os.close(fd) != 0) { bad = true; };
|
if (os.close(fd) != 0) { bad = true; };
|
||||||
@@ -1444,9 +1481,11 @@ fn cerrpath(head: str, path: *u8, tail: str) void = {
|
|||||||
cerr(tail);
|
cerr(tail);
|
||||||
};
|
};
|
||||||
|
|
||||||
fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
|
fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
|
||||||
objstem: *u8, incs: *u8, lf: *lflags, istest: i32, emitasm: i32,
|
rootidentity: *u8, out: *u8,
|
||||||
workdir: *u8, scratchout: **u8, graphout: **sepgraph) i32 = {
|
objstem: *u8, incs: *u8, lf: *lflags, packageonly: i32, istest: i32,
|
||||||
|
emitasm: i32, workdir: *u8, scratchout: **u8,
|
||||||
|
graphout: **sepgraph) i32 = {
|
||||||
let c6: *u8 = joinpathlit(selfdir, "w6c_ww");
|
let c6: *u8 = joinpathlit(selfdir, "w6c_ww");
|
||||||
let a6: *u8 = joinpathlit(selfdir, "w6a_ww");
|
let a6: *u8 = joinpathlit(selfdir, "w6a_ww");
|
||||||
let l6: *u8 = joinpathlit(selfdir, "w6l_ww");
|
let l6: *u8 = joinpathlit(selfdir, "w6l_ww");
|
||||||
@@ -1588,7 +1627,9 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
|
|||||||
pkgslot.len = SEP_MAXPKG;
|
pkgslot.len = SEP_MAXPKG;
|
||||||
let g: *sepgraph = alloc(sepgraph{pkg = pkgslot, n = 0})!;
|
let g: *sepgraph = alloc(sepgraph{pkg = pkgslot, n = 0})!;
|
||||||
if (graphout != nil) { *graphout = g; };
|
if (graphout != nil) { *graphout = g; };
|
||||||
let root: i32 = sepfindoradd(g, "\0".ptr, src, entryisdir);
|
let rootpath: *u8 = "\0".ptr;
|
||||||
|
if (packageonly != 0 && rootidentity != nil) { rootpath = rootidentity; };
|
||||||
|
let root: i32 = sepfindoradd(g, rootpath, src, entryisdir);
|
||||||
if (root < 0) { return 1; };
|
if (root < 0) { return 1; };
|
||||||
// #79 (-T): lib/test is the synth main's `test.run` callee but @test
|
// #79 (-T): lib/test is the synth main's `test.run` callee but @test
|
||||||
// files never `import test;`. Inject it as a direct dep of the root so
|
// files never `import test;`. Inject it as a direct dep of the root so
|
||||||
@@ -1616,6 +1657,11 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
if (seploadpkg(g, root, searchpath.ptr) < 0) { return 1; };
|
if (seploadpkg(g, root, searchpath.ptr) < 0) { return 1; };
|
||||||
|
let rootpackage: bool = packageonly != 0;
|
||||||
|
if (rootpackage && cstreqlit(g.pkg[root].name, "main")) {
|
||||||
|
cerr("ww: -p requires a non-main package\n");
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
|
|
||||||
let ci: i32 = 0;
|
let ci: i32 = 0;
|
||||||
for (ci < g.n) { g.pkg[ci].color = 0; ci += 1; };
|
for (ci < g.n) { g.pkg[ci].color = 0; ci += 1; };
|
||||||
@@ -1625,6 +1671,7 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
|
|||||||
stack.len = g.n;
|
stack.len = g.n;
|
||||||
let norder: i32 = 0;
|
let norder: i32 = 0;
|
||||||
if (septopovisit(g, root, order, &norder, stack, 0) < 0) { return 1; };
|
if (septopovisit(g, root, order, &norder, stack, 0) < 0) { return 1; };
|
||||||
|
if (!rootpackage) { g.pkg[root].path = "\0".ptr; };
|
||||||
|
|
||||||
let oi: i32 = 0;
|
let oi: i32 = 0;
|
||||||
for (oi < norder) {
|
for (oi < norder) {
|
||||||
@@ -1650,6 +1697,8 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
|
|||||||
cu = unitnew; cw = wwinew; cs = asmnew;
|
cu = unitnew; cw = wwinew; cs = asmnew;
|
||||||
co = objnew; ca = anew;
|
co = objnew; ca = anew;
|
||||||
};
|
};
|
||||||
|
let needsexport: bool = (pi != root) || rootpackage;
|
||||||
|
let needsarchive: bool = (pi != root) || rootpackage;
|
||||||
if (sepcomposeunit(g, pi, scratch, order, norder, searchpath.ptr, cu) < 0) {
|
if (sepcomposeunit(g, pi, scratch, order, norder, searchpath.ptr, cu) < 0) {
|
||||||
return 1;
|
return 1;
|
||||||
};
|
};
|
||||||
@@ -1659,7 +1708,7 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
|
|||||||
fresh = fileequal(unitnew, unitf);
|
fresh = fileequal(unitnew, unitf);
|
||||||
if (fresh) { fresh = fileisreg(asmf); };
|
if (fresh) { fresh = fileisreg(asmf); };
|
||||||
if (fresh) {
|
if (fresh) {
|
||||||
if (pi != root) {
|
if (needsexport) {
|
||||||
fresh = fileisreg(wwi);
|
fresh = fileisreg(wwi);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -1669,7 +1718,7 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
if (fresh) {
|
if (fresh) {
|
||||||
if (emitasm == 0 && pi != root) {
|
if (emitasm == 0 && needsarchive) {
|
||||||
fresh = filesizenonzero(apath);
|
fresh = filesizenonzero(apath);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -1695,12 +1744,12 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
|
|||||||
// synthesizes the test main; deps never get -T.
|
// synthesizes the test main; deps never get -T.
|
||||||
let roott: bool = (pi == root) && (istest != 0);
|
let roott: bool = (pi == root) && (istest != 0);
|
||||||
let alen: u64 = 8u64;
|
let alen: u64 = 8u64;
|
||||||
if (pi == root) { alen = 6u64; if (roott) { alen = 7u64; }; };
|
if (!needsexport) { alen = 6u64; if (roott) { alen = 7u64; }; };
|
||||||
let argv: []str = alloc([], alen)!;
|
let argv: []str = alloc([], alen)!;
|
||||||
append(argv, "w6c");
|
append(argv, "w6c");
|
||||||
if (roott) { append(argv, "-T"); };
|
if (roott) { append(argv, "-T"); };
|
||||||
append(argv, "-c");
|
append(argv, "-c");
|
||||||
if (pi != root) {
|
if (needsexport) {
|
||||||
append(argv, "-I");
|
append(argv, "-I");
|
||||||
append(argv, pathstr(cw));
|
append(argv, pathstr(cw));
|
||||||
};
|
};
|
||||||
@@ -1744,7 +1793,7 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
|
|||||||
// the build target), so `main` is defined before any archive is
|
// the build target), so `main` is defined before any archive is
|
||||||
// processed. The link
|
// processed. The link
|
||||||
// consumes `.o`/`.a`, never `.wwi`.
|
// consumes `.o`/`.a`, never `.wwi`.
|
||||||
if (emitasm == 0 && pi != root) {
|
if (emitasm == 0 && needsarchive) {
|
||||||
if (archiveo(co, ca) != 0) {
|
if (archiveo(co, ca) != 0) {
|
||||||
cerr("ww: archive failed\n");
|
cerr("ww: archive failed\n");
|
||||||
return 1;
|
return 1;
|
||||||
@@ -1754,7 +1803,7 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
|
|||||||
// them, unit strictly last.
|
// them, unit strictly last.
|
||||||
if (warm) {
|
if (warm) {
|
||||||
let bad: bool = false;
|
let bad: bool = false;
|
||||||
if (pi != root) {
|
if (needsexport) {
|
||||||
if (os.rename(pathstr(wwinew), pathstr(wwi)) != 0) {
|
if (os.rename(pathstr(wwinew), pathstr(wwi)) != 0) {
|
||||||
bad = true;
|
bad = true;
|
||||||
};
|
};
|
||||||
@@ -1772,7 +1821,7 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
if (!bad) {
|
if (!bad) {
|
||||||
if (emitasm == 0 && pi != root) {
|
if (emitasm == 0 && needsarchive) {
|
||||||
if (os.rename(pathstr(anew), pathstr(apath)) != 0) {
|
if (os.rename(pathstr(anew), pathstr(apath)) != 0) {
|
||||||
bad = true;
|
bad = true;
|
||||||
};
|
};
|
||||||
@@ -1821,6 +1870,17 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
if (emitasm != 0) { return 0; };
|
if (emitasm != 0) { return 0; };
|
||||||
|
if (rootpackage) {
|
||||||
|
let archive: *u8 = sepfname(g, root, scratch, ".a");
|
||||||
|
let iface: *u8 = sepfname(g, root, scratch, ".wwi");
|
||||||
|
let outiface: *u8 = appendlit(out, ".wwi");
|
||||||
|
if (copyfileatomic(archive, out) != 0
|
||||||
|
|| copyfileatomic(iface, outiface) != 0) {
|
||||||
|
cerrpath("ww: cannot write package artifact ", out, "\n");
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
// Reverse-topo link: root `.o` first (order[norder-1]), dependency `.a`
|
// Reverse-topo link: root `.o` first (order[norder-1]), dependency `.a`
|
||||||
// files after, then libwwrt.a (which still
|
// files after, then libwwrt.a (which still
|
||||||
@@ -1895,13 +1955,15 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
|
|||||||
// no-output single-file test remove internal scratch on success and failure.
|
// no-output single-file test remove internal scratch on success and failure.
|
||||||
// The path is non-nil only after this invocation successfully created the
|
// The path is non-nil only after this invocation successfully created the
|
||||||
// exact tree. Twin of the cstage build_one_sep wrapper.
|
// exact tree. Twin of the cstage build_one_sep wrapper.
|
||||||
fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
|
fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32,
|
||||||
objstem: *u8, incs: *u8, lf: *lflags, istest: i32, emitasm: i32,
|
rootidentity: *u8, out: *u8,
|
||||||
keepscratch: i32, workdir: *u8) i32 = {
|
objstem: *u8, incs: *u8, lf: *lflags, packageonly: i32, istest: i32,
|
||||||
|
emitasm: i32, keepscratch: i32, workdir: *u8) i32 = {
|
||||||
let scratch: *u8 = nil;
|
let scratch: *u8 = nil;
|
||||||
let g: *sepgraph = nil;
|
let g: *sepgraph = nil;
|
||||||
let r: i32 = buildonesepimpl(selfdir, src, entryisdir, out, objstem,
|
let r: i32 = buildonesepimpl(selfdir, src, entryisdir, rootidentity,
|
||||||
incs, lf, istest, emitasm, workdir, &scratch, &g);
|
out, objstem,
|
||||||
|
incs, lf, packageonly, istest, emitasm, workdir, &scratch, &g);
|
||||||
sepgraphfree(g);
|
sepgraphfree(g);
|
||||||
if (keepscratch == 0 && scratch != nil) {
|
if (keepscratch == 0 && scratch != nil) {
|
||||||
if (cstrendswithlit(scratch, ".sepwork")) {
|
if (cstrendswithlit(scratch, ".sepwork")) {
|
||||||
@@ -2001,7 +2063,7 @@ fn resolvemodule(selfdir: *u8, name: *u8, incs: *u8, isdir: *i32) *u8 = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
fn writeusage(fd: i32) void = {
|
fn writeusage(fd: i32) void = {
|
||||||
let s: str = "usage: ww [-V] <subcommand> [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 <basename>.ww\n";
|
let s: str = "usage: ww [-V] <subcommand> [args...]\n -V print version and exit\n build [-p] [-S] [-w DIR] [-I DIR] [-o FILE] [path] build a local package graph\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 -p emits a non-main archive FILE + FILE.wwi\n lib/... every package under lib, recursively (test only)\n . build the cwd's <basename>.ww\n";
|
||||||
os.write(fd, s.ptr, s.len: u64);
|
os.write(fd, s.ptr, s.len: u64);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2045,6 +2107,7 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
let outflag: *u8 = nil; // -o target (binary + intermediate stem); T3
|
let outflag: *u8 = nil; // -o target (binary + intermediate stem); T3
|
||||||
let workdir: *u8 = nil; // -w persistent package-artifact workdir
|
let workdir: *u8 = nil; // -w persistent package-artifact workdir
|
||||||
let emitasm: i32 = 0;
|
let emitasm: i32 = 0;
|
||||||
|
let packageonly: i32 = 0;
|
||||||
let incs: []u8 = alloc([], (os.PATH_MAX: u64) * 2u64)!;
|
let incs: []u8 = alloc([], (os.PATH_MAX: u64) * 2u64)!;
|
||||||
incs.len = ((os.PATH_MAX: u64) * 2u64): i32;
|
incs.len = ((os.PATH_MAX: u64) * 2u64): i32;
|
||||||
let incoff: u64 = 0u64;
|
let incoff: u64 = 0u64;
|
||||||
@@ -2064,6 +2127,8 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
if (p[0u64] == 45u8) { // '-'
|
if (p[0u64] == 45u8) { // '-'
|
||||||
if (cstreqlit(p, "-S")) {
|
if (cstreqlit(p, "-S")) {
|
||||||
emitasm = 1;
|
emitasm = 1;
|
||||||
|
} else { if (cstreqlit(p, "-p")) {
|
||||||
|
packageonly = 1;
|
||||||
} else { if (p[1u64] == 73u8) { // '-I'
|
} else { if (p[1u64] == 73u8) { // '-I'
|
||||||
let dir: *u8 = nil;
|
let dir: *u8 = nil;
|
||||||
if (p[2u64] != 0u8) {
|
if (p[2u64] != 0u8) {
|
||||||
@@ -2143,7 +2208,7 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
} else {
|
} else {
|
||||||
cerr("ww build: unknown flag\n");
|
cerr("ww build: unknown flag\n");
|
||||||
return 2;
|
return 2;
|
||||||
}; }; }; }; }; };
|
}; }; }; }; }; }; };
|
||||||
} else {
|
} else {
|
||||||
if (src == nil) { src = p; };
|
if (src == nil) { src = p; };
|
||||||
};
|
};
|
||||||
@@ -2154,12 +2219,26 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
let dot: [2]u8 = ['.': u8, 0u8];
|
let dot: [2]u8 = ['.': u8, 0u8];
|
||||||
src = &dot[0];
|
src = &dot[0];
|
||||||
};
|
};
|
||||||
|
if (packageonly != 0 && emitasm != 0) {
|
||||||
|
cerr("ww build: -p and -S cannot be combined\n");
|
||||||
|
return 2;
|
||||||
|
};
|
||||||
|
let requestedliteral: bool = false;
|
||||||
|
let requestedstat: os.filestat;
|
||||||
|
match (os.stat(&requestedstat, pathstr(src))) {
|
||||||
|
case void => requestedliteral = true;
|
||||||
|
case let e: os.oserror => void;
|
||||||
|
};
|
||||||
let isdir: i32 = 0;
|
let isdir: i32 = 0;
|
||||||
let resolved: *u8 = resolvemodule(selfdir, src, incs.ptr, &isdir);
|
let resolved: *u8 = resolvemodule(selfdir, src, incs.ptr, &isdir);
|
||||||
if (resolved == nil) {
|
if (resolved == nil) {
|
||||||
cerr("ww build: cannot find module\n");
|
cerr("ww build: cannot find module\n");
|
||||||
return 1;
|
return 1;
|
||||||
};
|
};
|
||||||
|
if (packageonly != 0 && isdir == 0) {
|
||||||
|
cerr("ww build: -p needs a package directory\n");
|
||||||
|
return 2;
|
||||||
|
};
|
||||||
let out: *u8 = nil;
|
let out: *u8 = nil;
|
||||||
let objstem: *u8 = nil;
|
let objstem: *u8 = nil;
|
||||||
if (outflag != nil) {
|
if (outflag != nil) {
|
||||||
@@ -2189,8 +2268,11 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
lf.nlibdirs = nlibdirs;
|
lf.nlibdirs = nlibdirs;
|
||||||
lf.libs = libs.ptr;
|
lf.libs = libs.ptr;
|
||||||
lf.nlibs = nlibs;
|
lf.nlibs = nlibs;
|
||||||
return buildonesep(selfdir, resolved, isdir, out, objstem, incs.ptr, &lf,
|
let rootidentity: *u8 = nil;
|
||||||
0i32, emitasm, 1i32, workdir);
|
if (packageonly != 0 && !requestedliteral) { rootidentity = src; };
|
||||||
|
return buildonesep(selfdir, resolved, isdir, rootidentity,
|
||||||
|
out, objstem, incs.ptr, &lf,
|
||||||
|
packageonly, 0i32, emitasm, 1i32, workdir);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Format the owned driver workspace /tmp/<prefix><pid> into buf. Pid is
|
// Format the owned driver workspace /tmp/<prefix><pid> into buf. Pid is
|
||||||
@@ -2354,8 +2436,8 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
lf.libs = libs.ptr;
|
lf.libs = libs.ptr;
|
||||||
lf.nlibs = nlibs;
|
lf.nlibs = nlibs;
|
||||||
// The freshly acquired directory owns both main and main.sepwork.
|
// The freshly acquired directory owns both main and main.sepwork.
|
||||||
if (buildonesep(selfdir, resolved, isdir, outp, outp, incs.ptr, &lf,
|
if (buildonesep(selfdir, resolved, isdir, nil, outp, outp, incs.ptr, &lf,
|
||||||
0i32, 0i32, 0i32, nil) != 0) {
|
0i32, 0i32, 0i32, 0i32, nil) != 0) {
|
||||||
let cleanrc: i32 = os.remove(pathstr(outp));
|
let cleanrc: i32 = os.remove(pathstr(outp));
|
||||||
if (cleanrc != 0 && cleanrc != -2i32) {
|
if (cleanrc != 0 && cleanrc != -2i32) {
|
||||||
cerr("ww: cannot remove temporary output\n");
|
cerr("ww: cannot remove temporary output\n");
|
||||||
@@ -2444,8 +2526,8 @@ fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32,
|
|||||||
lf.nlibs = 0;
|
lf.nlibs = 0;
|
||||||
let keep: i32 = 0;
|
let keep: i32 = 0;
|
||||||
if (outstem != nil) { keep = 1; };
|
if (outstem != nil) { keep = 1; };
|
||||||
let bres: i32 = buildonesep(selfdir, src, 0, outp, objstem, incs, &lf,
|
let bres: i32 = buildonesep(selfdir, src, 0, nil, outp, objstem, incs, &lf,
|
||||||
1i32, emitasm, keep, workdir);
|
0i32, 1i32, emitasm, keep, workdir);
|
||||||
if (bres != 0) {
|
if (bres != 0) {
|
||||||
if (owntmp) {
|
if (owntmp) {
|
||||||
let cleanrc: i32 = os.remove(pathstr(outp));
|
let cleanrc: i32 = os.remove(pathstr(outp));
|
||||||
|
|||||||
502
test/sep/localbuild_test.ww
Normal file
502
test/sep/localbuild_test.ww
Normal file
@@ -0,0 +1,502 @@
|
|||||||
|
package localbuild_test;
|
||||||
|
|
||||||
|
// End-to-end local package builds through both production drivers. Fixtures
|
||||||
|
// are created at runtime so there is no manifest or hidden graph input: every
|
||||||
|
// edge below comes from an ordinary WW import declaration.
|
||||||
|
|
||||||
|
import os;
|
||||||
|
import os.exec;
|
||||||
|
import strings;
|
||||||
|
import testenv;
|
||||||
|
import time;
|
||||||
|
|
||||||
|
fn fail(label: str, why: str) void = {
|
||||||
|
let m: str = strings.concat("localbuild FAIL: ", label, " -- ", why,
|
||||||
|
"\n");
|
||||||
|
os.write(2, m.ptr, m.len: u64);
|
||||||
|
assert(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
fn tmo() time.duration = {
|
||||||
|
return (240i64 * (time.second: i64)): time.duration;
|
||||||
|
};
|
||||||
|
|
||||||
|
fn mkdir(path: str) void = { assert(os.mkdir(path, 493) == 0); };
|
||||||
|
|
||||||
|
fn command(dir: str, name: str, argv: []str,
|
||||||
|
out: *testenv.commandout) void = {
|
||||||
|
testenv.runcommand(dir, dir, name, argv, tmo(), out);
|
||||||
|
};
|
||||||
|
|
||||||
|
fn code(dir: str, name: str, argv: []str) i32 = {
|
||||||
|
let co: testenv.commandout;
|
||||||
|
command(dir, name, argv, &co);
|
||||||
|
if (co.termination != exec.termination.EXIT) { return -1; };
|
||||||
|
return co.code;
|
||||||
|
};
|
||||||
|
|
||||||
|
fn build(dir: str, driver: str, tag: str, target: str, out: str) i32 = {
|
||||||
|
let av: []str = [testenv.driver(driver), "build", "-I", dir,
|
||||||
|
"-o", out, target];
|
||||||
|
return code(dir, strings.concat("build_", tag), av);
|
||||||
|
};
|
||||||
|
|
||||||
|
fn rejectstable(dir: str, label: str, target: str, needle: str) void = {
|
||||||
|
let drivers: []str = ["ww", "ww_ww", "ww", "ww_ww"];
|
||||||
|
let tags: []str = ["c1", "w1", "c2", "w2"];
|
||||||
|
let errors: []str = ["", "", "", ""];
|
||||||
|
let cwork: str = strings.concat(dir, "/work-", label, "-c");
|
||||||
|
let wwork: str = strings.concat(dir, "/work-", label, "-w");
|
||||||
|
mkdir(cwork); mkdir(wwork);
|
||||||
|
let i: i32 = 0;
|
||||||
|
for (i < drivers.len) {
|
||||||
|
let out: str = strings.concat(dir, "/reject-", label, "-", tags[i]);
|
||||||
|
let work: str = cwork;
|
||||||
|
if (i == 1 || i == 3) { work = wwork; };
|
||||||
|
let av: []str = [testenv.driver(drivers[i]), "build", "-I", dir,
|
||||||
|
"-w", work, "-o", out, target];
|
||||||
|
let co: testenv.commandout;
|
||||||
|
command(dir, strings.concat("reject_", label, "_", tags[i]), av,
|
||||||
|
&co);
|
||||||
|
if (co.termination != exec.termination.EXIT || co.code == 0) {
|
||||||
|
fail(label, strings.concat(drivers[i], " accepted invalid graph"));
|
||||||
|
};
|
||||||
|
if (!testenv.has(co.stderr, needle)) {
|
||||||
|
fail(label, strings.concat(drivers[i], " diagnostic missing '",
|
||||||
|
needle, "': ", co.stderr));
|
||||||
|
};
|
||||||
|
errors[i] = co.stderr;
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
if (!testenv.same(errors[0], errors[2])) {
|
||||||
|
fail(label, "C diagnostic changed across repeated builds");
|
||||||
|
};
|
||||||
|
if (!testenv.same(errors[1], errors[3])) {
|
||||||
|
fail(label, "WW diagnostic changed across repeated builds");
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn standalone_and_package_artifact() void = {
|
||||||
|
let td: str = testenv.fresh();
|
||||||
|
let app: str = strings.concat(td, "/app");
|
||||||
|
mkdir(app);
|
||||||
|
testenv.writefile(strings.concat(app, "/main.ww"), strings.concat(
|
||||||
|
"package main;\n",
|
||||||
|
"fn main() i32 = { return 23; };\n"));
|
||||||
|
// Ordinary builds must not accidentally select test-only sources.
|
||||||
|
testenv.writefile(strings.concat(app, "/broken_test.ww"),
|
||||||
|
"package other;\nthis is deliberately invalid\n");
|
||||||
|
let c1: str = strings.concat(td, "/standalone-c1");
|
||||||
|
let c2: str = strings.concat(td, "/standalone-c2");
|
||||||
|
let ww: str = strings.concat(td, "/standalone-ww");
|
||||||
|
if (build(td, "ww", "standalone_c1", app, c1) != 0
|
||||||
|
|| build(td, "ww", "standalone_c2", app, c2) != 0
|
||||||
|
|| build(td, "ww_ww", "standalone_ww", app, ww) != 0) {
|
||||||
|
fail("standalone", "manifest-free directory build failed");
|
||||||
|
};
|
||||||
|
let av1: []str = [c1];
|
||||||
|
let av2: []str = [ww];
|
||||||
|
if (code(td, "run_standalone_c", av1) != 23
|
||||||
|
|| code(td, "run_standalone_ww", av2) != 23) {
|
||||||
|
fail("standalone", "built executable returned the wrong value");
|
||||||
|
};
|
||||||
|
if (!testenv.same(testenv.readfile(c1), testenv.readfile(c2))) {
|
||||||
|
fail("standalone", "two clean builds were not byte-identical");
|
||||||
|
};
|
||||||
|
|
||||||
|
let lib: str = strings.concat(td, "/libonly");
|
||||||
|
mkdir(lib);
|
||||||
|
testenv.writefile(strings.concat(lib, "/lib.ww"), strings.concat(
|
||||||
|
"package libonly;\n",
|
||||||
|
"export fn shown() i32 = { return 7; };\n",
|
||||||
|
"fn hidden() i32 = { return 99; };\n"));
|
||||||
|
let ca: str = strings.concat(td, "/libonly-c.a");
|
||||||
|
let wa: str = strings.concat(td, "/libonly-w.a");
|
||||||
|
let cav: []str = [testenv.driver("ww"), "build", "-p", "-I", td,
|
||||||
|
"-o", ca, lib];
|
||||||
|
let wav: []str = [testenv.driver("ww_ww"), "build", "-p", "-I", td,
|
||||||
|
"-o", wa, lib];
|
||||||
|
if (code(td, "build_package_c", cav) != 0
|
||||||
|
|| code(td, "build_package_w", wav) != 0) {
|
||||||
|
fail("package-artifact", "non-main package build failed");
|
||||||
|
};
|
||||||
|
if (!testenv.exists(strings.concat(ca, ".wwi"))
|
||||||
|
|| !testenv.exists(strings.concat(wa, ".wwi"))) {
|
||||||
|
fail("package-artifact", "missing compiler export sidecar");
|
||||||
|
};
|
||||||
|
if (!testenv.same(testenv.readfile(ca), testenv.readfile(wa))
|
||||||
|
|| !testenv.same(testenv.readfile(strings.concat(ca, ".wwi")),
|
||||||
|
testenv.readfile(strings.concat(wa, ".wwi")))) {
|
||||||
|
fail("package-artifact", "C/WW package products differ");
|
||||||
|
};
|
||||||
|
let iface: str = testenv.readfile(strings.concat(ca, ".wwi"));
|
||||||
|
if (!testenv.has(iface, "shown") || testenv.has(iface, "hidden")) {
|
||||||
|
fail("package-artifact", "export interface crossed the private boundary");
|
||||||
|
};
|
||||||
|
testenv.clean(td);
|
||||||
|
};
|
||||||
|
|
||||||
|
fn writediamond(td: str, reverse: bool) str = {
|
||||||
|
let shared: str = strings.concat(td, "/shared");
|
||||||
|
let left: str = strings.concat(td, "/left");
|
||||||
|
let right: str = strings.concat(td, "/right");
|
||||||
|
let main: str = strings.concat(td, "/main");
|
||||||
|
if (reverse) { mkdir(left); mkdir(main); mkdir(shared); mkdir(right); }
|
||||||
|
else { mkdir(right); mkdir(shared); mkdir(main); mkdir(left); };
|
||||||
|
let zsrc: str = strings.concat(
|
||||||
|
"package shared;\n",
|
||||||
|
"fn hidden() i32 = { return 91; };\n");
|
||||||
|
let asrc: str = strings.concat(
|
||||||
|
"package shared;\n",
|
||||||
|
"export type token = struct { value: i32, };\n",
|
||||||
|
"export fn base() i32 = { return 3; };\n");
|
||||||
|
if (reverse) {
|
||||||
|
testenv.writefile(strings.concat(shared, "/a.ww"), asrc);
|
||||||
|
testenv.writefile(strings.concat(shared, "/z.ww"), zsrc);
|
||||||
|
} else {
|
||||||
|
testenv.writefile(strings.concat(shared, "/z.ww"), zsrc);
|
||||||
|
testenv.writefile(strings.concat(shared, "/a.ww"), asrc);
|
||||||
|
};
|
||||||
|
testenv.writefile(strings.concat(left, "/left.ww"), strings.concat(
|
||||||
|
"package left;\nimport shared;\nimport shared;\n",
|
||||||
|
"export fn value() i32 = { return shared.base() + 1; };\n"));
|
||||||
|
testenv.writefile(strings.concat(right, "/right.ww"), strings.concat(
|
||||||
|
"package right;\nimport shared;\n",
|
||||||
|
"export fn value() i32 = { return shared.base() + 2; };\n"));
|
||||||
|
testenv.writefile(strings.concat(main, "/main.ww"), strings.concat(
|
||||||
|
"package main;\nimport right;\nimport left;\n",
|
||||||
|
"fn main() i32 = { return left.value() + right.value(); };\n"));
|
||||||
|
return main;
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn direct_and_diamond() void = {
|
||||||
|
let td: str = testenv.fresh();
|
||||||
|
let treea: str = strings.concat(td, "/tree-a"); mkdir(treea);
|
||||||
|
let treeb: str = strings.concat(td, "/tree-b"); mkdir(treeb);
|
||||||
|
let main: str = writediamond(treea, false);
|
||||||
|
let shuffled: str = writediamond(treeb, true);
|
||||||
|
let drivers: []str = ["ww", "ww_ww"];
|
||||||
|
let tags: []str = ["c", "w"];
|
||||||
|
let i: i32 = 0;
|
||||||
|
for (i < drivers.len) {
|
||||||
|
let out: str = strings.concat(td, "/diamond-", tags[i]);
|
||||||
|
if (build(treea, drivers[i], strings.concat("diamond_", tags[i]),
|
||||||
|
main, out) != 0) {
|
||||||
|
fail("diamond", strings.concat(drivers[i], " build failed"));
|
||||||
|
};
|
||||||
|
let av: []str = [out];
|
||||||
|
if (code(td, strings.concat("run_diamond_", tags[i]), av) != 9) {
|
||||||
|
fail("diamond", "linked program returned the wrong value");
|
||||||
|
};
|
||||||
|
let scratch: str = strings.concat(out, ".sepwork/");
|
||||||
|
if (!testenv.exists(strings.concat(scratch, "shared.a"))
|
||||||
|
|| testenv.exists(strings.concat(scratch, "shared.shared.a"))) {
|
||||||
|
fail("diamond", "shared package was not interned exactly once");
|
||||||
|
};
|
||||||
|
let unit: str = testenv.readfile(strings.concat(scratch,
|
||||||
|
"__root.unit.ww"));
|
||||||
|
let lp: i32 = testenv.pos(unit, "//ww:module left\n");
|
||||||
|
let rp: i32 = testenv.pos(unit, "//ww:module right\n");
|
||||||
|
if (lp < 0 || rp < 0 || lp >= rp) {
|
||||||
|
fail("diamond", "dependency traversal did not byte-sort imports");
|
||||||
|
};
|
||||||
|
let sharedunit: str = testenv.readfile(strings.concat(scratch,
|
||||||
|
"shared.unit.ww"));
|
||||||
|
let ap: i32 = testenv.pos(sharedunit, "export type token");
|
||||||
|
let zp: i32 = testenv.pos(sharedunit, "fn hidden");
|
||||||
|
if (ap < 0 || zp < 0 || ap >= zp) {
|
||||||
|
fail("diamond", "package sources were not byte-sorted");
|
||||||
|
};
|
||||||
|
let leftiface: str = testenv.readfile(strings.concat(scratch,
|
||||||
|
"left.wwi"));
|
||||||
|
if (testenv.occurrences(leftiface, "import shared;") != 1) {
|
||||||
|
fail("diamond", "duplicate import escaped into export data");
|
||||||
|
};
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
let shuffledout: str = strings.concat(td, "/diamond-c-shuffled");
|
||||||
|
if (build(treeb, "ww", "diamond_c_shuffled", shuffled,
|
||||||
|
shuffledout) != 0) {
|
||||||
|
fail("diamond", "shuffled directory build failed");
|
||||||
|
};
|
||||||
|
let firstout: str = strings.concat(td, "/diamond-c");
|
||||||
|
if (!testenv.same(testenv.readfile(firstout),
|
||||||
|
testenv.readfile(shuffledout))) {
|
||||||
|
fail("diamond", "shuffled directory enumeration changed output");
|
||||||
|
};
|
||||||
|
let firstscratch: str = strings.concat(firstout, ".sepwork/");
|
||||||
|
let shuffledscratch: str = strings.concat(shuffledout, ".sepwork/");
|
||||||
|
if (!testenv.same(testenv.readfile(strings.concat(firstscratch,
|
||||||
|
"__root.unit.ww")), testenv.readfile(strings.concat(shuffledscratch,
|
||||||
|
"__root.unit.ww"))) || !testenv.same(testenv.readfile(strings.concat(
|
||||||
|
firstscratch, "shared.unit.ww")), testenv.readfile(strings.concat(
|
||||||
|
shuffledscratch, "shared.unit.ww")))) {
|
||||||
|
fail("diamond", "shuffled enumeration changed package units");
|
||||||
|
};
|
||||||
|
|
||||||
|
// Count actual compiler actions, rather than inferring interning from
|
||||||
|
// artifact names. The wrapper is selected through the production
|
||||||
|
// driver's existing tool override and records one row per w6c process.
|
||||||
|
let trace: str = strings.concat(td, "/compiler.trace");
|
||||||
|
let wrapper: str = strings.concat(td, "/trace-w6c.sh");
|
||||||
|
testenv.writefile(trace, "");
|
||||||
|
testenv.writefile(wrapper, strings.concat(
|
||||||
|
"printf '%s\\n' \"$*\" >> \"$WW_LOCALBUILD_TRACE\"\n",
|
||||||
|
"exec \"$WW_LOCALBUILD_W6C\" \"$@\"\n"));
|
||||||
|
let baseenv: []str = os.getenvs();
|
||||||
|
let env: []str = alloc([], (baseenv.len + 3): u64)!;
|
||||||
|
let ei: i32 = 0;
|
||||||
|
for (ei < baseenv.len) {
|
||||||
|
if (!strings.hasprefix(baseenv[ei], "WW_W6C=")
|
||||||
|
&& !strings.hasprefix(baseenv[ei], "WW_LOCALBUILD_TRACE=")
|
||||||
|
&& !strings.hasprefix(baseenv[ei], "WW_LOCALBUILD_W6C=")) {
|
||||||
|
append(env, baseenv[ei]);
|
||||||
|
};
|
||||||
|
ei += 1;
|
||||||
|
};
|
||||||
|
append(env, strings.concat("WW_W6C=/bin/sh ", wrapper));
|
||||||
|
append(env, strings.concat("WW_LOCALBUILD_TRACE=", trace));
|
||||||
|
append(env, strings.concat("WW_LOCALBUILD_W6C=",
|
||||||
|
testenv.driver("w6c")));
|
||||||
|
let countedout: str = strings.concat(td, "/diamond-counted");
|
||||||
|
let countedav: []str = [testenv.driver("ww"), "build", "-I", treea,
|
||||||
|
"-o", countedout, main];
|
||||||
|
let counted: testenv.commandout;
|
||||||
|
testenv.runcommandenv(td, td, "diamond_compile_count", countedav,
|
||||||
|
env, tmo(), &counted);
|
||||||
|
if (counted.termination != exec.termination.EXIT || counted.code != 0) {
|
||||||
|
fail("diamond", "instrumented build failed");
|
||||||
|
};
|
||||||
|
if (testenv.occurrences(testenv.readfile(trace), "shared.unit.ww") != 1) {
|
||||||
|
fail("diamond", "shared package was not compiled exactly once");
|
||||||
|
};
|
||||||
|
testenv.clean(td);
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn builtin_type_precedence() void = {
|
||||||
|
let td: str = testenv.fresh();
|
||||||
|
let dep: str = strings.concat(td, "/builtinshadow"); mkdir(dep);
|
||||||
|
let main: str = strings.concat(td, "/main"); mkdir(main);
|
||||||
|
testenv.writefile(strings.concat(dep, "/dep.ww"), strings.concat(
|
||||||
|
"package builtinshadow;\n",
|
||||||
|
"export type i32 = i8;\n",
|
||||||
|
"export fn width() i32 = { return size(i32): i32; };\n"));
|
||||||
|
testenv.writefile(strings.concat(main, "/main.ww"), strings.concat(
|
||||||
|
"package main;\nimport builtinshadow;\n",
|
||||||
|
"fn main() i32 = { return builtinshadow.width(); };\n"));
|
||||||
|
let drivers: []str = ["ww", "ww_ww"];
|
||||||
|
let tags: []str = ["c", "w"];
|
||||||
|
let outputs: []str = ["", ""];
|
||||||
|
let i: i32 = 0;
|
||||||
|
for (i < drivers.len) {
|
||||||
|
outputs[i] = strings.concat(td, "/builtin-", tags[i]);
|
||||||
|
if (build(td, drivers[i], strings.concat("builtin_", tags[i]),
|
||||||
|
main, outputs[i]) != 0) {
|
||||||
|
fail("builtin-type", strings.concat(drivers[i], " build failed"));
|
||||||
|
};
|
||||||
|
let av: []str = [outputs[i]];
|
||||||
|
if (code(td, strings.concat("run_builtin_", tags[i]), av) != 4) {
|
||||||
|
fail("builtin-type", strings.concat(drivers[i],
|
||||||
|
" let an imported type shadow intrinsic i32"));
|
||||||
|
};
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
if (!testenv.same(testenv.readfile(outputs[0]),
|
||||||
|
testenv.readfile(outputs[1]))) {
|
||||||
|
fail("builtin-type", "C/WW products differ");
|
||||||
|
};
|
||||||
|
testenv.clean(td);
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn export_visibility() void = {
|
||||||
|
let td: str = testenv.fresh();
|
||||||
|
let main: str = writediamond(td, false);
|
||||||
|
// The successful diamond already proves a direct exported declaration.
|
||||||
|
testenv.writefile(strings.concat(td, "/private.ww"), strings.concat(
|
||||||
|
"package main;\nimport shared;\n",
|
||||||
|
"fn main() i32 = { return shared.hidden(); };\n"));
|
||||||
|
rejectstable(td, "private", strings.concat(td, "/private.ww"),
|
||||||
|
"has no exported declaration 'hidden'");
|
||||||
|
testenv.writefile(strings.concat(td, "/transitive.ww"), strings.concat(
|
||||||
|
"package main;\nimport left;\n",
|
||||||
|
"fn main() i32 = { return shared.base(); };\n"));
|
||||||
|
rejectstable(td, "transitive", strings.concat(td, "/transitive.ww"),
|
||||||
|
"package 'shared' is not directly imported");
|
||||||
|
testenv.writefile(strings.concat(td, "/transitive-bare-value.ww"),
|
||||||
|
strings.concat("package main;\nimport left;\n",
|
||||||
|
"fn main() i32 = { return base(); };\n"));
|
||||||
|
rejectstable(td, "transitive-bare-value",
|
||||||
|
strings.concat(td, "/transitive-bare-value.ww"), "undefined: base");
|
||||||
|
testenv.writefile(strings.concat(td, "/transitive-bare-type.ww"),
|
||||||
|
strings.concat("package main;\nimport left;\n",
|
||||||
|
"fn main() i32 = { let x: token; return 0; };\n"));
|
||||||
|
rejectstable(td, "transitive-bare-type",
|
||||||
|
strings.concat(td, "/transitive-bare-type.ww"),
|
||||||
|
"unknown type 'token'");
|
||||||
|
// Keep the helper's main directory live so the graph also has a normal
|
||||||
|
// successful consumer in this fixture tree.
|
||||||
|
assert(testenv.exists(main));
|
||||||
|
testenv.clean(td);
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn import_diagnostics() void = {
|
||||||
|
let td: str = testenv.fresh();
|
||||||
|
let missing: str = strings.concat(td, "/missing"); mkdir(missing);
|
||||||
|
testenv.writefile(strings.concat(missing, "/main.ww"), strings.concat(
|
||||||
|
"package main;\nimport nowhere;\n",
|
||||||
|
"fn main() i32 = { return 0; };\n"));
|
||||||
|
rejectstable(td, "missing", missing, "cannot find package nowhere");
|
||||||
|
|
||||||
|
let self: str = strings.concat(td, "/self"); mkdir(self);
|
||||||
|
testenv.writefile(strings.concat(self, "/main.ww"), strings.concat(
|
||||||
|
"package main;\nimport self;\n",
|
||||||
|
"fn main() i32 = { return 0; };\n"));
|
||||||
|
rejectstable(td, "self", self, "self-import");
|
||||||
|
|
||||||
|
let malformed: str = strings.concat(td, "/malformed"); mkdir(malformed);
|
||||||
|
testenv.writefile(strings.concat(malformed, "/main.ww"), strings.concat(
|
||||||
|
"package main;\nimport ;\n",
|
||||||
|
"fn main() i32 = { return 0; };\n"));
|
||||||
|
rejectstable(td, "malformed", malformed,
|
||||||
|
"main.ww:2:8: error: expected identifier");
|
||||||
|
|
||||||
|
let attributed: str = strings.concat(td, "/attributed"); mkdir(attributed);
|
||||||
|
testenv.writefile(strings.concat(attributed, "/main.ww"), strings.concat(
|
||||||
|
"package main;\n@trace import nowhere;\n",
|
||||||
|
"fn main() i32 = { return 0; };\n"));
|
||||||
|
rejectstable(td, "attributed", attributed,
|
||||||
|
"import cannot be exported or attributed");
|
||||||
|
|
||||||
|
let exported: str = strings.concat(td, "/exported"); mkdir(exported);
|
||||||
|
testenv.writefile(strings.concat(exported, "/main.ww"), strings.concat(
|
||||||
|
"package main;\nexport import nowhere;\n",
|
||||||
|
"fn main() i32 = { return 0; };\n"));
|
||||||
|
rejectstable(td, "exported", exported,
|
||||||
|
"main.ww:2:8: error: import cannot be exported or attributed");
|
||||||
|
|
||||||
|
let conflict: str = strings.concat(td, "/conflict"); mkdir(conflict);
|
||||||
|
testenv.writefile(strings.concat(conflict, "/a.ww"),
|
||||||
|
"package main;\nfn main() i32 = { return 0; };\n");
|
||||||
|
testenv.writefile(strings.concat(conflict, "/z.ww"),
|
||||||
|
"package other;\nfn spare() void = { };\n");
|
||||||
|
rejectstable(td, "conflict", conflict, "conflicting package names");
|
||||||
|
|
||||||
|
let real: str = strings.concat(td, "/real"); mkdir(real);
|
||||||
|
testenv.writefile(strings.concat(real, "/real.ww"), strings.concat(
|
||||||
|
"package real;\n",
|
||||||
|
"export fn value() i32 = { return 1; };\n"));
|
||||||
|
let alias: str = strings.concat(td, "/alias");
|
||||||
|
assert(os.symlink(real, alias) == 0);
|
||||||
|
let aliasmain: str = strings.concat(td, "/alias-main"); mkdir(aliasmain);
|
||||||
|
testenv.writefile(strings.concat(aliasmain, "/main.ww"), strings.concat(
|
||||||
|
"package main;\nimport real;\nimport alias;\n",
|
||||||
|
"fn main() i32 = { return 0; };\n"));
|
||||||
|
rejectstable(td, "identity-alias", aliasmain, "identities");
|
||||||
|
testenv.clean(td);
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn package_artifact_identity_and_flag_contract() void = {
|
||||||
|
let td: str = testenv.fresh();
|
||||||
|
let root: str = strings.concat(td, "/root"); mkdir(root);
|
||||||
|
let foo: str = strings.concat(root, "/foo"); mkdir(foo);
|
||||||
|
let bar: str = strings.concat(foo, "/bar"); mkdir(bar);
|
||||||
|
testenv.writefile(strings.concat(bar, "/bar.ww"), strings.concat(
|
||||||
|
"package bar;\n",
|
||||||
|
"export fn nestedvalue() i32 = { return 41; };\n"));
|
||||||
|
let ca: str = strings.concat(td, "/nested-c.a");
|
||||||
|
let wa: str = strings.concat(td, "/nested-w.a");
|
||||||
|
let cav: []str = [testenv.driver("ww"), "build", "-p", "-I", root,
|
||||||
|
"-o", ca, "foo.bar"];
|
||||||
|
let wav: []str = [testenv.driver("ww_ww"), "build", "-p", "-I", root,
|
||||||
|
"-o", wa, "foo.bar"];
|
||||||
|
if (code(td, "nested_package_c", cav) != 0
|
||||||
|
|| code(td, "nested_package_w", wav) != 0) {
|
||||||
|
fail("package-identity", "nested logical package build failed");
|
||||||
|
};
|
||||||
|
if (!testenv.same(testenv.readfile(ca), testenv.readfile(wa))
|
||||||
|
|| !testenv.same(testenv.readfile(strings.concat(ca, ".wwi")),
|
||||||
|
testenv.readfile(strings.concat(wa, ".wwi")))) {
|
||||||
|
fail("package-identity", "nested C/WW package products differ");
|
||||||
|
};
|
||||||
|
let nestedasm: str = testenv.readfile(strings.concat(ca,
|
||||||
|
".sepwork/foo.bar.s"));
|
||||||
|
if (!testenv.has(nestedasm, "foo.bar.nestedvalue")) {
|
||||||
|
fail("package-identity", "archive lost the full logical import path");
|
||||||
|
};
|
||||||
|
|
||||||
|
let drivers: []str = ["ww", "ww_ww"];
|
||||||
|
let i: i32 = 0;
|
||||||
|
for (i < drivers.len) {
|
||||||
|
let badout: str = strings.concat(td, "/bad-flags-", drivers[i]);
|
||||||
|
let av: []str = [testenv.driver(drivers[i]), "build", "-p", "-S",
|
||||||
|
"-I", root, "-o", badout, "foo.bar"];
|
||||||
|
let co: testenv.commandout;
|
||||||
|
command(td, strings.concat("package_flag_contract_", drivers[i]),
|
||||||
|
av, &co);
|
||||||
|
if (co.termination != exec.termination.EXIT || co.code == 0
|
||||||
|
|| !testenv.has(co.stderr,
|
||||||
|
"ww build: -p and -S cannot be combined")) {
|
||||||
|
fail("package-flags", strings.concat(drivers[i],
|
||||||
|
" accepted -p -S"));
|
||||||
|
};
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
testenv.clean(td);
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn generated_test_hook_is_not_a_user_exemption() void = {
|
||||||
|
let td: str = testenv.fresh();
|
||||||
|
let src: str = strings.concat(td, "/case.ww");
|
||||||
|
testenv.writefile(src, strings.concat(
|
||||||
|
"package probe;\nimport test;\n",
|
||||||
|
"fn bad() void = { test.run(); };\n",
|
||||||
|
"@test fn one() void = {};\n"));
|
||||||
|
let compilers: []str = ["w6c", "w6c_ww"];
|
||||||
|
let i: i32 = 0;
|
||||||
|
for (i < compilers.len) {
|
||||||
|
let asm: str = strings.concat(td, "/case-", compilers[i], ".s");
|
||||||
|
let av: []str = [testenv.driver(compilers[i]), "-T", "-c", "-o",
|
||||||
|
asm, src];
|
||||||
|
let co: testenv.commandout;
|
||||||
|
command(td, strings.concat("test_hook_", compilers[i]), av, &co);
|
||||||
|
if (co.termination != exec.termination.EXIT || co.code == 0
|
||||||
|
|| !testenv.has(co.stderr,
|
||||||
|
"package 'test' has no exported declaration 'run'")) {
|
||||||
|
fail("test-hook", strings.concat(compilers[i],
|
||||||
|
" exempted a user-authored test.run"));
|
||||||
|
};
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
testenv.clean(td);
|
||||||
|
};
|
||||||
|
|
||||||
|
fn writecyclepkg(td: str, name: str, next: str) void = {
|
||||||
|
let dir: str = strings.concat(td, "/", name); mkdir(dir);
|
||||||
|
testenv.writefile(strings.concat(dir, "/p.ww"), strings.concat(
|
||||||
|
"package ", name, ";\nimport ", next, ";\n",
|
||||||
|
"export fn value() i32 = { return 1; };\n"));
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn cycle_diagnostics() void = {
|
||||||
|
let td: str = testenv.fresh();
|
||||||
|
let two: str = strings.concat(td, "/two-main"); mkdir(two);
|
||||||
|
testenv.writefile(strings.concat(two, "/main.ww"),
|
||||||
|
"package main;\nimport aa;\nfn main() i32 = { return 0; };\n");
|
||||||
|
writecyclepkg(td, "aa", "bb");
|
||||||
|
writecyclepkg(td, "bb", "aa");
|
||||||
|
rejectstable(td, "cycle-two", two,
|
||||||
|
"dependency cycle: aa -> bb -> aa");
|
||||||
|
rejectstable(td, "cycle-root", strings.concat(td, "/aa"),
|
||||||
|
"dependency cycle: aa -> bb -> aa");
|
||||||
|
|
||||||
|
// A second isolated root set avoids reusing the two-package graph.
|
||||||
|
let longroot: str = strings.concat(td, "/long-main"); mkdir(longroot);
|
||||||
|
testenv.writefile(strings.concat(longroot, "/main.ww"),
|
||||||
|
"package main;\nimport ca;\nfn main() i32 = { return 0; };\n");
|
||||||
|
writecyclepkg(td, "ca", "cb");
|
||||||
|
writecyclepkg(td, "cb", "cc");
|
||||||
|
writecyclepkg(td, "cc", "ca");
|
||||||
|
rejectstable(td, "cycle-long", longroot,
|
||||||
|
"dependency cycle: ca -> cb -> cc -> ca");
|
||||||
|
testenv.clean(td);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user