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