compiler.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. # python3
  2. # Copyright 2021 The Chromium Authors. All rights reserved.
  3. # Use of this source code is governed by a BSD-style license that can be
  4. # found in the LICENSE file.
  5. """Provide abstractions around concepts from the Rust compiler.
  6. This module provides abstractions around the compiler's targets (build
  7. architectures), and mappings between those architectures and GN conditionals."""
  8. from __future__ import annotations
  9. from enum import Enum
  10. import re
  11. class ArchSet:
  12. """A set of compiler target architectures.
  13. This is used to track the output of `cargo` or `rustc` and match it against
  14. different architectures.
  15. """
  16. def __init__(self, initial: set[str]):
  17. for a in initial:
  18. assert a in _RUSTC_ARCH_TO_BUILD_CONDITION
  19. # Internally stored as a set of architecture strings.
  20. self._set = initial
  21. def add_archset(self, other: ArchSet) -> bool:
  22. """Makes `self` into the union of `self` and `other`.
  23. Returns if anything was added to the ArchSet."""
  24. if self._set.issuperset(other._set):
  25. return False
  26. self._set.update(other._set)
  27. return True
  28. def has_arch(self, arch: str) -> bool:
  29. """Whether the `ArchSet` contains `arch`."""
  30. return arch in self._set
  31. def as_strings(self) -> set[str]:
  32. """Returns `self` as a raw set of strings."""
  33. return self._set
  34. def __bool__(self) -> bool:
  35. """Whether the `ArchSet` is non-empty."""
  36. return bool(self._set)
  37. def __len__(self) -> int:
  38. """The number of architectures in the `ArchSet`."""
  39. return len(self._set)
  40. def __repr__(self) -> str:
  41. """A string representation of the `ArchSet`."""
  42. return "ArchSet({})".format(repr(self._set))
  43. def __eq__(self, other: object) -> bool:
  44. if not isinstance(other, ArchSet):
  45. return NotImplemented
  46. """Whether `self` and `other` contain the same architectures."""
  47. return self._set == other._set
  48. def __and__(self, other: ArchSet) -> ArchSet:
  49. """An intersection of `self` and `other`.
  50. Returns a new `ArchSet` that contains only the architectures that are
  51. present in both `self` and `other`."""
  52. return ArchSet(initial=(self._set & other._set))
  53. @staticmethod
  54. def ALL() -> ArchSet:
  55. """All valid architectures."""
  56. return ArchSet({k for k in _RUSTC_ARCH_TO_BUILD_CONDITION.keys()})
  57. @staticmethod
  58. def ONE() -> ArchSet:
  59. """Arbitrary selection of a single architecture."""
  60. return ArchSet({"aarch64-apple-ios"})
  61. @staticmethod
  62. def EMPTY() -> ArchSet:
  63. """No architectures."""
  64. return ArchSet(set())
  65. class BuildCondition(Enum):
  66. """Each value corresponds to a BUILD file condition that can be branched on.
  67. These flags currently store the GN condition statements directly, to easily
  68. convert from a `BuildCondition` to a GN if-statement.
  69. """
  70. # For Android builds, these come from `android_abi_target` values in
  71. # //build/config/android/abi.gni.
  72. ANDROID_X86 = "is_android && target_cpu == \"x86\"",
  73. ANDROID_X64 = "is_android && target_cpu == \"x64\"",
  74. ANDROID_ARM = "is_android && target_cpu == \"arm\"",
  75. ANDROID_ARM64 = "is_android && target_cpu == \"arm64\"",
  76. # Not supported by rustc but is in //build/config/android/abi.gni
  77. # ANDROID_MIPS = "is_android && target_cpu == \"mipsel\"",
  78. # Not supported by rustc but is in //build/config/android/abi.gni
  79. # ANDROID_MIPS64 = "is_android && target_cpu == \"mips64el\"",
  80. # For Fuchsia builds, these come from //build/config/rust.gni.
  81. FUCHSIA_ARM64 = "is_fuchsia && target_cpu == \"arm64\"",
  82. FUCHSIA_X64 = "is_fuchsia && target_cpu == \"x64\"",
  83. # For iOS builds, these come from //build/config/rust.gni.
  84. IOS_ARM64 = "is_ios && target_cpu == \"arm64\"",
  85. IOS_ARM = "is_ios && target_cpu == \"arm\"",
  86. IOS_X64 = "is_ios && target_cpu == \"x64\"",
  87. IOS_X86 = "is_ios && target_cpu == \"x86\"",
  88. WINDOWS_X86 = "is_win && target_cpu == \"x86\"",
  89. WINDOWS_X64 = "is_win && target_cpu == \"x64\"",
  90. LINUX_X86 = "(is_linux || is_chromeos) && target_cpu == \"x86\"",
  91. LINUX_X64 = "(is_linux || is_chromeos) && target_cpu == \"x64\"",
  92. MAC_X64 = "is_mac && target_cpu == \"x64\"",
  93. MAC_ARM64 = "is_mac && target_cpu == \"arm64\"",
  94. # Combinations generated by BuildConditionSet._optimize()
  95. ALL_ARM32 = "target_cpu == \"arm\"",
  96. ALL_ARM64 = "target_cpu == \"arm64\"",
  97. ALL_X64 = "target_cpu == \"x64\"",
  98. ALL_X86 = "target_cpu == \"x86\"",
  99. ALL_ANDROID = "is_android",
  100. ALL_FUCHSIA = "is_fuchsia",
  101. ALL_IOS = "is_ios",
  102. ALL_WINDOWS = "is_win",
  103. ALL_LINUX = "is_linux || is_chromeos",
  104. ALL_MAC = "is_mac",
  105. NOT_ANDROID = "!is_android",
  106. NOT_FUCHSIA = "!is_fuchsia",
  107. NOT_IOS = "!is_ios",
  108. NOT_WINDOWS = "!is_win",
  109. NOT_LINUX = "!(is_linux || is_chromeos)",
  110. NOT_MAC = "!is_mac",
  111. def gn_condition(self) -> str:
  112. """Gets the GN conditional text that represents the `BuildCondition`."""
  113. return self.value[0]
  114. class BuildConditionSet:
  115. """A group of conditions for which the BUILD file can branch on.
  116. The conditions are each OR'd together, that is the set combines a group of
  117. conditions where any one of the conditions would be enough to satisfy the
  118. set.
  119. The group of conditions is built from an ArchSet, but provides a separate
  120. abstraction as it can be optimized to combine `BuildCondition`s, in order to
  121. cover multiple BUILD file conditions with fewer, more general conditions.
  122. An empty BuildConditionSet is never true, so a BUILD file output that would
  123. be conditional on such a set should be skipped entirely.
  124. """
  125. def __init__(self, arch_set: ArchSet):
  126. self.arch_set = arch_set
  127. def is_always_true(self):
  128. """Whether the set covers all possible BUILD file configurations."""
  129. return len(self.arch_set) == len(ArchSet.ALL())
  130. def inverted(self):
  131. inverse: set[str] = ArchSet.ALL().as_strings(
  132. ) - self.arch_set.as_strings()
  133. return BuildConditionSet(ArchSet(initial=inverse))
  134. def get_gn_conditions(self):
  135. """Generate the set of BUILD file conditions as text.
  136. Returns:
  137. A set of GN conditions (as strings) that should be evaluated.
  138. The result should be true if any of them is true.
  139. An empty set is returned to indicate there are no conditions.
  140. """
  141. # No arches are covered! We should not use this BuildConditionSet
  142. # to generate any output as it would always be `false`.
  143. assert self.arch_set, ("Generating BUILD rules for an empty "
  144. "BuildConditionSet (which is never true).")
  145. if self.is_always_true():
  146. return [] # All archs are covered, so no conditions needed.
  147. modes = {
  148. _RUSTC_ARCH_TO_BUILD_CONDITION[a]
  149. for a in self.arch_set.as_strings()
  150. }
  151. return [m.gn_condition() for m in self._optimize(modes)]
  152. def _optimize(self, modes: set[BuildCondition]) -> set[BuildCondition]:
  153. """Combine `BuildCondition`s into a smaller, more general set.
  154. Args:
  155. modes: A set of BuildConditions to optimize.
  156. Returns:
  157. A smaller set of BuildConditions, if it's possible to optimize, or
  158. the original `modes` set.
  159. """
  160. def build_cond(arch: str) -> BuildCondition:
  161. return _RUSTC_ARCH_TO_BUILD_CONDITION[arch]
  162. def build_conds_matching(matching: str) -> set[BuildCondition]:
  163. return {
  164. build_cond(arch)
  165. for arch in _RUSTC_ARCH_TO_BUILD_CONDITION
  166. if re.search(matching, arch)
  167. }
  168. # Defines a set of modes we can collapse more verbose modes down into.
  169. # For each pair, if all of the modes in the 2nd position are present,
  170. # we can replace them all with the mode in the 1st position.
  171. os_combinations: list[tuple[BuildCondition, set[BuildCondition]]] = [
  172. (BuildCondition.ALL_IOS,
  173. build_conds_matching(_RUSTC_ARCH_MATCH_IOS)),
  174. (BuildCondition.ALL_WINDOWS,
  175. build_conds_matching(_RUSTC_ARCH_MATCH_WINDOWS)),
  176. (BuildCondition.ALL_LINUX,
  177. build_conds_matching(_RUSTC_ARCH_MATCH_LINUX)),
  178. (BuildCondition.ALL_MAC,
  179. build_conds_matching(_RUSTC_ARCH_MATCH_MAC)),
  180. (BuildCondition.ALL_ANDROID,
  181. build_conds_matching(_RUSTC_ARCH_MATCH_ANDROID)),
  182. (BuildCondition.ALL_FUCHSIA,
  183. build_conds_matching(_RUSTC_ARCH_MATCH_FUCHSIA)),
  184. ]
  185. os_merges: list[tuple[BuildCondition, set[BuildCondition]]] = [
  186. (BuildCondition.NOT_ANDROID, {
  187. BuildCondition.ALL_FUCHSIA, BuildCondition.ALL_IOS,
  188. BuildCondition.ALL_WINDOWS, BuildCondition.ALL_LINUX,
  189. BuildCondition.ALL_MAC
  190. }),
  191. (BuildCondition.NOT_FUCHSIA, {
  192. BuildCondition.ALL_ANDROID, BuildCondition.ALL_IOS,
  193. BuildCondition.ALL_WINDOWS, BuildCondition.ALL_LINUX,
  194. BuildCondition.ALL_MAC
  195. }),
  196. (BuildCondition.NOT_IOS, {
  197. BuildCondition.ALL_ANDROID, BuildCondition.ALL_FUCHSIA,
  198. BuildCondition.ALL_WINDOWS, BuildCondition.ALL_LINUX,
  199. BuildCondition.ALL_MAC
  200. }),
  201. (BuildCondition.NOT_WINDOWS, {
  202. BuildCondition.ALL_ANDROID, BuildCondition.ALL_FUCHSIA,
  203. BuildCondition.ALL_IOS, BuildCondition.ALL_LINUX,
  204. BuildCondition.ALL_MAC
  205. }),
  206. (BuildCondition.NOT_LINUX, {
  207. BuildCondition.ALL_ANDROID, BuildCondition.ALL_FUCHSIA,
  208. BuildCondition.ALL_IOS, BuildCondition.ALL_WINDOWS,
  209. BuildCondition.ALL_MAC
  210. }),
  211. (BuildCondition.NOT_MAC, {
  212. BuildCondition.ALL_ANDROID, BuildCondition.ALL_FUCHSIA,
  213. BuildCondition.ALL_IOS, BuildCondition.ALL_WINDOWS,
  214. BuildCondition.ALL_LINUX
  215. })
  216. ]
  217. cpu_combinations: list[tuple[BuildCondition, set[BuildCondition]]] = [
  218. (BuildCondition.ALL_X86,
  219. build_conds_matching(_RUSTC_ARCH_MATCH_X86)),
  220. (BuildCondition.ALL_X64,
  221. build_conds_matching(_RUSTC_ARCH_MATCH_X64)),
  222. (BuildCondition.ALL_ARM32,
  223. build_conds_matching(_RUSTC_ARCH_MATCH_ARM32)),
  224. (BuildCondition.ALL_ARM64,
  225. build_conds_matching(_RUSTC_ARCH_MATCH_ARM64)),
  226. ]
  227. to_remove: set[BuildCondition] = set()
  228. for (combined, all_individual) in os_combinations:
  229. if modes & all_individual == all_individual:
  230. modes.add(combined)
  231. to_remove.update(all_individual)
  232. for (combined, all_individual) in os_merges:
  233. if modes & all_individual == all_individual:
  234. modes.add(combined)
  235. to_remove.update(all_individual)
  236. for (combined, all_individual) in cpu_combinations:
  237. # Only add cpu-specific things if it would add something new, if the
  238. # individual archs are not already covered by combined
  239. # `BuildCondition`s.
  240. if all_individual & to_remove != all_individual:
  241. if modes & all_individual == all_individual:
  242. modes.add(combined)
  243. to_remove.update(all_individual)
  244. for r in to_remove:
  245. modes.remove(r)
  246. return modes
  247. def __bool__(self) -> bool:
  248. """Whether the BuildConditionSet has any conditions."""
  249. return bool(self.arch_set)
  250. def __repr__(self) -> str:
  251. """A string representation of a `BuildConditionSet`."""
  252. return "BuildConditionSet({})".format(repr(self.arch_set))
  253. def __eq__(self, other: object) -> bool:
  254. if not isinstance(other, BuildConditionSet):
  255. return NotImplemented
  256. """Whether two sets cover the same BUILD file configurations."""
  257. return self.arch_set == other.arch_set
  258. @staticmethod
  259. def ALL() -> BuildConditionSet:
  260. """A set that covers all BUILD file configurations."""
  261. return BuildConditionSet(ArchSet.ALL())
  262. @staticmethod
  263. def EMPTY():
  264. """An empty set that represents never being true."""
  265. return BuildConditionSet(ArchSet.EMPTY())
  266. # Internal representations used by ArchSet.
  267. #
  268. # This is a set of compiler targets known by rustc that we support. The full
  269. # list is from `rustc --print target-list`.
  270. #
  271. # For each compiler target, we have a map to a BuildCondition which would
  272. # represent the BUILD file condition that is true for the compiler target.
  273. #
  274. # NOTE: If this changes, then also update the `BuildConditionSet._optimize()``
  275. # method that combines the `BuildCondition`s. Also update the matchers for sets
  276. # of compiler targets, such as `_RUSTC_ARCH_MATCH_ANDROID`, below.
  277. _RUSTC_ARCH_TO_BUILD_CONDITION = {
  278. "i686-linux-android": BuildCondition.ANDROID_X86,
  279. "x86_64-linux-android": BuildCondition.ANDROID_X64,
  280. "armv7-linux-androideabi": BuildCondition.ANDROID_ARM,
  281. "aarch64-linux-android": BuildCondition.ANDROID_ARM64,
  282. # Not supported by rustc but is in //build/config/android/abi.gni
  283. # "mipsel-linux-android",
  284. # Not supported by rustc but is in //build/config/android/abi.gni.
  285. # "mips64el-linux-android",
  286. "aarch64-fuchsia": BuildCondition.FUCHSIA_ARM64,
  287. "x86_64-fuchsia": BuildCondition.FUCHSIA_X64,
  288. "aarch64-apple-ios": BuildCondition.IOS_ARM64,
  289. "armv7-apple-ios": BuildCondition.IOS_ARM,
  290. "x86_64-apple-ios": BuildCondition.IOS_X64,
  291. "i386-apple-ios": BuildCondition.IOS_X86,
  292. # The winapi crate has dependencies that only exist on the "-gnu" flavour of
  293. # these windows targets. We would like to believe that we don't need them if
  294. # we are building with MSVC, or with clang which is pretending to be MSVC in
  295. # the Chromium build. If we get weird linking errors due to missing Windows
  296. # things in winapi, then we should probably change these to "-gnu".
  297. "i686-pc-windows-msvc": BuildCondition.WINDOWS_X86,
  298. "x86_64-pc-windows-msvc": BuildCondition.WINDOWS_X64,
  299. "i686-unknown-linux-gnu": BuildCondition.LINUX_X86,
  300. "x86_64-unknown-linux-gnu": BuildCondition.LINUX_X64,
  301. "x86_64-apple-darwin": BuildCondition.MAC_X64,
  302. "aarch64-apple-darwin": BuildCondition.MAC_ARM64,
  303. }
  304. # Regexs that will match all related architectures, and no unrelated ones.
  305. _RUSTC_ARCH_MATCH_ANDROID = r"-android"
  306. _RUSTC_ARCH_MATCH_FUCHSIA = r"-fuchsia"
  307. _RUSTC_ARCH_MATCH_IOS = r"-apple-ios"
  308. _RUSTC_ARCH_MATCH_WINDOWS = r"-windows"
  309. _RUSTC_ARCH_MATCH_LINUX = r"-linux-gnu"
  310. _RUSTC_ARCH_MATCH_MAC = r"-apple-darwin"
  311. _RUSTC_ARCH_MATCH_X86 = r"^i[36]86-"
  312. _RUSTC_ARCH_MATCH_X64 = r"^x86_64-"
  313. _RUSTC_ARCH_MATCH_ARM32 = r"^armv7-"
  314. _RUSTC_ARCH_MATCH_ARM64 = r"^aarch64-"