about summary refs log tree commit diff
path: root/src/librustpkg
diff options
context:
space:
mode:
authorTim Chevalier <chevalier@alum.wellesley.edu>2013-05-27 17:45:16 -0700
committerTim Chevalier <chevalier@alum.wellesley.edu>2013-06-01 18:48:07 -0700
commitc120464be07a5d14f10909ea6718c58c3aa911e4 (patch)
treef2b7694316b9ebf41403e786b23245c86179d1d7 /src/librustpkg
parent341678b815051717c86cc63a00a5d256bf5b2a35 (diff)
downloadrust-c120464be07a5d14f10909ea6718c58c3aa911e4.tar.gz
rust-c120464be07a5d14f10909ea6718c58c3aa911e4.zip
rustc/rusti/rustpkg: Infer packages from `extern mod` directives
This commit won't be quite as useful until I implement RUST_PATH and
until we change `extern mod` to take a general string instead of
an identifier (#5682 and #6407).

With that said, now if you're using rustpkg and a program contains:

extern mod foo;

rustpkg will attempt to search for `foo`, so that you don't have to
provide a -L directory explicitly. In addition, rustpkg will
actually try to build and install `foo`, unless it's already
installed (specifically, I tested that `extern mod extra;` would
not cause it to try to find source for `extra` and compile it
again).

This is as per #5681.

Incidentally, I changed some driver code to infer the link name
from the crate link_meta attributes. If that change isn't ok, say
something. Also, I changed the addl_lib_search_paths field in the
session options to be an @mut ~[Path] so that it can be modified
after expansion but before later phases.
Diffstat (limited to 'src/librustpkg')
-rw-r--r--src/librustpkg/conditions.rs6
-rw-r--r--src/librustpkg/package_id.rs132
-rw-r--r--src/librustpkg/package_path.rs55
-rw-r--r--src/librustpkg/path_util.rs136
-rw-r--r--src/librustpkg/rustpkg.rc113
-rw-r--r--src/librustpkg/search.rs25
-rw-r--r--src/librustpkg/target.rs23
-rw-r--r--src/librustpkg/tests.rs35
-rw-r--r--src/librustpkg/testsuite/pass/src/external-crate/main.rs7
-rw-r--r--src/librustpkg/testsuite/pass/src/foo/lib.rs11
-rw-r--r--src/librustpkg/util.rs347
-rw-r--r--src/librustpkg/workspace.rs2
12 files changed, 557 insertions, 335 deletions
diff --git a/src/librustpkg/conditions.rs b/src/librustpkg/conditions.rs
index 680e0924d79..caab16cd291 100644
--- a/src/librustpkg/conditions.rs
+++ b/src/librustpkg/conditions.rs
@@ -11,14 +11,14 @@
 // Useful conditions
 
 pub use core::path::Path;
-pub use util::PkgId;
+pub use package_id::PkgId;
 
 condition! {
     bad_path: (super::Path, ~str) -> super::Path;
 }
 
 condition! {
-    nonexistent_package: (super::PkgId, ~str) -> ();
+    nonexistent_package: (super::PkgId, ~str) -> super::Path;
 }
 
 condition! {
@@ -30,5 +30,5 @@ condition! {
 }
 
 condition! {
-    bad_pkg_id: (super::Path, ~str) -> ::util::PkgId;
+    bad_pkg_id: (super::Path, ~str) -> super::PkgId;
 }
diff --git a/src/librustpkg/package_id.rs b/src/librustpkg/package_id.rs
new file mode 100644
index 00000000000..ca0210f469b
--- /dev/null
+++ b/src/librustpkg/package_id.rs
@@ -0,0 +1,132 @@
+// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+pub use package_path::{RemotePath, LocalPath, normalize, hash};
+use extra::semver;
+use core::prelude::*;
+use core::result;
+
+/// 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.
+pub struct PkgId {
+    /// 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 conditions::bad_pkg_id::cond;
+
+        let p = Path(s);
+        if p.is_absolute {
+            return cond.raise((p, ~"absolute 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 {
+            local_path: local_path,
+            remote_path: remote_path,
+            short_name: short_name,
+            version: default_version()
+        }
+    }
+
+    fn hash(&self) -> ~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.local_path.to_str(), self.version.to_str())
+    }
+}
+
+/// A version is either an exact revision,
+/// or a semantic version
+pub enum Version {
+    ExactRevision(float),
+    SemVersion(semver::Version)
+}
+
+
+impl Ord for Version {
+    fn lt(&self, other: &Version) -> bool {
+        match (self, other) {
+            (&ExactRevision(f1), &ExactRevision(f2)) => f1 < f2,
+            (&SemVersion(ref v1), &SemVersion(ref v2)) => v1 < v2,
+            _ => false // incomparable, really
+        }
+    }
+    fn le(&self, other: &Version) -> bool {
+        match (self, other) {
+            (&ExactRevision(f1), &ExactRevision(f2)) => f1 <= f2,
+            (&SemVersion(ref v1), &SemVersion(ref v2)) => v1 <= v2,
+            _ => false // incomparable, really
+        }
+    }
+    fn ge(&self, other: &Version) -> bool {
+        match (self, other) {
+            (&ExactRevision(f1), &ExactRevision(f2)) => f1 > f2,
+            (&SemVersion(ref v1), &SemVersion(ref v2)) => v1 > v2,
+            _ => false // incomparable, really
+        }
+    }
+    fn gt(&self, other: &Version) -> bool {
+        match (self, other) {
+            (&ExactRevision(f1), &ExactRevision(f2)) => f1 >= f2,
+            (&SemVersion(ref v1), &SemVersion(ref v2)) => v1 >= v2,
+            _ => false // incomparable, really
+        }
+    }
+
+}
+
+impl ToStr for Version {
+    fn to_str(&self) -> ~str {
+        match *self {
+            ExactRevision(ref n) => n.to_str(),
+            SemVersion(ref v) => v.to_str()
+        }
+    }
+}
+
+pub fn parse_vers(vers: ~str) -> result::Result<semver::Version, ~str> {
+    match semver::parse(vers) {
+        Some(vers) => result::Ok(vers),
+        None => result::Err(~"could not parse version: invalid")
+    }
+}
diff --git a/src/librustpkg/package_path.rs b/src/librustpkg/package_path.rs
new file mode 100644
index 00000000000..a54f9ad152f
--- /dev/null
+++ b/src/librustpkg/package_path.rs
@@ -0,0 +1,55 @@
+// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// rustpkg utilities having to do with local and remote paths
+
+use core::path::Path;
+use core::option::Some;
+use core::{hash, str};
+use core::rt::io::Writer;
+use core::hash::Streaming;
+
+/// Wrappers to prevent local and remote paths from getting confused
+/// (These will go away after #6407)
+pub struct RemotePath (Path);
+pub struct LocalPath (Path);
+
+
+// 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)
+            }
+        }
+    }
+}
+
+pub fn write<W: Writer>(writer: &mut W, string: &str) {
+    let buffer = str::as_bytes_slice(string);
+    writer.write(buffer);
+}
+
+pub fn hash(data: ~str) -> ~str {
+    let hasher = &mut hash::default_state();
+    write(hasher, data);
+    hasher.result_str()
+}
diff --git a/src/librustpkg/path_util.rs b/src/librustpkg/path_util.rs
index 2fca0419629..7b4a9a63a4e 100644
--- a/src/librustpkg/path_util.rs
+++ b/src/librustpkg/path_util.rs
@@ -11,9 +11,9 @@
 // rustpkg utilities having to do with paths and directories
 
 use core::prelude::*;
-
-pub use util::{PkgId, RemotePath, LocalPath};
-pub use util::{normalize, OutputType, Main, Lib, Bench, Test};
+pub use package_path::{RemotePath, LocalPath};
+pub use package_id::{PkgId, Version};
+pub use target::{OutputType, Main, Lib, Test, Bench, Target, Build, Install};
 use core::libc::consts::os::posix88::{S_IRUSR, S_IWUSR, S_IXUSR};
 use core::os::mkdir_recursive;
 use core::os;
@@ -32,22 +32,38 @@ 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) }
 
-// n.b. So far this only handles local workspaces
 // n.b. The next three functions ignore the package version right
 // now. Should fix that.
 
 /// 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.local_path.to_str());
+    let pkgpath = workspace.push("src").push(pkgid.remote_path.to_str());
     os::path_is_dir(&pkgpath)
 }
 
-/// Return the directory for <pkgid>'s source files in <workspace>.
-/// Doesn't check that it exists.
-pub fn pkgid_src_in_workspace(pkgid: &PkgId, workspace: &Path) -> Path {
-    let result = workspace.push("src");
-    result.push(pkgid.local_path.to_str())
+/// Returns a list of possible directories
+/// for <pkgid>'s source files in <workspace>.
+/// Doesn't check that any of them exist.
+/// (for example, try both with and without the version)
+pub fn pkgid_src_in_workspace(pkgid: &PkgId, workspace: &Path) -> ~[Path] {
+    let mut results = ~[];
+    let result = workspace.push("src").push(fmt!("%s-%s",
+                     pkgid.local_path.to_str(), pkgid.version.to_str()));
+    results.push(result);
+    results.push(workspace.push("src").push_rel(&*pkgid.remote_path));
+    results
+}
+
+/// Returns a src for pkgid that does exist -- None if none of them do
+pub fn first_pkgid_src_in_workspace(pkgid: &PkgId, workspace: &Path) -> Option<Path> {
+    let rs = pkgid_src_in_workspace(pkgid, workspace);
+    for rs.each |p| {
+        if os::path_exists(p) {
+            return Some(copy *p);
+        }
+    }
+    None
 }
 
 /// Figure out what the executable name for <pkgid> in <workspace>'s build
@@ -55,7 +71,7 @@ pub fn pkgid_src_in_workspace(pkgid: &PkgId, workspace: &Path) -> Path {
 pub fn built_executable_in_workspace(pkgid: &PkgId, workspace: &Path) -> Option<Path> {
     let mut result = workspace.push("build");
     // should use a target-specific subdirectory
-    result = mk_output_path(Main, pkgid, &result);
+    result = mk_output_path(Main, Build, pkgid, &result);
     debug!("built_executable_in_workspace: checking whether %s exists",
            result.to_str());
     if os::path_exists(&result) {
@@ -83,7 +99,7 @@ pub fn built_bench_in_workspace(pkgid: &PkgId, workspace: &Path) -> Option<Path>
 fn output_in_workspace(pkgid: &PkgId, workspace: &Path, what: OutputType) -> Option<Path> {
     let mut result = workspace.push("build");
     // should use a target-specific subdirectory
-    result = mk_output_path(what, pkgid, &result);
+    result = mk_output_path(what, Build, pkgid, &result);
     debug!("output_in_workspace: checking whether %s exists",
            result.to_str());
     if os::path_exists(&result) {
@@ -98,17 +114,39 @@ fn output_in_workspace(pkgid: &PkgId, workspace: &Path, what: OutputType) -> Opt
 /// 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());
+                        // passing in local_path here sounds fishy
+    library_in_workspace(pkgid.local_path.to_str(), pkgid.short_name, Build,
+                         workspace, "build")
+}
+
+/// Does the actual searching stuff
+pub fn installed_library_in_workspace(short_name: &str, workspace: &Path) -> Option<Path> {
+    library_in_workspace(short_name, short_name, Install, workspace, "lib")
+}
+
+
+/// This doesn't take a PkgId, so we can use it for `extern mod` inference, where we
+/// don't know the entire package ID.
+/// `full_name` is used to figure out the directory to search.
+/// `short_name` is taken as the link name of the library.
+fn library_in_workspace(full_name: &str, short_name: &str, where: Target,
+                        workspace: &Path, prefix: &str) -> Option<Path> {
+    debug!("library_in_workspace: checking whether a library named %s exists",
+           short_name);
 
     // 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());
+
+    let dir_to_search = match where {
+        Build => workspace.push(prefix).push(full_name),
+        Install => workspace.push(prefix)
+    };
+    debug!("Listing directory %s", dir_to_search.to_str());
+    let dir_contents = os::list_dir(&dir_to_search);
     debug!("dir has %? entries", dir_contents.len());
 
-    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);
+    let lib_prefix = fmt!("%s%s", os::consts::DLL_PREFIX, short_name);
+    let lib_filetype = os::consts::DLL_SUFFIX;
 
     debug!("lib_prefix = %s and lib_filetype = %s", lib_prefix, lib_filetype);
 
@@ -116,9 +154,19 @@ pub fn built_library_in_workspace(pkgid: &PkgId, workspace: &Path) -> Option<Pat
     for dir_contents.each |&p| {
         let mut which = 0;
         let mut hash = None;
+        let p_path = Path(p);
+        let extension = p_path.filetype();
+        debug!("p = %s, p's extension is %?", p.to_str(), extension);
+        match extension {
+            Some(ref s) if lib_filetype == *s => (),
+            _ => loop
+        }
         // 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| {
+        let f_name = match p_path.filename() {
+            Some(s) => s, None => loop
+        };
+        for f_name.each_split_char('-') |piece| {
             debug!("a piece = %s", piece);
             if which == 0 && piece != lib_prefix {
                 break;
@@ -128,13 +176,6 @@ pub fn built_library_in_workspace(pkgid: &PkgId, workspace: &Path) -> Option<Pat
             }
             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 {
@@ -144,20 +185,19 @@ pub fn built_library_in_workspace(pkgid: &PkgId, workspace: &Path) -> Option<Pat
             }
         }
         if hash.is_some() {
-            result_filename = Some(p);
+            result_filename = Some(p_path);
             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)
+            let absolute_path = dir_to_search.push_rel(&result_filename);
+            debug!("result_filename = %s", absolute_path.to_str());
+            Some(absolute_path)
         }
     }
 }
@@ -166,33 +206,36 @@ pub fn built_library_in_workspace(pkgid: &PkgId, workspace: &Path) -> Option<Pat
 /// 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 {
-    target_file_in_workspace(pkgid, workspace, Main)
+    target_file_in_workspace(pkgid, workspace, Main, Install)
 }
 
 
 /// Returns the executable that would be installed for <pkgid>
 /// in <workspace>
-/// As a side effect, creates the bin-dir if it doesn't exist
+/// As a side effect, creates the lib-dir if it doesn't exist
 pub fn target_library_in_workspace(pkgid: &PkgId, workspace: &Path) -> Path {
-    target_file_in_workspace(pkgid, workspace, Lib)
+    target_file_in_workspace(pkgid, workspace, Lib, Install)
 }
 
 /// 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)
+    target_file_in_workspace(pkgid, workspace, Test, Install)
 }
 
 /// 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)
+    target_file_in_workspace(pkgid, workspace, Bench, Install)
 }
 
+
+/// Returns the path that pkgid `pkgid` would have if placed `where`
+/// in `workspace`
 fn target_file_in_workspace(pkgid: &PkgId, workspace: &Path,
-                            what: OutputType) -> Path {
+                            what: OutputType, where: Target) -> Path {
     use conditions::bad_path::cond;
 
     let subdir = match what {
@@ -202,7 +245,7 @@ fn target_file_in_workspace(pkgid: &PkgId, workspace: &Path,
     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, &result)
+    mk_output_path(what, where, pkgid, &result)
 }
 
 /// Return the directory for <pkgid>'s build artifacts in <workspace>.
@@ -224,15 +267,21 @@ 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, pkg_id: &PkgId, workspace: &Path) -> Path {
+pub fn mk_output_path(what: OutputType, where: Target,
+                      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",
+    let dir = match where {
+        // If we're installing, it just goes under <workspace>...
+        Install => copy *workspace, // bad copy, but I just couldn't make the borrow checker happy
+        // and if we're just building, it goes in a package-specific subdir
+        Build => workspace.push_rel(&*pkg_id.local_path)
+    };
+    debug!("[%?:%?] mk_output_path: short_name = %s, path = %s", what, where,
            if what == Lib { copy short_name_with_version } else { copy pkg_id.short_name },
            dir.to_str());
-    let output_path = match what {
+    let mut 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
@@ -244,6 +293,9 @@ pub fn mk_output_path(what: OutputType, pkg_id: &PkgId, workspace: &Path) -> Pat
                            }
                            os::EXE_SUFFIX))
     };
+    if !output_path.is_absolute() {
+        output_path = os::getcwd().push_rel(&output_path).normalize();
+    }
     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 ada03e777ca..005f2de3f42 100644
--- a/src/librustpkg/rustpkg.rc
+++ b/src/librustpkg/rustpkg.rc
@@ -35,15 +35,21 @@ use rustc::metadata::filesearch;
 use extra::{getopts};
 use syntax::{ast, diagnostic};
 use util::*;
-use path_util::{build_pkg_id_in_workspace, pkgid_src_in_workspace, u_rwx};
+use path_util::{build_pkg_id_in_workspace, pkgid_src_in_workspace, first_pkgid_src_in_workspace};
+use path_util::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 context::Ctx;
+use package_id::PkgId;
 
 mod conditions;
 mod context;
+mod package_id;
+mod package_path;
 mod path_util;
+mod search;
+mod target;
 #[cfg(test)]
 mod tests;
 mod util;
@@ -99,8 +105,7 @@ impl<'self> PkgScript<'self> {
         let input = driver::file_input(script);
         let sess = driver::build_session(options, diagnostic::emit);
         let cfg = driver::build_configuration(sess, binary, &input);
-        let (crate, _) = driver::compile_upto(sess, copy cfg, &input,
-                                              driver::cu_parse, None);
+        let (crate, _) = driver::compile_upto(sess, copy cfg, &input, driver::cu_parse, None);
         let work_dir = build_pkg_id_in_workspace(id, workspace);
 
         debug!("Returning package script with id %?", id);
@@ -134,11 +139,13 @@ impl<'self> PkgScript<'self> {
                 let root = r.pop().pop().pop().pop(); // :-\
                 debug!("Root is %s, calling compile_rest", root.to_str());
                 let exe = self.build_dir.push(~"pkg" + util::exe_suffix());
-                util::compile_crate_from_input(&self.input, self.id,
-                                               Some(copy self.build_dir),
-                                               sess, Some(crate),
-                                               &exe, @copy os::args()[0],
-                                               driver::cu_everything);
+                let binary = @copy os::args()[0];
+                util::compile_crate_from_input(&self.input,
+                                               &self.build_dir,
+                                               sess,
+                                               crate,
+                                               driver::build_configuration(sess,
+                                                                           binary, &self.input));
                 debug!("Running program: %s %s %s", exe.to_str(), root.to_str(), what);
                 let status = run::process_status(exe.to_str(), [root.to_str(), what]);
                 if status != 0 {
@@ -170,9 +177,9 @@ impl<'self> PkgScript<'self> {
 
 impl Ctx {
 
-    fn run(&self, cmd: ~str, args: ~[~str]) {
+    fn run(&self, cmd: &str, args: ~[~str]) {
         match cmd {
-            ~"build" => {
+            "build" => {
                 if args.len() < 1 {
                     return usage::build();
                 }
@@ -183,7 +190,7 @@ impl Ctx {
                     self.build(workspace, &pkgid);
                 }
             }
-            ~"clean" => {
+            "clean" => {
                 if args.len() < 1 {
                     return usage::build();
                 }
@@ -193,17 +200,17 @@ impl Ctx {
                 let cwd = os::getcwd();
                 self.clean(&cwd, &pkgid); // tjc: should use workspace, not cwd
             }
-            ~"do" => {
+            "do" => {
                 if args.len() < 2 {
                     return usage::do_cmd();
                 }
 
                 self.do_cmd(copy args[0], copy args[1]);
             }
-            ~"info" => {
+            "info" => {
                 self.info();
             }
-            ~"install" => {
+            "install" => {
                 if args.len() < 1 {
                     return usage::install();
                 }
@@ -215,24 +222,24 @@ impl Ctx {
                     self.install(workspace, &pkgid);
                 }
             }
-            ~"prefer" => {
+            "prefer" => {
                 if args.len() < 1 {
                     return usage::uninstall();
                 }
 
                 self.prefer(args[0], None);
             }
-            ~"test" => {
+            "test" => {
                 self.test();
             }
-            ~"uninstall" => {
+            "uninstall" => {
                 if args.len() < 1 {
                     return usage::uninstall();
                 }
 
                 self.uninstall(args[0], None);
             }
-            ~"unprefer" => {
+            "unprefer" => {
                 if args.len() < 1 {
                     return usage::uninstall();
                 }
@@ -249,7 +256,7 @@ impl Ctx {
     }
 
     fn build(&self, workspace: &Path, pkgid: &PkgId) {
-        let src_dir   = pkgid_src_in_workspace(pkgid, workspace);
+        let src_dir   = first_pkgid_src_in_workspace(pkgid, workspace);
         let build_dir = build_pkg_id_in_workspace(pkgid, workspace);
         debug!("Destination dir = %s", build_dir.to_str());
 
@@ -260,8 +267,8 @@ impl Ctx {
         // 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) {
+        debug!("Package source directory = %?", pkg_src_dir);
+        let cfgs = match pkg_src_dir.chain_ref(|p| src.package_script_option(p)) {
             Some(package_script_path) => {
                 let pscript = PkgScript::parse(package_script_path,
                                                workspace,
@@ -290,7 +297,7 @@ impl Ctx {
             // Find crates inside the workspace
             src.find_crates();
             // Build it!
-            src.build(&build_dir, cfgs, self.sysroot_opt);
+            src.build(self, build_dir, cfgs);
         }
     }
 
@@ -352,7 +359,7 @@ impl Ctx {
     }
 
     fn prefer(&self, _id: &str, _vers: Option<~str>)  {
-        fail!(~"prefer not yet implemented");
+        fail!("prefer not yet implemented");
     }
 
     fn test(&self)  {
@@ -522,21 +529,20 @@ impl PkgSrc {
     fn check_dir(&self) -> Path {
         use conditions::nonexistent_package::cond;
 
-        debug!("Pushing onto root: %s | %s", self.id.to_str(),
+        debug!("Pushing onto root: %s | %s", self.id.remote_path.to_str(),
                self.root.to_str());
-
-        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());
-
-        if !os::path_exists(&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"));
+        let dir;
+        let dirs = pkgid_src_in_workspace(&self.id, &self.root);
+        debug!("Checking dirs: %?", dirs);
+        let path = dirs.find(|d| os::path_exists(d));
+        match path {
+            Some(d) => dir = d,
+            None => dir = match self.fetch_git() {
+                None => cond.raise((copy self.id, ~"supplied path for package dir does not \
+                                      exist, and couldn't interpret it as a URL fragment")),
+                Some(d) => d
             }
         }
-
         if !os::path_is_dir(&dir) {
             cond.raise((copy self.id, ~"supplied path for package dir is a \
                                         non-directory"));
@@ -546,10 +552,10 @@ impl PkgSrc {
     }
 
     /// 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.
+    /// fetching it and caching it in a local directory. Return the cached directory
+    /// if this was successful, None otherwise
     /// (right now we only support git)
-    fn fetch_git(&self) -> bool {
+    fn fetch_git(&self) -> Option<Path> {
 
         let mut local = self.root.push("src");
         local = local.push(self.id.to_str());
@@ -561,9 +567,11 @@ impl PkgSrc {
 
         if run::process_output("git", [~"clone", copy url, local.to_str()]).status != 0 {
             util::note(fmt!("fetching %s failed: can't clone repository", url));
-            return false;
+            None
+        }
+        else {
+            Some(local)
         }
-        true
     }
 
 
@@ -655,7 +663,7 @@ impl PkgSrc {
     }
 
     fn build_crates(&self,
-                    maybe_sysroot: Option<@Path>,
+                    ctx: &Ctx,
                     dst_dir: &Path,
                     src_dir: &Path,
                     crates: &[Crate],
@@ -666,11 +674,14 @@ impl PkgSrc {
             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(maybe_sysroot, &self.id, path,
-                                     dst_dir,
-                                     crate.flags,
-                                     crate.cfgs + cfgs,
-                                     false, what);
+            let result = util::compile_crate(ctx,
+                                             &self.id,
+                                             path,
+                                             dst_dir,
+                                             crate.flags,
+                                             crate.cfgs + cfgs,
+                                             false,
+                                             what);
             if !result {
                 build_err::cond.raise(fmt!("build failure on %s",
                                            path.to_str()));
@@ -680,15 +691,15 @@ impl PkgSrc {
         }
     }
 
-    fn build(&self, dst_dir: &Path, cfgs: ~[~str], maybe_sysroot: Option<@Path>) {
+    fn build(&self, ctx: &Ctx, dst_dir: Path, cfgs: ~[~str]) {
         let dir = self.check_dir();
         debug!("Building libs");
-        self.build_crates(maybe_sysroot, dst_dir, &dir, self.libs, cfgs, Lib);
+        self.build_crates(ctx, &dst_dir, &dir, self.libs, cfgs, Lib);
         debug!("Building mains");
-        self.build_crates(maybe_sysroot, dst_dir, &dir, self.mains, cfgs, Main);
+        self.build_crates(ctx, &dst_dir, &dir, self.mains, cfgs, Main);
         debug!("Building tests");
-        self.build_crates(maybe_sysroot, dst_dir, &dir, self.tests, cfgs, Test);
+        self.build_crates(ctx, &dst_dir, &dir, self.tests, cfgs, Test);
         debug!("Building benches");
-        self.build_crates(maybe_sysroot, dst_dir, &dir, self.benchs, cfgs, Bench);
+        self.build_crates(ctx, &dst_dir, &dir, self.benchs, cfgs, Bench);
     }
 }
diff --git a/src/librustpkg/search.rs b/src/librustpkg/search.rs
new file mode 100644
index 00000000000..987e01009fc
--- /dev/null
+++ b/src/librustpkg/search.rs
@@ -0,0 +1,25 @@
+// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use path_util::installed_library_in_workspace;
+use core::prelude::*;
+
+/// If a library with path `p` matching pkg_id's name exists under sroot_opt,
+/// return Some(p). Return None if there's no such path or if sroot_opt is None.
+pub fn find_library_in_search_path(sroot_opt: Option<@Path>, short_name: &str) -> Option<Path> {
+    match sroot_opt {
+        Some(sroot) => {
+            debug!("Will search for a library with short name %s in \
+                    %s", short_name, (sroot.push("lib")).to_str());
+            installed_library_in_workspace(short_name, sroot)
+        }
+        None => None
+    }
+}
\ No newline at end of file
diff --git a/src/librustpkg/target.rs b/src/librustpkg/target.rs
new file mode 100644
index 00000000000..03c2f5a4fe4
--- /dev/null
+++ b/src/librustpkg/target.rs
@@ -0,0 +1,23 @@
+// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+
+// Data types that express build artifacts
+
+#[deriving(Eq)]
+pub enum OutputType { Main, Lib, Bench, Test }
+
+#[deriving(Eq)]
+pub enum Target {
+    // In-place build
+    Build,
+    // Install to bin/ or lib/ dir
+    Install
+}
diff --git a/src/librustpkg/tests.rs b/src/librustpkg/tests.rs
index 9499430ef4c..a96a7a0a5fc 100644
--- a/src/librustpkg/tests.rs
+++ b/src/librustpkg/tests.rs
@@ -17,10 +17,11 @@ use core::os;
 use core::prelude::*;
 use core::result;
 use extra::tempfile::mkdtemp;
-use util::{PkgId, default_version};
+use package_path::*;
+use package_id::{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, RemotePath, LocalPath, normalize,
+               make_dir_rwx, u_rwx,
                built_bench_in_workspace, built_test_in_workspace};
 
 fn fake_ctxt(sysroot_opt: Option<@Path>) -> Ctx {
@@ -52,7 +53,7 @@ fn remote_pkg() -> PkgId {
     }
 }
 
-fn writeFile(file_path: &Path, contents: ~str) {
+fn writeFile(file_path: &Path, contents: &str) {
     let out: @io::Writer =
         result::get(&io::file_writer(file_path,
                                      [io::Create, io::Truncate]));
@@ -64,15 +65,17 @@ fn mk_temp_workspace(short_name: &LocalPath) -> Path {
     // 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));
+    debug!("Created %s and does it exist? %?", package_dir.to_str(),
+          os::path_is_dir(&package_dir));
     // Create main, lib, test, and bench files
     writeFile(&package_dir.push("main.rs"),
-              ~"fn main() { let _x = (); }");
+              "fn main() { let _x = (); }");
     writeFile(&package_dir.push("lib.rs"),
-              ~"pub fn f() { let _x = (); }");
+              "pub fn f() { let _x = (); }");
     writeFile(&package_dir.push("test.rs"),
-              ~"#[test] pub fn f() { (); }");
+              "#[test] pub fn f() { (); }");
     writeFile(&package_dir.push("bench.rs"),
-              ~"#[bench] pub fn f() { (); }");
+              "#[bench] pub fn f() { (); }");
     workspace
 }
 
@@ -111,6 +114,8 @@ fn test_make_dir_rwx() {
 
 #[test]
 fn test_install_valid() {
+    use path_util::installed_library_in_workspace;
+
     let sysroot = test_sysroot();
     debug!("sysroot = %s", sysroot.to_str());
     let ctxt = fake_ctxt(Some(@sysroot));
@@ -123,10 +128,12 @@ fn test_install_valid() {
     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));
+
+    let lib = installed_library_in_workspace(temp_pkg_id.short_name, &temp_workspace);
+    debug!("lib = %?", lib);
+    assert!(lib.map_default(false, |l| os::path_exists(l)));
+    assert!(lib.map_default(false, |l| is_rwx(l)));
+
     // 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);
@@ -149,6 +156,7 @@ fn test_install_invalid() {
     }).in {
         do cond.trap(|_| {
             error_occurred = true;
+            copy temp_workspace
         }).in {
             ctxt.install(&temp_workspace, &pkgid);
         }
@@ -174,10 +182,11 @@ fn test_install_url() {
     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");
+    let built_test = built_test_in_workspace(&temp_pkg_id,
+                         &workspace).expect("test_install_url: built test should exist");
     assert!(os::path_exists(&built_test));
     let built_bench = built_bench_in_workspace(&temp_pkg_id,
-                                               &workspace).expect("test_install_url");
+                          &workspace).expect("test_install_url: built bench should exist");
     assert!(os::path_exists(&built_bench));
     // And that the test and bench executables aren't installed
     let test = target_test_in_workspace(&temp_pkg_id, &workspace);
diff --git a/src/librustpkg/testsuite/pass/src/external-crate/main.rs b/src/librustpkg/testsuite/pass/src/external-crate/main.rs
index d094bcd6bba..1e5c1d5e627 100644
--- a/src/librustpkg/testsuite/pass/src/external-crate/main.rs
+++ b/src/librustpkg/testsuite/pass/src/external-crate/main.rs
@@ -13,9 +13,12 @@ The test runner should check that, after `rustpkg install external crate`
   with RUST_PATH undefined in the environment
   and with `rustpkg install deeply/nested/path/foo` already
      executed:
-   * ./.rust/external_crate exists and is an executable
+   * ../bin/external_crate exists and is an executable
+
+  tjc: Also want a test like this where foo is an external URL,
+    which requires the `extern mod` changes
 */
 
-extern mod foo; // refers to deeply/nested/path/foo
+extern mod foo;
 
 fn main() {}
diff --git a/src/librustpkg/testsuite/pass/src/foo/lib.rs b/src/librustpkg/testsuite/pass/src/foo/lib.rs
new file mode 100644
index 00000000000..91fc466f8c6
--- /dev/null
+++ b/src/librustpkg/testsuite/pass/src/foo/lib.rs
@@ -0,0 +1,11 @@
+// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+fn f() {}
diff --git a/src/librustpkg/util.rs b/src/librustpkg/util.rs
index 34fd719fc4c..c5a5aaea178 100644
--- a/src/librustpkg/util.rs
+++ b/src/librustpkg/util.rs
@@ -9,23 +9,28 @@
 // except according to those terms.
 
 use core::prelude::*;
-use core::*;
-use core::cmp::Ord;
-use core::hash::Streaming;
-use core::rt::io::Writer;
+use core::{io, libc, os, result, str};
 use rustc::driver::{driver, session};
 use rustc::metadata::filesearch;
 use extra::getopts::groups::getopts;
-use extra::semver;
 use extra::term;
+#[cfg(not(test))]
+use extra::getopts;
 use syntax::ast_util::*;
-use syntax::codemap::{dummy_sp, spanned, dummy_spanned};
+use syntax::codemap::{dummy_sp, spanned};
+use syntax::codemap::dummy_spanned;
 use syntax::ext::base::ExtCtxt;
 use syntax::{ast, attr, codemap, diagnostic, fold};
 use syntax::ast::{meta_name_value, meta_list};
 use syntax::attr::{mk_attr};
 use rustc::back::link::output_type_exe;
+use rustc::driver::driver::compile_upto;
 use rustc::driver::session::{lib_crate, bin_crate};
+use context::Ctx;
+use package_id::PkgId;
+use path_util::target_library_in_workspace;
+use search::find_library_in_search_path;
+pub use target::{OutputType, Main, Lib, Bench, Test};
 
 static Commands: &'static [&'static str] =
     &["build", "clean", "do", "info", "install", "prefer", "test", "uninstall",
@@ -34,119 +39,6 @@ static Commands: &'static [&'static str] =
 
 pub type ExitCode = int; // For now
 
-/// A version is either an exact revision,
-/// or a semantic version
-pub enum Version {
-    ExactRevision(float),
-    SemVersion(semver::Version)
-}
-
-impl Ord for Version {
-    fn lt(&self, other: &Version) -> bool {
-        match (self, other) {
-            (&ExactRevision(f1), &ExactRevision(f2)) => f1 < f2,
-            (&SemVersion(ref v1), &SemVersion(ref v2)) => v1 < v2,
-            _ => false // incomparable, really
-        }
-    }
-    fn le(&self, other: &Version) -> bool {
-        match (self, other) {
-            (&ExactRevision(f1), &ExactRevision(f2)) => f1 <= f2,
-            (&SemVersion(ref v1), &SemVersion(ref v2)) => v1 <= v2,
-            _ => false // incomparable, really
-        }
-    }
-    fn ge(&self, other: &Version) -> bool {
-        match (self, other) {
-            (&ExactRevision(f1), &ExactRevision(f2)) => f1 > f2,
-            (&SemVersion(ref v1), &SemVersion(ref v2)) => v1 > v2,
-            _ => false // incomparable, really
-        }
-    }
-    fn gt(&self, other: &Version) -> bool {
-        match (self, other) {
-            (&ExactRevision(f1), &ExactRevision(f2)) => f1 >= f2,
-            (&SemVersion(ref v1), &SemVersion(ref v2)) => v1 >= v2,
-            _ => false // incomparable, really
-        }
-    }
-
-}
-
-impl ToStr for Version {
-    fn to_str(&self) -> ~str {
-        match *self {
-            ExactRevision(ref n) => n.to_str(),
-            SemVersion(ref v) => v.to_str()
-        }
-    }
-}
-
-#[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.
-pub struct PkgId {
-    /// 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
-}
-
-impl PkgId {
-    pub fn new(s: &str) -> PkgId {
-        use conditions::bad_pkg_id::cond;
-
-        let p = Path(s);
-        if p.is_absolute {
-            return cond.raise((p, ~"absolute 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 {
-            local_path: local_path,
-            remote_path: remote_path,
-            short_name: short_name,
-            version: default_version()
-        }
-    }
-
-    pub fn hash(&self) -> ~str {
-        fmt!("%s-%s-%s", self.remote_path.to_str(),
-             hash(self.remote_path.to_str() + self.version.to_str()),
-             self.version.to_str())
-    }
-
-    pub 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.local_path.to_str(), self.version.to_str())
-    }
-}
-
 pub struct Pkg {
     id: PkgId,
     bins: ~[~str],
@@ -264,13 +156,6 @@ pub fn ready_crate(sess: session::Session,
     @fold.fold_crate(crate)
 }
 
-pub fn parse_vers(vers: ~str) -> result::Result<semver::Version, ~str> {
-    match semver::parse(vers) {
-        Some(vers) => result::Ok(vers),
-        None => result::Err(~"could not parse version: invalid")
-    }
-}
-
 pub fn need_dir(s: &Path) {
     if !os::path_is_dir(s) && !os::make_dir(s, 493_i32) {
         fail!("can't create dir: %s", s.to_str());
@@ -316,15 +201,8 @@ pub fn error(msg: ~str) {
     }
 }
 
-pub fn hash(data: ~str) -> ~str {
-    let mut hasher = hash::default_state();
-    let buffer = str::as_bytes_slice(data);
-    hasher.write(buffer);
-    hasher.result_str()
-}
-
 // FIXME (#4432): Use workcache to only compile when needed
-pub fn compile_input(sysroot: Option<@Path>,
+pub fn compile_input(ctxt: &Ctx,
                      pkg_id: &PkgId,
                      in_file: &Path,
                      out_dir: &Path,
@@ -333,6 +211,8 @@ pub fn compile_input(sysroot: Option<@Path>,
                      opt: bool,
                      what: OutputType) -> bool {
 
+    let workspace = out_dir.pop().pop();
+
     assert!(in_file.components.len() > 1);
     let input = driver::file_input(copy *in_file);
     debug!("compile_input: %s / %?", in_file.to_str(), what);
@@ -340,23 +220,10 @@ pub fn compile_input(sysroot: Option<@Path>,
     // not sure if we should support anything else
 
     let binary = @(copy os::args()[0]);
-    let building_library = what == Lib;
-
-    let out_file = if building_library {
-        out_dir.push(os::dll_filename(pkg_id.short_name))
-    }
-    else {
-        out_dir.push(pkg_id.short_name + match what {
-            Test => ~"test", Bench => ~"bench", Main | Lib => ~""
-        } + os::EXE_SUFFIX)
-    };
 
-    debug!("compiling %s into %s",
-           in_file.to_str(),
-           out_file.to_str());
     debug!("flags: %s", str::connect(flags, " "));
     debug!("cfgs: %s", str::connect(cfgs, " "));
-    debug!("compile_input's sysroot = %?", sysroot);
+    debug!("compile_input's sysroot = %?", ctxt.sysroot_opt);
 
     let crate_type = match what {
         Lib => lib_crate,
@@ -372,28 +239,62 @@ pub fn compile_input(sysroot: Option<@Path>,
                           + flags
                           + cfgs.flat_map(|&c| { ~[~"--cfg", c] }),
                           driver::optgroups()).get();
-    let mut options = session::options {
+    let options = @session::options {
         crate_type: crate_type,
         optimize: if opt { session::Aggressive } else { session::No },
         test: what == Test || what == Bench,
-        maybe_sysroot: sysroot,
-        addl_lib_search_paths: ~[copy *out_dir],
+        maybe_sysroot: ctxt.sysroot_opt,
+        addl_lib_search_paths: @mut ~[copy *out_dir],
         // output_type should be conditional
         output_type: output_type_exe, // Use this to get a library? That's weird
         .. copy *driver::build_session_options(binary, &matches, diagnostic::emit)
     };
 
-    for cfgs.each |&cfg| {
-        options.cfg.push(attr::mk_word_item(@cfg));
-    }
+    let addl_lib_search_paths = @mut options.addl_lib_search_paths;
 
-    let sess = driver::build_session(@options, diagnostic::emit);
+    let sess = driver::build_session(options, diagnostic::emit);
+
+    // Infer dependencies that rustpkg needs to build, by scanning for
+    // `extern mod` directives.
+    let cfg = driver::build_configuration(sess, binary, &input);
+    let (crate_opt, _) = driver::compile_upto(sess, copy cfg, &input, driver::cu_expand, None);
+
+    let mut crate = match crate_opt {
+        Some(c) => c,
+        None => fail!("compile_input expected...")
+    };
+
+    // Not really right. Should search other workspaces too, and the installed
+    // database (which doesn't exist yet)
+    find_and_install_dependencies(ctxt, sess, &workspace, crate,
+                                  |p| {
+                                      debug!("a dependency: %s", p.to_str());
+                                      // Pass the directory containing a dependency
+                                      // as an additional lib search path
+                                      addl_lib_search_paths.push(p);
+                                  });
+
+    // Inject the link attributes so we get the right package name and version
+    if attr::find_linkage_metas(crate.node.attrs).is_empty() {
+        let short_name_to_use = match what {
+            Test  => fmt!("%stest", pkg_id.short_name),
+            Bench => fmt!("%sbench", pkg_id.short_name),
+            _     => copy pkg_id.short_name
+        };
+        debug!("Injecting link name: %s", short_name_to_use);
+        crate = @codemap::respan(crate.span, ast::crate_ {
+            attrs: ~[mk_attr(@dummy_spanned(
+                meta_list(@~"link",
+                 ~[@dummy_spanned(meta_name_value(@~"name",
+                                      mk_string_lit(@short_name_to_use))),
+                   @dummy_spanned(meta_name_value(@~"vers",
+                                      mk_string_lit(@(copy pkg_id.version.to_str()))))])))],
+            ..copy crate.node});
+    }
 
     debug!("calling compile_crate_from_input, out_dir = %s,
            building_library = %?", out_dir.to_str(), sess.building_library);
-    let _ = compile_crate_from_input(&input, pkg_id, Some(copy *out_dir), sess,
-                                     None, &out_file, binary,
-                                     driver::cu_everything);
+    compile_crate_from_input(&input, out_dir, sess, crate, copy cfg);
     true
 }
 
@@ -403,52 +304,31 @@ pub fn compile_input(sysroot: Option<@Path>,
 // call compile_upto and return the crate
 // also, too many arguments
 pub fn compile_crate_from_input(input: &driver::input,
-                                pkg_id: &PkgId,
-                                build_dir_opt: Option<Path>,
+                                build_dir: &Path,
                                 sess: session::Session,
-                                crate_opt: Option<@ast::crate>,
-                                out_file: &Path,
-                                binary: @~str,
-                                what: driver::compile_upto) -> @ast::crate {
-    debug!("Calling build_output_filenames with %? and %s", build_dir_opt, out_file.to_str());
-    let outputs = driver::build_output_filenames(input, &build_dir_opt,
-                                                 &Some(copy *out_file), sess);
-    debug!("Outputs are %? and output type = %?", outputs, sess.opts.output_type);
-    let cfg = driver::build_configuration(sess, binary, input);
-    match crate_opt {
-        Some(c) => {
-            debug!("Calling compile_rest, outputs = %?", outputs);
-            assert_eq!(what, driver::cu_everything);
-            driver::compile_rest(sess, cfg, driver::cu_everything, Some(outputs), Some(c));
-            c
-        }
-        None => {
-            debug!("Calling compile_upto, outputs = %?", outputs);
-            let (crate, _) = driver::compile_upto(sess, copy cfg, input,
-                                                  driver::cu_parse, Some(outputs));
-            let mut crate = crate.unwrap();
-
-            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)
-
-            debug!("How many attrs? %?", attr::find_linkage_metas(crate.node.attrs).len());
-
-            if attr::find_linkage_metas(crate.node.attrs).is_empty() {
-                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});
-            }
+                                crate: @ast::crate,
+                                cfg: ast::crate_cfg) {
+    debug!("Calling build_output_filenames with %s, building library? %?",
+           build_dir.to_str(), sess.building_library);
 
-            driver::compile_rest(sess, cfg, what, Some(outputs), Some(crate));
-            crate
-        }
+    // bad copy
+    let outputs = driver::build_output_filenames(input, &Some(copy *build_dir), &None,
+                                                 crate.node.attrs, sess);
+
+    debug!("Outputs are %? and output type = %?", outputs, sess.opts.output_type);
+    debug!("additional libraries:");
+    for sess.opts.addl_lib_search_paths.each |lib| {
+        debug!("an additional library: %s", lib.to_str());
     }
+
+    driver::compile_rest(sess,
+                         cfg,
+                         compile_upto {
+                             from: driver::cu_expand,
+                             to: driver::cu_everything
+                         },
+                         Some(outputs),
+                         Some(crate));
 }
 
 #[cfg(windows)]
@@ -462,7 +342,7 @@ pub fn exe_suffix() -> ~str { ~"" }
 
 // 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(ctxt: &Ctx, pkg_id: &PkgId,
                      crate: &Path, dir: &Path,
                      flags: &[~str], cfgs: &[~str], opt: bool,
                      what: OutputType) -> bool {
@@ -471,26 +351,51 @@ pub fn compile_crate(sysroot: Option<@Path>, pkg_id: &PkgId,
     for flags.each |&fl| {
         debug!("+++ %s", fl);
     }
-    compile_input(sysroot, pkg_id, crate, dir, flags, cfgs, opt, what)
+    compile_input(ctxt, 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)
+/// Collect all `extern mod` directives in `c`, then
+/// try to install their targets, failing if any target
+/// can't be found.
+fn find_and_install_dependencies(ctxt: &Ctx,
+                                 sess: session::Session,
+                                 workspace: &Path,
+                                 c: &ast::crate,
+                                 save: @fn(Path)
+                                ) {
+    // :-(
+    debug!("In find_and_install_dependencies...");
+    let my_workspace = copy *workspace;
+    let my_ctxt      = copy *ctxt;
+    for c.each_view_item() |vi: @ast::view_item| {
+        debug!("A view item!");
+        match vi.node {
+            // ignore metadata, I guess
+            ast::view_item_extern_mod(lib_ident, _, _) => {
+                match my_ctxt.sysroot_opt {
+                    Some(ref x) => debug!("sysroot: %s", x.to_str()),
+                    None => ()
+                };
+                let lib_name = sess.str_of(lib_ident);
+                match find_library_in_search_path(my_ctxt.sysroot_opt, *lib_name) {
+                    Some(installed_path) => {
+                        debug!("It exists: %s", installed_path.to_str());
+                    }
+                    None => {
+                        // Try to install it
+                        let pkg_id = PkgId::new(*lib_name);
+                        my_ctxt.install(&my_workspace, &pkg_id);
+                        // Also, add an additional search path
+                        let installed_path = target_library_in_workspace(&pkg_id,
+                                                                         &my_workspace).pop();
+                        debug!("Great, I installed %s, and it's in %s",
+                               *lib_name, installed_path.to_str());
+                        save(installed_path);
+                    }
+                }
             }
+            // Ignore `use`s
+            _ => ()
         }
     }
 }
@@ -526,10 +431,6 @@ 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;
diff --git a/src/librustpkg/workspace.rs b/src/librustpkg/workspace.rs
index cb9f735bce8..3010e27385f 100644
--- a/src/librustpkg/workspace.rs
+++ b/src/librustpkg/workspace.rs
@@ -11,7 +11,7 @@
 // rustpkg utilities having to do with workspaces
 
 use path_util::{rust_path, workspace_contains_package_id};
-use util::PkgId;
+use package_id::PkgId;
 use core::path::Path;
 
 pub fn pkg_parent_workspaces(pkgid: &PkgId, action: &fn(&Path) -> bool) -> bool {