Add support for detailed manifest dependencies
authorYehuda Katz + Carl Lerche <engineering@tilde.io>
Tue, 10 Jun 2014 00:51:53 +0000 (17:51 -0700)
committerTim Carey-Smith <tim@spork.in>
Tue, 10 Jun 2014 00:51:53 +0000 (17:51 -0700)
This commit supports the following format:

```toml
[dependencies.hamcrest]

version = "1.0"
git = "http://github.com/carllerche/hamcrest-rust"
```

MANIFEST.md
src/cargo/core/manifest.rs
src/cargo/lib.rs
src/cargo/ops/cargo_read_manifest.rs
src/cargo/sources/git/source.rs
tests/support.rs
tests/test_cargo_compile.rs

index e331a629b2be47c7cb8c952953cfdc029c708a72..9037b650386e109cf526a1a8c9a07c652afa60ad 100644 (file)
@@ -23,14 +23,14 @@ into Rust structs that are used throughout the built-in commands.
 
 ## The `[project]` Section
 
-* `name`: the name of the project (`~str`)
-* `version`: the version of the project, (`~str` that can be parsed
+* `name`: the name of the project (`String`)
+* `version`: the version of the project, (`String` that can be parsed
   via `semver::parse`)
 * `readme`: a Markdown-formatted file in the project that can be used as
   a description of the document in indexes (`Option<Path>`, relative to
   the project root, defaults to "./README.md", if found).
-* `tags`: an array of tags that can be used in indexes (`~[~str]`) 
-* `authors`: a list of authors in `name <email>` format (`~[~str]`). At
+* `tags`: an array of tags that can be used in indexes (`Vec<String>`) 
+* `authors`: a list of authors in `name <email>` format (`Vec<String>`). At
   least one `author` with email will probably be required to submit to
   the Cargo repository.
 * `src`: the root directory containing source files (`Option<Path>`,
@@ -45,7 +45,7 @@ We only plan to support a single lib at the moment because if you have
 multiple libs, you would want projects to be able to depend on them
 separately. If you don't care about that, why do you have separate libs?
 
-* `name`: the name of the library (`~str`, `hammer` would create a `libhammer`)
+* `name`: the name of the library (`String`, `hammer` would create a `libhammer`)
 * `path`: the location of the main crate file (`Option<Path>`, defaults to
   `src/<name>.rs`)
 
@@ -64,13 +64,27 @@ main library, it should be shipped as a separate package with a
 dependency on the main library to keep the usage requirements of the
 standalone library limited to the bare minimum requirements.
 
-* `name`: the name of the executable (`~str`, `hammer` would create a
+* `name`: the name of the executable (`String`, `hammer` would create a
   `hammer` executable)
 * `path`: the location of the main crate file for the executable
   (`Option<Path>`, defaults to `src/<name>.rs` if the project has only
   an executable, `src/bin/<name>.rs` if the project has both a lib and
   executable, see below)
 
+## The `[dependencies]` Section
+
+```toml
+[dependencies]
+
+rust-http = "1.x"
+hammer = ["> 1.2", "< 1.3.5"]
+
+[dependencies.hamcrest]
+
+version = "1.2.x"
+git = "http://github.com/carllerche/hamcrest"
+```
+
 ## Projects Containing Both `lib` and `executable`
 
 Most projects will primarily produce either a library or an executable.
index a2be98d7df5203ac54eaa897e3a46ca9e8683e84..6a57ca343f1d705e26d3c8209e7993798c9988c6 100644 (file)
@@ -2,7 +2,7 @@ use std::fmt;
 use std::fmt::{Show,Formatter};
 use std::collections::HashMap;
 use semver::Version;
-use serialize::{Encoder,Encodable};
+use serialize::{Encoder,Decoder,Encodable,Decodable};
 use core::{
     Dependency,
     NameVer,
@@ -10,7 +10,9 @@ use core::{
     Summary
 };
 use core::dependency::SerializedDependency;
-use util::CargoResult;
+use util::{CargoResult,Require,toml_error,simple_human};
+use toml;
+use toml::{Table, ParseError};
 
 #[deriving(PartialEq,Clone)]
 pub struct Manifest {
@@ -192,21 +194,75 @@ pub struct Project {
  * TODO: Make all struct fields private
  */
 
-#[deriving(Decodable,Encodable,PartialEq,Clone)]
+#[deriving(Encodable,PartialEq,Clone,Show)]
+pub enum TomlDependency {
+    SimpleDep(String),
+    DetailedDep(HashMap<String, String>)
+}
+
+#[deriving(Encodable,PartialEq,Clone)]
 pub struct TomlManifest {
     project: Box<Project>,
-    lib: Option<~[TomlLibTarget]>,
-    bin: Option<~[TomlBinTarget]>,
-    dependencies: Option<HashMap<String, String>>,
+    lib: Option<Vec<TomlLibTarget>>,
+    bin: Option<Vec<TomlBinTarget>>,
+    dependencies: Option<HashMap<String, TomlDependency>>,
 }
 
 impl TomlManifest {
+    pub fn from_toml(root: toml::Value) -> CargoResult<TomlManifest> {
+        fn decode<T: Decodable<toml::Decoder,toml::Error>>(root: &toml::Value, path: &str) -> Result<T, toml::Error> {
+            let root = match root.lookup(path) {
+                Some(val) => val,
+                None => return Err(toml::ParseError)
+            };
+            toml::from_toml(root.clone())
+        }
+
+        let project = try!(decode(&root, "project").map_err(|e| toml_error("ZOMG", e)));
+        let lib = decode(&root, "lib").ok();
+        let bin = decode(&root, "bin").ok();
+
+        let deps = root.lookup("dependencies");
+
+        let deps = match deps {
+            Some(deps) => {
+                let table = try!(deps.get_table().require(simple_human("dependencies must be a table"))).clone();
+
+                let mut deps: HashMap<String, TomlDependency> = HashMap::new();
+
+                for (k, v) in table.iter() {
+                    match v {
+                        &toml::String(ref string) => { deps.insert(k.clone(), SimpleDep(string.clone())); },
+                        &toml::Table(ref table) => {
+                            let mut details = HashMap::<String, String>::new();
+
+                            for (k, v) in table.iter() {
+                                let v = try!(v.get_str()
+                                             .require(simple_human("dependency values must be string")));
+
+                                details.insert(k.clone(), v.clone());
+                            }
+
+                            deps.insert(k.clone(), DetailedDep(details));
+                        },
+                        _ => ()
+                    }
+                }
+
+                Some(deps)
+            },
+            None => None
+        };
+
+        Ok(TomlManifest { project: box project, lib: lib, bin: bin, dependencies: deps })
+    }
+
     pub fn to_package(&self, path: &str) -> CargoResult<Package> {
         // TODO: Convert hte argument to take a Path
         let path = Path::new(path);
 
         // Get targets
-        let targets = normalize(&self.lib, &self.bin);
+        let targets = normalize(self.lib.as_ref().map(|l| l.as_slice()), self.bin.as_ref().map(|b| b.as_slice()));
 
         if targets.is_empty() {
             debug!("manifest has no build targets; project={}", self.project);
@@ -218,7 +274,13 @@ impl TomlManifest {
         match self.dependencies {
             Some(ref dependencies) => {
                 for (n, v) in dependencies.iter() {
-                    deps.push(try!(Dependency::parse(n.as_slice(), v.as_slice())));
+                    let version = match *v {
+                        SimpleDep(ref string) => string,
+                        DetailedDep(ref map) => try!(map.find_equiv(&"version")
+                                                     .require(simple_human("dependencies must include a version")))
+                    };
+
+                    deps.push(try!(Dependency::parse(n.as_slice(), version.as_slice())))
                 }
             }
             None => ()
@@ -248,7 +310,7 @@ struct TomlTarget {
     path: Option<String>
 }
 
-fn normalize(lib: &Option<~[TomlLibTarget]>, bin: &Option<~[TomlBinTarget]>) -> Vec<Target> {
+fn normalize(lib: Option<&[TomlLibTarget]>, bin: Option<&[TomlBinTarget]>) -> Vec<Target> {
     log!(4, "normalizing toml targets; lib={}; bin={}", lib, bin);
 
     fn lib_targets(dst: &mut Vec<Target>, libs: &[TomlLibTarget]) {
@@ -267,17 +329,17 @@ fn normalize(lib: &Option<~[TomlLibTarget]>, bin: &Option<~[TomlBinTarget]>) ->
     let mut ret = Vec::new();
 
     match (lib, bin) {
-        (&Some(ref libs), &Some(ref bins)) => {
+        (Some(ref libs), Some(ref bins)) => {
             lib_targets(&mut ret, libs.as_slice());
             bin_targets(&mut ret, bins.as_slice(), |bin| format!("src/bin/{}.rs", bin.name));
         },
-        (&Some(ref libs), &None) => {
+        (Some(ref libs), None) => {
             lib_targets(&mut ret, libs.as_slice());
         },
-        (&None, &Some(ref bins)) => {
+        (None, Some(ref bins)) => {
             bin_targets(&mut ret, bins.as_slice(), |bin| format!("src/{}.rs", bin.name));
         },
-        (&None, &None) => ()
+        (None, None) => ()
     }
 
     ret
index 94aa0f2783664a123e24ba456d1aa8b5ef685209..a4e693ad45aa64af3680ff141fa28e9fd5184c19 100644 (file)
@@ -4,6 +4,7 @@
 #![allow(deprecated_owned_vector)]
 #![feature(macro_rules,phase)]
 
+extern crate debug;
 extern crate term;
 extern crate url;
 extern crate serialize;
index ec57001bca1aef6d13eb36be2ecc96e109008ced..a4b197aaaacecd0f97fc29284d82a9eb11e26a7e 100644 (file)
@@ -19,7 +19,7 @@ fn parse_from_file(path: &str) -> CargoResult<toml::Value> {
 }
 
 fn load_toml(root: toml::Value) -> CargoResult<TomlManifest> {
-    from_toml::<TomlManifest>(root).map_err(to_cargo_err)
+    TomlManifest::from_toml(root)
 }
 
 fn to_cargo_err(err: toml::Error) -> CargoError {
index f1c5db2cce944410e3516335070bb86d65aa037c..bc985e4437a4c29e8f383693eae97cd191dd5634 100644 (file)
@@ -49,6 +49,7 @@ impl Source for GitSource {
     }
 
     fn get(&self, packages: &[NameVer]) -> CargoResult<Vec<Package>> {
+        // TODO: Support multiple manifests per repo
         let pkg = try!(read_manifest(&self.checkout_path));
 
         if packages.iter().any(|nv| pkg.is_for_name_ver(nv)) {
index 282eb0dd16b2509e0a78c36c1a53c4a7d7cc9407..efc34259a54b6287514d1294813ea945378cc9f4 100644 (file)
@@ -273,7 +273,7 @@ impl ham::Matcher<ProcessBuilder> for Execs {
     match res {
       Ok(out) => self.match_output(&out),
       Err(CargoError { kind: ProcessError(_, ref out), .. }) => self.match_output(out.get_ref()),
-      Err(_) => Err(format!("could not exec process {}", process))
+      Err(e) => Err(format!("could not exec process {}: {}", process, e))
     }
   }
 }
index 15d3a659fc6bc0eb9a608568b0a28a4740989d3e..f212d8ef8efb08b9dce3af3de6f73f586a441de4 100644 (file)
@@ -207,6 +207,79 @@ test!(cargo_compile_with_nested_deps {
       execs().with_stdout("test passed\n"));
 })
 
+test!(cargo_compile_with_nested_deps_longhand {
+    let mut p = project("foo");
+    let bar = p.root().join("bar");
+    let baz = p.root().join("baz");
+
+    p = p
+        .file(".cargo/config", format!(r#"
+            paths = ["{}", "{}"]
+        "#, bar.display(), baz.display()).as_slice())
+        .file("Cargo.toml", r#"
+            [project]
+
+            name = "foo"
+            version = "0.5.0"
+            authors = ["wycats@example.com"]
+
+            [dependencies.bar]
+
+            version = "0.5.0"
+
+            [[bin]]
+
+            name = "foo"
+        "#)
+        .file("src/foo.rs", main_file(r#""{}", bar::gimme()"#, ["bar"]).as_slice())
+        .file("bar/Cargo.toml", r#"
+            [project]
+
+            name = "bar"
+            version = "0.5.0"
+            authors = ["wycats@example.com"]
+
+            [dependencies.baz]
+
+            version = "0.5.0"
+
+            [[lib]]
+
+            name = "bar"
+        "#)
+        .file("bar/src/bar.rs", r#"
+            extern crate baz;
+
+            pub fn gimme() -> String {
+                baz::gimme()
+            }
+        "#)
+        .file("baz/Cargo.toml", r#"
+            [project]
+
+            name = "baz"
+            version = "0.5.0"
+            authors = ["wycats@example.com"]
+
+            [[lib]]
+
+            name = "baz"
+        "#)
+        .file("baz/src/baz.rs", r#"
+            pub fn gimme() -> String {
+                "test passed".to_str()
+            }
+        "#);
+
+    assert_that(p.cargo_process("cargo-compile"), execs());
+
+    assert_that(&p.root().join("target/foo"), existing_file());
+
+    assert_that(
+      cargo::util::process("foo").extra_path(p.root().join("target")),
+      execs().with_stdout("test passed\n"));
+})
+
 fn main_file(println: &str, deps: &[&str]) -> String {
     let mut buf = String::new();