cargo_lambda_build/
zig.rs1use 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};
7use serde::Serialize;
8use std::{path::PathBuf, process::Command};
9
10#[derive(Debug, Default, Serialize)]
11pub struct ZigInfo {
12 #[serde(default, skip_serializing_if = "Option::is_none")]
13 command: Option<String>,
14 #[serde(default, skip_serializing_if = "Option::is_none")]
15 install_options: Option<Vec<InstallOption>>,
16 #[serde(default, skip_serializing_if = "Option::is_none")]
17 version: Option<String>,
18}
19
20pub fn print_install_options(options: &[InstallOption]) {
22 println!("You can use any of the following options to install it:");
23 for option in options {
24 println!("\t* {}: `{}`", option, option.usage());
25 }
26 println!(
27 "Or download Zig 0.13.0 or newer from https://ziglang.org/download/ and add it to your PATH"
28 );
29}
30
31pub async fn install_zig_interactive(options: Vec<InstallOption>) -> Result<()> {
33 let choice = choose_option("Pick an option to install it:", options);
34
35 match choice {
36 Ok(choice) => choice.install().await.map(|_| ()),
37 Err(err) => Err(err).into_diagnostic(),
38 }
39}
40
41pub async fn check_installation() -> Result<ZigInfo> {
42 if let Ok((path, run_modifiers)) = Zig::find_zig() {
43 return get_zig_version(path, run_modifiers);
44 }
45
46 println!("Zig is not installed in your system.");
47 let options = install_options();
48 if options.is_empty() {
49 println!(
50 "Download Zig 0.13.0 or newer from https://ziglang.org/download/ and add it to your PATH"
51 );
52 return Err(BuildError::ZigMissing.into());
53 }
54
55 if options.len() == 1 {
56 let Some(choice) = options.first().cloned() else {
57 return Err(BuildError::ZigMissing.into());
58 };
59
60 choice.install().await?;
61 get_zig_info()
62 } else if is_stdin_tty() {
63 install_zig_interactive(options).await?;
64 get_zig_info()
65 } else {
66 print_install_options(&options);
67 Err(BuildError::ZigMissing.into())
68 }
69}
70
71pub fn get_zig_info() -> Result<ZigInfo> {
72 let Ok((path, run_modifiers)) = Zig::find_zig() else {
73 let options = install_options();
74 return Ok(ZigInfo {
75 install_options: Some(options),
76 ..Default::default()
77 });
78 };
79
80 get_zig_version(path, run_modifiers)
81}
82
83fn get_zig_version(
84 path: PathBuf,
85 run_modifiers: Vec<String>,
86) -> std::result::Result<ZigInfo, miette::Error> {
87 let mut cmd = Command::new(&path);
88 cmd.args(&run_modifiers);
89 cmd.arg("version");
90 let output = cmd.output().into_diagnostic()?;
91 let version = String::from_utf8(output.stdout)
92 .into_diagnostic()?
93 .trim()
94 .to_string();
95
96 let mut command = format!("{}", path.display());
97 if !run_modifiers.is_empty() {
98 command.push(' ');
99 command.push_str(&run_modifiers.join(" "));
100 };
101
102 Ok(ZigInfo {
103 command: Some(command),
104 version: Some(version),
105 ..Default::default()
106 })
107}
108
109#[derive(Clone, Debug)]
110pub enum InstallOption {
111 #[cfg(not(windows))]
112 Brew,
113 #[cfg(windows)]
114 Choco,
115 #[cfg(not(windows))]
116 Nix,
117 #[cfg(not(windows))]
118 Npm,
119 Pip3,
120 #[cfg(windows)]
121 Scoop,
122 #[cfg(windows)]
123 Winget,
124}
125
126impl serde::Serialize for InstallOption {
127 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
128 where
129 S: serde::Serializer,
130 {
131 serializer.serialize_str(self.usage())
132 }
133}
134
135impl std::fmt::Display for InstallOption {
136 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137 match self {
138 #[cfg(not(windows))]
139 InstallOption::Brew => write!(f, "Install with Homebrew"),
140 #[cfg(windows)]
141 InstallOption::Choco => write!(f, "Install with Chocolatey"),
142 #[cfg(not(windows))]
143 InstallOption::Nix => write!(f, "Install with Nix"),
144 #[cfg(not(windows))]
145 InstallOption::Npm => write!(f, "Install with NPM"),
146 InstallOption::Pip3 => write!(f, "Install with Pip3 (Python 3)"),
147 #[cfg(windows)]
148 InstallOption::Scoop => write!(f, "Install with Scoop"),
149 #[cfg(windows)]
150 InstallOption::Winget => write!(f, "Install with Winget"),
151 }
152 }
153}
154
155impl InstallOption {
156 pub fn usage(&self) -> &'static str {
157 match self {
158 #[cfg(not(windows))]
159 InstallOption::Brew => "brew install zig",
160 #[cfg(windows)]
161 InstallOption::Choco => "choco install zig",
162 #[cfg(not(windows))]
163 InstallOption::Nix => "nix-env -iA nixpkgs.zig",
164 #[cfg(not(windows))]
165 InstallOption::Npm => "npm install -g @ziglang/cli",
166 InstallOption::Pip3 => "pip3 install ziglang",
167 #[cfg(windows)]
168 InstallOption::Scoop => "scoop install zig",
169 #[cfg(windows)]
170 InstallOption::Winget => "winget install zig.zig",
171 }
172 }
173
174 pub async fn install(self) -> Result<()> {
175 let pb = Progress::start("Installing Zig...");
176 let usage = self.usage().split(' ').collect::<Vec<_>>();
177 let usage = usage.as_slice();
178 let result = silent_command(usage[0], &usage[1..usage.len()]).await;
179
180 match result {
181 Ok(_) => {
182 pb.finish("Zig installed");
183 Ok(())
184 }
185 Err(err) => {
186 pb.finish("Zig installation failed");
187 Err(err).into_diagnostic()
188 }
189 }
190 }
191}
192
193pub fn install_options() -> Vec<InstallOption> {
194 let mut options = Vec::new();
195
196 #[cfg(not(windows))]
197 if which::which("brew").is_ok() {
198 options.push(InstallOption::Brew);
199 }
200
201 #[cfg(windows)]
202 if which::which("choco").is_ok() {
203 options.push(InstallOption::Choco);
204 }
205
206 #[cfg(not(windows))]
207 if which::which("nix-env").is_ok() {
208 options.push(InstallOption::Nix);
209 }
210
211 #[cfg(not(windows))]
212 if which::which("npm").is_ok() {
213 options.push(InstallOption::Npm);
214 }
215
216 if which::which("pip3").is_ok() {
217 options.push(InstallOption::Pip3);
218 }
219
220 #[cfg(windows)]
221 if which::which("scoop").is_ok() {
222 options.push(InstallOption::Scoop);
223 }
224
225 #[cfg(windows)]
226 if which::which("winget").is_ok() {
227 options.push(InstallOption::Winget);
228 }
229
230 options
231}