bootloader_locator/
lib.rs1#![warn(missing_docs)]
4
5use std::{convert, fmt, io, path::PathBuf, process::Command, string};
6
7pub 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#[derive(Debug)]
44pub enum LocateError {
45 MetadataInvalid,
47 DependencyNotFound,
49 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#[derive(Debug)]
104pub enum CargoMetadataError {
105 Io(io::Error),
107 Failed {
109 stderr: Vec<u8>,
111 },
112 StringConversion(string::FromUtf8Error),
114 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}