Squashed 'third_party/rules_rust/' content from commit bf59038ca
git-subtree-dir: third_party/rules_rust
git-subtree-split: bf59038cac11798cbaef9f3bf965bad8182b97fa
Signed-off-by: Brian Silverman <bsilver16384@gmail.com>
Change-Id: I5a20e403203d670df467ea97dde9a4ac40339a8d
diff --git a/rust/private/rust_analyzer.bzl b/rust/private/rust_analyzer.bzl
new file mode 100644
index 0000000..e28ca30
--- /dev/null
+++ b/rust/private/rust_analyzer.bzl
@@ -0,0 +1,240 @@
+# Copyright 2020 Google
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Rust Analyzer Bazel rules.
+
+rust_analyzer will generate a rust-project.json file for the
+given targets. This file can be consumed by rust-analyzer as an alternative
+to Cargo.toml files.
+"""
+
+load("//rust/platform:triple_mappings.bzl", "system_to_dylib_ext", "triple_to_system")
+load("//rust/private:common.bzl", "rust_common")
+load("//rust/private:rustc.bzl", "BuildInfo")
+load("//rust/private:utils.bzl", "dedent", "find_toolchain")
+
+RustAnalyzerInfo = provider(
+ doc = "RustAnalyzerInfo holds rust crate metadata for targets",
+ fields = {
+ "build_info": "BuildInfo: build info for this crate if present",
+ "cfgs": "List[String]: features or other compilation --cfg settings",
+ "crate": "rust_common.crate_info",
+ "crate_specs": "Depset[File]: transitive closure of OutputGroupInfo files",
+ "deps": "List[RustAnalyzerInfo]: direct dependencies",
+ "env": "Dict{String: String}: Environment variables, used for the `env!` macro",
+ "proc_macro_dylib_path": "File: compiled shared library output of proc-macro rule",
+ },
+)
+
+def _rust_analyzer_aspect_impl(target, ctx):
+ if rust_common.crate_info not in target:
+ return []
+
+ toolchain = find_toolchain(ctx)
+
+ # Always add `test` & `debug_assertions`. See rust-analyzer source code:
+ # https://github.com/rust-analyzer/rust-analyzer/blob/2021-11-15/crates/project_model/src/workspace.rs#L529-L531
+ cfgs = ["test", "debug_assertions"]
+ if hasattr(ctx.rule.attr, "crate_features"):
+ cfgs += ['feature="{}"'.format(f) for f in ctx.rule.attr.crate_features]
+ if hasattr(ctx.rule.attr, "rustc_flags"):
+ cfgs += [f[6:] for f in ctx.rule.attr.rustc_flags if f.startswith("--cfg ") or f.startswith("--cfg=")]
+
+ # Save BuildInfo if we find any (for build script output)
+ build_info = None
+ for dep in ctx.rule.attr.deps:
+ if BuildInfo in dep:
+ build_info = dep[BuildInfo]
+
+ dep_infos = [dep[RustAnalyzerInfo] for dep in ctx.rule.attr.deps if RustAnalyzerInfo in dep]
+ if hasattr(ctx.rule.attr, "proc_macro_deps"):
+ dep_infos += [dep[RustAnalyzerInfo] for dep in ctx.rule.attr.proc_macro_deps if RustAnalyzerInfo in dep]
+ if hasattr(ctx.rule.attr, "crate") and ctx.rule.attr.crate != None:
+ dep_infos.append(ctx.rule.attr.crate[RustAnalyzerInfo])
+
+ crate_spec = ctx.actions.declare_file(ctx.label.name + ".rust_analyzer_crate_spec")
+
+ crate_info = target[rust_common.crate_info]
+
+ rust_analyzer_info = RustAnalyzerInfo(
+ crate = crate_info,
+ cfgs = cfgs,
+ env = getattr(ctx.rule.attr, "rustc_env", {}),
+ deps = dep_infos,
+ crate_specs = depset(direct = [crate_spec], transitive = [dep.crate_specs for dep in dep_infos]),
+ proc_macro_dylib_path = find_proc_macro_dylib_path(toolchain, target),
+ build_info = build_info,
+ )
+
+ ctx.actions.write(
+ output = crate_spec,
+ content = json.encode(_create_single_crate(ctx, rust_analyzer_info)),
+ )
+
+ return [
+ rust_analyzer_info,
+ OutputGroupInfo(rust_analyzer_crate_spec = rust_analyzer_info.crate_specs),
+ ]
+
+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.
+
+ 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.
+ """
+ if target[rust_common.crate_info].type != "proc-macro":
+ return None
+
+ dylib_ext = system_to_dylib_ext(triple_to_system(toolchain.target_triple))
+ for action in target.actions:
+ for output in action.outputs.to_list():
+ if output.extension == dylib_ext[1:]:
+ return output.path
+
+ # Failed to find the dylib path inside a proc-macro crate.
+ # TODO: Should this be an error?
+ return None
+
+rust_analyzer_aspect = aspect(
+ attr_aspects = ["deps", "proc_macro_deps", "crate"],
+ implementation = _rust_analyzer_aspect_impl,
+ toolchains = [str(Label("//rust:toolchain"))],
+ incompatible_use_toolchain_transition = True,
+ doc = "Annotates rust rules with RustAnalyzerInfo later used to build a rust-project.json",
+)
+
+_exec_root_tmpl = "__EXEC_ROOT__/"
+
+def _crate_id(crate_info):
+ """Returns a unique stable identifier for a crate
+
+ Returns:
+ (string): This crate's unique stable id.
+ """
+ return "ID-" + crate_info.root.path
+
+def _create_single_crate(ctx, info):
+ """Creates a crate in the rust-project.json format.
+
+ Args:
+ ctx (ctx): The rule context
+ info (RustAnalyzerInfo): RustAnalyzerInfo for the current crate
+
+ Returns:
+ (dict) The crate rust-project.json representation
+ """
+ crate_name = info.crate.name
+ crate = dict()
+ crate_id = _crate_id(info.crate)
+ crate["crate_id"] = crate_id
+ crate["display_name"] = crate_name
+ crate["edition"] = info.crate.edition
+ crate["env"] = {}
+ crate["crate_type"] = info.crate.type
+
+ # Switch on external/ to determine if crates are in the workspace or remote.
+ # TODO: Some folks may want to override this for vendored dependencies.
+ root_path = info.crate.root.path
+ root_dirname = info.crate.root.dirname
+ if root_path.startswith("external/"):
+ crate["is_workspace_member"] = False
+ crate["root_module"] = _exec_root_tmpl + root_path
+ crate_root = _exec_root_tmpl + root_dirname
+ else:
+ crate["is_workspace_member"] = True
+ crate["root_module"] = root_path
+ crate_root = root_dirname
+
+ if info.build_info != None:
+ out_dir_path = info.build_info.out_dir.path
+ crate["env"].update({"OUT_DIR": _exec_root_tmpl + out_dir_path})
+ crate["source"] = {
+ # We have to tell rust-analyzer about our out_dir since it's not under the crate root.
+ "exclude_dirs": [],
+ "include_dirs": [crate_root, _exec_root_tmpl + out_dir_path],
+ }
+
+ # TODO: The only imagined use case is an env var holding a filename in the workspace passed to a
+ # macro like include_bytes!. Other use cases might exist that require more complex logic.
+ expand_targets = getattr(ctx.rule.attr, "data", []) + getattr(ctx.rule.attr, "compile_data", [])
+ crate["env"].update({k: ctx.expand_location(v, expand_targets) for k, v in info.env.items()})
+
+ # Omit when a crate appears to depend on itself (e.g. foo_test crates).
+ # It can happen a single source file is present in multiple crates - there can
+ # be a `rust_library` with a `lib.rs` file, and a `rust_test` for the `test`
+ # module in that file. Tests can declare more dependencies than what library
+ # had. Therefore we had to collect all RustAnalyzerInfos for a given crate
+ # and take deps from all of them.
+
+ # There's one exception - if the dependency is the same crate name as the
+ # the crate being processed, we don't add it as a dependency to itself. This is
+ # common and expected - `rust_test.crate` pointing to the `rust_library`.
+ crate["deps"] = [_crate_id(dep.crate) for dep in info.deps if _crate_id(dep.crate) != crate_id]
+ crate["cfg"] = info.cfgs
+ crate["target"] = find_toolchain(ctx).target_triple
+ if info.proc_macro_dylib_path != None:
+ crate["proc_macro_dylib_path"] = _exec_root_tmpl + info.proc_macro_dylib_path
+ return crate
+
+def _rust_analyzer_detect_sysroot_impl(ctx):
+ rust_toolchain = find_toolchain(ctx)
+
+ if not rust_toolchain.rustc_srcs:
+ fail(
+ "Current Rust toolchain doesn't contain rustc sources in `rustc_srcs` attribute.",
+ "These are needed by rust analyzer.",
+ "If you are using the default Rust toolchain, add `rust_repositories(include_rustc_srcs = True, ...).` to your WORKSPACE file.",
+ )
+ sysroot_src = rust_toolchain.rustc_srcs.label.package + "/library"
+ if rust_toolchain.rustc_srcs.label.workspace_root:
+ sysroot_src = _exec_root_tmpl + rust_toolchain.rustc_srcs.label.workspace_root + "/" + sysroot_src
+
+ sysroot_src_file = ctx.actions.declare_file(ctx.label.name + ".rust_analyzer_sysroot_src")
+ ctx.actions.write(
+ output = sysroot_src_file,
+ content = sysroot_src,
+ )
+
+ return [DefaultInfo(files = depset([sysroot_src_file]))]
+
+rust_analyzer_detect_sysroot = rule(
+ implementation = _rust_analyzer_detect_sysroot_impl,
+ toolchains = ["@rules_rust//rust:toolchain"],
+ incompatible_use_toolchain_transition = True,
+ doc = dedent("""\
+ Detect the sysroot and store in a file for use by the gen_rust_project tool.
+ """),
+)
+
+def _rust_analyzer_impl(_ctx):
+ pass
+
+rust_analyzer = rule(
+ attrs = {
+ "targets": attr.label_list(
+ aspects = [rust_analyzer_aspect],
+ doc = "List of all targets to be included in the index",
+ ),
+ },
+ implementation = _rust_analyzer_impl,
+ toolchains = [str(Label("//rust:toolchain"))],
+ incompatible_use_toolchain_transition = True,
+ doc = dedent("""\
+ Deprecated: gen_rust_project can now create a rust-project.json without a rust_analyzer rule.
+ """),
+)