about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2013-06-09 13:16:27 -0700
committerbors <bors@rust-lang.org>2013-06-09 13:16:27 -0700
commit94f72dd73699701d7354e16950b9a547e11e015b (patch)
treefa5ed5ceb431d6425803123380a70d9e4d836c66
parent88c318d28c7d8fe8346d38d73cb0b1a30f33f278 (diff)
parentd92b4358d278afb6f0172f4cddb5a60ebcdff7ca (diff)
downloadrust-94f72dd73699701d7354e16950b9a547e11e015b.tar.gz
rust-94f72dd73699701d7354e16950b9a547e11e015b.zip
auto merge of #6904 : catamorphism/rust/rustpkg_version_vcs, r=catamorphism
r? @brson
-rw-r--r--src/librustpkg/crate.rs58
-rw-r--r--src/librustpkg/package_id.rs96
-rw-r--r--src/librustpkg/package_source.rs239
-rw-r--r--src/librustpkg/path_util.rs16
-rw-r--r--src/librustpkg/rustpkg.rc270
-rw-r--r--src/librustpkg/tests.rs113
-rw-r--r--src/librustpkg/util.rs13
-rw-r--r--src/librustpkg/version.rs215
-rw-r--r--src/libstd/num/float.rs2
9 files changed, 670 insertions, 352 deletions
diff --git a/src/librustpkg/crate.rs b/src/librustpkg/crate.rs
new file mode 100644
index 00000000000..5e8139063de
--- /dev/null
+++ b/src/librustpkg/crate.rs
@@ -0,0 +1,58 @@
+// Copyright 2012-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 core::path::Path;
+use core::vec;
+
+/// A crate is a unit of Rust code to be compiled into a binary or library
+pub struct Crate {
+    file: Path,
+    flags: ~[~str],
+    cfgs: ~[~str]
+}
+
+impl Crate {
+
+    pub fn new(p: &Path) -> Crate {
+        Crate {
+            file: copy *p,
+            flags: ~[],
+            cfgs: ~[]
+        }
+    }
+
+    fn flag(&self, flag: ~str) -> Crate {
+        Crate {
+            flags: vec::append(copy self.flags, [flag]),
+            .. copy *self
+        }
+    }
+
+    fn flags(&self, flags: ~[~str]) -> Crate {
+        Crate {
+            flags: vec::append(copy self.flags, flags),
+            .. copy *self
+        }
+    }
+
+    fn cfg(&self, cfg: ~str) -> Crate {
+        Crate {
+            cfgs: vec::append(copy self.cfgs, [cfg]),
+            .. copy *self
+        }
+    }
+
+    fn cfgs(&self, cfgs: ~[~str]) -> Crate {
+        Crate {
+            cfgs: vec::append(copy self.cfgs, cfgs),
+            .. copy *self
+        }
+    }
+}
diff --git a/src/librustpkg/package_id.rs b/src/librustpkg/package_id.rs
index 85c82787f26..022acc29c8b 100644
--- a/src/librustpkg/package_id.rs
+++ b/src/librustpkg/package_id.rs
@@ -9,12 +9,8 @@
 // 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) }
+use version::{try_getting_version, Version, NoVersion, split_version};
 
 /// Path-fragment identifier of a package such as
 /// 'github.com/graydon/test'; path must be a relative
@@ -39,6 +35,21 @@ impl PkgId {
     pub fn new(s: &str) -> PkgId {
         use conditions::bad_pkg_id::cond;
 
+        let mut given_version = None;
+
+        // Did the user request a specific version?
+        let s = match split_version(s) {
+            Some((path, v)) => {
+                debug!("s = %s, path = %s, v = %s", s, path, v.to_str());
+                given_version = Some(v);
+                path
+            }
+            None => {
+                debug!("%s has no explicit version", s);
+                s
+            }
+        };
+
         let p = Path(s);
         if p.is_absolute {
             return cond.raise((p, ~"absolute pkgid"));
@@ -49,11 +60,20 @@ impl 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));
+
+        let version = match given_version {
+            Some(v) => v,
+            None => match try_getting_version(&remote_path) {
+                Some(v) => v,
+                None => NoVersion
+            }
+        };
+
         PkgId {
             local_path: local_path,
             remote_path: remote_path,
             short_name: short_name,
-            version: default_version()
+            version: version
         }
     }
 
@@ -64,69 +84,17 @@ impl PkgId {
     }
 
     pub fn short_name_with_version(&self) -> ~str {
-        fmt!("%s-%s", self.short_name, self.version.to_str())
+        fmt!("%s%s", self.short_name, self.version.to_str())
     }
 }
 
 impl ToStr for PkgId {
     fn to_str(&self) -> ~str {
+        let maybe_dash = match self.version {
+            NoVersion => "",
+            _         => "-"
+        };
         // 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")
+        fmt!("%s%s%s", self.local_path.to_str(), maybe_dash, self.version.to_str())
     }
 }
diff --git a/src/librustpkg/package_source.rs b/src/librustpkg/package_source.rs
new file mode 100644
index 00000000000..7c4c6378124
--- /dev/null
+++ b/src/librustpkg/package_source.rs
@@ -0,0 +1,239 @@
+// Copyright 2012-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 target::*;
+use package_id::PkgId;
+use core::path::Path;
+use core::option::*;
+use core::{os, run, str, vec};
+use context::*;
+use crate::Crate;
+use path_util::pkgid_src_in_workspace;
+use util::{compile_crate, note};
+use version::{ExactRevision, SemanticVersion, NoVersion};
+
+// An enumeration of the unpacked source of a package workspace.
+// This contains a list of files found in the source workspace.
+pub struct PkgSrc {
+    root: Path, // root of where the package source code lives
+    dst_dir: Path, // directory where we will put the compiled output
+    id: PkgId,
+    libs: ~[Crate],
+    mains: ~[Crate],
+    tests: ~[Crate],
+    benchs: ~[Crate],
+}
+
+condition! {
+    build_err: (~str) -> ();
+}
+
+impl PkgSrc {
+
+    pub fn new(src_dir: &Path, dst_dir: &Path,
+                  id: &PkgId) -> PkgSrc {
+        PkgSrc {
+            root: copy *src_dir,
+            dst_dir: copy *dst_dir,
+            id: copy *id,
+            libs: ~[],
+            mains: ~[],
+            tests: ~[],
+            benchs: ~[]
+        }
+    }
+
+
+    fn check_dir(&self) -> Path {
+        use conditions::nonexistent_package::cond;
+
+        debug!("Pushing onto root: %s | %s", self.id.remote_path.to_str(),
+               self.root.to_str());
+        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"));
+        }
+
+        dir
+    }
+
+    /// Try interpreting self's package id as a remote package, and try
+    /// 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)
+    pub fn fetch_git(&self) -> Option<Path> {
+
+        let mut local = self.root.push("src");
+        local = local.push(self.id.to_str());
+        // Git can't clone into a non-empty directory
+        os::remove_dir_recursive(&local);
+
+        let url = fmt!("https://%s", self.id.remote_path.to_str());
+        let branch_args = match self.id.version {
+                      NoVersion => ~[],
+                      ExactRevision(ref s) => ~[~"--branch", copy *s],
+                      SemanticVersion(ref s) => ~[~"--branch", s.to_str()]
+        };
+
+
+        note(fmt!("git clone %s %s %?", url, local.to_str(), branch_args));
+
+        if run::process_output("git",
+                               ~[~"clone", copy url, local.to_str()] + branch_args).status != 0 {
+            note(fmt!("fetching %s failed: can't clone repository", url));
+            None
+        }
+        else {
+            Some(local)
+        }
+    }
+
+
+    // If a file named "pkg.rs" in the current directory exists,
+    // return the path for it. Otherwise, None
+    pub fn package_script_option(&self, cwd: &Path) -> Option<Path> {
+        let maybe_path = cwd.push("pkg.rs");
+        if os::path_exists(&maybe_path) {
+            Some(maybe_path)
+        }
+        else {
+            None
+        }
+    }
+
+    /// True if the given path's stem is self's pkg ID's stem
+    /// or if the pkg ID's stem is <rust-foo> and the given path's
+    /// stem is foo
+    /// Requires that dashes in p have already been normalized to
+    /// underscores
+    fn stem_matches(&self, p: &Path) -> bool {
+        let self_id = self.id.local_path.filestem();
+        if self_id == p.filestem() {
+            return true;
+        }
+        else {
+            for self_id.each |pth| {
+                if pth.starts_with("rust_") // because p is already normalized
+                    && match p.filestem() {
+                           Some(s) => str::eq_slice(s, pth.slice(5, pth.len())),
+                           None => false
+                       } { return true; }
+            }
+        }
+        false
+    }
+
+    fn push_crate(cs: &mut ~[Crate], prefix: uint, p: &Path) {
+        assert!(p.components.len() > prefix);
+        let mut sub = Path("");
+        for vec::slice(p.components, prefix,
+                       p.components.len()).each |c| {
+            sub = sub.push(*c);
+        }
+        debug!("found crate %s", sub.to_str());
+        cs.push(Crate::new(&sub));
+    }
+
+    /// Infers crates to build. Called only in the case where there
+    /// is no custom build logic
+    pub fn find_crates(&mut self) {
+        use conditions::missing_pkg_files::cond;
+
+        let dir = self.check_dir();
+        debug!("Called check_dir, I'm in %s", dir.to_str());
+        let prefix = dir.components.len();
+        debug!("Matching against %?", self.id.local_path.filestem());
+        for os::walk_dir(&dir) |pth| {
+            match pth.filename() {
+                Some(~"lib.rs") => PkgSrc::push_crate(&mut self.libs,
+                                                      prefix,
+                                                      pth),
+                Some(~"main.rs") => PkgSrc::push_crate(&mut self.mains,
+                                                       prefix,
+                                                       pth),
+                Some(~"test.rs") => PkgSrc::push_crate(&mut self.tests,
+                                                       prefix,
+                                                       pth),
+                Some(~"bench.rs") => PkgSrc::push_crate(&mut self.benchs,
+                                                        prefix,
+                                                        pth),
+                _ => ()
+            }
+        }
+
+        if self.libs.is_empty() && self.mains.is_empty()
+            && self.tests.is_empty() && self.benchs.is_empty() {
+
+            note(~"Couldn't infer any crates to build.\n\
+                         Try naming a crate `main.rs`, `lib.rs`, \
+                         `test.rs`, or `bench.rs`.");
+            cond.raise(copy self.id);
+        }
+
+        debug!("found %u libs, %u mains, %u tests, %u benchs",
+               self.libs.len(),
+               self.mains.len(),
+               self.tests.len(),
+               self.benchs.len())
+    }
+
+    fn build_crates(&self,
+                    ctx: &Ctx,
+                    dst_dir: &Path,
+                    src_dir: &Path,
+                    crates: &[Crate],
+                    cfgs: &[~str],
+                    what: OutputType) {
+        for crates.each |&crate| {
+            let path = &src_dir.push_rel(&crate.file).normalize();
+            note(fmt!("build_crates: compiling %s", path.to_str()));
+            note(fmt!("build_crates: destination dir is %s", dst_dir.to_str()));
+
+            let result = 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()));
+            }
+            debug!("Result of compiling %s was %?",
+                   path.to_str(), result);
+        }
+    }
+
+    pub fn build(&self, ctx: &Ctx, dst_dir: Path, cfgs: ~[~str]) {
+        let dir = self.check_dir();
+        debug!("Building libs in %s", dir.to_str());
+        self.build_crates(ctx, &dst_dir, &dir, self.libs, cfgs, Lib);
+        debug!("Building mains");
+        self.build_crates(ctx, &dst_dir, &dir, self.mains, cfgs, Main);
+        debug!("Building tests");
+        self.build_crates(ctx, &dst_dir, &dir, self.tests, cfgs, Test);
+        debug!("Building benches");
+        self.build_crates(ctx, &dst_dir, &dir, self.benchs, cfgs, Bench);
+    }
+}
diff --git a/src/librustpkg/path_util.rs b/src/librustpkg/path_util.rs
index 7b4a9a63a4e..af6b23ba755 100644
--- a/src/librustpkg/path_util.rs
+++ b/src/librustpkg/path_util.rs
@@ -12,7 +12,7 @@
 
 use core::prelude::*;
 pub use package_path::{RemotePath, LocalPath};
-pub use package_id::{PkgId, Version};
+pub use package_id::PkgId;
 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;
@@ -210,11 +210,17 @@ pub fn target_executable_in_workspace(pkgid: &PkgId, workspace: &Path) -> Path {
 }
 
 
-/// Returns the executable that would be installed for <pkgid>
-/// in <workspace>
+/// Returns the installed path for <built_library> in <workspace>
 /// 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, Install)
+pub fn target_library_in_workspace(workspace: &Path,
+                                   built_library: &Path) -> Path {
+    use conditions::bad_path::cond;
+    let result = workspace.push("lib");
+    if !os::path_exists(&result) && !mkdir_recursive(&result, u_rwx) {
+        cond.raise((copy result, ~"I couldn't create the library directory"));
+    }
+    result.push(built_library.filename().expect(fmt!("I don't know how to treat %s as a library",
+                                                   built_library.to_str())))
 }
 
 /// Returns the test executable that would be installed for <pkgid>
diff --git a/src/librustpkg/rustpkg.rc b/src/librustpkg/rustpkg.rc
index 5bc52b1eb35..37b8c2ad433 100644
--- a/src/librustpkg/rustpkg.rc
+++ b/src/librustpkg/rustpkg.rc
@@ -18,6 +18,7 @@
 #[license = "MIT/ASL2"];
 #[crate_type = "lib"];
 
+#[no_core];
 #[no_std];
 
 extern mod core(name = "std");
@@ -35,24 +36,28 @@ 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, first_pkgid_src_in_workspace};
+use path_util::{build_pkg_id_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;
+use package_source::PkgSrc;
 
 mod conditions;
 mod context;
+mod crate;
 mod package_id;
 mod package_path;
+mod package_source;
 mod path_util;
 mod search;
 mod target;
 #[cfg(test)]
 mod tests;
 mod util;
+mod version;
 mod workspace;
 
 pub mod usage;
@@ -335,11 +340,11 @@ impl Ctx {
         let maybe_executable = built_executable_in_workspace(id, workspace);
         let maybe_library = built_library_in_workspace(id, workspace);
         let target_exec = target_executable_in_workspace(id, workspace);
-        let target_lib = target_library_in_workspace(id, workspace);
+        let target_lib = maybe_library.map(|p| target_library_in_workspace(workspace, p));
 
-        debug!("target_exec = %s target_lib = %s \
+        debug!("target_exec = %s target_lib = %? \
                 maybe_executable = %? maybe_library = %?",
-               target_exec.to_str(), target_lib.to_str(),
+               target_exec.to_str(), target_lib,
                maybe_executable, maybe_library);
 
         for maybe_executable.each |exec| {
@@ -350,6 +355,8 @@ impl Ctx {
             }
         }
         for maybe_library.each |lib| {
+            let target_lib = (copy target_lib).expect(fmt!("I built %s but apparently \
+                                                didn't install it!", lib.to_str()));
             debug!("Copying: %s -> %s", lib.to_str(), target_lib.to_str());
             if !(os::mkdir_recursive(&target_lib.dir_path(), u_rwx) &&
                  os::copy_file(lib, &target_lib)) {
@@ -430,51 +437,6 @@ pub fn main() {
     }.run(cmd, args);
 }
 
-/// A crate is a unit of Rust code to be compiled into a binary or library
-pub struct Crate {
-    file: Path,
-    flags: ~[~str],
-    cfgs: ~[~str]
-}
-
-impl Crate {
-    pub fn new(p: &Path) -> Crate {
-        Crate {
-            file: copy *p,
-            flags: ~[],
-            cfgs: ~[]
-        }
-    }
-
-    pub fn flag(&self, flag: ~str) -> Crate {
-        Crate {
-            flags: vec::append(copy self.flags, [flag]),
-            .. copy *self
-        }
-    }
-
-    pub fn flags(&self, flags: ~[~str]) -> Crate {
-        Crate {
-            flags: vec::append(copy self.flags, flags),
-            .. copy *self
-        }
-    }
-
-    pub fn cfg(&self, cfg: ~str) -> Crate {
-        Crate {
-            cfgs: vec::append(copy self.cfgs, [cfg]),
-            .. copy *self
-        }
-    }
-
-    pub fn cfgs(&self, cfgs: ~[~str]) -> Crate {
-        Crate {
-            cfgs: vec::append(copy self.cfgs, cfgs),
-            .. copy *self
-        }
-    }
-}
-
 /**
  * Get the working directory of the package script.
  * Assumes that the package script has been compiled
@@ -493,213 +455,3 @@ pub fn work_dir() -> Path {
 pub fn src_dir() -> Path {
     os::getcwd()
 }
-
-// An enumeration of the unpacked source of a package workspace.
-// This contains a list of files found in the source workspace.
-pub struct PkgSrc {
-    root: Path, // root of where the package source code lives
-    dst_dir: Path, // directory where we will put the compiled output
-    id: PkgId,
-    libs: ~[Crate],
-    mains: ~[Crate],
-    tests: ~[Crate],
-    benchs: ~[Crate],
-}
-
-condition! {
-    build_err: (~str) -> ();
-}
-
-impl PkgSrc {
-
-    fn new(src_dir: &Path, dst_dir: &Path,
-                  id: &PkgId) -> PkgSrc {
-        PkgSrc {
-            root: copy *src_dir,
-            dst_dir: copy *dst_dir,
-            id: copy *id,
-            libs: ~[],
-            mains: ~[],
-            tests: ~[],
-            benchs: ~[]
-        }
-    }
-
-
-    fn check_dir(&self) -> Path {
-        use conditions::nonexistent_package::cond;
-
-        debug!("Pushing onto root: %s | %s", self.id.remote_path.to_str(),
-               self.root.to_str());
-        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"));
-        }
-
-        dir
-    }
-
-    /// Try interpreting self's package id as a remote package, and try
-    /// 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) -> Option<Path> {
-
-        let mut local = self.root.push("src");
-        local = local.push(self.id.to_str());
-        // Git can't clone into a non-empty directory
-        os::remove_dir_recursive(&local);
-
-        let url = fmt!("https://%s", self.id.remote_path.to_str());
-        util::note(fmt!("git clone %s %s", url, local.to_str()));
-
-        if run::process_output("git", [~"clone", copy url, local.to_str()]).status != 0 {
-            util::note(fmt!("fetching %s failed: can't clone repository", url));
-            None
-        }
-        else {
-            Some(local)
-        }
-    }
-
-
-    // If a file named "pkg.rs" in the current directory exists,
-    // return the path for it. Otherwise, None
-    fn package_script_option(&self, cwd: &Path) -> Option<Path> {
-        let maybe_path = cwd.push("pkg.rs");
-        if os::path_exists(&maybe_path) {
-            Some(maybe_path)
-        }
-        else {
-            None
-        }
-    }
-
-    /// True if the given path's stem is self's pkg ID's stem
-    /// or if the pkg ID's stem is <rust-foo> and the given path's
-    /// stem is foo
-    /// Requires that dashes in p have already been normalized to
-    /// underscores
-    fn stem_matches(&self, p: &Path) -> bool {
-        let self_id = self.id.local_path.filestem();
-        if self_id == p.filestem() {
-            return true;
-        }
-        else {
-            for self_id.each |pth| {
-                if pth.starts_with("rust_") // because p is already normalized
-                    && match p.filestem() {
-                           Some(s) => str::eq_slice(s, pth.slice(5, pth.len())),
-                           None => false
-                       } { return true; }
-            }
-        }
-        false
-    }
-
-    fn push_crate(cs: &mut ~[Crate], prefix: uint, p: &Path) {
-        assert!(p.components.len() > prefix);
-        let mut sub = Path("");
-        for vec::slice(p.components, prefix,
-                       p.components.len()).each |c| {
-            sub = sub.push(*c);
-        }
-        debug!("found crate %s", sub.to_str());
-        cs.push(Crate::new(&sub));
-    }
-
-    /// Infers crates to build. Called only in the case where there
-    /// is no custom build logic
-    fn find_crates(&mut self) {
-        use conditions::missing_pkg_files::cond;
-
-        let dir = self.check_dir();
-        let prefix = dir.components.len();
-        debug!("Matching against %?", self.id.local_path.filestem());
-        for os::walk_dir(&dir) |pth| {
-            match pth.filename() {
-                Some(~"lib.rs") => PkgSrc::push_crate(&mut self.libs,
-                                                      prefix,
-                                                      pth),
-                Some(~"main.rs") => PkgSrc::push_crate(&mut self.mains,
-                                                       prefix,
-                                                       pth),
-                Some(~"test.rs") => PkgSrc::push_crate(&mut self.tests,
-                                                       prefix,
-                                                       pth),
-                Some(~"bench.rs") => PkgSrc::push_crate(&mut self.benchs,
-                                                        prefix,
-                                                        pth),
-                _ => ()
-            }
-        }
-
-        if self.libs.is_empty() && self.mains.is_empty()
-            && self.tests.is_empty() && self.benchs.is_empty() {
-
-            util::note("Couldn't infer any crates to build.\n\
-                        Try naming a crate `main.rs`, `lib.rs`, \
-                        `test.rs`, or `bench.rs`.");
-            cond.raise(copy self.id);
-        }
-
-        debug!("found %u libs, %u mains, %u tests, %u benchs",
-               self.libs.len(),
-               self.mains.len(),
-               self.tests.len(),
-               self.benchs.len())
-    }
-
-    fn build_crates(&self,
-                    ctx: &Ctx,
-                    dst_dir: &Path,
-                    src_dir: &Path,
-                    crates: &[Crate],
-                    cfgs: &[~str],
-                    what: OutputType) {
-        for crates.each |&crate| {
-            let path = &src_dir.push_rel(&crate.file).normalize();
-            util::note(fmt!("build_crates: compiling %s", path.to_str()));
-            util::note(fmt!("build_crates: destination dir is %s", dst_dir.to_str()));
-
-            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()));
-            }
-            debug!("Result of compiling %s was %?",
-                   path.to_str(), result);
-        }
-    }
-
-    fn build(&self, ctx: &Ctx, dst_dir: Path, cfgs: ~[~str]) {
-        let dir = self.check_dir();
-        debug!("Building libs");
-        self.build_crates(ctx, &dst_dir, &dir, self.libs, cfgs, Lib);
-        debug!("Building mains");
-        self.build_crates(ctx, &dst_dir, &dir, self.mains, cfgs, Main);
-        debug!("Building tests");
-        self.build_crates(ctx, &dst_dir, &dir, self.tests, cfgs, Test);
-        debug!("Building benches");
-        self.build_crates(ctx, &dst_dir, &dir, self.benchs, cfgs, Bench);
-    }
-}
diff --git a/src/librustpkg/tests.rs b/src/librustpkg/tests.rs
index a96a7a0a5fc..8df84b27fcc 100644
--- a/src/librustpkg/tests.rs
+++ b/src/librustpkg/tests.rs
@@ -18,11 +18,15 @@ use core::prelude::*;
 use core::result;
 use extra::tempfile::mkdtemp;
 use package_path::*;
-use package_id::{PkgId, default_version};
+use package_id::PkgId;
+use package_source::*;
+use version::{ExactRevision, NoVersion, 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,
-               built_bench_in_workspace, built_test_in_workspace};
+               built_bench_in_workspace, built_test_in_workspace,
+               built_library_in_workspace, built_executable_in_workspace,
+                installed_library_in_workspace};
 
 fn fake_ctxt(sysroot_opt: Option<@Path>) -> Ctx {
     Ctx {
@@ -39,7 +43,7 @@ fn fake_pkg() -> PkgId {
         local_path: normalize(copy remote),
         remote_path: remote,
         short_name: sn,
-        version: default_version()
+        version: NoVersion
     }
 }
 
@@ -49,7 +53,7 @@ fn remote_pkg() -> PkgId {
         local_path: normalize(copy remote),
         remote_path: remote,
         short_name: ~"test_pkg",
-        version: default_version()
+        version: NoVersion
     }
 }
 
@@ -60,11 +64,21 @@ fn writeFile(file_path: &Path, contents: &str) {
     out.write_line(contents);
 }
 
-fn mk_temp_workspace(short_name: &LocalPath) -> Path {
+fn mk_empty_workspace(short_name: &LocalPath, version: &Version) -> Path {
     let workspace = mkdtemp(&os::tmpdir(), "test").expect("couldn't create temp dir");
     // include version number in directory name
-    let package_dir = workspace.push("src").push(fmt!("%s-0.1", short_name.to_str()));
+    let package_dir = workspace.push("src").push(fmt!("%s%s",
+                                                      short_name.to_str(), version.to_str()));
     assert!(os::mkdir_recursive(&package_dir, u_rwx));
+    package_dir.pop().pop()
+}
+
+fn mk_temp_workspace(short_name: &LocalPath, version: &Version) -> Path {
+    let package_dir = mk_empty_workspace(short_name,
+                                         version).push("src").push(fmt!("%s%s",
+                                                            short_name.to_str(),
+                                                            version.to_str()));
+
     debug!("Created %s and does it exist? %?", package_dir.to_str(),
           os::path_is_dir(&package_dir));
     // Create main, lib, test, and bench files
@@ -76,7 +90,7 @@ fn mk_temp_workspace(short_name: &LocalPath) -> Path {
               "#[test] pub fn f() { (); }");
     writeFile(&package_dir.push("bench.rs"),
               "#[bench] pub fn f() { (); }");
-    workspace
+    package_dir.pop().pop()
 }
 
 fn is_rwx(p: &Path) -> bool {
@@ -120,7 +134,7 @@ fn test_install_valid() {
     debug!("sysroot = %s", sysroot.to_str());
     let ctxt = fake_ctxt(Some(@sysroot));
     let temp_pkg_id = fake_pkg();
-    let temp_workspace = mk_temp_workspace(&temp_pkg_id.local_path);
+    let temp_workspace = mk_temp_workspace(&temp_pkg_id.local_path, &NoVersion);
     // should have test, bench, lib, and main
     ctxt.install(&temp_workspace, &temp_pkg_id);
     // Check that all files exist
@@ -178,7 +192,10 @@ fn test_install_url() {
     debug!("exec = %s", exec.to_str());
     assert!(os::path_exists(&exec));
     assert!(is_rwx(&exec));
-    let lib = target_library_in_workspace(&temp_pkg_id, &workspace);
+    let built_lib =
+        built_library_in_workspace(&temp_pkg_id,
+                                   &workspace).expect("test_install_url: built lib should exist");
+    let lib = target_library_in_workspace(&workspace, &built_lib);
     debug!("lib = %s", lib.to_str());
     assert!(os::path_exists(&lib));
     assert!(is_rwx(&lib));
@@ -212,16 +229,11 @@ fn test_package_ids_must_be_relative_path_like() {
 
     */
 
-    let default_version_str = "0.1";
-    let addversion = |s| {
-        fmt!("%s-%s", s, default_version_str)
-    };
-
     let whatever = PkgId::new("foo");
 
-    assert_eq!(addversion("foo"), whatever.to_str());
-    assert!(addversion("github.com/mozilla/rust") ==
-            PkgId::new("github.com/mozilla/rust").to_str());
+    assert_eq!(~"foo", whatever.to_str());
+    assert!("github.com/catamorphism/test_pkg" ==
+            PkgId::new("github.com/catamorphism/test-pkg").to_str());
 
     do cond.trap(|(p, e)| {
         assert!("" == p.to_str());
@@ -229,7 +241,7 @@ fn test_package_ids_must_be_relative_path_like() {
         copy whatever
     }).in {
         let x = PkgId::new("");
-        assert_eq!(addversion("foo"), x.to_str());
+        assert_eq!(~"foo", x.to_str());
     }
 
     do cond.trap(|(p, e)| {
@@ -238,7 +250,70 @@ fn test_package_ids_must_be_relative_path_like() {
         copy whatever
     }).in {
         let z = PkgId::new(os::make_absolute(&Path("foo/bar/quux")).to_str());
-        assert_eq!(addversion("foo"), z.to_str());
+        assert_eq!(~"foo", z.to_str());
+    }
+
+}
+
+#[test]
+fn test_package_version() {
+    let temp_pkg_id = PkgId::new("github.com/catamorphism/test_pkg_version");
+    match temp_pkg_id.version {
+        ExactRevision(~"0.4") => (),
+        _ => fail!(fmt!("test_package_version: package version was %?, expected Some(0.4)",
+                        temp_pkg_id.version))
     }
+    let temp = mk_empty_workspace(&LocalPath(Path("test_pkg_version")), &temp_pkg_id.version);
+    let ctx = fake_ctxt(Some(@test_sysroot()));
+    ctx.build(&temp, &temp_pkg_id);
+    assert!(match built_library_in_workspace(&temp_pkg_id, &temp) {
+        Some(p) => p.to_str().ends_with(fmt!("0.4%s", os::consts::DLL_SUFFIX)),
+        None    => false
+    });
+    assert!(built_executable_in_workspace(&temp_pkg_id, &temp)
+            == Some(temp.push("build").
+                    push("github.com").
+                    push("catamorphism").
+                    push("test_pkg_version").
+                    push("test_pkg_version")));
+}
+
+// FIXME #7006: Fails on linux for some reason
+#[test]
+#[ignore(cfg(target_os = "linux"))]
+fn test_package_request_version() {
+    let temp_pkg_id = PkgId::new("github.com/catamorphism/test_pkg_version#0.3");
+    let temp = mk_empty_workspace(&LocalPath(Path("test_pkg_version")), &ExactRevision(~"0.3"));
+    let pkg_src = PkgSrc::new(&temp, &temp, &temp_pkg_id);
+    match temp_pkg_id.version {
+        ExactRevision(~"0.3") => {
+            match pkg_src.fetch_git() {
+                Some(p) => {
+                    assert!(os::path_exists(&p.push("version-0.3-file.txt")));
+                    assert!(!os::path_exists(&p.push("version-0.4-file.txt")));
+
+                }
+                None => fail!("test_package_request_version: fetch_git failed")
+            }
+        }
+        ExactRevision(n) => {
+            fail!("n is %? and %? %s %?", n, n, if n == ~"0.3" { "==" } else { "!=" }, "0.3");
+        }
+        _ => fail!(fmt!("test_package_version: package version was %?, expected ExactRevision(0.3)",
+                        temp_pkg_id.version))
+    }
+    let c = fake_ctxt(Some(@test_sysroot()));
+    c.install(&temp, &temp_pkg_id);
+    debug!("installed_library_in_workspace(%s, %s) = %?", temp_pkg_id.short_name, temp.to_str(),
+           installed_library_in_workspace(temp_pkg_id.short_name, &temp));
+    assert!(match installed_library_in_workspace(temp_pkg_id.short_name, &temp) {
+        Some(p) => {
+            debug!("installed: %s", p.to_str());
+            p.to_str().ends_with(fmt!("0.3%s", os::consts::DLL_SUFFIX))
+        }
+        None    => false
+    });
+    assert!(target_executable_in_workspace(&temp_pkg_id, &temp)
+            == temp.push("bin").push("test_pkg_version"));
 
 }
diff --git a/src/librustpkg/util.rs b/src/librustpkg/util.rs
index 84af0ce5095..10d2d139a14 100644
--- a/src/librustpkg/util.rs
+++ b/src/librustpkg/util.rs
@@ -26,7 +26,7 @@ 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 path_util::{target_library_in_workspace, built_library_in_workspace};
 use search::find_library_in_search_path;
 pub use target::{OutputType, Main, Lib, Bench, Test};
 
@@ -274,7 +274,7 @@ pub fn compile_input(ctxt: &Ctx,
                  ~[@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()))))])))],
+                         mk_string_lit(@pkg_id.version.to_str_nonempty())))])))],
             ..copy crate.node});
     }
 
@@ -371,9 +371,14 @@ fn find_and_install_dependencies(ctxt: &Ctx,
                         // Try to install it
                         let pkg_id = PkgId::new(*lib_name);
                         my_ctxt.install(&my_workspace, &pkg_id);
+                        let built_lib =
+                            built_library_in_workspace(&pkg_id,
+                                &my_workspace).expect(fmt!("find_and_install_dependencies: \
+                                I thought I already built %s, but the library doesn't seem \
+                                to exist", *lib_name));
                         // Also, add an additional search path
-                        let installed_path = target_library_in_workspace(&pkg_id,
-                                                                         &my_workspace).pop();
+                        let installed_path = target_library_in_workspace(&my_workspace,
+                                                                         &built_lib).pop();
                         debug!("Great, I installed %s, and it's in %s",
                                *lib_name, installed_path.to_str());
                         save(installed_path);
diff --git a/src/librustpkg/version.rs b/src/librustpkg/version.rs
new file mode 100644
index 00000000000..92906f5af54
--- /dev/null
+++ b/src/librustpkg/version.rs
@@ -0,0 +1,215 @@
+// 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.
+
+/// A version is either an exact revision,
+/// or a semantic version
+
+extern mod std;
+
+use extra::semver;
+use core::prelude::*;
+use core::{char, os, result, run, str};
+use package_path::RemotePath;
+use extra::tempfile::mkdtemp;
+
+#[deriving(Eq)]
+pub enum Version {
+    ExactRevision(~str), // Should look like a m.n.(...).x
+    SemanticVersion(semver::Version),
+    NoVersion // user didn't specify a version
+}
+
+
+impl Ord for Version {
+    fn lt(&self, other: &Version) -> bool {
+        match (self, other) {
+            (&ExactRevision(ref f1), &ExactRevision(ref f2)) => f1 < f2,
+            (&SemanticVersion(ref v1), &SemanticVersion(ref v2)) => v1 < v2,
+            _ => false // incomparable, really
+        }
+    }
+    fn le(&self, other: &Version) -> bool {
+        match (self, other) {
+            (&ExactRevision(ref f1), &ExactRevision(ref f2)) => f1 <= f2,
+            (&SemanticVersion(ref v1), &SemanticVersion(ref v2)) => v1 <= v2,
+            _ => false // incomparable, really
+        }
+    }
+    fn ge(&self, other: &Version) -> bool {
+        match (self, other) {
+            (&ExactRevision(ref f1), &ExactRevision(ref f2)) => f1 > f2,
+            (&SemanticVersion(ref v1), &SemanticVersion(ref v2)) => v1 > v2,
+            _ => false // incomparable, really
+        }
+    }
+    fn gt(&self, other: &Version) -> bool {
+        match (self, other) {
+            (&ExactRevision(ref f1), &ExactRevision(ref f2)) => f1 >= f2,
+            (&SemanticVersion(ref v1), &SemanticVersion(ref v2)) => v1 >= v2,
+            _ => false // incomparable, really
+        }
+    }
+
+}
+
+impl ToStr for Version {
+    fn to_str(&self) -> ~str {
+        match *self {
+            ExactRevision(ref n) => fmt!("%s", n.to_str()),
+            SemanticVersion(ref v) => fmt!("%s", v.to_str()),
+            NoVersion => ~""
+        }
+    }
+}
+
+impl Version {
+    /// Fills in a bogus default version for NoVersion -- for use when
+    /// injecting link_meta attributes
+    fn to_str_nonempty(&self) -> ~str {
+        match *self {
+            NoVersion => ~"0.1",
+            _ => self.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")
+    }
+}
+
+
+/// If `remote_path` refers to a git repo that can be downloaded,
+/// and the most recent tag in that repo denotes a version, return it;
+/// otherwise, `None`
+pub fn try_getting_version(remote_path: &RemotePath) -> Option<Version> {
+    debug!("try_getting_version: %s", remote_path.to_str());
+    if is_url_like(remote_path) {
+        debug!("Trying to fetch its sources..");
+        let tmp_dir = mkdtemp(&os::tmpdir(),
+                              "test").expect("try_getting_version: couldn't create temp dir");
+        debug!("executing {git clone https://%s %s}", remote_path.to_str(), tmp_dir.to_str());
+        let outp  = run::process_output("git", [~"clone", fmt!("https://%s", remote_path.to_str()),
+                                                tmp_dir.to_str()]);
+        if outp.status == 0 {
+            debug!("Cloned it... ( %s, %s )",
+                   str::from_bytes(outp.output),
+                   str::from_bytes(outp.error));
+            let mut output = None;
+            debug!("executing {git --git-dir=%s tag -l}", tmp_dir.push(".git").to_str());
+            let outp = run::process_output("git",
+                                           [fmt!("--git-dir=%s", tmp_dir.push(".git").to_str()),
+                                            ~"tag", ~"-l"]);
+            let output_text = str::from_bytes(outp.output);
+            debug!("Full output: ( %s ) [%?]", output_text, outp.status);
+            for output_text.each_split_char('\n') |l| {
+                debug!("A line of output: %s", l);
+                if !l.is_whitespace() {
+                    output = Some(l);
+                }
+            }
+
+            output.chain(try_parsing_version)
+        }
+        else {
+            None
+        }
+    }
+    else {
+        None
+    }
+}
+
+// Being lazy since we don't have a regexp library now
+#[deriving(Eq)]
+enum ParseState {
+    Start,
+    SawDigit,
+    SawDot
+}
+
+fn try_parsing_version(s: &str) -> Option<Version> {
+    let s = s.trim();
+    debug!("Attempting to parse: %s", s);
+    let mut parse_state = Start;
+    // I gave up on using external iterators (tjc)
+    for str::to_chars(s).each() |&c| {
+        if char::is_digit(c) {
+            parse_state = SawDigit;
+        }
+        else if c == '.' && parse_state == SawDigit {
+            parse_state = SawDot;
+        }
+        else {
+            return None;
+        }
+    }
+    match parse_state {
+        SawDigit => Some(ExactRevision(s.to_owned())),
+        _        => None
+    }
+}
+
+/// Just an approximation
+fn is_url_like(p: &RemotePath) -> bool {
+    let mut n = 0;
+    for p.to_str().each_split_char('/') |_| {
+        n += 1;
+    }
+    n > 2
+}
+
+/// If s is of the form foo#bar, where bar is a valid version
+/// number, return the prefix before the # and the version.
+/// Otherwise, return None.
+pub fn split_version<'a>(s: &'a str) -> Option<(&'a str, Version)> {
+    // reject strings with multiple '#'s
+    if { let mut i: uint = 0; for str::to_chars(s).each |&c| { if c == '#' { i += 1; } }; i > 1 } {
+        return None;
+    }
+    match str::rfind_char(s, '#') {
+        Some(i) => {
+            debug!("in %s, i = %?", s, i);
+            let path = s.slice(0, i);
+            debug!("path = %s", path);
+            // n.b. for now, assuming an exact revision is intended, not a SemVer
+            Some((path, ExactRevision(s.slice(i + 1, s.len()).to_owned())))
+        }
+        None => {
+            debug!("%s doesn't look like an explicit-version thing", s);
+            None
+        }
+    }
+}
+
+#[test]
+fn test_parse_version() {
+    assert!(try_parsing_version("1.2") == Some(ExactRevision(~"1.2")));
+    assert!(try_parsing_version("1.0.17") == Some(ExactRevision(~"1.0.17")));
+    assert!(try_parsing_version("you're_a_kitty") == None);
+    assert!(try_parsing_version("42..1") == None);
+    assert!(try_parsing_version("17") == Some(ExactRevision(~"17")));
+    assert!(try_parsing_version(".1.2.3") == None);
+    assert!(try_parsing_version("2.3.") == None);
+}
+
+#[test]
+fn test_split_version() {
+    let s = "a/b/c#0.1";
+    debug!("== %? ==", split_version(s));
+    assert!(split_version(s) == Some((s.slice(0, 5), ExactRevision(~"0.1"))));
+    assert!(split_version("a/b/c") == None);
+    let s = "a#1.2";
+    assert!(split_version(s) == Some((s.slice(0, 1), ExactRevision(~"1.2"))));
+    assert!(split_version("a#a#3.4") == None);
+}
\ No newline at end of file
diff --git a/src/libstd/num/float.rs b/src/libstd/num/float.rs
index 97d661d8fe2..a96a854e6a0 100644
--- a/src/libstd/num/float.rs
+++ b/src/libstd/num/float.rs
@@ -47,7 +47,7 @@ pub static neg_infinity: float = -1.0/0.0;
 /* Module: consts */
 pub mod consts {
     // FIXME (requires Issue #1433 to fix): replace with mathematical
-    // staticants from cmath.
+    // constants from cmath.
     /// Archimedes' constant
     pub static pi: float = 3.14159265358979323846264338327950288;