waterui_cli/toolchain/
sccache.rs1use std::path::{Path, PathBuf};
4
5use smol::process::Command;
6
7use crate::{
8 brew::Brew,
9 toolchain::linux::{
10 LinuxPackageManagerError, has_supported_package_manager, install_named_packages,
11 },
12 toolchain::winget::{WingetInstallError, ensure_package_installed},
13 toolchain::{Host, Installation, Toolchain, ToolchainError},
14 utils::{CommandError, sccache_install_hint},
15};
16
17pub fn configure_compilation_cache(command: &mut Command, sccache_path: &Path) {
27 command.env("RUSTC_WRAPPER", sccache_path);
28}
29
30#[derive(Debug, Clone, Default)]
35pub struct Sccache;
36
37impl Sccache {
38 pub async fn path(&self, host: &Host) -> Result<PathBuf, which::Error> {
43 host.which("sccache").await
44 }
45
46 pub async fn is_available(&self, host: &Host) -> bool {
48 self.path(host).await.is_ok()
49 }
50}
51
52impl Toolchain for Sccache {
53 type Installation = SccacheInstallation;
54
55 async fn check(&self, host: &Host) -> Result<(), ToolchainError<Self::Installation>> {
56 if host.which("sccache").await.is_ok() {
57 Ok(())
58 } else if cfg!(target_os = "windows") {
59 if host.which("winget").await.is_ok() {
60 Err(ToolchainError::fixable(SccacheInstallation))
61 } else {
62 Err(ToolchainError::unfixable(
63 "sccache not found and winget is unavailable",
64 format!(
65 "Install Microsoft App Installer to provide winget, or install manually with {}.",
66 sccache_install_hint()
67 ),
68 ))
69 }
70 } else if cfg!(target_os = "macos") {
71 if host.which("brew").await.is_ok() {
72 Err(ToolchainError::fixable(SccacheInstallation))
73 } else {
74 Err(ToolchainError::unfixable(
75 "sccache not found and Homebrew is unavailable",
76 format!(
77 "Install Homebrew to enable automatic fixes, or install manually with {}.",
78 sccache_install_hint()
79 ),
80 ))
81 }
82 } else if cfg!(target_os = "linux") {
83 if has_supported_package_manager(host).await {
84 Err(ToolchainError::fixable(SccacheInstallation))
85 } else {
86 Err(ToolchainError::unfixable(
87 "sccache is missing and no supported package manager was found",
88 format!("Install manually with {}", sccache_install_hint()),
89 ))
90 }
91 } else {
92 Err(ToolchainError::unfixable(
93 "sccache not found",
94 format!(
95 "Install sccache manually ({}) and ensure `sccache` is available in PATH.",
96 sccache_install_hint()
97 ),
98 ))
99 }
100 }
101}
102
103#[derive(Debug, Clone)]
105pub struct SccacheInstallation;
106
107#[derive(Debug, thiserror::Error)]
109pub enum FailToInstallSccache {
110 #[error("Homebrew not found. Please install Homebrew to proceed.")]
112 BrewNotFound,
113
114 #[error("Failed to install sccache: {0}")]
116 Command(#[from] CommandError),
117
118 #[error(
120 "winget is required for automatic sccache installation on Windows. Install App Installer and retry."
121 )]
122 WingetNotFound,
123
124 #[error("Failed to install sccache via winget: {0}")]
126 WingetInstallFailed(String),
127
128 #[error(
130 "No supported Linux package manager found (apt-get, dnf, pacman, zypper, apk). Install sccache manually."
131 )]
132 UnsupportedPackageManager,
133
134 #[error(
136 "Automatic installation of sccache is not supported on this platform. \
137 Install manually with: cargo install sccache"
138 )]
139 UnsupportedPlatform,
140}
141
142impl Installation for SccacheInstallation {
143 type Error = FailToInstallSccache;
144
145 async fn install(&self, host: &Host) -> Result<(), Self::Error> {
146 if cfg!(target_os = "macos") {
147 let brew = Brew::default();
148
149 brew.check(host)
150 .await
151 .map_err(|_| FailToInstallSccache::BrewNotFound)?;
152 brew.install(host, "sccache").await?;
153
154 Ok(())
155 } else if cfg!(target_os = "windows") {
156 ensure_package_installed(host, "Mozilla.sccache")
157 .await
158 .map_err(map_winget_error_for_sccache)
159 } else if cfg!(target_os = "linux") {
160 install_named_packages(host, &["sccache"])
161 .await
162 .map_err(map_linux_error_for_sccache)
163 } else {
164 Err(FailToInstallSccache::UnsupportedPlatform)
165 }
166 }
167}
168
169fn map_linux_error_for_sccache(error: LinuxPackageManagerError) -> FailToInstallSccache {
170 match error {
171 LinuxPackageManagerError::UnsupportedPackageManager => {
172 FailToInstallSccache::UnsupportedPackageManager
173 }
174 LinuxPackageManagerError::Command(source) => FailToInstallSccache::Command(source),
175 }
176}
177
178fn map_winget_error_for_sccache(error: WingetInstallError) -> FailToInstallSccache {
179 match error {
180 WingetInstallError::WingetNotFound => FailToInstallSccache::WingetNotFound,
181 WingetInstallError::CommandFailed(err) => {
182 FailToInstallSccache::WingetInstallFailed(err.to_string())
183 }
184 WingetInstallError::NotInstalled { package_id } => {
185 FailToInstallSccache::WingetInstallFailed(format!(
186 "Package `{package_id}` is still missing after winget install; verify winget sources and retry."
187 ))
188 }
189 }
190}
191
192#[cfg(test)]
193mod host_tests {
194 use super::{Sccache, SccacheInstallation};
195 use crate::toolchain::testing::TestMachine;
196 use crate::toolchain::{Toolchain, ToolchainError};
197
198 fn check(machine: &TestMachine) -> Result<(), ToolchainError<SccacheInstallation>> {
199 let host = machine.host(Vec::<(String, String)>::new());
200 smol::block_on(Sccache.check(&host))
201 }
202
203 #[test]
204 fn ok_when_sccache_on_path() {
205 let machine = TestMachine::new();
206 machine.install("sccache");
207 check(&machine).expect("sccache on PATH must be ok");
208 }
209
210 #[test]
211 fn missing_without_installer_is_unfixable() {
212 let machine = TestMachine::new();
213 let result = check(&machine);
214 assert!(
215 matches!(result, Err(ToolchainError::Unfixable(_))),
216 "missing sccache without a package manager must be unfixable: {result:?}"
217 );
218 }
219
220 #[test]
221 fn missing_with_installer_is_fixable() {
222 let machine = TestMachine::new();
223 #[cfg(target_os = "macos")]
224 machine.install("brew");
225 #[cfg(target_os = "linux")]
226 machine.install("apt-get");
227 #[cfg(target_os = "windows")]
228 machine.install("winget");
229 let result = check(&machine);
230 assert!(
231 matches!(result, Err(ToolchainError::Fixable(_))),
232 "missing sccache with a package manager must be fixable: {result:?}"
233 );
234 }
235}