2
0

rustc_args.py 7.1 KB

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