//! In theory if we get past this phase it's a bug if a build fails, but in
//! practice that's likely not true!
-use std::collections::HashSet;
+use std::collections::HashMap;
use std::env;
-use std::ffi::{OsStr, OsString};
+use std::ffi::{OsString, OsStr};
use std::fs;
use std::process::Command;
+use std::path::PathBuf;
use build_helper::output;
use Build;
+struct Finder {
+ cache: HashMap<OsString, Option<PathBuf>>,
+ path: OsString,
+}
+
+impl Finder {
+ fn new() -> Self {
+ Self {
+ cache: HashMap::new(),
+ path: env::var_os("PATH").unwrap_or_default()
+ }
+ }
+
+ fn maybe_have<S: AsRef<OsStr>>(&mut self, cmd: S) -> Option<PathBuf> {
+ let cmd: OsString = cmd.as_ref().into();
+ let path = self.path.clone();
+ self.cache.entry(cmd.clone()).or_insert_with(|| {
+ for path in env::split_paths(&path) {
+ let target = path.join(&cmd);
+ let mut cmd_alt = cmd.clone();
+ cmd_alt.push(".exe");
+ if target.is_file() || // some/path/git
+ target.with_extension("exe").exists() || // some/path/git.exe
+ target.join(&cmd_alt).exists() { // some/path/git/git.exe
+ return Some(target);
+ }
+ }
+ None
+ }).clone()
+ }
+
+ fn must_have<S: AsRef<OsStr>>(&mut self, cmd: S) -> PathBuf {
+ self.maybe_have(&cmd).unwrap_or_else(|| {
+ panic!("\n\ncouldn't find required command: {:?}\n\n", cmd.as_ref());
+ })
+ }
+}
+
pub fn check(build: &mut Build) {
- let mut checked = HashSet::new();
- let path = env::var_os("PATH").unwrap_or(OsString::new());
+ let path = env::var_os("PATH").unwrap_or_default();
// On Windows, quotes are invalid characters for filename paths, and if
// one is present as part of the PATH then that can lead to the system
// being unable to identify the files properly. See
// https://github.com/rust-lang/rust/issues/34959 for more details.
- if cfg!(windows) {
- if path.to_string_lossy().contains("\"") {
- panic!("PATH contains invalid character '\"'");
- }
+ if cfg!(windows) && path.to_string_lossy().contains("\"") {
+ panic!("PATH contains invalid character '\"'");
}
- let have_cmd = |cmd: &OsStr| {
- for path in env::split_paths(&path) {
- let target = path.join(cmd);
- let mut cmd_alt = cmd.to_os_string();
- cmd_alt.push(".exe");
- if target.is_file() ||
- target.with_extension("exe").exists() ||
- target.join(cmd_alt).exists() {
- return Some(target);
- }
- }
- return None;
- };
-
- let mut need_cmd = |cmd: &OsStr| {
- if !checked.insert(cmd.to_owned()) {
- return
- }
- if have_cmd(cmd).is_none() {
- panic!("\n\ncouldn't find required command: {:?}\n\n", cmd);
- }
- };
+ let mut cmd_finder = Finder::new();
// If we've got a git directory we're gona need git to update
// submodules and learn about various other aspects.
- if build.src_is_git {
- need_cmd("git".as_ref());
+ if build.rust_info.is_git() {
+ cmd_finder.must_have("git");
}
// We need cmake, but only if we're actually building LLVM or sanitizers.
.filter_map(|host| build.config.target_config.get(host))
.any(|config| config.llvm_config.is_none());
if building_llvm || build.config.sanitizers {
- need_cmd("cmake".as_ref());
+ cmd_finder.must_have("cmake");
}
// Ninja is currently only used for LLVM itself.
- if building_llvm && build.config.ninja {
- // Some Linux distros rename `ninja` to `ninja-build`.
- // CMake can work with either binary name.
- if have_cmd("ninja-build".as_ref()).is_none() {
- need_cmd("ninja".as_ref());
- }
+ // Some Linux distros rename `ninja` to `ninja-build`.
+ // CMake can work with either binary name.
+ if building_llvm && build.config.ninja && cmd_finder.maybe_have("ninja-build").is_none() {
+ cmd_finder.must_have("ninja");
}
- if build.config.python.is_none() {
- build.config.python = have_cmd("python2.7".as_ref());
- }
- if build.config.python.is_none() {
- build.config.python = have_cmd("python2".as_ref());
- }
- if build.config.python.is_none() {
- need_cmd("python".as_ref());
- build.config.python = Some("python".into());
- }
- need_cmd(build.config.python.as_ref().unwrap().as_ref());
-
+ build.config.python = build.config.python.take().map(|p| cmd_finder.must_have(p))
+ .or_else(|| env::var_os("BOOTSTRAP_PYTHON").map(PathBuf::from)) // set by bootstrap.py
+ .or_else(|| cmd_finder.maybe_have("python2.7"))
+ .or_else(|| cmd_finder.maybe_have("python2"))
+ .or_else(|| Some(cmd_finder.must_have("python")));
- if let Some(ref s) = build.config.nodejs {
- need_cmd(s.as_ref());
- } else {
- // Look for the nodejs command, needed for emscripten testing
- if let Some(node) = have_cmd("node".as_ref()) {
- build.config.nodejs = Some(node);
- } else if let Some(node) = have_cmd("nodejs".as_ref()) {
- build.config.nodejs = Some(node);
- }
- }
+ build.config.nodejs = build.config.nodejs.take().map(|p| cmd_finder.must_have(p))
+ .or_else(|| cmd_finder.maybe_have("node"))
+ .or_else(|| cmd_finder.maybe_have("nodejs"));
- if let Some(ref gdb) = build.config.gdb {
- need_cmd(gdb.as_ref());
- } else {
- build.config.gdb = have_cmd("gdb".as_ref());
- }
+ build.config.gdb = build.config.gdb.take().map(|p| cmd_finder.must_have(p))
+ .or_else(|| cmd_finder.maybe_have("gdb"));
// We're gonna build some custom C code here and there, host triples
// also build some C++ shims for LLVM so we need a C++ compiler.
- for target in build.config.target.iter() {
+ for target in &build.config.target {
// On emscripten we don't actually need the C compiler to just
// build the target artifacts, only for testing. For the sake
// of easier bot configuration, just skip detection.
continue;
}
- need_cmd(build.cc(target).as_ref());
- if let Some(ar) = build.ar(target) {
- need_cmd(ar.as_ref());
+ cmd_finder.must_have(build.cc(*target));
+ if let Some(ar) = build.ar(*target) {
+ cmd_finder.must_have(ar);
}
}
- for host in build.config.host.iter() {
- need_cmd(build.cxx(host).as_ref());
- }
- // The msvc hosts don't use jemalloc, turn it off globally to
- // avoid packaging the dummy liballoc_jemalloc on that platform.
for host in build.config.host.iter() {
+ cmd_finder.must_have(build.cxx(*host).unwrap());
+
+ // The msvc hosts don't use jemalloc, turn it off globally to
+ // avoid packaging the dummy liballoc_jemalloc on that platform.
if host.contains("msvc") {
build.config.use_jemalloc = false;
}
}
// Externally configured LLVM requires FileCheck to exist
- let filecheck = build.llvm_filecheck(&build.config.build);
+ let filecheck = build.llvm_filecheck(build.build);
if !filecheck.starts_with(&build.out) && !filecheck.exists() && build.config.codegen_tests {
panic!("FileCheck executable {:?} does not exist", filecheck);
}
- for target in build.config.target.iter() {
+ for target in &build.config.target {
// Can't compile for iOS unless we're on macOS
if target.contains("apple-ios") &&
- !build.config.build.contains("apple-darwin") {
+ !build.build.contains("apple-darwin") {
panic!("the iOS target is only supported on macOS");
}
// Make sure musl-root is valid if specified
if target.contains("musl") && !target.contains("mips") {
- match build.musl_root(target) {
+ match build.musl_root(*target) {
Some(root) => {
if fs::metadata(root.join("lib/libc.a")).is_err() {
panic!("couldn't find libc.a in musl dir: {}",
}
}
- for host in build.flags.host.iter() {
- if !build.config.host.contains(host) {
- panic!("specified host `{}` is not in the ./configure list", host);
- }
- }
- for target in build.flags.target.iter() {
- if !build.config.target.contains(target) {
- panic!("specified target `{}` is not in the ./configure list",
- target);
- }
- }
-
let run = |cmd: &mut Command| {
cmd.output().map(|output| {
String::from_utf8_lossy(&output.stdout)
}
if let Some(ref s) = build.config.ccache {
- need_cmd(s.as_ref());
+ cmd_finder.must_have(s);
}
}