rustpkg: Implement install command

The install command should work now, though it only installs
    in-place (anything else has to wait until I implement RUST_PATH).

Also including:
    core: Add remove_directory_recursive, change copy_file

    Make copy_file preserve permissions, and add a remove_directory_recursive
    function.
This commit is contained in:
Tim Chevalier 2013-05-02 13:09:28 -07:00
parent bfd3cd8171
commit 4d4cabff9e
7 changed files with 328 additions and 85 deletions

View File

@ -772,6 +772,28 @@ pub fn list_dir_path(p: &Path) -> ~[~Path] {
list_dir(p).map(|f| ~p.push(*f))
}
/// Removes a directory at the specified path, after removing
/// all its contents. Use carefully!
pub fn remove_dir_recursive(p: &Path) -> bool {
let mut error_happened = false;
for walk_dir(p) |inner| {
if !error_happened {
if path_is_dir(inner) {
if !remove_dir_recursive(inner) {
error_happened = true;
}
}
else {
if !remove_file(inner) {
error_happened = true;
}
}
}
};
// Directory should now be empty
!error_happened && remove_dir(p)
}
/// Removes a directory at the specified path
pub fn remove_dir(p: &Path) -> bool {
return rmdir(p);
@ -877,6 +899,10 @@ pub fn copy_file(from: &Path, to: &Path) -> bool {
if istream as uint == 0u {
return false;
}
// Preserve permissions
let from_mode = from.get_mode().expect("copy_file: couldn't get permissions \
for source file");
let ostream = do as_c_charp(to.to_str()) |top| {
do as_c_charp("w+b") |modebuf| {
libc::fopen(top, modebuf)
@ -908,6 +934,15 @@ pub fn copy_file(from: &Path, to: &Path) -> bool {
}
fclose(istream);
fclose(ostream);
// Give the new file the old file's permissions
unsafe {
if do str::as_c_str(to.to_str()) |to_buf| {
libc::chmod(to_buf, from_mode as mode_t)
} != 0 {
return false; // should be a condition...
}
}
return ok;
}
}
@ -1594,6 +1629,7 @@ mod tests {
== buf.len() as size_t))
}
assert!((libc::fclose(ostream) == (0u as c_int)));
let in_mode = in.get_mode();
let rs = os::copy_file(&in, &out);
if (!os::path_exists(&in)) {
fail!(fmt!("%s doesn't exist", in.to_str()));
@ -1601,6 +1637,7 @@ mod tests {
assert!((rs));
let rslt = run::run_program(~"diff", ~[in.to_str(), out.to_str()]);
assert!((rslt == 0));
assert!(out.get_mode() == in_mode);
assert!((remove_file(&in)));
assert!((remove_file(&out)));
}

View File

@ -18,5 +18,13 @@ condition! {
}
condition! {
nonexistent_package: (super::PkgId, ~str) -> super::Path;
nonexistent_package: (super::PkgId, ~str) -> ();
}
condition! {
copy_failed: (super::Path, super::Path) -> ();
}
condition! {
missing_pkg_files: (super::PkgId) -> ();
}

View File

@ -12,6 +12,7 @@
use util::PkgId;
use core::libc::consts::os::posix88::{S_IRUSR, S_IWUSR, S_IXUSR};
use core::os::mkdir_recursive;
#[deriving(Eq)]
pub enum OutputType { Main, Lib, Bench, Test }
@ -23,7 +24,7 @@ pub fn rust_path() -> ~[Path] {
~[Path(".")]
}
static u_rwx: i32 = (S_IRUSR | S_IWUSR | S_IXUSR) as i32;
pub static u_rwx: i32 = (S_IRUSR | S_IWUSR | S_IXUSR) as i32;
/// Creates a directory that is readable, writeable,
/// and executable by the user. Returns true iff creation
@ -70,34 +71,137 @@ pub fn pkgid_src_in_workspace(pkgid: PkgId, workspace: &Path) -> Path {
result.push(pkgid.path.to_str())
}
/// Figure out what the executable name for <pkgid> in <workspace>'s build
/// directory is, and if the file exists, return it.
pub fn built_executable_in_workspace(pkgid: PkgId, workspace: &Path) -> Option<Path> {
let mut result = workspace.push("build");
result = result.push_rel(&pkgid.path);
// should use a target-specific subdirectory
result = mk_output_path(Main, fmt!("%s-%s", pkgid.path.to_str(), pkgid.version.to_str()),
result);
debug!("built_executable_in_workspace: checking whether %s exists",
result.to_str());
if os::path_exists(&result) {
Some(result)
}
else {
None
}
}
/// Figure out what the library name for <pkgid> in <workspace>'s build
/// directory is, and if the file exists, return it.
pub fn built_library_in_workspace(pkgid: PkgId, workspace: &Path) -> Option<Path> {
let mut result = workspace.push("build");
result = result.push_rel(&pkgid.path);
// should use a target-specific subdirectory
result = mk_output_path(Lib, pkgid.path.to_str(), result);
debug!("built_library_in_workspace: checking whether %s exists",
result.to_str());
// We don't know what the hash is, so we have to search through the directory
// contents
let dir_contents = os::list_dir(&result.pop());
debug!("dir has %? entries", dir_contents.len());
// n.b. This code assumes the pkgid's path only has one element
let lib_prefix = fmt!("%s%s", os::consts::DLL_PREFIX, pkgid.path.to_str());
let lib_filetype = fmt!("%s%s", pkgid.version.to_str(), os::consts::DLL_SUFFIX);
debug!("lib_prefix = %s and lib_filetype = %s", lib_prefix, lib_filetype);
let mut result_filename = None;
for dir_contents.each |&p| {
let mut which = 0;
let mut hash = None;
// Find a filename that matches the pattern: (lib_prefix)-hash-(version)(lib_suffix)
// and remember what the hash was
for p.each_split_char('-') |piece| {
debug!("a piece = %s", piece);
if which == 0 && piece != lib_prefix {
break;
}
else if which == 0 {
which += 1;
}
else if which == 1 {
hash = Some(piece.to_owned());
which += 1;
}
else if which == 2 && piece != lib_filetype {
hash = None;
break;
}
else if which == 2 {
break;
}
else {
// something went wrong
hash = None;
break;
}
}
if hash.is_some() {
result_filename = Some(p);
break;
}
}
// Return the filename that matches, which we now know exists
// (if result_filename != None)
debug!("result_filename = %?", result_filename);
match result_filename {
None => None,
Some(result_filename) => {
let result_filename = result.with_filename(result_filename);
debug!("result_filename = %s", result_filename.to_str());
Some(result_filename)
}
}
}
/// Returns the executable that would be installed for <pkgid>
/// in <workspace>
/// As a side effect, creates the bin-dir if it doesn't exist
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)
target_file_in_workspace(pkgid, workspace, Main)
}
/// Returns the executable that would be installed for <pkgid>
/// in <workspace>
/// As a side effect, creates the bin-dir if it doesn't exist
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)
target_file_in_workspace(pkgid, workspace, Lib)
}
/// 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)
target_file_in_workspace(pkgid, workspace, Test)
}
/// 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)
target_file_in_workspace(pkgid, workspace, Bench)
}
fn target_file_in_workspace(pkgid: PkgId, workspace: &Path,
what: OutputType) -> Path {
use conditions::bad_path::cond;
let (subdir, create_dir) = match what {
Main => ("bin", true), Lib => ("lib", true), Test | Bench => ("build", false)
};
let result = workspace.push(subdir);
if create_dir {
if !os::path_exists(&result) && !mkdir_recursive(&result, u_rwx) {
cond.raise((result, fmt!("I couldn't create the %s dir", subdir)));
}
}
mk_output_path(what, pkgid.path.to_str(), result)
}
/// Return the directory for <pkgid>'s build artifacts in <workspace>.
@ -123,7 +227,11 @@ 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 { ~"" },
match what {
Test => "test",
Bench => "bench",
_ => ""
}
os::EXE_SUFFIX))
}
}

View File

@ -34,6 +34,8 @@ use syntax::{ast, diagnostic};
use util::*;
use path_util::normalize;
use path_util::{build_pkg_id_in_workspace, pkgid_src_in_workspace};
use path_util::{built_executable_in_workspace, built_library_in_workspace};
use path_util::{target_executable_in_workspace, target_library_in_workspace};
use workspace::pkg_parent_workspaces;
use rustc::driver::session::{lib_crate, bin_crate, crate_type};
use context::Ctx;
@ -188,49 +190,7 @@ impl Ctx {
// argument
let pkgid = PkgId::new(args[0]);
for pkg_parent_workspaces(pkgid) |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(&workspace.push("src"), &build_dir, &pkgid);
debug!("Package src = %?", src);
// 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
}
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(&build_dir, cfgs);
}
self.build(workspace, pkgid);
}
}
~"clean" => {
@ -304,6 +264,53 @@ impl Ctx {
fail!(~"`do` not yet implemented");
}
fn build(&self, workspace: &Path, pkgid: PkgId) {
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(&workspace.push("src"), &build_dir, &pkgid);
debug!("Package src = %?", src);
// 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
}
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(&build_dir, cfgs);
}
}
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.
@ -325,9 +332,31 @@ impl Ctx {
fail!(~"info not yet implemented");
}
fn install(&self, _workspace: &Path, _id: PkgId) {
// stub
fail!(~"install not yet implemented");
fn install(&self, workspace: &Path, id: PkgId) {
use conditions::copy_failed::cond;
// Should use RUST_PATH in the future.
// Also should use workcache to not build if not necessary.
self.build(workspace, id);
// Now copy stuff into the install dirs
let maybe_executable = built_executable_in_workspace(id, workspace);
let maybe_library = built_library_in_workspace(id, workspace);
let target_exec = target_executable_in_workspace(id, workspace);
let target_lib = target_library_in_workspace(id, workspace);
for maybe_executable.each |exec| {
debug!("Copying: %s -> %s", exec.to_str(), target_exec.to_str());
if !os::copy_file(exec, &target_exec) {
cond.raise((*exec, target_exec));
}
}
for maybe_library.each |lib| {
debug!("Copying: %s -> %s", lib.to_str(), target_lib.to_str());
if !os::copy_file(lib, &target_lib) {
cond.raise((*lib, target_lib));
}
}
}
fn fetch(&self, _dir: &Path, _url: ~str, _target: Option<~str>) {
@ -610,7 +639,7 @@ impl PkgSrc {
fn check_dir(&self) -> Path {
use conditions::bad_path::cond;
use conditions::nonexistent_package::cond;
debug!("Pushing onto root: %s | %s", self.id.path.to_str(),
self.root.to_str());
@ -620,12 +649,12 @@ impl PkgSrc {
debug!("Checking dir: %s", dir.to_str());
if !os::path_exists(&dir) {
return cond.raise((dir, ~"missing package dir"));
cond.raise((self.id, ~"missing package dir"));
}
if !os::path_is_dir(&dir) {
return cond.raise((dir, ~"supplied path for package dir is a \
non-directory"));
cond.raise((self.id, ~"supplied path for package dir is a \
non-directory"));
}
dir
@ -680,6 +709,7 @@ impl PkgSrc {
/// is no custom build logic
fn find_crates(&mut self) {
use PkgSrc::push_crate;
use conditions::missing_pkg_files::cond;
let dir = self.check_dir();
let prefix = dir.components.len();
@ -704,7 +734,7 @@ impl PkgSrc {
util::note(~"Couldn't infer any crates to build.\n\
Try naming a crate `main.rs`, `lib.rs`, \
`test.rs`, or `bench.rs`.");
fail!(~"Failed to infer crates to build");
cond.raise(self.id);
}
debug!("found %u libs, %u mains, %u tests, %u benchs",

View File

@ -17,7 +17,8 @@ use std::tempfile::mkdtemp;
use util::{PkgId, default_version};
use path_util::{target_executable_in_workspace, target_library_in_workspace,
target_test_in_workspace, target_bench_in_workspace,
make_dir_rwx};
make_dir_rwx, u_rwx};
use core::os::mkdir_recursive;
fn fake_ctxt() -> Ctx {
Ctx {
@ -33,8 +34,27 @@ fn fake_pkg() -> PkgId {
}
}
fn mk_temp_workspace() -> Path {
mkdtemp(&os::tmpdir(), "test").expect("couldn't create temp dir")
fn writeFile(file_path: &Path, contents: ~str) {
let out: @io::Writer =
result::get(&io::file_writer(file_path,
~[io::Create, io::Truncate]));
out.write_line(contents);
}
fn mk_temp_workspace(short_name: &Path) -> Path {
let workspace = mkdtemp(&os::tmpdir(), "test").expect("couldn't create temp dir");
let package_dir = workspace.push(~"src").push_rel(short_name);
assert!(mkdir_recursive(&package_dir, u_rwx));
// Create main, lib, test, and bench files
writeFile(&package_dir.push(~"main.rs"),
~"fn main() { let _x = (); }");
writeFile(&package_dir.push(~"lib.rs"),
~"pub fn f() { let _x = (); }");
writeFile(&package_dir.push(~"test.rs"),
~"#[test] pub fn f() { (); }");
writeFile(&package_dir.push(~"bench.rs"),
~"#[bench] pub fn f() { (); }");
workspace
}
fn is_rwx(p: &Path) -> bool {
@ -42,11 +62,10 @@ fn is_rwx(p: &Path) -> bool {
match p.get_mode() {
None => return false,
Some(m) => {
Some(m) =>
((m & S_IRUSR as uint) == S_IRUSR as uint
&& (m & S_IWUSR as uint) == S_IWUSR as uint
&& (m & S_IXUSR as uint) == S_IXUSR as uint)
}
}
}
@ -54,48 +73,60 @@ fn is_rwx(p: &Path) -> bool {
fn test_make_dir_rwx() {
let temp = &os::tmpdir();
let dir = temp.push(~"quux");
let _ = os::remove_dir(&dir);
assert!(!os::path_exists(&dir) ||
os::remove_dir_recursive(&dir));
debug!("Trying to make %s", dir.to_str());
assert!(make_dir_rwx(&dir));
assert!(os::path_is_dir(&dir));
assert!(is_rwx(&dir));
assert!(os::remove_dir(&dir));
assert!(os::remove_dir_recursive(&dir));
}
#[test]
#[ignore(reason = "install not yet implemented")]
fn test_install_valid() {
use rustc::metadata::filesearch;
let sysroot = filesearch::get_rustpkg_sysroot();
debug!("sysroot = %s", sysroot.get().to_str());
let ctxt = fake_ctxt();
let temp_pkg_id = fake_pkg();
let temp_workspace = mk_temp_workspace();
let temp_workspace = mk_temp_workspace(&temp_pkg_id.path);
// should have test, bench, lib, and main
ctxt.install(&temp_workspace, temp_pkg_id);
// Check that all files exist
let exec = target_executable_in_workspace(temp_pkg_id, &temp_workspace);
debug!("exec = %s", exec.to_str());
assert!(os::path_exists(&exec));
assert!(is_rwx(&exec));
let lib = target_library_in_workspace(temp_pkg_id, &temp_workspace);
debug!("lib = %s", lib.to_str());
assert!(os::path_exists(&lib));
assert!(is_rwx(&lib));
// And that the test and bench executables aren't installed
assert!(!os::path_exists(&target_test_in_workspace(temp_pkg_id, &temp_workspace)));
assert!(!os::path_exists(&target_bench_in_workspace(temp_pkg_id, &temp_workspace)));
let bench = target_bench_in_workspace(temp_pkg_id, &temp_workspace);
debug!("bench = %s", bench.to_str());
assert!(!os::path_exists(&bench));
}
#[test]
#[ignore(reason = "install not yet implemented")]
fn test_install_invalid() {
use conditions::nonexistent_package::cond;
use cond1 = conditions::missing_pkg_files::cond;
let ctxt = fake_ctxt();
let pkgid = fake_pkg();
let temp_workspace = mk_temp_workspace();
let expected_path = Path(~"quux");
let substituted: Path = do cond.trap(|_| {
expected_path
let temp_workspace = mkdtemp(&os::tmpdir(), "test").expect("couldn't create temp dir");
let mut error_occurred = false;
let mut error1_occurred = false;
do cond1.trap(|_| {
error1_occurred = true;
}).in {
ctxt.install(&temp_workspace, pkgid);
// ok
fail!(~"test_install_invalid failed, should have raised a condition");
};
assert!(substituted == expected_path);
do cond.trap(|_| {
error_occurred = true;
}).in {
ctxt.install(&temp_workspace, pkgid);
}
}
assert!(error_occurred && error1_occurred);
}

View File

@ -477,6 +477,8 @@ pub fn compile_input(sysroot: Option<Path>,
let matches = getopts(~[~"-Z", ~"time-passes"]
+ if building_library { ~[~"--lib"] }
else if test { ~[~"--test"] }
// bench?
else { ~[] }
+ flags
+ cfgs.flat_map(|&c| { ~[~"--cfg", c] }),
@ -540,9 +542,13 @@ pub fn compile_crate_from_input(input: driver::input,
let (crate, _) = driver::compile_upto(sess, cfg, &input,
driver::cu_parse, Some(outputs));
debug!("About to inject link_meta info...");
// Inject the inferred link_meta info if it's not already there
// (assumes that name and vers are the only linkage metas)
let mut crate_to_use = crate;
debug!("How many attrs? %?", attr::find_linkage_metas(crate.node.attrs).len());
if attr::find_linkage_metas(crate.node.attrs).is_empty() {
crate_to_use = add_attrs(*crate, ~[mk_attr(@dummy_spanned(meta_list(@~"link",
// change PkgId to have a <shortname> field?
@ -552,7 +558,6 @@ pub fn compile_crate_from_input(input: driver::input,
mk_string_lit(@pkg_id.version.to_str())))])))]);
}
driver::compile_rest(sess, cfg, what, Some(outputs), Some(crate_to_use));
crate_to_use
}

View File

@ -27,7 +27,8 @@ pub fn mkdtemp(tmpdir: &Path, suffix: &str) -> Option<Path> {
mod tests {
use tempfile::mkdtemp;
use tempfile;
use core::os;
#[test]
fn test_mkdtemp() {
let p = mkdtemp(&Path("."), "foobar").unwrap();
@ -89,4 +90,27 @@ mod tests {
assert!(os::path_is_dir(&path2.pop()));
});
}
// Ideally this would be in core, but needs mkdtemp
#[test]
pub fn test_rmdir_recursive_ok() {
use core::libc::consts::os::posix88::{S_IRUSR, S_IWUSR, S_IXUSR};
use core::os;
let rwx = (S_IRUSR | S_IWUSR | S_IXUSR) as i32;
let tmpdir = mkdtemp(&os::tmpdir(), "test").expect("test_rmdir_recursive_ok: \
couldn't create temp dir");
let root = tmpdir.push("foo");
debug!("making %s", root.to_str());
assert!(os::make_dir(&root, rwx));
assert!(os::make_dir(&root.push("foo"), rwx));
assert!(os::make_dir(&root.push("foo").push("bar"), rwx));
assert!(os::make_dir(&root.push("foo").push("bar").push("blat"), rwx));
assert!(os::remove_dir_recursive(&root));
assert!(!os::path_exists(&root));
assert!(!os::path_exists(&root.push("bar")));
assert!(!os::path_exists(&root.push("bar").push("blat")));
}
}