1use std::{fmt::Display, path::PathBuf};
8
9use serde::{Deserialize, Serialize};
10
11use crate::{Env, PackageInfo};
12
13mod starting_binary;
14
15#[cfg(target_os = "android")]
20pub const ANDROID_ASSET_PROTOCOL_URI_PREFIX: &str = "asset://localhost/";
21
22#[derive(PartialEq, Eq, Copy, Debug, Clone, Serialize, Deserialize)]
24#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
25#[serde(rename_all = "camelCase")]
26#[non_exhaustive]
27pub enum Target {
28 #[serde(rename = "macOS")]
30 MacOS,
31 Windows,
33 Linux,
35 Android,
37 #[serde(rename = "iOS")]
39 Ios,
40}
41
42impl Display for Target {
43 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44 write!(
45 f,
46 "{}",
47 match self {
48 Self::MacOS => "macOS",
49 Self::Windows => "windows",
50 Self::Linux => "linux",
51 Self::Android => "android",
52 Self::Ios => "iOS",
53 }
54 )
55 }
56}
57
58impl Target {
59 pub fn from_triple(target: &str) -> Self {
61 if target.contains("darwin") {
62 Self::MacOS
63 } else if target.contains("windows") {
64 Self::Windows
65 } else if target.contains("android") {
66 Self::Android
67 } else if target.contains("ios") {
68 Self::Ios
69 } else {
70 Self::Linux
71 }
72 }
73
74 pub fn current() -> Self {
76 if cfg!(target_os = "macos") {
77 Self::MacOS
78 } else if cfg!(target_os = "windows") {
79 Self::Windows
80 } else if cfg!(target_os = "ios") {
81 Self::Ios
82 } else if cfg!(target_os = "android") {
83 Self::Android
84 } else {
85 Self::Linux
86 }
87 }
88
89 pub fn is_mobile(&self) -> bool {
91 matches!(self, Target::Android | Target::Ios)
92 }
93
94 pub fn is_desktop(&self) -> bool {
96 !self.is_mobile()
97 }
98}
99
100pub fn current_exe() -> std::io::Result<PathBuf> {
173 self::starting_binary::STARTING_BINARY.cloned()
174}
175
176pub fn target_triple() -> crate::Result<String> {
186 let arch = if cfg!(target_arch = "x86") {
187 "i686"
188 } else if cfg!(target_arch = "x86_64") {
189 "x86_64"
190 } else if cfg!(target_arch = "arm") {
191 "armv7"
192 } else if cfg!(target_arch = "aarch64") {
193 "aarch64"
194 } else if cfg!(target_arch = "riscv64") {
195 "riscv64"
196 } else {
197 return Err(crate::Error::Architecture);
198 };
199
200 let os = if cfg!(target_os = "linux") {
201 "unknown-linux"
202 } else if cfg!(target_os = "macos") {
203 "apple-darwin"
204 } else if cfg!(target_os = "windows") {
205 "pc-windows"
206 } else if cfg!(target_os = "freebsd") {
207 "unknown-freebsd"
208 } else {
209 return Err(crate::Error::Os);
210 };
211
212 let os = if cfg!(target_os = "macos") || cfg!(target_os = "freebsd") {
213 String::from(os)
214 } else {
215 let env = if cfg!(target_env = "gnu") {
216 "gnu"
217 } else if cfg!(target_env = "musl") {
218 "musl"
219 } else if cfg!(target_env = "msvc") {
220 "msvc"
221 } else {
222 return Err(crate::Error::Environment);
223 };
224
225 format!("{os}-{env}")
226 };
227
228 Ok(format!("{arch}-{os}"))
229}
230
231#[cfg(all(not(test), not(target_os = "android")))]
232fn is_cargo_output_directory(path: &std::path::Path) -> bool {
233 path.join(".cargo-lock").exists()
234}
235
236#[cfg(test)]
237const CARGO_OUTPUT_DIRECTORIES: &[&str] = &["debug", "release", "custom-profile"];
238
239#[cfg(test)]
240fn is_cargo_output_directory(path: &std::path::Path) -> bool {
241 let last_component = path
242 .components()
243 .last()
244 .unwrap()
245 .as_os_str()
246 .to_str()
247 .unwrap();
248 CARGO_OUTPUT_DIRECTORIES
249 .iter()
250 .any(|dirname| &last_component == dirname)
251}
252
253pub fn resource_dir(package_info: &PackageInfo, env: &Env) -> crate::Result<PathBuf> {
270 #[cfg(target_os = "android")]
271 return resource_dir_android(package_info, env);
272 #[cfg(not(target_os = "android"))]
273 {
274 let exe = current_exe()?;
275 resource_dir_from(exe, package_info, env)
276 }
277}
278
279#[cfg(target_os = "android")]
280fn resource_dir_android(_package_info: &PackageInfo, _env: &Env) -> crate::Result<PathBuf> {
281 Ok(PathBuf::from(ANDROID_ASSET_PROTOCOL_URI_PREFIX))
282}
283
284#[cfg(not(target_os = "android"))]
285#[allow(unused_variables)]
286fn resource_dir_from<P: AsRef<std::path::Path>>(
287 exe: P,
288 package_info: &PackageInfo,
289 env: &Env,
290) -> crate::Result<PathBuf> {
291 let exe_dir = exe.as_ref().parent().expect("failed to get exe directory");
292 let curr_dir = exe_dir.display().to_string();
293
294 let parts: Vec<&str> = curr_dir.split(std::path::MAIN_SEPARATOR).collect();
295 let len = parts.len();
296
297 if cfg!(target_os = "windows")
303 || ((len >= 2 && parts[len - 2] == "target") || (len >= 3 && parts[len - 3] == "target"))
304 && is_cargo_output_directory(exe_dir)
305 {
306 return Ok(exe_dir.to_path_buf());
307 }
308
309 #[allow(unused_mut, unused_assignments)]
310 let mut res = Err(crate::Error::UnsupportedPlatform);
311
312 #[cfg(target_os = "linux")]
313 {
314 res = if let Ok(bundle_dir) = exe_dir
316 .join(format!("../lib/{}", package_info.name))
317 .canonicalize()
318 {
319 Ok(bundle_dir)
320 } else if let Some(appdir) = &env.appdir {
321 let appdir: &std::path::Path = appdir.as_ref();
322 Ok(PathBuf::from(format!(
323 "{}/usr/lib/{}",
324 appdir.display(),
325 package_info.name
326 )))
327 } else {
328 Ok(PathBuf::from(format!("/usr/lib/{}", package_info.name)))
330 };
331 }
332
333 #[cfg(target_os = "macos")]
334 {
335 res = exe_dir
336 .join("../Resources")
337 .canonicalize()
338 .map_err(Into::into);
339 }
340
341 #[cfg(target_os = "ios")]
342 {
343 res = exe_dir.join("assets").canonicalize().map_err(Into::into);
344 }
345
346 res
347}
348
349#[cfg(feature = "build")]
350mod build {
351 use proc_macro2::TokenStream;
352 use quote::{quote, ToTokens, TokenStreamExt};
353
354 use super::*;
355
356 impl ToTokens for Target {
357 fn to_tokens(&self, tokens: &mut TokenStream) {
358 let prefix = quote! { ::tauri::utils::platform::Target };
359
360 tokens.append_all(match self {
361 Self::MacOS => quote! { #prefix::MacOS },
362 Self::Linux => quote! { #prefix::Linux },
363 Self::Windows => quote! { #prefix::Windows },
364 Self::Android => quote! { #prefix::Android },
365 Self::Ios => quote! { #prefix::Ios },
366 });
367 }
368 }
369}
370
371#[cfg(test)]
372mod tests {
373 use std::path::PathBuf;
374
375 use crate::{Env, PackageInfo};
376
377 #[test]
378 fn resolve_resource_dir() {
379 let package_info = PackageInfo {
380 name: "MyApp".into(),
381 version: "1.0.0".parse().unwrap(),
382 authors: "",
383 description: "",
384 crate_name: "my-app",
385 };
386 let env = Env::default();
387
388 let path = PathBuf::from("/path/to/target/aarch64-apple-darwin/debug/app");
389 let resource_dir = super::resource_dir_from(&path, &package_info, &env).unwrap();
390 assert_eq!(resource_dir, path.parent().unwrap());
391
392 let path = PathBuf::from("/path/to/target/custom-profile/app");
393 let resource_dir = super::resource_dir_from(&path, &package_info, &env).unwrap();
394 assert_eq!(resource_dir, path.parent().unwrap());
395
396 let path = PathBuf::from("/path/to/target/release/app");
397 let resource_dir = super::resource_dir_from(&path, &package_info, &env).unwrap();
398 assert_eq!(resource_dir, path.parent().unwrap());
399
400 let path = PathBuf::from("/path/to/target/unknown-profile/app");
401 #[allow(clippy::needless_borrows_for_generic_args)]
402 let resource_dir = super::resource_dir_from(&path, &package_info, &env);
403 #[cfg(target_os = "macos")]
404 assert!(resource_dir.is_err());
405 #[cfg(target_os = "linux")]
406 assert_eq!(resource_dir.unwrap(), PathBuf::from("/usr/lib/my-app"));
407 #[cfg(windows)]
408 assert_eq!(resource_dir.unwrap(), path.parent().unwrap());
409 }
410}