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