xref: /qemu/scripts/rust/rustc_args.py (revision 97ed1e9c8e2b0744508ef61cac8a23bb1e107820)
16fdc5bc1SManos Pitsidianakis#!/usr/bin/env python3
26fdc5bc1SManos Pitsidianakis
36fdc5bc1SManos Pitsidianakis"""Generate rustc arguments for meson rust builds.
46fdc5bc1SManos Pitsidianakis
56fdc5bc1SManos PitsidianakisThis program generates --cfg compile flags for the configuration headers passed
66fdc5bc1SManos Pitsidianakisas arguments.
76fdc5bc1SManos Pitsidianakis
86fdc5bc1SManos PitsidianakisCopyright (c) 2024 Linaro Ltd.
96fdc5bc1SManos Pitsidianakis
106fdc5bc1SManos PitsidianakisAuthors:
116fdc5bc1SManos Pitsidianakis Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
126fdc5bc1SManos Pitsidianakis
136fdc5bc1SManos PitsidianakisThis program is free software; you can redistribute it and/or modify
146fdc5bc1SManos Pitsidianakisit under the terms of the GNU General Public License as published by
156fdc5bc1SManos Pitsidianakisthe Free Software Foundation; either version 2 of the License, or
166fdc5bc1SManos Pitsidianakis(at your option) any later version.
176fdc5bc1SManos Pitsidianakis
186fdc5bc1SManos PitsidianakisThis program is distributed in the hope that it will be useful,
196fdc5bc1SManos Pitsidianakisbut WITHOUT ANY WARRANTY; without even the implied warranty of
206fdc5bc1SManos PitsidianakisMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
216fdc5bc1SManos PitsidianakisGNU General Public License for more details.
226fdc5bc1SManos Pitsidianakis
236fdc5bc1SManos PitsidianakisYou should have received a copy of the GNU General Public License
246fdc5bc1SManos Pitsidianakisalong with this program.  If not, see <http://www.gnu.org/licenses/>.
256fdc5bc1SManos Pitsidianakis"""
266fdc5bc1SManos Pitsidianakis
276fdc5bc1SManos Pitsidianakisimport argparse
28*97ed1e9cSPaolo Bonzinifrom dataclasses import dataclass
296fdc5bc1SManos Pitsidianakisimport logging
301de82059SPaolo Bonzinifrom pathlib import Path
31*97ed1e9cSPaolo Bonzinifrom typing import Any, Iterable, List, Mapping, Optional, Set
326fdc5bc1SManos Pitsidianakis
331de82059SPaolo Bonzinitry:
341de82059SPaolo Bonzini    import tomllib
351de82059SPaolo Bonziniexcept ImportError:
361de82059SPaolo Bonzini    import tomli as tomllib
376fdc5bc1SManos Pitsidianakis
386fdc5bc1SManos Pitsidianakis
391de82059SPaolo Bonziniclass CargoTOML:
401de82059SPaolo Bonzini    tomldata: Mapping[Any, Any]
411de82059SPaolo Bonzini    check_cfg: Set[str]
421de82059SPaolo Bonzini
431de82059SPaolo Bonzini    def __init__(self, path: str):
441de82059SPaolo Bonzini        with open(path, 'rb') as f:
451de82059SPaolo Bonzini            self.tomldata = tomllib.load(f)
461de82059SPaolo Bonzini
471de82059SPaolo Bonzini        self.check_cfg = set(self.find_check_cfg())
481de82059SPaolo Bonzini
491de82059SPaolo Bonzini    def find_check_cfg(self) -> Iterable[str]:
501de82059SPaolo Bonzini        toml_lints = self.lints
511de82059SPaolo Bonzini        rust_lints = toml_lints.get("rust", {})
521de82059SPaolo Bonzini        cfg_lint = rust_lints.get("unexpected_cfgs", {})
531de82059SPaolo Bonzini        return cfg_lint.get("check-cfg", [])
541de82059SPaolo Bonzini
551de82059SPaolo Bonzini    @property
561de82059SPaolo Bonzini    def lints(self) -> Mapping[Any, Any]:
571de82059SPaolo Bonzini        return self.get_table("lints")
581de82059SPaolo Bonzini
591de82059SPaolo Bonzini    def get_table(self, key: str) -> Mapping[Any, Any]:
601de82059SPaolo Bonzini        table = self.tomldata.get(key, {})
611de82059SPaolo Bonzini
621de82059SPaolo Bonzini        return table
631de82059SPaolo Bonzini
641de82059SPaolo Bonzini
65*97ed1e9cSPaolo Bonzini@dataclass
66*97ed1e9cSPaolo Bonziniclass LintFlag:
67*97ed1e9cSPaolo Bonzini    flags: List[str]
68*97ed1e9cSPaolo Bonzini    priority: int
69*97ed1e9cSPaolo Bonzini
70*97ed1e9cSPaolo Bonzini
71*97ed1e9cSPaolo Bonzinidef generate_lint_flags(cargo_toml: CargoTOML) -> Iterable[str]:
72*97ed1e9cSPaolo Bonzini    """Converts Cargo.toml lints to rustc -A/-D/-F/-W flags."""
73*97ed1e9cSPaolo Bonzini
74*97ed1e9cSPaolo Bonzini    toml_lints = cargo_toml.lints
75*97ed1e9cSPaolo Bonzini
76*97ed1e9cSPaolo Bonzini    lint_list = []
77*97ed1e9cSPaolo Bonzini    for k, v in toml_lints.items():
78*97ed1e9cSPaolo Bonzini        prefix = "" if k == "rust" else k + "::"
79*97ed1e9cSPaolo Bonzini        for lint, data in v.items():
80*97ed1e9cSPaolo Bonzini            level = data if isinstance(data, str) else data["level"]
81*97ed1e9cSPaolo Bonzini            priority = 0 if isinstance(data, str) else data.get("priority", 0)
82*97ed1e9cSPaolo Bonzini            if level == "deny":
83*97ed1e9cSPaolo Bonzini                flag = "-D"
84*97ed1e9cSPaolo Bonzini            elif level == "allow":
85*97ed1e9cSPaolo Bonzini                flag = "-A"
86*97ed1e9cSPaolo Bonzini            elif level == "warn":
87*97ed1e9cSPaolo Bonzini                flag = "-W"
88*97ed1e9cSPaolo Bonzini            elif level == "forbid":
89*97ed1e9cSPaolo Bonzini                flag = "-F"
90*97ed1e9cSPaolo Bonzini            else:
91*97ed1e9cSPaolo Bonzini                raise Exception(f"invalid level {level} for {prefix}{lint}")
92*97ed1e9cSPaolo Bonzini
93*97ed1e9cSPaolo Bonzini            # This may change if QEMU ever invokes clippy-driver or rustdoc by
94*97ed1e9cSPaolo Bonzini            # hand.  For now, check the syntax but do not add non-rustc lints to
95*97ed1e9cSPaolo Bonzini            # the command line.
96*97ed1e9cSPaolo Bonzini            if k == "rust":
97*97ed1e9cSPaolo Bonzini                lint_list.append(LintFlag(flags=[flag, prefix + lint], priority=priority))
98*97ed1e9cSPaolo Bonzini
99*97ed1e9cSPaolo Bonzini    lint_list.sort(key=lambda x: x.priority)
100*97ed1e9cSPaolo Bonzini    for lint in lint_list:
101*97ed1e9cSPaolo Bonzini        yield from lint.flags
102*97ed1e9cSPaolo Bonzini
103*97ed1e9cSPaolo Bonzini
1041de82059SPaolo Bonzinidef generate_cfg_flags(header: str, cargo_toml: CargoTOML) -> Iterable[str]:
1056fdc5bc1SManos Pitsidianakis    """Converts defines from config[..].h headers to rustc --cfg flags."""
1066fdc5bc1SManos Pitsidianakis
1076fdc5bc1SManos Pitsidianakis    with open(header, encoding="utf-8") as cfg:
1086fdc5bc1SManos Pitsidianakis        config = [l.split()[1:] for l in cfg if l.startswith("#define")]
1096fdc5bc1SManos Pitsidianakis
1106fdc5bc1SManos Pitsidianakis    cfg_list = []
1116fdc5bc1SManos Pitsidianakis    for cfg in config:
1121de82059SPaolo Bonzini        name = cfg[0]
1131de82059SPaolo Bonzini        if f'cfg({name})' not in cargo_toml.check_cfg:
1146fdc5bc1SManos Pitsidianakis            continue
1156fdc5bc1SManos Pitsidianakis        if len(cfg) >= 2 and cfg[1] != "1":
1166fdc5bc1SManos Pitsidianakis            continue
1176fdc5bc1SManos Pitsidianakis        cfg_list.append("--cfg")
1186fdc5bc1SManos Pitsidianakis        cfg_list.append(name)
1196fdc5bc1SManos Pitsidianakis    return cfg_list
1206fdc5bc1SManos Pitsidianakis
1216fdc5bc1SManos Pitsidianakis
1226fdc5bc1SManos Pitsidianakisdef main() -> None:
1236fdc5bc1SManos Pitsidianakis    parser = argparse.ArgumentParser()
1246fdc5bc1SManos Pitsidianakis    parser.add_argument("-v", "--verbose", action="store_true")
1256fdc5bc1SManos Pitsidianakis    parser.add_argument(
1266fdc5bc1SManos Pitsidianakis        "--config-headers",
1276fdc5bc1SManos Pitsidianakis        metavar="CONFIG_HEADER",
1286fdc5bc1SManos Pitsidianakis        action="append",
1296fdc5bc1SManos Pitsidianakis        dest="config_headers",
1306fdc5bc1SManos Pitsidianakis        help="paths to any configuration C headers (*.h files), if any",
1316fdc5bc1SManos Pitsidianakis        required=False,
1326fdc5bc1SManos Pitsidianakis        default=[],
1336fdc5bc1SManos Pitsidianakis    )
1341de82059SPaolo Bonzini    parser.add_argument(
1351de82059SPaolo Bonzini        metavar="TOML_FILE",
1361de82059SPaolo Bonzini        action="store",
1371de82059SPaolo Bonzini        dest="cargo_toml",
1381de82059SPaolo Bonzini        help="path to Cargo.toml file",
1391de82059SPaolo Bonzini    )
140*97ed1e9cSPaolo Bonzini    parser.add_argument(
141*97ed1e9cSPaolo Bonzini        "--features",
142*97ed1e9cSPaolo Bonzini        action="store_true",
143*97ed1e9cSPaolo Bonzini        dest="features",
144*97ed1e9cSPaolo Bonzini        help="generate --check-cfg arguments for features",
145*97ed1e9cSPaolo Bonzini        required=False,
146*97ed1e9cSPaolo Bonzini        default=None,
147*97ed1e9cSPaolo Bonzini    )
148*97ed1e9cSPaolo Bonzini    parser.add_argument(
149*97ed1e9cSPaolo Bonzini        "--lints",
150*97ed1e9cSPaolo Bonzini        action="store_true",
151*97ed1e9cSPaolo Bonzini        dest="lints",
152*97ed1e9cSPaolo Bonzini        help="generate arguments from [lints] table",
153*97ed1e9cSPaolo Bonzini        required=False,
154*97ed1e9cSPaolo Bonzini        default=None,
155*97ed1e9cSPaolo Bonzini    )
156*97ed1e9cSPaolo Bonzini    parser.add_argument(
157*97ed1e9cSPaolo Bonzini        "--rustc-version",
158*97ed1e9cSPaolo Bonzini        metavar="VERSION",
159*97ed1e9cSPaolo Bonzini        dest="rustc_version",
160*97ed1e9cSPaolo Bonzini        action="store",
161*97ed1e9cSPaolo Bonzini        help="version of rustc",
162*97ed1e9cSPaolo Bonzini        required=False,
163*97ed1e9cSPaolo Bonzini        default="1.0.0",
164*97ed1e9cSPaolo Bonzini    )
1656fdc5bc1SManos Pitsidianakis    args = parser.parse_args()
1666fdc5bc1SManos Pitsidianakis    if args.verbose:
1676fdc5bc1SManos Pitsidianakis        logging.basicConfig(level=logging.DEBUG)
1686fdc5bc1SManos Pitsidianakis    logging.debug("args: %s", args)
1691de82059SPaolo Bonzini
170*97ed1e9cSPaolo Bonzini    rustc_version = tuple((int(x) for x in args.rustc_version.split('.')[0:2]))
1711de82059SPaolo Bonzini    cargo_toml = CargoTOML(args.cargo_toml)
1721de82059SPaolo Bonzini
173*97ed1e9cSPaolo Bonzini    if args.lints:
174*97ed1e9cSPaolo Bonzini        for tok in generate_lint_flags(cargo_toml):
175*97ed1e9cSPaolo Bonzini            print(tok)
176*97ed1e9cSPaolo Bonzini
177*97ed1e9cSPaolo Bonzini    if rustc_version >= (1, 80):
178*97ed1e9cSPaolo Bonzini        if args.lints:
179*97ed1e9cSPaolo Bonzini            for cfg in sorted(cargo_toml.check_cfg):
180*97ed1e9cSPaolo Bonzini                print("--check-cfg")
181*97ed1e9cSPaolo Bonzini                print(cfg)
182*97ed1e9cSPaolo Bonzini        if args.features:
183*97ed1e9cSPaolo Bonzini            for feature in cargo_toml.get_table("features"):
184*97ed1e9cSPaolo Bonzini                if feature != "default":
185*97ed1e9cSPaolo Bonzini                    print("--check-cfg")
186*97ed1e9cSPaolo Bonzini                    print(f'cfg(feature,values("{feature}"))')
187*97ed1e9cSPaolo Bonzini
1886fdc5bc1SManos Pitsidianakis    for header in args.config_headers:
1891de82059SPaolo Bonzini        for tok in generate_cfg_flags(header, cargo_toml):
1906fdc5bc1SManos Pitsidianakis            print(tok)
1916fdc5bc1SManos Pitsidianakis
1926fdc5bc1SManos Pitsidianakis
1936fdc5bc1SManos Pitsidianakisif __name__ == "__main__":
1946fdc5bc1SManos Pitsidianakis    main()
195