-
Notifications
You must be signed in to change notification settings - Fork 13k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
rustc: Attempt to handle super long linker invocations
This commit adds logic to the compiler to attempt to handle super long linker invocations by falling back to the `@`-file syntax if the invoked command is too large. Each OS has a limit on how many arguments and how large the arguments can be when spawning a new process, and linkers tend to be one of those programs that can hit the limit! The logic implemented here is to unconditionally attempt to spawn a linker and then if it fails to spawn with an error from the OS that indicates the command line is too big we attempt a fallback. The fallback is roughly the same for all linkers where an argument pointing to a file, prepended with `@`, is passed. This file then contains all the various arguments that we want to pass to the linker. Closes #41190
- Loading branch information
1 parent
2f1ef9e
commit ed938f0
Showing
7 changed files
with
316 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT | ||
// file at the top-level directory of this distribution and at | ||
// http://rust-lang.org/COPYRIGHT. | ||
// | ||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | ||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | ||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | ||
// option. This file may not be copied, modified, or distributed | ||
// except according to those terms. | ||
|
||
//! A thin wrapper around `Command` in the standard library which allows us to | ||
//! read the arguments that are built up. | ||
use std::ffi::{OsStr, OsString}; | ||
use std::fmt; | ||
use std::io; | ||
use std::process::{self, Output, Child}; | ||
|
||
pub struct Command { | ||
program: OsString, | ||
args: Vec<OsString>, | ||
env: Vec<(OsString, OsString)>, | ||
} | ||
|
||
impl Command { | ||
pub fn new<P: AsRef<OsStr>>(program: P) -> Command { | ||
Command::_new(program.as_ref()) | ||
} | ||
|
||
fn _new(program: &OsStr) -> Command { | ||
Command { | ||
program: program.to_owned(), | ||
args: Vec::new(), | ||
env: Vec::new(), | ||
} | ||
} | ||
|
||
pub fn arg<P: AsRef<OsStr>>(&mut self, arg: P) -> &mut Command { | ||
self._arg(arg.as_ref()); | ||
self | ||
} | ||
|
||
pub fn args<I>(&mut self, args: I) -> &mut Command | ||
where I: IntoIterator, | ||
I::Item: AsRef<OsStr>, | ||
{ | ||
for arg in args { | ||
self._arg(arg.as_ref()); | ||
} | ||
self | ||
} | ||
|
||
fn _arg(&mut self, arg: &OsStr) { | ||
self.args.push(arg.to_owned()); | ||
} | ||
|
||
pub fn env<K, V>(&mut self, key: K, value: V) -> &mut Command | ||
where K: AsRef<OsStr>, | ||
V: AsRef<OsStr> | ||
{ | ||
self._env(key.as_ref(), value.as_ref()); | ||
self | ||
} | ||
|
||
pub fn envs<I, K, V>(&mut self, envs: I) -> &mut Command | ||
where I: IntoIterator<Item=(K, V)>, | ||
K: AsRef<OsStr>, | ||
V: AsRef<OsStr> | ||
{ | ||
for (key, value) in envs { | ||
self._env(key.as_ref(), value.as_ref()); | ||
} | ||
self | ||
} | ||
|
||
fn _env(&mut self, key: &OsStr, value: &OsStr) { | ||
self.env.push((key.to_owned(), value.to_owned())); | ||
} | ||
|
||
pub fn output(&mut self) -> io::Result<Output> { | ||
self.command().output() | ||
} | ||
|
||
pub fn spawn(&mut self) -> io::Result<Child> { | ||
self.command().spawn() | ||
} | ||
|
||
pub fn command(&self) -> process::Command { | ||
let mut ret = process::Command::new(&self.program); | ||
ret.args(&self.args); | ||
ret.envs(self.env.clone()); | ||
return ret | ||
} | ||
|
||
// extensions | ||
|
||
pub fn get_program(&self) -> &OsStr { | ||
&self.program | ||
} | ||
|
||
pub fn get_args(&self) -> &[OsString] { | ||
&self.args | ||
} | ||
|
||
pub fn get_env(&self) -> &[(OsString, OsString)] { | ||
&self.env | ||
} | ||
} | ||
|
||
impl fmt::Debug for Command { | ||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
self.command().fmt(f) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
-include ../tools.mk | ||
|
||
all: | ||
$(RUSTC) foo.rs -g | ||
RUSTC="$(RUSTC_ORIGINAL)" $(call RUN,foo) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT | ||
// file at the top-level directory of this distribution and at | ||
// http://rust-lang.org/COPYRIGHT. | ||
// | ||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | ||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | ||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | ||
// option. This file may not be copied, modified, or distributed | ||
// except according to those terms. | ||
|
||
// This is a test which attempts to blow out the system limit with how many | ||
// arguments can be passed to a process. This'll successively call rustc with | ||
// larger and larger argument lists in an attempt to find one that's way too | ||
// big for the system at hand. This file itself is then used as a "linker" to | ||
// detect when the process creation succeeds. | ||
// | ||
// Eventually we should see an argument that looks like `@` as we switch from | ||
// passing literal arguments to passing everything in the file. | ||
|
||
use std::env; | ||
use std::fs::{self, File}; | ||
use std::io::{BufWriter, Write, Read}; | ||
use std::path::PathBuf; | ||
use std::process::Command; | ||
|
||
fn main() { | ||
let tmpdir = PathBuf::from(env::var_os("TMPDIR").unwrap()); | ||
let ok = tmpdir.join("ok"); | ||
if env::var("YOU_ARE_A_LINKER").is_ok() { | ||
if let Some(file) = env::args().find(|a| a.contains("@")) { | ||
fs::copy(&file[1..], &ok).unwrap(); | ||
} | ||
return | ||
} | ||
|
||
let rustc = env::var_os("RUSTC").unwrap_or("rustc".into()); | ||
let me_as_linker = format!("linker={}", env::current_exe().unwrap().display()); | ||
for i in (1..).map(|i| i * 100) { | ||
println!("attempt: {}", i); | ||
let file = tmpdir.join("bar.rs"); | ||
let mut f = BufWriter::new(File::create(&file).unwrap()); | ||
let mut lib_name = String::new(); | ||
for _ in 0..i { | ||
lib_name.push_str("foo"); | ||
} | ||
for j in 0..i { | ||
writeln!(f, "#[link(name = \"{}{}\")]", lib_name, j).unwrap(); | ||
} | ||
writeln!(f, "extern {{}}\nfn main() {{}}").unwrap(); | ||
f.into_inner().unwrap(); | ||
|
||
drop(fs::remove_file(&ok)); | ||
let output = Command::new(&rustc) | ||
.arg(&file) | ||
.arg("-C").arg(&me_as_linker) | ||
.arg("--out-dir").arg(&tmpdir) | ||
.env("YOU_ARE_A_LINKER", "1") | ||
.output() | ||
.unwrap(); | ||
|
||
if !output.status.success() { | ||
let stderr = String::from_utf8_lossy(&output.stderr); | ||
panic!("status: {}\nstdout:\n{}\nstderr:\n{}", | ||
output.status, | ||
String::from_utf8_lossy(&output.stdout), | ||
stderr.lines().map(|l| { | ||
if l.len() > 200 { | ||
format!("{}...\n", &l[..200]) | ||
} else { | ||
format!("{}\n", l) | ||
} | ||
}).collect::<String>()); | ||
} | ||
|
||
if !ok.exists() { | ||
continue | ||
} | ||
|
||
let mut contents = String::new(); | ||
File::open(&ok).unwrap().read_to_string(&mut contents).unwrap(); | ||
|
||
for j in 0..i { | ||
assert!(contents.contains(&format!("{}{}", lib_name, j))); | ||
} | ||
|
||
break | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters