driver: ignore package documentation sources
This commit is contained in:
690
cmd/ww/main.c
690
cmd/ww/main.c
@@ -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 : ∏
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user