Skip to main content

waterui_cli/
toolchain.rs

1//! Toolchain management for `WaterUI` CLI
2
3use std::convert::Infallible;
4
5pub mod cargo_helpers;
6pub mod cmake;
7pub mod doctor;
8pub mod host;
9pub mod linux;
10pub mod meson;
11pub mod rust;
12pub mod sccache;
13#[cfg(test)]
14pub(crate) mod testing;
15pub mod web;
16pub mod windows_arm64_llvm;
17pub(crate) mod winget;
18
19pub use host::Host;
20/// A toolchain that cannot be fixed automatically.
21#[derive(Debug, Clone, thiserror::Error)]
22#[error("Unfixable toolchain: {message}\nSuggestion: {suggestion}")]
23pub struct UnfixableToolchain {
24    /// A message describing why the toolchain is unfixable.
25    message: String,
26    /// An suggestion for how to fix the toolchain manually.
27    suggestion: String,
28}
29
30impl UnfixableToolchain {
31    /// Create a new `UnfixableToolchain` with the given message and optional suggestion.
32    pub fn new(message: impl Into<String>, suggestion: impl Into<String>) -> Self {
33        Self {
34            message: message.into(),
35            suggestion: suggestion.into(),
36        }
37    }
38
39    /// Get the message describing why the toolchain is unfixable.
40    #[must_use]
41    pub fn message(&self) -> &str {
42        &self.message
43    }
44
45    /// Get the optional suggestion for how to fix the toolchain manually.
46    #[must_use]
47    pub fn suggestion(&self) -> &str {
48        &self.suggestion
49    }
50}
51
52/// Trait representing an installation plan for toolchain components.
53pub trait Installation: Send + Sync {
54    /// The error type returned if installation fails.
55    type Error: Into<eyre::Report> + Send;
56    /// Execute the installation plan against `host`.
57    fn install(&self, host: &Host) -> impl Future<Output = Result<(), Self::Error>> + Send;
58}
59
60/// Optional installation step.
61///
62/// This is used by composite toolchains (e.g. tuples) to represent "install if missing".
63impl<I: Installation> Installation for Option<I> {
64    type Error = eyre::Report;
65
66    async fn install(&self, host: &Host) -> Result<(), Self::Error> {
67        if let Some(install) = self {
68            install.install(host).await.map_err(Into::into)?;
69        }
70        Ok(())
71    }
72}
73
74/// An error indicating the state of the toolchain.
75#[derive(Debug, Clone, thiserror::Error)]
76pub enum ToolchainError<Install: Installation> {
77    /// The toolchain cannot be fixed automatically.
78    #[error("{0}")]
79    Unfixable(#[from] UnfixableToolchain),
80    /// The toolchain is missing components that can be installed.
81    #[error(
82        "Toolchain is missing components that can be fixed automatically. Run `water doctor --fix` for details."
83    )]
84    Fixable(Install),
85}
86
87impl<I: Installation> ToolchainError<I> {
88    /// Returns `true` if the toolchain can be fixed automatically.
89    #[must_use]
90    pub const fn is_fixable(&self) -> bool {
91        matches!(self, Self::Fixable(_))
92    }
93
94    /// Create a new `ToolchainError` indicating that the toolchain can be fixed automatically.
95    #[must_use]
96    pub const fn fixable(install: I) -> Self {
97        Self::Fixable(install)
98    }
99
100    /// Create a new `ToolchainError` indicating that the toolchain cannot be fixed automatically.
101    #[must_use]
102    pub fn unfixable(message: impl Into<String>, suggestion: impl Into<String>) -> Self {
103        Self::Unfixable(UnfixableToolchain::new(message, suggestion))
104    }
105}
106
107/// Trait for toolchain dependencies that can be checked and installed.
108///
109/// Implementors represent a specific toolchain configuration (e.g., Rust with
110/// certain targets, Android SDK with specific components).
111/// The associated `Installation` type preserves full type information through
112/// the composition, enabling zero-cost abstractions for parallel/sequential
113/// installation plans.
114pub trait Toolchain: Send + Sync {
115    /// The installation type returned by `fix()`.
116    type Installation: Installation;
117
118    /// Check if the toolchain is properly installed on `host`.
119    ///
120    /// Returns `Ok(())` if all components are available, or `Err` describing
121    /// what is missing.
122    fn check(
123        &self,
124        host: &Host,
125    ) -> impl Future<Output = Result<(), ToolchainError<Self::Installation>>> + Send;
126}
127
128impl Installation for Infallible {
129    type Error = Self;
130
131    fn install(&self, _host: &Host) -> impl Future<Output = Result<(), Self::Error>> + Send {
132        std::future::poll_fn(|_| {
133            unreachable!("an Infallible installation plan cannot be constructed")
134        })
135    }
136}
137
138macro_rules! tuples {
139    ($macro:ident) => {
140        $macro!(T0);
141        $macro!(T0, T1);
142        $macro!(T0, T1, T2);
143        $macro!(T0, T1, T2, T3);
144        $macro!(T0, T1, T2, T3, T4);
145        $macro!(T0, T1, T2, T3, T4, T5);
146        $macro!(T0, T1, T2, T3, T4, T5, T6);
147        $macro!(T0, T1, T2, T3, T4, T5, T6, T7);
148        $macro!(T0, T1, T2, T3, T4, T5, T6, T7, T8);
149        $macro!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9);
150        $macro!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
151        $macro!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);
152        $macro!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12);
153        $macro!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13);
154        $macro!(
155            T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14
156        );
157    };
158}
159
160macro_rules! impl_installations {
161    ($($ty:ident),*) => {
162        #[allow(unused_variables)]
163        #[allow(non_snake_case)]
164        impl<$($ty: Installation),*> Installation for ($($ty,)*) {
165            type Error = eyre::Report;
166            async fn install(&self, host: &Host) -> Result<(), Self::Error> {
167                let ($($ty,)*) = self;
168                $(
169                    $ty.install(host).await.map_err(|e| e.into())?;
170                )*
171                Ok(())
172            }
173        }
174    };
175}
176
177impl Installation for () {
178    type Error = eyre::Report;
179
180    fn install(&self, _host: &Host) -> impl Future<Output = Result<(), Self::Error>> + Send {
181        std::future::ready(Ok(()))
182    }
183}
184
185tuples!(impl_installations);
186
187macro_rules! impl_toolchains {
188    ($(($idx:tt, $ty:ident)),*) => {
189        #[allow(unused_variables)]
190        #[allow(non_snake_case)]
191        impl<$($ty: Toolchain),*> Toolchain for ($($ty,)*) {
192            // Each slot is `Some(install)` if that component is missing-and-fixable.
193            // Components that are already OK produce `None` and are skipped during installation.
194            type Installation = ($(Option<$ty::Installation>,)*);
195
196            async fn check(&self, host: &Host) -> Result<(), ToolchainError<Self::Installation>> {
197                #[allow(unused_mut)]
198                let mut any_fixable = false;
199                #[allow(unused_mut)]
200                let mut installs: Self::Installation = ($(None::<$ty::Installation>,)*);
201
202                $(
203                    match self.$idx.check(host).await {
204                        Ok(()) => {}
205                        Err(ToolchainError::Unfixable(u)) => {
206                            return Err(ToolchainError::Unfixable(u));
207                        }
208                        Err(ToolchainError::Fixable(install)) => {
209                            any_fixable = true;
210                            installs.$idx = Some(install);
211                        }
212                    }
213                )*
214
215                if any_fixable {
216                    Err(ToolchainError::Fixable(installs))
217                } else {
218                    Ok(())
219                }
220            }
221        }
222    };
223}
224
225impl Toolchain for () {
226    type Installation = ();
227
228    fn check(
229        &self,
230        _host: &Host,
231    ) -> impl Future<Output = Result<(), ToolchainError<Self::Installation>>> + Send {
232        std::future::ready(Ok(()))
233    }
234}
235
236macro_rules! tuples_idx {
237    ($macro:ident) => {
238        $macro!((0, T0));
239        $macro!((0, T0), (1, T1));
240        $macro!((0, T0), (1, T1), (2, T2));
241        $macro!((0, T0), (1, T1), (2, T2), (3, T3));
242        $macro!((0, T0), (1, T1), (2, T2), (3, T3), (4, T4));
243        $macro!((0, T0), (1, T1), (2, T2), (3, T3), (4, T4), (5, T5));
244        $macro!(
245            (0, T0),
246            (1, T1),
247            (2, T2),
248            (3, T3),
249            (4, T4),
250            (5, T5),
251            (6, T6)
252        );
253        $macro!(
254            (0, T0),
255            (1, T1),
256            (2, T2),
257            (3, T3),
258            (4, T4),
259            (5, T5),
260            (6, T6),
261            (7, T7)
262        );
263        $macro!(
264            (0, T0),
265            (1, T1),
266            (2, T2),
267            (3, T3),
268            (4, T4),
269            (5, T5),
270            (6, T6),
271            (7, T7),
272            (8, T8)
273        );
274        $macro!(
275            (0, T0),
276            (1, T1),
277            (2, T2),
278            (3, T3),
279            (4, T4),
280            (5, T5),
281            (6, T6),
282            (7, T7),
283            (8, T8),
284            (9, T9)
285        );
286        $macro!(
287            (0, T0),
288            (1, T1),
289            (2, T2),
290            (3, T3),
291            (4, T4),
292            (5, T5),
293            (6, T6),
294            (7, T7),
295            (8, T8),
296            (9, T9),
297            (10, T10)
298        );
299        $macro!(
300            (0, T0),
301            (1, T1),
302            (2, T2),
303            (3, T3),
304            (4, T4),
305            (5, T5),
306            (6, T6),
307            (7, T7),
308            (8, T8),
309            (9, T9),
310            (10, T10),
311            (11, T11)
312        );
313        $macro!(
314            (0, T0),
315            (1, T1),
316            (2, T2),
317            (3, T3),
318            (4, T4),
319            (5, T5),
320            (6, T6),
321            (7, T7),
322            (8, T8),
323            (9, T9),
324            (10, T10),
325            (11, T11),
326            (12, T12)
327        );
328        $macro!(
329            (0, T0),
330            (1, T1),
331            (2, T2),
332            (3, T3),
333            (4, T4),
334            (5, T5),
335            (6, T6),
336            (7, T7),
337            (8, T8),
338            (9, T9),
339            (10, T10),
340            (11, T11),
341            (12, T12),
342            (13, T13)
343        );
344        $macro!(
345            (0, T0),
346            (1, T1),
347            (2, T2),
348            (3, T3),
349            (4, T4),
350            (5, T5),
351            (6, T6),
352            (7, T7),
353            (8, T8),
354            (9, T9),
355            (10, T10),
356            (11, T11),
357            (12, T12),
358            (13, T13),
359            (14, T14)
360        );
361    };
362}
363
364tuples_idx!(impl_toolchains);