]>
Commit | Line | Data |
---|---|---|
61d63097 PB |
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 | ||
79fccf7e PB |
28 | # Options with nonstandard names (e.g. --with/--without) or OS-dependent |
29 | # defaults. Try not to add any. | |
3b4da132 | 30 | SKIP_OPTIONS = { |
3b4da132 | 31 | "default_devices", |
3b4da132 | 32 | "fuzzing_engine", |
3b4da132 PB |
33 | } |
34 | ||
79fccf7e PB |
35 | # Options whose name doesn't match the option for backwards compatibility |
36 | # reasons, because Meson gives them a funny name, or both | |
119fc611 | 37 | OPTION_NAMES = { |
c54b59ee PB |
38 | "b_coverage": "gcov", |
39 | "b_lto": "lto", | |
6739825a | 40 | "coroutine_backend": "with-coroutine", |
c0e705c6 | 41 | "debug": "debug-info", |
119fc611 | 42 | "malloc": "enable-malloc", |
b0b4323e | 43 | "pkgversion": "with-pkgversion", |
c09c1ce7 | 44 | "qemu_firmwarepath": "firmwarepath", |
c36dd41b | 45 | "qemu_suffix": "with-suffix", |
119fc611 | 46 | "trace_backends": "enable-trace-backends", |
4fda6011 | 47 | "trace_file": "with-trace-file", |
119fc611 PB |
48 | } |
49 | ||
39fb3cfc PB |
50 | # Options that configure autodetects, even though meson defines them as boolean |
51 | AUTO_OPTIONS = { | |
52 | "plugins", | |
090a188c | 53 | "werror", |
39fb3cfc PB |
54 | } |
55 | ||
79fccf7e PB |
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. | |
a70248db | 60 | BUILTIN_OPTIONS = { |
c54b59ee PB |
61 | "b_coverage", |
62 | "b_lto", | |
c36dd41b | 63 | "bindir", |
c09c1ce7 | 64 | "datadir", |
c0e705c6 | 65 | "debug", |
c09c1ce7 PB |
66 | "includedir", |
67 | "libdir", | |
68 | "libexecdir", | |
69 | "localedir", | |
70 | "localstatedir", | |
71 | "mandir", | |
c36dd41b | 72 | "prefix", |
a70248db | 73 | "strip", |
c09c1ce7 | 74 | "sysconfdir", |
090a188c | 75 | "werror", |
a70248db PB |
76 | } |
77 | ||
3b4da132 PB |
78 | LINE_WIDTH = 76 |
79 | ||
80 | ||
81 | # Convert the default value of an option to the string used in | |
82 | # the help message | |
808d15b3 PB |
83 | def get_help(opt): |
84 | if opt["name"] == "libdir": | |
85 | return 'system default' | |
86 | value = opt["value"] | |
3b4da132 PB |
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 | ||
94 | def 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 | ||
61d63097 PB |
106 | def sh_print(line=""): |
107 | print(' printf "%s\\n"', shlex.quote(line)) | |
108 | ||
109 | ||
3b4da132 PB |
110 | def help_line(left, opt, indent, long): |
111 | right = f'{opt["description"]}' | |
112 | if long: | |
808d15b3 | 113 | value = get_help(opt) |
119fc611 | 114 | if value != "auto" and value != "": |
3b4da132 PB |
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". | |
127 | def 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 | ||
119fc611 PB |
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" | |
139 | def 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 | ||
a70248db PB |
147 | def 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 | ||
61d63097 | 156 | def load_options(json): |
a70248db | 157 | json = [x for x in json if filter_options(x)] |
61d63097 PB |
158 | return sorted(json, key=lambda x: x["name"]) |
159 | ||
160 | ||
119fc611 PB |
161 | def cli_option(opt): |
162 | name = opt["name"] | |
163 | if name in OPTION_NAMES: | |
164 | return OPTION_NAMES[name] | |
165 | return name.replace("_", "-") | |
166 | ||
167 | ||
168 | def 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 | ||
177 | def cli_metavar(opt): | |
178 | if opt["type"] == "string": | |
179 | return "VALUE" | |
180 | if opt["type"] == "array": | |
8154f5e6 | 181 | return "CHOICES" if "choices" in opt else "VALUES" |
119fc611 PB |
182 | return "CHOICE" |
183 | ||
184 | ||
61d63097 PB |
185 | def print_help(options): |
186 | print("meson_options_help() {") | |
39fb3cfc | 187 | feature_opts = [] |
119fc611 PB |
188 | for opt in sorted(options, key=cli_help_key): |
189 | key = cli_help_key(opt) | |
3b4da132 PB |
190 | # The first section includes options that have an arguments, |
191 | # and booleans (i.e., only one of enable/disable makes sense) | |
119fc611 PB |
192 | if require_arg(opt): |
193 | metavar = cli_metavar(opt) | |
194 | left = f"--{key}={metavar}" | |
195 | help_line(left, opt, 27, True) | |
39fb3cfc | 196 | elif opt["type"] == "boolean" and opt["name"] not in AUTO_OPTIONS: |
119fc611 | 197 | left = f"--{key}" |
3b4da132 PB |
198 | help_line(left, opt, 27, False) |
199 | elif allow_arg(opt): | |
200 | if opt["type"] == "combo" and "enabled" in opt["choices"]: | |
119fc611 | 201 | left = f"--{key}[=CHOICE]" |
3b4da132 | 202 | else: |
119fc611 | 203 | left = f"--{key}=CHOICE" |
3b4da132 | 204 | help_line(left, opt, 27, True) |
39fb3cfc PB |
205 | else: |
206 | feature_opts.append(opt) | |
3b4da132 | 207 | |
61d63097 PB |
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() | |
39fb3cfc PB |
213 | for opt in sorted(feature_opts, key=cli_option): |
214 | key = cli_option(opt) | |
215 | help_line(key, opt, 18, False) | |
61d63097 PB |
216 | print("}") |
217 | ||
218 | ||
219 | def print_parse(options): | |
220 | print("_meson_option_parse() {") | |
221 | print(" case $1 in") | |
3b4da132 | 222 | for opt in options: |
119fc611 | 223 | key = cli_option(opt) |
3b4da132 | 224 | name = opt["name"] |
119fc611 | 225 | if require_arg(opt): |
8154f5e6 AO |
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" ;;') | |
119fc611 | 230 | elif opt["type"] == "boolean": |
3b4da132 PB |
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" ;;') | |
61d63097 PB |
240 | print(" *) return 1 ;;") |
241 | print(" esac") | |
242 | print("}") | |
243 | ||
244 | ||
245 | options = load_options(json.load(sys.stdin)) | |
246 | print("# This file is generated by meson-buildoptions.py, do not edit!") | |
247 | print_help(options) | |
248 | print_parse(options) |