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