xref: /aosp_15_r20/external/bazelbuild-rules_rust/rust/private/rust_analyzer.bzl (revision d4726bddaa87cc4778e7472feed243fa4b6c267f)
1*d4726bddSHONG Yifan# Copyright 2020 Google
2*d4726bddSHONG Yifan#
3*d4726bddSHONG Yifan# Licensed under the Apache License, Version 2.0 (the "License");
4*d4726bddSHONG Yifan# you may not use this file except in compliance with the License.
5*d4726bddSHONG Yifan# You may obtain a copy of the License at
6*d4726bddSHONG Yifan#
7*d4726bddSHONG Yifan#     http://www.apache.org/licenses/LICENSE-2.0
8*d4726bddSHONG Yifan#
9*d4726bddSHONG Yifan# Unless required by applicable law or agreed to in writing, software
10*d4726bddSHONG Yifan# distributed under the License is distributed on an "AS IS" BASIS,
11*d4726bddSHONG Yifan# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*d4726bddSHONG Yifan# See the License for the specific language governing permissions and
13*d4726bddSHONG Yifan# limitations under the License.
14*d4726bddSHONG Yifan
15*d4726bddSHONG Yifan"""
16*d4726bddSHONG YifanRust Analyzer Bazel rules.
17*d4726bddSHONG Yifan
18*d4726bddSHONG Yifanrust_analyzer will generate a rust-project.json file for the
19*d4726bddSHONG Yifangiven targets. This file can be consumed by rust-analyzer as an alternative
20*d4726bddSHONG Yifanto Cargo.toml files.
21*d4726bddSHONG Yifan"""
22*d4726bddSHONG Yifan
23*d4726bddSHONG Yifanload("//rust/platform:triple_mappings.bzl", "system_to_dylib_ext", "triple_to_system")
24*d4726bddSHONG Yifanload("//rust/private:common.bzl", "rust_common")
25*d4726bddSHONG Yifanload("//rust/private:providers.bzl", "RustAnalyzerGroupInfo", "RustAnalyzerInfo")
26*d4726bddSHONG Yifanload("//rust/private:rustc.bzl", "BuildInfo")
27*d4726bddSHONG Yifanload(
28*d4726bddSHONG Yifan    "//rust/private:utils.bzl",
29*d4726bddSHONG Yifan    "concat",
30*d4726bddSHONG Yifan    "dedent",
31*d4726bddSHONG Yifan    "dedup_expand_location",
32*d4726bddSHONG Yifan    "find_toolchain",
33*d4726bddSHONG Yifan)
34*d4726bddSHONG Yifan
35*d4726bddSHONG Yifandef write_rust_analyzer_spec_file(ctx, attrs, owner, base_info):
36*d4726bddSHONG Yifan    """Write a rust-analyzer spec info file.
37*d4726bddSHONG Yifan
38*d4726bddSHONG Yifan    Args:
39*d4726bddSHONG Yifan        ctx (ctx): The current rule's context object.
40*d4726bddSHONG Yifan        attrs (dict): A mapping of attributes.
41*d4726bddSHONG Yifan        owner (Label): The label of the owner of the spec info.
42*d4726bddSHONG Yifan        base_info (RustAnalyzerInfo): The data the resulting RustAnalyzerInfo is based on.
43*d4726bddSHONG Yifan
44*d4726bddSHONG Yifan    Returns:
45*d4726bddSHONG Yifan        RustAnalyzerInfo: Info with the embedded spec file.
46*d4726bddSHONG Yifan    """
47*d4726bddSHONG Yifan    crate_spec = ctx.actions.declare_file("{}.rust_analyzer_crate_spec.json".format(owner.name))
48*d4726bddSHONG Yifan
49*d4726bddSHONG Yifan    # Recreate the provider with the spec file embedded in it.
50*d4726bddSHONG Yifan    rust_analyzer_info = RustAnalyzerInfo(
51*d4726bddSHONG Yifan        aliases = base_info.aliases,
52*d4726bddSHONG Yifan        crate = base_info.crate,
53*d4726bddSHONG Yifan        cfgs = base_info.cfgs,
54*d4726bddSHONG Yifan        env = base_info.env,
55*d4726bddSHONG Yifan        deps = base_info.deps,
56*d4726bddSHONG Yifan        crate_specs = depset(direct = [crate_spec], transitive = [base_info.crate_specs]),
57*d4726bddSHONG Yifan        proc_macro_dylib_path = base_info.proc_macro_dylib_path,
58*d4726bddSHONG Yifan        build_info = base_info.build_info,
59*d4726bddSHONG Yifan    )
60*d4726bddSHONG Yifan
61*d4726bddSHONG Yifan    ctx.actions.write(
62*d4726bddSHONG Yifan        output = crate_spec,
63*d4726bddSHONG Yifan        content = json.encode_indent(
64*d4726bddSHONG Yifan            _create_single_crate(
65*d4726bddSHONG Yifan                ctx,
66*d4726bddSHONG Yifan                attrs,
67*d4726bddSHONG Yifan                rust_analyzer_info,
68*d4726bddSHONG Yifan            ),
69*d4726bddSHONG Yifan            indent = " " * 4,
70*d4726bddSHONG Yifan        ),
71*d4726bddSHONG Yifan    )
72*d4726bddSHONG Yifan
73*d4726bddSHONG Yifan    return rust_analyzer_info
74*d4726bddSHONG Yifan
75*d4726bddSHONG Yifandef _accumulate_rust_analyzer_info(dep_infos_to_accumulate, label_index_to_accumulate, dep):
76*d4726bddSHONG Yifan    if dep == None:
77*d4726bddSHONG Yifan        return
78*d4726bddSHONG Yifan    if RustAnalyzerInfo in dep:
79*d4726bddSHONG Yifan        label_index_to_accumulate[dep.label] = dep[RustAnalyzerInfo]
80*d4726bddSHONG Yifan        dep_infos_to_accumulate.append(dep[RustAnalyzerInfo])
81*d4726bddSHONG Yifan    if RustAnalyzerGroupInfo in dep:
82*d4726bddSHONG Yifan        for expanded_dep in dep[RustAnalyzerGroupInfo].deps:
83*d4726bddSHONG Yifan            label_index_to_accumulate[expanded_dep.crate.owner] = expanded_dep
84*d4726bddSHONG Yifan            dep_infos_to_accumulate.append(expanded_dep)
85*d4726bddSHONG Yifan
86*d4726bddSHONG Yifandef _accumulate_rust_analyzer_infos(dep_infos_to_accumulate, label_index_to_accumulate, deps_attr):
87*d4726bddSHONG Yifan    for dep in deps_attr:
88*d4726bddSHONG Yifan        _accumulate_rust_analyzer_info(dep_infos_to_accumulate, label_index_to_accumulate, dep)
89*d4726bddSHONG Yifan
90*d4726bddSHONG Yifandef _rust_analyzer_aspect_impl(target, ctx):
91*d4726bddSHONG Yifan    if (rust_common.crate_info not in target and
92*d4726bddSHONG Yifan        rust_common.test_crate_info not in target and
93*d4726bddSHONG Yifan        rust_common.crate_group_info not in target):
94*d4726bddSHONG Yifan        return []
95*d4726bddSHONG Yifan
96*d4726bddSHONG Yifan    if RustAnalyzerInfo in target or RustAnalyzerGroupInfo in target:
97*d4726bddSHONG Yifan        return []
98*d4726bddSHONG Yifan
99*d4726bddSHONG Yifan    toolchain = find_toolchain(ctx)
100*d4726bddSHONG Yifan
101*d4726bddSHONG Yifan    # Always add `test` & `debug_assertions`. See rust-analyzer source code:
102*d4726bddSHONG Yifan    # https://github.com/rust-analyzer/rust-analyzer/blob/2021-11-15/crates/project_model/src/workspace.rs#L529-L531
103*d4726bddSHONG Yifan    cfgs = ["test", "debug_assertions"]
104*d4726bddSHONG Yifan    if hasattr(ctx.rule.attr, "crate_features"):
105*d4726bddSHONG Yifan        cfgs += ['feature="{}"'.format(f) for f in ctx.rule.attr.crate_features]
106*d4726bddSHONG Yifan    if hasattr(ctx.rule.attr, "rustc_flags"):
107*d4726bddSHONG Yifan        cfgs += [f[6:] for f in ctx.rule.attr.rustc_flags if f.startswith("--cfg ") or f.startswith("--cfg=")]
108*d4726bddSHONG Yifan
109*d4726bddSHONG Yifan    build_info = None
110*d4726bddSHONG Yifan    dep_infos = []
111*d4726bddSHONG Yifan    labels_to_rais = {}
112*d4726bddSHONG Yifan
113*d4726bddSHONG Yifan    for dep in getattr(ctx.rule.attr, "deps", []):
114*d4726bddSHONG Yifan        # Save BuildInfo if we find any (for build script output)
115*d4726bddSHONG Yifan        if BuildInfo in dep:
116*d4726bddSHONG Yifan            build_info = dep[BuildInfo]
117*d4726bddSHONG Yifan
118*d4726bddSHONG Yifan    _accumulate_rust_analyzer_infos(dep_infos, labels_to_rais, getattr(ctx.rule.attr, "deps", []))
119*d4726bddSHONG Yifan    _accumulate_rust_analyzer_infos(dep_infos, labels_to_rais, getattr(ctx.rule.attr, "proc_macro_deps", []))
120*d4726bddSHONG Yifan
121*d4726bddSHONG Yifan    _accumulate_rust_analyzer_info(dep_infos, labels_to_rais, getattr(ctx.rule.attr, "crate", None))
122*d4726bddSHONG Yifan    _accumulate_rust_analyzer_info(dep_infos, labels_to_rais, getattr(ctx.rule.attr, "actual", None))
123*d4726bddSHONG Yifan
124*d4726bddSHONG Yifan    if rust_common.crate_group_info in target:
125*d4726bddSHONG Yifan        return [RustAnalyzerGroupInfo(deps = dep_infos)]
126*d4726bddSHONG Yifan    elif rust_common.crate_info in target:
127*d4726bddSHONG Yifan        crate_info = target[rust_common.crate_info]
128*d4726bddSHONG Yifan    elif rust_common.test_crate_info in target:
129*d4726bddSHONG Yifan        crate_info = target[rust_common.test_crate_info].crate
130*d4726bddSHONG Yifan    else:
131*d4726bddSHONG Yifan        fail("Unexpected target type: {}".format(target))
132*d4726bddSHONG Yifan
133*d4726bddSHONG Yifan    aliases = {}
134*d4726bddSHONG Yifan    for aliased_target, aliased_name in getattr(ctx.rule.attr, "aliases", {}).items():
135*d4726bddSHONG Yifan        if aliased_target.label in labels_to_rais:
136*d4726bddSHONG Yifan            aliases[labels_to_rais[aliased_target.label]] = aliased_name
137*d4726bddSHONG Yifan
138*d4726bddSHONG Yifan    rust_analyzer_info = write_rust_analyzer_spec_file(ctx, ctx.rule.attr, ctx.label, RustAnalyzerInfo(
139*d4726bddSHONG Yifan        aliases = aliases,
140*d4726bddSHONG Yifan        crate = crate_info,
141*d4726bddSHONG Yifan        cfgs = cfgs,
142*d4726bddSHONG Yifan        env = crate_info.rustc_env,
143*d4726bddSHONG Yifan        deps = dep_infos,
144*d4726bddSHONG Yifan        crate_specs = depset(transitive = [dep.crate_specs for dep in dep_infos]),
145*d4726bddSHONG Yifan        proc_macro_dylib_path = find_proc_macro_dylib_path(toolchain, target),
146*d4726bddSHONG Yifan        build_info = build_info,
147*d4726bddSHONG Yifan    ))
148*d4726bddSHONG Yifan
149*d4726bddSHONG Yifan    return [
150*d4726bddSHONG Yifan        rust_analyzer_info,
151*d4726bddSHONG Yifan        OutputGroupInfo(rust_analyzer_crate_spec = rust_analyzer_info.crate_specs),
152*d4726bddSHONG Yifan    ]
153*d4726bddSHONG Yifan
154*d4726bddSHONG Yifandef find_proc_macro_dylib_path(toolchain, target):
155*d4726bddSHONG Yifan    """Find the proc_macro_dylib_path of target. Returns None if target crate is not type proc-macro.
156*d4726bddSHONG Yifan
157*d4726bddSHONG Yifan    Args:
158*d4726bddSHONG Yifan        toolchain: The current rust toolchain.
159*d4726bddSHONG Yifan        target: The current target.
160*d4726bddSHONG Yifan    Returns:
161*d4726bddSHONG Yifan        (path): The path to the proc macro dylib, or None if this crate is not a proc-macro.
162*d4726bddSHONG Yifan    """
163*d4726bddSHONG Yifan    if rust_common.crate_info in target:
164*d4726bddSHONG Yifan        crate_info = target[rust_common.crate_info]
165*d4726bddSHONG Yifan    elif rust_common.test_crate_info in target:
166*d4726bddSHONG Yifan        crate_info = target[rust_common.test_crate_info].crate
167*d4726bddSHONG Yifan    else:
168*d4726bddSHONG Yifan        return None
169*d4726bddSHONG Yifan
170*d4726bddSHONG Yifan    if crate_info.type != "proc-macro":
171*d4726bddSHONG Yifan        return None
172*d4726bddSHONG Yifan
173*d4726bddSHONG Yifan    dylib_ext = system_to_dylib_ext(triple_to_system(toolchain.target_triple))
174*d4726bddSHONG Yifan    for action in target.actions:
175*d4726bddSHONG Yifan        for output in action.outputs.to_list():
176*d4726bddSHONG Yifan            if output.extension == dylib_ext[1:]:
177*d4726bddSHONG Yifan                return output.path
178*d4726bddSHONG Yifan
179*d4726bddSHONG Yifan    # Failed to find the dylib path inside a proc-macro crate.
180*d4726bddSHONG Yifan    # TODO: Should this be an error?
181*d4726bddSHONG Yifan    return None
182*d4726bddSHONG Yifan
183*d4726bddSHONG Yifanrust_analyzer_aspect = aspect(
184*d4726bddSHONG Yifan    attr_aspects = ["deps", "proc_macro_deps", "crate", "actual", "proto"],
185*d4726bddSHONG Yifan    implementation = _rust_analyzer_aspect_impl,
186*d4726bddSHONG Yifan    toolchains = [str(Label("//rust:toolchain_type"))],
187*d4726bddSHONG Yifan    doc = "Annotates rust rules with RustAnalyzerInfo later used to build a rust-project.json",
188*d4726bddSHONG Yifan)
189*d4726bddSHONG Yifan
190*d4726bddSHONG Yifan_EXEC_ROOT_TEMPLATE = "__EXEC_ROOT__/"
191*d4726bddSHONG Yifan_OUTPUT_BASE_TEMPLATE = "__OUTPUT_BASE__/"
192*d4726bddSHONG Yifan
193*d4726bddSHONG Yifandef _crate_id(crate_info):
194*d4726bddSHONG Yifan    """Returns a unique stable identifier for a crate
195*d4726bddSHONG Yifan
196*d4726bddSHONG Yifan    Returns:
197*d4726bddSHONG Yifan        (string): This crate's unique stable id.
198*d4726bddSHONG Yifan    """
199*d4726bddSHONG Yifan    return "ID-" + crate_info.root.path
200*d4726bddSHONG Yifan
201*d4726bddSHONG Yifandef _create_single_crate(ctx, attrs, info):
202*d4726bddSHONG Yifan    """Creates a crate in the rust-project.json format.
203*d4726bddSHONG Yifan
204*d4726bddSHONG Yifan    Args:
205*d4726bddSHONG Yifan        ctx (ctx): The rule context.
206*d4726bddSHONG Yifan        attrs (dict): A mapping of attributes.
207*d4726bddSHONG Yifan        info (RustAnalyzerInfo): RustAnalyzerInfo for the current crate.
208*d4726bddSHONG Yifan
209*d4726bddSHONG Yifan    Returns:
210*d4726bddSHONG Yifan        (dict) The crate rust-project.json representation
211*d4726bddSHONG Yifan    """
212*d4726bddSHONG Yifan    crate_name = info.crate.name
213*d4726bddSHONG Yifan    crate = dict()
214*d4726bddSHONG Yifan    crate_id = _crate_id(info.crate)
215*d4726bddSHONG Yifan    crate["crate_id"] = crate_id
216*d4726bddSHONG Yifan    crate["display_name"] = crate_name
217*d4726bddSHONG Yifan    crate["edition"] = info.crate.edition
218*d4726bddSHONG Yifan    crate["env"] = {}
219*d4726bddSHONG Yifan    crate["crate_type"] = info.crate.type
220*d4726bddSHONG Yifan
221*d4726bddSHONG Yifan    # Switch on external/ to determine if crates are in the workspace or remote.
222*d4726bddSHONG Yifan    # TODO: Some folks may want to override this for vendored dependencies.
223*d4726bddSHONG Yifan    is_external = info.crate.root.path.startswith("external/")
224*d4726bddSHONG Yifan    is_generated = not info.crate.root.is_source
225*d4726bddSHONG Yifan    path_prefix = _EXEC_ROOT_TEMPLATE if is_external or is_generated else ""
226*d4726bddSHONG Yifan    crate["is_workspace_member"] = not is_external
227*d4726bddSHONG Yifan    crate["root_module"] = path_prefix + info.crate.root.path
228*d4726bddSHONG Yifan    crate["source"] = {"exclude_dirs": [], "include_dirs": []}
229*d4726bddSHONG Yifan
230*d4726bddSHONG Yifan    if is_generated:
231*d4726bddSHONG Yifan        srcs = getattr(ctx.rule.files, "srcs", [])
232*d4726bddSHONG Yifan        src_map = {src.short_path: src for src in srcs if src.is_source}
233*d4726bddSHONG Yifan        if info.crate.root.short_path in src_map:
234*d4726bddSHONG Yifan            crate["root_module"] = src_map[info.crate.root.short_path].path
235*d4726bddSHONG Yifan            crate["source"]["include_dirs"].append(path_prefix + info.crate.root.dirname)
236*d4726bddSHONG Yifan
237*d4726bddSHONG Yifan    if info.build_info != None and info.build_info.out_dir != None:
238*d4726bddSHONG Yifan        out_dir_path = info.build_info.out_dir.path
239*d4726bddSHONG Yifan        crate["env"].update({"OUT_DIR": _EXEC_ROOT_TEMPLATE + out_dir_path})
240*d4726bddSHONG Yifan
241*d4726bddSHONG Yifan        # We have to tell rust-analyzer about our out_dir since it's not under the crate root.
242*d4726bddSHONG Yifan        crate["source"]["include_dirs"].extend([
243*d4726bddSHONG Yifan            path_prefix + info.crate.root.dirname,
244*d4726bddSHONG Yifan            _EXEC_ROOT_TEMPLATE + out_dir_path,
245*d4726bddSHONG Yifan        ])
246*d4726bddSHONG Yifan
247*d4726bddSHONG Yifan    # TODO: The only imagined use case is an env var holding a filename in the workspace passed to a
248*d4726bddSHONG Yifan    # macro like include_bytes!. Other use cases might exist that require more complex logic.
249*d4726bddSHONG Yifan    expand_targets = concat([getattr(attrs, attr, []) for attr in ["data", "compile_data"]])
250*d4726bddSHONG Yifan
251*d4726bddSHONG Yifan    crate["env"].update({k: dedup_expand_location(ctx, v, expand_targets) for k, v in info.env.items()})
252*d4726bddSHONG Yifan
253*d4726bddSHONG Yifan    # Omit when a crate appears to depend on itself (e.g. foo_test crates).
254*d4726bddSHONG Yifan    # It can happen a single source file is present in multiple crates - there can
255*d4726bddSHONG Yifan    # be a `rust_library` with a `lib.rs` file, and a `rust_test` for the `test`
256*d4726bddSHONG Yifan    # module in that file. Tests can declare more dependencies than what library
257*d4726bddSHONG Yifan    # had. Therefore we had to collect all RustAnalyzerInfos for a given crate
258*d4726bddSHONG Yifan    # and take deps from all of them.
259*d4726bddSHONG Yifan
260*d4726bddSHONG Yifan    # There's one exception - if the dependency is the same crate name as the
261*d4726bddSHONG Yifan    # the crate being processed, we don't add it as a dependency to itself. This is
262*d4726bddSHONG Yifan    # common and expected - `rust_test.crate` pointing to the `rust_library`.
263*d4726bddSHONG Yifan    crate["deps"] = [_crate_id(dep.crate) for dep in info.deps if _crate_id(dep.crate) != crate_id]
264*d4726bddSHONG Yifan    crate["aliases"] = {_crate_id(alias_target.crate): alias_name for alias_target, alias_name in info.aliases.items()}
265*d4726bddSHONG Yifan    crate["cfg"] = info.cfgs
266*d4726bddSHONG Yifan    crate["target"] = find_toolchain(ctx).target_triple.str
267*d4726bddSHONG Yifan    if info.proc_macro_dylib_path != None:
268*d4726bddSHONG Yifan        crate["proc_macro_dylib_path"] = _EXEC_ROOT_TEMPLATE + info.proc_macro_dylib_path
269*d4726bddSHONG Yifan    return crate
270*d4726bddSHONG Yifan
271*d4726bddSHONG Yifandef _rust_analyzer_toolchain_impl(ctx):
272*d4726bddSHONG Yifan    toolchain = platform_common.ToolchainInfo(
273*d4726bddSHONG Yifan        proc_macro_srv = ctx.executable.proc_macro_srv,
274*d4726bddSHONG Yifan        rustc = ctx.executable.rustc,
275*d4726bddSHONG Yifan        rustc_srcs = ctx.attr.rustc_srcs,
276*d4726bddSHONG Yifan    )
277*d4726bddSHONG Yifan
278*d4726bddSHONG Yifan    return [toolchain]
279*d4726bddSHONG Yifan
280*d4726bddSHONG Yifanrust_analyzer_toolchain = rule(
281*d4726bddSHONG Yifan    implementation = _rust_analyzer_toolchain_impl,
282*d4726bddSHONG Yifan    doc = "A toolchain for [rust-analyzer](https://rust-analyzer.github.io/).",
283*d4726bddSHONG Yifan    attrs = {
284*d4726bddSHONG Yifan        "proc_macro_srv": attr.label(
285*d4726bddSHONG Yifan            doc = "The path to a `rust_analyzer_proc_macro_srv` binary.",
286*d4726bddSHONG Yifan            cfg = "exec",
287*d4726bddSHONG Yifan            executable = True,
288*d4726bddSHONG Yifan            allow_single_file = True,
289*d4726bddSHONG Yifan        ),
290*d4726bddSHONG Yifan        "rustc": attr.label(
291*d4726bddSHONG Yifan            doc = "The path to a `rustc` binary.",
292*d4726bddSHONG Yifan            cfg = "exec",
293*d4726bddSHONG Yifan            executable = True,
294*d4726bddSHONG Yifan            allow_single_file = True,
295*d4726bddSHONG Yifan            mandatory = True,
296*d4726bddSHONG Yifan        ),
297*d4726bddSHONG Yifan        "rustc_srcs": attr.label(
298*d4726bddSHONG Yifan            doc = "The source code of rustc.",
299*d4726bddSHONG Yifan            mandatory = True,
300*d4726bddSHONG Yifan        ),
301*d4726bddSHONG Yifan    },
302*d4726bddSHONG Yifan)
303*d4726bddSHONG Yifan
304*d4726bddSHONG Yifandef _rust_analyzer_detect_sysroot_impl(ctx):
305*d4726bddSHONG Yifan    rust_analyzer_toolchain = ctx.toolchains[Label("@rules_rust//rust/rust_analyzer:toolchain_type")]
306*d4726bddSHONG Yifan
307*d4726bddSHONG Yifan    if not rust_analyzer_toolchain.rustc_srcs:
308*d4726bddSHONG Yifan        fail(
309*d4726bddSHONG Yifan            "Current Rust-Analyzer toolchain doesn't contain rustc sources in `rustc_srcs` attribute.",
310*d4726bddSHONG Yifan            "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.",
311*d4726bddSHONG Yifan        )
312*d4726bddSHONG Yifan
313*d4726bddSHONG Yifan    rustc_srcs = rust_analyzer_toolchain.rustc_srcs
314*d4726bddSHONG Yifan
315*d4726bddSHONG Yifan    sysroot_src = rustc_srcs.label.package + "/library"
316*d4726bddSHONG Yifan    if rustc_srcs.label.workspace_root:
317*d4726bddSHONG Yifan        sysroot_src = _OUTPUT_BASE_TEMPLATE + rustc_srcs.label.workspace_root + "/" + sysroot_src
318*d4726bddSHONG Yifan
319*d4726bddSHONG Yifan    rustc = rust_analyzer_toolchain.rustc
320*d4726bddSHONG Yifan    sysroot_dir, _, bin_dir = rustc.dirname.rpartition("/")
321*d4726bddSHONG Yifan    if bin_dir != "bin":
322*d4726bddSHONG Yifan        fail("The rustc path is expected to be relative to the sysroot as `bin/rustc`. Instead got: {}".format(
323*d4726bddSHONG Yifan            rustc.path,
324*d4726bddSHONG Yifan        ))
325*d4726bddSHONG Yifan
326*d4726bddSHONG Yifan    sysroot = "{}/{}".format(
327*d4726bddSHONG Yifan        _OUTPUT_BASE_TEMPLATE,
328*d4726bddSHONG Yifan        sysroot_dir,
329*d4726bddSHONG Yifan    )
330*d4726bddSHONG Yifan
331*d4726bddSHONG Yifan    toolchain_info = {
332*d4726bddSHONG Yifan        "sysroot": sysroot,
333*d4726bddSHONG Yifan        "sysroot_src": sysroot_src,
334*d4726bddSHONG Yifan    }
335*d4726bddSHONG Yifan
336*d4726bddSHONG Yifan    output = ctx.actions.declare_file(ctx.label.name + ".rust_analyzer_toolchain.json")
337*d4726bddSHONG Yifan    ctx.actions.write(
338*d4726bddSHONG Yifan        output = output,
339*d4726bddSHONG Yifan        content = json.encode_indent(toolchain_info, indent = " " * 4),
340*d4726bddSHONG Yifan    )
341*d4726bddSHONG Yifan
342*d4726bddSHONG Yifan    return [DefaultInfo(files = depset([output]))]
343*d4726bddSHONG Yifan
344*d4726bddSHONG Yifanrust_analyzer_detect_sysroot = rule(
345*d4726bddSHONG Yifan    implementation = _rust_analyzer_detect_sysroot_impl,
346*d4726bddSHONG Yifan    toolchains = [
347*d4726bddSHONG Yifan        "@rules_rust//rust:toolchain_type",
348*d4726bddSHONG Yifan        "@rules_rust//rust/rust_analyzer:toolchain_type",
349*d4726bddSHONG Yifan    ],
350*d4726bddSHONG Yifan    doc = dedent("""\
351*d4726bddSHONG Yifan        Detect the sysroot and store in a file for use by the gen_rust_project tool.
352*d4726bddSHONG Yifan    """),
353*d4726bddSHONG Yifan)
354