1use std::{
7 path::{Path, PathBuf},
8 process::Stdio,
9};
10
11use anyhow::{bail, ensure, Context, Result};
12use async_trait::async_trait;
13use tokio::process::Command;
14use tracing::{debug, error};
15
16pub async fn resolve_binary(name: &'static str, package: &'static str) -> Result<PathBuf> {
22 let current_binary = std::env::current_exe()?;
23 resolve_binary_in_same_directory_as(¤t_binary, name, package).await
24}
25
26pub fn current_binary_parent() -> Result<PathBuf> {
28 let current_binary = std::env::current_exe()?;
29 binary_parent(¤t_binary)
30}
31
32pub fn binary_parent(current_binary: &Path) -> Result<PathBuf> {
34 let mut current_binary_parent = current_binary
35 .canonicalize()
36 .with_context(|| format!("Failed to canonicalize '{}'", current_binary.display()))?;
37 current_binary_parent.pop();
38
39 #[cfg(with_testing)]
40 let current_binary_parent = if current_binary_parent.ends_with("target/debug/deps")
43 || current_binary_parent.ends_with("target/release/deps")
44 {
45 PathBuf::from(current_binary_parent.parent().unwrap())
46 } else {
47 current_binary_parent
48 };
49
50 Ok(current_binary_parent)
51}
52
53pub async fn resolve_binary_in_same_directory_as<P: AsRef<Path>>(
57 current_binary: P,
58 name: &'static str,
59 package: &'static str,
60) -> Result<PathBuf> {
61 let current_binary = current_binary.as_ref();
62 debug!(
63 "Resolving binary {name} based on the current binary path: {}",
64 current_binary.display()
65 );
66
67 let current_binary_parent =
68 binary_parent(current_binary).expect("Fetching binary directory should not fail");
69
70 let binary = current_binary_parent.join(name);
71 let version = format!("v{}", env!("CARGO_PKG_VERSION"));
72 if !binary.exists() {
73 error!(
74 "Cannot find a binary {name} in the directory {}. \
75 Consider using `cargo install {package}` or `cargo build -p {package}`",
76 current_binary_parent.display()
77 );
78 bail!("Failed to resolve binary {name}");
79 }
80
81 debug!("Checking the version of {}", binary.display());
83 let version_message = Command::new(&binary)
84 .arg("--version")
85 .output()
86 .await
87 .with_context(|| {
88 format!(
89 "Failed to execute and retrieve version from the binary {name} in directory {}",
90 current_binary_parent.display()
91 )
92 })?
93 .stdout;
94 let version_message = String::from_utf8_lossy(&version_message);
95 let found_version = parse_version_message(&version_message);
96 if version != found_version {
97 error!("The binary {name} in directory {} should have version {version} (found {found_version}). \
98 Consider using `cargo install {package} --version '{version}'` or `cargo build -p {package}`",
99 current_binary_parent.display()
100 );
101 bail!("Incorrect version for binary {name}");
102 }
103 debug!("{} has version {version}", binary.display());
104
105 Ok(binary)
106}
107
108pub fn parse_version_message(message: &str) -> String {
110 let mut lines = message.lines();
111 lines.next();
112 lines
113 .next()
114 .unwrap_or_default()
115 .trim()
116 .split(' ')
117 .last()
118 .expect("splitting strings gives non-empty lists")
119 .to_string()
120}
121
122#[async_trait]
124pub trait CommandExt: std::fmt::Debug {
125 fn spawn_into(&mut self) -> anyhow::Result<tokio::process::Child>;
128
129 async fn spawn_and_wait_for_stdout(&mut self) -> anyhow::Result<String>;
133
134 async fn spawn_and_wait(&mut self) -> anyhow::Result<()>;
137
138 fn description(&self) -> String {
140 format!("While executing {:?}", self)
141 }
142}
143
144#[async_trait]
145impl CommandExt for tokio::process::Command {
146 fn spawn_into(&mut self) -> anyhow::Result<tokio::process::Child> {
147 self.kill_on_drop(true);
148 debug!("Spawning {:?}", self);
149 let child = tokio::process::Command::spawn(self).with_context(|| self.description())?;
150 Ok(child)
151 }
152
153 async fn spawn_and_wait_for_stdout(&mut self) -> anyhow::Result<String> {
154 debug!("Spawning and waiting for {:?}", self);
155 self.stdout(Stdio::piped());
156 self.stderr(Stdio::inherit());
157 self.kill_on_drop(true);
158
159 let child = self.spawn().with_context(|| self.description())?;
160 let output = child
161 .wait_with_output()
162 .await
163 .with_context(|| self.description())?;
164 ensure!(
165 output.status.success(),
166 "{}: got non-zero error code {}",
167 self.description(),
168 output.status
169 );
170 String::from_utf8(output.stdout).with_context(|| self.description())
171 }
172
173 async fn spawn_and_wait(&mut self) -> anyhow::Result<()> {
174 debug!("Spawning and waiting for {:?}", self);
175 self.kill_on_drop(true);
176
177 let mut child = self.spawn().with_context(|| self.description())?;
178 let status = child.wait().await.with_context(|| self.description())?;
179 ensure!(
180 status.success(),
181 "{}: got non-zero error code {}",
182 self.description(),
183 status
184 );
185
186 Ok(())
187 }
188}