1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
// Copyright (c) The cargo-guppy Contributors
// SPDX-License-Identifier: MIT OR Apache-2.0
use crate::{graph::PackageGraph, Error};
use cargo_metadata::CargoOpt;
use serde::{Deserialize, Serialize};
use std::{convert::TryFrom, io, path::PathBuf, process::Command};
/// A builder for configuring `cargo metadata` invocations.
///
/// This is the most common entry point for constructing a `PackageGraph`.
///
/// ## Examples
///
/// Build a `PackageGraph` for the Cargo workspace in the current directory:
///
/// ```rust
/// use guppy::MetadataCommand;
/// use guppy::graph::PackageGraph;
///
/// let mut cmd = MetadataCommand::new();
/// let package_graph = PackageGraph::from_command(&mut cmd);
/// ```
#[derive(Clone, Debug, Default)]
pub struct MetadataCommand {
inner: cargo_metadata::MetadataCommand,
}
impl MetadataCommand {
/// Creates a default `cargo metadata` command builder.
///
/// By default, this will look for `Cargo.toml` in the ancestors of this process's current
/// directory.
pub fn new() -> Self {
let mut inner = cargo_metadata::MetadataCommand::new();
// Always use --all-features so that we get a full view of the graph.
inner.features(CargoOpt::AllFeatures);
Self { inner }
}
/// Sets the path to the `cargo` executable.
///
/// If unset, this will use the `$CARGO` environment variable, or else `cargo` from `$PATH`.
pub fn cargo_path(&mut self, path: impl Into<PathBuf>) -> &mut Self {
self.inner.cargo_path(path);
self
}
/// Sets the path to `Cargo.toml`.
///
/// By default, this will look for `Cargo.toml` in the ancestors of the current directory. Note
/// that this doesn't need to be the root `Cargo.toml` in a workspace -- any member of the
/// workspace is fine.
pub fn manifest_path(&mut self, path: impl Into<PathBuf>) -> &mut Self {
self.inner.manifest_path(path);
self
}
/// Sets the current directory of the `cargo metadata` process.
///
/// By default, the current directory will be inherited from this process.
pub fn current_dir(&mut self, path: impl Into<PathBuf>) -> &mut Self {
self.inner.current_dir(path);
self
}
/// Output information only about the workspace and do not fetch dependencies.
///
/// For full functionality, `cargo metadata` should be run without `--no-deps`, so that `guppy`
/// knows about third-party crates and dependency edges. However, `guppy` supports a "light"
/// mode if `--no-deps` is run, in which case the following limitations will apply:
/// * dependency queries will not work
/// * there will be no information about non-workspace crates
///
/// Constructing a graph with this option can be several times faster than the default.
pub fn no_deps(&mut self) -> &mut Self {
self.inner.no_deps();
self
}
// *Do not* implement features.
/// Arbitrary flags to pass to `cargo metadata`. These will be added to the end of the
/// command invocation.
///
/// Note that `guppy` internally:
/// * uses `--format-version 1` as its metadata format.
/// * passes in `--all-features`, so that `guppy` has a full view of the dependency graph.
///
/// Attempting to override either of those options may lead to unexpected results.
pub fn other_options(
&mut self,
options: impl IntoIterator<Item = impl Into<String>>,
) -> &mut Self {
self.inner
.other_options(options.into_iter().map(|s| s.into()).collect::<Vec<_>>());
self
}
/// Builds a [`Command`] instance. This is the first part of calling
/// [`exec`](Self::exec).
pub fn cargo_command(&self) -> Command {
self.inner.cargo_command()
}
/// Runs the configured `cargo metadata` and returns a deserialized `CargoMetadata`.
pub fn exec(&self) -> Result<CargoMetadata, Error> {
let inner = self.inner.exec().map_err(Error::command_error)?;
Ok(CargoMetadata(inner))
}
/// Runs the configured `cargo metadata` and returns a parsed `PackageGraph`.
pub fn build_graph(&self) -> Result<PackageGraph, Error> {
let metadata = self.exec()?;
metadata.build_graph()
}
}
/// Although consuming a `MetadataCommand` is not required for building a `PackageGraph`, this impl
/// is provided for convenience.
impl TryFrom<MetadataCommand> for PackageGraph {
type Error = Error;
fn try_from(command: MetadataCommand) -> Result<Self, Self::Error> {
command.build_graph()
}
}
impl<'a> TryFrom<&'a MetadataCommand> for PackageGraph {
type Error = Error;
fn try_from(command: &'a MetadataCommand) -> Result<Self, Self::Error> {
command.build_graph()
}
}
/// Deserialized Cargo metadata.
///
/// Returned by a `MetadataCommand` or constructed from `cargo metadata` JSON output.
///
/// This is an alternative entry point for constructing a `PackageGraph`, to be used if the JSON
/// output of `cargo metadata` is already available. To construct a `PackageGraph` from an on-disk
/// Cargo workspace, use [`MetadataCommand`](MetadataCommand).
///
/// This struct implements `serde::Serialize` and `Deserialize`.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(transparent)]
pub struct CargoMetadata(pub(crate) cargo_metadata::Metadata);
impl CargoMetadata {
/// Deserializes this JSON blob into a `CargoMetadata`.
pub fn parse_json(json: impl AsRef<str>) -> Result<Self, Error> {
let inner = serde_json::from_str(json.as_ref()).map_err(Error::MetadataParseError)?;
Ok(Self(inner))
}
/// Serializes this metadata into the given writer.
pub fn serialize(&self, writer: &mut impl io::Write) -> Result<(), Error> {
serde_json::to_writer(writer, &self.0).map_err(Error::MetadataSerializeError)
}
/// Parses this metadata and builds a `PackageGraph` from it.
pub fn build_graph(self) -> Result<PackageGraph, Error> {
PackageGraph::from_metadata(self)
}
}
impl TryFrom<CargoMetadata> for PackageGraph {
type Error = Error;
fn try_from(metadata: CargoMetadata) -> Result<Self, Self::Error> {
metadata.build_graph()
}
}