1#!/usr/bin/env python3 2 3"""Generate rustc arguments for meson rust builds. 4 5This program generates --cfg compile flags for the configuration headers passed 6as arguments. 7 8Copyright (c) 2024 Linaro Ltd. 9 10Authors: 11 Manos Pitsidianakis <manos.pitsidianakis@linaro.org> 12 13This program is free software; you can redistribute it and/or modify 14it under the terms of the GNU General Public License as published by 15the Free Software Foundation; either version 2 of the License, or 16(at your option) any later version. 17 18This program is distributed in the hope that it will be useful, 19but WITHOUT ANY WARRANTY; without even the implied warranty of 20MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21GNU General Public License for more details. 22 23You should have received a copy of the GNU General Public License 24along with this program. If not, see <http://www.gnu.org/licenses/>. 25""" 26 27import argparse 28from dataclasses import dataclass 29import logging 30from pathlib import Path 31from typing import Any, Iterable, List, Mapping, Optional, Set 32 33try: 34 import tomllib 35except ImportError: 36 import tomli as tomllib 37 38 39class CargoTOML: 40 tomldata: Mapping[Any, Any] 41 workspace_data: Mapping[Any, Any] 42 check_cfg: Set[str] 43 44 def __init__(self, path: Optional[str], workspace: Optional[str]): 45 if path is not None: 46 with open(path, 'rb') as f: 47 self.tomldata = tomllib.load(f) 48 else: 49 self.tomldata = {"lints": {"workspace": True}} 50 51 if workspace is not None: 52 with open(workspace, 'rb') as f: 53 self.workspace_data = tomllib.load(f) 54 if "workspace" not in self.workspace_data: 55 self.workspace_data["workspace"] = {} 56 57 self.check_cfg = set(self.find_check_cfg()) 58 59 def find_check_cfg(self) -> Iterable[str]: 60 toml_lints = self.lints 61 rust_lints = toml_lints.get("rust", {}) 62 cfg_lint = rust_lints.get("unexpected_cfgs", {}) 63 return cfg_lint.get("check-cfg", []) 64 65 @property 66 def lints(self) -> Mapping[Any, Any]: 67 return self.get_table("lints", True) 68 69 def get_table(self, key: str, can_be_workspace: bool = False) -> Mapping[Any, Any]: 70 table = self.tomldata.get(key, {}) 71 if can_be_workspace and table.get("workspace", False) is True: 72 table = self.workspace_data["workspace"].get(key, {}) 73 74 return table 75 76 77@dataclass 78class LintFlag: 79 flags: List[str] 80 priority: int 81 82 83def generate_lint_flags(cargo_toml: CargoTOML) -> Iterable[str]: 84 """Converts Cargo.toml lints to rustc -A/-D/-F/-W flags.""" 85 86 toml_lints = cargo_toml.lints 87 88 lint_list = [] 89 for k, v in toml_lints.items(): 90 prefix = "" if k == "rust" else k + "::" 91 for lint, data in v.items(): 92 level = data if isinstance(data, str) else data["level"] 93 priority = 0 if isinstance(data, str) else data.get("priority", 0) 94 if level == "deny": 95 flag = "-D" 96 elif level == "allow": 97 flag = "-A" 98 elif level == "warn": 99 flag = "-W" 100 elif level == "forbid": 101 flag = "-F" 102 else: 103 raise Exception(f"invalid level {level} for {prefix}{lint}") 104 105 # This may change if QEMU ever invokes clippy-driver or rustdoc by 106 # hand. For now, check the syntax but do not add non-rustc lints to 107 # the command line. 108 if k == "rust": 109 lint_list.append(LintFlag(flags=[flag, prefix + lint], priority=priority)) 110 111 lint_list.sort(key=lambda x: x.priority) 112 for lint in lint_list: 113 yield from lint.flags 114 115 116def generate_cfg_flags(header: str, cargo_toml: CargoTOML) -> Iterable[str]: 117 """Converts defines from config[..].h headers to rustc --cfg flags.""" 118 119 with open(header, encoding="utf-8") as cfg: 120 config = [l.split()[1:] for l in cfg if l.startswith("#define")] 121 122 cfg_list = [] 123 for cfg in config: 124 name = cfg[0] 125 if f'cfg({name})' not in cargo_toml.check_cfg: 126 continue 127 if len(cfg) >= 2 and cfg[1] != "1": 128 continue 129 cfg_list.append("--cfg") 130 cfg_list.append(name) 131 return cfg_list 132 133 134def main() -> None: 135 parser = argparse.ArgumentParser() 136 parser.add_argument("-v", "--verbose", action="store_true") 137 parser.add_argument( 138 "--config-headers", 139 metavar="CONFIG_HEADER", 140 action="append", 141 dest="config_headers", 142 help="paths to any configuration C headers (*.h files), if any", 143 required=False, 144 default=[], 145 ) 146 parser.add_argument( 147 metavar="TOML_FILE", 148 action="store", 149 dest="cargo_toml", 150 help="path to Cargo.toml file", 151 nargs='?', 152 ) 153 parser.add_argument( 154 "--workspace", 155 metavar="DIR", 156 action="store", 157 dest="workspace", 158 help="path to root of the workspace", 159 required=False, 160 default=None, 161 ) 162 parser.add_argument( 163 "--features", 164 action="store_true", 165 dest="features", 166 help="generate --check-cfg arguments for features", 167 required=False, 168 default=None, 169 ) 170 parser.add_argument( 171 "--lints", 172 action="store_true", 173 dest="lints", 174 help="generate arguments from [lints] table", 175 required=False, 176 default=None, 177 ) 178 parser.add_argument( 179 "--rustc-version", 180 metavar="VERSION", 181 dest="rustc_version", 182 action="store", 183 help="version of rustc", 184 required=False, 185 default="1.0.0", 186 ) 187 args = parser.parse_args() 188 if args.verbose: 189 logging.basicConfig(level=logging.DEBUG) 190 logging.debug("args: %s", args) 191 192 rustc_version = tuple((int(x) for x in args.rustc_version.split('.')[0:2])) 193 if args.workspace: 194 workspace_cargo_toml = Path(args.workspace, "Cargo.toml").resolve() 195 cargo_toml = CargoTOML(args.cargo_toml, str(workspace_cargo_toml)) 196 else: 197 cargo_toml = CargoTOML(args.cargo_toml, None) 198 199 if args.lints: 200 for tok in generate_lint_flags(cargo_toml): 201 print(tok) 202 203 if rustc_version >= (1, 80): 204 if args.lints: 205 for cfg in sorted(cargo_toml.check_cfg): 206 print("--check-cfg") 207 print(cfg) 208 if args.features: 209 for feature in cargo_toml.get_table("features"): 210 if feature != "default": 211 print("--check-cfg") 212 print(f'cfg(feature,values("{feature}"))') 213 214 for header in args.config_headers: 215 for tok in generate_cfg_flags(header, cargo_toml): 216 print(tok) 217 218 219if __name__ == "__main__": 220 main() 221