cbindgen/bindgen/cargo/
cargo_metadata.rs

1#![deny(missing_docs)]
2#![allow(dead_code)]
3//! Structured access to the output of `cargo metadata`
4//! Usually used from within a `cargo-*` executable
5
6// Forked from `https://github.com/oli-obk/cargo_metadata`
7// Modifications:
8//   1. Remove `resolve` from Metadata because it was causing parse failures
9//   2. Fix the `manifest-path` argument
10//   3. Add `--all-features` argument
11//   4. Remove the `--no-deps` argument
12
13use std::borrow::{Borrow, Cow};
14use std::collections::{HashMap, HashSet};
15use std::env;
16use std::error;
17use std::fmt;
18use std::hash::{Hash, Hasher};
19use std::io;
20use std::path::Path;
21use std::process::{Command, Output};
22use std::str::Utf8Error;
23
24#[derive(Clone, Deserialize, Debug)]
25/// Starting point for metadata returned by `cargo metadata`
26pub struct Metadata {
27    /// A list of all crates referenced by this crate (and the crate itself)
28    pub packages: HashSet<Package>,
29    version: usize,
30    /// path to the workspace containing the `Cargo.lock`
31    pub workspace_root: String,
32}
33
34/// A reference to a package including it's name and the specific version.
35#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)]
36pub struct PackageRef {
37    pub name: String,
38    pub version: Option<String>,
39}
40
41#[derive(Clone, Deserialize, Debug)]
42/// A crate
43pub struct Package {
44    #[serde(flatten)]
45    pub name_and_version: PackageRef,
46    id: String,
47    source: Option<String>,
48    /// List of dependencies of this particular package
49    pub dependencies: HashSet<Dependency>,
50    /// Targets provided by the crate (lib, bin, example, test, ...)
51    pub targets: Vec<Target>,
52    features: HashMap<String, Vec<String>>,
53    /// path containing the `Cargo.toml`
54    pub manifest_path: String,
55}
56
57#[derive(Clone, Deserialize, Debug)]
58/// A dependency of the main crate
59pub struct Dependency {
60    /// Name as given in the `Cargo.toml`
61    pub name: String,
62    source: Option<String>,
63    /// Whether this is required or optional
64    pub req: String,
65    kind: Option<String>,
66    optional: bool,
67    uses_default_features: bool,
68    features: Vec<String>,
69    pub target: Option<String>,
70}
71
72#[derive(Clone, Deserialize, Debug)]
73/// A single target (lib, bin, example, ...) provided by a crate
74pub struct Target {
75    /// Name as given in the `Cargo.toml` or generated from the file name
76    pub name: String,
77    /// Kind of target ("bin", "example", "test", "bench", "lib")
78    pub kind: Vec<String>,
79    /// Almost the same as `kind`, except when an example is a library instad of an executable.
80    /// In that case `crate_types` contains things like `rlib` and `dylib` while `kind` is `example`
81    #[serde(default)]
82    pub crate_types: Vec<String>,
83    /// Path to the main source file of the target
84    pub src_path: String,
85}
86
87#[derive(Debug)]
88/// Possible errors that can occur during metadata parsing.
89pub enum Error {
90    /// Error during execution of `cargo metadata`
91    Io(io::Error),
92    /// Metadata extraction failure
93    Metadata(Output),
94    /// Output of `cargo metadata` was not valid utf8
95    Utf8(Utf8Error),
96    /// Deserialization error (structure of json did not match expected structure)
97    Json(serde_json::Error),
98}
99
100impl From<io::Error> for Error {
101    fn from(err: io::Error) -> Self {
102        Error::Io(err)
103    }
104}
105impl From<Utf8Error> for Error {
106    fn from(err: Utf8Error) -> Self {
107        Error::Utf8(err)
108    }
109}
110impl From<serde_json::Error> for Error {
111    fn from(err: serde_json::Error) -> Self {
112        Error::Json(err)
113    }
114}
115
116impl fmt::Display for Error {
117    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
118        match self {
119            Error::Io(ref err) => err.fmt(f),
120            Error::Metadata(_) => write!(f, "Metadata error"),
121            Error::Utf8(ref err) => err.fmt(f),
122            Error::Json(ref err) => err.fmt(f),
123        }
124    }
125}
126
127impl error::Error for Error {
128    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
129        match self {
130            Error::Io(ref err) => Some(err),
131            Error::Metadata(_) => None,
132            Error::Utf8(ref err) => Some(err),
133            Error::Json(ref err) => Some(err),
134        }
135    }
136}
137
138// Implementations that let us lookup Packages and Dependencies by name (string)
139
140impl Borrow<PackageRef> for Package {
141    fn borrow(&self) -> &PackageRef {
142        &self.name_and_version
143    }
144}
145
146impl Hash for Package {
147    fn hash<H: Hasher>(&self, state: &mut H) {
148        self.name_and_version.hash(state);
149    }
150}
151
152impl PartialEq for Package {
153    fn eq(&self, other: &Self) -> bool {
154        self.name_and_version == other.name_and_version
155    }
156}
157
158impl Eq for Package {}
159
160impl Borrow<str> for Dependency {
161    fn borrow(&self) -> &str {
162        &self.name
163    }
164}
165
166impl Hash for Dependency {
167    fn hash<H: Hasher>(&self, state: &mut H) {
168        self.name.hash(state);
169    }
170}
171
172impl PartialEq for Dependency {
173    fn eq(&self, other: &Self) -> bool {
174        self.name == other.name
175    }
176}
177
178impl Eq for Dependency {}
179
180fn discover_target(manifest_path: &Path) -> Option<String> {
181    if let Ok(target) = std::env::var("TARGET") {
182        return Some(target);
183    }
184
185    // We must be running as a standalone script, not under cargo.
186    // Let's use the host platform instead.
187    // We figure out the host platform through rustc and use that.
188    // We unfortunatelly cannot go through cargo, since cargo rustc _also_ builds.
189    // If `rustc` fails to run, we just fall back to not passing --filter-platforms.
190    //
191    // NOTE: We set the current directory in case of rustup shenanigans.
192    let rustc = env::var("RUSTC").unwrap_or_else(|_| String::from("rustc"));
193    debug!("Discovering host platform by {rustc:?}");
194
195    let rustc_output = Command::new(rustc)
196        .current_dir(manifest_path.parent().unwrap())
197        .arg("-vV")
198        .output();
199    let rustc_output = match rustc_output {
200        Ok(ref out) => String::from_utf8_lossy(&out.stdout),
201        Err(..) => return None,
202    };
203
204    let field = "host: ";
205    rustc_output
206        .lines()
207        .find_map(|l| l.strip_prefix(field).map(|stripped| stripped.to_string()))
208}
209
210/// The main entry point to obtaining metadata
211pub fn metadata(
212    manifest_path: &Path,
213    existing_metadata_file: Option<&Path>,
214    only_target: bool,
215) -> Result<Metadata, Error> {
216    let output;
217    let metadata = match existing_metadata_file {
218        Some(path) => Cow::Owned(std::fs::read_to_string(path)?),
219        None => {
220            let target = if only_target {
221                let target = discover_target(manifest_path);
222                if target.is_none() {
223                    warn!(
224                        "Failed to discover host platform for cargo metadata; \
225                        will fetch dependencies for all platforms."
226                    );
227                }
228                target
229            } else {
230                None
231            };
232
233            let cargo = env::var("CARGO").unwrap_or_else(|_| String::from("cargo"));
234            let mut cmd = Command::new(cargo);
235            cmd.arg("metadata");
236            cmd.arg("--all-features");
237            cmd.arg("--format-version").arg("1");
238            if let Some(target) = target {
239                cmd.arg("--filter-platform").arg(target);
240            }
241            cmd.arg("--manifest-path");
242            cmd.arg(manifest_path);
243            output = cmd.output()?;
244            if !output.status.success() {
245                return Err(Error::Metadata(output));
246            }
247            Cow::Borrowed(std::str::from_utf8(&output.stdout)?)
248        }
249    };
250
251    let meta: Metadata = serde_json::from_str(&metadata)?;
252    Ok(meta)
253}