xref: /qemu/scripts/rust/rustc_args.py (revision 1d03e9771e05685e11bbd3cc8cdd072c02cf580d)
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
2897ed1e9cSPaolo Bonzinifrom dataclasses import dataclass
296fdc5bc1SManos Pitsidianakisimport logging
301de82059SPaolo Bonzinifrom pathlib import Path
3197ed1e9cSPaolo 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
38de98c175SPaolo BonziniSTRICT_LINTS = {"unknown_lints", "warnings"}
39de98c175SPaolo Bonzini
406fdc5bc1SManos Pitsidianakis
411de82059SPaolo Bonziniclass CargoTOML:
421de82059SPaolo Bonzini    tomldata: Mapping[Any, Any]
4390868c3dSPaolo Bonzini    workspace_data: Mapping[Any, Any]
441de82059SPaolo Bonzini    check_cfg: Set[str]
451de82059SPaolo Bonzini
4690868c3dSPaolo Bonzini    def __init__(self, path: Optional[str], workspace: Optional[str]):
4790868c3dSPaolo Bonzini        if path is not None:
481de82059SPaolo Bonzini            with open(path, 'rb') as f:
491de82059SPaolo Bonzini                self.tomldata = tomllib.load(f)
5090868c3dSPaolo Bonzini        else:
5190868c3dSPaolo Bonzini            self.tomldata = {"lints": {"workspace": True}}
5290868c3dSPaolo Bonzini
5390868c3dSPaolo Bonzini        if workspace is not None:
5490868c3dSPaolo Bonzini            with open(workspace, 'rb') as f:
5590868c3dSPaolo Bonzini                self.workspace_data = tomllib.load(f)
5690868c3dSPaolo Bonzini            if "workspace" not in self.workspace_data:
5790868c3dSPaolo Bonzini                self.workspace_data["workspace"] = {}
581de82059SPaolo Bonzini
591de82059SPaolo Bonzini        self.check_cfg = set(self.find_check_cfg())
601de82059SPaolo Bonzini
611de82059SPaolo Bonzini    def find_check_cfg(self) -> Iterable[str]:
621de82059SPaolo Bonzini        toml_lints = self.lints
631de82059SPaolo Bonzini        rust_lints = toml_lints.get("rust", {})
641de82059SPaolo Bonzini        cfg_lint = rust_lints.get("unexpected_cfgs", {})
651de82059SPaolo Bonzini        return cfg_lint.get("check-cfg", [])
661de82059SPaolo Bonzini
671de82059SPaolo Bonzini    @property
681de82059SPaolo Bonzini    def lints(self) -> Mapping[Any, Any]:
6990868c3dSPaolo Bonzini        return self.get_table("lints", True)
701de82059SPaolo Bonzini
7190868c3dSPaolo Bonzini    def get_table(self, key: str, can_be_workspace: bool = False) -> Mapping[Any, Any]:
721de82059SPaolo Bonzini        table = self.tomldata.get(key, {})
7390868c3dSPaolo Bonzini        if can_be_workspace and table.get("workspace", False) is True:
7490868c3dSPaolo Bonzini            table = self.workspace_data["workspace"].get(key, {})
751de82059SPaolo Bonzini
761de82059SPaolo Bonzini        return table
771de82059SPaolo Bonzini
781de82059SPaolo Bonzini
7997ed1e9cSPaolo Bonzini@dataclass
8097ed1e9cSPaolo Bonziniclass LintFlag:
8197ed1e9cSPaolo Bonzini    flags: List[str]
8297ed1e9cSPaolo Bonzini    priority: int
8397ed1e9cSPaolo Bonzini
8497ed1e9cSPaolo Bonzini
85de98c175SPaolo Bonzinidef generate_lint_flags(cargo_toml: CargoTOML, strict_lints: bool) -> Iterable[str]:
8697ed1e9cSPaolo Bonzini    """Converts Cargo.toml lints to rustc -A/-D/-F/-W flags."""
8797ed1e9cSPaolo Bonzini
8897ed1e9cSPaolo Bonzini    toml_lints = cargo_toml.lints
8997ed1e9cSPaolo Bonzini
9097ed1e9cSPaolo Bonzini    lint_list = []
9197ed1e9cSPaolo Bonzini    for k, v in toml_lints.items():
9297ed1e9cSPaolo Bonzini        prefix = "" if k == "rust" else k + "::"
9397ed1e9cSPaolo Bonzini        for lint, data in v.items():
9497ed1e9cSPaolo Bonzini            level = data if isinstance(data, str) else data["level"]
9597ed1e9cSPaolo Bonzini            priority = 0 if isinstance(data, str) else data.get("priority", 0)
9697ed1e9cSPaolo Bonzini            if level == "deny":
9797ed1e9cSPaolo Bonzini                flag = "-D"
9897ed1e9cSPaolo Bonzini            elif level == "allow":
9997ed1e9cSPaolo Bonzini                flag = "-A"
10097ed1e9cSPaolo Bonzini            elif level == "warn":
10197ed1e9cSPaolo Bonzini                flag = "-W"
10297ed1e9cSPaolo Bonzini            elif level == "forbid":
10397ed1e9cSPaolo Bonzini                flag = "-F"
10497ed1e9cSPaolo Bonzini            else:
10597ed1e9cSPaolo Bonzini                raise Exception(f"invalid level {level} for {prefix}{lint}")
10697ed1e9cSPaolo Bonzini
10797ed1e9cSPaolo Bonzini            # This may change if QEMU ever invokes clippy-driver or rustdoc by
10897ed1e9cSPaolo Bonzini            # hand.  For now, check the syntax but do not add non-rustc lints to
10997ed1e9cSPaolo Bonzini            # the command line.
110de98c175SPaolo Bonzini            if k == "rust" and not (strict_lints and lint in STRICT_LINTS):
11197ed1e9cSPaolo Bonzini                lint_list.append(LintFlag(flags=[flag, prefix + lint], priority=priority))
11297ed1e9cSPaolo Bonzini
113de98c175SPaolo Bonzini    if strict_lints:
114de98c175SPaolo Bonzini        for lint in STRICT_LINTS:
115de98c175SPaolo Bonzini            lint_list.append(LintFlag(flags=["-D", lint], priority=1000000))
116de98c175SPaolo Bonzini
11797ed1e9cSPaolo Bonzini    lint_list.sort(key=lambda x: x.priority)
11897ed1e9cSPaolo Bonzini    for lint in lint_list:
11997ed1e9cSPaolo Bonzini        yield from lint.flags
12097ed1e9cSPaolo Bonzini
12197ed1e9cSPaolo Bonzini
1221de82059SPaolo Bonzinidef generate_cfg_flags(header: str, cargo_toml: CargoTOML) -> Iterable[str]:
1236fdc5bc1SManos Pitsidianakis    """Converts defines from config[..].h headers to rustc --cfg flags."""
1246fdc5bc1SManos Pitsidianakis
1256fdc5bc1SManos Pitsidianakis    with open(header, encoding="utf-8") as cfg:
1266fdc5bc1SManos Pitsidianakis        config = [l.split()[1:] for l in cfg if l.startswith("#define")]
1276fdc5bc1SManos Pitsidianakis
1286fdc5bc1SManos Pitsidianakis    cfg_list = []
1296fdc5bc1SManos Pitsidianakis    for cfg in config:
1301de82059SPaolo Bonzini        name = cfg[0]
1311de82059SPaolo Bonzini        if f'cfg({name})' not in cargo_toml.check_cfg:
1326fdc5bc1SManos Pitsidianakis            continue
1336fdc5bc1SManos Pitsidianakis        if len(cfg) >= 2 and cfg[1] != "1":
1346fdc5bc1SManos Pitsidianakis            continue
1356fdc5bc1SManos Pitsidianakis        cfg_list.append("--cfg")
1366fdc5bc1SManos Pitsidianakis        cfg_list.append(name)
1376fdc5bc1SManos Pitsidianakis    return cfg_list
1386fdc5bc1SManos Pitsidianakis
1396fdc5bc1SManos Pitsidianakis
1406fdc5bc1SManos Pitsidianakisdef main() -> None:
1416fdc5bc1SManos Pitsidianakis    parser = argparse.ArgumentParser()
1426fdc5bc1SManos Pitsidianakis    parser.add_argument("-v", "--verbose", action="store_true")
1436fdc5bc1SManos Pitsidianakis    parser.add_argument(
1446fdc5bc1SManos Pitsidianakis        "--config-headers",
1456fdc5bc1SManos Pitsidianakis        metavar="CONFIG_HEADER",
1466fdc5bc1SManos Pitsidianakis        action="append",
1476fdc5bc1SManos Pitsidianakis        dest="config_headers",
1486fdc5bc1SManos Pitsidianakis        help="paths to any configuration C headers (*.h files), if any",
1496fdc5bc1SManos Pitsidianakis        required=False,
1506fdc5bc1SManos Pitsidianakis        default=[],
1516fdc5bc1SManos Pitsidianakis    )
1521de82059SPaolo Bonzini    parser.add_argument(
1531de82059SPaolo Bonzini        metavar="TOML_FILE",
1541de82059SPaolo Bonzini        action="store",
1551de82059SPaolo Bonzini        dest="cargo_toml",
1561de82059SPaolo Bonzini        help="path to Cargo.toml file",
15790868c3dSPaolo Bonzini        nargs='?',
15890868c3dSPaolo Bonzini    )
15990868c3dSPaolo Bonzini    parser.add_argument(
16090868c3dSPaolo Bonzini        "--workspace",
16190868c3dSPaolo Bonzini        metavar="DIR",
16290868c3dSPaolo Bonzini        action="store",
16390868c3dSPaolo Bonzini        dest="workspace",
16490868c3dSPaolo Bonzini        help="path to root of the workspace",
16590868c3dSPaolo Bonzini        required=False,
16690868c3dSPaolo Bonzini        default=None,
1671de82059SPaolo Bonzini    )
16897ed1e9cSPaolo Bonzini    parser.add_argument(
16997ed1e9cSPaolo Bonzini        "--features",
17097ed1e9cSPaolo Bonzini        action="store_true",
17197ed1e9cSPaolo Bonzini        dest="features",
17297ed1e9cSPaolo Bonzini        help="generate --check-cfg arguments for features",
17397ed1e9cSPaolo Bonzini        required=False,
17497ed1e9cSPaolo Bonzini        default=None,
17597ed1e9cSPaolo Bonzini    )
17697ed1e9cSPaolo Bonzini    parser.add_argument(
17797ed1e9cSPaolo Bonzini        "--lints",
17897ed1e9cSPaolo Bonzini        action="store_true",
17997ed1e9cSPaolo Bonzini        dest="lints",
18097ed1e9cSPaolo Bonzini        help="generate arguments from [lints] table",
18197ed1e9cSPaolo Bonzini        required=False,
18297ed1e9cSPaolo Bonzini        default=None,
18397ed1e9cSPaolo Bonzini    )
18497ed1e9cSPaolo Bonzini    parser.add_argument(
18597ed1e9cSPaolo Bonzini        "--rustc-version",
18697ed1e9cSPaolo Bonzini        metavar="VERSION",
18797ed1e9cSPaolo Bonzini        dest="rustc_version",
18897ed1e9cSPaolo Bonzini        action="store",
18997ed1e9cSPaolo Bonzini        help="version of rustc",
19097ed1e9cSPaolo Bonzini        required=False,
19197ed1e9cSPaolo Bonzini        default="1.0.0",
19297ed1e9cSPaolo Bonzini    )
193de98c175SPaolo Bonzini    parser.add_argument(
194de98c175SPaolo Bonzini        "--strict-lints",
195de98c175SPaolo Bonzini        action="store_true",
196de98c175SPaolo Bonzini        dest="strict_lints",
197de98c175SPaolo Bonzini        help="apply stricter checks (for nightly Rust)",
198de98c175SPaolo Bonzini        default=False,
199de98c175SPaolo Bonzini    )
2006fdc5bc1SManos Pitsidianakis    args = parser.parse_args()
2016fdc5bc1SManos Pitsidianakis    if args.verbose:
2026fdc5bc1SManos Pitsidianakis        logging.basicConfig(level=logging.DEBUG)
2036fdc5bc1SManos Pitsidianakis    logging.debug("args: %s", args)
2041de82059SPaolo Bonzini
20597ed1e9cSPaolo Bonzini    rustc_version = tuple((int(x) for x in args.rustc_version.split('.')[0:2]))
20690868c3dSPaolo Bonzini    if args.workspace:
20790868c3dSPaolo Bonzini        workspace_cargo_toml = Path(args.workspace, "Cargo.toml").resolve()
20890868c3dSPaolo Bonzini        cargo_toml = CargoTOML(args.cargo_toml, str(workspace_cargo_toml))
20990868c3dSPaolo Bonzini    else:
21090868c3dSPaolo Bonzini        cargo_toml = CargoTOML(args.cargo_toml, None)
2111de82059SPaolo Bonzini
21297ed1e9cSPaolo Bonzini    if args.lints:
213de98c175SPaolo Bonzini        for tok in generate_lint_flags(cargo_toml, args.strict_lints):
21497ed1e9cSPaolo Bonzini            print(tok)
21597ed1e9cSPaolo Bonzini
21697ed1e9cSPaolo Bonzini    if rustc_version >= (1, 80):
21797ed1e9cSPaolo Bonzini        if args.lints:
218*1d03e977SPaolo Bonzini            print("--check-cfg")
219*1d03e977SPaolo Bonzini            print("cfg(test)")
22097ed1e9cSPaolo Bonzini            for cfg in sorted(cargo_toml.check_cfg):
22197ed1e9cSPaolo Bonzini                print("--check-cfg")
22297ed1e9cSPaolo Bonzini                print(cfg)
22397ed1e9cSPaolo Bonzini        if args.features:
22497ed1e9cSPaolo Bonzini            for feature in cargo_toml.get_table("features"):
22597ed1e9cSPaolo Bonzini                if feature != "default":
22697ed1e9cSPaolo Bonzini                    print("--check-cfg")
22797ed1e9cSPaolo Bonzini                    print(f'cfg(feature,values("{feature}"))')
22897ed1e9cSPaolo Bonzini
2296fdc5bc1SManos Pitsidianakis    for header in args.config_headers:
2301de82059SPaolo Bonzini        for tok in generate_cfg_flags(header, cargo_toml):
2316fdc5bc1SManos Pitsidianakis            print(tok)
2326fdc5bc1SManos Pitsidianakis
2336fdc5bc1SManos Pitsidianakis
2346fdc5bc1SManos Pitsidianakisif __name__ == "__main__":
2356fdc5bc1SManos Pitsidianakis    main()
236