diff --git a/BUILD.bazel b/BUILD.bazel index 2eb9e03a25..1f4f2bcb7b 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -13,6 +13,7 @@ load( "is_proc_macro_dep_enabled", "no_std", "per_crate_rustc_flag", + "rustc_output_diagnostics", ) exports_files(["LICENSE"]) @@ -32,6 +33,13 @@ error_format( visibility = ["//visibility:public"], ) +# This setting may be changed from the command line to generate rustc diagnostics. +rustc_output_diagnostics( + name = "rustc_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( diff --git a/docs/flatten.md b/docs/flatten.md index e7e7cb4673..00af38c3ca 100644 --- a/docs/flatten.md +++ b/docs/flatten.md @@ -1439,8 +1439,8 @@ A toolchain for [rustfmt](https://rust-lang.github.io/rustfmt/)
 CrateInfo(aliases, compile_data, compile_data_targets, data, deps, edition, is_test, metadata, name,
-          output, owner, proc_macro_deps, root, rustc_env, rustc_env_files, srcs, type,
-          wrapped_crate_type)
+          output, owner, proc_macro_deps, root, rustc_env, rustc_env_files, rustc_output,
+          rustc_rmeta_output, srcs, type, wrapped_crate_type)
 
A provider containing general Crate information. @@ -1457,7 +1457,7 @@ A provider containing general Crate information. | 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 | @@ -1465,6 +1465,8 @@ A provider containing general Crate information. | 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 (see [rustc --crate-type](https://doc.rust-lang.org/rustc/command-line-arguments.html#--crate-type-a-list-of-types-of-crates-for-the-compiler-to-emit)). | | wrapped_crate_type | str, optional: The original crate type for targets generated using a previously defined crate (typically tests using the rust_test::crate attribute) | diff --git a/docs/providers.md b/docs/providers.md index c97ad9bce9..87a37924c1 100644 --- a/docs/providers.md +++ b/docs/providers.md @@ -11,8 +11,8 @@
 CrateInfo(aliases, compile_data, compile_data_targets, data, deps, edition, is_test, metadata, name,
-          output, owner, proc_macro_deps, root, rustc_env, rustc_env_files, srcs, type,
-          wrapped_crate_type)
+          output, owner, proc_macro_deps, root, rustc_env, rustc_env_files, rustc_output,
+          rustc_rmeta_output, srcs, type, wrapped_crate_type)
 
A provider containing general Crate information. @@ -29,7 +29,7 @@ A provider containing general Crate information. | 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 | @@ -37,6 +37,8 @@ A provider containing general Crate information. | 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 (see [rustc --crate-type](https://doc.rust-lang.org/rustc/command-line-arguments.html#--crate-type-a-list-of-types-of-crates-for-the-compiler-to-emit)). | | wrapped_crate_type | str, optional: The original crate type for targets generated using a previously defined crate (typically tests using the rust_test::crate attribute) | diff --git a/docs/rust_analyzer.md b/docs/rust_analyzer.md index bd91b0b790..fd4be79da7 100644 --- a/docs/rust_analyzer.md +++ b/docs/rust_analyzer.md @@ -78,6 +78,16 @@ to ensure a `rust-project.json` file is created and up to date when the editor i } ``` +#### Alternative vscode option (prototype) + +Add the following to your bazelrc: +``` +build --@rules_rust//:output_diagnostics=true --output_groups=+rust_lib_rustc_output,+rust_metadata_rustc_output +``` + +Then you can use a prototype [rust-analyzer plugin](https://marketplace.visualstudio.com/items?itemName=MattStark.bazel-rust-analyzer) that automatically collects the outputs whenever you recompile. + + diff --git a/docs/rust_analyzer.vm b/docs/rust_analyzer.vm index ad74f91a09..21f1f715d5 100644 --- a/docs/rust_analyzer.vm +++ b/docs/rust_analyzer.vm @@ -71,4 +71,14 @@ to ensure a `rust-project.json` file is created and up to date when the editor i ] } ``` + +#### Alternative vscode option (prototype) + +Add the following to your bazelrc: +``` +build --@rules_rust//:output_diagnostics=true --output_groups=+rust_lib_rustc_output,+rust_metadata_rustc_output +``` + +Then you can use a prototype [rust-analyzer plugin](https://marketplace.visualstudio.com/items?itemName=MattStark.bazel-rust-analyzer) that automatically collects the outputs whenever you recompile. + ]]# diff --git a/rust/defs.bzl b/rust/defs.bzl index 7c9724399e..e6afbba47c 100644 --- a/rust/defs.bzl +++ b/rust/defs.bzl @@ -52,6 +52,7 @@ load( _is_proc_macro_dep_enabled = "is_proc_macro_dep_enabled", _no_std = "no_std", _per_crate_rustc_flag = "per_crate_rustc_flag", + _rustc_output_diagnostics = "rustc_output_diagnostics", ) load( "//rust/private:rustdoc.bzl", @@ -109,6 +110,9 @@ rust_clippy = _rust_clippy capture_clippy_output = _capture_clippy_output # See @rules_rust//rust/private:clippy.bzl for a complete description. +rustc_output_diagnostics = _rustc_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. diff --git a/rust/private/common.bzl b/rust/private/common.bzl index efb4b2b6e5..f900379cbc 100644 --- a/rust/private/common.bzl +++ b/rust/private/common.bzl @@ -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": []}) if not "data" in kwargs: diff --git a/rust/private/providers.bzl b/rust/private/providers.bzl index e4db9c9f9d..cd7ec71b8e 100644 --- a/rust/private/providers.bzl +++ b/rust/private/providers.bzl @@ -24,7 +24,7 @@ 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", @@ -32,6 +32,8 @@ CrateInfo = provider( "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 " + @@ -94,6 +96,16 @@ DepVariantInfo = provider( }, ) +RustcOutputDiagnosticsInfo = provider( + doc = ( + "Save json diagnostics from rustc. Json diagnostics are able to be " + + "consumed by tools such as rust-analyzer to provide IDE integration" + ), + fields = { + "rustc_output_diagnostics": "bool: Whether or not to output diagnostics", + }, +) + StdLibInfo = provider( doc = ( "A collection of files either found within the `rust-stdlib` artifact or " + diff --git a/rust/private/rust.bzl b/rust/private/rust.bzl index 484e35d34e..d9caf28114 100644 --- a/rust/private/rust.bzl +++ b/rust/private/rust.bzl @@ -28,6 +28,7 @@ load( "determine_output_hash", "expand_dict_value_locations", "find_toolchain", + "generate_output_diagnostics", "get_edition", "get_import_macro_deps", "transform_deps", @@ -168,11 +169,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)) @@ -191,7 +194,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, @@ -239,6 +244,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, @@ -312,6 +318,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, @@ -355,6 +362,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 = rustc_env, rustc_env_files = ctx.files.rustc_env_files, @@ -641,6 +649,9 @@ _common_attrs = { allow_single_file = True, cfg = "exec", ), + "_rustc_output_diagnostics": attr.label( + default = Label("//:rustc_output_diagnostics"), + ), "_stamp_flag": attr.label( doc = "A setting used to determine whether or not the `--stamp` flag is enabled", default = Label("//rust/private:stamp"), diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl index 21f57b12c3..481ef25694 100644 --- a/rust/private/rustc.bzl +++ b/rust/private/rustc.bzl @@ -23,7 +23,7 @@ load( "CPP_LINK_STATIC_LIBRARY_ACTION_NAME", ) load("//rust/private:common.bzl", "rust_common") -load("//rust/private:providers.bzl", _BuildInfo = "BuildInfo") +load("//rust/private:providers.bzl", "RustcOutputDiagnosticsInfo", _BuildInfo = "BuildInfo") load("//rust/private:stamp.bzl", "is_stamping_enabled") load( "//rust/private:utils.bzl", @@ -929,6 +929,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, format = "--error-format=%s") @@ -1112,9 +1116,9 @@ def rustc_compile_action( """ crate_info = rust_common.create_crate_info(**crate_info_dict) - build_metadata = None - if "metadata" in crate_info_dict: - build_metadata = crate_info_dict["metadata"] + build_metadata = crate_info_dict.get("metadata", None) + rustc_output = crate_info_dict.get("rustc_output", None) + rustc_rmeta_output = crate_info_dict.get("rustc_rmeta_output", None) cc_toolchain, feature_configuration = find_cc_toolchain(ctx) @@ -1193,7 +1197,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), skip_expanding_rustc_env = skip_expanding_rustc_env, ) @@ -1256,6 +1260,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. @@ -1290,7 +1296,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", @@ -1456,12 +1462,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 @@ -2046,6 +2064,31 @@ error_format = rule( build_setting = config.string(flag = True), ) +def _rustc_output_diagnostics_impl(ctx): + """Implementation of the `rustc_output_diagnostics` rule + + Args: + ctx (ctx): The rule's context object + + Returns: + list: A list containing the RustcOutputDiagnosticsInfo provider + """ + return [RustcOutputDiagnosticsInfo( + rustc_output_diagnostics = ctx.build_setting_value, + )] + +rustc_output_diagnostics = rule( + doc = ( + "Setting this flag from the command line with `--@rules_rust//:rustc_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 = _rustc_output_diagnostics_impl, + build_setting = config.bool(flag = True), +) + def _extra_rustc_flags_impl(ctx): return ExtraRustcFlagsInfo(extra_rustc_flags = ctx.build_setting_value) diff --git a/rust/private/utils.bzl b/rust/private/utils.bzl index 89c41bbe04..36b83bfeb6 100644 --- a/rust/private/utils.bzl +++ b/rust/private/utils.bzl @@ -16,7 +16,7 @@ load("@bazel_skylib//lib:paths.bzl", "paths") load("@bazel_tools//tools/cpp:toolchain_utils.bzl", find_rules_cc_toolchain = "find_cpp_toolchain") -load(":providers.bzl", "BuildInfo", "CrateGroupInfo", "CrateInfo", "DepInfo", "DepVariantInfo") +load(":providers.bzl", "BuildInfo", "CrateGroupInfo", "CrateInfo", "DepInfo", "DepVariantInfo", "RustcOutputDiagnosticsInfo") UNSUPPORTED_FEATURES = [ "thin_lto", @@ -849,3 +849,29 @@ def _symlink_for_non_generated_source(ctx, src_file, package_root): return src_symlink else: return src_file + +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 None + provider = ctx.attr._rustc_output_diagnostics[RustcOutputDiagnosticsInfo] + if not provider.rustc_output_diagnostics: + return None + + return ctx.actions.declare_file( + sibling.basename + ".rustc-output", + sibling = sibling, + ) diff --git a/util/process_wrapper/main.rs b/util/process_wrapper/main.rs index e618f833da..86135a52e7 100644 --- a/util/process_wrapper/main.rs +++ b/util/process_wrapper/main.rs @@ -100,6 +100,19 @@ fn main() -> Result<(), ProcessWrapperError> { "unable to get child stderr".to_string(), ))?; + let mut output_file: Option = if let Some(output_file_name) = opts.output_file { + Some( + OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .open(output_file_name) + .map_err(|e| ProcessWrapperError(format!("Unable to open output_file: {}", e)))?, + ) + } else { + None + }; + let mut was_killed = false; let result = if let Some(format) = opts.rustc_output_format { let quit_on_rmeta = opts.rustc_quit_on_rmeta; @@ -107,13 +120,18 @@ fn main() -> Result<(), ProcessWrapperError> { // that we emitted a metadata file. let mut me = false; let metadata_emitted = &mut me; - let result = process_output(&mut child_stderr, stderr.as_mut(), move |line| { - if quit_on_rmeta { - rustc::stop_on_rmeta_completion(line, format, metadata_emitted) - } else { - rustc::process_json(line, format) - } - }); + let result = process_output( + &mut child_stderr, + stderr.as_mut(), + output_file.as_mut(), + move |line| { + if quit_on_rmeta { + rustc::stop_on_rmeta_completion(line, format, metadata_emitted) + } else { + rustc::process_json(line, format) + } + }, + ); if me { // If recv returns Ok(), a signal was sent in this channel so we should terminate the child process. // We can safely ignore the Result from kill() as we don't care if the process already terminated. @@ -123,9 +141,12 @@ fn main() -> Result<(), ProcessWrapperError> { result } else { // Process output normally by forwarding stderr - process_output(&mut child_stderr, stderr.as_mut(), move |line| { - Ok(LineOutput::Message(line)) - }) + process_output( + &mut child_stderr, + stderr.as_mut(), + output_file.as_mut(), + move |line| Ok(LineOutput::Message(line)), + ) }; result.map_err(|e| ProcessWrapperError(format!("failed to process stderr: {}", e)))?; diff --git a/util/process_wrapper/options.rs b/util/process_wrapper/options.rs index 9b43fae8b0..9cb64ae8e0 100644 --- a/util/process_wrapper/options.rs +++ b/util/process_wrapper/options.rs @@ -41,11 +41,13 @@ pub(crate) struct Options { pub(crate) stdout_file: Option, // If set, redirects the child process stderr to this file. pub(crate) stderr_file: Option, + // If set, also logs all unprocessed output from the rustc output to this file. + // Meant to be used to get json output out of rustc for tooling usage. + pub(crate) output_file: Option, // If set, it configures rustc to emit an rmeta file and then // quit. pub(crate) rustc_quit_on_rmeta: bool, - // If rustc_quit_on_rmeta is set to true, this controls the - // output format of rustc messages. + // This controls the output format of rustc messages. pub(crate) rustc_output_format: Option, } @@ -61,6 +63,7 @@ pub(crate) fn options() -> Result { let mut copy_output_raw = None; let mut stdout_file = None; let mut stderr_file = None; + let mut output_file = None; let mut rustc_quit_on_rmeta_raw = None; let mut rustc_output_format_raw = None; let mut flags = Flags::new(); @@ -93,6 +96,11 @@ pub(crate) fn options() -> Result { "Redirect subprocess stderr in this file.", &mut stderr_file, ); + flags.define_flag( + "--output-file", + "Log all unprocessed subprocess stderr in this file.", + &mut output_file, + ); flags.define_flag( "--rustc-quit-on-rmeta", "If enabled, this wrapper will terminate rustc after rmeta has been emitted.", @@ -201,6 +209,7 @@ pub(crate) fn options() -> Result { copy_output, stdout_file, stderr_file, + output_file, rustc_quit_on_rmeta, rustc_output_format, }) diff --git a/util/process_wrapper/output.rs b/util/process_wrapper/output.rs index 933804a09c..4b3604b18d 100644 --- a/util/process_wrapper/output.rs +++ b/util/process_wrapper/output.rs @@ -70,14 +70,16 @@ pub(crate) type LineResult = Result; /// to write_end. pub(crate) fn process_output( read_end: &mut dyn Read, - write_end: &mut dyn Write, + output_write_end: &mut dyn Write, + opt_file_write_end: Option<&mut std::fs::File>, mut process_line: F, ) -> ProcessResult where F: FnMut(String) -> LineResult, { let mut reader = io::BufReader::new(read_end); - let mut writer = io::LineWriter::new(write_end); + let mut output_writer = io::LineWriter::new(output_write_end); + let mut file_writer = opt_file_write_end.map(io::LineWriter::new); // If there was an error parsing a line failed_on contains the offending line // and the error message. let mut failed_on: Option<(String, String)> = None; @@ -87,8 +89,11 @@ where if read_bytes == 0 { break; } + if let Some(ref mut file) = file_writer { + file.write_all(line.as_bytes())? + } match process_line(line.clone()) { - Ok(LineOutput::Message(to_write)) => writer.write_all(to_write.as_bytes())?, + Ok(LineOutput::Message(to_write)) => output_writer.write_all(to_write.as_bytes())?, Ok(LineOutput::Skip) => {} Ok(LineOutput::Terminate) => return Ok(()), Err(msg) => { @@ -101,8 +106,8 @@ where // If we encountered an error processing a line we want to flush the rest of // reader into writer and return the error. if let Some((line, msg)) = failed_on { - writer.write_all(line.as_bytes())?; - io::copy(&mut reader, &mut writer)?; + output_writer.write_all(line.as_bytes())?; + io::copy(&mut reader, &mut output_writer)?; return Err(ProcessError::Process(msg)); } Ok(()) @@ -116,7 +121,7 @@ mod test { fn test_json_parsing_error() { let mut input = io::Cursor::new(b"ok text\nsome more\nerror text"); let mut output: Vec = vec![]; - let result = process_output(&mut input, &mut output, move |line| { + let result = process_output(&mut input, &mut output, None, move |line| { if line == "ok text\n" { Ok(LineOutput::Skip) } else { diff --git a/util/process_wrapper/rustc.rs b/util/process_wrapper/rustc.rs index 7802172688..0e67bc55a3 100644 --- a/util/process_wrapper/rustc.rs +++ b/util/process_wrapper/rustc.rs @@ -71,13 +71,9 @@ pub(crate) fn process_json(line: String, error_format: ErrorFormat) -> LineResul .parse() .map_err(|_| "error parsing rustc output as json".to_owned())?; Ok(match parsed.try_into() { - Ok(RustcMessage::Message(msg)) => match error_format { - // If the output should be json, we just forward the messages as-is - // using `line`. - ErrorFormat::Json => LineOutput::Message(line), - // Otherwise we return the rendered field. - _ => LineOutput::Message(msg), - }, + Ok(RustcMessage::Message(rendered)) => { + output_based_on_error_format(line, rendered, error_format) + } _ => LineOutput::Skip, }) } @@ -87,7 +83,8 @@ pub(crate) fn process_json(line: String, error_format: ErrorFormat) -> LineResul /// is emitted so the compiler can be terminated. /// This is used to implement pipelining in rules_rust, please see /// https://internals.rust-lang.org/t/evaluating-pipelined-rustc-compilation/10199 -/// Retuns an errors if parsing json fails. +/// Retuns an error if parsing json fails. +/// TODO: pass a function to handle the emit event and merge with process_json pub(crate) fn stop_on_rmeta_completion( line: String, error_format: ErrorFormat, @@ -101,13 +98,23 @@ pub(crate) fn stop_on_rmeta_completion( *kill = true; LineOutput::Terminate } - Ok(RustcMessage::Message(msg)) => match error_format { - // If the output should be json, we just forward the messages as-is - // using `line`. - ErrorFormat::Json => LineOutput::Message(line), - // Otherwise we return the rendered field. - _ => LineOutput::Message(msg), - }, + Ok(RustcMessage::Message(rendered)) => { + output_based_on_error_format(line, rendered, error_format) + } _ => LineOutput::Skip, }) } + +fn output_based_on_error_format( + line: String, + rendered: String, + error_format: ErrorFormat, +) -> LineOutput { + match error_format { + // If the output should be json, we just forward the messages as-is + // using `line`. + ErrorFormat::Json => LineOutput::Message(line), + // Otherwise we return the rendered field. + ErrorFormat::Rendered => LineOutput::Message(rendered), + } +}