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
use std::{error, path, process};

pub fn fetch() -> Result<Metadata, Box<dyn error::Error>> {
    let json = fetch_cargo_metadata_json()?;
    let json_value = json::parse(&json)?;
    Ok(Metadata::from(json_value))
}

fn fetch_cargo_metadata_json() -> Result<String, Box<dyn error::Error>> {
    let mut command = process::Command::new(crate::cargo_cmd());

    command.arg("metadata").arg("--format-version").arg("1");

    let output = command.stderr(process::Stdio::inherit()).output()?;

    if !output.status.success() {
        return Err("`cargo metadata` returned a non-zero status".into());
    }

    Ok(String::from_utf8(output.stdout)?)
}

#[derive(Clone)]
pub struct Dependency {
    pub name: String,
    pub rename: Option<String>,
    pub optional: bool,
}

impl From<json::JsonValue> for Dependency {
    fn from(json_value: json::JsonValue) -> Self {
        let name = json_value["name"].as_str().unwrap().to_owned();
        let rename = json_value["rename"].as_str().map(|s| s.to_string());
        let optional = json_value["optional"].as_bool().unwrap();

        Dependency { name, rename, optional }
    }
}

#[derive(Clone)]
pub struct Package {
    pub id: String,
    pub name: String,
    pub manifest_path: path::PathBuf,
    pub dependencies: Vec<Dependency>,
    pub features: Vec<String>,
    pub skip_feature_sets: Vec<Vec<String>>,
    pub skip_optional_dependencies: bool,
    pub extra_features: Vec<String>,
}

impl From<json::JsonValue> for Package {
    fn from(json_value: json::JsonValue) -> Self {
        let id = json_value["id"].as_str().unwrap().to_owned();
        let name = json_value["name"].as_str().unwrap().to_owned();
        let manifest_path =
            path::PathBuf::from(json_value["manifest_path"].as_str().unwrap().to_owned());
        let dependencies = json_value["dependencies"]
            .members()
            .map(|member| Dependency::from(member.to_owned()))
            .collect();
        let features = json_value["features"]
            .entries()
            .map(|(k, _v)| k.to_owned())
            .collect();
        let skip_feature_sets: Vec<Vec<String>> = json_value["metadata"]
            ["cargo-all-features"]
            ["skip_feature_sets"]
            .members()
            .map(|member| {
                member
                    .members()
                    .map(|feature| feature.as_str().unwrap().to_owned())
                    .collect()
            })
            .collect();
        let skip_optional_dependencies: bool = json_value["metadata"]
            ["cargo-all-features"]
            ["skip_optional_dependencies"]
            .as_bool()
            .unwrap_or(false);
        let extra_features: Vec<String> = json_value["metadata"]
            ["cargo-all-features"]
            ["extra_features"]
            .members()
            .map(|member| member.as_str().unwrap().to_owned())
            .collect();

        Package {
            id,
            name,
            manifest_path,
            dependencies,
            features,
            skip_feature_sets,
            skip_optional_dependencies,
            extra_features,
        }
    }
}

#[derive(Clone)]
pub struct Metadata {
    pub workspace_root: path::PathBuf,
    pub workspace_members: Vec<String>,
    pub packages: Vec<Package>,
}

impl From<json::JsonValue> for Metadata {
    fn from(json_value: json::JsonValue) -> Self {
        let workspace_root =
            path::PathBuf::from(json_value["workspace_root"].as_str().unwrap().to_owned());

        let workspace_members = json_value["workspace_members"]
            .members()
            .map(|member| member.as_str().unwrap().to_owned())
            .collect();

        let packages = json_value["packages"]
            .members()
            .map(|member| Package::from(member.to_owned()))
            .collect();

        Metadata {
            workspace_root,
            workspace_members,
            packages,
        }
    }
}