diff --git a/crates/tauri-bundler/src/bundle/linux/appimage/linuxdeploy.rs b/crates/tauri-bundler/src/bundle/linux/appimage/linuxdeploy.rs new file mode 100644 index 000000000..dccc3b5a2 --- /dev/null +++ b/crates/tauri-bundler/src/bundle/linux/appimage/linuxdeploy.rs @@ -0,0 +1,282 @@ +// Copyright 2016-2019 Cargo-Bundle developers +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use super::{super::debian, write_and_make_executable}; +use crate::{ + bundle::settings::Arch, + error::{Context, ErrorExt}, + utils::{fs_utils, http_utils::download, CommandExt}, + Settings, +}; +use std::{ + fs, + path::{Path, PathBuf}, + process::Command, +}; + +/// Bundles the project. +/// Returns a vector of PathBuf that shows where the AppImage was created. +pub fn bundle_project(settings: &Settings) -> crate::Result> { + // generate the deb binary name + let appimage_arch: &str = match settings.binary_arch() { + Arch::X86_64 => "amd64", + Arch::X86 => "i386", + Arch::AArch64 => "aarch64", + Arch::Armhf => "armhf", + target => { + return Err(crate::Error::ArchError(format!( + "Unsupported architecture: {target:?}" + ))); + } + }; + + let tools_arch = if settings.binary_arch() == Arch::Armhf { + "armhf" + } else { + settings.target().split('-').next().unwrap() + }; + + let output_path = settings.project_out_directory().join("bundle/appimage"); + if output_path.exists() { + fs::remove_dir_all(&output_path)?; + } + + let tools_path = settings + .local_tools_directory() + .map(|d| d.join(".tauri")) + .unwrap_or_else(|| { + dirs::cache_dir().map_or_else(|| output_path.to_path_buf(), |p| p.join("tauri")) + }); + + fs::create_dir_all(&tools_path)?; + + let linuxdeploy_path = prepare_tools( + &tools_path, + tools_arch, + settings.log_level() != log::Level::Error, + )?; + + let package_dir = settings.project_out_directory().join("bundle/appimage_deb"); + + let main_binary = settings.main_binary()?; + let product_name = settings.product_name(); + + let mut settings = settings.clone(); + if main_binary.name().contains(' ') { + let main_binary_path = settings.binary_path(main_binary); + let project_out_directory = settings.project_out_directory(); + + let main_binary_name_kebab = heck::AsKebabCase(main_binary.name()).to_string(); + let new_path = project_out_directory.join(&main_binary_name_kebab); + fs::copy(main_binary_path, new_path)?; + + let main_binary = settings.main_binary_mut()?; + main_binary.set_name(main_binary_name_kebab); + } + + // generate deb_folder structure + let (data_dir, icons) = debian::generate_data(&settings, &package_dir) + .with_context(|| "Failed to build data folders and files")?; + fs_utils::copy_custom_files(&settings.appimage().files, &data_dir) + .with_context(|| "Failed to copy custom files")?; + + fs::create_dir_all(&output_path)?; + let app_dir_path = output_path.join(format!("{}.AppDir", settings.product_name())); + let appimage_filename = format!( + "{}_{}_{}.AppImage", + settings.product_name(), + settings.version_string(), + appimage_arch + ); + let appimage_path = output_path.join(&appimage_filename); + fs_utils::create_dir(&app_dir_path, true)?; + + fs::create_dir_all(&tools_path)?; + let larger_icon = icons + .iter() + .filter(|i| i.width == i.height) + .max_by_key(|i| i.width) + .expect("couldn't find a square icon to use as AppImage icon"); + let larger_icon_path = larger_icon + .path + .strip_prefix(package_dir.join("data")) + .unwrap() + .to_string_lossy() + .to_string(); + + log::info!(action = "Bundling"; "{} ({})", appimage_filename, appimage_path.display()); + + let app_dir_usr = app_dir_path.join("usr/"); + let app_dir_usr_bin = app_dir_usr.join("bin/"); + let app_dir_usr_lib = app_dir_usr.join("lib/"); + + fs_utils::copy_dir(&data_dir.join("usr/"), &app_dir_usr)?; + + // Using create_dir_all for a single dir so we don't get errors if the path already exists + fs::create_dir_all(&app_dir_usr_bin)?; + fs::create_dir_all(app_dir_usr_lib)?; + + // Copy bins and libs that linuxdeploy doesn't know about + + // we also check if the user may have provided their own copy already + // xdg-open will be handled by the `files` config instead + if settings.deep_link_protocols().is_some() && !app_dir_usr_bin.join("xdg-open").exists() { + fs::copy("/usr/bin/xdg-mime", app_dir_usr_bin.join("xdg-mime")) + .fs_context("xdg-mime binary not found", "/usr/bin/xdg-mime".to_string())?; + } + + // we also check if the user may have provided their own copy already + if settings.appimage().bundle_xdg_open && !app_dir_usr_bin.join("xdg-open").exists() { + fs::copy("/usr/bin/xdg-open", app_dir_usr_bin.join("xdg-open")) + .fs_context("xdg-open binary not found", "/usr/bin/xdg-open".to_string())?; + } + + let search_dirs = [ + match settings.binary_arch() { + Arch::X86_64 => "/usr/lib/x86_64-linux-gnu/", + Arch::X86 => "/usr/lib/i386-linux-gnu/", + Arch::AArch64 => "/usr/lib/aarch64-linux-gnu/", + Arch::Armhf => "/usr/lib/arm-linux-gnueabihf/", + _ => unreachable!(), + }, + "/usr/lib64", + "/usr/lib", + "/usr/libexec", + ]; + + for file in [ + "WebKitNetworkProcess", + "WebKitWebProcess", + "injected-bundle/libwebkit2gtkinjectedbundle.so", + ] { + for source in search_dirs.map(PathBuf::from) { + // TODO: Check if it's the same dir name on all systems + let source = source.join("webkit2gtk-4.1").join(file); + if source.exists() { + fs_utils::copy_file( + &source, + &app_dir_path.join(source.strip_prefix("/").unwrap()), + )?; + } + } + } + + fs::copy( + tools_path.join(format!("AppRun-{tools_arch}")), + app_dir_path.join("AppRun"), + )?; + fs::copy( + app_dir_path.join(larger_icon_path), + app_dir_path.join(format!("{product_name}.png")), + )?; + std::os::unix::fs::symlink( + app_dir_path.join(format!("{product_name}.png")), + app_dir_path.join(".DirIcon"), + )?; + std::os::unix::fs::symlink( + app_dir_path.join(format!("usr/share/applications/{product_name}.desktop")), + app_dir_path.join(format!("{product_name}.desktop")), + )?; + + let log_level = match settings.log_level() { + log::Level::Error => "3", + log::Level::Warn => "2", + log::Level::Info => "1", + _ => "0", + }; + + let mut cmd = Command::new(linuxdeploy_path); + cmd.env("OUTPUT", &appimage_path); + cmd.env("ARCH", tools_arch); + // Looks like the cli arg isn't enough for the updated AppImage output-plugin. + cmd.env("APPIMAGE_EXTRACT_AND_RUN", "1"); + cmd.args([ + "--appimage-extract-and-run", + "--verbosity", + log_level, + "--appdir", + &app_dir_path.display().to_string(), + "--plugin", + "gtk", + ]); + if settings.appimage().bundle_media_framework { + cmd.args(["--plugin", "gstreamer"]); + } + cmd.args(["--output", "appimage"]); + + // Linuxdeploy logs everything into stderr so we have to ignore the output ourselves here + if settings.log_level() == log::Level::Error { + log::debug!(action = "Running"; "Command `linuxdeploy {}`", cmd.get_args().map(|arg| arg.to_string_lossy()).fold(String::new(), |acc, arg| format!("{acc} {arg}"))); + if !cmd.output()?.status.success() { + return Err(crate::Error::GenericError( + "failed to run linuxdeploy".to_string(), + )); + } + } else { + cmd.output_ok()?; + } + + fs::remove_dir_all(&package_dir)?; + Ok(vec![appimage_path]) +} + +// returns the linuxdeploy path to keep linuxdeploy_arch contained +fn prepare_tools(tools_path: &Path, arch: &str, verbose: bool) -> crate::Result { + let apprun = tools_path.join(format!("AppRun-{arch}")); + if !apprun.exists() { + let data = download(&format!( + "https://github.com/tauri-apps/binary-releases/releases/download/apprun-old/AppRun-{arch}" + ))?; + write_and_make_executable(&apprun, data)?; + } + + let linuxdeploy_arch = if arch == "i686" { "i383" } else { arch }; + let linuxdeploy = tools_path.join(format!("linuxdeploy-{linuxdeploy_arch}.AppImage")); + if !linuxdeploy.exists() { + let data = download(&format!("https://github.com/tauri-apps/binary-releases/releases/download/linuxdeploy/linuxdeploy-{linuxdeploy_arch}.AppImage"))?; + write_and_make_executable(&linuxdeploy, data)?; + } + + let gtk = tools_path.join("linuxdeploy-plugin-gtk.sh"); + if !gtk.exists() { + let data = download("https://raw.githubusercontent.com/tauri-apps/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh")?; + write_and_make_executable(>k, data)?; + } + + let gstreamer = tools_path.join("linuxdeploy-plugin-gstreamer.sh"); + if !gstreamer.exists() { + let data = download("https://raw.githubusercontent.com/tauri-apps/linuxdeploy-plugin-gstreamer/master/linuxdeploy-plugin-gstreamer.sh")?; + write_and_make_executable(&gstreamer, data)?; + } + + let appimage = tools_path.join("linuxdeploy-plugin-appimage.AppImage"); + if !appimage.exists() { + // This is optional, linuxdeploy will fall back to its built-in version if the download failed. + let data = download(&format!("https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-{arch}.AppImage")); + match data { + Ok(data) => write_and_make_executable(&appimage, data)?, + Err(err) => { + log::error!("Download of AppImage plugin failed. Using older built-in version instead."); + if verbose { + log::debug!("{err:?}"); + } + } + } + } + + // This should prevent linuxdeploy to be detected by appimage integration tools + let _ = Command::new("dd") + .args([ + "if=/dev/zero", + "bs=1", + "count=3", + "seek=8", + "conv=notrunc", + &format!("of={}", linuxdeploy.display()), + ]) + .output(); + + Ok(linuxdeploy) +} diff --git a/crates/tauri-bundler/src/bundle/linux/appimage/mod.rs b/crates/tauri-bundler/src/bundle/linux/appimage/mod.rs index 29eb87bee..57ff90969 100644 --- a/crates/tauri-bundler/src/bundle/linux/appimage/mod.rs +++ b/crates/tauri-bundler/src/bundle/linux/appimage/mod.rs @@ -1,287 +1,27 @@ -// Copyright 2016-2019 Cargo-Bundle developers // Copyright 2019-2024 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use super::debian; -use crate::{ - bundle::settings::Arch, - error::{Context, ErrorExt}, - utils::{fs_utils, http_utils::download, CommandExt}, - Settings, -}; use std::{ fs, path::{Path, PathBuf}, - process::Command, }; -/// Bundles the project. -/// Returns a vector of PathBuf that shows where the AppImage was created. +use crate::Settings; + +mod linuxdeploy; +mod sharun_cef; + +// TODO: Consider auto fallback to linuxdeploy on unsupported systems. pub fn bundle_project(settings: &Settings) -> crate::Result> { - // generate the deb binary name - let appimage_arch: &str = match settings.binary_arch() { - Arch::X86_64 => "amd64", - Arch::X86 => "i386", - Arch::AArch64 => "aarch64", - Arch::Armhf => "armhf", - target => { - return Err(crate::Error::ArchError(format!( - "Unsupported architecture: {target:?}" - ))); - } - }; - - let tools_arch = if settings.binary_arch() == Arch::Armhf { - "armhf" + if settings.bundle_settings().cef_path.is_some() { + sharun_cef::bundle_project(settings) } else { - settings.target().split('-').next().unwrap() - }; - - let output_path = settings.project_out_directory().join("bundle/appimage"); - if output_path.exists() { - fs::remove_dir_all(&output_path)?; + linuxdeploy::bundle_project(settings) } - - let tools_path = settings - .local_tools_directory() - .map(|d| d.join(".tauri")) - .unwrap_or_else(|| { - dirs::cache_dir().map_or_else(|| output_path.to_path_buf(), |p| p.join("tauri")) - }); - - fs::create_dir_all(&tools_path)?; - - let linuxdeploy_path = prepare_tools( - &tools_path, - tools_arch, - settings.log_level() != log::Level::Error, - )?; - - let package_dir = settings.project_out_directory().join("bundle/appimage_deb"); - - let main_binary = settings.main_binary()?; - let product_name = settings.product_name(); - - let mut settings = settings.clone(); - if main_binary.name().contains(' ') { - let main_binary_path = settings.binary_path(main_binary); - let project_out_directory = settings.project_out_directory(); - - let main_binary_name_kebab = heck::AsKebabCase(main_binary.name()).to_string(); - let new_path = project_out_directory.join(&main_binary_name_kebab); - fs::copy(main_binary_path, new_path)?; - - let main_binary = settings.main_binary_mut()?; - main_binary.set_name(main_binary_name_kebab); - } - - // generate deb_folder structure - let (data_dir, icons) = debian::generate_data(&settings, &package_dir) - .with_context(|| "Failed to build data folders and files")?; - fs_utils::copy_custom_files(&settings.appimage().files, &data_dir) - .with_context(|| "Failed to copy custom files")?; - - fs::create_dir_all(&output_path)?; - let app_dir_path = output_path.join(format!("{}.AppDir", settings.product_name())); - let appimage_filename = format!( - "{}_{}_{}.AppImage", - settings.product_name(), - settings.version_string(), - appimage_arch - ); - let appimage_path = output_path.join(&appimage_filename); - fs_utils::create_dir(&app_dir_path, true)?; - - fs::create_dir_all(&tools_path)?; - let larger_icon = icons - .iter() - .filter(|i| i.width == i.height) - .max_by_key(|i| i.width) - .expect("couldn't find a square icon to use as AppImage icon"); - let larger_icon_path = larger_icon - .path - .strip_prefix(package_dir.join("data")) - .unwrap() - .to_string_lossy() - .to_string(); - - log::info!(action = "Bundling"; "{} ({})", appimage_filename, appimage_path.display()); - - let app_dir_usr = app_dir_path.join("usr/"); - let app_dir_usr_bin = app_dir_usr.join("bin/"); - let app_dir_usr_lib = app_dir_usr.join("lib/"); - - fs_utils::copy_dir(&data_dir.join("usr/"), &app_dir_usr)?; - - // Using create_dir_all for a single dir so we don't get errors if the path already exists - fs::create_dir_all(&app_dir_usr_bin)?; - fs::create_dir_all(app_dir_usr_lib)?; - - // Copy bins and libs that linuxdeploy doesn't know about - - // we also check if the user may have provided their own copy already - // xdg-open will be handled by the `files` config instead - if settings.deep_link_protocols().is_some() && !app_dir_usr_bin.join("xdg-open").exists() { - fs::copy("/usr/bin/xdg-mime", app_dir_usr_bin.join("xdg-mime")) - .fs_context("xdg-mime binary not found", "/usr/bin/xdg-mime".to_string())?; - } - - // we also check if the user may have provided their own copy already - if settings.appimage().bundle_xdg_open && !app_dir_usr_bin.join("xdg-open").exists() { - fs::copy("/usr/bin/xdg-open", app_dir_usr_bin.join("xdg-open")) - .fs_context("xdg-open binary not found", "/usr/bin/xdg-open".to_string())?; - } - - let search_dirs = [ - match settings.binary_arch() { - Arch::X86_64 => "/usr/lib/x86_64-linux-gnu/", - Arch::X86 => "/usr/lib/i386-linux-gnu/", - Arch::AArch64 => "/usr/lib/aarch64-linux-gnu/", - Arch::Armhf => "/usr/lib/arm-linux-gnueabihf/", - _ => unreachable!(), - }, - "/usr/lib64", - "/usr/lib", - "/usr/libexec", - ]; - - for file in [ - "WebKitNetworkProcess", - "WebKitWebProcess", - "injected-bundle/libwebkit2gtkinjectedbundle.so", - ] { - for source in search_dirs.map(PathBuf::from) { - // TODO: Check if it's the same dir name on all systems - let source = source.join("webkit2gtk-4.1").join(file); - if source.exists() { - fs_utils::copy_file( - &source, - &app_dir_path.join(source.strip_prefix("/").unwrap()), - )?; - } - } - } - - fs::copy( - tools_path.join(format!("AppRun-{tools_arch}")), - app_dir_path.join("AppRun"), - )?; - fs::copy( - app_dir_path.join(larger_icon_path), - app_dir_path.join(format!("{product_name}.png")), - )?; - std::os::unix::fs::symlink( - app_dir_path.join(format!("{product_name}.png")), - app_dir_path.join(".DirIcon"), - )?; - std::os::unix::fs::symlink( - app_dir_path.join(format!("usr/share/applications/{product_name}.desktop")), - app_dir_path.join(format!("{product_name}.desktop")), - )?; - - let log_level = match settings.log_level() { - log::Level::Error => "3", - log::Level::Warn => "2", - log::Level::Info => "1", - _ => "0", - }; - - let mut cmd = Command::new(linuxdeploy_path); - cmd.env("OUTPUT", &appimage_path); - cmd.env("ARCH", tools_arch); - // Looks like the cli arg isn't enough for the updated AppImage output-plugin. - cmd.env("APPIMAGE_EXTRACT_AND_RUN", "1"); - cmd.args([ - "--appimage-extract-and-run", - "--verbosity", - log_level, - "--appdir", - &app_dir_path.display().to_string(), - "--plugin", - "gtk", - ]); - if settings.appimage().bundle_media_framework { - cmd.args(["--plugin", "gstreamer"]); - } - cmd.args(["--output", "appimage"]); - - // Linuxdeploy logs everything into stderr so we have to ignore the output ourselves here - if settings.log_level() == log::Level::Error { - log::debug!(action = "Running"; "Command `linuxdeploy {}`", cmd.get_args().map(|arg| arg.to_string_lossy()).fold(String::new(), |acc, arg| format!("{acc} {arg}"))); - if !cmd.output()?.status.success() { - return Err(crate::Error::GenericError( - "failed to run linuxdeploy".to_string(), - )); - } - } else { - cmd.output_ok()?; - } - - fs::remove_dir_all(&package_dir)?; - Ok(vec![appimage_path]) } -// returns the linuxdeploy path to keep linuxdeploy_arch contained -fn prepare_tools(tools_path: &Path, arch: &str, verbose: bool) -> crate::Result { - let apprun = tools_path.join(format!("AppRun-{arch}")); - if !apprun.exists() { - let data = download(&format!( - "https://github.com/tauri-apps/binary-releases/releases/download/apprun-old/AppRun-{arch}" - ))?; - write_and_make_executable(&apprun, &data)?; - } - - let linuxdeploy_arch = if arch == "i686" { "i386" } else { arch }; - let linuxdeploy = tools_path.join(format!("linuxdeploy-{linuxdeploy_arch}.AppImage")); - if !linuxdeploy.exists() { - let data = download(&format!("https://github.com/tauri-apps/binary-releases/releases/download/linuxdeploy/linuxdeploy-{linuxdeploy_arch}.AppImage"))?; - write_and_make_executable(&linuxdeploy, &data)?; - } - - let gtk = tools_path.join("linuxdeploy-plugin-gtk.sh"); - if !gtk.exists() { - let data = include_bytes!("./linuxdeploy-plugin-gtk.sh"); - write_and_make_executable(>k, data)?; - } - - let gstreamer = tools_path.join("linuxdeploy-plugin-gstreamer.sh"); - if !gstreamer.exists() { - let data = include_bytes!("./linuxdeploy-plugin-gstreamer.sh"); - write_and_make_executable(&gstreamer, data)?; - } - - let appimage = tools_path.join("linuxdeploy-plugin-appimage.AppImage"); - if !appimage.exists() { - // This is optional, linuxdeploy will fall back to its built-in version if the download failed. - let data = download(&format!("https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-{arch}.AppImage")); - match data { - Ok(data) => write_and_make_executable(&appimage, &data)?, - Err(err) => { - log::error!("Download of AppImage plugin failed. Using older built-in version instead."); - if verbose { - log::debug!("{err:?}"); - } - } - } - } - - // This should prevent linuxdeploy to be detected by appimage integration tools - let _ = Command::new("dd") - .args([ - "if=/dev/zero", - "bs=1", - "count=3", - "seek=8", - "conv=notrunc", - &format!("of={}", linuxdeploy.display()), - ]) - .output(); - - Ok(linuxdeploy) -} - -fn write_and_make_executable(path: &Path, data: &[u8]) -> std::io::Result<()> { +fn write_and_make_executable(path: &Path, data: Vec) -> std::io::Result<()> { use std::os::unix::fs::PermissionsExt; fs::write(path, data)?; diff --git a/crates/tauri-bundler/src/bundle/linux/appimage/sharun_cef.rs b/crates/tauri-bundler/src/bundle/linux/appimage/sharun_cef.rs new file mode 100644 index 000000000..86dc49628 --- /dev/null +++ b/crates/tauri-bundler/src/bundle/linux/appimage/sharun_cef.rs @@ -0,0 +1,203 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::{fs, path::PathBuf, process::Command}; + +use anyhow::Context; + +use crate::{ + bundle::{linux::debian, settings::Arch}, + utils::{fs_utils, http_utils::download, CommandExt}, + Settings, +}; + +use super::write_and_make_executable; + +// TODO: Test if bundling xdg-mime makes sense (eg does it even work if it's not on the host system?) +// TODO: Monitor TLS support / certificates - seems to be working in initial tests +pub fn bundle_project(settings: &Settings) -> crate::Result> { + // for backwards compat we keep the amd64 and i386 rewrites in the filename + let appimage_arch = match settings.binary_arch() { + Arch::X86_64 => "amd64", + //Arch::X86 => "i386", + Arch::AArch64 => "aarch64", + //Arch::Armhf => "armhf", + target => { + return Err(crate::Error::ArchError(format!( + "Unsupported architecture: {target:?}" + ))); + } + }; + //let tools_arch = settings.target().split('-').next().unwrap(); + + let output_path = settings.project_out_directory().join("bundle/appimage"); + if output_path.exists() { + fs::remove_dir_all(&output_path)?; + } + + let tools_path = settings + .local_tools_directory() + .map(|d| d.join(".tauri")) + .unwrap_or_else(|| { + dirs::cache_dir().map_or_else(|| output_path.to_path_buf(), |p| p.join("tauri")) + }); + + fs::create_dir_all(&tools_path)?; + + // TODO: mirror + let quick_sharun = tools_path.join("quick-sharun.sh"); + if !quick_sharun.exists() { + let data = download("https://raw.githubusercontent.com/pkgforge-dev/Anylinux-AppImages/refs/heads/main/useful-tools/quick-sharun.sh")?; + write_and_make_executable(&quick_sharun, data)?; + } + + let package_dir = settings + .project_out_directory() + .join("bundle/appimage_deb/"); + + let main_binary = settings.main_binary()?; + let product_name = settings.product_name(); + + let mut settings = settings.clone(); + if main_binary.name().contains(' ') { + let main_binary_path = settings.binary_path(main_binary); + let project_out_dir = settings.project_out_directory(); + + let main_binary_name_kebab = heck::AsKebabCase(main_binary.name()).to_string(); + let new_path = project_out_dir.join(&main_binary_name_kebab); + fs::copy(main_binary_path, new_path)?; + + let main_binary = settings.main_binary_mut()?; + main_binary.set_name(main_binary_name_kebab); + } + + // generate deb_folder structure + let (data_dir, icons) = debian::generate_data(&settings, &package_dir) + .with_context(|| "Failed to build data folders and files")?; + fs_utils::copy_custom_files(&settings.appimage().files, &data_dir) + .with_context(|| "Failed to copy custom files")?; + + fs::create_dir_all(data_dir.join("usr/bin/"))?; + fs::create_dir_all(data_dir.join("usr/lib/"))?; + fs::create_dir_all(data_dir.join("usr/lib/locales"))?; + + let cef_path = settings + .bundle_settings() + .cef_path + .clone() + .expect("this module is only called when cef_path is set"); + + let cef_files = [ + // required + "libcef.so", + "icudtl.dat", + "v8_context_snapshot.bin", + // required end + // "optional" - but not really since we want support for all of this + "chrome_100_percent.pak", + "chrome_200_percent.pak", + "resources.pak", + // ANGEL support + "libEGL.so", + "libGLESv2.so", + // SwANGLE support + "libvk_swiftshader.so", + "vk_swiftshader_icd.json", + "libvulkan.so.1", + // sandbox - may need to be behind a setting? + "chrome-sandbox", + // TODO: seccomp + ]; + + for f in cef_files { + let dest = if f == "chrome-sandbox" { + data_dir.join("usr/bin/").join(f) + } else { + data_dir.join("usr/lib/").join(f) + }; + fs::copy(cef_path.join(f), &dest)?; + let _ = Command::new("strip").arg(&dest).output_ok(); + } + let locales = [ + "en-US.pak", + "en-US_FEMININE.pak", + "en-US_MASCULINE.pak", + "en-US_NEUTER.pak", + ]; + + for f in locales { + fs::copy( + cef_path.join("locales").join(f), + data_dir.join("usr/lib/locales").join(f), + )?; + } + + fs::create_dir_all(&output_path)?; + let app_dir_path = output_path.join(format!("{}.AppDir", settings.product_name())); + let appimage_filename = format!( + "{}_{}_{appimage_arch}.AppImage", + settings.product_name(), + settings.version_string() + ); + let appimage_path = output_path.join(&appimage_filename); + + fs::create_dir_all(&tools_path)?; + let larger_icon = icons + .iter() + .filter(|i| i.width == i.height) + .max_by_key(|i| i.width) + .expect("couldn't find a square icon to use as AppImage icon"); + + log::info!(action = "Bundling"; "{} ({})", appimage_filename, appimage_path.display()); + + // TODO: + let _verbosity = match settings.log_level() { + log::Level::Error => "-q", // errors only + log::Level::Info => "", // errors + "normal logs" (mostly rpath) + log::Level::Trace => "-v", // You can expect way over 1k lines from just lib4bin on this level + _ => "", + }; + + let bins = settings.copy_binaries(&app_dir_path.join("usr/bin/"))?; + let bins = bins + .iter() + .map(|b| format!(" \"{}\"", b.to_string_lossy())) + .collect::(); + + // TODO: Consider to not rely on quick-sharun when we have more time + Command::new("/bin/sh") + .current_dir(&output_path) + .env("APPDIR", &app_dir_path) + .env("OUTNAME", &appimage_filename) + .env( + "DESKTOP", + data_dir.join(format!("usr/share/applications/{product_name}.desktop")), + ) + .env("ICON", &larger_icon.path) + .env("OUTPUT_APPIMAGE", "1") + .env("URUNTIME2APPIMAGE_SOURCE", "https://raw.githubusercontent.com/FabianLars/Anylinux-AppImages/refs/heads/main/useful-tools/uruntime2appimage.sh") + .args([ + "-c", + &format!( + r#""{}" "{}" {bins} "{}" "{}""#, + quick_sharun.to_string_lossy(), + data_dir + .join(format!("usr/bin/{}", main_binary.name())) + .to_string_lossy(), + // TODO: This may have to be in lib instead + data_dir.join("usr/bin/chrome-sandbox").to_string_lossy(), + data_dir.join("usr/lib/").to_string_lossy() + ), + ]) + .output_ok() + .context("quick-sharun command failed to run.")?; + + { + use std::os::unix::fs::PermissionsExt; + fs::set_permissions(&appimage_path, fs::Permissions::from_mode(0o770)).expect("perms"); + } + + fs::remove_dir_all(package_dir).expect("rmdir"); + Ok(vec![appimage_path]) +}