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 check_cfg: Set[str] 42 43 def __init__(self, path: str): 44 with open(path, 'rb') as f: 45 self.tomldata = tomllib.load(f) 46 47 self.check_cfg = set(self.find_check_cfg()) 48 49 def find_check_cfg(self) -> Iterable[str]: 50 toml_lints = self.lints 51 rust_lints = toml_lints.get("rust", {}) 52 cfg_lint = rust_lints.get("unexpected_cfgs", {}) 53 return cfg_lint.get("check-cfg", []) 54 55 @property 56 def lints(self) -> Mapping[Any, Any]: 57 return self.get_table("lints") 58 59 def get_table(self, key: str) -> Mapping[Any, Any]: 60 table = self.tomldata.get(key, {}) 61 62 return table 63 64 65@dataclass 66class LintFlag: 67 flags: List[str] 68 priority: int 69 70 71def generate_lint_flags(cargo_toml: CargoTOML) -> Iterable[str]: 72 """Converts Cargo.toml lints to rustc -A/-D/-F/-W flags.""" 73 74 toml_lints = cargo_toml.lints 75 76 lint_list = [] 77 for k, v in toml_lints.items(): 78 prefix = "" if k == "rust" else k + "::" 79 for lint, data in v.items(): 80 level = data if isinstance(data, str) else data["level"] 81 priority = 0 if isinstance(data, str) else data.get("priority", 0) 82 if level == "deny": 83 flag = "-D" 84 elif level == "allow": 85 flag = "-A" 86 elif level == "warn": 87 flag = "-W" 88 elif level == "forbid": 89 flag = "-F" 90 else: 91 raise Exception(f"invalid level {level} for {prefix}{lint}") 92 93 # This may change if QEMU ever invokes clippy-driver or rustdoc by 94 # hand. For now, check the syntax but do not add non-rustc lints to 95 # the command line. 96 if k == "rust": 97 lint_list.append(LintFlag(flags=[flag, prefix + lint], priority=priority)) 98 99 lint_list.sort(key=lambda x: x.priority) 100 for lint in lint_list: 101 yield from lint.flags 102 103 104def generate_cfg_flags(header: str, cargo_toml: CargoTOML) -> Iterable[str]: 105 """Converts defines from config[..].h headers to rustc --cfg flags.""" 106 107 with open(header, encoding="utf-8") as cfg: 108 config = [l.split()[1:] for l in cfg if l.startswith("#define")] 109 110 cfg_list = [] 111 for cfg in config: 112 name = cfg[0] 113 if f'cfg({name})' not in cargo_toml.check_cfg: 114 continue 115 if len(cfg) >= 2 and cfg[1] != "1": 116 continue 117 cfg_list.append("--cfg") 118 cfg_list.append(name) 119 return cfg_list 120 121 122def main() -> None: 123 parser = argparse.ArgumentParser() 124 parser.add_argument("-v", "--verbose", action="store_true") 125 parser.add_argument( 126 "--config-headers", 127 metavar="CONFIG_HEADER", 128 action="append", 129 dest="config_headers", 130 help="paths to any configuration C headers (*.h files), if any", 131 required=False, 132 default=[], 133 ) 134 parser.add_argument( 135 metavar="TOML_FILE", 136 action="store", 137 dest="cargo_toml", 138 help="path to Cargo.toml file", 139 ) 140 parser.add_argument( 141 "--features", 142 action="store_true", 143 dest="features", 144 help="generate --check-cfg arguments for features", 145 required=False, 146 default=None, 147 ) 148 parser.add_argument( 149 "--lints", 150 action="store_true", 151 dest="lints", 152 help="generate arguments from [lints] table", 153 required=False, 154 default=None, 155 ) 156 parser.add_argument( 157 "--rustc-version", 158 metavar="VERSION", 159 dest="rustc_version", 160 action="store", 161 help="version of rustc", 162 required=False, 163 default="1.0.0", 164 ) 165 args = parser.parse_args() 166 if args.verbose: 167 logging.basicConfig(level=logging.DEBUG) 168 logging.debug("args: %s", args) 169 170 rustc_version = tuple((int(x) for x in args.rustc_version.split('.')[0:2])) 171 cargo_toml = CargoTOML(args.cargo_toml) 172 173 if args.lints: 174 for tok in generate_lint_flags(cargo_toml): 175 print(tok) 176 177 if rustc_version >= (1, 80): 178 if args.lints: 179 for cfg in sorted(cargo_toml.check_cfg): 180 print("--check-cfg") 181 print(cfg) 182 if args.features: 183 for feature in cargo_toml.get_table("features"): 184 if feature != "default": 185 print("--check-cfg") 186 print(f'cfg(feature,values("{feature}"))') 187 188 for header in args.config_headers: 189 for tok in generate_cfg_flags(header, cargo_toml): 190 print(tok) 191 192 193if __name__ == "__main__": 194 main() 195