| 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 | |