generate_rust_analyzer.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. #!/usr/bin/env python3
  2. # SPDX-License-Identifier: GPL-2.0
  3. """generate_rust_analyzer - Generates the `rust-project.json` file for `rust-analyzer`.
  4. """
  5. import argparse
  6. import json
  7. import logging
  8. import os
  9. import pathlib
  10. import subprocess
  11. import sys
  12. def args_crates_cfgs(cfgs):
  13. crates_cfgs = {}
  14. for cfg in cfgs:
  15. crate, vals = cfg.split("=", 1)
  16. crates_cfgs[crate] = vals.split()
  17. return crates_cfgs
  18. def generate_crates(srctree, objtree, sysroot_src, external_src, cfgs, core_edition):
  19. # Generate the configuration list.
  20. cfg = []
  21. with open(objtree / "include" / "generated" / "rustc_cfg") as fd:
  22. for line in fd:
  23. line = line.replace("--cfg=", "")
  24. line = line.replace("\n", "")
  25. cfg.append(line)
  26. # Now fill the crates list -- dependencies need to come first.
  27. #
  28. # Avoid O(n^2) iterations by keeping a map of indexes.
  29. crates = []
  30. crates_indexes = {}
  31. crates_cfgs = args_crates_cfgs(cfgs)
  32. def append_crate(display_name, root_module, deps, cfg=[], is_workspace_member=True, is_proc_macro=False, edition="2021"):
  33. crate = {
  34. "display_name": display_name,
  35. "root_module": str(root_module),
  36. "is_workspace_member": is_workspace_member,
  37. "is_proc_macro": is_proc_macro,
  38. "deps": [{"crate": crates_indexes[dep], "name": dep} for dep in deps],
  39. "cfg": cfg,
  40. "edition": edition,
  41. "env": {
  42. "RUST_MODFILE": "This is only for rust-analyzer"
  43. }
  44. }
  45. if is_proc_macro:
  46. proc_macro_dylib_name = subprocess.check_output(
  47. [os.environ["RUSTC"], "--print", "file-names", "--crate-name", display_name, "--crate-type", "proc-macro", "-"],
  48. stdin=subprocess.DEVNULL,
  49. ).decode('utf-8').strip()
  50. crate["proc_macro_dylib_path"] = f"{objtree}/rust/{proc_macro_dylib_name}"
  51. crates_indexes[display_name] = len(crates)
  52. crates.append(crate)
  53. def append_sysroot_crate(
  54. display_name,
  55. deps,
  56. cfg=[],
  57. ):
  58. append_crate(
  59. display_name,
  60. sysroot_src / display_name / "src" / "lib.rs",
  61. deps,
  62. cfg,
  63. is_workspace_member=False,
  64. # Miguel Ojeda writes:
  65. #
  66. # > ... in principle even the sysroot crates may have different
  67. # > editions.
  68. # >
  69. # > For instance, in the move to 2024, it seems all happened at once
  70. # > in 1.87.0 in these upstream commits:
  71. # >
  72. # > 0e071c2c6a58 ("Migrate core to Rust 2024")
  73. # > f505d4e8e380 ("Migrate alloc to Rust 2024")
  74. # > 0b2489c226c3 ("Migrate proc_macro to Rust 2024")
  75. # > 993359e70112 ("Migrate std to Rust 2024")
  76. # >
  77. # > But in the previous move to 2021, `std` moved in 1.59.0, while
  78. # > the others in 1.60.0:
  79. # >
  80. # > b656384d8398 ("Update stdlib to the 2021 edition")
  81. # > 06a1c14d52a8 ("Switch all libraries to the 2021 edition")
  82. #
  83. # Link: https://lore.kernel.org/all/CANiq72kd9bHdKaAm=8xCUhSHMy2csyVed69bOc4dXyFAW4sfuw@mail.gmail.com/
  84. #
  85. # At the time of writing all rust versions we support build the
  86. # sysroot crates with the same edition. We may need to relax this
  87. # assumption if future edition moves span multiple rust versions.
  88. edition=core_edition,
  89. )
  90. # NB: sysroot crates reexport items from one another so setting up our transitive dependencies
  91. # here is important for ensuring that rust-analyzer can resolve symbols. The sources of truth
  92. # for this dependency graph are `(sysroot_src / crate / "Cargo.toml" for crate in crates)`.
  93. append_sysroot_crate("core", [], cfg=crates_cfgs.get("core", []))
  94. append_sysroot_crate("alloc", ["core"])
  95. append_sysroot_crate("std", ["alloc", "core"])
  96. append_sysroot_crate("proc_macro", ["core", "std"])
  97. append_crate(
  98. "compiler_builtins",
  99. srctree / "rust" / "compiler_builtins.rs",
  100. ["core"],
  101. )
  102. append_crate(
  103. "proc_macro2",
  104. srctree / "rust" / "proc-macro2" / "lib.rs",
  105. ["core", "alloc", "std", "proc_macro"],
  106. cfg=crates_cfgs["proc_macro2"],
  107. )
  108. append_crate(
  109. "quote",
  110. srctree / "rust" / "quote" / "lib.rs",
  111. ["core", "alloc", "std", "proc_macro", "proc_macro2"],
  112. cfg=crates_cfgs["quote"],
  113. edition="2018",
  114. )
  115. append_crate(
  116. "syn",
  117. srctree / "rust" / "syn" / "lib.rs",
  118. ["std", "proc_macro", "proc_macro2", "quote"],
  119. cfg=crates_cfgs["syn"],
  120. )
  121. append_crate(
  122. "macros",
  123. srctree / "rust" / "macros" / "lib.rs",
  124. ["std", "proc_macro", "proc_macro2", "quote", "syn"],
  125. is_proc_macro=True,
  126. )
  127. append_crate(
  128. "build_error",
  129. srctree / "rust" / "build_error.rs",
  130. ["core", "compiler_builtins"],
  131. )
  132. append_crate(
  133. "pin_init_internal",
  134. srctree / "rust" / "pin-init" / "internal" / "src" / "lib.rs",
  135. ["std", "proc_macro", "proc_macro2", "quote", "syn"],
  136. cfg=["kernel"],
  137. is_proc_macro=True,
  138. )
  139. append_crate(
  140. "pin_init",
  141. srctree / "rust" / "pin-init" / "src" / "lib.rs",
  142. ["core", "compiler_builtins", "pin_init_internal", "macros"],
  143. cfg=["kernel"],
  144. )
  145. append_crate(
  146. "ffi",
  147. srctree / "rust" / "ffi.rs",
  148. ["core", "compiler_builtins"],
  149. )
  150. def append_crate_with_generated(
  151. display_name,
  152. deps,
  153. ):
  154. append_crate(
  155. display_name,
  156. srctree / "rust"/ display_name / "lib.rs",
  157. deps,
  158. cfg=cfg,
  159. )
  160. crates[-1]["env"]["OBJTREE"] = str(objtree.resolve(True))
  161. crates[-1]["source"] = {
  162. "include_dirs": [
  163. str(srctree / "rust" / display_name),
  164. str(objtree / "rust")
  165. ],
  166. "exclude_dirs": [],
  167. }
  168. append_crate_with_generated("bindings", ["core", "ffi", "pin_init"])
  169. append_crate_with_generated("uapi", ["core", "ffi", "pin_init"])
  170. append_crate_with_generated("kernel", ["core", "macros", "build_error", "pin_init", "ffi", "bindings", "uapi"])
  171. def is_root_crate(build_file, target):
  172. try:
  173. return f"{target}.o" in open(build_file).read()
  174. except FileNotFoundError:
  175. return False
  176. # Then, the rest outside of `rust/`.
  177. #
  178. # We explicitly mention the top-level folders we want to cover.
  179. extra_dirs = map(lambda dir: srctree / dir, ("samples", "drivers"))
  180. if external_src is not None:
  181. extra_dirs = [external_src]
  182. for folder in extra_dirs:
  183. for path in folder.rglob("*.rs"):
  184. logging.info("Checking %s", path)
  185. name = path.name.replace(".rs", "")
  186. # Skip those that are not crate roots.
  187. if not is_root_crate(path.parent / "Makefile", name) and \
  188. not is_root_crate(path.parent / "Kbuild", name):
  189. continue
  190. logging.info("Adding %s", name)
  191. append_crate(
  192. name,
  193. path,
  194. ["core", "kernel", "pin_init"],
  195. cfg=cfg,
  196. )
  197. return crates
  198. def main():
  199. parser = argparse.ArgumentParser()
  200. parser.add_argument('--verbose', '-v', action='store_true')
  201. parser.add_argument('--cfgs', action='append', default=[])
  202. parser.add_argument("core_edition")
  203. parser.add_argument("srctree", type=pathlib.Path)
  204. parser.add_argument("objtree", type=pathlib.Path)
  205. parser.add_argument("sysroot", type=pathlib.Path)
  206. parser.add_argument("sysroot_src", type=pathlib.Path)
  207. parser.add_argument("exttree", type=pathlib.Path, nargs="?")
  208. args = parser.parse_args()
  209. logging.basicConfig(
  210. format="[%(asctime)s] [%(levelname)s] %(message)s",
  211. level=logging.INFO if args.verbose else logging.WARNING
  212. )
  213. rust_project = {
  214. "crates": generate_crates(args.srctree, args.objtree, args.sysroot_src, args.exttree, args.cfgs, args.core_edition),
  215. "sysroot": str(args.sysroot),
  216. }
  217. json.dump(rust_project, sys.stdout, sort_keys=True, indent=4)
  218. if __name__ == "__main__":
  219. main()