Add support for detailed manifest dependencies

This commit supports the following format:

```toml
[dependencies.hamcrest]

version = "1.0"
git = "http://github.com/carllerche/hamcrest-rust"
```
This commit is contained in:
Yehuda Katz + Carl Lerche 2014-06-09 17:51:53 -07:00 committed by Tim Carey-Smith
parent 9a1c63ea17
commit 92602f6db0
7 changed files with 172 additions and 21 deletions

View 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.

View 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

View 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;

View 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 {

View 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)) {

View 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))
}
}
}

View 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();