From 4d4cabff9ede49ae3642b05c4cfb023a0a9222b2 Mon Sep 17 00:00:00 2001
From: Tim Chevalier <chevalier@alum.wellesley.edu>
Date: Thu, 2 May 2013 13:09:28 -0700
Subject: [PATCH 1/4] 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.
---
 src/libcore/os.rs            |  37 ++++++++++
 src/librustpkg/conditions.rs |  10 ++-
 src/librustpkg/path_util.rs  | 130 +++++++++++++++++++++++++++++++---
 src/librustpkg/rustpkg.rc    | 132 +++++++++++++++++++++--------------
 src/librustpkg/tests.rs      |  71 +++++++++++++------
 src/librustpkg/util.rs       |   7 +-
 src/libstd/tempfile.rs       |  26 ++++++-
 7 files changed, 328 insertions(+), 85 deletions(-)

diff --git a/src/libcore/os.rs b/src/libcore/os.rs
index c4b03d76cef..7b68e6597a1 100644
--- a/src/libcore/os.rs
+++ b/src/libcore/os.rs
@@ -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)));
         }
diff --git a/src/librustpkg/conditions.rs b/src/librustpkg/conditions.rs
index 35e70af7914..5b19a3bd660 100644
--- a/src/librustpkg/conditions.rs
+++ b/src/librustpkg/conditions.rs
@@ -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) -> ();
 }
diff --git a/src/librustpkg/path_util.rs b/src/librustpkg/path_util.rs
index 0490f066f0b..161cb75e9e5 100644
--- a/src/librustpkg/path_util.rs
+++ b/src/librustpkg/path_util.rs
@@ -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))
     }
 }
diff --git a/src/librustpkg/rustpkg.rc b/src/librustpkg/rustpkg.rc
index a296f0ca32a..cc74f464e0e 100644
--- a/src/librustpkg/rustpkg.rc
+++ b/src/librustpkg/rustpkg.rc
@@ -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",
diff --git a/src/librustpkg/tests.rs b/src/librustpkg/tests.rs
index bcee2992e5a..f38fc88f727 100644
--- a/src/librustpkg/tests.rs
+++ b/src/librustpkg/tests.rs
@@ -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);
 }
diff --git a/src/librustpkg/util.rs b/src/librustpkg/util.rs
index 28198e59f86..1b3d72bf6aa 100644
--- a/src/librustpkg/util.rs
+++ b/src/librustpkg/util.rs
@@ -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
         }
diff --git a/src/libstd/tempfile.rs b/src/libstd/tempfile.rs
index 6da74834b1a..3d4e5bb8b79 100644
--- a/src/libstd/tempfile.rs
+++ b/src/libstd/tempfile.rs
@@ -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")));
+    }
 }

From 376a5526a78ab7acf9ba143264acc0c0e2e70541 Mon Sep 17 00:00:00 2001
From: Tim Chevalier <chevalier@alum.wellesley.edu>
Date: Thu, 2 May 2013 14:14:25 -0700
Subject: [PATCH 2/4] tidy

---
 src/libstd/tempfile.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/libstd/tempfile.rs b/src/libstd/tempfile.rs
index 3d4e5bb8b79..10645e947e2 100644
--- a/src/libstd/tempfile.rs
+++ b/src/libstd/tempfile.rs
@@ -28,7 +28,7 @@ mod tests {
     use tempfile::mkdtemp;
     use tempfile;
     use core::os;
-    
+
     #[test]
     fn test_mkdtemp() {
         let p = mkdtemp(&Path("."), "foobar").unwrap();

From c42f1218a0a7b3a5c84502f9cb4b123d65148f4c Mon Sep 17 00:00:00 2001
From: Tim Chevalier <chevalier@alum.wellesley.edu>
Date: Fri, 3 May 2013 16:47:53 -0700
Subject: [PATCH 3/4] rustpkg: Handle sysroot more correctly

In rustpkg, pass around sysroot; in rustpkg tests, set the sysroot
manually so that tests can find libcore and such.

With bonus metadata::filesearch refactoring to avoid copies.
---
 src/librustc/back/rpath.rs          |  2 +-
 src/librustc/driver/driver.rs       |  2 +-
 src/librustc/driver/session.rs      |  2 +-
 src/librustc/metadata/filesearch.rs | 54 ++++++++++++++++------------
 src/librustpkg/context.rs           |  3 ++
 src/librustpkg/path_util.rs         |  6 +---
 src/librustpkg/rustpkg.rc           | 29 ++++++++-------
 src/librustpkg/tests.rs             | 55 +++++++++++++++++++++++++----
 src/librustpkg/util.rs              |  5 +--
 9 files changed, 106 insertions(+), 52 deletions(-)

diff --git a/src/librustc/back/rpath.rs b/src/librustc/back/rpath.rs
index fab19b68174..fceff55abf8 100644
--- a/src/librustc/back/rpath.rs
+++ b/src/librustc/back/rpath.rs
@@ -40,7 +40,7 @@ pub fn get_rpath_flags(sess: session::Session, out_filename: &Path)
     // where rustrt is and we know every rust program needs it
     let libs = vec::append_one(libs, get_sysroot_absolute_rt_lib(sess));
 
-    let rpaths = get_rpaths(os, &sysroot, output, libs,
+    let rpaths = get_rpaths(os, sysroot, output, libs,
                             sess.opts.target_triple);
     rpaths_to_flags(rpaths)
 }
diff --git a/src/librustc/driver/driver.rs b/src/librustc/driver/driver.rs
index 5e5d0640d80..d968cf708d9 100644
--- a/src/librustc/driver/driver.rs
+++ b/src/librustc/driver/driver.rs
@@ -603,7 +603,7 @@ pub fn build_session_options(binary: @~str,
             link::output_type_bitcode
         } else { link::output_type_exe };
     let sysroot_opt = getopts::opt_maybe_str(matches, ~"sysroot");
-    let sysroot_opt = sysroot_opt.map(|m| Path(*m));
+    let sysroot_opt = sysroot_opt.map(|m| @Path(*m));
     let target_opt = getopts::opt_maybe_str(matches, ~"target");
     let target_feature_opt = getopts::opt_maybe_str(matches, ~"target-feature");
     let save_temps = getopts::opt_present(matches, ~"save-temps");
diff --git a/src/librustc/driver/session.rs b/src/librustc/driver/session.rs
index 237b03bc20f..04cf3ca64f9 100644
--- a/src/librustc/driver/session.rs
+++ b/src/librustc/driver/session.rs
@@ -125,7 +125,7 @@ pub struct options {
     output_type: back::link::output_type,
     addl_lib_search_paths: ~[Path],
     linker_args: ~[~str],
-    maybe_sysroot: Option<Path>,
+    maybe_sysroot: Option<@Path>,
     target_triple: ~str,
     target_feature: ~str,
     // User-specified cfg meta items. The compiler itself will add additional
diff --git a/src/librustc/metadata/filesearch.rs b/src/librustc/metadata/filesearch.rs
index c88d5437c84..ded0b314d44 100644
--- a/src/librustc/metadata/filesearch.rs
+++ b/src/librustc/metadata/filesearch.rs
@@ -20,41 +20,49 @@ pub fn pick_file(file: Path, path: &Path) -> Option<Path> {
 }
 
 pub trait FileSearch {
-    fn sysroot(&self) -> Path;
-    fn lib_search_paths(&self) -> ~[Path];
+    fn sysroot(&self) -> @Path;
+    fn for_each_lib_search_path(&self, f: &fn(&Path) -> bool);
     fn get_target_lib_path(&self) -> Path;
     fn get_target_lib_file_path(&self, file: &Path) -> Path;
 }
 
-pub fn mk_filesearch(maybe_sysroot: &Option<Path>,
+pub fn mk_filesearch(maybe_sysroot: &Option<@Path>,
                      target_triple: &str,
                      addl_lib_search_paths: ~[Path])
                   -> @FileSearch {
     struct FileSearchImpl {
-        sysroot: Path,
+        sysroot: @Path,
         addl_lib_search_paths: ~[Path],
         target_triple: ~str
     }
     impl FileSearch for FileSearchImpl {
-        fn sysroot(&self) -> Path { /*bad*/copy self.sysroot }
-        fn lib_search_paths(&self) -> ~[Path] {
-            let mut paths = /*bad*/copy self.addl_lib_search_paths;
+        fn sysroot(&self) -> @Path { self.sysroot }
+        fn for_each_lib_search_path(&self, f: &fn(&Path) -> bool) {
+            debug!("filesearch: searching additional lib search paths");
+            if !self.addl_lib_search_paths.each(f) {
+                return;
+            }
 
-            paths.push(
-                make_target_lib_path(&self.sysroot,
-                                     self.target_triple));
-            match get_rustpkg_lib_path_nearest() {
-              result::Ok(ref p) => paths.push((/*bad*/copy *p)),
-              result::Err(_) => ()
+            debug!("filesearch: searching target lib path");
+            if !f(&make_target_lib_path(self.sysroot,
+                                        self.target_triple)) {
+                return;
             }
-            match get_rustpkg_lib_path() {
-              result::Ok(ref p) => paths.push((/*bad*/copy *p)),
-              result::Err(_) => ()
-            }
-            paths
+            debug!("filesearch: searching rustpkg lib path nearest");
+            if match get_rustpkg_lib_path_nearest() {
+                    result::Ok(ref p) => f(p),
+                    result::Err(_) => true
+                } {
+                    return;
+                }
+           debug!("filesearch: searching rustpkg lib path");
+           match get_rustpkg_lib_path() {
+              result::Ok(ref p) => f(p),
+              result::Err(_) => true
+           }
         }
         fn get_target_lib_path(&self) -> Path {
-            make_target_lib_path(&self.sysroot, self.target_triple)
+            make_target_lib_path(self.sysroot, self.target_triple)
         }
         fn get_target_lib_file_path(&self, file: &Path) -> Path {
             self.get_target_lib_path().push_rel(file)
@@ -72,7 +80,7 @@ pub fn mk_filesearch(maybe_sysroot: &Option<Path>,
 
 pub fn search<T:Copy>(filesearch: @FileSearch, pick: pick<T>) -> Option<T> {
     let mut rslt = None;
-    for filesearch.lib_search_paths().each |lib_search_path| {
+    for filesearch.for_each_lib_search_path() |lib_search_path| {
         debug!("searching %s", lib_search_path.to_str());
         for os::list_dir_path(lib_search_path).each |path| {
             debug!("testing %s", path.to_str());
@@ -108,10 +116,10 @@ fn get_or_default_sysroot() -> Path {
     }
 }
 
-fn get_sysroot(maybe_sysroot: &Option<Path>) -> Path {
+fn get_sysroot(maybe_sysroot: &Option<@Path>) -> @Path {
     match *maybe_sysroot {
-      option::Some(ref sr) => (/*bad*/copy *sr),
-      option::None => get_or_default_sysroot()
+      option::Some(sr) => sr,
+      option::None => @get_or_default_sysroot()
     }
 }
 
diff --git a/src/librustpkg/context.rs b/src/librustpkg/context.rs
index db036f44a18..348d828bded 100644
--- a/src/librustpkg/context.rs
+++ b/src/librustpkg/context.rs
@@ -13,6 +13,9 @@
 use core::hashmap::HashMap;
 
 pub struct Ctx {
+    // Sysroot -- if this is None, uses rustc filesearch's
+    // idea of the default
+    sysroot_opt: Option<@Path>,
     // I'm not sure what this is for
     json: bool,
     // Cache of hashes of things already installed
diff --git a/src/librustpkg/path_util.rs b/src/librustpkg/path_util.rs
index 161cb75e9e5..d21fdcda7f7 100644
--- a/src/librustpkg/path_util.rs
+++ b/src/librustpkg/path_util.rs
@@ -29,11 +29,7 @@ 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
 /// succeeded.
-pub fn make_dir_rwx(p: &Path) -> bool {
-    use core::libc::consts::os::posix88::{S_IRUSR, S_IWUSR, S_IXUSR};
-
-    os::make_dir(p, u_rwx)
-}
+pub fn make_dir_rwx(p: &Path) -> bool { os::make_dir(p, u_rwx) }
 
 /// Replace all occurrences of '-' in the stem part of path with '_'
 /// This is because we treat rust-foo-bar-quux and rust_foo_bar_quux
diff --git a/src/librustpkg/rustpkg.rc b/src/librustpkg/rustpkg.rc
index cc74f464e0e..dd5806ba015 100644
--- a/src/librustpkg/rustpkg.rc
+++ b/src/librustpkg/rustpkg.rc
@@ -306,7 +306,7 @@ impl Ctx {
             // Find crates inside the workspace
             src.find_crates();
             // Build it!
-            src.build(&build_dir, cfgs);
+            src.build(&build_dir, cfgs, self.sysroot_opt);
         }
 
     }
@@ -506,6 +506,7 @@ pub fn main() {
     }
 
     Ctx {
+        sysroot_opt: None, // Currently, only tests override this
         json: json,
         dep_cache: @mut HashMap::new()
     }.run(cmd, args);
@@ -648,6 +649,8 @@ impl PkgSrc {
 
         debug!("Checking dir: %s", dir.to_str());
 
+        // tjc: Rather than erroring out, need to try downloading the
+        // contents of the path to a local directory (#5679)
         if !os::path_exists(&dir) {
             cond.raise((self.id, ~"missing package dir"));
         }
@@ -744,18 +747,20 @@ impl PkgSrc {
                self.benchs.len())
     }
 
-    fn build_crates(&self, dst_dir: &Path,
-                           src_dir: &Path,
-                           crates: &[Crate],
-                           cfgs: ~[~str],
-                           test: bool, crate_type: crate_type) {
+    fn build_crates(&self,
+                    maybe_sysroot: Option<@Path>,
+                    dst_dir: &Path,
+                    src_dir: &Path,
+                    crates: &[Crate],
+                    cfgs: ~[~str],
+                    test: bool, crate_type: crate_type) {
 
         for crates.each |&crate| {
             let path = &src_dir.push_rel(&crate.file).normalize();
             util::note(fmt!("build_crates: compiling %s", path.to_str()));
             util::note(fmt!("build_crates: destination dir is %s", dst_dir.to_str()));
 
-            let result = util::compile_crate(None, self.id, path,
+            let result = util::compile_crate(maybe_sysroot, self.id, path,
                                      dst_dir,
                                      crate.flags,
                                      crate.cfgs + cfgs,
@@ -769,15 +774,15 @@ impl PkgSrc {
         }
     }
 
-    fn build(&self, dst_dir: &Path, cfgs: ~[~str]) {
+    fn build(&self, dst_dir: &Path, cfgs: ~[~str], maybe_sysroot: Option<@Path>) {
         let dir = self.check_dir();
         debug!("Building libs");
-        self.build_crates(dst_dir, &dir, self.libs, cfgs, false, lib_crate);
+        self.build_crates(maybe_sysroot, dst_dir, &dir, self.libs, cfgs, false, lib_crate);
         debug!("Building mains");
-        self.build_crates(dst_dir, &dir, self.mains, cfgs, false, bin_crate);
+        self.build_crates(maybe_sysroot, dst_dir, &dir, self.mains, cfgs, false, bin_crate);
         debug!("Building tests");
-        self.build_crates(dst_dir, &dir, self.tests, cfgs, true, bin_crate);
+        self.build_crates(maybe_sysroot, dst_dir, &dir, self.tests, cfgs, true, bin_crate);
         debug!("Building benches");
-        self.build_crates(dst_dir, &dir, self.benchs, cfgs, true, bin_crate);
+        self.build_crates(maybe_sysroot, dst_dir, &dir, self.benchs, cfgs, true, bin_crate);
     }
 }
diff --git a/src/librustpkg/tests.rs b/src/librustpkg/tests.rs
index f38fc88f727..486e2959e9e 100644
--- a/src/librustpkg/tests.rs
+++ b/src/librustpkg/tests.rs
@@ -20,8 +20,9 @@ use path_util::{target_executable_in_workspace, target_library_in_workspace,
                make_dir_rwx, u_rwx};
 use core::os::mkdir_recursive;
 
-fn fake_ctxt() -> Ctx {
+fn fake_ctxt(sysroot_opt: Option<@Path>) -> Ctx {
     Ctx {
+        sysroot_opt: sysroot_opt,
         json: false,
         dep_cache: @mut HashMap::new()
     }
@@ -34,6 +35,13 @@ fn fake_pkg() -> PkgId {
     }
 }
 
+fn remote_pkg() -> PkgId {
+    PkgId {
+        path: Path(~"github.com/catamorphism/test-pkg"),
+        version: default_version()
+    }
+}
+
 fn writeFile(file_path: &Path, contents: ~str) {
     let out: @io::Writer =
         result::get(&io::file_writer(file_path,
@@ -69,6 +77,15 @@ fn is_rwx(p: &Path) -> bool {
     }
 }
 
+#[cfg(test)]
+fn test_sysroot() -> Path {
+    // Totally gross hack but it's just for test cases.
+    // Infer the sysroot from the exe name and tack "stage2"
+    // onto it. (Did I mention it was a gross hack?)
+    let self_path = os::self_exe_path().expect("Couldn't get self_exe path");
+    self_path.pop().push("stage2")
+}
+
 #[test]
 fn test_make_dir_rwx() {
     let temp = &os::tmpdir();
@@ -84,11 +101,9 @@ fn test_make_dir_rwx() {
 
 #[test]
 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 sysroot = test_sysroot();
+    debug!("sysroot = %s", sysroot.to_str());
+    let ctxt = fake_ctxt(Some(@sysroot));
     let temp_pkg_id = fake_pkg();
     let temp_workspace = mk_temp_workspace(&temp_pkg_id.path);
     // should have test, bench, lib, and main
@@ -114,7 +129,7 @@ fn test_install_invalid() {
     use conditions::nonexistent_package::cond;
     use cond1 = conditions::missing_pkg_files::cond;
 
-    let ctxt = fake_ctxt();
+    let ctxt = fake_ctxt(None);
     let pkgid = fake_pkg();
     let temp_workspace = mkdtemp(&os::tmpdir(), "test").expect("couldn't create temp dir");
     let mut error_occurred = false;
@@ -130,3 +145,29 @@ fn test_install_invalid() {
     }
     assert!(error_occurred && error1_occurred);
 }
+
+#[test]
+#[ignore(reason = "install from URL-fragment not yet implemented")]
+fn test_install_url() {
+    let sysroot = test_sysroot();
+    debug!("sysroot = %s", sysroot.to_str());
+    let ctxt = fake_ctxt(Some(@sysroot));
+    let temp_pkg_id = remote_pkg();
+    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)));
+    let bench = target_bench_in_workspace(temp_pkg_id, &temp_workspace);
+    debug!("bench = %s", bench.to_str());
+    assert!(!os::path_exists(&bench));
+}
\ No newline at end of file
diff --git a/src/librustpkg/util.rs b/src/librustpkg/util.rs
index 1b3d72bf6aa..0762fa4ad7f 100644
--- a/src/librustpkg/util.rs
+++ b/src/librustpkg/util.rs
@@ -435,7 +435,7 @@ pub fn add_pkg(pkg: &Pkg) -> bool {
 }
 
 // FIXME (#4432): Use workcache to only compile when needed
-pub fn compile_input(sysroot: Option<Path>,
+pub fn compile_input(sysroot: Option<@Path>,
                      pkg_id: PkgId,
                      in_file: &Path,
                      out_dir: &Path,
@@ -474,6 +474,7 @@ pub fn compile_input(sysroot: Option<Path>,
            out_file.to_str());
     debug!("flags: %s", str::connect(flags, ~" "));
     debug!("cfgs: %s", str::connect(cfgs, ~" "));
+    debug!("compile_input's sysroot = %?", sysroot);
 
     let matches = getopts(~[~"-Z", ~"time-passes"]
                           + if building_library { ~[~"--lib"] }
@@ -587,7 +588,7 @@ fn add_attrs(c: ast::crate, new_attrs: ~[attribute]) -> @ast::crate {
 
 // Called by build_crates
 // FIXME (#4432): Use workcache to only compile when needed
-pub fn compile_crate(sysroot: Option<Path>, pkg_id: PkgId,
+pub fn compile_crate(sysroot: Option<@Path>, pkg_id: PkgId,
                      crate: &Path, dir: &Path,
                      flags: ~[~str], cfgs: ~[~str], opt: bool,
                      test: bool, crate_type: crate_type) -> bool {

From 9f76ca6508d82036e6ff1723dcd395998a4baa81 Mon Sep 17 00:00:00 2001
From: Tim Chevalier <chevalier@alum.wellesley.edu>
Date: Fri, 3 May 2013 17:24:44 -0700
Subject: [PATCH 4/4] rustpkg: Make code actually compile

oops.
---
 src/librustc/metadata/filesearch.rs | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/src/librustc/metadata/filesearch.rs b/src/librustc/metadata/filesearch.rs
index ded0b314d44..7547f7f763a 100644
--- a/src/librustc/metadata/filesearch.rs
+++ b/src/librustc/metadata/filesearch.rs
@@ -39,9 +39,8 @@ pub fn mk_filesearch(maybe_sysroot: &Option<@Path>,
         fn sysroot(&self) -> @Path { self.sysroot }
         fn for_each_lib_search_path(&self, f: &fn(&Path) -> bool) {
             debug!("filesearch: searching additional lib search paths");
-            if !self.addl_lib_search_paths.each(f) {
-                return;
-            }
+            // a little weird
+            self.addl_lib_search_paths.each(f);
 
             debug!("filesearch: searching target lib path");
             if !f(&make_target_lib_path(self.sysroot,
@@ -59,7 +58,7 @@ pub fn mk_filesearch(maybe_sysroot: &Option<@Path>,
            match get_rustpkg_lib_path() {
               result::Ok(ref p) => f(p),
               result::Err(_) => true
-           }
+           };
         }
         fn get_target_lib_path(&self) -> Path {
             make_target_lib_path(self.sysroot, self.target_triple)