Template
feat(gen): emit config.status/config.log/config.h
This commit is contained in:
@@ -0,0 +1,280 @@
|
||||
/*
|
||||
* config.c - config.log / config.h / config.status emitters (todo 17).
|
||||
*
|
||||
* Four pure emitters; see gen/config.h for the model, the emitted text of
|
||||
* each section, the sed-quote bake-in security argument, the v1
|
||||
* limitations and the error categories.
|
||||
*
|
||||
* Copyright (c) 2026 huntedbytheirs
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include "gen/config.h"
|
||||
|
||||
#include "cli.h" /* STUPIDTOOLS_VERSION (the created-by / generated-by line) */
|
||||
|
||||
#include "error.h"
|
||||
#include "gen/sh_emit.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* ---- small helpers ----------------------------------------------------- */
|
||||
|
||||
/* True when `s` is a valid POSIX shell identifier (the have_<name> /
|
||||
* HAVE_<NAME> interpolation positions demand it; mirrors configure.c). */
|
||||
static bool
|
||||
valid_ident(const char *s)
|
||||
{
|
||||
const unsigned char *p;
|
||||
|
||||
if (s == NULL || *s == '\0') {
|
||||
return false;
|
||||
}
|
||||
p = (const unsigned char *)s;
|
||||
if (!((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') ||
|
||||
*p == '_')) {
|
||||
return false;
|
||||
}
|
||||
for (p++; *p != '\0'; p++) {
|
||||
if (!((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') ||
|
||||
(*p >= '0' && *p <= '9') || *p == '_')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Uppercase copy of a validated identifier: "pthread" -> "PTHREAD". */
|
||||
static char *
|
||||
ident_upper(const char *s)
|
||||
{
|
||||
size_t i;
|
||||
size_t n = strlen(s);
|
||||
char *up = malloc(n + 1);
|
||||
|
||||
if (up == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
for (i = 0; i < n; i++) {
|
||||
unsigned char c = (unsigned char)s[i];
|
||||
|
||||
up[i] = (char)((c >= 'a' && c <= 'z') ? c - 'a' + 'A' : c);
|
||||
}
|
||||
up[n] = '\0';
|
||||
return up;
|
||||
}
|
||||
|
||||
/* ---- fixed shell text (emitted verbatim via st_sh_emit_str) ------------ */
|
||||
|
||||
/* Section A: config.log init + original-args capture. Runs right after
|
||||
* the preamble. The `%` in `printf 'configure invocation: %s\n'` is why
|
||||
* this is emitted verbatim (fprintf would eat it). The two sed pipelines
|
||||
* rewrite every ' into '\'' so the values can later be baked into
|
||||
* config.status as single-quoted literals. */
|
||||
static const char CONFIG_LOG_INIT[] =
|
||||
"\n"
|
||||
"# --- config.log: transcript of this configure run ---\n"
|
||||
"# Probe snippets append their compiler stdout/stderr to $config_log\n"
|
||||
"# (see the probe section below); a `## results` summary of every\n"
|
||||
"# have_<feature> value is appended after probing. The original\n"
|
||||
"# invocation is captured here for config.status (written at the end\n"
|
||||
"# of this script), which re-runs configure with these arguments.\n"
|
||||
"st_orig_args=\"$@\"\n"
|
||||
"st_configure_path=$0\n"
|
||||
"# Single-quote-escaped copies (every ' becomes '\\'' in shell): the\n"
|
||||
"# values can then be baked into config.status as safe quoted\n"
|
||||
"# literals - nothing in them is ever re-expanded when config.status\n"
|
||||
"# runs.\n"
|
||||
"st_orig_args_q=$(printf '%s' \"$st_orig_args\" | sed \"s/'/'\\\\\\\\''/g\")\n"
|
||||
"st_configure_path_q=$(printf '%s' \"$st_configure_path\" | sed \"s/'/'\\\\\\\\''/g\")\n"
|
||||
": \"${config_log:=./config.log}\"\n"
|
||||
"{\n"
|
||||
" printf '%s\\n' 'This file contains any messages produced by compilers while'\n"
|
||||
" printf '%s\\n' 'running configure, to aid debugging if configure makes a mistake.'\n"
|
||||
" printf '%s\\n' ''\n"
|
||||
" printf '%s\\n' 'It was created by stupidtools configure "
|
||||
STUPIDTOOLS_VERSION
|
||||
".'\n"
|
||||
" printf 'configure invocation: %s\\n' \"$st_orig_args\"\n"
|
||||
" printf '%s\\n' \"timestamp: $(date)\"\n"
|
||||
" printf '%s\\n' \"host: $(uname -s) $(uname -m)\"\n"
|
||||
" printf '%s\\n' \"shell: ${SHELL:-unknown}\"\n"
|
||||
" printf '%s\\n' ''\n"
|
||||
"} > \"$config_log\"\n";
|
||||
|
||||
/* Section B (part 1): the `## results` summary appended to $config_log. */
|
||||
static const char RESULTS_PRE[] =
|
||||
"\n"
|
||||
"# --- probe results (appended to config.log) ---\n"
|
||||
"# One have_<feature>=yes|no line per feature, as determined by the\n"
|
||||
"# probes above. config.log is therefore: the header (top), every\n"
|
||||
"# probe's compiler output (middle), this summary (end).\n"
|
||||
"{\n"
|
||||
" printf '%s\\n' '## results'\n";
|
||||
|
||||
static const char RESULTS_POST[] =
|
||||
"} >> \"$config_log\"\n";
|
||||
|
||||
/* Section B (part 2): the ./config.h writer. */
|
||||
static const char CONFIG_H_PRE[] =
|
||||
"\n"
|
||||
"# --- config.h ---\n"
|
||||
"# HAVE_<FEATURE> defines written at CONFIGURE time: one define per\n"
|
||||
"# feature whose probe result was 'yes' (the content reflects the\n"
|
||||
"# actual probe results, never the generator's guesses). The\n"
|
||||
"# ST_CONFIG_H guard keeps the file safe to include repeatedly.\n"
|
||||
"{\n"
|
||||
" printf '%s\\n' '/* config.h - generated by stupidtools configure "
|
||||
STUPIDTOOLS_VERSION
|
||||
" - DO NOT EDIT. */'\n"
|
||||
" printf '%s\\n' '#ifndef ST_CONFIG_H'\n"
|
||||
" printf '%s\\n' '#define ST_CONFIG_H'\n"
|
||||
" printf '%s\\n' ''\n";
|
||||
|
||||
static const char CONFIG_H_POST[] =
|
||||
" printf '%s\\n' ''\n"
|
||||
" printf '%s\\n' '#endif /* ST_CONFIG_H */'\n"
|
||||
"} > ./config.h\n";
|
||||
|
||||
/* Section C: the ./config.status writer. The here-document is UNQUOTED on
|
||||
* purpose: configure-time expansion bakes $st_orig_args_q /
|
||||
* $st_configure_path_q into the file (expansion results are never
|
||||
* re-parsed, so nothing can execute), while every `\$` survives as a
|
||||
* literal `$` for config.status to expand when IT runs. */
|
||||
static const char CONFIG_STATUS[] =
|
||||
"\n"
|
||||
"# --- config.status ---\n"
|
||||
"# A self-contained POSIX-sh helper written at configure time. It\n"
|
||||
"# re-runs ./configure with the arguments of the ORIGINAL invocation\n"
|
||||
"# (captured at the top of this script and baked in here as quoted\n"
|
||||
"# literals, so no value is ever re-expanded when config.status\n"
|
||||
"# runs).\n"
|
||||
"cat > ./config.status <<ST_STATUS_EOF\n"
|
||||
"#!/bin/sh\n"
|
||||
"# config.status - re-runs ./configure with the original arguments.\n"
|
||||
"# Generated by stupidtools configure "
|
||||
STUPIDTOOLS_VERSION
|
||||
" - DO NOT EDIT.\n"
|
||||
"\n"
|
||||
"st_orig_args='$st_orig_args_q'\n"
|
||||
"st_configure_path='$st_configure_path_q'\n"
|
||||
"\n"
|
||||
"case \"\\${1-}\" in\n"
|
||||
" --recheck|'')\n"
|
||||
" exec \"\\${SHELL:-sh}\" \"\\$st_configure_path\" \\$st_orig_args\n"
|
||||
" ;;\n"
|
||||
" --config)\n"
|
||||
" printf '%s\\n' \"\\$st_orig_args\"\n"
|
||||
" ;;\n"
|
||||
" --help)\n"
|
||||
" printf '%s\\n' 'Usage: ./config.status [OPTION]'\n"
|
||||
" printf '%s\\n' 'Re-run the original ./configure invocation.'\n"
|
||||
" printf '%s\\n' 'Options:'\n"
|
||||
" printf '%s\\n' ' --recheck re-run ./configure with the original arguments'\n"
|
||||
" printf '%s\\n' ' --config print the original configure arguments'\n"
|
||||
" printf '%s\\n' ' --help print this help'\n"
|
||||
" ;;\n"
|
||||
" *)\n"
|
||||
" printf 'config.status: error: unrecognized option %s\\n' \"\\$1\" >&2\n"
|
||||
" exit 1\n"
|
||||
" ;;\n"
|
||||
"esac\n"
|
||||
"ST_STATUS_EOF\n"
|
||||
"chmod +x ./config.status\n";
|
||||
|
||||
/* ---- the public API ---------------------------------------------------- */
|
||||
|
||||
struct st_error *
|
||||
st_gen_config_log_init_emit(FILE *out)
|
||||
{
|
||||
if (out == NULL) {
|
||||
return st_error_usage("st_gen_config_log_init_emit: NULL argument");
|
||||
}
|
||||
if (st_sh_emit_str(out, CONFIG_LOG_INIT) < 0) {
|
||||
return st_error_io("I/O error emitting config.log init section");
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct st_error *
|
||||
st_gen_config_results_emit(FILE *out, const char *const *features,
|
||||
size_t count)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (out == NULL || (features == NULL && count > 0)) {
|
||||
return st_error_usage("st_gen_config_results_emit: NULL argument");
|
||||
}
|
||||
if (st_sh_emit_str(out, RESULTS_PRE) < 0) {
|
||||
return st_error_io("I/O error emitting probe results section");
|
||||
}
|
||||
for (i = 0; i < count; i++) {
|
||||
if (!valid_ident(features[i])) {
|
||||
return st_error_usage("feature name is not a valid shell "
|
||||
"identifier");
|
||||
}
|
||||
if (fprintf(out, " printf 'have_%s=%%s\\n' \"$have_%s\"\n",
|
||||
features[i], features[i]) < 0) {
|
||||
return st_error_io("I/O error emitting probe results section");
|
||||
}
|
||||
}
|
||||
if (st_sh_emit_str(out, RESULTS_POST) < 0) {
|
||||
return st_error_io("I/O error emitting probe results section");
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct st_error *
|
||||
st_gen_config_h_emit(FILE *out, const char *const *features, size_t count)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (out == NULL || (features == NULL && count > 0)) {
|
||||
return st_error_usage("st_gen_config_h_emit: NULL argument");
|
||||
}
|
||||
if (st_sh_emit_str(out, CONFIG_H_PRE) < 0) {
|
||||
return st_error_io("I/O error emitting config.h section");
|
||||
}
|
||||
for (i = 0; i < count; i++) {
|
||||
char *up;
|
||||
|
||||
if (!valid_ident(features[i])) {
|
||||
return st_error_usage("feature name is not a valid shell "
|
||||
"identifier");
|
||||
}
|
||||
up = ident_upper(features[i]);
|
||||
if (up == NULL) {
|
||||
return st_error_internal("out of memory uppercasing feature "
|
||||
"name");
|
||||
}
|
||||
if (fprintf(out,
|
||||
" if [ \"$have_%s\" = \"yes\" ]; then\n"
|
||||
" printf '%%s\\n' '#define HAVE_%s 1'\n"
|
||||
" fi\n",
|
||||
features[i], up) < 0) {
|
||||
free(up);
|
||||
return st_error_io("I/O error emitting config.h section");
|
||||
}
|
||||
free(up);
|
||||
}
|
||||
if (st_sh_emit_str(out, CONFIG_H_POST) < 0) {
|
||||
return st_error_io("I/O error emitting config.h section");
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct st_error *
|
||||
st_gen_config_status_emit(FILE *out)
|
||||
{
|
||||
if (out == NULL) {
|
||||
return st_error_usage("st_gen_config_status_emit: NULL argument");
|
||||
}
|
||||
if (st_sh_emit_str(out, CONFIG_STATUS) < 0) {
|
||||
return st_error_io("I/O error emitting config.status section");
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* config.h - emitters for the configure-time auxiliary artifacts
|
||||
* (todo 17): config.log, config.h and config.status.
|
||||
*
|
||||
* MODEL: like everything in src/gen/, these functions are PURE CODE
|
||||
* GENERATORS. They emit POSIX-sh sections INTO the generated ./configure;
|
||||
* the artifacts themselves are written AT CONFIGURE TIME by the generated
|
||||
* script, so their contents always reflect the actual probe results and
|
||||
* the actual invocation - never the generator's guesses.
|
||||
*
|
||||
* THE FOUR SECTIONS (see also gen/configure.h for their placement in the
|
||||
* generated script):
|
||||
*
|
||||
* st_gen_config_log_init_emit after the preamble: capture the
|
||||
* original invocation (st_orig_args="$@", st_configure_path=$0,
|
||||
* plus single-quote-escaped copies built with sed) and truncate-
|
||||
* write the transcript header (created-by line, original
|
||||
* arguments, timestamp, host, shell) into $config_log. $config_log
|
||||
* is defaulted here with `:=`, idempotently with todo 12's cache
|
||||
* boilerplate. Probe snippets (todo 12) append their compiler
|
||||
* stdout/stderr to the same file.
|
||||
* st_gen_config_results_emit after the probe section: append a
|
||||
* `## results` block to $config_log with one
|
||||
* `have_<feature>=yes|no` line per top-level feature.
|
||||
* st_gen_config_h_emit after the probe section: write
|
||||
* ./config.h with the ST_CONFIG_H guard and one
|
||||
* `#define HAVE_<FEATURE> 1` per feature whose have_<feature>
|
||||
* resolved to "yes" at configure time (the feature name upper-
|
||||
* cased; a failed feature gets no define).
|
||||
* st_gen_config_status_emit at the very end (after Makefile
|
||||
* substitution, before exit 0): write ./config.status - a
|
||||
* standalone POSIX-sh script with the captured arguments baked in
|
||||
* as quoted literals (so nothing in them is ever re-expanded when
|
||||
* config.status runs) - and chmod +x it. Its arms: ''/--recheck ->
|
||||
* `exec "${SHELL:-sh}" "$st_configure_path" $st_orig_args`;
|
||||
* --config -> print the original args; --help -> brief usage;
|
||||
* anything else -> error + exit 1.
|
||||
*
|
||||
* SECURITY (the sed-quote bake-in): the captured args/path are rewritten
|
||||
* with `sed "s/'/'\\''/g"` (every `'` becomes `'\''`) and stored in
|
||||
* st_orig_args_q / st_configure_path_q. config.status assigns them as
|
||||
* single-quoted literals (`st_orig_args='...'`), so a value containing
|
||||
* `$(...)`, backticks, spaces or `&` is inert both when configure writes
|
||||
* config.status (here-document expansion never re-parses expansion
|
||||
* results) and when config.status runs (the quotes are real script text
|
||||
* there). Proven by the injection test (todo 17, case f).
|
||||
*
|
||||
* KNOWN v1 LIMITATIONS (documented, deliberately simple):
|
||||
* - the recheck arm expands $st_orig_args UNQUOTED, so positional args
|
||||
* containing spaces or glob characters are not preserved through
|
||||
* --recheck (the v1 surface is --prefix/--host/--build);
|
||||
* - st_configure_path is captured verbatim ($0), so config.status must
|
||||
* be run from the same directory configure was run from;
|
||||
* - args containing literal newlines are pathological (they would break
|
||||
* the one-line bake-in).
|
||||
*
|
||||
* ERRORS: NULL arguments and feature names that are not valid POSIX shell
|
||||
* identifiers return ST_ERR_USAGE (the names land in have_<name> and
|
||||
* #define HAVE_<NAME> positions, which cannot be shell-quoted); stream
|
||||
* write failures return ST_ERR_IO.
|
||||
*
|
||||
* Copyright (c) 2026 huntedbytheirs
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef ST_GEN_CONFIG_H
|
||||
#define ST_GEN_CONFIG_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
|
||||
struct st_error;
|
||||
|
||||
/* Emit the config.log init + original-args capture section. */
|
||||
struct st_error *st_gen_config_log_init_emit(FILE *out);
|
||||
|
||||
/* Emit the post-probe `## results` summary appended to $config_log.
|
||||
* `features` holds the top-level feature names (validated identifiers),
|
||||
* one emitted `have_<name>=%s` line per entry. */
|
||||
struct st_error *st_gen_config_results_emit(FILE *out,
|
||||
const char *const *features,
|
||||
size_t count);
|
||||
|
||||
/* Emit the ./config.h writer (ST_CONFIG_H guard + per-feature defines). */
|
||||
struct st_error *st_gen_config_h_emit(FILE *out,
|
||||
const char *const *features,
|
||||
size_t count);
|
||||
|
||||
/* Emit the ./config.status writer (+ chmod +x). */
|
||||
struct st_error *st_gen_config_status_emit(FILE *out);
|
||||
|
||||
#endif /* ST_GEN_CONFIG_H */
|
||||
+79
-9
@@ -20,6 +20,7 @@
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include "gen/config.h"
|
||||
#include "gen/configure.h"
|
||||
|
||||
#include "cli.h" /* STUPIDTOOLS_VERSION (the generated-by version string) */
|
||||
@@ -327,9 +328,7 @@ static const char SUBST_LOOKUP_TAIL[] =
|
||||
" done < ./Makefile.in\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"st_subst\n"
|
||||
"\n"
|
||||
"exit 0\n";
|
||||
"st_subst\n";
|
||||
|
||||
/* ---- section emitters -------------------------------------------------- */
|
||||
|
||||
@@ -570,6 +569,27 @@ done:
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Append `name` (owned) to the feature-name list, growing it as needed.
|
||||
* Used by the probe-section loop to collect the names the todo-17 results
|
||||
* summary + config.h defines are emitted from. */
|
||||
static struct st_error *
|
||||
collect_feature_name(char ***names, size_t *count, size_t *cap, char *name)
|
||||
{
|
||||
if (*count == *cap) {
|
||||
size_t ncap = *cap == 0 ? 8 : *cap * 2;
|
||||
char **nn = realloc(*names, ncap * sizeof(*nn));
|
||||
|
||||
if (nn == NULL) {
|
||||
return st_error_internal("out of memory collecting feature "
|
||||
"names");
|
||||
}
|
||||
*names = nn;
|
||||
*cap = ncap;
|
||||
}
|
||||
(*names)[(*count)++] = name;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ---- the public API ---------------------------------------------------- */
|
||||
|
||||
struct st_error *
|
||||
@@ -583,6 +603,9 @@ st_gen_configure_emit(FILE *out, const struct st_kdl_document *doc,
|
||||
const struct st_kdl_node *node;
|
||||
char **subst_names = NULL;
|
||||
size_t n_subst = 0;
|
||||
char **feat_names = NULL;
|
||||
size_t n_feat = 0;
|
||||
size_t feat_cap = 0;
|
||||
|
||||
if (out == NULL || doc == NULL || ctx == NULL) {
|
||||
return st_error_usage("st_gen_configure_emit: NULL argument");
|
||||
@@ -634,7 +657,13 @@ st_gen_configure_emit(FILE *out, const struct st_kdl_document *doc,
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* 3. host detection */
|
||||
/* 3. config.log init + original-args capture (todo 17) */
|
||||
err = st_gen_config_log_init_emit(out);
|
||||
if (err != NULL) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* 4. host detection */
|
||||
if (st_sh_emit_str(out, HOST_DETECT_PRE) < 0) {
|
||||
err = st_error_io("I/O error emitting host detection");
|
||||
goto done;
|
||||
@@ -648,7 +677,7 @@ st_gen_configure_emit(FILE *out, const struct st_kdl_document *doc,
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* 4. toolchain defaults (before the cache boilerplate; see the header) */
|
||||
/* 5. toolchain defaults (before the cache boilerplate; see the header) */
|
||||
if (st_sh_emit_str(out, TOOLCHAIN_DEFAULTS_PRE) < 0) {
|
||||
err = st_error_io("I/O error emitting toolchain defaults");
|
||||
goto done;
|
||||
@@ -681,15 +710,18 @@ st_gen_configure_emit(FILE *out, const struct st_kdl_document *doc,
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* 5. cache boilerplate (todo 12) */
|
||||
/* 6. cache boilerplate (todo 12) */
|
||||
err = st_probe_emit_cache_functions(out);
|
||||
if (err != NULL) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* 6. probe section: each top-level `feature` node in document order */
|
||||
/* 7. probe section: each top-level `feature` node in document order.
|
||||
* The resolved feature names are collected here for the todo-17
|
||||
* results summary + config.h defines, emitted right after the loop. */
|
||||
for (node = doc->nodes; node != NULL; node = node->next) {
|
||||
bool is_feature = false;
|
||||
char *fname = NULL;
|
||||
|
||||
err = name_equals(&node->name, "feature", &is_feature);
|
||||
if (err != NULL) {
|
||||
@@ -702,21 +734,59 @@ st_gen_configure_emit(FILE *out, const struct st_kdl_document *doc,
|
||||
if (err != NULL) {
|
||||
goto done;
|
||||
}
|
||||
/* a successful emit_feature guarantees the name is a valid shell
|
||||
* identifier (it errors otherwise), so the have_<name> /
|
||||
* HAVE_<NAME> interpolation below is safe */
|
||||
err = extract_string(&node->args->value, "feature name", &fname);
|
||||
if (err != NULL) {
|
||||
goto done;
|
||||
}
|
||||
err = collect_feature_name(&feat_names, &n_feat, &feat_cap, fname);
|
||||
if (err != NULL) {
|
||||
free(fname);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
/* 7. substitution section */
|
||||
/* 8. probe-results summary + config.h (todo 17) */
|
||||
err = st_gen_config_results_emit(out, (const char *const *)feat_names,
|
||||
n_feat);
|
||||
if (err != NULL) {
|
||||
goto done;
|
||||
}
|
||||
err = st_gen_config_h_emit(out, (const char *const *)feat_names, n_feat);
|
||||
if (err != NULL) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* 9. substitution section */
|
||||
err = emit_subst_lookup(out, (const char *const *)subst_names, n_subst);
|
||||
if (err != NULL) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* 8. exit 0 is already the last line of the substitution tail */
|
||||
/* 10. config.status (todo 17): written after substitution, before the
|
||||
* final exit 0 */
|
||||
err = st_gen_config_status_emit(out);
|
||||
if (err != NULL) {
|
||||
goto done;
|
||||
}
|
||||
if (st_sh_emit_str(out, "\nexit 0\n") < 0) {
|
||||
err = st_error_io("I/O error emitting configure");
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* 11. final stream check */
|
||||
if (ferror(out)) {
|
||||
err = st_error_io("I/O error emitting configure");
|
||||
goto done;
|
||||
}
|
||||
|
||||
done:
|
||||
for (i = 0; i < n_feat; i++) {
|
||||
free(feat_names[i]);
|
||||
}
|
||||
free(feat_names);
|
||||
free(subst_names);
|
||||
return err;
|
||||
}
|
||||
|
||||
+31
-7
@@ -12,7 +12,7 @@
|
||||
* -------------------------------------------------------------
|
||||
* 1. `#!/bin/sh` + a header comment (generated-by, version).
|
||||
* 2. PREAMBLE — v1 MINIMAL argument handling, structurally marked with a
|
||||
* `# === preamble ===` comment so todo 18 replaces it whole:
|
||||
* `# --- PREAMBLE ---` comment so todo 18 replaces it whole:
|
||||
* - `--prefix=<dir>` stores $prefix (default /usr/local);
|
||||
* - `--host=<triplet>` / `--build=<triplet>` store $host / $build;
|
||||
* $cross_compiling=yes when --host differs from --build (a simple
|
||||
@@ -22,16 +22,23 @@
|
||||
* the positional form is accepted-but-ignored until todo 18;
|
||||
* - an unknown `--*` prints "unrecognized option" to stderr and
|
||||
* exits 1.
|
||||
* 3. HOST DETECTION: `st_os=$(uname -s)`, then the st_os_norm() emitted by
|
||||
* 3. CONFIG.LOG INIT (todo 17) — capture the original invocation
|
||||
* (st_orig_args="$@", st_configure_path=$0, plus single-quote-escaped
|
||||
* copies built with sed for the config.status bake-in) and
|
||||
* truncate-write the transcript header (created-by line, original
|
||||
* arguments, timestamp, host, shell) into $config_log (defaulted here
|
||||
* with `:=`, idempotently with todo 12's boilerplate default). Probe
|
||||
* snippets append their compiler stdout/stderr to the same file.
|
||||
* 4. HOST DETECTION: `st_os=$(uname -s)`, then the st_os_norm() emitted by
|
||||
* resolve.h's st_resolve_emit_os_norm() rewrites it in place; `st_cc_id
|
||||
* =unknown` (v1, no compiler-id sniffing at configure time).
|
||||
* 4. TOOLCHAIN DEFAULTS: for each registered var name (CC/CFLAGS/...) emit
|
||||
* 5. TOOLCHAIN DEFAULTS: for each registered var name (CC/CFLAGS/...) emit
|
||||
* `VAR="${VAR:-<default>}"` where <default> is the registry VALUE at
|
||||
* generation time (st_sh_quote'd; typically empty for CFLAGS), then
|
||||
* `LIBS=""`. Emitted BEFORE the probe cache boilerplate because that
|
||||
* boilerplate uses `:=` fallbacks that must not clobber these values.
|
||||
* 5. CACHE BOILERPLATE: probe.h's st_probe_emit_cache_functions (todo 12).
|
||||
* 6. PROBE SECTION: for each top-level `feature` node in document order,
|
||||
* 6. CACHE BOILERPLATE: probe.h's st_probe_emit_cache_functions (todo 12).
|
||||
* 7. PROBE SECTION: for each top-level `feature` node in document order,
|
||||
* for each check child, derive the checkname via resolve.h's
|
||||
* st_resolve_check_name, build the probe via checks.h's
|
||||
* st_check_probe_build, and emit the snippet via probe.h's
|
||||
@@ -42,14 +49,31 @@
|
||||
* v1 pkg-config guard `if command -v pkg-config >/dev/null 2>&1; then
|
||||
* ... LIBS="$LIBS $(pkg-config --libs 'X')" ...; fi`. Generic over the
|
||||
* check kinds, never hardcoded per feature name.
|
||||
* 7. SUBSTITUTION: reads ./Makefile.in (cwd, the v1 source-dir contract)
|
||||
* 8. PROBE RESULTS + CONFIG.H (todo 17) — append a `## results` block to
|
||||
* $config_log (one `have_<feature>=yes|no` line per top-level feature),
|
||||
* then write ./config.h with the ST_CONFIG_H guard and one
|
||||
* `#define HAVE_<FEATURE> 1` per feature whose have_<feature> resolved
|
||||
* to "yes". Both reflect the configure-time probe results, never
|
||||
* generation-time guesses; feature names are validated identifiers
|
||||
* (see ERRORS), so the have_<name> / HAVE_<NAME> interpolation is
|
||||
* safe.
|
||||
* 9. SUBSTITUTION: reads ./Makefile.in (cwd, the v1 source-dir contract)
|
||||
* and replaces every `@VAR@` (VAR = registered vars ∪ {LIBS, prefix,
|
||||
* srcdir}) with the shell variable's value, writing ./Makefile. An
|
||||
* UNRESOLVED `@SOMETHING@` is a CONFIGURE-TIME error naming the
|
||||
* variable (the generator cannot know the user's Makefile.in), reported
|
||||
* after substitution via the per-line lookup; a missing Makefile.in is
|
||||
* a clean error. See the SUBSTITUTION approach note below.
|
||||
* 8. `exit 0`.
|
||||
* 10. CONFIG.STATUS (todo 17) — write ./config.status (a standalone
|
||||
* POSIX-sh script with the original arguments baked in as quoted
|
||||
* literals, so nothing in them is ever re-expanded when it runs) and
|
||||
* chmod +x it. Arms: ''/--recheck -> `exec "${SHELL:-sh}"
|
||||
* "$st_configure_path" $st_orig_args`; --config -> print the original
|
||||
* args; --help -> brief usage; unknown -> error + exit 1. v1
|
||||
* limitations (documented in gen/config.h): positional args with
|
||||
* spaces/globs are not preserved by the unquoted recheck expansion;
|
||||
* config.status must run from the same directory as configure.
|
||||
* 11. `exit 0`.
|
||||
*
|
||||
* SUBSTITUTION APPROACH (documented because the plan flagged it)
|
||||
* --------------------------------------------------------------
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* LINK: ../../src/gen/configure.c ../../src/gen/sh_emit.c ../../src/detect/probe.c ../../src/detect/checks.c ../../src/detect/check_registry.c ../../src/detect/resolve.c ../../src/kdl/schema.c ../../src/kdl/parser.c ../../src/kdl/lexer.c ../../src/kdl/value.c ../../src/ext/abi.c ../../src/ext/lang_c.c ../../src/ext/lang_cpp.c ../../src/error.c ../../src/span.c */
|
||||
/* LINK: ../../src/gen/config.c ../../src/gen/configure.c ../../src/gen/sh_emit.c ../../src/detect/probe.c ../../src/detect/checks.c ../../src/detect/check_registry.c ../../src/detect/resolve.c ../../src/kdl/schema.c ../../src/kdl/parser.c ../../src/kdl/lexer.c ../../src/kdl/value.c ../../src/ext/abi.c ../../src/ext/lang_c.c ../../src/ext/lang_cpp.c ../../src/error.c ../../src/span.c */
|
||||
#ifndef _POSIX_C_SOURCE
|
||||
#define _POSIX_C_SOURCE 200809L /* mkdtemp/unsetenv/setenv (POSIX.1-2008) */
|
||||
#endif
|
||||
@@ -200,6 +200,90 @@ syntax_check(const char *shell, const char *path)
|
||||
return WEXITSTATUS(spawned);
|
||||
}
|
||||
|
||||
/* Run "./config.status <args>" with cwd = temp_dir; stdout to status.out,
|
||||
* stderr to status.err. Returns the exit status, or -1 if not spawned. */
|
||||
static int
|
||||
run_status(const char *args)
|
||||
{
|
||||
char cmd[2400];
|
||||
int n;
|
||||
int spawned;
|
||||
|
||||
n = snprintf(cmd, sizeof cmd,
|
||||
"cd '%s' && ./config.status %s >'%s/status.out' "
|
||||
"2>'%s/status.err'",
|
||||
temp_dir, args != NULL ? args : "", temp_dir, temp_dir);
|
||||
if (n < 0 || (size_t)n >= sizeof cmd) {
|
||||
return -1;
|
||||
}
|
||||
spawned = system(cmd);
|
||||
if (spawned == -1) {
|
||||
return -1;
|
||||
}
|
||||
return WEXITSTATUS(spawned);
|
||||
}
|
||||
|
||||
/* Slurp temp_dir/<name>, or NULL. Caller frees. */
|
||||
static char *
|
||||
slurp_named(const char *name)
|
||||
{
|
||||
char path[600];
|
||||
|
||||
if (mkpath(path, sizeof path, name) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
return slurp(path);
|
||||
}
|
||||
|
||||
/* Remove temp_dir/<name> (a missing file is not an error: the stale_state
|
||||
* checks remove outputs that may or may not exist). Returns 0 on success. */
|
||||
static int
|
||||
remove_named(const char *name)
|
||||
{
|
||||
char path[600];
|
||||
|
||||
if (mkpath(path, sizeof path, name) < 0) {
|
||||
return -1;
|
||||
}
|
||||
(void)remove(path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Parse + validate + emit an INLINE document into temp_dir/configure. */
|
||||
static int
|
||||
emit_inline_configure(struct st_ext_ctx *ctx, const char *docsrc)
|
||||
{
|
||||
struct st_kdl_document *doc = NULL;
|
||||
struct st_error *err = NULL;
|
||||
char conf[600];
|
||||
|
||||
doc = st_kdl_parse(docsrc, "inline.kdl", &err);
|
||||
if (err != NULL) {
|
||||
st_error_free(err);
|
||||
return -1;
|
||||
}
|
||||
if (doc == NULL) {
|
||||
return -1;
|
||||
}
|
||||
err = st_kdl_validate(doc);
|
||||
if (err != NULL) {
|
||||
st_error_free(err);
|
||||
st_kdl_document_free(doc);
|
||||
return -1;
|
||||
}
|
||||
if (mkpath(conf, sizeof conf, "configure") < 0) {
|
||||
st_kdl_document_free(doc);
|
||||
return -1;
|
||||
}
|
||||
err = st_gen_configure_emit_path(conf, doc, ctx);
|
||||
st_kdl_document_free(doc);
|
||||
if (err != NULL) {
|
||||
st_error_free(err);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- fixture loading (same as test_schema.c) -------------------------- */
|
||||
|
||||
static const char *
|
||||
@@ -618,6 +702,272 @@ test_cross_compile(const MunitParameter params[], void *data)
|
||||
return MUNIT_OK;
|
||||
}
|
||||
|
||||
/* ---- todo 17 (a): the three new sections stay shell-safe ------------- */
|
||||
|
||||
static MunitResult
|
||||
test_config_sections_syntax(const MunitParameter params[], void *data)
|
||||
{
|
||||
(void)params;
|
||||
(void)data;
|
||||
struct st_ext_ctx *ctx;
|
||||
char conf[600];
|
||||
char *bytes;
|
||||
|
||||
ctx = build_ctx();
|
||||
munit_assert_not_null(ctx);
|
||||
munit_assert_int(emit_fixture_configure(ctx), ==, 0);
|
||||
st_ext_ctx_free(ctx);
|
||||
|
||||
munit_assert_int(mkpath(conf, sizeof conf, "configure"), >, 0);
|
||||
munit_assert_int(syntax_check("sh", conf), ==, 0);
|
||||
munit_assert_int(syntax_check("bash", conf), ==, 0);
|
||||
munit_assert_int(syntax_check("zsh", conf), ==, 0);
|
||||
|
||||
bytes = slurp(conf);
|
||||
munit_assert_not_null(bytes);
|
||||
/* the three sections are structurally marked (--- not ===, per the
|
||||
* F3 banned-construct grep) */
|
||||
munit_assert_not_null(strstr(bytes, "# --- config.log:"));
|
||||
munit_assert_not_null(strstr(bytes, "# --- config.h ---"));
|
||||
munit_assert_not_null(strstr(bytes, "# --- config.status ---"));
|
||||
munit_assert_not_null(strstr(bytes, "'## results'"));
|
||||
free(bytes);
|
||||
return MUNIT_OK;
|
||||
}
|
||||
|
||||
/* ---- todo 17 (b): a real run writes config.h/config.log/config.status -- */
|
||||
|
||||
static MunitResult
|
||||
test_config_artifacts(const MunitParameter params[], void *data)
|
||||
{
|
||||
(void)params;
|
||||
(void)data;
|
||||
struct st_ext_ctx *ctx;
|
||||
char status[600];
|
||||
char *ch;
|
||||
char *cl;
|
||||
|
||||
ctx = build_ctx();
|
||||
munit_assert_not_null(ctx);
|
||||
munit_assert_int(emit_fixture_configure(ctx), ==, 0);
|
||||
st_ext_ctx_free(ctx);
|
||||
|
||||
munit_assert_int(write_makefile_in(STD_MAKEFILE_IN), ==, 0);
|
||||
munit_assert_int(run_configure(NULL), ==, 0);
|
||||
|
||||
/* config.h: the ST_CONFIG_H guard + one define per resolved feature
|
||||
* (content asserted, not mere existence) */
|
||||
ch = slurp_named("config.h");
|
||||
munit_assert_not_null(ch);
|
||||
munit_assert_not_null(strstr(ch, "#ifndef ST_CONFIG_H"));
|
||||
munit_assert_not_null(strstr(ch, "#define ST_CONFIG_H"));
|
||||
munit_assert_not_null(strstr(ch, "#define HAVE_PTHREAD 1"));
|
||||
munit_assert_not_null(strstr(ch, "#define HAVE_MATH 1"));
|
||||
free(ch);
|
||||
|
||||
/* config.log: the transcript header + the ## results summary */
|
||||
cl = slurp_named("config.log");
|
||||
munit_assert_not_null(cl);
|
||||
munit_assert_not_null(
|
||||
strstr(cl, "It was created by stupidtools configure"));
|
||||
munit_assert_not_null(strstr(cl, "## results"));
|
||||
munit_assert_not_null(strstr(cl, "have_pthread=yes"));
|
||||
munit_assert_not_null(strstr(cl, "have_math=yes"));
|
||||
free(cl);
|
||||
|
||||
/* config.status: exists, is executable, and is valid POSIX-sh */
|
||||
munit_assert_int(mkpath(status, sizeof status, "config.status"), >, 0);
|
||||
munit_assert_int(access(status, F_OK), ==, 0);
|
||||
munit_assert_int(access(status, X_OK), ==, 0);
|
||||
munit_assert_int(syntax_check("sh", status), ==, 0);
|
||||
munit_assert_int(syntax_check("bash", status), ==, 0);
|
||||
munit_assert_int(syntax_check("zsh", status), ==, 0);
|
||||
return MUNIT_OK;
|
||||
}
|
||||
|
||||
/* ---- todo 17 (c): config.status --config prints the original args ----- */
|
||||
|
||||
static MunitResult
|
||||
test_config_status_config(const MunitParameter params[], void *data)
|
||||
{
|
||||
(void)params;
|
||||
(void)data;
|
||||
struct st_ext_ctx *ctx;
|
||||
char *out;
|
||||
|
||||
ctx = build_ctx();
|
||||
munit_assert_not_null(ctx);
|
||||
munit_assert_int(emit_fixture_configure(ctx), ==, 0);
|
||||
st_ext_ctx_free(ctx);
|
||||
|
||||
munit_assert_int(write_makefile_in(STD_MAKEFILE_IN), ==, 0);
|
||||
munit_assert_int(run_configure("--prefix=/opt/x"), ==, 0);
|
||||
munit_assert_int(run_status("--config"), ==, 0);
|
||||
|
||||
out = slurp_named("status.out");
|
||||
munit_assert_not_null(out);
|
||||
munit_assert_string_equal(out, "--prefix=/opt/x\n");
|
||||
free(out);
|
||||
return MUNIT_OK;
|
||||
}
|
||||
|
||||
/* ---- todo 17 (d): config.status --recheck re-runs configure ----------- */
|
||||
|
||||
static MunitResult
|
||||
test_config_status_recheck(const MunitParameter params[], void *data)
|
||||
{
|
||||
(void)params;
|
||||
(void)data;
|
||||
struct st_ext_ctx *ctx;
|
||||
char *mk;
|
||||
char *ch;
|
||||
|
||||
ctx = build_ctx();
|
||||
munit_assert_not_null(ctx);
|
||||
munit_assert_int(emit_fixture_configure(ctx), ==, 0);
|
||||
st_ext_ctx_free(ctx);
|
||||
|
||||
munit_assert_int(write_makefile_in(STD_MAKEFILE_IN), ==, 0);
|
||||
munit_assert_int(run_configure("--prefix=/opt/x"), ==, 0);
|
||||
|
||||
/* stale_state: destroy the outputs, then --recheck regenerates them
|
||||
* (observable regeneration, not a cached no-op) */
|
||||
munit_assert_int(remove_named("Makefile"), ==, 0);
|
||||
munit_assert_int(remove_named("config.h"), ==, 0);
|
||||
munit_assert_int(run_status("--recheck"), ==, 0);
|
||||
|
||||
mk = makefile_text();
|
||||
munit_assert_not_null(mk);
|
||||
munit_assert_not_null(strstr(mk, "prefix = /opt/x"));
|
||||
free(mk);
|
||||
|
||||
ch = slurp_named("config.h");
|
||||
munit_assert_not_null(ch);
|
||||
munit_assert_not_null(strstr(ch, "#define HAVE_PTHREAD 1"));
|
||||
free(ch);
|
||||
return MUNIT_OK;
|
||||
}
|
||||
|
||||
/* ---- todo 17 (e): a failed feature gets no HAVE_ define --------------- */
|
||||
|
||||
static MunitResult
|
||||
test_config_failed_feature(const MunitParameter params[], void *data)
|
||||
{
|
||||
(void)params;
|
||||
(void)data;
|
||||
static const char *const docsrc =
|
||||
"project \"x\" version \"1.0\"\n"
|
||||
"feature \"nope\" { header \"nope_missing_xyz.h\" }\n"
|
||||
"feature \"ok\" { header \"unistd.h\" }\n";
|
||||
struct st_ext_ctx *ctx;
|
||||
char *ch;
|
||||
char *cl;
|
||||
|
||||
ctx = build_ctx();
|
||||
munit_assert_not_null(ctx);
|
||||
munit_assert_int(emit_inline_configure(ctx, docsrc), ==, 0);
|
||||
st_ext_ctx_free(ctx);
|
||||
|
||||
munit_assert_int(write_makefile_in("CC = @CC@\n"), ==, 0);
|
||||
munit_assert_int(run_configure(NULL), ==, 0);
|
||||
|
||||
ch = slurp_named("config.h");
|
||||
munit_assert_not_null(ch);
|
||||
munit_assert_null(strstr(ch, "HAVE_NOPE"));
|
||||
munit_assert_not_null(strstr(ch, "#define HAVE_OK 1"));
|
||||
free(ch);
|
||||
|
||||
cl = slurp_named("config.log");
|
||||
munit_assert_not_null(cl);
|
||||
munit_assert_not_null(strstr(cl, "have_nope=no"));
|
||||
munit_assert_not_null(strstr(cl, "have_ok=yes"));
|
||||
free(cl);
|
||||
return MUNIT_OK;
|
||||
}
|
||||
|
||||
/* ---- todo 17 (f): injected values never execute ----------------------- */
|
||||
|
||||
static MunitResult
|
||||
test_config_status_injection(const MunitParameter params[], void *data)
|
||||
{
|
||||
(void)params;
|
||||
(void)data;
|
||||
struct st_ext_ctx *ctx;
|
||||
char pwned1[600];
|
||||
char pwned2[600];
|
||||
char *mk;
|
||||
|
||||
ctx = build_ctx();
|
||||
munit_assert_not_null(ctx);
|
||||
munit_assert_int(emit_fixture_configure(ctx), ==, 0);
|
||||
st_ext_ctx_free(ctx);
|
||||
|
||||
munit_assert_int(mkpath(pwned1, sizeof pwned1, "pwned1"), >, 0);
|
||||
munit_assert_int(mkpath(pwned2, sizeof pwned2, "pwned2"), >, 0);
|
||||
munit_assert_int(write_makefile_in("prefix = @prefix@\n"), ==, 0);
|
||||
|
||||
/* The payloads use ${IFS} instead of a literal space: they contain no
|
||||
* whitespace, so they survive the (documented, v1) unquoted recheck
|
||||
* expansion as one word - yet they would create the marker files if
|
||||
* any shell ever executed them. */
|
||||
munit_assert_int(run_configure("--prefix='$(touch${IFS}pwned1)'"), ==, 0);
|
||||
munit_assert_int(access(pwned1, F_OK), ==, -1);
|
||||
munit_assert_int(run_status("--config"), ==, 0);
|
||||
munit_assert_int(access(pwned1, F_OK), ==, -1);
|
||||
munit_assert_int(run_status("--recheck"), ==, 0);
|
||||
munit_assert_int(access(pwned1, F_OK), ==, -1);
|
||||
mk = makefile_text();
|
||||
munit_assert_not_null(mk);
|
||||
munit_assert_not_null(strstr(mk, "prefix = $(touch${IFS}pwned1)"));
|
||||
free(mk);
|
||||
|
||||
/* payload 2: backticks - same guarantee, via a fresh configure run */
|
||||
munit_assert_int(run_configure("--prefix='`touch${IFS}pwned2`'"), ==, 0);
|
||||
munit_assert_int(access(pwned2, F_OK), ==, -1);
|
||||
munit_assert_int(run_status("--config"), ==, 0);
|
||||
munit_assert_int(access(pwned2, F_OK), ==, -1);
|
||||
munit_assert_int(run_status("--recheck"), ==, 0);
|
||||
munit_assert_int(access(pwned2, F_OK), ==, -1);
|
||||
mk = makefile_text();
|
||||
munit_assert_not_null(mk);
|
||||
munit_assert_not_null(strstr(mk, "prefix = `touch${IFS}pwned2`"));
|
||||
free(mk);
|
||||
return MUNIT_OK;
|
||||
}
|
||||
|
||||
/* ---- todo 17 (g): config.status --help and unknown-option handling ---- */
|
||||
|
||||
static MunitResult
|
||||
test_config_status_help(const MunitParameter params[], void *data)
|
||||
{
|
||||
(void)params;
|
||||
(void)data;
|
||||
struct st_ext_ctx *ctx;
|
||||
char *out;
|
||||
char *e;
|
||||
|
||||
ctx = build_ctx();
|
||||
munit_assert_not_null(ctx);
|
||||
munit_assert_int(emit_fixture_configure(ctx), ==, 0);
|
||||
st_ext_ctx_free(ctx);
|
||||
|
||||
munit_assert_int(write_makefile_in(STD_MAKEFILE_IN), ==, 0);
|
||||
munit_assert_int(run_configure(NULL), ==, 0);
|
||||
|
||||
munit_assert_int(run_status("--help"), ==, 0);
|
||||
out = slurp_named("status.out");
|
||||
munit_assert_not_null(out);
|
||||
munit_assert_not_null(strstr(out, "Usage"));
|
||||
free(out);
|
||||
|
||||
munit_assert_int(run_status("--bogus"), !=, 0);
|
||||
e = slurp_named("status.err");
|
||||
munit_assert_not_null(e);
|
||||
munit_assert_not_null(strstr(e, "unrecognized option"));
|
||||
free(e);
|
||||
return MUNIT_OK;
|
||||
}
|
||||
|
||||
static MunitTest tests[] = {
|
||||
{ "/gen/syntax", test_syntax, setup, teardown, MUNIT_TEST_OPTION_NONE,
|
||||
NULL },
|
||||
@@ -639,6 +989,20 @@ static MunitTest tests[] = {
|
||||
teardown, MUNIT_TEST_OPTION_NONE, NULL },
|
||||
{ "/gen/cross-compile", test_cross_compile, setup, teardown,
|
||||
MUNIT_TEST_OPTION_NONE, NULL },
|
||||
{ "/gen/config-sections-syntax", test_config_sections_syntax, setup,
|
||||
teardown, MUNIT_TEST_OPTION_NONE, NULL },
|
||||
{ "/gen/config-artifacts", test_config_artifacts, setup, teardown,
|
||||
MUNIT_TEST_OPTION_NONE, NULL },
|
||||
{ "/gen/config-status-config", test_config_status_config, setup,
|
||||
teardown, MUNIT_TEST_OPTION_NONE, NULL },
|
||||
{ "/gen/config-status-recheck", test_config_status_recheck, setup,
|
||||
teardown, MUNIT_TEST_OPTION_NONE, NULL },
|
||||
{ "/gen/config-failed-feature", test_config_failed_feature, setup,
|
||||
teardown, MUNIT_TEST_OPTION_NONE, NULL },
|
||||
{ "/gen/config-status-injection", test_config_status_injection, setup,
|
||||
teardown, MUNIT_TEST_OPTION_NONE, NULL },
|
||||
{ "/gen/config-status-help", test_config_status_help, setup, teardown,
|
||||
MUNIT_TEST_OPTION_NONE, NULL },
|
||||
{ NULL, NULL, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user