diff options
| author | Jing Peng <pj.hades@gmail.com> | 2023-02-26 15:27:27 -0500 |
|---|---|---|
| committer | Jing Peng <pj.hades@gmail.com> | 2023-06-06 17:53:29 -0400 |
| commit | 9b1a1e1d95d1e40bdf57ef9d37ccbac91fc9c280 (patch) | |
| tree | a9dfaf211a9e470ba26b20416916ab214a2be476 /compiler | |
| parent | 1221e43bdf413f7c405e9b17ef19d76c88222098 (diff) | |
| download | rust-9b1a1e1d95d1e40bdf57ef9d37ccbac91fc9c280.tar.gz rust-9b1a1e1d95d1e40bdf57ef9d37ccbac91fc9c280.zip | |
Write to stdout if `-` is given as output file
If `-o -` or `--emit KIND=-` is provided, output will be written to stdout instead. Binary output (`obj`, `llvm-bc`, `link` and `metadata`) being written this way will result in an error unless stdout is not a tty. Multiple output types going to stdout will trigger an error too, as they will all be mixded together.
Diffstat (limited to 'compiler')
21 files changed, 363 insertions, 96 deletions
diff --git a/compiler/rustc_codegen_ssa/messages.ftl b/compiler/rustc_codegen_ssa/messages.ftl index 9aa2b2e2b2e..5ecb63986fe 100644 --- a/compiler/rustc_codegen_ssa/messages.ftl +++ b/compiler/rustc_codegen_ssa/messages.ftl @@ -9,6 +9,8 @@ codegen_ssa_archive_build_failure = codegen_ssa_atomic_compare_exchange = Atomic compare-exchange intrinsic missing failure memory ordering +codegen_ssa_binary_output_to_tty = option `-o` or `--emit` is used to write binary output type `{$shorthand}` to stdout, but stdout is a tty + codegen_ssa_check_installed_visual_studio = please ensure that Visual Studio 2017 or later, or Build Tools for Visual Studio were installed with the Visual C++ option. codegen_ssa_copy_path = could not copy {$from} to {$to}: {$error} diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index 8a00c42a0e8..311e56cc7d1 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -8,7 +8,7 @@ use rustc_errors::{ErrorGuaranteed, Handler}; use rustc_fs_util::fix_windows_verbatim_for_gcc; use rustc_hir::def_id::{CrateNum, LOCAL_CRATE}; use rustc_metadata::find_native_static_library; -use rustc_metadata::fs::{emit_wrapper_file, METADATA_FILENAME}; +use rustc_metadata::fs::{copy_to_stdout, emit_wrapper_file, METADATA_FILENAME}; use rustc_middle::middle::debugger_visualizer::DebuggerVisualizerFile; use rustc_middle::middle::dependency_format::Linkage; use rustc_middle::middle::exported_symbols::SymbolExportKind; @@ -68,6 +68,7 @@ pub fn link_binary<'a>( ) -> Result<(), ErrorGuaranteed> { let _timer = sess.timer("link_binary"); let output_metadata = sess.opts.output_types.contains_key(&OutputType::Metadata); + let mut tempfiles_for_stdout_output: Vec<PathBuf> = Vec::new(); for &crate_type in sess.crate_types().iter() { // Ignore executable crates if we have -Z no-codegen, as they will error. if (sess.opts.unstable_opts.no_codegen || !sess.opts.output_types.should_codegen()) @@ -97,12 +98,15 @@ pub fn link_binary<'a>( .tempdir() .unwrap_or_else(|error| sess.emit_fatal(errors::CreateTempDir { error })); let path = MaybeTempDir::new(tmpdir, sess.opts.cg.save_temps); - let out_filename = out_filename( + let output = out_filename( sess, crate_type, outputs, codegen_results.crate_info.local_crate_name, ); + let crate_name = format!("{}", codegen_results.crate_info.local_crate_name); + let out_filename = + output.file_for_writing(outputs, OutputType::Exe, Some(crate_name.as_str())); match crate_type { CrateType::Rlib => { let _timer = sess.timer("link_rlib"); @@ -152,6 +156,17 @@ pub fn link_binary<'a>( ); } } + + if output.is_stdout() { + if output.is_tty() { + sess.emit_err(errors::BinaryOutputToTty { + shorthand: OutputType::Exe.shorthand(), + }); + } else if let Err(e) = copy_to_stdout(&out_filename) { + sess.emit_err(errors::CopyPath::new(&out_filename, output.as_path(), e)); + } + tempfiles_for_stdout_output.push(out_filename); + } } } @@ -189,6 +204,11 @@ pub fn link_binary<'a>( remove_temps_from_module(allocator_module); } + // Remove the temporary files if output goes to stdout + for temp in tempfiles_for_stdout_output { + ensure_removed(sess.diagnostic(), &temp); + } + // If no requested outputs require linking, then the object temporaries should // be kept. if !sess.opts.output_types.should_link() { diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index c323372bda4..a1e8725e08e 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -23,12 +23,13 @@ use rustc_hir::def_id::{CrateNum, LOCAL_CRATE}; use rustc_incremental::{ copy_cgu_workproduct_to_incr_comp_cache_dir, in_incr_comp_dir, in_incr_comp_dir_sess, }; +use rustc_metadata::fs::copy_to_stdout; use rustc_metadata::EncodedMetadata; use rustc_middle::dep_graph::{WorkProduct, WorkProductId}; use rustc_middle::middle::exported_symbols::SymbolExportInfo; use rustc_middle::ty::TyCtxt; use rustc_session::cgu_reuse_tracker::CguReuseTracker; -use rustc_session::config::{self, CrateType, Lto, OutputFilenames, OutputType}; +use rustc_session::config::{self, CrateType, Lto, OutFileName, OutputFilenames, OutputType}; use rustc_session::config::{Passes, SwitchWithOptPath}; use rustc_session::Session; use rustc_span::source_map::SourceMap; @@ -535,9 +536,16 @@ fn produce_final_output_artifacts( let mut user_wants_objects = false; // Produce final compile outputs. - let copy_gracefully = |from: &Path, to: &Path| { - if let Err(e) = fs::copy(from, to) { - sess.emit_err(errors::CopyPath::new(from, to, e)); + let copy_gracefully = |from: &Path, to: &OutFileName| match to { + OutFileName::Stdout => { + if let Err(e) = copy_to_stdout(from) { + sess.emit_err(errors::CopyPath::new(from, to.as_path(), e)); + } + } + OutFileName::Real(path) => { + if let Err(e) = fs::copy(from, path) { + sess.emit_err(errors::CopyPath::new(from, path, e)); + } } }; @@ -547,7 +555,12 @@ fn produce_final_output_artifacts( // to copy `foo.0.x` to `foo.x`. let module_name = Some(&compiled_modules.modules[0].name[..]); let path = crate_output.temp_path(output_type, module_name); - copy_gracefully(&path, &crate_output.path(output_type)); + let output = crate_output.path(output_type); + if !output_type.is_text_output() && output.is_tty() { + sess.emit_err(errors::BinaryOutputToTty { shorthand: output_type.shorthand() }); + } else { + copy_gracefully(&path, &output); + } if !sess.opts.cg.save_temps && !keep_numbered { // The user just wants `foo.x`, not `foo.#module-name#.x`. ensure_removed(sess.diagnostic(), &path); diff --git a/compiler/rustc_codegen_ssa/src/errors.rs b/compiler/rustc_codegen_ssa/src/errors.rs index cf4893b8226..3fed9ea0b41 100644 --- a/compiler/rustc_codegen_ssa/src/errors.rs +++ b/compiler/rustc_codegen_ssa/src/errors.rs @@ -83,6 +83,12 @@ impl IntoDiagnosticArg for DebugArgPath<'_> { } #[derive(Diagnostic)] +#[diag(codegen_ssa_binary_output_to_tty)] +pub struct BinaryOutputToTty { + pub shorthand: &'static str, +} + +#[derive(Diagnostic)] #[diag(codegen_ssa_ignoring_emit_path)] pub struct IgnoringEmitPath { pub extension: String, diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs index 40aa69e5a41..416603a0e1c 100644 --- a/compiler/rustc_driver_impl/src/lib.rs +++ b/compiler/rustc_driver_impl/src/lib.rs @@ -34,7 +34,9 @@ use rustc_interface::{interface, Queries}; use rustc_lint::LintStore; use rustc_metadata::locator; use rustc_session::config::{nightly_options, CG_OPTIONS, Z_OPTIONS}; -use rustc_session::config::{ErrorOutputType, Input, OutputType, PrintRequest, TrimmedDefPaths}; +use rustc_session::config::{ + ErrorOutputType, Input, OutFileName, OutputType, PrintRequest, TrimmedDefPaths, +}; use rustc_session::cstore::MetadataLoader; use rustc_session::getopts::{self, Matches}; use rustc_session::lint::{Lint, LintId}; @@ -437,9 +439,12 @@ fn run_compiler( } // Extract output directory and file from matches. -fn make_output(matches: &getopts::Matches) -> (Option<PathBuf>, Option<PathBuf>) { +fn make_output(matches: &getopts::Matches) -> (Option<PathBuf>, Option<OutFileName>) { let odir = matches.opt_str("out-dir").map(|o| PathBuf::from(&o)); - let ofile = matches.opt_str("o").map(|o| PathBuf::from(&o)); + let ofile = matches.opt_str("o").map(|o| match o.as_str() { + "-" => OutFileName::Stdout, + path => OutFileName::Real(PathBuf::from(path)), + }); (odir, ofile) } @@ -685,7 +690,7 @@ fn print_crate_info( for &style in &crate_types { let fname = rustc_session::output::filename_for_input(sess, style, id, &t_outputs); - safe_println!("{}", fname.file_name().unwrap().to_string_lossy()); + safe_println!("{}", fname.as_path().file_name().unwrap().to_string_lossy()); } } Cfg => { diff --git a/compiler/rustc_driver_impl/src/pretty.rs b/compiler/rustc_driver_impl/src/pretty.rs index ee64b18d3f6..24a5f4030b8 100644 --- a/compiler/rustc_driver_impl/src/pretty.rs +++ b/compiler/rustc_driver_impl/src/pretty.rs @@ -9,7 +9,7 @@ use rustc_hir_pretty as pprust_hir; use rustc_middle::hir::map as hir_map; use rustc_middle::mir::{write_mir_graphviz, write_mir_pretty}; use rustc_middle::ty::{self, TyCtxt}; -use rustc_session::config::{PpAstTreeMode, PpHirMode, PpMode, PpSourceMode}; +use rustc_session::config::{OutFileName, PpAstTreeMode, PpHirMode, PpMode, PpSourceMode}; use rustc_session::Session; use rustc_span::symbol::Ident; use rustc_span::FileName; @@ -359,8 +359,8 @@ fn get_source(sess: &Session) -> (String, FileName) { fn write_or_print(out: &str, sess: &Session) { match &sess.io.output_file { - None => print!("{out}"), - Some(p) => { + None | Some(OutFileName::Stdout) => print!("{out}"), + Some(OutFileName::Real(p)) => { if let Err(e) = std::fs::write(p, out) { sess.emit_fatal(UnprettyDumpFail { path: p.display().to_string(), diff --git a/compiler/rustc_interface/Cargo.toml b/compiler/rustc_interface/Cargo.toml index 2c7438ed9db..7826d42dcb2 100644 --- a/compiler/rustc_interface/Cargo.toml +++ b/compiler/rustc_interface/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [lib] [dependencies] +atty = "0.2.13" libloading = "0.7.1" tracing = "0.1" rustc-rayon-core = { version = "0.5.0", optional = true } diff --git a/compiler/rustc_interface/messages.ftl b/compiler/rustc_interface/messages.ftl index be1a75f020b..bd9fad8b042 100644 --- a/compiler/rustc_interface/messages.ftl +++ b/compiler/rustc_interface/messages.ftl @@ -33,6 +33,7 @@ interface_mixed_proc_macro_crate = interface_multiple_output_types_adaption = due to multiple output types requested, the explicitly specified output file name will be adapted for each output type +interface_multiple_output_types_to_stdout = can't use option `-o` or `--emit` to write multiple output types to stdout interface_out_dir_error = failed to find or create the directory specified by `--out-dir` diff --git a/compiler/rustc_interface/src/errors.rs b/compiler/rustc_interface/src/errors.rs index 0eedee25026..a9ab2720d89 100644 --- a/compiler/rustc_interface/src/errors.rs +++ b/compiler/rustc_interface/src/errors.rs @@ -108,3 +108,7 @@ pub struct IgnoringExtraFilename; #[derive(Diagnostic)] #[diag(interface_ignoring_out_dir)] pub struct IgnoringOutDir; + +#[derive(Diagnostic)] +#[diag(interface_multiple_output_types_to_stdout)] +pub struct MultipleOutputTypesToStdout; diff --git a/compiler/rustc_interface/src/interface.rs b/compiler/rustc_interface/src/interface.rs index 39d56897999..2edc72ba72e 100644 --- a/compiler/rustc_interface/src/interface.rs +++ b/compiler/rustc_interface/src/interface.rs @@ -14,7 +14,7 @@ use rustc_middle::{bug, ty}; use rustc_parse::maybe_new_parser_from_source_str; use rustc_query_impl::QueryCtxt; use rustc_query_system::query::print_query_stack; -use rustc_session::config::{self, ErrorOutputType, Input, OutputFilenames}; +use rustc_session::config::{self, ErrorOutputType, Input, OutFileName, OutputFilenames}; use rustc_session::config::{CheckCfg, ExpectedValues}; use rustc_session::lint; use rustc_session::parse::{CrateConfig, ParseSess}; @@ -252,7 +252,7 @@ pub struct Config { pub input: Input, pub output_dir: Option<PathBuf>, - pub output_file: Option<PathBuf>, + pub output_file: Option<OutFileName>, pub file_loader: Option<Box<dyn FileLoader + Send + Sync>>, pub locale_resources: &'static [&'static str], diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index 42d8d228091..83a74742f5b 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -24,7 +24,7 @@ use rustc_parse::{parse_crate_from_file, parse_crate_from_source_str, validate_a use rustc_passes::{self, hir_stats, layout_test}; use rustc_plugin_impl as plugin; use rustc_resolve::Resolver; -use rustc_session::config::{CrateType, Input, OutputFilenames, OutputType}; +use rustc_session::config::{CrateType, Input, OutFileName, OutputFilenames, OutputType}; use rustc_session::cstore::{MetadataLoader, Untracked}; use rustc_session::output::filename_for_input; use rustc_session::search_paths::PathKind; @@ -373,19 +373,23 @@ fn generated_output_paths( ) -> Vec<PathBuf> { let mut out_filenames = Vec::new(); for output_type in sess.opts.output_types.keys() { - let file = outputs.path(*output_type); + let out_filename = outputs.path(*output_type); + let file = out_filename.as_path().to_path_buf(); match *output_type { // If the filename has been overridden using `-o`, it will not be modified // by appending `.rlib`, `.exe`, etc., so we can skip this transformation. OutputType::Exe if !exact_name => { for crate_type in sess.crate_types().iter() { let p = filename_for_input(sess, *crate_type, crate_name, outputs); - out_filenames.push(p); + out_filenames.push(p.as_path().to_path_buf()); } } OutputType::DepInfo if sess.opts.unstable_opts.dep_info_omit_d_target => { // Don't add the dep-info output when omitting it from dep-info targets } + OutputType::DepInfo if out_filename.is_stdout() => { + // Don't add the dep-info output when it goes to stdout + } _ => { out_filenames.push(file); } @@ -452,7 +456,8 @@ fn write_out_deps(tcx: TyCtxt<'_>, outputs: &OutputFilenames, out_filenames: &[P if !sess.opts.output_types.contains_key(&OutputType::DepInfo) { return; } - let deps_filename = outputs.path(OutputType::DepInfo); + let deps_output = outputs.path(OutputType::DepInfo); + let deps_filename = deps_output.as_path(); let result: io::Result<()> = try { // Build a list of files used to compile the output and @@ -515,33 +520,47 @@ fn write_out_deps(tcx: TyCtxt<'_>, outputs: &OutputFilenames, out_filenames: &[P } } - let mut file = BufWriter::new(fs::File::create(&deps_filename)?); - for path in out_filenames { - writeln!(file, "{}: {}\n", path.display(), files.join(" "))?; - } + let write_deps_to_file = |file: &mut dyn Write| -> io::Result<()> { + for path in out_filenames { + writeln!(file, "{}: {}\n", path.display(), files.join(" "))?; + } - // Emit a fake target for each input file to the compilation. This - // prevents `make` from spitting out an error if a file is later - // deleted. For more info see #28735 - for path in files { - writeln!(file, "{path}:")?; - } + // Emit a fake target for each input file to the compilation. This + // prevents `make` from spitting out an error if a file is later + // deleted. For more info see #28735 + for path in files { + writeln!(file, "{path}:")?; + } - // Emit special comments with information about accessed environment variables. - let env_depinfo = sess.parse_sess.env_depinfo.borrow(); - if !env_depinfo.is_empty() { - let mut envs: Vec<_> = env_depinfo - .iter() - .map(|(k, v)| (escape_dep_env(*k), v.map(escape_dep_env))) - .collect(); - envs.sort_unstable(); - writeln!(file)?; - for (k, v) in envs { - write!(file, "# env-dep:{k}")?; - if let Some(v) = v { - write!(file, "={v}")?; - } + // Emit special comments with information about accessed environment variables. + let env_depinfo = sess.parse_sess.env_depinfo.borrow(); + if !env_depinfo.is_empty() { + let mut envs: Vec<_> = env_depinfo + .iter() + .map(|(k, v)| (escape_dep_env(*k), v.map(escape_dep_env))) + .collect(); + envs.sort_unstable(); writeln!(file)?; + for (k, v) in envs { + write!(file, "# env-dep:{k}")?; + if let Some(v) = v { + write!(file, "={v}")?; + } + writeln!(file)?; + } + } + + Ok(()) + }; + + match deps_output { + OutFileName::Stdout => { + let mut file = BufWriter::new(io::stdout()); + write_deps_to_file(&mut file)?; + } + OutFileName::Real(ref path) => { + let mut file = BufWriter::new(fs::File::create(path)?); + write_deps_to_file(&mut file)?; } } }; diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs index 28e719a40e5..77ee2b40e37 100644 --- a/compiler/rustc_interface/src/tests.rs +++ b/compiler/rustc_interface/src/tests.rs @@ -11,7 +11,7 @@ use rustc_session::config::InstrumentXRay; use rustc_session::config::TraitSolver; use rustc_session::config::{build_configuration, build_session_options, to_crate_config}; use rustc_session::config::{ - BranchProtection, Externs, OomStrategy, OutputType, OutputTypes, PAuthKey, PacRet, + BranchProtection, Externs, OomStrategy, OutFileName, OutputType, OutputTypes, PAuthKey, PacRet, ProcMacroExecutionStrategy, SymbolManglingVersion, WasiExecModel, }; use rustc_session::config::{CFGuard, ExternEntry, LinkerPluginLto, LtoCli, SwitchWithOptPath}; @@ -167,8 +167,14 @@ fn test_output_types_tracking_hash_different_paths() { let mut v2 = Options::default(); let mut v3 = Options::default(); - v1.output_types = OutputTypes::new(&[(OutputType::Exe, Some(PathBuf::from("./some/thing")))]); - v2.output_types = OutputTypes::new(&[(OutputType::Exe, Some(PathBuf::from("/some/thing")))]); + v1.output_types = OutputTypes::new(&[( + OutputType::Exe, + Some(OutFileName::Real(PathBuf::from("./some/thing"))), + )]); + v2.output_types = OutputTypes::new(&[( + OutputType::Exe, + Some(OutFileName::Real(PathBuf::from("/some/thing"))), + )]); v3.output_types = OutputTypes::new(&[(OutputType::Exe, None)]); assert_non_crate_hash_different(&v1, &v2); @@ -182,13 +188,13 @@ fn test_output_types_tracking_hash_different_construction_order() { let mut v2 = Options::default(); v1.output_types = OutputTypes::new(&[ - (OutputType::Exe, Some(PathBuf::from("./some/thing"))), - (OutputType::Bitcode, Some(PathBuf::from("./some/thing.bc"))), + (OutputType::Exe, Some(OutFileName::Real(PathBuf::from("./some/thing")))), + (OutputType::Bitcode, Some(OutFileName::Real(PathBuf::from("./some/thing.bc")))), ]); v2.output_types = OutputTypes::new(&[ - (OutputType::Bitcode, Some(PathBuf::from("./some/thing.bc"))), - (OutputType::Exe, Some(PathBuf::from("./some/thing"))), + (OutputType::Bitcode, Some(OutFileName::Real(PathBuf::from("./some/thing.bc")))), + (OutputType::Exe, Some(OutFileName::Real(PathBuf::from("./some/thing")))), ]); assert_same_hash(&v1, &v2); diff --git a/compiler/rustc_interface/src/util.rs b/compiler/rustc_interface/src/util.rs index cb19750203e..87252fefb1e 100644 --- a/compiler/rustc_interface/src/util.rs +++ b/compiler/rustc_interface/src/util.rs @@ -11,7 +11,7 @@ use rustc_parse::validate_attr; use rustc_session as session; use rustc_session::config::CheckCfg; use rustc_session::config::{self, CrateType}; -use rustc_session::config::{ErrorOutputType, OutputFilenames}; +use rustc_session::config::{ErrorOutputType, OutFileName, OutputFilenames, OutputTypes}; use rustc_session::filesearch::sysroot_candidates; use rustc_session::lint::{self, BuiltinLintDiagnostics, LintBuffer}; use rustc_session::parse::CrateConfig; @@ -500,7 +500,36 @@ pub fn collect_crate_types(session: &Session, attrs: &[ast::Attribute]) -> Vec<C base } +fn multiple_output_types_to_stdout( + output_types: &OutputTypes, + single_output_file_is_stdout: bool, +) -> bool { + if atty::is(atty::Stream::Stdout) { + // If stdout is a tty, check if multiple text output types are + // specified by `--emit foo=- --emit bar=-` or `-o - --emit foo,bar` + let named_text_types = output_types + .iter() + .filter(|(f, o)| f.is_text_output() && *o == &Some(OutFileName::Stdout)) + .count(); + let unnamed_text_types = + output_types.iter().filter(|(f, o)| f.is_text_output() && o.is_none()).count(); + named_text_types > 1 || unnamed_text_types > 1 && single_output_file_is_stdout + } else { + // Otherwise, all the output types should be checked + let named_types = + output_types.values().filter(|o| *o == &Some(OutFileName::Stdout)).count(); + let unnamed_types = output_types.values().filter(|o| o.is_none()).count(); + named_types > 1 || unnamed_types > 1 && single_output_file_is_stdout + } +} + pub fn build_output_filenames(attrs: &[ast::Attribute], sess: &Session) -> OutputFilenames { + if multiple_output_types_to_stdout( + &sess.opts.output_types, + sess.io.output_file == Some(OutFileName::Stdout), + ) { + sess.emit_fatal(errors::MultipleOutputTypesToStdout); + } match sess.io.output_file { None => { // "-" as input file will cause the parser to read from stdin so we @@ -544,7 +573,7 @@ pub fn build_output_filenames(attrs: &[ast::Attribute], sess: &Session) -> Outpu OutputFilenames::new( out_file.parent().unwrap_or_else(|| Path::new("")).to_path_buf(), - out_file.file_stem().unwrap_or_default().to_str().unwrap().to_string(), + out_file.filestem().unwrap_or_default().to_str().unwrap().to_string(), ofile, sess.io.temps_dir.clone(), sess.opts.cg.extra_filename.clone(), diff --git a/compiler/rustc_metadata/messages.ftl b/compiler/rustc_metadata/messages.ftl index 6d8601b9e2b..d6b08d840e3 100644 --- a/compiler/rustc_metadata/messages.ftl +++ b/compiler/rustc_metadata/messages.ftl @@ -4,6 +4,9 @@ metadata_as_needed_compatibility = metadata_bad_panic_strategy = the linked panic runtime `{$runtime}` is not compiled with this crate's panic strategy `{$strategy}` +metadata_binary_output_to_tty = + option `-o` or `--emit` is used to write binary output type `metadata` to stdout, but stdout is a tty + metadata_bundle_needs_static = linking modifier `bundle` is only compatible with `static` linking kind @@ -63,6 +66,9 @@ metadata_fail_seek_file = metadata_fail_write_file = failed to write to the file: {$err} +metadata_failed_copy_to_stdout = + failed to copy {$filename} to stdout: {$err} + metadata_failed_create_encoded_metadata = failed to create encoded metadata from file: {$err} @@ -72,6 +78,9 @@ metadata_failed_create_file = metadata_failed_create_tempdir = couldn't create a temp dir: {$err} +metadata_failed_remove = + failed to remove {$filename}: {$err} + metadata_failed_write_error = failed to write {$filename}: {$err} diff --git a/compiler/rustc_metadata/src/errors.rs b/compiler/rustc_metadata/src/errors.rs index a44c1dd582e..e110c68321d 100644 --- a/compiler/rustc_metadata/src/errors.rs +++ b/compiler/rustc_metadata/src/errors.rs @@ -396,6 +396,24 @@ pub struct FailedWriteError { } #[derive(Diagnostic)] +#[diag(metadata_failed_copy_to_stdout)] +pub struct FailedCopyToStdout { + pub filename: PathBuf, + pub err: Error, +} + +#[derive(Diagnostic)] +#[diag(metadata_failed_remove)] +pub struct FailedRemove { + pub filename: PathBuf, + pub err: Error, +} + +#[derive(Diagnostic)] +#[diag(metadata_binary_output_to_tty)] +pub struct BinaryOutputToTty; + +#[derive(Diagnostic)] #[diag(metadata_missing_native_library)] pub struct MissingNativeLibrary<'a> { libname: &'a str, diff --git a/compiler/rustc_metadata/src/fs.rs b/compiler/rustc_metadata/src/fs.rs index 08de828fbdb..5be99c8e4c0 100644 --- a/compiler/rustc_metadata/src/fs.rs +++ b/compiler/rustc_metadata/src/fs.rs @@ -1,18 +1,19 @@ use crate::errors::{ - FailedCreateEncodedMetadata, FailedCreateFile, FailedCreateTempdir, FailedWriteError, + BinaryOutputToTty, FailedCopyToStdout, FailedCreateEncodedMetadata, FailedCreateFile, + FailedCreateTempdir, FailedRemove, FailedWriteError, }; use crate::{encode_metadata, EncodedMetadata}; use rustc_data_structures::temp_dir::MaybeTempDir; use rustc_hir::def_id::LOCAL_CRATE; use rustc_middle::ty::TyCtxt; -use rustc_session::config::OutputType; +use rustc_session::config::{OutFileName, OutputType}; use rustc_session::output::filename_for_metadata; use rustc_session::{MetadataKind, Session}; use tempfile::Builder as TempFileBuilder; -use std::fs; use std::path::{Path, PathBuf}; +use std::{fs, io}; // FIXME(eddyb) maybe include the crate name in this? pub const METADATA_FILENAME: &str = "lib.rmeta"; @@ -74,26 +75,47 @@ pub fn encode_and_write_metadata(tcx: TyCtxt<'_>) -> (EncodedMetadata, bool) { // this file always exists. let need_metadata_file = tcx.sess.opts.output_types.contains_key(&OutputType::Metadata); let (metadata_filename, metadata_tmpdir) = if need_metadata_file { - if let Err(err) = non_durable_rename(&metadata_filename, &out_filename) { - tcx.sess.emit_fatal(FailedWriteError { filename: out_filename, err }); - } + let filename = match out_filename { + OutFileName::Real(ref path) => { + if let Err(err) = non_durable_rename(&metadata_filename, path) { + tcx.sess.emit_fatal(FailedWriteError { filename: path.to_path_buf(), err }); + } + path.clone() + } + OutFileName::Stdout => { + if out_filename.is_tty() { + tcx.sess.emit_err(BinaryOutputToTty); + } else if let Err(err) = copy_to_stdout(&metadata_filename) { + tcx.sess + .emit_err(FailedCopyToStdout { filename: metadata_filename.clone(), err }); + } + metadata_filename + } + }; if tcx.sess.opts.json_artifact_notifications { tcx.sess .parse_sess .span_diagnostic - .emit_artifact_notification(&out_filename, "metadata"); + .emit_artifact_notification(&out_filename.as_path(), "metadata"); } - (out_filename, None) + (filename, None) } else { (metadata_filename, Some(metadata_tmpdir)) }; // Load metadata back to memory: codegen may need to include it in object files. - let metadata = - EncodedMetadata::from_path(metadata_filename, metadata_tmpdir).unwrap_or_else(|err| { + let metadata = EncodedMetadata::from_path(metadata_filename.clone(), metadata_tmpdir) + .unwrap_or_else(|err| { tcx.sess.emit_fatal(FailedCreateEncodedMetadata { err }); }); + // Delete the temporary metadata file if output is stdout + if need_metadata_file && out_filename.is_stdout() { + if let Err(err) = fs::remove_file(&metadata_filename) { + tcx.sess.emit_err(FailedRemove { filename: metadata_filename, err }); + } + } + let need_metadata_module = metadata_kind == MetadataKind::Compressed; (metadata, need_metadata_module) @@ -116,3 +138,11 @@ pub fn non_durable_rename(src: &Path, dst: &Path) -> std::io::Result<()> { let _ = std::fs::remove_file(dst); std::fs::rename(src, dst) } + +pub fn copy_to_stdout(from: &Path) -> io::Result<()> { + let file = fs::File::open(from)?; + let mut reader = io::BufReader::new(file); + let mut stdout = io::stdout(); + io::copy(&mut reader, &mut stdout)?; + Ok(()) +} diff --git a/compiler/rustc_mir_transform/src/dump_mir.rs b/compiler/rustc_mir_transform/src/dump_mir.rs index 746e3d9652d..13841be494c 100644 --- a/compiler/rustc_mir_transform/src/dump_mir.rs +++ b/compiler/rustc_mir_transform/src/dump_mir.rs @@ -7,7 +7,7 @@ use crate::MirPass; use rustc_middle::mir::write_mir_pretty; use rustc_middle::mir::Body; use rustc_middle::ty::TyCtxt; -use rustc_session::config::OutputType; +use rustc_session::config::{OutFileName, OutputType}; pub struct Marker(pub &'static str); @@ -20,8 +20,15 @@ impl<'tcx> MirPass<'tcx> for Marker { } pub fn emit_mir(tcx: TyCtxt<'_>) -> io::Result<()> { - let path = tcx.output_filenames(()).path(OutputType::Mir); - let mut f = io::BufWriter::new(File::create(&path)?); - write_mir_pretty(tcx, None, &mut f)?; + match tcx.output_filenames(()).path(OutputType::Mir) { + OutFileName::Stdout => { + let mut f = io::stdout(); + write_mir_pretty(tcx, None, &mut f)?; + } + OutFileName::Real(path) => { + let mut f = io::BufWriter::new(File::create(&path)?); + write_mir_pretty(tcx, None, &mut f)?; + } + } Ok(()) } diff --git a/compiler/rustc_session/Cargo.toml b/compiler/rustc_session/Cargo.toml index 3af83aaaaa8..90ad3f90f2c 100644 --- a/compiler/rustc_session/Cargo.toml +++ b/compiler/rustc_session/Cargo.toml @@ -4,6 +4,7 @@ version = "0.0.0" edition = "2021" [dependencies] +atty = "0.2.13" getopts = "0.2" rustc_macros = { path = "../rustc_macros" } tracing = "0.1" diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index 6c8c8e484f9..b72a95639e6 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -30,6 +30,7 @@ use std::collections::btree_map::{ Iter as BTreeMapIter, Keys as BTreeMapKeysIter, Values as BTreeMapValuesIter, }; use std::collections::{BTreeMap, BTreeSet}; +use std::ffi::OsStr; use std::fmt; use std::hash::Hash; use std::iter; @@ -333,7 +334,7 @@ impl OutputType { } } - fn shorthand(&self) -> &'static str { + pub fn shorthand(&self) -> &'static str { match *self { OutputType::Bitcode => "llvm-bc", OutputType::Assembly => "asm", @@ -386,6 +387,18 @@ impl OutputType { OutputType::Exe => "", } } + + pub fn is_text_output(&self) -> bool { + match *self { + OutputType::Assembly + | OutputType::LlvmAssembly + | OutputType::Mir + | OutputType::DepInfo => true, + OutputType::Bitcode | OutputType::Object | OutputType::Metadata | OutputType::Exe => { + false + } + } + } } /// The type of diagnostics output to generate. @@ -438,14 +451,14 @@ pub enum ResolveDocLinks { /// dependency tracking for command-line arguments. Also only hash keys, since tracking /// should only depend on the output types, not the paths they're written to. #[derive(Clone, Debug, Hash, HashStable_Generic)] -pub struct OutputTypes(BTreeMap<OutputType, Option<PathBuf>>); +pub struct OutputTypes(BTreeMap<OutputType, Option<OutFileName>>); impl OutputTypes { - pub fn new(entries: &[(OutputType, Option<PathBuf>)]) -> OutputTypes { + pub fn new(entries: &[(OutputType, Option<OutFileName>)]) -> OutputTypes { OutputTypes(BTreeMap::from_iter(entries.iter().map(|&(k, ref v)| (k, v.clone())))) } - pub fn get(&self, key: &OutputType) -> Option<&Option<PathBuf>> { + pub fn get(&self, key: &OutputType) -> Option<&Option<OutFileName>> { self.0.get(key) } @@ -453,11 +466,15 @@ impl OutputTypes { self.0.contains_key(key) } - pub fn keys(&self) -> BTreeMapKeysIter<'_, OutputType, Option<PathBuf>> { + pub fn iter(&self) -> BTreeMapIter<'_, OutputType, Option<OutFileName>> { + self.0.iter() + } + + pub fn keys(&self) -> BTreeMapKeysIter<'_, OutputType, Option<OutFileName>> { self.0.keys() } - pub fn values(&self) -> BTreeMapValuesIter<'_, OutputType, Option<PathBuf>> { + pub fn values(&self) -> BTreeMapValuesIter<'_, OutputType, Option<OutFileName>> { self.0.values() } @@ -658,11 +675,71 @@ impl Input { } } +#[derive(Clone, Hash, Debug, HashStable_Generic, PartialEq)] +pub enum OutFileName { + Real(PathBuf), + Stdout, +} + +impl OutFileName { + pub fn parent(&self) -> Option<&Path> { + match *self { + OutFileName::Real(ref path) => path.parent(), + OutFileName::Stdout => None, + } + } + + pub fn filestem(&self) -> Option<&OsStr> { + match *self { + OutFileName::Real(ref path) => path.file_stem(), + OutFileName::Stdout => Some(OsStr::new("stdout")), + } + } + + pub fn is_stdout(&self) -> bool { + match *self { + OutFileName::Real(_) => false, + OutFileName::Stdout => true, + } + } + + pub fn is_tty(&self) -> bool { + match *self { + OutFileName::Real(_) => false, + OutFileName::Stdout => atty::is(atty::Stream::Stdout), + } + } + + pub fn as_path(&self) -> &Path { + match *self { + OutFileName::Real(ref path) => path.as_ref(), + OutFileName::Stdout => &Path::new("stdout"), + } + } + + /// For a given output filename, return the actual name of the file that + /// can be used to write codegen data of type `flavor`. For real-path + /// output filenames, this would be trivial as we can just use the path. + /// Otherwise for stdout, return a temporary path so that the codegen data + /// may be later copied to stdout. + pub fn file_for_writing( + &self, + outputs: &OutputFilenames, + flavor: OutputType, + codegen_unit_name: Option<&str>, + ) -> PathBuf { + match *self { + OutFileName::Real(ref path) => path.clone(), + OutFileName::Stdout => outputs.temp_path(flavor, codegen_unit_name), + } + } +} + #[derive(Clone, Hash, Debug, HashStable_Generic)] pub struct OutputFilenames { pub out_directory: PathBuf, filestem: String, - pub single_output_file: Option<PathBuf>, + pub single_output_file: Option<OutFileName>, pub temps_directory: Option<PathBuf>, pub outputs: OutputTypes, } @@ -675,7 +752,7 @@ impl OutputFilenames { pub fn new( out_directory: PathBuf, out_filestem: String, - single_output_file: Option<PathBuf>, + single_output_file: Option<OutFileName>, temps_directory: Option<PathBuf>, extra: String, outputs: OutputTypes, @@ -689,12 +766,12 @@ impl OutputFilenames { } } - pub fn path(&self, flavor: OutputType) -> PathBuf { + pub fn path(&self, flavor: OutputType) -> OutFileName { self.outputs .get(&flavor) .and_then(|p| p.to_owned()) .or_else(|| self.single_output_file.clone()) - .unwrap_or_else(|| self.output_path(flavor)) + .unwrap_or_else(|| OutFileName::Real(self.output_path(flavor))) } /// Gets the output path where a compilation artifact of the given type @@ -1821,7 +1898,10 @@ fn parse_output_types( for output_type in list.split(',') { let (shorthand, path) = match output_type.split_once('=') { None => (output_type, None), - Some((shorthand, path)) => (shorthand, Some(PathBuf::from(path))), + Some((shorthand, "-")) => (shorthand, Some(OutFileName::Stdout)), + Some((shorthand, path)) => { + (shorthand, Some(OutFileName::Real(PathBuf::from(path)))) + } }; let output_type = OutputType::from_shorthand(shorthand).unwrap_or_else(|| { early_error( @@ -2892,7 +2972,7 @@ pub(crate) mod dep_tracking { use super::{ BranchProtection, CFGuard, CFProtection, CrateType, DebugInfo, ErrorOutputType, InstrumentCoverage, InstrumentXRay, LdImpl, LinkerPluginLto, LocationDetail, LtoCli, - OomStrategy, OptLevel, OutputType, OutputTypes, Passes, ResolveDocLinks, + OomStrategy, OptLevel, OutFileName, OutputType, OutputTypes, Passes, ResolveDocLinks, SourceFileHashAlgorithm, SplitDwarfKind, SwitchWithOptPath, SymbolManglingVersion, TraitSolver, TrimmedDefPaths, }; @@ -2990,6 +3070,7 @@ pub(crate) mod dep_tracking { SourceFileHashAlgorithm, TrimmedDefPaths, Option<LdImpl>, + OutFileName, OutputType, RealFileName, LocationDetail, diff --git a/compiler/rustc_session/src/output.rs b/compiler/rustc_session/src/output.rs index fdb9fae44e1..2088744bc5b 100644 --- a/compiler/rustc_session/src/output.rs +++ b/compiler/rustc_session/src/output.rs @@ -1,5 +1,5 @@ //! Related to out filenames of compilation (e.g. save analysis, binaries). -use crate::config::{CrateType, Input, OutputFilenames, OutputType}; +use crate::config::{CrateType, Input, OutFileName, OutputFilenames, OutputType}; use crate::errors::{ CrateNameDoesNotMatch, CrateNameEmpty, CrateNameInvalid, FileIsNotWriteable, InvalidCharacterInCrateName, @@ -8,14 +8,14 @@ use crate::Session; use rustc_ast::{self as ast, attr}; use rustc_span::symbol::sym; use rustc_span::{Span, Symbol}; -use std::path::{Path, PathBuf}; +use std::path::Path; pub fn out_filename( sess: &Session, crate_type: CrateType, outputs: &OutputFilenames, crate_name: Symbol, -) -> PathBuf { +) -> OutFileName { let default_filename = filename_for_input(sess, crate_type, crate_name, outputs); let out_filename = outputs .outputs @@ -24,7 +24,9 @@ pub fn out_filename( .or_else(|| outputs.single_output_file.clone()) .unwrap_or(default_filename); - check_file_is_writeable(&out_filename, sess); + if let OutFileName::Real(ref path) = out_filename { + check_file_is_writeable(path, sess); + } out_filename } @@ -112,7 +114,7 @@ pub fn filename_for_metadata( sess: &Session, crate_name: Symbol, outputs: &OutputFilenames, -) -> PathBuf { +) -> OutFileName { // If the command-line specified the path, use that directly. if let Some(Some(out_filename)) = sess.opts.output_types.get(&OutputType::Metadata) { return out_filename.clone(); @@ -120,12 +122,13 @@ pub fn filename_for_metadata( let libname = format!("{}{}", crate_name, sess.opts.cg.extra_filename); - let out_filename = outputs - .single_output_file - .clone() - .unwrap_or_else(|| outputs.out_directory.join(&format!("lib{libname}.rmeta"))); + let out_filename = outputs.single_output_file.clone().unwrap_or_else(|| { + OutFileName::Real(outputs.out_directory.join(&format!("lib{libname}.rmeta"))) + }); - check_file_is_writeable(&out_filename, sess); + if let OutFileName::Real(ref path) = out_filename { + check_file_is_writeable(path, sess); + } out_filename } @@ -135,23 +138,33 @@ pub fn filename_for_input( crate_type: CrateType, crate_name: Symbol, outputs: &OutputFilenames, -) -> PathBuf { +) -> OutFileName { let libname = format!("{}{}", crate_name, sess.opts.cg.extra_filename); match crate_type { - CrateType::Rlib => outputs.out_directory.join(&format!("lib{libname}.rlib")), + CrateType::Rlib => { + OutFileName::Real(outputs.out_directory.join(&format!("lib{libname}.rlib"))) + } CrateType::Cdylib | CrateType::ProcMacro | CrateType::Dylib => { let (prefix, suffix) = (&sess.target.dll_prefix, &sess.target.dll_suffix); - outputs.out_directory.join(&format!("{prefix}{libname}{suffix}")) + OutFileName::Real(outputs.out_directory.join(&format!("{prefix}{libname}{suffix}"))) } CrateType::Staticlib => { let (prefix, suffix) = (&sess.target.staticlib_prefix, &sess.target.staticlib_suffix); - outputs.out_directory.join(&format!("{prefix}{libname}{suffix}")) + OutFileName::Real(outputs.out_directory.join(&format!("{prefix}{libname}{suffix}"))) } CrateType::Executable => { let suffix = &sess.target.exe_suffix; let out_filename = outputs.path(OutputType::Exe); - if suffix.is_empty() { out_filename } else { out_filename.with_extension(&suffix[1..]) } + if let OutFileName::Real(ref path) = out_filename { + if suffix.is_empty() { + out_filename + } else { + OutFileName::Real(path.with_extension(&suffix[1..])) + } + } else { + out_filename + } } } } diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index bbe52dbced0..08e2f19e11b 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -2,7 +2,9 @@ use crate::cgu_reuse_tracker::CguReuseTracker; use crate::code_stats::CodeStats; pub use crate::code_stats::{DataTypeKind, FieldInfo, FieldKind, SizeKind, VariantInfo}; use crate::config::Input; -use crate::config::{self, CrateType, InstrumentCoverage, OptLevel, OutputType, SwitchWithOptPath}; +use crate::config::{ + self, CrateType, InstrumentCoverage, OptLevel, OutFileName, OutputType, SwitchWithOptPath, +}; use crate::errors; use crate::parse::{add_feature_diagnostics, ParseSess}; use crate::search_paths::{PathKind, SearchPath}; @@ -135,7 +137,7 @@ pub struct Limits { pub struct CompilerIO { pub input: Input, pub output_dir: Option<PathBuf>, - pub output_file: Option<PathBuf>, + pub output_file: Option<OutFileName>, pub temps_dir: Option<PathBuf>, } |
