about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2013-06-01 19:22:42 -0700
committerbors <bors@rust-lang.org>2013-06-01 19:22:42 -0700
commitfc5debd8fdda339e1453dc43368f4bfb15459841 (patch)
treea445c8ab39274ce9520ff898a6b71f3702a09b59 /src
parent63417daea4058646ef07a880edc79653ddc16bbf (diff)
parent60126e9365e86f9697508455290850564ac9186e (diff)
downloadrust-fc5debd8fdda339e1453dc43368f4bfb15459841.tar.gz
rust-fc5debd8fdda339e1453dc43368f4bfb15459841.zip
auto merge of #6807 : catamorphism/rust/rustpkg-extern-mod, r=catamorphism
r? @graydon Addresses #5681
Diffstat (limited to 'src')
-rw-r--r--src/librustc/back/rpath.rs2
-rw-r--r--src/librustc/driver/driver.rs273
-rw-r--r--src/librustc/driver/session.rs6
-rw-r--r--src/librustc/metadata/filesearch.rs7
-rw-r--r--src/librustc/middle/typeck/astconv.rs2
-rw-r--r--src/librustc/rustc.rc10
-rw-r--r--src/librusti/rusti.rc13
-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
-rw-r--r--src/libsyntax/ast_util.rs15
-rw-r--r--src/test/run-pass/extern-mod-url.rs16
21 files changed, 762 insertions, 474 deletions
diff --git a/src/librustc/back/rpath.rs b/src/librustc/back/rpath.rs
index 16d10d0a35e..a84cb2cdbe1 100644
--- a/src/librustc/back/rpath.rs
+++ b/src/librustc/back/rpath.rs
@@ -178,7 +178,7 @@ pub fn get_absolute_rpath(lib: &Path) -> Path {
 pub fn get_install_prefix_rpath(target_triple: &str) -> Path {
     let install_prefix = env!("CFG_PREFIX");
 
-    if install_prefix == ~"" {
+    if install_prefix.is_empty() {
         fail!("rustc compiled without CFG_PREFIX environment variable");
     }
 
diff --git a/src/librustc/driver/driver.rs b/src/librustc/driver/driver.rs
index 5e33a8f78fe..9e23c7df212 100644
--- a/src/librustc/driver/driver.rs
+++ b/src/librustc/driver/driver.rs
@@ -161,8 +161,15 @@ pub fn parse_input(sess: Session, cfg: ast::crate_cfg, input: &input)
     }
 }
 
+/// First phase to do, last phase to do
 #[deriving(Eq)]
-pub enum compile_upto {
+pub struct compile_upto {
+    from: compile_phase,
+    to: compile_phase
+}
+
+#[deriving(Eq)]
+pub enum compile_phase {
     cu_parse,
     cu_expand,
     cu_typeck,
@@ -177,138 +184,147 @@ pub enum compile_upto {
 #[fixed_stack_segment]
 pub fn compile_rest(sess: Session,
                     cfg: ast::crate_cfg,
-                    upto: compile_upto,
+                    phases: compile_upto,
                     outputs: Option<@OutputFilenames>,
                     curr: Option<@ast::crate>)
     -> (Option<@ast::crate>, Option<ty::ctxt>) {
 
     let time_passes = sess.time_passes();
 
-    let (llmod, link_meta) = {
+    let mut crate_opt = curr;
 
-        let mut crate = curr.unwrap();
+    if phases.from == cu_parse || phases.from == cu_everything {
 
         *sess.building_library = session::building_library(
-            sess.opts.crate_type, crate, sess.opts.test);
+            sess.opts.crate_type, crate_opt.unwrap(), sess.opts.test);
 
-        crate = time(time_passes, ~"expansion", ||
+        crate_opt = Some(time(time_passes, ~"expansion", ||
                      syntax::ext::expand::expand_crate(sess.parse_sess, copy cfg,
-                                                       crate));
+                                                       crate_opt.unwrap())));
+
+        crate_opt = Some(time(time_passes, ~"configuration", ||
+                     front::config::strip_unconfigured_items(crate_opt.unwrap())));
+
+        crate_opt = Some(time(time_passes, ~"maybe building test harness", ||
+                     front::test::modify_for_testing(sess, crate_opt.unwrap())));
+    }
 
-        crate = time(time_passes, ~"configuration", ||
-                     front::config::strip_unconfigured_items(crate));
+    if phases.to == cu_expand { return (crate_opt, None); }
 
-        crate = time(time_passes, ~"maybe building test harness", ||
-                     front::test::modify_for_testing(sess, crate));
+    assert!(phases.from != cu_no_trans);
 
-        if upto == cu_expand { return (Some(crate), None); }
+    let mut crate = crate_opt.unwrap();
 
-        crate = time(time_passes, ~"intrinsic injection", ||
-                     front::intrinsic_inject::inject_intrinsic(sess, crate));
+    let (llmod, link_meta) = {
+    crate = time(time_passes, ~"intrinsic injection", ||
+                 front::intrinsic_inject::inject_intrinsic(sess, crate));
 
-        crate = time(time_passes, ~"extra injection", ||
-                     front::std_inject::maybe_inject_libstd_ref(sess, crate));
+    crate = time(time_passes, ~"extra injection", ||
+                 front::std_inject::maybe_inject_libstd_ref(sess, crate));
 
-        let ast_map = time(time_passes, ~"ast indexing", ||
-                           syntax::ast_map::map_crate(sess.diagnostic(), crate));
+    let ast_map = time(time_passes, ~"ast indexing", ||
+                       syntax::ast_map::map_crate(sess.diagnostic(), crate));
 
-        time(time_passes, ~"external crate/lib resolution", ||
-             creader::read_crates(sess.diagnostic(), crate, sess.cstore,
-                                  sess.filesearch,
-                                  session::sess_os_to_meta_os(sess.targ_cfg.os),
-                                  sess.opts.is_static,
-                                  sess.parse_sess.interner));
+    time(time_passes, ~"external crate/lib resolution", ||
+         creader::read_crates(sess.diagnostic(), crate, sess.cstore,
+                              sess.filesearch,
+                              session::sess_os_to_meta_os(sess.targ_cfg.os),
+                              sess.opts.is_static,
+                              sess.parse_sess.interner));
 
-        let lang_items = time(time_passes, ~"language item collection", ||
-                              middle::lang_items::collect_language_items(crate, sess));
+    let lang_items = time(time_passes, ~"language item collection", ||
+                          middle::lang_items::collect_language_items(crate, sess));
 
-        let middle::resolve::CrateMap {
-            def_map: def_map,
-            exp_map2: exp_map2,
-            trait_map: trait_map
-        } =
-            time(time_passes, ~"resolution", ||
-                 middle::resolve::resolve_crate(sess, lang_items, crate));
+    let middle::resolve::CrateMap {
+        def_map: def_map,
+        exp_map2: exp_map2,
+        trait_map: trait_map
+    } =
+        time(time_passes, ~"resolution", ||
+             middle::resolve::resolve_crate(sess, lang_items, crate));
 
-        time(time_passes, ~"looking for entry point",
-             || middle::entry::find_entry_point(sess, crate, ast_map));
+    time(time_passes, ~"looking for entry point",
+         || middle::entry::find_entry_point(sess, crate, ast_map));
 
-        let freevars = time(time_passes, ~"freevar finding", ||
-                            freevars::annotate_freevars(def_map, crate));
+    let freevars = time(time_passes, ~"freevar finding", ||
+                        freevars::annotate_freevars(def_map, crate));
 
-        let region_map = time(time_passes, ~"region resolution", ||
-                              middle::region::resolve_crate(sess, def_map, crate));
+    let freevars = time(time_passes, ~"freevar finding", ||
+                        freevars::annotate_freevars(def_map, crate));
 
-        let rp_set = time(time_passes, ~"region parameterization inference", ||
-                          middle::region::determine_rp_in_crate(sess, ast_map, def_map, crate));
+    let region_map = time(time_passes, ~"region resolution", ||
+                          middle::region::resolve_crate(sess, def_map, crate));
 
-        let ty_cx = ty::mk_ctxt(sess, def_map, ast_map, freevars,
-                                region_map, rp_set, lang_items);
+    let rp_set = time(time_passes, ~"region parameterization inference", ||
+                      middle::region::determine_rp_in_crate(sess, ast_map, def_map, crate));
 
-        // passes are timed inside typeck
-        let (method_map, vtable_map) = typeck::check_crate(
-            ty_cx, trait_map, crate);
+    let ty_cx = ty::mk_ctxt(sess, def_map, ast_map, freevars,
+                            region_map, rp_set, lang_items);
 
-        // These next two const passes can probably be merged
-        time(time_passes, ~"const marking", ||
-             middle::const_eval::process_crate(crate, ty_cx));
+    // passes are timed inside typeck
+    let (method_map, vtable_map) = typeck::check_crate(
+        ty_cx, trait_map, crate);
 
-        time(time_passes, ~"const checking", ||
-             middle::check_const::check_crate(sess, crate, ast_map, def_map,
-                                              method_map, ty_cx));
+    // These next two const passes can probably be merged
+    time(time_passes, ~"const marking", ||
+         middle::const_eval::process_crate(crate, ty_cx));
 
-        if upto == cu_typeck { return (Some(crate), Some(ty_cx)); }
+    time(time_passes, ~"const checking", ||
+         middle::check_const::check_crate(sess, crate, ast_map, def_map,
+                                          method_map, ty_cx));
 
-        time(time_passes, ~"privacy checking", ||
-             middle::privacy::check_crate(ty_cx, &method_map, crate));
+    if phases.to == cu_typeck { return (Some(crate), Some(ty_cx)); }
 
-        time(time_passes, ~"effect checking", ||
-             middle::effect::check_crate(ty_cx, method_map, crate));
+    time(time_passes, ~"privacy checking", ||
+         middle::privacy::check_crate(ty_cx, &method_map, crate));
 
-        time(time_passes, ~"loop checking", ||
-             middle::check_loop::check_crate(ty_cx, crate));
+    time(time_passes, ~"effect checking", ||
+         middle::effect::check_crate(ty_cx, method_map, crate));
 
-        let middle::moves::MoveMaps {moves_map, moved_variables_set,
-                                     capture_map} =
-            time(time_passes, ~"compute moves", ||
-                 middle::moves::compute_moves(ty_cx, method_map, crate));
+    time(time_passes, ~"loop checking", ||
+         middle::check_loop::check_crate(ty_cx, crate));
 
-        time(time_passes, ~"match checking", ||
-             middle::check_match::check_crate(ty_cx, method_map,
-                                              moves_map, crate));
+    let middle::moves::MoveMaps {moves_map, moved_variables_set,
+                                 capture_map} =
+        time(time_passes, ~"compute moves", ||
+             middle::moves::compute_moves(ty_cx, method_map, crate));
 
-        time(time_passes, ~"liveness checking", ||
-             middle::liveness::check_crate(ty_cx, method_map,
-                                           capture_map, crate));
+    time(time_passes, ~"match checking", ||
+         middle::check_match::check_crate(ty_cx, method_map,
+                                          moves_map, crate));
 
-        let (root_map, write_guard_map) =
-            time(time_passes, ~"borrow checking", ||
-                 middle::borrowck::check_crate(ty_cx, method_map,
-                                               moves_map, moved_variables_set,
-                                               capture_map, crate));
+    time(time_passes, ~"liveness checking", ||
+         middle::liveness::check_crate(ty_cx, method_map,
+                                       capture_map, crate));
 
-        time(time_passes, ~"kind checking", ||
-             kind::check_crate(ty_cx, method_map, crate));
+    let (root_map, write_guard_map) =
+        time(time_passes, ~"borrow checking", ||
+             middle::borrowck::check_crate(ty_cx, method_map,
+                                           moves_map, moved_variables_set,
+                                           capture_map, crate));
 
-        time(time_passes, ~"lint checking", ||
-             lint::check_crate(ty_cx, crate));
+    time(time_passes, ~"kind checking", ||
+         kind::check_crate(ty_cx, method_map, crate));
 
-        if upto == cu_no_trans { return (Some(crate), Some(ty_cx)); }
+    time(time_passes, ~"lint checking", ||
+         lint::check_crate(ty_cx, crate));
 
-        let maps = astencode::Maps {
-            root_map: root_map,
-            method_map: method_map,
-            vtable_map: vtable_map,
-            write_guard_map: write_guard_map,
-            moves_map: moves_map,
-            capture_map: capture_map
-        };
+    if phases.to == cu_no_trans { return (Some(crate), Some(ty_cx)); }
+
+    let maps = astencode::Maps {
+        root_map: root_map,
+        method_map: method_map,
+        vtable_map: vtable_map,
+        write_guard_map: write_guard_map,
+        moves_map: moves_map,
+        capture_map: capture_map
+    };
 
-        let outputs = outputs.get_ref();
-        time(time_passes, ~"translation", ||
-             trans::base::trans_crate(sess, crate, ty_cx,
-                                      &outputs.obj_filename,
-                                      exp_map2, maps))
+    let outputs = outputs.get_ref();
+    time(time_passes, ~"translation", ||
+         trans::base::trans_crate(sess, crate, ty_cx,
+                                  &outputs.obj_filename,
+                                  exp_map2, maps))
     };
 
     let outputs = outputs.get_ref();
@@ -351,7 +367,7 @@ pub fn compile_rest(sess: Session,
 }
 
 pub fn compile_upto(sess: Session, cfg: ast::crate_cfg,
-                input: &input, upto: compile_upto,
+                input: &input, upto: compile_phase,
                 outputs: Option<@OutputFilenames>)
     -> (Option<@ast::crate>, Option<ty::ctxt>) {
     let time_passes = sess.time_passes();
@@ -359,7 +375,8 @@ pub fn compile_upto(sess: Session, cfg: ast::crate_cfg,
                          || parse_input(sess, copy cfg, input) );
     if upto == cu_parse { return (Some(crate), None); }
 
-    compile_rest(sess, cfg, upto, outputs, Some(crate))
+    compile_rest(sess, cfg, compile_upto { from: cu_parse, to: upto },
+                 outputs, Some(crate))
 }
 
 pub fn compile_input(sess: Session, cfg: ast::crate_cfg, input: &input,
@@ -367,7 +384,7 @@ pub fn compile_input(sess: Session, cfg: ast::crate_cfg, input: &input,
     let upto = if sess.opts.parse_only { cu_parse }
                else if sess.opts.no_trans { cu_no_trans }
                else { cu_everything };
-    let outputs = build_output_filenames(input, outdir, output, sess);
+    let outputs = build_output_filenames(input, outdir, output, [], sess); // ???
     compile_upto(sess, cfg, input, upto, Some(outputs));
 }
 
@@ -645,8 +662,7 @@ pub fn build_session_options(binary: @~str,
               ~"2" => Default,
               ~"3" => Aggressive,
               _ => {
-                early_error(demitter, ~"optimization level needs " +
-                            "to be between 0-3")
+                early_error(demitter, ~"optimization level needs to be between 0-3")
               }
             }
         } else { No }
@@ -706,7 +722,7 @@ pub fn build_session_options(binary: @~str,
         save_temps: save_temps,
         jit: jit,
         output_type: output_type,
-        addl_lib_search_paths: addl_lib_search_paths,
+        addl_lib_search_paths: @mut addl_lib_search_paths,
         linker: linker,
         linker_args: linker_args,
         maybe_sysroot: sysroot_opt,
@@ -745,7 +761,7 @@ pub fn build_session_(sopts: @session::options,
     let filesearch = filesearch::mk_filesearch(
         &sopts.maybe_sysroot,
         sopts.target_triple,
-        /*bad*/copy sopts.addl_lib_search_paths);
+        sopts.addl_lib_search_paths);
     @Session_ {
         targ_cfg: target_cfg,
         opts: sopts,
@@ -854,6 +870,7 @@ pub struct OutputFilenames {
 pub fn build_output_filenames(input: &input,
                               odir: &Option<Path>,
                               ofile: &Option<Path>,
+                              attrs: &[ast::attribute],
                               sess: Session)
                            -> @OutputFilenames {
     let obj_path;
@@ -863,7 +880,6 @@ pub fn build_output_filenames(input: &input,
         sopts.output_type != link::output_type_exe ||
             sopts.is_static && *sess.building_library;
 
-
     let obj_suffix =
         match sopts.output_type {
           link::output_type_none => ~"none",
@@ -876,29 +892,44 @@ pub fn build_output_filenames(input: &input,
 
     match *ofile {
       None => {
-        // "-" as input file will cause the parser to read from stdin so we
-        // have to make up a name
-        // We want to toss everything after the final '.'
-        let dirpath = match *odir {
-          Some(ref d) => (/*bad*/copy *d),
-          None => match *input {
-            str_input(_) => os::getcwd(),
-            file_input(ref ifile) => (*ifile).dir_path()
+          // "-" as input file will cause the parser to read from stdin so we
+          // have to make up a name
+          // We want to toss everything after the final '.'
+          let dirpath = match *odir {
+              Some(ref d) => (/*bad*/copy *d),
+              None => match *input {
+                  str_input(_) => os::getcwd(),
+                  file_input(ref ifile) => (*ifile).dir_path()
+              }
+          };
+
+          let mut stem = match *input {
+              file_input(ref ifile) => (*ifile).filestem().get(),
+              str_input(_) => ~"rust_out"
+          };
+
+          // If a linkage name meta is present, we use it as the link name
+          let linkage_metas = attr::find_linkage_metas(attrs);
+          if !linkage_metas.is_empty() {
+              // But if a linkage meta is present, that overrides
+              let maybe_matches = attr::find_meta_items_by_name(linkage_metas, "name");
+              if !maybe_matches.is_empty() {
+                  match attr::get_meta_item_value_str(maybe_matches[0]) {
+                      Some(s) => stem = copy *s,
+                      _ => ()
+                  }
+              }
+              // If the name is missing, we just default to the filename
+              // version
           }
-        };
 
-        let stem = match *input {
-          file_input(ref ifile) => (*ifile).filestem().get(),
-          str_input(_) => ~"rust_out"
-        };
-
-        if *sess.building_library {
-            out_path = dirpath.push(os::dll_filename(stem));
-            obj_path = dirpath.push(stem).with_filetype(obj_suffix);
-        } else {
-            out_path = dirpath.push(stem);
-            obj_path = dirpath.push(stem).with_filetype(obj_suffix);
-        }
+          if *sess.building_library {
+              out_path = dirpath.push(os::dll_filename(stem));
+              obj_path = dirpath.push(stem).with_filetype(obj_suffix);
+          } else {
+              out_path = dirpath.push(stem);
+              obj_path = dirpath.push(stem).with_filetype(obj_suffix);
+          }
       }
 
       Some(ref out_file) => {
diff --git a/src/librustc/driver/session.rs b/src/librustc/driver/session.rs
index ab895221de1..d6a99b3edc9 100644
--- a/src/librustc/driver/session.rs
+++ b/src/librustc/driver/session.rs
@@ -136,7 +136,9 @@ pub struct options {
     save_temps: bool,
     jit: bool,
     output_type: back::link::output_type,
-    addl_lib_search_paths: ~[Path],
+    addl_lib_search_paths: @mut ~[Path], // This is mutable for rustpkg, which
+                                         // updates search paths based on the
+                                         // parsed code
     linker: Option<~str>,
     linker_args: ~[~str],
     maybe_sysroot: Option<@Path>,
@@ -316,7 +318,7 @@ pub fn basic_options() -> @options {
         save_temps: false,
         jit: false,
         output_type: link::output_type_exe,
-        addl_lib_search_paths: ~[],
+        addl_lib_search_paths: @mut ~[],
         linker: None,
         linker_args: ~[],
         maybe_sysroot: None,
diff --git a/src/librustc/metadata/filesearch.rs b/src/librustc/metadata/filesearch.rs
index 4dbcd21e8f3..f83d33dcb3c 100644
--- a/src/librustc/metadata/filesearch.rs
+++ b/src/librustc/metadata/filesearch.rs
@@ -35,17 +35,18 @@ pub trait FileSearch {
 
 pub fn mk_filesearch(maybe_sysroot: &Option<@Path>,
                      target_triple: &str,
-                     addl_lib_search_paths: ~[Path])
+                     addl_lib_search_paths: @mut ~[Path])
                   -> @FileSearch {
     struct FileSearchImpl {
         sysroot: @Path,
-        addl_lib_search_paths: ~[Path],
+        addl_lib_search_paths: @mut ~[Path],
         target_triple: ~str
     }
     impl FileSearch for FileSearchImpl {
         fn sysroot(&self) -> @Path { self.sysroot }
         fn for_each_lib_search_path(&self, f: &fn(&Path) -> bool) -> bool {
-            debug!("filesearch: searching additional lib search paths");
+            debug!("filesearch: searching additional lib search paths [%?]",
+                   self.addl_lib_search_paths.len());
             // a little weird
             self.addl_lib_search_paths.each(f);
 
diff --git a/src/librustc/middle/typeck/astconv.rs b/src/librustc/middle/typeck/astconv.rs
index 57e2a562b20..4b5f416cdd1 100644
--- a/src/librustc/middle/typeck/astconv.rs
+++ b/src/librustc/middle/typeck/astconv.rs
@@ -455,7 +455,7 @@ pub fn ast_ty_to_ty<AC:AstConv, RS:region_scope + Copy + 'static>(
           }
           _ => {
             tcx.sess.span_fatal(ast_ty.span,
-                                "found type name used as a variable");
+                                fmt!("found value name used as a type: %?", a_def));
           }
         }
       }
diff --git a/src/librustc/rustc.rc b/src/librustc/rustc.rc
index 07056a10f3a..baf920c04ac 100644
--- a/src/librustc/rustc.rc
+++ b/src/librustc/rustc.rc
@@ -183,11 +183,11 @@ pub fn version(argv0: &str) {
 
 pub fn usage(argv0: &str) {
     let message = fmt!("Usage: %s [OPTIONS] INPUT", argv0);
-    io::println(groups::usage(message, optgroups()) +
-                "Additional help:
-    -W help             Print 'lint' options and default settings
-    -Z help             Print internal options for debugging rustc
-");
+    io::println(fmt!("%s \
+                Additional help: \
+    -W help             Print 'lint' options and default settings \
+    -Z help             Print internal options for debugging rustc",
+                     groups::usage(message, optgroups())));
 }
 
 pub fn describe_warnings() {
diff --git a/src/librusti/rusti.rc b/src/librusti/rusti.rc
index ff8b4092fab..08325ffb2a3 100644
--- a/src/librusti/rusti.rc
+++ b/src/librusti/rusti.rc
@@ -121,7 +121,7 @@ fn run(repl: Repl, input: ~str) -> Repl {
     let options = @session::options {
         crate_type: session::unknown_crate,
         binary: binary,
-        addl_lib_search_paths: repl.lib_search_paths.map(|p| Path(*p)),
+        addl_lib_search_paths: @mut repl.lib_search_paths.map(|p| Path(*p)),
         jit: true,
         .. copy *session::basic_options()
     };
@@ -142,12 +142,13 @@ fn run(repl: Repl, input: ~str) -> Repl {
                                           binary,
                                           &wrapped);
 
-    let outputs = driver::build_output_filenames(&wrapped, &None, &None, sess);
+    let outputs = driver::build_output_filenames(&wrapped, &None, &None, [], sess);
     debug!("calling compile_upto");
 
     let crate = driver::parse_input(sess, copy cfg, &wrapped);
-    driver::compile_rest(sess, cfg, driver::cu_everything,
-                         Some(outputs), Some(crate));
+    driver::compile_rest(sess, cfg, driver::compile_upto { from: driver::cu_parse,
+                                                           to: driver::cu_everything },
+                          Some(outputs), Some(crate));
 
     let mut opt = None;
 
@@ -188,7 +189,7 @@ fn compile_crate(src_filename: ~str, binary: ~str) -> Option<bool> {
         let binary = @copy binary;
         let options = @session::options {
             binary: binary,
-            addl_lib_search_paths: ~[os::getcwd()],
+            addl_lib_search_paths: @mut ~[os::getcwd()],
             .. copy *session::basic_options()
         };
         let input = driver::file_input(copy src_path);
@@ -196,7 +197,7 @@ fn compile_crate(src_filename: ~str, binary: ~str) -> Option<bool> {
         *sess.building_library = true;
         let cfg = driver::build_configuration(sess, binary, &input);
         let outputs = driver::build_output_filenames(
-            &input, &None, &None, sess);
+            &input, &None, &None, [], sess);
         // If the library already exists and is newer than the source
         // file, skip compilation and return None.
         let mut should_compile = true;
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 {
diff --git a/src/libsyntax/ast_util.rs b/src/libsyntax/ast_util.rs
index eab8c0a12d3..95dce88d450 100644
--- a/src/libsyntax/ast_util.rs
+++ b/src/libsyntax/ast_util.rs
@@ -562,6 +562,21 @@ pub fn walk_pat(pat: @pat, it: &fn(@pat) -> bool) -> bool {
     }
 }
 
+pub trait EachViewItem {
+    pub fn each_view_item(&self, f: @fn(@ast::view_item) -> bool) -> bool;
+}
+
+impl EachViewItem for ast::crate {
+    fn each_view_item(&self, f: @fn(@ast::view_item) -> bool) -> bool {
+        let broke = @mut false;
+        let vtor: visit::vt<()> = visit::mk_simple_visitor(@visit::SimpleVisitor {
+            visit_view_item: |vi| { *broke = f(vi); }, ..*visit::default_simple_visitor()
+        });
+        visit::visit_crate(self, (), vtor);
+        true
+    }
+}
+
 pub fn view_path_id(p: @view_path) -> node_id {
     match p.node {
       view_path_simple(_, _, id) |
diff --git a/src/test/run-pass/extern-mod-url.rs b/src/test/run-pass/extern-mod-url.rs
new file mode 100644
index 00000000000..457c61067e3
--- /dev/null
+++ b/src/test/run-pass/extern-mod-url.rs
@@ -0,0 +1,16 @@
+// 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.
+
+// Just a test that new-style extern mods parse
+
+// xfail-test
+extern mod test = "github.com/catamorphism/test-pkg";
+
+fn main() {}
\ No newline at end of file