Skip to content

Commit ce6b9d3

Browse files
committed
Add a flag output_diagnostics.
When this flag is set, bazel will write diagnostics to the output groups rustc_output and rustc_rmeta_output.
1 parent cce7ac8 commit ce6b9d3

File tree

6 files changed

+109
-9
lines changed

6 files changed

+109
-9
lines changed

BUILD.bazel

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
2-
load("//rust:defs.bzl", "capture_clippy_output", "clippy_flags", "error_format", "extra_exec_rustc_flag", "extra_exec_rustc_flags", "extra_rustc_flag", "extra_rustc_flags", "is_proc_macro_dep", "is_proc_macro_dep_enabled", "per_crate_rustc_flag")
2+
load("//rust:defs.bzl", "capture_clippy_output", "clippy_flags", "error_format", "extra_exec_rustc_flag", "extra_exec_rustc_flags", "extra_rustc_flag", "extra_rustc_flags", "is_proc_macro_dep", "is_proc_macro_dep_enabled", "output_diagnostics", "per_crate_rustc_flag")
33

44
exports_files(["LICENSE"])
55

@@ -18,6 +18,13 @@ error_format(
1818
visibility = ["//visibility:public"],
1919
)
2020

21+
# This setting may be changed from the command line to generate rustc diagnostics.
22+
output_diagnostics(
23+
name = "output_diagnostics",
24+
build_setting_default = False,
25+
visibility = ["//visibility:public"],
26+
)
27+
2128
# This setting may be used to pass extra options to clippy from the command line.
2229
# It applies across all targets.
2330
clippy_flags(

rust/defs.bzl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ load(
4949
_extra_rustc_flags = "extra_rustc_flags",
5050
_is_proc_macro_dep = "is_proc_macro_dep",
5151
_is_proc_macro_dep_enabled = "is_proc_macro_dep_enabled",
52+
_output_diagnostics = "output_diagnostics",
5253
_per_crate_rustc_flag = "per_crate_rustc_flag",
5354
)
5455
load(
@@ -104,6 +105,9 @@ rust_clippy = _rust_clippy
104105
capture_clippy_output = _capture_clippy_output
105106
# See @rules_rust//rust/private:clippy.bzl for a complete description.
106107

108+
output_diagnostics = _output_diagnostics
109+
# See @rules_rust//rust/private:rustc.bzl for a complete description.
110+
107111
error_format = _error_format
108112
# See @rules_rust//rust/private:rustc.bzl for a complete description.
109113

rust/private/common.bzl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ def _create_crate_info(**kwargs):
5151
kwargs.update({"wrapped_crate_type": None})
5252
if not "metadata" in kwargs:
5353
kwargs.update({"metadata": None})
54+
if not "rustc_rmeta_output" in kwargs:
55+
kwargs.update({"rustc_rmeta_output": None})
56+
if not "rustc_output" in kwargs:
57+
kwargs.update({"rustc_output": None})
5458
if not "rustc_env_files" in kwargs:
5559
kwargs.update({"rustc_env_files": []})
5660
return CrateInfo(**kwargs)

rust/private/providers.bzl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,16 @@ CrateInfo = provider(
2323
"deps": "depset[DepVariantInfo]: This crate's (rust or cc) dependencies' providers.",
2424
"edition": "str: The edition of this crate.",
2525
"is_test": "bool: If the crate is being compiled in a test context",
26-
"metadata": "File: The rmeta file produced for this crate. It is optional.",
26+
"metadata": "File: The output from rustc from producing the output file. It is optional.",
2727
"name": "str: The name of this crate.",
2828
"output": "File: The output File that will be produced, depends on crate type.",
2929
"owner": "Label: The label of the target that produced this CrateInfo",
3030
"proc_macro_deps": "depset[DepVariantInfo]: This crate's rust proc_macro dependencies' providers.",
3131
"root": "File: The source File entrypoint to this crate, eg. lib.rs",
3232
"rustc_env": "Dict[String, String]: Additional `\"key\": \"value\"` environment variables to set for rustc.",
3333
"rustc_env_files": "[File]: Files containing additional environment variables to set for rustc.",
34+
"rustc_output": "File: The output from rustc from producing the output file. It is optional.",
35+
"rustc_rmeta_output": "File: The rmeta file produced for this crate. It is optional.",
3436
"srcs": "depset[File]: All source Files that are part of the crate.",
3537
"type": (
3638
"str: The type of this crate " +

rust/private/rust.bzl

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
load("@bazel_skylib//lib:paths.bzl", "paths")
1818
load("//rust/private:common.bzl", "rust_common")
19-
load("//rust/private:rustc.bzl", "rustc_compile_action")
19+
load("//rust/private:rustc.bzl", "OutputDiagnosticsInfo", "rustc_compile_action")
2020
load(
2121
"//rust/private:utils.bzl",
2222
"can_build_metadata",
@@ -65,6 +65,32 @@ def _assert_correct_dep_mapping(ctx):
6565
),
6666
)
6767

68+
def _generate_output_diagnostics(ctx, sibling, require_process_wrapper = True):
69+
"""Generates a .rustc-output file if it's required.
70+
71+
Args:
72+
ctx: (ctx): The current rule's context object
73+
sibling: (File): The file to generate the diagnostics for.
74+
require_process_wrapper: (bool): Whether to require the process wrapper
75+
in order to generate the .rustc-output file.
76+
Returns:
77+
Optional[File] The .rustc-object file, if generated.
78+
"""
79+
80+
# Since this feature requires error_format=json, we usually need
81+
# process_wrapper, since it can write the json here, then convert it to the
82+
# regular error format so the user can see the error properly.
83+
if require_process_wrapper and not ctx.attr._process_wrapper:
84+
return
85+
provider = ctx.attr._output_diagnostics[OutputDiagnosticsInfo]
86+
if not provider.output_diagnostics:
87+
return
88+
89+
return ctx.actions.declare_file(
90+
sibling.basename + ".rustc-output",
91+
sibling = sibling,
92+
)
93+
6894
def _determine_lib_name(name, crate_type, toolchain, lib_hash = None):
6995
"""See https://github.com/bazelbuild/rules_rust/issues/405
7096
@@ -279,11 +305,13 @@ def _rust_library_common(ctx, crate_type):
279305
rust_lib = ctx.actions.declare_file(rust_lib_name)
280306

281307
rust_metadata = None
308+
rustc_rmeta_output = None
282309
if can_build_metadata(toolchain, ctx, crate_type) and not ctx.attr.disable_pipelining:
283310
rust_metadata = ctx.actions.declare_file(
284311
paths.replace_extension(rust_lib_name, ".rmeta"),
285312
sibling = rust_lib,
286313
)
314+
rustc_rmeta_output = _generate_output_diagnostics(ctx, rust_metadata)
287315

288316
deps = transform_deps(ctx.attr.deps)
289317
proc_macro_deps = transform_deps(ctx.attr.proc_macro_deps + get_import_macro_deps(ctx))
@@ -301,7 +329,9 @@ def _rust_library_common(ctx, crate_type):
301329
proc_macro_deps = depset(proc_macro_deps),
302330
aliases = ctx.attr.aliases,
303331
output = rust_lib,
332+
rustc_output = _generate_output_diagnostics(ctx, rust_lib),
304333
metadata = rust_metadata,
334+
rustc_rmeta_output = rustc_rmeta_output,
305335
edition = get_edition(ctx.attr, toolchain, ctx.label),
306336
rustc_env = ctx.attr.rustc_env,
307337
rustc_env_files = ctx.files.rustc_env_files,
@@ -348,6 +378,7 @@ def _rust_binary_impl(ctx):
348378
proc_macro_deps = depset(proc_macro_deps),
349379
aliases = ctx.attr.aliases,
350380
output = output,
381+
rustc_output = _generate_output_diagnostics(ctx, output),
351382
edition = get_edition(ctx.attr, toolchain, ctx.label),
352383
rustc_env = ctx.attr.rustc_env,
353384
rustc_env_files = ctx.files.rustc_env_files,
@@ -414,6 +445,7 @@ def _rust_test_impl(ctx):
414445
proc_macro_deps = depset(proc_macro_deps, transitive = [crate.proc_macro_deps]),
415446
aliases = ctx.attr.aliases,
416447
output = output,
448+
rustc_output = _generate_output_diagnostics(ctx, output),
417449
edition = crate.edition,
418450
rustc_env = rustc_env,
419451
rustc_env_files = rustc_env_files,
@@ -447,6 +479,7 @@ def _rust_test_impl(ctx):
447479
proc_macro_deps = depset(proc_macro_deps),
448480
aliases = ctx.attr.aliases,
449481
output = output,
482+
rustc_output = _generate_output_diagnostics(ctx, output),
450483
edition = get_edition(ctx.attr, toolchain, ctx.label),
451484
rustc_env = ctx.attr.rustc_env,
452485
rustc_env_files = ctx.files.rustc_env_files,
@@ -682,6 +715,9 @@ _common_attrs = {
682715
"_is_proc_macro_dep_enabled": attr.label(
683716
default = Label("//:is_proc_macro_dep_enabled"),
684717
),
718+
"_output_diagnostics": attr.label(
719+
default = Label("//:output_diagnostics"),
720+
),
685721
"_per_crate_rustc_flag": attr.label(
686722
default = Label("//:experimental_per_crate_rustc_flag"),
687723
),

rust/private/rustc.bzl

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ ErrorFormatInfo = provider(
5353
fields = {"error_format": "(string) [" + ", ".join(_error_format_values) + "]"},
5454
)
5555

56+
OutputDiagnosticsInfo = provider(
57+
doc = "Save json diagnostics form rustc",
58+
fields = {"output_diagnostics": "(bool)"},
59+
)
60+
5661
ExtraRustcFlagsInfo = provider(
5762
doc = "Pass each value as an additional flag to non-exec rustc invocations",
5863
fields = {"extra_rustc_flags": "List[string] Extra flags to pass to rustc in non-exec configuration"},
@@ -881,7 +886,10 @@ def construct_arguments(
881886
if build_metadata:
882887
# Configure process_wrapper to terminate rustc when metadata are emitted
883888
process_wrapper_flags.add("--rustc-quit-on-rmeta", "true")
884-
889+
if crate_info.rustc_rmeta_output:
890+
process_wrapper_flags.add("--output-file", crate_info.rustc_rmeta_output.path)
891+
elif crate_info.rustc_output:
892+
process_wrapper_flags.add("--output-file", crate_info.rustc_output.path)
885893
rustc_flags.add("--error-format=" + error_format)
886894

887895
# Mangle symbols to disambiguate crates with the same name. This could
@@ -1056,6 +1064,8 @@ def rustc_compile_action(
10561064
- (DefaultInfo): The output file for this crate, and its runfiles.
10571065
"""
10581066
build_metadata = getattr(crate_info, "metadata", None)
1067+
rustc_output = getattr(crate_info, "rustc_output", None)
1068+
rustc_rmeta_output = getattr(crate_info, "rustc_rmeta_output", None)
10591069

10601070
cc_toolchain, feature_configuration = find_cc_toolchain(ctx)
10611071

@@ -1134,7 +1144,7 @@ def rustc_compile_action(
11341144
build_flags_files = build_flags_files,
11351145
force_all_deps_direct = force_all_deps_direct,
11361146
stamp = stamp,
1137-
use_json_output = bool(build_metadata),
1147+
use_json_output = bool(build_metadata) or bool(rustc_output) or bool(rustc_rmeta_output),
11381148
)
11391149

11401150
args_metadata = None
@@ -1194,6 +1204,8 @@ def rustc_compile_action(
11941204

11951205
# The action might generate extra output that we don't want to include in the `DefaultInfo` files.
11961206
action_outputs = list(outputs)
1207+
if rustc_output:
1208+
action_outputs.append(rustc_output)
11971209

11981210
# Rustc generates a pdb file (on Windows) or a dsym folder (on macos) so provide it in an output group for crate
11991211
# types that benefit from having debug information in a separate file.
@@ -1227,7 +1239,7 @@ def rustc_compile_action(
12271239
ctx.actions.run(
12281240
executable = ctx.executable._process_wrapper,
12291241
inputs = compile_inputs,
1230-
outputs = [build_metadata],
1242+
outputs = [build_metadata] + [x for x in [rustc_rmeta_output] if x],
12311243
env = env,
12321244
arguments = args_metadata.all,
12331245
mnemonic = "RustcMetadata",
@@ -1371,12 +1383,24 @@ def rustc_compile_action(
13711383

13721384
if toolchain.target_arch != "wasm32":
13731385
providers += establish_cc_info(ctx, attr, crate_info, toolchain, cc_toolchain, feature_configuration, interface_library)
1386+
1387+
output_group_info = {}
1388+
13741389
if pdb_file:
1375-
providers.append(OutputGroupInfo(pdb_file = depset([pdb_file])))
1390+
output_group_info["pdb_file"] = depset([pdb_file])
13761391
if dsym_folder:
1377-
providers.append(OutputGroupInfo(dsym_folder = depset([dsym_folder])))
1392+
output_group_info["dsym_folder"] = depset([dsym_folder])
13781393
if build_metadata:
1379-
providers.append(OutputGroupInfo(build_metadata = depset([build_metadata])))
1394+
output_group_info["build_metadata"] = depset([build_metadata])
1395+
if build_metadata:
1396+
output_group_info["build_metadata"] = depset([build_metadata])
1397+
if rustc_rmeta_output:
1398+
output_group_info["rustc_rmeta_output"] = depset([rustc_rmeta_output])
1399+
if rustc_output:
1400+
output_group_info["rustc_output"] = depset([rustc_output])
1401+
1402+
if output_group_info:
1403+
providers.append(OutputGroupInfo(**output_group_info))
13801404

13811405
return providers
13821406

@@ -1923,6 +1947,29 @@ error_format = rule(
19231947
build_setting = config.string(flag = True),
19241948
)
19251949

1950+
def _output_diagnostics_impl(ctx):
1951+
"""Implementation of the `output_diagnostics` rule
1952+
1953+
Args:
1954+
ctx (ctx): The rule's context object
1955+
1956+
Returns:
1957+
list: A list containing the OutputDiagnosticsInfo provider
1958+
"""
1959+
return [OutputDiagnosticsInfo(output_diagnostics = ctx.build_setting_value)]
1960+
1961+
output_diagnostics = rule(
1962+
doc = (
1963+
"Setting this flag from the command line with `--@rules_rust//:output_diagnostics` " +
1964+
"makes rules_rust save rustc json output(suitable for consumption by rust-analyzer) in a file. " +
1965+
"These are accessible via the " +
1966+
"`rustc_rmeta_output`(for pipelined compilation) and `rustc_output` output groups. " +
1967+
"You can find these using `bazel cquery`"
1968+
),
1969+
implementation = _output_diagnostics_impl,
1970+
build_setting = config.bool(flag = True),
1971+
)
1972+
19261973
def _extra_rustc_flags_impl(ctx):
19271974
return ExtraRustcFlagsInfo(extra_rustc_flags = ctx.build_setting_value)
19281975

0 commit comments

Comments
 (0)