From 5dc506c65a2714601b30002c55733f4f48fbf035 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Fri, 15 Jul 2022 01:53:30 +0200 Subject: [PATCH 01/46] feat(bundler): add `nsis`, closes #4450, closes #2319 --- core/tauri-utils/src/config.rs | 33 ++- tooling/bundler/Cargo.toml | 1 + tooling/bundler/src/bundle.rs | 4 +- tooling/bundler/src/bundle/settings.rs | 33 ++- tooling/bundler/src/bundle/windows/mod.rs | 2 + tooling/bundler/src/bundle/windows/msi/wix.rs | 112 +--------- tooling/bundler/src/bundle/windows/nsis.rs | 209 ++++++++++++++++++ .../bundle/windows/templates/installer.nsi | 173 +++++++++++++++ tooling/bundler/src/bundle/windows/util.rs | 177 +++++++++++++++ tooling/cli/Cargo.lock | 1 + tooling/cli/schema.json | 57 ++++- tooling/cli/src/helpers/config.rs | 10 + tooling/cli/src/interface/rust.rs | 3 +- 13 files changed, 707 insertions(+), 108 deletions(-) create mode 100644 tooling/bundler/src/bundle/windows/nsis.rs create mode 100644 tooling/bundler/src/bundle/windows/templates/installer.nsi create mode 100644 tooling/bundler/src/bundle/windows/util.rs diff --git a/core/tauri-utils/src/config.rs b/core/tauri-utils/src/config.rs index 2730b418d28..0bc69405792 100644 --- a/core/tauri-utils/src/config.rs +++ b/core/tauri-utils/src/config.rs @@ -76,6 +76,8 @@ pub enum BundleType { AppImage, /// The Microsoft Installer bundle (.msi). Msi, + /// The NSIS bundle (.exe). + Nsis, /// The macOS application bundle (.app). App, /// The Apple Disk Image bundle (.dmg). @@ -93,6 +95,7 @@ impl Display for BundleType { Self::Deb => "deb", Self::AppImage => "appimage", Self::Msi => "msi", + Self::Nsis => "nsis", Self::App => "app", Self::Dmg => "dmg", Self::Updater => "updater", @@ -120,6 +123,7 @@ impl<'de> Deserialize<'de> for BundleType { "deb" => Ok(Self::Deb), "appimage" => Ok(Self::AppImage), "msi" => Ok(Self::Msi), + "nsis" => Ok(Self::Nsis), "app" => Ok(Self::App), "dmg" => Ok(Self::Dmg), "updater" => Ok(Self::Updater), @@ -407,6 +411,30 @@ pub struct WixConfig { pub dialog_image_path: Option, } +/// Configuration for the Installer bundle using NSIS. +#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)] +#[cfg_attr(feature = "schema", derive(JsonSchema))] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct NsisConfig { + /// The path to the license file to render on the installer. + /// + /// Must be an RTF file, so if a different extension is provided, we convert it to the RTF format. + pub license: Option, + /// The path to a bitmap file to display on the header of installers pages. + /// + /// The recommended dimensions are 150pxx57px. + pub header_image: Option, + /// The path to a bitmap file for the Welcome page and the Finish page. + /// + /// The recommended dimensions are 164pxx314px. + pub sidebar_image: Option, + /// The path to an icon file used as the installer icon. + pub installer_icon: Option, + /// Whether the installation will be for all users or just the current user. + #[serde(default)] + pub per_machine: bool, +} + /// Install modes for the Webview2 runtime. /// Note that for the updater bundle [`Self::DownloadBootstrapper`] is used. /// @@ -499,6 +527,8 @@ pub struct WindowsConfig { pub allow_downgrades: bool, /// Configuration for the MSI generated with WiX. pub wix: Option, + /// Configuration for the installer generated with NSIS. + pub nsis: Option, } impl Default for WindowsConfig { @@ -512,6 +542,7 @@ impl Default for WindowsConfig { webview_fixed_runtime_path: None, allow_downgrades: default_allow_downgrades(), wix: None, + nsis: None, } } } @@ -529,7 +560,7 @@ pub struct BundleConfig { /// Whether Tauri should bundle your application or just output the executable. #[serde(default)] pub active: bool, - /// The bundle targets, currently supports ["deb", "appimage", "msi", "app", "dmg", "updater"] or "all". + /// The bundle targets, currently supports ["deb", "appimage", "nsis", "msi", "app", "dmg", "updater"] or "all". #[serde(default)] pub targets: BundleTarget, /// The application identifier in reverse domain name notation (e.g. `com.tauri.example`). diff --git a/tooling/bundler/Cargo.toml b/tooling/bundler/Cargo.toml index b44938b5407..5a046072a51 100644 --- a/tooling/bundler/Cargo.toml +++ b/tooling/bundler/Cargo.toml @@ -44,6 +44,7 @@ uuid = { version = "1", features = [ "v4", "v5" ] } bitness = "0.4" winreg = "0.10" sha2 = "0.10" +sha1 = "0.10" hex = "0.4" glob = "0.3" zip = "0.6" diff --git a/tooling/bundler/src/bundle.rs b/tooling/bundler/src/bundle.rs index 998fc5b7087..42734debb0b 100644 --- a/tooling/bundler/src/bundle.rs +++ b/tooling/bundler/src/bundle.rs @@ -23,7 +23,7 @@ pub use self::{ }, }; use log::{info, warn}; -pub use settings::{WindowsSettings, WixLanguage, WixLanguageConfig, WixSettings}; +pub use settings::{NsisSettings, WindowsSettings, WixLanguage, WixLanguageConfig, WixSettings}; use std::{fmt::Write, path::PathBuf}; @@ -50,6 +50,8 @@ pub fn bundle_project(settings: Settings) -> crate::Result> { PackageType::IosBundle => macos::ios::bundle_project(&settings)?, #[cfg(target_os = "windows")] PackageType::WindowsMsi => windows::msi::bundle_project(&settings, false)?, + #[cfg(target_os = "windows")] + PackageType::Nsis => windows::nsis::bundle_project(&settings)?, #[cfg(target_os = "linux")] PackageType::Deb => linux::debian::bundle_project(&settings)?, #[cfg(target_os = "linux")] diff --git a/tooling/bundler/src/bundle/settings.rs b/tooling/bundler/src/bundle/settings.rs index 2a8809df6f6..ba09d1fb594 100644 --- a/tooling/bundler/src/bundle/settings.rs +++ b/tooling/bundler/src/bundle/settings.rs @@ -25,6 +25,8 @@ pub enum PackageType { IosBundle, /// The Windows bundle (.msi). WindowsMsi, + /// The NSIS bundle (.exe). + Nsis, /// The Linux Debian package bundle (.deb). Deb, /// The Linux RPM bundle (.rpm). @@ -43,6 +45,7 @@ impl From for PackageType { BundleType::Deb => Self::Deb, BundleType::AppImage => Self::AppImage, BundleType::Msi => Self::WindowsMsi, + BundleType::Nsis => Self::Nsis, BundleType::App => Self::MacOsBundle, BundleType::Dmg => Self::Dmg, BundleType::Updater => Self::Updater, @@ -59,6 +62,7 @@ impl PackageType { "deb" => Some(PackageType::Deb), "ios" => Some(PackageType::IosBundle), "msi" => Some(PackageType::WindowsMsi), + "nsis" => Some(PackageType::Nsis), "app" => Some(PackageType::MacOsBundle), "rpm" => Some(PackageType::Rpm), "appimage" => Some(PackageType::AppImage), @@ -75,6 +79,7 @@ impl PackageType { PackageType::Deb => "deb", PackageType::IosBundle => "ios", PackageType::WindowsMsi => "msi", + PackageType::Nsis => "nsis", PackageType::MacOsBundle => "app", PackageType::Rpm => "rpm", PackageType::AppImage => "appimage", @@ -96,6 +101,8 @@ const ALL_PACKAGE_TYPES: &[PackageType] = &[ PackageType::IosBundle, #[cfg(target_os = "windows")] PackageType::WindowsMsi, + #[cfg(target_os = "windows")] + PackageType::Nsis, #[cfg(target_os = "macos")] PackageType::MacOsBundle, #[cfg(target_os = "linux")] @@ -239,6 +246,27 @@ pub struct WixSettings { pub dialog_image_path: Option, } +/// Settings specific to the NSIS implementation. +#[derive(Clone, Debug, Default)] +pub struct NsisSettings { + /// The path to the license file to render on the installer. + /// + /// Must be an RTF file, so if a different extension is provided, we convert it to the RTF format. + pub license: Option, + /// The path to a bitmap file to display on the header of installers pages. + /// + /// The recommended dimensions are 150pxx57px. + pub header_image: Option, + /// The path to a bitmap file for the Welcome page and the Finish page. + /// + /// The recommended dimensions are 164pxx314px. + pub sidebar_image: Option, + /// The path to an icon file used as the installer icon. + pub installer_icon: Option, + /// Whether the installation will be for all users or just the current user. + pub per_machine: bool, +} + /// The Windows bundle settings. #[derive(Clone, Debug)] pub struct WindowsSettings { @@ -253,6 +281,8 @@ pub struct WindowsSettings { pub tsp: bool, /// WiX configuration. pub wix: Option, + /// Nsis configuration. + pub nsis: Option, /// The path to the application icon. Defaults to `./icons/icon.ico`. pub icon_path: PathBuf, /// The installation mode for the Webview2 runtime. @@ -279,6 +309,7 @@ impl Default for WindowsSettings { timestamp_url: None, tsp: false, wix: None, + nsis: None, icon_path: PathBuf::from("icons/icon.ico"), webview_install_mode: Default::default(), webview_fixed_runtime_path: None, @@ -565,7 +596,7 @@ impl Settings { "macos" => vec![PackageType::MacOsBundle, PackageType::Dmg], "ios" => vec![PackageType::IosBundle], "linux" => vec![PackageType::Deb, PackageType::AppImage], - "windows" => vec![PackageType::WindowsMsi], + "windows" => vec![PackageType::WindowsMsi, PackageType::Nsis], os => { return Err(crate::Error::GenericError(format!( "Native {} bundles not yet supported.", diff --git a/tooling/bundler/src/bundle/windows/mod.rs b/tooling/bundler/src/bundle/windows/mod.rs index b9cf2ab9936..cab7194d83e 100644 --- a/tooling/bundler/src/bundle/windows/mod.rs +++ b/tooling/bundler/src/bundle/windows/mod.rs @@ -3,4 +3,6 @@ // SPDX-License-Identifier: MIT pub mod msi; +pub mod nsis; pub mod sign; +mod util; diff --git a/tooling/bundler/src/bundle/windows/msi/wix.rs b/tooling/bundler/src/bundle/windows/msi/wix.rs index dba96cfd8e3..8ca7cd76c66 100644 --- a/tooling/bundler/src/bundle/windows/msi/wix.rs +++ b/tooling/bundler/src/bundle/windows/msi/wix.rs @@ -2,28 +2,29 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use super::super::sign::{sign, SignParams}; use crate::bundle::{ common::CommandExt, path_utils::{copy_file, FileOpts}, settings::Settings, + windows::util::{ + download, download_and_verify, extract_zip, try_sign, validate_version, + WEBVIEW2_BOOTSTRAPPER_URL, WEBVIEW2_X64_INSTALLER_GUID, WEBVIEW2_X86_INSTALLER_GUID, + }, }; -use anyhow::{bail, Context}; +use anyhow::Context; use handlebars::{to_json, Handlebars}; use log::info; use regex::Regex; use serde::{Deserialize, Serialize}; -use sha2::Digest; use std::{ collections::{BTreeMap, HashMap}, fs::{create_dir_all, read_to_string, remove_dir_all, rename, write, File}, - io::{Cursor, Read, Write}, + io::Write, path::{Path, PathBuf}, process::Command, }; use tauri_utils::{config::WebviewInstallMode, resources::resource_relpath}; use uuid::Uuid; -use zip::ZipArchive; // URLS for the WIX toolchain. Can be used for crossplatform compilation. pub const WIX_URL: &str = @@ -31,9 +32,6 @@ pub const WIX_URL: &str = pub const WIX_SHA256: &str = "2c1888d5d1dba377fc7fa14444cf556963747ff9a0a289a3599cf09da03b9e2e"; pub const MSI_FOLDER_NAME: &str = "msi"; pub const MSI_UPDATER_FOLDER_NAME: &str = "msi-updater"; -const WEBVIEW2_BOOTSTRAPPER_URL: &str = "https://go.microsoft.com/fwlink/p/?LinkId=2124703"; -const WEBVIEW2_X86_INSTALLER_GUID: &str = "a17bde80-b5ab-47b5-8bbb-1cbe93fc6ec9"; -const WEBVIEW2_X64_INSTALLER_GUID: &str = "aa5fd9b3-dc11-4cbc-8343-a50f57b311e1"; // For Cross Platform Compilation. @@ -173,30 +171,6 @@ fn copy_icon(settings: &Settings, filename: &str, path: &Path) -> crate::Result< Ok(icon_target_path) } -fn download(url: &str) -> crate::Result> { - info!(action = "Downloading"; "{}", url); - let response = attohttpc::get(url).send()?; - response.bytes().map_err(Into::into) -} - -/// Function used to download Wix. Checks SHA256 to verify the download. -fn download_and_verify(url: &str, hash: &str) -> crate::Result> { - let data = download(url)?; - info!("validating hash"); - - let mut hasher = sha2::Sha256::new(); - hasher.update(&data); - - let url_hash = hasher.finalize().to_vec(); - let expected_hash = hex::decode(hash)?; - - if expected_hash == url_hash { - Ok(data) - } else { - Err(crate::Error::HashError) - } -} - /// The app installer output path. fn app_installer_output_path( settings: &Settings, @@ -233,31 +207,6 @@ fn app_installer_output_path( ))) } -/// Extracts the zips from Wix and VC_REDIST into a useable path. -fn extract_zip(data: &[u8], path: &Path) -> crate::Result<()> { - let cursor = Cursor::new(data); - - let mut zipa = ZipArchive::new(cursor)?; - - for i in 0..zipa.len() { - let mut file = zipa.by_index(i)?; - let dest_path = path.join(file.name()); - let parent = dest_path.parent().expect("Failed to get parent"); - - if !parent.exists() { - create_dir_all(parent)?; - } - - let mut buff: Vec = Vec::new(); - file.read_to_end(&mut buff)?; - let mut fileout = File::create(dest_path).expect("Failed to open file"); - - fileout.write_all(&buff)?; - } - - Ok(()) -} - /// Generates the UUID for the Wix template. fn generate_package_guid(settings: &Settings) -> Uuid { generate_guid(settings.bundle_identifier().as_bytes()) @@ -273,7 +222,7 @@ fn generate_guid(key: &[u8]) -> Uuid { pub fn get_and_extract_wix(path: &Path) -> crate::Result<()> { info!("Verifying wix package"); - let data = download_and_verify(WIX_URL, WIX_SHA256)?; + let data = download_and_verify(WIX_URL, WIX_SHA256, "sha256")?; info!("extracting WIX"); @@ -361,24 +310,6 @@ fn run_light( // Ok(()) // } -fn validate_version(version: &str) -> anyhow::Result<()> { - let version = semver::Version::parse(version).context("invalid app version")?; - if version.major > 255 { - bail!("app version major number cannot be greater than 255"); - } - if version.minor > 255 { - bail!("app version minor number cannot be greater than 255"); - } - if version.patch > 65535 { - bail!("app version patch number cannot be greater than 65535"); - } - if !(version.pre.is_empty() && version.build.is_empty()) { - bail!("app version cannot have build metadata or pre-release identifier"); - } - - Ok(()) -} - // Entry point for bundling and creating the MSI installer. For now the only supported platform is Windows x64. pub fn build_wix_app_installer( settings: &Settings, @@ -407,33 +338,8 @@ pub fn build_wix_app_installer( .find(|bin| bin.main()) .ok_or_else(|| anyhow::anyhow!("Failed to get main binary"))?; let app_exe_source = settings.binary_path(main_binary); - let try_sign = |file_path: &PathBuf| -> crate::Result<()> { - if let Some(certificate_thumbprint) = &settings.windows().certificate_thumbprint { - info!(action = "Signing"; "{}", file_path.display()); - sign( - &file_path, - &SignParams { - product_name: settings.product_name().into(), - digest_algorithm: settings - .windows() - .digest_algorithm - .as_ref() - .map(|algorithm| algorithm.to_string()) - .unwrap_or_else(|| "sha256".to_string()), - certificate_thumbprint: certificate_thumbprint.to_string(), - timestamp_url: settings - .windows() - .timestamp_url - .as_ref() - .map(|url| url.to_string()), - tsp: settings.windows().tsp, - }, - )?; - } - Ok(()) - }; - try_sign(&app_exe_source)?; + try_sign(&app_exe_source, &settings)?; let output_path = settings.project_out_directory().join("wix").join(arch); @@ -816,7 +722,7 @@ pub fn build_wix_app_installer( run_light(wix_toolset_path, &output_path, arguments, &msi_output_path)?; rename(&msi_output_path, &msi_path)?; - try_sign(&msi_path)?; + try_sign(&msi_path, &settings)?; output_paths.push(msi_path); } diff --git a/tooling/bundler/src/bundle/windows/nsis.rs b/tooling/bundler/src/bundle/windows/nsis.rs new file mode 100644 index 00000000000..2757cf38e89 --- /dev/null +++ b/tooling/bundler/src/bundle/windows/nsis.rs @@ -0,0 +1,209 @@ +// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use crate::{ + bundle::windows::util::{ + download, download_and_verify, extract_7z, extract_zip, remove_unc, try_sign, validate_version, + }, + Settings, +}; +use handlebars::{to_json, Handlebars}; +use log::{info, warn}; + +use std::{ + collections::BTreeMap, + fs::{create_dir_all, remove_dir_all, rename, write}, + path::{Path, PathBuf}, + process::Command, +}; + +// URLS for the NSIS toolchain. +const NSIS_URL: &str = + "https://sourceforge.net/projects/nsis/files/NSIS%203/3.08/nsis-3.08.zip/download"; +const NSIS_SHA1: &str = "057e83c7d82462ec394af76c87d06733605543d4"; +const NS_CURL_URL: &str = + "https://github.com/negrutiu/nsis-nscurl/releases/download/v1.2022.6.7/NScurl-1.2022.6.7.7z"; + +const NSIS_REQUIRED_FILES: &[&str] = &[ + "makensis.exe", + "Bin/makensis.exe", + "Stubs/zlib-x86-unicode", + "Plugins/x86-unicode/NScurl.dll", + "Include/MUI2.nsh", + "Include/FileFunc.nsh", + "Include/x64.nsh", +]; + +/// Runs all of the commands to build the NSIS installer. +/// Returns a vector of PathBuf that shows where the NSIS installer was created. +pub fn bundle_project(settings: &Settings) -> crate::Result> { + let nsis_path = dirs_next::cache_dir().unwrap().join("tauri").join("NSIS"); + let nsis_toolset_path = nsis_path.join("nsis-3.08"); + + if !nsis_path.exists() { + get_and_extract_nsis(&nsis_path)?; + } else if NSIS_REQUIRED_FILES + .iter() + .any(|p| !nsis_toolset_path.join(p).exists()) + { + warn!("NSIS directory is missing some files. Recreating it."); + std::fs::remove_dir_all(&nsis_path)?; + get_and_extract_nsis(&nsis_path)?; + } + + build_nsis_app_installer(settings, &nsis_toolset_path) +} + +// Gets NSIS and verifies the download via Sha256 +fn get_and_extract_nsis(path: &Path) -> crate::Result<()> { + info!("Verifying NSIS package"); + + let data = download_and_verify(NSIS_URL, NSIS_SHA1, "sha1")?; + + info!("extracting NSIS"); + extract_zip(&data, path)?; + + let data = download(NS_CURL_URL)?; + + info!("extracting NScurl"); + extract_7z(&data, &path.join("nsis-3.08").join("Plugins"))?; + + Ok(()) +} + +fn build_nsis_app_installer( + settings: &Settings, + nsis_toolset_path: &Path, +) -> crate::Result> { + let arch = match settings.binary_arch() { + "x86_64" => "x64", + "x86" => "x86", + target => { + return Err(crate::Error::ArchError(format!( + "unsupported target: {}", + target + ))) + } + }; + + validate_version(settings.version_string())?; + + info!("Target: {}", arch); + + let main_binary = settings + .binaries() + .iter() + .find(|bin| bin.main()) + .ok_or_else(|| anyhow::anyhow!("Failed to get main binary"))?; + let app_exe_source = settings.binary_path(main_binary); + + try_sign(&app_exe_source, &settings)?; + + let output_path = settings.project_out_directory().join("nsis").join(arch); + if output_path.exists() { + remove_dir_all(&output_path)?; + } + create_dir_all(&output_path)?; + + let mut data = BTreeMap::new(); + + let bundle_id = settings.bundle_identifier(); + let manufacturer = bundle_id.split('.').nth(1).unwrap_or(bundle_id); + + data.insert("bundle_id", to_json(bundle_id)); + data.insert("manufacturer", to_json(manufacturer)); + data.insert("product_name", to_json(settings.product_name())); + + let version = settings.version_string(); + data.insert("version", to_json(&version)); + + let mut s = version.split("."); + data.insert("version_major", to_json(s.next().unwrap())); + data.insert("version_minor", to_json(s.next().unwrap())); + + if let Some(nsis) = &settings.windows().nsis { + data.insert( + "install_mode", + to_json(if nsis.per_machine { + "perMachine" + } else { + "perUser" + }), + ); + + if let Some(license) = &nsis.license { + data.insert("license", to_json(remove_unc(license.canonicalize()?))); + } + if let Some(installer_icon) = &nsis.installer_icon { + data.insert( + "installer_icon", + to_json(remove_unc(installer_icon.canonicalize()?)), + ); + } + if let Some(header_image) = &nsis.header_image { + data.insert( + "header_image", + to_json(remove_unc(header_image.canonicalize()?)), + ); + } + if let Some(sidebar_image) = &nsis.sidebar_image { + data.insert( + "sidebar_image", + to_json(remove_unc(sidebar_image.canonicalize()?)), + ); + } + } + + let main_binary = settings + .binaries() + .iter() + .find(|bin| bin.main()) + .ok_or_else(|| anyhow::anyhow!("Failed to get main binary"))?; + data.insert( + "main_binary_name", + to_json(main_binary.name().replace(".exe", "")), + ); + data.insert( + "main_binary_path", + to_json(settings.binary_path(main_binary)), + ); + + let out_file = "nsis-output.exe"; + data.insert("out_file", to_json(&out_file)); + + let mut handlebars = Handlebars::new(); + handlebars + .register_template_string("installer.nsi", include_str!("./templates/installer.nsi")) + .map_err(|e| e.to_string()) + .expect("Failed to setup handlebar template"); + let installer_nsi_path = output_path.join("installer.nsi"); + write( + &installer_nsi_path, + handlebars.render("installer.nsi", &data)?, + )?; + + let package_base_name = format!( + "{}_{}_{}", + main_binary.name().replace(".exe", ""), + settings.version_string(), + arch, + ); + + let nsis_output_path = output_path.join(out_file); + let nsis_installer_path = settings + .project_out_directory() + .to_path_buf() + .join(format!("bundle/nsis/{}.exe", package_base_name)); + create_dir_all(nsis_installer_path.parent().unwrap())?; + + info!(action = "Running"; "makensis to produce {}", nsis_installer_path.display()); + Command::new(nsis_toolset_path.join("makensis.exe")) + .args(&[installer_nsi_path]) + .output()?; + + rename(&nsis_output_path, &nsis_installer_path)?; + try_sign(&nsis_installer_path, &settings)?; + + Ok(vec![nsis_installer_path]) +} diff --git a/tooling/bundler/src/bundle/windows/templates/installer.nsi b/tooling/bundler/src/bundle/windows/templates/installer.nsi new file mode 100644 index 00000000000..a3576244289 --- /dev/null +++ b/tooling/bundler/src/bundle/windows/templates/installer.nsi @@ -0,0 +1,173 @@ +; Imports +!include MUI2.nsh +!include FileFunc.nsh +!include x64.nsh +; --- + +; Variables +!define MANUFACTURER "{{{manufacturer}}}" +!define PRODUCTNAME "{{{product_name}}}" +!define VERSION "{{{version}}}" +!define VERSIONMAJOR "{{{version_major}}}" +!define VERSIONMINOR "{{{version_minor}}}" +!define INSTALLMODE "{{{installer_mode}}}" +!define LICENSE "{{{license}}}" +!define INSTALLERICON "{{{installer_icon}}}" +!define SIDEBARIMAGE "{{{sidebar_image}}}" +!define HEADERIMAGE "{{{header_image}}}" +!define MAINBINARYNAME "{{{main_binary_name}}}" +!define MAINBINARYPATH "{{{main_binary_path}}}" +!define APR "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCTNAME}" +Var AppStartMenuFolder ; Will be set through `MUI_PAGE_STARTMENU` page. Used to determine where to create the start menu shortcut +; --- + +Unicode true +Name "${PRODUCTNAME}" +OutFile "{{{out_file}}}" + +!if "${INSTALLMODE}" == "perMachine" + RequestExecutionLevel heighest + ; Set default install location + !if ${RunningX64} + InstallDir "$PROGRAMFILES64\${PRODUCTNAME}" + !else + InstallDir "$PROGRAMFILES\${PRODUCTNAME}" + !endif + ; Override with the previous install location if it exists + InstallDirRegKey HKLM "Software\${MANUFACTURER}\${PRODUCTNAME}" "" +!else + RequestExecutionLevel user + InstallDir "$LOCALAPPDATA\${PRODUCTNAME}" + InstallDirRegKey HKCU "Software\${MANUFACTURER}\${PRODUCTNAME}" "" +!endif + +!if "${INSTALLERICON}" != "" + !define MUI_ICON "${INSTALLERICON}" +!endif + +!if "${SIDEBARIMAGE}" != "" + !define MUI_WELCOMEFINISHPAGE_BITMAP "${SIDEBARIMAGE}" +!endif + +!if "${HEADERIMAGE}" != "" + !define MUI_HEADERIMAGE + !define MUI_HEADERIMAGE_BITMAP "${HEADERIMAGE}" +!endif + +; Don't auto jump to finish page after installation page, +; because the instalation page has useful info that can be used debug any issues with the installer. +!define MUI_FINISHPAGE_NOAUTOCLOSE + +; Use show readme button in the finish page to create a desktop shortcut +!define MUI_FINISHPAGE_SHOWREADME +!define MUI_FINISHPAGE_SHOWREADME_TEXT "Create desktop shortcut" +!define MUI_FINISHPAGE_SHOWREADME_FUNCTION "createDesktopShortcut" + +; Show run app after installation. +!define MUI_FINISHPAGE_RUN $INSTDIR\Resources.exe + +; Installer pages, must be ordered as they appear +!insertmacro MUI_PAGE_WELCOME +!if "${LICENSE}" != "" + !insertmacro MUI_PAGE_LICENSE "${LICENSE}" +!endif +!insertmacro MUI_PAGE_DIRECTORY +!insertmacro MUI_PAGE_STARTMENU Application $AppStartMenuFolder +!insertmacro MUI_PAGE_INSTFILES +!insertmacro MUI_PAGE_FINISH + +; Uninstaller pages +!insertmacro MUI_UNPAGE_CONFIRM +!insertmacro MUI_UNPAGE_INSTFILES + +; Languages +!insertmacro MUI_LANGUAGE English + +Function createDesktopShortcut + CreateShortcut "$DESKTOP\${MAINBINARYNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe" +FunctionEnd + +Section Webview2 + ; Check if Webview2 is already installed + ReadRegStr $4 HKLM "SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + ReadRegStr $5 HKCU "SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + ${If} $4 == "" + ${AndIf} $5 == "" + Delete "$TEMP\MicrosoftEdgeWebview2Setup.exe" + + DetailPrint "Downloading Webview2 installer..." + NScurl::http GET "https://go.microsoft.com/fwlink/p/?LinkId=2124703" "$TEMP\MicrosoftEdgeWebview2Setup.exe" /CANCEL /END + Pop $0 + ${If} $0 == "OK" + DetailPrint "Webview2 installer downloaded sucessfully" + ${Else} + DetailPrint "Error: Downloading Webview2 Failed - $0" + Goto abort + ${EndIf} + + DetailPrint "Installing Webview2..." + ExecWait "$TEMP\MicrosoftEdgeWebview2Setup.exe /install" $1 + ${If} $1 == 0 + DetailPrint "Webview2 installed sucessfully" + ${Else} + DetailPrint "Error: Installing Webview2 Failed with exit code $1" + Goto abort + ${EndIf} + ${EndIf} + + Goto done + + abort: + Abort "Failed to install Webview2. The app can't run without it. Try restarting the installer" + done: +SectionEnd + +Section Install + SetOutPath $INSTDIR + + ; Main executable + File "${MAINBINARYPATH}" + + ; Copy resources and external binaries + + ; Create uninstaller + WriteUninstaller "$INSTDIR\uninstall.exe" + + ; Save $INSTDIR in registry for future installations + WriteRegStr SHCTX "Software\${MANUFACTURER}\${PRODUCTNAME}" "" $INSTDIR + + ; Registry information for add/remove programs + WriteRegStr SHCTX "${APR}" "DisplayName" "${PRODUCTNAME}" + WriteRegStr SHCTX "${APR}" "DisplayIcon" "$\"$INSTDIR\Resources.exe$\"" + WriteRegStr SHCTX "${APR}" "DisplayVersion" "$\"${VERSION}$\"" + WriteRegStr SHCTX "${APR}" "Publisher" "${MANUFACTURER}" + WriteRegStr SHCTX "${APR}" "InstallLocation" "$\"$INSTDIR$\"" + WriteRegStr SHCTX "${APR}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" + WriteRegDWORD SHCTX "${APR}" "VersionMajor" ${VERSIONMAJOR} + WriteRegDWORD SHCTX "${APR}" "VersionMinor" ${VERSIONMINOR} + WriteRegDWORD SHCTX "${APR}" "NoModify" "1" + WriteRegDWORD SHCTX "${APR}" "NoRepair" "1" + ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 + IntFmt $0 "0x%08X" $0 + WriteRegDWORD SHCTX "${APR}" "EstimatedSize" "$0" + + ; Create start menu shortcut + !insertmacro MUI_STARTMENU_WRITE_BEGIN Application + CreateDirectory "$SMPROGRAMS\$AppStartMenuFolder" + CreateShortcut "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe" + !insertmacro MUI_STARTMENU_WRITE_END + +SectionEnd + +Section Uninstall + ; Delete the app directory and its content from disk + RMDir /r "$INSTDIR" + + ; Remove start menu and desktop shortcuts + !insertmacro MUI_STARTMENU_GETFOLDER Application $AppStartMenuFolder + RMDir /r "$SMPROGRAMS\$AppStartMenuFolder" + Delete "$DESKTOP\${MAINBINARYNAME}.lnk" + + ; Remove registry information for add/remove programs + DeleteRegKey SHCTX "${APR}" +SectionEnd diff --git a/tooling/bundler/src/bundle/windows/util.rs b/tooling/bundler/src/bundle/windows/util.rs new file mode 100644 index 00000000000..7c04ef80fd1 --- /dev/null +++ b/tooling/bundler/src/bundle/windows/util.rs @@ -0,0 +1,177 @@ +use std::{ + fs::{create_dir_all, remove_file, File}, + io::{Cursor, Read, Write}, + path::{Path, PathBuf}, + process::Command, +}; + +use anyhow::{bail, Context}; +use log::{debug, info}; +use sha2::Digest; +use zip::ZipArchive; + +use crate::{ + bundle::windows::sign::{sign, SignParams}, + Settings, +}; + +pub const WEBVIEW2_BOOTSTRAPPER_URL: &str = "https://go.microsoft.com/fwlink/p/?LinkId=2124703"; +pub const WEBVIEW2_X86_INSTALLER_GUID: &str = "a17bde80-b5ab-47b5-8bbb-1cbe93fc6ec9"; +pub const WEBVIEW2_X64_INSTALLER_GUID: &str = "aa5fd9b3-dc11-4cbc-8343-a50f57b311e1"; + +pub fn download(url: &str) -> crate::Result> { + info!(action = "Downloading"; "{}", url); + let response = attohttpc::get(url).send()?; + response.bytes().map_err(Into::into) +} + +/// Function used to download a file and checks SHA256 to verify the download. +pub fn download_and_verify(url: &str, hash: &str, hash_algorithim: &str) -> crate::Result> { + let data = download(url)?; + info!("validating hash"); + + match hash_algorithim { + "sha256" => { + let hasher = sha2::Sha256::new(); + verify(&data, hash, hasher)?; + } + "sha1" => { + let hasher = sha1::Sha1::new(); + verify(&data, hash, hasher)?; + } + // "sha256" => sha1::Sha1::new(), + _ => unimplemented!(), + } + + Ok(data) +} + +fn verify(data: &Vec, hash: &str, mut hasher: impl Digest) -> crate::Result<()> { + hasher.update(data); + + let url_hash = hasher.finalize().to_vec(); + let expected_hash = hex::decode(hash)?; + if expected_hash == url_hash { + Ok(()) + } else { + Err(crate::Error::HashError) + } +} + +pub fn validate_version(version: &str) -> anyhow::Result<()> { + let version = semver::Version::parse(version).context("invalid app version")?; + if version.major > 255 { + bail!("app version major number cannot be greater than 255"); + } + if version.minor > 255 { + bail!("app version minor number cannot be greater than 255"); + } + if version.patch > 65535 { + bail!("app version patch number cannot be greater than 65535"); + } + if !(version.pre.is_empty() && version.build.is_empty()) { + bail!("app version cannot have build metadata or pre-release identifier"); + } + + Ok(()) +} + +pub fn try_sign(file_path: &PathBuf, settings: &Settings) -> crate::Result<()> { + if let Some(certificate_thumbprint) = settings.windows().certificate_thumbprint.as_ref() { + info!(action = "Signing"; "{}", file_path.display()); + sign( + &file_path, + &SignParams { + product_name: settings.product_name().into(), + digest_algorithm: settings + .windows() + .digest_algorithm + .as_ref() + .map(|algorithm| algorithm.to_string()) + .unwrap_or_else(|| "sha256".to_string()), + certificate_thumbprint: certificate_thumbprint.to_string(), + timestamp_url: settings + .windows() + .timestamp_url + .as_ref() + .map(|url| url.to_string()), + tsp: settings.windows().tsp, + }, + )?; + } + Ok(()) +} + +/// Extracts the zips from memory into a useable path. +pub fn extract_zip(data: &[u8], path: &Path) -> crate::Result<()> { + let cursor = Cursor::new(data); + + let mut zipa = ZipArchive::new(cursor)?; + + for i in 0..zipa.len() { + let mut file = zipa.by_index(i)?; + let dest_path = path.join(file.name()); + let parent = dest_path.parent().expect("Failed to get parent"); + + if !parent.exists() { + create_dir_all(parent)?; + } + + let mut buff: Vec = Vec::new(); + file.read_to_end(&mut buff)?; + let mut fileout = File::create(dest_path).expect("Failed to open file"); + + fileout.write_all(&buff)?; + } + + Ok(()) +} + +const URL_7ZR: &str = "https://www.7-zip.org/a/7zr.exe"; + +pub fn extract_7z(data: &[u8], path: &Path) -> crate::Result<()> { + let bin_7z = { + debug!("checking for 7z.exe or 7zr.exe is in $PATH"); + if let Ok(_) = Command::new("7z.exe").spawn() { + "7z.exe".to_string() + } else if let Ok(_) = Command::new("7zr.exe").spawn() { + "7zr.exe".to_string() + } else { + get_or_download_7zr_bin()?.to_string_lossy().to_string() + } + }; + + let temp = path.join("temp.7z"); + { + let mut file = File::create(&temp)?; + file.write_all(&data)?; + } + + Command::new(bin_7z) + .args(&["x", &temp.to_string_lossy()]) + .current_dir(path) + .output()?; + + remove_file(temp)?; + + Ok(()) +} + +fn get_or_download_7zr_bin() -> crate::Result { + let tauri_tools_path = dirs_next::cache_dir().unwrap().join("tauri"); + let bin_7zr_path = tauri_tools_path.join("7zr.exe"); + + debug!("checking for 7zr.exe in {}", tauri_tools_path.display()); + if !bin_7zr_path.exists() { + info!("downloading 7zr.exe in {}", tauri_tools_path.display()); + let data = download(URL_7ZR)?; + let mut file = File::create(&bin_7zr_path)?; + file.write_all(&data)?; + }; + + Ok(bin_7zr_path) +} + +pub fn remove_unc>(p: P) -> PathBuf { + PathBuf::from(p.as_ref().to_string_lossy().replacen(r"\\?\", "", 1)) +} diff --git a/tooling/cli/Cargo.lock b/tooling/cli/Cargo.lock index a2f9bccc035..49119254e01 100644 --- a/tooling/cli/Cargo.lock +++ b/tooling/cli/Cargo.lock @@ -2787,6 +2787,7 @@ dependencies = [ "semver", "serde", "serde_json", + "sha1", "sha2", "strsim", "tar", diff --git a/tooling/cli/schema.json b/tooling/cli/schema.json index be024424191..b2a2a79dbf2 100644 --- a/tooling/cli/schema.json +++ b/tooling/cli/schema.json @@ -134,6 +134,7 @@ "allowDowngrades": true, "certificateThumbprint": null, "digestAlgorithm": null, + "nsis": null, "timestampUrl": null, "tsp": false, "webviewFixedRuntimePath": null, @@ -269,6 +270,7 @@ "allowDowngrades": true, "certificateThumbprint": null, "digestAlgorithm": null, + "nsis": null, "timestampUrl": null, "tsp": false, "webviewFixedRuntimePath": null, @@ -911,7 +913,7 @@ "type": "boolean" }, "targets": { - "description": "The bundle targets, currently supports [\"deb\", \"appimage\", \"msi\", \"app\", \"dmg\", \"updater\"] or \"all\".", + "description": "The bundle targets, currently supports [\"deb\", \"appimage\", \"nsis\", \"msi\", \"app\", \"dmg\", \"updater\"] or \"all\".", "default": "all", "allOf": [ { @@ -1018,6 +1020,7 @@ "allowDowngrades": true, "certificateThumbprint": null, "digestAlgorithm": null, + "nsis": null, "timestampUrl": null, "tsp": false, "webviewFixedRuntimePath": null, @@ -1069,6 +1072,7 @@ "deb", "appimage", "msi", + "nsis", "app", "dmg", "updater" @@ -1235,6 +1239,17 @@ "type": "null" } ] + }, + "nsis": { + "description": "Configuration for the installer generated with NSIS.", + "anyOf": [ + { + "$ref": "#/definitions/NsisConfig" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false @@ -1483,6 +1498,46 @@ }, "additionalProperties": false }, + "NsisConfig": { + "description": "Configuration for the Installer bundle using NSIS.", + "type": "object", + "properties": { + "license": { + "description": "The path to the license file to render on the installer.\n\nMust be an RTF file, so if a different extension is provided, we convert it to the RTF format.", + "type": [ + "string", + "null" + ] + }, + "headerImage": { + "description": "The path to a bitmap file to display on the header of installers pages.\n\nThe recommended dimensions are 150pxx57px.", + "type": [ + "string", + "null" + ] + }, + "sidebarImage": { + "description": "The path to a bitmap file for the Welcome page and the Finish page.\n\nThe recommended dimensions are 164pxx314px.", + "type": [ + "string", + "null" + ] + }, + "installerIcon": { + "description": "The path to an icon file used as the installer icon.", + "type": [ + "string", + "null" + ] + }, + "perMachine": { + "description": "Whether the installation will be for all users or just the current user.", + "default": false, + "type": "boolean" + } + }, + "additionalProperties": false + }, "AllowlistConfig": { "description": "Allowlist configuration.", "type": "object", diff --git a/tooling/cli/src/helpers/config.rs b/tooling/cli/src/helpers/config.rs index 7bbf63d80dc..9ae53ae666b 100644 --- a/tooling/cli/src/helpers/config.rs +++ b/tooling/cli/src/helpers/config.rs @@ -94,6 +94,16 @@ pub fn wix_settings(config: WixConfig) -> tauri_bundler::WixSettings { } } +pub fn nsis_settings(config: NsisConfig) -> tauri_bundler::NsisSettings { + tauri_bundler::NsisSettings { + license: config.license, + header_image: config.header_image, + sidebar_image: config.sidebar_image, + installer_icon: config.installer_icon, + per_machine: config.per_machine, + } +} + fn config_handle() -> &'static ConfigHandle { static CONFING_HANDLE: Lazy = Lazy::new(Default::default); &CONFING_HANDLE diff --git a/tooling/cli/src/interface/rust.rs b/tooling/cli/src/interface/rust.rs index 7085b41a561..97972526ac1 100644 --- a/tooling/cli/src/interface/rust.rs +++ b/tooling/cli/src/interface/rust.rs @@ -34,7 +34,7 @@ use super::{AppSettings, ExitReason, Interface}; use crate::{ helpers::{ app_paths::tauri_dir, - config::{reload as reload_config, wix_settings, Config}, + config::{nsis_settings, reload as reload_config, wix_settings, Config}, }, CommandExt, }; @@ -1094,6 +1094,7 @@ fn tauri_config_to_bundle_settings( wix.license = wix.license.map(|l| tauri_dir().join(l)); wix }), + nsis: config.windows.nsis.map(nsis_settings), icon_path: windows_icon_path, webview_install_mode: config.windows.webview_install_mode, webview_fixed_runtime_path: config.windows.webview_fixed_runtime_path, From 221cbc9af9d051eabf81f164ee12bd9f934d32a0 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Fri, 15 Jul 2022 02:06:30 +0200 Subject: [PATCH 02/46] remove invalid doc comment --- core/tauri-utils/src/config.rs | 2 -- tooling/bundler/src/bundle/settings.rs | 2 -- 2 files changed, 4 deletions(-) diff --git a/core/tauri-utils/src/config.rs b/core/tauri-utils/src/config.rs index 0bc69405792..aae6a950fb5 100644 --- a/core/tauri-utils/src/config.rs +++ b/core/tauri-utils/src/config.rs @@ -417,8 +417,6 @@ pub struct WixConfig { #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct NsisConfig { /// The path to the license file to render on the installer. - /// - /// Must be an RTF file, so if a different extension is provided, we convert it to the RTF format. pub license: Option, /// The path to a bitmap file to display on the header of installers pages. /// diff --git a/tooling/bundler/src/bundle/settings.rs b/tooling/bundler/src/bundle/settings.rs index ba09d1fb594..b5f4f6026a2 100644 --- a/tooling/bundler/src/bundle/settings.rs +++ b/tooling/bundler/src/bundle/settings.rs @@ -250,8 +250,6 @@ pub struct WixSettings { #[derive(Clone, Debug, Default)] pub struct NsisSettings { /// The path to the license file to render on the installer. - /// - /// Must be an RTF file, so if a different extension is provided, we convert it to the RTF format. pub license: Option, /// The path to a bitmap file to display on the header of installers pages. /// From 6c1b1c6b851c70a32679fc71c23f92bc2b0b5d54 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Fri, 15 Jul 2022 02:18:30 +0200 Subject: [PATCH 03/46] look for webview2 x64 per machine installation --- tooling/bundler/src/bundle/windows/msi/wix.rs | 8 ++++---- .../bundler/src/bundle/windows/templates/installer.nsi | 7 ++++++- tooling/bundler/src/bundle/windows/util.rs | 4 ---- tooling/cli/schema.json | 2 +- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/tooling/bundler/src/bundle/windows/msi/wix.rs b/tooling/bundler/src/bundle/windows/msi/wix.rs index 8ca7cd76c66..512e272eae6 100644 --- a/tooling/bundler/src/bundle/windows/msi/wix.rs +++ b/tooling/bundler/src/bundle/windows/msi/wix.rs @@ -6,10 +6,7 @@ use crate::bundle::{ common::CommandExt, path_utils::{copy_file, FileOpts}, settings::Settings, - windows::util::{ - download, download_and_verify, extract_zip, try_sign, validate_version, - WEBVIEW2_BOOTSTRAPPER_URL, WEBVIEW2_X64_INSTALLER_GUID, WEBVIEW2_X86_INSTALLER_GUID, - }, + windows::util::{download, download_and_verify, extract_zip, try_sign, validate_version}, }; use anyhow::Context; use handlebars::{to_json, Handlebars}; @@ -32,6 +29,9 @@ pub const WIX_URL: &str = pub const WIX_SHA256: &str = "2c1888d5d1dba377fc7fa14444cf556963747ff9a0a289a3599cf09da03b9e2e"; pub const MSI_FOLDER_NAME: &str = "msi"; pub const MSI_UPDATER_FOLDER_NAME: &str = "msi-updater"; +pub const WEBVIEW2_BOOTSTRAPPER_URL: &str = "https://go.microsoft.com/fwlink/p/?LinkId=2124703"; +pub const WEBVIEW2_X86_INSTALLER_GUID: &str = "a17bde80-b5ab-47b5-8bbb-1cbe93fc6ec9"; +pub const WEBVIEW2_X64_INSTALLER_GUID: &str = "aa5fd9b3-dc11-4cbc-8343-a50f57b311e1"; // For Cross Platform Compilation. diff --git a/tooling/bundler/src/bundle/windows/templates/installer.nsi b/tooling/bundler/src/bundle/windows/templates/installer.nsi index a3576244289..9506d8de218 100644 --- a/tooling/bundler/src/bundle/windows/templates/installer.nsi +++ b/tooling/bundler/src/bundle/windows/templates/installer.nsi @@ -89,8 +89,13 @@ FunctionEnd Section Webview2 ; Check if Webview2 is already installed - ReadRegStr $4 HKLM "SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + ${If} ${RunningX64} + ReadRegStr $4 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + ${Else} + ReadRegStr $4 HKLM "SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + ${EndIf} ReadRegStr $5 HKCU "SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + ${If} $4 == "" ${AndIf} $5 == "" Delete "$TEMP\MicrosoftEdgeWebview2Setup.exe" diff --git a/tooling/bundler/src/bundle/windows/util.rs b/tooling/bundler/src/bundle/windows/util.rs index 7c04ef80fd1..017c65456db 100644 --- a/tooling/bundler/src/bundle/windows/util.rs +++ b/tooling/bundler/src/bundle/windows/util.rs @@ -15,10 +15,6 @@ use crate::{ Settings, }; -pub const WEBVIEW2_BOOTSTRAPPER_URL: &str = "https://go.microsoft.com/fwlink/p/?LinkId=2124703"; -pub const WEBVIEW2_X86_INSTALLER_GUID: &str = "a17bde80-b5ab-47b5-8bbb-1cbe93fc6ec9"; -pub const WEBVIEW2_X64_INSTALLER_GUID: &str = "aa5fd9b3-dc11-4cbc-8343-a50f57b311e1"; - pub fn download(url: &str) -> crate::Result> { info!(action = "Downloading"; "{}", url); let response = attohttpc::get(url).send()?; diff --git a/tooling/cli/schema.json b/tooling/cli/schema.json index b2a2a79dbf2..34f63a42080 100644 --- a/tooling/cli/schema.json +++ b/tooling/cli/schema.json @@ -1503,7 +1503,7 @@ "type": "object", "properties": { "license": { - "description": "The path to the license file to render on the installer.\n\nMust be an RTF file, so if a different extension is provided, we convert it to the RTF format.", + "description": "The path to the license file to render on the installer.", "type": [ "string", "null" From ba83871637e85254e1161911dc49c5d2e564c0d9 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Fri, 15 Jul 2022 02:23:47 +0200 Subject: [PATCH 04/46] remove unnecessary pub --- tooling/bundler/src/bundle/windows/msi/wix.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tooling/bundler/src/bundle/windows/msi/wix.rs b/tooling/bundler/src/bundle/windows/msi/wix.rs index 512e272eae6..4ca80708676 100644 --- a/tooling/bundler/src/bundle/windows/msi/wix.rs +++ b/tooling/bundler/src/bundle/windows/msi/wix.rs @@ -29,9 +29,9 @@ pub const WIX_URL: &str = pub const WIX_SHA256: &str = "2c1888d5d1dba377fc7fa14444cf556963747ff9a0a289a3599cf09da03b9e2e"; pub const MSI_FOLDER_NAME: &str = "msi"; pub const MSI_UPDATER_FOLDER_NAME: &str = "msi-updater"; -pub const WEBVIEW2_BOOTSTRAPPER_URL: &str = "https://go.microsoft.com/fwlink/p/?LinkId=2124703"; -pub const WEBVIEW2_X86_INSTALLER_GUID: &str = "a17bde80-b5ab-47b5-8bbb-1cbe93fc6ec9"; -pub const WEBVIEW2_X64_INSTALLER_GUID: &str = "aa5fd9b3-dc11-4cbc-8343-a50f57b311e1"; +const WEBVIEW2_BOOTSTRAPPER_URL: &str = "https://go.microsoft.com/fwlink/p/?LinkId=2124703"; +const WEBVIEW2_X86_INSTALLER_GUID: &str = "a17bde80-b5ab-47b5-8bbb-1cbe93fc6ec9"; +const WEBVIEW2_X64_INSTALLER_GUID: &str = "aa5fd9b3-dc11-4cbc-8343-a50f57b311e1"; // For Cross Platform Compilation. From 77d893cf3fa77f67dd8f57abc53a0b4febda0fcb Mon Sep 17 00:00:00 2001 From: amrbashir Date: Fri, 15 Jul 2022 17:39:59 +0200 Subject: [PATCH 05/46] bundle resources --- tooling/bundler/src/bundle/windows/nsis.rs | 60 ++++++++++++++++--- .../bundle/windows/templates/installer.nsi | 9 ++- tooling/bundler/src/bundle/windows/util.rs | 2 +- 3 files changed, 61 insertions(+), 10 deletions(-) diff --git a/tooling/bundler/src/bundle/windows/nsis.rs b/tooling/bundler/src/bundle/windows/nsis.rs index 2757cf38e89..13c036a6df6 100644 --- a/tooling/bundler/src/bundle/windows/nsis.rs +++ b/tooling/bundler/src/bundle/windows/nsis.rs @@ -3,13 +3,19 @@ // SPDX-License-Identifier: MIT use crate::{ - bundle::windows::util::{ - download, download_and_verify, extract_7z, extract_zip, remove_unc, try_sign, validate_version, + bundle::{ + common::CommandExt, + windows::util::{ + download, download_and_verify, extract_7z, extract_zip, remove_unc_lossy, try_sign, + validate_version, + }, }, Settings, }; +use anyhow::Context; use handlebars::{to_json, Handlebars}; use log::{info, warn}; +use tauri_utils::resources::resource_relpath; use std::{ collections::BTreeMap, @@ -133,24 +139,27 @@ fn build_nsis_app_installer( ); if let Some(license) = &nsis.license { - data.insert("license", to_json(remove_unc(license.canonicalize()?))); + data.insert( + "license", + to_json(remove_unc_lossy(license.canonicalize()?)), + ); } if let Some(installer_icon) = &nsis.installer_icon { data.insert( "installer_icon", - to_json(remove_unc(installer_icon.canonicalize()?)), + to_json(remove_unc_lossy(installer_icon.canonicalize()?)), ); } if let Some(header_image) = &nsis.header_image { data.insert( "header_image", - to_json(remove_unc(header_image.canonicalize()?)), + to_json(remove_unc_lossy(header_image.canonicalize()?)), ); } if let Some(sidebar_image) = &nsis.sidebar_image { data.insert( "sidebar_image", - to_json(remove_unc(sidebar_image.canonicalize()?)), + to_json(remove_unc_lossy(sidebar_image.canonicalize()?)), ); } } @@ -172,6 +181,9 @@ fn build_nsis_app_installer( let out_file = "nsis-output.exe"; data.insert("out_file", to_json(&out_file)); + let resources = generate_resource_data(&settings)?; + data.insert("resources", to_json(resources)); + let mut handlebars = Handlebars::new(); handlebars .register_template_string("installer.nsi", include_str!("./templates/installer.nsi")) @@ -197,13 +209,45 @@ fn build_nsis_app_installer( .join(format!("bundle/nsis/{}.exe", package_base_name)); create_dir_all(nsis_installer_path.parent().unwrap())?; - info!(action = "Running"; "makensis to produce {}", nsis_installer_path.display()); + info!(action = "Running"; "makensis.exe to produce {}", nsis_installer_path.display()); Command::new(nsis_toolset_path.join("makensis.exe")) .args(&[installer_nsi_path]) - .output()?; + .current_dir(output_path) + .output_ok() + .context("error running makensis.exe")?; rename(&nsis_output_path, &nsis_installer_path)?; try_sign(&nsis_installer_path, &settings)?; Ok(vec![nsis_installer_path]) } + +/// Key is the original path and the value is the target path +type ResourcesMap = BTreeMap; +fn generate_resource_data(settings: &Settings) -> crate::Result { + let mut resources = ResourcesMap::new(); + let cwd = std::env::current_dir()?; + + let mut added_resources = Vec::new(); + + for src in settings.resource_files() { + let src = src?; + + let resource_path = remove_unc_lossy(cwd.join(src.clone()).canonicalize()?); + + // In some glob resource paths like `assets/**/*` a file might appear twice + // because the `tauri_utils::resources::ResourcePaths` iterator also reads a directory + // when it finds one. So we must check it before processing the file. + if added_resources.contains(&resource_path) { + continue; + } + + added_resources.push(resource_path.clone()); + + let target_path = resource_relpath(&src); + + resources.insert(resource_path, target_path); + } + + Ok(resources) +} diff --git a/tooling/bundler/src/bundle/windows/templates/installer.nsi b/tooling/bundler/src/bundle/windows/templates/installer.nsi index 9506d8de218..b7c059201a4 100644 --- a/tooling/bundler/src/bundle/windows/templates/installer.nsi +++ b/tooling/bundler/src/bundle/windows/templates/installer.nsi @@ -133,7 +133,14 @@ Section Install ; Main executable File "${MAINBINARYPATH}" - ; Copy resources and external binaries + ; Copy resources + {{#each resources}} + ${GetParent} "{{this}}" $R1 + CreateDirectory "$INSTDIR\$R1" + File /a /oname={{this}} {{@key}} + {{/each}} + + ; Copy external binaries ; Create uninstaller WriteUninstaller "$INSTDIR\uninstall.exe" diff --git a/tooling/bundler/src/bundle/windows/util.rs b/tooling/bundler/src/bundle/windows/util.rs index 017c65456db..4893525dabf 100644 --- a/tooling/bundler/src/bundle/windows/util.rs +++ b/tooling/bundler/src/bundle/windows/util.rs @@ -168,6 +168,6 @@ fn get_or_download_7zr_bin() -> crate::Result { Ok(bin_7zr_path) } -pub fn remove_unc>(p: P) -> PathBuf { +pub fn remove_unc_lossy>(p: P) -> PathBuf { PathBuf::from(p.as_ref().to_string_lossy().replacen(r"\\?\", "", 1)) } From ed6fe409730aacce835566033f22631d0091f7b7 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Fri, 15 Jul 2022 18:56:21 +0200 Subject: [PATCH 06/46] bundle externalBinaries --- tooling/bundler/src/bundle/windows/nsis.rs | 46 ++++++++++++++++--- .../bundle/windows/templates/installer.nsi | 3 ++ 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/tooling/bundler/src/bundle/windows/nsis.rs b/tooling/bundler/src/bundle/windows/nsis.rs index 13c036a6df6..78e60072ab2 100644 --- a/tooling/bundler/src/bundle/windows/nsis.rs +++ b/tooling/bundler/src/bundle/windows/nsis.rs @@ -184,6 +184,9 @@ fn build_nsis_app_installer( let resources = generate_resource_data(&settings)?; data.insert("resources", to_json(resources)); + let binaries = generate_binaries_data(settings)?; + data.insert("binaries", to_json(binaries)); + let mut handlebars = Handlebars::new(); handlebars .register_template_string("installer.nsi", include_str!("./templates/installer.nsi")) @@ -217,12 +220,12 @@ fn build_nsis_app_installer( .context("error running makensis.exe")?; rename(&nsis_output_path, &nsis_installer_path)?; - try_sign(&nsis_installer_path, &settings)?; + try_sign(&nsis_installer_path, settings)?; Ok(vec![nsis_installer_path]) } -/// Key is the original path and the value is the target path +/// BTreeMap type ResourcesMap = BTreeMap; fn generate_resource_data(settings: &Settings) -> crate::Result { let mut resources = ResourcesMap::new(); @@ -232,8 +235,7 @@ fn generate_resource_data(settings: &Settings) -> crate::Result { for src in settings.resource_files() { let src = src?; - - let resource_path = remove_unc_lossy(cwd.join(src.clone()).canonicalize()?); + let resource_path = remove_unc_lossy(cwd.join(&src).canonicalize()?); // In some glob resource paths like `assets/**/*` a file might appear twice // because the `tauri_utils::resources::ResourcePaths` iterator also reads a directory @@ -241,13 +243,45 @@ fn generate_resource_data(settings: &Settings) -> crate::Result { if added_resources.contains(&resource_path) { continue; } - added_resources.push(resource_path.clone()); let target_path = resource_relpath(&src); - resources.insert(resource_path, target_path); } Ok(resources) } + +/// BTreeMap +type BinariesMap = BTreeMap; +fn generate_binaries_data(settings: &Settings) -> crate::Result { + let mut binaries = BinariesMap::new(); + let cwd = std::env::current_dir()?; + + for src in settings.external_binaries() { + let src = src?; + let binary_path = remove_unc_lossy(cwd.join(&src).canonicalize()?); + let dest_filename = src + .file_name() + .expect("failed to extract external binary filename") + .to_string_lossy() + .replace(&format!("-{}", settings.target()), ""); + binaries.insert(binary_path, dest_filename); + } + + for bin in settings.binaries() { + if !bin.main() { + let bin_path = settings.binary_path(bin); + binaries.insert( + bin_path.clone(), + bin_path + .file_name() + .expect("failed to extract external binary filename") + .to_string_lossy() + .to_string(), + ); + } + } + + Ok(binaries) +} diff --git a/tooling/bundler/src/bundle/windows/templates/installer.nsi b/tooling/bundler/src/bundle/windows/templates/installer.nsi index b7c059201a4..5c34b745755 100644 --- a/tooling/bundler/src/bundle/windows/templates/installer.nsi +++ b/tooling/bundler/src/bundle/windows/templates/installer.nsi @@ -141,6 +141,9 @@ Section Install {{/each}} ; Copy external binaries + {{#each binaries}} + File /a /oname={{this}} {{@key}} + {{/each}} ; Create uninstaller WriteUninstaller "$INSTDIR\uninstall.exe" From 2404f04dec23ab0346c649a39ba172b58261eb26 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Fri, 15 Jul 2022 18:59:48 +0200 Subject: [PATCH 07/46] fix launch after install option --- tooling/bundler/src/bundle/windows/templates/installer.nsi | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tooling/bundler/src/bundle/windows/templates/installer.nsi b/tooling/bundler/src/bundle/windows/templates/installer.nsi index 5c34b745755..a6b5349662c 100644 --- a/tooling/bundler/src/bundle/windows/templates/installer.nsi +++ b/tooling/bundler/src/bundle/windows/templates/installer.nsi @@ -16,7 +16,7 @@ !define SIDEBARIMAGE "{{{sidebar_image}}}" !define HEADERIMAGE "{{{header_image}}}" !define MAINBINARYNAME "{{{main_binary_name}}}" -!define MAINBINARYPATH "{{{main_binary_path}}}" +!define MAINBINARYSRCPATH "{{{main_binary_path}}}" !define APR "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCTNAME}" Var AppStartMenuFolder ; Will be set through `MUI_PAGE_STARTMENU` page. Used to determine where to create the start menu shortcut ; --- @@ -64,7 +64,7 @@ OutFile "{{{out_file}}}" !define MUI_FINISHPAGE_SHOWREADME_FUNCTION "createDesktopShortcut" ; Show run app after installation. -!define MUI_FINISHPAGE_RUN $INSTDIR\Resources.exe +!define MUI_FINISHPAGE_RUN $INSTDIR\${MAINBINARYNAME}.exe ; Installer pages, must be ordered as they appear !insertmacro MUI_PAGE_WELCOME @@ -131,7 +131,7 @@ Section Install SetOutPath $INSTDIR ; Main executable - File "${MAINBINARYPATH}" + File "${MAINBINARYSRCPATH}" ; Copy resources {{#each resources}} From f6f2a18c945c07212a08e99cfe103cce0198058b Mon Sep 17 00:00:00 2001 From: amrbashir Date: Fri, 15 Jul 2022 22:09:41 +0200 Subject: [PATCH 08/46] set AppUserModelID --- tooling/bundler/src/bundle/windows/nsis.rs | 39 ++++++++++++------- .../bundle/windows/templates/installer.nsi | 2 + tooling/bundler/src/bundle/windows/util.rs | 10 ++++- 3 files changed, 36 insertions(+), 15 deletions(-) diff --git a/tooling/bundler/src/bundle/windows/nsis.rs b/tooling/bundler/src/bundle/windows/nsis.rs index 78e60072ab2..deaa49c7e1c 100644 --- a/tooling/bundler/src/bundle/windows/nsis.rs +++ b/tooling/bundler/src/bundle/windows/nsis.rs @@ -19,7 +19,7 @@ use tauri_utils::resources::resource_relpath; use std::{ collections::BTreeMap, - fs::{create_dir_all, remove_dir_all, rename, write}, + fs::{copy, create_dir_all, remove_dir_all, rename, write}, path::{Path, PathBuf}, process::Command, }; @@ -30,12 +30,14 @@ const NSIS_URL: &str = const NSIS_SHA1: &str = "057e83c7d82462ec394af76c87d06733605543d4"; const NS_CURL_URL: &str = "https://github.com/negrutiu/nsis-nscurl/releases/download/v1.2022.6.7/NScurl-1.2022.6.7.7z"; +const NSIS_APPLICATION_ID_URL: &str = "https://github.com/connectiblutz/NSIS-ApplicationID/releases/download/1.1/NSIS-ApplicationID.zip"; const NSIS_REQUIRED_FILES: &[&str] = &[ "makensis.exe", "Bin/makensis.exe", "Stubs/zlib-x86-unicode", "Plugins/x86-unicode/NScurl.dll", + "Plugins/x86-unicode/ApplicationID.dll", "Include/MUI2.nsh", "Include/FileFunc.nsh", "Include/x64.nsh", @@ -44,36 +46,47 @@ const NSIS_REQUIRED_FILES: &[&str] = &[ /// Runs all of the commands to build the NSIS installer. /// Returns a vector of PathBuf that shows where the NSIS installer was created. pub fn bundle_project(settings: &Settings) -> crate::Result> { - let nsis_path = dirs_next::cache_dir().unwrap().join("tauri").join("NSIS"); - let nsis_toolset_path = nsis_path.join("nsis-3.08"); + let tauri_tools_path = dirs_next::cache_dir().unwrap().join("tauri"); + let nsis_toolset_path = tauri_tools_path.join("NSIS"); - if !nsis_path.exists() { - get_and_extract_nsis(&nsis_path)?; + if !nsis_toolset_path.exists() { + get_and_extract_nsis(&nsis_toolset_path, &tauri_tools_path)?; } else if NSIS_REQUIRED_FILES .iter() .any(|p| !nsis_toolset_path.join(p).exists()) { warn!("NSIS directory is missing some files. Recreating it."); - std::fs::remove_dir_all(&nsis_path)?; - get_and_extract_nsis(&nsis_path)?; + std::fs::remove_dir_all(&nsis_toolset_path)?; + get_and_extract_nsis(&nsis_toolset_path, &tauri_tools_path)?; } build_nsis_app_installer(settings, &nsis_toolset_path) } -// Gets NSIS and verifies the download via Sha256 -fn get_and_extract_nsis(path: &Path) -> crate::Result<()> { +// Gets NSIS and verifies the download via Sha1 +fn get_and_extract_nsis(nsis_toolset_path: &Path, tauri_tools_path: &Path) -> crate::Result<()> { info!("Verifying NSIS package"); let data = download_and_verify(NSIS_URL, NSIS_SHA1, "sha1")?; - info!("extracting NSIS"); - extract_zip(&data, path)?; + extract_zip(&data, tauri_tools_path)?; + rename(tauri_tools_path.join("nsis-3.08"), nsis_toolset_path)?; - let data = download(NS_CURL_URL)?; + let nsis_plugins = nsis_toolset_path.join("Plugins"); + let data = download(NS_CURL_URL)?; info!("extracting NScurl"); - extract_7z(&data, &path.join("nsis-3.08").join("Plugins"))?; + extract_7z(&data, &nsis_plugins)?; + + let data = download(NSIS_APPLICATION_ID_URL)?; + info!("extracting NSIS-ApplicationID"); + extract_zip(&data, &nsis_plugins)?; + copy( + nsis_plugins + .join("ReleaseUnicode") + .join("ApplicationID.dll"), + nsis_plugins.join("x86-unicode").join("ApplicationID.dll"), + )?; Ok(()) } diff --git a/tooling/bundler/src/bundle/windows/templates/installer.nsi b/tooling/bundler/src/bundle/windows/templates/installer.nsi index a6b5349662c..060d875f133 100644 --- a/tooling/bundler/src/bundle/windows/templates/installer.nsi +++ b/tooling/bundler/src/bundle/windows/templates/installer.nsi @@ -170,6 +170,8 @@ Section Install !insertmacro MUI_STARTMENU_WRITE_BEGIN Application CreateDirectory "$SMPROGRAMS\$AppStartMenuFolder" CreateShortcut "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe" + ApplicationID::Set "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk" "{{{bundle_id}}}" + !insertmacro MUI_STARTMENU_WRITE_END SectionEnd diff --git a/tooling/bundler/src/bundle/windows/util.rs b/tooling/bundler/src/bundle/windows/util.rs index 4893525dabf..aad9b26f569 100644 --- a/tooling/bundler/src/bundle/windows/util.rs +++ b/tooling/bundler/src/bundle/windows/util.rs @@ -106,7 +106,13 @@ pub fn extract_zip(data: &[u8], path: &Path) -> crate::Result<()> { for i in 0..zipa.len() { let mut file = zipa.by_index(i)?; + let dest_path = path.join(file.name()); + if file.is_dir() { + create_dir_all(&dest_path)?; + continue; + } + let parent = dest_path.parent().expect("Failed to get parent"); if !parent.exists() { @@ -128,9 +134,9 @@ const URL_7ZR: &str = "https://www.7-zip.org/a/7zr.exe"; pub fn extract_7z(data: &[u8], path: &Path) -> crate::Result<()> { let bin_7z = { debug!("checking for 7z.exe or 7zr.exe is in $PATH"); - if let Ok(_) = Command::new("7z.exe").spawn() { + if let Ok(_) = Command::new("7z.exe").output() { "7z.exe".to_string() - } else if let Ok(_) = Command::new("7zr.exe").spawn() { + } else if let Ok(_) = Command::new("7zr.exe").output() { "7zr.exe".to_string() } else { get_or_download_7zr_bin()?.to_string_lossy().to_string() From e5b72f7d59afd8f1dfdbe593f5086a3acaae0934 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Fri, 15 Jul 2022 22:13:07 +0200 Subject: [PATCH 09/46] set AppUserModelID for desktop shortcut too --- tooling/bundler/src/bundle/windows/templates/installer.nsi | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tooling/bundler/src/bundle/windows/templates/installer.nsi b/tooling/bundler/src/bundle/windows/templates/installer.nsi index 060d875f133..fbf89b2289e 100644 --- a/tooling/bundler/src/bundle/windows/templates/installer.nsi +++ b/tooling/bundler/src/bundle/windows/templates/installer.nsi @@ -17,6 +17,7 @@ !define HEADERIMAGE "{{{header_image}}}" !define MAINBINARYNAME "{{{main_binary_name}}}" !define MAINBINARYSRCPATH "{{{main_binary_path}}}" +!define BUNDLEID "{{{bundle_id}}}" !define APR "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCTNAME}" Var AppStartMenuFolder ; Will be set through `MUI_PAGE_STARTMENU` page. Used to determine where to create the start menu shortcut ; --- @@ -85,6 +86,7 @@ OutFile "{{{out_file}}}" Function createDesktopShortcut CreateShortcut "$DESKTOP\${MAINBINARYNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe" + ApplicationID::Set "$DESKTOP\${MAINBINARYNAME}.lnk" "${BUNDLEID}" FunctionEnd Section Webview2 @@ -170,7 +172,7 @@ Section Install !insertmacro MUI_STARTMENU_WRITE_BEGIN Application CreateDirectory "$SMPROGRAMS\$AppStartMenuFolder" CreateShortcut "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe" - ApplicationID::Set "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk" "{{{bundle_id}}}" + ApplicationID::Set "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk" "${BUNDLEID}" !insertmacro MUI_STARTMENU_WRITE_END From 7d9f88e94f914438f01e4fd4c569c8b241e34491 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Sat, 16 Jul 2022 21:53:45 +0200 Subject: [PATCH 10/46] add `allowDowngrades`, x32, lzma compression and reinstall page --- tooling/bundler/src/bundle/windows/nsis.rs | 12 +- .../bundle/windows/templates/installer.nsi | 188 ++++++++++++++++-- 2 files changed, 183 insertions(+), 17 deletions(-) diff --git a/tooling/bundler/src/bundle/windows/nsis.rs b/tooling/bundler/src/bundle/windows/nsis.rs index deaa49c7e1c..2c8ff39a950 100644 --- a/tooling/bundler/src/bundle/windows/nsis.rs +++ b/tooling/bundler/src/bundle/windows/nsis.rs @@ -35,7 +35,8 @@ const NSIS_APPLICATION_ID_URL: &str = "https://github.com/connectiblutz/NSIS-App const NSIS_REQUIRED_FILES: &[&str] = &[ "makensis.exe", "Bin/makensis.exe", - "Stubs/zlib-x86-unicode", + "Stubs/lzma-x86-unicode", + "Stubs/lzma_solid-x86-unicode", "Plugins/x86-unicode/NScurl.dll", "Plugins/x86-unicode/ApplicationID.dll", "Include/MUI2.nsh", @@ -130,6 +131,7 @@ fn build_nsis_app_installer( let bundle_id = settings.bundle_identifier(); let manufacturer = bundle_id.split('.').nth(1).unwrap_or(bundle_id); + data.insert("arch", to_json(arch)); data.insert("bundle_id", to_json(bundle_id)); data.insert("manufacturer", to_json(manufacturer)); data.insert("product_name", to_json(settings.product_name())); @@ -140,6 +142,12 @@ fn build_nsis_app_installer( let mut s = version.split("."); data.insert("version_major", to_json(s.next().unwrap())); data.insert("version_minor", to_json(s.next().unwrap())); + data.insert("version_build", to_json(s.next().unwrap())); + + data.insert( + "allow_downgrades", + to_json(settings.windows().allow_downgrades), + ); if let Some(nsis) = &settings.windows().nsis { data.insert( @@ -212,7 +220,7 @@ fn build_nsis_app_installer( )?; let package_base_name = format!( - "{}_{}_{}", + "{}_{}_{}-setup", main_binary.name().replace(".exe", ""), settings.version_string(), arch, diff --git a/tooling/bundler/src/bundle/windows/templates/installer.nsi b/tooling/bundler/src/bundle/windows/templates/installer.nsi index fbf89b2289e..52d2c2ded09 100644 --- a/tooling/bundler/src/bundle/windows/templates/installer.nsi +++ b/tooling/bundler/src/bundle/windows/templates/installer.nsi @@ -1,15 +1,19 @@ -; Imports +;-------------------------------- +; Header files !include MUI2.nsh !include FileFunc.nsh !include x64.nsh -; --- +!include WordFunc.nsh + +;-------------------------------- +; definitions -; Variables !define MANUFACTURER "{{{manufacturer}}}" !define PRODUCTNAME "{{{product_name}}}" !define VERSION "{{{version}}}" !define VERSIONMAJOR "{{{version_major}}}" !define VERSIONMINOR "{{{version_minor}}}" +!define VERSIONBUILD "{{{version_build}}}" !define INSTALLMODE "{{{installer_mode}}}" !define LICENSE "{{{license}}}" !define INSTALLERICON "{{{installer_icon}}}" @@ -18,19 +22,34 @@ !define MAINBINARYNAME "{{{main_binary_name}}}" !define MAINBINARYSRCPATH "{{{main_binary_path}}}" !define BUNDLEID "{{{bundle_id}}}" +!define OUTFILE "{{{out_file}}}" +!define ARCH "{{{arch}}}" +!define ALLOWDOWNGRADES "{{{allow_downgrades}}}" !define APR "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCTNAME}" -Var AppStartMenuFolder ; Will be set through `MUI_PAGE_STARTMENU` page. Used to determine where to create the start menu shortcut -; --- -Unicode true +;-------------------------------- +; Variables + +Var AppStartMenuFolder +Var ReinstallPageCheck + +;-------------------------------- +; General onfiguration + Name "${PRODUCTNAME}" -OutFile "{{{out_file}}}" +OutFile "${OUTFILE}" +Unicode true +SetCompressor /SOLID lzma !if "${INSTALLMODE}" == "perMachine" RequestExecutionLevel heighest ; Set default install location - !if ${RunningX64} - InstallDir "$PROGRAMFILES64\${PRODUCTNAME}" + !if ${RunningX64} + !if "${ARCH}" == "x64" + InstallDir "$PROGRAMFILES64\${PRODUCTNAME}" + !else + InstallDir "$PROGRAMFILES\${PRODUCTNAME}" + !endif !else InstallDir "$PROGRAMFILES\${PRODUCTNAME}" !endif @@ -42,6 +61,9 @@ OutFile "{{{out_file}}}" InstallDirRegKey HKCU "Software\${MANUFACTURER}\${PRODUCTNAME}" "" !endif +;-------------------------------- +;Interface Settings + !if "${INSTALLERICON}" != "" !define MUI_ICON "${INSTALLERICON}" !endif @@ -56,7 +78,7 @@ OutFile "{{{out_file}}}" !endif ; Don't auto jump to finish page after installation page, -; because the instalation page has useful info that can be used debug any issues with the installer. +; because the installation page has useful info that can be used debug any issues with the installer. !define MUI_FINISHPAGE_NOAUTOCLOSE ; Use show readme button in the finish page to create a desktop shortcut @@ -67,8 +89,12 @@ OutFile "{{{out_file}}}" ; Show run app after installation. !define MUI_FINISHPAGE_RUN $INSTDIR\${MAINBINARYNAME}.exe +;-------------------------------- +; Installer pages + ; Installer pages, must be ordered as they appear !insertmacro MUI_PAGE_WELCOME +Page custom PageReinstall PageLeaveReinstall !if "${LICENSE}" != "" !insertmacro MUI_PAGE_LICENSE "${LICENSE}" !endif @@ -77,17 +103,18 @@ OutFile "{{{out_file}}}" !insertmacro MUI_PAGE_INSTFILES !insertmacro MUI_PAGE_FINISH +;-------------------------------- ; Uninstaller pages + !insertmacro MUI_UNPAGE_CONFIRM !insertmacro MUI_UNPAGE_INSTFILES -; Languages +;-------------------------------- +;Languages !insertmacro MUI_LANGUAGE English -Function createDesktopShortcut - CreateShortcut "$DESKTOP\${MAINBINARYNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe" - ApplicationID::Set "$DESKTOP\${MAINBINARYNAME}.lnk" "${BUNDLEID}" -FunctionEnd +;-------------------------------- +;Installer Sections Section Webview2 ; Check if Webview2 is already installed @@ -162,6 +189,7 @@ Section Install WriteRegStr SHCTX "${APR}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" WriteRegDWORD SHCTX "${APR}" "VersionMajor" ${VERSIONMAJOR} WriteRegDWORD SHCTX "${APR}" "VersionMinor" ${VERSIONMINOR} + WriteRegDWORD SHCTX "${APR}" "VersionBuild" ${VERSIONBUILD} WriteRegDWORD SHCTX "${APR}" "NoModify" "1" WriteRegDWORD SHCTX "${APR}" "NoRepair" "1" ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 @@ -190,3 +218,133 @@ Section Uninstall ; Remove registry information for add/remove programs DeleteRegKey SHCTX "${APR}" SectionEnd + +;-------------------------------- +;Installer Functions + +Function createDesktopShortcut + CreateShortcut "$DESKTOP\${MAINBINARYNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe" + ApplicationID::Set "$DESKTOP\${MAINBINARYNAME}.lnk" "${BUNDLEID}" +FunctionEnd + +Function PageReinstall + ; Check if there is an existing installation, if not, abort the reinstall page + ReadRegStr $R0 SHCTX "${APR}" "" + ReadRegStr $R1 SHCTX "${APR}" "UninstallString" + ${IfThen} "$R0$R1" == "" ${|} Abort ${|} + + ; Compare this installar version with the existing installation and modify the messages presented to the user accordingly + StrCpy $R4 "older" + ReadRegStr $R0 SHCTX "${APR}" "DisplayVersion" + ${IfThen} $R0 == "" ${|} StrCpy $R4 "unknown" ${|} + + ${VersionCompare} "$\"${VERSION}$\"" $R0 $R0 + ; Reinstalling the same version + ${If} $R0 == 0 + StrCpy $R1 "${PRODUCTNAME} ${VERSION} is already installed. Select the operation you want to perform and click Next to continue." + StrCpy $R2 "Add/Reinstall components" + StrCpy $R3 "Uninstall ${PRODUCTNAME}" + !insertmacro MUI_HEADER_TEXT "Already Installed" "Choose the maintenance option to perform." + StrCpy $R0 "2" + ; Upgrading + ${ElseIf} $R0 == 1 + StrCpy $R1 "An $R4 version of ${PRODUCTNAME} is installed on your system. It's recommended that you uninstall the current version before installing. Select the operation you want to perform and click Next to continue." + StrCpy $R2 "Uninstall before installing" + StrCpy $R3 "Do not uninstall" + !insertmacro MUI_HEADER_TEXT "Already Installed" "Choose how you want to install ${PRODUCTNAME}." + StrCpy $R0 "1" + ; Downgrading + !if "${ALLOWDOWNGRADES}" == "true" + ${ElseIf} $R0 == 2 + StrCpy $R1 "A newer version of ${PRODUCTNAME} is already installed! It is not recommended that you install an older version. If you really want to install this older version, it's better to uninstall the current version first. Select the operation you want to perform and click Next to continue." + StrCpy $R2 "Uninstall before installing" + StrCpy $R3 "Do not uninstall" + !insertmacro MUI_HEADER_TEXT "Already Installed" "Choose how you want to install ${PRODUCTNAME}." + StrCpy $R0 "1" + !endif + ${Else} + Abort + ${EndIf} + + nsDialogs::Create 1018 + Pop $R4 + + ${NSD_CreateLabel} 0 0 100% 24u $R1 + Pop $R1 + + ${NSD_CreateRadioButton} 30u 50u -30u 8u $R2 + Pop $R2 + ${NSD_OnClick} $R2 PageReinstallUpdateSelection + + ${NSD_CreateRadioButton} 30u 70u -30u 8u $R3 + Pop $R3 + ${NSD_OnClick} $R3 PageReinstallUpdateSelection + + ${If} $ReinstallPageCheck != 2 + SendMessage $R2 ${BM_SETCHECK} ${BST_CHECKED} 0 + ${Else} + SendMessage $R3 ${BM_SETCHECK} ${BST_CHECKED} 0 + ${EndIf} + + ${NSD_SetFocus} $R2 + + nsDialogs::Show +FunctionEnd + +Function PageReinstallUpdateSelection + Pop $R1 + + ${NSD_GetState} $R2 $R1 + + ${If} $R1 == ${BST_CHECKED} + StrCpy $ReinstallPageCheck 1 + ${Else} + StrCpy $ReinstallPageCheck 2 + ${EndIf} + +FunctionEnd + +Function PageLeaveReinstall + ${NSD_GetState} $R2 $R1 + + ; $R0 holds whether we are reinstallign the same version or not + ; $R0 == "1" -> different versions + ; $R0 == "2" -> same version + ; + ; $R1 holds the radio buttons state. its meaning is dependant on the context + + StrCmp $R0 "1" 0 +2 ; Existing install is not the same version? + StrCmp $R1 "1" reinst_uninstall reinst_done + + StrCmp $R1 "1" reinst_done ; Same version, skip to add/reinstall components? + + reinst_uninstall: + ReadRegStr $R1 SHCTX "${APR}" "UninstallString" + + HideWindow + + ClearErrors + ExecWait '$R1 _?=$INSTDIR' $0 + + BringToFront + + ${IfThen} ${Errors} ${|} StrCpy $0 2 ${|} ; ExecWait failed, set fake exit code + + ${If} $0 <> 0 + ${OrIf} ${FileExists} "$INSTDIR\${MAINBINARYNAME}.exe" + ${If} $0 = 1 ; User aborted uninstaller? + StrCmp $R0 "2" 0 +2 ; Is the existing install the same version? + Quit ; ...yes, already installed, we are done + Abort + ${EndIf} + MessageBox MB_ICONEXCLAMATION "Unable to uninstall!" + Abort + ${Else} + StrCpy $0 $R1 1 + ${IfThen} $0 == '"' ${|} StrCpy $R1 $R1 -1 1 ${|} ; Strip quotes from UninstallString + Delete $R1 + RMDir $INSTDIR + ${EndIf} + + reinst_done: +FunctionEnd From ab4e3c6a9016d206bd5b59a32537a6704b84a91e Mon Sep 17 00:00:00 2001 From: amrbashir Date: Sat, 16 Jul 2022 22:18:23 +0200 Subject: [PATCH 11/46] fix `allowDowngrades` and disable moving on without uninstalling first --- .../bundler/src/bundle/windows/templates/installer.nsi | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tooling/bundler/src/bundle/windows/templates/installer.nsi b/tooling/bundler/src/bundle/windows/templates/installer.nsi index 52d2c2ded09..c06ba7db791 100644 --- a/tooling/bundler/src/bundle/windows/templates/installer.nsi +++ b/tooling/bundler/src/bundle/windows/templates/installer.nsi @@ -254,14 +254,16 @@ Function PageReinstall !insertmacro MUI_HEADER_TEXT "Already Installed" "Choose how you want to install ${PRODUCTNAME}." StrCpy $R0 "1" ; Downgrading - !if "${ALLOWDOWNGRADES}" == "true" ${ElseIf} $R0 == 2 StrCpy $R1 "A newer version of ${PRODUCTNAME} is already installed! It is not recommended that you install an older version. If you really want to install this older version, it's better to uninstall the current version first. Select the operation you want to perform and click Next to continue." StrCpy $R2 "Uninstall before installing" + !if "${ALLOWDOWNGRADES}" == "true" StrCpy $R3 "Do not uninstall" + !else + StrCpy $R3 "Do not uninstall (Downgrading without uninstall is disabled for this installer)" + !endif !insertmacro MUI_HEADER_TEXT "Already Installed" "Choose how you want to install ${PRODUCTNAME}." StrCpy $R0 "1" - !endif ${Else} Abort ${EndIf} @@ -278,6 +280,10 @@ Function PageReinstall ${NSD_CreateRadioButton} 30u 70u -30u 8u $R3 Pop $R3 + ; disable this radio button if downgards are not allowed + !if "${ALLOWDOWNGRADES}" == "false" + EnableWindow $R3 0 + !endif ${NSD_OnClick} $R3 PageReinstallUpdateSelection ${If} $ReinstallPageCheck != 2 From bdb7d9638ba97806f625c247539f0fc2d5b68f95 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Sun, 17 Jul 2022 16:29:13 +0200 Subject: [PATCH 12/46] abort silent installer if allowdowngrades is disabled --- .../bundle/windows/templates/installer.nsi | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/tooling/bundler/src/bundle/windows/templates/installer.nsi b/tooling/bundler/src/bundle/windows/templates/installer.nsi index c06ba7db791..9253bf177a4 100644 --- a/tooling/bundler/src/bundle/windows/templates/installer.nsi +++ b/tooling/bundler/src/bundle/windows/templates/installer.nsi @@ -116,6 +116,22 @@ Page custom PageReinstall PageLeaveReinstall ;-------------------------------- ;Installer Sections +Section SilentChecks + ; Abort silent installer if downgrades is disabled + !if "${ALLOWDOWNGRADES}" == "false" + IfSilent 0 silent_checks_done + System::Call 'kernel32::AttachConsole(i -1)i.r0' ;attach to parent console + ${If} $0 != 0 + System::Call 'kernel32::GetStdHandle(i -11)i.r0' ;console attached -- get stdout + System::call 'kernel32::SetConsoleTextAttribute(i r0, i 0x0004)'; set red color + FileWrite $0 "A newer version is already installed! Automatic silent downgrades are disabled for this installer.$\nIt is not recommended that you install an older version. If you really want to install this older version, you have to uninstall the current version first.$\n" + System::call 'kernel32::SetConsoleTextAttribute(i r0, i 0x0004)'; set red color + ${EndIf} + Abort + !endif + silent_checks_done: +SectionEnd + Section Webview2 ; Check if Webview2 is already installed ${If} ${RunningX64} @@ -258,9 +274,9 @@ Function PageReinstall StrCpy $R1 "A newer version of ${PRODUCTNAME} is already installed! It is not recommended that you install an older version. If you really want to install this older version, it's better to uninstall the current version first. Select the operation you want to perform and click Next to continue." StrCpy $R2 "Uninstall before installing" !if "${ALLOWDOWNGRADES}" == "true" - StrCpy $R3 "Do not uninstall" + StrCpy $R3 "Do not uninstall" !else - StrCpy $R3 "Do not uninstall (Downgrading without uninstall is disabled for this installer)" + StrCpy $R3 "Do not uninstall (Downgrading without uninstall is disabled for this installer)" !endif !insertmacro MUI_HEADER_TEXT "Already Installed" "Choose how you want to install ${PRODUCTNAME}." StrCpy $R0 "1" @@ -280,7 +296,7 @@ Function PageReinstall ${NSD_CreateRadioButton} 30u 70u -30u 8u $R3 Pop $R3 - ; disable this radio button if downgards are not allowed + ; disable this radio button if downgrades are not allowed !if "${ALLOWDOWNGRADES}" == "false" EnableWindow $R3 0 !endif From 8fba86a14c1b21b29ea06f3f24c991ae90497388 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Sun, 17 Jul 2022 18:58:00 +0200 Subject: [PATCH 13/46] webview2 install modes --- tooling/bundler/src/bundle.rs | 2 +- tooling/bundler/src/bundle/windows/msi/wix.rs | 18 ++-- tooling/bundler/src/bundle/windows/nsis.rs | 97 ++++++++++++++++++- .../bundle/windows/templates/installer.nsi | 54 ++++++++--- tooling/bundler/src/bundle/windows/util.rs | 4 + 5 files changed, 148 insertions(+), 27 deletions(-) diff --git a/tooling/bundler/src/bundle.rs b/tooling/bundler/src/bundle.rs index 42734debb0b..43310622c4e 100644 --- a/tooling/bundler/src/bundle.rs +++ b/tooling/bundler/src/bundle.rs @@ -51,7 +51,7 @@ pub fn bundle_project(settings: Settings) -> crate::Result> { #[cfg(target_os = "windows")] PackageType::WindowsMsi => windows::msi::bundle_project(&settings, false)?, #[cfg(target_os = "windows")] - PackageType::Nsis => windows::nsis::bundle_project(&settings)?, + PackageType::Nsis => windows::nsis::bundle_project(&settings, false)?, #[cfg(target_os = "linux")] PackageType::Deb => linux::debian::bundle_project(&settings)?, #[cfg(target_os = "linux")] diff --git a/tooling/bundler/src/bundle/windows/msi/wix.rs b/tooling/bundler/src/bundle/windows/msi/wix.rs index 4ca80708676..7992e7a3305 100644 --- a/tooling/bundler/src/bundle/windows/msi/wix.rs +++ b/tooling/bundler/src/bundle/windows/msi/wix.rs @@ -6,7 +6,10 @@ use crate::bundle::{ common::CommandExt, path_utils::{copy_file, FileOpts}, settings::Settings, - windows::util::{download, download_and_verify, extract_zip, try_sign, validate_version}, + windows::util::{ + download, download_and_verify, extract_zip, try_sign, validate_version, + WEBVIEW2_BOOTSTRAPPER_URL, WEBVIEW2_X64_INSTALLER_GUID, WEBVIEW2_X86_INSTALLER_GUID, + }, }; use anyhow::Context; use handlebars::{to_json, Handlebars}; @@ -29,9 +32,6 @@ pub const WIX_URL: &str = pub const WIX_SHA256: &str = "2c1888d5d1dba377fc7fa14444cf556963747ff9a0a289a3599cf09da03b9e2e"; pub const MSI_FOLDER_NAME: &str = "msi"; pub const MSI_UPDATER_FOLDER_NAME: &str = "msi-updater"; -const WEBVIEW2_BOOTSTRAPPER_URL: &str = "https://go.microsoft.com/fwlink/p/?LinkId=2124703"; -const WEBVIEW2_X86_INSTALLER_GUID: &str = "a17bde80-b5ab-47b5-8bbb-1cbe93fc6ec9"; -const WEBVIEW2_X64_INSTALLER_GUID: &str = "aa5fd9b3-dc11-4cbc-8343-a50f57b311e1"; // For Cross Platform Compilation. @@ -420,10 +420,12 @@ pub fn build_wix_app_installer( } else { WEBVIEW2_X86_INSTALLER_GUID }; - let mut offline_installer_path = dirs_next::cache_dir().unwrap(); - offline_installer_path.push("tauri"); - offline_installer_path.push(guid); - offline_installer_path.push(arch); + let offline_installer_path = dirs_next::cache_dir() + .unwrap() + .join("tauri") + .join("Webview2OfflineInstaller") + .join(guid) + .join(arch); create_dir_all(&offline_installer_path)?; let webview2_installer_path = offline_installer_path.join("MicrosoftEdgeWebView2RuntimeInstaller.exe"); diff --git a/tooling/bundler/src/bundle/windows/nsis.rs b/tooling/bundler/src/bundle/windows/nsis.rs index 2c8ff39a950..25a449bca56 100644 --- a/tooling/bundler/src/bundle/windows/nsis.rs +++ b/tooling/bundler/src/bundle/windows/nsis.rs @@ -7,7 +7,8 @@ use crate::{ common::CommandExt, windows::util::{ download, download_and_verify, extract_7z, extract_zip, remove_unc_lossy, try_sign, - validate_version, + validate_version, WEBVIEW2_BOOTSTRAPPER_URL, WEBVIEW2_X64_INSTALLER_GUID, + WEBVIEW2_X86_INSTALLER_GUID, }, }, Settings, @@ -15,7 +16,7 @@ use crate::{ use anyhow::Context; use handlebars::{to_json, Handlebars}; use log::{info, warn}; -use tauri_utils::resources::resource_relpath; +use tauri_utils::{config::WebviewInstallMode, resources::resource_relpath}; use std::{ collections::BTreeMap, @@ -46,7 +47,7 @@ const NSIS_REQUIRED_FILES: &[&str] = &[ /// Runs all of the commands to build the NSIS installer. /// Returns a vector of PathBuf that shows where the NSIS installer was created. -pub fn bundle_project(settings: &Settings) -> crate::Result> { +pub fn bundle_project(settings: &Settings, updater: bool) -> crate::Result> { let tauri_tools_path = dirs_next::cache_dir().unwrap().join("tauri"); let nsis_toolset_path = tauri_tools_path.join("NSIS"); @@ -61,7 +62,7 @@ pub fn bundle_project(settings: &Settings) -> crate::Result> { get_and_extract_nsis(&nsis_toolset_path, &tauri_tools_path)?; } - build_nsis_app_installer(settings, &nsis_toolset_path) + build_nsis_app_installer(settings, &nsis_toolset_path, &tauri_tools_path, updater) } // Gets NSIS and verifies the download via Sha1 @@ -95,6 +96,8 @@ fn get_and_extract_nsis(nsis_toolset_path: &Path, tauri_tools_path: &Path) -> cr fn build_nsis_app_installer( settings: &Settings, nsis_toolset_path: &Path, + tauri_tools_path: &Path, + updater: bool, ) -> crate::Result> { let arch = match settings.binary_arch() { "x86_64" => "x64", @@ -208,6 +211,92 @@ fn build_nsis_app_installer( let binaries = generate_binaries_data(settings)?; data.insert("binaries", to_json(binaries)); + let silent_webview2_install = if let WebviewInstallMode::DownloadBootstrapper { silent } + | WebviewInstallMode::EmbedBootstrapper { silent } + | WebviewInstallMode::OfflineInstaller { silent } = + settings.windows().webview_install_mode + { + silent + } else { + true + }; + + let webview2_install_mode = if updater { + WebviewInstallMode::DownloadBootstrapper { + silent: silent_webview2_install, + } + } else { + let mut webview_install_mode = settings.windows().webview_install_mode.clone(); + if let Some(fixed_runtime_path) = settings.windows().webview_fixed_runtime_path.clone() { + webview_install_mode = WebviewInstallMode::FixedRuntime { + path: fixed_runtime_path, + }; + } else if let Some(wix) = &settings.windows().wix { + if wix.skip_webview_install { + webview_install_mode = WebviewInstallMode::Skip; + } + } + webview_install_mode + }; + + let webview2_installer_args = to_json(if silent_webview2_install { + "/silent" + } else { + "" + }); + + data.insert("webview2_installer_args", to_json(webview2_installer_args)); + data.insert( + "install_webview2_mode", + to_json(match webview2_install_mode { + WebviewInstallMode::DownloadBootstrapper { silent: _ } => "downloadBootstrapper", + WebviewInstallMode::EmbedBootstrapper { silent: _ } => "embedBootstrapper", + WebviewInstallMode::OfflineInstaller { silent: _ } => "offlineInstaller", + _ => "", + }), + ); + + match webview2_install_mode { + WebviewInstallMode::EmbedBootstrapper { silent: _ } => { + let webview2_bootstrapper_path = tauri_tools_path.join("MicrosoftEdgeWebview2Setup.exe"); + std::fs::write( + &webview2_bootstrapper_path, + download(WEBVIEW2_BOOTSTRAPPER_URL)?, + )?; + data.insert( + "webview2_bootstrapper_path", + to_json(webview2_bootstrapper_path), + ); + } + WebviewInstallMode::OfflineInstaller { silent: _ } => { + let guid = if arch == "x64" { + WEBVIEW2_X64_INSTALLER_GUID + } else { + WEBVIEW2_X86_INSTALLER_GUID + }; + let offline_installer_path = tauri_tools_path + .join("Webview2OfflineInstaller") + .join(guid) + .join(arch); + create_dir_all(&offline_installer_path)?; + let webview2_installer_path = + offline_installer_path.join("MicrosoftEdgeWebView2RuntimeInstaller.exe"); + if !webview2_installer_path.exists() { + std::fs::write( + &webview2_installer_path, + download( + &format!("https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/{}/MicrosoftEdgeWebView2RuntimeInstaller{}.exe", + guid, + arch.to_uppercase(), + ), + )?, + )?; + } + data.insert("webview2_installer_path", to_json(webview2_installer_path)); + } + _ => {} + } + let mut handlebars = Handlebars::new(); handlebars .register_template_string("installer.nsi", include_str!("./templates/installer.nsi")) diff --git a/tooling/bundler/src/bundle/windows/templates/installer.nsi b/tooling/bundler/src/bundle/windows/templates/installer.nsi index 9253bf177a4..284880e0167 100644 --- a/tooling/bundler/src/bundle/windows/templates/installer.nsi +++ b/tooling/bundler/src/bundle/windows/templates/installer.nsi @@ -25,6 +25,10 @@ !define OUTFILE "{{{out_file}}}" !define ARCH "{{{arch}}}" !define ALLOWDOWNGRADES "{{{allow_downgrades}}}" +!define INSTALLWEBVIEW2MODE "{{{install_webview2_mode}}}" +!define WEBVIEW2INSTALLERARGS "{{{webview2_installer_args}}}" +!define WEBVIEW2BOOTSTRAPPERPATH "{{{webview2_bootstrapper_path}}}" +!define WEBVIEW2INSTALLERPATH "{{{webview2_installer_path}}}" !define APR "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCTNAME}" ;-------------------------------- @@ -133,7 +137,7 @@ Section SilentChecks SectionEnd Section Webview2 - ; Check if Webview2 is already installed + ; Check if Webview2 is already installed and skip this section ${If} ${RunningX64} ReadRegStr $4 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" ${Else} @@ -141,34 +145,56 @@ Section Webview2 ${EndIf} ReadRegStr $5 HKCU "SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" - ${If} $4 == "" - ${AndIf} $5 == "" - Delete "$TEMP\MicrosoftEdgeWebview2Setup.exe" + StrCmp $4 "" 0 done + StrCmp $5 "" 0 done + + ;-------------------------------- + ; Webview2 install modes - DetailPrint "Downloading Webview2 installer..." + !if "${INSTALLWEBVIEW2MODE}" == "downloadBootstrapper" + Delete "$TEMP\MicrosoftEdgeWebview2Setup.exe" + DetailPrint "Downloading Webview2 bootstrapper..." NScurl::http GET "https://go.microsoft.com/fwlink/p/?LinkId=2124703" "$TEMP\MicrosoftEdgeWebview2Setup.exe" /CANCEL /END Pop $0 ${If} $0 == "OK" - DetailPrint "Webview2 installer downloaded sucessfully" + DetailPrint "Webview2 bootstrapper downloaded sucessfully" ${Else} DetailPrint "Error: Downloading Webview2 Failed - $0" - Goto abort + Abort "Failed to install Webview2. The app can't run without it. Try restarting the installer" ${EndIf} + StrCpy $6 "$TEMP\MicrosoftEdgeWebview2Setup.exe" + Goto install_webview2 + !endif + !if "${INSTALLWEBVIEW2MODE}" == "embedBootstrapper" + CreateDirectory "$INSTDIR\redist" + File /oname="$INSTDIR\redist\MicrosoftEdgeWebview2Setup.exe" "WEBVIEW2BOOTSTRAPPERPATH" DetailPrint "Installing Webview2..." - ExecWait "$TEMP\MicrosoftEdgeWebview2Setup.exe /install" $1 + StrCpy $6 "$INSTDIR\redist\MicrosoftEdgeWebview2Setup.exe" + Goto install_webview2 + !endif + + !if "${INSTALLWEBVIEW2MODE}" == "offlineInstaller" + CreateDirectory "$INSTDIR\redist" + File /oname="$INSTDIR\redist\MicrosoftEdgeWebView2RuntimeInstaller.exe" "WEBVIEW2INSTALLERPATH" + DetailPrint "Installing Webview2..." + StrCpy $6 "$INSTDIR\redist\MicrosoftEdgeWebView2RuntimeInstaller.exe" + Goto install_webview2 + !endif + + Goto done + + install_webview2: + DetailPrint "Installing Webview2..." + ; $6 holds the path to the webview2 installer + ExecWait "$6 /install ${WEBVIEW2INSTALLERARGS}" $1 ${If} $1 == 0 DetailPrint "Webview2 installed sucessfully" ${Else} DetailPrint "Error: Installing Webview2 Failed with exit code $1" - Goto abort + Abort "Failed to install Webview2. The app can't run without it. Try restarting the installer" ${EndIf} - ${EndIf} - - Goto done - abort: - Abort "Failed to install Webview2. The app can't run without it. Try restarting the installer" done: SectionEnd diff --git a/tooling/bundler/src/bundle/windows/util.rs b/tooling/bundler/src/bundle/windows/util.rs index aad9b26f569..4e40b2990b9 100644 --- a/tooling/bundler/src/bundle/windows/util.rs +++ b/tooling/bundler/src/bundle/windows/util.rs @@ -10,6 +10,10 @@ use log::{debug, info}; use sha2::Digest; use zip::ZipArchive; +pub const WEBVIEW2_BOOTSTRAPPER_URL: &str = "https://go.microsoft.com/fwlink/p/?LinkId=2124703"; +pub const WEBVIEW2_X86_INSTALLER_GUID: &str = "a17bde80-b5ab-47b5-8bbb-1cbe93fc6ec9"; +pub const WEBVIEW2_X64_INSTALLER_GUID: &str = "aa5fd9b3-dc11-4cbc-8343-a50f57b311e1"; + use crate::{ bundle::windows::sign::{sign, SignParams}, Settings, From a4df69d282f36db31a491a15e1e65705e6e365de Mon Sep 17 00:00:00 2001 From: amrbashir Date: Mon, 18 Jul 2022 14:21:35 +0200 Subject: [PATCH 14/46] Prompt to kill if app is running --- tooling/bundler/src/bundle/windows/nsis.rs | 25 +++++--- .../bundle/windows/templates/installer.nsi | 61 +++++++++++++++---- tooling/bundler/src/bundle/windows/util.rs | 2 +- 3 files changed, 66 insertions(+), 22 deletions(-) diff --git a/tooling/bundler/src/bundle/windows/nsis.rs b/tooling/bundler/src/bundle/windows/nsis.rs index 25a449bca56..f5501613af6 100644 --- a/tooling/bundler/src/bundle/windows/nsis.rs +++ b/tooling/bundler/src/bundle/windows/nsis.rs @@ -6,7 +6,7 @@ use crate::{ bundle::{ common::CommandExt, windows::util::{ - download, download_and_verify, extract_7z, extract_zip, remove_unc_lossy, try_sign, + download, download_and_verify, extract_with_7z, extract_zip, remove_unc_lossy, try_sign, validate_version, WEBVIEW2_BOOTSTRAPPER_URL, WEBVIEW2_X64_INSTALLER_GUID, WEBVIEW2_X86_INSTALLER_GUID, }, @@ -29,9 +29,10 @@ use std::{ const NSIS_URL: &str = "https://sourceforge.net/projects/nsis/files/NSIS%203/3.08/nsis-3.08.zip/download"; const NSIS_SHA1: &str = "057e83c7d82462ec394af76c87d06733605543d4"; -const NS_CURL_URL: &str = +const NSIS_NSCURL_URL: &str = "https://github.com/negrutiu/nsis-nscurl/releases/download/v1.2022.6.7/NScurl-1.2022.6.7.7z"; -const NSIS_APPLICATION_ID_URL: &str = "https://github.com/connectiblutz/NSIS-ApplicationID/releases/download/1.1/NSIS-ApplicationID.zip"; +const NSIS_APPLICATIONID_URL: &str = "https://github.com/connectiblutz/NSIS-ApplicationID/releases/download/1.1/NSIS-ApplicationID.zip"; +const NSIS_NSPROCESS_URL: &str = "https://nsis.sourceforge.io/mediawiki/images/1/18/NsProcess.zip"; const NSIS_REQUIRED_FILES: &[&str] = &[ "makensis.exe", @@ -40,6 +41,7 @@ const NSIS_REQUIRED_FILES: &[&str] = &[ "Stubs/lzma_solid-x86-unicode", "Plugins/x86-unicode/NScurl.dll", "Plugins/x86-unicode/ApplicationID.dll", + "Plugins/x86-unicode/nsProcess.dll", "Include/MUI2.nsh", "Include/FileFunc.nsh", "Include/x64.nsh", @@ -76,12 +78,12 @@ fn get_and_extract_nsis(nsis_toolset_path: &Path, tauri_tools_path: &Path) -> cr let nsis_plugins = nsis_toolset_path.join("Plugins"); - let data = download(NS_CURL_URL)?; - info!("extracting NScurl"); - extract_7z(&data, &nsis_plugins)?; + let data = download(NSIS_NSCURL_URL)?; + info!("extracting NSIS NScurl plugin"); + extract_with_7z(&data, &nsis_plugins)?; - let data = download(NSIS_APPLICATION_ID_URL)?; - info!("extracting NSIS-ApplicationID"); + let data = download(NSIS_APPLICATIONID_URL)?; + info!("extracting NSIS ApplicationID plugin"); extract_zip(&data, &nsis_plugins)?; copy( nsis_plugins @@ -90,6 +92,13 @@ fn get_and_extract_nsis(nsis_toolset_path: &Path, tauri_tools_path: &Path) -> cr nsis_plugins.join("x86-unicode").join("ApplicationID.dll"), )?; + let data = download(NSIS_NSPROCESS_URL)?; + info!("extracting NSIS NsProcess plugin"); + extract_with_7z(&data, &nsis_plugins)?; + copy( + nsis_plugins.join("Plugin").join("nsProcessW.dll"), + nsis_plugins.join("x86-unicode").join("nsProcess.dll"), + )?; Ok(()) } diff --git a/tooling/bundler/src/bundle/windows/templates/installer.nsi b/tooling/bundler/src/bundle/windows/templates/installer.nsi index 284880e0167..e205971dc0e 100644 --- a/tooling/bundler/src/bundle/windows/templates/installer.nsi +++ b/tooling/bundler/src/bundle/windows/templates/installer.nsi @@ -88,7 +88,7 @@ SetCompressor /SOLID lzma ; Use show readme button in the finish page to create a desktop shortcut !define MUI_FINISHPAGE_SHOWREADME !define MUI_FINISHPAGE_SHOWREADME_TEXT "Create desktop shortcut" -!define MUI_FINISHPAGE_SHOWREADME_FUNCTION "createDesktopShortcut" +!define MUI_FINISHPAGE_SHOWREADME_FUNCTION "CreateDesktopShortcut" ; Show run app after installation. !define MUI_FINISHPAGE_RUN $INSTDIR\${MAINBINARYNAME}.exe @@ -117,23 +117,56 @@ Page custom PageReinstall PageLeaveReinstall ;Languages !insertmacro MUI_LANGUAGE English + +;-------------------------------- +; Macros + +!macro CheckIfAppIsRunning + nsProcess::_FindProcess "${MAINBINARYNAME}.exe" + Pop $R0 + ${If} $R0 = 0 + IfSilent silent ui + silent: + System::Call 'kernel32::AttachConsole(i -1)i.r0' + ${If} $0 != 0 + System::Call 'kernel32::GetStdHandle(i -11)i.r0' + System::call 'kernel32::SetConsoleTextAttribute(i r0, i 0x0004)' ; set red color + FileWrite $0 "${PRODUCTNAME} is running. Please close it first then try again.$\n" + ${EndIf} + Abort + ui: + MessageBox MB_OKCANCEL "${PRODUCTNAME} is running$\nClick OK to kill it" IDOK ok IDCANCEL cancel + ok: + nsProcess::_KillProcess "${MAINBINARYNAME}.exe" + Pop $R0 + Sleep 500 + ${If} $R0 = 0 + Goto done + ${Else} + Abort "Failed to kill ${PRODUCTNAME}. Please close it first then try again" + ${EndIf} + cancel: + Abort "${PRODUCTNAME} is running. Please close it first then try again" + ${EndIf} + done: +!macroend + ;-------------------------------- ;Installer Sections -Section SilentChecks +Section EarlyChecks ; Abort silent installer if downgrades is disabled !if "${ALLOWDOWNGRADES}" == "false" - IfSilent 0 silent_checks_done - System::Call 'kernel32::AttachConsole(i -1)i.r0' ;attach to parent console + IfSilent 0 done + System::Call 'kernel32::AttachConsole(i -1)i.r0' ${If} $0 != 0 - System::Call 'kernel32::GetStdHandle(i -11)i.r0' ;console attached -- get stdout - System::call 'kernel32::SetConsoleTextAttribute(i r0, i 0x0004)'; set red color + System::Call 'kernel32::GetStdHandle(i -11)i.r0' + System::call 'kernel32::SetConsoleTextAttribute(i r0, i 0x0004)' ; set red color FileWrite $0 "A newer version is already installed! Automatic silent downgrades are disabled for this installer.$\nIt is not recommended that you install an older version. If you really want to install this older version, you have to uninstall the current version first.$\n" - System::call 'kernel32::SetConsoleTextAttribute(i r0, i 0x0004)'; set red color ${EndIf} Abort !endif - silent_checks_done: + done: SectionEnd Section Webview2 @@ -201,7 +234,9 @@ SectionEnd Section Install SetOutPath $INSTDIR - ; Main executable + !insertmacro CheckIfAppIsRunning + + ; Copy main executable File "${MAINBINARYSRCPATH}" ; Copy resources @@ -249,6 +284,8 @@ Section Install SectionEnd Section Uninstall + !insertmacro CheckIfAppIsRunning + ; Delete the app directory and its content from disk RMDir /r "$INSTDIR" @@ -262,9 +299,9 @@ Section Uninstall SectionEnd ;-------------------------------- -;Installer Functions +; Functions -Function createDesktopShortcut +Function CreateDesktopShortcut CreateShortcut "$DESKTOP\${MAINBINARYNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe" ApplicationID::Set "$DESKTOP\${MAINBINARYNAME}.lnk" "${BUNDLEID}" FunctionEnd @@ -360,10 +397,8 @@ Function PageLeaveReinstall ; $R0 == "2" -> same version ; ; $R1 holds the radio buttons state. its meaning is dependant on the context - StrCmp $R0 "1" 0 +2 ; Existing install is not the same version? StrCmp $R1 "1" reinst_uninstall reinst_done - StrCmp $R1 "1" reinst_done ; Same version, skip to add/reinstall components? reinst_uninstall: diff --git a/tooling/bundler/src/bundle/windows/util.rs b/tooling/bundler/src/bundle/windows/util.rs index 4e40b2990b9..51344267658 100644 --- a/tooling/bundler/src/bundle/windows/util.rs +++ b/tooling/bundler/src/bundle/windows/util.rs @@ -135,7 +135,7 @@ pub fn extract_zip(data: &[u8], path: &Path) -> crate::Result<()> { const URL_7ZR: &str = "https://www.7-zip.org/a/7zr.exe"; -pub fn extract_7z(data: &[u8], path: &Path) -> crate::Result<()> { +pub fn extract_with_7z(data: &[u8], path: &Path) -> crate::Result<()> { let bin_7z = { debug!("checking for 7z.exe or 7zr.exe is in $PATH"); if let Ok(_) = Command::new("7z.exe").output() { From 79159443560557fe84ca2f70e4f793f7a04ca892 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Mon, 18 Jul 2022 14:24:33 +0200 Subject: [PATCH 15/46] fix NSIS warning --- tooling/bundler/src/bundle/windows/templates/installer.nsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling/bundler/src/bundle/windows/templates/installer.nsi b/tooling/bundler/src/bundle/windows/templates/installer.nsi index e205971dc0e..7f4be4feccd 100644 --- a/tooling/bundler/src/bundle/windows/templates/installer.nsi +++ b/tooling/bundler/src/bundle/windows/templates/installer.nsi @@ -165,8 +165,8 @@ Section EarlyChecks FileWrite $0 "A newer version is already installed! Automatic silent downgrades are disabled for this installer.$\nIt is not recommended that you install an older version. If you really want to install this older version, you have to uninstall the current version first.$\n" ${EndIf} Abort - !endif done: + !endif SectionEnd Section Webview2 From 2662ac6310c2ef3f969bf99bcb3d44dda9592751 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Mon, 18 Jul 2022 17:25:14 +0200 Subject: [PATCH 16/46] updater --- core/tauri/src/updater/core.rs | 14 ++-- tooling/bundler/src/bundle/updater_bundle.rs | 80 ++++++++++++------- tooling/bundler/src/bundle/windows/msi.rs | 2 - tooling/bundler/src/bundle/windows/msi/wix.rs | 8 +- tooling/bundler/src/bundle/windows/nsis.rs | 9 ++- 5 files changed, 66 insertions(+), 47 deletions(-) diff --git a/core/tauri/src/updater/core.rs b/core/tauri/src/updater/core.rs index 19ce91def8e..3e7f490ee54 100644 --- a/core/tauri/src/updater/core.rs +++ b/core/tauri/src/updater/core.rs @@ -692,17 +692,19 @@ fn copy_files_and_run(archive_buffer: R, extract_path: &Path) -> } // Windows - +// // ### Expected structure: // ├── [AppName]_[version]_x64.msi.zip # ZIP generated by tauri-bundler // │ └──[AppName]_[version]_x64.msi # Application MSI +// ├── [AppName]_[version]_x64-setup.exe.zip # ZIP generated by tauri-bundler +// │ └──[AppName]_[version]_x64-setup.exe # NSIS installer // └── ... - +// // ## MSI // Update server can provide a MSI for Windows. (Generated with tauri-bundler from *Wix*) // To replace current version of the application. In later version we'll offer // incremental update to push specific binaries. - +// // ## EXE // Update server can provide a custom EXE (installer) who can run any task. #[cfg(target_os = "windows")] @@ -730,8 +732,6 @@ fn copy_files_and_run( extractor.extract_into(&tmp_dir)?; let paths = read_dir(&tmp_dir)?; - // This consumes the TempDir without deleting directory on the filesystem, - // meaning that the directory will no longer be automatically deleted. for path in paths { let found_path = path?.path(); @@ -796,7 +796,7 @@ fn copy_files_and_run( "Start-Process", "-Wait", "-FilePath", - "msiexec", + "C:\\Windows\\system32\\msiexec.exe", "-ArgumentList", ]) .arg("/i,") @@ -808,7 +808,7 @@ fn copy_files_and_run( if powershell_install_res.is_err() { // fallback to running msiexec directly - relaunch won't be available // we use this here in case powershell fails in an older machine somehow - let _ = Command::new("msiexec.exe") + let _ = Command::new("C:\\Windows\\system32\\msiexec.exe") .arg("/i") .arg(found_path) .args(msiexec_args) diff --git a/tooling/bundler/src/bundle/updater_bundle.rs b/tooling/bundler/src/bundle/updater_bundle.rs index 4e179e0e2ce..900b6c0d9e3 100644 --- a/tooling/bundler/src/bundle/updater_bundle.rs +++ b/tooling/bundler/src/bundle/updater_bundle.rs @@ -10,8 +10,6 @@ use super::macos::app; #[cfg(target_os = "linux")] use super::linux::appimage; -#[cfg(target_os = "windows")] -use super::windows::msi; use log::error; #[cfg(target_os = "windows")] use std::{fs::File, io::prelude::*}; @@ -126,55 +124,83 @@ fn bundle_update(settings: &Settings, bundles: &[Bundle]) -> crate::Result crate::Result> { use crate::bundle::settings::WebviewInstallMode; + use crate::bundle::windows::{msi, nsis}; + use crate::PackageType; + + // find our installers or rebuild + let mut bundle_paths = Vec::new(); + let mut rebuild_installers = || -> crate::Result<()> { + for bundle in bundles { + match bundle.package_type { + PackageType::WindowsMsi => bundle_paths.extend(msi::bundle_project(settings, true)?), + PackageType::Nsis => bundle_paths.extend(nsis::bundle_project(settings, true)?), + _ => {} + }; + } + Ok(()) + }; - // find our .msi or rebuild - let bundle_paths = if matches!( + if matches!( settings.windows().webview_install_mode, WebviewInstallMode::OfflineInstaller { .. } | WebviewInstallMode::EmbedBootstrapper { .. } ) { - msi::bundle_project(settings, true)? + rebuild_installers()?; } else { let paths = bundles .iter() - .find(|bundle| bundle.package_type == crate::PackageType::WindowsMsi) + .find(|bundle| { + matches!( + bundle.package_type, + PackageType::WindowsMsi | PackageType::Nsis + ) + }) .map(|bundle| bundle.bundle_paths.clone()) .unwrap_or_default(); - // we expect our .msi files to be on `bundle_paths` + // we expect our installer files to be on `bundle_paths` if paths.is_empty() { - msi::bundle_project(settings, false)? + rebuild_installers()?; } else { - paths + bundle_paths.extend(paths); } }; - let mut msi_archived_paths = Vec::new(); - + let mut installers_archived_paths = Vec::new(); for source_path in bundle_paths { // add .zip to our path - let msi_archived_path = source_path - .components() - .fold(PathBuf::new(), |mut p, c| { - if let std::path::Component::Normal(name) = c { - if name == msi::MSI_UPDATER_FOLDER_NAME { - p.push(msi::MSI_FOLDER_NAME); - return p; + let (archived_path, bundle_name) = + source_path + .components() + .fold((PathBuf::new(), String::new()), |(mut p, mut b), c| { + if let std::path::Component::Normal(name) = c { + if let Some(name) = name.to_str() { + dbg!(name); + // installers bundled for updater should be put in a directory named `${bundle_name}-updater` + if matches!(name, "msi-updater" | "nsis-updater") { + b = name.strip_suffix("-updater").unwrap().to_string(); + p.push(&b); + return (p, b); + } + + if matches!(name, "msi" | "nsis") { + b = name.to_string(); + } + } } - } - p.push(c); - p - }) - .with_extension("msi.zip"); + p.push(c); + (p, b) + }); + let archived_path = archived_path.with_extension(format!("{}.zip", bundle_name)); - info!(action = "Bundling"; "{}", msi_archived_path.display()); + info!(action = "Bundling"; "{}", archived_path.display()); // Create our gzip file - create_zip(&source_path, &msi_archived_path).with_context(|| "Failed to zip update MSI")?; + create_zip(&source_path, &archived_path).with_context(|| "Failed to zip update MSI")?; - msi_archived_paths.push(msi_archived_path); + installers_archived_paths.push(archived_path); } - Ok(msi_archived_paths) + Ok(installers_archived_paths) } #[cfg(target_os = "windows")] diff --git a/tooling/bundler/src/bundle/windows/msi.rs b/tooling/bundler/src/bundle/windows/msi.rs index b5772bbbf4a..8d597ef7f47 100644 --- a/tooling/bundler/src/bundle/windows/msi.rs +++ b/tooling/bundler/src/bundle/windows/msi.rs @@ -4,8 +4,6 @@ mod wix; -pub use wix::{MSI_FOLDER_NAME, MSI_UPDATER_FOLDER_NAME}; - use crate::Settings; use log::warn; diff --git a/tooling/bundler/src/bundle/windows/msi/wix.rs b/tooling/bundler/src/bundle/windows/msi/wix.rs index 7992e7a3305..8dd7a76e88b 100644 --- a/tooling/bundler/src/bundle/windows/msi/wix.rs +++ b/tooling/bundler/src/bundle/windows/msi/wix.rs @@ -30,8 +30,6 @@ use uuid::Uuid; pub const WIX_URL: &str = "https://github.com/wixtoolset/wix3/releases/download/wix3112rtm/wix311-binaries.zip"; pub const WIX_SHA256: &str = "2c1888d5d1dba377fc7fa14444cf556963747ff9a0a289a3599cf09da03b9e2e"; -pub const MSI_FOLDER_NAME: &str = "msi"; -pub const MSI_UPDATER_FOLDER_NAME: &str = "msi-updater"; // For Cross Platform Compilation. @@ -198,11 +196,7 @@ fn app_installer_output_path( Ok(settings.project_out_directory().to_path_buf().join(format!( "bundle/{}/{}.msi", - if updater { - MSI_UPDATER_FOLDER_NAME - } else { - MSI_FOLDER_NAME - }, + if updater { "msi-updater" } else { "msi" }, package_base_name ))) } diff --git a/tooling/bundler/src/bundle/windows/nsis.rs b/tooling/bundler/src/bundle/windows/nsis.rs index f5501613af6..3eed4445eb9 100644 --- a/tooling/bundler/src/bundle/windows/nsis.rs +++ b/tooling/bundler/src/bundle/windows/nsis.rs @@ -325,10 +325,11 @@ fn build_nsis_app_installer( ); let nsis_output_path = output_path.join(out_file); - let nsis_installer_path = settings - .project_out_directory() - .to_path_buf() - .join(format!("bundle/nsis/{}.exe", package_base_name)); + let nsis_installer_path = settings.project_out_directory().to_path_buf().join(format!( + "bundle/{}/{}.exe", + if updater { "nsis-updater" } else { "nsis" }, + package_base_name + )); create_dir_all(nsis_installer_path.parent().unwrap())?; info!(action = "Running"; "makensis.exe to produce {}", nsis_installer_path.display()); From d0b7250ea932dbd4b2d64c2e154675ee0aa82a1b Mon Sep 17 00:00:00 2001 From: amrbashir Date: Mon, 18 Jul 2022 17:26:15 +0200 Subject: [PATCH 17/46] remove a stray dbg! --- tooling/bundler/src/bundle/updater_bundle.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tooling/bundler/src/bundle/updater_bundle.rs b/tooling/bundler/src/bundle/updater_bundle.rs index 900b6c0d9e3..7ef4d1965e1 100644 --- a/tooling/bundler/src/bundle/updater_bundle.rs +++ b/tooling/bundler/src/bundle/updater_bundle.rs @@ -174,7 +174,6 @@ fn bundle_update(settings: &Settings, bundles: &[Bundle]) -> crate::Result Date: Mon, 18 Jul 2022 17:28:48 +0200 Subject: [PATCH 18/46] changefile --- .changes/nsis.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .changes/nsis.md diff --git a/.changes/nsis.md b/.changes/nsis.md new file mode 100644 index 00000000000..bb3a7dc4fe3 --- /dev/null +++ b/.changes/nsis.md @@ -0,0 +1,8 @@ +--- +"tauri-bundler": patch +"tauri-utils": patch +"cli.rs": patch +"cli.js": patch +--- + +Add `nsis` bundle target From b8825b7136b336b6b99f6f03839de89648a47085 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Wed, 20 Jul 2022 15:36:11 +0200 Subject: [PATCH 19/46] adjust bump to minor --- .changes/nsis.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.changes/nsis.md b/.changes/nsis.md index bb3a7dc4fe3..2a196dd7485 100644 --- a/.changes/nsis.md +++ b/.changes/nsis.md @@ -1,8 +1,8 @@ --- -"tauri-bundler": patch -"tauri-utils": patch -"cli.rs": patch -"cli.js": patch +"tauri-bundler": minor +"tauri-utils": minor +"cli.rs": minor +"cli.js": minor --- Add `nsis` bundle target From f6ac3fd0da3537b6cfe07faca3323b4795c22d58 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Wed, 2 Nov 2022 01:57:23 +0200 Subject: [PATCH 20/46] fix APR display icon path --- tooling/bundler/src/bundle/windows/templates/installer.nsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling/bundler/src/bundle/windows/templates/installer.nsi b/tooling/bundler/src/bundle/windows/templates/installer.nsi index 7f4be4feccd..dd8246b131c 100644 --- a/tooling/bundler/src/bundle/windows/templates/installer.nsi +++ b/tooling/bundler/src/bundle/windows/templates/installer.nsi @@ -259,7 +259,7 @@ Section Install ; Registry information for add/remove programs WriteRegStr SHCTX "${APR}" "DisplayName" "${PRODUCTNAME}" - WriteRegStr SHCTX "${APR}" "DisplayIcon" "$\"$INSTDIR\Resources.exe$\"" + WriteRegStr SHCTX "${APR}" "DisplayIcon" "$\"$INSTDIR\${MAINBINARYNAME}.exe$\"" WriteRegStr SHCTX "${APR}" "DisplayVersion" "$\"${VERSION}$\"" WriteRegStr SHCTX "${APR}" "Publisher" "${MANUFACTURER}" WriteRegStr SHCTX "${APR}" "InstallLocation" "$\"$INSTDIR$\"" From e9904a7147db231c86246626156c1be3395e81dc Mon Sep 17 00:00:00 2001 From: amrbashir Date: Thu, 8 Dec 2022 13:41:30 +0200 Subject: [PATCH 21/46] fmt --- tooling/cli/src/interface/rust.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling/cli/src/interface/rust.rs b/tooling/cli/src/interface/rust.rs index ed72b89b60b..0fbe864df20 100644 --- a/tooling/cli/src/interface/rust.rs +++ b/tooling/cli/src/interface/rust.rs @@ -36,7 +36,7 @@ use tauri_utils::config::parse::is_configuration_file; use super::{AppSettings, ExitReason, Interface}; use crate::helpers::{ app_paths::{app_dir, tauri_dir}, - config::{nsis_settings,reload as reload_config, wix_settings, Config}, + config::{nsis_settings, reload as reload_config, wix_settings, Config}, }; mod cargo_config; From 8f3c9397aa8ca0b9de3ab16350c5edb30ae85e7e Mon Sep 17 00:00:00 2001 From: amrbashir Date: Thu, 8 Dec 2022 15:45:23 +0200 Subject: [PATCH 22/46] fix `!define` call --- tooling/bundler/src/bundle/windows/templates/installer.nsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling/bundler/src/bundle/windows/templates/installer.nsi b/tooling/bundler/src/bundle/windows/templates/installer.nsi index dd8246b131c..7cad1d1cc03 100644 --- a/tooling/bundler/src/bundle/windows/templates/installer.nsi +++ b/tooling/bundler/src/bundle/windows/templates/installer.nsi @@ -91,7 +91,7 @@ SetCompressor /SOLID lzma !define MUI_FINISHPAGE_SHOWREADME_FUNCTION "CreateDesktopShortcut" ; Show run app after installation. -!define MUI_FINISHPAGE_RUN $INSTDIR\${MAINBINARYNAME}.exe +!define MUI_FINISHPAGE_RUN "$INSTDIR\${MAINBINARYNAME}.exe" ;-------------------------------- ; Installer pages From bfbe0e7f64ed5cd01ba54bfcf47e516713c7de9c Mon Sep 17 00:00:00 2001 From: amrbashir Date: Thu, 8 Dec 2022 16:39:19 +0200 Subject: [PATCH 23/46] fix bundling updater for multiple bundle targets --- tooling/bundler/src/bundle/updater_bundle.rs | 5 +++-- tooling/cli/src/build.rs | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/tooling/bundler/src/bundle/updater_bundle.rs b/tooling/bundler/src/bundle/updater_bundle.rs index 70d0a68eb5c..ed51b503efb 100644 --- a/tooling/bundler/src/bundle/updater_bundle.rs +++ b/tooling/bundler/src/bundle/updater_bundle.rs @@ -149,14 +149,15 @@ fn bundle_update(settings: &Settings, bundles: &[Bundle]) -> crate::Result>(); // we expect our installer files to be on `bundle_paths` if paths.is_empty() { diff --git a/tooling/cli/src/build.rs b/tooling/cli/src/build.rs index 24970604d51..c55c55cc77b 100644 --- a/tooling/cli/src/build.rs +++ b/tooling/cli/src/build.rs @@ -373,7 +373,10 @@ fn print_signed_updater_archive(output_paths: &[PathBuf]) -> crate::Result<()> { let msg = format!("{} {} at:", output_paths.len(), pluralised); info!("{}", msg); for path in output_paths { - info!(" {}", path.display()); + info!( + " {}", + path.display().to_string().replacen(r"\\?\", "", 1) + ); } Ok(()) } From cf145be313c41ec77d373114a6f89bb019838952 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Thu, 8 Dec 2022 16:43:05 +0200 Subject: [PATCH 24/46] remove console window from api example on Windows --- examples/api/src-tauri/src/main.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/examples/api/src-tauri/src/main.rs b/examples/api/src-tauri/src/main.rs index 1ade16f98e3..542ed35e553 100644 --- a/examples/api/src-tauri/src/main.rs +++ b/examples/api/src-tauri/src/main.rs @@ -1,3 +1,8 @@ +#![cfg_attr( + all(not(debug_assertions), target_os = "windows"), + windows_subsystem = "windows" +)] + #[cfg(desktop)] mod desktop; From ce0dea53a33ce13c5ba70361fe72bf500d174493 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Thu, 8 Dec 2022 17:28:59 +0200 Subject: [PATCH 25/46] strip unc on windows only --- tooling/cli/src/build.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tooling/cli/src/build.rs b/tooling/cli/src/build.rs index c55c55cc77b..9827231c93e 100644 --- a/tooling/cli/src/build.rs +++ b/tooling/cli/src/build.rs @@ -373,10 +373,12 @@ fn print_signed_updater_archive(output_paths: &[PathBuf]) -> crate::Result<()> { let msg = format!("{} {} at:", output_paths.len(), pluralised); info!("{}", msg); for path in output_paths { - info!( - " {}", + info!(" {}", { + #[cfg(unix)] + return path.display(); + #[cfg(windows)] path.display().to_string().replacen(r"\\?\", "", 1) - ); + }); } Ok(()) } From 9413ee66d3797e0daccf9c89fe17c6c0cc948c5e Mon Sep 17 00:00:00 2001 From: amrbashir Date: Thu, 8 Dec 2022 17:48:00 +0200 Subject: [PATCH 26/46] fix cli build on unix --- tooling/cli/src/build.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tooling/cli/src/build.rs b/tooling/cli/src/build.rs index 9827231c93e..54654851d35 100644 --- a/tooling/cli/src/build.rs +++ b/tooling/cli/src/build.rs @@ -373,12 +373,13 @@ fn print_signed_updater_archive(output_paths: &[PathBuf]) -> crate::Result<()> { let msg = format!("{} {} at:", output_paths.len(), pluralised); info!("{}", msg); for path in output_paths { - info!(" {}", { - #[cfg(unix)] - return path.display(); - #[cfg(windows)] + #[cfg(unix)] + info!(" {}", path.display()); + #[cfg(windows)] + info!( + " {}", path.display().to_string().replacen(r"\\?\", "", 1) - }); + ); } Ok(()) } From 6279feaae2b2b0f40137badb9171043d60278bf9 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Thu, 8 Dec 2022 20:28:01 +0200 Subject: [PATCH 27/46] download NSIS plugins from `tauri-apps/binary-releases` --- tooling/bundler/src/bundle/windows/nsis.rs | 16 +++---- tooling/bundler/src/bundle/windows/util.rs | 50 +--------------------- 2 files changed, 10 insertions(+), 56 deletions(-) diff --git a/tooling/bundler/src/bundle/windows/nsis.rs b/tooling/bundler/src/bundle/windows/nsis.rs index 3eed4445eb9..29da9e7f33a 100644 --- a/tooling/bundler/src/bundle/windows/nsis.rs +++ b/tooling/bundler/src/bundle/windows/nsis.rs @@ -6,9 +6,8 @@ use crate::{ bundle::{ common::CommandExt, windows::util::{ - download, download_and_verify, extract_with_7z, extract_zip, remove_unc_lossy, try_sign, - validate_version, WEBVIEW2_BOOTSTRAPPER_URL, WEBVIEW2_X64_INSTALLER_GUID, - WEBVIEW2_X86_INSTALLER_GUID, + download, download_and_verify, extract_zip, remove_unc_lossy, try_sign, validate_version, + WEBVIEW2_BOOTSTRAPPER_URL, WEBVIEW2_X64_INSTALLER_GUID, WEBVIEW2_X86_INSTALLER_GUID, }, }, Settings, @@ -30,9 +29,10 @@ const NSIS_URL: &str = "https://sourceforge.net/projects/nsis/files/NSIS%203/3.08/nsis-3.08.zip/download"; const NSIS_SHA1: &str = "057e83c7d82462ec394af76c87d06733605543d4"; const NSIS_NSCURL_URL: &str = - "https://github.com/negrutiu/nsis-nscurl/releases/download/v1.2022.6.7/NScurl-1.2022.6.7.7z"; -const NSIS_APPLICATIONID_URL: &str = "https://github.com/connectiblutz/NSIS-ApplicationID/releases/download/1.1/NSIS-ApplicationID.zip"; -const NSIS_NSPROCESS_URL: &str = "https://nsis.sourceforge.io/mediawiki/images/1/18/NsProcess.zip"; + "https://github.com/tauri-apps/binary-releases/releases/download/nsis-plugins-v0/NScurl-1.2022.6.7.zip"; +const NSIS_APPLICATIONID_URL: &str = "https://github.com/tauri-apps/binary-releases/releases/download/nsis-plugins-v0/NSIS-ApplicationID.zip"; +const NSIS_NSPROCESS_URL: &str = + "https://github.com/tauri-apps/binary-releases/releases/download/nsis-plugins-v0/NsProcess.zip"; const NSIS_REQUIRED_FILES: &[&str] = &[ "makensis.exe", @@ -80,7 +80,7 @@ fn get_and_extract_nsis(nsis_toolset_path: &Path, tauri_tools_path: &Path) -> cr let data = download(NSIS_NSCURL_URL)?; info!("extracting NSIS NScurl plugin"); - extract_with_7z(&data, &nsis_plugins)?; + extract_zip(&data, &nsis_plugins)?; let data = download(NSIS_APPLICATIONID_URL)?; info!("extracting NSIS ApplicationID plugin"); @@ -94,7 +94,7 @@ fn get_and_extract_nsis(nsis_toolset_path: &Path, tauri_tools_path: &Path) -> cr let data = download(NSIS_NSPROCESS_URL)?; info!("extracting NSIS NsProcess plugin"); - extract_with_7z(&data, &nsis_plugins)?; + extract_zip(&data, &nsis_plugins)?; copy( nsis_plugins.join("Plugin").join("nsProcessW.dll"), nsis_plugins.join("x86-unicode").join("nsProcess.dll"), diff --git a/tooling/bundler/src/bundle/windows/util.rs b/tooling/bundler/src/bundle/windows/util.rs index 51344267658..183d1c8e549 100644 --- a/tooling/bundler/src/bundle/windows/util.rs +++ b/tooling/bundler/src/bundle/windows/util.rs @@ -1,12 +1,11 @@ use std::{ - fs::{create_dir_all, remove_file, File}, + fs::{create_dir_all, File}, io::{Cursor, Read, Write}, path::{Path, PathBuf}, - process::Command, }; use anyhow::{bail, Context}; -use log::{debug, info}; +use log::info; use sha2::Digest; use zip::ZipArchive; @@ -133,51 +132,6 @@ pub fn extract_zip(data: &[u8], path: &Path) -> crate::Result<()> { Ok(()) } -const URL_7ZR: &str = "https://www.7-zip.org/a/7zr.exe"; - -pub fn extract_with_7z(data: &[u8], path: &Path) -> crate::Result<()> { - let bin_7z = { - debug!("checking for 7z.exe or 7zr.exe is in $PATH"); - if let Ok(_) = Command::new("7z.exe").output() { - "7z.exe".to_string() - } else if let Ok(_) = Command::new("7zr.exe").output() { - "7zr.exe".to_string() - } else { - get_or_download_7zr_bin()?.to_string_lossy().to_string() - } - }; - - let temp = path.join("temp.7z"); - { - let mut file = File::create(&temp)?; - file.write_all(&data)?; - } - - Command::new(bin_7z) - .args(&["x", &temp.to_string_lossy()]) - .current_dir(path) - .output()?; - - remove_file(temp)?; - - Ok(()) -} - -fn get_or_download_7zr_bin() -> crate::Result { - let tauri_tools_path = dirs_next::cache_dir().unwrap().join("tauri"); - let bin_7zr_path = tauri_tools_path.join("7zr.exe"); - - debug!("checking for 7zr.exe in {}", tauri_tools_path.display()); - if !bin_7zr_path.exists() { - info!("downloading 7zr.exe in {}", tauri_tools_path.display()); - let data = download(URL_7ZR)?; - let mut file = File::create(&bin_7zr_path)?; - file.write_all(&data)?; - }; - - Ok(bin_7zr_path) -} - pub fn remove_unc_lossy>(p: P) -> PathBuf { PathBuf::from(p.as_ref().to_string_lossy().replacen(r"\\?\", "", 1)) } From e36610f2593a07e8954bc3811b4a8b9ead03f9b0 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 14 Dec 2022 20:03:08 -0300 Subject: [PATCH 28/46] fmt docs [skip ci] --- core/config-schema/schema.json | 4 ++-- core/tauri-utils/src/config.rs | 4 ++-- tooling/bundler/src/bundle/settings.rs | 4 ++-- tooling/cli/schema.json | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/core/config-schema/schema.json b/core/config-schema/schema.json index a9f8827ddc5..ddb7629b358 100644 --- a/core/config-schema/schema.json +++ b/core/config-schema/schema.json @@ -1651,14 +1651,14 @@ ] }, "headerImage": { - "description": "The path to a bitmap file to display on the header of installers pages.\n\nThe recommended dimensions are 150pxx57px.", + "description": "The path to a bitmap file to display on the header of installers pages.\n\nThe recommended dimensions are 150px x 57px.", "type": [ "string", "null" ] }, "sidebarImage": { - "description": "The path to a bitmap file for the Welcome page and the Finish page.\n\nThe recommended dimensions are 164pxx314px.", + "description": "The path to a bitmap file for the Welcome page and the Finish page.\n\nThe recommended dimensions are 164px x 314px.", "type": [ "string", "null" diff --git a/core/tauri-utils/src/config.rs b/core/tauri-utils/src/config.rs index bd52d03ebc3..61c933a484a 100644 --- a/core/tauri-utils/src/config.rs +++ b/core/tauri-utils/src/config.rs @@ -429,11 +429,11 @@ pub struct NsisConfig { pub license: Option, /// The path to a bitmap file to display on the header of installers pages. /// - /// The recommended dimensions are 150pxx57px. + /// The recommended dimensions are 150px x 57px. pub header_image: Option, /// The path to a bitmap file for the Welcome page and the Finish page. /// - /// The recommended dimensions are 164pxx314px. + /// The recommended dimensions are 164px x 314px. pub sidebar_image: Option, /// The path to an icon file used as the installer icon. pub installer_icon: Option, diff --git a/tooling/bundler/src/bundle/settings.rs b/tooling/bundler/src/bundle/settings.rs index ca1e31c3054..b27c04aacb2 100644 --- a/tooling/bundler/src/bundle/settings.rs +++ b/tooling/bundler/src/bundle/settings.rs @@ -256,11 +256,11 @@ pub struct NsisSettings { pub license: Option, /// The path to a bitmap file to display on the header of installers pages. /// - /// The recommended dimensions are 150pxx57px. + /// The recommended dimensions are 150px x 57px. pub header_image: Option, /// The path to a bitmap file for the Welcome page and the Finish page. /// - /// The recommended dimensions are 164pxx314px. + /// The recommended dimensions are 164px x 314px. pub sidebar_image: Option, /// The path to an icon file used as the installer icon. pub installer_icon: Option, diff --git a/tooling/cli/schema.json b/tooling/cli/schema.json index a9f8827ddc5..ddb7629b358 100644 --- a/tooling/cli/schema.json +++ b/tooling/cli/schema.json @@ -1651,14 +1651,14 @@ ] }, "headerImage": { - "description": "The path to a bitmap file to display on the header of installers pages.\n\nThe recommended dimensions are 150pxx57px.", + "description": "The path to a bitmap file to display on the header of installers pages.\n\nThe recommended dimensions are 150px x 57px.", "type": [ "string", "null" ] }, "sidebarImage": { - "description": "The path to a bitmap file for the Welcome page and the Finish page.\n\nThe recommended dimensions are 164pxx314px.", + "description": "The path to a bitmap file for the Welcome page and the Finish page.\n\nThe recommended dimensions are 164px x 314px.", "type": [ "string", "null" From e57a2b46fb073945d5bbba54ef16f1953594f3c6 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 14 Dec 2022 20:11:53 -0300 Subject: [PATCH 29/46] use constants [skip ci] --- tooling/bundler/src/bundle/updater_bundle.rs | 5 +++-- tooling/bundler/src/bundle/windows/msi.rs | 2 ++ tooling/bundler/src/bundle/windows/msi/wix.rs | 9 ++++++++- tooling/bundler/src/bundle/windows/nsis.rs | 9 ++++++++- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/tooling/bundler/src/bundle/updater_bundle.rs b/tooling/bundler/src/bundle/updater_bundle.rs index ed51b503efb..9601a5deae8 100644 --- a/tooling/bundler/src/bundle/updater_bundle.rs +++ b/tooling/bundler/src/bundle/updater_bundle.rs @@ -177,13 +177,14 @@ fn bundle_update(settings: &Settings, bundles: &[Bundle]) -> crate::Result Date: Wed, 14 Dec 2022 20:45:08 -0300 Subject: [PATCH 30/46] add HashAlgorithm enum [skip ci] --- tooling/bundler/src/bundle/windows/msi/wix.rs | 4 ++-- tooling/bundler/src/bundle/windows/nsis.rs | 5 +++-- tooling/bundler/src/bundle/windows/util.rs | 17 ++++++++++++----- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/tooling/bundler/src/bundle/windows/msi/wix.rs b/tooling/bundler/src/bundle/windows/msi/wix.rs index e060c8ea54c..1174b0c9535 100644 --- a/tooling/bundler/src/bundle/windows/msi/wix.rs +++ b/tooling/bundler/src/bundle/windows/msi/wix.rs @@ -8,7 +8,7 @@ use crate::bundle::{ path_utils::{copy_file, FileOpts}, settings::Settings, windows::util::{ - download, download_and_verify, extract_zip, try_sign, validate_version, + download, download_and_verify, extract_zip, try_sign, validate_version, HashAlgorithm, WEBVIEW2_BOOTSTRAPPER_URL, WEBVIEW2_X64_INSTALLER_GUID, WEBVIEW2_X86_INSTALLER_GUID, }, }; @@ -224,7 +224,7 @@ fn generate_guid(key: &[u8]) -> Uuid { pub fn get_and_extract_wix(path: &Path) -> crate::Result<()> { info!("Verifying wix package"); - let data = download_and_verify(WIX_URL, WIX_SHA256, "sha256")?; + let data = download_and_verify(WIX_URL, WIX_SHA256, HashAlgorithm::Sha256)?; info!("extracting WIX"); diff --git a/tooling/bundler/src/bundle/windows/nsis.rs b/tooling/bundler/src/bundle/windows/nsis.rs index b365079d88a..dc2e141d9db 100644 --- a/tooling/bundler/src/bundle/windows/nsis.rs +++ b/tooling/bundler/src/bundle/windows/nsis.rs @@ -7,7 +7,8 @@ use crate::{ common::CommandExt, windows::util::{ download, download_and_verify, extract_zip, remove_unc_lossy, try_sign, validate_version, - WEBVIEW2_BOOTSTRAPPER_URL, WEBVIEW2_X64_INSTALLER_GUID, WEBVIEW2_X86_INSTALLER_GUID, + HashAlgorithm, WEBVIEW2_BOOTSTRAPPER_URL, WEBVIEW2_X64_INSTALLER_GUID, + WEBVIEW2_X86_INSTALLER_GUID, }, }, Settings, @@ -74,7 +75,7 @@ pub fn bundle_project(settings: &Settings, updater: bool) -> crate::Result crate::Result<()> { info!("Verifying NSIS package"); - let data = download_and_verify(NSIS_URL, NSIS_SHA1, "sha1")?; + let data = download_and_verify(NSIS_URL, NSIS_SHA1, HashAlgorithm::Sha1)?; info!("extracting NSIS"); extract_zip(&data, tauri_tools_path)?; rename(tauri_tools_path.join("nsis-3.08"), nsis_toolset_path)?; diff --git a/tooling/bundler/src/bundle/windows/util.rs b/tooling/bundler/src/bundle/windows/util.rs index 183d1c8e549..d1740ca1832 100644 --- a/tooling/bundler/src/bundle/windows/util.rs +++ b/tooling/bundler/src/bundle/windows/util.rs @@ -24,22 +24,29 @@ pub fn download(url: &str) -> crate::Result> { response.bytes().map_err(Into::into) } +pub enum HashAlgorithm { + Sha256, + Sha1, +} + /// Function used to download a file and checks SHA256 to verify the download. -pub fn download_and_verify(url: &str, hash: &str, hash_algorithim: &str) -> crate::Result> { +pub fn download_and_verify( + url: &str, + hash: &str, + hash_algorithim: HashAlgorithm, +) -> crate::Result> { let data = download(url)?; info!("validating hash"); match hash_algorithim { - "sha256" => { + HashAlgorithm::Sha256 => { let hasher = sha2::Sha256::new(); verify(&data, hash, hasher)?; } - "sha1" => { + HashAlgorithm::Sha1 => { let hasher = sha1::Sha1::new(); verify(&data, hash, hasher)?; } - // "sha256" => sha1::Sha1::new(), - _ => unimplemented!(), } Ok(data) From f5aaed7b72bec527278a57c4c51f559b4dd9c68a Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 14 Dec 2022 20:45:53 -0300 Subject: [PATCH 31/46] add missing license header [skip ci] --- tooling/bundler/src/bundle/windows/util.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tooling/bundler/src/bundle/windows/util.rs b/tooling/bundler/src/bundle/windows/util.rs index d1740ca1832..88e7d7cadcc 100644 --- a/tooling/bundler/src/bundle/windows/util.rs +++ b/tooling/bundler/src/bundle/windows/util.rs @@ -1,3 +1,7 @@ +// Copyright 2019-2022 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + use std::{ fs::{create_dir_all, File}, io::{Cursor, Read, Write}, From 9d10c0f20099b5e921bf26d0a9be84ce4255f6d6 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 14 Dec 2022 23:10:21 -0300 Subject: [PATCH 32/46] fix consts [skip ci] --- tooling/bundler/src/bundle/windows/msi/wix.rs | 4 ++-- tooling/bundler/src/bundle/windows/nsis.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tooling/bundler/src/bundle/windows/msi/wix.rs b/tooling/bundler/src/bundle/windows/msi/wix.rs index 1174b0c9535..19e61a6dce5 100644 --- a/tooling/bundler/src/bundle/windows/msi/wix.rs +++ b/tooling/bundler/src/bundle/windows/msi/wix.rs @@ -27,8 +27,8 @@ use std::{ use tauri_utils::{config::WebviewInstallMode, resources::resource_relpath}; use uuid::Uuid; -pub const OUTPUT_FOLDER_NAME: &str = "nsis"; -pub const UPDATER_OUTPUT_FOLDER_NAME: &str = "nsis"; +pub const OUTPUT_FOLDER_NAME: &str = "msi"; +pub const UPDATER_OUTPUT_FOLDER_NAME: &str = "msi-updater"; // URLS for the WIX toolchain. Can be used for cross-platform compilation. pub const WIX_URL: &str = diff --git a/tooling/bundler/src/bundle/windows/nsis.rs b/tooling/bundler/src/bundle/windows/nsis.rs index dc2e141d9db..d53626f7053 100644 --- a/tooling/bundler/src/bundle/windows/nsis.rs +++ b/tooling/bundler/src/bundle/windows/nsis.rs @@ -26,7 +26,7 @@ use std::{ }; pub const OUTPUT_FOLDER_NAME: &str = "nsis"; -pub const UPDATER_OUTPUT_FOLDER_NAME: &str = "nsis"; +pub const UPDATER_OUTPUT_FOLDER_NAME: &str = "nsis-updater"; // URLS for the NSIS toolchain. const NSIS_URL: &str = From 4b2cc740b3cb5a9d18e6c53bf66c463cf44805bf Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 14 Dec 2022 23:50:31 -0300 Subject: [PATCH 33/46] silent updater install --- core/tauri/src/updater/core.rs | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/core/tauri/src/updater/core.rs b/core/tauri/src/updater/core.rs index 7a7c8cb3769..9f53e852a65 100644 --- a/core/tauri/src/updater/core.rs +++ b/core/tauri/src/updater/core.rs @@ -7,7 +7,8 @@ use super::error::{Error, Result}; use crate::api::file::{ArchiveFormat, Extract, Move}; use crate::{ api::http::{ClientBuilder, HttpRequestBuilder}, - AppHandle, Manager, Runtime, + utils::config::WindowsUpdateInstallMode, + AppHandle, Config, Manager, Runtime, }; use base64::decode; use http::{ @@ -612,15 +613,7 @@ impl Update { archive_buffer, &self.extract_path, self.with_elevated_task, - self - .app - .config() - .tauri - .updater - .windows - .install_mode - .clone() - .msiexec_args(), + &self.app.config(), )?; #[cfg(not(target_os = "windows"))] copy_files_and_run(archive_buffer, &self.extract_path)?; @@ -719,7 +712,7 @@ fn copy_files_and_run( archive_buffer: R, _extract_path: &Path, with_elevated_task: bool, - msiexec_args: &[&str], + config: &Config, ) -> Result { // FIXME: We need to create a memory buffer with the MSI and then run it. // (instead of extracting the MSI to a temp path) @@ -745,9 +738,11 @@ fn copy_files_and_run( // If it's an `exe` we expect an installer not a runtime. if found_path.extension() == Some(OsStr::new("exe")) { // Run the EXE - Command::new(found_path) - .spawn() - .expect("installer failed to start"); + let mut installer = Command::new(found_path); + if WindowsUpdateInstallMode::Quiet == config.tauri.updater.windows.install_mode { + installer.arg("/S"); + } + installer.spawn().expect("installer failed to start"); exit(0); } else if found_path.extension() == Some(OsStr::new("msi")) { @@ -801,6 +796,14 @@ fn copy_files_and_run( msi_path_arg.push(&found_path); msi_path_arg.push("\"\"\""); + let msiexec_args = config + .tauri + .updater + .windows + .install_mode + .clone() + .msiexec_args(); + // run the installer and relaunch the application let system_root = std::env::var("SYSTEMROOT"); let powershell_path = system_root.as_ref().map_or_else( From 92cc7dcdf7368cd0546b5540adbff4b2ad7d25aa Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 14 Dec 2022 23:51:12 -0300 Subject: [PATCH 34/46] installerargs option for updater --- core/config-schema/schema.json | 19 +++++++++++++++---- core/tauri-utils/src/config.rs | 8 ++++++-- core/tauri/src/updater/core.rs | 19 +++++++++++++------ tooling/cli/schema.json | 19 +++++++++++++++---- 4 files changed, 49 insertions(+), 16 deletions(-) diff --git a/core/config-schema/schema.json b/core/config-schema/schema.json index ddb7629b358..4b629955286 100644 --- a/core/config-schema/schema.json +++ b/core/config-schema/schema.json @@ -169,7 +169,8 @@ "dialog": true, "pubkey": "", "windows": { - "installMode": "passive" + "installMode": "passive", + "installerArgs": [] } }, "windows": [] @@ -427,7 +428,8 @@ "dialog": true, "pubkey": "", "windows": { - "installMode": "passive" + "installMode": "passive", + "installerArgs": [] } }, "allOf": [ @@ -2620,7 +2622,8 @@ "windows": { "description": "The Windows configuration for the updater.", "default": { - "installMode": "passive" + "installMode": "passive", + "installerArgs": [] }, "allOf": [ { @@ -2640,6 +2643,14 @@ "description": "The updater configuration for Windows.", "type": "object", "properties": { + "installerArgs": { + "description": "Additional arguments given to the NSIS or WiX installer.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, "installMode": { "description": "The installation mode for the update on Windows. Defaults to `passive`.", "default": "passive", @@ -2663,7 +2674,7 @@ ] }, { - "description": "The quiet mode means there's no user interaction required. Requires admin privileges if the installer does.", + "description": "The quiet mode means there's no user interaction required. Requires admin privileges if the installer does (WiX).", "type": "string", "enum": [ "quiet" diff --git a/core/tauri-utils/src/config.rs b/core/tauri-utils/src/config.rs index 61c933a484a..78c1ed2da9a 100644 --- a/core/tauri-utils/src/config.rs +++ b/core/tauri-utils/src/config.rs @@ -2316,7 +2316,7 @@ pub enum WindowsUpdateInstallMode { /// Specifies there's a basic UI during the installation process, including a final dialog box at the end. BasicUi, /// The quiet mode means there's no user interaction required. - /// Requires admin privileges if the installer does. + /// Requires admin privileges if the installer does (WiX). Quiet, /// Specifies unattended mode, which means the installation only shows a progress bar. Passive, @@ -2388,6 +2388,9 @@ impl<'de> Deserialize<'de> for WindowsUpdateInstallMode { #[cfg_attr(feature = "schema", derive(JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct UpdaterWindowsConfig { + /// Additional arguments given to the NSIS or WiX installer. + #[serde(default, alias = "installer-args")] + pub installer_args: Vec, /// The installation mode for the update on Windows. Defaults to `passive`. #[serde(default, alias = "install-mode")] pub install_mode: WindowsUpdateInstallMode, @@ -3362,7 +3365,8 @@ mod build { impl ToTokens for UpdaterWindowsConfig { fn to_tokens(&self, tokens: &mut TokenStream) { let install_mode = &self.install_mode; - literal_struct!(tokens, UpdaterWindowsConfig, install_mode); + let installer_args = vec_lit(&self.installer_args, str_lit); + literal_struct!(tokens, UpdaterWindowsConfig, install_mode, installer_args); } } diff --git a/core/tauri/src/updater/core.rs b/core/tauri/src/updater/core.rs index 9f53e852a65..d8bda015b29 100644 --- a/core/tauri/src/updater/core.rs +++ b/core/tauri/src/updater/core.rs @@ -7,8 +7,7 @@ use super::error::{Error, Result}; use crate::api::file::{ArchiveFormat, Extract, Move}; use crate::{ api::http::{ClientBuilder, HttpRequestBuilder}, - utils::config::WindowsUpdateInstallMode, - AppHandle, Config, Manager, Runtime, + AppHandle, Manager, Runtime, }; use base64::decode; use http::{ @@ -712,7 +711,7 @@ fn copy_files_and_run( archive_buffer: R, _extract_path: &Path, with_elevated_task: bool, - config: &Config, + config: &crate::Config, ) -> Result { // FIXME: We need to create a memory buffer with the MSI and then run it. // (instead of extracting the MSI to a temp path) @@ -739,9 +738,13 @@ fn copy_files_and_run( if found_path.extension() == Some(OsStr::new("exe")) { // Run the EXE let mut installer = Command::new(found_path); - if WindowsUpdateInstallMode::Quiet == config.tauri.updater.windows.install_mode { + if crate::utils::config::WindowsUpdateInstallMode::Quiet + == config.tauri.updater.windows.install_mode + { installer.arg("/S"); } + installer.args(&config.tauri.updater.windows.installer_args); + installer.spawn().expect("installer failed to start"); exit(0); @@ -796,13 +799,17 @@ fn copy_files_and_run( msi_path_arg.push(&found_path); msi_path_arg.push("\"\"\""); - let msiexec_args = config + let mut msiexec_args = config .tauri .updater .windows .install_mode .clone() - .msiexec_args(); + .msiexec_args() + .iter() + .map(|p| p.to_string()) + .collect::>(); + msiexec_args.extend(config.tauri.updater.windows.installer_args.clone()); // run the installer and relaunch the application let system_root = std::env::var("SYSTEMROOT"); diff --git a/tooling/cli/schema.json b/tooling/cli/schema.json index ddb7629b358..4b629955286 100644 --- a/tooling/cli/schema.json +++ b/tooling/cli/schema.json @@ -169,7 +169,8 @@ "dialog": true, "pubkey": "", "windows": { - "installMode": "passive" + "installMode": "passive", + "installerArgs": [] } }, "windows": [] @@ -427,7 +428,8 @@ "dialog": true, "pubkey": "", "windows": { - "installMode": "passive" + "installMode": "passive", + "installerArgs": [] } }, "allOf": [ @@ -2620,7 +2622,8 @@ "windows": { "description": "The Windows configuration for the updater.", "default": { - "installMode": "passive" + "installMode": "passive", + "installerArgs": [] }, "allOf": [ { @@ -2640,6 +2643,14 @@ "description": "The updater configuration for Windows.", "type": "object", "properties": { + "installerArgs": { + "description": "Additional arguments given to the NSIS or WiX installer.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, "installMode": { "description": "The installation mode for the update on Windows. Defaults to `passive`.", "default": "passive", @@ -2663,7 +2674,7 @@ ] }, { - "description": "The quiet mode means there's no user interaction required. Requires admin privileges if the installer does.", + "description": "The quiet mode means there's no user interaction required. Requires admin privileges if the installer does (WiX).", "type": "string", "enum": [ "quiet" From 5df4876b2eee1f8375a99370c3cf6fa2d5a2cbc6 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 14 Dec 2022 23:53:00 -0300 Subject: [PATCH 35/46] add updater test --- core/tests/app-updater/src/main.rs | 7 +- core/tests/app-updater/tests/update.rs | 214 +++++++++++++------------ 2 files changed, 121 insertions(+), 100 deletions(-) diff --git a/core/tests/app-updater/src/main.rs b/core/tests/app-updater/src/main.rs index d957fa1fd23..70816ebe98f 100644 --- a/core/tests/app-updater/src/main.rs +++ b/core/tests/app-updater/src/main.rs @@ -8,6 +8,11 @@ )] fn main() { + let mut context = tauri::generate_context!(); + context.config_mut().tauri.updater.windows.installer_args = vec![format!( + "/D={}", + std::env::current_exe().unwrap().parent().unwrap().display() + )]; tauri::Builder::default() .setup(|app| { let handle = app.handle(); @@ -28,6 +33,6 @@ fn main() { }); Ok(()) }) - .run(tauri::generate_context!()) + .run(context) .expect("error while running tauri application"); } diff --git a/core/tests/app-updater/tests/update.rs b/core/tests/app-updater/tests/update.rs index c4726171183..b17ef7c628e 100644 --- a/core/tests/app-updater/tests/update.rs +++ b/core/tests/app-updater/tests/update.rs @@ -63,7 +63,7 @@ fn build_app(cli_bin_path: &Path, cwd: &Path, config: &Config, bundle_updater: b .current_dir(&cwd); #[cfg(windows)] - command.args(["--bundles", "msi"]); + command.args(["--bundles", "msi", "nsis"]); #[cfg(target_os = "linux")] command.args(["--bundles", "appimage"]); #[cfg(target_os = "macos")] @@ -86,7 +86,7 @@ fn build_app(cli_bin_path: &Path, cwd: &Path, config: &Config, bundle_updater: b } #[cfg(target_os = "linux")] -fn bundle_path(root_dir: &Path, version: &str) -> PathBuf { +fn bundle_paths(root_dir: &Path, version: &str) -> Vec { root_dir.join(format!( "target/debug/bundle/appimage/app-updater_{}_amd64.AppImage", version @@ -94,21 +94,27 @@ fn bundle_path(root_dir: &Path, version: &str) -> PathBuf { } #[cfg(target_os = "macos")] -fn bundle_path(root_dir: &Path, _version: &str) -> PathBuf { - root_dir.join(format!("target/debug/bundle/macos/app-updater.app")) +fn bundle_paths(root_dir: &Path, _version: &str) -> Vec { + vec![root_dir.join(format!("target/debug/bundle/macos/app-updater.app"))] } #[cfg(target_os = "ios")] -fn bundle_path(root_dir: &Path, _version: &str) -> PathBuf { - root_dir.join(format!("target/debug/bundle/ios/app-updater.app")) +fn bundle_paths(root_dir: &Path, _version: &str) -> Vec { + vec![root_dir.join(format!("target/debug/bundle/ios/app-updater.app"))] } #[cfg(windows)] -fn bundle_path(root_dir: &Path, version: &str) -> PathBuf { - root_dir.join(format!( - "target/debug/bundle/msi/app-updater_{}_x64_en-US.msi", - version - )) +fn bundle_paths(root_dir: &Path, version: &str) -> Vec { + vec![ + root_dir.join(format!( + "target/debug/bundle/nsis/app-updater_{}_x64-setup.exe", + version + )), + root_dir.join(format!( + "target/debug/bundle/msi/app-updater_{}_x64_en-US.msi", + version + )), + ] } #[test] @@ -142,97 +148,107 @@ fn update_app() { // bundle app update build_app(&cli_bin_path, &manifest_dir, &config, true); - let updater_ext = if cfg!(windows) { "zip" } else { "tar.gz" }; - - let out_bundle_path = bundle_path(&root_dir, "1.0.0"); - let signature_path = out_bundle_path.with_extension(format!( - "{}.{}.sig", - out_bundle_path.extension().unwrap().to_str().unwrap(), - updater_ext - )); - let signature = std::fs::read_to_string(&signature_path) - .unwrap_or_else(|_| panic!("failed to read signature file {}", signature_path.display())); - let out_updater_path = out_bundle_path.with_extension(format!( - "{}.{}", - out_bundle_path.extension().unwrap().to_str().unwrap(), - updater_ext - )); - let updater_path = root_dir.join(format!( - "target/debug/{}", - out_updater_path.file_name().unwrap().to_str().unwrap() - )); - std::fs::rename(&out_updater_path, &updater_path).expect("failed to rename bundle"); - - std::thread::spawn(move || { - // start the updater server - let server = tiny_http::Server::http("localhost:3007").expect("failed to start updater server"); - - loop { - if let Ok(request) = server.recv() { - 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 body = serde_json::to_vec(&Update { - version: "1.0.0", - date: time::OffsetDateTime::now_utc() - .format(&time::format_description::well_known::Rfc3339) - .unwrap(), - platforms, - }) - .unwrap(); - let len = body.len(); - let response = tiny_http::Response::new( - tiny_http::StatusCode(200), - Vec::new(), - std::io::Cursor::new(body), - Some(len), - None, - ); - let _ = request.respond(response); + let updater_zip_ext = if cfg!(windows) { "zip" } else { "tar.gz" }; + + for out_bundle_path in bundle_paths(&root_dir, "1.0.0") { + let bundle_updater_ext = out_bundle_path + .extension() + .unwrap() + .to_str() + .unwrap() + .replace("exe", "nsis"); + let signature_path = + out_bundle_path.with_extension(format!("{}.{}.sig", bundle_updater_ext, updater_zip_ext)); + let signature = std::fs::read_to_string(&signature_path) + .unwrap_or_else(|_| panic!("failed to read signature file {}", signature_path.display())); + let out_updater_path = + out_bundle_path.with_extension(format!("{}.{}", bundle_updater_ext, updater_zip_ext)); + let updater_path = root_dir.join(format!( + "target/debug/{}", + 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(); + std::thread::spawn(move || { + // start the updater server + let server = + tiny_http::Server::http("localhost:3007").expect("failed to start updater server"); + + loop { + if let Ok(request) = server.recv() { + 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 body = serde_json::to_vec(&Update { + version: "1.0.0", + date: time::OffsetDateTime::now_utc() + .format(&time::format_description::well_known::Rfc3339) + .unwrap(), + platforms, + }) + .unwrap(); + let len = body.len(); + let response = tiny_http::Response::new( + tiny_http::StatusCode(200), + Vec::new(), + std::io::Cursor::new(body), + Some(len), + None, + ); + let _ = request.respond(response); + } + "/download" => { + let _ = request.respond(tiny_http::Response::from_file( + File::open(&updater_path).unwrap_or_else(|_| { + panic!("failed to open updater bundle {}", updater_path.display()) + }), + )); + // close server + return; + } + _ => (), } - "/download" => { - let _ = request.respond(tiny_http::Response::from_file( - File::open(&updater_path).unwrap_or_else(|_| { - panic!("failed to open updater bundle {}", updater_path.display()) - }), - )); - } - _ => (), } } - } - }); - - config.package.version = "0.1.0"; - - // bundle initial app version - build_app(&cli_bin_path, &manifest_dir, &config, false); - - let mut binary_cmd = if cfg!(windows) { - Command::new(root_dir.join("target/debug/app-updater.exe")) - } else if cfg!(target_os = "macos") { - Command::new(bundle_path(&root_dir, "0.1.0").join("Contents/MacOS/app-updater")) - } 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_path(&root_dir, "0.1.0")); - c - } else { - Command::new(bundle_path(&root_dir, "0.1.0")) - }; + }); + + config.package.version = "0.1.0"; + + // bundle initial app version + build_app(&cli_bin_path, &manifest_dir, &config, false); + + let mut binary_cmd = if cfg!(windows) { + Command::new(root_dir.join("target/debug/app-updater.exe")) + } else if cfg!(target_os = "macos") { + Command::new( + bundle_paths(&root_dir, "0.1.0") + .first() + .unwrap() + .join("Contents/MacOS/app-updater"), + ) + } 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()); + c + } else { + Command::new(bundle_paths(&root_dir, "0.1.0").first().unwrap()) + }; + + let status = binary_cmd.status().expect("failed to run app"); - let status = binary_cmd.status().expect("failed to run app"); - - if !status.success() { - panic!("failed to run app"); + if !status.success() { + panic!("failed to run app"); + } } } From 04391bda85e97f3fabab709806b3b2d096a1cae1 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Thu, 15 Dec 2022 00:09:12 -0300 Subject: [PATCH 36/46] optimize test --- core/tests/app-updater/tests/update.rs | 119 +++++++++++++++++++------ 1 file changed, 91 insertions(+), 28 deletions(-) diff --git a/core/tests/app-updater/tests/update.rs b/core/tests/app-updater/tests/update.rs index b17ef7c628e..a40df12ef82 100644 --- a/core/tests/app-updater/tests/update.rs +++ b/core/tests/app-updater/tests/update.rs @@ -54,7 +54,13 @@ fn get_cli_bin_path(cli_dir: &Path, debug: bool) -> Option { } } -fn build_app(cli_bin_path: &Path, cwd: &Path, config: &Config, bundle_updater: bool) { +fn build_app( + cli_bin_path: &Path, + cwd: &Path, + config: &Config, + bundle_updater: bool, + target: BundleTarget, +) { let mut command = Command::new(&cli_bin_path); command .args(["build", "--debug", "--verbose"]) @@ -62,18 +68,22 @@ fn build_app(cli_bin_path: &Path, cwd: &Path, config: &Config, bundle_updater: b .arg(serde_json::to_string(config).unwrap()) .current_dir(&cwd); - #[cfg(windows)] - command.args(["--bundles", "msi", "nsis"]); #[cfg(target_os = "linux")] - command.args(["--bundles", "appimage"]); + command.args(["--bundles", target.name()]); #[cfg(target_os = "macos")] - command.args(["--bundles", "app"]); + command.args(["--bundles", target.name()]); if bundle_updater { + #[cfg(windows)] + command.args(["--bundles", "msi", "nsis"]); + command .env("TAURI_PRIVATE_KEY", UPDATER_PRIVATE_KEY) .env("TAURI_KEY_PASSWORD", "") .args(["--bundles", "updater"]); + } else { + #[cfg(windows)] + command.args(["--bundles", target.name()]); } let status = command @@ -85,35 +95,81 @@ fn build_app(cli_bin_path: &Path, cwd: &Path, config: &Config, bundle_updater: b } } +enum BundleTarget { + AppImage, + + App, + + Msi, + Nsis, +} + +impl BundleTarget { + fn name(self) -> &'static str { + match self { + Self::AppImage => "appimage", + Self::App => "app", + Self::Msi => "msi", + Self::Nsis => "nsis", + } + } +} + +impl Default for BundleTarget { + fn default() -> Self { + #[cfg(any(target_os = "macos", target_os = "ios"))] + return Self::App; + #[cfg(target_os = "linux")] + return Self::App; + #[cfg(windows)] + return Self::Nsis; + } +} + #[cfg(target_os = "linux")] -fn bundle_paths(root_dir: &Path, version: &str) -> Vec { - root_dir.join(format!( - "target/debug/bundle/appimage/app-updater_{}_amd64.AppImage", - version - )) +fn bundle_paths(root_dir: &Path, version: &str) -> Vec<(BundleTarget, PathBuf)> { + vec![( + BundleTarget::AppImage, + root_dir.join(format!( + "target/debug/bundle/appimage/app-updater_{}_amd64.AppImage", + version + )), + )] } #[cfg(target_os = "macos")] -fn bundle_paths(root_dir: &Path, _version: &str) -> Vec { - vec![root_dir.join(format!("target/debug/bundle/macos/app-updater.app"))] +fn bundle_paths(root_dir: &Path, _version: &str) -> Vec<(BundleTarget, PathBuf)> { + vec![( + BundleTarget::App, + root_dir.join(format!("target/debug/bundle/macos/app-updater.app")), + )] } #[cfg(target_os = "ios")] -fn bundle_paths(root_dir: &Path, _version: &str) -> Vec { - vec![root_dir.join(format!("target/debug/bundle/ios/app-updater.app"))] +fn bundle_paths(root_dir: &Path, _version: &str) -> Vec<(BundleTarget, PathBuf)> { + vec![( + BundleTarget::App, + root_dir.join(format!("target/debug/bundle/ios/app-updater.app")), + )] } #[cfg(windows)] -fn bundle_paths(root_dir: &Path, version: &str) -> Vec { +fn bundle_paths(root_dir: &Path, version: &str) -> Vec<(BundleTarget, PathBuf)> { vec![ - root_dir.join(format!( - "target/debug/bundle/nsis/app-updater_{}_x64-setup.exe", - version - )), - root_dir.join(format!( - "target/debug/bundle/msi/app-updater_{}_x64_en-US.msi", - version - )), + ( + BundleTarget::Msi, + root_dir.join(format!( + "target/debug/bundle/msi/app-updater_{}_x64_en-US.msi", + version + )), + ), + ( + BundleTarget::Nsis, + root_dir.join(format!( + "target/debug/bundle/nsis/app-updater_{}_x64-setup.exe", + version + )), + ), ] } @@ -146,11 +202,17 @@ fn update_app() { }; // bundle app update - build_app(&cli_bin_path, &manifest_dir, &config, true); + build_app( + &cli_bin_path, + &manifest_dir, + &config, + true, + Default::default(), + ); let updater_zip_ext = if cfg!(windows) { "zip" } else { "tar.gz" }; - for out_bundle_path in bundle_paths(&root_dir, "1.0.0") { + for (bundle_target, out_bundle_path) in bundle_paths(&root_dir, "1.0.0") { let bundle_updater_ext = out_bundle_path .extension() .unwrap() @@ -225,7 +287,7 @@ fn update_app() { config.package.version = "0.1.0"; // bundle initial app version - build_app(&cli_bin_path, &manifest_dir, &config, false); + build_app(&cli_bin_path, &manifest_dir, &config, false, bundle_target); let mut binary_cmd = if cfg!(windows) { Command::new(root_dir.join("target/debug/app-updater.exe")) @@ -234,15 +296,16 @@ fn update_app() { bundle_paths(&root_dir, "0.1.0") .first() .unwrap() + .1 .join("Contents/MacOS/app-updater"), ) } 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()); + .arg(&bundle_paths(&root_dir, "0.1.0").first().unwrap().1); c } else { - Command::new(bundle_paths(&root_dir, "0.1.0").first().unwrap()) + Command::new(&bundle_paths(&root_dir, "0.1.0").first().unwrap().1) }; let status = binary_cmd.status().expect("failed to run app"); From 3eb73d13e2789db6c68c096ee8f203064409858c Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Thu, 15 Dec 2022 00:28:46 -0300 Subject: [PATCH 37/46] fix wix test [skip ci] --- core/tests/app-updater/src/main.rs | 10 ++++++---- core/tests/app-updater/tests/update.rs | 11 +++++++---- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/core/tests/app-updater/src/main.rs b/core/tests/app-updater/src/main.rs index 70816ebe98f..dbfb665cb41 100644 --- a/core/tests/app-updater/src/main.rs +++ b/core/tests/app-updater/src/main.rs @@ -9,10 +9,12 @@ fn main() { let mut context = tauri::generate_context!(); - context.config_mut().tauri.updater.windows.installer_args = vec![format!( - "/D={}", - std::env::current_exe().unwrap().parent().unwrap().display() - )]; + if std::env::var("TARGET").unwrap_or_default() == "nsis" { + context.config_mut().tauri.updater.windows.installer_args = vec![format!( + "/D={}", + std::env::current_exe().unwrap().parent().unwrap().display() + )]; + } tauri::Builder::default() .setup(|app| { let handle = app.handle(); diff --git a/core/tests/app-updater/tests/update.rs b/core/tests/app-updater/tests/update.rs index a40df12ef82..7e4619f935f 100644 --- a/core/tests/app-updater/tests/update.rs +++ b/core/tests/app-updater/tests/update.rs @@ -95,6 +95,7 @@ fn build_app( } } +#[derive(Copy, Clone)] enum BundleTarget { AppImage, @@ -157,16 +158,16 @@ fn bundle_paths(root_dir: &Path, _version: &str) -> Vec<(BundleTarget, PathBuf)> fn bundle_paths(root_dir: &Path, version: &str) -> Vec<(BundleTarget, PathBuf)> { vec![ ( - BundleTarget::Msi, + BundleTarget::Nsis, root_dir.join(format!( - "target/debug/bundle/msi/app-updater_{}_x64_en-US.msi", + "target/debug/bundle/nsis/app-updater_{}_x64-setup.exe", version )), ), ( - BundleTarget::Nsis, + BundleTarget::Msi, root_dir.join(format!( - "target/debug/bundle/nsis/app-updater_{}_x64-setup.exe", + "target/debug/bundle/msi/app-updater_{}_x64_en-US.msi", version )), ), @@ -308,6 +309,8 @@ fn update_app() { Command::new(&bundle_paths(&root_dir, "0.1.0").first().unwrap().1) }; + binary_cmd.env("TARGET", bundle_target.name()); + let status = binary_cmd.status().expect("failed to run app"); if !status.success() { From 6d09c2937dae448a6cb8ba531433cbb859f999aa Mon Sep 17 00:00:00 2001 From: amrbashir Date: Fri, 16 Dec 2022 02:08:28 +0200 Subject: [PATCH 38/46] Allow `major.minor.patch-pre` in NSIS --- tooling/bundler/src/bundle/windows/nsis.rs | 24 +-- .../windows/templates/SemverCompare.nsh | 150 ++++++++++++++++++ .../bundle/windows/templates/installer.nsi | 15 +- .../bundle/windows/templates/strExplode.nsh | 116 ++++++++++++++ 4 files changed, 285 insertions(+), 20 deletions(-) create mode 100644 tooling/bundler/src/bundle/windows/templates/SemverCompare.nsh create mode 100644 tooling/bundler/src/bundle/windows/templates/strExplode.nsh diff --git a/tooling/bundler/src/bundle/windows/nsis.rs b/tooling/bundler/src/bundle/windows/nsis.rs index d53626f7053..050ef43c428 100644 --- a/tooling/bundler/src/bundle/windows/nsis.rs +++ b/tooling/bundler/src/bundle/windows/nsis.rs @@ -6,9 +6,8 @@ use crate::{ bundle::{ common::CommandExt, windows::util::{ - download, download_and_verify, extract_zip, remove_unc_lossy, try_sign, validate_version, - HashAlgorithm, WEBVIEW2_BOOTSTRAPPER_URL, WEBVIEW2_X64_INSTALLER_GUID, - WEBVIEW2_X86_INSTALLER_GUID, + download, download_and_verify, extract_zip, remove_unc_lossy, try_sign, HashAlgorithm, + WEBVIEW2_BOOTSTRAPPER_URL, WEBVIEW2_X64_INSTALLER_GUID, WEBVIEW2_X86_INSTALLER_GUID, }, }, Settings, @@ -49,6 +48,8 @@ const NSIS_REQUIRED_FILES: &[&str] = &[ "Include/MUI2.nsh", "Include/FileFunc.nsh", "Include/x64.nsh", + "Include/semverCompare.nsh", + "Include/strExplode.nsh", ]; /// Runs all of the commands to build the NSIS installer. @@ -103,6 +104,16 @@ fn get_and_extract_nsis(nsis_toolset_path: &Path, tauri_tools_path: &Path) -> cr nsis_plugins.join("Plugin").join("nsProcessW.dll"), nsis_plugins.join("x86-unicode").join("nsProcess.dll"), )?; + + write( + nsis_toolset_path.join("Include").join("semverCompare.nsh"), + include_str!("./templates/semverCompare.nsh"), + )?; + write( + nsis_toolset_path.join("Include").join("strExplode.nsh"), + include_str!("./templates/strExplode.nsh"), + )?; + Ok(()) } @@ -123,8 +134,6 @@ fn build_nsis_app_installer( } }; - validate_version(settings.version_string())?; - info!("Target: {}", arch); let main_binary = settings @@ -155,11 +164,6 @@ fn build_nsis_app_installer( let version = settings.version_string(); data.insert("version", to_json(&version)); - let mut s = version.split("."); - data.insert("version_major", to_json(s.next().unwrap())); - data.insert("version_minor", to_json(s.next().unwrap())); - data.insert("version_build", to_json(s.next().unwrap())); - data.insert( "allow_downgrades", to_json(settings.windows().allow_downgrades), diff --git a/tooling/bundler/src/bundle/windows/templates/SemverCompare.nsh b/tooling/bundler/src/bundle/windows/templates/SemverCompare.nsh new file mode 100644 index 00000000000..e3bf6361e97 --- /dev/null +++ b/tooling/bundler/src/bundle/windows/templates/SemverCompare.nsh @@ -0,0 +1,150 @@ +!include "LogicLib.nsh" +!include "strExplode.nsh" + +!macro SemverCompare Ver1 Ver2 return_var + Push `${Ver2}` + Push `${Ver1}` + Call Func_SemverCompare + Pop ${return_var} +!macroend + +Function Func_SemverCompare + Var /GLOBAL VER1 + Var /GLOBAL VER2 + Var /GLOBAL V1 + Var /GLOBAL V2 + Var /GLOBAL V3 + Var /GLOBAL V4 + Var /GLOBAL V5 + Var /GLOBAL VR1 + Var /GLOBAL VR2 + Var /GLOBAL VR3 + Var /GLOBAL VR4 + Var /GLOBAL VR5 + Var /GLOBAL RET + + StrCpy $VER1 "" + StrCpy $VER2 "" + StrCpy $V1 "" + StrCpy $V2 "" + StrCpy $V3 "" + StrCpy $V4 "" + StrCpy $V5 "" + StrCpy $VR1 "" + StrCpy $VR2 "" + StrCpy $VR3 "" + StrCpy $VR4 "" + StrCpy $VR5 "" + StrCpy $RET "" + + Pop $VER1 + Pop $VER2 + + !insertmacro strExplode $0 '.' $VER1 + Pop $V1 + Pop $V2 + Pop $V3 + Pop $V5 + !insertmacro strExplode $0 '-' $V3 + Pop $V3 + Pop $V4 + + !insertmacro strExplode $0 '.' $VER2 + Pop $VR1 + Pop $VR2 + Pop $VR3 + Pop $VR5 + !insertmacro strExplode $0 '-' $VR3 + Pop $VR3 + Pop $VR4 + + ${If} $V1 > $VR1 + Goto higher + ${EndIf} + + ${If} $V1 < $VR1 + Goto lower + ${EndIf} + + ${If} $V2 > $VR2 + Goto higher + ${EndIf} + + ${If} $V2 < $VR2 + Goto lower + ${EndIf} + + ${If} $V3 > $VR3 + Goto higher + ${EndIf} + + ${If} $V3 < $VR3 + Goto lower + ${EndIf} + + ${If} $V4 == "beta" + ${If} $VR4 == "" + Goto lower + ${ElseIf} $VR4 == "alpha" + Goto higher + ${EndIf} + ${EndIf} + ${If} $V4 == "alpha" + ${If} $VR4 == "" + Goto lower + ${ElseIf} $VR4 == "beta" + Goto lower + ${EndIf} + ${EndIf} + ${If} $V4 == "" + ${If} $VR4 == "" + Goto equal + ${Else} + Goto higher + ${EndIf} + ${EndIf} + + ${If} $VR4 == "beta" + ${If} $V4 == "" + Goto higher + ${ElseIf} $V4 == "alpha" + Goto lower + ${EndIf} + ${EndIf} + ${If} $VR4 == "alpha" + ${If} $V4 == "" + Goto higher + ${ElseIf} $V4 == "beta" + Goto higher + ${EndIf} + ${EndIf} + ${If} $VR4 == "" + ${If} $V4 == "" + Goto equal + ${Else} + Goto lower + ${EndIf} + ${EndIf} + + ${If} $V5 > $VR5 + Goto higher + ${EndIf} + + ${If} $V5 < $VR5 + Goto lower + ${EndIf} + + Goto equal + + higher: + StrCpy $RET 1 + Goto done + lower: + StrCpy $RET -1 + Goto done + equal: + StrCpy $RET 0 + Goto done + done: + Push $RET +FunctionEnd \ No newline at end of file diff --git a/tooling/bundler/src/bundle/windows/templates/installer.nsi b/tooling/bundler/src/bundle/windows/templates/installer.nsi index 7cad1d1cc03..fcf67ad0f71 100644 --- a/tooling/bundler/src/bundle/windows/templates/installer.nsi +++ b/tooling/bundler/src/bundle/windows/templates/installer.nsi @@ -4,6 +4,7 @@ !include FileFunc.nsh !include x64.nsh !include WordFunc.nsh +!include SemverCompare.nsh ;-------------------------------- ; definitions @@ -11,9 +12,6 @@ !define MANUFACTURER "{{{manufacturer}}}" !define PRODUCTNAME "{{{product_name}}}" !define VERSION "{{{version}}}" -!define VERSIONMAJOR "{{{version_major}}}" -!define VERSIONMINOR "{{{version_minor}}}" -!define VERSIONBUILD "{{{version_build}}}" !define INSTALLMODE "{{{installer_mode}}}" !define LICENSE "{{{license}}}" !define INSTALLERICON "{{{installer_icon}}}" @@ -260,13 +258,10 @@ Section Install ; Registry information for add/remove programs WriteRegStr SHCTX "${APR}" "DisplayName" "${PRODUCTNAME}" WriteRegStr SHCTX "${APR}" "DisplayIcon" "$\"$INSTDIR\${MAINBINARYNAME}.exe$\"" - WriteRegStr SHCTX "${APR}" "DisplayVersion" "$\"${VERSION}$\"" + WriteRegStr SHCTX "${APR}" "DisplayVersion" "${VERSION}" WriteRegStr SHCTX "${APR}" "Publisher" "${MANUFACTURER}" WriteRegStr SHCTX "${APR}" "InstallLocation" "$\"$INSTDIR$\"" WriteRegStr SHCTX "${APR}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" - WriteRegDWORD SHCTX "${APR}" "VersionMajor" ${VERSIONMAJOR} - WriteRegDWORD SHCTX "${APR}" "VersionMinor" ${VERSIONMINOR} - WriteRegDWORD SHCTX "${APR}" "VersionBuild" ${VERSIONBUILD} WriteRegDWORD SHCTX "${APR}" "NoModify" "1" WriteRegDWORD SHCTX "${APR}" "NoRepair" "1" ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 @@ -317,7 +312,7 @@ Function PageReinstall ReadRegStr $R0 SHCTX "${APR}" "DisplayVersion" ${IfThen} $R0 == "" ${|} StrCpy $R4 "unknown" ${|} - ${VersionCompare} "$\"${VERSION}$\"" $R0 $R0 + !insertmacro SemverCompare "${VERSION}" $R0 $R0 ; Reinstalling the same version ${If} $R0 == 0 StrCpy $R1 "${PRODUCTNAME} ${VERSION} is already installed. Select the operation you want to perform and click Next to continue." @@ -333,7 +328,7 @@ Function PageReinstall !insertmacro MUI_HEADER_TEXT "Already Installed" "Choose how you want to install ${PRODUCTNAME}." StrCpy $R0 "1" ; Downgrading - ${ElseIf} $R0 == 2 + ${ElseIf} $R0 == -1 StrCpy $R1 "A newer version of ${PRODUCTNAME} is already installed! It is not recommended that you install an older version. If you really want to install this older version, it's better to uninstall the current version first. Select the operation you want to perform and click Next to continue." StrCpy $R2 "Uninstall before installing" !if "${ALLOWDOWNGRADES}" == "true" @@ -392,7 +387,7 @@ FunctionEnd Function PageLeaveReinstall ${NSD_GetState} $R2 $R1 - ; $R0 holds whether we are reinstallign the same version or not + ; $R0 holds whether we are reinstalling the same version or not ; $R0 == "1" -> different versions ; $R0 == "2" -> same version ; diff --git a/tooling/bundler/src/bundle/windows/templates/strExplode.nsh b/tooling/bundler/src/bundle/windows/templates/strExplode.nsh new file mode 100644 index 00000000000..f10784b8d6a --- /dev/null +++ b/tooling/bundler/src/bundle/windows/templates/strExplode.nsh @@ -0,0 +1,116 @@ +; Original file: https://github.com/fatalwall/NSIS_strExplode/blob/1c1bdd5390c2c1f9812f2e385208e9f29ad01ade/strExplode.nsh +; +;===================================================================================== +;= +;= Project Name: NSIS_DotNetVersion +;= +;= File Name: strExplode.nsh +;= File Version: 1.0 Beta +;= +;= Descritoin: NSIS Library used to split a string into an array based +;= on a separator character +;= +;===================================================================================== +;= Copyright (C) 2018 Peter Varney - All Rights Reserved +;= You may use, distribute and modify this code under the +;= terms of the MIT license, +;= +;= You should have received a copy of the MIT license with +;= this file. If not, visit : https://github.com/fatalwall/NSIS_strExplode +;===================================================================================== +;= +;= Usage: +;= strExplode Length Separator String +;= +;= Length Variable such as $0 that the resutl is returned in +;= Separator A single character which will be used to split the passed string +;= String Original String which is passed in +;= +;= Example 1 - Known count of elements returned: +;= !include strExplode.nsh +;= strExplode $0 '.' '4.7.1' +;= ${If} $0 == 3 +;= Pop $1 +;= Pop $2 +;= Pop $3 +;= DetailPrint "First Part: $1" +;= DetailPrint "Sectond Part: $2" +;= DetailPrint "Third Part: $3" +;= ${EndIf} +;= +;= Example 2 - Unknown count of elements returned: +;= !include strExplode.nsh +;= strExplode $0 '.' '4.7.1' +;= ${do} +;= Pop $1 +;= ${If} ${Errors} +;= ;All Parts have been popped +;= ClearErrors +;= ${ExitDo} +;= ${Else} +;= DetailPrint "Part Value: $1" +;= ${EndIf} +;= ${loop} +;= +;===================================================================================== + +!include 'LogicLib.nsh' + +!ifndef strExplode +!define strExplode "!insertmacro strExplode" + + +VAR EVAL_PART +VAR PARAM_SEPARATOR +VAR PARAM_STRING +VAR EVAL_CHAR + +VAR INT_RETURN + +!macro strExplode Length Separator String + Push `${Separator}` + Push `${String}` + !ifdef __UNINSTALL__ + Call un.strExplode + !else + Call strExplode + !endif + Pop `${Length}` +!macroend + +!macro Func_strExplode un + Function ${un}strExplode + ClearErrors + + Pop $PARAM_STRING + Pop $PARAM_SEPARATOR + StrCpy $EVAL_PART '' ;ensur variable is blank + StrCpy $INT_RETURN 0 ; Initialize Counter + ${Do} + StrCpy $EVAL_CHAR $PARAM_STRING "" -1 ;Get Last Character + ${If} $EVAL_CHAR == '' + ;End of string + IntOp $INT_RETURN $INT_RETURN + 1 ; Increment Counter + push $EVAL_PART + ${ExitDo} + ${ElseIf} $EVAL_CHAR == $PARAM_SEPARATOR + ;Seperator found. Splitting + IntOp $INT_RETURN $INT_RETURN + 1 ; Increment Counter + push $EVAL_PART + StrCpy $EVAL_PART '' ; clear + ${Else} + ;add next character to item + StrCpy $EVAL_PART "$EVAL_CHAR$EVAL_PART" + ${EndIf} + StrCpy $PARAM_STRING $PARAM_STRING -1 ;remove Last character + ${Loop} + + ;Number of items string was split into + push $INT_RETURN + FunctionEnd +!macroend + +!insertmacro Func_strExplode "" +!insertmacro Func_strExplode "un." + +!endif From 70b06b60c7057a9878d53bb2d0f68e2c79aacf83 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Wed, 21 Dec 2022 02:37:35 +0200 Subject: [PATCH 39/46] switch to `NSIS-SemverCompare` plugin --- tooling/bundler/src/bundle/windows/nsis.rs | 17 +- .../windows/templates/SemverCompare.nsh | 150 ------------------ .../bundle/windows/templates/installer.nsi | 4 +- .../bundle/windows/templates/strExplode.nsh | 116 -------------- 4 files changed, 12 insertions(+), 275 deletions(-) delete mode 100644 tooling/bundler/src/bundle/windows/templates/SemverCompare.nsh delete mode 100644 tooling/bundler/src/bundle/windows/templates/strExplode.nsh diff --git a/tooling/bundler/src/bundle/windows/nsis.rs b/tooling/bundler/src/bundle/windows/nsis.rs index 050ef43c428..dd73707319c 100644 --- a/tooling/bundler/src/bundle/windows/nsis.rs +++ b/tooling/bundler/src/bundle/windows/nsis.rs @@ -36,6 +36,9 @@ const NSIS_NSCURL_URL: &str = const NSIS_APPLICATIONID_URL: &str = "https://github.com/tauri-apps/binary-releases/releases/download/nsis-plugins-v0/NSIS-ApplicationID.zip"; const NSIS_NSPROCESS_URL: &str = "https://github.com/tauri-apps/binary-releases/releases/download/nsis-plugins-v0/NsProcess.zip"; +const NSIS_SEMVER_COMPARE: &str = + "https://github.com/tauri-apps/NSIS-SemverCompare/releases/download/v0.1.1/SemverCompare.dll"; +const NSIS_SEMVER_COMPARE_SHA1: &str = "B5619EAA0279DE40BCCC61D7EB9A0869D0EAE006"; const NSIS_REQUIRED_FILES: &[&str] = &[ "makensis.exe", @@ -45,11 +48,10 @@ const NSIS_REQUIRED_FILES: &[&str] = &[ "Plugins/x86-unicode/NScurl.dll", "Plugins/x86-unicode/ApplicationID.dll", "Plugins/x86-unicode/nsProcess.dll", + "Plugins/x86-unicode/SemverCompare.dll", "Include/MUI2.nsh", "Include/FileFunc.nsh", "Include/x64.nsh", - "Include/semverCompare.nsh", - "Include/strExplode.nsh", ]; /// Runs all of the commands to build the NSIS installer. @@ -105,13 +107,14 @@ fn get_and_extract_nsis(nsis_toolset_path: &Path, tauri_tools_path: &Path) -> cr nsis_plugins.join("x86-unicode").join("nsProcess.dll"), )?; - write( - nsis_toolset_path.join("Include").join("semverCompare.nsh"), - include_str!("./templates/semverCompare.nsh"), + let data = download_and_verify( + NSIS_SEMVER_COMPARE, + NSIS_SEMVER_COMPARE_SHA1, + HashAlgorithm::Sha1, )?; write( - nsis_toolset_path.join("Include").join("strExplode.nsh"), - include_str!("./templates/strExplode.nsh"), + nsis_plugins.join("x86-unicode").join("SemverCompare.dll"), + data, )?; Ok(()) diff --git a/tooling/bundler/src/bundle/windows/templates/SemverCompare.nsh b/tooling/bundler/src/bundle/windows/templates/SemverCompare.nsh deleted file mode 100644 index e3bf6361e97..00000000000 --- a/tooling/bundler/src/bundle/windows/templates/SemverCompare.nsh +++ /dev/null @@ -1,150 +0,0 @@ -!include "LogicLib.nsh" -!include "strExplode.nsh" - -!macro SemverCompare Ver1 Ver2 return_var - Push `${Ver2}` - Push `${Ver1}` - Call Func_SemverCompare - Pop ${return_var} -!macroend - -Function Func_SemverCompare - Var /GLOBAL VER1 - Var /GLOBAL VER2 - Var /GLOBAL V1 - Var /GLOBAL V2 - Var /GLOBAL V3 - Var /GLOBAL V4 - Var /GLOBAL V5 - Var /GLOBAL VR1 - Var /GLOBAL VR2 - Var /GLOBAL VR3 - Var /GLOBAL VR4 - Var /GLOBAL VR5 - Var /GLOBAL RET - - StrCpy $VER1 "" - StrCpy $VER2 "" - StrCpy $V1 "" - StrCpy $V2 "" - StrCpy $V3 "" - StrCpy $V4 "" - StrCpy $V5 "" - StrCpy $VR1 "" - StrCpy $VR2 "" - StrCpy $VR3 "" - StrCpy $VR4 "" - StrCpy $VR5 "" - StrCpy $RET "" - - Pop $VER1 - Pop $VER2 - - !insertmacro strExplode $0 '.' $VER1 - Pop $V1 - Pop $V2 - Pop $V3 - Pop $V5 - !insertmacro strExplode $0 '-' $V3 - Pop $V3 - Pop $V4 - - !insertmacro strExplode $0 '.' $VER2 - Pop $VR1 - Pop $VR2 - Pop $VR3 - Pop $VR5 - !insertmacro strExplode $0 '-' $VR3 - Pop $VR3 - Pop $VR4 - - ${If} $V1 > $VR1 - Goto higher - ${EndIf} - - ${If} $V1 < $VR1 - Goto lower - ${EndIf} - - ${If} $V2 > $VR2 - Goto higher - ${EndIf} - - ${If} $V2 < $VR2 - Goto lower - ${EndIf} - - ${If} $V3 > $VR3 - Goto higher - ${EndIf} - - ${If} $V3 < $VR3 - Goto lower - ${EndIf} - - ${If} $V4 == "beta" - ${If} $VR4 == "" - Goto lower - ${ElseIf} $VR4 == "alpha" - Goto higher - ${EndIf} - ${EndIf} - ${If} $V4 == "alpha" - ${If} $VR4 == "" - Goto lower - ${ElseIf} $VR4 == "beta" - Goto lower - ${EndIf} - ${EndIf} - ${If} $V4 == "" - ${If} $VR4 == "" - Goto equal - ${Else} - Goto higher - ${EndIf} - ${EndIf} - - ${If} $VR4 == "beta" - ${If} $V4 == "" - Goto higher - ${ElseIf} $V4 == "alpha" - Goto lower - ${EndIf} - ${EndIf} - ${If} $VR4 == "alpha" - ${If} $V4 == "" - Goto higher - ${ElseIf} $V4 == "beta" - Goto higher - ${EndIf} - ${EndIf} - ${If} $VR4 == "" - ${If} $V4 == "" - Goto equal - ${Else} - Goto lower - ${EndIf} - ${EndIf} - - ${If} $V5 > $VR5 - Goto higher - ${EndIf} - - ${If} $V5 < $VR5 - Goto lower - ${EndIf} - - Goto equal - - higher: - StrCpy $RET 1 - Goto done - lower: - StrCpy $RET -1 - Goto done - equal: - StrCpy $RET 0 - Goto done - done: - Push $RET -FunctionEnd \ No newline at end of file diff --git a/tooling/bundler/src/bundle/windows/templates/installer.nsi b/tooling/bundler/src/bundle/windows/templates/installer.nsi index fcf67ad0f71..4f247ca326c 100644 --- a/tooling/bundler/src/bundle/windows/templates/installer.nsi +++ b/tooling/bundler/src/bundle/windows/templates/installer.nsi @@ -4,7 +4,6 @@ !include FileFunc.nsh !include x64.nsh !include WordFunc.nsh -!include SemverCompare.nsh ;-------------------------------- ; definitions @@ -312,7 +311,8 @@ Function PageReinstall ReadRegStr $R0 SHCTX "${APR}" "DisplayVersion" ${IfThen} $R0 == "" ${|} StrCpy $R4 "unknown" ${|} - !insertmacro SemverCompare "${VERSION}" $R0 $R0 + SemverCompare::SemverCompare "${VERSION}" $R0 + Pop $R0 ; Reinstalling the same version ${If} $R0 == 0 StrCpy $R1 "${PRODUCTNAME} ${VERSION} is already installed. Select the operation you want to perform and click Next to continue." diff --git a/tooling/bundler/src/bundle/windows/templates/strExplode.nsh b/tooling/bundler/src/bundle/windows/templates/strExplode.nsh deleted file mode 100644 index f10784b8d6a..00000000000 --- a/tooling/bundler/src/bundle/windows/templates/strExplode.nsh +++ /dev/null @@ -1,116 +0,0 @@ -; Original file: https://github.com/fatalwall/NSIS_strExplode/blob/1c1bdd5390c2c1f9812f2e385208e9f29ad01ade/strExplode.nsh -; -;===================================================================================== -;= -;= Project Name: NSIS_DotNetVersion -;= -;= File Name: strExplode.nsh -;= File Version: 1.0 Beta -;= -;= Descritoin: NSIS Library used to split a string into an array based -;= on a separator character -;= -;===================================================================================== -;= Copyright (C) 2018 Peter Varney - All Rights Reserved -;= You may use, distribute and modify this code under the -;= terms of the MIT license, -;= -;= You should have received a copy of the MIT license with -;= this file. If not, visit : https://github.com/fatalwall/NSIS_strExplode -;===================================================================================== -;= -;= Usage: -;= strExplode Length Separator String -;= -;= Length Variable such as $0 that the resutl is returned in -;= Separator A single character which will be used to split the passed string -;= String Original String which is passed in -;= -;= Example 1 - Known count of elements returned: -;= !include strExplode.nsh -;= strExplode $0 '.' '4.7.1' -;= ${If} $0 == 3 -;= Pop $1 -;= Pop $2 -;= Pop $3 -;= DetailPrint "First Part: $1" -;= DetailPrint "Sectond Part: $2" -;= DetailPrint "Third Part: $3" -;= ${EndIf} -;= -;= Example 2 - Unknown count of elements returned: -;= !include strExplode.nsh -;= strExplode $0 '.' '4.7.1' -;= ${do} -;= Pop $1 -;= ${If} ${Errors} -;= ;All Parts have been popped -;= ClearErrors -;= ${ExitDo} -;= ${Else} -;= DetailPrint "Part Value: $1" -;= ${EndIf} -;= ${loop} -;= -;===================================================================================== - -!include 'LogicLib.nsh' - -!ifndef strExplode -!define strExplode "!insertmacro strExplode" - - -VAR EVAL_PART -VAR PARAM_SEPARATOR -VAR PARAM_STRING -VAR EVAL_CHAR - -VAR INT_RETURN - -!macro strExplode Length Separator String - Push `${Separator}` - Push `${String}` - !ifdef __UNINSTALL__ - Call un.strExplode - !else - Call strExplode - !endif - Pop `${Length}` -!macroend - -!macro Func_strExplode un - Function ${un}strExplode - ClearErrors - - Pop $PARAM_STRING - Pop $PARAM_SEPARATOR - StrCpy $EVAL_PART '' ;ensur variable is blank - StrCpy $INT_RETURN 0 ; Initialize Counter - ${Do} - StrCpy $EVAL_CHAR $PARAM_STRING "" -1 ;Get Last Character - ${If} $EVAL_CHAR == '' - ;End of string - IntOp $INT_RETURN $INT_RETURN + 1 ; Increment Counter - push $EVAL_PART - ${ExitDo} - ${ElseIf} $EVAL_CHAR == $PARAM_SEPARATOR - ;Seperator found. Splitting - IntOp $INT_RETURN $INT_RETURN + 1 ; Increment Counter - push $EVAL_PART - StrCpy $EVAL_PART '' ; clear - ${Else} - ;add next character to item - StrCpy $EVAL_PART "$EVAL_CHAR$EVAL_PART" - ${EndIf} - StrCpy $PARAM_STRING $PARAM_STRING -1 ;remove Last character - ${Loop} - - ;Number of items string was split into - push $INT_RETURN - FunctionEnd -!macroend - -!insertmacro Func_strExplode "" -!insertmacro Func_strExplode "un." - -!endif From a8cfd7da23c858741afba2b8bb1b2919ad93d915 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Wed, 21 Dec 2022 20:17:44 +0200 Subject: [PATCH 40/46] add support for both modes --- core/config-schema/schema.json | 36 +- core/tauri-utils/src/config.rs | 31 +- tooling/bundler/src/bundle/settings.rs | 4 +- tooling/bundler/src/bundle/windows/nsis.rs | 13 +- .../bundle/windows/templates/installer.nsi | 488 ++++++++++-------- tooling/cli/schema.json | 36 +- tooling/cli/src/helpers/config.rs | 2 +- 7 files changed, 366 insertions(+), 244 deletions(-) diff --git a/core/config-schema/schema.json b/core/config-schema/schema.json index d9ad455100a..1fbfb8cef0b 100644 --- a/core/config-schema/schema.json +++ b/core/config-schema/schema.json @@ -1687,14 +1687,44 @@ "null" ] }, - "perMachine": { + "installMode": { "description": "Whether the installation will be for all users or just the current user.", - "default": false, - "type": "boolean" + "default": "currentUser", + "allOf": [ + { + "$ref": "#/definitions/NSISInstallerMode" + } + ] } }, "additionalProperties": false }, + "NSISInstallerMode": { + "description": "Install Modes for the NSIS installer.", + "oneOf": [ + { + "description": "Default mode for the installer and install the app by default in a directory that doesn't require Adminstrator access.\n\nInstaller metadata will also ve saved under `HKCU` registry path.", + "type": "string", + "enum": [ + "currentUser" + ] + }, + { + "description": "Install the app by default in a `Prgoram Files` which require Adminstrator access and so the install will also require Adminstrator access.\n\nInstaller metadata will also ve saved under `HKLM` registry path.", + "type": "string", + "enum": [ + "perMachine" + ] + }, + { + "description": "Combines both modes and allows the user to choose at install time whether to install for current user or per machine. Note that this mode will also require Adminstrator access even if the user wants to install it for current user only.\n\nInstaller metadata will also ve saved under `HKLM` or `HKCU` registry path based on the user choice.", + "type": "string", + "enum": [ + "both" + ] + } + ] + }, "AllowlistConfig": { "description": "Allowlist configuration.", "type": "object", diff --git a/core/tauri-utils/src/config.rs b/core/tauri-utils/src/config.rs index 60b60deaf0e..815f82c8045 100644 --- a/core/tauri-utils/src/config.rs +++ b/core/tauri-utils/src/config.rs @@ -439,7 +439,36 @@ pub struct NsisConfig { pub installer_icon: Option, /// Whether the installation will be for all users or just the current user. #[serde(default)] - pub per_machine: bool, + pub install_mode: NSISInstallerMode, +} + +/// Install Modes for the NSIS installer. +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[cfg_attr(feature = "schema", derive(JsonSchema))] +pub enum NSISInstallerMode { + /// Default mode for the installer and install the app + /// by default in a directory that doesn't require Adminstrator access. + /// + /// Installer metadata will also ve saved under `HKCU` registry path. + CurrentUser, + /// Install the app by default in a `Prgoram Files` which require Adminstrator access + /// and so the install will also require Adminstrator access. + /// + /// Installer metadata will also ve saved under `HKLM` registry path. + PerMachine, + /// Combines both modes and allows the user to choose at install time + /// whether to install for current user or per machine. Note that this mode + /// will also require Adminstrator access even if the user wants to install it for current user only. + /// + /// Installer metadata will also ve saved under `HKLM` or `HKCU` registry path based on the user choice. + Both, +} + +impl Default for NSISInstallerMode { + fn default() -> Self { + Self::CurrentUser + } } /// Install modes for the Webview2 runtime. diff --git a/tooling/bundler/src/bundle/settings.rs b/tooling/bundler/src/bundle/settings.rs index b0043867028..7fbf2d8d8b3 100644 --- a/tooling/bundler/src/bundle/settings.rs +++ b/tooling/bundler/src/bundle/settings.rs @@ -7,7 +7,7 @@ use super::category::AppCategory; use crate::bundle::{common, platform::target_triple}; pub use tauri_utils::config::WebviewInstallMode; use tauri_utils::{ - config::BundleType, + config::{BundleType, NSISInstallerMode}, resources::{external_binaries, ResourcePaths}, }; @@ -265,7 +265,7 @@ pub struct NsisSettings { /// The path to an icon file used as the installer icon. pub installer_icon: Option, /// Whether the installation will be for all users or just the current user. - pub per_machine: bool, + pub install_mode: NSISInstallerMode, } /// The Windows bundle settings. diff --git a/tooling/bundler/src/bundle/windows/nsis.rs b/tooling/bundler/src/bundle/windows/nsis.rs index dd73707319c..461b68d7b3a 100644 --- a/tooling/bundler/src/bundle/windows/nsis.rs +++ b/tooling/bundler/src/bundle/windows/nsis.rs @@ -15,7 +15,10 @@ use crate::{ use anyhow::Context; use handlebars::{to_json, Handlebars}; use log::{info, warn}; -use tauri_utils::{config::WebviewInstallMode, resources::resource_relpath}; +use tauri_utils::{ + config::{NSISInstallerMode, WebviewInstallMode}, + resources::resource_relpath, +}; use std::{ collections::BTreeMap, @@ -175,10 +178,10 @@ fn build_nsis_app_installer( if let Some(nsis) = &settings.windows().nsis { data.insert( "install_mode", - to_json(if nsis.per_machine { - "perMachine" - } else { - "perUser" + to_json(match nsis.install_mode { + NSISInstallerMode::CurrentUser => "currentUser", + NSISInstallerMode::PerMachine => "perMachine", + NSISInstallerMode::Both => "both", }), ); diff --git a/tooling/bundler/src/bundle/windows/templates/installer.nsi b/tooling/bundler/src/bundle/windows/templates/installer.nsi index 4f247ca326c..acfcd9d095c 100644 --- a/tooling/bundler/src/bundle/windows/templates/installer.nsi +++ b/tooling/bundler/src/bundle/windows/templates/installer.nsi @@ -1,17 +1,15 @@ -;-------------------------------- -; Header files +Var AppStartMenuFolder +Var ReinstallPageCheck + !include MUI2.nsh !include FileFunc.nsh !include x64.nsh !include WordFunc.nsh -;-------------------------------- -; definitions - !define MANUFACTURER "{{{manufacturer}}}" !define PRODUCTNAME "{{{product_name}}}" !define VERSION "{{{version}}}" -!define INSTALLMODE "{{{installer_mode}}}" +!define INSTALLMODE "{{{install_mode}}}" !define LICENSE "{{{license}}}" !define INSTALLERICON "{{{installer_icon}}}" !define SIDEBARIMAGE "{{{sidebar_image}}}" @@ -26,16 +24,7 @@ !define WEBVIEW2INSTALLERARGS "{{{webview2_installer_args}}}" !define WEBVIEW2BOOTSTRAPPERPATH "{{{webview2_bootstrapper_path}}}" !define WEBVIEW2INSTALLERPATH "{{{webview2_installer_path}}}" -!define APR "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCTNAME}" - -;-------------------------------- -; Variables - -Var AppStartMenuFolder -Var ReinstallPageCheck - -;-------------------------------- -; General onfiguration +!define UNINSTKEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCTNAME}" Name "${PRODUCTNAME}" OutFile "${OUTFILE}" @@ -43,27 +32,28 @@ Unicode true SetCompressor /SOLID lzma !if "${INSTALLMODE}" == "perMachine" - RequestExecutionLevel heighest - ; Set default install location - !if ${RunningX64} - !if "${ARCH}" == "x64" - InstallDir "$PROGRAMFILES64\${PRODUCTNAME}" - !else - InstallDir "$PROGRAMFILES\${PRODUCTNAME}" - !endif - !else - InstallDir "$PROGRAMFILES\${PRODUCTNAME}" - !endif - ; Override with the previous install location if it exists - InstallDirRegKey HKLM "Software\${MANUFACTURER}\${PRODUCTNAME}" "" -!else - RequestExecutionLevel user - InstallDir "$LOCALAPPDATA\${PRODUCTNAME}" - InstallDirRegKey HKCU "Software\${MANUFACTURER}\${PRODUCTNAME}" "" + RequestExecutionLevel highest !endif -;-------------------------------- -;Interface Settings +!if "${INSTALLMODE}" == "both" + !define MULTIUSER_MUI + !define MULTIUSER_EXECUTIONLEVEL Highest + !define MULTIUSER_INSTALLMODE_INSTDIR "${PRODUCTNAME}" + !define MULTIUSER_INSTALLMODE_COMMANDLINE + !if "${ARCH}" == "x64" + !define MULTIUSER_USE_PROGRAMFILES64 + !endif + !define MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_KEY "${UNINSTKEY}" + !define MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME "CurrentUser" + !define MULTIUSER_INSTALLMODEPAGE_SHOWUSERNAME + !define MULTIUSER_INSTALLMODE_FUNCTION RestorePreviousInstallLocation + Function RestorePreviousInstallLocation + ReadRegStr $4 SHCTX "Software\${MANUFACTURER}\${PRODUCTNAME}" "" + StrCmp $4 "" +2 0 + StrCpy $INSTDIR $4 + FunctionEnd + !include MultiUser.nsh +!endif !if "${INSTALLERICON}" != "" !define MUI_ICON "${INSTALLERICON}" @@ -81,75 +71,187 @@ SetCompressor /SOLID lzma ; Don't auto jump to finish page after installation page, ; because the installation page has useful info that can be used debug any issues with the installer. !define MUI_FINISHPAGE_NOAUTOCLOSE - ; Use show readme button in the finish page to create a desktop shortcut !define MUI_FINISHPAGE_SHOWREADME !define MUI_FINISHPAGE_SHOWREADME_TEXT "Create desktop shortcut" -!define MUI_FINISHPAGE_SHOWREADME_FUNCTION "CreateDesktopShortcut" - +!define MUI_FINISHPAGE_SHOWREADME_FUNCTION CreateDesktopShortcut +Function CreateDesktopShortcut + CreateShortcut "$DESKTOP\${MAINBINARYNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe" + ApplicationID::Set "$DESKTOP\${MAINBINARYNAME}.lnk" "${BUNDLEID}" +FunctionEnd ; Show run app after installation. !define MUI_FINISHPAGE_RUN "$INSTDIR\${MAINBINARYNAME}.exe" -;-------------------------------- -; Installer pages +Function .onInit + !if "${INSTALLMODE}" == "currentUser" + SetShellVarContext current + !else if "${INSTALLMODE}" == "perMachine" + SetShellVarContext all + !endif + + !if "${INSTALLMODE}" == "perMachine" + ; Set default install location + ${If} ${RunningX64} + !if "${ARCH}" == "x64" + StrCpy $INSTDIR "$PROGRAMFILES64\${PRODUCTNAME}" + !else + StrCpy $INSTDIR "$PROGRAMFILES\${PRODUCTNAME}" + !endif + ${Else} + StrCpy $INSTDIR "$PROGRAMFILES\${PRODUCTNAME}" + ${EndIf} + !else if "${INSTALLMODE}" == "currentUser" + StrCpy $INSTDIR "$LOCALAPPDATA\${PRODUCTNAME}" + !endif + + !if "${INSTALLMODE}" == "both" + !insertmacro MULTIUSER_INIT + !endif +FunctionEnd ; Installer pages, must be ordered as they appear !insertmacro MUI_PAGE_WELCOME -Page custom PageReinstall PageLeaveReinstall !if "${LICENSE}" != "" !insertmacro MUI_PAGE_LICENSE "${LICENSE}" !endif -!insertmacro MUI_PAGE_DIRECTORY -!insertmacro MUI_PAGE_STARTMENU Application $AppStartMenuFolder -!insertmacro MUI_PAGE_INSTFILES -!insertmacro MUI_PAGE_FINISH +!if "${INSTALLMODE}" == "both" + !insertmacro MULTIUSER_PAGE_INSTALLMODE +!endif +Page custom PageReinstall PageLeaveReinstall +Function PageReinstall + ; Check if there is an existing installation, if not, abort the reinstall page + ReadRegStr $R0 SHCTX "${UNINSTKEY}" "" + ReadRegStr $R1 SHCTX "${UNINSTKEY}" "UninstallString" + ${IfThen} "$R0$R1" == "" ${|} Abort ${|} -;-------------------------------- -; Uninstaller pages + ; Compare this installar version with the existing installation and modify the messages presented to the user accordingly + StrCpy $R4 "older" + ReadRegStr $R0 SHCTX "${UNINSTKEY}" "DisplayVersion" + ${IfThen} $R0 == "" ${|} StrCpy $R4 "unknown" ${|} -!insertmacro MUI_UNPAGE_CONFIRM -!insertmacro MUI_UNPAGE_INSTFILES + SemverCompare::SemverCompare "${VERSION}" $R0 + Pop $R0 + ; Reinstalling the same version + ${If} $R0 == 0 + StrCpy $R1 "${PRODUCTNAME} ${VERSION} is already installed. Select the operation you want to perform and click Next to continue." + StrCpy $R2 "Add/Reinstall components" + StrCpy $R3 "Uninstall ${PRODUCTNAME}" + !insertmacro MUI_HEADER_TEXT "Already Installed" "Choose the maintenance option to perform." + StrCpy $R0 "2" + ; Upgrading + ${ElseIf} $R0 == 1 + StrCpy $R1 "An $R4 version of ${PRODUCTNAME} is installed on your system. It's recommended that you uninstall the current version before installing. Select the operation you want to perform and click Next to continue." + StrCpy $R2 "Uninstall before installing" + StrCpy $R3 "Do not uninstall" + !insertmacro MUI_HEADER_TEXT "Already Installed" "Choose how you want to install ${PRODUCTNAME}." + StrCpy $R0 "1" + ; Downgrading + ${ElseIf} $R0 == -1 + StrCpy $R1 "A newer version of ${PRODUCTNAME} is already installed! It is not recommended that you install an older version. If you really want to install this older version, it's better to uninstall the current version first. Select the operation you want to perform and click Next to continue." + StrCpy $R2 "Uninstall before installing" + !if "${ALLOWDOWNGRADES}" == "true" + StrCpy $R3 "Do not uninstall" + !else + StrCpy $R3 "Do not uninstall (Downgrading without uninstall is disabled for this installer)" + !endif + !insertmacro MUI_HEADER_TEXT "Already Installed" "Choose how you want to install ${PRODUCTNAME}." + StrCpy $R0 "1" + ${Else} + Abort + ${EndIf} -;-------------------------------- -;Languages -!insertmacro MUI_LANGUAGE English + nsDialogs::Create 1018 + Pop $R4 + ${NSD_CreateLabel} 0 0 100% 24u $R1 + Pop $R1 -;-------------------------------- -; Macros + ${NSD_CreateRadioButton} 30u 50u -30u 8u $R2 + Pop $R2 + ${NSD_OnClick} $R2 PageReinstallUpdateSelection -!macro CheckIfAppIsRunning - nsProcess::_FindProcess "${MAINBINARYNAME}.exe" - Pop $R0 - ${If} $R0 = 0 - IfSilent silent ui - silent: - System::Call 'kernel32::AttachConsole(i -1)i.r0' - ${If} $0 != 0 - System::Call 'kernel32::GetStdHandle(i -11)i.r0' - System::call 'kernel32::SetConsoleTextAttribute(i r0, i 0x0004)' ; set red color - FileWrite $0 "${PRODUCTNAME} is running. Please close it first then try again.$\n" + ${NSD_CreateRadioButton} 30u 70u -30u 8u $R3 + Pop $R3 + ; disable this radio button if downgrades are not allowed + !if "${ALLOWDOWNGRADES}" == "false" + EnableWindow $R3 0 + !endif + ${NSD_OnClick} $R3 PageReinstallUpdateSelection + + ${If} $ReinstallPageCheck != 2 + SendMessage $R2 ${BM_SETCHECK} ${BST_CHECKED} 0 + ${Else} + SendMessage $R3 ${BM_SETCHECK} ${BST_CHECKED} 0 + ${EndIf} + + ${NSD_SetFocus} $R2 + + nsDialogs::Show +FunctionEnd +Function PageReinstallUpdateSelection + Pop $R1 + + ${NSD_GetState} $R2 $R1 + + ${If} $R1 == ${BST_CHECKED} + StrCpy $ReinstallPageCheck 1 + ${Else} + StrCpy $ReinstallPageCheck 2 + ${EndIf} + +FunctionEnd +Function PageLeaveReinstall + ${NSD_GetState} $R2 $R1 + + ; $R0 holds whether we are reinstalling the same version or not + ; $R0 == "1" -> different versions + ; $R0 == "2" -> same version + ; + ; $R1 holds the radio buttons state. its meaning is dependant on the context + StrCmp $R0 "1" 0 +2 ; Existing install is not the same version? + StrCmp $R1 "1" reinst_uninstall reinst_done + StrCmp $R1 "1" reinst_done ; Same version, skip to add/reinstall components? + + reinst_uninstall: + ReadRegStr $4 SHCTX "Software\${MANUFACTURER}\${PRODUCTNAME}" "" + ReadRegStr $R1 SHCTX "${UNINSTKEY}" "UninstallString" + + HideWindow + + ClearErrors + ExecWait '$R1 _?=$4' $0 + + BringToFront + + ${IfThen} ${Errors} ${|} StrCpy $0 2 ${|} ; ExecWait failed, set fake exit code + + ${If} $0 <> 0 + ${OrIf} ${FileExists} "$INSTDIR\${MAINBINARYNAME}.exe" + ${If} $0 = 1 ; User aborted uninstaller? + StrCmp $R0 "2" 0 +2 ; Is the existing install the same version? + Quit ; ...yes, already installed, we are done + Abort ${EndIf} + MessageBox MB_ICONEXCLAMATION "Unable to uninstall!" Abort - ui: - MessageBox MB_OKCANCEL "${PRODUCTNAME} is running$\nClick OK to kill it" IDOK ok IDCANCEL cancel - ok: - nsProcess::_KillProcess "${MAINBINARYNAME}.exe" - Pop $R0 - Sleep 500 - ${If} $R0 = 0 - Goto done - ${Else} - Abort "Failed to kill ${PRODUCTNAME}. Please close it first then try again" - ${EndIf} - cancel: - Abort "${PRODUCTNAME} is running. Please close it first then try again" - ${EndIf} - done: -!macroend + ${Else} + StrCpy $0 $R1 1 + ${IfThen} $0 == '"' ${|} StrCpy $R1 $R1 -1 1 ${|} ; Strip quotes from UninstallString + Delete $R1 + RMDir $INSTDIR + ${EndIf} -;-------------------------------- -;Installer Sections + reinst_done: +FunctionEnd +!insertmacro MUI_PAGE_DIRECTORY +!insertmacro MUI_PAGE_STARTMENU Application $AppStartMenuFolder +!insertmacro MUI_PAGE_INSTFILES +!insertmacro MUI_PAGE_FINISH +; Uninstaller pages +!insertmacro MUI_UNPAGE_CONFIRM +!insertmacro MUI_UNPAGE_INSTFILES +;Languages +!insertmacro MUI_LANGUAGE English Section EarlyChecks ; Abort silent installer if downgrades is disabled @@ -228,6 +330,36 @@ Section Webview2 done: SectionEnd +!macro CheckIfAppIsRunning + nsProcess::_FindProcess "${MAINBINARYNAME}.exe" + Pop $R0 + ${If} $R0 = 0 + IfSilent silent ui + silent: + System::Call 'kernel32::AttachConsole(i -1)i.r0' + ${If} $0 != 0 + System::Call 'kernel32::GetStdHandle(i -11)i.r0' + System::call 'kernel32::SetConsoleTextAttribute(i r0, i 0x0004)' ; set red color + FileWrite $0 "${PRODUCTNAME} is running. Please close it first then try again.$\n" + ${EndIf} + Abort + ui: + MessageBox MB_OKCANCEL "${PRODUCTNAME} is running$\nClick OK to kill it" IDOK ok IDCANCEL cancel + ok: + nsProcess::_KillProcess "${MAINBINARYNAME}.exe" + Pop $R0 + Sleep 500 + ${If} $R0 = 0 + Goto done + ${Else} + Abort "Failed to kill ${PRODUCTNAME}. Please close it first then try again" + ${EndIf} + cancel: + Abort "${PRODUCTNAME} is running. Please close it first then try again" + ${EndIf} + done: +!macroend + Section Install SetOutPath $INSTDIR @@ -254,18 +386,29 @@ Section Install ; Save $INSTDIR in registry for future installations WriteRegStr SHCTX "Software\${MANUFACTURER}\${PRODUCTNAME}" "" $INSTDIR + !if "${INSTALLMODE}" == "both" + ; Save install mode to be selected by default for the next installation such as updating + WriteRegStr SHCTX "${UNINSTKEY}" $MultiUser.InstallMode 1 + + ; Save install mode to be read by the uninstaller in order to remove the correct + ; registry key + FileOpen $4 "$INSTDIR\installmode" w + FileWrite $4 $MultiUser.InstallMode + FileClose $4 + !endif + ; Registry information for add/remove programs - WriteRegStr SHCTX "${APR}" "DisplayName" "${PRODUCTNAME}" - WriteRegStr SHCTX "${APR}" "DisplayIcon" "$\"$INSTDIR\${MAINBINARYNAME}.exe$\"" - WriteRegStr SHCTX "${APR}" "DisplayVersion" "${VERSION}" - WriteRegStr SHCTX "${APR}" "Publisher" "${MANUFACTURER}" - WriteRegStr SHCTX "${APR}" "InstallLocation" "$\"$INSTDIR$\"" - WriteRegStr SHCTX "${APR}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" - WriteRegDWORD SHCTX "${APR}" "NoModify" "1" - WriteRegDWORD SHCTX "${APR}" "NoRepair" "1" + WriteRegStr SHCTX "${UNINSTKEY}" "DisplayName" "${PRODUCTNAME}" + WriteRegStr SHCTX "${UNINSTKEY}" "DisplayIcon" "$\"$INSTDIR\${MAINBINARYNAME}.exe$\"" + WriteRegStr SHCTX "${UNINSTKEY}" "DisplayVersion" "${VERSION}" + WriteRegStr SHCTX "${UNINSTKEY}" "Publisher" "${MANUFACTURER}" + WriteRegStr SHCTX "${UNINSTKEY}" "InstallLocation" "$\"$INSTDIR$\"" + WriteRegStr SHCTX "${UNINSTKEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" + WriteRegDWORD SHCTX "${UNINSTKEY}" "NoModify" "1" + WriteRegDWORD SHCTX "${UNINSTKEY}" "NoRepair" "1" ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 IntFmt $0 "0x%08X" $0 - WriteRegDWORD SHCTX "${APR}" "EstimatedSize" "$0" + WriteRegDWORD SHCTX "${UNINSTKEY}" "EstimatedSize" "$0" ; Create start menu shortcut !insertmacro MUI_STARTMENU_WRITE_BEGIN Application @@ -277,9 +420,33 @@ Section Install SectionEnd +Function un.onInit + !if "${INSTALLMODE}" == "both" + !insertmacro MULTIUSER_UNINIT + !endif +FunctionEnd + Section Uninstall !insertmacro CheckIfAppIsRunning + ; Remove registry information for add/remove programs + !if "${INSTALLMODE}" == "both" + ; Get the saved install mode + FileOpen $4 "$INSTDIR\installmode" r + FileRead $4 $1 + FileClose $4 + + ${If} $1 == "AllUsers" + DeleteRegKey HKLM "${UNINSTKEY}" + ${ElseIf} $1 == "CurrentUser" + DeleteRegKey HKCU "${UNINSTKEY}" + ${EndIf} + !else if "${INSTALLMODE}" == "perMachine" + DeleteRegKey HKLM "${UNINSTKEY}" + !else + DeleteRegKey HKCU "${UNINSTKEY}" + !endif + ; Delete the app directory and its content from disk RMDir /r "$INSTDIR" @@ -287,142 +454,5 @@ Section Uninstall !insertmacro MUI_STARTMENU_GETFOLDER Application $AppStartMenuFolder RMDir /r "$SMPROGRAMS\$AppStartMenuFolder" Delete "$DESKTOP\${MAINBINARYNAME}.lnk" - - ; Remove registry information for add/remove programs - DeleteRegKey SHCTX "${APR}" SectionEnd -;-------------------------------- -; Functions - -Function CreateDesktopShortcut - CreateShortcut "$DESKTOP\${MAINBINARYNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe" - ApplicationID::Set "$DESKTOP\${MAINBINARYNAME}.lnk" "${BUNDLEID}" -FunctionEnd - -Function PageReinstall - ; Check if there is an existing installation, if not, abort the reinstall page - ReadRegStr $R0 SHCTX "${APR}" "" - ReadRegStr $R1 SHCTX "${APR}" "UninstallString" - ${IfThen} "$R0$R1" == "" ${|} Abort ${|} - - ; Compare this installar version with the existing installation and modify the messages presented to the user accordingly - StrCpy $R4 "older" - ReadRegStr $R0 SHCTX "${APR}" "DisplayVersion" - ${IfThen} $R0 == "" ${|} StrCpy $R4 "unknown" ${|} - - SemverCompare::SemverCompare "${VERSION}" $R0 - Pop $R0 - ; Reinstalling the same version - ${If} $R0 == 0 - StrCpy $R1 "${PRODUCTNAME} ${VERSION} is already installed. Select the operation you want to perform and click Next to continue." - StrCpy $R2 "Add/Reinstall components" - StrCpy $R3 "Uninstall ${PRODUCTNAME}" - !insertmacro MUI_HEADER_TEXT "Already Installed" "Choose the maintenance option to perform." - StrCpy $R0 "2" - ; Upgrading - ${ElseIf} $R0 == 1 - StrCpy $R1 "An $R4 version of ${PRODUCTNAME} is installed on your system. It's recommended that you uninstall the current version before installing. Select the operation you want to perform and click Next to continue." - StrCpy $R2 "Uninstall before installing" - StrCpy $R3 "Do not uninstall" - !insertmacro MUI_HEADER_TEXT "Already Installed" "Choose how you want to install ${PRODUCTNAME}." - StrCpy $R0 "1" - ; Downgrading - ${ElseIf} $R0 == -1 - StrCpy $R1 "A newer version of ${PRODUCTNAME} is already installed! It is not recommended that you install an older version. If you really want to install this older version, it's better to uninstall the current version first. Select the operation you want to perform and click Next to continue." - StrCpy $R2 "Uninstall before installing" - !if "${ALLOWDOWNGRADES}" == "true" - StrCpy $R3 "Do not uninstall" - !else - StrCpy $R3 "Do not uninstall (Downgrading without uninstall is disabled for this installer)" - !endif - !insertmacro MUI_HEADER_TEXT "Already Installed" "Choose how you want to install ${PRODUCTNAME}." - StrCpy $R0 "1" - ${Else} - Abort - ${EndIf} - - nsDialogs::Create 1018 - Pop $R4 - - ${NSD_CreateLabel} 0 0 100% 24u $R1 - Pop $R1 - - ${NSD_CreateRadioButton} 30u 50u -30u 8u $R2 - Pop $R2 - ${NSD_OnClick} $R2 PageReinstallUpdateSelection - - ${NSD_CreateRadioButton} 30u 70u -30u 8u $R3 - Pop $R3 - ; disable this radio button if downgrades are not allowed - !if "${ALLOWDOWNGRADES}" == "false" - EnableWindow $R3 0 - !endif - ${NSD_OnClick} $R3 PageReinstallUpdateSelection - - ${If} $ReinstallPageCheck != 2 - SendMessage $R2 ${BM_SETCHECK} ${BST_CHECKED} 0 - ${Else} - SendMessage $R3 ${BM_SETCHECK} ${BST_CHECKED} 0 - ${EndIf} - - ${NSD_SetFocus} $R2 - - nsDialogs::Show -FunctionEnd - -Function PageReinstallUpdateSelection - Pop $R1 - - ${NSD_GetState} $R2 $R1 - - ${If} $R1 == ${BST_CHECKED} - StrCpy $ReinstallPageCheck 1 - ${Else} - StrCpy $ReinstallPageCheck 2 - ${EndIf} - -FunctionEnd - -Function PageLeaveReinstall - ${NSD_GetState} $R2 $R1 - - ; $R0 holds whether we are reinstalling the same version or not - ; $R0 == "1" -> different versions - ; $R0 == "2" -> same version - ; - ; $R1 holds the radio buttons state. its meaning is dependant on the context - StrCmp $R0 "1" 0 +2 ; Existing install is not the same version? - StrCmp $R1 "1" reinst_uninstall reinst_done - StrCmp $R1 "1" reinst_done ; Same version, skip to add/reinstall components? - - reinst_uninstall: - ReadRegStr $R1 SHCTX "${APR}" "UninstallString" - - HideWindow - - ClearErrors - ExecWait '$R1 _?=$INSTDIR' $0 - - BringToFront - - ${IfThen} ${Errors} ${|} StrCpy $0 2 ${|} ; ExecWait failed, set fake exit code - - ${If} $0 <> 0 - ${OrIf} ${FileExists} "$INSTDIR\${MAINBINARYNAME}.exe" - ${If} $0 = 1 ; User aborted uninstaller? - StrCmp $R0 "2" 0 +2 ; Is the existing install the same version? - Quit ; ...yes, already installed, we are done - Abort - ${EndIf} - MessageBox MB_ICONEXCLAMATION "Unable to uninstall!" - Abort - ${Else} - StrCpy $0 $R1 1 - ${IfThen} $0 == '"' ${|} StrCpy $R1 $R1 -1 1 ${|} ; Strip quotes from UninstallString - Delete $R1 - RMDir $INSTDIR - ${EndIf} - - reinst_done: -FunctionEnd diff --git a/tooling/cli/schema.json b/tooling/cli/schema.json index d9ad455100a..1fbfb8cef0b 100644 --- a/tooling/cli/schema.json +++ b/tooling/cli/schema.json @@ -1687,14 +1687,44 @@ "null" ] }, - "perMachine": { + "installMode": { "description": "Whether the installation will be for all users or just the current user.", - "default": false, - "type": "boolean" + "default": "currentUser", + "allOf": [ + { + "$ref": "#/definitions/NSISInstallerMode" + } + ] } }, "additionalProperties": false }, + "NSISInstallerMode": { + "description": "Install Modes for the NSIS installer.", + "oneOf": [ + { + "description": "Default mode for the installer and install the app by default in a directory that doesn't require Adminstrator access.\n\nInstaller metadata will also ve saved under `HKCU` registry path.", + "type": "string", + "enum": [ + "currentUser" + ] + }, + { + "description": "Install the app by default in a `Prgoram Files` which require Adminstrator access and so the install will also require Adminstrator access.\n\nInstaller metadata will also ve saved under `HKLM` registry path.", + "type": "string", + "enum": [ + "perMachine" + ] + }, + { + "description": "Combines both modes and allows the user to choose at install time whether to install for current user or per machine. Note that this mode will also require Adminstrator access even if the user wants to install it for current user only.\n\nInstaller metadata will also ve saved under `HKLM` or `HKCU` registry path based on the user choice.", + "type": "string", + "enum": [ + "both" + ] + } + ] + }, "AllowlistConfig": { "description": "Allowlist configuration.", "type": "object", diff --git a/tooling/cli/src/helpers/config.rs b/tooling/cli/src/helpers/config.rs index 7ff4ae7cd04..a0ebd1eef89 100644 --- a/tooling/cli/src/helpers/config.rs +++ b/tooling/cli/src/helpers/config.rs @@ -103,7 +103,7 @@ pub fn nsis_settings(config: NsisConfig) -> tauri_bundler::NsisSettings { header_image: config.header_image, sidebar_image: config.sidebar_image, installer_icon: config.installer_icon, - per_machine: config.per_machine, + install_mode: config.install_mode, } } From 38a60fe63aab0c922be14dec689a698b240caebd Mon Sep 17 00:00:00 2001 From: amrbashir Date: Thu, 22 Dec 2022 02:49:11 +0200 Subject: [PATCH 41/46] hide `installmode` file and make it readonly --- tooling/bundler/src/bundle/windows/templates/installer.nsi | 1 + 1 file changed, 1 insertion(+) diff --git a/tooling/bundler/src/bundle/windows/templates/installer.nsi b/tooling/bundler/src/bundle/windows/templates/installer.nsi index acfcd9d095c..75562dd461a 100644 --- a/tooling/bundler/src/bundle/windows/templates/installer.nsi +++ b/tooling/bundler/src/bundle/windows/templates/installer.nsi @@ -395,6 +395,7 @@ Section Install FileOpen $4 "$INSTDIR\installmode" w FileWrite $4 $MultiUser.InstallMode FileClose $4 + SetFileAttributes "$INSTDIR\installmode" HIDDEN|READONLY !endif ; Registry information for add/remove programs From 50ba0e32b08c92918c83064404888b8d36a571f7 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Thu, 22 Dec 2022 17:49:38 +0200 Subject: [PATCH 42/46] Update core/tauri-utils/src/config.rs Co-authored-by: Fabian-Lars --- core/tauri-utils/src/config.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/core/tauri-utils/src/config.rs b/core/tauri-utils/src/config.rs index 815f82c8045..30ea82958ba 100644 --- a/core/tauri-utils/src/config.rs +++ b/core/tauri-utils/src/config.rs @@ -447,21 +447,22 @@ pub struct NsisConfig { #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "schema", derive(JsonSchema))] pub enum NSISInstallerMode { - /// Default mode for the installer and install the app - /// by default in a directory that doesn't require Adminstrator access. + /// Default mode for the installer. /// - /// Installer metadata will also ve saved under `HKCU` registry path. + /// Install the app by default in a directory that doesn't require Administrator access. + /// + /// Installer metadata will be saved under the `HKCU` registry path. CurrentUser, - /// Install the app by default in a `Prgoram Files` which require Adminstrator access - /// and so the install will also require Adminstrator access. + /// Install the app by default in the `Program Files` folder directory requires Administrator + /// access for the installation. /// - /// Installer metadata will also ve saved under `HKLM` registry path. + /// Installer metadata will be saved under the `HKLM` registry path. PerMachine, /// Combines both modes and allows the user to choose at install time - /// whether to install for current user or per machine. Note that this mode - /// will also require Adminstrator access even if the user wants to install it for current user only. + /// whether to install for the current user or per machine. Note that this mode + /// will require Administrator access even if the user wants to install it for the current user only. /// - /// Installer metadata will also ve saved under `HKLM` or `HKCU` registry path based on the user choice. + /// Installer metadata will be saved under the `HKLM` or `HKCU` registry path based on the user's choice. Both, } From e36f74ab9ef449ade2b6419f92455c069e8cfa4d Mon Sep 17 00:00:00 2001 From: amrbashir Date: Thu, 22 Dec 2022 17:51:02 +0200 Subject: [PATCH 43/46] update schema.json --- core/config-schema/schema.json | 6 +++--- tooling/cli/schema.json | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/core/config-schema/schema.json b/core/config-schema/schema.json index 1fbfb8cef0b..5802ab296eb 100644 --- a/core/config-schema/schema.json +++ b/core/config-schema/schema.json @@ -1703,21 +1703,21 @@ "description": "Install Modes for the NSIS installer.", "oneOf": [ { - "description": "Default mode for the installer and install the app by default in a directory that doesn't require Adminstrator access.\n\nInstaller metadata will also ve saved under `HKCU` registry path.", + "description": "Default mode for the installer.\n\nInstall the app by default in a directory that doesn't require Administrator access.\n\nInstaller metadata will be saved under the `HKCU` registry path.", "type": "string", "enum": [ "currentUser" ] }, { - "description": "Install the app by default in a `Prgoram Files` which require Adminstrator access and so the install will also require Adminstrator access.\n\nInstaller metadata will also ve saved under `HKLM` registry path.", + "description": "Install the app by default in the `Program Files` folder directory requires Administrator access for the installation.\n\nInstaller metadata will be saved under the `HKLM` registry path.", "type": "string", "enum": [ "perMachine" ] }, { - "description": "Combines both modes and allows the user to choose at install time whether to install for current user or per machine. Note that this mode will also require Adminstrator access even if the user wants to install it for current user only.\n\nInstaller metadata will also ve saved under `HKLM` or `HKCU` registry path based on the user choice.", + "description": "Combines both modes and allows the user to choose at install time whether to install for the current user or per machine. Note that this mode will require Administrator access even if the user wants to install it for the current user only.\n\nInstaller metadata will be saved under the `HKLM` or `HKCU` registry path based on the user's choice.", "type": "string", "enum": [ "both" diff --git a/tooling/cli/schema.json b/tooling/cli/schema.json index 1fbfb8cef0b..5802ab296eb 100644 --- a/tooling/cli/schema.json +++ b/tooling/cli/schema.json @@ -1703,21 +1703,21 @@ "description": "Install Modes for the NSIS installer.", "oneOf": [ { - "description": "Default mode for the installer and install the app by default in a directory that doesn't require Adminstrator access.\n\nInstaller metadata will also ve saved under `HKCU` registry path.", + "description": "Default mode for the installer.\n\nInstall the app by default in a directory that doesn't require Administrator access.\n\nInstaller metadata will be saved under the `HKCU` registry path.", "type": "string", "enum": [ "currentUser" ] }, { - "description": "Install the app by default in a `Prgoram Files` which require Adminstrator access and so the install will also require Adminstrator access.\n\nInstaller metadata will also ve saved under `HKLM` registry path.", + "description": "Install the app by default in the `Program Files` folder directory requires Administrator access for the installation.\n\nInstaller metadata will be saved under the `HKLM` registry path.", "type": "string", "enum": [ "perMachine" ] }, { - "description": "Combines both modes and allows the user to choose at install time whether to install for current user or per machine. Note that this mode will also require Adminstrator access even if the user wants to install it for current user only.\n\nInstaller metadata will also ve saved under `HKLM` or `HKCU` registry path based on the user choice.", + "description": "Combines both modes and allows the user to choose at install time whether to install for the current user or per machine. Note that this mode will require Administrator access even if the user wants to install it for the current user only.\n\nInstaller metadata will be saved under the `HKLM` or `HKCU` registry path based on the user's choice.", "type": "string", "enum": [ "both" From 51dcd1458d979fc76df0b8064324dc330f530cc7 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Mon, 2 Jan 2023 15:03:22 +0200 Subject: [PATCH 44/46] use the new rust veresion of `nsis-semvercompare` --- core/tauri-utils/src/config.rs | 2 +- tooling/bundler/src/bundle/windows/nsis.rs | 13 ++++++++----- .../src/bundle/windows/templates/installer.nsi | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/core/tauri-utils/src/config.rs b/core/tauri-utils/src/config.rs index 30ea82958ba..34a1d018208 100644 --- a/core/tauri-utils/src/config.rs +++ b/core/tauri-utils/src/config.rs @@ -443,7 +443,7 @@ pub struct NsisConfig { } /// Install Modes for the NSIS installer. -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "schema", derive(JsonSchema))] pub enum NSISInstallerMode { diff --git a/tooling/bundler/src/bundle/windows/nsis.rs b/tooling/bundler/src/bundle/windows/nsis.rs index 461b68d7b3a..1a887a0e192 100644 --- a/tooling/bundler/src/bundle/windows/nsis.rs +++ b/tooling/bundler/src/bundle/windows/nsis.rs @@ -40,8 +40,8 @@ const NSIS_APPLICATIONID_URL: &str = "https://github.com/tauri-apps/binary-relea const NSIS_NSPROCESS_URL: &str = "https://github.com/tauri-apps/binary-releases/releases/download/nsis-plugins-v0/NsProcess.zip"; const NSIS_SEMVER_COMPARE: &str = - "https://github.com/tauri-apps/NSIS-SemverCompare/releases/download/v0.1.1/SemverCompare.dll"; -const NSIS_SEMVER_COMPARE_SHA1: &str = "B5619EAA0279DE40BCCC61D7EB9A0869D0EAE006"; + "https://github.com/tauri-apps/nsis-semvercompare/releases/download/v0.2.0/nsis_semvercompare.dll"; +const NSIS_SEMVER_COMPARE_SHA1: &str = "5A1A233F427C993B7E6A5821761BF5819507B29C"; const NSIS_REQUIRED_FILES: &[&str] = &[ "makensis.exe", @@ -51,7 +51,7 @@ const NSIS_REQUIRED_FILES: &[&str] = &[ "Plugins/x86-unicode/NScurl.dll", "Plugins/x86-unicode/ApplicationID.dll", "Plugins/x86-unicode/nsProcess.dll", - "Plugins/x86-unicode/SemverCompare.dll", + "Plugins/x86-unicode/nsis_semvercompare.dll", "Include/MUI2.nsh", "Include/FileFunc.nsh", "Include/x64.nsh", @@ -116,7 +116,9 @@ fn get_and_extract_nsis(nsis_toolset_path: &Path, tauri_tools_path: &Path) -> cr HashAlgorithm::Sha1, )?; write( - nsis_plugins.join("x86-unicode").join("SemverCompare.dll"), + nsis_plugins + .join("x86-unicode") + .join("nsis_semvercompare.dll"), data, )?; @@ -352,7 +354,8 @@ fn build_nsis_app_installer( info!(action = "Running"; "makensis.exe to produce {}", nsis_installer_path.display()); Command::new(nsis_toolset_path.join("makensis.exe")) - .args(&[installer_nsi_path]) + .arg("/V4") + .arg(installer_nsi_path) .current_dir(output_path) .output_ok() .context("error running makensis.exe")?; diff --git a/tooling/bundler/src/bundle/windows/templates/installer.nsi b/tooling/bundler/src/bundle/windows/templates/installer.nsi index 75562dd461a..b2583e62802 100644 --- a/tooling/bundler/src/bundle/windows/templates/installer.nsi +++ b/tooling/bundler/src/bundle/windows/templates/installer.nsi @@ -129,7 +129,7 @@ Function PageReinstall ReadRegStr $R0 SHCTX "${UNINSTKEY}" "DisplayVersion" ${IfThen} $R0 == "" ${|} StrCpy $R4 "unknown" ${|} - SemverCompare::SemverCompare "${VERSION}" $R0 + nsis_semvercompare::SemverCompare "${VERSION}" $R0 Pop $R0 ; Reinstalling the same version ${If} $R0 == 0 From b678c97ed8fe8c05b69e98b41984a8bd3dcb6bc6 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Mon, 2 Jan 2023 15:04:23 +0200 Subject: [PATCH 45/46] insert `install_mode` even if the `nsis` object is not defined in tauri conf --- tooling/bundler/src/bundle/windows/nsis.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/tooling/bundler/src/bundle/windows/nsis.rs b/tooling/bundler/src/bundle/windows/nsis.rs index 1a887a0e192..f9c63d0a637 100644 --- a/tooling/bundler/src/bundle/windows/nsis.rs +++ b/tooling/bundler/src/bundle/windows/nsis.rs @@ -177,16 +177,9 @@ fn build_nsis_app_installer( to_json(settings.windows().allow_downgrades), ); + let mut install_mode = NSISInstallerMode::CurrentUser; if let Some(nsis) = &settings.windows().nsis { - data.insert( - "install_mode", - to_json(match nsis.install_mode { - NSISInstallerMode::CurrentUser => "currentUser", - NSISInstallerMode::PerMachine => "perMachine", - NSISInstallerMode::Both => "both", - }), - ); - + install_mode = nsis.install_mode; if let Some(license) = &nsis.license { data.insert( "license", @@ -212,6 +205,14 @@ fn build_nsis_app_installer( ); } } + data.insert( + "install_mode", + to_json(match install_mode { + NSISInstallerMode::CurrentUser => "currentUser", + NSISInstallerMode::PerMachine => "perMachine", + NSISInstallerMode::Both => "both", + }), + ); let main_binary = settings .binaries() From d1179c78e2ce04067461461868f66d2020802f4b Mon Sep 17 00:00:00 2001 From: amrbashir Date: Mon, 2 Jan 2023 16:07:40 +0200 Subject: [PATCH 46/46] delete only the files we install, no recursive --- tooling/bundler/src/bundle/windows/nsis.rs | 13 +++++-- .../bundle/windows/templates/installer.nsi | 35 ++++++++++++++----- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/tooling/bundler/src/bundle/windows/nsis.rs b/tooling/bundler/src/bundle/windows/nsis.rs index f9c63d0a637..a5a8dd66d78 100644 --- a/tooling/bundler/src/bundle/windows/nsis.rs +++ b/tooling/bundler/src/bundle/windows/nsis.rs @@ -368,7 +368,7 @@ fn build_nsis_app_installer( } /// BTreeMap -type ResourcesMap = BTreeMap; +type ResourcesMap = BTreeMap; fn generate_resource_data(settings: &Settings) -> crate::Result { let mut resources = ResourcesMap::new(); let cwd = std::env::current_dir()?; @@ -388,7 +388,16 @@ fn generate_resource_data(settings: &Settings) -> crate::Result { added_resources.push(resource_path.clone()); let target_path = resource_relpath(&src); - resources.insert(resource_path, target_path); + resources.insert( + resource_path, + ( + target_path + .parent() + .map(|p| p.to_string_lossy().to_string()) + .unwrap_or_default(), + target_path, + ), + ); } Ok(resources) diff --git a/tooling/bundler/src/bundle/windows/templates/installer.nsi b/tooling/bundler/src/bundle/windows/templates/installer.nsi index b2583e62802..943baf3a5a3 100644 --- a/tooling/bundler/src/bundle/windows/templates/installer.nsi +++ b/tooling/bundler/src/bundle/windows/templates/installer.nsi @@ -370,14 +370,13 @@ Section Install ; Copy resources {{#each resources}} - ${GetParent} "{{this}}" $R1 - CreateDirectory "$INSTDIR\$R1" - File /a /oname={{this}} {{@key}} + CreateDirectory "$INSTDIR\\{{this.[0]}}" + File /a "/oname={{this.[1]}}" "{{@key}}" {{/each}} ; Copy external binaries {{#each binaries}} - File /a /oname={{this}} {{@key}} + File /a "/oname={{this}}" "{{@key}}" {{/each}} ; Create uninstaller @@ -416,7 +415,6 @@ Section Install CreateDirectory "$SMPROGRAMS\$AppStartMenuFolder" CreateShortcut "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe" ApplicationID::Set "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk" "${BUNDLEID}" - !insertmacro MUI_STARTMENU_WRITE_END SectionEnd @@ -436,6 +434,7 @@ Section Uninstall FileOpen $4 "$INSTDIR\installmode" r FileRead $4 $1 FileClose $4 + Delete "$INSTDIR\installmode" ${If} $1 == "AllUsers" DeleteRegKey HKLM "${UNINSTKEY}" @@ -449,11 +448,31 @@ Section Uninstall !endif ; Delete the app directory and its content from disk - RMDir /r "$INSTDIR" + ; Copy main executable + Delete "$INSTDIR\${MAINBINARYNAME}.exe" + + ; Delete resources + {{#each resources}} + Delete "$INSTDIR\\{{this.[1]}}" + RMDir "$INSTDIR\\{{this.[0]}}" + {{/each}} - ; Remove start menu and desktop shortcuts + ; Delete external binaries + {{#each binaries}} + Delete "$INSTDIR\\{{this}}" + {{/each}} + + ; Delete uninstaller + Delete "$INSTDIR\uninstall.exe" + + RMDir "$INSTDIR" + + ; Remove start menu shortcut !insertmacro MUI_STARTMENU_GETFOLDER Application $AppStartMenuFolder - RMDir /r "$SMPROGRAMS\$AppStartMenuFolder" + Delete "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk" + RMDir "$SMPROGRAMS\$AppStartMenuFolder" + + ; Remove desktop shortcuts Delete "$DESKTOP\${MAINBINARYNAME}.lnk" SectionEnd