ww build: honor output directories
This commit is contained in:
229
cmd/ww/main.c
229
cmd/ww/main.c
@@ -25,7 +25,7 @@
|
|||||||
static const char *usage =
|
static const char *usage =
|
||||||
"usage: ww [-V] <subcommand> [args...]\n"
|
"usage: ww [-V] <subcommand> [args...]\n"
|
||||||
" -V print version and exit\n"
|
" -V print version and exit\n"
|
||||||
" build [-S] [-w DIR] [-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"
|
" run [path] ... build then exec, passing extra args to the program\n"
|
||||||
" test [-S -o STEM] [-w DIR] [options] [path ...] build/run tests; -S emits package asm\n"
|
" test [-S -o STEM] [-w DIR] [options] [path ...] build/run tests; -S emits package asm\n"
|
||||||
" version print version and exit\n"
|
" version print version and exit\n"
|
||||||
@@ -34,7 +34,8 @@ static const char *usage =
|
|||||||
" foo.ww literal file\n"
|
" foo.ww literal file\n"
|
||||||
" foo search cwd, -I dirs, then the source library for foo.ww or foo/\n"
|
" foo search cwd, -I dirs, then the source library for foo.ww or foo/\n"
|
||||||
" lib/foo directory: build its package sources\n"
|
" lib/foo directory: build its package sources\n"
|
||||||
" -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"
|
" lib/... every eligible package under lib, recursively\n"
|
||||||
" . build the cwd's <basename>.ww\n";
|
" . build the cwd's <basename>.ww\n";
|
||||||
|
|
||||||
@@ -1256,6 +1257,7 @@ struct seppkg {
|
|||||||
int ngenerated_targets;
|
int ngenerated_targets;
|
||||||
int generated_targetcap;
|
int generated_targetcap;
|
||||||
int failed; /* discovery/compile failure reaches this action */
|
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 test_support; /* compiler-generated -T support package */
|
||||||
int loaded; /* directory membership/name loaded exactly once */
|
int loaded; /* directory membership/name loaded exactly once */
|
||||||
int export_changed; /* staged export differs from committed export */
|
int export_changed; /* staged export differs from committed export */
|
||||||
@@ -1306,6 +1308,7 @@ struct sepproduct {
|
|||||||
int variant;
|
int variant;
|
||||||
int directory_product;
|
int directory_product;
|
||||||
int no_tests;
|
int no_tests;
|
||||||
|
int build_action; /* loaded product retained in the action list */
|
||||||
int context;
|
int context;
|
||||||
int root;
|
int root;
|
||||||
int variant_root; /* retained single-unit root outside directory products */
|
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)
|
sep_validate_artifact_paths(struct sepgraph *g, const char *scratch)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < g->n; i++) {
|
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
|
if (g->pkg[i].storage == NULL
|
||||||
&& sep_assign_storage(&g->pkg[i], scratch) < 0) return -1;
|
&& 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 {
|
do {
|
||||||
changed = 0;
|
changed = 0;
|
||||||
for (int i = 0; i < g->n && !changed; i++) {
|
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++) {
|
for (int j = i + 1; j < g->n; j++) {
|
||||||
if (g->pkg[j].failed || !g->pkg[j].loaded
|
if (g->pkg[j].failed || !g->pkg[j].loaded
|
||||||
|
|| !g->pkg[j].action
|
||||||
|| strcmp(g->pkg[i].storage,
|
|| strcmp(g->pkg[i].storage,
|
||||||
g->pkg[j].storage) != 0)
|
g->pkg[j].storage) != 0)
|
||||||
continue;
|
continue;
|
||||||
@@ -2932,7 +2938,8 @@ sep_validate_workdir_owners(const struct sepgraph *g, const char *scratch)
|
|||||||
{
|
{
|
||||||
char unit[SEP_ARTIFACT_MAX];
|
char unit[SEP_ARTIFACT_MAX];
|
||||||
for (int i = 0; i < g->n; i++) {
|
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)
|
if (sep_fname(g, i, scratch, ".unit.ww", unit, sizeof unit) < 0)
|
||||||
return -1;
|
return -1;
|
||||||
if (sep_validate_unit_owner(unit, &g->pkg[i]) < 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->is_dir = src->is_dir;
|
||||||
p->variant = SEP_VARIANT_TEST_COPY;
|
p->variant = SEP_VARIANT_TEST_COPY;
|
||||||
p->role = src->role;
|
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->loaded = src->loaded;
|
||||||
p->failed = src->failed;
|
p->failed = src->failed;
|
||||||
p->test_support = src->test_support;
|
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",
|
".o.new", ".a.new", ".init.unit.new", ".init.s.new",
|
||||||
".init.o.new" };
|
".init.o.new" };
|
||||||
for (int pi = 0; pi < g->n; pi++) {
|
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++) {
|
for (size_t si = 0; si < nelem(suffix); si++) {
|
||||||
char path[SEP_ARTIFACT_MAX];
|
char path[SEP_ARTIFACT_MAX];
|
||||||
if (sep_fname(g, pi, scratch, suffix[si], path,
|
if (sep_fname(g, pi, scratch, suffix[si], path,
|
||||||
@@ -5365,7 +5380,8 @@ sep_discard_request_staging(struct sepgraph *g, const char *scratch, int warm,
|
|||||||
".init.unit.ww", ".init.s", ".init.o" };
|
".init.unit.ww", ".init.s", ".init.o" };
|
||||||
const char **suffix = warm ? warm_suffix : cold_suffix;
|
const char **suffix = warm ? warm_suffix : cold_suffix;
|
||||||
int rc = 0;
|
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++) {
|
for (size_t i = 0; i < nelem(warm_suffix); i++) {
|
||||||
char path[SEP_ARTIFACT_MAX];
|
char path[SEP_ARTIFACT_MAX];
|
||||||
if (sep_fname(g, pi, scratch, suffix[i], path,
|
if (sep_fname(g, pi, scratch, suffix[i], path,
|
||||||
@@ -5373,6 +5389,7 @@ sep_discard_request_staging(struct sepgraph *g, const char *scratch, int warm,
|
|||||||
|| (unlink(path) != 0 && errno != ENOENT))
|
|| (unlink(path) != 0 && errno != ENOENT))
|
||||||
rc = -1;
|
rc = -1;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
for (int i = 0; i < nproducts; i++) {
|
for (int i = 0; i < nproducts; i++) {
|
||||||
const char *path[] = { products[i].stage_out,
|
const char *path[] = { products[i].stage_out,
|
||||||
products[i].stage_publish, products[i].stage_iface,
|
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,
|
int require_command, int is_test,
|
||||||
struct sepproduct *products, int nproducts, int emit_asm,
|
struct sepproduct *products, int nproducts, int emit_asm,
|
||||||
const char *workdir, int create_workdir, const char *create_output_dir,
|
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,
|
char *scratchout, size_t scratchoutsz,
|
||||||
struct sepgraph **graphout)
|
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
|
int root_package = !is_test && nproducts == 1
|
||||||
&& !g->pkg[products[0].root].failed
|
&& !g->pkg[products[0].root].failed
|
||||||
&& !sep_root_is_command(&g->pkg[products[0].root]);
|
&& !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 *order = calloc((size_t)g->n, sizeof *order);
|
||||||
int *stack = calloc((size_t)g->n, sizeof *stack);
|
int *stack = calloc((size_t)g->n, sizeof *stack);
|
||||||
int norder = 0;
|
int norder = 0;
|
||||||
@@ -5980,6 +5995,24 @@ build_one_sep_impl(const char *src, int entry_is_dir,
|
|||||||
return 1;
|
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
|
if (!g->pkg[products[0].root].failed
|
||||||
&& root_package && publish_package && !emit_asm
|
&& root_package && publish_package && !emit_asm
|
||||||
&& validate_package_output_path(out) < 0) {
|
&& 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 pi = 0; pi < g->n; pi++) g->pkg[pi].color = 0;
|
||||||
for (int i = 0; i < nproducts; i++) {
|
for (int i = 0; i < nproducts; i++) {
|
||||||
int root = products[i].root;
|
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) {
|
&& sep_topo_visit(g, root, order, &norder, stack, 0) < 0) {
|
||||||
free(stack); free(order); return 1;
|
free(stack); free(order); return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
free(stack);
|
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
|
/* Propagate already-known package-load failures through the union before
|
||||||
* acquiring scratch or completion state. Independent sibling roots may
|
* acquiring scratch or completion state. Independent sibling roots may
|
||||||
* remain viable for deterministic staging/diagnosis, but any failed product
|
* 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;
|
int viable_product = 0;
|
||||||
for (int i = 0; i < nproducts; i++)
|
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) {
|
if (!viable_product) {
|
||||||
free(order);
|
free(order);
|
||||||
return 1;
|
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,
|
if (sep_validate_request_staging(g, scratch, warm, products, nproducts,
|
||||||
root_package, publish_package, emit_asm, is_test) < 0) {
|
root_package, publish_package, emit_asm, is_test) < 0) {
|
||||||
sep_free_product_staging(products, nproducts);
|
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. */
|
* output paths, and action closures have passed their pre-tool checks. */
|
||||||
struct sep_created_dirs created_output = {0};
|
struct sep_created_dirs created_output = {0};
|
||||||
struct sep_created_dirs created_work = {0};
|
struct sep_created_dirs created_work = {0};
|
||||||
if (create_output_dir != NULL && create_output_dir[0] != '\0'
|
if (!emit_asm && create_output_dir != NULL && create_output_dir[0] != '\0'
|
||||||
&& sep_mkdirs(create_output_dir, is_test ? 0777 : 0700,
|
&& sep_mkdirs(create_output_dir, 0777,
|
||||||
&created_output) != 0) {
|
&created_output) != 0) {
|
||||||
fprintf(stderr, is_test
|
fprintf(stderr, is_test
|
||||||
? "ww: cannot create test output directory %s\n"
|
? "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,
|
const struct seplinkflags *linkflags, int publish_package,
|
||||||
int require_command, int is_test,
|
int require_command, int is_test,
|
||||||
int root_variant, const char *test_package, int emit_asm,
|
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};
|
char scratch[PATH_MAX] = {0};
|
||||||
struct sepgraph *g = NULL;
|
struct sepgraph *g = NULL;
|
||||||
@@ -6717,6 +6785,7 @@ build_one_sep(const char *src, int entry_is_dir, const char *root_identity,
|
|||||||
.publish = NULL,
|
.publish = NULL,
|
||||||
.artifact = NULL,
|
.artifact = NULL,
|
||||||
.variant = root_variant,
|
.variant = root_variant,
|
||||||
|
.build_action = 1,
|
||||||
.root = -1,
|
.root = -1,
|
||||||
.variant_root = -1,
|
.variant_root = -1,
|
||||||
.support = -1,
|
.support = -1,
|
||||||
@@ -6725,7 +6794,8 @@ build_one_sep(const char *src, int entry_is_dir, const char *root_identity,
|
|||||||
product.artifact = "__root";
|
product.artifact = "__root";
|
||||||
int r = build_one_sep_impl(src, entry_is_dir, out, objstem,
|
int r = build_one_sep_impl(src, entry_is_dir, out, objstem,
|
||||||
extra_includes, linkflags, publish_package, require_command, is_test,
|
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);
|
sizeof scratch, &g);
|
||||||
sep_graph_free(g);
|
sep_graph_free(g);
|
||||||
if (!keepscratch && scratch[0]) {
|
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,
|
const char *extra_includes, const char *workdir,
|
||||||
struct sepproduct *products, int nproducts, int is_test,
|
struct sepproduct *products, int nproducts, int is_test,
|
||||||
int publish_package, const struct seplinkflags *linkflags, int emit_asm,
|
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};
|
char scratch[PATH_MAX] = {0};
|
||||||
struct sepgraph *g = NULL;
|
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,
|
int r = build_one_sep_impl(src, 1, products[0].out,
|
||||||
products[0].out, extra_includes, linkflags, publish_package, 0, is_test,
|
products[0].out, extra_includes, linkflags, publish_package, 0, is_test,
|
||||||
products, nproducts, emit_asm, workdir, create_workdir,
|
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);
|
sep_graph_free(g);
|
||||||
return r;
|
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';
|
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
|
static int
|
||||||
resolve_module(const char *name, const char *incs, char *out, size_t outsz,
|
resolve_module(const char *name, const char *incs, char *out, size_t outsz,
|
||||||
int *is_dir)
|
int *is_dir)
|
||||||
@@ -7043,6 +7150,7 @@ do_build(int argc, char **argv)
|
|||||||
free(incs);
|
free(incs);
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
int add_dot = src == NULL;
|
||||||
if (src == NULL) src = ".";
|
if (src == NULL) src = ".";
|
||||||
if (strstr(src, "...") != NULL || next < argc || saw_terminator) {
|
if (strstr(src, "...") != NULL || next < argc || saw_terminator) {
|
||||||
free(incs);
|
free(incs);
|
||||||
@@ -7060,11 +7168,32 @@ do_build(int argc, char **argv)
|
|||||||
char out[PATH_MAX];
|
char out[PATH_MAX];
|
||||||
const char *objstem = NULL;
|
const char *objstem = NULL;
|
||||||
int discard_output = strcmp(outflag, "/dev/null") == 0;
|
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) {
|
if (outflag[0] && !discard_output) {
|
||||||
/* -o sets both the binary path and the intermediate stem so
|
/* -o sets both the binary path and the intermediate stem so
|
||||||
* artifacts land beside the requested output (T3). */
|
* artifacts land beside the requested output (T3). */
|
||||||
|
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);
|
memcpy(out, outflag, strlen(outflag) + 1);
|
||||||
|
}
|
||||||
objstem = out;
|
objstem = out;
|
||||||
|
} else if (is_dir && root_identity != NULL) {
|
||||||
|
build_import_leaf(root_identity, out, sizeof out);
|
||||||
} else if (is_dir) {
|
} else if (is_dir) {
|
||||||
char tmp[PATH_MAX];
|
char tmp[PATH_MAX];
|
||||||
memcpy(tmp, resolved, strlen(resolved) + 1);
|
memcpy(tmp, resolved, strlen(resolved) + 1);
|
||||||
@@ -7075,7 +7204,8 @@ do_build(int argc, char **argv)
|
|||||||
} else {
|
} else {
|
||||||
basename_no_ext(resolved, out, sizeof out);
|
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) {
|
if (discard_output) {
|
||||||
char tmpdir[PATH_MAX], tmp[PATH_MAX];
|
char tmpdir[PATH_MAX], tmp[PATH_MAX];
|
||||||
int dn = snprintf(tmpdir, sizeof tmpdir, "/tmp/ww_build_%d", getpid());
|
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. */
|
* library compilation still need request-private product paths. */
|
||||||
int rc = build_one_sep(resolved, is_dir, root_identity, tmp, tmp, incs,
|
int rc = build_one_sep(resolved, is_dir, root_identity, tmp, tmp, incs,
|
||||||
&linkflags, 0, 0, 0, SEP_VARIANT_PRODUCTION, NULL,
|
&linkflags, 0, 0, 0, SEP_VARIANT_PRODUCTION, NULL,
|
||||||
emit_asm, 0, workdir);
|
emit_asm, 0, workdir, NULL, NULL, 0);
|
||||||
int cleanfail = 0;
|
int cleanfail = 0;
|
||||||
if (unlink(tmp) != 0 && errno != ENOENT) {
|
if (unlink(tmp) != 0 && errno != ENOENT) {
|
||||||
fputs("ww: cannot remove temporary output\n", stderr);
|
fputs("ww: cannot remove temporary output\n", stderr);
|
||||||
@@ -7111,7 +7241,8 @@ do_build(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
int rc = build_one_sep(resolved, is_dir, root_identity, out, objstem, incs,
|
int rc = build_one_sep(resolved, is_dir, root_identity, out, objstem, incs,
|
||||||
&linkflags, outflag[0] != '\0', 0, 0, SEP_VARIANT_PRODUCTION, NULL,
|
&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);
|
free(incs);
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
@@ -7156,7 +7287,7 @@ do_run(int argc, char **argv)
|
|||||||
const char *root_identity = !literal && is_dir ? src : NULL;
|
const char *root_identity = !literal && is_dir ? src : NULL;
|
||||||
int buildrc = build_one_sep(resolved, is_dir, root_identity, tmp, tmp, incs,
|
int buildrc = build_one_sep(resolved, is_dir, root_identity, tmp, tmp, incs,
|
||||||
&linkflags,
|
&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);
|
free(incs);
|
||||||
if (buildrc != 0) {
|
if (buildrc != 0) {
|
||||||
if (unlink(tmp) != 0 && errno != ENOENT)
|
if (unlink(tmp) != 0 && errno != ENOENT)
|
||||||
@@ -7226,6 +7357,10 @@ do_test(int argc, char **argv)
|
|||||||
int package_publish = 0;
|
int package_publish = 0;
|
||||||
int package_create_workdir = 0;
|
int package_create_workdir = 0;
|
||||||
const char *package_create_output_dir = NULL;
|
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};
|
struct seplinkflags package_linkflags = {0};
|
||||||
int afterdash = 0;
|
int afterdash = 0;
|
||||||
const char *request_identity = NULL;
|
const char *request_identity = NULL;
|
||||||
@@ -7296,6 +7431,34 @@ do_test(int argc, char **argv)
|
|||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
package_create_output_dir = argv[++i];
|
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) {
|
} else if (strcmp(argv[i], "--ww-package-test") == 0) {
|
||||||
if (i + 9 >= argc) {
|
if (i + 9 >= argc) {
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
@@ -7363,6 +7526,7 @@ do_test(int argc, char **argv)
|
|||||||
products[nproducts].directory_product = 1;
|
products[nproducts].directory_product = 1;
|
||||||
products[nproducts].no_tests = test_product
|
products[nproducts].no_tests = test_product
|
||||||
&& !has_internal && !has_external;
|
&& !has_internal && !has_external;
|
||||||
|
products[nproducts].build_action = 1;
|
||||||
products[nproducts].root = -1;
|
products[nproducts].root = -1;
|
||||||
products[nproducts].variant_root = -1;
|
products[nproducts].variant_root = -1;
|
||||||
products[nproducts].production_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");
|
fprintf(stderr, "ww test: invalid private directory creation\n");
|
||||||
return 2;
|
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) {
|
if (package_create_workdir && nproducts == 0) {
|
||||||
fprintf(stderr, "ww test: invalid private directory creation\n");
|
fprintf(stderr, "ww test: invalid private directory creation\n");
|
||||||
return 2;
|
return 2;
|
||||||
@@ -7595,7 +7765,10 @@ do_test(int argc, char **argv)
|
|||||||
incs, workdir, products, nproducts,
|
incs, workdir, products, nproducts,
|
||||||
package_build ? 0 : 1, package_publish,
|
package_build ? 0 : 1, package_publish,
|
||||||
package_build ? &package_linkflags : NULL, emit_asm,
|
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(products);
|
||||||
free(incs);
|
free(incs);
|
||||||
return r;
|
return r;
|
||||||
@@ -7643,7 +7816,7 @@ do_test(int argc, char **argv)
|
|||||||
outstem[0] && !discard_output ? outstem : tmp,
|
outstem[0] && !discard_output ? outstem : tmp,
|
||||||
incs, NULL, 0, 0, 1,
|
incs, NULL, 0, 0, 1,
|
||||||
SEP_VARIANT_PRODUCTION, NULL, emit_asm,
|
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 (br != 0) {
|
||||||
if (owntmp && unlink(outp) != 0 && errno != ENOENT)
|
if (owntmp && unlink(outp) != 0 && errno != ENOENT)
|
||||||
fputs("ww: cannot remove temporary output\n", stderr);
|
fputs("ww: cannot remove temporary output\n", stderr);
|
||||||
@@ -7711,7 +7884,7 @@ do_test(int argc, char **argv)
|
|||||||
int br = build_one_sep(target, 0, NULL, outp,
|
int br = build_one_sep(target, 0, NULL, outp,
|
||||||
outstem[0] && !discard_output ? outstem : tmp,
|
outstem[0] && !discard_output ? outstem : tmp,
|
||||||
incs, NULL, 0, 0, 1, SEP_VARIANT_PRODUCTION, NULL, emit_asm,
|
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 (br != 0) {
|
||||||
if (owntmp && unlink(outp) != 0 && errno != ENOENT)
|
if (owntmp && unlink(outp) != 0 && errno != ENOENT)
|
||||||
fputs("ww: cannot remove temporary output\n", stderr);
|
fputs("ww: cannot remove temporary output\n", stderr);
|
||||||
@@ -7759,7 +7932,9 @@ do_test(int argc, char **argv)
|
|||||||
int r = build_package_tests(target, request_identity, incs, workdir,
|
int r = build_package_tests(target, request_identity, incs, workdir,
|
||||||
products, nproducts, package_build ? 0 : 1,
|
products, nproducts, package_build ? 0 : 1,
|
||||||
package_publish, package_build ? &package_linkflags : NULL,
|
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(products);
|
||||||
free(incs);
|
free(incs);
|
||||||
return r;
|
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
|
caller-output rollback, occupied caller stages, and output permissions are
|
||||||
inapplicable to the exact discard branch because it creates no public inode.
|
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
|
## 12. Candidate architectures and hard-gate decision
|
||||||
|
|
||||||
Five candidates were developed as coherent systems, not as feature bins.
|
Five candidates were developed as coherent systems, not as feature bins.
|
||||||
|
|||||||
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
|
`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
|
import of a package declared `main` is rejected, except for the toolchain's
|
||||||
colocated external-test wiring.
|
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`
|
- A newly published `ww build` command is created with permission `0777`
|
||||||
filtered by the invoking process's umask. A newly published non-command
|
filtered by the invoking process's umask. A newly published non-command
|
||||||
archive, and the adjacent WW interface required to consume it, use `0666`
|
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
|
but publishes nothing and does not create an otherwise unneeded output
|
||||||
directory. Successful `-c` output is silent apart from no-test reporting.
|
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
|
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
|
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
|
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
|
Separate compilation is the only driver build path; no compatibility mode
|
||||||
switch remains.
|
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
|
`test/package/package_test.ww` also owns the dual-stage
|
||||||
`declared_name_identity_and_file_import_scope` and
|
`declared_name_identity_and_file_import_scope` and
|
||||||
`explicit_import_alias_binding_modes` observers. They generate temporary
|
`explicit_import_alias_binding_modes` observers. They generate temporary
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ type pkgfolder = struct {
|
|||||||
type pkggroup = struct {
|
type pkggroup = struct {
|
||||||
dir: str,
|
dir: str,
|
||||||
pkg: str,
|
pkg: str,
|
||||||
|
basename: str,
|
||||||
testname: str,
|
testname: str,
|
||||||
publish: str,
|
publish: str,
|
||||||
prodpkg: str,
|
prodpkg: str,
|
||||||
@@ -58,6 +59,11 @@ type pkgplan = struct {
|
|||||||
buildonly: bool,
|
buildonly: bool,
|
||||||
publish: bool,
|
publish: bool,
|
||||||
emitasm: bool,
|
emitasm: bool,
|
||||||
|
outputpatherror: bool,
|
||||||
|
defaultoutputdir: str,
|
||||||
|
outputcollisionbase: str,
|
||||||
|
outputcollisiondir: str,
|
||||||
|
suppressbuildreports: bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
def PKG_COUNT_MAX: i32 = 2147483647;
|
def PKG_COUNT_MAX: i32 = 2147483647;
|
||||||
@@ -1368,8 +1374,9 @@ fn pkgsetplanpaths(p: *pkgplan, groups: []pkggroup, root: str, index: i32,
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
if (buildonly) {
|
if (buildonly) {
|
||||||
if (outputdir && strings.compare(g.pkg, "main") == 0) {
|
if (outputdir && strings.compare(g.pkg, "main") == 0
|
||||||
if (!pkgjoinpath(outname, pkgbase(g.dir), &g.bin)) { return false; };
|
&& !p.outputpatherror && !p.emitasm) {
|
||||||
|
if (!pkgjoinpath(outname, g.basename, &g.bin)) { return false; };
|
||||||
} else if (outname.len != 0 && !outputdir) { g.bin = outname; }
|
} else if (outname.len != 0 && !outputdir) { g.bin = outname; }
|
||||||
else if (!pkgstring(&g.bin, g.root, "/package.build")) { return false; };
|
else if (!pkgstring(&g.bin, g.root, "/package.build")) { return false; };
|
||||||
} else {
|
} else {
|
||||||
@@ -1416,7 +1423,7 @@ fn pkgreportcommand(fd: i32, kind: str, g: *pkggroup,
|
|||||||
fn pkgstartbuild(p: *pkgplan, groups: []pkggroup, builder: str, includes: []str,
|
fn pkgstartbuild(p: *pkgplan, groups: []pkggroup, builder: str, includes: []str,
|
||||||
libdirs: []str, libs: []str, h: *exec.process) bool = {
|
libdirs: []str, libs: []str, h: *exec.process) bool = {
|
||||||
let nproducts: i32 = p.end - p.start;
|
let nproducts: i32 = p.end - p.start;
|
||||||
let capacity: i32 = 16;
|
let capacity: i32 = 24;
|
||||||
if (nproducts < 0 || nproducts > (PKG_COUNT_MAX - capacity) / 10) {
|
if (nproducts < 0 || nproducts > (PKG_COUNT_MAX - capacity) / 10) {
|
||||||
pkgputln(os.STDERR_FILENO, "wwtest package: package graph is too large");
|
pkgputln(os.STDERR_FILENO, "wwtest package: package graph is too large");
|
||||||
return false;
|
return false;
|
||||||
@@ -1457,6 +1464,18 @@ fn pkgstartbuild(p: *pkgplan, groups: []pkggroup, builder: str, includes: []str,
|
|||||||
append(ba, "--ww-create-output-dir");
|
append(ba, "--ww-create-output-dir");
|
||||||
append(ba, p.outputdir);
|
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) {
|
if (p.identity.len != 0) {
|
||||||
append(ba, "--ww-root-identity");
|
append(ba, "--ww-root-identity");
|
||||||
append(ba, p.identity);
|
append(ba, p.identity);
|
||||||
@@ -1627,8 +1646,10 @@ fn pkgemitplan(p: *pkgplan, groups: []pkggroup,
|
|||||||
let i: i32 = p.start;
|
let i: i32 = p.start;
|
||||||
for (i < p.end) {
|
for (i < p.end) {
|
||||||
if (!pkgproductbuilt(&groups[i], buildonly)) {
|
if (!pkgproductbuilt(&groups[i], buildonly)) {
|
||||||
|
if (!p.suppressbuildreports) {
|
||||||
pkgreportcommand(os.STDERR_FILENO, "build", &groups[i],
|
pkgreportcommand(os.STDERR_FILENO, "build", &groups[i],
|
||||||
&p.buildres);
|
&p.buildres);
|
||||||
|
};
|
||||||
failed += 1;
|
failed += 1;
|
||||||
} else if (buildonly) {
|
} else if (buildonly) {
|
||||||
void;
|
void;
|
||||||
@@ -2165,6 +2186,7 @@ export fn packagecommand(args: []str) int = {
|
|||||||
g.dir = f.path;
|
g.dir = f.path;
|
||||||
g.pkg = family;
|
g.pkg = family;
|
||||||
if (g.pkg.len == 0) { g.pkg = srcs[f.start].pkg; };
|
if (g.pkg.len == 0) { g.pkg = srcs[f.start].pkg; };
|
||||||
|
g.basename = "";
|
||||||
g.testname = "";
|
g.testname = "";
|
||||||
g.publish = "";
|
g.publish = "";
|
||||||
g.prodpkg = f.prodpkg;
|
g.prodpkg = f.prodpkg;
|
||||||
@@ -2193,57 +2215,48 @@ export fn packagecommand(args: []str) int = {
|
|||||||
return 1;
|
return 1;
|
||||||
};
|
};
|
||||||
pkgsortgroups(groups);
|
pkgsortgroups(groups);
|
||||||
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);
|
|
||||||
defaultout = true;
|
|
||||||
};
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
let outputdir: bool = buildonly && explicitout && !buildnull
|
|
||||||
&& pkgoutputdir(outname);
|
|
||||||
if (outputdir) {
|
|
||||||
let mainpackages: i32 = 0;
|
|
||||||
i = 0;
|
i = 0;
|
||||||
for (i < groups.len) {
|
for (i < groups.len) {
|
||||||
if (strings.compare(groups[i].pkg, "main") == 0) {
|
groups[i].basename = pkgbase(groups[i].dir);
|
||||||
mainpackages += 1;
|
if (roots.len == 1 && !anyrecurse && requestidentity.len != 0) {
|
||||||
|
groups[i].basename = pkgimportbase(requestidentity);
|
||||||
};
|
};
|
||||||
i += 1;
|
i += 1;
|
||||||
};
|
};
|
||||||
if (mainpackages == 0) {
|
let defaultout: bool = false;
|
||||||
pkgputln(os.STDERR_FILENO, "ww: no main packages to build");
|
if (buildonly && !buildnull && outname.len == 0 && folders.len == 1
|
||||||
return 1;
|
&& groups.len == 1 && strings.compare(groups[0].pkg, "main") == 0) {
|
||||||
|
outname = groups[0].basename;
|
||||||
|
defaultout = true;
|
||||||
};
|
};
|
||||||
|
let defaultoutputdir: str = "";
|
||||||
|
if (defaultout && pkgstatdir(outname)) {
|
||||||
|
defaultoutputdir = outname;
|
||||||
|
};
|
||||||
|
let outputdir: bool = buildonly && explicitout && !buildnull
|
||||||
|
&& pkgoutputdir(outname);
|
||||||
|
let outputpatherror: bool = false;
|
||||||
|
let outputcollisionbase: str = "";
|
||||||
|
let outputcollisiondir: str = "";
|
||||||
|
if (outputdir) {
|
||||||
i = 0;
|
i = 0;
|
||||||
for (i < groups.len) {
|
for (i < groups.len) {
|
||||||
if (strings.compare(groups[i].pkg, "main") == 0) {
|
if (strings.compare(groups[i].pkg, "main") == 0) {
|
||||||
let slash: i64 = 1i64;
|
let slash: i64 = 1i64;
|
||||||
if (strings.hassuffix(outname, "/")) { slash = 0i64; };
|
if (strings.hassuffix(outname, "/")) { slash = 0i64; };
|
||||||
let outputlen: i64 = outname.len: i64 + slash
|
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) {
|
if (outputlen + 1i64 > os.PATH_MAX: i64) {
|
||||||
pkgputln(os.STDERR_FILENO,
|
outputpatherror = true;
|
||||||
"ww: command output path is too long");
|
|
||||||
return 1;
|
|
||||||
};
|
};
|
||||||
let j: i32 = 0;
|
let j: i32 = 0;
|
||||||
for (j < i) {
|
for (j < i) {
|
||||||
if (strings.compare(groups[j].pkg, "main") == 0
|
if (strings.compare(groups[j].pkg, "main") == 0
|
||||||
&& strings.compare(pkgbase(groups[j].dir),
|
&& strings.compare(groups[j].basename,
|
||||||
pkgbase(groups[i].dir)) == 0) {
|
groups[i].basename) == 0
|
||||||
pkgput(os.STDERR_FILENO,
|
&& outputcollisionbase.len == 0) {
|
||||||
"ww: multiple commands produce output basename ");
|
outputcollisionbase = groups[i].basename;
|
||||||
pkgputquoted(os.STDERR_FILENO, pkgbase(groups[i].dir));
|
outputcollisiondir = outname;
|
||||||
pkgput(os.STDERR_FILENO, " in directory ");
|
|
||||||
pkgputquoted(os.STDERR_FILENO, outname);
|
|
||||||
pkgputln(os.STDERR_FILENO, "");
|
|
||||||
return 1;
|
|
||||||
};
|
};
|
||||||
j += 1;
|
j += 1;
|
||||||
};
|
};
|
||||||
@@ -2273,11 +2286,9 @@ export fn packagecommand(args: []str) int = {
|
|||||||
};
|
};
|
||||||
i = 0;
|
i = 0;
|
||||||
for (i < groups.len) {
|
for (i < groups.len) {
|
||||||
let leaf: str = pkgbase(groups[i].dir);
|
if (!pkgstring(&groups[i].testname, groups[i].basename, ".test")) {
|
||||||
if (roots.len == 1 && !anyrecurse && requestidentity.len != 0) {
|
return 1;
|
||||||
leaf = pkgimportbase(requestidentity);
|
|
||||||
};
|
};
|
||||||
if (!pkgstring(&groups[i].testname, leaf, ".test")) { return 1; };
|
|
||||||
groups[i].publish = "";
|
groups[i].publish = "";
|
||||||
if (testretain && !groups[i].notests && !testnull) {
|
if (testretain && !groups[i].notests && !testnull) {
|
||||||
if (!explicitout) {
|
if (!explicitout) {
|
||||||
@@ -2368,6 +2379,12 @@ export fn packagecommand(args: []str) int = {
|
|||||||
&& groups.len == 1
|
&& groups.len == 1
|
||||||
&& strings.compare(groups[0].pkg, "main") != 0;
|
&& strings.compare(groups[0].pkg, "main") != 0;
|
||||||
plan.emitasm = emitasm;
|
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);
|
append(plans, plan);
|
||||||
let createdir: str = "";
|
let createdir: str = "";
|
||||||
if (buildonly && outputdir) {
|
if (buildonly && outputdir) {
|
||||||
|
|||||||
@@ -1465,6 +1465,7 @@ type seppkg = struct {
|
|||||||
generatedtargets: []i32,
|
generatedtargets: []i32,
|
||||||
ngeneratedtargets: i32,
|
ngeneratedtargets: i32,
|
||||||
failed: bool,
|
failed: bool,
|
||||||
|
action: bool, // reached by this request's semantic action list
|
||||||
testsupport: bool,
|
testsupport: bool,
|
||||||
loaded: bool,
|
loaded: bool,
|
||||||
exportchanged: bool,
|
exportchanged: bool,
|
||||||
@@ -1513,6 +1514,7 @@ type sepproduct = struct {
|
|||||||
variant: i32,
|
variant: i32,
|
||||||
directoryproduct: bool,
|
directoryproduct: bool,
|
||||||
notests: bool,
|
notests: bool,
|
||||||
|
buildaction: bool, // loaded product retained in the action list
|
||||||
context: i32,
|
context: i32,
|
||||||
root: i32,
|
root: i32,
|
||||||
variantroot: 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].generatedtargets = emptytargets;
|
||||||
g.pkg[g.n].ngeneratedtargets = 0;
|
g.pkg[g.n].ngeneratedtargets = 0;
|
||||||
g.pkg[g.n].failed = false;
|
g.pkg[g.n].failed = false;
|
||||||
|
g.pkg[g.n].action = false;
|
||||||
g.pkg[g.n].testsupport = false;
|
g.pkg[g.n].testsupport = false;
|
||||||
g.pkg[g.n].loaded = false;
|
g.pkg[g.n].loaded = false;
|
||||||
g.pkg[g.n].exportchanged = 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 = {
|
fn sepvalidateartifactpaths(g: *sepgraph, scratch: *u8) i32 = {
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
for (i < g.n) {
|
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
|
if (g.pkg[i].storage == nil
|
||||||
&& sepassignstorage(&g.pkg[i], scratch) < 0) { return -1; };
|
&& sepassignstorage(&g.pkg[i], scratch) < 0) { return -1; };
|
||||||
i += 1;
|
i += 1;
|
||||||
@@ -3547,10 +3552,12 @@ fn sepvalidateartifactpaths(g: *sepgraph, scratch: *u8) i32 = {
|
|||||||
changed = false;
|
changed = false;
|
||||||
i = 0;
|
i = 0;
|
||||||
for (i < g.n && !changed) {
|
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;
|
let j: i32 = i + 1;
|
||||||
for (j < g.n) {
|
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)) {
|
|| !cstreq(g.pkg[i].storage, g.pkg[j].storage)) {
|
||||||
j += 1; continue;
|
j += 1; continue;
|
||||||
};
|
};
|
||||||
@@ -4350,6 +4357,7 @@ fn sepaddgeneratedmain(g: *sepgraph, product: *sepproduct, ordinal: i32,
|
|||||||
ti += 1;
|
ti += 1;
|
||||||
};
|
};
|
||||||
p.failed = false;
|
p.failed = false;
|
||||||
|
p.action = false;
|
||||||
p.testsupport = false;
|
p.testsupport = false;
|
||||||
p.loaded = true;
|
p.loaded = true;
|
||||||
p.exportchanged = false;
|
p.exportchanged = false;
|
||||||
@@ -4473,6 +4481,7 @@ fn sepclonefortest(g: *sepgraph, original: i32, owner: *u8,
|
|||||||
p.generatedtargets = emptytargets;
|
p.generatedtargets = emptytargets;
|
||||||
p.ngeneratedtargets = 0;
|
p.ngeneratedtargets = 0;
|
||||||
p.failed = src.failed;
|
p.failed = src.failed;
|
||||||
|
p.action = false;
|
||||||
p.testsupport = src.testsupport;
|
p.testsupport = src.testsupport;
|
||||||
p.loaded = src.loaded;
|
p.loaded = src.loaded;
|
||||||
p.exportchanged = false;
|
p.exportchanged = false;
|
||||||
@@ -4501,6 +4510,15 @@ fn sepclonefortest(g: *sepgraph, original: i32, owner: *u8,
|
|||||||
seppkgfreeowned(&p);
|
seppkgfreeowned(&p);
|
||||||
return -1;
|
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) {
|
if (src.nsources > 0) {
|
||||||
let sources: []*u8;
|
let sources: []*u8;
|
||||||
@@ -5856,7 +5874,7 @@ fn sepvalidateunitowner(g: *sepgraph, pi: i32, scratch: *u8) i32 = {
|
|||||||
fn sepvalidateworkdirowners(g: *sepgraph, scratch: *u8) i32 = {
|
fn sepvalidateworkdirowners(g: *sepgraph, scratch: *u8) i32 = {
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
for (i < g.n) {
|
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; };
|
&& sepvalidateunitowner(g, i, scratch) < 0) { return -1; };
|
||||||
i += 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"];
|
".a.new", ".init.unit.new", ".init.s.new", ".init.o.new"];
|
||||||
let pi: i32 = 0;
|
let pi: i32 = 0;
|
||||||
for (pi < g.n) {
|
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;
|
let si: i32 = 0;
|
||||||
for (si < suffix.len) {
|
for (si < suffix.len) {
|
||||||
let path: *u8 = sepfname(g, pi, scratch, suffix[si]);
|
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 rc: i32 = 0;
|
||||||
let pi: i32 = 0;
|
let pi: i32 = 0;
|
||||||
for (pi < g.n) {
|
for (pi < g.n) {
|
||||||
|
if (!g.pkg[pi].action) { pi += 1; continue; };
|
||||||
let si: i32 = 0;
|
let si: i32 = 0;
|
||||||
for (si < suffix.len) {
|
for (si < suffix.len) {
|
||||||
let path: *u8 = sepfname(g, pi, scratch, suffix[si]);
|
let path: *u8 = sepfname(g, pi, scratch, suffix[si]);
|
||||||
@@ -6945,6 +6964,8 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
|
|||||||
publishpackage: i32, requirecommand: i32, istest: i32,
|
publishpackage: i32, requirecommand: i32, istest: i32,
|
||||||
products: *sepproduct, nproducts: i32, emitasm: i32,
|
products: *sepproduct, nproducts: i32, emitasm: i32,
|
||||||
workdir: *u8, createworkdir: bool, createoutputdir: *u8,
|
workdir: *u8, createworkdir: bool, createoutputdir: *u8,
|
||||||
|
defaultoutputdir: *u8, outputpatherror: bool,
|
||||||
|
outputcollisionbase: *u8, outputcollisiondir: *u8,
|
||||||
scratchout: **u8,
|
scratchout: **u8,
|
||||||
graphout: **sepgraph) i32 = {
|
graphout: **sepgraph) i32 = {
|
||||||
sepfatalallocation = false;
|
sepfatalallocation = false;
|
||||||
@@ -7511,9 +7532,6 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
|
|||||||
let rootpackage: bool = istest == 0 && nproducts == 1
|
let rootpackage: bool = istest == 0 && nproducts == 1
|
||||||
&& !g.pkg[products[0].root].failed
|
&& !g.pkg[products[0].root].failed
|
||||||
&& !seprootiscommand(&g.pkg[products[0].root]);
|
&& !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 ci: i32 = 0;
|
||||||
let order: []i32;
|
let order: []i32;
|
||||||
let stack: []i32;
|
let stack: []i32;
|
||||||
@@ -7572,6 +7590,22 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
|
|||||||
return 1;
|
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
|
if (!g.pkg[products[0].root].failed
|
||||||
&& rootpackage && publishpackage != 0 && emitasm == 0
|
&& rootpackage && publishpackage != 0 && emitasm == 0
|
||||||
&& validatepackageoutputpath(out) < 0) { return 1; };
|
&& validatepackageoutputpath(out) < 0) { return 1; };
|
||||||
@@ -7580,12 +7614,25 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
|
|||||||
producti = 0;
|
producti = 0;
|
||||||
for (producti < nproducts) {
|
for (producti < nproducts) {
|
||||||
let root: i32 = products[producti].root;
|
let root: i32 = products[producti].root;
|
||||||
if (!g.pkg[root].failed) {
|
if (products[producti].buildaction && !g.pkg[root].failed) {
|
||||||
if (septopovisit(g, root, order,
|
if (septopovisit(g, root, order,
|
||||||
&norder, stack, 0) < 0) { return 1; };
|
&norder, stack, 0) < 0) { return 1; };
|
||||||
};
|
};
|
||||||
producti += 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
|
// Propagate already-known package-load failures before scratch or status
|
||||||
// acquisition. Good sibling roots may remain viable for deterministic
|
// acquisition. Good sibling roots may remain viable for deterministic
|
||||||
// staging/diagnosis, but any failure rejects publication; an entirely failed
|
// 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;
|
let viableproduct: bool = false;
|
||||||
producti = 0;
|
producti = 0;
|
||||||
for (producti < nproducts) {
|
for (producti < nproducts) {
|
||||||
if (!g.pkg[products[producti].root].failed) {
|
if (products[producti].buildaction
|
||||||
|
&& !g.pkg[products[producti].root].failed) {
|
||||||
viableproduct = true;
|
viableproduct = true;
|
||||||
};
|
};
|
||||||
producti += 1;
|
producti += 1;
|
||||||
};
|
};
|
||||||
if (!viableproduct) { return 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,
|
if (sepvalidaterequeststaging(g, scratch, warm, products, nproducts,
|
||||||
rootpackage, publishpackage, emitasm, istest) < 0) {
|
rootpackage, publishpackage, emitasm, istest) < 0) {
|
||||||
sepfreeproductstaging(products, nproducts);
|
sepfreeproductstaging(products, nproducts);
|
||||||
@@ -7622,10 +7688,8 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
|
|||||||
let createdwork: sepcreateddirs;
|
let createdwork: sepcreateddirs;
|
||||||
createdoutput.n = 0;
|
createdoutput.n = 0;
|
||||||
createdwork.n = 0;
|
createdwork.n = 0;
|
||||||
let outputmode: i32 = 448;
|
if (emitasm == 0 && createoutputdir != nil
|
||||||
if (istest != 0) { outputmode = 511; };
|
&& sepmkdirsrecord(createoutputdir, 511, &createdoutput) != 0) {
|
||||||
if (createoutputdir != nil
|
|
||||||
&& sepmkdirsrecord(createoutputdir, outputmode, &createdoutput) != 0) {
|
|
||||||
if (istest != 0) {
|
if (istest != 0) {
|
||||||
cerrpath("ww: cannot create test output directory ",
|
cerrpath("ww: cannot create test output directory ",
|
||||||
createoutputdir, "\n");
|
createoutputdir, "\n");
|
||||||
@@ -8149,7 +8213,8 @@ fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32,
|
|||||||
objstem: *u8, incs: *u8, lf: *lflags, publishpackage: i32,
|
objstem: *u8, incs: *u8, lf: *lflags, publishpackage: i32,
|
||||||
requirecommand: i32, istest: i32,
|
requirecommand: i32, istest: i32,
|
||||||
rootvariant: i32, testpackage: *u8, emitasm: 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 scratch: *u8 = nil;
|
||||||
let g: *sepgraph = nil;
|
let g: *sepgraph = nil;
|
||||||
let product: sepproduct;
|
let product: sepproduct;
|
||||||
@@ -8169,12 +8234,15 @@ fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32,
|
|||||||
product.variant = rootvariant;
|
product.variant = rootvariant;
|
||||||
product.directoryproduct = false;
|
product.directoryproduct = false;
|
||||||
product.notests = false;
|
product.notests = false;
|
||||||
|
product.buildaction = true;
|
||||||
product.root = -1;
|
product.root = -1;
|
||||||
product.variantroot = -1;
|
product.variantroot = -1;
|
||||||
product.support = -1;
|
product.support = -1;
|
||||||
let r: i32 = buildonesepimpl(selfdir, src, entryisdir, out, objstem,
|
let r: i32 = buildonesepimpl(selfdir, src, entryisdir, out, objstem,
|
||||||
incs, lf, publishpackage, requirecommand, istest, &product, 1,
|
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);
|
sepgraphfree(g);
|
||||||
if (keepscratch == 0 && scratch != nil) {
|
if (keepscratch == 0 && scratch != nil) {
|
||||||
if (cstrendswithlit(scratch, ".sepwork")) {
|
if (cstrendswithlit(scratch, ".sepwork")) {
|
||||||
@@ -8205,7 +8273,9 @@ fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32,
|
|||||||
fn buildpackagetests(selfdir: *u8, src: *u8, rootidentity: *u8,
|
fn buildpackagetests(selfdir: *u8, src: *u8, rootidentity: *u8,
|
||||||
incs: *u8, workdir: *u8, products: *sepproduct, nproducts: i32,
|
incs: *u8, workdir: *u8, products: *sepproduct, nproducts: i32,
|
||||||
istest: i32, publishpackage: i32, lf: *lflags, emitasm: 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 scratch: *u8 = nil;
|
||||||
let g: *sepgraph = nil;
|
let g: *sepgraph = nil;
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
@@ -8217,7 +8287,8 @@ fn buildpackagetests(selfdir: *u8, src: *u8, rootidentity: *u8,
|
|||||||
products[0].out, products[0].out, incs, lf,
|
products[0].out, products[0].out, incs, lf,
|
||||||
publishpackage, 0, istest,
|
publishpackage, 0, istest,
|
||||||
products, nproducts, emitasm, workdir, createworkdir,
|
products, nproducts, emitasm, workdir, createworkdir,
|
||||||
createoutputdir, &scratch, &g);
|
createoutputdir, defaultoutputdir, outputpatherror,
|
||||||
|
outputcollisionbase, outputcollisiondir, &scratch, &g);
|
||||||
sepgraphfree(g);
|
sepgraphfree(g);
|
||||||
return r;
|
return r;
|
||||||
};
|
};
|
||||||
@@ -8313,7 +8384,7 @@ fn resolvemodule(selfdir: *u8, name: *u8, incs: *u8, isdir: *i32) *u8 = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
fn writeusage(fd: i32) void = {
|
fn writeusage(fd: i32) void = {
|
||||||
let s: str = "usage: ww [-V] <subcommand> [args...]\n -V print version and exit\n build [-S] [-w DIR] [-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);
|
os.write(fd, s.ptr, s.len: u64);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -8352,6 +8423,54 @@ fn defaultoutpath(src: *u8) *u8 = {
|
|||||||
return out.ptr;
|
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 = {
|
fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||||
let src: *u8 = nil;
|
let src: *u8 = nil;
|
||||||
let srcindex: i32 = -1;
|
let srcindex: i32 = -1;
|
||||||
@@ -8504,11 +8623,34 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
let objstem: *u8 = nil;
|
let objstem: *u8 = nil;
|
||||||
let discardoutput: bool = outflag != nil
|
let discardoutput: bool = outflag != nil
|
||||||
&& cstreqlit(outflag, "/dev/null");
|
&& 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) {
|
if (outflag != nil && outflag[0u64] != 0u8 && !discardoutput) {
|
||||||
// -o sets both the binary path and the intermediate stem so
|
// -o sets both the binary path and the intermediate stem so
|
||||||
// artifacts land beside the requested output (T3).
|
// artifacts land beside the requested output (T3).
|
||||||
|
if (outputdir) {
|
||||||
|
out = buildoutputpath(outflag, resolved);
|
||||||
|
if (out == nil) {
|
||||||
|
outputpatherror = true;
|
||||||
|
out = defaultoutpath(resolved);
|
||||||
|
};
|
||||||
|
createoutputdir = outflag;
|
||||||
|
} else {
|
||||||
out = outflag;
|
out = outflag;
|
||||||
objstem = outflag;
|
};
|
||||||
|
objstem = out;
|
||||||
|
} else { if (isdir != 0 && rootidentity != nil) {
|
||||||
|
out = defaultimportoutpath(rootidentity);
|
||||||
} else { if (isdir != 0) {
|
} else { if (isdir != 0) {
|
||||||
let rlen: u64 = cstrlen(resolved);
|
let rlen: u64 = cstrlen(resolved);
|
||||||
for (rlen > 1u64) {
|
for (rlen > 1u64) {
|
||||||
@@ -8525,14 +8667,16 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
cstrseal(out, off);
|
cstrseal(out, off);
|
||||||
} else {
|
} else {
|
||||||
out = defaultoutpath(resolved);
|
out = defaultoutpath(resolved);
|
||||||
}; };
|
}; }; };
|
||||||
|
let defaultoutputdir: *u8 = nil;
|
||||||
|
if ((outflag == nil || outflag[0u64] == 0u8) && buildoutputdir(out)) {
|
||||||
|
defaultoutputdir = out;
|
||||||
|
};
|
||||||
let lf: lflags;
|
let lf: lflags;
|
||||||
lf.libdirs = &libdirs[0];
|
lf.libdirs = &libdirs[0];
|
||||||
lf.nlibdirs = nlibdirs;
|
lf.nlibdirs = nlibdirs;
|
||||||
lf.libs = &libs[0];
|
lf.libs = &libs[0];
|
||||||
lf.nlibs = nlibs;
|
lf.nlibs = nlibs;
|
||||||
let rootidentity: *u8 = nil;
|
|
||||||
if (!requestedliteral && isdir != 0) { rootidentity = src; };
|
|
||||||
if (discardoutput) {
|
if (discardoutput) {
|
||||||
let tmp: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
let tmp: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||||
tmp.len = os.PATH_MAX;
|
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,
|
let rc: i32 = buildonesep(selfdir, resolved, isdir, rootidentity,
|
||||||
outp, outp, incs.ptr, &lf,
|
outp, outp, incs.ptr, &lf,
|
||||||
0i32, 0i32, 0i32, SEP_VARIANT_PRODUCTION, nil,
|
0i32, 0i32, 0i32, SEP_VARIANT_PRODUCTION, nil,
|
||||||
emitasm, 0i32, workdir);
|
emitasm, 0i32, workdir, nil, nil, false);
|
||||||
let cleanbad: bool = false;
|
let cleanbad: bool = false;
|
||||||
let cleanrc: i32 = os.remove(pathstr(outp));
|
let cleanrc: i32 = os.remove(pathstr(outp));
|
||||||
if (cleanrc != 0 && cleanrc != -2i32) {
|
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,
|
return buildonesep(selfdir, resolved, isdir, rootidentity,
|
||||||
out, objstem, incs.ptr, &lf,
|
out, objstem, incs.ptr, &lf,
|
||||||
publishpackage, 0i32, 0i32, SEP_VARIANT_PRODUCTION, nil,
|
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
|
// 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,
|
if (buildonesep(selfdir, resolved, isdir, rootidentity,
|
||||||
outp, outp, incs.ptr, &lf,
|
outp, outp, incs.ptr, &lf,
|
||||||
0i32, 1i32, 0i32, SEP_VARIANT_PRODUCTION, nil,
|
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));
|
let cleanrc: i32 = os.remove(pathstr(outp));
|
||||||
if (cleanrc != 0 && cleanrc != -2i32) {
|
if (cleanrc != 0 && cleanrc != -2i32) {
|
||||||
cerr("ww: cannot remove temporary output\n");
|
cerr("ww: cannot remove temporary output\n");
|
||||||
@@ -8841,7 +8986,7 @@ fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32,
|
|||||||
if (retainout) { keep = 1; };
|
if (retainout) { keep = 1; };
|
||||||
let bres: i32 = buildonesep(selfdir, src, 0, nil, outp, objstem, incs, &lf,
|
let bres: i32 = buildonesep(selfdir, src, 0, nil, outp, objstem, incs, &lf,
|
||||||
0i32, 0i32, 1i32, SEP_VARIANT_PRODUCTION, nil,
|
0i32, 0i32, 1i32, SEP_VARIANT_PRODUCTION, nil,
|
||||||
emitasm, keep, workdir);
|
emitasm, keep, workdir, nil, nil, false);
|
||||||
if (bres != 0) {
|
if (bres != 0) {
|
||||||
if (owntmp) {
|
if (owntmp) {
|
||||||
let cleanrc: i32 = os.remove(pathstr(outp));
|
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 packagepublish: bool = false;
|
||||||
let packagecreateworkdir: bool = false;
|
let packagecreateworkdir: bool = false;
|
||||||
let packagecreateoutputdir: *u8 = nil;
|
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 maxpackagelflags: i32 = 32;
|
||||||
let packagelibdirs: [32]*u8;
|
let packagelibdirs: [32]*u8;
|
||||||
let packagelibs: [32]*u8;
|
let packagelibs: [32]*u8;
|
||||||
@@ -9008,6 +9157,37 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
i += 2;
|
i += 2;
|
||||||
continue;
|
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 (cstreqlit(p, "--ww-package-test")) {
|
||||||
if (i + 9 >= argc) {
|
if (i + 9 >= argc) {
|
||||||
cerr("ww test: --ww-package-test needs kind, package, production, internal, external, directory, output, publication, and status\n");
|
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; };
|
if (buildproduct) { product.variant = SEP_VARIANT_PRODUCTION; };
|
||||||
product.directoryproduct = true;
|
product.directoryproduct = true;
|
||||||
product.notests = testproduct && !hasinternal && !hasexternal;
|
product.notests = testproduct && !hasinternal && !hasexternal;
|
||||||
|
product.buildaction = true;
|
||||||
product.root = -1;
|
product.root = -1;
|
||||||
product.variantroot = -1;
|
product.variantroot = -1;
|
||||||
product.productionroot = -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");
|
cerr("ww test: invalid private directory creation\n");
|
||||||
return 2;
|
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) {
|
if (packagecreateworkdir && products.len == 0) {
|
||||||
cerr("ww test: invalid private directory creation\n");
|
cerr("ww test: invalid private directory creation\n");
|
||||||
return 2;
|
return 2;
|
||||||
@@ -9388,7 +9575,9 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
incs.ptr, workdir,
|
incs.ptr, workdir,
|
||||||
products.ptr, products.len, packagemode, publishmode,
|
products.ptr, products.len, packagemode, publishmode,
|
||||||
&packagelinks, emitasm, packagecreateworkdir,
|
&packagelinks, emitasm, packagecreateworkdir,
|
||||||
packagecreateoutputdir);
|
packagecreateoutputdir, packagedefaultoutputdir,
|
||||||
|
packageoutputpatherror, packageoutputcollisionbase,
|
||||||
|
packageoutputcollisiondir);
|
||||||
};
|
};
|
||||||
let replacement: *u8 = nil;
|
let replacement: *u8 = nil;
|
||||||
if (resolved != target) { replacement = resolved; };
|
if (resolved != target) { replacement = resolved; };
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user