rust/src/librustpkg/package_source.rs
2013-12-29 15:25:37 -05:00

526 lines
22 KiB
Rust

// Copyright 2012-2013 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.
extern mod extra;
use target::*;
use crate_id::CrateId;
use std::io;
use std::io::fs;
use std::os;
use context::*;
use crate::Crate;
use messages::*;
use source_control::{safe_git_clone, git_clone_url, DirToUse, CheckedOutSources};
use source_control::make_read_only;
use path_util::{find_dir_using_rust_path_hack, make_dir_rwx_recursive, default_workspace};
use path_util::{target_build_dir, versionize, dir_has_crate_file};
use util::{compile_crate, DepMap};
use workcache_support;
use workcache_support::{digest_only_date, digest_file_with_date, crate_tag};
use extra::workcache;
use extra::treemap::TreeMap;
use rustc::driver::session;
// An enumeration of the unpacked source of a package workspace.
// This contains a list of files found in the source workspace.
#[deriving(Clone)]
pub struct PkgSrc {
/// Root of where the package source code lives
source_workspace: Path,
/// If build_in_destination is true, temporary results should
/// go in the build/ subdirectory of the destination workspace.
/// (Otherwise, they go in the build/ subdirectory of the
/// source workspace.) This happens if the "RUST_PATH hack" is
/// in effect, or if sources were fetched from a remote
/// repository.
build_in_destination: bool,
/// Where to install the results. May or may not be the same
/// as source_workspace
destination_workspace: Path,
// Directory to start looking in for packages -- normally
// this is workspace/src/id but it may be just workspace
start_dir: Path,
id: CrateId,
libs: ~[Crate],
mains: ~[Crate],
tests: ~[Crate],
benchs: ~[Crate],
}
pub enum BuildSort { InPlace, Discovered }
impl ToStr for PkgSrc {
fn to_str(&self) -> ~str {
format!("Package ID {} in start dir {} [workspaces = {} -> {}]",
self.id.to_str(),
self.start_dir.display(),
self.source_workspace.display(),
self.destination_workspace.display())
}
}
condition! {
// #6009: should this be pub or not, when #8215 is fixed?
build_err: (~str) -> ~str;
}
impl PkgSrc {
pub fn new(mut source_workspace: Path,
destination_workspace: Path,
use_rust_path_hack: bool,
id: CrateId) -> PkgSrc {
use conditions::nonexistent_package::cond;
debug!("Checking package source for package ID {}, \
workspace = {} -> {}, use_rust_path_hack = {:?}",
id.to_str(),
source_workspace.display(),
destination_workspace.display(),
use_rust_path_hack);
let mut destination_workspace = destination_workspace.clone();
let mut to_try = ~[];
let mut output_names = ~[];
let build_dir = target_build_dir(&source_workspace);
if use_rust_path_hack {
to_try.push(source_workspace.clone());
} else {
// We search for sources under both src/ and build/ , because build/ is where
// automatically-checked-out sources go.
let mut result = source_workspace.join("src");
result.push(&id.path.dir_path());
result.push(format!("{}-{}", id.short_name, id.version.to_str()));
to_try.push(result);
let mut result = source_workspace.join("src");
result.push(&id.path);
to_try.push(result);
let mut result = build_dir.join("src");
result.push(&id.path.dir_path());
result.push(format!("{}-{}", id.short_name, id.version.to_str()));
to_try.push(result.clone());
output_names.push(result);
let mut other_result = build_dir.join("src");
other_result.push(&id.path);
to_try.push(other_result.clone());
output_names.push(other_result);
}
debug!("Checking dirs: {:?}", to_try.map(|p| p.display().to_str()).connect(":"));
let path = to_try.iter().find(|&d| d.is_dir()
&& dir_has_crate_file(d));
// See the comments on the definition of PkgSrc
let mut build_in_destination = use_rust_path_hack;
debug!("1. build_in_destination = {:?}", build_in_destination);
let dir: Path = match path {
Some(d) => (*d).clone(),
None => {
// See if any of the prefixes of this package ID form a valid package ID
// That is, is this a package ID that points into the middle of a workspace?
for (prefix, suffix) in id.prefixes() {
let crate_id = CrateId::new(prefix.as_str().unwrap());
let path = build_dir.join(&crate_id.path);
debug!("in loop: checking if {} is a directory", path.display());
if path.is_dir() {
let ps = PkgSrc::new(source_workspace,
destination_workspace,
use_rust_path_hack,
crate_id);
match ps {
PkgSrc {
source_workspace: source,
destination_workspace: destination,
start_dir: start,
id: id, .. } => {
let result = PkgSrc {
source_workspace: source.clone(),
build_in_destination: build_in_destination,
destination_workspace: destination,
start_dir: start.join(&suffix),
id: id,
libs: ~[],
mains: ~[],
tests: ~[],
benchs: ~[]
};
debug!("pkgsrc: Returning {}", result.to_str());
return result;
}
}
};
}
// Ok, no prefixes work, so try fetching from git
let mut ok_d = None;
for w in output_names.iter() {
debug!("Calling fetch_git on {}", w.display());
let target_dir_opt = PkgSrc::fetch_git(w, &id);
for p in target_dir_opt.iter() {
ok_d = Some(p.clone());
build_in_destination = true;
debug!("2. build_in_destination = {:?}", build_in_destination);
break;
}
match ok_d {
Some(ref d) => {
if d.is_ancestor_of(&id.path)
|| d.is_ancestor_of(&versionize(&id.path, &id.version)) {
// Strip off the package ID
source_workspace = d.clone();
for _ in id.path.components() {
source_workspace.pop();
}
// Strip off the src/ part
source_workspace.pop();
// Strip off the build/<target-triple> part to get the workspace
destination_workspace = source_workspace.clone();
destination_workspace.pop();
destination_workspace.pop();
}
break;
}
None => ()
}
}
match ok_d {
Some(d) => d,
None => {
// See if the sources are in $CWD
let cwd = os::getcwd();
if dir_has_crate_file(&cwd) {
return PkgSrc {
// In this case, source_workspace isn't really a workspace.
// This data structure needs yet more refactoring.
source_workspace: cwd.clone(),
destination_workspace: default_workspace(),
build_in_destination: true,
start_dir: cwd,
id: id,
libs: ~[],
mains: ~[],
benchs: ~[],
tests: ~[]
}
} else if use_rust_path_hack {
match find_dir_using_rust_path_hack(&id) {
Some(d) => d,
None => {
cond.raise((id.clone(),
~"supplied path for package dir does not \
exist, and couldn't interpret it as a URL fragment"))
}
}
}
else {
cond.raise((id.clone(),
~"supplied path for package dir does not \
exist, and couldn't interpret it as a URL fragment"))
}
}
}
}
};
debug!("3. build_in_destination = {:?}", build_in_destination);
debug!("source: {} dest: {}", source_workspace.display(), destination_workspace.display());
debug!("For package id {}, returning {}", id.to_str(), dir.display());
if !dir.is_dir() {
cond.raise((id.clone(), ~"supplied path for package dir is a \
non-directory"));
}
PkgSrc {
source_workspace: source_workspace.clone(),
build_in_destination: build_in_destination,
destination_workspace: destination_workspace,
start_dir: dir,
id: id,
libs: ~[],
mains: ~[],
tests: ~[],
benchs: ~[]
}
}
/// Try interpreting self's package id as a git repository, and try
/// fetching it and caching it in a local directory. Return the cached directory
/// if this was successful, None otherwise. Similarly, if the package id
/// refers to a git repo on the local version, also check it out.
/// (right now we only support git)
pub fn fetch_git(local: &Path, crateid: &CrateId) -> Option<Path> {
use conditions::git_checkout_failed::cond;
let cwd = os::getcwd();
debug!("Checking whether {} (path = {}) exists locally. Cwd = {}, does it? {:?}",
crateid.to_str(), crateid.path.display(),
cwd.display(),
crateid.path.exists());
match safe_git_clone(&crateid.path, &crateid.version, local) {
CheckedOutSources => {
make_read_only(local);
Some(local.clone())
}
DirToUse(clone_target) => {
if crateid.path.components().nth(1).is_none() {
// If a non-URL, don't bother trying to fetch
return None;
}
// FIXME (#9639): This needs to handle non-utf8 paths
let url = format!("https://{}", crateid.path.as_str().unwrap());
debug!("Fetching package: git clone {} {} [version={}]",
url, clone_target.display(), crateid.version.to_str());
let mut failed = false;
cond.trap(|_| {
failed = true;
}).inside(|| git_clone_url(url, &clone_target, &crateid.version));
if failed {
return None;
}
// Move clone_target to local.
// First, create all ancestor directories.
let moved = make_dir_rwx_recursive(&local.dir_path())
&& io::result(|| fs::rename(&clone_target, local)).is_ok();
if moved { Some(local.clone()) }
else { None }
}
}
}
// If a file named "pkg.rs" in the start directory exists,
// return the path for it. Otherwise, None
pub fn package_script_option(&self) -> Option<Path> {
let maybe_path = self.start_dir.join("pkg.rs");
debug!("package_script_option: checking whether {} exists", maybe_path.display());
if maybe_path.exists() {
Some(maybe_path)
} else {
None
}
}
pub fn push_crate(cs: &mut ~[Crate], prefix: uint, p: &Path) {
let mut it = p.components().peekable();
if prefix > 0 {
it.nth(prefix-1); // skip elements
}
assert!(it.peek().is_some());
let mut sub = Path::new(".");
for c in it {
sub.push(c);
}
debug!("Will compile crate {}", sub.display());
cs.push(Crate::new(&sub));
}
/// Infers crates to build. Called only in the case where there
/// is no custom build logic
pub fn find_crates(&mut self) {
self.find_crates_with_filter(|_| true);
}
pub fn find_crates_with_filter(&mut self, filter: |&str| -> bool) {
use conditions::missing_pkg_files::cond;
let prefix = self.start_dir.components().len();
debug!("Matching against {}", self.id.short_name);
for pth in fs::walk_dir(&self.start_dir) {
let maybe_known_crate_set = match pth.filename_str() {
Some(filename) if filter(filename) => match filename {
"lib.rs" => Some(&mut self.libs),
"main.rs" => Some(&mut self.mains),
"test.rs" => Some(&mut self.tests),
"bench.rs" => Some(&mut self.benchs),
_ => None
},
_ => None
};
match maybe_known_crate_set {
Some(crate_set) => PkgSrc::push_crate(crate_set, prefix, &pth),
None => ()
}
}
let crate_sets = [&self.libs, &self.mains, &self.tests, &self.benchs];
if crate_sets.iter().all(|crate_set| crate_set.is_empty()) {
note("Couldn't infer any crates to build.\n\
Try naming a crate `main.rs`, `lib.rs`, \
`test.rs`, or `bench.rs`.");
cond.raise(self.id.clone());
}
debug!("In {}, found {} libs, {} mains, {} tests, {} benchs",
self.start_dir.display(),
self.libs.len(),
self.mains.len(),
self.tests.len(),
self.benchs.len())
}
fn build_crates(&self,
ctx: &BuildContext,
deps: &mut DepMap,
crates: &[Crate],
cfgs: &[~str],
what: OutputType,
inputs_to_discover: &[(~str, Path)]) {
for crate in crates.iter() {
let path = self.start_dir.join(&crate.file);
debug!("build_crates: compiling {}", path.display());
let cfgs = crate.cfgs + cfgs;
ctx.workcache_context.with_prep(crate_tag(&path), |prep| {
debug!("Building crate {}, declaring it as an input", path.display());
// FIXME (#9639): This needs to handle non-utf8 paths
prep.declare_input("file", path.as_str().unwrap(),
workcache_support::digest_file_with_date(&path));
let subpath = path.clone();
let subcfgs = cfgs.clone();
let subcx = ctx.clone();
let id = self.id.clone();
let sub_dir = self.build_workspace().clone();
let sub_flags = crate.flags.clone();
let sub_deps = deps.clone();
let inputs = inputs_to_discover.map(|&(ref k, ref p)|
(k.clone(), p.as_str().unwrap().to_owned()));
prep.exec(proc(exec) {
for &(ref kind, ref p) in inputs.iter() {
let pth = Path::new(p.clone());
exec.discover_input(*kind, *p, if *kind == ~"file" {
digest_file_with_date(&pth)
} else if *kind == ~"binary" {
digest_only_date(&Path::new(p.clone()))
} else {
fail!("Bad kind in build_crates")
});
}
debug!("Compiling crate {}; its output will be in {}",
subpath.display(), sub_dir.display());
let opt: session::OptLevel = subcx.context.rustc_flags.optimization_level;
let result = compile_crate(&subcx,
exec,
&id,
&subpath,
&sub_dir,
&mut (sub_deps.clone()),
sub_flags,
subcfgs,
opt,
what);
// XXX: result is an Option<Path>. The following code did not take that
// into account. I'm not sure if the workcache really likes seeing the
// output as "Some(\"path\")". But I don't know what to do about it.
// FIXME (#9639): This needs to handle non-utf8 paths
let result = result.as_ref().map(|p|p.as_str().unwrap());
debug!("Result of compiling {} was {}", subpath.display(), result.to_str());
result.to_str()
})
});
}
}
/// Declare all the crate files in the package source as inputs
/// (to the package)
pub fn declare_inputs(&self, prep: &mut workcache::Prep) {
let to_do = ~[self.libs.clone(), self.mains.clone(),
self.tests.clone(), self.benchs.clone()];
debug!("In declare inputs, self = {}", self.to_str());
for cs in to_do.iter() {
for c in cs.iter() {
let path = self.start_dir.join(&c.file);
debug!("Declaring input: {}", path.display());
// FIXME (#9639): This needs to handle non-utf8 paths
prep.declare_input("file", path.as_str().unwrap(),
workcache_support::digest_file_with_date(&path.clone()));
}
}
}
pub fn build(&self,
build_context: &BuildContext,
// DepMap is a map from str (crate name) to (kind, name) --
// it tracks discovered dependencies per-crate
cfgs: ~[~str],
inputs_to_discover: &[(~str, Path)]) -> DepMap {
let mut deps = TreeMap::new();
let libs = self.libs.clone();
let mains = self.mains.clone();
let tests = self.tests.clone();
let benchs = self.benchs.clone();
debug!("Building libs in {}, destination = {}",
self.source_workspace.display(),
self.build_workspace().display());
self.build_crates(build_context,
&mut deps,
libs,
cfgs,
Lib,
inputs_to_discover);
debug!("Building mains");
self.build_crates(build_context,
&mut deps,
mains,
cfgs,
Main,
inputs_to_discover);
debug!("Building tests");
self.build_crates(build_context,
&mut deps,
tests,
cfgs,
Test,
inputs_to_discover);
debug!("Building benches");
self.build_crates(build_context,
&mut deps,
benchs,
cfgs,
Bench,
inputs_to_discover);
deps
}
/// Return the workspace to put temporary files in. See the comment on `PkgSrc`
pub fn build_workspace<'a>(&'a self) -> &'a Path {
if self.build_in_destination {
&self.destination_workspace
}
else {
&self.source_workspace
}
}
/// Debugging
pub fn dump_crates(&self) {
let crate_sets = [&self.libs, &self.mains, &self.tests, &self.benchs];
for crate_set in crate_sets.iter() {
for c in crate_set.iter() {
debug!("Built crate: {}", c.file.display())
}
}
}
}