1
Fork 0

Auto merge of #111626 - pjhades:output, r=b-naber

Write to stdout if `-` is given as output file

With this PR, if `-o -` or `--emit KIND=-` is provided, output will be written to stdout instead. Binary output (those of type `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.

This implements https://github.com/rust-lang/compiler-team/issues/431

The idea behind the changes is to introduce an `OutFileName` enum that represents the output - be it a real path or stdout - and to use this enum along the code paths that handle different output types.
This commit is contained in:
bors 2023-06-09 09:45:40 +00:00
commit 343ad6f059
32 changed files with 439 additions and 101 deletions

View file

@ -3595,6 +3595,7 @@ dependencies = [
name = "rustc_interface" name = "rustc_interface"
version = "0.0.0" version = "0.0.0"
dependencies = [ dependencies = [
"atty",
"libloading", "libloading",
"rustc-rayon", "rustc-rayon",
"rustc-rayon-core", "rustc-rayon-core",
@ -4059,6 +4060,7 @@ dependencies = [
name = "rustc_session" name = "rustc_session"
version = "0.0.0" version = "0.0.0"
dependencies = [ dependencies = [
"atty",
"getopts", "getopts",
"libc", "libc",
"rustc_ast", "rustc_ast",

View file

@ -9,6 +9,8 @@ codegen_ssa_archive_build_failure =
codegen_ssa_atomic_compare_exchange = Atomic compare-exchange intrinsic missing failure memory ordering 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_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} codegen_ssa_copy_path = could not copy {$from} to {$to}: {$error}

View file

@ -8,7 +8,7 @@ use rustc_errors::{ErrorGuaranteed, Handler};
use rustc_fs_util::fix_windows_verbatim_for_gcc; use rustc_fs_util::fix_windows_verbatim_for_gcc;
use rustc_hir::def_id::{CrateNum, LOCAL_CRATE}; use rustc_hir::def_id::{CrateNum, LOCAL_CRATE};
use rustc_metadata::find_native_static_library; 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::debugger_visualizer::DebuggerVisualizerFile;
use rustc_middle::middle::dependency_format::Linkage; use rustc_middle::middle::dependency_format::Linkage;
use rustc_middle::middle::exported_symbols::SymbolExportKind; use rustc_middle::middle::exported_symbols::SymbolExportKind;
@ -68,6 +68,7 @@ pub fn link_binary<'a>(
) -> Result<(), ErrorGuaranteed> { ) -> Result<(), ErrorGuaranteed> {
let _timer = sess.timer("link_binary"); let _timer = sess.timer("link_binary");
let output_metadata = sess.opts.output_types.contains_key(&OutputType::Metadata); 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() { for &crate_type in sess.crate_types().iter() {
// Ignore executable crates if we have -Z no-codegen, as they will error. // 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()) if (sess.opts.unstable_opts.no_codegen || !sess.opts.output_types.should_codegen())
@ -97,12 +98,15 @@ pub fn link_binary<'a>(
.tempdir() .tempdir()
.unwrap_or_else(|error| sess.emit_fatal(errors::CreateTempDir { error })); .unwrap_or_else(|error| sess.emit_fatal(errors::CreateTempDir { error }));
let path = MaybeTempDir::new(tmpdir, sess.opts.cg.save_temps); let path = MaybeTempDir::new(tmpdir, sess.opts.cg.save_temps);
let out_filename = out_filename( let output = out_filename(
sess, sess,
crate_type, crate_type,
outputs, outputs,
codegen_results.crate_info.local_crate_name, 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 { match crate_type {
CrateType::Rlib => { CrateType::Rlib => {
let _timer = sess.timer("link_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_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 // If no requested outputs require linking, then the object temporaries should
// be kept. // be kept.
if !sess.opts.output_types.should_link() { if !sess.opts.output_types.should_link() {

View file

@ -23,12 +23,13 @@ use rustc_hir::def_id::{CrateNum, LOCAL_CRATE};
use rustc_incremental::{ use rustc_incremental::{
copy_cgu_workproduct_to_incr_comp_cache_dir, in_incr_comp_dir, in_incr_comp_dir_sess, 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_metadata::EncodedMetadata;
use rustc_middle::dep_graph::{WorkProduct, WorkProductId}; use rustc_middle::dep_graph::{WorkProduct, WorkProductId};
use rustc_middle::middle::exported_symbols::SymbolExportInfo; use rustc_middle::middle::exported_symbols::SymbolExportInfo;
use rustc_middle::ty::TyCtxt; use rustc_middle::ty::TyCtxt;
use rustc_session::cgu_reuse_tracker::CguReuseTracker; 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::config::{Passes, SwitchWithOptPath};
use rustc_session::Session; use rustc_session::Session;
use rustc_span::source_map::SourceMap; use rustc_span::source_map::SourceMap;
@ -535,9 +536,16 @@ fn produce_final_output_artifacts(
let mut user_wants_objects = false; let mut user_wants_objects = false;
// Produce final compile outputs. // Produce final compile outputs.
let copy_gracefully = |from: &Path, to: &Path| { let copy_gracefully = |from: &Path, to: &OutFileName| match to {
if let Err(e) = fs::copy(from, to) { OutFileName::Stdout => {
sess.emit_err(errors::CopyPath::new(from, to, e)); 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`. // to copy `foo.0.x` to `foo.x`.
let module_name = Some(&compiled_modules.modules[0].name[..]); let module_name = Some(&compiled_modules.modules[0].name[..]);
let path = crate_output.temp_path(output_type, module_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 { if !sess.opts.cg.save_temps && !keep_numbered {
// The user just wants `foo.x`, not `foo.#module-name#.x`. // The user just wants `foo.x`, not `foo.#module-name#.x`.
ensure_removed(sess.diagnostic(), &path); ensure_removed(sess.diagnostic(), &path);

View file

@ -82,6 +82,12 @@ impl IntoDiagnosticArg for DebugArgPath<'_> {
} }
} }
#[derive(Diagnostic)]
#[diag(codegen_ssa_binary_output_to_tty)]
pub struct BinaryOutputToTty {
pub shorthand: &'static str,
}
#[derive(Diagnostic)] #[derive(Diagnostic)]
#[diag(codegen_ssa_ignoring_emit_path)] #[diag(codegen_ssa_ignoring_emit_path)]
pub struct IgnoringEmitPath { pub struct IgnoringEmitPath {

View file

@ -34,7 +34,9 @@ use rustc_interface::{interface, Queries};
use rustc_lint::LintStore; use rustc_lint::LintStore;
use rustc_metadata::locator; use rustc_metadata::locator;
use rustc_session::config::{nightly_options, CG_OPTIONS, Z_OPTIONS}; 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::cstore::MetadataLoader;
use rustc_session::getopts::{self, Matches}; use rustc_session::getopts::{self, Matches};
use rustc_session::lint::{Lint, LintId}; use rustc_session::lint::{Lint, LintId};
@ -454,9 +456,12 @@ fn run_compiler(
} }
// Extract output directory and file from matches. // 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 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) (odir, ofile)
} }
@ -702,7 +707,7 @@ fn print_crate_info(
for &style in &crate_types { for &style in &crate_types {
let fname = let fname =
rustc_session::output::filename_for_input(sess, style, id, &t_outputs); 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 => { Cfg => {

View file

@ -9,7 +9,7 @@ use rustc_hir_pretty as pprust_hir;
use rustc_middle::hir::map as hir_map; use rustc_middle::hir::map as hir_map;
use rustc_middle::mir::{write_mir_graphviz, write_mir_pretty}; use rustc_middle::mir::{write_mir_graphviz, write_mir_pretty};
use rustc_middle::ty::{self, TyCtxt}; 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_session::Session;
use rustc_span::symbol::Ident; use rustc_span::symbol::Ident;
use rustc_span::FileName; use rustc_span::FileName;
@ -359,8 +359,8 @@ fn get_source(sess: &Session) -> (String, FileName) {
fn write_or_print(out: &str, sess: &Session) { fn write_or_print(out: &str, sess: &Session) {
match &sess.io.output_file { match &sess.io.output_file {
None => print!("{out}"), None | Some(OutFileName::Stdout) => print!("{out}"),
Some(p) => { Some(OutFileName::Real(p)) => {
if let Err(e) = std::fs::write(p, out) { if let Err(e) = std::fs::write(p, out) {
sess.emit_fatal(UnprettyDumpFail { sess.emit_fatal(UnprettyDumpFail {
path: p.display().to_string(), path: p.display().to_string(),

View file

@ -6,6 +6,7 @@ edition = "2021"
[lib] [lib]
[dependencies] [dependencies]
atty = "0.2.13"
libloading = "0.7.1" libloading = "0.7.1"
tracing = "0.1" tracing = "0.1"
rustc-rayon-core = { version = "0.5.0", optional = true } rustc-rayon-core = { version = "0.5.0", optional = true }

View file

@ -33,6 +33,7 @@ interface_mixed_proc_macro_crate =
interface_multiple_output_types_adaption = interface_multiple_output_types_adaption =
due to multiple output types requested, the explicitly specified output file name will be adapted for each output type 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 = interface_out_dir_error =
failed to find or create the directory specified by `--out-dir` failed to find or create the directory specified by `--out-dir`

View file

@ -108,3 +108,7 @@ pub struct IgnoringExtraFilename;
#[derive(Diagnostic)] #[derive(Diagnostic)]
#[diag(interface_ignoring_out_dir)] #[diag(interface_ignoring_out_dir)]
pub struct IgnoringOutDir; pub struct IgnoringOutDir;
#[derive(Diagnostic)]
#[diag(interface_multiple_output_types_to_stdout)]
pub struct MultipleOutputTypesToStdout;

View file

@ -14,7 +14,7 @@ use rustc_middle::{bug, ty};
use rustc_parse::maybe_new_parser_from_source_str; use rustc_parse::maybe_new_parser_from_source_str;
use rustc_query_impl::QueryCtxt; use rustc_query_impl::QueryCtxt;
use rustc_query_system::query::print_query_stack; 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::config::{CheckCfg, ExpectedValues};
use rustc_session::lint; use rustc_session::lint;
use rustc_session::parse::{CrateConfig, ParseSess}; use rustc_session::parse::{CrateConfig, ParseSess};
@ -252,7 +252,7 @@ pub struct Config {
pub input: Input, pub input: Input,
pub output_dir: Option<PathBuf>, 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 file_loader: Option<Box<dyn FileLoader + Send + Sync>>,
pub locale_resources: &'static [&'static str], pub locale_resources: &'static [&'static str],

View file

@ -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_passes::{self, hir_stats, layout_test};
use rustc_plugin_impl as plugin; use rustc_plugin_impl as plugin;
use rustc_resolve::Resolver; 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::cstore::{MetadataLoader, Untracked};
use rustc_session::output::filename_for_input; use rustc_session::output::filename_for_input;
use rustc_session::search_paths::PathKind; use rustc_session::search_paths::PathKind;
@ -373,19 +373,23 @@ fn generated_output_paths(
) -> Vec<PathBuf> { ) -> Vec<PathBuf> {
let mut out_filenames = Vec::new(); let mut out_filenames = Vec::new();
for output_type in sess.opts.output_types.keys() { 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 { match *output_type {
// If the filename has been overridden using `-o`, it will not be modified // If the filename has been overridden using `-o`, it will not be modified
// by appending `.rlib`, `.exe`, etc., so we can skip this transformation. // by appending `.rlib`, `.exe`, etc., so we can skip this transformation.
OutputType::Exe if !exact_name => { OutputType::Exe if !exact_name => {
for crate_type in sess.crate_types().iter() { for crate_type in sess.crate_types().iter() {
let p = filename_for_input(sess, *crate_type, crate_name, outputs); 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 => { 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 // 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); 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) { if !sess.opts.output_types.contains_key(&OutputType::DepInfo) {
return; 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 { let result: io::Result<()> = try {
// Build a list of files used to compile the output and // Build a list of files used to compile the output and
@ -515,7 +520,7 @@ fn write_out_deps(tcx: TyCtxt<'_>, outputs: &OutputFilenames, out_filenames: &[P
} }
} }
let mut file = BufWriter::new(fs::File::create(&deps_filename)?); let write_deps_to_file = |file: &mut dyn Write| -> io::Result<()> {
for path in out_filenames { for path in out_filenames {
writeln!(file, "{}: {}\n", path.display(), files.join(" "))?; writeln!(file, "{}: {}\n", path.display(), files.join(" "))?;
} }
@ -544,6 +549,20 @@ fn write_out_deps(tcx: TyCtxt<'_>, outputs: &OutputFilenames, out_filenames: &[P
writeln!(file)?; 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)?;
}
}
}; };
match result { match result {

View file

@ -11,7 +11,7 @@ use rustc_session::config::InstrumentXRay;
use rustc_session::config::TraitSolver; use rustc_session::config::TraitSolver;
use rustc_session::config::{build_configuration, build_session_options, to_crate_config}; use rustc_session::config::{build_configuration, build_session_options, to_crate_config};
use rustc_session::config::{ use rustc_session::config::{
BranchProtection, Externs, OomStrategy, OutputType, OutputTypes, PAuthKey, PacRet, BranchProtection, Externs, OomStrategy, OutFileName, OutputType, OutputTypes, PAuthKey, PacRet,
ProcMacroExecutionStrategy, SymbolManglingVersion, WasiExecModel, ProcMacroExecutionStrategy, SymbolManglingVersion, WasiExecModel,
}; };
use rustc_session::config::{CFGuard, ExternEntry, LinkerPluginLto, LtoCli, SwitchWithOptPath}; 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 v2 = Options::default();
let mut v3 = Options::default(); let mut v3 = Options::default();
v1.output_types = OutputTypes::new(&[(OutputType::Exe, Some(PathBuf::from("./some/thing")))]); v1.output_types = OutputTypes::new(&[(
v2.output_types = OutputTypes::new(&[(OutputType::Exe, Some(PathBuf::from("/some/thing")))]); 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)]); v3.output_types = OutputTypes::new(&[(OutputType::Exe, None)]);
assert_non_crate_hash_different(&v1, &v2); 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(); let mut v2 = Options::default();
v1.output_types = OutputTypes::new(&[ v1.output_types = OutputTypes::new(&[
(OutputType::Exe, Some(PathBuf::from("./some/thing"))), (OutputType::Exe, Some(OutFileName::Real(PathBuf::from("./some/thing")))),
(OutputType::Bitcode, Some(PathBuf::from("./some/thing.bc"))), (OutputType::Bitcode, Some(OutFileName::Real(PathBuf::from("./some/thing.bc")))),
]); ]);
v2.output_types = OutputTypes::new(&[ v2.output_types = OutputTypes::new(&[
(OutputType::Bitcode, Some(PathBuf::from("./some/thing.bc"))), (OutputType::Bitcode, Some(OutFileName::Real(PathBuf::from("./some/thing.bc")))),
(OutputType::Exe, Some(PathBuf::from("./some/thing"))), (OutputType::Exe, Some(OutFileName::Real(PathBuf::from("./some/thing")))),
]); ]);
assert_same_hash(&v1, &v2); assert_same_hash(&v1, &v2);

View file

@ -11,7 +11,7 @@ use rustc_parse::validate_attr;
use rustc_session as session; use rustc_session as session;
use rustc_session::config::CheckCfg; use rustc_session::config::CheckCfg;
use rustc_session::config::{self, CrateType}; 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::filesearch::sysroot_candidates;
use rustc_session::lint::{self, BuiltinLintDiagnostics, LintBuffer}; use rustc_session::lint::{self, BuiltinLintDiagnostics, LintBuffer};
use rustc_session::parse::CrateConfig; use rustc_session::parse::CrateConfig;
@ -500,7 +500,36 @@ pub fn collect_crate_types(session: &Session, attrs: &[ast::Attribute]) -> Vec<C
base 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 { 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 { match sess.io.output_file {
None => { None => {
// "-" as input file will cause the parser to read from stdin so we // "-" 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( OutputFilenames::new(
out_file.parent().unwrap_or_else(|| Path::new("")).to_path_buf(), 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, ofile,
sess.io.temps_dir.clone(), sess.io.temps_dir.clone(),
sess.opts.cg.extra_filename.clone(), sess.opts.cg.extra_filename.clone(),

View file

@ -4,6 +4,9 @@ metadata_as_needed_compatibility =
metadata_bad_panic_strategy = metadata_bad_panic_strategy =
the linked panic runtime `{$runtime}` is not compiled with this crate's panic strategy `{$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 = metadata_bundle_needs_static =
linking modifier `bundle` is only compatible with `static` linking kind linking modifier `bundle` is only compatible with `static` linking kind
@ -63,6 +66,9 @@ metadata_fail_seek_file =
metadata_fail_write_file = metadata_fail_write_file =
failed to write to the file: {$err} failed to write to the file: {$err}
metadata_failed_copy_to_stdout =
failed to copy {$filename} to stdout: {$err}
metadata_failed_create_encoded_metadata = metadata_failed_create_encoded_metadata =
failed to create encoded metadata from file: {$err} failed to create encoded metadata from file: {$err}

View file

@ -395,6 +395,17 @@ pub struct FailedWriteError {
pub err: Error, pub err: Error,
} }
#[derive(Diagnostic)]
#[diag(metadata_failed_copy_to_stdout)]
pub struct FailedCopyToStdout {
pub filename: PathBuf,
pub err: Error,
}
#[derive(Diagnostic)]
#[diag(metadata_binary_output_to_tty)]
pub struct BinaryOutputToTty;
#[derive(Diagnostic)] #[derive(Diagnostic)]
#[diag(metadata_missing_native_library)] #[diag(metadata_missing_native_library)]
pub struct MissingNativeLibrary<'a> { pub struct MissingNativeLibrary<'a> {

View file

@ -1,18 +1,19 @@
use crate::errors::{ use crate::errors::{
FailedCreateEncodedMetadata, FailedCreateFile, FailedCreateTempdir, FailedWriteError, BinaryOutputToTty, FailedCopyToStdout, FailedCreateEncodedMetadata, FailedCreateFile,
FailedCreateTempdir, FailedWriteError,
}; };
use crate::{encode_metadata, EncodedMetadata}; use crate::{encode_metadata, EncodedMetadata};
use rustc_data_structures::temp_dir::MaybeTempDir; use rustc_data_structures::temp_dir::MaybeTempDir;
use rustc_hir::def_id::LOCAL_CRATE; use rustc_hir::def_id::LOCAL_CRATE;
use rustc_middle::ty::TyCtxt; 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::output::filename_for_metadata;
use rustc_session::{MetadataKind, Session}; use rustc_session::{MetadataKind, Session};
use tempfile::Builder as TempFileBuilder; use tempfile::Builder as TempFileBuilder;
use std::fs;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::{fs, io};
// FIXME(eddyb) maybe include the crate name in this? // FIXME(eddyb) maybe include the crate name in this?
pub const METADATA_FILENAME: &str = "lib.rmeta"; pub const METADATA_FILENAME: &str = "lib.rmeta";
@ -74,23 +75,37 @@ pub fn encode_and_write_metadata(tcx: TyCtxt<'_>) -> (EncodedMetadata, bool) {
// this file always exists. // this file always exists.
let need_metadata_file = tcx.sess.opts.output_types.contains_key(&OutputType::Metadata); let need_metadata_file = tcx.sess.opts.output_types.contains_key(&OutputType::Metadata);
let (metadata_filename, metadata_tmpdir) = if need_metadata_file { let (metadata_filename, metadata_tmpdir) = if need_metadata_file {
if let Err(err) = non_durable_rename(&metadata_filename, &out_filename) { let filename = match out_filename {
tcx.sess.emit_fatal(FailedWriteError { filename: out_filename, err }); 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 { if tcx.sess.opts.json_artifact_notifications {
tcx.sess tcx.sess
.parse_sess .parse_sess
.span_diagnostic .span_diagnostic
.emit_artifact_notification(&out_filename, "metadata"); .emit_artifact_notification(&out_filename.as_path(), "metadata");
} }
(out_filename, None) (filename, None)
} else { } else {
(metadata_filename, Some(metadata_tmpdir)) (metadata_filename, Some(metadata_tmpdir))
}; };
// Load metadata back to memory: codegen may need to include it in object files. // Load metadata back to memory: codegen may need to include it in object files.
let metadata = let metadata = EncodedMetadata::from_path(metadata_filename.clone(), metadata_tmpdir)
EncodedMetadata::from_path(metadata_filename, metadata_tmpdir).unwrap_or_else(|err| { .unwrap_or_else(|err| {
tcx.sess.emit_fatal(FailedCreateEncodedMetadata { err }); tcx.sess.emit_fatal(FailedCreateEncodedMetadata { err });
}); });
@ -116,3 +131,11 @@ pub fn non_durable_rename(src: &Path, dst: &Path) -> std::io::Result<()> {
let _ = std::fs::remove_file(dst); let _ = std::fs::remove_file(dst);
std::fs::rename(src, 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(())
}

View file

@ -7,7 +7,7 @@ use crate::MirPass;
use rustc_middle::mir::write_mir_pretty; use rustc_middle::mir::write_mir_pretty;
use rustc_middle::mir::Body; use rustc_middle::mir::Body;
use rustc_middle::ty::TyCtxt; use rustc_middle::ty::TyCtxt;
use rustc_session::config::OutputType; use rustc_session::config::{OutFileName, OutputType};
pub struct Marker(pub &'static str); pub struct Marker(pub &'static str);
@ -20,8 +20,15 @@ impl<'tcx> MirPass<'tcx> for Marker {
} }
pub fn emit_mir(tcx: TyCtxt<'_>) -> io::Result<()> { pub fn emit_mir(tcx: TyCtxt<'_>) -> io::Result<()> {
let path = tcx.output_filenames(()).path(OutputType::Mir); 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)?); let mut f = io::BufWriter::new(File::create(&path)?);
write_mir_pretty(tcx, None, &mut f)?; write_mir_pretty(tcx, None, &mut f)?;
}
}
Ok(()) Ok(())
} }

View file

@ -4,6 +4,7 @@ version = "0.0.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
atty = "0.2.13"
getopts = "0.2" getopts = "0.2"
rustc_macros = { path = "../rustc_macros" } rustc_macros = { path = "../rustc_macros" }
tracing = "0.1" tracing = "0.1"

View file

@ -30,6 +30,7 @@ use std::collections::btree_map::{
Iter as BTreeMapIter, Keys as BTreeMapKeysIter, Values as BTreeMapValuesIter, Iter as BTreeMapIter, Keys as BTreeMapKeysIter, Values as BTreeMapValuesIter,
}; };
use std::collections::{BTreeMap, BTreeSet}; use std::collections::{BTreeMap, BTreeSet};
use std::ffi::OsStr;
use std::fmt; use std::fmt;
use std::hash::Hash; use std::hash::Hash;
use std::iter; use std::iter;
@ -335,7 +336,7 @@ impl OutputType {
} }
} }
fn shorthand(&self) -> &'static str { pub fn shorthand(&self) -> &'static str {
match *self { match *self {
OutputType::Bitcode => "llvm-bc", OutputType::Bitcode => "llvm-bc",
OutputType::Assembly => "asm", OutputType::Assembly => "asm",
@ -388,6 +389,18 @@ impl OutputType {
OutputType::Exe => "", 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. /// The type of diagnostics output to generate.
@ -440,14 +453,14 @@ pub enum ResolveDocLinks {
/// dependency tracking for command-line arguments. Also only hash keys, since tracking /// 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. /// should only depend on the output types, not the paths they're written to.
#[derive(Clone, Debug, Hash, HashStable_Generic)] #[derive(Clone, Debug, Hash, HashStable_Generic)]
pub struct OutputTypes(BTreeMap<OutputType, Option<PathBuf>>); pub struct OutputTypes(BTreeMap<OutputType, Option<OutFileName>>);
impl OutputTypes { 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())))) 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) self.0.get(key)
} }
@ -455,11 +468,15 @@ impl OutputTypes {
self.0.contains_key(key) 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() self.0.keys()
} }
pub fn values(&self) -> BTreeMapValuesIter<'_, OutputType, Option<PathBuf>> { pub fn values(&self) -> BTreeMapValuesIter<'_, OutputType, Option<OutFileName>> {
self.0.values() self.0.values()
} }
@ -662,11 +679,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)] #[derive(Clone, Hash, Debug, HashStable_Generic)]
pub struct OutputFilenames { pub struct OutputFilenames {
pub out_directory: PathBuf, pub out_directory: PathBuf,
filestem: String, filestem: String,
pub single_output_file: Option<PathBuf>, pub single_output_file: Option<OutFileName>,
pub temps_directory: Option<PathBuf>, pub temps_directory: Option<PathBuf>,
pub outputs: OutputTypes, pub outputs: OutputTypes,
} }
@ -679,7 +756,7 @@ impl OutputFilenames {
pub fn new( pub fn new(
out_directory: PathBuf, out_directory: PathBuf,
out_filestem: String, out_filestem: String,
single_output_file: Option<PathBuf>, single_output_file: Option<OutFileName>,
temps_directory: Option<PathBuf>, temps_directory: Option<PathBuf>,
extra: String, extra: String,
outputs: OutputTypes, outputs: OutputTypes,
@ -693,12 +770,12 @@ impl OutputFilenames {
} }
} }
pub fn path(&self, flavor: OutputType) -> PathBuf { pub fn path(&self, flavor: OutputType) -> OutFileName {
self.outputs self.outputs
.get(&flavor) .get(&flavor)
.and_then(|p| p.to_owned()) .and_then(|p| p.to_owned())
.or_else(|| self.single_output_file.clone()) .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 /// Gets the output path where a compilation artifact of the given type
@ -1825,7 +1902,10 @@ fn parse_output_types(
for output_type in list.split(',') { for output_type in list.split(',') {
let (shorthand, path) = match output_type.split_once('=') { let (shorthand, path) = match output_type.split_once('=') {
None => (output_type, None), 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(|| { let output_type = OutputType::from_shorthand(shorthand).unwrap_or_else(|| {
early_error( early_error(
@ -2909,7 +2989,7 @@ pub(crate) mod dep_tracking {
use super::{ use super::{
BranchProtection, CFGuard, CFProtection, CrateType, DebugInfo, ErrorOutputType, BranchProtection, CFGuard, CFProtection, CrateType, DebugInfo, ErrorOutputType,
InstrumentCoverage, InstrumentXRay, LdImpl, LinkerPluginLto, LocationDetail, LtoCli, InstrumentCoverage, InstrumentXRay, LdImpl, LinkerPluginLto, LocationDetail, LtoCli,
OomStrategy, OptLevel, OutputType, OutputTypes, Passes, ResolveDocLinks, OomStrategy, OptLevel, OutFileName, OutputType, OutputTypes, Passes, ResolveDocLinks,
SourceFileHashAlgorithm, SplitDwarfKind, SwitchWithOptPath, SymbolManglingVersion, SourceFileHashAlgorithm, SplitDwarfKind, SwitchWithOptPath, SymbolManglingVersion,
TraitSolver, TrimmedDefPaths, TraitSolver, TrimmedDefPaths,
}; };
@ -3007,6 +3087,7 @@ pub(crate) mod dep_tracking {
SourceFileHashAlgorithm, SourceFileHashAlgorithm,
TrimmedDefPaths, TrimmedDefPaths,
Option<LdImpl>, Option<LdImpl>,
OutFileName,
OutputType, OutputType,
RealFileName, RealFileName,
LocationDetail, LocationDetail,

View file

@ -1,5 +1,5 @@
//! Related to out filenames of compilation (e.g. save analysis, binaries). //! 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::{ use crate::errors::{
CrateNameDoesNotMatch, CrateNameEmpty, CrateNameInvalid, FileIsNotWriteable, CrateNameDoesNotMatch, CrateNameEmpty, CrateNameInvalid, FileIsNotWriteable,
InvalidCharacterInCrateName, InvalidCharacterInCrateName,
@ -8,14 +8,14 @@ use crate::Session;
use rustc_ast::{self as ast, attr}; use rustc_ast::{self as ast, attr};
use rustc_span::symbol::sym; use rustc_span::symbol::sym;
use rustc_span::{Span, Symbol}; use rustc_span::{Span, Symbol};
use std::path::{Path, PathBuf}; use std::path::Path;
pub fn out_filename( pub fn out_filename(
sess: &Session, sess: &Session,
crate_type: CrateType, crate_type: CrateType,
outputs: &OutputFilenames, outputs: &OutputFilenames,
crate_name: Symbol, crate_name: Symbol,
) -> PathBuf { ) -> OutFileName {
let default_filename = filename_for_input(sess, crate_type, crate_name, outputs); let default_filename = filename_for_input(sess, crate_type, crate_name, outputs);
let out_filename = outputs let out_filename = outputs
.outputs .outputs
@ -24,7 +24,9 @@ pub fn out_filename(
.or_else(|| outputs.single_output_file.clone()) .or_else(|| outputs.single_output_file.clone())
.unwrap_or(default_filename); .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 out_filename
} }
@ -112,7 +114,7 @@ pub fn filename_for_metadata(
sess: &Session, sess: &Session,
crate_name: Symbol, crate_name: Symbol,
outputs: &OutputFilenames, outputs: &OutputFilenames,
) -> PathBuf { ) -> OutFileName {
// If the command-line specified the path, use that directly. // If the command-line specified the path, use that directly.
if let Some(Some(out_filename)) = sess.opts.output_types.get(&OutputType::Metadata) { if let Some(Some(out_filename)) = sess.opts.output_types.get(&OutputType::Metadata) {
return out_filename.clone(); return out_filename.clone();
@ -120,12 +122,13 @@ pub fn filename_for_metadata(
let libname = format!("{}{}", crate_name, sess.opts.cg.extra_filename); let libname = format!("{}{}", crate_name, sess.opts.cg.extra_filename);
let out_filename = outputs let out_filename = outputs.single_output_file.clone().unwrap_or_else(|| {
.single_output_file OutFileName::Real(outputs.out_directory.join(&format!("lib{libname}.rmeta")))
.clone() });
.unwrap_or_else(|| 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 out_filename
} }
@ -135,23 +138,33 @@ pub fn filename_for_input(
crate_type: CrateType, crate_type: CrateType,
crate_name: Symbol, crate_name: Symbol,
outputs: &OutputFilenames, outputs: &OutputFilenames,
) -> PathBuf { ) -> OutFileName {
let libname = format!("{}{}", crate_name, sess.opts.cg.extra_filename); let libname = format!("{}{}", crate_name, sess.opts.cg.extra_filename);
match crate_type { 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 => { CrateType::Cdylib | CrateType::ProcMacro | CrateType::Dylib => {
let (prefix, suffix) = (&sess.target.dll_prefix, &sess.target.dll_suffix); 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 => { CrateType::Staticlib => {
let (prefix, suffix) = (&sess.target.staticlib_prefix, &sess.target.staticlib_suffix); 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 => { CrateType::Executable => {
let suffix = &sess.target.exe_suffix; let suffix = &sess.target.exe_suffix;
let out_filename = outputs.path(OutputType::Exe); 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
}
} }
} }
} }

View file

@ -2,7 +2,9 @@ use crate::cgu_reuse_tracker::CguReuseTracker;
use crate::code_stats::CodeStats; use crate::code_stats::CodeStats;
pub use crate::code_stats::{DataTypeKind, FieldInfo, FieldKind, SizeKind, VariantInfo}; pub use crate::code_stats::{DataTypeKind, FieldInfo, FieldKind, SizeKind, VariantInfo};
use crate::config::Input; 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::errors;
use crate::parse::{add_feature_diagnostics, ParseSess}; use crate::parse::{add_feature_diagnostics, ParseSess};
use crate::search_paths::{PathKind, SearchPath}; use crate::search_paths::{PathKind, SearchPath};
@ -133,7 +135,7 @@ pub struct Limits {
pub struct CompilerIO { pub struct CompilerIO {
pub input: Input, pub input: Input,
pub output_dir: Option<PathBuf>, pub output_dir: Option<PathBuf>,
pub output_file: Option<PathBuf>, pub output_file: Option<OutFileName>,
pub temps_dir: Option<PathBuf>, pub temps_dir: Option<PathBuf>,
} }

View file

@ -202,6 +202,12 @@ flag](codegen-options/index.md#extra-filename). The files are written to the
current directory unless the [`--out-dir` flag](#option-out-dir) is used. Each current directory unless the [`--out-dir` flag](#option-out-dir) is used. Each
emission type may also specify the output filename with the form `KIND=PATH`, emission type may also specify the output filename with the form `KIND=PATH`,
which takes precedence over the `-o` flag. which takes precedence over the `-o` flag.
Specifying `-o -` or `--emit KIND=-` asks rustc to emit to stdout.
Text output types (`asm`, `dep-info`, `llvm-ir` and `mir`) can be written to
stdout despite it being a tty or not. This will result in an error if any
binary output type is written to stdout that is a tty.
This will also result in an error if multiple output types
would be written to stdout, because they would be all mixed together.
[LLVM bitcode]: https://llvm.org/docs/BitCodeFormat.html [LLVM bitcode]: https://llvm.org/docs/BitCodeFormat.html
[LLVM IR]: https://llvm.org/docs/LangRef.html [LLVM IR]: https://llvm.org/docs/LangRef.html

View file

@ -62,7 +62,7 @@ impl CodegenBackend for TheBackend {
codegen_results: CodegenResults, codegen_results: CodegenResults,
outputs: &OutputFilenames, outputs: &OutputFilenames,
) -> Result<(), ErrorGuaranteed> { ) -> Result<(), ErrorGuaranteed> {
use rustc_session::{config::CrateType, output::out_filename}; use rustc_session::{config::{CrateType, OutFileName}, output::out_filename};
use std::io::Write; use std::io::Write;
let crate_name = codegen_results.crate_info.local_crate_name; let crate_name = codegen_results.crate_info.local_crate_name;
for &crate_type in sess.opts.crate_types.iter() { for &crate_type in sess.opts.crate_types.iter() {
@ -70,9 +70,17 @@ impl CodegenBackend for TheBackend {
sess.fatal(format!("Crate type is {:?}", crate_type)); sess.fatal(format!("Crate type is {:?}", crate_type));
} }
let output_name = out_filename(sess, crate_type, &outputs, crate_name); let output_name = out_filename(sess, crate_type, &outputs, crate_name);
let mut out_file = ::std::fs::File::create(output_name).unwrap(); match output_name {
OutFileName::Real(ref path) => {
let mut out_file = ::std::fs::File::create(path).unwrap();
write!(out_file, "This has been \"compiled\" successfully.").unwrap(); write!(out_file, "This has been \"compiled\" successfully.").unwrap();
} }
OutFileName::Stdout => {
let mut stdout = std::io::stdout();
write!(stdout, "This has been \"compiled\" successfully.").unwrap();
}
}
}
Ok(()) Ok(())
} }
} }

View file

@ -6,7 +6,7 @@ extern crate rustc_session;
extern crate rustc_span; extern crate rustc_span;
use rustc_interface::interface; use rustc_interface::interface;
use rustc_session::config::{Input, Options, OutputType, OutputTypes}; use rustc_session::config::{Input, Options, OutFileName, OutputType, OutputTypes};
use rustc_span::source_map::FileName; use rustc_span::source_map::FileName;
use std::path::PathBuf; use std::path::PathBuf;
@ -50,7 +50,7 @@ fn compile(code: String, output: PathBuf, sysroot: PathBuf) {
crate_cfg: Default::default(), crate_cfg: Default::default(),
crate_check_cfg: Default::default(), crate_check_cfg: Default::default(),
input, input,
output_file: Some(output), output_file: Some(OutFileName::Real(output)),
output_dir: None, output_dir: None,
file_loader: None, file_loader: None,
locale_resources: &[], locale_resources: &[],

View file

@ -0,0 +1,51 @@
include ../tools.mk
SRC=test.rs
OUT=$(TMPDIR)/out
all: asm llvm-ir dep-info mir llvm-bc obj metadata link multiple-types multiple-types-option-o
asm: $(OUT)
$(RUSTC) --emit asm=$(OUT)/$@ $(SRC)
$(RUSTC) --emit asm=- $(SRC) | diff - $(OUT)/$@
llvm-ir: $(OUT)
$(RUSTC) --emit llvm-ir=$(OUT)/$@ $(SRC)
$(RUSTC) --emit llvm-ir=- $(SRC) | diff - $(OUT)/$@
dep-info: $(OUT)
$(RUSTC) -Z dep-info-omit-d-target=yes --emit dep-info=$(OUT)/$@ $(SRC)
$(RUSTC) --emit dep-info=- $(SRC) | diff - $(OUT)/$@
mir: $(OUT)
$(RUSTC) --emit mir=$(OUT)/$@ $(SRC)
$(RUSTC) --emit mir=- $(SRC) | diff - $(OUT)/$@
llvm-bc: $(OUT)
$(RUSTC) --emit llvm-bc=- $(SRC) 1>/dev/ptmx 2>$(OUT)/$@ || true
diff $(OUT)/$@ emit-llvm-bc.stderr
obj: $(OUT)
$(RUSTC) --emit obj=- $(SRC) 1>/dev/ptmx 2>$(OUT)/$@ || true
diff $(OUT)/$@ emit-obj.stderr
# For metadata output, a temporary directory will be created to hold the temporary
# metadata file. But when output is stdout, the temporary directory will be located
# in the same place as $(SRC), which is mounted as read-only in the tests. Thus as
# a workaround, $(SRC) is copied to the test output directory $(OUT) and we compile
# it there.
metadata: $(OUT)
cp $(SRC) $(OUT)
(cd $(OUT); $(RUSTC) --emit metadata=- $(SRC) 1>/dev/ptmx 2>$(OUT)/$@ || true)
diff $(OUT)/$@ emit-metadata.stderr
link: $(OUT)
$(RUSTC) --emit link=- $(SRC) 1>/dev/ptmx 2>$(OUT)/$@ || true
diff $(OUT)/$@ emit-link.stderr
multiple-types: $(OUT)
$(RUSTC) --emit asm=- --emit llvm-ir=- --emit dep-info=- --emit mir=- $(SRC) 2>$(OUT)/$@ || true
diff $(OUT)/$@ emit-multiple-types.stderr
multiple-types-option-o: $(OUT)
$(RUSTC) -o - --emit asm,llvm-ir,dep-info,mir $(SRC) 2>$(OUT)/$@ || true
diff $(OUT)/$@ emit-multiple-types.stderr
$(OUT):
mkdir -p $(OUT)

View file

@ -0,0 +1,4 @@
error: option `-o` or `--emit` is used to write binary output type `link` to stdout, but stdout is a tty
error: aborting due to previous error

View file

@ -0,0 +1,4 @@
error: option `-o` or `--emit` is used to write binary output type `llvm-bc` to stdout, but stdout is a tty
error: aborting due to previous error

View file

@ -0,0 +1,4 @@
error: option `-o` or `--emit` is used to write binary output type `metadata` to stdout, but stdout is a tty
error: aborting due to previous error

View file

@ -0,0 +1,4 @@
error: can't use option `-o` or `--emit` to write multiple output types to stdout
error: aborting due to previous error

View file

@ -0,0 +1,4 @@
error: option `-o` or `--emit` is used to write binary output type `obj` to stdout, but stdout is a tty
error: aborting due to previous error

View file

@ -0,0 +1 @@
#![crate_type = "rlib"]