diff --git a/.changes/wix-fragments.md b/.changes/wix-fragments.md new file mode 100644 index 000000000..6f20c1398 --- /dev/null +++ b/.changes/wix-fragments.md @@ -0,0 +1,5 @@ +--- +"tauri-bundler": patch +--- + +Adds support to `wix` fragments for custom .msi installer functionality. diff --git a/tooling/bundler/src/bundle.rs b/tooling/bundler/src/bundle.rs index d7a67057b..0a10aa7d0 100644 --- a/tooling/bundler/src/bundle.rs +++ b/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; diff --git a/tooling/bundler/src/bundle/settings.rs b/tooling/bundler/src/bundle/settings.rs index c17a94280..7589e1ded 100644 --- a/tooling/bundler/src/bundle/settings.rs +++ b/tooling/bundler/src/bundle/settings.rs @@ -165,6 +165,24 @@ pub struct MacOsSettings { pub entitlements: Option, } +#[cfg(windows)] +#[derive(Clone, Debug, Deserialize, Default)] +#[serde(rename_all = "camelCase")] +pub struct WixSettings { + #[serde(default)] + pub fragment_paths: Vec, + #[serde(default)] + pub component_group_refs: Vec, + #[serde(default)] + pub component_refs: Vec, + #[serde(default)] + pub feature_group_refs: Vec, + #[serde(default)] + pub feature_refs: Vec, + #[serde(default)] + pub merge_refs: Vec, +} + /// The Windows bundle settings. #[cfg(windows)] #[derive(Clone, Debug, Deserialize, Default)] @@ -172,6 +190,7 @@ pub struct WindowsSettings { pub digest_algorithm: Option, pub certificate_thumbprint: Option, pub timestamp_url: Option, + pub wix: Option, } /// The bundle settings of the BuildArtifact we're bundling. diff --git a/tooling/bundler/src/bundle/templates/main.wxs b/tooling/bundler/src/bundle/templates/main.wxs index c804e9142..cbf0dc397 100644 --- a/tooling/bundler/src/bundle/templates/main.wxs +++ b/tooling/bundler/src/bundle/templates/main.wxs @@ -157,6 +157,24 @@ + + {{#each component_group_refs as |id| ~}} + + {{/each~}} + {{#each component_refs as |id| ~}} + + {{/each~}} + {{#each feature_group_refs as |id| ~}} + + {{/each~}} + {{#each feature_refs as |id| ~}} + + {{/each~}} + {{#each merge_refs as |id| ~}} + + {{/each~}} + + diff --git a/tooling/bundler/src/bundle/updater_bundle.rs b/tooling/bundler/src/bundle/updater_bundle.rs index 1122209ff..8bffbbbfa 100644 --- a/tooling/bundler/src/bundle/updater_bundle.rs +++ b/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 crate::Result { +pub fn create_zip(src_file: &Path, dst_file: &Path) -> crate::Result { 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 crate::Result { 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, 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 { diff --git a/tooling/bundler/src/bundle/wix.rs b/tooling/bundler/src/bundle/wix.rs index b3a67cd29..494449acd 100644 --- a/tooling/bundler/src/bundle/wix.rs +++ b/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()]; - for basename in &input_basenames { - let wxs = format!("{}.wxs", basename); + let current_dir = std::env::current_dir()?; + for fragment_path in fragment_paths { + candle_inputs.push(current_dir.join(fragment_path)); + } + + 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 { let mut kit_bin_paths: Vec = 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 { /* 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); } } diff --git a/tooling/cli.rs/config_definition.rs b/tooling/cli.rs/config_definition.rs index bf5ec3cbe..f0b834a6b 100644 --- a/tooling/cli.rs/config_definition.rs +++ b/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, } +#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, JsonSchema)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct WixConfig { + #[serde(default)] + pub fragment_paths: Vec, + #[serde(default)] + pub component_group_refs: Vec, + #[serde(default)] + pub component_refs: Vec, + #[serde(default)] + pub feature_group_refs: Vec, + #[serde(default)] + pub feature_refs: Vec, + #[serde(default)] + pub merge_refs: Vec, +} + #[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct WindowsConfig { pub digest_algorithm: Option, pub certificate_thumbprint: Option, pub timestamp_url: Option, + pub wix: Option, } #[skip_serializing_none] diff --git a/tooling/cli.rs/schema.json b/tooling/cli.rs/schema.json index a301f5b88..92d241595 100644 --- a/tooling/cli.rs/schema.json +++ b/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 diff --git a/tooling/cli.rs/src/build/rust.rs b/tooling/cli.rs/src/build/rust.rs index 6ef9283f2..3cf572a1d 100644 --- a/tooling/cli.rs/src/build/rust.rs +++ b/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, diff --git a/tooling/cli.rs/src/helpers/config.rs b/tooling/cli.rs/src/helpers/config.rs index f5cf8ab36..e8c55ac21 100644 --- a/tooling/cli.rs/src/helpers/config.rs +++ b/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 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,