Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rust-analyzer discoverConfig integration #3073

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ internal_deps = use_extension("//rust/private:internal_extensions.bzl", "i")
use_repo(
internal_deps,
"rrra__anyhow-1.0.71",
"rrra__camino-1.1.9",
"rrra__clap-4.3.11",
"rrra__env_logger-0.10.0",
"rrra__itertools-0.11.0",
Expand Down
6 changes: 5 additions & 1 deletion extensions/prost/private/prost.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,9 @@ def _rust_prost_aspect_impl(target, ctx):
env = dep_variant_info.crate_info.rustc_env,
deps = depset([dep.id for dep in rust_analyzer_deps]).to_list(),
crate_specs = depset(transitive = [dep.crate_specs for dep in rust_analyzer_deps]),
proc_macro_dylib_path = None,
proc_macro_dylibs = depset(transitive = [dep.proc_macro_dylibs for dep in rust_analyzer_deps]),
build_info_out_dirs = depset(transitive = [dep.build_info_out_dirs for dep in rust_analyzer_deps]),
proc_macro_dylib = None,
build_info = dep_variant_info.build_info,
))

Expand Down Expand Up @@ -354,6 +356,8 @@ def _rust_prost_library_impl(ctx):
),
RustAnalyzerGroupInfo(
crate_specs = proto_dep[RustAnalyzerInfo].crate_specs,
proc_macro_dylibs = proto_dep[RustAnalyzerInfo].proc_macro_dylibs,
build_script_out_dirs = proto_dep[RustAnalyzerInfo].build_script_out_dirs,
deps = proto_dep[RustAnalyzerInfo].deps,
),
]
Expand Down
6 changes: 5 additions & 1 deletion rust/private/providers.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -162,17 +162,21 @@ RustAnalyzerInfo = provider(
"cfgs": "List[String]: features or other compilation `--cfg` settings",
"crate": "CrateInfo: Crate information.",
"crate_specs": "Depset[File]: transitive closure of OutputGroupInfo files",
"proc_macro_dylibs": "Depset[File]: transitive closure of OutputGroupInfo files",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should have clear names and comments (the comment on crate_specs is bad; maybe fix while here?)

"build_info_out_dirs": "Depset[File]: transitive closure of OutputGroupInfo files",
"deps": "List[String]: IDs of direct dependency crates",
"env": "Dict[String: String]: Environment variables, used for the `env!` macro",
"id": "String: Arbitrary unique ID for this crate",
"proc_macro_dylib_path": "File: compiled shared library output of proc-macro rule",
"proc_macro_dylib": "File: compiled shared library output of proc-macro rule",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: "if this is a proc-macro target, the shared-library output"?

(wasn't clear to me which macro, and "rule" is wrong here I think)

},
)

RustAnalyzerGroupInfo = provider(
doc = "RustAnalyzerGroupInfo holds multiple RustAnalyzerInfos",
fields = {
"crate_specs": "Depset[File]: transitive closure of OutputGroupInfo files",
"proc_macro_dylibs": "Depset[File]: transitive closure of OutputGroupInfo files",
"build_info_out_dirs": "Depset[File]: transitive closure of OutputGroupInfo files",
"deps": "List[String]: crate IDs of direct dependencies",
},
)
55 changes: 44 additions & 11 deletions rust/private/rust_analyzer.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ def write_rust_analyzer_spec_file(ctx, attrs, owner, base_info):
RustAnalyzerInfo: Info with the embedded spec file.
"""
crate_spec = ctx.actions.declare_file("{}.rust_analyzer_crate_spec.json".format(owner.name))
proc_macro_dylibs = [base_info.proc_macro_dylib] if base_info.proc_macro_dylib else None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function is just to write the file, everything with the RustAnalyzerInfo should be a pure passthrough.

(This function is ugly and there's probably a way to get rid of it, but failing that it should at least stay trivial)

build_info_out_dirs = [base_info.build_info.out_dir] if base_info.build_info != None and base_info.build_info.out_dir != None else None

# Recreate the provider with the spec file embedded in it.
rust_analyzer_info = RustAnalyzerInfo(
Expand All @@ -55,7 +57,9 @@ def write_rust_analyzer_spec_file(ctx, attrs, owner, base_info):
deps = base_info.deps,
id = base_info.id,
crate_specs = depset(direct = [crate_spec], transitive = [base_info.crate_specs]),
proc_macro_dylib_path = base_info.proc_macro_dylib_path,
proc_macro_dylibs = depset(direct = proc_macro_dylibs, transitive = [base_info.proc_macro_dylibs]),
build_info_out_dirs = depset(direct = build_info_out_dirs, transitive = [base_info.build_info_out_dirs]),
proc_macro_dylib = base_info.proc_macro_dylib,
build_info = base_info.build_info,
)

Expand Down Expand Up @@ -101,23 +105,36 @@ def _rust_analyzer_aspect_impl(target, ctx):
# Gather required info from dependencies.
label_to_id = {} # {Label of dependency => crate_id}
crate_specs = [] # [depset of File - transitive crate_spec.json files]
proc_macro_dylibs = [] # [depset of File - transitive crate_spec.json files]
build_script_out_dirs = [] # [depset of File - transitive crate_spec.json files]
attrs = ctx.rule.attr
all_deps = getattr(attrs, "deps", []) + getattr(attrs, "proc_macro_deps", []) + \
[dep for dep in [getattr(attrs, "crate", None), getattr(attrs, "actual", None)] if dep != None]
for dep in all_deps:
if RustAnalyzerInfo in dep:
label_to_id[dep.label] = dep[RustAnalyzerInfo].id
crate_specs.append(dep[RustAnalyzerInfo].crate_specs)
proc_macro_dylibs.append(dep[RustAnalyzerInfo].proc_macro_dylibs)
build_script_out_dirs.append(dep[RustAnalyzerInfo].build_info_out_dirs)
if RustAnalyzerGroupInfo in dep:
for expanded_dep in dep[RustAnalyzerGroupInfo].deps:
label_to_id[expanded_dep] = expanded_dep
crate_specs.append(dep[RustAnalyzerGroupInfo].crate_specs)
proc_macro_dylibs.append(dep[RustAnalyzerGroupInfo].proc_macro_dylibs)
build_script_out_dirs.append(dep[RustAnalyzerGroupInfo].build_info_out_dirs)

deps = label_to_id.values()
crate_specs = depset(transitive = crate_specs)
proc_macro_dylibs = depset(transitive = proc_macro_dylibs)
build_script_out_dirs = depset(transitive = build_script_out_dirs)

if rust_common.crate_group_info in target:
return [RustAnalyzerGroupInfo(deps = deps, crate_specs = crate_specs)]
return [RustAnalyzerGroupInfo(
deps = deps,
crate_specs = crate_specs,
proc_macro_dylibs = proc_macro_dylibs,
build_info_out_dirs = build_script_out_dirs,
)]
elif rust_common.crate_info in target:
crate_info = target[rust_common.crate_info]
elif rust_common.test_crate_info in target:
Expand All @@ -134,6 +151,8 @@ def _rust_analyzer_aspect_impl(target, ctx):
# An arbitrary unique and stable identifier.
crate_id = "ID-" + crate_info.root.path

proc_macro_dylib = find_proc_macro_dylib(toolchain, target)

rust_analyzer_info = write_rust_analyzer_spec_file(ctx, ctx.rule.attr, ctx.label, RustAnalyzerInfo(
id = crate_id,
aliases = aliases,
Expand All @@ -142,23 +161,29 @@ def _rust_analyzer_aspect_impl(target, ctx):
env = crate_info.rustc_env,
deps = deps,
crate_specs = crate_specs,
proc_macro_dylib_path = find_proc_macro_dylib_path(toolchain, target),
proc_macro_dylibs = proc_macro_dylibs,
build_info_out_dirs = build_script_out_dirs,
proc_macro_dylib = proc_macro_dylib,
build_info = build_info,
))

return [
rust_analyzer_info,
OutputGroupInfo(rust_analyzer_crate_spec = rust_analyzer_info.crate_specs),
OutputGroupInfo(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think rust_analyzer_build_info_out_dirs is really about ensuring generated sources are available, and should be named as such: rust_analyzer_srcs. See https://github.com/bazelbuild/rules_rust/pull/3031/files. These don't need to be distinct output groups as from the caller's point of view it doesn't make sense to want one without the other.

Having three output groups does make sense to me though: crate specs are the bare minimum and I can imagine producing the rest asynchronously, and proc macros are optional (we use a toolchain that doesn't even have the proc-macro-srv...)

rust_analyzer_crate_spec = rust_analyzer_info.crate_specs,
rust_analyzer_proc_macro_dylibs = rust_analyzer_info.proc_macro_dylibs,
rust_analyzer_build_info_out_dirs = rust_analyzer_info.build_info_out_dirs,
),
]

def find_proc_macro_dylib_path(toolchain, target):
"""Find the proc_macro_dylib_path of target. Returns None if target crate is not type proc-macro.
def find_proc_macro_dylib(toolchain, target):
"""Find the proc_macro_dylib of target. Returns None if target crate is not type proc-macro.

Args:
toolchain: The current rust toolchain.
target: The current target.
Returns:
(path): The path to the proc macro dylib, or None if this crate is not a proc-macro.
(File): The path to the proc macro dylib, or None if this crate is not a proc-macro.
"""
if rust_common.crate_info in target:
crate_info = target[rust_common.crate_info]
Expand All @@ -174,7 +199,7 @@ def find_proc_macro_dylib_path(toolchain, target):
for action in target.actions:
for output in action.outputs.to_list():
if output.extension == dylib_ext[1:]:
return output.path
return output

# Failed to find the dylib path inside a proc-macro crate.
# TODO: Should this be an error?
Expand All @@ -188,7 +213,7 @@ rust_analyzer_aspect = aspect(
)

# Paths in the generated JSON file begin with one of these placeholders.
# The gen_rust_project driver will replace them with absolute paths.
# The `rust-analyzer` driver will replace them with absolute paths.
_WORKSPACE_TEMPLATE = "__WORKSPACE__/"
_EXEC_ROOT_TEMPLATE = "__EXEC_ROOT__/"
_OUTPUT_BASE_TEMPLATE = "__OUTPUT_BASE__/"
Expand All @@ -211,6 +236,7 @@ def _create_single_crate(ctx, attrs, info):
crate["edition"] = info.crate.edition
crate["env"] = {}
crate["crate_type"] = info.crate.type
crate["is_test"] = info.crate.is_test

# Switch on external/ to determine if crates are in the workspace or remote.
# TODO: Some folks may want to override this for vendored dependencies.
Expand All @@ -221,6 +247,13 @@ def _create_single_crate(ctx, attrs, info):
crate["root_module"] = path_prefix + info.crate.root.path
crate["source"] = {"exclude_dirs": [], "include_dirs": []}

# Store build system related info only for local crates
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this comment echoes the code, can you say why instead?

To me it's not obvious why, but I guess it's to prevent blaze test runnables from showing up for other crates? But is "local" the right choice there, rather than "top-level" i.e. targets from the selected blaze package?

if not is_external and not is_generated:
crate["build"] = {
"label": ctx.label.package + ":" + ctx.label.name,
"build_file": _WORKSPACE_TEMPLATE + ctx.build_file_path,
}

if is_generated:
srcs = getattr(ctx.rule.files, "srcs", [])
src_map = {src.short_path: src for src in srcs if src.is_source}
Expand Down Expand Up @@ -259,8 +292,8 @@ def _create_single_crate(ctx, attrs, info):
crate["cfg"] = info.cfgs
toolchain = find_toolchain(ctx)
crate["target"] = (_EXEC_ROOT_TEMPLATE + toolchain.target_json.path) if toolchain.target_json else toolchain.target_flag_value
if info.proc_macro_dylib_path != None:
crate["proc_macro_dylib_path"] = _EXEC_ROOT_TEMPLATE + info.proc_macro_dylib_path
if info.proc_macro_dylib != None:
crate["proc_macro_dylib_path"] = _EXEC_ROOT_TEMPLATE + info.proc_macro_dylib.path
return crate

def _rust_analyzer_toolchain_impl(ctx):
Expand Down
4 changes: 4 additions & 0 deletions tools/rust_analyzer/3rdparty/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ crates_vendor(
],
version = "4.3.11",
),
"camino": crate.spec(
features = ["serde1"],
version = "1.1.9",
),
"env_logger": crate.spec(
version = "0.10.0",
),
Expand Down
12 changes: 11 additions & 1 deletion tools/rust_analyzer/3rdparty/Cargo.Bazel.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions tools/rust_analyzer/3rdparty/crates/BUILD.bazel

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

133 changes: 133 additions & 0 deletions tools/rust_analyzer/3rdparty/crates/BUILD.camino-1.1.9.bazel

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading