diff --git a/CHANGELOG.md b/CHANGELOG.md index b5a7634..794a327 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Added + +- Support for building a custom sysroot when compiling natively. + ## [v0.3.2] - 2017-01-03 ### Changed diff --git a/Cargo.lock b/Cargo.lock index 09fbcc2..e519dc1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5,6 +5,7 @@ dependencies = [ "error-chain 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "fs2 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", "tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -101,6 +102,32 @@ name = "num-traits" version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "owning_ref" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "parking_lot" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "owning_ref 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot_core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "parking_lot_core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rand" version = "0.3.15" @@ -148,6 +175,11 @@ dependencies = [ "serde 0.8.21 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "smallvec" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "tempdir" version = "0.3.5" @@ -196,6 +228,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum libc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "a51822fc847e7a8101514d1d44e354ba2ffa7d4c194dcab48870740e327cac70" "checksum num-traits 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)" = "a16a42856a256b39c6d3484f097f6713e14feacd9bfb02290917904fae46c81c" +"checksum owning_ref 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8d91377085359426407a287ab16884a0111ba473aa6844ff01d4ec20ce3d75e7" +"checksum parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "e1435e7a2a00dfebededd6c6bdbd54008001e94b4a2aadd6aef0dc4c56317621" +"checksum parking_lot_core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fb1b97670a2ffadce7c397fb80a3d687c4f3060140b885621ef1653d0e5d5068" "checksum rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "022e0636ec2519ddae48154b028864bdce4eaf7d35226ab8e65c611be97b189d" "checksum rustc-demangle 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1430d286cadb237c17c885e25447c982c97113926bb579f4379c0eca8d9586dc" "checksum rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "237546c689f20bb44980270c73c3b9edd0891c1be49cc1274406134a66d3957b" @@ -203,6 +238,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "d4f410fedcf71af0345d7607d246e7ad15faaadd49d240ee3b24e5dc21a820ac" "checksum serde 0.8.21 (registry+https://github.com/rust-lang/crates.io-index)" = "7b7c6bf11cf766473ea1d53eb4e3bc4e80f31f50082fc24077cf06f600279a66" "checksum serde_json 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3f7d3c184d35801fb8b32b46a7d58d57dbcc150b0eb2b46a1eb79645e8ecfd5b" +"checksum smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "fcc8d19212aacecf95e4a7a2179b26f7aeb9732a915cf01f05b0d3e044865410" "checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6" "checksum toml 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "736b60249cb25337bc196faa43ee12c705e426f3d55c214d73a4e7be06f92cb4" "checksum walkdir 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "dd7c16466ecc507c7cb5988db03e6eab4aaeab89a5c37a29251fcfd3ac9b7afe" diff --git a/Cargo.toml b/Cargo.toml index a3b9589..f7fbef2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,9 @@ tempdir = "0.3.5" toml = "0.2.1" walkdir = "1.0.3" +[dev-dependencies.parking_lot] +features = ["nightly"] +version = "0.3.6" + [features] -# Causes Xargo to always build the sysroot using the dev profile -# This is mainly for shorter test times dev = [] diff --git a/README.md b/README.md index c719cf6..b9572a9 100644 --- a/README.md +++ b/README.md @@ -150,10 +150,6 @@ $ xargo build --target msp430-none-elf because `std` and other standard crates depend on unstable features so it's not possible to build the sysroot with stable or beta. -- Because of how sysroots work, `xargo` *can't*, and won't, build a sysroot for - the HOST. IOW, `xargo` will only build/use sysroots when you are cross - compiling. - - As of nightly-2016-12-19, `std` can't be compiled from the `rust-src` component *without* patching the source. diff --git a/src/main.rs b/src/main.rs index 8b039ed..aacb1dc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,7 @@ extern crate tempdir; extern crate toml; extern crate walkdir; +use std::hash::{Hash, Hasher}; use std::io::Write; use std::path::{Path, PathBuf}; use std::process::ExitStatus; @@ -30,6 +31,44 @@ mod sysroot; mod util; mod xargo; +// We use a different sysroot for Native compilation to avoid file locking +// +// Cross compilation requires `lib/rustlib/$HOST` to match `rustc`'s sysroot, +// whereas Native compilation wants to use a custom `lib/rustlib/$HOST`. If each +// mode has its own sysroot then we avoid sharing that directory and thus file +// locking it. +pub enum CompilationMode { + Cross(Target), + Native(String), +} + +impl CompilationMode { + fn hash(&self, hasher: &mut H) -> Result<()> + where H: Hasher + { + match *self { + CompilationMode::Cross(ref target) => target.hash(hasher)?, + CompilationMode::Native(ref triple) => triple.hash(hasher), + } + + Ok(()) + } + + fn triple(&self) -> &str { + match *self { + CompilationMode::Cross(ref target) => target.triple(), + CompilationMode::Native(ref triple) => triple, + } + } + + fn is_native(&self) -> bool { + match *self { + CompilationMode::Native(_) => true, + _ => false, + } + } +} + pub fn main() { fn show_backtrace() -> bool { env::var("RUST_BACKTRACE").as_ref().map(|s| &s[..]) == Ok("1") @@ -107,33 +146,31 @@ fn run() -> Result { } }; - let target = if let Some(triple) = args.target() { - if triple != meta.host { - Target::new(triple, &cd, verbose)? + let cmode = if let Some(triple) = args.target() { + if triple == meta.host { + Some(CompilationMode::Native(meta.host.clone())) } else { - None + Target::new(triple, &cd, verbose)?.map(CompilationMode::Cross) } } else { if let Some(ref config) = config { if let Some(triple) = config.target()? { - if triple != meta.host { - Target::new(triple, &cd, verbose)? - } else { - None - } + Target::new(triple, &cd, verbose) + ? + .map(CompilationMode::Cross) } else { - None + Some(CompilationMode::Native(meta.host.clone())) } } else { - None + Some(CompilationMode::Native(meta.host.clone())) } }; - if let Some(target) = target { - let home = xargo::home()?; - let rustflags = cargo::rustflags(config.as_ref(), target.triple())?; + if let Some(cmode) = cmode { + let home = xargo::home(&cmode)?; + let rustflags = cargo::rustflags(config.as_ref(), cmode.triple())?; - sysroot::update(&target, + sysroot::update(&cmode, &home, &root, &rustflags, @@ -142,7 +179,7 @@ fn run() -> Result { &sysroot, verbose)?; return xargo::run(&args, - &target, + &cmode, rustflags, &home, &meta, diff --git a/src/rustc.rs b/src/rustc.rs index b0f41b3..3d701ef 100644 --- a/src/rustc.rs +++ b/src/rustc.rs @@ -51,7 +51,7 @@ impl Src { } } -/// Path to the original sysroot +/// Path to `rustc`'s sysroot pub struct Sysroot { path: PathBuf, } diff --git a/src/sysroot.rs b/src/sysroot.rs index c1990fc..f638f5c 100644 --- a/src/sysroot.rs +++ b/src/sysroot.rs @@ -9,10 +9,11 @@ use rustc_version::VersionMeta; use tempdir::TempDir; use toml::Value; +use CompilationMode; use cargo::{Root, Rustflags}; use errors::*; use extensions::CommandExt; -use rustc::{Src, Sysroot, Target}; +use rustc::{Src, Sysroot}; use util; use xargo::Home; use {cargo, xargo}; @@ -27,7 +28,7 @@ fn profile() -> &'static str { "release" } -fn build(target: &Target, +fn build(cmode: &CompilationMode, deps: &Dependencies, ctoml: &cargo::Toml, home: &Home, @@ -73,7 +74,7 @@ version = "0.0.0" } cmd.arg("--manifest-path"); cmd.arg(td.join("Cargo.toml")); - cmd.args(&["--target", target.triple()]); + cmd.args(&["--target", cmode.triple()]); if verbose { cmd.arg("-v"); @@ -87,13 +88,13 @@ version = "0.0.0" } // Copy artifacts to Xargo sysroot - let rustlib = home.lock_rw(target.triple())?; + let rustlib = home.lock_rw(cmode.triple())?; rustlib.remove_siblings() .chain_err(|| format!("couldn't clear {}", rustlib.path().display()))?; let dst = rustlib.parent().join("lib"); util::mkdir(&dst)?; util::cp_r(&td.join("target") - .join(target.triple()) + .join(cmode.triple()) .join(profile()) .join("deps"), &dst)?; @@ -104,9 +105,9 @@ version = "0.0.0" Ok(()) } -fn old_hash(target: &str, home: &Home) -> Result> { +fn old_hash(cmode: &CompilationMode, home: &Home) -> Result> { // FIXME this should be `lock_ro` - let lock = home.lock_rw(target)?; + let lock = home.lock_rw(cmode.triple())?; let hfile = lock.parent().join(".hash"); if hfile.exists() { @@ -125,7 +126,7 @@ fn old_hash(target: &str, home: &Home) -> Result> { /// - The target specification file, is any /// - `[profile.release]` in `Cargo.toml` /// - `rustc` commit hash -fn hash(target: &Target, +fn hash(cmode: &CompilationMode, dependencies: &Dependencies, rustflags: &Rustflags, ctoml: &cargo::Toml, @@ -137,7 +138,7 @@ fn hash(target: &Target, rustflags.hash(&mut hasher); - target.hash(&mut hasher)?; + cmode.hash(&mut hasher)?; if let Some(profile) = ctoml.profile() { profile.hash(&mut hasher); @@ -150,7 +151,7 @@ fn hash(target: &Target, Ok(hasher.finish()) } -pub fn update(target: &Target, +pub fn update(cmode: &CompilationMode, home: &Home, root: &Root, rustflags: &Rustflags, @@ -162,15 +163,19 @@ pub fn update(target: &Target, let ctoml = cargo::toml(root)?; let xtoml = xargo::toml(root)?; - let deps = Dependencies::from(xtoml.as_ref(), target.triple(), &src)?; + let deps = Dependencies::from(xtoml.as_ref(), cmode.triple(), &src)?; - let hash = hash(target, &deps, rustflags, &ctoml, meta)?; + let hash = hash(cmode, &deps, rustflags, &ctoml, meta)?; - if old_hash(target.triple(), home)? != Some(hash) { - build(target, &deps, &ctoml, home, rustflags, hash, verbose)?; + if old_hash(cmode, home)? != Some(hash) { + build(cmode, &deps, &ctoml, home, rustflags, hash, verbose)?; } // copy host artifacts into the sysroot, if necessary + if cmode.is_native() { + return Ok(()) + } + let lock = home.lock_rw(&meta.host)?; let hfile = lock.parent().join(".hash"); diff --git a/src/xargo.rs b/src/xargo.rs index 16d7c76..2d46689 100644 --- a/src/xargo.rs +++ b/src/xargo.rs @@ -5,16 +5,16 @@ use std::{env, mem}; use toml::Value; use rustc_version::VersionMeta; +use CompilationMode; use cargo::{Config, Root, Rustflags, Subcommand}; use cli::Args; use errors::*; use extensions::CommandExt; use flock::{FileLock, Filesystem}; -use rustc::Target; use {cargo, util}; pub fn run(args: &Args, - target: &Target, + cmode: &CompilationMode, rustflags: Rustflags, home: &Home, meta: &VersionMeta, @@ -26,12 +26,13 @@ pub fn run(args: &Args, if args.subcommand() == Some(Subcommand::Doc) { cmd.env("RUSTDOCFLAGS", - cargo::rustdocflags(config, target.triple())?.for_xargo(home)); + cargo::rustdocflags(config, cmode.triple())?.for_xargo(home)); } cmd.env("RUSTFLAGS", rustflags.for_xargo(home)); - let locks = (home.lock_ro(&meta.host), home.lock_ro(target.triple())); + let locks = (home.lock_ro(&meta.host), + home.lock_ro(cmode.triple())); let status = cmd.run_and_get_status(verbose)?; @@ -49,34 +50,43 @@ impl Home { self.path.display() } - pub fn lock_ro(&self, target: &str) -> Result { + fn path(&self, triple: &str) -> Filesystem { self.path .join("lib/rustlib") - .join(target) - .open_ro(".sentinel", &format!("{}'s sysroot", target)) + .join(triple) + } + + pub fn lock_ro(&self, triple: &str) -> Result { + let fs = self.path(triple); + + fs.open_ro(".sentinel", &format!("{}'s sysroot", triple)) .chain_err(|| { - format!("couldn't lock {}'s sysroot as read-only", target) + format!("couldn't lock {}'s sysroot as read-only", triple) }) } - pub fn lock_rw(&self, target: &str) -> Result { - self.path - .join("lib/rustlib") - .join(target) - .open_rw(".sentinel", &format!("{}'s sysroot", target)) + pub fn lock_rw(&self, triple: &str) -> Result { + let fs = self.path(triple); + + fs.open_rw(".sentinel", &format!("{}'s sysroot", triple)) .chain_err(|| { - format!("couldn't lock {}'s sysroot as read-write", target) + format!("couldn't lock {}'s sysroot as read-only", triple) }) } } -pub fn home() -> Result { +pub fn home(cmode: &CompilationMode) -> Result { let p = if let Some(h) = env::var_os("XARGO_HOME") { PathBuf::from(h) } else { - env::home_dir() + let mut p = env::home_dir() .ok_or_else(|| "couldn't find your home directory. Is $HOME set?")? - .join(".xargo") + .join(".xargo"); + + if cmode.is_native() { + p.push("HOST"); + } + p }; Ok(Home { path: Filesystem::new(p) }) diff --git a/tests/smoke.rs b/tests/smoke.rs index 3f6461a..19a89db 100644 --- a/tests/smoke.rs +++ b/tests/smoke.rs @@ -1,8 +1,10 @@ #![cfg_attr(not(feature = "dev"), allow(dead_code))] #![deny(warnings)] +#![feature(const_fn)] #[macro_use] extern crate error_chain; +extern crate parking_lot; extern crate rustc_version; extern crate tempdir; @@ -12,6 +14,7 @@ use std::path::{Path, PathBuf}; use std::process::Command; use std::{env, fs}; +use parking_lot::{Mutex, MutexGuard}; use tempdir::TempDir; use errors::*; @@ -193,7 +196,6 @@ impl Project { }) } - /// Calls `xargo build` fn build(&self, target: &str) -> Result<()> { xargo() @@ -250,6 +252,66 @@ impl Drop for Project { } } +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() -> Result { + // There can only be one instance of this type at any point in time + static ONCE: Mutex<()> = Mutex::new(()); + + let guard = ONCE.lock(); + + let td = + TempDir::new("xargo") + .chain_err(|| "couldn't create a temporary directory")?; + + xargo()? + .args(&["init", "--lib", "--vcs", "none", "--name", "host"]) + .current_dir(td.path()) + .run()?; + + write(&td.path().join("src/lib.rs"), false, "#![no_std]")?; + + Ok(HProject { + _guard: guard, + host: host(), + td: td, + }) + } + + /// Calls `xargo build` and collects STDERR + fn build_and_get_stderr(&self) -> Result { + let mut cmd = xargo()?; + cmd.arg("build"); + + cmd.arg("-v") + .current_dir(self.td.path()) + .run_and_get_stderr() + } + +} + +impl Drop for HProject { + fn drop(&mut self) { + hcleanup(&self.host).unwrap() + } +} + /// Test vanilla `xargo build` #[cfg(feature = "dev")] #[test] @@ -587,19 +649,40 @@ fn unchanged_specification() { run!() } -/// No sysroot should be built for the host +/// Check that a sysroot is built for the host #[cfg(feature = "dev")] #[test] -fn no_sysroot_for_host() { +fn host_once() { fn run() -> Result<()> { - const TARGET: &'static str = "__no_sysroot_for_host"; + let target = host(); + let project = HProject::new()?; - let project = Project::new(TARGET)?; + let stderr = project.build_and_get_stderr()?; + + assert!(sysroot_was_built(&stderr, &target)); + + Ok(()) + } + + run!() +} + +/// Check that the sysroot is not rebuilt when `xargo build` is called a second +/// time +#[cfg(feature = "dev")] +#[test] +fn host_twice() { + fn run() -> Result<()> { + let target = host(); + let project = HProject::new()?; + + let stderr = project.build_and_get_stderr()?; + + assert!(sysroot_was_built(&stderr, &target)); - let host = host(); - let stderr = project.build_and_get_stderr(Some(&host))?; + let stderr = project.build_and_get_stderr()?; - assert!(!sysroot_was_built(&stderr, &host)); + assert!(!sysroot_was_built(&stderr, &target)); Ok(()) }