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}