use super::category::AppCategory;
use crate::bundle::{common, platform::target_triple};
use std::{
collections::HashMap,
path::{Path, PathBuf},
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PackageType {
MacOsBundle,
IosBundle,
#[cfg(target_os = "windows")]
WindowsMsi,
Deb,
Rpm,
AppImage,
Dmg,
Updater,
}
impl PackageType {
pub fn from_short_name(name: &str) -> Option<PackageType> {
match name {
"deb" => Some(PackageType::Deb),
"ios" => Some(PackageType::IosBundle),
#[cfg(target_os = "windows")]
"msi" => Some(PackageType::WindowsMsi),
"app" => Some(PackageType::MacOsBundle),
"rpm" => Some(PackageType::Rpm),
"appimage" => Some(PackageType::AppImage),
"dmg" => Some(PackageType::Dmg),
"updater" => Some(PackageType::Updater),
_ => None,
}
}
#[allow(clippy::trivially_copy_pass_by_ref)]
pub fn short_name(&self) -> &'static str {
match *self {
PackageType::Deb => "deb",
PackageType::IosBundle => "ios",
#[cfg(target_os = "windows")]
PackageType::WindowsMsi => "msi",
PackageType::MacOsBundle => "app",
PackageType::Rpm => "rpm",
PackageType::AppImage => "appimage",
PackageType::Dmg => "dmg",
PackageType::Updater => "updater",
}
}
pub fn all() -> &'static [PackageType] {
ALL_PACKAGE_TYPES
}
}
const ALL_PACKAGE_TYPES: &[PackageType] = &[
PackageType::Deb,
PackageType::IosBundle,
#[cfg(target_os = "windows")]
PackageType::WindowsMsi,
PackageType::MacOsBundle,
PackageType::Rpm,
PackageType::Dmg,
PackageType::AppImage,
PackageType::Updater,
];
#[derive(Debug, Clone)]
pub struct PackageSettings {
pub product_name: String,
pub version: String,
pub description: String,
pub homepage: Option<String>,
pub authors: Option<Vec<String>>,
pub default_run: Option<String>,
}
#[derive(Debug, Clone)]
pub struct UpdaterSettings {
pub active: bool,
pub endpoints: Option<Vec<String>>,
pub pubkey: Option<String>,
pub dialog: bool,
}
#[derive(Clone, Debug, Default)]
pub struct DebianSettings {
pub depends: Option<Vec<String>>,
pub use_bootstrapper: Option<bool>,
pub files: HashMap<PathBuf, PathBuf>,
}
#[derive(Clone, Debug, Default)]
pub struct MacOsSettings {
pub frameworks: Option<Vec<String>>,
pub minimum_system_version: Option<String>,
pub license: Option<String>,
pub use_bootstrapper: Option<bool>,
pub exception_domain: Option<String>,
pub signing_identity: Option<String>,
pub entitlements: Option<String>,
}
#[cfg(windows)]
#[derive(Clone, Debug, Default)]
pub struct WixSettings {
pub template: Option<PathBuf>,
pub fragment_paths: Vec<PathBuf>,
pub component_group_refs: Vec<String>,
pub component_refs: Vec<String>,
pub feature_group_refs: Vec<String>,
pub feature_refs: Vec<String>,
pub merge_refs: Vec<String>,
pub skip_webview_install: bool,
}
#[cfg(windows)]
#[derive(Clone, Debug, Default)]
pub struct WindowsSettings {
pub digest_algorithm: Option<String>,
pub certificate_thumbprint: Option<String>,
pub timestamp_url: Option<String>,
pub wix: Option<WixSettings>,
}
#[derive(Clone, Debug, Default)]
pub struct BundleSettings {
pub identifier: Option<String>,
pub icon: Option<Vec<String>>,
pub resources: Option<Vec<String>>,
pub copyright: Option<String>,
pub category: Option<AppCategory>,
pub short_description: Option<String>,
pub long_description: Option<String>,
pub bin: Option<HashMap<String, BundleSettings>>,
pub external_bin: Option<Vec<String>>,
pub deb: DebianSettings,
pub macos: MacOsSettings,
pub updater: Option<UpdaterSettings>,
#[cfg(windows)]
pub windows: WindowsSettings,
}
#[derive(Clone, Debug)]
pub struct BundleBinary {
name: String,
src_path: Option<String>,
main: bool,
}
impl BundleBinary {
pub fn new(name: String, main: bool) -> Self {
Self {
name: if cfg!(windows) {
format!("{}.exe", name)
} else {
name
},
src_path: None,
main,
}
}
pub fn set_src_path(mut self, src_path: Option<String>) -> Self {
self.src_path = src_path;
self
}
pub fn set_main(&mut self, main: bool) {
self.main = main;
}
pub fn set_name(&mut self, name: String) {
self.name = name;
}
pub fn name(&self) -> &str {
&self.name
}
#[cfg(windows)]
pub fn main(&self) -> bool {
self.main
}
pub fn src_path(&self) -> &Option<String> {
&self.src_path
}
}
#[derive(Clone, Debug)]
pub struct Settings {
package: PackageSettings,
package_types: Option<Vec<PackageType>>,
project_out_directory: PathBuf,
is_verbose: bool,
bundle_settings: BundleSettings,
binaries: Vec<BundleBinary>,
}
#[derive(Default)]
pub struct SettingsBuilder {
project_out_directory: Option<PathBuf>,
verbose: bool,
package_types: Option<Vec<PackageType>>,
package_settings: Option<PackageSettings>,
bundle_settings: BundleSettings,
binaries: Vec<BundleBinary>,
}
impl SettingsBuilder {
pub fn new() -> Self {
Default::default()
}
pub fn project_out_directory<P: AsRef<Path>>(mut self, path: P) -> Self {
self
.project_out_directory
.replace(path.as_ref().to_path_buf());
self
}
pub fn verbose(mut self) -> Self {
self.verbose = true;
self
}
pub fn package_types(mut self, package_types: Vec<PackageType>) -> Self {
self.package_types = Some(package_types);
self
}
pub fn package_settings(mut self, settings: PackageSettings) -> Self {
self.package_settings.replace(settings);
self
}
pub fn bundle_settings(mut self, settings: BundleSettings) -> Self {
self.bundle_settings = settings;
self
}
pub fn binaries(mut self, binaries: Vec<BundleBinary>) -> Self {
self.binaries = binaries;
self
}
pub fn build(self) -> crate::Result<Settings> {
let bundle_settings = parse_external_bin(self.bundle_settings)?;
Ok(Settings {
package: self.package_settings.expect("package settings is required"),
package_types: self.package_types,
is_verbose: self.verbose,
project_out_directory: self
.project_out_directory
.expect("out directory is required"),
binaries: self.binaries,
bundle_settings,
})
}
}
impl Settings {
pub fn project_out_directory(&self) -> &Path {
&self.project_out_directory
}
pub fn binary_arch(&self) -> &str {
std::env::consts::ARCH
}
pub fn main_binary_name(&self) -> &str {
self
.binaries
.iter()
.find(|bin| bin.main)
.expect("failed to find main binary")
.name
.as_str()
}
pub fn binary_path(&self, binary: &BundleBinary) -> PathBuf {
let mut path = self.project_out_directory.clone();
path.push(binary.name());
path
}
pub fn binaries(&self) -> &Vec<BundleBinary> {
&self.binaries
}
pub fn package_types(&self) -> crate::Result<Vec<PackageType>> {
let target_os = std::env::consts::OS;
let mut platform_types = match target_os {
"macos" => vec![PackageType::MacOsBundle, PackageType::Dmg],
"ios" => vec![PackageType::IosBundle],
"linux" => vec![PackageType::Deb, PackageType::AppImage],
#[cfg(target_os = "windows")]
"windows" => vec![PackageType::WindowsMsi],
os => {
return Err(crate::Error::GenericError(format!(
"Native {} bundles not yet supported.",
os
)))
}
};
if self.is_update_enabled() {
platform_types.push(PackageType::Updater)
}
if let Some(package_types) = &self.package_types {
let mut types = vec![];
for package_type in package_types {
let package_type = *package_type;
if platform_types
.clone()
.into_iter()
.any(|t| t == package_type)
{
types.push(package_type);
}
}
Ok(types)
} else {
Ok(platform_types)
}
}
pub fn is_verbose(&self) -> bool {
self.is_verbose
}
pub fn product_name(&self) -> &str {
&self.package.product_name
}
pub fn bundle_identifier(&self) -> &str {
self.bundle_settings.identifier.as_deref().unwrap_or("")
}
pub fn icon_files(&self) -> ResourcePaths<'_> {
match self.bundle_settings.icon {
Some(ref paths) => ResourcePaths::new(paths.as_slice(), false),
None => ResourcePaths::new(&[], false),
}
}
pub fn resource_files(&self) -> ResourcePaths<'_> {
match self.bundle_settings.resources {
Some(ref paths) => ResourcePaths::new(paths.as_slice(), true),
None => ResourcePaths::new(&[], true),
}
}
pub fn external_binaries(&self) -> ResourcePaths<'_> {
match self.bundle_settings.external_bin {
Some(ref paths) => ResourcePaths::new(paths.as_slice(), true),
None => ResourcePaths::new(&[], true),
}
}
pub fn copy_binaries(&self, path: &Path) -> crate::Result<()> {
for src in self.external_binaries() {
let src = src?;
let dest = path.join(
src
.file_name()
.expect("failed to extract external binary filename"),
);
common::copy_file(&src, &dest)?;
}
Ok(())
}
pub fn copy_resources(&self, path: &Path) -> crate::Result<()> {
for src in self.resource_files() {
let src = src?;
let dest = path.join(common::resource_relpath(&src));
common::copy_file(&src, &dest)?;
}
Ok(())
}
pub fn version_string(&self) -> &str {
&self.package.version
}
pub fn copyright_string(&self) -> Option<&str> {
self.bundle_settings.copyright.as_deref()
}
pub fn author_names(&self) -> &[String] {
match self.package.authors {
Some(ref names) => names.as_slice(),
None => &[],
}
}
pub fn authors_comma_separated(&self) -> Option<String> {
let names = self.author_names();
if names.is_empty() {
None
} else {
Some(names.join(", "))
}
}
pub fn homepage_url(&self) -> &str {
&self.package.homepage.as_deref().unwrap_or("")
}
pub fn app_category(&self) -> Option<AppCategory> {
self.bundle_settings.category
}
pub fn short_description(&self) -> &str {
self
.bundle_settings
.short_description
.as_ref()
.unwrap_or(&self.package.description)
}
pub fn long_description(&self) -> Option<&str> {
self.bundle_settings.long_description.as_deref()
}
pub fn deb(&self) -> &DebianSettings {
&self.bundle_settings.deb
}
pub fn macos(&self) -> &MacOsSettings {
&self.bundle_settings.macos
}
#[cfg(windows)]
pub fn windows(&self) -> &WindowsSettings {
&self.bundle_settings.windows
}
pub fn is_update_enabled(&self) -> bool {
match &self.bundle_settings.updater {
Some(val) => val.active,
None => false,
}
}
pub fn is_updater_pubkey(&self) -> bool {
match &self.bundle_settings.updater {
Some(val) => val.pubkey.is_some(),
None => false,
}
}
#[cfg(test)]
pub fn updater_pubkey(&self) -> Option<&str> {
self
.bundle_settings
.updater
.as_ref()
.expect("Updater is not defined")
.pubkey
.as_deref()
}
}
fn parse_external_bin(bundle_settings: BundleSettings) -> crate::Result<BundleSettings> {
let target_triple = target_triple()?;
let mut win_paths = Vec::new();
let external_bin = match bundle_settings.external_bin {
Some(paths) => {
for curr_path in paths.iter() {
win_paths.push(format!(
"{}-{}{}",
curr_path,
target_triple,
if cfg!(windows) { ".exe" } else { "" }
));
}
Some(win_paths)
}
None => Some(vec![]),
};
Ok(BundleSettings {
external_bin,
..bundle_settings
})
}
pub struct ResourcePaths<'a> {
pattern_iter: std::slice::Iter<'a, String>,
glob_iter: Option<glob::Paths>,
walk_iter: Option<walkdir::IntoIter>,
allow_walk: bool,
current_pattern: Option<String>,
current_pattern_is_valid: bool,
}
impl<'a> ResourcePaths<'a> {
fn new(patterns: &'a [String], allow_walk: bool) -> ResourcePaths<'a> {
ResourcePaths {
pattern_iter: patterns.iter(),
glob_iter: None,
walk_iter: None,
allow_walk,
current_pattern: None,
current_pattern_is_valid: false,
}
}
}
impl<'a> Iterator for ResourcePaths<'a> {
type Item = crate::Result<PathBuf>;
fn next(&mut self) -> Option<crate::Result<PathBuf>> {
loop {
if let Some(ref mut walk_entries) = self.walk_iter {
if let Some(entry) = walk_entries.next() {
let entry = match entry {
Ok(entry) => entry,
Err(error) => return Some(Err(crate::Error::from(error))),
};
let path = entry.path();
if path.is_dir() {
continue;
}
self.current_pattern_is_valid = true;
return Some(Ok(path.to_path_buf()));
}
}
self.walk_iter = None;
if let Some(ref mut glob_paths) = self.glob_iter {
if let Some(glob_result) = glob_paths.next() {
let path = match glob_result {
Ok(path) => path,
Err(error) => return Some(Err(crate::Error::from(error))),
};
if path.is_dir() {
if self.allow_walk {
let walk = walkdir::WalkDir::new(path);
self.walk_iter = Some(walk.into_iter());
continue;
} else {
let msg = format!("{:?} is a directory", path);
return Some(Err(crate::Error::GenericError(msg)));
}
}
self.current_pattern_is_valid = true;
return Some(Ok(path));
} else if let Some(current_path) = &self.current_pattern {
if !self.current_pattern_is_valid {
return Some(Err(crate::Error::GenericError(format!(
"Path matching '{}' not found",
current_path
))));
}
}
}
self.glob_iter = None;
if let Some(pattern) = self.pattern_iter.next() {
self.current_pattern = Some(pattern.to_string());
self.current_pattern_is_valid = false;
let glob = match glob::glob(pattern) {
Ok(glob) => glob,
Err(error) => return Some(Err(crate::Error::from(error))),
};
self.glob_iter = Some(glob);
continue;
}
return None;
}
}
}