1use template::Template;
2
3use crate::package_manager::PackageManager;
4use crate::utils::colors::*;
5use crate::{args::TauriVersion, internal::template};
6use std::process::{Command, Output};
7
8fn is_cli_installed(cli: &str, arg: &str) -> bool {
9 Command::new(cli)
10 .arg(arg)
11 .output()
12 .map(|o| o.status.success())
13 .unwrap_or(false)
14}
15
16fn is_tauri_cli_installed(tauri_version: TauriVersion) -> bool {
17 let check = |o: Output| match o.status.success() {
18 true => String::from_utf8_lossy(&o.stdout)
19 .split_once(' ')
20 .map(|(_, v)| v.starts_with(&tauri_version.to_string()))
21 .unwrap_or(false),
22 s => s,
23 };
24 Command::new("cargo")
25 .args(["tauri", "-V"])
26 .output()
27 .map(check)
28 .or_else(|_| Command::new("tauri").arg("-V").output().map(check))
29 .unwrap_or(false)
30}
31
32fn is_wasm32_installed() -> bool {
33 Command::new("rustup")
34 .args(["target", "list", "--installed"])
35 .output()
36 .map(|o| {
37 let s = String::from_utf8_lossy(&o.stdout);
38 s.contains("wasm32-unknown-unknown")
39 })
40 .unwrap_or(false)
41}
42
43#[cfg(windows)]
44fn is_webview2_installed() -> bool {
45 let powershell_path = std::env::var("SYSTEMROOT").map_or_else(
46 |_| "powershell.exe".to_string(),
47 |p| format!("{p}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"),
48 );
49 let output = Command::new(&powershell_path)
51 .args(["-NoProfile", "-Command"])
52 .arg("Get-ItemProperty -Path 'HKLM:\\SOFTWARE\\WOW6432Node\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}' | ForEach-Object {$_.pv}")
53 .output().map(|o|o.status.success());
54 if let Ok(o) = output {
55 if o {
56 return true;
57 }
58 }
59 let output = Command::new(&powershell_path)
61 .args(["-NoProfile", "-Command"])
62 .arg("Get-ItemProperty -Path 'HKLM:\\SOFTWARE\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}' | ForEach-Object {$_.pv}")
63 .output().map(|o|o.status.success());
64 if let Ok(o) = output {
65 if o {
66 return true;
67 }
68 }
69 let output = Command::new(&powershell_path)
71 .args(["-NoProfile", "-Command"])
72 .arg("Get-ItemProperty -Path 'HKCU:\\SOFTWARE\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}' | ForEach-Object {$_.pv}")
73 .output().map(|o|o.status.success());
74 if let Ok(o) = output {
75 if o {
76 return true;
77 }
78 }
79
80 false
81}
82
83#[cfg(any(
84 target_os = "linux",
85 target_os = "dragonfly",
86 target_os = "freebsd",
87 target_os = "openbsd",
88 target_os = "netbsd"
89))]
90fn is_webkit2gtk_installed(tauri_version: TauriVersion) -> bool {
91 Command::new("pkg-config")
92 .arg(match tauri_version {
93 TauriVersion::V1 => "webkit2gtk-4.0",
94 TauriVersion::V2 => "webkit2gtk-4.1",
95 })
96 .output()
97 .map(|o| o.status.success())
98 .unwrap_or(false)
99}
100
101#[cfg(any(
102 target_os = "linux",
103 target_os = "dragonfly",
104 target_os = "freebsd",
105 target_os = "openbsd",
106 target_os = "netbsd"
107))]
108fn is_rsvg2_installed() -> bool {
109 Command::new("pkg-config")
110 .arg("librsvg-2.0")
111 .output()
112 .map(|o| o.status.success())
113 .unwrap_or(false)
114}
115
116#[cfg(target_os = "macos")]
117fn is_xcode_command_line_tools_installed() -> bool {
118 Command::new("xcode-select")
119 .arg("-p")
120 .output()
121 .map(|o| o.status.success())
122 .unwrap_or(false)
123}
124
125struct Dep<'a> {
126 name: &'a str,
127 instruction: String,
128 exists: &'a dyn Fn() -> bool,
129 skip: bool,
130}
131
132pub fn print_missing_deps(
134 pkg_manager: PackageManager,
135 template: Template,
136 tauri_version: TauriVersion,
137) -> bool {
138 let rustc_installed = is_cli_installed("rustc", "-V");
139 let cargo_installed = is_cli_installed("cargo", "-V");
140
141 #[cfg(any(
142 target_os = "linux",
143 target_os = "dragonfly",
144 target_os = "freebsd",
145 target_os = "openbsd",
146 target_os = "netbsd"
147 ))]
148 let (webkit2gtk_installed, rsvg2_installed) =
149 (is_webkit2gtk_installed(tauri_version), is_rsvg2_installed());
150
151 let deps: &[Dep<'_>] = &[
152 Dep {
153 name: "Rust",
154 instruction: format!("Visit {BLUE}{BOLD}https://www.rust-lang.org/learn/get-started#installing-rust{RESET}"),
155 exists: &|| rustc_installed && cargo_installed,
156 skip: rustc_installed || cargo_installed,
157 },
158 Dep {
159 name: "rustc",
160 instruction: format!("Visit {BLUE}{BOLD}https://www.rust-lang.org/learn/get-started#installing-rust{RESET} to install Rust"),
161 exists: &|| rustc_installed,
162 skip: !rustc_installed && !cargo_installed,
163 },
164 Dep {
165 name: "Cargo",
166 instruction: format!("Visit {BLUE}{BOLD}https://www.rust-lang.org/learn/get-started#installing-rust{RESET} to install Rust"),
167 exists: &|| cargo_installed,
168 skip: !rustc_installed && !cargo_installed,
169 },
170 Dep {
171 name: "Tauri CLI",
172 instruction: match tauri_version {
173 TauriVersion::V1 => format!("Run `{BLUE}{BOLD}cargo install tauri-cli --version '^1.0.0' --locked{RESET}`"),
174 TauriVersion::V2 => format!("Run `{BLUE}{BOLD}cargo install tauri-cli --version '^2.0.0' --locked{RESET}`"),
175 },
176 exists: &|| is_tauri_cli_installed(tauri_version),
177 skip: pkg_manager.is_node() || !template.needs_tauri_cli(),
178 },
179 Dep {
180 name: "Trunk",
181 instruction: format!("Run `{BLUE}{BOLD}cargo install trunk --locked{RESET}`"),
182 exists: &|| is_cli_installed("trunk", "-V"),
183 skip: pkg_manager.is_node() || !template.needs_trunk(),
184 },
185 Dep {
186 name: "Dioxus CLI",
187 instruction: format!("Run `{BLUE}{BOLD}cargo install dioxus-cli --locked{RESET}`"),
188 exists: &|| is_cli_installed("dx", "-V"),
189 skip: pkg_manager.is_node() || !template.needs_dioxus_cli(),
190 },
191 Dep {
192 name: "wasm32 target",
193 instruction: format!("Run `{BLUE}{BOLD}rustup target add wasm32-unknown-unknown{RESET}`"),
194 exists: &is_wasm32_installed,
195 skip: pkg_manager.is_node() || !template.needs_wasm32_target(),
196 },
197 Dep {
198 name: "Node.js",
199 instruction: format!("Visit {BLUE}{BOLD}https://nodejs.org/{RESET}"),
200 exists: &|| is_cli_installed("node", "-v"),
201 skip: !pkg_manager.is_node(),
202 },
203 Dep {
204 name: "Deno",
205 instruction: format!("Visit {BLUE}{BOLD}https://deno.land/{RESET}"),
206 exists: &|| is_cli_installed("deno", "-v"),
207 skip: pkg_manager != PackageManager::Deno,
208 },
209 Dep {
210 name: "Bun",
211 instruction: format!("Visit {BLUE}{BOLD}https://bun.sh/{RESET}"),
212 exists: &|| is_cli_installed("bun", "-v"),
213 skip: pkg_manager != PackageManager::Bun,
214 },
215 #[cfg(windows)]
216 Dep {
217 name: "Webview2",
218 instruction: format!("Visit {BLUE}{BOLD}https://go.microsoft.com/fwlink/p/?LinkId=2124703{RESET}"),
219 exists: &is_webview2_installed,
220 skip: false,
221 },
222 #[cfg(any(
223 target_os = "linux",
224 target_os = "dragonfly",
225 target_os = "freebsd",
226 target_os = "openbsd",
227 target_os = "netbsd"
228 ))]
229 Dep {
230 name: "webkit2gtk & rsvg2",
231 instruction: format!("Visit {BLUE}{BOLD}{}{RESET}", match tauri_version {
232 TauriVersion::V1 => "https://v1.tauri.app/v1/guides/getting-started/prerequisites#setting-up-linux",
233 TauriVersion::V2 => "https://tauri.app/guides/prerequisites/#linux",
234 }),
235 exists: &|| webkit2gtk_installed && rsvg2_installed,
236 skip: webkit2gtk_installed || rsvg2_installed,
237 },
238 #[cfg(any(
239 target_os = "linux",
240 target_os = "dragonfly",
241 target_os = "freebsd",
242 target_os = "openbsd",
243 target_os = "netbsd"
244 ))]
245 Dep {
246 name: "webkit2gtk",
247 instruction: format!("Visit {BLUE}{BOLD}{}{RESET}", match tauri_version {
248 TauriVersion::V1 => "https://v1.tauri.app/v1/guides/getting-started/prerequisites#setting-up-linux",
249 TauriVersion::V2 => "https://tauri.app/guides/prerequisites/#linux",
250 }),
251 exists: &|| webkit2gtk_installed,
252 skip: !rsvg2_installed && !webkit2gtk_installed,
253 },
254 #[cfg(any(
255 target_os = "linux",
256 target_os = "dragonfly",
257 target_os = "freebsd",
258 target_os = "openbsd",
259 target_os = "netbsd"
260 ))]
261 Dep {
262 name: "rsvg2",
263 instruction: format!("Visit {BLUE}{BOLD}{}{RESET}", match tauri_version {
264 TauriVersion::V1 => "https://v1.tauri.app/v1/guides/getting-started/prerequisites#setting-up-linux",
265 TauriVersion::V2 => "https://tauri.app/guides/prerequisites/#linux",
266 }),
267 exists: &|| rsvg2_installed,
268 skip: !rsvg2_installed && !webkit2gtk_installed,
269 },
270 #[cfg(target_os = "macos")]
271 Dep {
272 name: "Xcode Command Line Tools",
273 instruction: format!("Run `{BLUE}{BOLD}xcode-select --install{RESET}`"),
274 exists: &is_xcode_command_line_tools_installed,
275 skip: false,
276 },
277 Dep {
278 name: ".NET",
279 instruction: format!("Visit {BLUE}{BOLD}https://dotnet.microsoft.com/download{RESET}"),
280 exists: &|| is_cli_installed("dotnet", "--info"),
281 skip: !template.needs_dotnet() || pkg_manager.is_node(),
282 }
283 ];
284
285 let missing_deps: Vec<(&str, &str)> = deps
286 .iter()
287 .filter(|dep| !dep.skip && !(dep.exists)())
288 .map(|dep| (dep.name, dep.instruction.as_str()))
289 .collect();
290
291 let (largest_first_cell, largest_second_cell) =
292 missing_deps
293 .iter()
294 .fold((0, 0), |(mut prev_f, mut prev_s), (f, s)| {
295 let f_len = f.len();
296 if f_len > prev_f {
297 prev_f = f_len;
298 }
299
300 let s_len = remove_colors(s).len();
301 if s_len > prev_s {
302 prev_s = s_len;
303 }
304
305 (prev_f, prev_s)
306 });
307
308 if !missing_deps.is_empty() {
309 println!("\n\nYour system is {YELLOW}missing dependencies{RESET} (or they do not exist in {YELLOW}$PATH{RESET}):");
310 for (index, (name, instruction)) in missing_deps.iter().enumerate() {
311 if index == 0 {
312 println!(
313 "╭{}┬{}╮",
314 "─".repeat(largest_first_cell + 2),
315 "─".repeat(largest_second_cell + 2)
316 );
317 } else {
318 println!(
319 "├{}┼{}┤",
320 "─".repeat(largest_first_cell + 2),
321 "─".repeat(largest_second_cell + 2)
322 );
323 }
324 println!(
325 "│ {YELLOW}{name}{RESET}{} │ {instruction}{} │",
326 " ".repeat(largest_first_cell - name.len()),
327 " ".repeat(largest_second_cell - remove_colors(instruction).len()),
328 );
329 }
330 println!(
331 "╰{}┴{}╯",
332 "─".repeat(largest_first_cell + 2),
333 "─".repeat(largest_second_cell + 2),
334 );
335 println!();
336
337 true
338 } else {
339 false
340 }
341}