Skip to content

Commit

Permalink
rustc: Support output filenames for each emit type
Browse files Browse the repository at this point in the history
Currently the compiler supports the ability to emit multiple output types as
part of one compilation (e.g. asm, LLVM IR, bytecode, link, dep-info, etc). It
does not, however, support the ability to customize the output filename for each
of these output types. The `-o` flag is ignored if multiple emit types are
specified (and the compiler emits a warning about this).

Normally this doesn't matter too much, but in the case of `dep-info` it can lead
to a number of problems (e.g. see #28716). By allowing customization of the
output filename for each emit type we're able to solve the problems in that
issue.

This commit adds support for the `--emit` option to the compiler to look like:

    rustc foo.rs --emit dep-info=.deps/foo.d,link

This indicates that the `dep-info` output type will be placed at `.deps/foo.d`
and the `link` output type will otherwise be determined via the `--out-dir` and
`-o` flags.

Closes #28716
  • Loading branch information
alexcrichton committed Sep 30, 2015
1 parent 3e6d724 commit 8c963c0
Show file tree
Hide file tree
Showing 9 changed files with 165 additions and 142 deletions.
7 changes: 5 additions & 2 deletions man/rustc.1
Expand Up @@ -53,7 +53,9 @@ Comma separated list of types of crates for the compiler to emit.
Specify the name of the crate being built.
.TP
\fB\-\-emit\fR [asm|llvm\-bc|llvm\-ir|obj|link|dep\-info]
Configure the output that \fBrustc\fR will produce.
Configure the output that \fBrustc\fR will produce. Each option may also be of
the form KIND=PATH to specify the explicit output location for that particular
emission kind.
.TP
\fB\-\-print\fR [crate\-name|file\-names|sysroot]
Comma separated list of compiler information to print on stdout.
Expand All @@ -66,7 +68,8 @@ Equivalent to \fI\-C\ opt\-level=2\fR.
.TP
\fB\-o\fR \fIFILENAME\fR
Write output to \fIFILENAME\fR.
Ignored if multiple \fI\-\-emit\fR outputs are specified.
Ignored if multiple \fI\-\-emit\fR outputs are specified which don't have an
explicit path otherwise.
.TP
\fB\-\-out\-dir\fR \fIDIR\fR
Write output to compiler\[hy]chosen filename in \fIDIR\fR.
Expand Down
76 changes: 34 additions & 42 deletions src/librustc/session/config.rs
Expand Up @@ -15,7 +15,6 @@ pub use self::EntryFnType::*;
pub use self::CrateType::*;
pub use self::Passes::*;
pub use self::OptLevel::*;
pub use self::OutputType::*;
pub use self::DebugInfoLevel::*;

use session::{early_error, early_warn, Session};
Expand Down Expand Up @@ -62,14 +61,14 @@ pub enum DebugInfoLevel {
FullDebugInfo,
}

#[derive(Clone, Copy, PartialEq, PartialOrd, Ord, Eq)]
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub enum OutputType {
OutputTypeBitcode,
OutputTypeAssembly,
OutputTypeLlvmAssembly,
OutputTypeObject,
OutputTypeExe,
OutputTypeDepInfo,
Bitcode,
Assembly,
LlvmAssembly,
Object,
Exe,
DepInfo,
}

#[derive(Clone)]
Expand All @@ -85,7 +84,7 @@ pub struct Options {
pub lint_opts: Vec<(String, lint::Level)>,
pub lint_cap: Option<lint::Level>,
pub describe_lints: bool,
pub output_types: Vec<OutputType>,
pub output_types: HashMap<OutputType, Option<PathBuf>>,
// This was mutable for rustpkg, which updates search paths based on the
// parsed code. It remains mutable in case its replacements wants to use
// this.
Expand All @@ -105,8 +104,6 @@ pub struct Options {
pub always_build_mir: bool,
pub no_analysis: bool,
pub debugging_opts: DebuggingOptions,
/// Whether to write dependency files. It's (enabled, optional filename).
pub write_dependency_info: (bool, Option<PathBuf>),
pub prints: Vec<PrintRequest>,
pub cg: CodegenOptions,
pub color: ColorConfig,
Expand Down Expand Up @@ -151,26 +148,25 @@ pub struct OutputFilenames {
pub out_filestem: String,
pub single_output_file: Option<PathBuf>,
pub extra: String,
pub outputs: HashMap<OutputType, Option<PathBuf>>,
}

impl OutputFilenames {
pub fn path(&self, flavor: OutputType) -> PathBuf {
match self.single_output_file {
Some(ref path) => return path.clone(),
None => {}
}
self.temp_path(flavor)
self.outputs.get(&flavor).and_then(|p| p.to_owned())
.or_else(|| self.single_output_file.clone())
.unwrap_or_else(|| self.temp_path(flavor))
}

pub fn temp_path(&self, flavor: OutputType) -> PathBuf {
let base = self.out_directory.join(&self.filestem());
match flavor {
OutputTypeBitcode => base.with_extension("bc"),
OutputTypeAssembly => base.with_extension("s"),
OutputTypeLlvmAssembly => base.with_extension("ll"),
OutputTypeObject => base.with_extension("o"),
OutputTypeDepInfo => base.with_extension("d"),
OutputTypeExe => base,
OutputType::Bitcode => base.with_extension("bc"),
OutputType::Assembly => base.with_extension("s"),
OutputType::LlvmAssembly => base.with_extension("ll"),
OutputType::Object => base.with_extension("o"),
OutputType::DepInfo => base.with_extension("d"),
OutputType::Exe => base,
}
}

Expand Down Expand Up @@ -206,7 +202,7 @@ pub fn basic_options() -> Options {
lint_opts: Vec::new(),
lint_cap: None,
describe_lints: false,
output_types: Vec::new(),
output_types: HashMap::new(),
search_paths: SearchPaths::new(),
maybe_sysroot: None,
target_triple: host_triple().to_string(),
Expand All @@ -218,7 +214,6 @@ pub fn basic_options() -> Options {
always_build_mir: false,
no_analysis: false,
debugging_opts: basic_debugging_options(),
write_dependency_info: (false, None),
prints: Vec::new(),
cg: basic_codegen_options(),
color: Auto,
Expand Down Expand Up @@ -907,31 +902,30 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options {
unsafe { llvm::LLVMSetDebug(1); }
}

let mut output_types = Vec::new();
let mut output_types = HashMap::new();
if !debugging_opts.parse_only && !no_trans {
let unparsed_output_types = matches.opt_strs("emit");
for unparsed_output_type in &unparsed_output_types {
for part in unparsed_output_type.split(',') {
let output_type = match part {
"asm" => OutputTypeAssembly,
"llvm-ir" => OutputTypeLlvmAssembly,
"llvm-bc" => OutputTypeBitcode,
"obj" => OutputTypeObject,
"link" => OutputTypeExe,
"dep-info" => OutputTypeDepInfo,
_ => {
for list in matches.opt_strs("emit") {
for output_type in list.split(',') {
let mut parts = output_type.splitn(2, '=');
let output_type = match parts.next().unwrap() {
"asm" => OutputType::Assembly,
"llvm-ir" => OutputType::LlvmAssembly,
"llvm-bc" => OutputType::Bitcode,
"obj" => OutputType::Object,
"link" => OutputType::Exe,
"dep-info" => OutputType::DepInfo,
part => {
early_error(color, &format!("unknown emission type: `{}`",
part))
}
};
output_types.push(output_type)
let path = parts.next().map(PathBuf::from);
output_types.insert(output_type, path);
}
}
};
output_types.sort();
output_types.dedup();
if output_types.is_empty() {
output_types.push(OutputTypeExe);
output_types.insert(OutputType::Exe, None);
}

let cg = build_codegen_options(matches, color);
Expand Down Expand Up @@ -1004,7 +998,6 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options {

let cfg = parse_cfgspecs(matches.opt_strs("cfg"));
let test = matches.opt_present("test");
let write_dependency_info = (output_types.contains(&OutputTypeDepInfo), None);

let prints = matches.opt_strs("print").into_iter().map(|s| {
match &*s {
Expand Down Expand Up @@ -1059,7 +1052,6 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options {
always_build_mir: always_build_mir,
no_analysis: no_analysis,
debugging_opts: debugging_opts,
write_dependency_info: write_dependency_info,
prints: prints,
cg: cg,
color: color,
Expand Down
52 changes: 21 additions & 31 deletions src/librustc_driver/driver.rs
Expand Up @@ -12,7 +12,7 @@ use rustc::front;
use rustc::front::map as hir_map;
use rustc_mir as mir;
use rustc::session::Session;
use rustc::session::config::{self, Input, OutputFilenames};
use rustc::session::config::{self, Input, OutputFilenames, OutputType};
use rustc::session::search_paths::PathKind;
use rustc::lint;
use rustc::metadata;
Expand All @@ -36,6 +36,7 @@ use super::Compilation;

use serialize::json;

use std::collections::HashMap;
use std::env;
use std::ffi::{OsString, OsStr};
use std::fs;
Expand Down Expand Up @@ -117,7 +118,7 @@ pub fn compile_input(sess: Session,
let arenas = ty::CtxtArenas::new();
let ast_map = make_map(&sess, &mut hir_forest);

write_out_deps(&sess, input, &outputs, &id[..]);
write_out_deps(&sess, &outputs, &id);

controller_entry_point!(after_write_deps,
sess,
Expand Down Expand Up @@ -807,16 +808,16 @@ pub fn phase_5_run_llvm_passes(sess: &Session,
trans: &trans::CrateTranslation,
outputs: &OutputFilenames) {
if sess.opts.cg.no_integrated_as {
let output_type = config::OutputTypeAssembly;

let mut map = HashMap::new();
map.insert(OutputType::Assembly, None);
time(sess.time_passes(), "LLVM passes", ||
write::run_passes(sess, trans, &[output_type], outputs));
write::run_passes(sess, trans, &map, outputs));

write::run_assembler(sess, outputs);

// Remove assembly source, unless --save-temps was specified
if !sess.opts.cg.save_temps {
fs::remove_file(&outputs.temp_path(config::OutputTypeAssembly)).unwrap();
fs::remove_file(&outputs.temp_path(OutputType::Assembly)).unwrap();
}
} else {
time(sess.time_passes(), "LLVM passes", ||
Expand Down Expand Up @@ -847,16 +848,12 @@ fn escape_dep_filename(filename: &str) -> String {
filename.replace(" ", "\\ ")
}

fn write_out_deps(sess: &Session,
input: &Input,
outputs: &OutputFilenames,
id: &str) {

fn write_out_deps(sess: &Session, outputs: &OutputFilenames, id: &str) {
let mut out_filenames = Vec::new();
for output_type in &sess.opts.output_types {
for output_type in sess.opts.output_types.keys() {
let file = outputs.path(*output_type);
match *output_type {
config::OutputTypeExe => {
OutputType::Exe => {
for output in sess.crate_types.borrow().iter() {
let p = link::filename_for_input(sess, *output, id,
outputs);
Expand All @@ -867,23 +864,11 @@ fn write_out_deps(sess: &Session,
}
}

// Write out dependency rules to the dep-info file if requested with
// --dep-info
let deps_filename = match sess.opts.write_dependency_info {
// Use filename from --dep-file argument if given
(true, Some(ref filename)) => filename.clone(),
// Use default filename: crate source filename with extension replaced
// by ".d"
(true, None) => match *input {
Input::File(..) => outputs.with_extension("d"),
Input::Str(..) => {
sess.warn("can not write --dep-info without a filename \
when compiling stdin.");
return
},
},
_ => return,
};
// Write out dependency rules to the dep-info file if requested
if !sess.opts.output_types.contains_key(&OutputType::DepInfo) {
return
}
let deps_filename = outputs.path(OutputType::DepInfo);

let result = (|| -> io::Result<()> {
// Build a list of files used to compile the output and
Expand Down Expand Up @@ -1012,11 +997,15 @@ pub fn build_output_filenames(input: &Input,
out_filestem: stem,
single_output_file: None,
extra: sess.opts.cg.extra_filename.clone(),
outputs: sess.opts.output_types.clone(),
}
}

Some(ref out_file) => {
let ofile = if sess.opts.output_types.len() > 1 {
let unnamed_output_types = sess.opts.output_types.values()
.filter(|a| a.is_none())
.count();
let ofile = if unnamed_output_types > 1 {
sess.warn("ignoring specified output filename because multiple \
outputs were requested");
None
Expand All @@ -1035,6 +1024,7 @@ pub fn build_output_filenames(input: &Input,
.to_str().unwrap().to_string(),
single_output_file: ofile,
extra: sess.opts.cg.extra_filename.clone(),
outputs: sess.opts.output_types.clone(),
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/librustc_driver/lib.rs
Expand Up @@ -63,7 +63,7 @@ use rustc_resolve as resolve;
use rustc_trans::back::link;
use rustc_trans::save;
use rustc::session::{config, Session, build_session};
use rustc::session::config::{Input, PrintRequest};
use rustc::session::config::{Input, PrintRequest, OutputType};
use rustc::lint::Lint;
use rustc::lint;
use rustc::metadata;
Expand Down Expand Up @@ -382,7 +382,7 @@ impl<'a> CompilerCalls<'a> for RustcDefaultCalls {
control.after_analysis.stop = Compilation::Stop;
}

if !sess.opts.output_types.iter().any(|&i| i == config::OutputTypeExe) {
if !sess.opts.output_types.keys().any(|&i| i == OutputType::Exe) {
control.after_llvm.stop = Compilation::Stop;
}

Expand Down
18 changes: 10 additions & 8 deletions src/librustc_trans/back/link.rs
Expand Up @@ -16,7 +16,7 @@ use super::msvc;
use super::svh::Svh;
use session::config;
use session::config::NoDebugInfo;
use session::config::{OutputFilenames, Input, OutputTypeBitcode, OutputTypeExe, OutputTypeObject};
use session::config::{OutputFilenames, Input, OutputType};
use session::search_paths::PathKind;
use session::Session;
use metadata::common::LinkMeta;
Expand Down Expand Up @@ -486,7 +486,7 @@ pub fn filename_for_input(sess: &Session,
}
config::CrateTypeExecutable => {
let suffix = &sess.target.target.options.exe_suffix;
let out_filename = outputs.path(OutputTypeExe);
let out_filename = outputs.path(OutputType::Exe);
if suffix.is_empty() {
out_filename.to_path_buf()
} else {
Expand Down Expand Up @@ -527,10 +527,12 @@ fn link_binary_output(sess: &Session,
outputs: &OutputFilenames,
crate_name: &str) -> PathBuf {
let objects = object_filenames(sess, outputs);
let out_filename = match outputs.single_output_file {
Some(ref file) => file.clone(),
None => 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.outputs.get(&OutputType::Exe)
.and_then(|s| s.to_owned())
.or_else(|| outputs.single_output_file.clone())
.unwrap_or(default_filename);

// Make sure files are writeable. Mac, FreeBSD, and Windows system linkers
// check this already -- however, the Linux linker will happily overwrite a
Expand Down Expand Up @@ -571,7 +573,7 @@ fn link_binary_output(sess: &Session,
fn object_filenames(sess: &Session, outputs: &OutputFilenames) -> Vec<PathBuf> {
(0..sess.opts.cg.codegen_units).map(|i| {
let ext = format!("{}.o", i);
outputs.temp_path(OutputTypeObject).with_extension(&ext)
outputs.temp_path(OutputType::Object).with_extension(&ext)
}).collect()
}

Expand Down Expand Up @@ -718,7 +720,7 @@ fn link_rlib<'a>(sess: &'a Session,
// See the bottom of back::write::run_passes for an explanation
// of when we do and don't keep .0.bc files around.
let user_wants_numbered_bitcode =
sess.opts.output_types.contains(&OutputTypeBitcode) &&
sess.opts.output_types.contains_key(&OutputType::Bitcode) &&
sess.opts.cg.codegen_units > 1;
if !sess.opts.cg.save_temps && !user_wants_numbered_bitcode {
remove(sess, &bc_filename);
Expand Down

0 comments on commit 8c963c0

Please sign in to comment.