Browse Source

feat(bundler): add `wix > version` option (#11388)

* feat(bundler): add `wix > version` option

closes #11253

* Update crates/tauri-bundler/src/bundle/settings.rs

---------

Co-authored-by: Lucas Fernandes Nogueira <lucas@tauri.app>
Amr Bashir 9 months ago
parent
commit
c8f55b615d

+ 6 - 0
.changes/bundler-wix-version.md

@@ -0,0 +1,6 @@
+---
+"tauri-bundler": "patch:feat"
+---
+
+Add `bundler > windows > wix > version` to manually specify a wix-compatible version.
+

+ 9 - 0
crates/tauri-bundler/src/bundle/settings.rs

@@ -351,6 +351,15 @@ impl Default for WixLanguage {
 /// Settings specific to the WiX implementation.
 #[derive(Clone, Debug, Default)]
 pub struct WixSettings {
+  /// MSI installer version in the format `major.minor.patch.build` (build is optional).
+  ///
+  /// Because a valid version is required for MSI installer, it will be derived from [`PackageSettings::version`] if this field is not set.
+  ///
+  /// The first field is the major version and has a maximum value of 255. The second field is the minor version and has a maximum value of 255.
+  /// The third and fourth fields have a maximum value of 65,535.
+  ///
+  /// See <https://learn.microsoft.com/en-us/windows/win32/msi/productversion> for more info.
+  pub version: Option<String>,
   /// A GUID upgrade code for MSI installer. This code **_must stay the same across all of your updates_**,
   /// otherwise, Windows will treat your update as a different app and your users will have duplicate versions of your app.
   ///

+ 68 - 7
crates/tauri-bundler/src/bundle/windows/msi/mod.rs

@@ -282,19 +282,37 @@ fn clear_env_for_wix(cmd: &mut Command) {
   }
 }
 
-// WiX requires versions to be numeric only in a `major.minor.patch.build` format
-pub fn convert_version(version_str: &str) -> anyhow::Result<String> {
-  let version = semver::Version::parse(version_str).context("invalid app version")?;
-  if version.major > 255 {
+fn validate_wix_version(version_str: &str) -> anyhow::Result<()> {
+  let components = version_str
+    .split('.')
+    .flat_map(|c| c.parse::<u64>().ok())
+    .collect::<Vec<_>>();
+
+  anyhow::ensure!(
+    components.len() >= 3,
+    "app wix version should be in the format major.minor.patch.build (build is optional)"
+  );
+
+  if components[0] > 255 {
     bail!("app version major number cannot be greater than 255");
   }
-  if version.minor > 255 {
+  if components[1] > 255 {
     bail!("app version minor number cannot be greater than 255");
   }
-  if version.patch > 65535 {
+  if components[2] > 65535 {
     bail!("app version patch number cannot be greater than 65535");
   }
 
+  if components.len() == 4 && components[3] > 65535 {
+    bail!("app version build number cannot be greater than 65535");
+  }
+
+  Ok(())
+}
+
+// WiX requires versions to be numeric only in a `major.minor.patch.build` format
+fn convert_version(version_str: &str) -> anyhow::Result<String> {
+  let version = semver::Version::parse(version_str).context("invalid app version")?;
   if !version.build.is_empty() {
     let build = version.build.parse::<u64>();
     if build.map(|b| b <= 65535).unwrap_or_default() {
@@ -433,7 +451,18 @@ pub fn build_wix_app_installer(
     }
   };
 
-  let app_version = convert_version(settings.version_string())?;
+  let app_version = if let Some(version) = settings
+    .windows()
+    .wix
+    .as_ref()
+    .and_then(|wix| wix.version.clone())
+  {
+    version
+  } else {
+    convert_version(settings.version_string())?
+  };
+
+  validate_wix_version(&app_version)?;
 
   // target only supports x64.
   log::info!("Target: {}", arch);
@@ -1056,3 +1085,35 @@ fn generate_resource_data(settings: &Settings) -> crate::Result<ResourceMap> {
 
   Ok(resources)
 }
+
+#[cfg(test)]
+mod tests {
+  use super::*;
+
+  #[test]
+  fn validates_wix_version() {
+    assert!(validate_wix_version("1.1.1").is_ok());
+    assert!(validate_wix_version("1.1.1.1").is_ok());
+    assert!(validate_wix_version("255.1.1.1").is_ok());
+    assert!(validate_wix_version("1.255.1.1").is_ok());
+    assert!(validate_wix_version("1.1.65535.1").is_ok());
+    assert!(validate_wix_version("1.1.1.65535").is_ok());
+
+    assert!(validate_wix_version("256.1.1.1").is_err());
+    assert!(validate_wix_version("1.256.1.1").is_err());
+    assert!(validate_wix_version("1.1.65536.1").is_err());
+    assert!(validate_wix_version("1.1.1.65536").is_err());
+  }
+
+  #[test]
+  fn converts_version_to_wix() {
+    assert_eq!(convert_version("1.1.2").unwrap(), "1.1.2");
+    assert_eq!(convert_version("1.1.2-4").unwrap(), "1.1.2.4");
+    assert_eq!(convert_version("1.1.2-65535").unwrap(), "1.1.2.65535");
+    assert_eq!(convert_version("1.1.2+2").unwrap(), "1.1.2.2");
+
+    assert!(convert_version("1.1.2-alpha").is_err());
+    assert!(convert_version("1.1.2-alpha.4").is_err());
+    assert!(convert_version("1.1.2+asd.3").is_err());
+  }
+}

+ 7 - 0
crates/tauri-cli/config.schema.json

@@ -2170,6 +2170,13 @@
       "description": "Configuration for the MSI bundle using WiX.\n\n See more: <https://v2.tauri.app/reference/config/#wixconfig>",
       "type": "object",
       "properties": {
+        "version": {
+          "description": "MSI installer version in the format `major.minor.patch.build` (build is optional).\n\n Because a valid version is required for MSI installer, it will be derived from [`Config::version`] if this field is not set.\n\n The first field is the major version and has a maximum value of 255. The second field is the minor version and has a maximum value of 255.\n The third and foruth fields have a maximum value of 65,535.\n\n See <https://learn.microsoft.com/en-us/windows/win32/msi/productversion> for more info.",
+          "type": [
+            "string",
+            "null"
+          ]
+        },
         "upgradeCode": {
           "description": "A GUID upgrade code for MSI installer. This code **_must stay the same across all of your updates_**,\n otherwise, Windows will treat your update as a different app and your users will have duplicate versions of your app.\n\n By default, tauri generates this code by generating a Uuid v5 using the string `<productName>.exe.app.x64` in the DNS namespace.\n You can use Tauri's CLI to generate and print this code for you, run `tauri inspect wix-upgrade-code`.\n\n It is recommended that you set this value in your tauri config file to avoid accidental changes in your upgrade code\n whenever you want to change your product name.",
           "type": [

+ 1 - 0
crates/tauri-cli/src/helpers/config.rs

@@ -62,6 +62,7 @@ pub type ConfigHandle = Arc<Mutex<Option<ConfigMetadata>>>;
 
 pub fn wix_settings(config: WixConfig) -> tauri_bundler::WixSettings {
   tauri_bundler::WixSettings {
+    version: config.version,
     upgrade_code: config.upgrade_code,
     language: tauri_bundler::WixLanguage(match config.language {
       WixLanguage::One(lang) => vec![(lang, Default::default())],

+ 7 - 0
crates/tauri-schema-generator/schemas/config.schema.json

@@ -2170,6 +2170,13 @@
       "description": "Configuration for the MSI bundle using WiX.\n\n See more: <https://v2.tauri.app/reference/config/#wixconfig>",
       "type": "object",
       "properties": {
+        "version": {
+          "description": "MSI installer version in the format `major.minor.patch.build` (build is optional).\n\n Because a valid version is required for MSI installer, it will be derived from [`Config::version`] if this field is not set.\n\n The first field is the major version and has a maximum value of 255. The second field is the minor version and has a maximum value of 255.\n The third and foruth fields have a maximum value of 65,535.\n\n See <https://learn.microsoft.com/en-us/windows/win32/msi/productversion> for more info.",
+          "type": [
+            "string",
+            "null"
+          ]
+        },
         "upgradeCode": {
           "description": "A GUID upgrade code for MSI installer. This code **_must stay the same across all of your updates_**,\n otherwise, Windows will treat your update as a different app and your users will have duplicate versions of your app.\n\n By default, tauri generates this code by generating a Uuid v5 using the string `<productName>.exe.app.x64` in the DNS namespace.\n You can use Tauri's CLI to generate and print this code for you, run `tauri inspect wix-upgrade-code`.\n\n It is recommended that you set this value in your tauri config file to avoid accidental changes in your upgrade code\n whenever you want to change your product name.",
           "type": [

+ 9 - 0
crates/tauri-utils/src/config.rs

@@ -658,6 +658,15 @@ impl Default for WixLanguage {
 #[cfg_attr(feature = "schema", derive(JsonSchema))]
 #[serde(rename_all = "camelCase", deny_unknown_fields)]
 pub struct WixConfig {
+  /// MSI installer version in the format `major.minor.patch.build` (build is optional).
+  ///
+  /// Because a valid version is required for MSI installer, it will be derived from [`Config::version`] if this field is not set.
+  ///
+  /// The first field is the major version and has a maximum value of 255. The second field is the minor version and has a maximum value of 255.
+  /// The third and foruth fields have a maximum value of 65,535.
+  ///
+  /// See <https://learn.microsoft.com/en-us/windows/win32/msi/productversion> for more info.
+  pub version: Option<String>,
   /// A GUID upgrade code for MSI installer. This code **_must stay the same across all of your updates_**,
   /// otherwise, Windows will treat your update as a different app and your users will have duplicate versions of your app.
   ///