cbindgen/bindgen/cargo/
cargo_metadata.rs1#![deny(missing_docs)]
2#![allow(dead_code)]
3use 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)]
25pub struct Metadata {
27 pub packages: HashSet<Package>,
29 version: usize,
30 pub workspace_root: String,
32}
33
34#[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)]
42pub struct Package {
44 #[serde(flatten)]
45 pub name_and_version: PackageRef,
46 id: String,
47 source: Option<String>,
48 pub dependencies: HashSet<Dependency>,
50 pub targets: Vec<Target>,
52 features: HashMap<String, Vec<String>>,
53 pub manifest_path: String,
55}
56
57#[derive(Clone, Deserialize, Debug)]
58pub struct Dependency {
60 pub name: String,
62 source: Option<String>,
63 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)]
73pub struct Target {
75 pub name: String,
77 pub kind: Vec<String>,
79 #[serde(default)]
82 pub crate_types: Vec<String>,
83 pub src_path: String,
85}
86
87#[derive(Debug)]
88pub enum Error {
90 Io(io::Error),
92 Metadata(Output),
94 Utf8(Utf8Error),
96 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
138impl 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 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
210pub 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}