diff options
author | Dylan Baker <dylan@pnwbakers.com> | 2022-08-11 15:28:25 -0700 |
---|---|---|
committer | Eli Schwartz <eschwartz93@gmail.com> | 2022-09-12 18:51:27 -0400 |
commit | 9a645c1c5a3a30d48f5addc1e4d73d9144ac8356 (patch) | |
tree | a93588d0ba410dd835e0a6089e83d87a86eaa006 | |
parent | 0ef3c2b4366f0e6256d55b3e5967bde681b6d606 (diff) | |
download | meson-9a645c1c5a3a30d48f5addc1e4d73d9144ac8356.zip meson-9a645c1c5a3a30d48f5addc1e4d73d9144ac8356.tar.gz meson-9a645c1c5a3a30d48f5addc1e4d73d9144ac8356.tar.bz2 |
rust: Generate a rust-project.json file when rust targets are present
When at least one Rust target is present, we now generate a
rust-project.json file, which can be consumed by rust-analyzer. This is
placed in the build directory, and the editor must be configured to look
for this (as it is not a default search path).
-rw-r--r-- | docs/markdown/Rust.md | 11 | ||||
-rw-r--r-- | docs/markdown/snippets/rust_project_json.md | 5 | ||||
-rw-r--r-- | mesonbuild/backend/ninjabackend.py | 112 |
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: |