feat(gen): generate --help and argument parsing

Replace the v1 minimal preamble in the generated ./configure with the
full argument-parsing section (src/gen/args.{h,c}): -h/--help usage,
--version, --prefix/--exec-prefix/--host/--build in = and space forms,
per-DSL-option --enable-<name>/--disable-<name> (default from the
option's default property), generic positional VAR=VALUE overrides
(identifier-validated, single-quote-escaped eval assignment), unknown
--* -> usage on stderr + exit 1, cross_compiling=yes when --host differs
from --build. The substitution set gains exec_prefix/host/build/
cross_compiling plus one enable_<name> per option. Option names are
validated as shell identifiers at generation time.

Tests: 12 new /gen/args/* cases (help lists --prefix/--enable-debug/
--disable-debug; CC=clang positional + env; enable/debug default;
cross_compiling observable; --bogus/--bogus=1/--enable- errors;
--prefix missing/empty; space form; exec_prefix defaulting; VAR=VALUE
injection inert; hostile option name errors at generation) - 28/28
green, three-shell -n + banned-construct sweep clean.
This commit is contained in:
2026-08-29 00:35:07 -04:00
parent 08e1ea93dd
commit 9f41034197
5 changed files with 1333 additions and 61 deletions
+40 -42
View File
@@ -20,6 +20,7 @@
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "gen/args.h"
#include "gen/config.h"
#include "gen/configure.h"
@@ -205,39 +206,6 @@ name_equals(const struct st_kdl_token_ref *name, const char *s, bool *out)
/* The `%` in `printf '... %s ...'` is why these are emitted with
* st_sh_emit_str (verbatim), not fprintf (which would eat `%s`). */
static const char PREAMBLE[] =
"\n"
"# --- PREAMBLE ---\n"
"# v1 MINIMAL argument handling. todo 18 replaces this whole section\n"
"# with the full --help / --enable-*/--with-*/VAR=VALUE parser.\n"
"prefix=/usr/local\n"
"srcdir=.\n"
"build=\n"
"host=\n"
"cross_compiling=no\n"
"\n"
"# VAR=VALUE overrides reach configure as environment variables (the\n"
"# invoking shell exported them, e.g. `CC=clang ./configure`), so v1\n"
"# needs no handling here. The positional form (`./configure CC=clang`)\n"
"# is accepted but ignored until todo 18 exports it.\n"
"for st_arg in \"$@\"; do\n"
" case \"$st_arg\" in\n"
" --prefix=*) prefix=${st_arg#--prefix=} ;;\n"
" --build=*) build=${st_arg#--build=} ;;\n"
" --host=*) host=${st_arg#--host=} ;;\n"
" *=*) : ;;\n"
" *)\n"
" printf 'configure: error: unrecognized option %s\\n' \"$st_arg\" >&2\n"
" exit 1\n"
" ;;\n"
" esac\n"
"done\n"
"\n"
"# cross-compiling when --host differs from --build (both set)\n"
"if [ -n \"$host\" ] && [ \"$host\" != \"$build\" ]; then\n"
" cross_compiling=yes\n"
"fi\n";
static const char HOST_DETECT_PRE[] =
"\n"
"# --- host detection ---\n";
@@ -601,6 +569,7 @@ st_gen_configure_emit(FILE *out, const struct st_kdl_document *doc,
size_t i;
struct st_error *err = NULL;
const struct st_kdl_node *node;
struct st_gen_args args;
char **subst_names = NULL;
size_t n_subst = 0;
char **feat_names = NULL;
@@ -610,19 +579,27 @@ st_gen_configure_emit(FILE *out, const struct st_kdl_document *doc,
if (out == NULL || doc == NULL || ctx == NULL) {
return st_error_usage("st_gen_configure_emit: NULL argument");
}
memset(&args, 0, sizeof args);
err = st_gen_args_collect(doc, &args);
if (err != NULL) {
goto done;
}
reg = st_ext_var_registry(ctx);
if (reg == NULL) {
return st_error_usage("st_gen_configure_emit: ctx has no variable "
"registry");
err = st_error_usage("st_gen_configure_emit: ctx has no variable "
"registry");
goto done;
}
n_reg = st_registry_var_count(reg);
/* collect the full @VAR@ substitution set: registered vars + the
* built-ins LIBS/prefix/srcdir */
n_subst = n_reg + 3;
* built-ins LIBS/prefix/exec_prefix/srcdir/host/build/cross_compiling
* + one enable_<name> per DSL option (todo 18) */
n_subst = n_reg + 7 + args.option_count;
subst_names = calloc(n_subst, sizeof(*subst_names));
if (subst_names == NULL) {
return st_error_internal("out of memory building substitution set");
err = st_error_internal("out of memory building substitution set");
goto done;
}
for (i = 0; i < n_reg; i++) {
const char *name = st_registry_var_name(reg, i);
@@ -640,7 +617,20 @@ st_gen_configure_emit(FILE *out, const struct st_kdl_document *doc,
}
subst_names[n_reg + 0] = "LIBS";
subst_names[n_reg + 1] = "prefix";
subst_names[n_reg + 2] = "srcdir";
subst_names[n_reg + 2] = "exec_prefix";
subst_names[n_reg + 3] = "srcdir";
subst_names[n_reg + 4] = "host";
subst_names[n_reg + 5] = "build";
subst_names[n_reg + 6] = "cross_compiling";
for (i = 0; i < args.option_count; i++) {
subst_names[n_reg + 7 + i] = strbuild("enable_%s",
args.options[i].name);
if (subst_names[n_reg + 7 + i] == NULL) {
err = st_error_internal("out of memory building substitution "
"set");
goto done;
}
}
/* 1. shebang + header comment */
if (st_sh_emit_str(out, "#!/bin/sh\n") < 0 ||
@@ -651,9 +641,9 @@ st_gen_configure_emit(FILE *out, const struct st_kdl_document *doc,
goto done;
}
/* 2. preamble (minimal args; todo 18 replaces it) */
if (st_sh_emit_str(out, PREAMBLE) < 0) {
err = st_error_io("I/O error emitting preamble");
/* 2. preamble: full argument parsing (todo 18) */
err = st_gen_args_emit(out, &args);
if (err != NULL) {
goto done;
}
@@ -787,7 +777,15 @@ done:
free(feat_names[i]);
}
free(feat_names);
/* the enable_<name> entries are owned (strbuild); the rest of
* subst_names holds borrowed literals/registry pointers */
if (subst_names != NULL && n_reg + 7 <= n_subst) {
for (i = n_reg + 7; i < n_subst; i++) {
free(subst_names[i]);
}
}
free(subst_names);
st_gen_args_free(&args);
return err;
}