From a46a574e9e16f0f8fab14b2805bc090d68f4c9be Mon Sep 17 00:00:00 2001 From: winger Date: Fri, 20 Mar 2015 13:29:43 -0700 Subject: [PATCH] Dependencies -> metadata - Rebase to latest - Fix build - Rename dependencies -> metadata - Implement rustc-serialize based output with toml and json support - Add output-format and features flags - Output status messages to stderr instead of stdout --- Cargo.lock | 10 +- src/bin/cargo.rs | 1 + src/bin/dependencies.rs | 54 ------- src/bin/metadata.rs | 66 ++++++++ src/cargo/core/shell.rs | 2 +- src/cargo/ops/cargo_compile.rs | 5 +- src/cargo/ops/cargo_output_dependencies.rs | 159 ------------------ src/cargo/ops/cargo_output_metadata.rs | 179 +++++++++++++++++++++ src/cargo/ops/mod.rs | 4 +- src/cargo/ops/resolve.rs | 8 +- src/cargo/util/errors.rs | 2 + 11 files changed, 263 insertions(+), 227 deletions(-) delete mode 100644 src/bin/dependencies.rs create mode 100644 src/bin/metadata.rs delete mode 100644 src/cargo/ops/cargo_output_dependencies.rs create mode 100644 src/cargo/ops/cargo_output_metadata.rs diff --git a/Cargo.lock b/Cargo.lock index 1a92674f03a..53eb71dd2fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7,7 +7,7 @@ dependencies = [ "docopt 0.6.51 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "flate2 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "git2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "git2 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "git2-curl 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "glob 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "hamcrest 0.1.0 (git+https://github.com/carllerche/hamcrest-rust.git)", @@ -18,7 +18,7 @@ dependencies = [ "registry 0.1.0", "rustc-serialize 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "semver 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", - "tar 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "tar 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", "tempdir 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "term 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "threadpool 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -98,7 +98,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "git2" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -113,7 +113,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "curl 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "git2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "git2 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", "url 0.2.27 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -254,7 +254,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "tar" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] diff --git a/src/bin/cargo.rs b/src/bin/cargo.rs index a7cec69bd64..004bb929eb0 100644 --- a/src/bin/cargo.rs +++ b/src/bin/cargo.rs @@ -63,6 +63,7 @@ macro_rules! each_subcommand{ ($mac:ident) => ({ $mac!(clean); $mac!(dependencies); $mac!(doc); + $mac!(metadata); $mac!(fetch); $mac!(generate_lockfile); $mac!(git_checkout); diff --git a/src/bin/dependencies.rs b/src/bin/dependencies.rs deleted file mode 100644 index e403afbdb86..00000000000 --- a/src/bin/dependencies.rs +++ /dev/null @@ -1,54 +0,0 @@ -use std::os; -use cargo::ops; -use cargo::util::{CliResult, CliError, Config}; -use cargo::util::important_paths::find_root_manifest_for_cwd; - -#[derive(RustcDecodable)] -struct Options { - flag_output_path: Option, - flag_manifest_path: Option, - flag_verbose: bool, -} - -pub const USAGE: &'static str = " -Output the resolved dependencies of a project, the concrete used versions -including overrides, in a TOML format - -Usage: - cargo dependencies [options] - -Options: - -h, --help Print this message - -o, --output-path PATH Path the output is written to, otherwise stdout is used - --manifest-path PATH Path to the manifest - -v, --verbose Use verbose output - -The TOML format is e.g.: - - [dependencies.libA] - version = \"0.1\", - path = '/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/libA-0.1' - dependencies = [\"libB\"] - - [dependencies.libB] - version = \"0.4\", - path = '/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/libB-0.4' - dependencies = [] - -"; - -pub fn execute(options: Options, config: &Config) -> CliResult> { - debug!("executing; cmd=cargo-dependencies; args={:?}", os::args()); - config.shell().set_verbose(options.flag_verbose); - - let manifest = try!(find_root_manifest_for_cwd(options.flag_manifest_path)); - - let out = options.flag_output_path - .map_or(ops::OutputTo::StdOut, |str| ops::OutputTo::Path(Path::new(str))); - - let options = ops::OutputOptions { out: out, config: config }; - - ops::output_dependencies(&manifest, &options) - .map(|_| None) - .map_err(|err| CliError::from_boxed(err, 101)) -} diff --git a/src/bin/metadata.rs b/src/bin/metadata.rs new file mode 100644 index 00000000000..df91b6f01e8 --- /dev/null +++ b/src/bin/metadata.rs @@ -0,0 +1,66 @@ +use cargo::ops::{outputMetadata, OutputTo, OutputFormat, OutputMetadataOptions}; +use cargo::util::{CliResult, CliError, Config}; +use cargo::util::important_paths::find_root_manifest_for_cwd; + +#[derive(RustcDecodable)] +struct Options { + flag_output_path: OutputTo, + flag_manifest_path: Option, + flag_verbose: bool, + flag_output_format: OutputFormat, + flag_features: String, +} + +pub const USAGE: &'static str = r#" +Output the resolved dependencies of a project, the concrete used versions +including overrides, in machine-readable format. + +Warning! This command is experimental and output format it subject to change in future. + +Usage: + cargo metadata [options] + +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 Comma-separated list of features [default: default] + --manifest-path PATH Path to the manifest + -v, --verbose Use verbose output + +The TOML format is e.g.: + + 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 execute(options: Options, config: &Config) -> CliResult> { + config.shell().set_verbose(options.flag_verbose); + + let manifest = try!(find_root_manifest_for_cwd(options.flag_manifest_path)); + let options = OutputMetadataOptions { + manifest_path: &manifest, + output_to: options.flag_output_path, + output_format: options.flag_output_format, + features: options.flag_features.split(',').map(|x| x.to_string()).collect::>(), + }; + + outputMetadata(options, config) + .map(|_| None) + .map_err(|err| CliError::from_boxed(err, 101)) +} diff --git a/src/cargo/core/shell.rs b/src/cargo/core/shell.rs index ad28ae63ddb..f98f6cb7467 100644 --- a/src/cargo/core/shell.rs +++ b/src/cargo/core/shell.rs @@ -55,7 +55,7 @@ impl MultiShell { pub fn status(&mut self, status: T, message: U) -> io::Result<()> where T: fmt::Display, U: fmt::Display { - self.out().say_status(status, message, GREEN) + self.err().say_status(status, message, GREEN) } pub fn verbose(&mut self, mut callback: F) -> io::Result<()> diff --git a/src/cargo/ops/cargo_compile.rs b/src/cargo/ops/cargo_compile.rs index eb5269cb02f..cde36e71b6e 100644 --- a/src/cargo/ops/cargo_compile.rs +++ b/src/cargo/ops/cargo_compile.rs @@ -33,7 +33,7 @@ use core::{Source, PackageSet, Package, Target, PackageId}; use core::resolver::Method; use ops::{self, BuildOutput, ExecEngine}; use sources::{PathSource}; -use util::config::Config; +use util::config::{ConfigValue, Config}; use util::{CargoResult, human, ChainError, profile}; /// Contains informations about how a package should be compiled. @@ -91,8 +91,7 @@ pub fn compile_pkg(package: &Package, options: &CompileOptions) return Err(human("jobs must be at least 1")) } - let override_ids = try!(ops::source_ids_from_config(config, - package.get_root())); + let override_ids = try!(ops::source_ids_from_config(config, package.root())); let (packages, resolve_with_overrides, sources) = { let rustc_host = config.rustc_host().to_string(); diff --git a/src/cargo/ops/cargo_output_dependencies.rs b/src/cargo/ops/cargo_output_dependencies.rs deleted file mode 100644 index ed17e373388..00000000000 --- a/src/cargo/ops/cargo_output_dependencies.rs +++ /dev/null @@ -1,159 +0,0 @@ -use std::io; -use std::collections::HashMap; -use std::collections::hash_map::Entry::{Occupied, Vacant}; -use util::{CargoResult, profile}; -use util::config::Config; -use core::{Source, SourceId, PackageId}; -use core::registry::PackageRegistry; -use core::resolver::{Resolve, Method}; -use sources::PathSource; -use ops; - -#[derive(Eq, PartialEq)] -/// Where the dependencies should be written to. -pub enum OutputTo { - Path(Path), - StdOut -} - -pub struct OutputOptions<'a, 'b: 'a> { - pub out: OutputTo, - pub config: &'a Config<'b> -} - -/// 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.: -/// -/// [dependencies.libA] -/// version = "0.1", -/// path = '/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/libA-0.1' -/// dependencies = ["libB"] -/// -/// [dependencies.libB] -/// version = "0.4", -/// path = '/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/libB-0.4' -/// dependencies = [] -/// -pub fn output_dependencies(manifest: &Path, options: &OutputOptions) -> CargoResult<()> { - let OutputOptions { ref out, config } = *options; - let resolved_deps = try!(resolve_dependencies(manifest, config)); - let src_paths = try!(source_paths(&resolved_deps, config)); - - struct Dependency { - name: String, - version: String, - path: Path, - dependencies: Vec - }; - - let mut dependencies: Vec = Vec::new(); - - for (package_id, src_path) in src_paths.iter() { - if *package_id == *resolved_deps.root() { - continue; - } - - let mut dependency = Dependency { - name: package_id.get_name().to_string(), - version: format!("{}", package_id.get_version()), - path: src_path.clone(), - dependencies: Vec::new() - }; - - if let Some(mut dep_deps) = resolved_deps.deps(package_id) { - for dep_dep in dep_deps { - dependency.dependencies.push(dep_dep.get_name().to_string()); - } - } - - dependencies.push(dependency); - } - - let mut toml_str = String::new(); - - for dep in dependencies.iter() { - toml_str.push_str(format!("\n[dependencies.{}]\n", dep.name).as_slice()); - toml_str.push_str(format!("version = \"{}\"\n", dep.version).as_slice()); - toml_str.push_str(format!("path = '{:?}'\n", dep.path).as_slice()); - toml_str.push_str(format!("dependencies = {:?}\n", dep.dependencies).as_slice()); - } - - match *out { - OutputTo::StdOut => println!("{}", toml_str), - OutputTo::Path(ref path) => { - let mut file = try!(io::File::open_mode(path, io::Truncate, io::ReadWrite)); - try!(file.write_str(toml_str.as_slice())); - } - } - - Ok(()) -} - -/// Returns the source path for each `PackageId` in `Resolve`. -fn source_paths(resolve: &Resolve, config: &Config) -> CargoResult> { - let package_ids: Vec<&PackageId> = resolve.iter().collect(); - let mut source_id_to_package_ids: HashMap<&SourceId, Vec> = HashMap::new(); - - // group PackageId by SourceId - for package_id in package_ids.iter() { - match source_id_to_package_ids.entry(package_id.get_source_id()) { - Occupied(mut entry) => entry.get_mut().push((*package_id).clone()), - Vacant(entry) => { entry.insert(vec![(*package_id).clone()]); } - } - } - - let mut package_id_to_path: HashMap = HashMap::new(); - - for (mut source_id, ref package_ids) in source_id_to_package_ids.iter() { - let mut source = source_id.load(config); - try!(source.update()); - try!(source.download(package_ids.as_slice())); - let packages = try!(source.get(package_ids.as_slice())); - - for package in packages.iter() { - match package_id_to_path.entry(package.get_package_id().clone()) { - Occupied(_) => {}, - Vacant(entry) => { entry.insert(package.get_root()); } - } - } - } - - Ok(package_id_to_path) -} - -/// Loads the manifest and resolves the dependencies of the project to the -/// concrete used versions. Afterwards available overrides of dependencies are applied. -fn resolve_dependencies(manifest: &Path, config: &Config) -> CargoResult { - let mut source = try!(PathSource::for_path(&manifest.dir_path(), config)); - try!(source.update()); - - let package = try!(source.get_root_package()); - debug!("loaded package; package={}", package); - - for key in package.get_manifest().get_warnings().iter() { - try!(config.shell().warn(key)) - } - - let override_ids = try!(ops::source_ids_from_config(config, package.get_root())); - let rustc_host = config.rustc_host().to_string(); - let mut registry = PackageRegistry::new(config); - - // First, resolve the package's *listed* dependencies, as well as - // downloading and updating all remotes and such. - let resolved = try!(ops::resolve_pkg(&mut registry, &package)); - - // Second, resolve with precisely what we're doing. Filter out - // transitive dependencies if necessary, specify features, handle - // overrides, etc. - let _p = profile::start("resolving w/ overrides..."); - - try!(registry.add_overrides(override_ids)); - - let platform = Some(rustc_host.as_slice()); - let method = Method::Required(false, &[], true, platform); - - ops::resolve_with_previous(&mut registry, &package, method, Some(&resolved), None) -} diff --git a/src/cargo/ops/cargo_output_metadata.rs b/src/cargo/ops/cargo_output_metadata.rs new file mode 100644 index 00000000000..880628ad2d9 --- /dev/null +++ b/src/cargo/ops/cargo_output_metadata.rs @@ -0,0 +1,179 @@ +use std::borrow::Cow; +use std::collections::HashMap; +use std::io::Write; +use std::fs; +use std::path::{Path, PathBuf}; +use rustc_serialize::{json, Decoder, Decodable}; +use toml; +use util::CargoResult; +use util::config::Config; +use term::color::BLACK; +use core::{Source, Package, PackageId}; +use core::registry::PackageRegistry; +use core::resolver::{Resolve, Method}; +use sources::PathSource; +use ops; + +#[derive(RustcDecodable)] +pub enum OutputFormat { + Toml, + Json, +} + +/// Where the dependencies should be written to. +pub enum OutputTo { + Path(PathBuf), + StdOut, +} + +impl Decodable for OutputTo { + fn decode(d: &mut D) -> Result { + d.read_option(|d, b| { + if b { + Ok(OutputTo::Path(try!(Decodable::decode(d)))) + } else { + Ok(OutputTo::StdOut) + } + }) + } +} + +pub struct OutputMetadataOptions<'a> { + pub output_to: OutputTo, + pub output_format: OutputFormat, + pub manifest_path: &'a Path, + pub features: Vec, +} + +/// 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.: +/// +/// 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 outputMetadata(opt: OutputMetadataOptions, config: &Config) -> CargoResult<()> { + let (resolved_deps, packages) = try!(resolve_dependencies(opt.manifest_path, opt.features, config)); + + #[derive(RustcEncodable)] + struct PackageInfo<'a> { + version: String, + path: Cow<'a, str>, + dependencies: Vec, + features: Option<&'a HashMap>>, + }; + + #[derive(RustcEncodable)] + struct ExportInfo<'a> { + root: String, + packages: HashMap> + } + + let mut output = ExportInfo { + root: resolved_deps.root().name().to_string(), + packages: HashMap::new() + }; + + for package in packages.iter() { + let mut package_info = PackageInfo { + version: format!("{}", package.version()), + path: (*package.root()).to_string_lossy(), + dependencies: Vec::new(), + features: { + let features = package.manifest().summary().features(); + if features.is_empty() { + None + } else { + Some(features) + } + }, + }; + + if let Some(dep_deps) = resolved_deps.deps(package.package_id()) { + for dep_dep in dep_deps { + package_info.dependencies.push(dep_dep.name().to_string()); + } + } + + output.packages.insert(package.name().to_string(), package_info); + } + + let serialized_str = match opt.output_format { + OutputFormat::Toml => toml::encode_str(&output), + OutputFormat::Json => try!(json::encode(&output)), + }; + + match opt.output_to { + OutputTo::StdOut => try!(config.shell().say(serialized_str, BLACK)), + OutputTo::Path(ref path) => { + let mut file = try!(fs::File::create(path)); + try!(file.write_all(serialized_str.as_bytes())); + } + } + + Ok(()) +} + +/// Loads the manifest and resolves the dependencies of the project to the +/// concrete used versions. Afterwards available overrides of dependencies are applied. +fn resolve_dependencies(manifest: &Path, features: Vec, config: &Config) + -> CargoResult<(Resolve, Vec)> { + let mut source = try!(PathSource::for_path(manifest.parent().unwrap(), config)); + try!(source.update()); + + let package = try!(source.root_package()); + + for key in package.manifest().warnings().iter() { + try!(config.shell().warn(key)) + } + + let override_ids = try!(ops::source_ids_from_config(config, package.root())); + let mut registry = PackageRegistry::new(config); + + // First, resolve the package's *listed* dependencies, as well as + // downloading and updating all remotes and such. + let resolved = try!(ops::resolve_pkg(&mut registry, &package)); + + // Second, resolve with precisely what we're doing. Filter out + // transitive dependencies if necessary, specify features, handle + // overrides, etc. + try!(registry.add_overrides(override_ids)); + + let rustc_host = config.rustc_host().to_string(); + let default_feature = features.contains(&"default".to_string()); + let filtered_features = features.into_iter().filter(|s| s.as_slice() != "default").collect::>(); + + let platform = Some(rustc_host.as_slice()); + let method = Method::Required { + dev_deps: false, + features: &filtered_features, + uses_default_features: default_feature, + target_platform: platform + }; + + let resolved_specific = try!(ops::resolve_with_previous(&mut registry, &package, method, Some(&resolved), None)); + + let package_ids: Vec = resolved_specific.iter().cloned().collect(); + let packages = try!(registry.get(&package_ids)); + for package in packages.iter() { + debug!("{}: {:?}", package.package_id(), package.root()); + } + //debug!("{}", try!(json::encode(&try!(registry.get(&package_ids))))); + + Ok((resolved_specific, packages)) +} diff --git a/src/cargo/ops/mod.rs b/src/cargo/ops/mod.rs index 7781a37f3c6..24e29d3c8dc 100644 --- a/src/cargo/ops/mod.rs +++ b/src/cargo/ops/mod.rs @@ -22,15 +22,15 @@ pub use self::registry::{modify_owners, yank, OwnersOptions}; pub use self::cargo_fetch::{fetch}; pub use self::cargo_pkgid::pkgid; pub use self::resolve::{resolve_pkg, resolve_with_previous, source_ids_from_config}; -pub use self::cargo_output_dependencies::{output_dependencies, OutputTo, OutputOptions}; +pub use self::cargo_output_metadata::{outputMetadata, OutputTo, OutputFormat, OutputMetadataOptions}; mod cargo_clean; mod cargo_compile; mod cargo_doc; +mod cargo_output_metadata; mod cargo_fetch; mod cargo_generate_lockfile; mod cargo_new; -mod cargo_output_dependencies; mod cargo_package; mod cargo_pkgid; mod cargo_read_manifest; diff --git a/src/cargo/ops/resolve.rs b/src/cargo/ops/resolve.rs index e0212b10441..1e5b8742995 100644 --- a/src/cargo/ops/resolve.rs +++ b/src/cargo/ops/resolve.rs @@ -1,4 +1,5 @@ use std::collections::{HashMap, HashSet}; +use std::path::Path; use core::{Package, PackageId, SourceId}; use core::registry::PackageRegistry; @@ -124,7 +125,8 @@ pub fn resolve_with_previous<'a>(registry: &mut PackageRegistry, } } -pub fn source_ids_from_config(config: &Config, cur_path: Path) -> CargoResult> { +pub fn source_ids_from_config(config: &Config, cur_path: &Path) -> CargoResult> { + let configs = try!(config.values()); debug!("loaded config; configs={:?}", configs); let config_paths = match configs.get("paths") { @@ -139,10 +141,10 @@ pub fn source_ids_from_config(config: &Config, cur_path: Path) -> CargoResult