Template
feat: self-host configure generation for stupidtools
Wire the full pipeline into the binary: parse + validate the KDL buildfile, discover extensions, bridge Lua, detect the C toolchain, and emit ./configure. Adds the project's own stupid.kdl build file and a temp-dir-isolated self-host integration test proving the generated configure + make rebuild the tool with -std=c23. tests/run.sh tests 6+8 re-pin the interim 'accepted, no read' CLI assertions to the wired 'missing buildfile -> exit 1' behavior.
This commit is contained in:
+8
-2
@@ -6,7 +6,10 @@ bin_PROGRAMS = stupidtools
|
||||
stupidtools_SOURCES = \
|
||||
main.c cli.c \
|
||||
error.c span.c \
|
||||
ext/discovery.c ext/lua.c ext/abi.c ext/lang_c.c ext/lang_cpp.c \
|
||||
ext/discovery.c ext/lua.c ext/abi.c ext/lang_c.c ext/lang_cpp.c ext/api.c \
|
||||
kdl/lexer.c kdl/parser.c kdl/value.c kdl/schema.c \
|
||||
detect/check_registry.c detect/checks.c detect/probe.c detect/resolve.c \
|
||||
gen/sh_emit.c gen/configure.c gen/config.c gen/args.c \
|
||||
../thirdparty/lua/lapi.c ../thirdparty/lua/lauxlib.c \
|
||||
../thirdparty/lua/lbaselib.c ../thirdparty/lua/lcode.c \
|
||||
../thirdparty/lua/lctype.c ../thirdparty/lua/ldebug.c \
|
||||
@@ -27,4 +30,7 @@ stupidtools_SOURCES = \
|
||||
# the six libm shims in lua.c keep the link free of -lm).
|
||||
noinst_HEADERS = cli.h error.h span.h \
|
||||
ext/discovery.h ext/lua.h ext/abi.h ext/lang_c.h ext/lang_cpp.h \
|
||||
ext/lang_common.h
|
||||
ext/lang_common.h ext/api.h \
|
||||
kdl/lexer.h kdl/ast.h kdl/value.h kdl/schema.h \
|
||||
detect/check_registry.h detect/checks.h detect/probe.h detect/resolve.h \
|
||||
gen/sh_emit.h gen/configure.h gen/config.h gen/args.h
|
||||
|
||||
+163
-45
@@ -4,25 +4,45 @@
|
||||
* Copyright (c) 2026 huntedbytheirs
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*
|
||||
* Entrypoint: parse argv via cli.c, dispatch --help/--version/usage
|
||||
* errors, and reserve the `stupidtools <buildfile>` shape. Buildfile
|
||||
* processing arrives in later todos (16+).
|
||||
* Entrypoint. Parses argv via cli.c, then runs the full pipeline for a
|
||||
* single buildfile:
|
||||
*
|
||||
* Interim extension pipeline (todo 20): with --ext-dir flags or a
|
||||
* STUPIDTOOLS_EXT environment, run extension discovery, print what
|
||||
* loaded, and exit 0 -- until todo 23 folds discovery into the full
|
||||
* buildfile pipeline.
|
||||
* 1. read the buildfile (argv[1]) into memory;
|
||||
* 2. parse it (st_kdl_parse) and validate it against the DSL
|
||||
* (st_kdl_validate);
|
||||
* 3. build the extension context + sandboxed Lua runtime, discover
|
||||
* every extension (builtin C modules first, then --ext-dir /
|
||||
* STUPIDTOOLS_EXT / the user dir / the builtin dir -- see
|
||||
* ext/discovery.h), flush Lua registrations into the ctx
|
||||
* (st_ext_bridge_lua), and detect the C toolchain
|
||||
* (st_ext_detect_language(ctx, "c"));
|
||||
* 4. emit ./configure (st_gen_configure_emit_path) into the current
|
||||
* working directory -- the generated script reads ./Makefile.in
|
||||
* and writes ./Makefile there at configure time (the v1 source-dir
|
||||
* contract, see gen/configure.h);
|
||||
* 5. print a one-line summary and exit 0.
|
||||
*
|
||||
* Any failure prints its diagnostic via st_error_print to stderr and
|
||||
* exits 1 (runtime error); CLI usage errors exit 2 (see cli.h). Only
|
||||
* main.c exits; every library path returns an error.
|
||||
*/
|
||||
|
||||
#include "cli.h"
|
||||
|
||||
#include "error.h"
|
||||
#include "ext/abi.h"
|
||||
#include "ext/api.h"
|
||||
#include "ext/discovery.h"
|
||||
#include "ext/lua.h"
|
||||
#include "gen/configure.h"
|
||||
#include "kdl/ast.h"
|
||||
#include "kdl/schema.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Discovery's log sink: route the "loaded extension: ..." lines to
|
||||
* stdout so scripts can grep them cleanly. */
|
||||
@@ -32,35 +52,78 @@ stdout_log(const char *fmt, va_list ap)
|
||||
vfprintf(stdout, fmt, ap);
|
||||
}
|
||||
|
||||
/* Creates a fresh ctx + runtime, discovers everything (builtin modules
|
||||
* first, then the --ext-dir dirs, STUPIDTOOLS_EXT, the user dir and the
|
||||
* builtin dir -- see ext/discovery.h), and prints each loaded module.
|
||||
* Returns CLI_EXIT_OK on success; a load error prints its diagnostic to
|
||||
* stderr and returns CLI_EXIT_RUNTIME (fail-fast contract). */
|
||||
static int
|
||||
run_extension_discovery(const struct cli_opts *opts)
|
||||
/* Reads the whole of `path` into a freshly malloc'd NUL-terminated
|
||||
* buffer (owned by the caller). Returns NULL on success; an owned
|
||||
* ST_ERR_IO error naming the file on failure (unreadable file or OOM).
|
||||
* The read uses rb so embedded NULs don't truncate the parse. */
|
||||
static struct st_error *
|
||||
read_file(const char *path, char **out)
|
||||
{
|
||||
struct st_ext_ctx *ctx = st_ext_ctx_new();
|
||||
struct st_lua_rt *rt = st_lua_rt_new();
|
||||
struct st_error *err = NULL;
|
||||
int rc = CLI_EXIT_RUNTIME;
|
||||
FILE *f;
|
||||
long size;
|
||||
char *buf;
|
||||
size_t got;
|
||||
size_t need;
|
||||
struct st_error *err;
|
||||
|
||||
if (ctx == NULL || rt == NULL) {
|
||||
fprintf(stderr, "%s: error: out of memory\n", opts->program);
|
||||
} else {
|
||||
st_ext_discover_set_log(stdout_log);
|
||||
err = st_ext_discover(ctx, rt, opts->ext_dirs,
|
||||
opts->ext_dir_count);
|
||||
if (err != NULL) {
|
||||
st_error_print(stderr, err);
|
||||
st_error_free(err);
|
||||
} else {
|
||||
rc = CLI_EXIT_OK;
|
||||
}
|
||||
f = fopen(path, "rb");
|
||||
if (f == NULL) {
|
||||
need = strlen(path) + 64;
|
||||
buf = malloc(need);
|
||||
if (buf == NULL)
|
||||
return st_error_internal("out of memory");
|
||||
(void)snprintf(buf, need, "cannot read build file '%s': %s",
|
||||
path, strerror(errno));
|
||||
err = st_error_io(buf);
|
||||
free(buf);
|
||||
return err;
|
||||
}
|
||||
st_lua_rt_free(rt);
|
||||
st_ext_ctx_free(ctx);
|
||||
return rc;
|
||||
if (fseek(f, 0, SEEK_END) != 0) {
|
||||
(void)fclose(f);
|
||||
need = strlen(path) + 64;
|
||||
buf = malloc(need);
|
||||
if (buf == NULL)
|
||||
return st_error_internal("out of memory");
|
||||
(void)snprintf(buf, need, "cannot read build file '%s': %s",
|
||||
path, strerror(errno));
|
||||
err = st_error_io(buf);
|
||||
free(buf);
|
||||
return err;
|
||||
}
|
||||
size = ftell(f);
|
||||
if (size < 0 || fseek(f, 0, SEEK_SET) != 0) {
|
||||
(void)fclose(f);
|
||||
need = strlen(path) + 64;
|
||||
buf = malloc(need);
|
||||
if (buf == NULL)
|
||||
return st_error_internal("out of memory");
|
||||
(void)snprintf(buf, need, "cannot read build file '%s': %s",
|
||||
path, strerror(errno));
|
||||
err = st_error_io(buf);
|
||||
free(buf);
|
||||
return err;
|
||||
}
|
||||
buf = malloc((size_t)size + 1);
|
||||
if (buf == NULL) {
|
||||
(void)fclose(f);
|
||||
return st_error_internal("out of memory");
|
||||
}
|
||||
got = fread(buf, 1, (size_t)size, f);
|
||||
if (got != (size_t)size || fclose(f) != 0) {
|
||||
free(buf);
|
||||
need = strlen(path) + 64;
|
||||
buf = malloc(need);
|
||||
if (buf == NULL)
|
||||
return st_error_internal("out of memory");
|
||||
(void)snprintf(buf, need, "cannot read build file '%s': %s",
|
||||
path, strerror(errno));
|
||||
err = st_error_io(buf);
|
||||
free(buf);
|
||||
return err;
|
||||
}
|
||||
buf[size] = '\0';
|
||||
*out = buf;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int
|
||||
@@ -68,7 +131,12 @@ main(int argc, char **argv)
|
||||
{
|
||||
struct cli_opts opts;
|
||||
enum cli_action action;
|
||||
const char *env;
|
||||
struct st_error *err = NULL;
|
||||
char *src = NULL;
|
||||
struct st_kdl_document *doc = NULL;
|
||||
struct st_ext_ctx *ctx = NULL;
|
||||
struct st_lua_rt *rt = NULL;
|
||||
int rc = CLI_EXIT_RUNTIME;
|
||||
|
||||
action = cli_parse(argc, argv, &opts);
|
||||
|
||||
@@ -84,14 +152,6 @@ main(int argc, char **argv)
|
||||
break;
|
||||
}
|
||||
|
||||
if (opts.ext_dir_count > 0) {
|
||||
return run_extension_discovery(&opts);
|
||||
}
|
||||
env = getenv("STUPIDTOOLS_EXT");
|
||||
if (env != NULL && env[0] != '\0') {
|
||||
return run_extension_discovery(&opts);
|
||||
}
|
||||
|
||||
if (opts.buildfile == NULL) {
|
||||
fprintf(stderr, "%s: error: no build file given\n",
|
||||
opts.program);
|
||||
@@ -99,7 +159,65 @@ main(int argc, char **argv)
|
||||
return CLI_EXIT_RUNTIME;
|
||||
}
|
||||
|
||||
/* Accepted: the buildfile is parsed in todo 16+, not read here. */
|
||||
(void)opts.buildfile;
|
||||
return CLI_EXIT_OK;
|
||||
/* 1. read the buildfile. */
|
||||
err = read_file(opts.buildfile, &src);
|
||||
if (err != NULL)
|
||||
goto fail;
|
||||
|
||||
/* 2. parse + validate. `src` must outlive the document (the AST
|
||||
* borrows its token slices), so it is freed last. */
|
||||
doc = st_kdl_parse(src, opts.buildfile, &err);
|
||||
if (doc == NULL)
|
||||
goto fail;
|
||||
|
||||
err = st_kdl_validate(doc);
|
||||
if (err != NULL)
|
||||
goto fail;
|
||||
|
||||
/* 3. extensions + toolchain. Discovery runs the builtin modules
|
||||
* itself (st_ext_init_builtins), so we never call it here. */
|
||||
ctx = st_ext_ctx_new();
|
||||
rt = st_lua_rt_new();
|
||||
if (ctx == NULL || rt == NULL) {
|
||||
err = st_error_internal("out of memory");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
st_ext_discover_set_log(stdout_log);
|
||||
err = st_ext_discover(ctx, rt, opts.ext_dirs, opts.ext_dir_count);
|
||||
if (err != NULL)
|
||||
goto fail;
|
||||
|
||||
err = st_ext_bridge_lua(ctx, rt);
|
||||
if (err != NULL)
|
||||
goto fail;
|
||||
|
||||
err = st_ext_detect_language(ctx, "c");
|
||||
if (err != NULL)
|
||||
goto fail;
|
||||
|
||||
/* 4. emit ./configure into the current working directory. */
|
||||
err = st_gen_configure_emit_path("configure", doc, ctx);
|
||||
if (err != NULL)
|
||||
goto fail;
|
||||
|
||||
/* 5. summary. */
|
||||
{
|
||||
const char *cc = st_registry_get_var(st_ext_var_registry(ctx),
|
||||
"CC");
|
||||
printf("wrote ./configure (CC=%s)\n",
|
||||
cc != NULL ? cc : "cc");
|
||||
}
|
||||
rc = CLI_EXIT_OK;
|
||||
|
||||
fail:
|
||||
if (err != NULL) {
|
||||
st_error_print(stderr, err);
|
||||
st_error_free(err);
|
||||
}
|
||||
st_lua_rt_free(rt);
|
||||
st_ext_ctx_free(ctx);
|
||||
st_kdl_document_free(doc);
|
||||
free(src);
|
||||
return rc;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user