xref: /qemu/scripts/meson-buildoptions.py (revision e20d68aa)
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
28SKIP_OPTIONS = {
29    "default_devices",
30    "fuzzing_engine",
31}
32
33OPTION_NAMES = {
34    "b_coverage": "gcov",
35    "b_lto": "lto",
36    "coroutine_backend": "with-coroutine",
37    "debug": "debug-info",
38    "malloc": "enable-malloc",
39    "pkgversion": "with-pkgversion",
40    "qemu_firmwarepath": "firmwarepath",
41    "qemu_suffix": "with-suffix",
42    "trace_backends": "enable-trace-backends",
43    "trace_file": "with-trace-file",
44}
45
46# Options that configure autodetects, even though meson defines them as boolean
47AUTO_OPTIONS = {
48    "plugins",
49    "werror",
50}
51
52BUILTIN_OPTIONS = {
53    "b_coverage",
54    "b_lto",
55    "bindir",
56    "datadir",
57    "debug",
58    "includedir",
59    "libdir",
60    "libexecdir",
61    "localedir",
62    "localstatedir",
63    "mandir",
64    "prefix",
65    "strip",
66    "sysconfdir",
67    "werror",
68}
69
70LINE_WIDTH = 76
71
72
73# Convert the default value of an option to the string used in
74# the help message
75def get_help(opt):
76    if opt["name"] == "libdir":
77        return 'system default'
78    value = opt["value"]
79    if isinstance(value, list):
80        return ",".join(value)
81    if isinstance(value, bool):
82        return "enabled" if value else "disabled"
83    return str(value)
84
85
86def wrap(left, text, indent):
87    spaces = " " * indent
88    if len(left) >= indent:
89        yield left
90        left = spaces
91    else:
92        left = (left + spaces)[0:indent]
93    yield from textwrap.wrap(
94        text, width=LINE_WIDTH, initial_indent=left, subsequent_indent=spaces
95    )
96
97
98def sh_print(line=""):
99    print('  printf "%s\\n"', shlex.quote(line))
100
101
102def help_line(left, opt, indent, long):
103    right = f'{opt["description"]}'
104    if long:
105        value = get_help(opt)
106        if value != "auto" and value != "":
107            right += f" [{value}]"
108    if "choices" in opt and long:
109        choices = "/".join(sorted(opt["choices"]))
110        right += f" (choices: {choices})"
111    for x in wrap("  " + left, right, indent):
112        sh_print(x)
113
114
115# Return whether the option (a dictionary) can be used with
116# arguments.  Booleans can never be used with arguments;
117# combos allow an argument only if they accept other values
118# than "auto", "enabled", and "disabled".
119def allow_arg(opt):
120    if opt["type"] == "boolean":
121        return False
122    if opt["type"] != "combo":
123        return True
124    return not (set(opt["choices"]) <= {"auto", "disabled", "enabled"})
125
126
127# Return whether the option (a dictionary) can be used without
128# arguments.  Booleans can only be used without arguments;
129# combos require an argument if they accept neither "enabled"
130# nor "disabled"
131def require_arg(opt):
132    if opt["type"] == "boolean":
133        return False
134    if opt["type"] != "combo":
135        return True
136    return not ({"enabled", "disabled"}.intersection(opt["choices"]))
137
138
139def filter_options(json):
140    if ":" in json["name"]:
141        return False
142    if json["section"] == "user":
143        return json["name"] not in SKIP_OPTIONS
144    else:
145        return json["name"] in BUILTIN_OPTIONS
146
147
148def load_options(json):
149    json = [x for x in json if filter_options(x)]
150    return sorted(json, key=lambda x: x["name"])
151
152
153def cli_option(opt):
154    name = opt["name"]
155    if name in OPTION_NAMES:
156        return OPTION_NAMES[name]
157    return name.replace("_", "-")
158
159
160def cli_help_key(opt):
161    key = cli_option(opt)
162    if require_arg(opt):
163        return key
164    if opt["type"] == "boolean" and opt["value"]:
165        return f"disable-{key}"
166    return f"enable-{key}"
167
168
169def cli_metavar(opt):
170    if opt["type"] == "string":
171        return "VALUE"
172    if opt["type"] == "array":
173        return "CHOICES" if "choices" in opt else "VALUES"
174    return "CHOICE"
175
176
177def print_help(options):
178    print("meson_options_help() {")
179    feature_opts = []
180    for opt in sorted(options, key=cli_help_key):
181        key = cli_help_key(opt)
182        # The first section includes options that have an arguments,
183        # and booleans (i.e., only one of enable/disable makes sense)
184        if require_arg(opt):
185            metavar = cli_metavar(opt)
186            left = f"--{key}={metavar}"
187            help_line(left, opt, 27, True)
188        elif opt["type"] == "boolean" and opt["name"] not in AUTO_OPTIONS:
189            left = f"--{key}"
190            help_line(left, opt, 27, False)
191        elif allow_arg(opt):
192            if opt["type"] == "combo" and "enabled" in opt["choices"]:
193                left = f"--{key}[=CHOICE]"
194            else:
195                left = f"--{key}=CHOICE"
196            help_line(left, opt, 27, True)
197        else:
198            feature_opts.append(opt)
199
200    sh_print()
201    sh_print("Optional features, enabled with --enable-FEATURE and")
202    sh_print("disabled with --disable-FEATURE, default is enabled if available")
203    sh_print("(unless built with --without-default-features):")
204    sh_print()
205    for opt in sorted(feature_opts, key=cli_option):
206        key = cli_option(opt)
207        help_line(key, opt, 18, False)
208    print("}")
209
210
211def print_parse(options):
212    print("_meson_option_parse() {")
213    print("  case $1 in")
214    for opt in options:
215        key = cli_option(opt)
216        name = opt["name"]
217        if require_arg(opt):
218            if opt["type"] == "array" and not "choices" in opt:
219                print(f'    --{key}=*) quote_sh "-D{name}=$(meson_option_build_array $2)" ;;')
220            else:
221                print(f'    --{key}=*) quote_sh "-D{name}=$2" ;;')
222        elif opt["type"] == "boolean":
223            print(f'    --enable-{key}) printf "%s" -D{name}=true ;;')
224            print(f'    --disable-{key}) printf "%s" -D{name}=false ;;')
225        else:
226            if opt["type"] == "combo" and "enabled" in opt["choices"]:
227                print(f'    --enable-{key}) printf "%s" -D{name}=enabled ;;')
228            if opt["type"] == "combo" and "disabled" in opt["choices"]:
229                print(f'    --disable-{key}) printf "%s" -D{name}=disabled ;;')
230            if allow_arg(opt):
231                print(f'    --enable-{key}=*) quote_sh "-D{name}=$2" ;;')
232    print("    *) return 1 ;;")
233    print("  esac")
234    print("}")
235
236
237options = load_options(json.load(sys.stdin))
238print("# This file is generated by meson-buildoptions.py, do not edit!")
239print_help(options)
240print_parse(options)
241