rustpkg: Make path searching work as described in the rustpkg doc

rustpkg now searches for package directories in ./src rather than
in . . I also added a rudimentary RUST_PATH that's currently
hard-wired to the current directory. rustpkg now uses src/, lib/,
and build/ directories as described in the manual.

Most of the existing test scenarios build now; the README file
(in a separate commit) explains which ones.
This commit is contained in:
Tim Chevalier 2013-04-22 17:54:54 -07:00
parent c2af1de95b
commit 6a3e26aa40
3 changed files with 190 additions and 151 deletions

View File

@ -15,39 +15,43 @@ use core::{os, str};
use core::option::*;
use util::PkgId;
/// Returns the output directory to use.
/// Right now is always the default, should
/// support changing it.
pub fn dest_dir(pkgid: PkgId) -> Path {
default_dest_dir(&pkgid.path)
#[deriving(Eq)]
pub enum OutputType { Main, Lib, Bench, Test }
/// Returns the value of RUST_PATH, as a list
/// of Paths. In general this should be read from the
/// environment; for now, it's hard-wired to just be "."
pub fn rust_path() -> ~[Path] {
~[Path(".")]
}
/// Returns the default output directory for compilation.
/// Creates that directory if it doesn't exist.
pub fn default_dest_dir(pkg_dir: &Path) -> Path {
/// Creates a directory that is readable, writeable,
/// and executable by the user. Returns true iff creation
/// succeeded.
pub fn make_dir_rwx(p: &Path) -> bool {
use core::libc::consts::os::posix88::{S_IRUSR, S_IWUSR, S_IXUSR};
use conditions::bad_path::cond;
// For now: assumes that pkg_dir exists and is relative
// to the CWD. Change this later when we do path searching.
let rslt = pkg_dir.push("build");
let is_dir = os::path_is_dir(&rslt);
if os::path_exists(&rslt) {
if is_dir {
rslt
}
else {
cond.raise((rslt, ~"Path names a file that isn't a directory"))
}
os::make_dir(p, (S_IRUSR | S_IWUSR | S_IXUSR) as i32)
}
/// Creates a directory that is readable, writeable,
/// and executable by the user. Returns true iff creation
/// succeeded. Also creates all intermediate subdirectories
/// if they don't already exist.
pub fn mkdir_recursive(p: &Path) -> bool {
if os::path_is_dir(p) {
return true;
}
let parent = p.dir_path();
debug!("mkdir_recursive: parent = %s",
parent.to_str());
if parent.to_str() == ~"."
|| parent.to_str() == ~"/" { // !!!
// No parent directories to create
os::path_is_dir(&parent) && make_dir_rwx(p)
}
else {
// Create it
if os::make_dir(&rslt, (S_IRUSR | S_IWUSR | S_IXUSR) as i32) {
rslt
}
else {
cond.raise((rslt, ~"Could not create directory"))
}
mkdir_recursive(&parent) && make_dir_rwx(p)
}
}
@ -69,34 +73,94 @@ pub fn normalize(p: ~Path) -> ~Path {
}
}
#[cfg(test)]
mod test {
use core::{os, rand};
use core::path::Path;
use path_util::*;
use core::rand::RngUtil;
// n.b. So far this only handles local workspaces
// n.b. The next three functions ignore the package version right
// now. Should fix that.
// Helper function to create a directory name that doesn't exist
pub fn mk_nonexistent(tmpdir: &Path, suffix: &str) -> Path {
let r = rand::rng();
for 1000.times {
let p = tmpdir.push(r.gen_str(16) + suffix);
if !os::path_exists(&p) {
return p;
}
}
fail!(~"Couldn't compute a non-existent path name; this is worrisome")
/// True if there's a directory in <workspace> with
/// pkgid's short name
pub fn workspace_contains_package_id(pkgid: PkgId, workspace: &Path) -> bool {
let pkgpath = workspace.push("src").push(pkgid.path.to_str());
os::path_is_dir(&pkgpath)
}
/// Return the directory for <pkgid>'s source files in <workspace>.
/// Doesn't check that it exists.
pub fn pkgid_src_in_workspace(pkgid: PkgId, workspace: &Path) -> Path {
let result = workspace.push("src");
result.push(pkgid.path.to_str())
}
/// Returns the executable that would be installed for <pkgid>
/// in <workspace>
pub fn target_executable_in_workspace(pkgid: PkgId, workspace: &Path) -> Path {
let result = workspace.push("bin");
// should use a target-specific subdirectory
mk_output_path(Main, pkgid.path.to_str(), result)
}
/// Returns the executable that would be installed for <pkgid>
/// in <workspace>
pub fn target_library_in_workspace(pkgid: PkgId, workspace: &Path) -> Path {
let result = workspace.push("lib");
mk_output_path(Lib, pkgid.path.to_str(), result)
}
/// Returns the test executable that would be installed for <pkgid>
/// in <workspace>
pub fn target_test_in_workspace(pkgid: PkgId, workspace: &Path) -> Path {
let result = workspace.push("build");
mk_output_path(Test, pkgid.path.to_str(), result)
}
/// Returns the bench executable that would be installed for <pkgid>
/// in <workspace>
pub fn target_bench_in_workspace(pkgid: PkgId, workspace: &Path) -> Path {
let result = workspace.push("build");
mk_output_path(Bench, pkgid.path.to_str(), result)
}
/// Return the directory for <pkgid>'s build artifacts in <workspace>.
/// Creates it if it doesn't exist.
pub fn build_pkg_id_in_workspace(pkgid: PkgId, workspace: &Path) -> Path {
use conditions::bad_path::cond;
let mut result = workspace.push("build");
// n.b. Should actually use a target-specific
// subdirectory of build/
result = result.push(normalize(~pkgid.path).to_str());
if os::path_exists(&result) || mkdir_recursive(&result) {
result
}
#[test]
fn default_dir_ok() {
let the_path = os::tmpdir();
let substitute_path = Path("xyzzy");
assert!(default_dest_dir(&the_path) == the_path.push(~"build"));
let nonexistent_path = mk_nonexistent(&the_path, "quux");
let bogus = do ::conditions::bad_path::cond.trap(|_| {
substitute_path
}).in { default_dest_dir(&nonexistent_path) };
assert!(bogus == substitute_path);
else {
cond.raise((result, fmt!("Could not create directory for package %s", pkgid.to_str())))
}
}
/// Return the output file for a given directory name,
/// given whether we're building a library and whether we're building tests
pub fn mk_output_path(what: OutputType, short_name: ~str, dir: Path) -> Path {
match what {
Lib => dir.push(os::dll_filename(short_name)),
_ => dir.push(fmt!("%s%s%s", short_name,
if what == Test { ~"test" } else { ~"" },
os::EXE_SUFFIX))
}
}
#[cfg(test)]
mod test {
use core::os;
#[test]
fn recursive_mkdir_ok() {
let root = os::tmpdir();
let path = "xy/z/zy";
let nested = root.push(path);
assert!(super::mkdir_recursive(&nested));
assert!(os::path_is_dir(&root.push("xy")));
assert!(os::path_is_dir(&root.push("xy/z")));
assert!(os::path_is_dir(&nested));
}
}

View File

@ -36,7 +36,8 @@ use rustc::metadata::filesearch;
use std::{getopts};
use syntax::{ast, diagnostic};
use util::*;
use path_util::{dest_dir, normalize};
use path_util::{normalize, workspace_contains_package_id};
use path_util::{build_pkg_id_in_workspace, pkgid_src_in_workspace, rust_path};
use rustc::driver::session::{lib_crate, bin_crate, crate_type};
mod conditions;
@ -70,7 +71,7 @@ impl PkgScript {
/// Given the path name for a package script
/// and a package ID, parse the package script into
/// a PkgScript that we can then execute
fn parse(script: Path, id: PkgId) -> PkgScript {
fn parse(script: Path, workspace: &Path, id: PkgId) -> PkgScript {
// Get the executable name that was invoked
let binary = os::args()[0];
// Build the rustc session data structures to pass
@ -85,7 +86,7 @@ impl PkgScript {
let cfg = driver::build_configuration(sess, @binary, &input);
let (crate, _) = driver::compile_upto(sess, cfg, &input,
driver::cu_parse, None);
let work_dir = dest_dir(id);
let work_dir = build_pkg_id_in_workspace(id, workspace);
debug!("Returning package script with id %?", id);
@ -193,54 +194,60 @@ impl Ctx {
// The package id is presumed to be the first command-line
// argument
let pkgid = PkgId::new(args[0]);
// Should allow the build directory to be configured.
// Right now it's always the "build" subdirectory in
// the package directory
let dst_dir = dest_dir(pkgid);
debug!("Destination dir = %s", dst_dir.to_str());
// Right now, we assume the pkgid path is a valid dir
// relative to the CWD. In the future, we should search
// paths
let cwd = os::getcwd().normalize();
debug!("Current working directory = %s", cwd.to_str());
// Using the RUST_PATH, find workspaces that contain
// this package ID
let workspaces = rust_path().filtered(|ws|
workspace_contains_package_id(pkgid, ws));
if workspaces.is_empty() {
fail!(fmt!("Package %s not found in any of \
the following workspaces: %s",
pkgid.path.to_str(),
rust_path().to_str()));
}
for workspaces.each |workspace| {
let src_dir = pkgid_src_in_workspace(pkgid, workspace);
let build_dir = build_pkg_id_in_workspace(pkgid, workspace);
debug!("Destination dir = %s", build_dir.to_str());
// Create the package source
let mut src = PkgSrc::new(&cwd, &dst_dir, &pkgid);
debug!("Package src = %?", src);
// Create the package source
let mut src = PkgSrc::new(&workspace.push("src"), &build_dir, &pkgid);
debug!("Package src = %?", src);
// Is there custom build logic? If so, use it
let pkg_src_dir = cwd.push_rel(&pkgid.path);
let mut custom = false;;
debug!("Package source directory = %s", pkg_src_dir.to_str());
let cfgs = match src.package_script_option(&pkg_src_dir) {
Some(package_script_path) => {
let pscript = PkgScript::parse(package_script_path,
pkgid);
// Limited right now -- we're only running the post_build
// hook and probably fail otherwise
// also post_build should be called pre_build
let (cfgs, hook_result) = pscript.run_custom(~"post_build");
debug!("Command return code = %?", hook_result);
if hook_result != 0 {
fail!(fmt!("Error running custom build command"))
// Is there custom build logic? If so, use it
let pkg_src_dir = src_dir;
let mut custom = false;
debug!("Package source directory = %s", pkg_src_dir.to_str());
let cfgs = match src.package_script_option(&pkg_src_dir) {
Some(package_script_path) => {
let pscript = PkgScript::parse(package_script_path,
workspace,
pkgid);
// Limited right now -- we're only running the post_build
// hook and probably fail otherwise
// also post_build should be called pre_build
let (cfgs, hook_result) = pscript.run_custom(~"post_build");
debug!("Command return code = %?", hook_result);
if hook_result != 0 {
fail!(fmt!("Error running custom build command"))
}
custom = true;
// otherwise, the package script succeeded
cfgs
}
custom = true;
// otherwise, the package script succeeded
cfgs
}
None => {
debug!("No package script, continuing");
~[]
}
};
None => {
debug!("No package script, continuing");
~[]
}
};
// If there was a package script, it should have finished
// the build already. Otherwise...
if !custom {
// Find crates inside the workspace
src.find_crates();
// Build it!
src.build(&dst_dir, cfgs);
// If there was a package script, it should have finished
// the build already. Otherwise...
if !custom {
// Find crates inside the workspace
src.find_crates();
// Build it!
src.build(&build_dir, cfgs);
}
}
}
~"clean" => {
@ -250,8 +257,8 @@ impl Ctx {
// The package id is presumed to be the first command-line
// argument
let pkgid = PkgId::new(args[0]);
self.clean(pkgid);
let cwd = os::getcwd();
self.clean(&cwd, pkgid); // tjc: should use workspace, not cwd
}
~"do" => {
if args.len() < 2 {
@ -304,57 +311,16 @@ impl Ctx {
}
fn do_cmd(&self, cmd: ~str, pkgname: ~str) {
match cmd {
~"build" | ~"test" => {
util::error(~"that command cannot be manually called");
fail!(~"do_cmd");
}
_ => {}
}
let cwd = &os::getcwd();
let pkgid = PkgId::new(pkgname);
// Always use the "build" subdirectory of the package dir,
// but we should allow this to be configured
let dst_dir = dest_dir(pkgid);
let mut src = PkgSrc::new(cwd, &dst_dir, &pkgid);
match src.package_script_option(cwd) {
Some(script_path) => {
let script = PkgScript::parse(script_path, pkgid);
let (_, status) = script.run_custom(cmd); // Ignore cfgs?
if status == 42 {
util::error(~"no fns are listening for that cmd");
fail!(~"do_cmd");
}
}
None => {
util::error(fmt!("invoked `do`, but there is no package script in %s",
cwd.to_str()));
fail!(~"do_cmd");
}
}
// stub
fail!("`do` not yet implemented");
}
fn build(&self, _dir: &Path, _verbose: bool, _opt: bool,
_test: bool) -> Option<PkgScript> {
// either not needed anymore,
// or needed only when we don't have a package script. Not sure which one.
fail!();
}
fn compile(&self, _crate: &Path, _dir: &Path, _flags: ~[~str],
_cfgs: ~[~str], _opt: bool, _test: bool) {
// What's the difference between build and compile?
fail!(~"compile not yet implemented");
}
fn clean(&self, id: PkgId) {
fn clean(&self, workspace: &Path, id: PkgId) {
// Could also support a custom build hook in the pkg
// script for cleaning files rustpkg doesn't know about.
// Do something reasonable for now
let dir = dest_dir(id);
let dir = build_pkg_id_in_workspace(id, workspace);
util::note(fmt!("Cleaning package %s (removing directory %s)",
id.to_str(), dir.to_str()));
if os::path_exists(&dir) {

View File

@ -116,7 +116,14 @@ pub impl PkgId {
impl ToStr for PkgId {
fn to_str(&self) -> ~str {
// should probably use the filestem and not the whole path
fmt!("%s-%s", self.path.to_str(), self.version.to_str())
fmt!("%s-%s", self.path.to_str(),
// Replace dots with -s in the version
// this is because otherwise rustc will think
// that foo-0.1 has .1 as its extension
// (Temporary hack until I figure out how to
// get rustc to not name the object file
// foo-0.o if I pass in foo-0.1 to build_output_filenames)
str::replace(self.version.to_str(), ".", "-"))
}
}
@ -438,7 +445,9 @@ pub fn compile_input(sysroot: Option<Path>,
test: bool,
crate_type: session::crate_type) -> bool {
let short_name = pkg_id.to_str();
// Want just the directory component here
let pkg_filename = pkg_id.path.filename().expect(~"Weird pkg id");
let short_name = fmt!("%s-%s", pkg_filename, pkg_id.version.to_str());
assert!(in_file.components.len() > 1);
let input = driver::file_input(copy *in_file);
@ -515,7 +524,7 @@ pub fn compile_crate_from_input(input: driver::input,
out_file: Path,
binary: ~str,
what: driver::compile_upto) -> @ast::crate {
debug!("Calling build_output_filenames with %?", build_dir_opt);
debug!("Calling build_output_filenames with %? and %s", build_dir_opt, out_file.to_str());
let outputs = driver::build_output_filenames(&input, &build_dir_opt, &Some(out_file), sess);
debug!("Outputs are %? and output type = %?", outputs, sess.opts.output_type);
let cfg = driver::build_configuration(sess, @binary, &input);