diff --git a/.changes/updater-new-bundle-support.md b/.changes/updater-new-bundle-support.md new file mode 100644 index 0000000000..99cf2e8893 --- /dev/null +++ b/.changes/updater-new-bundle-support.md @@ -0,0 +1,6 @@ +--- +"updater": minor +"updater-js": minor +--- + +Updater plugin now supports all bundle types: Deb, Rpm and AppImage for Linux; NSiS, MSI for Windows. diff --git a/.gitignore b/.gitignore index 38051f6577..41022b01cf 100644 --- a/.gitignore +++ b/.gitignore @@ -58,4 +58,4 @@ pids .idea debug.log TODO.md -.aider* +.aider.* diff --git a/Cargo.lock b/Cargo.lock index ec2e74e8cc..ccb0977813 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6359,9 +6359,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tauri" -version = "2.8.2" +version = "2.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a54629607ea3084a8b455c1ebe888cbdfc4de02fa5edb2e40db0dc97091007e3" +checksum = "5d545ccf7b60dcd44e07c6fb5aeb09140966f0aabd5d2aa14a6821df7bc99348" dependencies = [ "anyhow", "bytes", @@ -7007,9 +7007,9 @@ dependencies = [ [[package]] name = "tauri-runtime-wry" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bb0f10f831f75832ac74d14d98f701868f9a8adccef2c249b466cf70b607db9" +checksum = "c1fe9d48bd122ff002064e88cfcd7027090d789c4302714e68fcccba0f4b7807" dependencies = [ "gtk", "http", @@ -8789,9 +8789,9 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "wry" -version = "0.53.1" +version = "0.53.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5698e50a589268aec06d2219f48b143222f7b5ad9aa690118b8dce0a8dcac574" +checksum = "e3b6763512fe4b51c80b3ce9b50939d682acb4de335dfabbdb20d7a2642199b7" dependencies = [ "base64 0.22.1", "block2 0.6.0", diff --git a/plugins/updater/src/error.rs b/plugins/updater/src/error.rs index b82e7d551f..c44f986275 100644 --- a/plugins/updater/src/error.rs +++ b/plugins/updater/src/error.rs @@ -39,9 +39,14 @@ pub enum Error { /// `reqwest` crate errors. #[error(transparent)] Reqwest(#[from] reqwest::Error), - /// The platform was not found on the updater JSON response. - #[error("the platform `{0}` was not found on the response `platforms` object")] + /// The platform was not found in the updater JSON response. + #[error("the platform `{0}` was not found in the response `platforms` object")] TargetNotFound(String), + /// Neither the platform nor the fallback platform was found in the updater JSON response. + #[error( + "None of the fallback platforms `{0:?}` were found in the response `platforms` object" + )] + TargetsNotFound(Vec), /// Download failed #[error("`{0}`")] Network(String), @@ -69,6 +74,8 @@ pub enum Error { AuthenticationFailed, #[error("Failed to install .deb package")] DebInstallFailed, + #[error("Failed to install package")] + PackageInstallFailed, #[error("invalid updater binary format")] InvalidUpdaterFormat, #[error(transparent)] diff --git a/plugins/updater/src/updater.rs b/plugins/updater/src/updater.rs index 707c14895c..aae07b0c50 100644 --- a/plugins/updater/src/updater.rs +++ b/plugins/updater/src/updater.rs @@ -26,7 +26,13 @@ use reqwest::{ }; use semver::Version; use serde::{de::Error as DeError, Deserialize, Deserializer, Serialize}; -use tauri::{utils::platform::current_exe, AppHandle, Resource, Runtime}; +use tauri::{ + utils::{ + config::BundleType, + platform::{bundle_type, current_exe}, + }, + AppHandle, Resource, Runtime, +}; use time::OffsetDateTime; use url::Url; @@ -37,6 +43,31 @@ use crate::{ const UPDATER_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),); +#[derive(Copy, Clone)] +pub enum Installer { + AppImage, + Deb, + Rpm, + + App, + + Msi, + Nsis, +} + +impl Installer { + fn name(self) -> &'static str { + match self { + Self::AppImage => "appimage", + Self::Deb => "deb", + Self::Rpm => "rpm", + Self::App => "app", + Self::Msi => "msi", + Self::Nsis => "nsis", + } + } +} + #[derive(Debug, Deserialize, Serialize, Clone)] pub struct ReleaseManifestPlatform { /// Download URL for the platform @@ -265,13 +296,7 @@ impl UpdaterBuilder { return Err(Error::EmptyEndpoints); }; - let arch = get_updater_arch().ok_or(Error::UnsupportedArch)?; - let (target, json_target) = if let Some(target) = self.target { - (target.clone(), target) - } else { - let target = get_updater_target().ok_or(Error::UnsupportedOs)?; - (target.to_string(), format!("{target}-{arch}")) - }; + let arch = updater_arch().ok_or(Error::UnsupportedArch)?; let executable_path = self.executable_path.clone().unwrap_or(current_exe()?); @@ -294,8 +319,7 @@ impl UpdaterBuilder { installer_args: self.installer_args, current_exe_args: self.current_exe_args, arch, - target, - json_target, + target: self.target, headers: self.headers, extract_path, on_before_exit: self.on_before_exit, @@ -327,10 +351,9 @@ pub struct Updater { proxy: Option, endpoints: Vec, arch: &'static str, - // The `{{target}}` variable we replace in the endpoint - target: String, - // The value we search if the updater server returns a JSON with the `platforms` object - json_target: String, + // The `{{target}}` variable we replace in the endpoint and serach for in the JSON, + // this is either the user provided target or the current operating system by default + target: Option, headers: HeaderMap, extract_path: PathBuf, on_before_exit: Option, @@ -359,6 +382,11 @@ impl Updater { std::env::set_var("SSL_CERT_DIR", "/etc/ssl/certs"); } } + let target = if let Some(target) = &self.target { + target + } else { + updater_os().ok_or(Error::UnsupportedOs)? + }; let mut remote_release: Option = None; let mut raw_json: Option = None; @@ -381,11 +409,11 @@ impl Updater { .to_string() // url::Url automatically url-encodes the path components .replace("%7B%7Bcurrent_version%7D%7D", &encoded_version) - .replace("%7B%7Btarget%7D%7D", &self.target) + .replace("%7B%7Btarget%7D%7D", target) .replace("%7B%7Barch%7D%7D", self.arch) // but not query parameters .replace("{{current_version}}", &encoded_version) - .replace("{{target}}", &self.target) + .replace("{{target}}", target) .replace("{{arch}}", self.arch) .parse()?; @@ -466,6 +494,9 @@ impl Updater { None => release.version > self.current_version, }; + let installer = installer_for_bundle_type(bundle_type()); + let (download_url, signature) = self.get_urls(&release, &installer)?; + let update = if should_update { Some(Update { run_on_main_thread: self.run_on_main_thread.clone(), @@ -473,12 +504,12 @@ impl Updater { on_before_exit: self.on_before_exit.clone(), app_name: self.app_name.clone(), current_version: self.current_version.to_string(), - target: self.target.clone(), + target: target.to_owned(), extract_path: self.extract_path.clone(), version: release.version.to_string(), date: release.pub_date, - download_url: release.download_url(&self.json_target)?.to_owned(), - signature: release.signature(&self.json_target)?.to_owned(), + download_url: download_url.clone(), + signature: signature.to_owned(), body: release.notes, raw_json: raw_json.unwrap(), timeout: None, @@ -494,6 +525,38 @@ impl Updater { Ok(update) } + + fn get_urls<'a>( + &self, + release: &'a RemoteRelease, + installer: &Option, + ) -> Result<(&'a Url, &'a String)> { + // Use the user provided target + if let Some(target) = &self.target { + return Ok((release.download_url(target)?, release.signature(target)?)); + } + + // Or else we search for [`{os}-{arch}-{installer}`, `{os}-{arch}`] in order + let os = updater_os().ok_or(Error::UnsupportedOs)?; + let arch = self.arch; + let mut targets = Vec::new(); + if let Some(installer) = installer { + let installer = installer.name(); + targets.push(format!("{os}-{arch}-{installer}")); + } + targets.push(format!("{os}-{arch}")); + + for target in &targets { + log::debug!("Searching for updater target '{target}' in release data"); + if let (Ok(download_url), Ok(signature)) = + (release.download_url(target), release.signature(target)) + { + return Ok((download_url, signature)); + }; + } + + Err(Error::TargetsNotFound(targets)) + } } #[derive(Clone)] @@ -511,7 +574,8 @@ pub struct Update { pub version: String, /// Update publish date pub date: Option, - /// Target + /// The `{{target}}` variable we replace in the endpoint and search for in the JSON, + /// this is either the user provided target or the current operating system by default pub target: String, /// Download URL announced pub download_url: Url, @@ -852,11 +916,10 @@ impl Update { /// └── ... /// fn install_inner(&self, bytes: &[u8]) -> Result<()> { - if self.is_deb_package() { - self.install_deb(bytes) - } else { - // Handle AppImage or other formats - self.install_appimage(bytes) + match installer_for_bundle_type(bundle_type()) { + Some(Installer::Deb) => self.install_deb(bytes), + Some(Installer::Rpm) => self.install_rpm(bytes), + _ => self.install_appimage(bytes), } } @@ -933,39 +996,6 @@ impl Update { Err(Error::TempDirNotOnSameMountPoint) } - fn is_deb_package(&self) -> bool { - // First check if we're in a typical Debian installation path - let in_system_path = self - .extract_path - .to_str() - .map(|p| p.starts_with("/usr")) - .unwrap_or(false); - - if !in_system_path { - return false; - } - - // Then verify it's actually a Debian-based system by checking for dpkg - let dpkg_exists = std::path::Path::new("/var/lib/dpkg").exists(); - let apt_exists = std::path::Path::new("/etc/apt").exists(); - - // Additional check for the package in dpkg database - let package_in_dpkg = if let Ok(output) = std::process::Command::new("dpkg") - .args(["-S", &self.extract_path.to_string_lossy()]) - .output() - { - output.status.success() - } else { - false - }; - - // Consider it a deb package only if: - // 1. We're in a system path AND - // 2. We have Debian package management tools AND - // 3. The binary is tracked by dpkg - dpkg_exists && apt_exists && package_in_dpkg - } - fn install_deb(&self, bytes: &[u8]) -> Result<()> { // First verify the bytes are actually a .deb package if !infer::archive::is_deb(bytes) { @@ -973,6 +1003,18 @@ impl Update { return Err(Error::InvalidUpdaterFormat); } + self.try_tmp_locations(bytes, "dpkg", "-i") + } + + fn install_rpm(&self, bytes: &[u8]) -> Result<()> { + // First verify the bytes are actually a .rpm package + if !infer::archive::is_rpm(bytes) { + return Err(Error::InvalidUpdaterFormat); + } + self.try_tmp_locations(bytes, "rpm", "-U") + } + + fn try_tmp_locations(&self, bytes: &[u8], install_cmd: &str, install_arg: &str) -> Result<()> { // Try different temp directories let tmp_dir_locations = vec![ Box::new(|| Some(std::env::temp_dir())) as Box Option>, @@ -984,15 +1026,19 @@ impl Update { for tmp_dir_location in tmp_dir_locations { if let Some(path) = tmp_dir_location() { if let Ok(tmp_dir) = tempfile::Builder::new() - .prefix("tauri_deb_update") + .prefix("tauri_rpm_update") .tempdir_in(path) { - let deb_path = tmp_dir.path().join("package.deb"); + let pkg_path = tmp_dir.path().join("package.rpm"); // Try writing the .deb file - if std::fs::write(&deb_path, bytes).is_ok() { + if std::fs::write(&pkg_path, bytes).is_ok() { // If write succeeds, proceed with installation - return self.try_install_with_privileges(&deb_path); + return self.try_install_with_privileges( + &pkg_path, + install_cmd, + install_arg, + ); } // If write fails, continue to next temp location } @@ -1003,12 +1049,17 @@ impl Update { Err(Error::TempDirNotFound) } - fn try_install_with_privileges(&self, deb_path: &Path) -> Result<()> { + fn try_install_with_privileges( + &self, + pkg_path: &Path, + install_cmd: &str, + install_arg: &str, + ) -> Result<()> { // 1. First try using pkexec (graphical sudo prompt) if let Ok(status) = std::process::Command::new("pkexec") - .arg("dpkg") - .arg("-i") - .arg(deb_path) + .arg(install_cmd) + .arg(install_arg) + .arg(pkg_path) .status() { if status.success() { @@ -1019,7 +1070,7 @@ impl Update { // 2. Try zenity or kdialog for a graphical sudo experience if let Ok(password) = self.get_password_graphically() { - if self.install_with_sudo(deb_path, &password)? { + if self.install_with_sudo(pkg_path, &password, install_cmd, install_arg)? { log::debug!("installed deb with GUI sudo"); return Ok(()); } @@ -1027,16 +1078,16 @@ impl Update { // 3. Final fallback: terminal sudo let status = std::process::Command::new("sudo") - .arg("dpkg") - .arg("-i") - .arg(deb_path) + .arg(install_cmd) + .arg(install_arg) + .arg(pkg_path) .status()?; if status.success() { log::debug!("installed deb with sudo"); Ok(()) } else { - Err(Error::DebInstallFailed) + Err(Error::PackageInstallFailed) } } @@ -1070,15 +1121,21 @@ impl Update { Err(Error::AuthenticationFailed) } - fn install_with_sudo(&self, deb_path: &Path, password: &str) -> Result { + fn install_with_sudo( + &self, + pkg_path: &Path, + password: &str, + install_cmd: &str, + install_arg: &str, + ) -> Result { use std::io::Write; use std::process::{Command, Stdio}; let mut child = Command::new("sudo") .arg("-S") // read password from stdin - .arg("dpkg") - .arg("-i") - .arg(deb_path) + .arg(install_cmd) + .arg(install_arg) + .arg(pkg_path) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) @@ -1086,7 +1143,7 @@ impl Update { if let Some(mut stdin) = child.stdin.take() { // Write password to stdin - writeln!(stdin, "{}", password)?; + writeln!(stdin, "{password}")?; } let status = child.wait()?; @@ -1199,16 +1256,18 @@ impl Update { } } -/// Gets the target string used on the updater. +/// Gets the base target string used by the updater. If bundle type is available it +/// will be added to this string when selecting the download URL and signature. +/// `tauri::utils::platform::bundle_type` method is used to obtain current bundle type. pub fn target() -> Option { - if let (Some(target), Some(arch)) = (get_updater_target(), get_updater_arch()) { + if let (Some(target), Some(arch)) = (updater_os(), updater_arch()) { Some(format!("{target}-{arch}")) } else { None } } -pub(crate) fn get_updater_target() -> Option<&'static str> { +fn updater_os() -> Option<&'static str> { if cfg!(target_os = "linux") { Some("linux") } else if cfg!(target_os = "macos") { @@ -1221,7 +1280,7 @@ pub(crate) fn get_updater_target() -> Option<&'static str> { } } -pub(crate) fn get_updater_arch() -> Option<&'static str> { +fn updater_arch() -> Option<&'static str> { if cfg!(target_arch = "x86") { Some("i686") } else if cfg!(target_arch = "x86_64") { @@ -1315,6 +1374,18 @@ impl<'de> Deserialize<'de> for RemoteRelease { } } +fn installer_for_bundle_type(bundle: Option) -> Option { + match bundle? { + BundleType::Deb => Some(Installer::Deb), + BundleType::Rpm => Some(Installer::Rpm), + BundleType::AppImage => Some(Installer::AppImage), + BundleType::Msi => Some(Installer::Msi), + BundleType::Nsis => Some(Installer::Nsis), + BundleType::App => Some(Installer::App), // App is also returned for Dmg type + _ => None, + } +} + fn parse_version<'de, D>(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, diff --git a/plugins/updater/tests/app-updater/tauri.conf.json b/plugins/updater/tests/app-updater/tauri.conf.json index f2c6df21be..fc70993ebb 100644 --- a/plugins/updater/tests/app-updater/tauri.conf.json +++ b/plugins/updater/tests/app-updater/tauri.conf.json @@ -2,6 +2,7 @@ "identifier": "com.tauri.updater", "plugins": { "updater": { + "dangerousInsecureTransportProtocol": true, "endpoints": ["http://localhost:3007"], "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEUwNDRGMjkwRjg2MDhCRDAKUldUUWkyRDRrUEpFNEQ4SmdwcU5PaXl6R2ZRUUNvUnhIaVkwVUltV0NMaEx6VTkrWVhpT0ZqeEEK", "windows": { diff --git a/plugins/updater/tests/app-updater/tests/update.rs b/plugins/updater/tests/app-updater/tests/update.rs index d308b31737..f962eff9ec 100644 --- a/plugins/updater/tests/app-updater/tests/update.rs +++ b/plugins/updater/tests/app-updater/tests/update.rs @@ -17,6 +17,7 @@ use tauri::utils::config::{Updater, V1Compatible}; const UPDATER_PRIVATE_KEY: &str = "dW50cnVzdGVkIGNvbW1lbnQ6IHJzaWduIGVuY3J5cHRlZCBzZWNyZXQga2V5ClJXUlRZMEl5TlFOMFpXYzJFOUdjeHJEVXY4WE1TMUxGNDJVUjNrMmk1WlR3UVJVUWwva0FBQkFBQUFBQUFBQUFBQUlBQUFBQUpVK3ZkM3R3eWhyN3hiUXhQb2hvWFVzUW9FbEs3NlNWYjVkK1F2VGFRU1FEaGxuRUtlell5U0gxYS9DbVRrS0YyZVJGblhjeXJibmpZeGJjS0ZKSUYwYndYc2FCNXpHalM3MHcrODMwN3kwUG9SOWpFNVhCSUd6L0E4TGRUT096TEtLR1JwT1JEVFU9Cg=="; const UPDATED_EXIT_CODE: i32 = 0; +const ERROR_EXIT_CODE: i32 = 1; const UP_TO_DATE_EXIT_CODE: i32 = 2; #[derive(Serialize)] @@ -48,7 +49,7 @@ struct Update { fn build_app(cwd: &Path, config: &Config, bundle_updater: bool, target: BundleTarget) { let mut command = Command::new("cargo"); command - .args(["tauri", "build", "--debug", "--verbose"]) + .args(["tauri", "build", "--verbose"]) .arg("--config") .arg(serde_json::to_string(config).unwrap()) .env("TAURI_SIGNING_PRIVATE_KEY", UPDATER_PRIVATE_KEY) @@ -80,6 +81,8 @@ fn build_app(cwd: &Path, config: &Config, bundle_updater: bool, target: BundleTa #[derive(Copy, Clone)] enum BundleTarget { AppImage, + Deb, + Rpm, App, @@ -91,6 +94,8 @@ impl BundleTarget { fn name(self) -> &'static str { match self { Self::AppImage => "appimage", + Self::Deb => "deb", + Self::Rpm => "rpm", Self::App => "app", Self::Msi => "msi", Self::Nsis => "nsis", @@ -109,57 +114,168 @@ impl Default for BundleTarget { } } +fn target_to_platforms( + update_platform: Option, + signature: String, +) -> HashMap { + let mut platforms = HashMap::new(); + if let Some(platform) = update_platform { + platforms.insert( + platform, + PlatformUpdate { + signature, + url: "http://localhost:3007/download", + with_elevated_task: false, + }, + ); + } + + platforms +} + #[cfg(target_os = "linux")] -fn bundle_paths(root_dir: &Path, version: &str) -> Vec<(BundleTarget, PathBuf)> { - vec![( - BundleTarget::AppImage, - root_dir.join(format!( - "target/debug/bundle/appimage/app-updater_{version}_amd64.AppImage" - )), - )] +fn test_cases( + root_dir: &Path, + version: &str, + target: String, +) -> Vec<(BundleTarget, PathBuf, Option, Vec)> { + vec![ + // update using fallback + ( + BundleTarget::AppImage, + root_dir.join(format!( + "target/release/bundle/appimage/app-updater_{version}_amd64.AppImage" + )), + Some(target.clone()), + vec![UPDATED_EXIT_CODE, UP_TO_DATE_EXIT_CODE], + ), + // update using full name + ( + BundleTarget::AppImage, + root_dir.join(format!( + "target/release/bundle/appimage/app-updater_{version}_amd64.AppImage" + )), + Some(format!("{target}-{}", BundleTarget::AppImage.name())), + vec![UPDATED_EXIT_CODE, UP_TO_DATE_EXIT_CODE], + ), + // no update + ( + BundleTarget::AppImage, + root_dir.join(format!( + "target/release/bundle/appimage/app-updater_{version}_amd64.AppImage" + )), + None, + vec![ERROR_EXIT_CODE], + ), + ] } #[cfg(target_os = "macos")] -fn bundle_paths(root_dir: &Path, _version: &str) -> Vec<(BundleTarget, PathBuf)> { - vec![( - BundleTarget::App, - root_dir.join("target/debug/bundle/macos/app-updater.app"), - )] +fn test_cases( + root_dir: &Path, + _version: &str, + target: String, +) -> Vec<(BundleTarget, PathBuf, Option, Vec)> { + vec![ + ( + BundleTarget::App, + root_dir.join("target/release/bundle/macos/app-updater.app"), + Some(target.clone()), + vec![UPDATED_EXIT_CODE, UP_TO_DATE_EXIT_CODE], + ), + // update with installer + ( + BundleTarget::App, + root_dir.join("target/release/bundle/macos/app-updater.app"), + Some(format!("{target}-{}", BundleTarget::App.name())), + vec![UPDATED_EXIT_CODE, UP_TO_DATE_EXIT_CODE], + ), + // no update + ( + BundleTarget::App, + root_dir.join("target/release/bundle/macos/app-updater.app"), + None, + vec![ERROR_EXIT_CODE], + ), + ] } #[cfg(target_os = "ios")] -fn bundle_paths(root_dir: &Path, _version: &str) -> Vec<(BundleTarget, PathBuf)> { +fn bundle_paths( + root_dir: &Path, + _version: &str, + v1compatible: bool, +) -> Vec<(BundleTarget, PathBuf)> { vec![( BundleTarget::App, - root_dir.join("target/debug/bundle/ios/app-updater.ipa"), + root_dir.join("target/release/bundle/ios/app-updater.ipa"), )] } #[cfg(target_os = "android")] -fn bundle_path(root_dir: &Path, _version: &str) -> PathBuf { - root_dir.join("target/debug/bundle/android/app-updater.apk") +fn bundle_path(root_dir: &Path, _version: &str, v1compatible: bool) -> PathBuf { + root_dir.join("target/release/bundle/android/app-updater.apk") } #[cfg(windows)] -fn bundle_paths(root_dir: &Path, version: &str) -> Vec<(BundleTarget, PathBuf)> { +fn test_cases( + root_dir: &Path, + version: &str, + target: String, +) -> Vec<(BundleTarget, PathBuf, Option, Vec)> { vec![ ( BundleTarget::Nsis, root_dir.join(format!( - "target/debug/bundle/nsis/app-updater_{version}_x64-setup.exe" + "target/release/bundle/nsis/app-updater_{version}_x64-setup.exe" )), + Some(target.clone()), + vec![UPDATED_EXIT_CODE], + ), + ( + BundleTarget::Nsis, + root_dir.join(format!( + "target/release/bundle/nsis/app-updater_{version}_x64-setup.exe" + )), + Some(format!("{target}-{}", BundleTarget::Nsis.name())), + vec![UPDATED_EXIT_CODE], + ), + ( + BundleTarget::Nsis, + root_dir.join(format!( + "target/release/bundle/nsis/app-updater_{version}_x64-setup.exe" + )), + None, + vec![ERROR_EXIT_CODE], + ), + ( + BundleTarget::Msi, + root_dir.join(format!( + "target/release/bundle/msi/app-updater_{version}_x64_en-US.msi" + )), + Some(target.clone()), + vec![UPDATED_EXIT_CODE], + ), + ( + BundleTarget::Msi, + root_dir.join(format!( + "target/release/bundle/msi/app-updater_{version}_x64_en-US.msi" + )), + Some(format!("{target}-{}", BundleTarget::Msi.name())), + vec![UPDATED_EXIT_CODE], ), ( BundleTarget::Msi, root_dir.join(format!( - "target/debug/bundle/msi/app-updater_{version}_x64_en-US.msi" + "target/release/bundle/msi/app-updater_{version}_x64_en-US.msi" )), + None, + vec![ERROR_EXIT_CODE], ), ] } #[test] -#[ignore] fn update_app() { let target = tauri_plugin_updater::target().expect("running updater test in an unsupported platform"); @@ -185,9 +301,6 @@ fn update_app() { Updater::String(V1Compatible::V1Compatible) ); - // bundle app update - build_app(&manifest_dir, &config, true, Default::default()); - let updater_zip_ext = if v1_compatible { if cfg!(windows) { Some("zip") @@ -200,7 +313,13 @@ fn update_app() { None }; - for (bundle_target, out_bundle_path) in bundle_paths(&root_dir, "1.0.0") { + for (bundle_target, out_bundle_path, update_platform, status_checks) in + test_cases(&root_dir, "1.0.0", target.clone()) + { + // bundle app update + config.version = "1.0.0"; + build_app(&manifest_dir, &config, true, BundleTarget::default()); + let bundle_updater_ext = if v1_compatible { out_bundle_path .extension() @@ -228,13 +347,11 @@ fn update_app() { }); let out_updater_path = out_bundle_path.with_extension(updater_extension); let updater_path = root_dir.join(format!( - "target/debug/{}", + "target/release/{}", out_updater_path.file_name().unwrap().to_str().unwrap() )); std::fs::rename(&out_updater_path, &updater_path).expect("failed to rename bundle"); - let target = target.clone(); - // start the updater server let server = Arc::new( tiny_http::Server::http("localhost:3007").expect("failed to start updater server"), @@ -245,16 +362,9 @@ fn update_app() { for request in server_.incoming_requests() { match request.url() { "/" => { - let mut platforms = HashMap::new(); - - platforms.insert( - target.clone(), - PlatformUpdate { - signature: signature.clone(), - url: "http://localhost:3007/download", - with_elevated_task: false, - }, - ); + let platforms = + target_to_platforms(update_platform.clone(), signature.clone()); + let body = serde_json::to_vec(&Update { version: "1.0.0", date: time::OffsetDateTime::now_utc() @@ -293,19 +403,12 @@ fn update_app() { // bundle initial app version build_app(&manifest_dir, &config, false, bundle_target); - let status_checks = if matches!(bundle_target, BundleTarget::Msi) { - // for msi we can't really check if the app was updated, because we can't change the install path - vec![UPDATED_EXIT_CODE] - } else { - vec![UPDATED_EXIT_CODE, UP_TO_DATE_EXIT_CODE] - }; - for expected_exit_code in status_checks { let mut binary_cmd = if cfg!(windows) { - Command::new(root_dir.join("target/debug/app-updater.exe")) + Command::new(root_dir.join("target/release/app-updater.exe")) } else if cfg!(target_os = "macos") { Command::new( - bundle_paths(&root_dir, "0.1.0") + test_cases(&root_dir, "0.1.0", target.clone()) .first() .unwrap() .1 @@ -313,11 +416,20 @@ fn update_app() { ) } else if std::env::var("CI").map(|v| v == "true").unwrap_or_default() { let mut c = Command::new("xvfb-run"); - c.arg("--auto-servernum") - .arg(&bundle_paths(&root_dir, "0.1.0").first().unwrap().1); + c.arg("--auto-servernum").arg( + &test_cases(&root_dir, "0.1.0", target.clone()) + .first() + .unwrap() + .1, + ); c } else { - Command::new(&bundle_paths(&root_dir, "0.1.0").first().unwrap().1) + Command::new( + &test_cases(&root_dir, "0.1.0", target.clone()) + .first() + .unwrap() + .1, + ) }; binary_cmd.env("TARGET", bundle_target.name()); @@ -327,7 +439,7 @@ fn update_app() { if code != expected_exit_code { panic!( - "failed to run app, expected exit code {expected_exit_code}, got {code}" + "failed to run app bundled as {}, expected exit code {expected_exit_code}, got {code}", bundle_target.name() ); } #[cfg(windows)]