about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/librustpkg/conditions.rs4
-rw-r--r--src/librustpkg/path_util.rs108
-rw-r--r--src/librustpkg/rustpkg.rc230
-rw-r--r--src/librustpkg/tests.rs90
-rw-r--r--src/librustpkg/util.rs288
-rw-r--r--src/librustpkg/workspace.rs7
m---------src/libuv0
7 files changed, 296 insertions, 431 deletions
diff --git a/src/librustpkg/conditions.rs b/src/librustpkg/conditions.rs
index 5b19a3bd660..680e0924d79 100644
--- a/src/librustpkg/conditions.rs
+++ b/src/librustpkg/conditions.rs
@@ -28,3 +28,7 @@ condition! {
 condition! {
     missing_pkg_files: (super::PkgId) -> ();
 }
+
+condition! {
+    bad_pkg_id: (super::Path, ~str) -> ::util::PkgId;
+}
diff --git a/src/librustpkg/path_util.rs b/src/librustpkg/path_util.rs
index 477a7af4550..bbd8d092354 100644
--- a/src/librustpkg/path_util.rs
+++ b/src/librustpkg/path_util.rs
@@ -10,12 +10,10 @@
 
 // rustpkg utilities having to do with paths and directories
 
-use util::PkgId;
+pub use util::{PkgId, RemotePath, LocalPath};
 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 }
+pub use util::{normalize, OutputType, Main, Lib, Bench, Test};
 
 /// Returns the value of RUST_PATH, as a list
 /// of Paths. In general this should be read from the
@@ -31,24 +29,6 @@ pub static u_rwx: i32 = (S_IRUSR | S_IWUSR | S_IXUSR) as i32;
 /// succeeded.
 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
-/// as the same name
-pub fn normalize(p: ~Path) -> ~Path {
-    match p.filestem() {
-        None => p,
-        Some(st) => {
-            let replaced = str::replace(st, "-", "_");
-            if replaced != st {
-                ~p.with_filestem(replaced)
-            }
-            else {
-                p
-            }
-        }
-    }
-}
-
 // n.b. So far this only handles local workspaces
 // n.b. The next three functions ignore the package version right
 // now. Should fix that.
@@ -56,7 +36,7 @@ pub fn normalize(p: ~Path) -> ~Path {
 /// 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());
+    let pkgpath = workspace.push("src").push(pkgid.local_path.to_str());
     os::path_is_dir(&pkgpath)
 }
 
@@ -64,34 +44,58 @@ pub fn workspace_contains_package_id(pkgid: &PkgId, workspace: &Path) -> bool {
 /// 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())
+    result.push(pkgid.local_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);
+    result = mk_output_path(Main, pkgid, &result);
     debug!("built_executable_in_workspace: checking whether %s exists",
            result.to_str());
     if os::path_exists(&result) {
         Some(result)
     }
     else {
+        // This is not an error, but it's worth logging it
+        error!(fmt!("built_executable_in_workspace: %s does not exist", result.to_str()));
         None
     }
 }
 
-/// Figure out what the library name for <pkgid> in <workspace>'s build
+/// Figure out what the test 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> {
+pub fn built_test_in_workspace(pkgid: &PkgId, workspace: &Path) -> Option<Path> {
+    output_in_workspace(pkgid, workspace, Test)
+}
+
+/// Figure out what the test name for <pkgid> in <workspace>'s build
+/// directory is, and if the file exists, return it.
+pub fn built_bench_in_workspace(pkgid: &PkgId, workspace: &Path) -> Option<Path> {
+    output_in_workspace(pkgid, workspace, Bench)
+}
+
+fn output_in_workspace(pkgid: &PkgId, workspace: &Path, what: OutputType) -> 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);
+    result = mk_output_path(what, pkgid, &result);
+    debug!("output_in_workspace: checking whether %s exists",
+           result.to_str());
+    if os::path_exists(&result) {
+        Some(result)
+    }
+    else {
+        error!(fmt!("output_in_workspace: %s does not exist", result.to_str()));
+        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 result = mk_output_path(Lib, pkgid, &workspace.push("build"));
     debug!("built_library_in_workspace: checking whether %s exists",
            result.to_str());
 
@@ -100,8 +104,7 @@ pub fn built_library_in_workspace(pkgid: &PkgId, workspace: &Path) -> Option<Pat
     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_prefix = fmt!("%s%s", os::consts::DLL_PREFIX, pkgid.short_name);
     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);
@@ -173,12 +176,14 @@ pub fn target_library_in_workspace(pkgid: &PkgId, workspace: &Path) -> Path {
 
 /// Returns the test executable that would be installed for <pkgid>
 /// in <workspace>
+/// note that we *don't* install test executables, so this is just for unit testing
 pub fn target_test_in_workspace(pkgid: &PkgId, workspace: &Path) -> Path {
     target_file_in_workspace(pkgid, workspace, Test)
 }
 
 /// Returns the bench executable that would be installed for <pkgid>
 /// in <workspace>
+/// note that we *don't* install bench executables, so this is just for unit testing
 pub fn target_bench_in_workspace(pkgid: &PkgId, workspace: &Path) -> Path {
     target_file_in_workspace(pkgid, workspace, Bench)
 }
@@ -187,18 +192,14 @@ 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 subdir = match what {
+        Lib => "lib", Main | Test | Bench => "bin"
     };
     let result = workspace.push(subdir);
-    if create_dir {
-        if !os::path_exists(&result) && !mkdir_recursive(&result, u_rwx) {
-            cond.raise((copy result,
-                        fmt!("I couldn't create the %s dir", subdir)));
-        }
+    if !os::path_exists(&result) && !mkdir_recursive(&result, u_rwx) {
+        cond.raise((copy result, fmt!("I couldn't create the %s dir", subdir)));
     }
-    mk_output_path(what, pkgid.path.to_str(), result)
-
+    mk_output_path(what, pkgid, &result)
 }
 
 /// Return the directory for <pkgid>'s build artifacts in <workspace>.
@@ -209,7 +210,7 @@ pub fn build_pkg_id_in_workspace(pkgid: &PkgId, workspace: &Path) -> Path {
     let mut result = workspace.push("build");
     // n.b. Should actually use a target-specific
     // subdirectory of build/
-    result = result.push(normalize(~copy pkgid.path).to_str());
+    result = result.push_rel(&*pkgid.local_path);
     if os::path_exists(&result) || os::mkdir_recursive(&result, u_rwx) {
         result
     }
@@ -220,15 +221,26 @@ pub fn build_pkg_id_in_workspace(pkgid: &PkgId, workspace: &Path) -> Path {
 
 /// 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,
+pub fn mk_output_path(what: OutputType, pkg_id: &PkgId, workspace: &Path) -> Path {
+    let short_name_with_version = pkg_id.short_name_with_version();
+    // Not local_path.dir_path()! For package foo/bar/blat/, we want
+    // the executable blat-0.5 to live under blat/
+    let dir = workspace.push_rel(&*pkg_id.local_path);
+    debug!("mk_output_path: short_name = %s, path = %s",
+           if what == Lib { copy short_name_with_version } else { copy pkg_id.short_name },
+           dir.to_str());
+    let output_path = match what {
+        // this code is duplicated from elsewhere; fix this
+        Lib => dir.push(os::dll_filename(short_name_with_version)),
+        // executable names *aren't* versioned
+        _ => dir.push(fmt!("%s%s%s", copy pkg_id.short_name,
                            match what {
                                Test => "test",
                                Bench => "bench",
                                _     => ""
                            }
                            os::EXE_SUFFIX))
-    }
+    };
+    debug!("mk_output_path: returning %s", output_path.to_str());
+    output_path
 }
diff --git a/src/librustpkg/rustpkg.rc b/src/librustpkg/rustpkg.rc
index 884f0a73589..a69613776ef 100644
--- a/src/librustpkg/rustpkg.rc
+++ b/src/librustpkg/rustpkg.rc
@@ -30,12 +30,10 @@ use rustc::metadata::filesearch;
 use std::{getopts};
 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::{build_pkg_id_in_workspace, pkgid_src_in_workspace, u_rwx};
 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;
 
 mod conditions;
@@ -159,27 +157,6 @@ impl<'self> PkgScript<'self> {
 impl Ctx {
 
     fn run(&self, cmd: ~str, args: ~[~str]) {
-        let root = util::root();
-
-        util::need_dir(&root);
-        util::need_dir(&root.push(~"work"));
-        util::need_dir(&root.push(~"lib"));
-        util::need_dir(&root.push(~"bin"));
-        util::need_dir(&root.push(~"tmp"));
-
-        fn sep_name_vers(in: ~str) -> (Option<~str>, Option<~str>) {
-            let mut name = None;
-            let mut vers = None;
-
-            for str::each_split_char(in, '@') |s| {
-                if      name.is_none() { name = Some(s.to_owned()); }
-                else if vers.is_none() { vers = Some(s.to_owned()); }
-                else                   { break;               }
-            }
-
-            (name, vers)
-        }
-
         match cmd {
             ~"build" => {
                 if args.len() < 1 {
@@ -229,9 +206,7 @@ impl Ctx {
                     return usage::uninstall();
                 }
 
-                let (name, vers) = sep_name_vers(copy args[0]);
-
-                self.prefer(name.get(), vers);
+                self.prefer(args[0], None);
             }
             ~"test" => {
                 self.test();
@@ -241,20 +216,16 @@ impl Ctx {
                     return usage::uninstall();
                 }
 
-                let (name, vers) = sep_name_vers(copy args[0]);
-
-                self.uninstall(name.get(), vers);
+                self.uninstall(args[0], None);
             }
             ~"unprefer" => {
                 if args.len() < 1 {
                     return usage::uninstall();
                 }
 
-                let (name, vers) = sep_name_vers(copy args[0]);
-
-                self.unprefer(name.get(), vers);
+                self.unprefer(args[0], None);
             }
-            _ => fail!("reached an unhandled command")
+            _ => fail!(fmt!("I don't know the command `%s`", cmd))
         }
     }
 
@@ -269,7 +240,7 @@ impl Ctx {
         debug!("Destination dir = %s", build_dir.to_str());
 
         // Create the package source
-        let mut src = PkgSrc::new(&workspace.push("src"), &build_dir, pkgid);
+        let mut src = PkgSrc::new(workspace, &build_dir, pkgid);
         debug!("Package src = %?", src);
 
         // Is there custom build logic? If so, use it
@@ -307,7 +278,6 @@ impl Ctx {
             // Build it!
             src.build(&build_dir, cfgs, self.sysroot_opt);
         }
-
     }
 
     fn clean(&self, workspace: &Path, id: &PkgId)  {
@@ -319,7 +289,7 @@ impl Ctx {
         util::note(fmt!("Cleaning package %s (removing directory %s)",
                         id.to_str(), dir.to_str()));
         if os::path_exists(&dir) {
-            util::remove_dir_r(&dir);
+            os::remove_dir_recursive(&dir);
             util::note(fmt!("Removed directory %s", dir.to_str()));
         }
 
@@ -337,6 +307,8 @@ impl Ctx {
         // Should use RUST_PATH in the future.
         // Also should use workcache to not build if not necessary.
         self.build(workspace, id);
+        debug!("install: workspace = %s, id = %s", workspace.to_str(),
+               id.to_str());
 
         // Now copy stuff into the install dirs
         let maybe_executable = built_executable_in_workspace(id, workspace);
@@ -344,104 +316,29 @@ impl Ctx {
         let target_exec = target_executable_in_workspace(id, workspace);
         let target_lib = target_library_in_workspace(id, workspace);
 
+        debug!("target_exec = %s target_lib = %s \
+                maybe_executable = %? maybe_library = %?",
+               target_exec.to_str(), target_lib.to_str(),
+               maybe_executable, maybe_library);
+
         for maybe_executable.each |exec| {
             debug!("Copying: %s -> %s", exec.to_str(), target_exec.to_str());
-            if !os::copy_file(exec, &target_exec) {
+            if !(os::mkdir_recursive(&target_exec.dir_path(), u_rwx) &&
+                 os::copy_file(exec, &target_exec)) {
                 cond.raise((copy *exec, copy 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) {
+            if !(os::mkdir_recursive(&target_lib.dir_path(), u_rwx) &&
+                 os::copy_file(lib, &target_lib)) {
                 cond.raise((copy *lib, copy target_lib));
             }
         }
     }
 
-    fn fetch(&self, _dir: &Path, _url: ~str, _target: Option<~str>)  {
-        // stub
-        fail!("fetch not yet implemented");
-    }
-
-    fn fetch_curl(&self, dir: &Path, url: ~str)  {
-        util::note(fmt!("fetching from %s using curl", url));
-
-        let tar = dir.dir_path().push(&dir.file_path().to_str() + ~".tar");
-
-        if run::program_output(~"curl", ~[~"-f", ~"-s",
-                                          ~"-o", tar.to_str(),
-                                          url]).status != 0 {
-            util::error(~"fetching failed: downloading using curl failed");
-
-            fail!();
-        }
-
-        if run::program_output(~"tar", ~[~"-x", ~"--strip-components=1",
-                                         ~"-C", dir.to_str(), ~"-f",
-                                         tar.to_str()]).status != 0 {
-            util::error(~"fetching failed: extracting using tar failed" +
-                        ~"(is it a valid tar archive?)");
-
-           fail!();
-        }
-    }
-
-    fn fetch_git(&self, dir: &Path, url: ~str, mut target: Option<~str>)  {
-        util::note(fmt!("fetching from %s using git", url));
-
-        // Git can't clone into a non-empty directory
-        util::remove_dir_r(dir);
-
-        if run::program_output(~"git", ~[~"clone", url,
-                                         dir.to_str()]).status != 0 {
-            util::error(~"fetching failed: can't clone repository");
-            fail!();
-        }
-
-        if !target.is_none() {
-            let mut success = true;
-
-            do util::temp_change_dir(dir) {
-                success = run::program_output(~"git",
-                                              ~[~"checkout",
-                                                target.swap_unwrap()]).status != 0
-            }
-
-            if !success {
-                util::error(~"fetching failed: can't checkout target");
-                fail!();
-            }
-        }
-    }
-
-    fn prefer(&self, id: ~str, vers: Option<~str>)  {
-        let package = match util::get_pkg(id, vers) {
-            result::Ok(package) => package,
-            result::Err(err) => {
-                util::error(err);
-                fail!(); // Condition?
-            }
-        };
-        let name = package.id.path.to_str(); // ???
-
-        util::note(fmt!("preferring %s v%s", name, package.id.version.to_str()));
-
-        let bin_dir = util::root().push(~"bin");
-
-        for package.bins.each |&bin| {
-            let path = Path(bin);
-            let mut name = None;
-            for str::each_split_char(path.file_path().to_str(), '-') |s| {
-                name = Some(s.to_owned());
-                break;
-            }
-            let out = bin_dir.push(name.unwrap());
-
-            util::link_exe(&path, &out);
-            util::note(fmt!("linked %s", out.to_str()));
-        }
-
-        util::note(fmt!("preferred %s v%s", name, package.id.version.to_str()));
+    fn prefer(&self, _id: &str, _vers: Option<~str>)  {
+        fail!(~"prefer not yet implemented");
     }
 
     fn test(&self)  {
@@ -449,15 +346,16 @@ impl Ctx {
         fail!("test not yet implemented");
     }
 
-    fn uninstall(&self, _id: ~str, _vers: Option<~str>)  {
+    fn uninstall(&self, _id: &str, _vers: Option<~str>)  {
         fail!("uninstall not yet implemented");
     }
 
-    fn unprefer(&self, _id: ~str, _vers: Option<~str>)  {
+    fn unprefer(&self, _id: &str, _vers: Option<~str>)  {
         fail!("unprefer not yet implemented");
     }
 }
 
+
 pub fn main() {
     io::println("WARNING: The Rust package manager is experimental and may be unstable");
 
@@ -518,32 +416,6 @@ pub struct Crate {
     cfgs: ~[~str]
 }
 
-pub struct Listener {
-    cmds: ~[~str],
-    cb: ~fn()
-}
-
-pub fn run(listeners: ~[Listener]) {
-    let rcmd = copy os::args()[2];
-    let mut found = false;
-
-    for listeners.each |listener| {
-        for listener.cmds.each |&cmd| {
-            if cmd == rcmd {
-                (listener.cb)();
-
-                found = true;
-
-                break;
-            }
-        }
-    }
-
-    if !found {
-        os::set_exit_status(42);
-    }
-}
-
 pub impl Crate {
 
     fn new(p: &Path) -> Crate {
@@ -602,10 +474,6 @@ pub fn src_dir() -> Path {
     os::getcwd()
 }
 
-condition! {
-    bad_pkg_id: (super::Path, ~str) -> ::util::PkgId;
-}
-
 // An enumeration of the unpacked source of a package workspace.
 // This contains a list of files found in the source workspace.
 pub struct PkgSrc {
@@ -641,17 +509,19 @@ impl PkgSrc {
     fn check_dir(&self) -> Path {
         use conditions::nonexistent_package::cond;
 
-        debug!("Pushing onto root: %s | %s", self.id.path.to_str(),
+        debug!("Pushing onto root: %s | %s", self.id.to_str(),
                self.root.to_str());
 
-        let dir = self.root.push_rel(&self.id.path).normalize();
+        let mut dir = self.root.push("src");
+        dir = dir.push(self.id.to_str()); // ?? Should this use the version number?
 
         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((copy self.id, ~"missing package dir"));
+            if !self.fetch_git() {
+                cond.raise((copy self.id, ~"supplied path for package dir does not \
+                    exist, and couldn't interpret it as a URL fragment"));
+            }
         }
 
         if !os::path_is_dir(&dir) {
@@ -662,6 +532,28 @@ impl PkgSrc {
         dir
     }
 
+    /// Try interpreting self's package id as a remote package, and try
+    /// fetching it and caching it in a local directory. If that didn't
+    /// work, return false.
+    /// (right now we only support git)
+    fn fetch_git(&self) -> bool {
+
+        let mut local = self.root.push("src");
+        local = local.push(self.id.to_str());
+        // Git can't clone into a non-empty directory
+        os::remove_dir_recursive(&local);
+
+        let url = fmt!("https://%s", self.id.remote_path.to_str());
+        util::note(fmt!("git clone %s %s", url, local.to_str()));
+
+        if run::program_output(~"git", ~[~"clone", copy url, local.to_str()]).status != 0 {
+            util::note(fmt!("fetching %s failed: can't clone repository", url));
+            return false;
+        }
+        true
+    }
+
+
     // If a file named "pkg.rs" in the current directory exists,
     // return the path for it. Otherwise, None
     fn package_script_option(&self, cwd: &Path) -> Option<Path> {
@@ -680,7 +572,7 @@ impl PkgSrc {
     /// Requires that dashes in p have already been normalized to
     /// underscores
     fn stem_matches(&self, p: &Path) -> bool {
-        let self_id = normalize(~copy self.id.path).filestem();
+        let self_id = self.id.local_path.filestem();
         if self_id == p.filestem() {
             return true;
         }
@@ -715,7 +607,7 @@ impl PkgSrc {
 
         let dir = self.check_dir();
         let prefix = dir.components.len();
-        debug!("Matching against %?", self.id.path.filestem());
+        debug!("Matching against %?", self.id.local_path.filestem());
         for os::walk_dir(&dir) |pth| {
             match pth.filename() {
                 Some(~"lib.rs") => push_crate(&mut self.libs,
@@ -752,8 +644,7 @@ impl PkgSrc {
                     src_dir: &Path,
                     crates: &[Crate],
                     cfgs: &[~str],
-                    test: bool, crate_type: crate_type) {
-
+                    what: OutputType) {
         for crates.each |&crate| {
             let path = &src_dir.push_rel(&crate.file).normalize();
             util::note(fmt!("build_crates: compiling %s", path.to_str()));
@@ -763,7 +654,7 @@ impl PkgSrc {
                                      dst_dir,
                                      crate.flags,
                                      crate.cfgs + cfgs,
-                                     false, test, crate_type);
+                                     false, what);
             if !result {
                 build_err::cond.raise(fmt!("build failure on %s",
                                            path.to_str()));
@@ -776,12 +667,13 @@ impl PkgSrc {
     fn build(&self, dst_dir: &Path, cfgs: ~[~str], maybe_sysroot: Option<@Path>) {
         let dir = self.check_dir();
         debug!("Building libs");
-        self.build_crates(maybe_sysroot, dst_dir, &dir, self.libs, cfgs, false, lib_crate);
+        self.build_crates(maybe_sysroot, dst_dir, &dir, self.libs, cfgs, Lib);
         debug!("Building mains");
-        self.build_crates(maybe_sysroot, dst_dir, &dir, self.mains, cfgs, false, bin_crate);
+        self.build_crates(maybe_sysroot, dst_dir, &dir, self.mains, cfgs, Main);
         debug!("Building tests");
-        self.build_crates(maybe_sysroot, dst_dir, &dir, self.tests, cfgs, true, bin_crate);
+        self.build_crates(maybe_sysroot, dst_dir, &dir, self.tests, cfgs, Test);
         debug!("Building benches");
-        self.build_crates(maybe_sysroot, dst_dir, &dir, self.benchs, cfgs, true, bin_crate);
+        self.build_crates(maybe_sysroot, dst_dir, &dir, self.benchs, cfgs, Bench);
     }
 }
+
diff --git a/src/librustpkg/tests.rs b/src/librustpkg/tests.rs
index e661e758d41..487e8d0da36 100644
--- a/src/librustpkg/tests.rs
+++ b/src/librustpkg/tests.rs
@@ -17,8 +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, u_rwx};
-use core::os::mkdir_recursive;
+               make_dir_rwx, u_rwx, RemotePath, LocalPath, normalize,
+               built_bench_in_workspace, built_test_in_workspace};
 
 fn fake_ctxt(sysroot_opt: Option<@Path>) -> Ctx {
     Ctx {
@@ -29,15 +29,22 @@ fn fake_ctxt(sysroot_opt: Option<@Path>) -> Ctx {
 }
 
 fn fake_pkg() -> PkgId {
+    let sn = ~"bogus";
+    let remote = RemotePath(Path(sn));
     PkgId {
-        path: Path(~"bogus"),
+        local_path: normalize(copy remote),
+        remote_path: remote,
+        short_name: sn,
         version: default_version()
     }
 }
 
 fn remote_pkg() -> PkgId {
+    let remote = RemotePath(Path(~"github.com/catamorphism/test-pkg"));
     PkgId {
-        path: Path(~"github.com/catamorphism/test-pkg"),
+        local_path: normalize(copy remote),
+        remote_path: remote,
+        short_name: ~"test_pkg",
         version: default_version()
     }
 }
@@ -49,10 +56,11 @@ fn writeFile(file_path: &Path, contents: ~str) {
     out.write_line(contents);
 }
 
-fn mk_temp_workspace(short_name: &Path) -> Path {
+fn mk_temp_workspace(short_name: &LocalPath) -> 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));
+    // include version number in directory name
+    let package_dir = workspace.push(~"src").push(fmt!("%s-0.1", short_name.to_str()));
+    assert!(os::mkdir_recursive(&package_dir, u_rwx));
     // Create main, lib, test, and bench files
     writeFile(&package_dir.push(~"main.rs"),
               ~"fn main() { let _x = (); }");
@@ -104,7 +112,7 @@ fn test_install_valid() {
     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);
+    let temp_workspace = mk_temp_workspace(&temp_pkg_id.local_path);
     // should have test, bench, lib, and main
     ctxt.install(&temp_workspace, &temp_pkg_id);
     // Check that all files exist
@@ -146,27 +154,79 @@ fn test_install_invalid() {
 }
 
 #[test]
-#[ignore(reason = "install from URL-fragment not yet implemented")]
 fn test_install_url() {
+    let workspace = mkdtemp(&os::tmpdir(), "test").expect("couldn't create temp dir");
     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);
+    ctxt.install(&workspace, &temp_pkg_id);
     // Check that all files exist
-    let exec = target_executable_in_workspace(&temp_pkg_id, &temp_workspace);
+    let exec = target_executable_in_workspace(&temp_pkg_id, &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);
+    let lib = target_library_in_workspace(&temp_pkg_id, &workspace);
     debug!("lib = %s", lib.to_str());
     assert!(os::path_exists(&lib));
     assert!(is_rwx(&lib));
+    let built_test = built_test_in_workspace(&temp_pkg_id, &workspace).expect(~"test_install_url");
+    assert!(os::path_exists(&built_test));
+    let built_bench = built_bench_in_workspace(&temp_pkg_id,
+                                               &workspace).expect(~"test_install_url");
+    assert!(os::path_exists(&built_bench));
     // 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);
+    let test = target_test_in_workspace(&temp_pkg_id, &workspace);
+    assert!(!os::path_exists(&test));
+    debug!("test = %s", test.to_str());
+    let bench = target_bench_in_workspace(&temp_pkg_id, &workspace);
     debug!("bench = %s", bench.to_str());
     assert!(!os::path_exists(&bench));
 }
+
+#[test]
+fn test_package_ids_must_be_relative_path_like() {
+    use conditions::bad_pkg_id::cond;
+
+    /*
+    Okay:
+    - One identifier, with no slashes
+    - Several slash-delimited things, with no / at the root
+
+    Not okay:
+    - Empty string
+    - Absolute path (as per os::is_absolute)
+
+    */
+
+    let default_version_str = "0.1";
+    let addversion = |s| {
+        fmt!("%s-%s", s, default_version_str)
+    };
+
+    let whatever = PkgId::new("foo");
+
+    assert!(addversion("foo") == whatever.to_str());
+    assert!(addversion("github.com/mozilla/rust") ==
+            PkgId::new("github.com/mozilla/rust").to_str());
+
+    do cond.trap(|(p, e)| {
+        assert!("" == p.to_str());
+        assert!("0-length pkgid" == e);
+        copy whatever
+    }).in {
+        let x = PkgId::new("");
+        assert!(addversion("foo") == x.to_str());
+    }
+
+    do cond.trap(|(p, e)| {
+        assert!(p.to_str() == os::make_absolute(&Path("foo/bar/quux")).to_str());
+        assert!("absolute pkgid" == e);
+        copy whatever
+    }).in {
+        let z = PkgId::new(os::make_absolute(&Path("foo/bar/quux")).to_str());
+        assert!(addversion("foo") == z.to_str());
+    }
+
+}
diff --git a/src/librustpkg/util.rs b/src/librustpkg/util.rs
index e9c4b7fde55..4a9c276948a 100644
--- a/src/librustpkg/util.rs
+++ b/src/librustpkg/util.rs
@@ -13,20 +13,18 @@ use core::cmp::Ord;
 use core::hash::Streaming;
 use core::rt::io::Writer;
 use rustc::driver::{driver, session};
-use rustc::driver::session::{lib_crate, unknown_crate};
 use rustc::metadata::filesearch;
 use std::getopts::groups::getopts;
 use std::semver;
-use std::{json, term, getopts};
+use std::{term, getopts};
 use syntax::ast_util::*;
 use syntax::codemap::{dummy_sp, spanned, dummy_spanned};
 use syntax::ext::base::{mk_ctxt, ext_ctxt};
-use syntax::ext::build;
 use syntax::{ast, attr, codemap, diagnostic, fold};
-use syntax::ast::{meta_name_value, meta_list, attribute};
+use syntax::ast::{meta_name_value, meta_list};
 use syntax::attr::{mk_attr};
 use rustc::back::link::output_type_exe;
-use rustc::driver::session::{lib_crate, unknown_crate, crate_type};
+use rustc::driver::session::{lib_crate, bin_crate};
 
 static Commands: &'static [&'static str] =
     &["build", "clean", "do", "info", "install", "prefer", "test", "uninstall",
@@ -83,20 +81,34 @@ impl ToStr for Version {
     }
 }
 
+#[deriving(Eq)]
+pub enum OutputType { Main, Lib, Bench, Test }
+
 /// Placeholder
 pub fn default_version() -> Version { ExactRevision(0.1) }
 
-// Path-fragment identifier of a package such as
-// 'github.com/graydon/test'; path must be a relative
-// path with >=1 component.
+/// Path-fragment identifier of a package such as
+/// 'github.com/graydon/test'; path must be a relative
+/// path with >=1 component.
 pub struct PkgId {
-    path: Path,
+    /// Remote path: for example, github.com/mozilla/quux-whatever
+    remote_path: RemotePath,
+    /// Local path: for example, /home/quux/github.com/mozilla/quux_whatever
+    /// Note that '-' normalizes to '_' when mapping a remote path
+    /// onto a local path
+    /// Also, this will change when we implement #6407, though we'll still
+    /// need to keep track of separate local and remote paths
+    local_path: LocalPath,
+    /// Short name. This is the local path's filestem, but we store it
+    /// redundantly so as to not call get() everywhere (filestem() returns an
+    /// option)
+    short_name: ~str,
     version: Version
 }
 
 pub impl PkgId {
     fn new(s: &str) -> PkgId {
-        use bad_pkg_id::cond;
+        use conditions::bad_pkg_id::cond;
 
         let p = Path(s);
         if p.is_absolute {
@@ -105,31 +117,32 @@ pub impl PkgId {
         if p.components.len() < 1 {
             return cond.raise((p, ~"0-length pkgid"));
         }
+        let remote_path = RemotePath(p);
+        let local_path = normalize(copy remote_path);
+        let short_name = (copy local_path).filestem().expect(fmt!("Strange path! %s", s));
         PkgId {
-            path: p,
+            local_path: local_path,
+            remote_path: remote_path,
+            short_name: short_name,
             version: default_version()
         }
     }
 
     fn hash(&self) -> ~str {
-        fmt!("%s-%s-%s", self.path.to_str(),
-             hash(self.path.to_str() + self.version.to_str()),
+        fmt!("%s-%s-%s", self.remote_path.to_str(),
+             hash(self.remote_path.to_str() + self.version.to_str()),
              self.version.to_str())
     }
 
+    fn short_name_with_version(&self) -> ~str {
+        fmt!("%s-%s", self.short_name, self.version.to_str())
+    }
 }
 
 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(),
-             // 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(), ".", "-"))
+        fmt!("%s-%s", self.local_path.to_str(), self.version.to_str())
     }
 }
 
@@ -156,26 +169,6 @@ pub fn is_cmd(cmd: &str) -> bool {
     Commands.any(|&c| c == cmd)
 }
 
-pub fn parse_name(id: ~str) -> result::Result<~str, ~str> {
-    let mut last_part = None;
-
-    for str::each_split_char(id, '.') |part| {
-        for str::each_char(part) |char| {
-            if char::is_whitespace(char) {
-                return result::Err(
-                    ~"could not parse id: contains whitespace");
-            } else if char::is_uppercase(char) {
-                return result::Err(
-                    ~"could not parse id: should be all lowercase");
-            }
-        }
-        last_part = Some(part.to_owned());
-    }
-    if last_part.is_none() { return result::Err(~"could not parse id: is empty"); }
-
-    result::Ok(last_part.unwrap())
-}
-
 struct ListenerFn {
     cmds: ~[~str],
     span: codemap::span,
@@ -248,52 +241,6 @@ fn fold_item(ctx: @mut ReadyCtx,
     res
 }
 
-fn add_pkg_module(ctx: @mut ReadyCtx, m: ast::_mod) -> ast::_mod {
-    let listeners = mk_listener_vec(ctx);
-    let ext_cx = ctx.ext_cx;
-    let item = quote_item! (
-        mod __pkg {
-            extern mod rustpkg (vers="0.7-pre");
-            static listeners : &[rustpkg::Listener] = $listeners;
-            #[main]
-            fn main() {
-                rustpkg::run(listeners);
-            }
-        }
-    );
-    ast::_mod {
-        items: vec::append_one(/*bad*/copy m.items, item.get()),
-        .. m
-    }
-}
-
-fn mk_listener_vec(ctx: @mut ReadyCtx) -> @ast::expr {
-    let descs = do ctx.fns.map |listener| {
-        mk_listener_rec(ctx, listener)
-    };
-    let ext_cx = ctx.ext_cx;
-    build::mk_slice_vec_e(ext_cx, dummy_sp(), descs)
-}
-
-fn mk_listener_rec(ctx: @mut ReadyCtx, listener: &ListenerFn) -> @ast::expr {
-    let span = listener.span;
-    let cmds = do listener.cmds.map |&cmd| {
-        let ext_cx = ctx.ext_cx;
-        build::mk_base_str(ext_cx, span, cmd)
-    };
-
-    let ext_cx = ctx.ext_cx;
-    let cmds_expr = build::mk_slice_vec_e(ext_cx, span, cmds);
-    let cb_expr = build::mk_path(ext_cx, span, copy listener.path);
-
-    quote_expr!(
-        Listener {
-            cmds: $cmds_expr,
-            cb: $cb_expr
-        }
-    )
-}
-
 /// Generate/filter main function, add the list of commands, etc.
 pub fn ready_crate(sess: session::Session,
                    crate: @ast::crate) -> @ast::crate {
@@ -375,67 +322,6 @@ pub fn hash(data: ~str) -> ~str {
     hasher.result_str()
 }
 
-pub fn temp_change_dir<T>(dir: &Path, cb: &fn() -> T) {
-    let cwd = os::getcwd();
-
-    os::change_dir(dir);
-    cb();
-    os::change_dir(&cwd);
-}
-
-pub fn touch(path: &Path) {
-    match io::mk_file_writer(path, ~[io::Create]) {
-        result::Ok(writer) => writer.write_line(~""),
-        _ => {}
-    }
-}
-
-pub fn remove_dir_r(path: &Path) {
-    for os::walk_dir(path) |&file| {
-        let mut cdir = file;
-
-        loop {
-            if os::path_is_dir(&cdir) {
-                os::remove_dir(&cdir);
-            } else {
-                os::remove_file(&cdir);
-            }
-
-            cdir = cdir.dir_path();
-
-            if cdir == *path { break; }
-        }
-    }
-
-    os::remove_dir(path);
-}
-
-pub fn wait_for_lock(path: &Path) {
-    if os::path_exists(path) {
-        warn(fmt!("the database appears locked, please wait (or rm %s)",
-                        path.to_str()));
-
-        loop {
-            if !os::path_exists(path) { break; }
-        }
-    }
-}
-
-pub fn load_pkgs() -> result::Result<~[json::Json], ~str> {
-    fail!("load_pkg not implemented");
-}
-
-pub fn get_pkg(_id: ~str,
-               _vers: Option<~str>) -> result::Result<Pkg, ~str> {
-    fail!("get_pkg not implemented");
-}
-
-pub fn add_pkg(pkg: &Pkg) -> bool {
-    note(fmt!("Would be adding package, but add_pkg is not yet implemented %s",
-         pkg.to_str()));
-    false
-}
-
 // FIXME (#4432): Use workcache to only compile when needed
 pub fn compile_input(sysroot: Option<@Path>,
                      pkg_id: &PkgId,
@@ -444,31 +330,24 @@ pub fn compile_input(sysroot: Option<@Path>,
                      flags: &[~str],
                      cfgs: &[~str],
                      opt: bool,
-                     test: bool,
-                     crate_type: session::crate_type) -> bool {
-
-    // 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());
+                     what: OutputType) -> bool {
 
     assert!(in_file.components.len() > 1);
     let input = driver::file_input(copy *in_file);
-    debug!("compile_input: %s / %?", in_file.to_str(), crate_type);
+    debug!("compile_input: %s / %?", in_file.to_str(), what);
     // tjc: by default, use the package ID name as the link name
     // not sure if we should support anything else
 
-    let binary = @copy os::args()[0];
-    let building_library = match crate_type {
-        lib_crate | unknown_crate => true,
-        _ => false
-    };
+    let binary = @(copy os::args()[0]);
+    let building_library = what == Lib;
 
     let out_file = if building_library {
-        out_dir.push(os::dll_filename(short_name))
+        out_dir.push(os::dll_filename(pkg_id.short_name))
     }
     else {
-        out_dir.push(short_name + if test { ~"test" } else { ~"" }
-                     + os::EXE_SUFFIX)
+        out_dir.push(pkg_id.short_name + match what {
+            Test => ~"test", Bench => ~"bench", Main | Lib => ~""
+        } + os::EXE_SUFFIX)
     };
 
     debug!("compiling %s into %s",
@@ -478,18 +357,24 @@ pub fn compile_input(sysroot: Option<@Path>,
     debug!("cfgs: %s", str::connect(cfgs, ~" "));
     debug!("compile_input's sysroot = %?", sysroot);
 
+    let crate_type = match what {
+        Lib => lib_crate,
+        Test | Bench | Main => bin_crate
+    };
     let matches = getopts(~[~"-Z", ~"time-passes"]
-                          + if building_library { ~[~"--lib"] }
-                            else if test { ~[~"--test"] }
-                            // bench?
-                            else { ~[] }
+                          + match what {
+                              Lib => ~[~"--lib"],
+                              // --test compiles both #[test] and #[bench] fns
+                              Test | Bench => ~[~"--test"],
+                              Main => ~[]
+                          }
                           + flags
                           + cfgs.flat_map(|&c| { ~[~"--cfg", c] }),
                           driver::optgroups()).get();
     let mut options = session::options {
         crate_type: crate_type,
         optimize: if opt { session::Aggressive } else { session::No },
-        test: test,
+        test: what == Test || what == Bench,
         maybe_sysroot: sysroot,
         addl_lib_search_paths: ~[copy *out_dir],
         // output_type should be conditional
@@ -540,26 +425,27 @@ pub fn compile_crate_from_input(input: &driver::input,
             debug!("Calling compile_upto, outputs = %?", outputs);
             let (crate, _) = driver::compile_upto(sess, copy cfg, input,
                                                   driver::cu_parse, Some(outputs));
+            let mut crate = crate;
 
             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(copy *crate,
-                    ~[mk_attr(@dummy_spanned(meta_list(@~"link",
-                                                  // change PkgId to have a <shortname> field?
-                    ~[@dummy_spanned(meta_name_value(@~"name",
-                                                    mk_string_lit(@pkg_id.path.filestem().get()))),
-                      @dummy_spanned(meta_name_value(@~"vers",
-                                                    mk_string_lit(@pkg_id.version.to_str())))])))]);
+                crate = @codemap::respan(crate.span, ast::crate_ {
+                    attrs: ~[mk_attr(@dummy_spanned(
+                        meta_list(@~"link",
+                                  ~[@dummy_spanned(meta_name_value(@~"name",
+                                        mk_string_lit(@(copy pkg_id.short_name)))),
+                                    @dummy_spanned(meta_name_value(@~"vers",
+                                        mk_string_lit(@(copy pkg_id.version.to_str()))))])))],
+                    ..copy crate.node});
             }
 
-            driver::compile_rest(sess, cfg, what, Some(outputs), Some(crate_to_use));
-            crate_to_use
+            driver::compile_rest(sess, cfg, what, Some(outputs), Some(crate));
+            crate
         }
     }
 }
@@ -573,30 +459,41 @@ pub fn exe_suffix() -> ~str { ~".exe" }
 #[cfg(target_os = "macos")]
 pub fn exe_suffix() -> ~str { ~"" }
 
-
-/// Returns a copy of crate `c` with attributes `attrs` added to its
-/// attributes
-fn add_attrs(mut c: ast::crate, new_attrs: ~[attribute]) -> @ast::crate {
-    c.node.attrs += new_attrs;
-    @c
-}
-
 // Called by build_crates
 // FIXME (#4432): Use workcache to only compile when needed
 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 {
+                     what: OutputType) -> bool {
     debug!("compile_crate: crate=%s, dir=%s", crate.to_str(), dir.to_str());
     debug!("compile_crate: short_name = %s, flags =...", pkg_id.to_str());
     for flags.each |&fl| {
         debug!("+++ %s", fl);
     }
-    compile_input(sysroot, pkg_id,
-                  crate, dir, flags, cfgs, opt, test, crate_type)
+    compile_input(sysroot, pkg_id, crate, dir, flags, cfgs, opt, what)
+}
+
+// normalize should be the only way to construct a LocalPath
+// (though this isn't enforced)
+/// 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
+/// as the same name
+pub fn normalize(p_: RemotePath) -> LocalPath {
+    let RemotePath(p) = p_;
+    match p.filestem() {
+        None => LocalPath(p),
+        Some(st) => {
+            let replaced = str::replace(st, "-", "_");
+            if replaced != st {
+                LocalPath(p.with_filestem(replaced))
+            }
+            else {
+                LocalPath(p)
+            }
+        }
+    }
 }
 
-
 #[cfg(windows)]
 pub fn link_exe(_src: &Path, _dest: &Path) -> bool {
     /* FIXME (#1768): Investigate how to do this on win32
@@ -628,9 +525,13 @@ pub fn mk_string_lit(s: @~str) -> ast::lit {
     }
 }
 
+/// Wrappers to prevent local and remote paths from getting confused
+pub struct RemotePath (Path);
+pub struct LocalPath (Path);
+
 #[cfg(test)]
 mod test {
-    use super::{is_cmd, parse_name};
+    use super::is_cmd;
 
     #[test]
     fn test_is_cmd() {
@@ -645,9 +546,4 @@ mod test {
         assert!(is_cmd(~"unprefer"));
     }
 
-    #[test]
-    fn test_parse_name() {
-        assert!(parse_name(~"org.mozilla.servo").get() == ~"servo");
-        assert!(parse_name(~"org. mozilla.servo 2131").is_err());
-    }
 }
diff --git a/src/librustpkg/workspace.rs b/src/librustpkg/workspace.rs
index 8941dbfd20e..cb9f735bce8 100644
--- a/src/librustpkg/workspace.rs
+++ b/src/librustpkg/workspace.rs
@@ -21,9 +21,10 @@ pub fn pkg_parent_workspaces(pkgid: &PkgId, action: &fn(&Path) -> bool) -> bool
         workspace_contains_package_id(pkgid, ws));
     if workspaces.is_empty() {
         // tjc: make this a condition
-        fail!("Package %s not found in any of the following workspaces: %s",
-              pkgid.path.to_str(),
-              rust_path().to_str());
+        fail!("Package %s not found in any of \
+                    the following workspaces: %s",
+                   pkgid.remote_path.to_str(),
+                   rust_path().to_str());
     }
     for workspaces.each |ws| {
         if action(ws) {
diff --git a/src/libuv b/src/libuv
-Subproject 97ac7c087a0caf6b0f611b80e14f7fe3cb18bb2
+Subproject 218ab86721eefd7b7e97fa6d9f95a80a1fa8686