Skip to main content

waterui_cli/
toolchain.rs

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