#![deny(missing_debug_implementations)]
extern crate atty;
#[macro_use]
extern crate error_chain;
#[macro_use]
extern crate lazy_static;
extern crate libc;
extern crate rustc_version;
extern crate semver;
extern crate toml;
#[cfg(not(target_os = "windows"))]
extern crate nix;
#[cfg(target_os = "windows")]
extern crate winapi;
mod cargo;
mod cli;
mod docker;
mod errors;
mod extensions;
mod file;
mod id;
mod interpreter;
mod rustc;
mod rustup;
use std::io::Write;
use std::process::ExitStatus;
use std::{env, io, process};
use toml::{Value, value::Table};
use cargo::{Root, Subcommand};
use errors::*;
use rustc::{TargetList, VersionMetaExt};
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Host {
Other,
X86_64AppleDarwin,
X86_64UnknownLinuxGnu,
X86_64PcWindowsMsvc
}
impl Host {
fn is_supported(&self, target: Option<&Target>) -> bool {
if *self == Host::X86_64AppleDarwin {
target.map(|t| t.triple() == "i686-apple-darwin" || t.needs_docker()).unwrap_or(false)
} else if *self == Host::X86_64UnknownLinuxGnu {
target.map(|t| t.needs_docker()).unwrap_or(true)
} else if *self == Host::X86_64PcWindowsMsvc {
target.map(|t| t.needs_docker()).unwrap_or(false)
} else {
false
}
}
fn triple(&self) -> &'static str {
match *self {
Host::X86_64AppleDarwin => "x86_64-apple-darwin",
Host::X86_64UnknownLinuxGnu => "x86_64-unknown-linux-gnu",
Host::X86_64PcWindowsMsvc => "x86_64-pc-windows-msvc",
Host::Other => unimplemented!()
}
}
}
impl<'a> From<&'a str> for Host {
fn from(s: &str) -> Host {
match s {
"x86_64-apple-darwin" => Host::X86_64AppleDarwin,
"x86_64-unknown-linux-gnu" => Host::X86_64UnknownLinuxGnu,
"x86_64-pc-windows-msvc" => Host::X86_64PcWindowsMsvc,
_ => Host::Other,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Target {
BuiltIn { triple: String },
Custom { triple: String },
}
impl Target {
fn new_built_in(triple: &str) -> Self {
Target::BuiltIn { triple: triple.to_owned() }
}
fn new_custom(triple: &str) -> Self {
Target::Custom { triple: triple.to_owned() }
}
fn triple(&self) -> &str {
match *self {
Target::BuiltIn{ref triple} => triple,
Target::Custom{ref triple} => triple,
}
}
fn is_bare_metal(&self) -> bool {
self.triple().contains("thumb")
}
fn is_builtin(&self) -> bool {
match *self {
Target::BuiltIn{ .. } => true,
Target::Custom{ .. } => false,
}
}
fn is_bsd(&self) -> bool {
self.triple().contains("bsd") || self.triple().contains("dragonfly")
}
fn is_solaris(&self) -> bool {
self.triple().contains("solaris")
}
fn is_android(&self) -> bool {
self.triple().contains("android")
}
fn is_emscripten(&self) -> bool {
self.triple().contains("emscripten")
}
fn is_linux(&self) -> bool {
self.triple().contains("linux") && !self.is_android()
}
fn is_windows(&self) -> bool {
self.triple().contains("windows")
}
fn needs_docker(&self) -> bool {
self.is_linux() || self.is_android() || self.is_bare_metal() || self.is_bsd() ||
self.is_solaris() || !self.is_builtin() || self.is_windows() || self.is_emscripten()
}
fn needs_interpreter(&self) -> bool {
let native = self.triple().starts_with("x86_64") ||
self.triple().starts_with("i586") ||
self.triple().starts_with("i686");
!native && (self.is_linux() || self.is_windows() || self.is_bare_metal())
}
}
impl Target {
fn from(triple: &str, target_list: &TargetList) -> Target {
if target_list.contains(triple) {
Target::new_built_in(triple)
} else {
Target::new_custom(triple)
}
}
}
impl From<Host> for Target {
fn from(host: Host) -> Target {
match host {
Host::X86_64UnknownLinuxGnu => Target::new_built_in("x86_64-unknown-linux-gnu"),
Host::X86_64AppleDarwin => Target::new_built_in("x86_64-apple-darwin"),
Host::X86_64PcWindowsMsvc => Target::new_built_in("x86_64-pc-windows-msvc"),
Host::Other => unimplemented!(),
}
}
}
pub fn main() {
fn show_backtrace() -> bool {
env::var("RUST_BACKTRACE").as_ref().map(|s| &s[..]) == Ok("1")
}
match run() {
Err(e) => {
let stderr = io::stderr();
let mut stderr = stderr.lock();
writeln!(stderr, "error: {}", e).ok();
for e in e.iter().skip(1) {
writeln!(stderr, "caused by: {}", e).ok();
}
if show_backtrace() {
if let Some(backtrace) = e.backtrace() {
writeln!(stderr, "{:?}", backtrace).ok();
}
} else {
writeln!(stderr,
"note: run with `RUST_BACKTRACE=1` for a backtrace")
.ok();
}
process::exit(1)
}
Ok(status) => {
if !status.success() {
process::exit(status.code().unwrap_or(1))
}
}
}
}
fn run() -> Result<ExitStatus> {
let target_list = rustc::target_list(false)?;
let args = cli::parse(&target_list);
if args.all.iter().any(|a| a == "--version" || a == "-V") &&
args.subcommand.is_none() {
println!(concat!("cross ", env!("CARGO_PKG_VERSION"), "{}"),
include_str!(concat!(env!("OUT_DIR"), "/commit-info.txt")));
}
let verbose =
args.all.iter().any(|a| a == "--verbose" || a == "-v" || a == "-vv");
let version_meta = rustc_version::version_meta().chain_err(|| "couldn't fetch the `rustc` version")?;
if let Some(root) = cargo::root()? {
let host = version_meta.host();
if host.is_supported(args.target.as_ref()) {
let target = args.target
.unwrap_or(Target::from(host.triple(), &target_list));
let toml = toml(&root)?;
let sysroot = rustc::sysroot(&host, &target, verbose)?;
let toolchain = sysroot.file_name().and_then(|file_name| file_name.to_str())
.ok_or("couldn't get toolchain name")?;
let installed_toolchains = rustup::installed_toolchains(verbose)?;
if !installed_toolchains.into_iter().any(|t| t == toolchain) {
rustup::install_toolchain(&toolchain, verbose)?;
}
let available_targets = rustup::available_targets(&toolchain, verbose)?;
let uses_xargo = !target.is_builtin() ||
!available_targets.contains(&target) ||
if let Some(toml) = toml.as_ref() {
toml.xargo(&target)?
} else {
None
}
.unwrap_or(false);
if !uses_xargo && !available_targets.is_installed(&target) {
rustup::install(&target, &toolchain, verbose)?;
} else if !rustup::component_is_installed("rust-src", toolchain, verbose)? {
rustup::install_component("rust-src", toolchain, verbose)?;
}
if args.subcommand.map(|sc| sc == Subcommand::Clippy).unwrap_or(false) {
if !rustup::component_is_installed("clippy", toolchain, verbose)? {
rustup::install_component("clippy", toolchain, verbose)?;
}
}
let needs_interpreter = args.subcommand.map(|sc| sc.needs_interpreter()).unwrap_or(false);
if target.needs_docker() &&
args.subcommand.map(|sc| sc.needs_docker()).unwrap_or(false) {
if version_meta.needs_interpreter() &&
needs_interpreter &&
target.needs_interpreter() &&
!interpreter::is_registered(&target)? {
docker::register(&target, verbose)?
}
return docker::run(&target,
&args.all,
&root,
toml.as_ref(),
uses_xargo,
&sysroot,
verbose);
}
}
}
cargo::run(&args.all, verbose)
}
#[derive(Debug)]
pub struct Toml {
table: Table,
}
impl Toml {
pub fn image(&self, target: &Target) -> Result<Option<&str>> {
let triple = target.triple();
if let Some(value) = self.table.get("target").and_then(|t| t.get(triple)).and_then(|t| t.get("image")) {
Ok(Some(value.as_str()
.ok_or_else(|| {
format!("target.{}.image must be a string", triple)
})?))
} else {
Ok(None)
}
}
pub fn runner(&self, target: &Target) -> Result<Option<String>> {
let triple = target.triple();
if let Some(value) = self.table.get("target").and_then(|t| t.get(triple)).and_then(|t| t.get("runner")) {
let value = value.as_str()
.ok_or_else(|| format!("target.{}.runner must be a string", triple))?
.to_string();
Ok(Some(value))
} else {
Ok(None)
}
}
pub fn xargo(&self, target: &Target) -> Result<Option<bool>> {
let triple = target.triple();
if let Some(value) = self.table.get("build").and_then(|b| b.get("xargo")) {
return Ok(Some(value.as_bool()
.ok_or_else(|| "build.xargo must be a boolean")?));
}
if let Some(value) = self.table.get("target").and_then(|b| b.get(triple)).and_then(|t| t.get("xargo")) {
Ok(Some(value.as_bool()
.ok_or_else(|| {
format!("target.{}.xargo must be a boolean", triple)
})?))
} else {
Ok(None)
}
}
pub fn env_passthrough(&self, target: &Target) -> Result<Vec<&str>> {
let mut bwl = self.build_env_passthrough()?;
let mut twl = self.target_env_passthrough(target)?;
bwl.extend(twl.drain(..));
Ok(bwl)
}
fn build_env_passthrough(&self) -> Result<Vec<&str>> {
match self.table.get("build").and_then(|b| b.get("env")).and_then(|e| e.get("passthrough")) {
Some(&Value::Array(ref vec)) => {
if vec.iter().any(|val| val.as_str().is_none()) {
bail!("every build.env.passthrough element must be a string");
}
Ok(vec.iter().map(|val| val.as_str().unwrap()).collect())
},
_ => Ok(Vec::new()),
}
}
fn target_env_passthrough(&self, target: &Target) -> Result<Vec<&str>> {
let triple = target.triple();
let key = format!("target.{}.env.passthrough", triple);
match self.table.get("target").and_then(|t| t.get(triple)).and_then(|t| t.get("env")).and_then(|e| e.get("passthrough")) {
Some(&Value::Array(ref vec)) => {
if vec.iter().any(|val| val.as_str().is_none()) {
bail!("every {} element must be a string", key);
}
Ok(vec.iter().map(|val| val.as_str().unwrap()).collect())
},
_ => Ok(Vec::new()),
}
}
}
fn toml(root: &Root) -> Result<Option<Toml>> {
let path = root.path().join("Cross.toml");
if path.exists() {
Ok(Some(Toml {
table: if let Ok(Value::Table(table)) = file::read(&path)?.parse() {
table
} else {
return Err(format!("couldn't parse {} as TOML table", path.display()).into())
},
}))
} else {
Ok(None)
}
}