Skip to content

Commit bbe3257

Browse files
kloenkBobo1239ojeda
committed
add rust-analyzer make target
This target creates ${objtree}/rust-project.json which can be read by rust-analyzer to help with autocompletion for the kernel crate. The raw bindings do not work yet, as rust-analyzer seems to have a problem with the include macro. Co-authored-by: Boris-Chengbiao Zhou <[email protected]> Co-authored-by: Miguel Ojeda <[email protected]> Signed-off-by: Finn Behrens <[email protected]>
1 parent 2222bec commit bbe3257

File tree

5 files changed

+146
-40
lines changed

5 files changed

+146
-40
lines changed

Documentation/rust/quick-start.rst

+2-5
Original file line numberDiff line numberDiff line change
@@ -155,11 +155,8 @@ definition, and other features.
155155
``rust-analyzer`` will need to be
156156
`configured <https://rust-analyzer.github.io/manual.html#non-cargo-based-projects>`_
157157
to work with the kernel by adding a ``rust-project.json`` file in the root folder.
158-
The example ``Documentation/rust/rust-project.json`` can
159-
be used after updating ``sysroot_src`` and including the relevant modules.
160-
The path to ``sysroot_src`` is given by::
161-
162-
$(rustc --print sysroot)/lib/rustlib/src/rust/library
158+
A ``rust-project.json`` can be generated by building the Make target ``rust-analyzer``,
159+
which will create a ``rust-project.json`` in the root of the output directory.
163160

164161

165162
Configuration

Documentation/rust/rust-project.json

-35
This file was deleted.

Makefile

+6
Original file line numberDiff line numberDiff line change
@@ -1738,6 +1738,8 @@ help:
17381738
@echo ' is formatted, printing a diff otherwise.'
17391739
@echo ' rustdoc - Generate Rust documentation'
17401740
@echo ' (requires kernel .config)'
1741+
@echo ' rust-analyzer - Generate rust-project.json rust-analyzer support file'
1742+
@echo ' (requires kernel .config)'
17411743
@echo ''
17421744
@$(if $(dtstree), \
17431745
echo 'Devicetree:'; \
@@ -1830,6 +1832,10 @@ rustfmt:
18301832
rustfmtcheck:
18311833
find -name '*.rs' | xargs $(RUSTFMT) --check
18321834

1835+
# IDE support targets
1836+
PHONY += rust-analyzer
1837+
rust-analyzer: prepare0
1838+
$(Q)$(MAKE) $(build)=rust $@
18331839

18341840
# Misc
18351841
# ---------------------------------------------------------------------------

rust/Makefile

+3
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,9 @@ quiet_cmd_rustc_library = $(if $(skip_clippy),RUSTC,$(RUSTC_OR_CLIPPY_QUIET)) L
129129
rustc_sysroot = $(shell $(RUSTC) $(rustc_flags) --print sysroot)
130130
RUST_LIB_SRC ?= $(rustc_sysroot)/lib/rustlib/src/rust/library
131131

132+
rust-analyzer:
133+
$(Q)$(srctree)/scripts/generate_rust_analyzer.py $(srctree) $(objtree) $(RUST_LIB_SRC) $(objtree)/rust/bindings_generated.rs > $(objtree)/rust-project.json
134+
132135
.SECONDEXPANSION:
133136
$(objtree)/rust/core.o: private skip_clippy = 1
134137
$(objtree)/rust/core.o: $$(RUST_LIB_SRC)/core/src/lib.rs FORCE

scripts/generate_rust_analyzer.py

+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
#!/usr/bin/env python3
2+
"""generate_rust_analyzer - Generates the `rust-project.json` file for `rust-analyzer`.
3+
"""
4+
5+
import argparse
6+
import json
7+
import logging
8+
import pathlib
9+
import sys
10+
11+
def generate_crates(srctree, objtree, sysroot_src, bindings_file):
12+
# Generate the configuration list.
13+
cfg = []
14+
with open(objtree / "include" / "generated" / "rustc_cfg") as fd:
15+
for line in fd:
16+
line = line.replace("--cfg=", "")
17+
line = line.replace("\n", "")
18+
cfg.append(line)
19+
20+
# Now fill the crates list -- dependencies need to come first.
21+
#
22+
# Avoid O(n^2) iterations by keeping a map of indexes.
23+
crates = []
24+
crates_indexes = {}
25+
26+
def append_crate(display_name, root_module, is_workspace_member, deps, cfg):
27+
crates_indexes[display_name] = len(crates)
28+
crates.append({
29+
"display_name": display_name,
30+
"root_module": str(root_module),
31+
"is_workspace_member": is_workspace_member,
32+
"deps": [{"crate": crates_indexes[dep], "name": dep} for dep in deps],
33+
"cfg": cfg,
34+
"edition": "2018",
35+
"env": {
36+
"RUST_MODFILE": "This is only for rust-analyzer"
37+
}
38+
})
39+
40+
# First, the ones in `rust/` since they are a bit special.
41+
append_crate(
42+
"core",
43+
sysroot_src / "core" / "src" / "lib.rs",
44+
False,
45+
[],
46+
[],
47+
)
48+
49+
append_crate(
50+
"compiler_builtins",
51+
srctree / "rust" / "compiler_builtins.rs",
52+
True,
53+
[],
54+
[],
55+
)
56+
57+
append_crate(
58+
"alloc",
59+
sysroot_src / "alloc" / "src" / "lib.rs",
60+
False,
61+
["core", "compiler_builtins"],
62+
[],
63+
)
64+
65+
append_crate(
66+
"module",
67+
srctree / "rust" / "module.rs",
68+
True,
69+
[],
70+
[],
71+
)
72+
crates[-1]["proc_macro_dylib_path"] = "rust/libmodule.so"
73+
74+
append_crate(
75+
"kernel",
76+
srctree / "rust" / "kernel" / "lib.rs",
77+
True,
78+
["core", "alloc", "module"],
79+
cfg,
80+
)
81+
crates[-1]["env"]["RUST_BINDINGS_FILE"] = str(bindings_file.resolve(True))
82+
crates[-1]["source"] = {
83+
"include_dirs": [
84+
str(srctree / "rust" / "kernel"),
85+
str(objtree / "rust")
86+
],
87+
"exclude_dirs": [],
88+
}
89+
90+
# Then, the rest outside of `rust/`.
91+
#
92+
# We explicitly mention the top-level folders we want to cover.
93+
for folder in ("samples", "drivers"):
94+
for path in (srctree / folder).rglob("*.rs"):
95+
logging.info("Checking %s", path)
96+
name = path.name.replace(".rs", "")
97+
98+
# Skip those that are not crate roots.
99+
if f"{name}.o" not in open(path.parent / "Makefile").read():
100+
continue
101+
102+
logging.info("Adding %s", name)
103+
append_crate(
104+
name,
105+
path,
106+
True,
107+
["core", "alloc", "kernel"],
108+
cfg,
109+
)
110+
111+
return crates
112+
113+
def main():
114+
parser = argparse.ArgumentParser()
115+
parser.add_argument('--verbose', '-v', action='store_true')
116+
parser.add_argument("srctree", type=pathlib.Path)
117+
parser.add_argument("objtree", type=pathlib.Path)
118+
parser.add_argument("sysroot_src", type=pathlib.Path)
119+
parser.add_argument("bindings_file", type=pathlib.Path)
120+
args = parser.parse_args()
121+
122+
logging.basicConfig(
123+
format="[%(asctime)s] [%(levelname)s] %(message)s",
124+
level=logging.INFO if args.verbose else logging.WARNING
125+
)
126+
127+
rust_project = {
128+
"crates": generate_crates(args.srctree, args.objtree, args.sysroot_src, args.bindings_file),
129+
"sysroot_src": str(args.sysroot_src),
130+
}
131+
132+
json.dump(rust_project, sys.stdout, sort_keys=True, indent=4)
133+
134+
if __name__ == "__main__":
135+
main()

0 commit comments

Comments
 (0)