Skip to content

Commit b24bf7e

Browse files
committed
metadata: use existing infra for JSON output
1 parent 874fe26 commit b24bf7e

File tree

5 files changed

+239
-251
lines changed

5 files changed

+239
-251
lines changed

src/bin/metadata.rs

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@ extern crate docopt;
33
extern crate rustc_serialize;
44
extern crate toml;
55

6-
use std::path::PathBuf;
7-
8-
use cargo::ops::{output_metadata, OutputTo, OutputMetadataOptions};
6+
use cargo::ops::{output_metadata, OutputMetadataOptions, ExportInfo};
97
use cargo::util::important_paths::find_root_manifest_for_wd;
108
use cargo::util::{CliResult, Config};
119

@@ -16,8 +14,6 @@ struct Options {
1614
flag_format_version: u32,
1715
flag_manifest_path: Option<String>,
1816
flag_no_default_features: bool,
19-
flag_output_format: String,
20-
flag_output_path: Option<String>,
2117
flag_quiet: bool,
2218
flag_verbose: bool,
2319
}
@@ -31,9 +27,6 @@ Usage:
3127
3228
Options:
3329
-h, --help Print this message
34-
-o, --output-path PATH Path the output is written to, otherwise stdout is used
35-
-f, --output-format FMT Output format [default: toml]
36-
Valid values: toml, json
3730
--features FEATURES Space-separated list of features
3831
--no-default-features Do not include the `default` feature
3932
--manifest-path PATH Path to the manifest
@@ -44,25 +37,18 @@ Options:
4437
--color WHEN Coloring: auto, always, never
4538
";
4639

47-
pub fn execute(options: Options, config: &Config) -> CliResult<Option<()>> {
40+
pub fn execute(options: Options, config: &Config) -> CliResult<Option<ExportInfo>> {
4841
try!(config.shell().set_verbosity(options.flag_verbose, options.flag_quiet));
4942
try!(config.shell().set_color_config(options.flag_color.as_ref().map(|s| &s[..])));
5043
let manifest = try!(find_root_manifest_for_wd(options.flag_manifest_path, config.cwd()));
5144

52-
let output_to = match options.flag_output_path {
53-
Some(path) => OutputTo::File(PathBuf::from(path)),
54-
None => OutputTo::StdOut
55-
};
56-
5745
let options = OutputMetadataOptions {
5846
features: options.flag_features,
5947
manifest_path: &manifest,
6048
no_default_features: options.flag_no_default_features,
61-
output_format: options.flag_output_format,
62-
output_to: output_to,
6349
version: options.flag_format_version,
6450
};
6551

66-
try!(output_metadata(options, config));
67-
Ok(None)
52+
let result = try!(output_metadata(options, config));
53+
Ok(Some(result))
6854
}

src/cargo/ops/cargo_output_metadata.rs

Lines changed: 14 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,45 @@
1-
use std::ascii::AsciiExt;
2-
use std::path::{Path, PathBuf};
1+
use std::path::Path;
32

43
use core::resolver::Resolve;
54
use core::{Source, Package};
65
use ops;
7-
use rustc_serialize::json;
86
use sources::PathSource;
9-
use toml;
107
use util::config::Config;
11-
use util::{paths, CargoResult};
8+
use util::CargoResult;
129

1310
const VERSION: u32 = 1;
1411

15-
/// Where the dependencies should be written to.
16-
pub enum OutputTo {
17-
File(PathBuf),
18-
StdOut,
19-
}
20-
2112
pub struct OutputMetadataOptions<'a> {
2213
pub features: Vec<String>,
2314
pub manifest_path: &'a Path,
2415
pub no_default_features: bool,
25-
pub output_format: String,
26-
pub output_to: OutputTo,
2716
pub version: u32,
2817
}
2918

3019
/// Loads the manifest, resolves the dependencies of the project to the concrete
31-
/// used versions - considering overrides - and writes all dependencies in a TOML
32-
/// format to stdout or the specified file.
33-
///
34-
/// The TOML format is e.g.:
35-
/// ```toml
36-
/// root = "libA"
37-
///
38-
/// [packages.libA]
39-
/// dependencies = ["libB"]
40-
/// path = "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/libA-0.1"
41-
/// version = "0.1"
42-
///
43-
/// [packages.libB]
44-
/// dependencies = []
45-
/// path = "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/libB-0.4"
46-
/// version = "0.4"
47-
///
48-
/// [packages.libB.features]
49-
/// featureA = ["featureB"]
50-
/// featureB = []
51-
/// ```
52-
pub fn output_metadata(opt: OutputMetadataOptions, config: &Config) -> CargoResult<()> {
20+
/// used versions - considering overrides - and writes all dependencies in a JSON
21+
/// format to stdout.
22+
pub fn output_metadata<'a>(opt: OutputMetadataOptions,
23+
config: &'a Config)
24+
-> CargoResult<ExportInfo> {
5325
let deps = try!(resolve_dependencies(opt.manifest_path,
5426
config,
5527
opt.features,
5628
opt.no_default_features));
5729
let (packages, resolve) = deps;
5830

5931
assert_eq!(opt.version, VERSION);
60-
let output = ExportInfo {
61-
packages: &packages,
62-
resolve: &resolve,
32+
Ok(ExportInfo {
33+
packages: packages,
34+
resolve: resolve,
6335
version: VERSION,
64-
};
65-
66-
let serialized_str = match &opt.output_format.to_ascii_uppercase()[..] {
67-
"TOML" => toml::encode_str(&output),
68-
"JSON" => try!(json::encode(&output)),
69-
_ => bail!("unknown format: {}, supported formats are TOML, JSON.",
70-
opt.output_format),
71-
};
72-
73-
match opt.output_to {
74-
OutputTo::StdOut => println!("{}", serialized_str),
75-
OutputTo::File(ref path) => try!(paths::write(path, serialized_str.as_bytes()))
76-
}
77-
78-
Ok(())
36+
})
7937
}
8038

8139
#[derive(RustcEncodable)]
82-
struct ExportInfo<'a> {
83-
packages: &'a [Package],
84-
resolve: &'a Resolve,
40+
pub struct ExportInfo {
41+
packages: Vec<Package>,
42+
resolve: Resolve,
8543
version: u32,
8644
}
8745

src/cargo/ops/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ pub use self::registry::{modify_owners, yank, OwnersOptions};
2323
pub use self::cargo_fetch::{fetch, get_resolved_packages};
2424
pub use self::cargo_pkgid::pkgid;
2525
pub use self::resolve::{resolve_pkg, resolve_with_previous};
26-
pub use self::cargo_output_metadata::{output_metadata, OutputTo, OutputMetadataOptions};
26+
pub use self::cargo_output_metadata::{output_metadata, OutputMetadataOptions, ExportInfo};
2727

2828
mod cargo_clean;
2929
mod cargo_compile;

tests/support/mod.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use std::process::Output;
1010
use std::str;
1111
use std::usize;
1212

13+
use rustc_serialize::json::Json;
1314
use url::Url;
1415
use hamcrest as ham;
1516
use cargo::util::ProcessBuilder;
@@ -271,6 +272,7 @@ pub struct Execs {
271272
expect_exit_code: Option<i32>,
272273
expect_stdout_contains: Vec<String>,
273274
expect_stderr_contains: Vec<String>,
275+
expect_json: Option<Json>,
274276
}
275277

276278
impl Execs {
@@ -299,6 +301,11 @@ impl Execs {
299301
self
300302
}
301303

304+
pub fn with_json(mut self, expected: &str) -> Execs {
305+
self.expect_json = Some(Json::from_str(expected).unwrap());
306+
self
307+
}
308+
302309
fn match_output(&self, actual: &Output) -> ham::MatchResult {
303310
self.match_status(actual)
304311
.and(self.match_stdout(actual))
@@ -330,6 +337,10 @@ impl Execs {
330337
try!(self.match_std(Some(expect), &actual.stderr, "stderr",
331338
&actual.stdout, true));
332339
}
340+
341+
if let Some(ref expect_json) = self.expect_json {
342+
try!(self.match_json(expect_json, &actual.stdout));
343+
}
333344
Ok(())
334345
}
335346

@@ -379,6 +390,27 @@ impl Execs {
379390

380391
}
381392

393+
fn match_json(&self, expected: &Json, stdout: &[u8]) -> ham::MatchResult {
394+
let stdout = match str::from_utf8(stdout) {
395+
Err(..) => return Err("stdout was not utf8 encoded".to_owned()),
396+
Ok(stdout) => stdout,
397+
};
398+
399+
let actual = match Json::from_str(stdout) {
400+
Err(..) => return Err(format!("Invalid json {}", stdout)),
401+
Ok(actual) => actual,
402+
};
403+
404+
match find_mismatch(expected, &actual) {
405+
Some((expected_part, actual_part)) => Err(format!(
406+
"JSON mismatch\nExpected:\n{}\nWas:\n{}\nExpected part:\n{}\nActual part:\n{}\n",
407+
expected.pretty(), actual.pretty(),
408+
expected_part.pretty(), actual_part.pretty()
409+
)),
410+
None => Ok(()),
411+
}
412+
}
413+
382414
fn diff_lines<'a>(&self, actual: str::Lines<'a>, expected: str::Lines<'a>,
383415
partial: bool) -> Vec<String> {
384416
let actual = actual.take(if partial {
@@ -419,6 +451,49 @@ fn lines_match(expected: &str, mut actual: &str) -> bool {
419451
actual.is_empty() || expected.ends_with("[..]")
420452
}
421453

454+
// Compares JSON object for approximate equality.
455+
// You can use `[..]` wildcard in strings (useful for OS dependent things such as paths).
456+
// Arrays are sorted before comparison.
457+
fn find_mismatch<'a>(expected: &'a Json, actual: &'a Json) -> Option<(&'a Json, &'a Json)> {
458+
use rustc_serialize::json::Json::*;
459+
match (expected, actual) {
460+
(&I64(l), &I64(r)) if l == r => None,
461+
(&F64(l), &F64(r)) if l == r => None,
462+
(&U64(l), &U64(r)) if l == r => None,
463+
(&Boolean(l), &Boolean(r)) if l == r => None,
464+
(&String(ref l), &String(ref r)) if lines_match(l, r) => None,
465+
(&Array(ref l), &Array(ref r)) => {
466+
if l.len() != r.len() {
467+
return Some((expected, actual));
468+
}
469+
470+
fn sorted(xs: &Vec<Json>) -> Vec<&Json> {
471+
let mut result = xs.iter().collect::<Vec<_>>();
472+
// `unwrap` should be safe because JSON spec does not allow NaNs
473+
result.sort_by(|x, y| x.partial_cmp(y).unwrap());
474+
result
475+
}
476+
477+
sorted(l).iter().zip(sorted(r))
478+
.filter_map(|(l, r)| find_mismatch(l, r))
479+
.nth(0)
480+
}
481+
(&Object(ref l), &Object(ref r)) => {
482+
let same_keys = l.len() == r.len() && l.keys().all(|k| r.contains_key(k));
483+
if !same_keys {
484+
return Some((expected, actual));
485+
}
486+
487+
l.values().zip(r.values())
488+
.filter_map(|(l, r)| find_mismatch(l, r))
489+
.nth(0)
490+
}
491+
(&Null, &Null) => None,
492+
_ => Some((expected, actual)),
493+
}
494+
495+
}
496+
422497
struct ZipAll<I1: Iterator, I2: Iterator> {
423498
first: I1,
424499
second: I2,
@@ -486,6 +561,7 @@ pub fn execs() -> Execs {
486561
expect_exit_code: None,
487562
expect_stdout_contains: Vec::new(),
488563
expect_stderr_contains: Vec::new(),
564+
expect_json: None,
489565
}
490566
}
491567

0 commit comments

Comments
 (0)