. "$BST_LIB" # gengetopt is GNU's command-line-parser generator (meta-oe 2.23, # recipes-support): it reads a .ggo option spec and writes a C parser -- # cmdline.c/cmdline.h -- built around getopt_long. Neither busybox nor toybox # ships a gengetopt applet, so a bare `gengetopt` is this port. # # It is C++ (10 .cc/.cpp sources plus a C++ yacc parser and lex scanner), riding # the exceptions-off libc++ arm via the recipe's wasmc++-glibc CXX -- so unlike # the pure-C generators on this image (83-lemon, 83-byacc, 90-bison) every case # below is also exercising libc++'s string/vector/iostream/fstream on wasm32. # # THE CENTRAL ASSERTION IS A BYTE COMPARISON, not a content grep. gengetopt's # output is fully determined by its input spec and its compiled-in skeletons, and # the generator is deterministic (proved natively: two runs with identical # command lines produce identical bytes). So data/gengetopt-expected-cmdline.[ch] # are the files a NATIVE x86_64 build of this same 2.23 tarball produced from # data/gengetopt-fixture.ggo, and the target's output must match them exactly. # That is a far stronger claim than "the output looks right": it says the C++ # port emits the same parser a native compiler does, character for character. # # The one licensed difference is the 9-line autogenerated banner at the top of # cmdline.c, whose 4th line records the argv[0] and arguments of the invocation # ("generated with the following command:") -- a path that is necessarily the # target's, not the build host's. cmdline.h carries no such line and is compared # whole. Stripping exactly those 9 lines is why the banner's own shape is # asserted first: if gengetopt ever changed that block's length, the body # comparison would silently start at the wrong offset, and a real difference # could hide inside the skew. require gengetopt gengetopt/present work=${TMPDIR:-/tmp}/bst-gengetopt.$$ rm -rf "$work"; mkdir -p "$work/gen" "$work/dir" "$work/idem1" "$work/idem2" log=$work/log ggo=$BST_DATA/gengetopt-fixture.ggo check gengetopt/fixture-shipped test -f "$ggo" # --version exits 0 and names the release. The recipe's PV and this string have # to agree; a mismatch means the image shipped a different tarball than the # reference output was generated from, which would make every byte comparison # below meaningless -- so this is a precondition, not a cosmetic banner check. check_out gengetopt/version "GNU gengetopt 2.23" gengetopt --version # gengetopt's own --help, which is itself generated by gengetopt from its own # .ggo at build time -- so a correct --help is the tool's first self-application. check_out gengetopt/help-usage "Usage: gengetopt [OPTION]..." gengetopt --help check_out gengetopt/help-purpose "This program generates a C function that uses getopt_long" \ gengetopt --help # --------------------------------------------------------------------------- # Generate from the fixture. rc 0 and both files written. # --------------------------------------------------------------------------- ( cd "$work/gen" && gengetopt -i "$ggo" ) >"$log" 2>&1 check_eq gengetopt/rc-generate 0 "$?" check gengetopt/wrote-c test -f "$work/gen/cmdline.c" check gengetopt/wrote-h test -f "$work/gen/cmdline.h" # The autogenerated banner: exactly the 9 lines the body comparison strips, and # line 4 records the invocation (the one target-specific line in the file). check_eq gengetopt/banner-open "/*" "$(sed -n '1p' "$work/gen/cmdline.c")" check_eq gengetopt/banner-close "*/" "$(sed -n '9p' "$work/gen/cmdline.c")" check_out gengetopt/banner-version "File autogenerated by gengetopt version 2.23" \ sed -n '2p' "$work/gen/cmdline.c" check_out gengetopt/banner-command "generated with the following command:" \ sed -n '3p' "$work/gen/cmdline.c" check_out gengetopt/banner-records-argv "gengetopt" sed -n '4p' "$work/gen/cmdline.c" # THE central case: the generated parser body is byte-identical to what a native # x86_64 build of gengetopt 2.23 produced from the same spec. tail -n +10 "$work/gen/cmdline.c" > "$work/body.c" tail -n +10 "$BST_DATA/gengetopt-expected-cmdline.c" > "$work/ref.c" check_rc gengetopt/c-matches-native 0 cmp -s "$work/body.c" "$work/ref.c" # cmdline.h carries no invocation line, so it is compared whole. check_rc gengetopt/h-matches-native 0 \ cmp -s "$work/gen/cmdline.h" "$BST_DATA/gengetopt-expected-cmdline.h" # --------------------------------------------------------------------------- # Content of the generated parser. These overlap the byte comparison on purpose: # when a cmp fails it says only "differs", and these say WHICH part of the # generator broke -- the spec reader, the type mapper, or the enum table writer. # --------------------------------------------------------------------------- # The spec's package/version reach the header as the parser's identity macros. check_out gengetopt/h-package "#define CMDLINE_PARSER_PACKAGE \"brintos-demo\"" \ cat "$work/gen/cmdline.h" check_out gengetopt/h-version "#define CMDLINE_PARSER_VERSION \"1.2.3\"" \ cat "$work/gen/cmdline.h" # ...and the purpose/usage strings reach the C file as definitions. check_out gengetopt/c-purpose "gengetopt selftest fixture" cat "$work/gen/cmdline.c" check_out gengetopt/c-usage "Usage: brintos-demo [OPTION]..." cat "$work/gen/cmdline.c" # Each declared option becomes a typed field plus its _given counter: this is the # type mapping (string->char *, int->int, flag->int, enum->enum) that is the # whole point of the tool. check_out gengetopt/field-string "char * name_arg;" cat "$work/gen/cmdline.h" check_out gengetopt/field-int "int count_arg;" cat "$work/gen/cmdline.h" check_out gengetopt/field-flag "int verbose_flag;" cat "$work/gen/cmdline.h" check_out gengetopt/field-enum "enum enum_mode mode_arg;" cat "$work/gen/cmdline.h" check_out gengetopt/field-given "unsigned int name_given" cat "$work/gen/cmdline.h" # The enum option generates both a C enum (with the documented -1 NULL member and # zero-based enumerators in declaration order) and a NULL-terminated string table # the parser matches the argument against. check_out gengetopt/enum-decl \ "enum enum_mode { mode__NULL = -1, mode_arg_fast = 0, mode_arg_slow };" \ cat "$work/gen/cmdline.h" check_out gengetopt/enum-table \ "const char *cmdline_parser_mode_values[] = {\"fast\", \"slow\", 0};" \ cat "$work/gen/cmdline.c" # The public entry points the generated parser is consumed through. check_out gengetopt/api-parser "int cmdline_parser (int argc, char **argv," cat "$work/gen/cmdline.h" check_out gengetopt/api-init "void cmdline_parser_init" cat "$work/gen/cmdline.h" check_out gengetopt/api-free "void cmdline_parser_free" cat "$work/gen/cmdline.h" check_out gengetopt/api-required "int cmdline_parser_required" cat "$work/gen/cmdline.h" # The generated C is what the image's own C toolchain would have to accept; there # is no compiler on this image, so the closest available structural check is that # the file is complete -- it opens the extern "C" guard for C++ consumers and # closes the include guard at the end. check_out gengetopt/h-cplusplus-guard "extern \"C\"" cat "$work/gen/cmdline.h" check_out gengetopt/h-include-guard "#define CMDLINE_H" cat "$work/gen/cmdline.h" # --------------------------------------------------------------------------- # --show-help / --show-version: gengetopt renders the GENERATED program's help # without writing any file. This is the tool interpreting its own spec end to # end, and the text is fully determined by the fixture -- defaults, typestr and # the enum's possible-values list all appear. # --------------------------------------------------------------------------- check_out gengetopt/show-version "brintos-demo 1.2.3" gengetopt -i "$ggo" --show-version check_out gengetopt/show-help-usage "Usage: brintos-demo [OPTION]..." \ gengetopt -i "$ggo" --show-help check_out gengetopt/show-help-typestr "-n, --name=TEXT" gengetopt -i "$ggo" --show-help check_out gengetopt/show-help-default "(default=\`7')" gengetopt -i "$ggo" --show-help check_out gengetopt/show-help-flagoff "(default=off)" gengetopt -i "$ggo" --show-help check_out gengetopt/show-help-enum "possible values=\"fast\", \"slow\"" \ gengetopt -i "$ggo" --show-help # --show-help writes nothing to disk: it is a rendering mode, not a generate mode. # Run it in a directory of its own so "no cmdline.c here" means it wrote none, # rather than meaning we looked somewhere it was never going to write. mkdir -p "$work/sh" ( cd "$work/sh" && gengetopt -i "$ggo" --show-help ) >/dev/null 2>&1 check_eq gengetopt/rc-show-help 0 "$?" check_rc gengetopt/show-help-nofiles 1 test -f "$work/sh/cmdline.c" # --------------------------------------------------------------------------- # Output-shaping flags. Each is asserted differentially -- the symbol it adds is # absent from the baseline generate above, so the case proves the FLAG did it and # not that the symbol is simply always present. # --------------------------------------------------------------------------- # --func-name renames the API, --file-name renames the files, --output-dir moves # them; the baseline's cmdline.* must not appear in the target directory. ( cd "$work" && gengetopt -i "$ggo" --func-name=myparse --file-name=myopts \ --output-dir="$work/dir" ) >"$log" 2>&1 check_eq gengetopt/rc-rename 0 "$?" check gengetopt/rename-c test -f "$work/dir/myopts.c" check gengetopt/rename-h test -f "$work/dir/myopts.h" check_rc gengetopt/outdir-clean 1 test -f "$work/dir/cmdline.c" check_out gengetopt/funcname-applied "int myparse (int argc, char **argv," cat "$work/dir/myopts.h" check_rc gengetopt/funcname-replaced 1 grep -q "int cmdline_parser (" "$work/dir/myopts.h" # The renamed header's include guard follows the file name, not the function. check_out gengetopt/filename-guard "MYOPTS_H" cat "$work/dir/myopts.h" # -u/--unamed-opts collects non-option arguments into inputs[]/inputs_num. mkdir -p "$work/u" ( cd "$work/u" && gengetopt -i "$ggo" -u ) >"$log" 2>&1 check_eq gengetopt/rc-unamed 0 "$?" check_out gengetopt/unamed-inputs "char **inputs" cat "$work/u/cmdline.h" check_out gengetopt/unamed-count "unsigned inputs_num" cat "$work/u/cmdline.h" check_rc gengetopt/unamed-absent-baseline 1 grep -q "char \*\*inputs" "$work/gen/cmdline.h" # -C/--conf-parser emits the config-file reader pair. mkdir -p "$work/c" ( cd "$work/c" && gengetopt -i "$ggo" -C ) >"$log" 2>&1 check_eq gengetopt/rc-confparser 0 "$?" check_out gengetopt/conf-configfile "cmdline_parser_configfile" cat "$work/c/cmdline.h" check_out gengetopt/conf-config-file "cmdline_parser_config_file" cat "$work/c/cmdline.h" check_rc gengetopt/conf-absent-baseline 1 grep -q "cmdline_parser_configfile" "$work/gen/cmdline.h" # -S/--string-parser emits the parse-from-a-string family. mkdir -p "$work/s" ( cd "$work/s" && gengetopt -i "$ggo" -S ) >"$log" 2>&1 check_eq gengetopt/rc-stringparser 0 "$?" check_out gengetopt/string-parser "int cmdline_parser_string (const char *cmdline," cat "$work/s/cmdline.h" check_out gengetopt/string-parser-ext "cmdline_parser_string_ext" cat "$work/s/cmdline.h" check_rc gengetopt/string-absent-baseline 1 grep -q "cmdline_parser_string" "$work/gen/cmdline.h" # --------------------------------------------------------------------------- # Determinism: the same spec through the same command line twice, byte for byte. # This is what licenses the reference comparison above -- if the generator # carried a timestamp or a hash-order dependence into its output, no expected # file could exist. It is also the case most likely to catch a wasm32-specific # fault: an unordered container iterated in allocation order would differ here # while every content grep above still passed. # --------------------------------------------------------------------------- ( cd "$work/idem1" && gengetopt -i "$ggo" ) >/dev/null 2>&1 ( cd "$work/idem2" && gengetopt -i "$ggo" ) >/dev/null 2>&1 check_rc gengetopt/idempotent-c 0 cmp -s "$work/idem1/cmdline.c" "$work/idem2/cmdline.c" check_rc gengetopt/idempotent-h 0 cmp -s "$work/idem1/cmdline.h" "$work/idem2/cmdline.h" # --------------------------------------------------------------------------- # Error paths. Each reports its own diagnostic and exits 1 -- and on a spec error # no output file is written, which a caller reading only the rc would not learn. # --------------------------------------------------------------------------- mkdir -p "$work/err" ( cd "$work/err" && gengetopt -i nosuch.ggo ) >"$log" 2>&1 check_eq gengetopt/rc-noinput 1 "$?" check_out gengetopt/msg-noinput "Error opening input file: nosuch.ggo" cat "$log" check_rc gengetopt/noinput-nofiles 1 test -f "$work/err/cmdline.c" # A syntax error in the spec: the diagnostic carries file, line and the offending # token, and the parser (the C++ yacc grammar) is what produces it -- so this # case is also the on-target proof that the generated-at-build-time C++ parser # and lexer work, which nothing else here reaches. printf 'package "x"\noption bogus\n' > "$work/err/bad.ggo" ( cd "$work/err" && gengetopt -i bad.ggo ) >"$log" 2>&1 check_eq gengetopt/rc-syntax 1 "$?" check_out gengetopt/msg-syntax "bad.ggo:2: syntax error" cat "$log" check_rc gengetopt/syntax-nofiles 1 test -f "$work/err/cmdline.c" # An unknown switch to gengetopt itself, rejected by gengetopt's own generated # parser -- the tool's second self-application, and the one that exercises the # error path of the parser it generates for everyone else. ( cd "$work/err" && gengetopt -Z ) >"$log" 2>&1 check_eq gengetopt/rc-badopt 1 "$?" check_out gengetopt/msg-badopt "invalid option" cat "$log" check_out gengetopt/msg-badopt-hint "Run gengetopt --help to see the list of options." cat "$log" rm -rf "$work"