| #!/usr/bin/env python3 | |
| # SPDX-License-Identifier: GPL-2.0 | |
| """generate_rust_analyzer - Generates the `rust-project.json` file for `rust-analyzer`. | |
| """ | |
| import argparse | |
| import json | |
| import logging | |
| import os | |
| import pathlib | |
| import subprocess | |
| import sys | |
| def args_crates_cfgs(cfgs): | |
| crates_cfgs = {} | |
| for cfg in cfgs: | |
| crate, vals = cfg.split("=", 1) | |
| crates_cfgs[crate] = vals.replace("--cfg", "").split() | |
| return crates_cfgs | |
| def generate_crates(srctree, objtree, sysroot_src, external_src, cfgs, core_edition): | |
| # Generate the configuration list. | |
| cfg = [] | |
| with open(objtree / "include" / "generated" / "rustc_cfg") as fd: | |
| for line in fd: | |
| line = line.replace("--cfg=", "") | |
| line = line.replace("\n", "") | |
| cfg.append(line) | |
| # Now fill the crates list -- dependencies need to come first. | |
| # | |
| # Avoid O(n^2) iterations by keeping a map of indexes. | |
| crates = [] | |
| crates_indexes = {} | |
| crates_cfgs = args_crates_cfgs(cfgs) | |
| def append_crate(display_name, root_module, deps, cfg=[], is_workspace_member=True, is_proc_macro=False, edition="2021"): | |
| crate = { | |
| "display_name": display_name, | |
| "root_module": str(root_module), | |
| "is_workspace_member": is_workspace_member, | |
| "is_proc_macro": is_proc_macro, | |
| "deps": [{"crate": crates_indexes[dep], "name": dep} for dep in deps], | |
| "cfg": cfg, | |
| "edition": edition, | |
| "env": { | |
| "RUST_MODFILE": "This is only for rust-analyzer" | |
| } | |
| } | |
| if is_proc_macro: | |
| proc_macro_dylib_name = subprocess.check_output( | |
| [os.environ["RUSTC"], "--print", "file-names", "--crate-name", display_name, "--crate-type", "proc-macro", "-"], | |
| stdin=subprocess.DEVNULL, | |
| ).decode('utf-8').strip() | |
| crate["proc_macro_dylib_path"] = f"{objtree}/rust/{proc_macro_dylib_name}" | |
| crates_indexes[display_name] = len(crates) | |
| crates.append(crate) | |
| def append_sysroot_crate( | |
| display_name, | |
| deps, | |
| cfg=[], | |
| ): | |
| append_crate( | |
| display_name, | |
| sysroot_src / display_name / "src" / "lib.rs", | |
| deps, | |
| cfg, | |
| is_workspace_member=False, | |
| # Miguel Ojeda writes: | |
| # | |
| # > ... in principle even the sysroot crates may have different | |
| # > editions. | |
| # > | |
| # > For instance, in the move to 2024, it seems all happened at once | |
| # > in 1.87.0 in these upstream commits: | |
| # > | |
| # > 0e071c2c6a58 ("Migrate core to Rust 2024") | |
| # > f505d4e8e380 ("Migrate alloc to Rust 2024") | |
| # > 0b2489c226c3 ("Migrate proc_macro to Rust 2024") | |
| # > 993359e70112 ("Migrate std to Rust 2024") | |
| # > | |
| # > But in the previous move to 2021, `std` moved in 1.59.0, while | |
| # > the others in 1.60.0: | |
| # > | |
| # > b656384d8398 ("Update stdlib to the 2021 edition") | |
| # > 06a1c14d52a8 ("Switch all libraries to the 2021 edition") | |
| # | |
| # Link: https://lore.kernel.org/all/CANiq72kd9bHdKaAm=8xCUhSHMy2csyVed69bOc4dXyFAW4sfuw@mail.gmail.com/ | |
| # | |
| # At the time of writing all rust versions we support build the | |
| # sysroot crates with the same edition. We may need to relax this | |
| # assumption if future edition moves span multiple rust versions. | |
| edition=core_edition, | |
| ) | |
| # NB: sysroot crates reexport items from one another so setting up our transitive dependencies | |
| # here is important for ensuring that rust-analyzer can resolve symbols. The sources of truth | |
| # for this dependency graph are `(sysroot_src / crate / "Cargo.toml" for crate in crates)`. | |
| append_sysroot_crate("core", [], cfg=crates_cfgs.get("core", [])) | |
| append_sysroot_crate("alloc", ["core"]) | |
| append_sysroot_crate("std", ["alloc", "core"]) | |
| append_sysroot_crate("proc_macro", ["core", "std"]) | |
| append_crate( | |
| "compiler_builtins", | |
| srctree / "rust" / "compiler_builtins.rs", | |
| ["core"], | |
| ) | |
| append_crate( | |
| "macros", | |
| srctree / "rust" / "macros" / "lib.rs", | |
| ["std", "proc_macro"], | |
| is_proc_macro=True, | |
| ) | |
| append_crate( | |
| "build_error", | |
| srctree / "rust" / "build_error.rs", | |
| ["core", "compiler_builtins"], | |
| ) | |
| append_crate( | |
| "pin_init_internal", | |
| srctree / "rust" / "pin-init" / "internal" / "src" / "lib.rs", | |
| ["std", "proc_macro"], | |
| cfg=["kernel"], | |
| is_proc_macro=True, | |
| ) | |
| append_crate( | |
| "pin_init", | |
| srctree / "rust" / "pin-init" / "src" / "lib.rs", | |
| ["core", "compiler_builtins", "pin_init_internal", "macros"], | |
| cfg=["kernel"], | |
| ) | |
| append_crate( | |
| "ffi", | |
| srctree / "rust" / "ffi.rs", | |
| ["core", "compiler_builtins"], | |
| ) | |
| def append_crate_with_generated( | |
| display_name, | |
| deps, | |
| ): | |
| append_crate( | |
| display_name, | |
| srctree / "rust"/ display_name / "lib.rs", | |
| deps, | |
| cfg=cfg, | |
| ) | |
| crates[-1]["env"]["OBJTREE"] = str(objtree.resolve(True)) | |
| crates[-1]["source"] = { | |
| "include_dirs": [ | |
| str(srctree / "rust" / display_name), | |
| str(objtree / "rust") | |
| ], | |
| "exclude_dirs": [], | |
| } | |
| append_crate_with_generated("bindings", ["core", "ffi", "pin_init"]) | |
| append_crate_with_generated("uapi", ["core", "ffi", "pin_init"]) | |
| append_crate_with_generated("kernel", ["core", "macros", "build_error", "pin_init", "ffi", "bindings", "uapi"]) | |
| scripts = srctree / "scripts" | |
| makefile = (scripts / "Makefile").read_text() | |
| for path in scripts.glob("*.rs"): | |
| name = path.stem | |
| if f"{name}-rust" not in makefile: | |
| continue | |
| append_crate( | |
| name, | |
| path, | |
| ["std"], | |
| ) | |
| def is_root_crate(build_file, target): | |
| try: | |
| contents = build_file.read_text() | |
| except FileNotFoundError: | |
| return False | |
| return f"{target}.o" in contents | |
| # Then, the rest outside of `rust/`. | |
| # | |
| # We explicitly mention the top-level folders we want to cover. | |
| extra_dirs = map(lambda dir: srctree / dir, ("samples", "drivers")) | |
| if external_src is not None: | |
| extra_dirs = [external_src] | |
| for folder in extra_dirs: | |
| for path in folder.rglob("*.rs"): | |
| logging.info("Checking %s", path) | |
| name = path.stem | |
| # Skip those that are not crate roots. | |
| if not is_root_crate(path.parent / "Makefile", name) and \ | |
| not is_root_crate(path.parent / "Kbuild", name): | |
| continue | |
| logging.info("Adding %s", name) | |
| append_crate( | |
| name, | |
| path, | |
| ["core", "kernel", "pin_init"], | |
| cfg=cfg, | |
| ) | |
| return crates | |
| def main(): | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument('--verbose', '-v', action='store_true') | |
| parser.add_argument('--cfgs', action='append', default=[]) | |
| parser.add_argument("core_edition") | |
| parser.add_argument("srctree", type=pathlib.Path) | |
| parser.add_argument("objtree", type=pathlib.Path) | |
| parser.add_argument("sysroot", type=pathlib.Path) | |
| parser.add_argument("sysroot_src", type=pathlib.Path) | |
| parser.add_argument("exttree", type=pathlib.Path, nargs="?") | |
| args = parser.parse_args() | |
| logging.basicConfig( | |
| format="[%(asctime)s] [%(levelname)s] %(message)s", | |
| level=logging.INFO if args.verbose else logging.WARNING | |
| ) | |
| rust_project = { | |
| "crates": generate_crates(args.srctree, args.objtree, args.sysroot_src, args.exttree, args.cfgs, args.core_edition), | |
| "sysroot": str(args.sysroot), | |
| } | |
| json.dump(rust_project, sys.stdout, sort_keys=True, indent=4) | |
| if __name__ == "__main__": | |
| main() |