extern crate rustc_serialize;
extern crate toml;
-use std::path::PathBuf;
-
-use cargo::ops::{output_metadata, OutputTo, OutputMetadataOptions};
+use cargo::ops::{output_metadata, OutputMetadataOptions, ExportInfo};
use cargo::util::important_paths::find_root_manifest_for_wd;
use cargo::util::{CliResult, Config};
flag_format_version: u32,
flag_manifest_path: Option<String>,
flag_no_default_features: bool,
- flag_output_format: String,
- flag_output_path: Option<String>,
flag_quiet: bool,
flag_verbose: bool,
}
Options:
-h, --help Print this message
- -o, --output-path PATH Path the output is written to, otherwise stdout is used
- -f, --output-format FMT Output format [default: toml]
- Valid values: toml, json
--features FEATURES Space-separated list of features
--no-default-features Do not include the `default` feature
--manifest-path PATH Path to the manifest
--color WHEN Coloring: auto, always, never
";
-pub fn execute(options: Options, config: &Config) -> CliResult<Option<()>> {
+pub fn execute(options: Options, config: &Config) -> CliResult<Option<ExportInfo>> {
try!(config.shell().set_verbosity(options.flag_verbose, options.flag_quiet));
try!(config.shell().set_color_config(options.flag_color.as_ref().map(|s| &s[..])));
let manifest = try!(find_root_manifest_for_wd(options.flag_manifest_path, config.cwd()));
- let output_to = match options.flag_output_path {
- Some(path) => OutputTo::File(PathBuf::from(path)),
- None => OutputTo::StdOut
- };
-
let options = OutputMetadataOptions {
features: options.flag_features,
manifest_path: &manifest,
no_default_features: options.flag_no_default_features,
- output_format: options.flag_output_format,
- output_to: output_to,
version: options.flag_format_version,
};
- try!(output_metadata(options, config));
- Ok(None)
+ let result = try!(output_metadata(options, config));
+ Ok(Some(result))
}
-use std::ascii::AsciiExt;
-use std::path::{Path, PathBuf};
+use std::path::Path;
use core::resolver::Resolve;
use core::{Source, Package};
use ops;
-use rustc_serialize::json;
use sources::PathSource;
-use toml;
use util::config::Config;
-use util::{paths, CargoResult};
+use util::CargoResult;
const VERSION: u32 = 1;
-/// Where the dependencies should be written to.
-pub enum OutputTo {
- File(PathBuf),
- StdOut,
-}
-
pub struct OutputMetadataOptions<'a> {
pub features: Vec<String>,
pub manifest_path: &'a Path,
pub no_default_features: bool,
- pub output_format: String,
- pub output_to: OutputTo,
pub version: u32,
}
/// Loads the manifest, resolves the dependencies of the project to the concrete
-/// used versions - considering overrides - and writes all dependencies in a TOML
-/// format to stdout or the specified file.
-///
-/// The TOML format is e.g.:
-/// ```toml
-/// root = "libA"
-///
-/// [packages.libA]
-/// dependencies = ["libB"]
-/// path = "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/libA-0.1"
-/// version = "0.1"
-///
-/// [packages.libB]
-/// dependencies = []
-/// path = "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/libB-0.4"
-/// version = "0.4"
-///
-/// [packages.libB.features]
-/// featureA = ["featureB"]
-/// featureB = []
-/// ```
-pub fn output_metadata(opt: OutputMetadataOptions, config: &Config) -> CargoResult<()> {
+/// used versions - considering overrides - and writes all dependencies in a JSON
+/// format to stdout.
+pub fn output_metadata<'a>(opt: OutputMetadataOptions,
+ config: &'a Config)
+ -> CargoResult<ExportInfo> {
let deps = try!(resolve_dependencies(opt.manifest_path,
config,
opt.features,
let (packages, resolve) = deps;
assert_eq!(opt.version, VERSION);
- let output = ExportInfo {
- packages: &packages,
- resolve: &resolve,
+ Ok(ExportInfo {
+ packages: packages,
+ resolve: resolve,
version: VERSION,
- };
-
- let serialized_str = match &opt.output_format.to_ascii_uppercase()[..] {
- "TOML" => toml::encode_str(&output),
- "JSON" => try!(json::encode(&output)),
- _ => bail!("unknown format: {}, supported formats are TOML, JSON.",
- opt.output_format),
- };
-
- match opt.output_to {
- OutputTo::StdOut => println!("{}", serialized_str),
- OutputTo::File(ref path) => try!(paths::write(path, serialized_str.as_bytes()))
- }
-
- Ok(())
+ })
}
#[derive(RustcEncodable)]
-struct ExportInfo<'a> {
- packages: &'a [Package],
- resolve: &'a Resolve,
+pub struct ExportInfo {
+ packages: Vec<Package>,
+ resolve: Resolve,
version: u32,
}
pub use self::cargo_fetch::{fetch, get_resolved_packages};
pub use self::cargo_pkgid::pkgid;
pub use self::resolve::{resolve_pkg, resolve_with_previous};
-pub use self::cargo_output_metadata::{output_metadata, OutputTo, OutputMetadataOptions};
+pub use self::cargo_output_metadata::{output_metadata, OutputMetadataOptions, ExportInfo};
mod cargo_clean;
mod cargo_compile;
use std::str;
use std::usize;
+use rustc_serialize::json::Json;
use url::Url;
use hamcrest as ham;
use cargo::util::ProcessBuilder;
expect_exit_code: Option<i32>,
expect_stdout_contains: Vec<String>,
expect_stderr_contains: Vec<String>,
+ expect_json: Option<Json>,
}
impl Execs {
self
}
+ pub fn with_json(mut self, expected: &str) -> Execs {
+ self.expect_json = Some(Json::from_str(expected).unwrap());
+ self
+ }
+
fn match_output(&self, actual: &Output) -> ham::MatchResult {
self.match_status(actual)
.and(self.match_stdout(actual))
try!(self.match_std(Some(expect), &actual.stderr, "stderr",
&actual.stdout, true));
}
+
+ if let Some(ref expect_json) = self.expect_json {
+ try!(self.match_json(expect_json, &actual.stdout));
+ }
Ok(())
}
}
+ fn match_json(&self, expected: &Json, stdout: &[u8]) -> ham::MatchResult {
+ let stdout = match str::from_utf8(stdout) {
+ Err(..) => return Err("stdout was not utf8 encoded".to_owned()),
+ Ok(stdout) => stdout,
+ };
+
+ let actual = match Json::from_str(stdout) {
+ Err(..) => return Err(format!("Invalid json {}", stdout)),
+ Ok(actual) => actual,
+ };
+
+ match find_mismatch(expected, &actual) {
+ Some((expected_part, actual_part)) => Err(format!(
+ "JSON mismatch\nExpected:\n{}\nWas:\n{}\nExpected part:\n{}\nActual part:\n{}\n",
+ expected.pretty(), actual.pretty(),
+ expected_part.pretty(), actual_part.pretty()
+ )),
+ None => Ok(()),
+ }
+ }
+
fn diff_lines<'a>(&self, actual: str::Lines<'a>, expected: str::Lines<'a>,
partial: bool) -> Vec<String> {
let actual = actual.take(if partial {
actual.is_empty() || expected.ends_with("[..]")
}
+// Compares JSON object for approximate equality.
+// You can use `[..]` wildcard in strings (useful for OS dependent things such as paths).
+// Arrays are sorted before comparison.
+fn find_mismatch<'a>(expected: &'a Json, actual: &'a Json) -> Option<(&'a Json, &'a Json)> {
+ use rustc_serialize::json::Json::*;
+ match (expected, actual) {
+ (&I64(l), &I64(r)) if l == r => None,
+ (&F64(l), &F64(r)) if l == r => None,
+ (&U64(l), &U64(r)) if l == r => None,
+ (&Boolean(l), &Boolean(r)) if l == r => None,
+ (&String(ref l), &String(ref r)) if lines_match(l, r) => None,
+ (&Array(ref l), &Array(ref r)) => {
+ if l.len() != r.len() {
+ return Some((expected, actual));
+ }
+
+ fn sorted(xs: &Vec<Json>) -> Vec<&Json> {
+ let mut result = xs.iter().collect::<Vec<_>>();
+ // `unwrap` should be safe because JSON spec does not allow NaNs
+ result.sort_by(|x, y| x.partial_cmp(y).unwrap());
+ result
+ }
+
+ sorted(l).iter().zip(sorted(r))
+ .filter_map(|(l, r)| find_mismatch(l, r))
+ .nth(0)
+ }
+ (&Object(ref l), &Object(ref r)) => {
+ let same_keys = l.len() == r.len() && l.keys().all(|k| r.contains_key(k));
+ if !same_keys {
+ return Some((expected, actual));
+ }
+
+ l.values().zip(r.values())
+ .filter_map(|(l, r)| find_mismatch(l, r))
+ .nth(0)
+ }
+ (&Null, &Null) => None,
+ _ => Some((expected, actual)),
+ }
+
+}
+
struct ZipAll<I1: Iterator, I2: Iterator> {
first: I1,
second: I2,
expect_exit_code: None,
expect_stdout_contains: Vec::new(),
expect_stderr_contains: Vec::new(),
+ expect_json: None,
}
}
-use std::fs::File;
-use std::io::prelude::*;
-
-use hamcrest::{assert_that, existing_file, is, equal_to};
-use rustc_serialize::json::Json;
+use hamcrest::{assert_that, existing_file};
use support::registry::Package;
use support::{project, execs, basic_bin_manifest};
-fn setup() {
-}
+fn setup() {}
test!(cargo_metadata_simple {
let p = project("foo")
.file("Cargo.toml", &basic_bin_manifest("foo"));
- assert_that(p.cargo_process("metadata"), execs().with_stdout(r#"version = 1
-
-[[packages]]
-dependencies = []
-id = "foo 0.5.0 [..]"
-manifest_path = "[..]Cargo.toml"
-name = "foo"
-version = "0.5.0"
-
-[packages.features]
-
-[[packages.targets]]
-kind = ["bin"]
-name = "foo"
-src_path = "src[..]foo.rs"
-
-[resolve]
-package = []
-
-[resolve.root]
-dependencies = []
-name = "foo"
-version = "0.5.0"
-
-"#));
-});
-
-
-test!(cargo_metadata_simple_json {
- let p = project("foo")
- .file("Cargo.toml", &basic_bin_manifest("foo"));
-
- assert_that(p.cargo_process("metadata").arg("-f").arg("json"), execs().with_stdout(r#"
- {
- "packages": [
- {
- "name": "foo",
- "version": "0.5.0",
- "id": "foo[..]",
- "source": null,
- "dependencies": [],
- "targets": [
- {
- "kind": [
- "bin"
- ],
- "name": "foo",
- "src_path": "src[..]foo.rs"
- }
- ],
- "features": {},
- "manifest_path": "[..]Cargo.toml"
- }
- ],
- "resolve": {
- "package": [],
- "root": {
- "name": "foo",
- "version": "0.5.0",
- "source": null,
- "dependencies" : []
- },
- "metadata": null
+ assert_that(p.cargo_process("metadata"), execs().with_json(r#"
+ {
+ "packages": [
+ {
+ "name": "foo",
+ "version": "0.5.0",
+ "id": "foo[..]",
+ "source": null,
+ "dependencies": [],
+ "targets": [
+ {
+ "kind": [
+ "bin"
+ ],
+ "name": "foo",
+ "src_path": "src[..]foo.rs"
+ }
+ ],
+ "features": {},
+ "manifest_path": "[..]Cargo.toml"
+ }
+ ],
+ "resolve": {
+ "package": [],
+ "root": {
+ "name": "foo",
+ "version": "0.5.0",
+ "source": null,
+ "dependencies" : []
},
- "version": 1
- }"#.split_whitespace().collect::<String>()));
+ "metadata": null
+ },
+ "version": 1
+ }"#));
});
Package::new("baz", "0.0.1").publish();
Package::new("bar", "0.0.1").dep("baz", "0.0.1").publish();
- assert_that(p.cargo_process("metadata")
- .arg("--output-path").arg("metadata.json")
- .arg("-f").arg("json"),
-
- execs().with_status(0));
-
- let outputfile = p.root().join("metadata.json");
- assert_that(&outputfile, existing_file());
-
- let mut output = String::new();
- File::open(&outputfile).unwrap().read_to_string(&mut output).unwrap();
- let result = Json::from_str(&output).unwrap();
- println!("{}", result.pretty());
-
- let packages = result.find("packages")
- .and_then(|o| o.as_array())
- .expect("no packages");
-
- assert_that(packages.len(), is(equal_to(3)));
-
- let root = result.find_path(&["resolve", "root"])
- .and_then(|o| o.as_object())
- .expect("no root");
-
- // source is null because foo is root
- let foo_id_start = format!("{} {}", root["name"].as_string().unwrap(),
- root["version"].as_string().unwrap());
- let foo_name = packages.iter().find(|o| {
- o.find("id").and_then(|i| i.as_string()).unwrap()
- .starts_with(&foo_id_start)
- }).and_then(|p| p.find("name"))
- .and_then(|n| n.as_string())
- .expect("no root package");
- assert_that(foo_name, is(equal_to("foo")));
-
- let foo_deps = root["dependencies"].as_array().expect("no root deps");
- assert_that(foo_deps.len(), is(equal_to(1)));
-
- let bar = &foo_deps[0].as_string().expect("bad root dep");
-
- let check_name_for_id = |id: &str, expected_name: &str| {
- let name = packages.iter().find(|o| {
- id == o.find("id").and_then(|i| i.as_string()).unwrap()
- }).and_then(|p| p.find("name"))
- .and_then(|n| n.as_string())
- .expect(&format!("no {} in packages", expected_name));
-
- assert_that(name, is(equal_to(expected_name)));
- };
-
- let find_deps = |id: &str| -> Vec<_> {
- result.find_path(&["resolve", "package"])
- .and_then(|o| o.as_array()).expect("resolve.package is not an array")
- .iter()
- .find(|o| {
- let o = o.as_object().expect("package is not an object");
- let o_id = format!("{} {} ({})",
- o["name"].as_string().unwrap(),
- o["version"].as_string().unwrap(),
- o["source"].as_string().unwrap());
- id == o_id
- })
- .and_then(|o| o.find("dependencies"))
- .and_then(|o| o.as_array())
- .and_then(|a| a.iter()
- .map(|o| o.as_string())
- .collect())
- .expect(&format!("no deps for {}", id))
- };
-
-
- check_name_for_id(bar, "bar");
- let bar_deps = find_deps(&bar);
- assert_that(bar_deps.len(), is(equal_to(1)));
-
- let baz = &bar_deps[0];
- check_name_for_id(baz, "baz");
- let baz_deps = find_deps(&baz);
- assert_that(baz_deps.len(), is(equal_to(0)));
+ assert_that(p.cargo_process("metadata").arg("-q"), execs().with_json(r#"
+ {
+ "packages": [
+ {
+ "dependencies": [],
+ "features": {},
+ "id": "baz 0.0.1 (registry+file:[..])",
+ "manifest_path": "[..]Cargo.toml",
+ "name": "baz",
+ "source": "registry+file:[..]",
+ "targets": [
+ {
+ "kind": [
+ "lib"
+ ],
+ "name": "baz",
+ "src_path": "[..]lib.rs"
+ }
+ ],
+ "version": "0.0.1"
+ },
+ {
+ "dependencies": [
+ {
+ "features": [],
+ "kind": null,
+ "name": "baz",
+ "optional": false,
+ "req": "^0.0.1",
+ "source": "registry+file:[..]",
+ "target": null,
+ "uses_default_features": true
+ }
+ ],
+ "features": {},
+ "id": "bar 0.0.1 (registry+file:[..])",
+ "manifest_path": "[..]Cargo.toml",
+ "name": "bar",
+ "source": "registry+file:[..]",
+ "targets": [
+ {
+ "kind": [
+ "lib"
+ ],
+ "name": "bar",
+ "src_path": "[..]lib.rs"
+ }
+ ],
+ "version": "0.0.1"
+ },
+ {
+ "dependencies": [
+ {
+ "features": [],
+ "kind": null,
+ "name": "bar",
+ "optional": false,
+ "req": "*",
+ "source": "registry+file:[..]",
+ "target": null,
+ "uses_default_features": true
+ }
+ ],
+ "features": {},
+ "id": "foo 0.5.0 (path+file:[..]foo)",
+ "manifest_path": "[..]Cargo.toml",
+ "name": "foo",
+ "source": null,
+ "targets": [
+ {
+ "kind": [
+ "bin"
+ ],
+ "name": "foo",
+ "src_path": "[..]foo.rs"
+ }
+ ],
+ "version": "0.5.0"
+ }
+ ],
+ "resolve": {
+ "metadata": null,
+ "package": [
+ {
+ "dependencies": [
+ "baz 0.0.1 (registry+file:[..])"
+ ],
+ "name": "bar",
+ "source": "registry+file:[..]",
+ "version": "0.0.1"
+ },
+ {
+ "dependencies": [],
+ "name": "baz",
+ "source": "registry+file:[..]",
+ "version": "0.0.1"
+ }
+ ],
+ "root": {
+ "dependencies": [
+ "bar 0.0.1 (registry+file:[..])"
+ ],
+ "name": "foo",
+ "source": null,
+ "version": "0.5.0"
+ }
+ },
+ "version": 1
+ }"#));
});
test!(cargo_metadata_with_invalid_manifest {
Caused by:
no `package` or `project` section found."))
});
-
-test!(cargo_metadata_with_invalid_output_format {
- let p = project("foo")
- .file("Cargo.toml", &basic_bin_manifest("foo"));
-
- assert_that(p.cargo_process("metadata").arg("--output-format").arg("XML"),
- execs().with_status(101)
- .with_stderr("unknown format: XML, supported formats are TOML, JSON."))
-});
-
-test!(cargo_metadata_simple_file {
- let p = project("foo")
- .file("Cargo.toml", &basic_bin_manifest("foo"));
-
- assert_that(p.cargo_process("metadata").arg("--output-path").arg("metadata.toml"),
- execs().with_status(0));
-
- let outputfile = p.root().join("metadata.toml");
- assert_that(&outputfile, existing_file());
-
- let mut output = String::new();
- File::open(&outputfile).unwrap().read_to_string(&mut output).unwrap();
-
- assert_that(output[..].contains(r#"name = "foo""#), is(equal_to(true)));
-});