Skip to content
Merged
8 changes: 8 additions & 0 deletions .changes/fix-wix-resource-target-name.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"tauri-bundler": patch:bug
"tauri-cli": patch:bug
"@tauri-apps/cli": patch:bug
---


Fix WiX bundler doesn't respect the resource's target file name.
189 changes: 80 additions & 109 deletions crates/tauri-bundler/src/bundle/windows/msi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,6 @@ const UUID_NAMESPACE: [u8; 16] = [
0xfd, 0x85, 0x95, 0xa8, 0x17, 0xa3, 0x47, 0x4e, 0xa6, 0x16, 0x76, 0x14, 0x8d, 0xfa, 0x0c, 0x7b,
];

/// Mapper between a resource directory name and its ResourceDirectory descriptor.
type ResourceMap = BTreeMap<String, ResourceDirectory>;

#[derive(Debug, Deserialize)]
struct LanguageMetadata {
#[serde(rename = "asciiCode")]
Expand All @@ -123,28 +120,36 @@ struct Binary {

/// A Resource file to bundle with WIX.
/// This data structure is needed because WIX requires each path to have its own `id` and `guid`.
#[derive(Serialize, Clone)]
struct ResourceFile {
/// the GUID to use on the WIX XML.
guid: String,
/// the id to use on the WIX XML.
id: String,
/// the file path.
path: PathBuf,
/// the source file path.
source_path: PathBuf,
/// file name override, defaulting to the file name of [`Self::source_path`].
target_name_override: Option<String>,
}

impl ResourceFile {
fn new(source_path: PathBuf, target_name_override: Option<String>) -> Self {
Self {
id: format!("I{}", Uuid::new_v4().as_simple()),
guid: Uuid::new_v4().to_string(),
source_path,
target_name_override,
}
}
}

/// A resource directory to bundle with WIX.
/// This data structure is needed because WIX requires each path to have its own `id` and `guid`.
#[derive(Serialize)]
#[derive(Default)]
struct ResourceDirectory {
/// the directory path.
path: String,
/// the directory name of the described resource.
name: String,
/// the files of the described resource directory.
files: Vec<ResourceFile>,
/// the directories that are children of the described resource directory.
directories: Vec<ResourceDirectory>,
directories: HashMap<String, ResourceDirectory>,
}

impl ResourceDirectory {
Expand All @@ -154,38 +159,45 @@ impl ResourceDirectory {
}

/// Generates the wix XML string to bundle this directory resources recursively
fn get_wix_data(self) -> crate::Result<(String, Vec<String>)> {
fn render_wix(self, directory_name: Option<String>) -> crate::Result<(String, Vec<String>)> {
let mut files = String::from("");
let mut file_ids = Vec::new();
for file in self.files {
file_ids.push(file.id.clone());
let ResourceFile {
id,
guid,
source_path,
target_name_override,
} = file;
let name_attribute = target_name_override
.map(|name| format!(r#"Name="{}" "#, html_escape(&name)))
.unwrap_or_default();
let source_path = html_escape(&source_path.to_string_lossy());
files.push_str(
format!(
r#"<Component Id="{id}" Guid="{guid}" Win64="$(var.Win64)" KeyPath="yes"><File Id="PathFile_{id}" Source="{path}" /></Component>"#,
id = file.id,
guid = file.guid,
path = html_escape(&file.path.display().to_string())
).as_str()
&format!(
r#"<Component Id="{id}" Guid="{guid}" Win64="$(var.Win64)" KeyPath="yes"><File Id="PathFile_{id}" Source="{source_path}" {name_attribute}/></Component>"#,
)
);
file_ids.push(id);
}
let mut directories = String::from("");
for directory in self.directories {
let (wix_string, ids) = directory.get_wix_data()?;
for (directory_name, directory) in self.directories {
let (wix_string, ids) = directory.render_wix(Some(directory_name))?;
for id in ids {
file_ids.push(id)
}
directories.push_str(wix_string.as_str());
}
let wix_string = if self.name.is_empty() {
format!("{files}{directories}")
} else {
let wix_string = if let Some(directory_name) = directory_name {
format!(
r#"<Directory Id="I{id}" Name="{name}">{files}{directories}</Directory>"#,
id = Uuid::new_v4().as_simple(),
name = html_escape(&self.name),
name = html_escape(&directory_name),
files = files,
directories = directories,
)
} else {
format!("{files}{directories}")
};

Ok((wix_string, file_ids))
Expand Down Expand Up @@ -633,15 +645,7 @@ pub fn build_wix_app_installer(
data.insert("binaries", binaries_json);

let resources = generate_resource_data(settings)?;
let mut resources_wix_string = String::from("");
let mut files_ids = Vec::new();
for (_, dir) in resources {
let (wix_string, ids) = dir.get_wix_data()?;
resources_wix_string.push_str(wix_string.as_str());
for id in ids {
files_ids.push(id);
}
}
let (resources_wix_string, files_ids) = resources.render_wix(None)?;

data.insert("resources", to_json(resources_wix_string));
data.insert("resource_file_ids", to_json(files_ids));
Expand Down Expand Up @@ -987,11 +991,11 @@ fn get_merge_modules(settings: &Settings) -> crate::Result<Vec<MergeModule>> {
}

/// Generates the data required for the resource bundling on wix
fn generate_resource_data(settings: &Settings) -> crate::Result<ResourceMap> {
let mut resources = ResourceMap::new();
fn generate_resource_data(settings: &Settings) -> crate::Result<ResourceDirectory> {
let cwd = std::env::current_dir()?;

let mut added_resources = Vec::new();
let mut root_resource_directory = ResourceDirectory::default();
let mut added_resources = HashSet::new();

for resource in settings.resource_files().iter() {
let resource = resource?;
Expand All @@ -1004,75 +1008,44 @@ fn generate_resource_data(settings: &Settings) -> crate::Result<ResourceMap> {
if added_resources.contains(&resource_path) {
continue;
}
added_resources.push(resource_path.clone());
added_resources.insert(resource_path.clone());

if settings.windows().can_sign() && should_sign(&resource_path)? {
try_sign(&resource_path, settings)?;
}

let resource_entry = ResourceFile {
id: format!("I{}", Uuid::new_v4().as_simple()),
guid: Uuid::new_v4().to_string(),
path: resource_path.clone(),
};
let resource_entry = ResourceFile::new(
resource_path,
Some(
resource
.target()
.file_name()
.expect("failed to read resource file name")
.to_string_lossy()
.into_owned(),
),
);

// split the resource path directories
let target_path = resource.target();
let components_count = target_path.components().count();
let directories = target_path
.components()
.take(components_count - 1) // the last component is the file
.collect::<Vec<_>>();

// transform the directory structure to a chained vec structure
let first_directory = directories
.first()
.map(|d| d.as_os_str().to_string_lossy().into_owned())
.unwrap_or_else(String::new);

if !resources.contains_key(&first_directory) {
resources.insert(
first_directory.clone(),
ResourceDirectory {
path: first_directory.clone(),
name: first_directory.clone(),
directories: vec![],
files: vec![],
},
);
}
let mut directory_entry = &mut root_resource_directory;

let mut directory_entry = resources
.get_mut(&first_directory)
.expect("Unable to handle resources");

let mut path = String::new();
// the first component is already parsed on `first_directory` so we skip(1)
for directory in directories.into_iter().skip(1) {
for directory in directories {
let directory_name = directory
.as_os_str()
.to_os_string()
.into_string()
.expect("failed to read resource folder name");
path.push_str(directory_name.as_str());
path.push(std::path::MAIN_SEPARATOR);

let index = directory_entry
directory_entry = directory_entry
.directories
.iter()
.position(|f| f.path == path);
match index {
Some(i) => directory_entry = directory_entry.directories.get_mut(i).unwrap(),
None => {
directory_entry.directories.push(ResourceDirectory {
path: path.clone(),
name: directory_name,
directories: vec![],
files: vec![],
});
directory_entry = directory_entry.directories.iter_mut().last().unwrap();
}
}
.entry(directory_name)
.or_default();
}
directory_entry.add_file(resource_entry);
}
Expand All @@ -1085,12 +1058,8 @@ fn generate_resource_data(settings: &Settings) -> crate::Result<ResourceMap> {
if added_resources.contains(&resource_path.to_path_buf()) {
continue;
}
added_resources.push(resource_path.to_path_buf());
dlls.push(ResourceFile {
id: format!("I{}", Uuid::new_v4().as_simple()),
guid: Uuid::new_v4().to_string(),
path: resource_path.to_path_buf(),
});
added_resources.insert(resource_path.to_path_buf());
dlls.push(ResourceFile::new(resource_path.to_path_buf(), None));
}
}

Expand All @@ -1113,27 +1082,13 @@ fn generate_resource_data(settings: &Settings) -> crate::Result<ResourceMap> {
try_sign(resource_path, settings)?;
}

dlls.push(ResourceFile {
id: format!("I{}", Uuid::new_v4().as_simple()),
guid: Uuid::new_v4().to_string(),
path: resource_path.to_path_buf(),
});
dlls.push(ResourceFile::new(resource_path.to_path_buf(), None));
}
}

if !dlls.is_empty() {
resources
.entry("".to_string())
.and_modify(|r| r.files.append(&mut dlls))
.or_insert(ResourceDirectory {
path: "".to_string(),
name: "".to_string(),
directories: vec![],
files: dlls,
});
}
root_resource_directory.files.extend(dlls);

Ok(resources)
Ok(root_resource_directory)
}

#[cfg(test)]
Expand Down Expand Up @@ -1176,4 +1131,20 @@ mod tests {
assert_eq!(wix_identifier(""), "_");
assert_eq!(wix_identifier("app_1.2"), "app_1.2");
}

#[test]
fn includes_mapped_resource_file_name_in_wix_data() {
let resource = ResourceFile::new("MyFile".into(), Some("myFileRenamed".into()));
let resource_id = resource.id.clone();
let directory = ResourceDirectory {
files: vec![resource],
directories: HashMap::new(),
};

let (wix_data, file_ids) = directory.render_wix(None).unwrap();

assert_eq!(file_ids, vec![resource_id]);
assert!(wix_data.contains(r#"Name="myFileRenamed""#));
assert!(wix_data.contains(r#"Source="MyFile""#));
}
}
Loading