cargo_packager_resource_resolver/
lib.rs

1// Copyright 2023-2023 CrabNebula Ltd.
2// SPDX-License-Identifier: Apache-2.0
3// SPDX-License-Identifier: MIT
4
5//! # cargo-packager-resource-resolver
6//!
7//! Resource resolver for apps that were packaged by [`cargo-packager`](https://docs.rs/cargo-packager).
8//!
9//! It resolves the root path which contains resources, which was set using the `resources`
10//! field of [cargo packager configuration](https://docs.rs/cargo-packager/latest/cargo_packager/config/struct.Config.html).
11//!
12//! ## Get the resource path
13//!
14//! ```
15//! use cargo_packager_resource_resolver::{resources_dir, PackageFormat};
16//!
17//! let resource_path = resources_dir(PackageFormat::Nsis).unwrap();
18//! ```
19//! ## Automatically detect formats
20//!
21//! <div class="warning">
22//!
23//! This feature is only available for apps that were built with cargo packager. So the node js binding will not work.
24//!
25//! </div>
26//!
27//! 1. Make sure to use the `before_each_package_command` field of [cargo packager configuration](https://docs.rs/cargo-packager/latest/cargo_packager/config/struct.Config.html) to build your app (this will not work with the `before_packaging_command` field).
28//! 2. Active the feature `auto-detect-format`.
29//!
30//! ```rs
31//! use cargo_packager_resource_resolver::{resources_dir, current_format};
32//!
33//! let resource_path = resources_dir(current_format()).unwrap();
34//! ```
35//!
36use std::path::PathBuf;
37
38use cargo_packager_utils::current_exe::current_exe;
39pub use cargo_packager_utils::PackageFormat;
40use error::Result;
41
42mod error;
43
44pub use error::Error;
45
46/// Get the current package format.
47/// Can only be used if the app was build with cargo-packager
48/// and when the `before-each-package-command` Cargo feature is enabled.
49#[cfg(feature = "auto-detect-format")]
50pub fn current_format() -> crate::Result<PackageFormat> {
51    // sync with PackageFormat::short_name function of packager crate
52    // maybe having a special crate for the Config struct,
53    // that both packager and resource-resolver could be a
54    // better alternative
55    if cfg!(CARGO_PACKAGER_FORMAT = "app") {
56        Ok(PackageFormat::App)
57    } else if cfg!(CARGO_PACKAGER_FORMAT = "dmg") {
58        Ok(PackageFormat::Dmg)
59    } else if cfg!(CARGO_PACKAGER_FORMAT = "wix") {
60        Ok(PackageFormat::Wix)
61    } else if cfg!(CARGO_PACKAGER_FORMAT = "nsis") {
62        Ok(PackageFormat::Nsis)
63    } else if cfg!(CARGO_PACKAGER_FORMAT = "deb") {
64        Ok(PackageFormat::Deb)
65    } else if cfg!(CARGO_PACKAGER_FORMAT = "appimage") {
66        Ok(PackageFormat::AppImage)
67    } else if cfg!(CARGO_PACKAGER_FORMAT = "pacman") {
68        Ok(PackageFormat::Pacman)
69    } else {
70        Err(Error::UnkownPackageFormat)
71    }
72}
73
74/// Retrieve the resource path of your app, packaged with cargo packager.
75///
76/// ## Example
77///
78/// ```
79/// use cargo_packager_resource_resolver::{resources_dir, PackageFormat};
80///
81/// let resource_path = resources_dir(PackageFormat::Nsis).unwrap();
82/// ```
83pub fn resources_dir(package_format: PackageFormat) -> Result<PathBuf> {
84    match package_format {
85        PackageFormat::App | PackageFormat::Dmg => {
86            let exe = current_exe()?;
87            let exe_dir = exe
88                .parent()
89                .ok_or_else(|| Error::ParentNotFound(exe.clone()))?;
90            Ok(exe_dir.join("../Resources"))
91        }
92        PackageFormat::Wix | PackageFormat::Nsis => {
93            let exe = current_exe()?;
94            let exe_dir = exe
95                .parent()
96                .ok_or_else(|| Error::ParentNotFound(exe.clone()))?;
97            Ok(exe_dir.to_path_buf())
98        }
99        PackageFormat::Deb | PackageFormat::Pacman => {
100            let exe = current_exe()?;
101            let exe_name = exe.file_name().unwrap().to_string_lossy();
102
103            let path = format!("/usr/lib/{}/", exe_name);
104            Ok(PathBuf::from(path))
105        }
106
107        PackageFormat::AppImage => {
108            let appdir = std::env::var_os("APPDIR").ok_or(Error::AppDirNotFound)?;
109
110            // validate that we're actually running on an AppImage
111            // an AppImage is mounted to `/$TEMPDIR/.mount_${appPrefix}${hash}`
112            // see https://github.com/AppImage/AppImageKit/blob/1681fd84dbe09c7d9b22e13cdb16ea601aa0ec47/src/runtime.c#L501
113            // note that it is safe to use `std::env::current_exe` here since we just loaded an AppImage.
114            let is_temp = std::env::current_exe()
115                .map(|p| {
116                    p.display()
117                        .to_string()
118                        .starts_with(&format!("{}/.mount_", std::env::temp_dir().display()))
119                })
120                .unwrap_or(true);
121
122            if !is_temp {
123                return Err(Error::InvalidAppImage);
124            }
125
126            let appdir: &std::path::Path = appdir.as_ref();
127
128            let exe = current_exe()?;
129            let exe_name = exe.file_name().unwrap().to_string_lossy();
130
131            Ok(PathBuf::from(format!(
132                "{}/usr/lib/{}",
133                appdir.display(),
134                exe_name
135            )))
136        }
137        _ => Err(Error::UnsupportedPackageFormat),
138    }
139}