cargo_lambda_build/
zig.rs

1use crate::error::BuildError;
2use cargo_lambda_interactive::{
3    choose_option, command::silent_command, is_stdin_tty, progress::Progress,
4};
5use cargo_zigbuild::Zig;
6use miette::{IntoDiagnostic, Result};
7
8/// Print information about the Zig installation.
9pub fn print_install_options(options: &[InstallOption]) {
10    println!("Zig is not installed in your system.");
11    if is_stdin_tty() {
12        println!("Run `cargo lambda system --install-zig` to install Zig.")
13    }
14
15    if !options.is_empty() {
16        println!("You can use any of the following options to install it:");
17        for option in options {
18            println!("\t* {}: `{}`", option, option.usage());
19        }
20    }
21    println!(
22        "\t* Download Zig 0.9.1 or newer from https://ziglang.org/download/ and add it to your PATH"
23    );
24}
25
26/// Install Zig using a choice prompt.
27pub async fn install_zig(options: Vec<InstallOption>) -> Result<()> {
28    let choice = choose_option(
29        "Zig is not installed in your system.\nHow do you want to install Zig?",
30        options,
31    );
32
33    match choice {
34        Ok(choice) => choice.install().await.map(|_| ()),
35        Err(err) => Err(err).into_diagnostic(),
36    }
37}
38
39pub async fn check_installation() -> Result<()> {
40    if Zig::find_zig().is_ok() {
41        return Ok(());
42    }
43
44    let options = install_options();
45
46    if !is_stdin_tty() || options.is_empty() {
47        print_install_options(&options);
48        return Err(BuildError::ZigMissing.into());
49    }
50
51    install_zig(options).await
52}
53
54#[derive(Debug)]
55pub enum InstallOption {
56    #[cfg(not(windows))]
57    Brew,
58    #[cfg(windows)]
59    Choco,
60    #[cfg(not(windows))]
61    Npm,
62    Pip3,
63    #[cfg(windows)]
64    Scoop,
65}
66
67impl serde::Serialize for InstallOption {
68    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
69    where
70        S: serde::Serializer,
71    {
72        serializer.serialize_str(self.usage())
73    }
74}
75
76impl std::fmt::Display for InstallOption {
77    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78        match self {
79            #[cfg(not(windows))]
80            InstallOption::Brew => write!(f, "Install with Homebrew"),
81            #[cfg(windows)]
82            InstallOption::Choco => write!(f, "Install with Chocolatey"),
83            #[cfg(not(windows))]
84            InstallOption::Npm => write!(f, "Install with NPM"),
85            InstallOption::Pip3 => write!(f, "Install with Pip3 (Python 3)"),
86            #[cfg(windows)]
87            InstallOption::Scoop => write!(f, "Install with Scoop"),
88        }
89    }
90}
91
92impl InstallOption {
93    pub fn usage(&self) -> &'static str {
94        match self {
95            #[cfg(not(windows))]
96            InstallOption::Brew => "brew install zig",
97            #[cfg(windows)]
98            InstallOption::Choco => "choco install zig",
99            #[cfg(not(windows))]
100            InstallOption::Npm => "npm install -g @ziglang/cli",
101            InstallOption::Pip3 => "pip3 install ziglang",
102            #[cfg(windows)]
103            InstallOption::Scoop => "scoop install zig",
104        }
105    }
106
107    pub async fn install(self) -> Result<()> {
108        let pb = Progress::start("Installing Zig...");
109        let usage = self.usage().split(' ').collect::<Vec<_>>();
110        let usage = usage.as_slice();
111        let result = silent_command(usage[0], &usage[1..usage.len()]).await;
112
113        let finish = if result.is_ok() {
114            "Zig installed"
115        } else {
116            "Failed to install Zig"
117        };
118        pb.finish(finish);
119
120        result
121    }
122}
123
124pub fn install_options() -> Vec<InstallOption> {
125    let mut options = Vec::new();
126
127    #[cfg(not(windows))]
128    if which::which("brew").is_ok() {
129        options.push(InstallOption::Brew);
130    }
131
132    #[cfg(windows)]
133    if which::which("choco").is_ok() {
134        options.push(InstallOption::Choco);
135    }
136
137    #[cfg(windows)]
138    if which::which("scoop").is_ok() {
139        options.push(InstallOption::Scoop);
140    }
141
142    if which::which("pip3").is_ok() {
143        options.push(InstallOption::Pip3);
144    }
145
146    #[cfg(not(windows))]
147    if which::which("npm").is_ok() {
148        options.push(InstallOption::Npm);
149    }
150    options
151}