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 107*2409089bSPaolo Bonzini if not (strict_lints and lint in STRICT_LINTS): 10897ed1e9cSPaolo Bonzini lint_list.append(LintFlag(flags=[flag, prefix + lint], priority=priority)) 10997ed1e9cSPaolo Bonzini 110de98c175SPaolo Bonzini if strict_lints: 111de98c175SPaolo Bonzini for lint in STRICT_LINTS: 112de98c175SPaolo Bonzini lint_list.append(LintFlag(flags=["-D", lint], priority=1000000)) 113de98c175SPaolo Bonzini 11497ed1e9cSPaolo Bonzini lint_list.sort(key=lambda x: x.priority) 11597ed1e9cSPaolo Bonzini for lint in lint_list: 11697ed1e9cSPaolo Bonzini yield from lint.flags 11797ed1e9cSPaolo Bonzini 11897ed1e9cSPaolo Bonzini 1191de82059SPaolo Bonzinidef generate_cfg_flags(header: str, cargo_toml: CargoTOML) -> Iterable[str]: 1206fdc5bc1SManos Pitsidianakis """Converts defines from config[..].h headers to rustc --cfg flags.""" 1216fdc5bc1SManos Pitsidianakis 1226fdc5bc1SManos Pitsidianakis with open(header, encoding="utf-8") as cfg: 1236fdc5bc1SManos Pitsidianakis config = [l.split()[1:] for l in cfg if l.startswith("#define")] 1246fdc5bc1SManos Pitsidianakis 1256fdc5bc1SManos Pitsidianakis cfg_list = [] 1266fdc5bc1SManos Pitsidianakis for cfg in config: 1271de82059SPaolo Bonzini name = cfg[0] 1281de82059SPaolo Bonzini if f'cfg({name})' not in cargo_toml.check_cfg: 1296fdc5bc1SManos Pitsidianakis continue 1306fdc5bc1SManos Pitsidianakis if len(cfg) >= 2 and cfg[1] != "1": 1316fdc5bc1SManos Pitsidianakis continue 1326fdc5bc1SManos Pitsidianakis cfg_list.append("--cfg") 1336fdc5bc1SManos Pitsidianakis cfg_list.append(name) 1346fdc5bc1SManos Pitsidianakis return cfg_list 1356fdc5bc1SManos Pitsidianakis 1366fdc5bc1SManos Pitsidianakis 1376fdc5bc1SManos Pitsidianakisdef main() -> None: 1386fdc5bc1SManos Pitsidianakis parser = argparse.ArgumentParser() 1396fdc5bc1SManos Pitsidianakis parser.add_argument("-v", "--verbose", action="store_true") 1406fdc5bc1SManos Pitsidianakis parser.add_argument( 1416fdc5bc1SManos Pitsidianakis "--config-headers", 1426fdc5bc1SManos Pitsidianakis metavar="CONFIG_HEADER", 1436fdc5bc1SManos Pitsidianakis action="append", 1446fdc5bc1SManos Pitsidianakis dest="config_headers", 1456fdc5bc1SManos Pitsidianakis help="paths to any configuration C headers (*.h files), if any", 1466fdc5bc1SManos Pitsidianakis required=False, 1476fdc5bc1SManos Pitsidianakis default=[], 1486fdc5bc1SManos Pitsidianakis ) 1491de82059SPaolo Bonzini parser.add_argument( 1501de82059SPaolo Bonzini metavar="TOML_FILE", 1511de82059SPaolo Bonzini action="store", 1521de82059SPaolo Bonzini dest="cargo_toml", 1531de82059SPaolo Bonzini help="path to Cargo.toml file", 15490868c3dSPaolo Bonzini nargs='?', 15590868c3dSPaolo Bonzini ) 15690868c3dSPaolo Bonzini parser.add_argument( 15790868c3dSPaolo Bonzini "--workspace", 15890868c3dSPaolo Bonzini metavar="DIR", 15990868c3dSPaolo Bonzini action="store", 16090868c3dSPaolo Bonzini dest="workspace", 16190868c3dSPaolo Bonzini help="path to root of the workspace", 16290868c3dSPaolo Bonzini required=False, 16390868c3dSPaolo Bonzini default=None, 1641de82059SPaolo Bonzini ) 16597ed1e9cSPaolo Bonzini parser.add_argument( 16697ed1e9cSPaolo Bonzini "--features", 16797ed1e9cSPaolo Bonzini action="store_true", 16897ed1e9cSPaolo Bonzini dest="features", 16997ed1e9cSPaolo Bonzini help="generate --check-cfg arguments for features", 17097ed1e9cSPaolo Bonzini required=False, 17197ed1e9cSPaolo Bonzini default=None, 17297ed1e9cSPaolo Bonzini ) 17397ed1e9cSPaolo Bonzini parser.add_argument( 17497ed1e9cSPaolo Bonzini "--lints", 17597ed1e9cSPaolo Bonzini action="store_true", 17697ed1e9cSPaolo Bonzini dest="lints", 17797ed1e9cSPaolo Bonzini help="generate arguments from [lints] table", 17897ed1e9cSPaolo Bonzini required=False, 17997ed1e9cSPaolo Bonzini default=None, 18097ed1e9cSPaolo Bonzini ) 18197ed1e9cSPaolo Bonzini parser.add_argument( 18297ed1e9cSPaolo Bonzini "--rustc-version", 18397ed1e9cSPaolo Bonzini metavar="VERSION", 18497ed1e9cSPaolo Bonzini dest="rustc_version", 18597ed1e9cSPaolo Bonzini action="store", 18697ed1e9cSPaolo Bonzini help="version of rustc", 18797ed1e9cSPaolo Bonzini required=False, 18897ed1e9cSPaolo Bonzini default="1.0.0", 18997ed1e9cSPaolo Bonzini ) 190de98c175SPaolo Bonzini parser.add_argument( 191de98c175SPaolo Bonzini "--strict-lints", 192de98c175SPaolo Bonzini action="store_true", 193de98c175SPaolo Bonzini dest="strict_lints", 194de98c175SPaolo Bonzini help="apply stricter checks (for nightly Rust)", 195de98c175SPaolo Bonzini default=False, 196de98c175SPaolo Bonzini ) 1976fdc5bc1SManos Pitsidianakis args = parser.parse_args() 1986fdc5bc1SManos Pitsidianakis if args.verbose: 1996fdc5bc1SManos Pitsidianakis logging.basicConfig(level=logging.DEBUG) 2006fdc5bc1SManos Pitsidianakis logging.debug("args: %s", args) 2011de82059SPaolo Bonzini 20297ed1e9cSPaolo Bonzini rustc_version = tuple((int(x) for x in args.rustc_version.split('.')[0:2])) 20390868c3dSPaolo Bonzini if args.workspace: 20490868c3dSPaolo Bonzini workspace_cargo_toml = Path(args.workspace, "Cargo.toml").resolve() 20590868c3dSPaolo Bonzini cargo_toml = CargoTOML(args.cargo_toml, str(workspace_cargo_toml)) 20690868c3dSPaolo Bonzini else: 20790868c3dSPaolo Bonzini cargo_toml = CargoTOML(args.cargo_toml, None) 2081de82059SPaolo Bonzini 20997ed1e9cSPaolo Bonzini if args.lints: 210de98c175SPaolo Bonzini for tok in generate_lint_flags(cargo_toml, args.strict_lints): 21197ed1e9cSPaolo Bonzini print(tok) 21297ed1e9cSPaolo Bonzini 21397ed1e9cSPaolo Bonzini if rustc_version >= (1, 80): 21497ed1e9cSPaolo Bonzini if args.lints: 2151d03e977SPaolo Bonzini print("--check-cfg") 2161d03e977SPaolo Bonzini print("cfg(test)") 21797ed1e9cSPaolo Bonzini for cfg in sorted(cargo_toml.check_cfg): 21897ed1e9cSPaolo Bonzini print("--check-cfg") 21997ed1e9cSPaolo Bonzini print(cfg) 22097ed1e9cSPaolo Bonzini if args.features: 22197ed1e9cSPaolo Bonzini for feature in cargo_toml.get_table("features"): 22297ed1e9cSPaolo Bonzini if feature != "default": 22397ed1e9cSPaolo Bonzini print("--check-cfg") 22497ed1e9cSPaolo Bonzini print(f'cfg(feature,values("{feature}"))') 22597ed1e9cSPaolo Bonzini 2266fdc5bc1SManos Pitsidianakis for header in args.config_headers: 2271de82059SPaolo Bonzini for tok in generate_cfg_flags(header, cargo_toml): 2286fdc5bc1SManos Pitsidianakis print(tok) 2296fdc5bc1SManos Pitsidianakis 2306fdc5bc1SManos Pitsidianakis 2316fdc5bc1SManos Pitsidianakisif __name__ == "__main__": 2326fdc5bc1SManos Pitsidianakis main() 233