waterui_cli/
toolchain.rs

1//! Toolchain management for `WaterUI` CLI
2
3use std::convert::Infallible;
4
5use color_eyre::eyre;
6
7pub mod cmake;
8pub mod doctor;
9/// A toolchain that cannot be fixed automatically.
10#[derive(Debug, Clone, thiserror::Error)]
11#[error("Unfixable toolchain: {message}\n Suggestion: {suggestion}")]
12pub struct UnfixableToolchain {
13    /// A message describing why the toolchain is unfixable.
14    message: String,
15    /// An suggestion for how to fix the toolchain manually.
16    suggestion: String,
17}
18
19impl UnfixableToolchain {
20    /// Create a new `UnfixableToolchain` with the given message and optional suggestion.
21    pub fn new(message: impl Into<String>, suggestion: impl Into<String>) -> Self {
22        Self {
23            message: message.into(),
24            suggestion: suggestion.into(),
25        }
26    }
27
28    /// Get the message describing why the toolchain is unfixable.
29    #[must_use]
30    pub fn message(&self) -> &str {
31        &self.message
32    }
33
34    /// Get the optional suggestion for how to fix the toolchain manually.
35    #[must_use]
36    pub fn suggestion(&self) -> &str {
37        &self.suggestion
38    }
39}
40
41/// Trait representing an installation plan for toolchain components.
42pub trait Installation: Send + Sync {
43    /// The error type returned if installation fails.
44    type Error: Into<eyre::Report> + Send;
45    /// Execute the installation plan.
46    fn install(&self) -> impl Future<Output = Result<(), Self::Error>> + Send;
47}
48
49/// An error indicating the state of the toolchain.
50#[derive(Debug, Clone, thiserror::Error)]
51pub enum ToolchainError<Install: Installation> {
52    /// The toolchain cannot be fixed automatically.
53    #[error("Unfixable toolchain, consider manual intervention")]
54    Unfixable(#[from] UnfixableToolchain),
55    /// The toolchain is missing components that can be installed.
56    #[error("Toolchain is missing, but can be fixed automatically")]
57    Fixable(Install),
58}
59
60impl<I: Installation> ToolchainError<I> {
61    /// Returns `true` if the toolchain can be fixed automatically.
62    #[must_use]
63    pub const fn is_fixable(&self) -> bool {
64        matches!(self, Self::Fixable(_))
65    }
66
67    /// Create a new `ToolchainError` indicating that the toolchain can be fixed automatically.
68    #[must_use]
69    pub const fn fixable(install: I) -> Self {
70        Self::Fixable(install)
71    }
72
73    /// Create a new `ToolchainError` indicating that the toolchain cannot be fixed automatically.
74    #[must_use]
75    pub fn unfixable(message: impl Into<String>, suggestion: impl Into<String>) -> Self {
76        Self::Unfixable(UnfixableToolchain::new(message, suggestion))
77    }
78}
79
80/// Trait for toolchain dependencies that can be checked and installed.
81///
82/// Implementors represent a specific toolchain configuration (e.g., Rust with
83/// certain targets, Android SDK with specific components).
84/// The associated `Installation` type preserves full type information through
85/// the composition, enabling zero-cost abstractions for parallel/sequential
86/// installation plans.
87pub trait Toolchain: Send + Sync {
88    /// The installation type returned by `fix()`.
89    type Installation: Installation;
90
91    /// Check if the toolchain is properly installed.
92    ///
93    /// Returns `Ok(())` if all components are available, or `Err` describing
94    /// what is missing.
95    fn check(&self) -> impl Future<Output = Result<(), ToolchainError<Self::Installation>>> + Send;
96}
97
98impl Installation for Infallible {
99    type Error = Self;
100
101    async fn install(&self) -> Result<(), Self::Error> {
102        unreachable!()
103    }
104}
105
106impl Toolchain for Infallible {
107    type Installation = Self;
108
109    async fn check(&self) -> Result<(), crate::toolchain::ToolchainError<Self::Installation>> {
110        unreachable!()
111    }
112}
113
114macro_rules! tuples {
115    ($macro:ident) => {
116        $macro!();
117        $macro!(T0);
118        $macro!(T0, T1);
119        $macro!(T0, T1, T2);
120        $macro!(T0, T1, T2, T3);
121        $macro!(T0, T1, T2, T3, T4);
122        $macro!(T0, T1, T2, T3, T4, T5);
123        $macro!(T0, T1, T2, T3, T4, T5, T6);
124        $macro!(T0, T1, T2, T3, T4, T5, T6, T7);
125        $macro!(T0, T1, T2, T3, T4, T5, T6, T7, T8);
126        $macro!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9);
127        $macro!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
128        $macro!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);
129        $macro!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12);
130        $macro!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13);
131        $macro!(
132            T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14
133        );
134    };
135}
136
137macro_rules! impl_installations {
138    ($($ty:ident),*) => {
139        #[allow(unused_variables)]
140        #[allow(non_snake_case)]
141        impl<$($ty: Installation),*> Installation for ($($ty,)*) {
142            type Error = eyre::Report;
143            async fn install(&self) -> Result<(), Self::Error> {
144                let ($($ty,)*) = self;
145                $(
146                    $ty.install().await.map_err(|e| e.into())?;
147                )*
148                Ok(())
149            }
150        }
151    };
152}
153
154tuples!(impl_installations);
155
156macro_rules! impl_toolchains {
157    ($($ty:ident),*) => {
158        #[allow(unused_variables)]
159        #[allow(non_snake_case)]
160        impl<$($ty: Toolchain),*> Toolchain for ($($ty,)*) {
161            type Installation = ($($ty::Installation,)*);
162
163            async fn check(&self) -> Result<(), ToolchainError<Self::Installation>> {
164                let ($($ty,)*) = self;
165                $(
166                    match $ty.check().await {
167                        Ok(()) => {}
168                        Err(e) => {
169                            return Err(match e {
170                                ToolchainError::Unfixable(u) => ToolchainError::Unfixable(u),
171                                ToolchainError::Fixable(_) => ToolchainError::Unfixable(
172                                    UnfixableToolchain::new(
173                                        format!("One of the toolchains requires fixing"),
174                                        "Run the fix command to install missing components",
175                                    )
176                                ),
177                            });
178                        }
179                    }
180                )*
181                Ok(())
182            }
183        }
184    };
185}
186
187tuples!(impl_toolchains);