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 38STRICT_LINTS = {"unknown_lints", "warnings"} 39 40 41class CargoTOML: 42 tomldata: Mapping[Any, Any] 43 workspace_data: Mapping[Any, Any] 44 check_cfg: Set[str] 45 46 def __init__(self, path: Optional[str], workspace: Optional[str]): 47 if path is not None: 48 with open(path, 'rb') as f: 49 self.tomldata = tomllib.load(f) 50 else: 51 self.tomldata = {"lints": {"workspace": True}} 52 53 if workspace is not None: 54 with open(workspace, 'rb') as f: 55 self.workspace_data = tomllib.load(f) 56 if "workspace" not in self.workspace_data: 57 self.workspace_data["workspace"] = {} 58 59 self.check_cfg = set(self.find_check_cfg()) 60 61 def find_check_cfg(self) -> Iterable[str]: 62 toml_lints = self.lints 63 rust_lints = toml_lints.get("rust", {}) 64 cfg_lint = rust_lints.get("unexpected_cfgs", {}) 65 return cfg_lint.get("check-cfg", []) 66 67 @property 68 def lints(self) -> Mapping[Any, Any]: 69 return self.get_table("lints", True) 70 71 def get_table(self, key: str, can_be_workspace: bool = False) -> Mapping[Any, Any]: 72 table = self.tomldata.get(key, {}) 73 if can_be_workspace and table.get("workspace", False) is True: 74 table = self.workspace_data["workspace"].get(key, {}) 75 76 return table 77 78 79@dataclass 80class LintFlag: 81 flags: List[str] 82 priority: int 83 84 85def generate_lint_flags(cargo_toml: CargoTOML, strict_lints: bool) -> Iterable[str]: 86 """Converts Cargo.toml lints to rustc -A/-D/-F/-W flags.""" 87 88 toml_lints = cargo_toml.lints 89 90 lint_list = [] 91 for k, v in toml_lints.items(): 92 prefix = "" if k == "rust" else k + "::" 93 for lint, data in v.items(): 94 level = data if isinstance(data, str) else data["level"] 95 priority = 0 if isinstance(data, str) else data.get("priority", 0) 96 if level == "deny": 97 flag = "-D" 98 elif level == "allow": 99 flag = "-A" 100 elif level == "warn": 101 flag = "-W" 102 elif level == "forbid": 103 flag = "-F" 104 else: 105 raise Exception(f"invalid level {level} for {prefix}{lint}") 106 107 if not (strict_lints and lint in STRICT_LINTS): 108 lint_list.append(LintFlag(flags=[flag, prefix + lint], priority=priority)) 109 110 if strict_lints: 111 for lint in STRICT_LINTS: 112 lint_list.append(LintFlag(flags=["-D", lint], priority=1000000)) 113 114 lint_list.sort(key=lambda x: x.priority) 115 for lint in lint_list: 116 yield from lint.flags 117 118 119def generate_cfg_flags(header: str, cargo_toml: CargoTOML) -> Iterable[str]: 120 """Converts defines from config[..].h headers to rustc --cfg flags.""" 121 122 with open(header, encoding="utf-8") as cfg: 123 config = [l.split()[1:] for l in cfg if l.startswith("#define")] 124 125 cfg_list = [] 126 for cfg in config: 127 name = cfg[0] 128 if f'cfg({name})' not in cargo_toml.check_cfg: 129 continue 130 if len(cfg) >= 2 and cfg[1] != "1": 131 continue 132 cfg_list.append("--cfg") 133 cfg_list.append(name) 134 return cfg_list 135 136 137def main() -> None: 138 parser = argparse.ArgumentParser() 139 parser.add_argument("-v", "--verbose", action="store_true") 140 parser.add_argument( 141 "--config-headers", 142 metavar="CONFIG_HEADER", 143 action="append", 144 dest="config_headers", 145 help="paths to any configuration C headers (*.h files), if any", 146 required=False, 147 default=[], 148 ) 149 parser.add_argument( 150 metavar="TOML_FILE", 151 action="store", 152 dest="cargo_toml", 153 help="path to Cargo.toml file", 154 nargs='?', 155 ) 156 parser.add_argument( 157 "--workspace", 158 metavar="DIR", 159 action="store", 160 dest="workspace", 161 help="path to root of the workspace", 162 required=False, 163 default=None, 164 ) 165 parser.add_argument( 166 "--features", 167 action="store_true", 168 dest="features", 169 help="generate --check-cfg arguments for features", 170 required=False, 171 default=None, 172 ) 173 parser.add_argument( 174 "--lints", 175 action="store_true", 176 dest="lints", 177 help="generate arguments from [lints] table", 178 required=False, 179 default=None, 180 ) 181 parser.add_argument( 182 "--rustc-version", 183 metavar="VERSION", 184 dest="rustc_version", 185 action="store", 186 help="version of rustc", 187 required=False, 188 default="1.0.0", 189 ) 190 parser.add_argument( 191 "--strict-lints", 192 action="store_true", 193 dest="strict_lints", 194 help="apply stricter checks (for nightly Rust)", 195 default=False, 196 ) 197 args = parser.parse_args() 198 if args.verbose: 199 logging.basicConfig(level=logging.DEBUG) 200 logging.debug("args: %s", args) 201 202 rustc_version = tuple((int(x) for x in args.rustc_version.split('.')[0:2])) 203 if args.workspace: 204 workspace_cargo_toml = Path(args.workspace, "Cargo.toml").resolve() 205 cargo_toml = CargoTOML(args.cargo_toml, str(workspace_cargo_toml)) 206 else: 207 cargo_toml = CargoTOML(args.cargo_toml, None) 208 209 if args.lints: 210 for tok in generate_lint_flags(cargo_toml, args.strict_lints): 211 print(tok) 212 213 if rustc_version >= (1, 80): 214 if args.lints: 215 print("--check-cfg") 216 print("cfg(test)") 217 for cfg in sorted(cargo_toml.check_cfg): 218 print("--check-cfg") 219 print(cfg) 220 if args.features: 221 for feature in cargo_toml.get_table("features"): 222 if feature != "default": 223 print("--check-cfg") 224 print(f'cfg(feature,values("{feature}"))') 225 226 for header in args.config_headers: 227 for tok in generate_cfg_flags(header, cargo_toml): 228 print(tok) 229 230 231if __name__ == "__main__": 232 main() 233