driver: ignore package documentation sources

This commit is contained in:
2026-08-23 11:29:25 +09:00
parent f6fabfc6ac
commit e67b8e1bc4
8 changed files with 2419 additions and 389 deletions

View File

@@ -42,9 +42,15 @@ static const char *usage =
static char *self_dir;
static const char *self_path;
static struct {
char *path;
char *buf;
u64 len;
} source_documentation_preload;
static char *sep_sprintf(const char *, ...);
static int sep_reserve(void **, int *, int, size_t);
static int sep_fail_size(void);
static void source_operand_no_sources(const char *);
static const char *
envpath(const char *name)
@@ -536,11 +542,8 @@ static int sep_slurp(const char*, char**, u64*);
* rather than a textual attribute scan, whether a production source contains
* @test; otherwise valid whitespace/comments could silently drop a test. */
static int
source_has_test_decl(const char *path)
source_has_test_decl(const char *path, char *buf, u64 len)
{
char *buf;
u64 len;
if (sep_slurp(path, &buf, &len) < 0) return -1;
/* Keep directory-loader package-clause diagnostics stable. The full
* parser reports its language-level "missing package clause" first;
* the imports-only pass owns the package-loader wording and also avoids
@@ -553,14 +556,12 @@ source_has_test_decl(const char *path)
Node *imports = parseimports(&ip);
if (il.errs || ip.errs) {
freearena(ia);
free(buf);
return -1;
}
if (imports->module == NULL) {
Pos pp = { path, 1, 1 };
errorf(pp, "invalid or missing package clause");
freearena(ia);
free(buf);
return -1;
}
freearena(ia);
@@ -572,7 +573,6 @@ source_has_test_decl(const char *path)
Node *file = parsefile(&p);
if (l.errs || p.errs) {
freearena(a);
free(buf);
return -1;
}
int found = 0;
@@ -584,7 +584,6 @@ source_has_test_decl(const char *path)
break;
}
freearena(a);
free(buf);
return found;
}
@@ -655,14 +654,8 @@ sep_reserve(void **buf, int *cap, int need, size_t elemsz)
* The coordinator chooses variants, but the command owns which real source
* paths enter a package compilation. */
static int
source_package_name(const char *path, char **out)
source_package_name(const char *path, char *buf, u64 len, char **out)
{
char *buf;
u64 len;
if (sep_slurp(path, &buf, &len) < 0) {
fprintf(stderr, "ww: cannot read %s\n", path);
return -1;
}
Arena *a = newarena();
Lex l;
Parser p;
@@ -671,25 +664,21 @@ source_package_name(const char *path, char **out)
Node *imports = parseimports(&p);
if (l.errs || p.errs) {
freearena(a);
free(buf);
return -1;
}
if (imports->module == NULL) {
Pos pp = { path, 1, 1 };
errorf(pp, "invalid or missing package clause");
freearena(a);
free(buf);
return -1;
}
*out = strdup(imports->module);
if (*out == NULL) {
sep_fail_nomem();
freearena(a);
free(buf);
return -1;
}
freearena(a);
free(buf);
return 0;
}
@@ -798,6 +787,205 @@ source_build_header(const char *path)
return 0;
}
/* Go 1.26.5 reserves the exact package name "documentation" for files that
* are ignored by the package loader. Avoid parsing ordinary sources on this
* early selection pass: only a quiet, exact package-clause candidate enters
* the narrow package/import-header parser. */
static int
source_documentation_skip_trivia(const char *src, u64 len, u64 *at)
{
u64 i = *at;
for (;;) {
while (i < len && (src[i] == ' ' || src[i] == '\t'
|| src[i] == '\r' || src[i] == '\n'))
i++;
if (i + 1 < len && src[i] == '/' && src[i + 1] == '/') {
i += 2;
while (i < len && src[i] != '\n') i++;
continue;
}
if (i + 1 < len && src[i] == '/' && src[i + 1] == '*') {
i += 2;
while (i + 1 < len
&& !(src[i] == '*' && src[i + 1] == '/'))
i++;
if (i + 1 >= len) return 0;
i += 2;
continue;
}
break;
}
*at = i;
return 1;
}
static int
source_documentation_ident(unsigned char c)
{
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
|| (c >= '0' && c <= '9') || c == '_';
}
static int
source_documentation_word(const char *src, u64 len, u64 *at,
const char *word)
{
u64 n = (u64)strlen(word), i = *at;
if (i > len || len - i < n || memcmp(src + i, word, (size_t)n) != 0)
return 0;
if (len - i > n
&& source_documentation_ident((unsigned char)src[i + n]))
return 0;
*at = i + n;
return 1;
}
static int
source_documentation_candidate(const char *src, u64 len)
{
u64 at = 0;
if (len >= 3 && (unsigned char)src[0] == 0xef
&& (unsigned char)src[1] == 0xbb
&& (unsigned char)src[2] == 0xbf)
at = 3;
if (!source_documentation_skip_trivia(src, len, &at)
|| !source_documentation_word(src, len, &at, "package")
|| !source_documentation_skip_trivia(src, len, &at)
|| !source_documentation_word(src, len, &at, "documentation")
|| !source_documentation_skip_trivia(src, len, &at))
return 0;
return at < len && src[at] == ';';
}
/* -1: diagnosed header error; 0: ordinary source; 1: documentation source.
* The caller owns BUF. The raw-'i' arm mirrors readGoInfo's attempt to read
* another import before it knows whether the complete keyword follows. Exact
* import tokens have already been consumed (or diagnosed) by the parser. */
static int
source_documentation_buffer(const char *path, char *buf, u64 len)
{
if (!source_documentation_candidate(buf, len)) return 0;
Arena *a = newarena();
Lex l;
Parser p;
lexinit(&l, a, path, buf, len);
parserinit(&p, a, &l);
Node *header = parsepackageheader(&p);
if (l.errs || p.errs) {
freearena(a);
return -1;
}
if (header->module == NULL) {
Pos pp = { path, 1, 1 };
errorf(pp, "invalid or missing package clause");
freearena(a);
return -1;
}
if (l.pos < len && buf[l.pos] == 'i') {
Pos ip = { path, l.line, l.col };
errorf(ip, "expected top-level decl");
freearena(a);
return -1;
}
int documentation = strcmp(header->module, "documentation") == 0;
freearena(a);
return documentation;
}
static void
source_documentation_preload_clear(void)
{
free(source_documentation_preload.path);
free(source_documentation_preload.buf);
memset(&source_documentation_preload, 0,
sizeof source_documentation_preload);
}
/* A regular named root is read exactly once. An ordinary result carries the
* same bytes into sep_scan_file; a documentation result is rejected before a
* run/test scratch directory can be acquired. Nonregular sources never enter
* this preflight and retain their established stream/error route. */
static int
source_preload_documentation(const char *path)
{
source_documentation_preload_clear();
char *buf;
u64 len;
if (sep_slurp(path, &buf, &len) < 0) {
fprintf(stderr, "ww: cannot read %s\n", path);
return -1;
}
int documentation = source_documentation_buffer(path, buf, len);
if (documentation != 0) {
free(buf);
return documentation;
}
char *copy = strdup(path);
if (copy == NULL) {
sep_fail_nomem();
free(buf);
return -1;
}
source_documentation_preload.path = copy;
source_documentation_preload.buf = buf;
source_documentation_preload.len = len;
return 0;
}
static int
source_regular_file(const char *path)
{
struct stat st;
return stat(path, &st) == 0 && S_ISREG(st.st_mode);
}
struct sepsource {
char *path;
char *buf;
u64 len;
};
struct sepdirobs_source {
char *name; /* one eligible directory member name */
char *path; /* first request spelling, for diagnostics */
char *buf; /* exact regular-file bytes, once observed */
u64 len;
char *package; /* test source's once-parsed package name */
int is_test;
int observed; /* stat/read/classification has run */
int result; /* -2 diagnosed, 0 ignored, 1 ordinary */
};
struct sepdirobs {
char *canon; /* canonical directory cache key */
char *entry; /* first request spelling */
struct sepdirobs_source *source;
int nsource;
int sourcecap;
int enumerated;
int enum_result; /* -1 unreadable directory, 0 success */
};
static int
source_snapshot_add(struct sepsource **list, int *n, int *cap,
const char *path, const char *buf, u64 len)
{
if (*n == INT_MAX) return sep_fail_size();
if (sep_reserve((void **)list, cap, *n + 1, sizeof **list) < 0)
return -1;
char *pathcopy = strdup(path);
char *bufcopy = malloc((size_t)len + 1);
if (pathcopy == NULL || bufcopy == NULL) {
free(pathcopy);
free(bufcopy);
return sep_fail_nomem();
}
memcpy(bufcopy, buf, (size_t)len + 1);
(*list)[*n] = (struct sepsource){ pathcopy, bufcopy, len };
(*n)++;
return 0;
}
static int
source_list_add(char ***list, int *n, int *cap, const char *path)
{
@@ -823,15 +1011,14 @@ source_list_free(char **list, int n)
* byte-sorted so compiler test discovery is deterministic without generated
* package amalgamation. */
static int
enumerate_dir_ww(const char *dirpath, int variant, const char *test_package,
char ***out_files)
sep_dir_membership(struct sepdirobs *obs)
{
DIR *d = opendir(dirpath);
if (d == NULL) { *out_files = NULL; return -1; }
if (obs->enumerated) return obs->enum_result;
obs->enumerated = 1;
DIR *d = opendir(obs->entry);
if (d == NULL) return obs->enum_result = -1;
char **names = NULL;
int nnames = 0, capnames = 0;
char **prod = NULL, **tests = NULL;
int nprod = 0, capprod = 0, ntests = 0, captests = 0;
struct dirent *ent = NULL;
for (;;) {
errno = 0;
@@ -842,147 +1029,104 @@ enumerate_dir_ww(const char *dirpath, int variant, const char *test_package,
if (nl <= 3) continue;
if (nm[0] == '.' || nm[0] == '_') continue;
if (strcmp(nm + nl - 3, ".ww") != 0) continue;
if (!sep_source_matches_target(nm)) continue;
if (source_list_add(&names, &nnames, &capnames, nm) < 0) {
source_list_free(names, nnames);
closedir(d);
*out_files = NULL;
return -2;
return obs->enum_result = -2;
}
}
int direrr = errno;
closedir(d);
if (direrr != 0) {
source_list_free(names, nnames);
*out_files = NULL;
return -1;
return obs->enum_result = -1;
}
if (nnames > 1) qsort(names, (size_t)nnames, sizeof *names, strs_cmp);
for (int ni = 0; ni < nnames; ni++) {
const char *nm = names[ni];
size_t nl = strlen(nm);
if (!sep_source_matches_target(nm)) continue;
int is_test = nl >= 8 && strcmp(nm + nl - 8, "_test.ww") == 0;
if (variant == SEP_VARIANT_PRODUCTION && is_test) continue;
if (variant == SEP_VARIANT_EXTERNAL && !is_test) continue;
char path[PATH_MAX];
int pn = snprintf(path, sizeof path, "%s/%s", dirpath, nm);
if (pn < 0 || (size_t)pn >= sizeof path) {
fprintf(stderr, "ww: package source path is too long\n");
if (obs->nsource == INT_MAX
|| sep_reserve((void **)&obs->source, &obs->sourcecap,
obs->nsource + 1, sizeof *obs->source) < 0) {
source_list_free(names, nnames);
source_list_free(prod, nprod);
source_list_free(tests, ntests);
*out_files = NULL;
return -2;
return obs->enum_result = -2;
}
struct stat st;
if (lstat(path, &st) != 0) {
fprintf(stderr,
"ww: %s: package source is not a regular file\n", path);
struct sepdirobs_source *s = &obs->source[obs->nsource];
memset(s, 0, sizeof *s);
obs->nsource++;
s->name = strdup(nm);
if (s->name == NULL) {
sep_fail_nomem();
source_list_free(names, nnames);
source_list_free(prod, nprod);
source_list_free(tests, ntests);
*out_files = NULL;
return -2;
}
if (S_ISLNK(st.st_mode)) {
if (stat(path, &st) != 0) {
fprintf(stderr,
"ww: %s: package source is not a regular file\n",
path);
source_list_free(names, nnames);
source_list_free(prod, nprod);
source_list_free(tests, ntests);
*out_files = NULL;
return -2;
}
/* go/build ignores a source-shaped symlink to a directory. */
if (S_ISDIR(st.st_mode)) continue;
}
if (!S_ISREG(st.st_mode)) {
fprintf(stderr,
"ww: %s: package source is not a regular file\n", path);
source_list_free(names, nnames);
source_list_free(prod, nprod);
source_list_free(tests, ntests);
*out_files = NULL;
return -2;
}
int has_test = !is_test ? source_has_test_decl(path) : 0;
if (has_test < 0) {
source_list_free(names, nnames);
source_list_free(prod, nprod);
source_list_free(tests, ntests);
*out_files = NULL;
return -2;
}
if (has_test > 0) {
fprintf(stderr,
"ww: %s: @test declaration outside *_test.ww\n",
path);
source_list_free(names, nnames);
source_list_free(prod, nprod);
source_list_free(tests, ntests);
*out_files = NULL;
return -2;
}
if (is_test) {
char *package = NULL;
if (source_package_name(path, &package) < 0) {
source_list_free(names, nnames);
source_list_free(prod, nprod);
source_list_free(tests, ntests);
*out_files = NULL;
return -2;
}
if (test_package == NULL || strcmp(package, test_package) != 0) {
free(package);
continue;
}
free(package);
}
char ***list = is_test ? &tests : &prod;
int *n = is_test ? &ntests : &nprod;
int *cap = is_test ? &captests : &capprod;
if (source_list_add(list, n, cap, path) < 0) {
source_list_free(names, nnames);
source_list_free(prod, nprod);
source_list_free(tests, ntests);
*out_files = NULL;
return -2;
return obs->enum_result = -2;
}
s->is_test = nl >= 8 && strcmp(nm + nl - 8, "_test.ww") == 0;
}
source_list_free(names, nnames);
if (nprod > 1) qsort(prod, (size_t)nprod, sizeof *prod, strs_cmp);
if (ntests > 1) qsort(tests, (size_t)ntests, sizeof *tests, strs_cmp);
if (nprod > INT_MAX - ntests) {
sep_fail_size();
source_list_free(prod, nprod);
source_list_free(tests, ntests);
*out_files = NULL;
return -2;
return obs->enum_result = 0;
}
/* Observe a member lazily, after variant eligibility. The cached result is
* authoritative for the rest of this request, including documentation and
* diagnosed failures, so later variants cannot restat or reread the path. */
static int
sep_dir_observe_source(struct sepdirobs *obs, struct sepdirobs_source *s)
{
if (s->observed) return s->result;
s->observed = 1;
char path[PATH_MAX];
int pn = snprintf(path, sizeof path, "%s/%s", obs->entry, s->name);
if (pn < 0 || (size_t)pn >= sizeof path) {
fprintf(stderr, "ww: package source path is too long\n");
return s->result = -2;
}
int total = nprod + ntests;
if ((size_t)total > (size_t)-1 / sizeof *prod) {
sep_fail_size();
source_list_free(prod, nprod);
source_list_free(tests, ntests);
*out_files = NULL;
return -2;
}
char **all = total ? malloc((size_t)total * sizeof *all) : NULL;
if (total && all == NULL) {
s->path = strdup(path);
if (s->path == NULL) {
sep_fail_nomem();
source_list_free(prod, nprod);
source_list_free(tests, ntests);
*out_files = NULL;
return -2;
return s->result = -2;
}
for (int i = 0; i < nprod; i++) all[i] = prod[i];
for (int i = 0; i < ntests; i++) all[nprod + i] = tests[i];
free(prod);
free(tests);
*out_files = all;
return total;
struct stat st;
if (lstat(s->path, &st) != 0) {
fprintf(stderr,
"ww: %s: package source is not a regular file\n", s->path);
return s->result = -2;
}
if (S_ISLNK(st.st_mode)) {
if (stat(s->path, &st) != 0) {
fprintf(stderr,
"ww: %s: package source is not a regular file\n", s->path);
return s->result = -2;
}
/* go/build ignores a source-shaped symlink to a directory. */
if (S_ISDIR(st.st_mode)) return s->result = 0;
}
if (!S_ISREG(st.st_mode)) {
fprintf(stderr,
"ww: %s: package source is not a regular file\n", s->path);
return s->result = -2;
}
if (sep_slurp(s->path, &s->buf, &s->len) < 0) {
fprintf(stderr, "ww: cannot read %s\n", s->path);
return s->result = -2;
}
int documentation = source_documentation_buffer(s->path, s->buf, s->len);
if (documentation < 0) return s->result = -2;
if (documentation > 0) return s->result = 0;
if (s->is_test) {
if (source_package_name(s->path, s->buf, s->len,
&s->package) < 0)
return s->result = -2;
} else {
int has_test = source_has_test_decl(s->path, s->buf, s->len);
if (has_test < 0) return s->result = -2;
if (has_test > 0) {
fprintf(stderr,
"ww: %s: @test declaration outside *_test.ww\n", s->path);
return s->result = -2;
}
}
return s->result = 1;
}
/* ww build — separate-compilation driver (task #46/c3).
@@ -1433,8 +1577,10 @@ struct seppkg {
char *name; /* validated declared name; directory packages only */
char *test_package; /* selected test package; root variants only */
char *for_test; /* directory product owning a recompiled action */
char **sources; /* owned, byte-sorted selected paths; dirs only */
struct sepsource *sources; /* owned byte-sorted path+byte snapshots; dirs */
int nsources;
char *entry_buf; /* request snapshot for a regular one-file root */
u64 entry_len;
int is_dir;
int variant; /* SEP_VARIANT_*; dependencies are production */
int role; /* normal, reserved test support, or generated main */
@@ -1473,6 +1619,9 @@ struct sepgraph {
struct seppkg *pkg;
int n;
int pkgcap;
struct sepdirobs *dirobs; /* request-owned canonical observations */
int ndirobs;
int dirobscap;
struct sepcontext *context;
int ncontext;
int contextcap;
@@ -1521,6 +1670,78 @@ sep_reserve_packages(struct sepgraph *g, int need)
sizeof *g->pkg);
}
static struct sepdirobs *
sep_graph_dir_observation(struct sepgraph *g, const struct seppkg *p)
{
for (int i = 0; i < g->ndirobs; i++)
if (strcmp(g->dirobs[i].canon, p->canon) == 0)
return &g->dirobs[i];
if (g->ndirobs == INT_MAX
|| sep_reserve((void **)&g->dirobs, &g->dirobscap,
g->ndirobs + 1, sizeof *g->dirobs) < 0)
return NULL;
struct sepdirobs *obs = &g->dirobs[g->ndirobs];
memset(obs, 0, sizeof *obs);
obs->canon = strdup(p->canon);
obs->entry = strdup(p->entry);
if (obs->canon == NULL || obs->entry == NULL) {
sep_fail_nomem();
free(obs->canon);
free(obs->entry);
memset(obs, 0, sizeof *obs);
return NULL;
}
g->ndirobs++;
return obs;
}
/* Select one action's ordered source set from request-owned observations.
* Production members precede same-package tests. Crucially, variant filters
* run before sep_dir_observe_source, so a production-only request merely sees
* test names in readdir data and never stats or opens their paths. */
static int
sep_select_dir_sources(struct sepgraph *g, struct seppkg *p)
{
struct sepdirobs *obs = sep_graph_dir_observation(g, p);
if (obs == NULL) return -2;
int er = sep_dir_membership(obs);
if (er != 0) return er;
/* Diagnose and cache observations in the directory's one global byte
* order. Source assembly below retains the language's production-before-
* test order without changing filesystem diagnostic precedence. */
for (int i = 0; i < obs->nsource; i++) {
struct sepdirobs_source *s = &obs->source[i];
if (p->variant == SEP_VARIANT_PRODUCTION && s->is_test)
continue;
if (p->variant == SEP_VARIANT_EXTERNAL && !s->is_test)
continue;
if (sep_dir_observe_source(obs, s) < 0) return -2;
}
int cap = 0;
for (int partition = 0; partition < 2; partition++) {
for (int i = 0; i < obs->nsource; i++) {
struct sepdirobs_source *s = &obs->source[i];
if ((partition == 0 && s->is_test)
|| (partition == 1 && !s->is_test))
continue;
if (p->variant == SEP_VARIANT_PRODUCTION && s->is_test)
continue;
if (p->variant == SEP_VARIANT_EXTERNAL && !s->is_test)
continue;
int observed = s->result;
if (observed == 0) continue;
if (s->is_test
&& (p->test_package == NULL
|| strcmp(s->package, p->test_package) != 0))
continue;
if (source_snapshot_add(&p->sources, &p->nsources, &cap,
s->path, s->buf, s->len) < 0)
return -2;
}
}
return p->nsources;
}
static int
sep_reserve_folds(struct sepfoldset *set, int need)
{
@@ -2628,8 +2849,12 @@ sep_find_or_add_role(struct sepgraph *g, const char *path, const char *entry,
static void
sep_pkg_free_fields(struct seppkg *p)
{
for (int j = 0; j < p->nsources; j++) free(p->sources[j]);
for (int j = 0; j < p->nsources; j++) {
free(p->sources[j].path);
free(p->sources[j].buf);
}
free(p->sources);
free(p->entry_buf);
for (int j = 0; j < p->bindings.n; j++) {
free(p->bindings.v[j].name);
free(p->bindings.v[j].source);
@@ -2651,8 +2876,8 @@ sep_pkg_free_fields(struct seppkg *p)
memset(p, 0, sizeof *p);
}
/* Release the one package-owned directory-membership list. Every graph exit
* funnels through this function; regular-file nodes own no source list. */
/* Release variant-owned byte copies and request-owned canonical-directory
* memberships/observations. Every graph exit funnels through this function. */
static void
sep_graph_free(struct sepgraph *g)
{
@@ -2674,8 +2899,21 @@ sep_graph_free(struct sepgraph *g)
free(g->context[i].route);
free(g->context[i].source_root);
}
for (int i = 0; i < g->ndirobs; i++) {
struct sepdirobs *obs = &g->dirobs[i];
for (int j = 0; j < obs->nsource; j++) {
free(obs->source[j].name);
free(obs->source[j].path);
free(obs->source[j].buf);
free(obs->source[j].package);
}
free(obs->source);
free(obs->canon);
free(obs->entry);
}
free(g->package_folds.v);
free(g->file_folds.v);
free(g->dirobs);
free(g->context);
free(g->pkg);
free(g);
@@ -3507,16 +3745,65 @@ static int
sep_scan_file(struct sepgraph *g, int pi, const char *file,
int context, struct ImportSet *filevisit,
struct sepbindset *bindings, struct sepchildren *children,
int owned_source)
int owned_source, const char *selected_buf, u64 selected_len)
{
if (import_seen(filevisit, file)) return 0;
if (import_add(filevisit, file) < 0) return -1;
char *buf;
u64 len;
if (sep_slurp(file, &buf, &len) < 0) {
int documentation_prechecked = 0;
if (selected_buf != NULL) {
buf = malloc((size_t)selected_len + 1);
if (buf == NULL) return sep_fail_nomem();
memcpy(buf, selected_buf, (size_t)selected_len + 1);
len = selected_len;
documentation_prechecked = 1;
} else if (!owned_source && g->pkg[pi].entry_buf != NULL
&& strcmp(g->pkg[pi].entry, file) == 0) {
buf = malloc((size_t)g->pkg[pi].entry_len + 1);
if (buf == NULL) return sep_fail_nomem();
memcpy(buf, g->pkg[pi].entry_buf,
(size_t)g->pkg[pi].entry_len + 1);
len = g->pkg[pi].entry_len;
documentation_prechecked = 1;
} else if (!owned_source && source_documentation_preload.path != NULL
&& strcmp(source_documentation_preload.path, file) == 0) {
buf = source_documentation_preload.buf;
len = source_documentation_preload.len;
free(source_documentation_preload.path);
source_documentation_preload.path = NULL;
source_documentation_preload.buf = NULL;
source_documentation_preload.len = 0;
documentation_prechecked = 1;
} else if (sep_slurp(file, &buf, &len) < 0) {
fprintf(stderr, "ww: cannot read %s\n", file);
return -1;
}
if (!owned_source && g->pkg[pi].entry_buf == NULL
&& strcmp(g->pkg[pi].entry, file) == 0
&& (documentation_prechecked || source_regular_file(file))) {
g->pkg[pi].entry_buf = malloc((size_t)len + 1);
if (g->pkg[pi].entry_buf == NULL) {
sep_fail_nomem();
free(buf);
return -1;
}
memcpy(g->pkg[pi].entry_buf, buf, (size_t)len + 1);
g->pkg[pi].entry_len = len;
}
if (!owned_source && !documentation_prechecked
&& source_regular_file(file)) {
int documentation = source_documentation_buffer(file, buf, len);
if (documentation < 0) {
free(buf);
return -1;
}
if (documentation > 0) {
source_operand_no_sources(file);
free(buf);
return -1;
}
}
Arena *a = newarena();
Lex l;
Parser p;
@@ -4052,10 +4339,21 @@ sep_clone_for_test(struct sepgraph *g, int original, const char *owner,
}
int sourcecap = 0;
for (int i = 0; i < src->nsources; i++) {
if (source_list_add(&p->sources, &p->nsources, &sourcecap,
src->sources[i]) < 0)
if (source_snapshot_add(&p->sources, &p->nsources, &sourcecap,
src->sources[i].path, src->sources[i].buf,
src->sources[i].len) < 0)
goto fail;
}
if (src->entry_buf != NULL) {
p->entry_buf = malloc((size_t)src->entry_len + 1);
if (p->entry_buf == NULL) {
sep_fail_nomem();
goto fail;
}
memcpy(p->entry_buf, src->entry_buf,
(size_t)src->entry_len + 1);
p->entry_len = src->entry_len;
}
for (int i = 0; i < src->bindings.n; i++) {
struct sepbind *b = &src->bindings.v[i];
Pos pos = { b->source, b->line, b->col };
@@ -4161,17 +4459,14 @@ sep_prepare_pkg_context(struct sepgraph *g, int pi, int context,
if (!g->pkg[pi].loaded) {
g->pkg[pi].loaded = 1;
if (g->pkg[pi].is_dir) {
const char *test_package = g->pkg[pi].test_package;
g->pkg[pi].nsources = enumerate_dir_ww(g->pkg[pi].entry,
g->pkg[pi].variant, test_package,
&g->pkg[pi].sources);
if (g->pkg[pi].nsources == -2) {
rc = -1; /* diagnosed in enumerate_dir_ww */
} else if (g->pkg[pi].nsources < 0) {
int selected = sep_select_dir_sources(g, &g->pkg[pi]);
if (selected == -2) {
rc = -1; /* diagnosed during source observation */
} else if (selected < 0) {
fprintf(stderr, "ww: cannot read directory %s\n",
g->pkg[pi].entry);
rc = -1;
} else if (g->pkg[pi].nsources == 0) {
} else if (selected == 0) {
fprintf(stderr,
"ww: %s: directory contains no WW package sources\n",
g->pkg[pi].entry);
@@ -4179,16 +4474,18 @@ sep_prepare_pkg_context(struct sepgraph *g, int pi, int context,
}
for (int i = 0; i < g->pkg[pi].nsources && rc == 0; i++)
rc = sep_register_file_fold(g, g->pkg[pi].canon,
g->pkg[pi].sources[i]);
g->pkg[pi].sources[i].path);
}
}
if (g->pkg[pi].is_dir) {
for (int i = 0; i < g->pkg[pi].nsources && rc == 0; i++)
rc = sep_scan_file(g, pi, g->pkg[pi].sources[i],
context, &fv, &bindings, children, 1);
rc = sep_scan_file(g, pi, g->pkg[pi].sources[i].path,
context, &fv, &bindings, children, 1,
g->pkg[pi].sources[i].buf,
g->pkg[pi].sources[i].len);
} else if (rc == 0) {
rc = sep_scan_file(g, pi, g->pkg[pi].entry, context,
&fv, &bindings, children, 0);
&fv, &bindings, children, 0, NULL, 0);
}
sep_import_set_free(&fv);
if (bindings.n > 1)
@@ -4827,13 +5124,18 @@ sep_validate_module_closure(struct sepgraph *g, const int *order, int n,
* //ww:module-reset primary boundary. No source or export outside pi's
* sorted owned-source set may enter this unit. */
static int
sep_emit_body(FILE *out, const char *path, const char *modpath)
sep_emit_body(FILE *out, const char *path, const char *modpath,
const char *selected_buf, u64 selected_len)
{
char *buf;
u64 len;
if (sep_slurp(path, &buf, &len) < 0) {
fprintf(stderr, "ww: cannot read %s\n", path);
return -1;
char *owned = NULL;
const char *buf = selected_buf;
u64 len = selected_len;
if (buf == NULL) {
if (sep_slurp(path, &owned, &len) < 0) {
fprintf(stderr, "ww: cannot read %s\n", path);
return -1;
}
buf = owned;
}
/* #57: tag the primary body by its full dotted import path so the
* definer mangles == the importer reference; a root build (path "")
@@ -4859,7 +5161,7 @@ sep_emit_body(FILE *out, const char *path, const char *modpath)
!= (size_t)(len - off)
|| fputc('\n', out) == EOF)
bad = 1;
free(buf);
free(owned);
if (bad) {
fprintf(stderr, "ww: cannot write package unit\n");
return -1;
@@ -4931,10 +5233,12 @@ sep_compose_unit(struct sepgraph *g, int pi, const char *scratch,
bodyrc = -1;
} else if (g->pkg[pi].is_dir) {
for (int i = 0; i < g->pkg[pi].nsources && bodyrc == 0; i++)
bodyrc = sep_emit_body(u, g->pkg[pi].sources[i],
g->pkg[pi].path);
bodyrc = sep_emit_body(u, g->pkg[pi].sources[i].path,
g->pkg[pi].path, g->pkg[pi].sources[i].buf,
g->pkg[pi].sources[i].len);
} else {
bodyrc = sep_emit_body(u, g->pkg[pi].entry, g->pkg[pi].path);
bodyrc = sep_emit_body(u, g->pkg[pi].entry, g->pkg[pi].path,
g->pkg[pi].entry_buf, g->pkg[pi].entry_len);
}
const char *own_suffix;
size_t own_parents;
@@ -7169,6 +7473,7 @@ build_one_sep(const char *src, int entry_is_dir, const char *root_identity,
}
}
}
source_documentation_preload_clear();
return r;
}
@@ -7619,6 +7924,18 @@ do_build(int argc, char **argv)
free(incs);
return 1;
}
if (!is_dir && source_regular_file(resolved)) {
int documentation = source_preload_documentation(resolved);
if (documentation < 0) {
free(incs);
return 1;
}
if (documentation > 0) {
source_operand_no_sources(resolved);
free(incs);
return 1;
}
}
char out[PATH_MAX];
const char *objstem = NULL;
int discard_output = strcmp(outflag, "/dev/null") == 0;
@@ -7736,6 +8053,18 @@ do_run(int argc, char **argv)
free(incs);
return 1;
}
if (!is_dir && source_regular_file(resolved)) {
int documentation = source_preload_documentation(resolved);
if (documentation < 0) {
free(incs);
return 1;
}
if (documentation > 0) {
source_operand_no_sources(resolved);
free(incs);
return 1;
}
}
char tmpdir[PATH_MAX], tmp[PATH_MAX];
snprintf(tmpdir, sizeof tmpdir, "/tmp/ww_run_%d", getpid());
if (mkdir(tmpdir, 0700) != 0) {
@@ -7797,6 +8126,17 @@ do_run(int argc, char **argv)
return rc;
}
static int
source_test_preflight(const char *path, int compileonly, int emit_asm)
{
if (!source_regular_file(path)) return 0;
int documentation = source_preload_documentation(path);
if (documentation > 0) source_operand_no_sources(path);
if (documentation == 0) return 0;
if (!compileonly && !emit_asm) fputs("FAIL\n", stdout);
return 1;
}
static int
do_test(int argc, char **argv)
{
@@ -8293,6 +8633,8 @@ do_test(int argc, char **argv)
"ww test: package-test variant needs one directory\n");
return 2;
}
if (source_test_preflight(resolved, compileonly, emit_asm))
return 1;
char tmpdir[PATH_MAX] = {0}, tmp[PATH_MAX];
const char *outp;
int retain_output = outstem[0] && !discard_output;
@@ -8375,6 +8717,8 @@ do_test(int argc, char **argv)
"ww test: package options need a directory\n");
return 2;
}
if (source_test_preflight(target, compileonly, emit_asm))
return 1;
char tmpdir[PATH_MAX] = {0}, tmp[PATH_MAX];
const char *outp;
int retain_output = outstem[0] && !discard_output;