mirror of
https://github.com/tauri-apps/tauri.git
synced 2024-12-25 03:33:36 +03:00
feat(bundler) appimage (#37)
* add basic setup * add basic download logic * build test * add yaml parser. * finish appimage * cleanup and comment * add comment * modify chmod * fix line endings. * remove "cat runtime" * match desktop files * add path utils * add opts * add fileopts and diropts * create, create all and remove * copy file function * add copy logic and new error * add target to gitignore * refactor deb::bundle_project * finish refactor
This commit is contained in:
parent
70ccd1659d
commit
52e20d2544
9
.gitignore
vendored
9
.gitignore
vendored
@ -60,7 +60,14 @@ debug.log
|
||||
package-lock.json
|
||||
.vscode/settings.json
|
||||
|
||||
# Quasar output
|
||||
bundle.json
|
||||
config.json
|
||||
|
||||
# rust compiled folders
|
||||
target
|
||||
|
||||
src-tauri
|
||||
test/jest/tmp
|
||||
/tauri.conf.js
|
||||
/tauri.conf.js
|
||||
|
||||
|
@ -21,6 +21,7 @@ image = "0.12"
|
||||
libflate = "0.1"
|
||||
md5 = "0.3"
|
||||
msi = "0.2"
|
||||
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
strsim = "0.7"
|
||||
|
@ -1,3 +1,4 @@
|
||||
mod appimage_bundle;
|
||||
mod category;
|
||||
mod common;
|
||||
mod deb_bundle;
|
||||
@ -7,6 +8,7 @@ mod msi_bundle;
|
||||
mod osx_bundle;
|
||||
mod rpm_bundle;
|
||||
mod settings;
|
||||
mod path_utils;
|
||||
mod wix;
|
||||
|
||||
pub use self::common::{print_error, print_finished};
|
||||
@ -22,8 +24,11 @@ pub fn bundle_project(settings: Settings) -> crate::Result<Vec<PathBuf>> {
|
||||
// use dmg bundler
|
||||
// PackageType::OsxBundle => dmg_bundle::bundle_project(&settings)?,
|
||||
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)?,
|
||||
PackageType::AppImage => appimage_bundle::bundle_project(&settings)?,
|
||||
PackageType::Dmg => dmg_bundle::bundle_project(&settings)?,
|
||||
});
|
||||
}
|
||||
|
100
tools/rust/cargo-tauri-bundle/src/bundle/appimage_bundle.rs
Normal file
100
tools/rust/cargo-tauri-bundle/src/bundle/appimage_bundle.rs
Normal file
@ -0,0 +1,100 @@
|
||||
use super::common;
|
||||
use super::deb_bundle;
|
||||
use crate::Settings;
|
||||
use super::path_utils;
|
||||
|
||||
use handlebars::Handlebars;
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::fs::write;
|
||||
use std::path::PathBuf;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
// Create handlebars template for shell script
|
||||
lazy_static! {
|
||||
static ref HANDLEBARS: Handlebars = {
|
||||
let mut handlebars = Handlebars::new();
|
||||
|
||||
handlebars
|
||||
.register_template_string("appimage", include_str!("templates/appimage"))
|
||||
.unwrap();
|
||||
handlebars
|
||||
};
|
||||
}
|
||||
|
||||
// bundle the project.
|
||||
pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
|
||||
// generate the deb binary name
|
||||
let arch = match settings.binary_arch() {
|
||||
"x86" => "i386",
|
||||
"x86_64" => "amd64",
|
||||
other => other,
|
||||
};
|
||||
let package_base_name = format!(
|
||||
"{}_{}_{}",
|
||||
settings.binary_name(),
|
||||
settings.version_string(),
|
||||
arch
|
||||
);
|
||||
let base_dir = settings.project_out_directory().join("bundle/deb");
|
||||
let package_dir = base_dir.join(&package_base_name);
|
||||
if package_dir.exists() {
|
||||
fs::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 upcase = settings.binary_name().to_uppercase();
|
||||
|
||||
// setup data to insert into shell script
|
||||
let mut sh_map = BTreeMap::new();
|
||||
sh_map.insert("app_name", settings.binary_name());
|
||||
sh_map.insert("bundle_name", package_base_name.as_str());
|
||||
sh_map.insert("app_name_uppercase", upcase.as_str());
|
||||
|
||||
// initialize shell script template.
|
||||
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(),
|
||||
)?;
|
||||
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)
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("Failed to chmod script");
|
||||
|
||||
// execute the shell script to build the appimage.
|
||||
Command::new(&sh_file)
|
||||
.current_dir(output_path)
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("Failed to execute shell script");
|
||||
|
||||
Ok(vec![sh_file])
|
||||
}
|
@ -56,15 +56,7 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
|
||||
}
|
||||
let package_path = base_dir.join(package_name);
|
||||
|
||||
// Generate data files.
|
||||
let data_dir = package_dir.join("data");
|
||||
let binary_dest = data_dir.join("usr/bin").join(settings.binary_name());
|
||||
common::copy_file(settings.binary_path(), &binary_dest)
|
||||
.chain_err(|| "Failed to copy binary file")?;
|
||||
transfer_resource_files(settings, &data_dir).chain_err(|| "Failed to copy resource files")?;
|
||||
generate_icon_files(settings, &data_dir).chain_err(|| "Failed to create icon files")?;
|
||||
generate_desktop_file(settings, &data_dir).chain_err(|| "Failed to create desktop file")?;
|
||||
|
||||
let data_dir = generate_folders(settings, &package_dir).chain_err(|| "Failed to build folders")?;
|
||||
// Generate control files.
|
||||
let control_dir = package_dir.join("control");
|
||||
generate_control_file(settings, arch, &control_dir, &data_dir)
|
||||
@ -90,6 +82,20 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
|
||||
Ok(vec![package_path])
|
||||
}
|
||||
|
||||
pub fn generate_folders(settings: &Settings, package_dir: &Path) -> crate::Result<PathBuf> {
|
||||
|
||||
// Generate data files.
|
||||
let data_dir = package_dir.join("data");
|
||||
let binary_dest = data_dir.join("usr/bin").join(settings.binary_name());
|
||||
common::copy_file(settings.binary_path(), &binary_dest)
|
||||
.chain_err(|| "Failed to copy binary file")?;
|
||||
transfer_resource_files(settings, &data_dir).chain_err(|| "Failed to copy resource files")?;
|
||||
generate_icon_files(settings, &data_dir).chain_err(|| "Failed to create icon files")?;
|
||||
generate_desktop_file(settings, &data_dir).chain_err(|| "Failed to create desktop file")?;
|
||||
|
||||
Ok(data_dir)
|
||||
}
|
||||
|
||||
/// Generate the application desktop file and store it under the `data_dir`.
|
||||
fn generate_desktop_file(settings: &Settings, data_dir: &Path) -> crate::Result<()> {
|
||||
let bin_name = settings.binary_name();
|
||||
|
256
tools/rust/cargo-tauri-bundle/src/bundle/path_utils.rs
Normal file
256
tools/rust/cargo-tauri-bundle/src/bundle/path_utils.rs
Normal file
@ -0,0 +1,256 @@
|
||||
use std::fs::{create_dir, create_dir_all, read_dir, remove_dir_all};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DirOpts {
|
||||
pub depth: u64,
|
||||
}
|
||||
|
||||
pub struct FileOpts {
|
||||
pub overwrite: bool,
|
||||
pub skip: bool,
|
||||
pub buffer_size: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Options {
|
||||
pub overwrite: bool,
|
||||
pub skip: bool,
|
||||
pub buffer_size: usize,
|
||||
pub copy_files: bool,
|
||||
pub content_only: bool,
|
||||
pub depth: u64,
|
||||
}
|
||||
|
||||
pub struct DirInfo {
|
||||
pub size: u64,
|
||||
pub files: Vec<String>,
|
||||
pub directories: Vec<String>,
|
||||
}
|
||||
|
||||
impl Options {
|
||||
pub fn new() -> Options {
|
||||
Options {
|
||||
overwrite: false,
|
||||
skip: false,
|
||||
buffer_size: 64000,
|
||||
copy_files: false,
|
||||
content_only: false,
|
||||
depth: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DirOpts {
|
||||
pub fn new() -> DirOpts {
|
||||
DirOpts { depth: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl FileOpts {
|
||||
pub fn new() -> FileOpts {
|
||||
FileOpts {
|
||||
overwrite: false,
|
||||
skip: false,
|
||||
buffer_size: 64000,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create<P>(path: P, erase: bool) -> crate::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
if erase && path.as_ref().exists() {
|
||||
remove(&path)?;
|
||||
}
|
||||
Ok(create_dir(&path)?)
|
||||
}
|
||||
|
||||
pub fn create_all<P>(path: P, erase: bool) -> crate::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
if erase && path.as_ref().exists() {
|
||||
remove(&path)?;
|
||||
}
|
||||
Ok(create_dir_all(&path)?)
|
||||
}
|
||||
|
||||
pub fn remove<P: AsRef<Path>>(path: P) -> crate::Result<()> {
|
||||
if path.as_ref().exists() {
|
||||
Ok(remove_dir_all(path)?)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn copy_file<P, Q>(from: P, to: Q, options: &FileOpts) -> crate::Result<u64>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
{
|
||||
let from = from.as_ref();
|
||||
if !from.exists() {
|
||||
if let Some(msg) = from.to_str() {
|
||||
let msg = format!("Path \"{}\" does not exist or you don't have access", msg);
|
||||
return Err(msg.into());
|
||||
}
|
||||
return Err("Path does not exist Or you don't have access!".into());
|
||||
}
|
||||
|
||||
if !from.is_file() {
|
||||
if let Some(msg) = from.to_str() {
|
||||
let msg = format!("Path \"{}\" is not a file!", msg);
|
||||
return Err(msg.into());
|
||||
}
|
||||
return Err("Path is not a file!".into());
|
||||
}
|
||||
if !options.overwrite && to.as_ref().exists() {
|
||||
if options.skip {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
if let Some(msg) = to.as_ref().to_str() {
|
||||
let msg = format!("Path \"{}\" is exist", msg);
|
||||
return Err(msg.into());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(std::fs::copy(from, to)?)
|
||||
}
|
||||
|
||||
pub fn copy<P, Q>(from: P, to: Q, options: &Options) -> crate::Result<u64>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
{
|
||||
let from = from.as_ref();
|
||||
if !from.exists() {
|
||||
if let Some(msg) = from.to_str() {
|
||||
let msg = format!("Path \"{}\" does not exist or you don't have access!", msg);
|
||||
return Err(msg.into());
|
||||
}
|
||||
return Err("Path does not exist Or you don't have access!".into());
|
||||
}
|
||||
if !from.is_dir() {
|
||||
if let Some(msg) = from.to_str() {
|
||||
let msg = format!("Path \"{}\" is not a directory!", msg);
|
||||
return Err(msg.into());
|
||||
}
|
||||
return Err("Path is not a directory!".into());
|
||||
}
|
||||
let dir_name;
|
||||
if let Some(val) = from.components().last() {
|
||||
dir_name = val.as_os_str();
|
||||
} else {
|
||||
return Err("Invalid folder from".into());
|
||||
}
|
||||
let mut to: PathBuf = to.as_ref().to_path_buf();
|
||||
if !options.content_only && ((options.copy_files && to.exists()) || !options.copy_files) {
|
||||
to.push(dir_name);
|
||||
}
|
||||
|
||||
let mut read_options = DirOpts::new();
|
||||
if options.depth > 0 {
|
||||
read_options.depth = options.depth;
|
||||
}
|
||||
|
||||
let dir_content = get_dir_info(from, &read_options)?;
|
||||
for directory in dir_content.directories {
|
||||
let tmp_to = Path::new(&directory).strip_prefix(from)?;
|
||||
let dir = to.join(&tmp_to);
|
||||
if !dir.exists() {
|
||||
if options.copy_files {
|
||||
create_all(dir, false)?;
|
||||
} else {
|
||||
create(dir, false)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut result: u64 = 0;
|
||||
for file in dir_content.files {
|
||||
let to = to.to_path_buf();
|
||||
let tp = Path::new(&file).strip_prefix(from)?;
|
||||
let path = to.join(&tp);
|
||||
|
||||
let file_options = FileOpts {
|
||||
overwrite: options.overwrite,
|
||||
skip: options.skip,
|
||||
buffer_size: options.buffer_size,
|
||||
};
|
||||
let mut result_copy: crate::Result<u64>;
|
||||
let mut work = true;
|
||||
|
||||
while work {
|
||||
result_copy = copy_file(&file, &path, &file_options);
|
||||
match result_copy {
|
||||
Ok(val) => {
|
||||
result += val;
|
||||
work = false;
|
||||
}
|
||||
Err(err) => {
|
||||
let err_msg = err.to_string();
|
||||
return Err(err_msg.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn get_dir_info<P>(path: P, options: &DirOpts) -> crate::Result<DirInfo>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let mut depth = 0;
|
||||
if options.depth != 0 {
|
||||
depth = options.depth + 1;
|
||||
}
|
||||
_get_dir_info(path, depth)
|
||||
}
|
||||
|
||||
fn _get_dir_info<P>(path: P, mut depth: u64) -> crate::Result<DirInfo>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let mut directories = Vec::new();
|
||||
let mut files = Vec::new();
|
||||
let mut size = 0;
|
||||
let item = path.as_ref().to_str();
|
||||
if !item.is_some() {
|
||||
return Err("Invalid path".into());
|
||||
}
|
||||
let item = item.unwrap().to_string();
|
||||
|
||||
if path.as_ref().is_dir() {
|
||||
directories.push(item);
|
||||
if depth == 0 || depth > 1 {
|
||||
if depth > 1 {
|
||||
depth -= 1;
|
||||
}
|
||||
for entry in read_dir(&path)? {
|
||||
let _path = entry?.path();
|
||||
|
||||
match _get_dir_info(_path, depth) {
|
||||
Ok(items) => {
|
||||
let mut _files = items.files;
|
||||
let mut _dirrectories = items.directories;
|
||||
size += items.size;
|
||||
files.append(&mut _files);
|
||||
directories.append(&mut _dirrectories);
|
||||
}
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
size = path.as_ref().metadata()?.len();
|
||||
files.push(item);
|
||||
}
|
||||
Ok(DirInfo {
|
||||
size: size,
|
||||
files: files,
|
||||
directories: directories,
|
||||
})
|
||||
}
|
@ -17,6 +17,7 @@ pub enum PackageType {
|
||||
WindowsMsi,
|
||||
Deb,
|
||||
Rpm,
|
||||
AppImage,
|
||||
Dmg,
|
||||
}
|
||||
|
||||
@ -29,6 +30,7 @@ impl PackageType {
|
||||
"msi" => Some(PackageType::WindowsMsi),
|
||||
"osx" => Some(PackageType::OsxBundle),
|
||||
"rpm" => Some(PackageType::Rpm),
|
||||
"appimage" => Some(PackageType::AppImage),
|
||||
"dmg" => Some(PackageType::Dmg),
|
||||
_ => None,
|
||||
}
|
||||
@ -41,6 +43,7 @@ impl PackageType {
|
||||
PackageType::WindowsMsi => "msi",
|
||||
PackageType::OsxBundle => "osx",
|
||||
PackageType::Rpm => "rpm",
|
||||
PackageType::AppImage => "appimage",
|
||||
PackageType::Dmg => "dmg",
|
||||
}
|
||||
}
|
||||
|
28
tools/rust/cargo-tauri-bundle/src/bundle/templates/appimage
Normal file
28
tools/rust/cargo-tauri-bundle/src/bundle/templates/appimage
Normal file
@ -0,0 +1,28 @@
|
||||
#!/bin/bash
|
||||
|
||||
mkdir -p {{app_name}}.AppDir
|
||||
cp -r bundle/deb/{{bundle_name}}/data/usr {{app_name}}.AppDir
|
||||
cp {{app_name}} {{app_name}}.AppDir/AppRun
|
||||
|
||||
cd {{app_name}}.AppDir
|
||||
|
||||
cp usr/share/icons/hicolor/256x265/apps/{{app_name}}.png {{app_name}}.png
|
||||
|
||||
echo '[Desktop Entry]' > {{app_name}}.desktop
|
||||
echo 'Version=1.0' >> {{app_name}}.desktop
|
||||
echo 'Comment=A Tauri App' >> {{app_name}}.desktop
|
||||
echo 'Exec={{app_name}}' >> {{app_name}}.desktop
|
||||
echo 'Icon={{app_name}}' >> {{app_name}}.desktop
|
||||
echo 'Name={{app_name_uppercase}}' >> {{app_name}}.desktop
|
||||
echo 'Terminal=false' >> {{app_name}}.desktop
|
||||
echo 'Type=Application' >> {{app_name}}.desktop
|
||||
echo 'Categories=X-Web;' >> {{app_name}}.desktop
|
||||
|
||||
cp {{app_name}}.desktop {{app_name}}.AppDir/usr/share/applications/{{app_name}}.desktop
|
||||
|
||||
cd ..
|
||||
|
||||
mksquashfs {{app_name}}.AppDir {{app_name}}.squashfs -root-owned -noappend
|
||||
# cat runtime >> {{app_name}}.AppImage
|
||||
cat {{app_name}}.squashfs >> {{app_name}}.AppImage
|
||||
chmod a+x {{app_name}}.AppImage
|
@ -18,7 +18,7 @@ use std::env;
|
||||
use std::process;
|
||||
|
||||
error_chain! {
|
||||
foreign_links {
|
||||
foreign_links {
|
||||
Glob(::glob::GlobError);
|
||||
GlobPattern(::glob::PatternError);
|
||||
Io(::std::io::Error);
|
||||
@ -27,8 +27,9 @@ error_chain! {
|
||||
Term(::term::Error);
|
||||
Toml(::toml::de::Error);
|
||||
Walkdir(::walkdir::Error);
|
||||
StripError(std::path::StripPrefixError);
|
||||
}
|
||||
errors { }
|
||||
errors {}
|
||||
}
|
||||
|
||||
/// Runs `cargo build` to make sure the binary file is up-to-date.
|
||||
@ -133,8 +134,7 @@ fn run() -> crate::Result<()> {
|
||||
if let Some(m) = m.subcommand_matches("tauri-bundle") {
|
||||
if m.is_present("version") {
|
||||
println!("{}", crate_version!());
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
let output_paths = env::current_dir()
|
||||
.map_err(From::from)
|
||||
.and_then(|d| Settings::new(d, m))
|
||||
|
Loading…
Reference in New Issue
Block a user