summaryrefslogtreecommitdiffstats
path: root/src/bindgen/cargo/cargo_metadata.rs
blob: 01ab80f0626ab8dc88dd72818a3b3be093a0cc20 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
#![deny(missing_docs)]
#![allow(dead_code)]
//! Structured access to the output of `cargo metadata`
//! Usually used from within a `cargo-*` executable

// Forked from `https://github.com/oli-obk/cargo_metadata`
// Modifications:
//   1. Remove `resolve` from Metadata because it was causing parse failures
//   2. Fix the `manifest-path` argument
//   3. Add `--all-features` argument
//   4. Remove the `--no-deps` argument

use std::borrow::{Borrow, Cow};
use std::collections::{HashMap, HashSet};
use std::env;
use std::error;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::io;
use std::path::Path;
use std::process::{Command, Output};
use std::str::Utf8Error;

#[derive(Clone, Deserialize, Debug)]
/// Starting point for metadata returned by `cargo metadata`
pub struct Metadata {
    /// A list of all crates referenced by this crate (and the crate itself)
    pub packages: HashSet<Package>,
    version: usize,
    /// path to the workspace containing the `Cargo.lock`
    pub workspace_root: String,
}

/// A reference to a package including it's name and the specific version.
#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)]
pub struct PackageRef {
    pub name: String,
    pub version: Option<String>,
}

#[derive(Clone, Deserialize, Debug)]
/// A crate
pub struct Package {
    #[serde(flatten)]
    pub name_and_version: PackageRef,
    id: String,
    source: Option<String>,
    /// List of dependencies of this particular package
    pub dependencies: HashSet<Dependency>,
    /// Targets provided by the crate (lib, bin, example, test, ...)
    pub targets: Vec<Target>,
    features: HashMap<String, Vec<String>>,
    /// path containing the `Cargo.toml`
    pub manifest_path: String,
}

#[derive(Clone, Deserialize, Debug)]
/// A dependency of the main crate
pub struct Dependency {
    /// Name as given in the `Cargo.toml`
    pub name: String,
    source: Option<String>,
    /// Whether this is required or optional
    pub req: String,
    kind: Option<String>,
    optional: bool,
    uses_default_features: bool,
    features: Vec<String>,
    pub target: Option<String>,
}

#[derive(Clone, Deserialize, Debug)]
/// A single target (lib, bin, example, ...) provided by a crate
pub struct Target {
    /// Name as given in the `Cargo.toml` or generated from the file name
    pub name: String,
    /// Kind of target ("bin", "example", "test", "bench", "lib")
    pub kind: Vec<String>,
    /// Almost the same as `kind`, except when an example is a library instad of an executable.
    /// In that case `crate_types` contains things like `rlib` and `dylib` while `kind` is `example`
    #[serde(default)]
    pub crate_types: Vec<String>,
    /// Path to the main source file of the target
    pub src_path: String,
}

#[derive(Debug)]
/// Possible errors that can occur during metadata parsing.
pub enum Error {
    /// Error during execution of `cargo metadata`
    Io(io::Error),
    /// Metadata extraction failure
    Metadata(Output),
    /// Output of `cargo metadata` was not valid utf8
    Utf8(Utf8Error),
    /// Deserialization error (structure of json did not match expected structure)
    Json(serde_json::Error),
}

impl From<io::Error> for Error {
    fn from(err: io::Error) -> Self {
        Error::Io(err)
    }
}
impl From<Utf8Error> for Error {
    fn from(err: Utf8Error) -> Self {
        Error::Utf8(err)
    }
}
impl From<serde_json::Error> for Error {
    fn from(err: serde_json::Error) -> Self {
        Error::Json(err)
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Error::Io(ref err) => err.fmt(f),
            Error::Metadata(_) => write!(f, "Metadata error"),
            Error::Utf8(ref err) => err.fmt(f),
            Error::Json(ref err) => err.fmt(f),
        }
    }
}

impl error::Error for Error {
    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
        match self {
            Error::Io(ref err) => Some(err),
            Error::Metadata(_) => None,
            Error::Utf8(ref err) => Some(err),
            Error::Json(ref err) => Some(err),
        }
    }
}

// Implementations that let us lookup Packages and Dependencies by name (string)

impl Borrow<PackageRef> for Package {
    fn borrow(&self) -> &PackageRef {
        &self.name_and_version
    }
}

impl Hash for Package {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.name_and_version.hash(state);
    }
}

impl PartialEq for Package {
    fn eq(&self, other: &Self) -> bool {
        self.name_and_version == other.name_and_version
    }
}

impl Eq for Package {}

impl Borrow<str> for Dependency {
    fn borrow(&self) -> &str {
        &self.name
    }
}

impl Hash for Dependency {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.name.hash(state);
    }
}

impl PartialEq for Dependency {
    fn eq(&self, other: &Self) -> bool {
        self.name == other.name
    }
}

impl Eq for Dependency {}

fn discover_target(manifest_path: &Path) -> Option<String> {
    if let Ok(target) = std::env::var("TARGET") {
        return Some(target);
    }

    // We must be running as a standalone script, not under cargo.
    // Let's use the host platform instead.
    // We figure out the host platform through rustc and use that.
    // We unfortunatelly cannot go through cargo, since cargo rustc _also_ builds.
    // If `rustc` fails to run, we just fall back to not passing --filter-platforms.
    //
    // NOTE: We set the current directory in case of rustup shenanigans.
    let rustc = env::var("RUSTC").unwrap_or_else(|_| String::from("rustc"));
    debug!("Discovering host platform by {:?}", rustc);

    let rustc_output = Command::new(rustc)
        .current_dir(manifest_path.parent().unwrap())
        .arg("-vV")
        .output();
    let rustc_output = match rustc_output {
        Ok(ref out) => String::from_utf8_lossy(&out.stdout),
        Err(..) => return None,
    };

    let field = "host: ";
    rustc_output
        .lines()
        .find_map(|l| l.strip_prefix(field).map(|stripped| stripped.to_string()))
}

/// The main entry point to obtaining metadata
pub fn metadata(
    manifest_path: &Path,
    existing_metadata_file: Option<&Path>,
    only_target: bool,
) -> Result<Metadata, Error> {
    let output;
    let metadata = match existing_metadata_file {
        Some(path) => Cow::Owned(std::fs::read_to_string(path)?),
        None => {
            let target = if only_target {
                let target = discover_target(manifest_path);
                if target.is_none() {
                    warn!(
                        "Failed to discover host platform for cargo metadata; \
                        will fetch dependencies for all platforms."
                    );
                }
                target
            } else {
                None
            };

            let cargo = env::var("CARGO").unwrap_or_else(|_| String::from("cargo"));
            let mut cmd = Command::new(cargo);
            cmd.arg("metadata");
            cmd.arg("--all-features");
            cmd.arg("--format-version").arg("1");
            if let Some(target) = target {
                cmd.arg("--filter-platform").arg(target);
            }
            cmd.arg("--manifest-path");
            cmd.arg(manifest_path);
            output = cmd.output()?;
            if !output.status.success() {
                return Err(Error::Metadata(output));
            }
            Cow::Borrowed(std::str::from_utf8(&output.stdout)?)
        }
    };

    let meta: Metadata = serde_json::from_str(&metadata)?;
    Ok(meta)
}