| #! /usr/bin/env python3 | |
| # Generate configure command line options handling code, based on Meson's | |
| # user build options introspection data | |
| # | |
| # Copyright (C) 2021 Red Hat, Inc. | |
| # | |
| # Author: Paolo Bonzini <pbonzini@redhat.com> | |
| # | |
| # This program is free software; you can redistribute it and/or modify | |
| # it under the terms of the GNU General Public License as published by | |
| # the Free Software Foundation; either version 2, or (at your option) | |
| # any later version. | |
| # | |
| # This program is distributed in the hope that it will be useful, | |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| # GNU General Public License for more details. | |
| # | |
| # You should have received a copy of the GNU General Public License | |
| # along with this program. If not, see <https://www.gnu.org/licenses/>. | |
| import json | |
| import textwrap | |
| import shlex | |
| import sys | |
| # Options with nonstandard names (e.g. --with/--without) or OS-dependent | |
| # defaults. Try not to add any. | |
| SKIP_OPTIONS = { | |
| "default_devices", | |
| "fuzzing_engine", | |
| } | |
| # Options whose name doesn't match the option for backwards compatibility | |
| # reasons, because Meson gives them a funny name, or both | |
| OPTION_NAMES = { | |
| "b_coverage": "gcov", | |
| "b_lto": "lto", | |
| "coroutine_backend": "with-coroutine", | |
| "debug": "debug-info", | |
| "malloc": "enable-malloc", | |
| "pkgversion": "with-pkgversion", | |
| "qemu_firmwarepath": "firmwarepath", | |
| "qemu_suffix": "with-suffix", | |
| "trace_backends": "enable-trace-backends", | |
| "trace_file": "with-trace-file", | |
| } | |
| # Options that configure autodetects, even though meson defines them as boolean | |
| AUTO_OPTIONS = { | |
| "plugins", | |
| "werror", | |
| } | |
| # Options that configure prints help for, so we can skip | |
| CONFIGURE_HELP = { | |
| "gdb", | |
| "tcg_tests_cross", | |
| } | |
| # Builtin options that should be definable via configure. Some of the others | |
| # we really do not want (e.g. c_args is defined via the native file, not | |
| # via -D, because it's a mix of CFLAGS and --extra-cflags); for specific | |
| # cases "../configure -D" can be used as an escape hatch. | |
| BUILTIN_OPTIONS = { | |
| "b_coverage", | |
| "b_lto", | |
| "bindir", | |
| "datadir", | |
| "debug", | |
| "includedir", | |
| "libdir", | |
| "libexecdir", | |
| "localedir", | |
| "localstatedir", | |
| "mandir", | |
| "prefix", | |
| "strip", | |
| "sysconfdir", | |
| "werror", | |
| } | |
| LINE_WIDTH = 76 | |
| # Convert the default value of an option to the string used in | |
| # the help message | |
| def get_help(opt): | |
| if opt["name"] == "libdir": | |
| return 'system default' | |
| value = opt["value"] | |
| if isinstance(value, list): | |
| return ",".join(value) | |
| if isinstance(value, bool): | |
| return "enabled" if value else "disabled" | |
| return str(value) | |
| def wrap(left, text, indent): | |
| spaces = " " * indent | |
| if len(left) >= indent: | |
| yield left | |
| left = spaces | |
| else: | |
| left = (left + spaces)[0:indent] | |
| yield from textwrap.wrap( | |
| text, width=LINE_WIDTH, initial_indent=left, subsequent_indent=spaces | |
| ) | |
| def sh_print(line=""): | |
| print(' printf "%s\\n"', shlex.quote(line)) | |
| def help_line(left, opt, indent, long): | |
| right = f'{opt["description"]}' | |
| if long: | |
| value = get_help(opt) | |
| if value not in {"", "auto"}: | |
| right += f" [{value}]" | |
| if "choices" in opt and long: | |
| choices = "/".join(sorted(opt["choices"])) | |
| right += f" (choices: {choices})" | |
| for line in wrap(" " + left, right, indent): | |
| sh_print(line) | |
| # Return whether the option (a dictionary) can be used with | |
| # arguments. Booleans can never be used with arguments; | |
| # combos allow an argument only if they accept other values | |
| # than "auto", "enabled", and "disabled". | |
| def allow_arg(opt): | |
| if opt["type"] == "boolean": | |
| return False | |
| if opt["type"] != "combo": | |
| return True | |
| return not (set(opt["choices"]) <= {"auto", "disabled", "enabled"}) | |
| # Return whether the option (a dictionary) can be used without | |
| # arguments. Booleans can only be used without arguments; | |
| # combos require an argument if they accept neither "enabled" | |
| # nor "disabled" | |
| def require_arg(opt): | |
| if opt["type"] == "boolean": | |
| return False | |
| if opt["type"] != "combo": | |
| return True | |
| return not ({"enabled", "disabled"}.intersection(opt["choices"])) | |
| def filter_options(opt): | |
| if ":" in opt["name"]: | |
| return False | |
| if opt["section"] == "user": | |
| return opt["name"] not in SKIP_OPTIONS | |
| else: | |
| return opt["name"] in BUILTIN_OPTIONS | |
| def load_options(opts): | |
| opts = [opt for opt in opts if filter_options(opt)] | |
| return sorted(opts, key=lambda opt: opt["name"]) | |
| def cli_option(opt): | |
| name = opt["name"] | |
| if name in OPTION_NAMES: | |
| return OPTION_NAMES[name] | |
| return name.replace("_", "-") | |
| def cli_help_key(opt): | |
| key = cli_option(opt) | |
| if require_arg(opt): | |
| return key | |
| if opt["type"] == "boolean" and opt["value"]: | |
| return f"disable-{key}" | |
| return f"enable-{key}" | |
| def cli_metavar(opt): | |
| if opt["type"] == "string": | |
| return "VALUE" | |
| if opt["type"] == "array": | |
| return "CHOICES" if "choices" in opt else "VALUES" | |
| return "CHOICE" | |
| def print_help(options): | |
| print("meson_options_help() {") | |
| feature_opts = [] | |
| for opt in sorted(options, key=cli_help_key): | |
| key = cli_help_key(opt) | |
| # The first section includes options that have an arguments, | |
| # and booleans (i.e., only one of enable/disable makes sense) | |
| if any(opt["name"].startswith(h) for h in CONFIGURE_HELP): | |
| pass | |
| elif require_arg(opt): | |
| metavar = cli_metavar(opt) | |
| left = f"--{key}={metavar}" | |
| help_line(left, opt, 27, True) | |
| elif opt["type"] == "boolean" and opt["name"] not in AUTO_OPTIONS: | |
| left = f"--{key}" | |
| help_line(left, opt, 27, False) | |
| elif allow_arg(opt): | |
| if opt["type"] == "combo" and "enabled" in opt["choices"]: | |
| left = f"--{key}[=CHOICE]" | |
| else: | |
| left = f"--{key}=CHOICE" | |
| help_line(left, opt, 27, True) | |
| else: | |
| feature_opts.append(opt) | |
| sh_print() | |
| sh_print("Optional features, enabled with --enable-FEATURE and") | |
| sh_print("disabled with --disable-FEATURE, default is enabled if available") | |
| sh_print("(unless built with --without-default-features):") | |
| sh_print() | |
| for opt in sorted(feature_opts, key=cli_option): | |
| key = cli_option(opt) | |
| help_line(key, opt, 18, False) | |
| print("}") | |
| def print_parse(options): | |
| print("_meson_option_parse() {") | |
| print(" case $1 in") | |
| for opt in options: | |
| key = cli_option(opt) | |
| name = opt["name"] | |
| if require_arg(opt): | |
| if opt["type"] == "array" and "choices" not in opt: | |
| print(f' --{key}=*) quote_sh "-D{name}=$(meson_option_build_array $2)" ;;') | |
| else: | |
| print(f' --{key}=*) quote_sh "-D{name}=$2" ;;') | |
| elif opt["type"] == "boolean": | |
| print(f' --enable-{key}) printf "%s" -D{name}=true ;;') | |
| print(f' --disable-{key}) printf "%s" -D{name}=false ;;') | |
| else: | |
| if opt["type"] == "combo" and "enabled" in opt["choices"]: | |
| print(f' --enable-{key}) printf "%s" -D{name}=enabled ;;') | |
| if opt["type"] == "combo" and "disabled" in opt["choices"]: | |
| print(f' --disable-{key}) printf "%s" -D{name}=disabled ;;') | |
| if allow_arg(opt): | |
| print(f' --enable-{key}=*) quote_sh "-D{name}=$2" ;;') | |
| print(" *) return 1 ;;") | |
| print(" esac") | |
| print("}") | |
| def main(): | |
| json_data = sys.stdin.read() | |
| try: | |
| options = load_options(json.loads(json_data)) | |
| except: | |
| print("Failure in scripts/meson-buildoptions.py parsing stdin as json", | |
| file=sys.stderr) | |
| print(json_data, file=sys.stderr) | |
| sys.exit(1) | |
| print("# This file is generated by meson-buildoptions.py, do not edit!") | |
| print_help(options) | |
| print_parse(options) | |
| sys.exit(main()) |