feat(tauri) bundle formats config on tauri.js, fix bundler appim… (#537)

* feat(tauri) bundle formats config on tauri.js, fix bundler appimage

* fix(bundler) dmg chmod correctly
This commit is contained in:
Lucas Fernandes Nogueira 2020-04-03 13:30:55 -03:00 committed by GitHub
parent f36ce6564c
commit f24f7e18f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 223 additions and 177 deletions

View File

@ -32,14 +32,14 @@ term = "0.6.1"
toml = "0.5.6"
uuid = { version = "0.8", features = ["v5"] }
walkdir = "2"
lazy_static = { version = "1.4" }
handlebars = { version = "3.0" }
[target.'cfg(target_os = "windows")'.dependencies]
attohttpc = { version = "0.12.0" }
regex = { version = "1" }
[target.'cfg(not(target_os = "linux"))'.dependencies]
handlebars = { version = "3.0" }
lazy_static = { version = "1.4" }
zip = { version = "0.5" }
sha2 = { version = "0.8" }
hex = { version = "0.4" }
@ -50,8 +50,3 @@ tempfile = "3"
[[bin]]
name = "cargo-tauri-bundler"
path = "src/main.rs"
[features]
appimage = []
ios = []
dmg = []

View File

@ -1,11 +1,8 @@
#[cfg(feature = "appimage")]
mod appimage_bundle;
mod category;
mod common;
mod deb_bundle;
#[cfg(feature = "dmg")]
mod dmg_bundle;
#[cfg(feature = "ios")]
mod ios_bundle;
#[cfg(target_os = "windows")]
mod msi_bundle;
@ -28,19 +25,12 @@ pub fn bundle_project(settings: Settings) -> crate::Result<Vec<PathBuf>> {
for package_type in settings.package_types()? {
paths.append(&mut match package_type {
PackageType::OsxBundle => osx_bundle::bundle_project(&settings)?,
#[cfg(feature = "ios")]
PackageType::IosBundle => ios_bundle::bundle_project(&settings)?,
// use dmg bundler
// PackageType::OsxBundle => dmg_bundle::bundle_project(&settings)?,
#[cfg(target_os = "windows")]
PackageType::WindowsMsi => msi_bundle::bundle_project(&settings)?,
// force appimage on linux
// PackageType::Deb => appimage_bundle::bundle_project(&settings)?,
PackageType::Deb => deb_bundle::bundle_project(&settings)?,
PackageType::Rpm => rpm_bundle::bundle_project(&settings)?,
#[cfg(feature = "appimage")]
PackageType::AppImage => appimage_bundle::bundle_project(&settings)?,
#[cfg(feature = "dmg")]
PackageType::Dmg => dmg_bundle::bundle_project(&settings)?,
});
}

View File

@ -40,20 +40,19 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
);
let base_dir = settings.project_out_directory().join("bundle/deb");
let package_dir = base_dir.join(&package_base_name);
if package_dir.exists() {
remove_dir_all(&package_dir)
.chain_err(|| format!("Failed to remove old {}", package_base_name))?;
}
// generate deb_folder structure
deb_bundle::generate_folders(settings, &package_dir)?;
let _app_dir_path = path_utils::create(
settings
.project_out_directory()
.join(format!("{}.AppDir", settings.binary_name())),
true,
);
let output_path = settings.project_out_directory().join("bundle/appimage");
if output_path.exists() {
remove_dir_all(&output_path)
.chain_err(|| format!("Failed to remove old {}", package_base_name))?;
}
std::fs::create_dir_all(output_path.clone())?;
let app_dir_path = output_path.join(format!("{}.AppDir", settings.binary_name()));
let appimage_path = output_path.join(format!("{}.AppImage", settings.binary_name()));
path_utils::create(app_dir_path.clone(), true)?;
let upcase = settings.binary_name().to_uppercase();
@ -67,27 +66,20 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
let temp = HANDLEBARS
.render("appimage", &sh_map)
.or_else(|e| Err(e.to_string()))?;
let output_path = settings.project_out_directory();
// create the shell script file in the target/ folder.
let sh_file = output_path.join("build_appimage");
common::print_bundling(
format!(
"{:?}",
&output_path.join(format!("{}.AppImage", settings.binary_name()))
)
.as_str(),
)?;
common::print_bundling(format!("{:?}", &appimage_path).as_str())?;
write(&sh_file, temp).or_else(|e| Err(e.to_string()))?;
// chmod script for execution
Command::new("chmod")
.arg("777")
.arg(&sh_file)
.current_dir(output_path)
.current_dir(output_path.clone())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.output()
.expect("Failed to chmod script");
// execute the shell script to build the appimage.
@ -98,5 +90,5 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
.spawn()
.expect("Failed to execute shell script");
Ok(vec![sh_file])
Ok(vec![appimage_path])
}

View File

@ -23,7 +23,7 @@ use crate::{ResultExt, Settings};
use ar;
use icns;
use image::png::{PngDecoder};
use image::png::PngDecoder;
use image::{self, GenericImageView, ImageDecoder};
use libflate::gzip;
use md5;
@ -334,7 +334,7 @@ fn generate_icon_files(settings: &Settings, data_dir: &PathBuf) -> crate::Result
let dest_path = get_dest_path(width, height, is_high_density);
icon.write_to(
&mut common::create_file(&dest_path)?,
image::ImageOutputFormat::Png
image::ImageOutputFormat::Png,
)?;
}
}

View File

@ -68,7 +68,7 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
.current_dir(output_path)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.output()
.expect("Failed to chmod script");
// execute the bundle script

View File

@ -12,143 +12,175 @@ use super::common;
use crate::{ResultExt, Settings};
use icns;
use image::png::{PNGDecoder, PNGEncoder};
use image::png::PngDecoder;
use image::{self, GenericImageView, ImageDecoder};
use std::collections::BTreeSet;
use std::convert::TryInto;
use std::ffi::OsStr;
use std::fs::{self, File};
use std::io::Write;
use std::path::{Path, PathBuf};
pub fn bundle_project(settings: &Settings) -> ::Result<Vec<PathBuf>> {
pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
common::print_warning("iOS bundle support is still experimental.")?;
let app_bundle_name = format!("{}.app", settings.bundle_name());
common::print_bundling(&app_bundle_name)?;
let bundle_dir = settings.project_out_directory().join("bundle/ios").join(&app_bundle_name);
let bundle_dir = settings
.project_out_directory()
.join("bundle/ios")
.join(&app_bundle_name);
if bundle_dir.exists() {
fs::remove_dir_all(&bundle_dir).chain_err(|| {
format!("Failed to remove old {}", app_bundle_name)
})?;
fs::remove_dir_all(&bundle_dir)
.chain_err(|| format!("Failed to remove old {}", app_bundle_name))?;
}
fs::create_dir_all(&bundle_dir).chain_err(|| {
format!("Failed to create bundle directory at {:?}", bundle_dir)
})?;
fs::create_dir_all(&bundle_dir)
.chain_err(|| format!("Failed to create bundle directory at {:?}", bundle_dir))?;
for src in settings.resource_files() {
let src = src?;
let dest = bundle_dir.join(common::resource_relpath(&src));
common::copy_file(&src, &dest).chain_err(|| {
format!("Failed to copy resource file {:?}", src)
})?;
let src = src?;
let dest = bundle_dir.join(common::resource_relpath(&src));
common::copy_file(&src, &dest)
.chain_err(|| format!("Failed to copy resource file {:?}", src))?;
}
let icon_filenames = generate_icon_files(&bundle_dir, settings).chain_err(|| {
"Failed to create app icons"
})?;
generate_info_plist(&bundle_dir, settings, &icon_filenames).chain_err(|| {
"Failed to create Info.plist"
})?;
let icon_filenames =
generate_icon_files(&bundle_dir, settings).chain_err(|| "Failed to create app icons")?;
generate_info_plist(&bundle_dir, settings, &icon_filenames)
.chain_err(|| "Failed to create Info.plist")?;
let bin_path = bundle_dir.join(&settings.bundle_name());
common::copy_file(settings.binary_path(), &bin_path).chain_err(|| {
format!("Failed to copy binary from {:?}", settings.binary_path())
})?;
common::copy_file(settings.binary_path(), &bin_path)
.chain_err(|| format!("Failed to copy binary from {:?}", settings.binary_path()))?;
Ok(vec![bundle_dir])
}
/// Generate the icon files and store them under the `bundle_dir`.
fn generate_icon_files(bundle_dir: &Path, settings: &Settings) -> ::Result<Vec<String>> {
fn generate_icon_files(bundle_dir: &Path, settings: &Settings) -> crate::Result<Vec<String>> {
let mut filenames = Vec::new();
{
let mut get_dest_path = |width: u32, height: u32, is_retina: bool| {
let filename = format!("icon_{}x{}{}.png",
width,
height,
if is_retina { "@2x" } else { "" });
let path = bundle_dir.join(&filename);
filenames.push(filename);
path
};
let mut sizes = BTreeSet::new();
// Prefer PNG files.
for icon_path in settings.icon_files() {
let icon_path = icon_path?;
if icon_path.extension() != Some(OsStr::new("png")) {
continue;
}
let mut decoder = PNGDecoder::new(File::open(&icon_path)?);
let (width, height) = decoder.dimensions()?;
let is_retina = common::is_retina(&icon_path);
let mut get_dest_path = |width: u32, height: u32, is_retina: bool| {
let filename = format!(
"icon_{}x{}{}.png",
width,
height,
if is_retina { "@2x" } else { "" }
);
let path = bundle_dir.join(&filename);
filenames.push(filename);
path
};
let mut sizes = BTreeSet::new();
// Prefer PNG files.
for icon_path in settings.icon_files() {
let icon_path = icon_path?;
if icon_path.extension() != Some(OsStr::new("png")) {
continue;
}
let decoder = PngDecoder::new(File::open(&icon_path)?)?;
let width = decoder.dimensions().0;
let height = decoder.dimensions().1;
let is_retina = common::is_retina(&icon_path);
if !sizes.contains(&(width, height, is_retina)) {
sizes.insert((width, height, is_retina));
let dest_path = get_dest_path(width, height, is_retina);
common::copy_file(&icon_path, &dest_path)?;
}
}
// Fall back to non-PNG files for any missing sizes.
for icon_path in settings.icon_files() {
let icon_path = icon_path?;
if icon_path.extension() == Some(OsStr::new("png")) {
continue;
} else if icon_path.extension() == Some(OsStr::new("icns")) {
let icon_family = icns::IconFamily::read(File::open(&icon_path)?)?;
for icon_type in icon_family.available_icons() {
let width = icon_type.screen_width();
let height = icon_type.screen_height();
let is_retina = icon_type.pixel_density() > 1;
if !sizes.contains(&(width, height, is_retina)) {
sizes.insert((width, height, is_retina));
let dest_path = get_dest_path(width, height, is_retina);
common::copy_file(&icon_path, &dest_path)?;
}
}
// Fall back to non-PNG files for any missing sizes.
for icon_path in settings.icon_files() {
let icon_path = icon_path?;
if icon_path.extension() == Some(OsStr::new("png")) {
continue;
} else if icon_path.extension() == Some(OsStr::new("icns")) {
let icon_family = icns::IconFamily::read(File::open(&icon_path)?)?;
for icon_type in icon_family.available_icons() {
let width = icon_type.screen_width();
let height = icon_type.screen_height();
let is_retina = icon_type.pixel_density() > 1;
if !sizes.contains(&(width, height, is_retina)) {
sizes.insert((width, height, is_retina));
let dest_path = get_dest_path(width, height, is_retina);
let icon = icon_family.get_icon_with_type(icon_type)?;
icon.write_png(File::create(dest_path)?)?;
}
}
} else {
let icon = try!(image::open(&icon_path));
let (width, height) = icon.dimensions();
let is_retina = common::is_retina(&icon_path);
if !sizes.contains(&(width, height, is_retina)) {
sizes.insert((width, height, is_retina));
let dest_path = get_dest_path(width, height, is_retina);
let encoder = PNGEncoder::new(common::create_file(&dest_path)?);
encoder.encode(&icon.raw_pixels(), width, height, icon.color())?;
}
sizes.insert((width, height, is_retina));
let dest_path = get_dest_path(width, height, is_retina);
let icon = icon_family.get_icon_with_type(icon_type)?;
icon.write_png(File::create(dest_path)?)?;
}
}
} else {
let icon = image::open(&icon_path)?;
let (width, height) = icon.dimensions();
let is_retina = common::is_retina(&icon_path);
if !sizes.contains(&(width, height, is_retina)) {
sizes.insert((width, height, is_retina));
let dest_path = get_dest_path(width, height, is_retina);
icon.write_to(
&mut common::create_file(&dest_path)?,
image::ImageOutputFormat::Png,
)?;
}
}
}
}
Ok(filenames)
}
fn generate_info_plist(bundle_dir: &Path, settings: &Settings, icon_filenames: &Vec<String>) -> ::Result<()> {
fn generate_info_plist(
bundle_dir: &Path,
settings: &Settings,
icon_filenames: &Vec<String>,
) -> crate::Result<()> {
let file = &mut common::create_file(&bundle_dir.join("Info.plist"))?;
write!(file,
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
write!(
file,
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \
\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n\
<plist version=\"1.0\">\n\
<dict>\n")?;
write!(file, " <key>CFBundleIdentifier</key>\n <string>{}</string>\n", settings.bundle_identifier())?;
write!(file, " <key>CFBundleDisplayName</key>\n <string>{}</string>\n", settings.bundle_name())?;
write!(file, " <key>CFBundleName</key>\n <string>{}</string>\n", settings.bundle_name())?;
write!(file, " <key>CFBundleExecutable</key>\n <string>{}</string>\n", settings.binary_name())?;
write!(file, " <key>CFBundleVersion</key>\n <string>{}</string>\n", settings.version_string())?;
write!(file, " <key>CFBundleShortVersionString</key>\n <string>{}</string>\n", settings.version_string())?;
write!(file, " <key>CFBundleDevelopmentRegion</key>\n <string>en_US</string>\n")?;
<dict>\n"
)?;
write!(
file,
" <key>CFBundleIdentifier</key>\n <string>{}</string>\n",
settings.bundle_identifier()
)?;
write!(
file,
" <key>CFBundleDisplayName</key>\n <string>{}</string>\n",
settings.bundle_name()
)?;
write!(
file,
" <key>CFBundleName</key>\n <string>{}</string>\n",
settings.bundle_name()
)?;
write!(
file,
" <key>CFBundleExecutable</key>\n <string>{}</string>\n",
settings.binary_name()
)?;
write!(
file,
" <key>CFBundleVersion</key>\n <string>{}</string>\n",
settings.version_string()
)?;
write!(
file,
" <key>CFBundleShortVersionString</key>\n <string>{}</string>\n",
settings.version_string()
)?;
write!(
file,
" <key>CFBundleDevelopmentRegion</key>\n <string>en_US</string>\n"
)?;
if !icon_filenames.is_empty() {
write!(file, " <key>CFBundleIconFiles</key>\n <array>\n")?;
for filename in icon_filenames {
write!(file, " <string>{}</string>\n", filename)?;
}
write!(file, " </array>\n")?;
write!(file, " <key>CFBundleIconFiles</key>\n <array>\n")?;
for filename in icon_filenames {
write!(file, " <string>{}</string>\n", filename)?;
}
write!(file, " </array>\n")?;
}
write!(file, " <key>LSRequiresIPhoneOS</key>\n <true/>\n")?;
write!(file, "</dict>\n</plist>\n")?;
file.flush()?;
Ok(())
}
}

View File

@ -375,7 +375,11 @@ fn create_icns_file(
}
for (icon, next_size_down, density) in images_to_resize {
let icon = icon.resize_exact(next_size_down, next_size_down, image::imageops::FilterType::Lanczos3);
let icon = icon.resize_exact(
next_size_down,
next_size_down,
image::imageops::FilterType::Lanczos3,
);
add_icon_to_family(icon, density, &mut family)?;
}

View File

@ -19,15 +19,12 @@ use std::path::{Path, PathBuf};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PackageType {
OsxBundle,
#[cfg(feature = "ios")]
IosBundle,
#[cfg(target_os = "windows")]
WindowsMsi,
Deb,
Rpm,
#[cfg(feature = "appimage")]
AppImage,
#[cfg(feature = "dmg")]
Dmg,
}
@ -36,15 +33,12 @@ impl PackageType {
// Other types we may eventually want to support: apk
match name {
"deb" => Some(PackageType::Deb),
#[cfg(feature = "ios")]
"ios" => Some(PackageType::IosBundle),
#[cfg(target_os = "windows")]
"msi" => Some(PackageType::WindowsMsi),
"osx" => Some(PackageType::OsxBundle),
"rpm" => Some(PackageType::Rpm),
#[cfg(feature = "appimage")]
"appimage" => Some(PackageType::AppImage),
#[cfg(feature = "dmg")]
"dmg" => Some(PackageType::Dmg),
_ => None,
}
@ -53,15 +47,12 @@ impl PackageType {
pub fn short_name(&self) -> &'static str {
match *self {
PackageType::Deb => "deb",
#[cfg(feature = "ios")]
PackageType::IosBundle => "ios",
#[cfg(target_os = "windows")]
PackageType::WindowsMsi => "msi",
PackageType::OsxBundle => "osx",
PackageType::Rpm => "rpm",
#[cfg(feature = "appimage")]
PackageType::AppImage => "appimage",
#[cfg(feature = "dmg")]
PackageType::Dmg => "dmg",
}
}
@ -73,14 +64,13 @@ impl PackageType {
const ALL_PACKAGE_TYPES: &[PackageType] = &[
PackageType::Deb,
#[cfg(feature = "ios")]
PackageType::IosBundle,
#[cfg(target_os = "windows")]
PackageType::WindowsMsi,
PackageType::OsxBundle,
PackageType::Rpm,
#[cfg(feature = "dmg")]
PackageType::Dmg,
PackageType::AppImage,
];
#[derive(Clone, Debug)]
@ -143,7 +133,7 @@ struct CargoSettings {
#[derive(Clone, Debug)]
pub struct Settings {
package: PackageSettings,
package_type: Option<PackageType>, // If `None`, use the default package type for this os
package_types: Option<Vec<PackageType>>, // If `None`, use the default package type for this os
target: Option<(String, TargetInfo)>,
features: Option<Vec<String>>,
project_out_directory: PathBuf,
@ -169,11 +159,19 @@ impl CargoSettings {
impl Settings {
pub fn new(current_dir: PathBuf, matches: &ArgMatches<'_>) -> crate::Result<Self> {
let package_type = match matches.value_of("format") {
Some(name) => match PackageType::from_short_name(name) {
Some(package_type) => Some(package_type),
None => bail!("Unsupported bundle format: {}", name),
},
let package_types = match matches.values_of("format") {
Some(names) => {
let mut types = vec![];
for name in names {
match PackageType::from_short_name(name) {
Some(package_type) => {
types.push(package_type);
}
None => bail!("Unsupported bundle format: {}", name),
}
}
Some(types)
}
None => None,
};
let build_artifact = if let Some(bin) = matches.value_of("bin") {
@ -248,7 +246,7 @@ impl Settings {
Ok(Settings {
package,
package_type,
package_types,
target,
features,
build_artifact,
@ -350,29 +348,41 @@ impl Settings {
&self.binary_path
}
/// If a specific package type was specified by the command-line, returns
/// that package type; otherwise, if a target triple was specified by the
/// If a list of package types was specified by the command-line, returns
/// that list filtered by the current target's available targets;
/// otherwise, if a target triple was specified by the
/// command-line, returns the native package type(s) for that target;
/// otherwise, returns the native package type(s) for the host platform.
/// Fails if the host/target's native package type is not supported.
pub fn package_types(&self) -> crate::Result<Vec<PackageType>> {
if let Some(package_type) = self.package_type {
Ok(vec![package_type])
let target_os = if let Some((_, ref info)) = self.target {
info.target_os()
} else {
let target_os = if let Some((_, ref info)) = self.target {
info.target_os()
} else {
std::env::consts::OS
};
match target_os {
"macos" => Ok(vec![PackageType::OsxBundle]),
#[cfg(feature = "ios")]
"ios" => Ok(vec![PackageType::IosBundle]),
"linux" => Ok(vec![PackageType::Deb]), // TODO: Do Rpm too, once it's implemented.
#[cfg(target_os = "windows")]
"windows" => Ok(vec![PackageType::WindowsMsi]),
os => bail!("Native {} bundles not yet supported.", os),
std::env::consts::OS
};
let platform_types = match target_os {
"macos" => vec![PackageType::OsxBundle, PackageType::Dmg],
"ios" => vec![PackageType::IosBundle],
"linux" => vec![PackageType::Deb, PackageType::AppImage],
#[cfg(target_os = "windows")]
"windows" => vec![PackageType::WindowsMsi],
os => bail!("Native {} bundles not yet supported.", os),
};
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)
}
}

View File

@ -1,5 +1,5 @@
use serde::Deserialize;
use super::category::AppCategory;
use serde::Deserialize;
use std::path::PathBuf;
use std::fs;
@ -67,4 +67,4 @@ pub fn get() -> crate::Result<Config> {
None => Err(crate::Error::from("Couldn't get tauri config; please specify the TAURI_CONFIG or TAURI_DIR environment variables"))
}
}
}
}

View File

@ -97,6 +97,7 @@ fn run() -> crate::Result<()> {
.long("format")
.value_name("FORMAT")
.possible_values(&all_formats)
.multiple(true)
.help("Which bundle format to produce"),
)
.arg(

View File

@ -164,7 +164,12 @@ class Runner {
cargoArgs: [
cfg.tauri.bundle.active ? 'tauri-bundler' : 'build',
'--features',
...features
...features,
...(
cfg.tauri.bundle.active && Array.isArray(cfg.tauri.bundle.targets) && cfg.tauri.bundle.targets.length
? ['--format'].concat(cfg.tauri.bundle.targets)
: []
)
]
.concat(cfg.ctx.debug ? [] : ['--release'])
.concat(target ? ['--target', target] : [])

View File

@ -12,6 +12,7 @@ export default {
},
bundle: {
active: true,
targets: 'all', // or an array of targets
identifier: 'com.tauri.dev',
icon: ['icons/32x32.png', 'icons/128x128.png', 'icons/128x128@2x.png', 'icons/icon.icns', 'icons/icon.ico'],
resources: [],

View File

@ -16,7 +16,6 @@ export interface TauriConfig {
targetName: string
exitOnPanic?: boolean
}
bundle: {}
tauri: {
inlinedAssets: string[]
devPath: string
@ -25,6 +24,23 @@ export interface TauriConfig {
}
bundle: {
active: boolean
targets?: string | string[]
identifier: string
icon: string[]
resources?: string[]
externalBin?: string[]
copyright?: string
category: string
shortDescription?: string
longDescription?: string
deb?: {
depends?: string[]
}
osx?: {
frameworks?: string[]
minimumSystemVersion?: string
}
exceptionDomain?: string
}
whitelist: {
all: boolean