1#! /usr/bin/env python3 2 3# Generate configure command line options handling code, based on Meson's 4# user build options introspection data 5# 6# Copyright (C) 2021 Red Hat, Inc. 7# 8# Author: Paolo Bonzini <pbonzini@redhat.com> 9# 10# This program is free software; you can redistribute it and/or modify 11# it under the terms of the GNU General Public License as published by 12# the Free Software Foundation; either version 2, or (at your option) 13# any later version. 14# 15# This program is distributed in the hope that it will be useful, 16# but WITHOUT ANY WARRANTY; without even the implied warranty of 17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18# GNU General Public License for more details. 19# 20# You should have received a copy of the GNU General Public License 21# along with this program. If not, see <https://www.gnu.org/licenses/>. 22 23import json 24import textwrap 25import shlex 26import sys 27 28# Options with nonstandard names (e.g. --with/--without) or OS-dependent 29# defaults. Try not to add any. 30SKIP_OPTIONS = { 31 "default_devices", 32 "fuzzing_engine", 33} 34 35# Options whose name doesn't match the option for backwards compatibility 36# reasons, because Meson gives them a funny name, or both 37OPTION_NAMES = { 38 "b_coverage": "gcov", 39 "b_lto": "lto", 40 "coroutine_backend": "with-coroutine", 41 "debug": "debug-info", 42 "malloc": "enable-malloc", 43 "pkgversion": "with-pkgversion", 44 "qemu_firmwarepath": "firmwarepath", 45 "qemu_suffix": "with-suffix", 46 "trace_backends": "enable-trace-backends", 47 "trace_file": "with-trace-file", 48} 49 50# Options that configure autodetects, even though meson defines them as boolean 51AUTO_OPTIONS = { 52 "plugins", 53 "werror", 54} 55 56# Builtin options that should be definable via configure. Some of the others 57# we really do not want (e.g. c_args is defined via the native file, not 58# via -D, because it's a mix of CFLAGS and --extra-cflags); for specific 59# cases "../configure -D" can be used as an escape hatch. 60BUILTIN_OPTIONS = { 61 "b_coverage", 62 "b_lto", 63 "bindir", 64 "datadir", 65 "debug", 66 "includedir", 67 "libdir", 68 "libexecdir", 69 "localedir", 70 "localstatedir", 71 "mandir", 72 "prefix", 73 "strip", 74 "sysconfdir", 75 "werror", 76} 77 78LINE_WIDTH = 76 79 80 81# Convert the default value of an option to the string used in 82# the help message 83def get_help(opt): 84 if opt["name"] == "libdir": 85 return 'system default' 86 value = opt["value"] 87 if isinstance(value, list): 88 return ",".join(value) 89 if isinstance(value, bool): 90 return "enabled" if value else "disabled" 91 return str(value) 92 93 94def wrap(left, text, indent): 95 spaces = " " * indent 96 if len(left) >= indent: 97 yield left 98 left = spaces 99 else: 100 left = (left + spaces)[0:indent] 101 yield from textwrap.wrap( 102 text, width=LINE_WIDTH, initial_indent=left, subsequent_indent=spaces 103 ) 104 105 106def sh_print(line=""): 107 print(' printf "%s\\n"', shlex.quote(line)) 108 109 110def help_line(left, opt, indent, long): 111 right = f'{opt["description"]}' 112 if long: 113 value = get_help(opt) 114 if value != "auto" and value != "": 115 right += f" [{value}]" 116 if "choices" in opt and long: 117 choices = "/".join(sorted(opt["choices"])) 118 right += f" (choices: {choices})" 119 for x in wrap(" " + left, right, indent): 120 sh_print(x) 121 122 123# Return whether the option (a dictionary) can be used with 124# arguments. Booleans can never be used with arguments; 125# combos allow an argument only if they accept other values 126# than "auto", "enabled", and "disabled". 127def allow_arg(opt): 128 if opt["type"] == "boolean": 129 return False 130 if opt["type"] != "combo": 131 return True 132 return not (set(opt["choices"]) <= {"auto", "disabled", "enabled"}) 133 134 135# Return whether the option (a dictionary) can be used without 136# arguments. Booleans can only be used without arguments; 137# combos require an argument if they accept neither "enabled" 138# nor "disabled" 139def require_arg(opt): 140 if opt["type"] == "boolean": 141 return False 142 if opt["type"] != "combo": 143 return True 144 return not ({"enabled", "disabled"}.intersection(opt["choices"])) 145 146 147def filter_options(json): 148 if ":" in json["name"]: 149 return False 150 if json["section"] == "user": 151 return json["name"] not in SKIP_OPTIONS 152 else: 153 return json["name"] in BUILTIN_OPTIONS 154 155 156def load_options(json): 157 json = [x for x in json if filter_options(x)] 158 return sorted(json, key=lambda x: x["name"]) 159 160 161def cli_option(opt): 162 name = opt["name"] 163 if name in OPTION_NAMES: 164 return OPTION_NAMES[name] 165 return name.replace("_", "-") 166 167 168def cli_help_key(opt): 169 key = cli_option(opt) 170 if require_arg(opt): 171 return key 172 if opt["type"] == "boolean" and opt["value"]: 173 return f"disable-{key}" 174 return f"enable-{key}" 175 176 177def cli_metavar(opt): 178 if opt["type"] == "string": 179 return "VALUE" 180 if opt["type"] == "array": 181 return "CHOICES" if "choices" in opt else "VALUES" 182 return "CHOICE" 183 184 185def print_help(options): 186 print("meson_options_help() {") 187 feature_opts = [] 188 for opt in sorted(options, key=cli_help_key): 189 key = cli_help_key(opt) 190 # The first section includes options that have an arguments, 191 # and booleans (i.e., only one of enable/disable makes sense) 192 if require_arg(opt): 193 metavar = cli_metavar(opt) 194 left = f"--{key}={metavar}" 195 help_line(left, opt, 27, True) 196 elif opt["type"] == "boolean" and opt["name"] not in AUTO_OPTIONS: 197 left = f"--{key}" 198 help_line(left, opt, 27, False) 199 elif allow_arg(opt): 200 if opt["type"] == "combo" and "enabled" in opt["choices"]: 201 left = f"--{key}[=CHOICE]" 202 else: 203 left = f"--{key}=CHOICE" 204 help_line(left, opt, 27, True) 205 else: 206 feature_opts.append(opt) 207 208 sh_print() 209 sh_print("Optional features, enabled with --enable-FEATURE and") 210 sh_print("disabled with --disable-FEATURE, default is enabled if available") 211 sh_print("(unless built with --without-default-features):") 212 sh_print() 213 for opt in sorted(feature_opts, key=cli_option): 214 key = cli_option(opt) 215 help_line(key, opt, 18, False) 216 print("}") 217 218 219def print_parse(options): 220 print("_meson_option_parse() {") 221 print(" case $1 in") 222 for opt in options: 223 key = cli_option(opt) 224 name = opt["name"] 225 if require_arg(opt): 226 if opt["type"] == "array" and not "choices" in opt: 227 print(f' --{key}=*) quote_sh "-D{name}=$(meson_option_build_array $2)" ;;') 228 else: 229 print(f' --{key}=*) quote_sh "-D{name}=$2" ;;') 230 elif opt["type"] == "boolean": 231 print(f' --enable-{key}) printf "%s" -D{name}=true ;;') 232 print(f' --disable-{key}) printf "%s" -D{name}=false ;;') 233 else: 234 if opt["type"] == "combo" and "enabled" in opt["choices"]: 235 print(f' --enable-{key}) printf "%s" -D{name}=enabled ;;') 236 if opt["type"] == "combo" and "disabled" in opt["choices"]: 237 print(f' --disable-{key}) printf "%s" -D{name}=disabled ;;') 238 if allow_arg(opt): 239 print(f' --enable-{key}=*) quote_sh "-D{name}=$2" ;;') 240 print(" *) return 1 ;;") 241 print(" esac") 242 print("}") 243 244 245options = load_options(json.load(sys.stdin)) 246print("# This file is generated by meson-buildoptions.py, do not edit!") 247print_help(options) 248print_parse(options) 249