Skip to content

Commit

Permalink
Add a flag output_diagnostics.
Browse files Browse the repository at this point in the history
When this flag is set, bazel will write diagnostics to the output groups rustc_output and rustc_rmeta_output.
  • Loading branch information
matts1 committed Apr 26, 2023
1 parent cce7ac8 commit ce6b9d3
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 9 deletions.
9 changes: 8 additions & 1 deletion BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
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")
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")

exports_files(["LICENSE"])

Expand All @@ -18,6 +18,13 @@ error_format(
visibility = ["//visibility:public"],
)

# This setting may be changed from the command line to generate rustc diagnostics.
output_diagnostics(
name = "output_diagnostics",
build_setting_default = False,
visibility = ["//visibility:public"],
)

# This setting may be used to pass extra options to clippy from the command line.
# It applies across all targets.
clippy_flags(
Expand Down
4 changes: 4 additions & 0 deletions rust/defs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ load(
_extra_rustc_flags = "extra_rustc_flags",
_is_proc_macro_dep = "is_proc_macro_dep",
_is_proc_macro_dep_enabled = "is_proc_macro_dep_enabled",
_output_diagnostics = "output_diagnostics",
_per_crate_rustc_flag = "per_crate_rustc_flag",
)
load(
Expand Down Expand Up @@ -104,6 +105,9 @@ rust_clippy = _rust_clippy
capture_clippy_output = _capture_clippy_output
# See @rules_rust//rust/private:clippy.bzl for a complete description.

output_diagnostics = _output_diagnostics
# See @rules_rust//rust/private:rustc.bzl for a complete description.

error_format = _error_format
# See @rules_rust//rust/private:rustc.bzl for a complete description.

Expand Down
4 changes: 4 additions & 0 deletions rust/private/common.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ def _create_crate_info(**kwargs):
kwargs.update({"wrapped_crate_type": None})
if not "metadata" in kwargs:
kwargs.update({"metadata": None})
if not "rustc_rmeta_output" in kwargs:
kwargs.update({"rustc_rmeta_output": None})
if not "rustc_output" in kwargs:
kwargs.update({"rustc_output": None})
if not "rustc_env_files" in kwargs:
kwargs.update({"rustc_env_files": []})
return CrateInfo(**kwargs)
Expand Down
4 changes: 3 additions & 1 deletion rust/private/providers.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,16 @@ CrateInfo = provider(
"deps": "depset[DepVariantInfo]: This crate's (rust or cc) dependencies' providers.",
"edition": "str: The edition of this crate.",
"is_test": "bool: If the crate is being compiled in a test context",
"metadata": "File: The rmeta file produced for this crate. It is optional.",
"metadata": "File: The output from rustc from producing the output file. It is optional.",
"name": "str: The name of this crate.",
"output": "File: The output File that will be produced, depends on crate type.",
"owner": "Label: The label of the target that produced this CrateInfo",
"proc_macro_deps": "depset[DepVariantInfo]: This crate's rust proc_macro dependencies' providers.",
"root": "File: The source File entrypoint to this crate, eg. lib.rs",
"rustc_env": "Dict[String, String]: Additional `\"key\": \"value\"` environment variables to set for rustc.",
"rustc_env_files": "[File]: Files containing additional environment variables to set for rustc.",
"rustc_output": "File: The output from rustc from producing the output file. It is optional.",
"rustc_rmeta_output": "File: The rmeta file produced for this crate. It is optional.",
"srcs": "depset[File]: All source Files that are part of the crate.",
"type": (
"str: The type of this crate " +
Expand Down
38 changes: 37 additions & 1 deletion rust/private/rust.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

load("@bazel_skylib//lib:paths.bzl", "paths")
load("//rust/private:common.bzl", "rust_common")
load("//rust/private:rustc.bzl", "rustc_compile_action")
load("//rust/private:rustc.bzl", "OutputDiagnosticsInfo", "rustc_compile_action")
load(
"//rust/private:utils.bzl",
"can_build_metadata",
Expand Down Expand Up @@ -65,6 +65,32 @@ def _assert_correct_dep_mapping(ctx):
),
)

def _generate_output_diagnostics(ctx, sibling, require_process_wrapper = True):
"""Generates a .rustc-output file if it's required.
Args:
ctx: (ctx): The current rule's context object
sibling: (File): The file to generate the diagnostics for.
require_process_wrapper: (bool): Whether to require the process wrapper
in order to generate the .rustc-output file.
Returns:
Optional[File] The .rustc-object file, if generated.
"""

# Since this feature requires error_format=json, we usually need
# process_wrapper, since it can write the json here, then convert it to the
# regular error format so the user can see the error properly.
if require_process_wrapper and not ctx.attr._process_wrapper:
return
provider = ctx.attr._output_diagnostics[OutputDiagnosticsInfo]
if not provider.output_diagnostics:
return

return ctx.actions.declare_file(
sibling.basename + ".rustc-output",
sibling = sibling,
)

def _determine_lib_name(name, crate_type, toolchain, lib_hash = None):
"""See https://github.com/bazelbuild/rules_rust/issues/405
Expand Down Expand Up @@ -279,11 +305,13 @@ def _rust_library_common(ctx, crate_type):
rust_lib = ctx.actions.declare_file(rust_lib_name)

rust_metadata = None
rustc_rmeta_output = None
if can_build_metadata(toolchain, ctx, crate_type) and not ctx.attr.disable_pipelining:
rust_metadata = ctx.actions.declare_file(
paths.replace_extension(rust_lib_name, ".rmeta"),
sibling = rust_lib,
)
rustc_rmeta_output = _generate_output_diagnostics(ctx, rust_metadata)

deps = transform_deps(ctx.attr.deps)
proc_macro_deps = transform_deps(ctx.attr.proc_macro_deps + get_import_macro_deps(ctx))
Expand All @@ -301,7 +329,9 @@ def _rust_library_common(ctx, crate_type):
proc_macro_deps = depset(proc_macro_deps),
aliases = ctx.attr.aliases,
output = rust_lib,
rustc_output = _generate_output_diagnostics(ctx, rust_lib),
metadata = rust_metadata,
rustc_rmeta_output = rustc_rmeta_output,
edition = get_edition(ctx.attr, toolchain, ctx.label),
rustc_env = ctx.attr.rustc_env,
rustc_env_files = ctx.files.rustc_env_files,
Expand Down Expand Up @@ -348,6 +378,7 @@ def _rust_binary_impl(ctx):
proc_macro_deps = depset(proc_macro_deps),
aliases = ctx.attr.aliases,
output = output,
rustc_output = _generate_output_diagnostics(ctx, output),
edition = get_edition(ctx.attr, toolchain, ctx.label),
rustc_env = ctx.attr.rustc_env,
rustc_env_files = ctx.files.rustc_env_files,
Expand Down Expand Up @@ -414,6 +445,7 @@ def _rust_test_impl(ctx):
proc_macro_deps = depset(proc_macro_deps, transitive = [crate.proc_macro_deps]),
aliases = ctx.attr.aliases,
output = output,
rustc_output = _generate_output_diagnostics(ctx, output),
edition = crate.edition,
rustc_env = rustc_env,
rustc_env_files = rustc_env_files,
Expand Down Expand Up @@ -447,6 +479,7 @@ def _rust_test_impl(ctx):
proc_macro_deps = depset(proc_macro_deps),
aliases = ctx.attr.aliases,
output = output,
rustc_output = _generate_output_diagnostics(ctx, output),
edition = get_edition(ctx.attr, toolchain, ctx.label),
rustc_env = ctx.attr.rustc_env,
rustc_env_files = ctx.files.rustc_env_files,
Expand Down Expand Up @@ -682,6 +715,9 @@ _common_attrs = {
"_is_proc_macro_dep_enabled": attr.label(
default = Label("//:is_proc_macro_dep_enabled"),
),
"_output_diagnostics": attr.label(
default = Label("//:output_diagnostics"),
),
"_per_crate_rustc_flag": attr.label(
default = Label("//:experimental_per_crate_rustc_flag"),
),
Expand Down
59 changes: 53 additions & 6 deletions rust/private/rustc.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ ErrorFormatInfo = provider(
fields = {"error_format": "(string) [" + ", ".join(_error_format_values) + "]"},
)

OutputDiagnosticsInfo = provider(
doc = "Save json diagnostics form rustc",
fields = {"output_diagnostics": "(bool)"},
)

ExtraRustcFlagsInfo = provider(
doc = "Pass each value as an additional flag to non-exec rustc invocations",
fields = {"extra_rustc_flags": "List[string] Extra flags to pass to rustc in non-exec configuration"},
Expand Down Expand Up @@ -881,7 +886,10 @@ def construct_arguments(
if build_metadata:
# Configure process_wrapper to terminate rustc when metadata are emitted
process_wrapper_flags.add("--rustc-quit-on-rmeta", "true")

if crate_info.rustc_rmeta_output:
process_wrapper_flags.add("--output-file", crate_info.rustc_rmeta_output.path)
elif crate_info.rustc_output:
process_wrapper_flags.add("--output-file", crate_info.rustc_output.path)
rustc_flags.add("--error-format=" + error_format)

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

cc_toolchain, feature_configuration = find_cc_toolchain(ctx)

Expand Down Expand Up @@ -1134,7 +1144,7 @@ def rustc_compile_action(
build_flags_files = build_flags_files,
force_all_deps_direct = force_all_deps_direct,
stamp = stamp,
use_json_output = bool(build_metadata),
use_json_output = bool(build_metadata) or bool(rustc_output) or bool(rustc_rmeta_output),
)

args_metadata = None
Expand Down Expand Up @@ -1194,6 +1204,8 @@ def rustc_compile_action(

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

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

if toolchain.target_arch != "wasm32":
providers += establish_cc_info(ctx, attr, crate_info, toolchain, cc_toolchain, feature_configuration, interface_library)

output_group_info = {}

if pdb_file:
providers.append(OutputGroupInfo(pdb_file = depset([pdb_file])))
output_group_info["pdb_file"] = depset([pdb_file])
if dsym_folder:
providers.append(OutputGroupInfo(dsym_folder = depset([dsym_folder])))
output_group_info["dsym_folder"] = depset([dsym_folder])
if build_metadata:
providers.append(OutputGroupInfo(build_metadata = depset([build_metadata])))
output_group_info["build_metadata"] = depset([build_metadata])
if build_metadata:
output_group_info["build_metadata"] = depset([build_metadata])
if rustc_rmeta_output:
output_group_info["rustc_rmeta_output"] = depset([rustc_rmeta_output])
if rustc_output:
output_group_info["rustc_output"] = depset([rustc_output])

if output_group_info:
providers.append(OutputGroupInfo(**output_group_info))

return providers

Expand Down Expand Up @@ -1923,6 +1947,29 @@ error_format = rule(
build_setting = config.string(flag = True),
)

def _output_diagnostics_impl(ctx):
"""Implementation of the `output_diagnostics` rule
Args:
ctx (ctx): The rule's context object
Returns:
list: A list containing the OutputDiagnosticsInfo provider
"""
return [OutputDiagnosticsInfo(output_diagnostics = ctx.build_setting_value)]

output_diagnostics = rule(
doc = (
"Setting this flag from the command line with `--@rules_rust//:output_diagnostics` " +
"makes rules_rust save rustc json output(suitable for consumption by rust-analyzer) in a file. " +
"These are accessible via the " +
"`rustc_rmeta_output`(for pipelined compilation) and `rustc_output` output groups. " +
"You can find these using `bazel cquery`"
),
implementation = _output_diagnostics_impl,
build_setting = config.bool(flag = True),
)

def _extra_rustc_flags_impl(ctx):
return ExtraRustcFlagsInfo(extra_rustc_flags = ctx.build_setting_value)

Expand Down

0 comments on commit ce6b9d3

Please sign in to comment.