bootloader_locator/
lib.rs

1//! Helper crate for locating a `bootloader` dependency on the file system.
2
3#![warn(missing_docs)]
4
5use std::{convert, fmt, io, path::PathBuf, process::Command, string};
6
7/// Locates the dependency with the given name on the file system.
8///
9/// Returns the manifest path of the bootloader, i.e. the path to the Cargo.toml on the file
10/// system.
11pub fn locate_bootloader(dependency_name: &str) -> Result<PathBuf, LocateError> {
12    let metadata = metadata()?;
13
14    let root = metadata["resolve"]["root"]
15        .as_str()
16        .ok_or(LocateError::MetadataInvalid)?;
17
18    let root_resolve = metadata["resolve"]["nodes"]
19        .members()
20        .find(|r| r["id"] == root)
21        .ok_or(LocateError::MetadataInvalid)?;
22
23    let dependency = root_resolve["deps"]
24        .members()
25        .find(|d| d["name"] == dependency_name)
26        .ok_or(LocateError::DependencyNotFound)?;
27    let dependency_id = dependency["pkg"]
28        .as_str()
29        .ok_or(LocateError::MetadataInvalid)?;
30
31    let dependency_package = metadata["packages"]
32        .members()
33        .find(|p| p["id"] == dependency_id)
34        .ok_or(LocateError::MetadataInvalid)?;
35    let dependency_manifest = dependency_package["manifest_path"]
36        .as_str()
37        .ok_or(LocateError::MetadataInvalid)?;
38
39    Ok(dependency_manifest.into())
40}
41
42/// Failed to locate the bootloader dependency with the given name.
43#[derive(Debug)]
44pub enum LocateError {
45    /// The project metadata returned from `cargo metadata` was not valid.
46    MetadataInvalid,
47    /// No dependency with the given name found in the project metadata.
48    DependencyNotFound,
49    /// Failed to query project metadata.
50    Metadata(CargoMetadataError),
51}
52
53impl fmt::Display for LocateError {
54    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55        match self {
56            LocateError::MetadataInvalid => write!(f, "The `cargo metadata` output was not valid"),
57            LocateError::DependencyNotFound => write!(
58                f,
59                "Could not find a dependency with the given name in the `cargo metadata` output"
60            ),
61            LocateError::Metadata(source) => {
62                write!(f, "Failed to retrieve project metadata: {}", source)
63            }
64        }
65    }
66}
67
68impl std::error::Error for LocateError {
69    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
70        match self {
71            LocateError::MetadataInvalid => None,
72            LocateError::DependencyNotFound => None,
73            LocateError::Metadata(source) => Some(source),
74        }
75    }
76}
77
78impl convert::From<CargoMetadataError> for LocateError {
79    fn from(source: CargoMetadataError) -> Self {
80        LocateError::Metadata(source)
81    }
82}
83
84fn metadata() -> Result<json::JsonValue, CargoMetadataError> {
85    let mut cmd = Command::new(env!("CARGO"));
86    cmd.arg("metadata");
87    cmd.arg("--format-version").arg("1");
88    let output = cmd.output()?;
89
90    if !output.status.success() {
91        return Err(CargoMetadataError::Failed {
92            stderr: output.stderr,
93        });
94    }
95
96    let output = String::from_utf8(output.stdout)?;
97    let parsed = json::parse(&output)?;
98
99    Ok(parsed)
100}
101
102/// Failed to query project metadata.
103#[derive(Debug)]
104pub enum CargoMetadataError {
105    /// An I/O error that occurred while trying to execute `cargo metadata`.
106    Io(io::Error),
107    /// The command `cargo metadata` did not exit successfully.
108    Failed {
109        /// The standard error output of `cargo metadata`.
110        stderr: Vec<u8>,
111    },
112    /// The output of `cargo metadata` was not valid UTF-8.
113    StringConversion(string::FromUtf8Error),
114    /// An error occurred while parsing the output of `cargo metadata` as JSON.
115    ParseJson(json::Error),
116}
117
118impl fmt::Display for CargoMetadataError {
119    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120        match self {
121            CargoMetadataError::Io(err) => write!(f, "Failed to execute `cargo metadata`: {}", err),
122            CargoMetadataError::Failed { stderr } => write!(
123                f,
124                "`cargo metadata` was not successful: {}",
125                String::from_utf8_lossy(stderr)
126            ),
127            CargoMetadataError::StringConversion(err) => write!(
128                f,
129                "Failed to convert the `cargo metadata` output to a string: {}",
130                err
131            ),
132            CargoMetadataError::ParseJson(err) => write!(
133                f,
134                "Failed to parse `cargo metadata` output as JSON: {}",
135                err
136            ),
137        }
138    }
139}
140
141impl std::error::Error for CargoMetadataError {
142    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
143        match self {
144            CargoMetadataError::Io(err) => Some(err),
145            CargoMetadataError::Failed { stderr: _ } => None,
146            CargoMetadataError::StringConversion(err) => Some(err),
147            CargoMetadataError::ParseJson(err) => Some(err),
148        }
149    }
150}
151
152impl convert::From<io::Error> for CargoMetadataError {
153    fn from(source: io::Error) -> Self {
154        CargoMetadataError::Io(source)
155    }
156}
157
158impl convert::From<string::FromUtf8Error> for CargoMetadataError {
159    fn from(source: string::FromUtf8Error) -> Self {
160        CargoMetadataError::StringConversion(source)
161    }
162}
163
164impl convert::From<json::Error> for CargoMetadataError {
165    fn from(source: json::Error) -> Self {
166        CargoMetadataError::ParseJson(source)
167    }
168}