create_tauri_app/
deps.rs

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    // check 64bit per-system installation
50    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    // check 32bit per-system installation
60    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    // check per-user installation
70    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
132/// Print missing deps in a table and returns whether there was any missing deps.
133pub 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}