ww build: honor output directories
This commit is contained in:
233
cmd/ww/main.c
233
cmd/ww/main.c
@@ -25,7 +25,7 @@
|
||||
static const char *usage =
|
||||
"usage: ww [-V] <subcommand> [args...]\n"
|
||||
" -V print version and exit\n"
|
||||
" build [-S] [-w DIR] [-I DIR] [-o FILE] [path ...] build local package graphs\n"
|
||||
" build [-S] [-w DIR] [-I DIR] [-o FILE|DIR] [path ...] build local package graphs\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"
|
||||
@@ -34,7 +34,8 @@ static const char *usage =
|
||||
" 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"
|
||||
" -o publishes a non-main archive FILE + FILE.wwi\n"
|
||||
" -o FILE publishes a non-main archive FILE + FILE.wwi\n"
|
||||
" -o DIR publishes each selected command beneath DIR\n"
|
||||
" lib/... every eligible package under lib, recursively\n"
|
||||
" . build the cwd's <basename>.ww\n";
|
||||
|
||||
@@ -1256,6 +1257,7 @@ struct seppkg {
|
||||
int ngenerated_targets;
|
||||
int generated_targetcap;
|
||||
int failed; /* discovery/compile failure reaches this action */
|
||||
int action; /* reached by this request's semantic action list */
|
||||
int test_support; /* compiler-generated -T support package */
|
||||
int loaded; /* directory membership/name loaded exactly once */
|
||||
int export_changed; /* staged export differs from committed export */
|
||||
@@ -1306,6 +1308,7 @@ struct sepproduct {
|
||||
int variant;
|
||||
int directory_product;
|
||||
int no_tests;
|
||||
int build_action; /* loaded product retained in the action list */
|
||||
int context;
|
||||
int root;
|
||||
int variant_root; /* retained single-unit root outside directory products */
|
||||
@@ -2847,7 +2850,8 @@ static int
|
||||
sep_validate_artifact_paths(struct sepgraph *g, const char *scratch)
|
||||
{
|
||||
for (int i = 0; i < g->n; i++) {
|
||||
if (g->pkg[i].failed || !g->pkg[i].loaded) continue;
|
||||
if (g->pkg[i].failed || !g->pkg[i].loaded || !g->pkg[i].action)
|
||||
continue;
|
||||
if (g->pkg[i].storage == NULL
|
||||
&& sep_assign_storage(&g->pkg[i], scratch) < 0) return -1;
|
||||
}
|
||||
@@ -2855,9 +2859,11 @@ sep_validate_artifact_paths(struct sepgraph *g, const char *scratch)
|
||||
do {
|
||||
changed = 0;
|
||||
for (int i = 0; i < g->n && !changed; i++) {
|
||||
if (g->pkg[i].failed || !g->pkg[i].loaded) continue;
|
||||
if (g->pkg[i].failed || !g->pkg[i].loaded
|
||||
|| !g->pkg[i].action) continue;
|
||||
for (int j = i + 1; j < g->n; j++) {
|
||||
if (g->pkg[j].failed || !g->pkg[j].loaded
|
||||
|| !g->pkg[j].action
|
||||
|| strcmp(g->pkg[i].storage,
|
||||
g->pkg[j].storage) != 0)
|
||||
continue;
|
||||
@@ -2932,7 +2938,8 @@ sep_validate_workdir_owners(const struct sepgraph *g, const char *scratch)
|
||||
{
|
||||
char unit[SEP_ARTIFACT_MAX];
|
||||
for (int i = 0; i < g->n; i++) {
|
||||
if (g->pkg[i].failed || !g->pkg[i].loaded) continue;
|
||||
if (g->pkg[i].failed || !g->pkg[i].loaded || !g->pkg[i].action)
|
||||
continue;
|
||||
if (sep_fname(g, i, scratch, ".unit.ww", unit, sizeof unit) < 0)
|
||||
return -1;
|
||||
if (sep_validate_unit_owner(unit, &g->pkg[i]) < 0) return -1;
|
||||
@@ -3833,6 +3840,13 @@ sep_clone_for_test(struct sepgraph *g, int original, const char *owner,
|
||||
p->is_dir = src->is_dir;
|
||||
p->variant = SEP_VARIANT_TEST_COPY;
|
||||
p->role = src->role;
|
||||
/* A test copy is a product-scoped action even when its replaced source
|
||||
* node is not in the final action closure. Preserve its established
|
||||
* complete-action locator without relying on a collision with that
|
||||
* inactive source node during artifact validation. */
|
||||
p->storage = sep_storage_digest(p);
|
||||
p->storage_hashed = 1;
|
||||
if (p->storage == NULL) goto fail;
|
||||
p->loaded = src->loaded;
|
||||
p->failed = src->failed;
|
||||
p->test_support = src->test_support;
|
||||
@@ -5222,7 +5236,8 @@ sep_validate_request_staging(struct sepgraph *g, const char *scratch, int warm,
|
||||
".o.new", ".a.new", ".init.unit.new", ".init.s.new",
|
||||
".init.o.new" };
|
||||
for (int pi = 0; pi < g->n; pi++) {
|
||||
if (g->pkg[pi].failed || !g->pkg[pi].loaded) continue;
|
||||
if (g->pkg[pi].failed || !g->pkg[pi].loaded
|
||||
|| !g->pkg[pi].action) continue;
|
||||
for (size_t si = 0; si < nelem(suffix); si++) {
|
||||
char path[SEP_ARTIFACT_MAX];
|
||||
if (sep_fname(g, pi, scratch, suffix[si], path,
|
||||
@@ -5365,14 +5380,16 @@ sep_discard_request_staging(struct sepgraph *g, const char *scratch, int warm,
|
||||
".init.unit.ww", ".init.s", ".init.o" };
|
||||
const char **suffix = warm ? warm_suffix : cold_suffix;
|
||||
int rc = 0;
|
||||
for (int pi = 0; pi < g->n; pi++)
|
||||
for (int pi = 0; pi < g->n; pi++) {
|
||||
if (!g->pkg[pi].action) continue;
|
||||
for (size_t i = 0; i < nelem(warm_suffix); i++) {
|
||||
char path[SEP_ARTIFACT_MAX];
|
||||
if (sep_fname(g, pi, scratch, suffix[i], path,
|
||||
sizeof path) < 0
|
||||
|| (unlink(path) != 0 && errno != ENOENT))
|
||||
rc = -1;
|
||||
rc = -1;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < nproducts; i++) {
|
||||
const char *path[] = { products[i].stage_out,
|
||||
products[i].stage_publish, products[i].stage_iface,
|
||||
@@ -5474,6 +5491,8 @@ build_one_sep_impl(const char *src, int entry_is_dir,
|
||||
int require_command, int is_test,
|
||||
struct sepproduct *products, int nproducts, int emit_asm,
|
||||
const char *workdir, int create_workdir, const char *create_output_dir,
|
||||
const char *default_output_dir, int output_path_error,
|
||||
const char *output_collision_base, const char *output_collision_dir,
|
||||
char *scratchout, size_t scratchoutsz,
|
||||
struct sepgraph **graphout)
|
||||
{
|
||||
@@ -5927,10 +5946,6 @@ build_one_sep_impl(const char *src, int entry_is_dir,
|
||||
int root_package = !is_test && nproducts == 1
|
||||
&& !g->pkg[products[0].root].failed
|
||||
&& !sep_root_is_command(&g->pkg[products[0].root]);
|
||||
if (sep_validate_artifact_paths(g, scratch) < 0)
|
||||
return 1;
|
||||
if (warm && workdir_exists && sep_validate_workdir_owners(g, scratch) < 0)
|
||||
return 1;
|
||||
int *order = calloc((size_t)g->n, sizeof *order);
|
||||
int *stack = calloc((size_t)g->n, sizeof *stack);
|
||||
int norder = 0;
|
||||
@@ -5980,6 +5995,24 @@ build_one_sep_impl(const char *src, int entry_is_dir,
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (!is_test && create_output_dir != NULL) {
|
||||
int actions = 0;
|
||||
for (int i = 0; i < nproducts; i++) {
|
||||
int root = products[i].root;
|
||||
if (g->pkg[root].failed) {
|
||||
free(stack); free(order);
|
||||
return 1;
|
||||
}
|
||||
products[i].build_action =
|
||||
sep_root_is_command(&g->pkg[root]);
|
||||
if (products[i].build_action) actions++;
|
||||
}
|
||||
if (actions == 0) {
|
||||
fputs("ww: no main packages to build\n", stderr);
|
||||
free(stack); free(order);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (!g->pkg[products[0].root].failed
|
||||
&& root_package && publish_package && !emit_asm
|
||||
&& validate_package_output_path(out) < 0) {
|
||||
@@ -5989,12 +6022,23 @@ build_one_sep_impl(const char *src, int entry_is_dir,
|
||||
for (int pi = 0; pi < g->n; pi++) g->pkg[pi].color = 0;
|
||||
for (int i = 0; i < nproducts; i++) {
|
||||
int root = products[i].root;
|
||||
if (!g->pkg[root].failed
|
||||
if (products[i].build_action && !g->pkg[root].failed
|
||||
&& sep_topo_visit(g, root, order, &norder, stack, 0) < 0) {
|
||||
free(stack); free(order); return 1;
|
||||
}
|
||||
}
|
||||
free(stack);
|
||||
for (int pi = 0; pi < g->n; pi++) g->pkg[pi].action = 0;
|
||||
for (int oi = 0; oi < norder; oi++) g->pkg[order[oi]].action = 1;
|
||||
if (sep_validate_artifact_paths(g, scratch) < 0) {
|
||||
free(order);
|
||||
return 1;
|
||||
}
|
||||
if (warm && workdir_exists
|
||||
&& sep_validate_workdir_owners(g, scratch) < 0) {
|
||||
free(order);
|
||||
return 1;
|
||||
}
|
||||
/* Propagate already-known package-load failures through the union before
|
||||
* acquiring scratch or completion state. Independent sibling roots may
|
||||
* remain viable for deterministic staging/diagnosis, but any failed product
|
||||
@@ -6007,11 +6051,34 @@ build_one_sep_impl(const char *src, int entry_is_dir,
|
||||
}
|
||||
int viable_product = 0;
|
||||
for (int i = 0; i < nproducts; i++)
|
||||
if (!g->pkg[products[i].root].failed) viable_product = 1;
|
||||
if (products[i].build_action
|
||||
&& !g->pkg[products[i].root].failed) viable_product = 1;
|
||||
if (!viable_product) {
|
||||
free(order);
|
||||
return 1;
|
||||
}
|
||||
if (!is_test && !emit_asm && output_path_error) {
|
||||
fputs("ww: command output path is too long\n", stderr);
|
||||
free(order);
|
||||
return 1;
|
||||
}
|
||||
if (!is_test && !emit_asm && output_collision_base != NULL) {
|
||||
fputs("ww: multiple commands produce output basename ", stderr);
|
||||
sep_put_quoted(output_collision_base);
|
||||
fputs(" in directory ", stderr);
|
||||
sep_put_quoted(output_collision_dir);
|
||||
fputc('\n', stderr);
|
||||
free(order);
|
||||
return 1;
|
||||
}
|
||||
if (!is_test && !emit_asm && default_output_dir != NULL && nproducts == 1
|
||||
&& sep_root_is_command(&g->pkg[products[0].root])) {
|
||||
fprintf(stderr,
|
||||
"ww: build output \"%s\" already exists and is a directory\n",
|
||||
default_output_dir);
|
||||
free(order);
|
||||
return 1;
|
||||
}
|
||||
if (sep_validate_request_staging(g, scratch, warm, products, nproducts,
|
||||
root_package, publish_package, emit_asm, is_test) < 0) {
|
||||
sep_free_product_staging(products, nproducts);
|
||||
@@ -6023,8 +6090,8 @@ build_one_sep_impl(const char *src, int entry_is_dir,
|
||||
* output paths, and action closures have passed their pre-tool checks. */
|
||||
struct sep_created_dirs created_output = {0};
|
||||
struct sep_created_dirs created_work = {0};
|
||||
if (create_output_dir != NULL && create_output_dir[0] != '\0'
|
||||
&& sep_mkdirs(create_output_dir, is_test ? 0777 : 0700,
|
||||
if (!emit_asm && create_output_dir != NULL && create_output_dir[0] != '\0'
|
||||
&& sep_mkdirs(create_output_dir, 0777,
|
||||
&created_output) != 0) {
|
||||
fprintf(stderr, is_test
|
||||
? "ww: cannot create test output directory %s\n"
|
||||
@@ -6704,7 +6771,8 @@ build_one_sep(const char *src, int entry_is_dir, const char *root_identity,
|
||||
const struct seplinkflags *linkflags, int publish_package,
|
||||
int require_command, int is_test,
|
||||
int root_variant, const char *test_package, int emit_asm,
|
||||
int keepscratch, const char *workdir)
|
||||
int keepscratch, const char *workdir, const char *create_output_dir,
|
||||
const char *default_output_dir, int output_path_error)
|
||||
{
|
||||
char scratch[PATH_MAX] = {0};
|
||||
struct sepgraph *g = NULL;
|
||||
@@ -6717,6 +6785,7 @@ build_one_sep(const char *src, int entry_is_dir, const char *root_identity,
|
||||
.publish = NULL,
|
||||
.artifact = NULL,
|
||||
.variant = root_variant,
|
||||
.build_action = 1,
|
||||
.root = -1,
|
||||
.variant_root = -1,
|
||||
.support = -1,
|
||||
@@ -6725,7 +6794,8 @@ build_one_sep(const char *src, int entry_is_dir, const char *root_identity,
|
||||
product.artifact = "__root";
|
||||
int r = build_one_sep_impl(src, entry_is_dir, out, objstem,
|
||||
extra_includes, linkflags, publish_package, require_command, is_test,
|
||||
&product, 1, emit_asm, workdir, 0, NULL, scratch,
|
||||
&product, 1, emit_asm, workdir, 0, create_output_dir,
|
||||
default_output_dir, output_path_error, NULL, NULL, scratch,
|
||||
sizeof scratch, &g);
|
||||
sep_graph_free(g);
|
||||
if (!keepscratch && scratch[0]) {
|
||||
@@ -6761,7 +6831,9 @@ build_package_tests(const char *src, const char *root_identity,
|
||||
const char *extra_includes, const char *workdir,
|
||||
struct sepproduct *products, int nproducts, int is_test,
|
||||
int publish_package, const struct seplinkflags *linkflags, int emit_asm,
|
||||
int create_workdir, const char *create_output_dir)
|
||||
int create_workdir, const char *create_output_dir,
|
||||
const char *default_output_dir, int output_path_error,
|
||||
const char *output_collision_base, const char *output_collision_dir)
|
||||
{
|
||||
char scratch[PATH_MAX] = {0};
|
||||
struct sepgraph *g = NULL;
|
||||
@@ -6770,7 +6842,9 @@ build_package_tests(const char *src, const char *root_identity,
|
||||
int r = build_one_sep_impl(src, 1, products[0].out,
|
||||
products[0].out, extra_includes, linkflags, publish_package, 0, is_test,
|
||||
products, nproducts, emit_asm, workdir, create_workdir,
|
||||
create_output_dir, scratch, sizeof scratch, &g);
|
||||
create_output_dir, default_output_dir, output_path_error,
|
||||
output_collision_base, output_collision_dir,
|
||||
scratch, sizeof scratch, &g);
|
||||
sep_graph_free(g);
|
||||
return r;
|
||||
}
|
||||
@@ -6818,6 +6892,39 @@ basename_no_ext(const char *path, char *out, size_t outsz)
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
}
|
||||
|
||||
/* Go's build -o directory branch follows an existing destination through
|
||||
* stat, and a trailing platform separator declares a directory which the
|
||||
* request may need to create. WW's platform separator is '/'. */
|
||||
static int
|
||||
build_output_dir(const char *path)
|
||||
{
|
||||
struct stat st;
|
||||
size_t n = strlen(path);
|
||||
if (n != 0 && path[n - 1] == '/') return 1;
|
||||
return stat(path, &st) == 0 && S_ISDIR(st.st_mode);
|
||||
}
|
||||
|
||||
static int
|
||||
build_output_path(const char *dir, const char *src, char *out, size_t outsz)
|
||||
{
|
||||
char base[PATH_MAX];
|
||||
size_t n = strlen(dir);
|
||||
int written;
|
||||
basename_no_ext(src, base, sizeof base);
|
||||
written = snprintf(out, outsz, "%s%s%s", dir,
|
||||
n != 0 && dir[n - 1] == '/' ? "" : "/", base);
|
||||
if (written < 0 || (size_t)written >= outsz) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
build_import_leaf(const char *identity, char *out, size_t outsz)
|
||||
{
|
||||
const char *leaf = strrchr(identity, '.');
|
||||
leaf = leaf ? leaf + 1 : identity;
|
||||
snprintf(out, outsz, "%s", leaf);
|
||||
}
|
||||
|
||||
static int
|
||||
resolve_module(const char *name, const char *incs, char *out, size_t outsz,
|
||||
int *is_dir)
|
||||
@@ -7043,6 +7150,7 @@ do_build(int argc, char **argv)
|
||||
free(incs);
|
||||
return 2;
|
||||
}
|
||||
int add_dot = src == NULL;
|
||||
if (src == NULL) src = ".";
|
||||
if (strstr(src, "...") != NULL || next < argc || saw_terminator) {
|
||||
free(incs);
|
||||
@@ -7060,11 +7168,32 @@ do_build(int argc, char **argv)
|
||||
char out[PATH_MAX];
|
||||
const char *objstem = NULL;
|
||||
int discard_output = strcmp(outflag, "/dev/null") == 0;
|
||||
int output_dir = outflag[0] && !discard_output
|
||||
&& build_output_dir(outflag);
|
||||
const char *root_identity = !literal && is_dir ? src : NULL;
|
||||
if (output_dir && is_dir) {
|
||||
free(incs);
|
||||
return exec_package_command(argc, argv, src,
|
||||
add_dot ? NULL : resolved, root_identity, add_dot, 1);
|
||||
}
|
||||
const char *create_output_dir = NULL;
|
||||
int output_path_error = 0;
|
||||
if (outflag[0] && !discard_output) {
|
||||
/* -o sets both the binary path and the intermediate stem so
|
||||
* artifacts land beside the requested output (T3). */
|
||||
memcpy(out, outflag, strlen(outflag) + 1);
|
||||
if (output_dir) {
|
||||
if (build_output_path(outflag, resolved, out,
|
||||
sizeof out) < 0) {
|
||||
output_path_error = 1;
|
||||
basename_no_ext(resolved, out, sizeof out);
|
||||
}
|
||||
create_output_dir = outflag;
|
||||
} else {
|
||||
memcpy(out, outflag, strlen(outflag) + 1);
|
||||
}
|
||||
objstem = out;
|
||||
} else if (is_dir && root_identity != NULL) {
|
||||
build_import_leaf(root_identity, out, sizeof out);
|
||||
} else if (is_dir) {
|
||||
char tmp[PATH_MAX];
|
||||
memcpy(tmp, resolved, strlen(resolved) + 1);
|
||||
@@ -7075,7 +7204,8 @@ do_build(int argc, char **argv)
|
||||
} else {
|
||||
basename_no_ext(resolved, out, sizeof out);
|
||||
}
|
||||
const char *root_identity = !literal && is_dir ? src : NULL;
|
||||
const char *default_output_dir = !outflag[0] && build_output_dir(out)
|
||||
? out : NULL;
|
||||
if (discard_output) {
|
||||
char tmpdir[PATH_MAX], tmp[PATH_MAX];
|
||||
int dn = snprintf(tmpdir, sizeof tmpdir, "/tmp/ww_build_%d", getpid());
|
||||
@@ -7095,7 +7225,7 @@ do_build(int argc, char **argv)
|
||||
* library compilation still need request-private product paths. */
|
||||
int rc = build_one_sep(resolved, is_dir, root_identity, tmp, tmp, incs,
|
||||
&linkflags, 0, 0, 0, SEP_VARIANT_PRODUCTION, NULL,
|
||||
emit_asm, 0, workdir);
|
||||
emit_asm, 0, workdir, NULL, NULL, 0);
|
||||
int cleanfail = 0;
|
||||
if (unlink(tmp) != 0 && errno != ENOENT) {
|
||||
fputs("ww: cannot remove temporary output\n", stderr);
|
||||
@@ -7111,7 +7241,8 @@ do_build(int argc, char **argv)
|
||||
}
|
||||
int rc = build_one_sep(resolved, is_dir, root_identity, out, objstem, incs,
|
||||
&linkflags, outflag[0] != '\0', 0, 0, SEP_VARIANT_PRODUCTION, NULL,
|
||||
emit_asm, 1, workdir);
|
||||
emit_asm, 1, workdir, create_output_dir, default_output_dir,
|
||||
output_path_error);
|
||||
free(incs);
|
||||
return rc;
|
||||
}
|
||||
@@ -7156,7 +7287,7 @@ do_run(int argc, char **argv)
|
||||
const char *root_identity = !literal && is_dir ? src : NULL;
|
||||
int buildrc = build_one_sep(resolved, is_dir, root_identity, tmp, tmp, incs,
|
||||
&linkflags,
|
||||
0, 1, 0, SEP_VARIANT_PRODUCTION, NULL, 0, 0, NULL);
|
||||
0, 1, 0, SEP_VARIANT_PRODUCTION, NULL, 0, 0, NULL, NULL, NULL, 0);
|
||||
free(incs);
|
||||
if (buildrc != 0) {
|
||||
if (unlink(tmp) != 0 && errno != ENOENT)
|
||||
@@ -7226,6 +7357,10 @@ do_test(int argc, char **argv)
|
||||
int package_publish = 0;
|
||||
int package_create_workdir = 0;
|
||||
const char *package_create_output_dir = NULL;
|
||||
int package_output_path_error = 0;
|
||||
const char *package_default_output_dir = NULL;
|
||||
const char *package_output_collision_base = NULL;
|
||||
const char *package_output_collision_dir = NULL;
|
||||
struct seplinkflags package_linkflags = {0};
|
||||
int afterdash = 0;
|
||||
const char *request_identity = NULL;
|
||||
@@ -7296,6 +7431,34 @@ do_test(int argc, char **argv)
|
||||
return 2;
|
||||
}
|
||||
package_create_output_dir = argv[++i];
|
||||
} else if (strcmp(argv[i],
|
||||
"--ww-command-output-path-error") == 0) {
|
||||
if (package_output_path_error) {
|
||||
fprintf(stderr,
|
||||
"ww test: invalid --ww-command-output-path-error\n");
|
||||
return 2;
|
||||
}
|
||||
package_output_path_error = 1;
|
||||
} else if (strcmp(argv[i], "--ww-default-output-dir") == 0) {
|
||||
if (i + 1 >= argc || package_default_output_dir != NULL
|
||||
|| argv[i + 1][0] == '\0') {
|
||||
fprintf(stderr,
|
||||
"ww test: invalid --ww-default-output-dir\n");
|
||||
return 2;
|
||||
}
|
||||
package_default_output_dir = argv[++i];
|
||||
} else if (strcmp(argv[i],
|
||||
"--ww-command-output-collision") == 0) {
|
||||
if (i + 2 >= argc
|
||||
|| package_output_collision_base != NULL
|
||||
|| argv[i + 1][0] == '\0'
|
||||
|| argv[i + 2][0] == '\0') {
|
||||
fprintf(stderr,
|
||||
"ww test: invalid --ww-command-output-collision\n");
|
||||
return 2;
|
||||
}
|
||||
package_output_collision_base = argv[++i];
|
||||
package_output_collision_dir = argv[++i];
|
||||
} else if (strcmp(argv[i], "--ww-package-test") == 0) {
|
||||
if (i + 9 >= argc) {
|
||||
fprintf(stderr,
|
||||
@@ -7363,6 +7526,7 @@ do_test(int argc, char **argv)
|
||||
products[nproducts].directory_product = 1;
|
||||
products[nproducts].no_tests = test_product
|
||||
&& !has_internal && !has_external;
|
||||
products[nproducts].build_action = 1;
|
||||
products[nproducts].root = -1;
|
||||
products[nproducts].variant_root = -1;
|
||||
products[nproducts].production_root = -1;
|
||||
@@ -7494,6 +7658,12 @@ do_test(int argc, char **argv)
|
||||
fprintf(stderr, "ww test: invalid private directory creation\n");
|
||||
return 2;
|
||||
}
|
||||
if ((package_output_path_error || package_default_output_dir != NULL
|
||||
|| package_output_collision_base != NULL)
|
||||
&& (!package_build || nproducts == 0)) {
|
||||
fprintf(stderr, "ww test: invalid private output preflight\n");
|
||||
return 2;
|
||||
}
|
||||
if (package_create_workdir && nproducts == 0) {
|
||||
fprintf(stderr, "ww test: invalid private directory creation\n");
|
||||
return 2;
|
||||
@@ -7595,7 +7765,10 @@ do_test(int argc, char **argv)
|
||||
incs, workdir, products, nproducts,
|
||||
package_build ? 0 : 1, package_publish,
|
||||
package_build ? &package_linkflags : NULL, emit_asm,
|
||||
package_create_workdir, package_create_output_dir);
|
||||
package_create_workdir, package_create_output_dir,
|
||||
package_default_output_dir, package_output_path_error,
|
||||
package_output_collision_base,
|
||||
package_output_collision_dir);
|
||||
free(products);
|
||||
free(incs);
|
||||
return r;
|
||||
@@ -7643,7 +7816,7 @@ do_test(int argc, char **argv)
|
||||
outstem[0] && !discard_output ? outstem : tmp,
|
||||
incs, NULL, 0, 0, 1,
|
||||
SEP_VARIANT_PRODUCTION, NULL, emit_asm,
|
||||
outstem[0] && !discard_output ? 1 : 0, workdir);
|
||||
outstem[0] && !discard_output ? 1 : 0, workdir, NULL, NULL, 0);
|
||||
if (br != 0) {
|
||||
if (owntmp && unlink(outp) != 0 && errno != ENOENT)
|
||||
fputs("ww: cannot remove temporary output\n", stderr);
|
||||
@@ -7711,7 +7884,7 @@ do_test(int argc, char **argv)
|
||||
int br = build_one_sep(target, 0, NULL, outp,
|
||||
outstem[0] && !discard_output ? outstem : tmp,
|
||||
incs, NULL, 0, 0, 1, SEP_VARIANT_PRODUCTION, NULL, emit_asm,
|
||||
outstem[0] && !discard_output ? 1 : 0, workdir);
|
||||
outstem[0] && !discard_output ? 1 : 0, workdir, NULL, NULL, 0);
|
||||
if (br != 0) {
|
||||
if (owntmp && unlink(outp) != 0 && errno != ENOENT)
|
||||
fputs("ww: cannot remove temporary output\n", stderr);
|
||||
@@ -7759,7 +7932,9 @@ do_test(int argc, char **argv)
|
||||
int r = build_package_tests(target, request_identity, incs, workdir,
|
||||
products, nproducts, package_build ? 0 : 1,
|
||||
package_publish, package_build ? &package_linkflags : NULL,
|
||||
emit_asm, package_create_workdir, package_create_output_dir);
|
||||
emit_asm, package_create_workdir, package_create_output_dir,
|
||||
package_default_output_dir, package_output_path_error,
|
||||
package_output_collision_base, package_output_collision_dir);
|
||||
free(products);
|
||||
free(incs);
|
||||
return r;
|
||||
|
||||
@@ -6533,6 +6533,233 @@ runtime cwd/environment/stdin. Build runtime behavior is inapplicable, and
|
||||
caller-output rollback, occupied caller stages, and output permissions are
|
||||
inapplicable to the exact discard branch because it creates no public inode.
|
||||
|
||||
### 11.30 Implemented single-root build output directories
|
||||
|
||||
An explicit `ww build -o OUT` now treats `OUT` as a directory when ordinary
|
||||
`stat` reports an existing directory or the spelling ends in `/`. The rule is
|
||||
independent of package count. A selected command directory publishes
|
||||
`OUT/<requested-import-leaf>` (falling back to the selected local directory
|
||||
leaf when no contextual identity exists); a raw command-line source publishes
|
||||
`OUT/<source-basename-without-.ww>`. A missing trailing-slash hierarchy is
|
||||
created from `0777`, filtered by caller umask, through the existing checked
|
||||
directory ledger. All roots load first, but independently selected non-main
|
||||
roots are omitted from the directory branch's action list. A selection
|
||||
containing no command—including a raw non-main root—rejects as
|
||||
`ww: no main packages to build` without running a producer or changing the
|
||||
output directory. After a lone command's default basename is synthesized, an
|
||||
existing directory at that
|
||||
basename instead rejects as
|
||||
`ww: build output "<name>" already exists and is a directory`; a non-main
|
||||
package has no default public output and is unaffected.
|
||||
|
||||
#### Pinned Go evidence and fact classification
|
||||
|
||||
The sole authority is official Go 1.26.5 at commit
|
||||
`c19862e5f8415b4f24b189d065ed739517c548ba`:
|
||||
|
||||
- `runBuild` completes package loading and package-error checking before output
|
||||
handling at
|
||||
[`cmd/go/internal/work/build.go`, lines 459–471](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/build.go#L459-L471).
|
||||
After synthesizing a lone `main` package's default output at lines 473–478,
|
||||
its output branch classifies an existing directory through
|
||||
`os.Stat`, or a spelling ending in `/` or the host path separator, at
|
||||
[lines 508–518](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/build.go#L508-L518).
|
||||
It then creates install actions only for packages named `main`, targets each
|
||||
at the output directory joined with `DefaultExecName`, rejects an empty
|
||||
command action list as `no main packages to build`, and executes that graph
|
||||
at
|
||||
[lines 519–535](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/build.go#L519-L535).
|
||||
The non-directory single-output branch is separate at
|
||||
[lines 537–548](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/build.go#L537-L548).
|
||||
- `DefaultExecName` uses the final import-path element for a directory package
|
||||
and the source basename for command-line files
|
||||
([`cmd/go/internal/load/pkg.go`, lines 1727–1769](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/pkg.go#L1727-L1769)).
|
||||
- `BuildInstallFunc` creates the target parent before installation, and
|
||||
`Shell.Mkdir` implements that operation as `os.MkdirAll(dir, 0777)`
|
||||
([`cmd/go/internal/work/exec.go`, lines 1975–2000](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/exec.go#L1975-L2000),
|
||||
[`cmd/go/internal/work/shell.go`, lines 283–301](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/shell.go#L283-L301)).
|
||||
- Official
|
||||
[`build_output.txt`, lines 10–29 and 41–45](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/build_output.txt#L10-L45)
|
||||
asserts a raw source's default basename, a missing trailing-slash directory,
|
||||
and an existing directory destination.
|
||||
- Official
|
||||
[`build_multi_main.txt`, lines 1–16](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/build_multi_main.txt#L1-L16)
|
||||
asserts command fan-out beneath one directory, no-main rejection, the
|
||||
implicit-default-existing-directory error, and raw command-line-source
|
||||
placement beneath an existing output directory.
|
||||
|
||||
Load-before-output ordering, `stat`/separator classification, command-only
|
||||
installation, import-leaf naming, destination joining, no-main rejection, and
|
||||
the `0777` parent-creation request are directly implemented by the pinned
|
||||
source. Raw-source basename and existing/missing directory behavior are
|
||||
directly asserted by official testdata. The resulting parent mode after umask
|
||||
is derived from the pinned `MkdirAll` call. Application to
|
||||
exactly one directory package is derived from `runBuild`: the directory branch
|
||||
tests output form, not package cardinality, while the cardinality check exists
|
||||
only in the later non-directory branch. These facts apply to WW's local
|
||||
manifest-free command and raw-source products without importing Go's module,
|
||||
cache, registry, distribution, or network behavior. The installed host Go
|
||||
version is not authority.
|
||||
|
||||
#### Direct pre-fix measurements
|
||||
|
||||
Fresh public Cstage (`out/bin/ww`) and WWstage (`out/bin/ww_ww`) probes measured
|
||||
the same divergence before production edits:
|
||||
|
||||
- one command directory plus an existing output directory spelled without `/`
|
||||
returned 0, renamed the caller's directory to a PID-bearing transaction
|
||||
backup, installed a 4,268-byte ELF executable at the directory pathname, and
|
||||
diagnosed failure to remove the nonempty backup;
|
||||
- the same existing directory spelled with `/` returned 1 with
|
||||
`ww: cannot preserve transaction destination .../`;
|
||||
- a missing nested spelling ending in `/` returned 1 while trying to acquire
|
||||
`<OUT>/.sepwork` before the hierarchy existed;
|
||||
- a raw `main.ww` plus an existing trailing-slash destination failed through
|
||||
the same transaction-destination path instead of producing `OUT/main`; and
|
||||
- a non-main directory plus an existing directory spelled without `/` returned
|
||||
0, replaced the directory pathname by an archive, wrote its `.wwi` beside
|
||||
that pathname, and stranded the former directory as a transaction backup;
|
||||
and
|
||||
- for both a command directory and a raw command-line source, an existing
|
||||
directory at the synthesized default basename was renamed to a transaction
|
||||
backup and replaced by the executable; both stages returned 0 and diagnosed
|
||||
inability to remove the deliberately nonempty backup.
|
||||
|
||||
Completion review measured four additional stage-identical pre-completion
|
||||
behaviors before their production edits. A mixed command/non-main request
|
||||
compiled the independent non-main root in both stages; a raw non-main root
|
||||
returned 0 and published an archive plus `.wwi`; a contextual `alias` symlink
|
||||
to physical directory `physical` published `OUT/physical`; and an exactly
|
||||
arranged umask `000` produced newly created output parents with mode `0700`.
|
||||
Pinned Go instead loads then omits the independent non-main action, rejects the
|
||||
raw no-command selection, uses the requested import leaf, and requests parent
|
||||
mode `0777`. The preserved traces and stat results are in the session evidence
|
||||
ledger.
|
||||
|
||||
Final review then measured four stage-identical load-precedence leaks before
|
||||
the completion edit. A non-main root with a missing import reported only
|
||||
`no main packages`; a command with a missing import and an overlong derived
|
||||
directory destination reported only the path error; two colliding command
|
||||
basenames, one with a missing import, reported only the duplicate-destination
|
||||
error; and a recursive command with a missing import plus an implicit default
|
||||
directory collision reported only the collision. Pinned `runBuild` lines
|
||||
470–471 load and check all selected packages before any output handling at
|
||||
lines 473–548. Missing-package diagnostics therefore precede no-main,
|
||||
derived-path, duplicate-destination, and implicit-default checks. Extending
|
||||
that boundary to WW's transactional duplicate guard is derived from the pinned
|
||||
ordering because the guard is WW-local output preflight. The exact probes and
|
||||
outputs are preserved in the evidence ledger.
|
||||
|
||||
Those are directly measured WW facts. Both drivers interpreted every non-null
|
||||
single-root `-o` as one file and object stem. The compiler, assembler, linker,
|
||||
archive writer, and shared package coordinator were not the cause.
|
||||
|
||||
#### Ownership, actions, publication, and identity
|
||||
|
||||
`internal/wwpackage.packagecommand` remains the shared package-request output
|
||||
owner. It classifies output directories, discovers and groups the complete
|
||||
selection, and passes every selected root plus output-preflight metadata into
|
||||
the shared separate-build executor. Only after that executor has loaded all
|
||||
packages and imports does it retain command roots, derive their complete action
|
||||
closure, and evaluate no-main, path-length, duplicate-destination, and
|
||||
implicit-default checks. A separate presentation field carries the requested
|
||||
import leaf (or local-path fallback) through collision preflight and
|
||||
publication; canonical physical directory metadata remains loader metadata.
|
||||
The coordinator commits the retained products through one request transaction.
|
||||
The dispatch part of the gap belonged to the early single-root compatibility
|
||||
choice in `cmd/ww.do_build` and `selfhost/cmd/ww.dobuild`; completion review
|
||||
also closed the coordinator's independent-non-main action,
|
||||
physical-leaf-presentation, and load-precedence leaks.
|
||||
|
||||
After ordinary argument parsing and root resolution, both drivers now apply the
|
||||
same exact Unix classification. A directory root with a directory output enters
|
||||
the existing package coordinator. An explicit logical root carries its
|
||||
unchanged logical root identity while the corresponding argument is replaced
|
||||
by the already-resolved loader route; a default invocation inserts exactly one
|
||||
`.`; a literal directory remains literal. Thus the shared loader, grouping,
|
||||
graph, action, output-preflight, and publication rules operate exactly as they
|
||||
do for a larger request.
|
||||
|
||||
The raw-file compatibility route remains driver-owned. It derives the joined
|
||||
command path, uses that path as the existing direct action's product and stem,
|
||||
and passes the classified directory to the shared transaction's checked
|
||||
creation ledger. A bounds failure is carried as preflight metadata so source
|
||||
and import errors, and raw no-main rejection, retain pinned load-first
|
||||
precedence before the path diagnostic. It does not manufacture a package
|
||||
request or change raw-source graph identity. `stat` follows a symlinked output
|
||||
directory; lexical publication remains beneath the requested symlink spelling.
|
||||
Exact `/dev/null` is classified first by the completed discard rule and never
|
||||
enters this directory branch.
|
||||
|
||||
For an implicit default, the drivers pass the existing-directory collision as
|
||||
preflight metadata to the common separate-build executor. The executor waits
|
||||
until package/import loading, contextual checks, and graph-cycle validation
|
||||
have established the root action kind. It rejects a command before scratch,
|
||||
workdir, tool, stage, or destination acquisition, but lets a non-main package
|
||||
perform its unchanged no-public-output build. This keeps raw and directory
|
||||
compatibility routes on the same diagnostic-precedence rule without deriving
|
||||
kind from a path, filename, declared-name guess, or driver-side source scan.
|
||||
|
||||
Loading and source/import rejection precede every output-derived rejection,
|
||||
including no-main, derived-path length, duplicate destination, and implicit
|
||||
default collision; all precede output creation. A selection with no command
|
||||
rejects after full package/import loading and graph validation but before
|
||||
compiler, assembler, linker, or directory mutation. In a mixed request,
|
||||
independently selected non-main roots have no action; a non-main package
|
||||
reachable as a command dependency still performs its ordinary semantic action.
|
||||
Repeated exact roots retain canonical graph/action deduplication. Successful
|
||||
commands compile, assemble, archive, and link normally; `-S -w` remains
|
||||
action-only and publishes no command. Directory form still selects only
|
||||
commands and therefore retains no-main rejection, but destination length,
|
||||
duplicate publication names, implicit destination collision, and output-parent
|
||||
creation belong to the install action that `-S` never reaches. They are
|
||||
inapplicable to that assembly-only request. An explicit external workdir
|
||||
prevents an adjacent `<command>.sepwork`; without `-w`, that established WW
|
||||
scratch tree remains an ordinary retained build artifact beneath the output
|
||||
directory.
|
||||
|
||||
Existing destination contents and symlink targets survive successful
|
||||
publication. Compiler failure, linker failure, or linker signal preserves the
|
||||
prior command and every committed persistent artifact, removes `.new` and
|
||||
transaction stages, and rolls back only directory prefixes created for the
|
||||
failed request. Every caller-output prefix is requested as `0777` and filtered
|
||||
once by the caller umask; persistent/private work directories keep their
|
||||
separate modes. The raw route has the same missing-directory rollback through
|
||||
the shared creation ledger. Independent Cstage and WWstage processes use
|
||||
disjoint output, work, stage, and process ownership and may complete
|
||||
concurrently. `ww build` itself has no runtime action; direct execution of the
|
||||
published command verifies its ordinary program exit result, while runtime
|
||||
failure/timeout policy remains owned by `ww run` and `ww test`.
|
||||
|
||||
This is output disposition and dispatch only. Dotted package/import identity,
|
||||
declared package name, physical source directory metadata, file-local import
|
||||
bindings, graph edges, action and storage keys, symbols, `.wwi`, compiler,
|
||||
assembler, archive and linker semantic inputs, artifact bytes, invalidation,
|
||||
and public-file output-mode formula are unchanged. Correcting caller-output
|
||||
parent creation metadata does not enter any semantic identity. There is no
|
||||
persisted-byte contract change: build workdir format remains `18`, test
|
||||
workdir format remains `19`, and semantic storage remains `3`.
|
||||
|
||||
The WW-native owner `single_root_build_output_directory` covers literal,
|
||||
logical, default-dot, symlinked, raw-file, explicit existing/missing, and
|
||||
implicit-default existing-directory forms in both stages; basename selection
|
||||
and directory-content preservation; logical alias versus physical-leaf
|
||||
separation; command rejection, raw no-main rejection, non-main no-output
|
||||
behavior, long raw-output and load-error precedence; mixed command-only and
|
||||
repeated roots; skipped-root import rejection; and no-main, directory-derived
|
||||
path, duplicate-destination, and recursive implicit-collision precedence with
|
||||
zero tool activity; exact `0777` missing-parent creation under umask `000`;
|
||||
the already-aligned retained-test control; `-S -w` command selection with no
|
||||
install-only preflight or output-directory creation; cold, warm,
|
||||
and invalidated persistence; exact compiler/assembler/linker action traces;
|
||||
successful program exit results; compiler and linker failure; linker signal;
|
||||
prior-state and newly-created-directory rollback; `.new` and transaction
|
||||
cleanup; concurrent stage isolation; executable and semantic-artifact bytes;
|
||||
and exact diagnostic parity. Existing owners continue to cover public-file
|
||||
output umasks, occupied stages, generalized multi-product transactions, test
|
||||
runtime failure and timeout, null discard, and broader package/import graph
|
||||
matrices.
|
||||
|
||||
## 12. Candidate architectures and hard-gate decision
|
||||
|
||||
Five candidates were developed as coherent systems, not as feature bins.
|
||||
|
||||
29
docs/spec.md
29
docs/spec.md
@@ -310,6 +310,35 @@ ImportPath = ident { "." ident } .
|
||||
`fn main`; path and directory spelling do not classify commands. An ordinary
|
||||
import of a package declared `main` is rejected, except for the toolchain's
|
||||
colocated external-test wiring.
|
||||
- For `ww build`, an explicit `-o` names an output directory when ordinary
|
||||
`stat` reports an existing directory (following symlinks) or its spelling
|
||||
ends in `/`. This classification is independent of whether one or many
|
||||
package roots were requested. Each selected command is published beneath
|
||||
that directory using the final component of its requested contextual import
|
||||
name, with the selected local directory leaf as the fallback when no such
|
||||
identity exists. Independently selected non-main roots load but do not enter
|
||||
the action list; non-main command dependencies retain ordinary actions, and
|
||||
a selection containing no command rejects before tools or output creation.
|
||||
A raw command-line `.ww` source uses its source basename without `.ww`, and
|
||||
a raw non-main source is the same no-command rejection. Missing
|
||||
trailing-slash hierarchies are created transactionally from `0777`, filtered
|
||||
by the caller's umask.
|
||||
Loading and package/import diagnostics precede no-main, derived-path,
|
||||
duplicate-destination, implicit-default, and other output preflight; all of
|
||||
those checks precede creation. Producer failure or interruption preserves
|
||||
existing directory contents and removes only request-created prefixes and
|
||||
stages. A non-directory output retains the single-product file/archive rule.
|
||||
If a lone
|
||||
command's synthesized default basename already names a directory, loading
|
||||
and graph validation complete and the build rejects before tools without
|
||||
changing that directory; a non-main package synthesizes no default public
|
||||
output. Output paths and directory metadata never become package, import,
|
||||
graph, action,
|
||||
symbol, artifact, `.wwi`, or persistence identity.
|
||||
Assembly-only `-S` retains the directory form's command-action selection and
|
||||
no-main rejection, but it reaches no install action: destination length,
|
||||
duplicate publication names, implicit destination collision, and output
|
||||
parent creation are therefore inapplicable.
|
||||
- A newly published `ww build` command is created with permission `0777`
|
||||
filtered by the invoking process's umask. A newly published non-command
|
||||
archive, and the adjacent WW interface required to consume it, use `0666`
|
||||
|
||||
@@ -234,6 +234,28 @@ package with no selected test source validates production
|
||||
but publishes nothing and does not create an otherwise unneeded output
|
||||
directory. Successful `-c` output is silent apart from no-test reporting.
|
||||
|
||||
The package suite also pins the adjacent `ww build` output-directory branch.
|
||||
After normal loading, an existing directory (including a symlink to one) or an
|
||||
explicit spelling ending in `/` receives each command under its requested
|
||||
import-leaf name (or local directory-leaf fallback) even for exactly one
|
||||
selected root; a raw `.ww` command uses its source basename. A missing
|
||||
trailing-slash hierarchy is created from `0777`, filtered by umask, by the
|
||||
request transaction. A directory or raw no-main-only selection and an import
|
||||
failure run no tools and do not create output. Independently selected non-main
|
||||
siblings in a mixed request load but have no action; command dependencies still
|
||||
build. Package/import rejection precedes no-main, derived destination length,
|
||||
duplicate command basename, and implicit default-directory preflight. If the
|
||||
synthesized default command basename already exists as a directory,
|
||||
load/graph validation precedes a zero-tool rejection and preserves that
|
||||
directory; a non-main package has no corresponding default output.
|
||||
Persistent work reuse, invalidation, failures, interruption, and
|
||||
concurrent drivers retain the same action and rollback rules; output form does
|
||||
not enter package/import/action identity. This build branch starts no runtime
|
||||
process, while direct execution of its published binary remains an artifact
|
||||
check rather than part of `ww build`. Assembly-only `-S` still selects command
|
||||
actions and rejects a no-command directory request, but it performs no
|
||||
install-only destination validation or output-directory creation.
|
||||
|
||||
The retained executable is byte-identical to the temporary runnable and has
|
||||
executable mode `0777` filtered by the caller's umask, but it is never the path
|
||||
executed by the coordinator. Publication participates in the driver's one
|
||||
@@ -312,6 +334,24 @@ precede those coordinator-owned source-validation parses.
|
||||
Separate compilation is the only driver build path; no compatibility mode
|
||||
switch remains.
|
||||
|
||||
`test/package/package_test.ww` owns the focused dual-stage
|
||||
`single_root_build_output_directory` observer. It covers existing and missing
|
||||
directory spellings; trailing and non-trailing forms; literal, logical,
|
||||
default-dot, symlink, raw-file, and implicit-default routes; build/test
|
||||
adjacency; mixed and repeated roots; command-only publication, no-main and
|
||||
implicit-collision rejection, non-main default behavior; import
|
||||
diagnostic precedence over no-main, derived-path, duplicate-destination, and
|
||||
recursive implicit-collision checks; `-S -w` command selection with long,
|
||||
duplicate, implicit, and missing-directory publication preflight omitted;
|
||||
cold, warm, and invalidated persistence;
|
||||
compiler, assembler, and linker action traces; compiler/linker failure and
|
||||
linker signal; prior-state and directory-creation rollback; concurrent
|
||||
isolation; executable modes and runtime exit values; `.new`/transaction
|
||||
cleanup; and Cstage/WWstage diagnostic, binary, assembly, and persistent
|
||||
semantic-artifact byte parity. General output permissions, occupied stages,
|
||||
multi-product transactions, null discard, and test runtime timeout remain with
|
||||
their existing observers.
|
||||
|
||||
`test/package/package_test.ww` also owns the dual-stage
|
||||
`declared_name_identity_and_file_import_scope` and
|
||||
`explicit_import_alias_binding_modes` observers. They generate temporary
|
||||
|
||||
@@ -24,6 +24,7 @@ type pkgfolder = struct {
|
||||
type pkggroup = struct {
|
||||
dir: str,
|
||||
pkg: str,
|
||||
basename: str,
|
||||
testname: str,
|
||||
publish: str,
|
||||
prodpkg: str,
|
||||
@@ -58,6 +59,11 @@ type pkgplan = struct {
|
||||
buildonly: bool,
|
||||
publish: bool,
|
||||
emitasm: bool,
|
||||
outputpatherror: bool,
|
||||
defaultoutputdir: str,
|
||||
outputcollisionbase: str,
|
||||
outputcollisiondir: str,
|
||||
suppressbuildreports: bool,
|
||||
};
|
||||
|
||||
def PKG_COUNT_MAX: i32 = 2147483647;
|
||||
@@ -1368,8 +1374,9 @@ fn pkgsetplanpaths(p: *pkgplan, groups: []pkggroup, root: str, index: i32,
|
||||
return false;
|
||||
};
|
||||
if (buildonly) {
|
||||
if (outputdir && strings.compare(g.pkg, "main") == 0) {
|
||||
if (!pkgjoinpath(outname, pkgbase(g.dir), &g.bin)) { return false; };
|
||||
if (outputdir && strings.compare(g.pkg, "main") == 0
|
||||
&& !p.outputpatherror && !p.emitasm) {
|
||||
if (!pkgjoinpath(outname, g.basename, &g.bin)) { return false; };
|
||||
} else if (outname.len != 0 && !outputdir) { g.bin = outname; }
|
||||
else if (!pkgstring(&g.bin, g.root, "/package.build")) { return false; };
|
||||
} else {
|
||||
@@ -1416,7 +1423,7 @@ fn pkgreportcommand(fd: i32, kind: str, g: *pkggroup,
|
||||
fn pkgstartbuild(p: *pkgplan, groups: []pkggroup, builder: str, includes: []str,
|
||||
libdirs: []str, libs: []str, h: *exec.process) bool = {
|
||||
let nproducts: i32 = p.end - p.start;
|
||||
let capacity: i32 = 16;
|
||||
let capacity: i32 = 24;
|
||||
if (nproducts < 0 || nproducts > (PKG_COUNT_MAX - capacity) / 10) {
|
||||
pkgputln(os.STDERR_FILENO, "wwtest package: package graph is too large");
|
||||
return false;
|
||||
@@ -1457,6 +1464,18 @@ fn pkgstartbuild(p: *pkgplan, groups: []pkggroup, builder: str, includes: []str,
|
||||
append(ba, "--ww-create-output-dir");
|
||||
append(ba, p.outputdir);
|
||||
};
|
||||
if (p.outputpatherror) {
|
||||
append(ba, "--ww-command-output-path-error");
|
||||
};
|
||||
if (p.defaultoutputdir.len != 0) {
|
||||
append(ba, "--ww-default-output-dir");
|
||||
append(ba, p.defaultoutputdir);
|
||||
};
|
||||
if (p.outputcollisionbase.len != 0) {
|
||||
append(ba, "--ww-command-output-collision");
|
||||
append(ba, p.outputcollisionbase);
|
||||
append(ba, p.outputcollisiondir);
|
||||
};
|
||||
if (p.identity.len != 0) {
|
||||
append(ba, "--ww-root-identity");
|
||||
append(ba, p.identity);
|
||||
@@ -1627,8 +1646,10 @@ fn pkgemitplan(p: *pkgplan, groups: []pkggroup,
|
||||
let i: i32 = p.start;
|
||||
for (i < p.end) {
|
||||
if (!pkgproductbuilt(&groups[i], buildonly)) {
|
||||
pkgreportcommand(os.STDERR_FILENO, "build", &groups[i],
|
||||
&p.buildres);
|
||||
if (!p.suppressbuildreports) {
|
||||
pkgreportcommand(os.STDERR_FILENO, "build", &groups[i],
|
||||
&p.buildres);
|
||||
};
|
||||
failed += 1;
|
||||
} else if (buildonly) {
|
||||
void;
|
||||
@@ -2165,6 +2186,7 @@ export fn packagecommand(args: []str) int = {
|
||||
g.dir = f.path;
|
||||
g.pkg = family;
|
||||
if (g.pkg.len == 0) { g.pkg = srcs[f.start].pkg; };
|
||||
g.basename = "";
|
||||
g.testname = "";
|
||||
g.publish = "";
|
||||
g.prodpkg = f.prodpkg;
|
||||
@@ -2193,57 +2215,48 @@ export fn packagecommand(args: []str) int = {
|
||||
return 1;
|
||||
};
|
||||
pkgsortgroups(groups);
|
||||
i = 0;
|
||||
for (i < groups.len) {
|
||||
groups[i].basename = pkgbase(groups[i].dir);
|
||||
if (roots.len == 1 && !anyrecurse && requestidentity.len != 0) {
|
||||
groups[i].basename = pkgimportbase(requestidentity);
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
let defaultout: bool = false;
|
||||
if (buildonly && !buildnull && outname.len == 0 && folders.len == 1
|
||||
&& groups.len == 1 && strings.compare(groups[0].pkg, "main") == 0) {
|
||||
outname = pkgbase(groups[0].dir);
|
||||
outname = groups[0].basename;
|
||||
defaultout = true;
|
||||
};
|
||||
let defaultoutputdir: str = "";
|
||||
if (defaultout && pkgstatdir(outname)) {
|
||||
pkgput(os.STDERR_FILENO, "ww: build output \"");
|
||||
pkgput(os.STDERR_FILENO, outname);
|
||||
pkgputln(os.STDERR_FILENO, "\" already exists and is a directory");
|
||||
return 1;
|
||||
defaultoutputdir = outname;
|
||||
};
|
||||
let outputdir: bool = buildonly && explicitout && !buildnull
|
||||
&& pkgoutputdir(outname);
|
||||
let outputpatherror: bool = false;
|
||||
let outputcollisionbase: str = "";
|
||||
let outputcollisiondir: str = "";
|
||||
if (outputdir) {
|
||||
let mainpackages: i32 = 0;
|
||||
i = 0;
|
||||
for (i < groups.len) {
|
||||
if (strings.compare(groups[i].pkg, "main") == 0) {
|
||||
mainpackages += 1;
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
if (mainpackages == 0) {
|
||||
pkgputln(os.STDERR_FILENO, "ww: no main packages to build");
|
||||
return 1;
|
||||
};
|
||||
i = 0;
|
||||
for (i < groups.len) {
|
||||
if (strings.compare(groups[i].pkg, "main") == 0) {
|
||||
let slash: i64 = 1i64;
|
||||
if (strings.hassuffix(outname, "/")) { slash = 0i64; };
|
||||
let outputlen: i64 = outname.len: i64 + slash
|
||||
+ pkgbase(groups[i].dir).len: i64;
|
||||
+ groups[i].basename.len: i64;
|
||||
if (outputlen + 1i64 > os.PATH_MAX: i64) {
|
||||
pkgputln(os.STDERR_FILENO,
|
||||
"ww: command output path is too long");
|
||||
return 1;
|
||||
outputpatherror = true;
|
||||
};
|
||||
let j: i32 = 0;
|
||||
for (j < i) {
|
||||
if (strings.compare(groups[j].pkg, "main") == 0
|
||||
&& strings.compare(pkgbase(groups[j].dir),
|
||||
pkgbase(groups[i].dir)) == 0) {
|
||||
pkgput(os.STDERR_FILENO,
|
||||
"ww: multiple commands produce output basename ");
|
||||
pkgputquoted(os.STDERR_FILENO, pkgbase(groups[i].dir));
|
||||
pkgput(os.STDERR_FILENO, " in directory ");
|
||||
pkgputquoted(os.STDERR_FILENO, outname);
|
||||
pkgputln(os.STDERR_FILENO, "");
|
||||
return 1;
|
||||
&& strings.compare(groups[j].basename,
|
||||
groups[i].basename) == 0
|
||||
&& outputcollisionbase.len == 0) {
|
||||
outputcollisionbase = groups[i].basename;
|
||||
outputcollisiondir = outname;
|
||||
};
|
||||
j += 1;
|
||||
};
|
||||
@@ -2273,11 +2286,9 @@ export fn packagecommand(args: []str) int = {
|
||||
};
|
||||
i = 0;
|
||||
for (i < groups.len) {
|
||||
let leaf: str = pkgbase(groups[i].dir);
|
||||
if (roots.len == 1 && !anyrecurse && requestidentity.len != 0) {
|
||||
leaf = pkgimportbase(requestidentity);
|
||||
if (!pkgstring(&groups[i].testname, groups[i].basename, ".test")) {
|
||||
return 1;
|
||||
};
|
||||
if (!pkgstring(&groups[i].testname, leaf, ".test")) { return 1; };
|
||||
groups[i].publish = "";
|
||||
if (testretain && !groups[i].notests && !testnull) {
|
||||
if (!explicitout) {
|
||||
@@ -2368,6 +2379,12 @@ export fn packagecommand(args: []str) int = {
|
||||
&& groups.len == 1
|
||||
&& strings.compare(groups[0].pkg, "main") != 0;
|
||||
plan.emitasm = emitasm;
|
||||
plan.outputpatherror = outputpatherror;
|
||||
plan.defaultoutputdir = defaultoutputdir;
|
||||
plan.outputcollisionbase = outputcollisionbase;
|
||||
plan.outputcollisiondir = outputcollisiondir;
|
||||
plan.suppressbuildreports = buildonly
|
||||
&& (outputdir || defaultoutputdir.len != 0);
|
||||
append(plans, plan);
|
||||
let createdir: str = "";
|
||||
if (buildonly && outputdir) {
|
||||
|
||||
@@ -1465,6 +1465,7 @@ type seppkg = struct {
|
||||
generatedtargets: []i32,
|
||||
ngeneratedtargets: i32,
|
||||
failed: bool,
|
||||
action: bool, // reached by this request's semantic action list
|
||||
testsupport: bool,
|
||||
loaded: bool,
|
||||
exportchanged: bool,
|
||||
@@ -1513,6 +1514,7 @@ type sepproduct = struct {
|
||||
variant: i32,
|
||||
directoryproduct: bool,
|
||||
notests: bool,
|
||||
buildaction: bool, // loaded product retained in the action list
|
||||
context: i32,
|
||||
root: i32,
|
||||
variantroot: i32,
|
||||
@@ -2973,6 +2975,7 @@ fn sepfindoraddvariant(g: *sepgraph, path: *u8, entry: *u8,
|
||||
g.pkg[g.n].generatedtargets = emptytargets;
|
||||
g.pkg[g.n].ngeneratedtargets = 0;
|
||||
g.pkg[g.n].failed = false;
|
||||
g.pkg[g.n].action = false;
|
||||
g.pkg[g.n].testsupport = false;
|
||||
g.pkg[g.n].loaded = false;
|
||||
g.pkg[g.n].exportchanged = false;
|
||||
@@ -3537,7 +3540,9 @@ fn sepfname(g: *sepgraph, pi: i32, scratch: *u8, suffix: str) *u8 = {
|
||||
fn sepvalidateartifactpaths(g: *sepgraph, scratch: *u8) i32 = {
|
||||
let i: i32 = 0;
|
||||
for (i < g.n) {
|
||||
if (g.pkg[i].failed || !g.pkg[i].loaded) { i += 1; continue; };
|
||||
if (g.pkg[i].failed || !g.pkg[i].loaded || !g.pkg[i].action) {
|
||||
i += 1; continue;
|
||||
};
|
||||
if (g.pkg[i].storage == nil
|
||||
&& sepassignstorage(&g.pkg[i], scratch) < 0) { return -1; };
|
||||
i += 1;
|
||||
@@ -3547,10 +3552,12 @@ fn sepvalidateartifactpaths(g: *sepgraph, scratch: *u8) i32 = {
|
||||
changed = false;
|
||||
i = 0;
|
||||
for (i < g.n && !changed) {
|
||||
if (g.pkg[i].failed || !g.pkg[i].loaded) { i += 1; continue; };
|
||||
if (g.pkg[i].failed || !g.pkg[i].loaded || !g.pkg[i].action) {
|
||||
i += 1; continue;
|
||||
};
|
||||
let j: i32 = i + 1;
|
||||
for (j < g.n) {
|
||||
if (g.pkg[j].failed || !g.pkg[j].loaded
|
||||
if (g.pkg[j].failed || !g.pkg[j].loaded || !g.pkg[j].action
|
||||
|| !cstreq(g.pkg[i].storage, g.pkg[j].storage)) {
|
||||
j += 1; continue;
|
||||
};
|
||||
@@ -4350,6 +4357,7 @@ fn sepaddgeneratedmain(g: *sepgraph, product: *sepproduct, ordinal: i32,
|
||||
ti += 1;
|
||||
};
|
||||
p.failed = false;
|
||||
p.action = false;
|
||||
p.testsupport = false;
|
||||
p.loaded = true;
|
||||
p.exportchanged = false;
|
||||
@@ -4473,6 +4481,7 @@ fn sepclonefortest(g: *sepgraph, original: i32, owner: *u8,
|
||||
p.generatedtargets = emptytargets;
|
||||
p.ngeneratedtargets = 0;
|
||||
p.failed = src.failed;
|
||||
p.action = false;
|
||||
p.testsupport = src.testsupport;
|
||||
p.loaded = src.loaded;
|
||||
p.exportchanged = false;
|
||||
@@ -4501,6 +4510,15 @@ fn sepclonefortest(g: *sepgraph, original: i32, owner: *u8,
|
||||
seppkgfreeowned(&p);
|
||||
return -1;
|
||||
};
|
||||
// A test copy is product-scoped even when its replaced source node is not
|
||||
// in the final action closure. Preserve the complete-action locator without
|
||||
// relying on a storage collision with that inactive source node.
|
||||
p.storage = sepstoragedigest(&p);
|
||||
p.storagehashed = true;
|
||||
if (p.storage == nil) {
|
||||
seppkgfreeowned(&p);
|
||||
return -1;
|
||||
};
|
||||
|
||||
if (src.nsources > 0) {
|
||||
let sources: []*u8;
|
||||
@@ -5856,7 +5874,7 @@ fn sepvalidateunitowner(g: *sepgraph, pi: i32, scratch: *u8) i32 = {
|
||||
fn sepvalidateworkdirowners(g: *sepgraph, scratch: *u8) i32 = {
|
||||
let i: i32 = 0;
|
||||
for (i < g.n) {
|
||||
if (!g.pkg[i].failed && g.pkg[i].loaded
|
||||
if (!g.pkg[i].failed && g.pkg[i].loaded && g.pkg[i].action
|
||||
&& sepvalidateunitowner(g, i, scratch) < 0) { return -1; };
|
||||
i += 1;
|
||||
};
|
||||
@@ -6035,7 +6053,7 @@ fn sepvalidaterequeststaging(g: *sepgraph, scratch: *u8, warm: bool,
|
||||
".a.new", ".init.unit.new", ".init.s.new", ".init.o.new"];
|
||||
let pi: i32 = 0;
|
||||
for (pi < g.n) {
|
||||
if (!g.pkg[pi].failed && g.pkg[pi].loaded) {
|
||||
if (!g.pkg[pi].failed && g.pkg[pi].loaded && g.pkg[pi].action) {
|
||||
let si: i32 = 0;
|
||||
for (si < suffix.len) {
|
||||
let path: *u8 = sepfname(g, pi, scratch, suffix[si]);
|
||||
@@ -6437,6 +6455,7 @@ fn sepdiscardrequeststaging(g: *sepgraph, scratch: *u8, warm: bool,
|
||||
let rc: i32 = 0;
|
||||
let pi: i32 = 0;
|
||||
for (pi < g.n) {
|
||||
if (!g.pkg[pi].action) { pi += 1; continue; };
|
||||
let si: i32 = 0;
|
||||
for (si < suffix.len) {
|
||||
let path: *u8 = sepfname(g, pi, scratch, suffix[si]);
|
||||
@@ -6941,10 +6960,12 @@ fn sepfinishrequest(selfdir: *u8, l6: *u8, c6: *u8, a6: *u8,
|
||||
};
|
||||
|
||||
fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
|
||||
out: *u8, objstem: *u8, incs: *u8, lf: *lflags,
|
||||
publishpackage: i32, requirecommand: i32, istest: i32,
|
||||
products: *sepproduct, nproducts: i32, emitasm: i32,
|
||||
workdir: *u8, createworkdir: bool, createoutputdir: *u8,
|
||||
out: *u8, objstem: *u8, incs: *u8, lf: *lflags,
|
||||
publishpackage: i32, requirecommand: i32, istest: i32,
|
||||
products: *sepproduct, nproducts: i32, emitasm: i32,
|
||||
workdir: *u8, createworkdir: bool, createoutputdir: *u8,
|
||||
defaultoutputdir: *u8, outputpatherror: bool,
|
||||
outputcollisionbase: *u8, outputcollisiondir: *u8,
|
||||
scratchout: **u8,
|
||||
graphout: **sepgraph) i32 = {
|
||||
sepfatalallocation = false;
|
||||
@@ -7511,9 +7532,6 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
|
||||
let rootpackage: bool = istest == 0 && nproducts == 1
|
||||
&& !g.pkg[products[0].root].failed
|
||||
&& !seprootiscommand(&g.pkg[products[0].root]);
|
||||
if (sepvalidateartifactpaths(g, scratch) < 0) { return 1; };
|
||||
if (warm && workdirexists
|
||||
&& sepvalidateworkdirowners(g, scratch) < 0) { return 1; };
|
||||
let ci: i32 = 0;
|
||||
let order: []i32;
|
||||
let stack: []i32;
|
||||
@@ -7572,6 +7590,22 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
|
||||
return 1;
|
||||
};
|
||||
};
|
||||
if (istest == 0 && createoutputdir != nil) {
|
||||
let actions: i32 = 0;
|
||||
producti = 0;
|
||||
for (producti < nproducts) {
|
||||
let root: i32 = products[producti].root;
|
||||
if (g.pkg[root].failed) { return 1; };
|
||||
products[producti].buildaction =
|
||||
seprootiscommand(&g.pkg[root]);
|
||||
if (products[producti].buildaction) { actions += 1; };
|
||||
producti += 1;
|
||||
};
|
||||
if (actions == 0) {
|
||||
cerr("ww: no main packages to build\n");
|
||||
return 1;
|
||||
};
|
||||
};
|
||||
if (!g.pkg[products[0].root].failed
|
||||
&& rootpackage && publishpackage != 0 && emitasm == 0
|
||||
&& validatepackageoutputpath(out) < 0) { return 1; };
|
||||
@@ -7580,12 +7614,25 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
|
||||
producti = 0;
|
||||
for (producti < nproducts) {
|
||||
let root: i32 = products[producti].root;
|
||||
if (!g.pkg[root].failed) {
|
||||
if (products[producti].buildaction && !g.pkg[root].failed) {
|
||||
if (septopovisit(g, root, order,
|
||||
&norder, stack, 0) < 0) { return 1; };
|
||||
};
|
||||
producti += 1;
|
||||
};
|
||||
let actionpi: i32 = 0;
|
||||
for (actionpi < g.n) {
|
||||
g.pkg[actionpi].action = false;
|
||||
actionpi += 1;
|
||||
};
|
||||
let actionoi: i32 = 0;
|
||||
for (actionoi < norder) {
|
||||
g.pkg[order[actionoi]].action = true;
|
||||
actionoi += 1;
|
||||
};
|
||||
if (sepvalidateartifactpaths(g, scratch) < 0) { return 1; };
|
||||
if (warm && workdirexists
|
||||
&& sepvalidateworkdirowners(g, scratch) < 0) { return 1; };
|
||||
// Propagate already-known package-load failures before scratch or status
|
||||
// acquisition. Good sibling roots may remain viable for deterministic
|
||||
// staging/diagnosis, but any failure rejects publication; an entirely failed
|
||||
@@ -7605,12 +7652,31 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
|
||||
let viableproduct: bool = false;
|
||||
producti = 0;
|
||||
for (producti < nproducts) {
|
||||
if (!g.pkg[products[producti].root].failed) {
|
||||
if (products[producti].buildaction
|
||||
&& !g.pkg[products[producti].root].failed) {
|
||||
viableproduct = true;
|
||||
};
|
||||
producti += 1;
|
||||
};
|
||||
if (!viableproduct) { return 1; };
|
||||
if (istest == 0 && emitasm == 0 && outputpatherror) {
|
||||
cerr("ww: command output path is too long\n");
|
||||
return 1;
|
||||
};
|
||||
if (istest == 0 && emitasm == 0 && outputcollisionbase != nil) {
|
||||
cerr("ww: multiple commands produce output basename ");
|
||||
sepputquoted(outputcollisionbase);
|
||||
cerr(" in directory ");
|
||||
sepputquoted(outputcollisiondir);
|
||||
cerr("\n");
|
||||
return 1;
|
||||
};
|
||||
if (istest == 0 && emitasm == 0 && defaultoutputdir != nil && nproducts == 1
|
||||
&& seprootiscommand(&g.pkg[products[0].root])) {
|
||||
cerrpath("ww: build output \"", defaultoutputdir,
|
||||
"\" already exists and is a directory\n");
|
||||
return 1;
|
||||
};
|
||||
if (sepvalidaterequeststaging(g, scratch, warm, products, nproducts,
|
||||
rootpackage, publishpackage, emitasm, istest) < 0) {
|
||||
sepfreeproductstaging(products, nproducts);
|
||||
@@ -7622,10 +7688,8 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
|
||||
let createdwork: sepcreateddirs;
|
||||
createdoutput.n = 0;
|
||||
createdwork.n = 0;
|
||||
let outputmode: i32 = 448;
|
||||
if (istest != 0) { outputmode = 511; };
|
||||
if (createoutputdir != nil
|
||||
&& sepmkdirsrecord(createoutputdir, outputmode, &createdoutput) != 0) {
|
||||
if (emitasm == 0 && createoutputdir != nil
|
||||
&& sepmkdirsrecord(createoutputdir, 511, &createdoutput) != 0) {
|
||||
if (istest != 0) {
|
||||
cerrpath("ww: cannot create test output directory ",
|
||||
createoutputdir, "\n");
|
||||
@@ -8149,7 +8213,8 @@ fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32,
|
||||
objstem: *u8, incs: *u8, lf: *lflags, publishpackage: i32,
|
||||
requirecommand: i32, istest: i32,
|
||||
rootvariant: i32, testpackage: *u8, emitasm: i32,
|
||||
keepscratch: i32, workdir: *u8) i32 = {
|
||||
keepscratch: i32, workdir: *u8, createoutputdir: *u8,
|
||||
defaultoutputdir: *u8, outputpatherror: bool) i32 = {
|
||||
let scratch: *u8 = nil;
|
||||
let g: *sepgraph = nil;
|
||||
let product: sepproduct;
|
||||
@@ -8169,12 +8234,15 @@ fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32,
|
||||
product.variant = rootvariant;
|
||||
product.directoryproduct = false;
|
||||
product.notests = false;
|
||||
product.buildaction = true;
|
||||
product.root = -1;
|
||||
product.variantroot = -1;
|
||||
product.support = -1;
|
||||
let r: i32 = buildonesepimpl(selfdir, src, entryisdir, out, objstem,
|
||||
incs, lf, publishpackage, requirecommand, istest, &product, 1,
|
||||
emitasm, workdir, false, nil, &scratch, &g);
|
||||
emitasm, workdir, false, createoutputdir, defaultoutputdir,
|
||||
outputpatherror, nil, nil,
|
||||
&scratch, &g);
|
||||
sepgraphfree(g);
|
||||
if (keepscratch == 0 && scratch != nil) {
|
||||
if (cstrendswithlit(scratch, ".sepwork")) {
|
||||
@@ -8205,7 +8273,9 @@ fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32,
|
||||
fn buildpackagetests(selfdir: *u8, src: *u8, rootidentity: *u8,
|
||||
incs: *u8, workdir: *u8, products: *sepproduct, nproducts: i32,
|
||||
istest: i32, publishpackage: i32, lf: *lflags, emitasm: i32,
|
||||
createworkdir: bool, createoutputdir: *u8) i32 = {
|
||||
createworkdir: bool, createoutputdir: *u8, defaultoutputdir: *u8,
|
||||
outputpatherror: bool, outputcollisionbase: *u8,
|
||||
outputcollisiondir: *u8) i32 = {
|
||||
let scratch: *u8 = nil;
|
||||
let g: *sepgraph = nil;
|
||||
let i: i32 = 0;
|
||||
@@ -8217,7 +8287,8 @@ fn buildpackagetests(selfdir: *u8, src: *u8, rootidentity: *u8,
|
||||
products[0].out, products[0].out, incs, lf,
|
||||
publishpackage, 0, istest,
|
||||
products, nproducts, emitasm, workdir, createworkdir,
|
||||
createoutputdir, &scratch, &g);
|
||||
createoutputdir, defaultoutputdir, outputpatherror,
|
||||
outputcollisionbase, outputcollisiondir, &scratch, &g);
|
||||
sepgraphfree(g);
|
||||
return r;
|
||||
};
|
||||
@@ -8313,7 +8384,7 @@ fn resolvemodule(selfdir: *u8, name: *u8, incs: *u8, isdir: *i32) *u8 = {
|
||||
};
|
||||
|
||||
fn writeusage(fd: i32) void = {
|
||||
let s: str = "usage: ww [-V] <subcommand> [args...]\n -V print version and exit\n build [-S] [-w DIR] [-I DIR] [-o FILE] [path ...] build local package graphs\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 -o publishes a non-main archive FILE + FILE.wwi\n lib/... every eligible package under lib, recursively\n . build the cwd's <basename>.ww\n";
|
||||
let s: str = "usage: ww [-V] <subcommand> [args...]\n -V print version and exit\n build [-S] [-w DIR] [-I DIR] [-o FILE|DIR] [path ...] build local package graphs\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 -o FILE publishes a non-main archive FILE + FILE.wwi\n -o DIR publishes each selected command beneath DIR\n lib/... every eligible package under lib, recursively\n . build the cwd's <basename>.ww\n";
|
||||
os.write(fd, s.ptr, s.len: u64);
|
||||
};
|
||||
|
||||
@@ -8352,6 +8423,54 @@ fn defaultoutpath(src: *u8) *u8 = {
|
||||
return out.ptr;
|
||||
};
|
||||
|
||||
// Go's build -o directory branch follows an existing destination through
|
||||
// stat, and a trailing platform separator declares a directory which the
|
||||
// request may need to create. WW's platform separator is '/'.
|
||||
fn buildoutputdir(path: *u8) bool = {
|
||||
if (cstrendswithlit(path, "/")) { return true; };
|
||||
let fi: os.filestat;
|
||||
match (os.stat(&fi, pathstr(path))) {
|
||||
case void => {
|
||||
let typ: u32 = (fi.mode: u32) & 61440u32;
|
||||
return typ == os.mode.DIR: u32;
|
||||
};
|
||||
case let e: os.oserror => return false;
|
||||
};
|
||||
};
|
||||
|
||||
fn buildoutputpath(dir: *u8, src: *u8) *u8 = {
|
||||
let base: *u8 = defaultoutpath(src);
|
||||
let need: u64 = 0u64;
|
||||
if (!sepaddbytes(&need, cstrlen(dir))
|
||||
|| (!cstrendswithlit(dir, "/") && !sepaddbytes(&need, 1u64))
|
||||
|| !sepaddbytes(&need, cstrlen(base))
|
||||
|| !sepaddbytes(&need, 1u64)
|
||||
|| need > os.PATH_MAX: u64) {
|
||||
return nil;
|
||||
};
|
||||
if (cstrendswithlit(dir, "/")) {
|
||||
return sepappendlit(dir, pathstr(base));
|
||||
};
|
||||
return sepjoinpath(dir, base);
|
||||
};
|
||||
|
||||
fn defaultimportoutpath(identity: *u8) *u8 = {
|
||||
let n: u64 = cstrlen(identity);
|
||||
let start: u64 = 0u64;
|
||||
let i: u64 = 0u64;
|
||||
for (i < n) {
|
||||
if (identity[i] == 46u8) { start = i + 1u64; };
|
||||
i += 1u64;
|
||||
};
|
||||
let out: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||
out.len = os.PATH_MAX;
|
||||
let off: u64 = 0u64;
|
||||
i = start;
|
||||
for (i < n) { out[off] = identity[i]; off += 1u64; i += 1u64; };
|
||||
cstrseal(out.ptr, off);
|
||||
return out.ptr;
|
||||
};
|
||||
|
||||
fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
let src: *u8 = nil;
|
||||
let srcindex: i32 = -1;
|
||||
@@ -8504,11 +8623,34 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
let objstem: *u8 = nil;
|
||||
let discardoutput: bool = outflag != nil
|
||||
&& cstreqlit(outflag, "/dev/null");
|
||||
let outputdir: bool = outflag != nil && outflag[0u64] != 0u8
|
||||
&& !discardoutput && buildoutputdir(outflag);
|
||||
let rootidentity: *u8 = nil;
|
||||
if (!requestedliteral && isdir != 0) { rootidentity = src; };
|
||||
if (outputdir && isdir != 0) {
|
||||
let coordinatortarget: *u8 = resolved;
|
||||
if (srcindex < 0) { coordinatortarget = nil; };
|
||||
return execpackagetests(selfdir, argv, argc, start, srcindex,
|
||||
coordinatortarget, rootidentity, srcindex < 0, true);
|
||||
};
|
||||
let createoutputdir: *u8 = nil;
|
||||
let outputpatherror: bool = false;
|
||||
if (outflag != nil && outflag[0u64] != 0u8 && !discardoutput) {
|
||||
// -o sets both the binary path and the intermediate stem so
|
||||
// artifacts land beside the requested output (T3).
|
||||
out = outflag;
|
||||
objstem = outflag;
|
||||
if (outputdir) {
|
||||
out = buildoutputpath(outflag, resolved);
|
||||
if (out == nil) {
|
||||
outputpatherror = true;
|
||||
out = defaultoutpath(resolved);
|
||||
};
|
||||
createoutputdir = outflag;
|
||||
} else {
|
||||
out = outflag;
|
||||
};
|
||||
objstem = out;
|
||||
} else { if (isdir != 0 && rootidentity != nil) {
|
||||
out = defaultimportoutpath(rootidentity);
|
||||
} else { if (isdir != 0) {
|
||||
let rlen: u64 = cstrlen(resolved);
|
||||
for (rlen > 1u64) {
|
||||
@@ -8525,14 +8667,16 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
cstrseal(out, off);
|
||||
} else {
|
||||
out = defaultoutpath(resolved);
|
||||
}; };
|
||||
}; }; };
|
||||
let defaultoutputdir: *u8 = nil;
|
||||
if ((outflag == nil || outflag[0u64] == 0u8) && buildoutputdir(out)) {
|
||||
defaultoutputdir = out;
|
||||
};
|
||||
let lf: lflags;
|
||||
lf.libdirs = &libdirs[0];
|
||||
lf.nlibdirs = nlibdirs;
|
||||
lf.libs = &libs[0];
|
||||
lf.nlibs = nlibs;
|
||||
let rootidentity: *u8 = nil;
|
||||
if (!requestedliteral && isdir != 0) { rootidentity = src; };
|
||||
if (discardoutput) {
|
||||
let tmp: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||
tmp.len = os.PATH_MAX;
|
||||
@@ -8547,7 +8691,7 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
let rc: i32 = buildonesep(selfdir, resolved, isdir, rootidentity,
|
||||
outp, outp, incs.ptr, &lf,
|
||||
0i32, 0i32, 0i32, SEP_VARIANT_PRODUCTION, nil,
|
||||
emitasm, 0i32, workdir);
|
||||
emitasm, 0i32, workdir, nil, nil, false);
|
||||
let cleanbad: bool = false;
|
||||
let cleanrc: i32 = os.remove(pathstr(outp));
|
||||
if (cleanrc != 0 && cleanrc != -2i32) {
|
||||
@@ -8566,7 +8710,8 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
return buildonesep(selfdir, resolved, isdir, rootidentity,
|
||||
out, objstem, incs.ptr, &lf,
|
||||
publishpackage, 0i32, 0i32, SEP_VARIANT_PRODUCTION, nil,
|
||||
emitasm, 1i32, workdir);
|
||||
emitasm, 1i32, workdir, createoutputdir, defaultoutputdir,
|
||||
outputpatherror);
|
||||
};
|
||||
|
||||
// Format the owned driver workspace /tmp/<prefix><pid> into buf. Pid is
|
||||
@@ -8748,7 +8893,7 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
if (buildonesep(selfdir, resolved, isdir, rootidentity,
|
||||
outp, outp, incs.ptr, &lf,
|
||||
0i32, 1i32, 0i32, SEP_VARIANT_PRODUCTION, nil,
|
||||
0i32, 0i32, nil) != 0) {
|
||||
0i32, 0i32, nil, nil, nil, false) != 0) {
|
||||
let cleanrc: i32 = os.remove(pathstr(outp));
|
||||
if (cleanrc != 0 && cleanrc != -2i32) {
|
||||
cerr("ww: cannot remove temporary output\n");
|
||||
@@ -8841,7 +8986,7 @@ fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32,
|
||||
if (retainout) { keep = 1; };
|
||||
let bres: i32 = buildonesep(selfdir, src, 0, nil, outp, objstem, incs, &lf,
|
||||
0i32, 0i32, 1i32, SEP_VARIANT_PRODUCTION, nil,
|
||||
emitasm, keep, workdir);
|
||||
emitasm, keep, workdir, nil, nil, false);
|
||||
if (bres != 0) {
|
||||
if (owntmp) {
|
||||
let cleanrc: i32 = os.remove(pathstr(outp));
|
||||
@@ -8942,6 +9087,10 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
let packagepublish: bool = false;
|
||||
let packagecreateworkdir: bool = false;
|
||||
let packagecreateoutputdir: *u8 = nil;
|
||||
let packageoutputpatherror: bool = false;
|
||||
let packagedefaultoutputdir: *u8 = nil;
|
||||
let packageoutputcollisionbase: *u8 = nil;
|
||||
let packageoutputcollisiondir: *u8 = nil;
|
||||
let maxpackagelflags: i32 = 32;
|
||||
let packagelibdirs: [32]*u8;
|
||||
let packagelibs: [32]*u8;
|
||||
@@ -9008,6 +9157,37 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
i += 2;
|
||||
continue;
|
||||
};
|
||||
if (cstreqlit(p, "--ww-command-output-path-error")) {
|
||||
if (packageoutputpatherror) {
|
||||
cerr("ww test: invalid --ww-command-output-path-error\n");
|
||||
return 2;
|
||||
};
|
||||
packageoutputpatherror = true;
|
||||
i += 1;
|
||||
continue;
|
||||
};
|
||||
if (cstreqlit(p, "--ww-default-output-dir")) {
|
||||
if (i + 1 >= argc || packagedefaultoutputdir != nil
|
||||
|| argv[i + 1][0u64] == 0u8) {
|
||||
cerr("ww test: invalid --ww-default-output-dir\n");
|
||||
return 2;
|
||||
};
|
||||
packagedefaultoutputdir = argv[i + 1];
|
||||
i += 2;
|
||||
continue;
|
||||
};
|
||||
if (cstreqlit(p, "--ww-command-output-collision")) {
|
||||
if (i + 2 >= argc || packageoutputcollisionbase != nil
|
||||
|| argv[i + 1][0u64] == 0u8
|
||||
|| argv[i + 2][0u64] == 0u8) {
|
||||
cerr("ww test: invalid --ww-command-output-collision\n");
|
||||
return 2;
|
||||
};
|
||||
packageoutputcollisionbase = argv[i + 1];
|
||||
packageoutputcollisiondir = argv[i + 2];
|
||||
i += 3;
|
||||
continue;
|
||||
};
|
||||
if (cstreqlit(p, "--ww-package-test")) {
|
||||
if (i + 9 >= argc) {
|
||||
cerr("ww test: --ww-package-test needs kind, package, production, internal, external, directory, output, publication, and status\n");
|
||||
@@ -9067,6 +9247,7 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
if (buildproduct) { product.variant = SEP_VARIANT_PRODUCTION; };
|
||||
product.directoryproduct = true;
|
||||
product.notests = testproduct && !hasinternal && !hasexternal;
|
||||
product.buildaction = true;
|
||||
product.root = -1;
|
||||
product.variantroot = -1;
|
||||
product.productionroot = -1;
|
||||
@@ -9243,6 +9424,12 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
cerr("ww test: invalid private directory creation\n");
|
||||
return 2;
|
||||
};
|
||||
if ((packageoutputpatherror || packagedefaultoutputdir != nil
|
||||
|| packageoutputcollisionbase != nil)
|
||||
&& (!packagebuild || products.len == 0)) {
|
||||
cerr("ww test: invalid private output preflight\n");
|
||||
return 2;
|
||||
};
|
||||
if (packagecreateworkdir && products.len == 0) {
|
||||
cerr("ww test: invalid private directory creation\n");
|
||||
return 2;
|
||||
@@ -9388,7 +9575,9 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
incs.ptr, workdir,
|
||||
products.ptr, products.len, packagemode, publishmode,
|
||||
&packagelinks, emitasm, packagecreateworkdir,
|
||||
packagecreateoutputdir);
|
||||
packagecreateoutputdir, packagedefaultoutputdir,
|
||||
packageoutputpatherror, packageoutputcollisionbase,
|
||||
packageoutputcollisiondir);
|
||||
};
|
||||
let replacement: *u8 = nil;
|
||||
if (resolved != target) { replacement = resolved; };
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user