guppy/
metadata_command.rs

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