In order to run tests, previous commits have cfg'd out various parts of rustbuild. Generally speaking, these are filesystem-related operations and process-spawning related parts. Then, rustbuild is run "as normal" and the various steps that where run are retrieved from the cache and checked against the expected results. Note that this means that the current implementation primarily tests "what" we build, but doesn't actually test that what we build *will* build. In other words, it doesn't do any form of dependency verification for any crate. This is possible to implement, but is considered future work. This implementation strives to cfg out as little code as possible; it also does not currently test anywhere near all of rustbuild. The current tests are also not checked for "correctness," rather, they simply represent what we do as of this commit, which may be wrong. Test cases are drawn from the old implementation of rustbuild, though the expected results may vary.
294 lines
8.1 KiB
Rust
294 lines
8.1 KiB
Rust
// 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.
|
|
|
|
use std::any::{Any, TypeId};
|
|
use std::borrow::Borrow;
|
|
use std::cell::RefCell;
|
|
use std::collections::HashMap;
|
|
use std::convert::AsRef;
|
|
use std::ffi::OsStr;
|
|
use std::fmt;
|
|
use std::hash::{Hash, Hasher};
|
|
use std::marker::PhantomData;
|
|
use std::mem;
|
|
use std::ops::Deref;
|
|
use std::path::{Path, PathBuf};
|
|
use std::sync::Mutex;
|
|
use std::cmp::{PartialOrd, Ord, Ordering};
|
|
|
|
use builder::Step;
|
|
|
|
pub struct Interned<T>(usize, PhantomData<*const T>);
|
|
|
|
impl Default for Interned<String> {
|
|
fn default() -> Self {
|
|
INTERNER.intern_string(String::default())
|
|
}
|
|
}
|
|
|
|
impl Default for Interned<PathBuf> {
|
|
fn default() -> Self {
|
|
INTERNER.intern_path(PathBuf::default())
|
|
}
|
|
}
|
|
|
|
impl<T> Copy for Interned<T> {}
|
|
impl<T> Clone for Interned<T> {
|
|
fn clone(&self) -> Interned<T> {
|
|
*self
|
|
}
|
|
}
|
|
|
|
impl<T> PartialEq for Interned<T> {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
self.0 == other.0
|
|
}
|
|
}
|
|
impl<T> Eq for Interned<T> {}
|
|
|
|
impl PartialEq<str> for Interned<String> {
|
|
fn eq(&self, other: &str) -> bool {
|
|
*self == other
|
|
}
|
|
}
|
|
impl<'a> PartialEq<&'a str> for Interned<String> {
|
|
fn eq(&self, other: &&str) -> bool {
|
|
**self == **other
|
|
}
|
|
}
|
|
impl<'a, T> PartialEq<&'a Interned<T>> for Interned<T> {
|
|
fn eq(&self, other: &&Self) -> bool {
|
|
self.0 == other.0
|
|
}
|
|
}
|
|
impl<'a, T> PartialEq<Interned<T>> for &'a Interned<T> {
|
|
fn eq(&self, other: &Interned<T>) -> bool {
|
|
self.0 == other.0
|
|
}
|
|
}
|
|
|
|
unsafe impl<T> Send for Interned<T> {}
|
|
unsafe impl<T> Sync for Interned<T> {}
|
|
|
|
impl fmt::Display for Interned<String> {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
let s: &str = &*self;
|
|
f.write_str(s)
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for Interned<String> {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
let s: &str = &*self;
|
|
f.write_fmt(format_args!("{:?}", s))
|
|
}
|
|
}
|
|
impl fmt::Debug for Interned<PathBuf> {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
let s: &Path = &*self;
|
|
f.write_fmt(format_args!("{:?}", s))
|
|
}
|
|
}
|
|
|
|
impl Hash for Interned<String> {
|
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
let l = INTERNER.strs.lock().unwrap();
|
|
l.get(*self).hash(state)
|
|
}
|
|
}
|
|
|
|
impl Hash for Interned<PathBuf> {
|
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
let l = INTERNER.paths.lock().unwrap();
|
|
l.get(*self).hash(state)
|
|
}
|
|
}
|
|
|
|
impl Deref for Interned<String> {
|
|
type Target = str;
|
|
fn deref(&self) -> &'static str {
|
|
let l = INTERNER.strs.lock().unwrap();
|
|
unsafe { mem::transmute::<&str, &'static str>(l.get(*self)) }
|
|
}
|
|
}
|
|
|
|
impl Deref for Interned<PathBuf> {
|
|
type Target = Path;
|
|
fn deref(&self) -> &'static Path {
|
|
let l = INTERNER.paths.lock().unwrap();
|
|
unsafe { mem::transmute::<&Path, &'static Path>(l.get(*self)) }
|
|
}
|
|
}
|
|
|
|
impl AsRef<Path> for Interned<PathBuf> {
|
|
fn as_ref(&self) -> &'static Path {
|
|
let l = INTERNER.paths.lock().unwrap();
|
|
unsafe { mem::transmute::<&Path, &'static Path>(l.get(*self)) }
|
|
}
|
|
}
|
|
|
|
impl AsRef<Path> for Interned<String> {
|
|
fn as_ref(&self) -> &'static Path {
|
|
let l = INTERNER.strs.lock().unwrap();
|
|
unsafe { mem::transmute::<&Path, &'static Path>(l.get(*self).as_ref()) }
|
|
}
|
|
}
|
|
|
|
impl AsRef<OsStr> for Interned<PathBuf> {
|
|
fn as_ref(&self) -> &'static OsStr {
|
|
let l = INTERNER.paths.lock().unwrap();
|
|
unsafe { mem::transmute::<&OsStr, &'static OsStr>(l.get(*self).as_ref()) }
|
|
}
|
|
}
|
|
|
|
impl AsRef<OsStr> for Interned<String> {
|
|
fn as_ref(&self) -> &'static OsStr {
|
|
let l = INTERNER.strs.lock().unwrap();
|
|
unsafe { mem::transmute::<&OsStr, &'static OsStr>(l.get(*self).as_ref()) }
|
|
}
|
|
}
|
|
|
|
impl PartialOrd<Interned<String>> for Interned<String> {
|
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
let l = INTERNER.strs.lock().unwrap();
|
|
l.get(*self).partial_cmp(l.get(*other))
|
|
}
|
|
}
|
|
|
|
impl Ord for Interned<String> {
|
|
fn cmp(&self, other: &Self) -> Ordering {
|
|
let l = INTERNER.strs.lock().unwrap();
|
|
l.get(*self).cmp(l.get(*other))
|
|
}
|
|
}
|
|
|
|
struct TyIntern<T> {
|
|
items: Vec<T>,
|
|
set: HashMap<T, Interned<T>>,
|
|
}
|
|
|
|
impl<T: Hash + Clone + Eq> TyIntern<T> {
|
|
fn new() -> TyIntern<T> {
|
|
TyIntern {
|
|
items: Vec::new(),
|
|
set: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
fn intern_borrow<B>(&mut self, item: &B) -> Interned<T>
|
|
where
|
|
B: Eq + Hash + ToOwned<Owned=T> + ?Sized,
|
|
T: Borrow<B>,
|
|
{
|
|
if let Some(i) = self.set.get(&item) {
|
|
return *i;
|
|
}
|
|
let item = item.to_owned();
|
|
let interned = Interned(self.items.len(), PhantomData::<*const T>);
|
|
self.set.insert(item.clone(), interned);
|
|
self.items.push(item);
|
|
interned
|
|
}
|
|
|
|
fn intern(&mut self, item: T) -> Interned<T> {
|
|
if let Some(i) = self.set.get(&item) {
|
|
return *i;
|
|
}
|
|
let interned = Interned(self.items.len(), PhantomData::<*const T>);
|
|
self.set.insert(item.clone(), interned);
|
|
self.items.push(item);
|
|
interned
|
|
}
|
|
|
|
fn get(&self, i: Interned<T>) -> &T {
|
|
&self.items[i.0]
|
|
}
|
|
}
|
|
|
|
pub struct Interner {
|
|
strs: Mutex<TyIntern<String>>,
|
|
paths: Mutex<TyIntern<PathBuf>>,
|
|
}
|
|
|
|
impl Interner {
|
|
fn new() -> Interner {
|
|
Interner {
|
|
strs: Mutex::new(TyIntern::new()),
|
|
paths: Mutex::new(TyIntern::new()),
|
|
}
|
|
}
|
|
|
|
pub fn intern_str(&self, s: &str) -> Interned<String> {
|
|
self.strs.lock().unwrap().intern_borrow(s)
|
|
}
|
|
pub fn intern_string(&self, s: String) -> Interned<String> {
|
|
self.strs.lock().unwrap().intern(s)
|
|
}
|
|
|
|
pub fn intern_path(&self, s: PathBuf) -> Interned<PathBuf> {
|
|
self.paths.lock().unwrap().intern(s)
|
|
}
|
|
}
|
|
|
|
lazy_static! {
|
|
pub static ref INTERNER: Interner = Interner::new();
|
|
}
|
|
|
|
/// This is essentially a HashMap which allows storing any type in its input and
|
|
/// any type in its output. It is a write-once cache; values are never evicted,
|
|
/// which means that references to the value can safely be returned from the
|
|
/// get() method.
|
|
#[derive(Debug)]
|
|
pub struct Cache(
|
|
RefCell<HashMap<
|
|
TypeId,
|
|
Box<Any>, // actually a HashMap<Step, Interned<Step::Output>>
|
|
>>
|
|
);
|
|
|
|
impl Cache {
|
|
pub fn new() -> Cache {
|
|
Cache(RefCell::new(HashMap::new()))
|
|
}
|
|
|
|
pub fn put<S: Step>(&self, step: S, value: S::Output) {
|
|
let mut cache = self.0.borrow_mut();
|
|
let type_id = TypeId::of::<S>();
|
|
let stepcache = cache.entry(type_id)
|
|
.or_insert_with(|| Box::new(HashMap::<S, S::Output>::new()))
|
|
.downcast_mut::<HashMap<S, S::Output>>()
|
|
.expect("invalid type mapped");
|
|
assert!(!stepcache.contains_key(&step), "processing {:?} a second time", step);
|
|
stepcache.insert(step, value);
|
|
}
|
|
|
|
pub fn get<S: Step>(&self, step: &S) -> Option<S::Output> {
|
|
let mut cache = self.0.borrow_mut();
|
|
let type_id = TypeId::of::<S>();
|
|
let stepcache = cache.entry(type_id)
|
|
.or_insert_with(|| Box::new(HashMap::<S, S::Output>::new()))
|
|
.downcast_mut::<HashMap<S, S::Output>>()
|
|
.expect("invalid type mapped");
|
|
stepcache.get(step).cloned()
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub fn all<S: Ord + Copy + Step>(&mut self) -> Vec<(S, S::Output)> {
|
|
let cache = self.0.get_mut();
|
|
let type_id = TypeId::of::<S>();
|
|
let mut v = cache.remove(&type_id)
|
|
.map(|b| b.downcast::<HashMap<S, S::Output>>().expect("correct type"))
|
|
.map(|m| m.into_iter().collect::<Vec<_>>())
|
|
.unwrap_or_default();
|
|
v.sort_by_key(|&(a, _)| a);
|
|
v
|
|
}
|
|
}
|