master
py 266 lines 7.87 KB
Raw
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
23 import json
24 import textwrap
25 import shlex
26 import sys
27
28 # Options with nonstandard names (e.g. --with/--without) or OS-dependent
29 # defaults. Try not to add any.
30 SKIP_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
37 OPTION_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
51 AUTO_OPTIONS = {
52 "plugins",
53 "werror",
54 }
55
56 # Options that configure prints help for, so we can skip
57 CONFIGURE_HELP = {
58 "gdb",
59 }
60
61 # Builtin options that should be definable via configure. Some of the others
62 # we really do not want (e.g. c_args is defined via the native file, not
63 # via -D, because it's a mix of CFLAGS and --extra-cflags); for specific
64 # cases "../configure -D" can be used as an escape hatch.
65 BUILTIN_OPTIONS = {
66 "b_coverage",
67 "b_lto",
68 "bindir",
69 "datadir",
70 "debug",
71 "includedir",
72 "libdir",
73 "libexecdir",
74 "localedir",
75 "localstatedir",
76 "mandir",
77 "prefix",
78 "strip",
79 "sysconfdir",
80 "werror",
81 }
82
83 LINE_WIDTH = 76
84
85
86 # Convert the default value of an option to the string used in
87 # the help message
88 def get_help(opt):
89 if opt["name"] == "libdir":
90 return 'system default'
91 value = opt["value"]
92 if isinstance(value, list):
93 return ",".join(value)
94 if isinstance(value, bool):
95 return "enabled" if value else "disabled"
96 return str(value)
97
98
99 def wrap(left, text, indent):
100 spaces = " " * indent
101 if len(left) >= indent:
102 yield left
103 left = spaces
104 else:
105 left = (left + spaces)[0:indent]
106 yield from textwrap.wrap(
107 text, width=LINE_WIDTH, initial_indent=left, subsequent_indent=spaces
108 )
109
110
111 def sh_print(line=""):
112 print(' printf "%s\\n"', shlex.quote(line))
113
114
115 def help_line(left, opt, indent, long):
116 right = f'{opt["description"]}'
117 if long:
118 value = get_help(opt)
119 if value not in {"", "auto"}:
120 right += f" [{value}]"
121 if "choices" in opt and long:
122 choices = "/".join(sorted(opt["choices"]))
123 right += f" (choices: {choices})"
124 for line in wrap(" " + left, right, indent):
125 sh_print(line)
126
127
128 # Return whether the option (a dictionary) can be used with
129 # arguments. Booleans can never be used with arguments;
130 # combos allow an argument only if they accept other values
131 # than "auto", "enabled", and "disabled".
132 def allow_arg(opt):
133 if opt["type"] == "boolean":
134 return False
135 if opt["type"] != "combo":
136 return True
137 return not (set(opt["choices"]) <= {"auto", "disabled", "enabled"})
138
139
140 # Return whether the option (a dictionary) can be used without
141 # arguments. Booleans can only be used without arguments;
142 # combos require an argument if they accept neither "enabled"
143 # nor "disabled"
144 def require_arg(opt):
145 if opt["type"] == "boolean":
146 return False
147 if opt["type"] != "combo":
148 return True
149 return not ({"enabled", "disabled"}.intersection(opt["choices"]))
150
151
152 def filter_options(opt):
153 if ":" in opt["name"]:
154 return False
155 if opt["section"] == "user":
156 return opt["name"] not in SKIP_OPTIONS
157 else:
158 return opt["name"] in BUILTIN_OPTIONS
159
160
161 def load_options(opts):
162 opts = [opt for opt in opts if filter_options(opt)]
163 return sorted(opts, key=lambda opt: opt["name"])
164
165
166 def cli_option(opt):
167 name = opt["name"]
168 if name in OPTION_NAMES:
169 return OPTION_NAMES[name]
170 return name.replace("_", "-")
171
172
173 def cli_help_key(opt):
174 key = cli_option(opt)
175 if require_arg(opt):
176 return key
177 if opt["type"] == "boolean" and opt["value"]:
178 return f"disable-{key}"
179 return f"enable-{key}"
180
181
182 def cli_metavar(opt):
183 if opt["type"] == "string":
184 return "VALUE"
185 if opt["type"] == "array":
186 return "CHOICES" if "choices" in opt else "VALUES"
187 return "CHOICE"
188
189
190 def print_help(options):
191 print("meson_options_help() {")
192 feature_opts = []
193 for opt in sorted(options, key=cli_help_key):
194 key = cli_help_key(opt)
195 # The first section includes options that have an arguments,
196 # and booleans (i.e., only one of enable/disable makes sense)
197 if opt["name"] in CONFIGURE_HELP:
198 pass
199 elif require_arg(opt):
200 metavar = cli_metavar(opt)
201 left = f"--{key}={metavar}"
202 help_line(left, opt, 27, True)
203 elif opt["type"] == "boolean" and opt["name"] not in AUTO_OPTIONS:
204 left = f"--{key}"
205 help_line(left, opt, 27, False)
206 elif allow_arg(opt):
207 if opt["type"] == "combo" and "enabled" in opt["choices"]:
208 left = f"--{key}[=CHOICE]"
209 else:
210 left = f"--{key}=CHOICE"
211 help_line(left, opt, 27, True)
212 else:
213 feature_opts.append(opt)
214
215 sh_print()
216 sh_print("Optional features, enabled with --enable-FEATURE and")
217 sh_print("disabled with --disable-FEATURE, default is enabled if available")
218 sh_print("(unless built with --without-default-features):")
219 sh_print()
220 for opt in sorted(feature_opts, key=cli_option):
221 key = cli_option(opt)
222 help_line(key, opt, 18, False)
223 print("}")
224
225
226 def print_parse(options):
227 print("_meson_option_parse() {")
228 print(" case $1 in")
229 for opt in options:
230 key = cli_option(opt)
231 name = opt["name"]
232 if require_arg(opt):
233 if opt["type"] == "array" and "choices" not in opt:
234 print(f' --{key}=*) quote_sh "-D{name}=$(meson_option_build_array $2)" ;;')
235 else:
236 print(f' --{key}=*) quote_sh "-D{name}=$2" ;;')
237 elif opt["type"] == "boolean":
238 print(f' --enable-{key}) printf "%s" -D{name}=true ;;')
239 print(f' --disable-{key}) printf "%s" -D{name}=false ;;')
240 else:
241 if opt["type"] == "combo" and "enabled" in opt["choices"]:
242 print(f' --enable-{key}) printf "%s" -D{name}=enabled ;;')
243 if opt["type"] == "combo" and "disabled" in opt["choices"]:
244 print(f' --disable-{key}) printf "%s" -D{name}=disabled ;;')
245 if allow_arg(opt):
246 print(f' --enable-{key}=*) quote_sh "-D{name}=$2" ;;')
247 print(" *) return 1 ;;")
248 print(" esac")
249 print("}")
250
251
252 def main():
253 json_data = sys.stdin.read()
254 try:
255 options = load_options(json.loads(json_data))
256 except:
257 print("Failure in scripts/meson-buildoptions.py parsing stdin as json",
258 file=sys.stderr)
259 print(json_data, file=sys.stderr)
260 sys.exit(1)
261 print("# This file is generated by meson-buildoptions.py, do not edit!")
262 print_help(options)
263 print_parse(options)
264
265
266 sys.exit(main())