1use std::{fmt::Display, path::PathBuf};
8
9use serde::{Deserialize, Serialize};
10
11use crate::{config::BundleType, 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 .next_back()
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> {
269 #[cfg(target_os = "android")]
270 return resource_dir_android(package_info, env);
271 #[cfg(not(target_os = "android"))]
272 {
273 let exe = current_exe()?;
274 resource_dir_from(exe, package_info, env)
275 }
276}
277
278#[cfg(target_os = "android")]
279fn resource_dir_android(_package_info: &PackageInfo, _env: &Env) -> crate::Result<PathBuf> {
280 Ok(PathBuf::from(ANDROID_ASSET_PROTOCOL_URI_PREFIX))
281}
282
283#[cfg(not(target_os = "android"))]
284#[allow(unused_variables)]
285fn resource_dir_from<P: AsRef<std::path::Path>>(
286 exe: P,
287 package_info: &PackageInfo,
288 env: &Env,
289) -> crate::Result<PathBuf> {
290 let exe_dir = exe.as_ref().parent().expect("failed to get exe directory");
291 let curr_dir = exe_dir.display().to_string();
292
293 let parts: Vec<&str> = curr_dir.split(std::path::MAIN_SEPARATOR).collect();
294 let len = parts.len();
295
296 if cfg!(target_os = "windows")
302 || ((len >= 2 && parts[len - 2] == "target") || (len >= 3 && parts[len - 3] == "target"))
303 && is_cargo_output_directory(exe_dir)
304 {
305 return Ok(exe_dir.to_path_buf());
306 }
307
308 #[allow(unused_mut, unused_assignments)]
309 let mut res = Err(crate::Error::UnsupportedPlatform);
310
311 #[cfg(target_os = "linux")]
312 {
313 res = if let Ok(bundle_dir) = exe_dir
315 .join(format!("../lib/{}", package_info.name))
316 .canonicalize()
317 {
318 Ok(bundle_dir)
319 } else if let Some(appdir) = &env.appdir {
320 let appdir: &std::path::Path = appdir.as_ref();
321 Ok(PathBuf::from(format!(
322 "{}/usr/lib/{}",
323 appdir.display(),
324 package_info.name
325 )))
326 } else {
327 Ok(PathBuf::from(format!("/usr/lib/{}", package_info.name)))
329 };
330 }
331
332 #[cfg(target_os = "macos")]
333 {
334 res = exe_dir
335 .join("../Resources")
336 .canonicalize()
337 .map_err(Into::into);
338 }
339
340 #[cfg(target_os = "ios")]
341 {
342 res = exe_dir.join("assets").canonicalize().map_err(Into::into);
343 }
344
345 res
346}
347
348#[used]
351#[no_mangle]
352#[cfg_attr(not(target_vendor = "apple"), link_section = ".taubndl")]
353#[cfg_attr(target_vendor = "apple", link_section = "__DATA,taubndl")]
354static mut __TAURI_BUNDLE_TYPE: &str = "UNK";
357
358pub fn bundle_type() -> Option<BundleType> {
361 unsafe {
362 match __TAURI_BUNDLE_TYPE {
363 "DEB" => Some(BundleType::Deb),
364 "RPM" => Some(BundleType::Rpm),
365 "APP" => Some(BundleType::AppImage),
366 "MSI" => Some(BundleType::Msi),
367 "NSS" => Some(BundleType::Nsis),
368 _ => {
369 if cfg!(target_os = "macos") {
370 Some(BundleType::App)
371 } else {
372 None
373 }
374 }
375 }
376 }
377}
378
379#[cfg(feature = "build")]
380mod build {
381 use proc_macro2::TokenStream;
382 use quote::{quote, ToTokens, TokenStreamExt};
383
384 use super::*;
385
386 impl ToTokens for Target {
387 fn to_tokens(&self, tokens: &mut TokenStream) {
388 let prefix = quote! { ::tauri::utils::platform::Target };
389
390 tokens.append_all(match self {
391 Self::MacOS => quote! { #prefix::MacOS },
392 Self::Linux => quote! { #prefix::Linux },
393 Self::Windows => quote! { #prefix::Windows },
394 Self::Android => quote! { #prefix::Android },
395 Self::Ios => quote! { #prefix::Ios },
396 });
397 }
398 }
399}
400
401#[cfg(test)]
402mod tests {
403 use std::path::PathBuf;
404
405 use crate::{Env, PackageInfo};
406
407 #[test]
408 fn resolve_resource_dir() {
409 let package_info = PackageInfo {
410 name: "MyApp".into(),
411 version: "1.0.0".parse().unwrap(),
412 authors: "",
413 description: "",
414 crate_name: "my-app",
415 };
416 let env = Env::default();
417
418 let path = PathBuf::from("/path/to/target/aarch64-apple-darwin/debug/app");
419 let resource_dir = super::resource_dir_from(&path, &package_info, &env).unwrap();
420 assert_eq!(resource_dir, path.parent().unwrap());
421
422 let path = PathBuf::from("/path/to/target/custom-profile/app");
423 let resource_dir = super::resource_dir_from(&path, &package_info, &env).unwrap();
424 assert_eq!(resource_dir, path.parent().unwrap());
425
426 let path = PathBuf::from("/path/to/target/release/app");
427 let resource_dir = super::resource_dir_from(&path, &package_info, &env).unwrap();
428 assert_eq!(resource_dir, path.parent().unwrap());
429
430 let path = PathBuf::from("/path/to/target/unknown-profile/app");
431 #[allow(clippy::needless_borrows_for_generic_args)]
432 let resource_dir = super::resource_dir_from(&path, &package_info, &env);
433 #[cfg(target_os = "macos")]
434 assert!(resource_dir.is_err());
435 #[cfg(target_os = "linux")]
436 assert_eq!(resource_dir.unwrap(), PathBuf::from("/usr/lib/MyApp"));
437 #[cfg(windows)]
438 assert_eq!(resource_dir.unwrap(), path.parent().unwrap());
439 }
440}