#![deny(warnings)]
#[macro_use]
extern crate error_chain;
#[macro_use]
extern crate lazy_static;
extern crate parking_lot;
extern crate rustc_version;
extern crate tempdir;
extern crate dirs;
use std::fs::OpenOptions;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::{env, fs};
use parking_lot::{Mutex, MutexGuard};
use tempdir::TempDir;
use errors::*;
mod errors {
#![allow(unused_doc_comments)]
error_chain!();
}
macro_rules! run {
() => {
if let Err(e) = run() {
panic!("{}", e)
}
}
}
fn home() -> Result<PathBuf> {
if let Some(h) = env::var_os("XARGO_HOME") {
Ok(PathBuf::from(h))
} else {
Ok(
dirs::home_dir()
.ok_or_else(|| "couldn't find your home directory. Is $HOME set?")?
.join(".xargo"),
)
}
}
fn cleanup(target: &str) -> Result<()> {
let p = home()?.join("lib/rustlib").join(target);
if p.exists() {
fs::remove_dir_all(&p).chain_err(|| format!("couldn't clean sysroot for {}", target))
} else {
Ok(())
}
}
fn exists(krate: &str, target: &str) -> Result<bool> {
let p = home()?.join("lib/rustlib").join(target).join("lib");
for e in fs::read_dir(&p).chain_err(|| format!("couldn't read the directory {}", p.display()))?
{
let e = e.chain_err(|| {
format!(
"error reading the contents of the directory {}",
p.display()
)
})?;
if e.file_name().to_string_lossy().contains(krate) {
return Ok(true);
}
}
Ok(false)
}
fn host() -> String {
rustc_version::version_meta().unwrap().host
}
fn mkdir(path: &Path) -> Result<()> {
fs::create_dir(path).chain_err(|| {
format!("couldn't create the directory {}", path.display())
})
}
fn sysroot_was_built(stderr: &str, target: &str) -> bool {
stderr.lines().filter(|l| l.starts_with("+")).any(|l| {
l.contains("cargo") && l.contains("build") && l.contains("--target") && l.contains(target)
&& l.contains("-p") && l.contains("core")
})
}
fn write(path: &Path, append: bool, contents: &str) -> Result<()> {
let p = path.display();
let mut opts = OpenOptions::new();
if append {
opts.append(true);
} else {
opts.create(true);
opts.truncate(true);
}
opts.write(true)
.open(path)
.chain_err(|| format!("couldn't open {}", p))?
.write_all(contents.as_bytes())
.chain_err(|| format!("couldn't write to {}", p))
}
fn xargo() -> Result<Command> {
let mut p = env::current_exe().chain_err(|| "couldn't get path to current executable")?;
p.pop();
p.pop();
p.push("xargo");
Ok(Command::new(p))
}
fn xargo_check() -> Result<Command> {
let mut p = env::current_exe().chain_err(|| "couldn't get path to current executable")?;
p.pop();
p.pop();
p.push("xargo-check");
Ok(Command::new(p))
}
trait CommandExt {
fn run(&mut self) -> Result<()>;
fn run_and_get_stderr(&mut self) -> Result<String>;
}
impl CommandExt for Command {
fn run(&mut self) -> Result<()> {
let status = self.status()
.chain_err(|| format!("couldn't execute `{:?}`", self))?;
if status.success() {
Ok(())
} else {
Err(format!(
"`{:?}` failed with exit code: {:?}",
self,
status.code()
))?
}
}
fn run_and_get_stderr(&mut self) -> Result<String> {
let out = self.output()
.chain_err(|| format!("couldn't execute `{:?}`", self))?;
let stderr = String::from_utf8(out.stderr)
.chain_err(|| format!("`{:?}` output was not UTF-8", self));
if out.status.success() {
stderr
} else {
match stderr {
Ok(e) => print!("{}", e),
Err(e) => print!("{}", e),
}
Err(format!(
"`{:?}` failed with exit code: {:?}",
self,
out.status.code()
))?
}
}
}
struct Project {
name: &'static str,
td: TempDir,
}
fn create_simple_project(project_path: &Path, name: &'static str, library_source: &'static str) -> Result<()> {
xargo()?
.args(&["init", "-q", "--lib", "--vcs", "none", "--name", name])
.current_dir(project_path)
.run()?;
write(&project_path.join("src/lib.rs"), false, library_source)?;
Ok(())
}
impl Project {
fn new(name: &'static str) -> Result<Self> {
Self::new_in(std::env::temp_dir(), name)
}
fn new_in(dir: PathBuf, name: &'static str) -> Result<Self> {
const JSON: &'static str = r#"
{
"arch": "arm",
"data-layout": "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64",
"linker-flavor": "gcc",
"llvm-target": "thumbv6m-none-eabi",
"max-atomic-width": 0,
"os": "none",
"target-c-int-width": "32",
"target-endian": "little",
"target-pointer-width": "32"
}
"#;
let td = TempDir::new_in(dir, "xargo").chain_err(|| "couldn't create a temporary directory")?;
create_simple_project(td.path(), name, "#![no_std]")?;
write(&td.path().join(format!("{}.json", name)), false, JSON)?;
Ok(Project { name: name, td: td })
}
fn build(&self, target: &str) -> Result<()> {
self.build_and_get_stderr(Some(target))?;
Ok(())
}
fn build_from_workdir(&self, target: &str, working_dir: &Path) -> Result<()> {
self.build_from_workdir_and_get_stderr(Some(target), working_dir)?;
Ok(())
}
fn build_and_get_stderr(&self, target: Option<&str>) -> Result<String> {
let mut cmd = xargo()?;
cmd.arg("build");
if let Some(target) = target {
cmd.args(&["--target", target]);
}
cmd.arg("-v")
.current_dir(self.td.path())
.run_and_get_stderr()
}
fn build_from_workdir_and_get_stderr(&self, target: Option<&str>, working_dir: &Path) -> Result<String> {
let mut cmd = xargo()?;
cmd.env("RUST_TARGET_PATH", &self.td.path());
cmd.arg("build");
cmd.args(&["--manifest-path", &self.td.path().join("Cargo.toml").to_str().unwrap()]);
if let Some(target) = target {
cmd.args(&["--target", target]);
}
cmd.arg("-v")
.current_dir(working_dir)
.run_and_get_stderr()
}
fn cargo_toml(&self, contents: &str) -> Result<()> {
write(&self.td.path().join("Cargo.toml"), true, contents)
}
fn config(&self, contents: &str) -> Result<()> {
mkdir(&self.td.path().join(".cargo"))?;
write(&self.td.path().join(".cargo/config"), false, contents)
}
fn doc(&self, target: &str) -> Result<()> {
xargo()?
.args(&["doc", "--target", target])
.current_dir(self.td.path())
.run_and_get_stderr()?;
Ok(())
}
fn xargo_toml(&self, toml: &str) -> Result<()> {
write(&self.td.path().join("Xargo.toml"), false, toml)
}
}
impl Drop for Project {
fn drop(&mut self) {
cleanup(self.name).unwrap()
}
}
fn hcleanup(triple: &str) -> Result<()> {
let p = home()?.join("HOST/lib/rustlib").join(triple);
if p.exists() {
fs::remove_dir_all(&p).chain_err(|| format!("couldn't clean sysroot for {}", triple))
} else {
Ok(())
}
}
struct HProject {
_guard: MutexGuard<'static, ()>,
host: String,
td: TempDir,
}
impl HProject {
fn new(test: bool) -> Result<Self> {
lazy_static! {
static ref ONCE: Mutex<()> = Mutex::new(());
}
let guard = ONCE.lock();
let td = TempDir::new("xar go").chain_err(|| "couldn't create a temporary directory")?;
xargo()?
.args(&["init", "-q", "--lib", "--vcs", "none", "--name", "host"])
.current_dir(td.path())
.run()?;
if test {
write(
&td.path().join("src/lib.rs"),
false,
"fn _f(_: Vec<std::fs::File>) {}",
)?;
} else {
write(&td.path().join("src/lib.rs"), false, "#![no_std]")?;
}
Ok(HProject {
_guard: guard,
host: host(),
td: td,
})
}
fn build(&self, verb: &str) -> Result<()> {
xargo()?.arg(verb).current_dir(self.td.path()).run_and_get_stderr()?;
Ok(())
}
fn build_and_get_stderr(&self) -> Result<String> {
let mut cmd = xargo()?;
cmd.arg("build");
cmd.arg("-v")
.current_dir(self.td.path())
.run_and_get_stderr()
}
fn xargo_toml(&self, toml: &str) -> Result<()> {
write(&self.td.path().join("Xargo.toml"), false, toml)
}
fn xargo_check_subcommand(&self, subcommand: Option<&str>, target: Option<&str>) -> Result<String> {
let mut cmd = xargo_check()?;
if let Some(subcommand) = subcommand {
cmd.arg(subcommand);
}
if let Some(target) = target {
cmd.args(&["--target", target]);
}
cmd
.current_dir(self.td.path())
.run_and_get_stderr()
}
}
impl Drop for HProject {
fn drop(&mut self) {
hcleanup(&self.host).unwrap()
}
}
#[test]
fn simple() {
fn run() -> Result<()> {
const TARGET: &'static str = "thumbv6m-simple-eabi";
let project = Project::new(TARGET)?;
project.build(TARGET)?;
assert!(exists("core", TARGET)?);
Ok(())
}
run!()
}
#[test]
fn target_dependencies() {
fn run() -> Result<()> {
const TARGET: &'static str = "thumbv7m-none-eabi";
const STAGE1: &'static str = "stage1";
let td = TempDir::new("xargo").chain_err(|| "couldn't create a temporary directory")?;
let project = Project::new_in(td.path().to_path_buf(), TARGET)?;
let stage1_path = td.path().join(STAGE1);
mkdir(stage1_path.as_path())?;
create_simple_project(stage1_path.as_path(), STAGE1, "#![no_std]")?;
write(&td.path().join("Xargo.toml"), false,
r#"
[target.thumbv7m-none-eabi.dependencies.alloc]
[target.thumbv7m-none-eabi.dependencies.stage1]
stage = 1
path = "stage1"
"#,
)?;
project.build(TARGET)?;
assert!(exists("core", TARGET)?);
assert!(exists("alloc", TARGET)?);
assert!(exists("stage1", TARGET)?);
Ok(())
}
run!()
}
#[test]
fn dependencies() {
fn run() -> Result<()> {
const TARGET: &'static str = "thumbv6m-none-eabi";
let project = Project::new(TARGET)?;
project.xargo_toml(
r#"
[dependencies.alloc]
"#,
)?;
project.build(TARGET)?;
assert!(exists("core", TARGET)?);
assert!(exists("alloc", TARGET)?);
Ok(())
}
run!()
}
#[test]
fn build_outside_project_dir() {
fn run() -> Result<()> {
const TARGET: &'static str = "thumbv6m-workdir-eabi";
let project = Project::new(TARGET)?;
project.xargo_toml(
r#"
[dependencies.alloc]
"#,
)?;
let workdir = TempDir::new("xargo_workdir").chain_err(|| "couldn't create a temporary directory")?;
project.build_from_workdir(TARGET, &workdir.path())?;
Ok(())
}
let r = run();
assert!(r.is_ok());
}
#[test]
fn doc() {
fn run() -> Result<()> {
const TARGET: &'static str = "thumbv6m-doc-eabi";
let project = Project::new(TARGET)?;
project.doc(TARGET)?;
assert!(exists("core", TARGET)?);
Ok(())
}
run!()
}
#[test]
fn twice() {
fn run() -> Result<()> {
const TARGET: &'static str = "thumbv6m-twice-eabi";
let project = Project::new(TARGET)?;
let stderr = project.build_and_get_stderr(Some(TARGET))?;
assert!(sysroot_was_built(&stderr, TARGET));
let stderr = project.build_and_get_stderr(Some(TARGET))?;
assert!(!sysroot_was_built(&stderr, TARGET));
Ok(())
}
run!()
}
#[test]
fn build_target() {
fn run() -> Result<()> {
const TARGET: &'static str = "thumbv6m-build_target-eabi";
let project = Project::new(TARGET)?;
project.config(
r#"
[build]
target = "thumbv6m-build_target-eabi"
"#,
)?;
let stderr = project.build_and_get_stderr(None)?;
assert!(sysroot_was_built(&stderr, TARGET));
Ok(())
}
run!()
}
#[test]
fn override_build_target() {
fn run() -> Result<()> {
const TARGET: &'static str = "thumbv6m-override_build_target-eabi";
let project = Project::new(TARGET)?;
project.config(
r#"
[build]
target = "BAD"
"#,
)?;
let stderr = project.build_and_get_stderr(Some(TARGET))?;
assert!(sysroot_was_built(&stderr, TARGET));
Ok(())
}
run!()
}
#[test]
fn lto_changed() {
fn run() -> Result<()> {
const TARGET: &'static str = "thumbv6m-lto_changed-eabi";
let project = Project::new(TARGET)?;
let stderr = project.build_and_get_stderr(Some(TARGET))?;
assert!(sysroot_was_built(&stderr, TARGET));
project.cargo_toml(
r#"
[profile.release]
lto = true
"#,
)?;
let stderr = project.build_and_get_stderr(Some(TARGET))?;
assert!(!sysroot_was_built(&stderr, TARGET));
Ok(())
}
run!()
}
#[test]
fn rustflags_changed() {
fn run() -> Result<()> {
const TARGET: &'static str = "thumbv6m-rustflags_changed-eabi";
let project = Project::new(TARGET)?;
let stderr = project.build_and_get_stderr(Some(TARGET))?;
assert!(sysroot_was_built(&stderr, TARGET));
project.config(
r#"
[build]
rustflags = ["--cfg", "xargo"]
"#,
)?;
let stderr = project.build_and_get_stderr(Some(TARGET))?;
assert!(sysroot_was_built(&stderr, TARGET));
Ok(())
}
run!()
}
#[test]
fn rustflags() {
fn run() -> Result<()> {
const TARGET: &'static str = "thumbv6m-rustflags-eabi";
let project = Project::new(TARGET)?;
project.config(
r#"
[build]
rustflags = ["--cfg", 'xargo="y e s"']
"#,
)?;
let stderr = project.build_and_get_stderr(Some(TARGET))?;
assert!(
stderr.contains(r#"'xargo="y e s"'"#),
"unexpected stderr:\n{}", stderr
);
Ok(())
}
run!()
}
#[test]
fn panic_abort() {
fn run() -> Result<()> {
const TARGET: &'static str = "thumbv6m-panic_abort-eabi";
let project = Project::new(TARGET)?;
project.cargo_toml(
r#"
[profile.release]
panic = "abort"
"#,
)?;
let stderr = project.build_and_get_stderr(Some(TARGET))?;
assert!(
stderr
.lines()
.filter(|l| !l.starts_with("+") && l.contains("--release"))
.all(|l| l.contains("-C") && l.contains("panic=abort"))
);
Ok(())
}
run!()
}
#[test]
fn link_arg() {
fn run() -> Result<()> {
const TARGET: &'static str = "thumbv6m-link_arg-eabi";
let project = Project::new(TARGET)?;
let stderr = project.build_and_get_stderr(Some(TARGET))?;
assert!(sysroot_was_built(&stderr, TARGET));
project.config(
r#"
[target.__link_arg]
rustflags = ["-C", "link-arg=-lfoo"]
"#,
)?;
let stderr = project.build_and_get_stderr(Some(TARGET))?;
assert!(!sysroot_was_built(&stderr, TARGET));
Ok(())
}
run!()
}
#[test]
fn specification_changed() {
fn run() -> Result<()> {
const JSON: &'static str = r#"
{
"arch": "arm",
"data-layout": "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64",
"linker-flavor": "gcc",
"llvm-target": "thumbv6m-none-eabi",
"max-atomic-width": 0,
"os": "none",
"panic-strategy": "abort",
"target-c-int-width": "32",
"target-endian": "little",
"target-pointer-width": "32"
}
"#;
const TARGET: &'static str = "thumbv6m-specification_changed-eabi";
let project = Project::new(TARGET)?;
let stderr = project.build_and_get_stderr(Some(TARGET))?;
assert!(sysroot_was_built(&stderr, TARGET));
write(
&project.td.path().join("thumbv6m-specification_changed-eabi.json"),
false,
JSON,
)?;
let stderr = project.build_and_get_stderr(Some(TARGET))?;
assert!(sysroot_was_built(&stderr, TARGET));
Ok(())
}
run!()
}
#[test]
fn unchanged_specification() {
fn run() -> Result<()> {
const JSON: &'static str = r#"
{
"arch": "arm",
"data-layout": "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64",
"linker-flavor": "gcc",
"llvm-target": "thumbv6m-none-eabi",
"os": "none",
"max-atomic-width": 0,
"target-c-int-width": "32",
"target-endian": "little",
"target-pointer-width": "32"
}
"#;
const TARGET: &'static str = "thumbv6m-unchanged_specification-eabi";
let project = Project::new(TARGET)?;
let stderr = project.build_and_get_stderr(Some(TARGET))?;
assert!(sysroot_was_built(&stderr, TARGET));
write(
&project.td.path().join("thumbv6m-unchanged_specification-eabi.json"),
false,
JSON,
)?;
let stderr = project.build_and_get_stderr(Some(TARGET))?;
assert!(!sysroot_was_built(&stderr, TARGET));
Ok(())
}
run!()
}
#[test]
fn host_once() {
fn run() -> Result<()> {
let target = host();
let project = HProject::new(false)?;
let stderr = project.build_and_get_stderr()?;
assert!(sysroot_was_built(&stderr, &target));
Ok(())
}
run!()
}
#[test]
fn host_twice() {
fn run() -> Result<()> {
let target = host();
let project = HProject::new(false)?;
let stderr = project.build_and_get_stderr()?;
assert!(sysroot_was_built(&stderr, &target));
let stderr = project.build_and_get_stderr()?;
assert!(!sysroot_was_built(&stderr, &target));
Ok(())
}
run!()
}
#[test]
fn host_libtest() {
fn run() -> Result<()> {
let project = HProject::new(true)?;
project.xargo_toml(
"
[dependencies.std]
features = [\"panic_unwind\"]
[dependencies.test]
",
)?;
project.build("test")
}
run!()
}
#[test]
fn host_liballoc() {
fn run() -> Result<()> {
let project = HProject::new(false)?;
project.xargo_toml(
"
[dependencies.core]
stage = 0
[dependencies.alloc]
stage = 1
",
)?;
project.build("build")
}
run!()
}
#[test]
fn host_patch() {
fn run() -> Result<()> {
let project = HProject::new(false)?;
project.xargo_toml(
r#"
[dependencies.std]
features = ["panic_unwind"]
[patch.crates-io.cc]
git = "https://github.com/alexcrichton/cc-rs"
tag = "1.0.47"
"#,
)?;
let stderr = project.build_and_get_stderr()?;
assert!(stderr
.lines()
.any(|line| line.contains("Compiling cc ")
&& line.contains("https://github.com/alexcrichton/cc-rs")),
"Looks like patching did not work. stderr:\n{}", stderr
);
Ok(())
}
let is_pinned = std::env::var("TRAVIS_RUST_VERSION").ok().map_or(false,
|var| var.starts_with("nightly-"));
if is_pinned {
run!()
}
}
#[test]
fn cargo_check_check() {
fn run() -> Result<()> {
let project = HProject::new(false)?;
project.xargo_check_subcommand(Some("check"), None)?;
Ok(())
}
run!()
}
#[test]
fn cargo_check_check_no_ctoml() {
fn run() -> Result<()> {
const TARGET: &str = "i686-pc-windows-gnu";
let project = HProject::new(false)?;
cleanup(TARGET)?;
project.xargo_toml("")?;
std::fs::remove_file(project.td.path().join("Cargo.toml"))
.chain_err(|| format!("Could not remove Cargo.toml"))?;
let stderr = project.xargo_check_subcommand(None, Some(TARGET))?;
assert!(stderr.contains("Checking core"), "Looks like checking did not work. stderr:\n{}", stderr);
Ok(())
}
run!()
}