use std::collections::HashMap;
use std::collections::hash_map::Entry::*;
use std::io::Result as IoResult;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering::Relaxed;
use std::time::SystemTime;
use std::ffi::{OsString, OsStr};
use std::fs::Metadata;
use std::path::{Path, PathBuf};
use std::mem::replace;
use var_os_or;
quick_error! {
#[derive(Debug)]
pub enum RecipientsError {
EnvError(name: &'static str) {
description("environment variable error")
display("Undefined environment variable '{}'", name)
}
InvalidOutDir(out_dir: OsString) {
description("could not find deps dir from OUT_DIR")
display("Could not find deps from using '{}'", Path::new(&out_dir).display())
}
}
}
struct Address {
file_name: OsString,
last_modified: IoResult<SystemTime>,
is_watched: AtomicBool,
}
impl Address {
fn new(file_name: OsString, metadata: IoResult<Metadata>) -> Self {
Address {
file_name,
last_modified: metadata.and_then(|v| v.modified()),
is_watched: AtomicBool::new(false),
}
}
fn is_newer(&self, other: &Self) -> bool {
match (self.last_modified.as_ref(), other.last_modified.as_ref()) {
(Ok(a), Ok(b)) => a >= b,
(_, Err(_)) => true,
_ => false,
}
}
fn watch(&self) -> bool {
!self.is_watched.load(Relaxed) && !self.is_watched.swap(true, Relaxed)
}
}
struct Addresses {
most_recent: Address,
previous: Vec<OsString>,
}
impl Addresses {
fn new(most_recent: Address) -> Self {
Addresses {
most_recent,
previous: Vec::new(),
}
}
}
pub struct Recipients {
deps_dir: PathBuf,
relative_deps_dir: Option<PathBuf>,
addresses: HashMap<String, Addresses>,
}
impl Recipients {
pub fn new() -> Result<Self, RecipientsError> {
let out_dir = var_os_or("OUT_DIR", RecipientsError::EnvError)?;
let manifest_dir = var_os_or("CARGO_MANIFEST_DIR", RecipientsError::EnvError)?;
Self::with_env(&out_dir, &manifest_dir)
}
fn get_deps_dir<S>(out_dir: &S) -> Result<PathBuf, RecipientsError>
where
S: ?Sized + AsRef<OsStr>,
{
let out_dir = out_dir.as_ref();
match Path::new(out_dir)
.parent()
.and_then(Path::parent)
.and_then(Path::parent) {
None => Err(RecipientsError::InvalidOutDir(out_dir.to_owned())),
Some(d) => Ok(d.join(Path::new("deps"))),
}
}
pub(super) fn with_env<S, T>(out_dir: &S, manifest_dir: &T) -> Result<Self, RecipientsError>
where
S: ?Sized + AsRef<OsStr>,
T: ?Sized + AsRef<OsStr>,
{
let manifest_dir = manifest_dir.as_ref();
let deps_dir = Self::get_deps_dir(out_dir.as_ref())?;
Ok(Self::with_path(deps_dir, manifest_dir))
}
fn with_path<P>(deps_dir: PathBuf, manifest_dir: &P) -> Self
where
P: ?Sized + AsRef<Path>,
{
let relative_deps_dir = deps_dir.strip_prefix(manifest_dir).ok().map(PathBuf::from);
let mut addresses = HashMap::new();
for file in deps_dir.read_dir().unwrap() {
let file = file.unwrap();
let file_name = file.file_name();
let utf_file_name: String = {
let utf_file_name = if let Some(file_name) = file_name.to_str() {
file_name
} else {
continue;
};
let utf_file_name =
if let Some(file_name) = utf_file_name.splitn(2, "lib").nth(1) {
file_name
} else {
continue;
};
if !utf_file_name.ends_with(".rlib") {
continue;
}
let utf_file_name = if let Some(file_name) = utf_file_name.splitn(2, '-').nth(0) {
file_name
} else {
continue;
};
utf_file_name.into()
};
let info = Address::new(file_name, file.metadata());
match addresses.entry(utf_file_name.clone()) {
Vacant(entry) => {
entry.insert(Addresses::new(info));
}
Occupied(mut entry) => {
let entry = entry.get_mut();
let old = if entry.most_recent.is_newer(&info) {
info
} else {
replace(&mut entry.most_recent, info)
};
entry.previous.push(old.file_name);
}
}
}
Recipients {
deps_dir,
relative_deps_dir,
addresses,
}
}
pub(super) fn get(&self, name: &str) -> Option<PathBuf> {
let name = name.replace('-', "_");
self.addresses.get(&name).map(|address| {
if !address.previous.is_empty() {
println!(
"cargo:warning=duplicate entries for {}, using '{}', ignoring '{:?}'",
name,
Path::new(&address.most_recent.file_name).display(),
&address.previous,
);
}
let file_name = Path::new(&address.most_recent.file_name);
let dest = self.deps_dir.join(file_name);
if address.most_recent.watch() {
let rel_dest = self.relative_deps_dir.as_ref().map(
|dir| dir.join(file_name),
);
let dest = rel_dest.as_ref().unwrap_or(&dest);
println!("cargo:rerun-if-changed={}", dest.display());
}
dest
})
}
}
#[cfg(test)]
mod test {
use std::fs::{create_dir_all, File};
use std::path::PathBuf;
use std::ffi::OsStr;
use tempdir::TempDir;
use super::Recipients;
#[test]
fn check_deps_dir() {
let base_dir = PathBuf::new().join("example");
let deps_dir = base_dir.join("deps");
let out_dir = base_dir.join("build").join("example").join("out");
assert_eq!(
Recipients::get_deps_dir::<OsStr>(out_dir.as_ref()).unwrap(),
deps_dir
);
}
include!(concat!(env!("OUT_DIR"), "/recipients.rs"));
#[test]
fn verify_tmp_deps() {
let base_dir = TempDir::new("example").unwrap();
let deps_dir = base_dir.path().join("deps");
let out_dir = base_dir.path().join("build").join("example").join("out");
create_dir_all(&out_dir).unwrap();
create_dir_all(&deps_dir).unwrap();
File::create(deps_dir.join("libdhltest_dash-d15ea5e.rlib")).unwrap();
File::create(deps_dir.join("libdhltest_underscore-deadbeef.rlib")).unwrap();
File::create(deps_dir.join("libdhltest-c000l0ff.rlib")).unwrap();
let r = Recipients::with_env(&out_dir, base_dir.path()).unwrap();
r.get("dhltest").unwrap();
r.get("dhltest-dash").unwrap();
r.get("dhltest_underscore").unwrap();
base_dir.close().unwrap();
}
}