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 {
195 return Err(crate::Error::Architecture);
196 };
197
198 let os = if cfg!(target_os = "linux") {
199 "unknown-linux"
200 } else if cfg!(target_os = "macos") {
201 "apple-darwin"
202 } else if cfg!(target_os = "windows") {
203 "pc-windows"
204 } else if cfg!(target_os = "freebsd") {
205 "unknown-freebsd"
206 } else {
207 return Err(crate::Error::Os);
208 };
209
210 let os = if cfg!(target_os = "macos") || cfg!(target_os = "freebsd") {
211 String::from(os)
212 } else {
213 let env = if cfg!(target_env = "gnu") {
214 "gnu"
215 } else if cfg!(target_env = "musl") {
216 "musl"
217 } else if cfg!(target_env = "msvc") {
218 "msvc"
219 } else {
220 return Err(crate::Error::Environment);
221 };
222
223 format!("{os}-{env}")
224 };
225
226 Ok(format!("{arch}-{os}"))
227}
228
229#[cfg(all(not(test), not(target_os = "android")))]
230fn is_cargo_output_directory(path: &std::path::Path) -> bool {
231 path.join(".cargo-lock").exists()
232}
233
234#[cfg(test)]
235const CARGO_OUTPUT_DIRECTORIES: &[&str] = &["debug", "release", "custom-profile"];
236
237#[cfg(test)]
238fn is_cargo_output_directory(path: &std::path::Path) -> bool {
239 let last_component = path
240 .components()
241 .last()
242 .unwrap()
243 .as_os_str()
244 .to_str()
245 .unwrap();
246 CARGO_OUTPUT_DIRECTORIES
247 .iter()
248 .any(|dirname| &last_component == dirname)
249}
250
251pub fn resource_dir(package_info: &PackageInfo, env: &Env) -> crate::Result<PathBuf> {
268 #[cfg(target_os = "android")]
269 return resource_dir_android(package_info, env);
270 #[cfg(not(target_os = "android"))]
271 {
272 let exe = current_exe()?;
273 resource_dir_from(exe, package_info, env)
274 }
275}
276
277#[cfg(target_os = "android")]
278fn resource_dir_android(_package_info: &PackageInfo, _env: &Env) -> crate::Result<PathBuf> {
279 Ok(PathBuf::from(ANDROID_ASSET_PROTOCOL_URI_PREFIX))
280}
281
282#[cfg(not(target_os = "android"))]
283#[allow(unused_variables)]
284fn resource_dir_from<P: AsRef<std::path::Path>>(
285 exe: P,
286 package_info: &PackageInfo,
287 env: &Env,
288) -> crate::Result<PathBuf> {
289 let exe_dir = exe.as_ref().parent().expect("failed to get exe directory");
290 let curr_dir = exe_dir.display().to_string();
291
292 let parts: Vec<&str> = curr_dir.split(std::path::MAIN_SEPARATOR).collect();
293 let len = parts.len();
294
295 if cfg!(target_os = "windows")
301 || ((len >= 2 && parts[len - 2] == "target") || (len >= 3 && parts[len - 3] == "target"))
302 && is_cargo_output_directory(exe_dir)
303 {
304 return Ok(exe_dir.to_path_buf());
305 }
306
307 #[allow(unused_mut, unused_assignments)]
308 let mut res = Err(crate::Error::UnsupportedPlatform);
309
310 #[cfg(target_os = "linux")]
311 {
312 res = if let Ok(bundle_dir) = exe_dir
314 .join(format!("../lib/{}", package_info.name))
315 .canonicalize()
316 {
317 Ok(bundle_dir)
318 } else if let Some(appdir) = &env.appdir {
319 let appdir: &std::path::Path = appdir.as_ref();
320 Ok(PathBuf::from(format!(
321 "{}/usr/lib/{}",
322 appdir.display(),
323 package_info.name
324 )))
325 } else {
326 Ok(PathBuf::from(format!("/usr/lib/{}", package_info.name)))
328 };
329 }
330
331 #[cfg(target_os = "macos")]
332 {
333 res = exe_dir
334 .join("../Resources")
335 .canonicalize()
336 .map_err(Into::into);
337 }
338
339 #[cfg(target_os = "ios")]
340 {
341 res = exe_dir.join("assets").canonicalize().map_err(Into::into);
342 }
343
344 res
345}
346
347#[cfg(feature = "build")]
348mod build {
349 use proc_macro2::TokenStream;
350 use quote::{quote, ToTokens, TokenStreamExt};
351
352 use super::*;
353
354 impl ToTokens for Target {
355 fn to_tokens(&self, tokens: &mut TokenStream) {
356 let prefix = quote! { ::tauri::utils::platform::Target };
357
358 tokens.append_all(match self {
359 Self::MacOS => quote! { #prefix::MacOS },
360 Self::Linux => quote! { #prefix::Linux },
361 Self::Windows => quote! { #prefix::Windows },
362 Self::Android => quote! { #prefix::Android },
363 Self::Ios => quote! { #prefix::Ios },
364 });
365 }
366 }
367}
368
369#[cfg(test)]
370mod tests {
371 use std::path::PathBuf;
372
373 use crate::{Env, PackageInfo};
374
375 #[test]
376 fn resolve_resource_dir() {
377 let package_info = PackageInfo {
378 name: "MyApp".into(),
379 version: "1.0.0".parse().unwrap(),
380 authors: "",
381 description: "",
382 crate_name: "my-app",
383 };
384 let env = Env::default();
385
386 let path = PathBuf::from("/path/to/target/aarch64-apple-darwin/debug/app");
387 let resource_dir = super::resource_dir_from(&path, &package_info, &env).unwrap();
388 assert_eq!(resource_dir, path.parent().unwrap());
389
390 let path = PathBuf::from("/path/to/target/custom-profile/app");
391 let resource_dir = super::resource_dir_from(&path, &package_info, &env).unwrap();
392 assert_eq!(resource_dir, path.parent().unwrap());
393
394 let path = PathBuf::from("/path/to/target/release/app");
395 let resource_dir = super::resource_dir_from(&path, &package_info, &env).unwrap();
396 assert_eq!(resource_dir, path.parent().unwrap());
397
398 let path = PathBuf::from("/path/to/target/unknown-profile/app");
399 #[allow(clippy::needless_borrows_for_generic_args)]
400 let resource_dir = super::resource_dir_from(&path, &package_info, &env);
401 #[cfg(target_os = "macos")]
402 assert!(resource_dir.is_err());
403 #[cfg(target_os = "linux")]
404 assert_eq!(resource_dir.unwrap(), PathBuf::from("/usr/lib/my-app"));
405 #[cfg(windows)]
406 assert_eq!(resource_dir.unwrap(), path.parent().unwrap());
407 }
408}