aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/markdown/Rust.md11
-rw-r--r--docs/markdown/snippets/rust_project_json.md5
-rw-r--r--mesonbuild/backend/ninjabackend.py112
3 files changed, 127 insertions, 1 deletions
diff --git a/docs/markdown/Rust.md b/docs/markdown/Rust.md
index 97a98dc..b7e36e3 100644
--- a/docs/markdown/Rust.md
+++ b/docs/markdown/Rust.md
@@ -54,3 +54,14 @@ executable(
)
)
```
+
+## Use with rust-analyzer
+
+*Since 0.64.0.*
+
+Meson will generate a `rust-project.json` file in the root of the build
+directory if there are any rust targets in the project. Most IDEs will need to
+be configured to use the file as it's not in the source root (Meson does not
+write files into the source directory). [See the upstream
+docs](https://rust-analyzer.github.io/manual.html#non-cargo-based-projects) for
+more information on how to configure that.
diff --git a/docs/markdown/snippets/rust_project_json.md b/docs/markdown/snippets/rust_project_json.md
new file mode 100644
index 0000000..e87a7db
--- /dev/null
+++ b/docs/markdown/snippets/rust_project_json.md
@@ -0,0 +1,5 @@
+## Generates rust-project.json when there are Rust targets
+
+This is a format similar to compile_commands.json, but specifically used by the
+official rust LSP, rust-analyzer. It is generated automatically if there are
+Rust targets, and is placed in the build directory.
diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py
index 872cbdf..45c0e9d 100644
--- a/mesonbuild/backend/ninjabackend.py
+++ b/mesonbuild/backend/ninjabackend.py
@@ -14,6 +14,7 @@
from __future__ import annotations
from collections import OrderedDict
+from dataclasses import dataclass
from enum import Enum, unique
from functools import lru_cache
from pathlib import PurePath, Path
@@ -52,12 +53,16 @@ from .backends import CleanTrees
from ..build import GeneratedList, InvalidArguments
if T.TYPE_CHECKING:
+ from typing_extensions import Literal
+
from .._typing import ImmutableListProtocol
from ..build import ExtractedObjects
from ..interpreter import Interpreter
from ..linkers import DynamicLinker, StaticLinker
from ..compilers.cs import CsCompiler
+ RUST_EDITIONS = Literal['2015', '2018', '2021']
+
FORTRAN_INCLUDE_PAT = r"^\s*#?include\s*['\"](\w+\.\w+)['\"]"
FORTRAN_MODULE_PAT = r"^\s*\bmodule\b\s+(\w+)\s*(?:!+.*)*$"
@@ -424,6 +429,56 @@ class NinjaBuildElement:
raise MesonException(f'Multiple producers for Ninja target "{n}". Please rename your targets.')
self.all_outputs[n] = True
+@dataclass
+class RustDep:
+
+ name: str
+
+ # equal to the order value of the `RustCrate`
+ crate: int
+
+ def to_json(self) -> T.Dict[str, object]:
+ return {
+ "crate": self.crate,
+ "name": self.name,
+ }
+
+@dataclass
+class RustCrate:
+
+ # When the json file is written, the list of Crates will be sorted by this
+ # value
+ order: int
+
+ display_name: str
+ root_module: str
+ edition: RUST_EDITIONS
+ deps: T.List[RustDep]
+ cfg: T.List[str]
+ is_proc_macro: bool
+
+ # This is set to True for members of this project, and False for all
+ # subprojects
+ is_workspace_member: bool
+ proc_macro_dylib_path: T.Optional[str] = None
+
+ def to_json(self) -> T.Dict[str, object]:
+ ret: T.Dict[str, object] = {
+ "display_name": self.display_name,
+ "root_module": self.root_module,
+ "edition": self.edition,
+ "cfg": self.cfg,
+ "is_proc_macro": self.is_proc_macro,
+ "deps": [d.to_json() for d in self.deps],
+ }
+
+ if self.is_proc_macro:
+ assert self.proc_macro_dylib_path is not None, "This shouldn't happen"
+ ret["proc_macro_dylib_path"] = self.proc_macro_dylib_path
+
+ return ret
+
+
class NinjaBackend(backends.Backend):
def __init__(self, build: T.Optional[build.Build], interpreter: T.Optional[Interpreter]):
@@ -434,6 +489,7 @@ class NinjaBackend(backends.Backend):
self.all_outputs = {}
self.introspection_data = {}
self.created_llvm_ir_rule = PerMachine(False, False)
+ self.rust_crates: T.Dict[str, RustCrate] = {}
def create_phony_target(self, all_outputs, dummy_outfile, rulename, phony_infilename, implicit_outs=None):
'''
@@ -593,6 +649,21 @@ class NinjaBackend(backends.Backend):
subprocess.call(self.ninja_command + ['-t', 'restat'])
subprocess.call(self.ninja_command + ['-t', 'cleandead'])
self.generate_compdb()
+ self.generate_rust_project_json()
+
+ def generate_rust_project_json(self) -> None:
+ """Generate a rust-analyzer compatible rust-project.json file."""
+ if not self.rust_crates:
+ return
+ with open(os.path.join(self.environment.get_build_dir(), 'rust-project.json'),
+ 'w', encoding='utf-8') as f:
+ json.dump(
+ {
+ "sysroot_src": os.path.join(self.environment.coredata.compilers.host['rust'].get_sysroot(),
+ 'lib/rustlib/src/rust/library/'),
+ "crates": [c.to_json() for c in self.rust_crates.values()],
+ },
+ f, indent=4)
# http://clang.llvm.org/docs/JSONCompilationDatabase.html
def generate_compdb(self):
@@ -1697,6 +1768,34 @@ class NinjaBackend(backends.Backend):
first_file = str(out)
return orderdeps, first_file
+ def _add_rust_project_entry(self, name: str, main_rust_file: str, args: CompilerArgs,
+ from_subproject: bool, is_proc_macro: bool,
+ output: str, deps: T.List[RustDep]) -> None:
+ raw_edition: T.Optional[str] = mesonlib.first(reversed(args), lambda x: x.startswith('--edition'))
+ edition: RUST_EDITIONS = '2015' if not raw_edition else raw_edition.split('=')[-1]
+
+ cfg: T.List[str] = []
+ arg_itr: T.Iterator[str] = iter(args)
+ for arg in arg_itr:
+ if arg == '--cfg':
+ cfg.append(next(arg))
+ elif arg.startswith('--cfg'):
+ cfg.append(arg[len('--cfg'):])
+
+ crate = RustCrate(
+ len(self.rust_crates),
+ name,
+ main_rust_file,
+ edition,
+ deps,
+ cfg,
+ is_workspace_member=not from_subproject,
+ is_proc_macro=is_proc_macro,
+ proc_macro_dylib_path=output if is_proc_macro else None,
+ )
+
+ self.rust_crates[name] = crate
+
def generate_rust_target(self, target: build.BuildTarget) -> None:
rustc = target.compilers['rust']
# Rust compiler takes only the main file as input and
@@ -1714,6 +1813,9 @@ class NinjaBackend(backends.Backend):
for t in itertools.chain(target.link_targets, target.link_whole_targets)
]
+ # Dependencies for rust-project.json
+ project_deps: T.List[RustDep] = []
+
orderdeps: T.List[str] = []
main_rust_file = None
@@ -1786,7 +1888,8 @@ class NinjaBackend(backends.Backend):
depfile = os.path.join(target.subdir, target.name + '.d')
args += ['--emit', f'dep-info={depfile}', '--emit', 'link']
args += target.get_extra_args('rust')
- args += rustc.get_output_args(os.path.join(target.subdir, target.get_filename()))
+ output = rustc.get_output_args(os.path.join(target.subdir, target.get_filename()))
+ args += output
linkdirs = mesonlib.OrderedSet()
external_deps = target.external_deps.copy()
for d in itertools.chain(target.link_targets, target.link_whole_targets):
@@ -1796,6 +1899,7 @@ class NinjaBackend(backends.Backend):
# dependency, so that collisions with libraries in rustc's
# sysroot don't cause ambiguity
args += ['--extern', '{}={}'.format(d.name, os.path.join(d.subdir, d.filename))]
+ project_deps.append(RustDep(d.name, self.rust_crates[d.name].order))
elif d.typename == 'static library':
# Rustc doesn't follow Meson's convention that static libraries
# are called .a, and import libraries are .lib, so we have to
@@ -1857,6 +1961,12 @@ class NinjaBackend(backends.Backend):
# installations
for rpath_arg in rpath_args:
args += ['-C', 'link-arg=' + rpath_arg + ':' + os.path.join(rustc.get_sysroot(), 'lib')]
+
+ self._add_rust_project_entry(target.name, main_rust_file, args, bool(target.subproject),
+ #XXX: There is a fix for this pending
+ getattr(target, 'rust_crate_type', '') == 'procmacro',
+ output, project_deps)
+
compiler_name = self.get_compiler_rule_name('rust', target.for_machine)
element = NinjaBuildElement(self.all_outputs, target_name, compiler_name, main_rust_file)
if orderdeps: