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 | |
13 | use std::borrow::{Borrow, Cow}; |
14 | use std::collections::{HashMap, HashSet}; |
15 | use std::env; |
16 | use std::error; |
17 | use std::fmt; |
18 | use std::hash::{Hash, Hasher}; |
19 | use std::io; |
20 | use std::path::Path; |
21 | use std::process::{Command, Output}; |
22 | use std::str::Utf8Error; |
23 | |
24 | #[derive (Clone, Deserialize, Debug)] |
25 | /// Starting point for metadata returned by `cargo metadata` |
26 | pub 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)] |
36 | pub struct PackageRef { |
37 | pub name: String, |
38 | pub version: Option<String>, |
39 | } |
40 | |
41 | #[derive (Clone, Deserialize, Debug)] |
42 | /// A crate |
43 | pub 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 |
59 | pub 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 |
74 | pub 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. |
89 | pub 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 | |
100 | impl From<io::Error> for Error { |
101 | fn from(err: io::Error) -> Self { |
102 | Error::Io(err) |
103 | } |
104 | } |
105 | impl From<Utf8Error> for Error { |
106 | fn from(err: Utf8Error) -> Self { |
107 | Error::Utf8(err) |
108 | } |
109 | } |
110 | impl From<serde_json::Error> for Error { |
111 | fn from(err: serde_json::Error) -> Self { |
112 | Error::Json(err) |
113 | } |
114 | } |
115 | |
116 | impl fmt::Display for Error { |
117 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
118 | match self { |
119 | Error::Io(ref err: &Error) => err.fmt(f), |
120 | Error::Metadata(_) => write!(f, "Metadata error" ), |
121 | Error::Utf8(ref err: &Utf8Error) => err.fmt(f), |
122 | Error::Json(ref err: &Error) => err.fmt(f), |
123 | } |
124 | } |
125 | } |
126 | |
127 | impl error::Error for Error { |
128 | fn source(&self) -> Option<&(dyn error::Error + 'static)> { |
129 | match self { |
130 | Error::Io(ref err: &Error) => Some(err), |
131 | Error::Metadata(_) => None, |
132 | Error::Utf8(ref err: &Utf8Error) => Some(err), |
133 | Error::Json(ref err: &Error) => Some(err), |
134 | } |
135 | } |
136 | } |
137 | |
138 | // Implementations that let us lookup Packages and Dependencies by name (string) |
139 | |
140 | impl Borrow<PackageRef> for Package { |
141 | fn borrow(&self) -> &PackageRef { |
142 | &self.name_and_version |
143 | } |
144 | } |
145 | |
146 | impl Hash for Package { |
147 | fn hash<H: Hasher>(&self, state: &mut H) { |
148 | self.name_and_version.hash(state); |
149 | } |
150 | } |
151 | |
152 | impl PartialEq for Package { |
153 | fn eq(&self, other: &Self) -> bool { |
154 | self.name_and_version == other.name_and_version |
155 | } |
156 | } |
157 | |
158 | impl Eq for Package {} |
159 | |
160 | impl Borrow<str> for Dependency { |
161 | fn borrow(&self) -> &str { |
162 | &self.name |
163 | } |
164 | } |
165 | |
166 | impl Hash for Dependency { |
167 | fn hash<H: Hasher>(&self, state: &mut H) { |
168 | self.name.hash(state); |
169 | } |
170 | } |
171 | |
172 | impl PartialEq for Dependency { |
173 | fn eq(&self, other: &Self) -> bool { |
174 | self.name == other.name |
175 | } |
176 | } |
177 | |
178 | impl Eq for Dependency {} |
179 | |
180 | fn 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 |
211 | pub 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 | } |
254 | |