Эх сурвалжийг харах

feat(bundler): implement wix fragments, closes #1528 (#1601)

Lucas Fernandes Nogueira 4 жил өмнө
parent
commit
69ea51ec93

+ 5 - 0
.changes/wix-fragments.md

@@ -0,0 +1,5 @@
+---
+"tauri-bundler": patch
+---
+
+Adds support to `wix` fragments for custom .msi installer functionality.

+ 1 - 1
tooling/bundler/src/bundle.rs

@@ -28,7 +28,7 @@ pub use self::{
   },
 };
 #[cfg(windows)]
-pub use settings::WindowsSettings;
+pub use settings::{WindowsSettings, WixSettings};
 
 use common::print_finished;
 

+ 19 - 0
tooling/bundler/src/bundle/settings.rs

@@ -165,6 +165,24 @@ pub struct MacOsSettings {
   pub entitlements: Option<String>,
 }
 
+#[cfg(windows)]
+#[derive(Clone, Debug, Deserialize, Default)]
+#[serde(rename_all = "camelCase")]
+pub struct WixSettings {
+  #[serde(default)]
+  pub fragment_paths: Vec<PathBuf>,
+  #[serde(default)]
+  pub component_group_refs: Vec<String>,
+  #[serde(default)]
+  pub component_refs: Vec<String>,
+  #[serde(default)]
+  pub feature_group_refs: Vec<String>,
+  #[serde(default)]
+  pub feature_refs: Vec<String>,
+  #[serde(default)]
+  pub merge_refs: Vec<String>,
+}
+
 /// The Windows bundle settings.
 #[cfg(windows)]
 #[derive(Clone, Debug, Deserialize, Default)]
@@ -172,6 +190,7 @@ pub struct WindowsSettings {
   pub digest_algorithm: Option<String>,
   pub certificate_thumbprint: Option<String>,
   pub timestamp_url: Option<String>,
+  pub wix: Option<WixSettings>,
 }
 
 /// The bundle settings of the BuildArtifact we're bundling.

+ 18 - 0
tooling/bundler/src/bundle/templates/main.wxs

@@ -157,6 +157,24 @@
             </Feature>
         </Feature>
 
+        <Feature Id="External" AllowAdvertise="no" Absent="disallow">
+            {{#each component_group_refs as |id| ~}}
+            <ComponentGroupRef Id="{{ id }}"/>
+            {{/each~}}
+            {{#each component_refs as |id| ~}}
+            <ComponentRef Id="{{ id }}"/>
+            {{/each~}}
+            {{#each feature_group_refs as |id| ~}}
+            <FeatureGroupRef Id="{{ id }}"/>
+            {{/each~}}
+            {{#each feature_refs as |id| ~}}
+            <FeatureRef Id="{{ id }}"/>
+            {{/each~}}
+            {{#each merge_refs as |id| ~}}
+            <MergeRef Id="{{ id }}"/>
+            {{/each~}}
+        </Feature>
+
         <!-- WebView2 -->
         <Property Id="WVRTINSTALLED">
             <RegistrySearch Id="WVRTInstalled" Root="HKLM" Key="SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" Name="pv" Type="raw" Win64="no"/>

+ 3 - 5
tooling/bundler/src/bundle/updater_bundle.rs

@@ -3,8 +3,6 @@
 // SPDX-License-Identifier: MIT
 
 use super::common;
-use libflate::gzip;
-use walkdir::WalkDir;
 
 #[cfg(target_os = "macos")]
 use super::macos_bundle;
@@ -159,7 +157,7 @@ fn bundle_update(settings: &Settings, bundles: &[Bundle]) -> crate::Result<Vec<P
 }
 
 #[cfg(target_os = "windows")]
-pub fn create_zip(src_file: &PathBuf, dst_file: &PathBuf) -> crate::Result<PathBuf> {
+pub fn create_zip(src_file: &Path, dst_file: &Path) -> crate::Result<PathBuf> {
   let parent_dir = dst_file.parent().expect("No data in parent");
   fs::create_dir_all(parent_dir)?;
   let writer = common::create_file(dst_file)?;
@@ -186,7 +184,7 @@ pub fn create_zip(src_file: &PathBuf, dst_file: &PathBuf) -> crate::Result<PathB
 #[cfg(not(target_os = "windows"))]
 fn create_tar(src_dir: &Path, dest_path: &Path) -> crate::Result<PathBuf> {
   let dest_file = common::create_file(&dest_path)?;
-  let gzip_encoder = gzip::Encoder::new(dest_file)?;
+  let gzip_encoder = libflate::gzip::Encoder::new(dest_file)?;
 
   let gzip_encoder = create_tar_from_src(src_dir, gzip_encoder)?;
   let mut dest_file = gzip_encoder.finish().into_result()?;
@@ -210,7 +208,7 @@ fn create_tar_from_src<P: AsRef<Path>, W: Write>(src_dir: P, dest_file: W) -> cr
 
     tar_builder.append_file(file_name, &mut src_file)?;
   } else {
-    for entry in WalkDir::new(&src_dir) {
+    for entry in walkdir::WalkDir::new(&src_dir) {
       let entry = entry?;
       let src_path = entry.path();
       if src_path == src_dir {

+ 29 - 69
tooling/bundler/src/bundle/wix.rs

@@ -62,7 +62,7 @@ lazy_static! {
 
     handlebars
       .register_template_string("main.wxs", include_str!("templates/main.wxs"))
-      .or_else(|e| Err(e.to_string()))
+      .map_err(|e| e.to_string())
       .expect("Failed to setup handlebar template");
     handlebars
   };
@@ -143,7 +143,7 @@ impl ResourceDirectory {
       }
       directories.push_str(wix_string.as_str());
     }
-    let wix_string = if self.name == "" {
+    let wix_string = if self.name.is_empty() {
       format!("{}{}", files, directories)
     } else {
       format!(
@@ -278,64 +278,12 @@ pub fn get_and_extract_wix(path: &Path) -> crate::Result<()> {
   extract_zip(&data, path)
 }
 
-// For if bundler needs DLL files.
-
-// fn run_heat_exe(
-//   wix_toolset_path: &Path,
-//   build_path: &Path,
-//   harvest_dir: &Path,
-//   platform: &str,
-// ) -> Result<(), String> {
-//   let mut args = vec!["dir"];
-
-//   let harvest_str = harvest_dir.display().to_string();
-
-//   args.push(&harvest_str);
-//   args.push("-platform");
-//   args.push(platform);
-//   args.push("-cg");
-//   args.push("AppFiles");
-//   args.push("-dr");
-//   args.push("APPLICATIONFOLDER");
-//   args.push("-gg");
-//   args.push("-srd");
-//   args.push("-out");
-//   args.push("appdir.wxs");
-//   args.push("-var");
-//   args.push("var.SourceDir");
-
-//   let heat_exe = wix_toolset_path.join("heat.exe");
-
-//   let mut cmd = Command::new(&heat_exe)
-//     .args(&args)
-//     .stdout(Stdio::piped())
-//     .current_dir(build_path)
-//     .spawn()
-//     .expect("error running heat.exe");
-
-//   {
-//     let stdout = cmd.stdout.as_mut().unwrap();
-//     let reader = BufReader::new(stdout);
-
-//     for line in reader.lines() {
-//       info!(logger, "{}", line.unwrap());
-//     }
-//   }
-
-//   let status = cmd.wait().unwrap();
-//   if status.success() {
-//     Ok(())
-//   } else {
-//     Err("error running heat.exe".to_string())
-//   }
-// }
-
 /// Runs the Candle.exe executable for Wix. Candle parses the wxs file and generates the code for building the installer.
 fn run_candle(
   settings: &Settings,
   wix_toolset_path: &Path,
-  build_path: &Path,
-  wxs_file_name: &str,
+  cwd: &Path,
+  wxs_file_path: &Path,
 ) -> crate::Result<()> {
   let arch = match settings.binary_arch() {
     "x86_64" => "x64",
@@ -357,7 +305,7 @@ fn run_candle(
   let args = vec![
     "-arch".to_string(),
     arch.to_string(),
-    wxs_file_name.to_string(),
+    wxs_file_path.to_string_lossy().to_string(),
     format!(
       "-dSourceDir={}",
       settings.binary_path(main_binary).display()
@@ -365,13 +313,10 @@ fn run_candle(
   ];
 
   let candle_exe = wix_toolset_path.join("candle.exe");
-  common::print_info(format!("running candle for {}", wxs_file_name).as_str())?;
+  common::print_info(format!("running candle for {:?}", wxs_file_path).as_str())?;
 
   let mut cmd = Command::new(&candle_exe);
-  cmd
-    .args(&args)
-    .stdout(Stdio::piped())
-    .current_dir(build_path);
+  cmd.args(&args).stdout(Stdio::piped()).current_dir(cwd);
 
   common::print_info("running candle.exe")?;
   common::execute_with_verbosity(&mut cmd, &settings).map_err(|_| {
@@ -532,6 +477,17 @@ pub fn build_wix_app_installer(
 
   data.insert("icon_path", to_json(icon_path));
 
+  let mut fragment_paths = Vec::new();
+
+  if let Some(wix) = &settings.windows().wix {
+    data.insert("component_group_refs", to_json(&wix.component_group_refs));
+    data.insert("component_refs", to_json(&wix.component_refs));
+    data.insert("feature_group_refs", to_json(&wix.feature_group_refs));
+    data.insert("feature_refs", to_json(&wix.feature_refs));
+    data.insert("merge_refs", to_json(&wix.merge_refs));
+    fragment_paths = wix.fragment_paths.clone();
+  }
+
   let temp = HANDLEBARS.render("main.wxs", &data)?;
 
   if output_path.exists() {
@@ -543,14 +499,18 @@ pub fn build_wix_app_installer(
   let main_wxs_path = output_path.join("main.wxs");
   write(&main_wxs_path, temp)?;
 
-  let input_basenames = vec!["main"];
+  let mut candle_inputs = vec!["main.wxs".into()];
+
+  let current_dir = std::env::current_dir()?;
+  for fragment_path in fragment_paths {
+    candle_inputs.push(current_dir.join(fragment_path));
+  }
 
-  for basename in &input_basenames {
-    let wxs = format!("{}.wxs", basename);
+  for wxs in &candle_inputs {
     run_candle(settings, &wix_toolset_path, &output_path, &wxs)?;
   }
 
-  let wixobjs = vec!["main.wixobj"];
+  let wixobjs = vec!["*.wixobj"];
   let target = run_light(
     &wix_toolset_path,
     &output_path,
@@ -600,12 +560,12 @@ fn locate_signtool() -> crate::Result<PathBuf> {
   let mut kit_bin_paths: Vec<PathBuf> = installed_kits
     .iter()
     .rev()
-    .map(|kit| kits_root_10_bin_path.join(kit).to_path_buf())
+    .map(|kit| kits_root_10_bin_path.join(kit))
     .collect();
 
   /* Add kits root bin path.
   For Windows SDK 10 versions earlier than v10.0.15063.468, signtool will be located there. */
-  kit_bin_paths.push(kits_root_10_bin_path.to_path_buf());
+  kit_bin_paths.push(kits_root_10_bin_path);
 
   // Choose which version of SignTool to use based on OS bitness
   let arch_dir = match bitness::os_bitness().expect("failed to get os bitness") {
@@ -622,7 +582,7 @@ fn locate_signtool() -> crate::Result<PathBuf> {
     /* Check if SignTool exists at this location. */
     if signtool_path.exists() {
       // SignTool found. Return it.
-      return Ok(signtool_path.to_path_buf());
+      return Ok(signtool_path);
     }
   }
 

+ 19 - 1
tooling/cli.rs/config_definition.rs

@@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize};
 use serde_json::Value as JsonValue;
 use serde_with::skip_serializing_none;
 
-use std::collections::HashMap;
+use std::{collections::HashMap, path::PathBuf};
 
 #[derive(Debug, PartialEq, Clone, Deserialize, Serialize, JsonSchema)]
 #[serde(untagged)]
@@ -41,12 +41,30 @@ pub struct MacConfig {
   pub entitlements: Option<String>,
 }
 
+#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, JsonSchema)]
+#[serde(rename_all = "camelCase", deny_unknown_fields)]
+pub struct WixConfig {
+  #[serde(default)]
+  pub fragment_paths: Vec<PathBuf>,
+  #[serde(default)]
+  pub component_group_refs: Vec<String>,
+  #[serde(default)]
+  pub component_refs: Vec<String>,
+  #[serde(default)]
+  pub feature_group_refs: Vec<String>,
+  #[serde(default)]
+  pub feature_refs: Vec<String>,
+  #[serde(default)]
+  pub merge_refs: Vec<String>,
+}
+
 #[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, JsonSchema)]
 #[serde(rename_all = "camelCase", deny_unknown_fields)]
 pub struct WindowsConfig {
   pub digest_algorithm: Option<String>,
   pub certificate_thumbprint: Option<String>,
   pub timestamp_url: Option<String>,
+  pub wix: Option<WixConfig>,
 }
 
 #[skip_serializing_none]

+ 64 - 3
tooling/cli.rs/schema.json

@@ -90,7 +90,8 @@
           "windows": {
             "certificateThumbprint": null,
             "digestAlgorithm": null,
-            "timestampUrl": null
+            "timestampUrl": null,
+            "wix": null
           }
         },
         "updater": {
@@ -343,7 +344,8 @@
           "default": {
             "certificateThumbprint": null,
             "digestAlgorithm": null,
-            "timestampUrl": null
+            "timestampUrl": null,
+            "wix": null
           },
           "allOf": [
             {
@@ -909,7 +911,8 @@
             "windows": {
               "certificateThumbprint": null,
               "digestAlgorithm": null,
-              "timestampUrl": null
+              "timestampUrl": null,
+              "wix": null
             }
           },
           "allOf": [
@@ -1159,6 +1162,64 @@
             "string",
             "null"
           ]
+        },
+        "wix": {
+          "anyOf": [
+            {
+              "$ref": "#/definitions/WixConfig"
+            },
+            {
+              "type": "null"
+            }
+          ]
+        }
+      },
+      "additionalProperties": false
+    },
+    "WixConfig": {
+      "type": "object",
+      "properties": {
+        "componentGroupRefs": {
+          "default": [],
+          "type": "array",
+          "items": {
+            "type": "string"
+          }
+        },
+        "componentRefs": {
+          "default": [],
+          "type": "array",
+          "items": {
+            "type": "string"
+          }
+        },
+        "featureGroupRefs": {
+          "default": [],
+          "type": "array",
+          "items": {
+            "type": "string"
+          }
+        },
+        "featureRefs": {
+          "default": [],
+          "type": "array",
+          "items": {
+            "type": "string"
+          }
+        },
+        "fragmentPaths": {
+          "default": [],
+          "type": "array",
+          "items": {
+            "type": "string"
+          }
+        },
+        "mergeRefs": {
+          "default": [],
+          "type": "array",
+          "items": {
+            "type": "string"
+          }
         }
       },
       "additionalProperties": false

+ 1 - 0
tooling/cli.rs/src/build/rust.rs

@@ -352,6 +352,7 @@ fn tauri_config_to_bundle_settings(
       timestamp_url: config.windows.timestamp_url,
       digest_algorithm: config.windows.digest_algorithm,
       certificate_thumbprint: config.windows.certificate_thumbprint,
+      wix: config.windows.wix.map(|w| w.into()),
     },
     updater: Some(UpdaterSettings {
       active: updater_config.active,

+ 14 - 0
tooling/cli.rs/src/helpers/config.rs

@@ -12,6 +12,20 @@ use serde_json::Value as JsonValue;
 mod config_definition;
 pub use config_definition::*;
 
+#[cfg(windows)]
+impl From<WixConfig> for tauri_bundler::WixSettings {
+  fn from(config: WixConfig) -> tauri_bundler::WixSettings {
+    tauri_bundler::WixSettings {
+      fragment_paths: config.fragment_paths,
+      component_group_refs: config.component_group_refs,
+      component_refs: config.component_refs,
+      feature_group_refs: config.feature_group_refs,
+      feature_refs: config.feature_refs,
+      merge_refs: config.merge_refs,
+    }
+  }
+}
+
 use std::{
   env::set_var,
   fs::File,