18c4555ccSMiguel Ojeda#!/usr/bin/env python3
28c4555ccSMiguel Ojeda# SPDX-License-Identifier: GPL-2.0
38c4555ccSMiguel Ojeda"""generate_rust_analyzer - Generates the `rust-project.json` file for `rust-analyzer`.
48c4555ccSMiguel Ojeda"""
58c4555ccSMiguel Ojeda
68c4555ccSMiguel Ojedaimport argparse
78c4555ccSMiguel Ojedaimport json
88c4555ccSMiguel Ojedaimport logging
949a9ef76SVinay Varmaimport os
108c4555ccSMiguel Ojedaimport pathlib
110730422bSTamir Dubersteinimport subprocess
128c4555ccSMiguel Ojedaimport sys
138c4555ccSMiguel Ojeda
144f353e0dSMartin Rodriguez Reboredodef args_crates_cfgs(cfgs):
154f353e0dSMartin Rodriguez Reboredo    crates_cfgs = {}
164f353e0dSMartin Rodriguez Reboredo    for cfg in cfgs:
174f353e0dSMartin Rodriguez Reboredo        crate, vals = cfg.split("=", 1)
184f353e0dSMartin Rodriguez Reboredo        crates_cfgs[crate] = vals.replace("--cfg", "").split()
194f353e0dSMartin Rodriguez Reboredo
204f353e0dSMartin Rodriguez Reboredo    return crates_cfgs
214f353e0dSMartin Rodriguez Reboredo
224f353e0dSMartin Rodriguez Reboredodef generate_crates(srctree, objtree, sysroot_src, external_src, cfgs):
238c4555ccSMiguel Ojeda    # Generate the configuration list.
248c4555ccSMiguel Ojeda    cfg = []
258c4555ccSMiguel Ojeda    with open(objtree / "include" / "generated" / "rustc_cfg") as fd:
268c4555ccSMiguel Ojeda        for line in fd:
278c4555ccSMiguel Ojeda            line = line.replace("--cfg=", "")
288c4555ccSMiguel Ojeda            line = line.replace("\n", "")
298c4555ccSMiguel Ojeda            cfg.append(line)
308c4555ccSMiguel Ojeda
318c4555ccSMiguel Ojeda    # Now fill the crates list -- dependencies need to come first.
328c4555ccSMiguel Ojeda    #
338c4555ccSMiguel Ojeda    # Avoid O(n^2) iterations by keeping a map of indexes.
348c4555ccSMiguel Ojeda    crates = []
358c4555ccSMiguel Ojeda    crates_indexes = {}
364f353e0dSMartin Rodriguez Reboredo    crates_cfgs = args_crates_cfgs(cfgs)
378c4555ccSMiguel Ojeda
388c4555ccSMiguel Ojeda    def append_crate(display_name, root_module, deps, cfg=[], is_workspace_member=True, is_proc_macro=False):
390730422bSTamir Duberstein        crate = {
408c4555ccSMiguel Ojeda            "display_name": display_name,
418c4555ccSMiguel Ojeda            "root_module": str(root_module),
428c4555ccSMiguel Ojeda            "is_workspace_member": is_workspace_member,
438c4555ccSMiguel Ojeda            "is_proc_macro": is_proc_macro,
448c4555ccSMiguel Ojeda            "deps": [{"crate": crates_indexes[dep], "name": dep} for dep in deps],
458c4555ccSMiguel Ojeda            "cfg": cfg,
468c4555ccSMiguel Ojeda            "edition": "2021",
478c4555ccSMiguel Ojeda            "env": {
488c4555ccSMiguel Ojeda                "RUST_MODFILE": "This is only for rust-analyzer"
498c4555ccSMiguel Ojeda            }
500730422bSTamir Duberstein        }
510730422bSTamir Duberstein        if is_proc_macro:
520730422bSTamir Duberstein            proc_macro_dylib_name = subprocess.check_output(
530730422bSTamir Duberstein                [os.environ["RUSTC"], "--print", "file-names", "--crate-name", display_name, "--crate-type", "proc-macro", "-"],
540730422bSTamir Duberstein                stdin=subprocess.DEVNULL,
550730422bSTamir Duberstein            ).decode('utf-8').strip()
560730422bSTamir Duberstein            crate["proc_macro_dylib_path"] = f"{objtree}/rust/{proc_macro_dylib_name}"
570730422bSTamir Duberstein        crates_indexes[display_name] = len(crates)
580730422bSTamir Duberstein        crates.append(crate)
598c4555ccSMiguel Ojeda
602e0f91abSTamir Duberstein    def append_sysroot_crate(
612e0f91abSTamir Duberstein        display_name,
622e0f91abSTamir Duberstein        deps,
632e0f91abSTamir Duberstein        cfg=[],
642e0f91abSTamir Duberstein    ):
658c4555ccSMiguel Ojeda        append_crate(
662e0f91abSTamir Duberstein            display_name,
672e0f91abSTamir Duberstein            sysroot_src / display_name / "src" / "lib.rs",
682e0f91abSTamir Duberstein            deps,
692e0f91abSTamir Duberstein            cfg,
708c4555ccSMiguel Ojeda            is_workspace_member=False,
718c4555ccSMiguel Ojeda        )
728c4555ccSMiguel Ojeda
732e0f91abSTamir Duberstein    # NB: sysroot crates reexport items from one another so setting up our transitive dependencies
742e0f91abSTamir Duberstein    # here is important for ensuring that rust-analyzer can resolve symbols. The sources of truth
752e0f91abSTamir Duberstein    # for this dependency graph are `(sysroot_src / crate / "Cargo.toml" for crate in crates)`.
762e0f91abSTamir Duberstein    append_sysroot_crate("core", [], cfg=crates_cfgs.get("core", []))
772e0f91abSTamir Duberstein    append_sysroot_crate("alloc", ["core"])
782e0f91abSTamir Duberstein    append_sysroot_crate("std", ["alloc", "core"])
792e0f91abSTamir Duberstein    append_sysroot_crate("proc_macro", ["core", "std"])
802e0f91abSTamir Duberstein
818c4555ccSMiguel Ojeda    append_crate(
828c4555ccSMiguel Ojeda        "compiler_builtins",
838c4555ccSMiguel Ojeda        srctree / "rust" / "compiler_builtins.rs",
848c4555ccSMiguel Ojeda        [],
858c4555ccSMiguel Ojeda    )
868c4555ccSMiguel Ojeda
878c4555ccSMiguel Ojeda    append_crate(
888c4555ccSMiguel Ojeda        "macros",
898c4555ccSMiguel Ojeda        srctree / "rust" / "macros" / "lib.rs",
902e0f91abSTamir Duberstein        ["std", "proc_macro"],
918c4555ccSMiguel Ojeda        is_proc_macro=True,
928c4555ccSMiguel Ojeda    )
938c4555ccSMiguel Ojeda
948c4555ccSMiguel Ojeda    append_crate(
95ecaa6ddfSGary Guo        "build_error",
96ecaa6ddfSGary Guo        srctree / "rust" / "build_error.rs",
97ecaa6ddfSGary Guo        ["core", "compiler_builtins"],
98ecaa6ddfSGary Guo    )
99ecaa6ddfSGary Guo
100ecaa6ddfSGary Guo    append_crate(
101d7659accSMiguel Ojeda        "pin_init_internal",
102dbd5058bSBenno Lossin        srctree / "rust" / "pin-init" / "internal" / "src" / "lib.rs",
103d7659accSMiguel Ojeda        [],
104d7659accSMiguel Ojeda        cfg=["kernel"],
105d7659accSMiguel Ojeda        is_proc_macro=True,
106d7659accSMiguel Ojeda    )
107d7659accSMiguel Ojeda
108d7659accSMiguel Ojeda    append_crate(
109d7659accSMiguel Ojeda        "pin_init",
110dbd5058bSBenno Lossin        srctree / "rust" / "pin-init" / "src" / "lib.rs",
111d7659accSMiguel Ojeda        ["core", "pin_init_internal", "macros"],
112d7659accSMiguel Ojeda        cfg=["kernel"],
113d7659accSMiguel Ojeda    )
114d7659accSMiguel Ojeda
115*05a2b001SLukas Fischer    append_crate(
116*05a2b001SLukas Fischer        "ffi",
117*05a2b001SLukas Fischer        srctree / "rust" / "ffi.rs",
118*05a2b001SLukas Fischer        ["core", "compiler_builtins"],
119*05a2b001SLukas Fischer    )
120*05a2b001SLukas Fischer
121d1f92805STamir Duberstein    def append_crate_with_generated(
122d1f92805STamir Duberstein        display_name,
123d1f92805STamir Duberstein        deps,
124d1f92805STamir Duberstein    ):
1258c4555ccSMiguel Ojeda        append_crate(
126d1f92805STamir Duberstein            display_name,
127d1f92805STamir Duberstein            srctree / "rust"/ display_name / "lib.rs",
128d1f92805STamir Duberstein            deps,
1298c4555ccSMiguel Ojeda            cfg=cfg,
1308c4555ccSMiguel Ojeda        )
1318c4555ccSMiguel Ojeda        crates[-1]["env"]["OBJTREE"] = str(objtree.resolve(True))
1328c4555ccSMiguel Ojeda        crates[-1]["source"] = {
1338c4555ccSMiguel Ojeda            "include_dirs": [
134d1f92805STamir Duberstein                str(srctree / "rust" / display_name),
1358c4555ccSMiguel Ojeda                str(objtree / "rust")
1368c4555ccSMiguel Ojeda            ],
1378c4555ccSMiguel Ojeda            "exclude_dirs": [],
1388c4555ccSMiguel Ojeda        }
1398c4555ccSMiguel Ojeda
140*05a2b001SLukas Fischer    append_crate_with_generated("bindings", ["core", "ffi"])
141*05a2b001SLukas Fischer    append_crate_with_generated("uapi", ["core", "ffi"])
142*05a2b001SLukas Fischer    append_crate_with_generated("kernel", ["core", "macros", "build_error", "pin_init", "ffi", "bindings", "uapi"])
143d1f92805STamir Duberstein
14449a9ef76SVinay Varma    def is_root_crate(build_file, target):
14549a9ef76SVinay Varma        try:
14649a9ef76SVinay Varma            return f"{target}.o" in open(build_file).read()
14749a9ef76SVinay Varma        except FileNotFoundError:
14849a9ef76SVinay Varma            return False
14949a9ef76SVinay Varma
1508c4555ccSMiguel Ojeda    # Then, the rest outside of `rust/`.
1518c4555ccSMiguel Ojeda    #
1528c4555ccSMiguel Ojeda    # We explicitly mention the top-level folders we want to cover.
15349a9ef76SVinay Varma    extra_dirs = map(lambda dir: srctree / dir, ("samples", "drivers"))
15449a9ef76SVinay Varma    if external_src is not None:
15549a9ef76SVinay Varma        extra_dirs = [external_src]
15649a9ef76SVinay Varma    for folder in extra_dirs:
15749a9ef76SVinay Varma        for path in folder.rglob("*.rs"):
1588c4555ccSMiguel Ojeda            logging.info("Checking %s", path)
1598c4555ccSMiguel Ojeda            name = path.name.replace(".rs", "")
1608c4555ccSMiguel Ojeda
1618c4555ccSMiguel Ojeda            # Skip those that are not crate roots.
16249a9ef76SVinay Varma            if not is_root_crate(path.parent / "Makefile", name) and \
16349a9ef76SVinay Varma               not is_root_crate(path.parent / "Kbuild", name):
1645c7548d5SAsahi Lina                continue
1658c4555ccSMiguel Ojeda
1668c4555ccSMiguel Ojeda            logging.info("Adding %s", name)
1678c4555ccSMiguel Ojeda            append_crate(
1688c4555ccSMiguel Ojeda                name,
1698c4555ccSMiguel Ojeda                path,
170392e34b6SDanilo Krummrich                ["core", "kernel"],
1718c4555ccSMiguel Ojeda                cfg=cfg,
1728c4555ccSMiguel Ojeda            )
1738c4555ccSMiguel Ojeda
1748c4555ccSMiguel Ojeda    return crates
1758c4555ccSMiguel Ojeda
1768c4555ccSMiguel Ojedadef main():
1778c4555ccSMiguel Ojeda    parser = argparse.ArgumentParser()
1788c4555ccSMiguel Ojeda    parser.add_argument('--verbose', '-v', action='store_true')
1794f353e0dSMartin Rodriguez Reboredo    parser.add_argument('--cfgs', action='append', default=[])
1808c4555ccSMiguel Ojeda    parser.add_argument("srctree", type=pathlib.Path)
1818c4555ccSMiguel Ojeda    parser.add_argument("objtree", type=pathlib.Path)
182fe992163SSarthak Singh    parser.add_argument("sysroot", type=pathlib.Path)
1838c4555ccSMiguel Ojeda    parser.add_argument("sysroot_src", type=pathlib.Path)
18449a9ef76SVinay Varma    parser.add_argument("exttree", type=pathlib.Path, nargs="?")
1858c4555ccSMiguel Ojeda    args = parser.parse_args()
1868c4555ccSMiguel Ojeda
1878c4555ccSMiguel Ojeda    logging.basicConfig(
1888c4555ccSMiguel Ojeda        format="[%(asctime)s] [%(levelname)s] %(message)s",
1898c4555ccSMiguel Ojeda        level=logging.INFO if args.verbose else logging.WARNING
1908c4555ccSMiguel Ojeda    )
1918c4555ccSMiguel Ojeda
192fe992163SSarthak Singh    # Making sure that the `sysroot` and `sysroot_src` belong to the same toolchain.
193fe992163SSarthak Singh    assert args.sysroot in args.sysroot_src.parents
194fe992163SSarthak Singh
1958c4555ccSMiguel Ojeda    rust_project = {
1964f353e0dSMartin Rodriguez Reboredo        "crates": generate_crates(args.srctree, args.objtree, args.sysroot_src, args.exttree, args.cfgs),
197fe992163SSarthak Singh        "sysroot": str(args.sysroot),
1988c4555ccSMiguel Ojeda    }
1998c4555ccSMiguel Ojeda
2008c4555ccSMiguel Ojeda    json.dump(rust_project, sys.stdout, sort_keys=True, indent=4)
2018c4555ccSMiguel Ojeda
2028c4555ccSMiguel Ojedaif __name__ == "__main__":
2038c4555ccSMiguel Ojeda    main()
204