From b069e0ee10cd714fd362f0df2fbd687a40bcf1d6 Mon Sep 17 00:00:00 2001 From: JKarasti Date: Thu, 12 Sep 2024 18:16:23 +0300 Subject: [PATCH 01/22] Fix: SyntaxWarning while generating Dangerzone.wxs --- install/windows/build-wxs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/windows/build-wxs.py b/install/windows/build-wxs.py index 14aa92d..ab5397d 100644 --- a/install/windows/build-wxs.py +++ b/install/windows/build-wxs.py @@ -82,7 +82,7 @@ def build_dir_xml(root, data): component_el, "RegistryValue", Root="HKCU", - Key="Software\Freedom of the Press Foundation\Dangerzone", + Key="Software\\Freedom of the Press Foundation\\Dangerzone", Name="installed", Type="integer", Value="1", From ba20f5a4d0d617e459c3596eb1a9d8d1de64c0b2 Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 20:07:52 +0300 Subject: [PATCH 02/22] Fix: Make generated WiX authoring pass WixCop checks WixCop.exe is a built in formatting tool that comes with WiX toolset v3. This fixes `wix convert` command not beins able to run --- install/windows/build-wxs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install/windows/build-wxs.py b/install/windows/build-wxs.py index ab5397d..434badc 100644 --- a/install/windows/build-wxs.py +++ b/install/windows/build-wxs.py @@ -234,10 +234,10 @@ def main(): ET.SubElement(feature_el, "ComponentRef", Id=component_id) ET.SubElement(feature_el, "ComponentRef", Id="ApplicationShortcuts") - print('') + print('') print(f'') print('') - ET.indent(root_el) + ET.indent(root_el, space=" ") print(ET.tostring(root_el).decode()) From 6049a627cb27f0be3326fc0e61906707f83e4109 Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 20:16:35 +0300 Subject: [PATCH 03/22] Change: Stop generating an XML declaration at the top of the WiX authoring It's not needed anymore. --- install/windows/build-wxs.py | 1 - 1 file changed, 1 deletion(-) diff --git a/install/windows/build-wxs.py b/install/windows/build-wxs.py index 434badc..5bd4465 100644 --- a/install/windows/build-wxs.py +++ b/install/windows/build-wxs.py @@ -234,7 +234,6 @@ def main(): ET.SubElement(feature_el, "ComponentRef", Id=component_id) ET.SubElement(feature_el, "ComponentRef", Id="ApplicationShortcuts") - print('') print(f'') print('') ET.indent(root_el, space=" ") From 435ebf007228383b14135e702babfdd0bca739a8 Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 20:18:58 +0300 Subject: [PATCH 04/22] Change: Update WiX schema namespace Also rename `root_el` to `wix_el`. WiX version 5 uses the same namespace. --- install/windows/build-wxs.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/install/windows/build-wxs.py b/install/windows/build-wxs.py index 5bd4465..c96c6c5 100644 --- a/install/windows/build-wxs.py +++ b/install/windows/build-wxs.py @@ -158,9 +158,10 @@ def main(): ) ) - root_el = ET.Element("Wix", xmlns="http://schemas.microsoft.com/wix/2006/wi") + # Add the Wix root element + wix_el = ET.Element("Wix", xmlns="http://wixtoolset.org/schemas/v4/wxs") product_el = ET.SubElement( - root_el, + wix_el, "Product", Name="Dangerzone", Manufacturer="Freedom of the Press Foundation", @@ -236,8 +237,8 @@ def main(): print(f'') print('') - ET.indent(root_el, space=" ") - print(ET.tostring(root_el).decode()) + ET.indent(wix_el, space=" ") + print(ET.tostring(wix_el).decode()) if __name__ == "__main__": From c62efc7cdd91056de900e0894d2e266f24309fc3 Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 20:21:48 +0300 Subject: [PATCH 05/22] Change: Rename `INSTALLDIR` to `INSTALLFOLDER` It's the new default name for it --- install/windows/build-wxs.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/install/windows/build-wxs.py b/install/windows/build-wxs.py index c96c6c5..49cc83a 100644 --- a/install/windows/build-wxs.py +++ b/install/windows/build-wxs.py @@ -17,7 +17,7 @@ def build_data(dirname, dir_prefix, id_, name): if os.path.isfile(filename): data["files"].append(os.path.join(dir_prefix, basename)) elif os.path.isdir(filename): - if id_ == "INSTALLDIR": + if id_ == "INSTALLFOLDER": id_prefix = "Folder" else: id_prefix = id_ @@ -42,7 +42,7 @@ def build_data(dirname, dir_prefix, id_, name): ) if len(data["files"]) > 0: - if id_ == "INSTALLDIR": + if id_ == "INSTALLFOLDER": data["component_id"] = "ApplicationFiles" else: data["component_id"] = "FolderComponent" + id_[len("Folder") :] @@ -75,8 +75,8 @@ def build_dir_xml(root, data): Id="ApplicationShortcut1", Name="Dangerzone", Description="Dangerzone", - Target="[INSTALLDIR]dangerzone.exe", - WorkingDirectory="INSTALLDIR", + Target="[INSTALLFOLDER]dangerzone.exe", + WorkingDirectory="INSTALLFOLDER", ) ET.SubElement( component_el, @@ -153,7 +153,7 @@ def main(): build_data( dist_dir, "exe.win-amd64-3.12", - "INSTALLDIR", + "INSTALLFOLDER", "Dangerzone", ) ) @@ -204,7 +204,7 @@ def main(): product_el, "Property", Id="WIXUI_INSTALLDIR", - Value="INSTALLDIR", + Value="INSTALLFOLDER", ) ET.SubElement(product_el, "UIRef", Id="WixUI_InstallDir") ET.SubElement(product_el, "UIRef", Id="WixUI_ErrorProgressText") From 9104af81c3b2be085619a52edf00fe759bb1405c Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 20:31:58 +0300 Subject: [PATCH 06/22] Change: Merge Product into Package element - The Keywords and Description items move under a new SummaryInformation element. - Shuffle things around so that elements previously under the product element are now under the Package element. - Rename SummaryCodepage in SummaryInformation to Codepage and remove a duplicate Manufacturer item. - Remove InstallerVersion and let WiX set it to default value. (500 a.k.a Windows 7) --- install/windows/build-wxs.py | 48 +++++++++++++++++------------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/install/windows/build-wxs.py b/install/windows/build-wxs.py index 49cc83a..b494009 100644 --- a/install/windows/build-wxs.py +++ b/install/windows/build-wxs.py @@ -160,77 +160,75 @@ def main(): # Add the Wix root element wix_el = ET.Element("Wix", xmlns="http://wixtoolset.org/schemas/v4/wxs") - product_el = ET.SubElement( + + # Add the Package element + package_el = ET.SubElement( wix_el, - "Product", + "Package", Name="Dangerzone", Manufacturer="Freedom of the Press Foundation", - Id="*", UpgradeCode="$(var.ProductUpgradeCode)", Language="1033", + Compressed="yes", Codepage="1252", Version="$(var.ProductVersion)", ) ET.SubElement( - product_el, - "Package", - Id="*", + package_el, + "SummaryInformation", Keywords="Installer", Description="Dangerzone $(var.ProductVersion) Installer", - Manufacturer="Freedom of the Press Foundation", - InstallerVersion="100", - Languages="1033", - Compressed="yes", - SummaryCodepage="1252", + Codepage="1252", ) - ET.SubElement(product_el, "Media", Id="1", Cabinet="product.cab", EmbedCab="yes") + ET.SubElement(package_el, "Media", Id="1", Cabinet="product.cab", EmbedCab="yes") ET.SubElement( - product_el, "Icon", Id="ProductIcon", SourceFile="..\\share\\dangerzone.ico" + package_el, "Icon", Id="ProductIcon", SourceFile="..\\share\\dangerzone.ico" ) - ET.SubElement(product_el, "Property", Id="ARPPRODUCTICON", Value="ProductIcon") + ET.SubElement(package_el, "Property", Id="ARPPRODUCTICON", Value="ProductIcon") ET.SubElement( - product_el, + package_el, "Property", Id="ARPHELPLINK", Value="https://dangerzone.rocks", ) ET.SubElement( - product_el, + package_el, "Property", Id="ARPURLINFOABOUT", Value="https://freedom.press", ) ET.SubElement( - product_el, + package_el, "Property", Id="WIXUI_INSTALLDIR", Value="INSTALLFOLDER", ) - ET.SubElement(product_el, "UIRef", Id="WixUI_InstallDir") - ET.SubElement(product_el, "UIRef", Id="WixUI_ErrorProgressText") + ET.SubElement(package_el, "UIRef", Id="WixUI_InstallDir") + ET.SubElement(package_el, "UIRef", Id="WixUI_ErrorProgressText") ET.SubElement( - product_el, + package_el, "WixVariable", Id="WixUILicenseRtf", Value="..\\install\\windows\\license.rtf", ) ET.SubElement( - product_el, + package_el, "WixVariable", Id="WixUIDialogBmp", Value="..\\install\\windows\\dialog.bmp", ) ET.SubElement( - product_el, + package_el, "MajorUpgrade", AllowSameVersionUpgrades="yes", DowngradeErrorMessage="A newer version of [ProductName] is already installed. If you are sure you want to downgrade, remove the existing installation via Programs and Features.", ) - build_dir_xml(product_el, data) - component_ids = build_components_xml(product_el, data) + build_dir_xml(package_el, data) + component_ids = build_components_xml(package_el, data) - feature_el = ET.SubElement(product_el, "Feature", Id="DefaultFeature", Level="1") + # Add the Feature element + feature_el = ET.SubElement(package_el, "Feature", Id="DefaultFeature", Level="1") for component_id in component_ids: ET.SubElement(feature_el, "ComponentRef", Id=component_id) ET.SubElement(feature_el, "ComponentRef", Id="ApplicationShortcuts") From 17f802915b8ade2aa796c609f9ce391ffd70af32 Mon Sep 17 00:00:00 2001 From: jkarasti Date: Mon, 11 Nov 2024 19:06:10 +0200 Subject: [PATCH 07/22] Change: Disable `AllowSameVersionUpgrades` Since running `wix msi validate` with it set to `yes` causes an error. --- install/windows/build-wxs.py | 1 - 1 file changed, 1 deletion(-) diff --git a/install/windows/build-wxs.py b/install/windows/build-wxs.py index b494009..7a0d8f1 100644 --- a/install/windows/build-wxs.py +++ b/install/windows/build-wxs.py @@ -220,7 +220,6 @@ def main(): ET.SubElement( package_el, "MajorUpgrade", - AllowSameVersionUpgrades="yes", DowngradeErrorMessage="A newer version of [ProductName] is already installed. If you are sure you want to downgrade, remove the existing installation via Programs and Features.", ) From 3c1e82d4f1476dc7c77c427a4e9fc6b5d925339c Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 20:43:31 +0300 Subject: [PATCH 08/22] Change: Wrap `ProgramMenuFolder` component with a `StandardDirectory` component --- install/windows/build-wxs.py | 65 ++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/install/windows/build-wxs.py b/install/windows/build-wxs.py index 7a0d8f1..08396de 100644 --- a/install/windows/build-wxs.py +++ b/install/windows/build-wxs.py @@ -61,34 +61,6 @@ def build_dir_xml(root, data): for subdata in data["dirs"]: build_dir_xml(el, subdata) - # If this is the ProgramMenuFolder, add the menu component - if "id" in data and data["id"] == "ProgramMenuFolder": - component_el = ET.SubElement( - el, - "Component", - Id="ApplicationShortcuts", - Guid="539e7de8-a124-4c09-aa55-0dd516aad7bc", - ) - ET.SubElement( - component_el, - "Shortcut", - Id="ApplicationShortcut1", - Name="Dangerzone", - Description="Dangerzone", - Target="[INSTALLFOLDER]dangerzone.exe", - WorkingDirectory="INSTALLFOLDER", - ) - ET.SubElement( - component_el, - "RegistryValue", - Root="HKCU", - Key="Software\\Freedom of the Press Foundation\\Dangerzone", - Name="installed", - Type="integer", - Value="1", - KeyPath="yes", - ) - def build_components_xml(root, data): component_ids = [] @@ -142,10 +114,6 @@ def main(): "id": "ProgramFilesFolder", "dirs": [], }, - { - "id": "ProgramMenuFolder", - "dirs": [], - }, ], } @@ -223,6 +191,39 @@ def main(): DowngradeErrorMessage="A newer version of [ProductName] is already installed. If you are sure you want to downgrade, remove the existing installation via Programs and Features.", ) + # Add the ProgramMenuFolder StandardDirectory + programmenufolder_el = ET.SubElement( + package_el, + "StandardDirectory", + Id="ProgramMenuFolder", + ) + # Add a shortcut for Dangerzone in the Start menu + shortcut_el = ET.SubElement( + programmenufolder_el, + "Component", + Id="ApplicationShortcuts", + Guid="539e7de8-a124-4c09-aa55-0dd516aad7bc", + ) + ET.SubElement( + shortcut_el, + "Shortcut", + Id="DangerzoneStartMenuShortcut", + Name="Dangerzone", + Description="Dangerzone", + Target="[INSTALLFOLDER]dangerzone.exe", + WorkingDirectory="INSTALLFOLDER", + ) + ET.SubElement( + shortcut_el, + "RegistryValue", + Root="HKCU", + Key="Software\\Freedom of the Press Foundation\\Dangerzone", + Name="installed", + Type="integer", + Value="1", + KeyPath="yes", + ) + build_dir_xml(package_el, data) component_ids = build_components_xml(package_el, data) From a2c9e9deed5ebf9a0483a0e1c0ee2e0dce1c0cd8 Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 21:17:43 +0300 Subject: [PATCH 09/22] Change: Wrap `ProgramFilesFolder` component with a `StandardDirectory` component --- install/windows/build-wxs.py | 55 ++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/install/windows/build-wxs.py b/install/windows/build-wxs.py index 08396de..6b94291 100644 --- a/install/windows/build-wxs.py +++ b/install/windows/build-wxs.py @@ -67,20 +67,19 @@ def build_components_xml(root, data): if "component_id" in data: component_ids.append(data["component_id"]) - for subdata in data["dirs"]: - if "component_guid" in subdata: - dir_ref_el = ET.SubElement(root, "DirectoryRef", Id=subdata["id"]) + if "component_guid" in data: + dir_ref_el = ET.SubElement(root, "DirectoryRef", Id=data["id"]) component_el = ET.SubElement( dir_ref_el, "Component", - Id=subdata["component_id"], - Guid=subdata["component_guid"], + Id=data["component_id"], + Guid=data["component_guid"], ) - for filename in subdata["files"]: + for filename in data["files"]: file_el = ET.SubElement( component_el, "File", Source=filename, Id="file_" + uuid.uuid4().hex ) - + for subdata in data["dirs"]: component_ids += build_components_xml(root, subdata) return component_ids @@ -106,24 +105,12 @@ def main(): print("You must build the dangerzone binary before running this") return - data = { - "id": "TARGETDIR", - "name": "SourceDir", - "dirs": [ - { - "id": "ProgramFilesFolder", - "dirs": [], - }, - ], - } - - data["dirs"][0]["dirs"].append( - build_data( - dist_dir, - "exe.win-amd64-3.12", - "INSTALLFOLDER", - "Dangerzone", - ) + # Prepare data for WiX file harvesting from the output of cx_Freeze + data = build_data( + dist_dir, + "exe.win-amd64-3.12", + "INSTALLFOLDER", + "Dangerzone", ) # Add the Wix root element @@ -224,8 +211,22 @@ def main(): KeyPath="yes", ) - build_dir_xml(package_el, data) - component_ids = build_components_xml(package_el, data) + # Add the ProgramFilesFolder StandardDirectory + programfilesfolder_el = ET.SubElement( + package_el, + "StandardDirectory", + Id="ProgramFilesFolder", + ) + + # Create the directory structure for the installed product + build_dir_xml(programfilesfolder_el, data) + + # Create a component group for application components + applicationcomponents_el = ET.SubElement( + package_el, "ComponentGroup", Id="ApplicationComponents" + ) + # Populate the application components group with components for the installed package + build_components_xml(applicationcomponents_el, data) # Add the Feature element feature_el = ET.SubElement(package_el, "Feature", Id="DefaultFeature", Level="1") From 38890259eb13578e4316e97429ab1694adccf301 Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 21:21:00 +0300 Subject: [PATCH 10/22] Change: Convert Wix UI extension authoring to WiX Toolset v5 Due to limitations of the xml.etree.ElementTree library, add the items in the root element as a dictionary --- install/windows/build-wxs.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/install/windows/build-wxs.py b/install/windows/build-wxs.py index 6b94291..fdeac9b 100644 --- a/install/windows/build-wxs.py +++ b/install/windows/build-wxs.py @@ -114,7 +114,13 @@ def main(): ) # Add the Wix root element - wix_el = ET.Element("Wix", xmlns="http://wixtoolset.org/schemas/v4/wxs") + wix_el = ET.Element( + "Wix", + { + "xmlns": "http://wixtoolset.org/schemas/v4/wxs", + "xmlns:ui": "http://wixtoolset.org/schemas/v4/wxs/ui", + }, + ) # Add the Package element package_el = ET.SubElement( @@ -153,12 +159,8 @@ def main(): Value="https://freedom.press", ) ET.SubElement( - package_el, - "Property", - Id="WIXUI_INSTALLDIR", - Value="INSTALLFOLDER", + package_el, "ui:WixUI", Id="WixUI_InstallDir", InstallDirectory="INSTALLFOLDER" ) - ET.SubElement(package_el, "UIRef", Id="WixUI_InstallDir") ET.SubElement(package_el, "UIRef", Id="WixUI_ErrorProgressText") ET.SubElement( package_el, From 4d133331877cb39c258e0473d3e74554b0bbfb98 Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 21:23:20 +0300 Subject: [PATCH 11/22] Change: Swap Media element with MediaTemplate This is a new default and makes authoring slightly simpler without any functional changes. --- install/windows/build-wxs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/windows/build-wxs.py b/install/windows/build-wxs.py index fdeac9b..2849591 100644 --- a/install/windows/build-wxs.py +++ b/install/windows/build-wxs.py @@ -141,7 +141,7 @@ def main(): Description="Dangerzone $(var.ProductVersion) Installer", Codepage="1252", ) - ET.SubElement(package_el, "Media", Id="1", Cabinet="product.cab", EmbedCab="yes") + ET.SubElement(package_el, "MediaTemplate", EmbedCab="yes") ET.SubElement( package_el, "Icon", Id="ProductIcon", SourceFile="..\\share\\dangerzone.ico" ) From ba84c0fb681138191a026b93084cbab6a1c03f20 Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 21:34:05 +0300 Subject: [PATCH 12/22] Refactor: Simplify `build_data()` function - Rename variables to be more clear about what they do: - reorganise code - simplify a few checks --- install/windows/build-wxs.py | 61 ++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/install/windows/build-wxs.py b/install/windows/build-wxs.py index 2849591..55d799f 100644 --- a/install/windows/build-wxs.py +++ b/install/windows/build-wxs.py @@ -4,49 +4,50 @@ import uuid import xml.etree.ElementTree as ET -def build_data(dirname, dir_prefix, id_, name): +def build_data(base_path, path_prefix, dir_id, dir_name): data = { - "id": id_, - "name": name, + "directory_name": dir_name, + "directory_id": dir_id, "files": [], "dirs": [], } - for basename in os.listdir(dirname): - filename = os.path.join(dirname, basename) - if os.path.isfile(filename): - data["files"].append(os.path.join(dir_prefix, basename)) - elif os.path.isdir(filename): - if id_ == "INSTALLFOLDER": - id_prefix = "Folder" + if dir_id == "INSTALLFOLDER": + data["component_id"] = "ApplicationFiles" + else: + data["component_id"] = "Component" + dir_id + data["component_guid"] = str(uuid.uuid4()) + + for entry in os.listdir(base_path): + entry_path = os.path.join(base_path, entry) + if os.path.isfile(entry_path): + data["files"].append(os.path.join(path_prefix, entry)) + elif os.path.isdir(entry_path): + if dir_id == "INSTALLFOLDER": + next_dir_prefix = "Folder" else: - id_prefix = id_ + next_dir_prefix = dir_id # Skip lib/PySide6/examples folder due to ilegal file names - if "\\build\\exe.win-amd64-3.12\\lib\\PySide6\\examples" in dirname: + if "\\build\\exe.win-amd64-3.12\\lib\\PySide6\\examples" in base_path: continue # Skip lib/PySide6/qml/QtQuick folder due to ilegal file names # XXX Since we're not using Qml it should be no problem - if "\\build\\exe.win-amd64-3.12\\lib\\PySide6\\qml\\QtQuick" in dirname: + if "\\build\\exe.win-amd64-3.12\\lib\\PySide6\\qml\\QtQuick" in base_path: continue - id_value = f"{id_prefix}{basename.capitalize().replace('-', '_')}" - data["dirs"].append( - build_data( - os.path.join(dirname, basename), - os.path.join(dir_prefix, basename), - id_value, - basename, - ) + next_dir_id = next_dir_prefix + entry.capitalize().replace("-", "_") + subdata = build_data( + os.path.join(base_path, entry), + os.path.join(path_prefix, entry), + next_dir_id, + entry, ) - if len(data["files"]) > 0: - if id_ == "INSTALLFOLDER": - data["component_id"] = "ApplicationFiles" - else: - data["component_id"] = "FolderComponent" + id_[len("Folder") :] - data["component_guid"] = str(uuid.uuid4()) + # Add the subdirectory only if it contains files or subdirectories + if subdata["files"] or subdata["dirs"]: + data["dirs"].append(subdata) return data @@ -54,9 +55,9 @@ def build_data(dirname, dir_prefix, id_, name): def build_dir_xml(root, data): attrs = {} if "id" in data: - attrs["Id"] = data["id"] + attrs["Id"] = data["directory_id"] if "name" in data: - attrs["Name"] = data["name"] + attrs["Name"] = data["directory_name"] el = ET.SubElement(root, "Directory", attrs) for subdata in data["dirs"]: build_dir_xml(el, subdata) @@ -68,7 +69,7 @@ def build_components_xml(root, data): component_ids.append(data["component_id"]) if "component_guid" in data: - dir_ref_el = ET.SubElement(root, "DirectoryRef", Id=data["id"]) + dir_ref_el = ET.SubElement(root, "DirectoryRef", Id=data["directory_id"]) component_el = ET.SubElement( dir_ref_el, "Component", From 84b8c5106c03d9cff889ba4330e92efa4b8fd91b Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 21:57:38 +0300 Subject: [PATCH 13/22] Change: Wrap all files to be included in the .msi in a `ComponentGroupRef` With this, all the files are organised into Components, each of which points to a Directory defined in the StandardDirectory element. This simplifies the Feature element considerable as only thing it needs to include everything in the built msi is a reference to `ApplicationComponents` --- install/windows/build-wxs.py | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/install/windows/build-wxs.py b/install/windows/build-wxs.py index 55d799f..68dd6d4 100644 --- a/install/windows/build-wxs.py +++ b/install/windows/build-wxs.py @@ -64,26 +64,17 @@ def build_dir_xml(root, data): def build_components_xml(root, data): - component_ids = [] - if "component_id" in data: - component_ids.append(data["component_id"]) - - if "component_guid" in data: - dir_ref_el = ET.SubElement(root, "DirectoryRef", Id=data["directory_id"]) - component_el = ET.SubElement( - dir_ref_el, - "Component", - Id=data["component_id"], - Guid=data["component_guid"], - ) - for filename in data["files"]: - file_el = ET.SubElement( - component_el, "File", Source=filename, Id="file_" + uuid.uuid4().hex - ) + component_el = ET.SubElement( + root, + "Component", + Id=data["component_id"], + Guid=data["component_guid"], + Directory=data["directory_id"], + ) + for filename in data["files"]: + ET.SubElement(component_el, "File", Source=filename) for subdata in data["dirs"]: - component_ids += build_components_xml(root, subdata) - - return component_ids + build_components_xml(root, subdata) def main(): @@ -233,8 +224,7 @@ def main(): # Add the Feature element feature_el = ET.SubElement(package_el, "Feature", Id="DefaultFeature", Level="1") - for component_id in component_ids: - ET.SubElement(feature_el, "ComponentRef", Id=component_id) + ET.SubElement(feature_el, "ComponentGroupRef", Id="ApplicationComponents") ET.SubElement(feature_el, "ComponentRef", Id="ApplicationShortcuts") print(f'') From 90a2843836d1b6a1d91ecc0e0948410b9c8589ae Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 22:12:40 +0300 Subject: [PATCH 14/22] Refactor: `build_dir_xml()` function - rename for clarity - remove unnecessary checks --- install/windows/build-wxs.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/install/windows/build-wxs.py b/install/windows/build-wxs.py index 68dd6d4..fb81ad1 100644 --- a/install/windows/build-wxs.py +++ b/install/windows/build-wxs.py @@ -52,15 +52,13 @@ def build_data(base_path, path_prefix, dir_id, dir_name): return data -def build_dir_xml(root, data): +def build_directory_xml(root, data): attrs = {} - if "id" in data: - attrs["Id"] = data["directory_id"] - if "name" in data: - attrs["Name"] = data["directory_name"] - el = ET.SubElement(root, "Directory", attrs) + attrs["Id"] = data["directory_id"] + attrs["Name"] = data["directory_name"] + directory_el = ET.SubElement(root, "Directory", attrs) for subdata in data["dirs"]: - build_dir_xml(el, subdata) + build_directory_xml(directory_el, subdata) def build_components_xml(root, data): @@ -213,7 +211,7 @@ def main(): ) # Create the directory structure for the installed product - build_dir_xml(programfilesfolder_el, data) + build_directory_xml(programfilesfolder_el, data) # Create a component group for application components applicationcomponents_el = ET.SubElement( From 7f5793fadaf53c63352740d186537c9fcf3c224a Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 22:17:03 +0300 Subject: [PATCH 15/22] Change: Write dangerzone version and upgradecode into Package and SummaryInformation elements directly --- install/windows/build-wxs.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/install/windows/build-wxs.py b/install/windows/build-wxs.py index fb81ad1..a1ee569 100644 --- a/install/windows/build-wxs.py +++ b/install/windows/build-wxs.py @@ -118,17 +118,17 @@ def main(): "Package", Name="Dangerzone", Manufacturer="Freedom of the Press Foundation", - UpgradeCode="$(var.ProductUpgradeCode)", + UpgradeCode="12b9695c-965b-4be0-bc33-21274e809576", Language="1033", Compressed="yes", Codepage="1252", - Version="$(var.ProductVersion)", + Version=version, ) ET.SubElement( package_el, "SummaryInformation", Keywords="Installer", - Description="Dangerzone $(var.ProductVersion) Installer", + Description="Dangerzone " + version + " Installer", Codepage="1252", ) ET.SubElement(package_el, "MediaTemplate", EmbedCab="yes") @@ -225,8 +225,6 @@ def main(): ET.SubElement(feature_el, "ComponentGroupRef", Id="ApplicationComponents") ET.SubElement(feature_el, "ComponentRef", Id="ApplicationShortcuts") - print(f'') - print('') ET.indent(wix_el, space=" ") print(ET.tostring(wix_el).decode()) From 10bc6aa8f184c8737fd0d976919e82647ec713c8 Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 22:31:16 +0300 Subject: [PATCH 16/22] Fix: Make GUIDs uppercase See [1] [1] https://learn.microsoft.com/en-us/windows/win32/msi/guid --- install/windows/build-wxs.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/install/windows/build-wxs.py b/install/windows/build-wxs.py index a1ee569..7ae4028 100644 --- a/install/windows/build-wxs.py +++ b/install/windows/build-wxs.py @@ -16,7 +16,7 @@ def build_data(base_path, path_prefix, dir_id, dir_name): data["component_id"] = "ApplicationFiles" else: data["component_id"] = "Component" + dir_id - data["component_guid"] = str(uuid.uuid4()) + data["component_guid"] = str(uuid.uuid4()).upper() for entry in os.listdir(base_path): entry_path = os.path.join(base_path, entry) @@ -118,7 +118,7 @@ def main(): "Package", Name="Dangerzone", Manufacturer="Freedom of the Press Foundation", - UpgradeCode="12b9695c-965b-4be0-bc33-21274e809576", + UpgradeCode="12B9695C-965B-4BE0-BC33-21274E809576", Language="1033", Compressed="yes", Codepage="1252", @@ -181,7 +181,7 @@ def main(): programmenufolder_el, "Component", Id="ApplicationShortcuts", - Guid="539e7de8-a124-4c09-aa55-0dd516aad7bc", + Guid="539E7DE8-A124-4C09-AA55-0DD516AAD7BC", ) ET.SubElement( shortcut_el, From 58ecccfba2cd132743a2cbdac5f951df1a8f2110 Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 22:35:34 +0300 Subject: [PATCH 17/22] Change: Write Dangerzone.wxs inside the script directly Also reduce duplication slightly by definig `build_dir`, `cx_freeze_dir` and `dist_dir` --- install/windows/build-wxs.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/install/windows/build-wxs.py b/install/windows/build-wxs.py index 7ae4028..7a15cdb 100644 --- a/install/windows/build-wxs.py +++ b/install/windows/build-wxs.py @@ -86,11 +86,15 @@ def main(): # -rc markers. version = f.read().strip().split("-")[0] - dist_dir = os.path.join( + build_dir = os.path.join( os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "build", - "exe.win-amd64-3.12", ) + + cx_freeze_dir = "exe.win-amd64-3.12" + + dist_dir = os.path.join(build_dir, cx_freeze_dir) + if not os.path.exists(dist_dir): print("You must build the dangerzone binary before running this") return @@ -98,7 +102,7 @@ def main(): # Prepare data for WiX file harvesting from the output of cx_Freeze data = build_data( dist_dir, - "exe.win-amd64-3.12", + cx_freeze_dir, "INSTALLFOLDER", "Dangerzone", ) @@ -226,7 +230,9 @@ def main(): ET.SubElement(feature_el, "ComponentRef", Id="ApplicationShortcuts") ET.indent(wix_el, space=" ") - print(ET.tostring(wix_el).decode()) + + with open(os.path.join(build_dir, "Dangerzone.wxs"), "w") as wxs_file: + wxs_file.write(ET.tostring(wix_el).decode()) if __name__ == "__main__": From e7ae5f3cb2f1e5c6c3da795abb94deaacb4cb293 Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 22:38:47 +0300 Subject: [PATCH 18/22] CI: Use WiX Toolset v5 to build the msi --- .github/workflows/ci.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2eb14a2..86842ba 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -113,10 +113,14 @@ jobs: key: v1-tessdata-${{ hashFiles('./install/common/download-tessdata.py') }} - name: Run CLI tests run: poetry run make test - # Taken from: https://github.com/orgs/community/discussions/27149#discussioncomment-3254829 - - name: Set path for candle and light - run: echo "C:\Program Files (x86)\WiX Toolset v3.14\bin" >> $GITHUB_PATH - shell: bash + - name: Set up .NET CLI environment + uses: actions/setup-dotnet@v4 + with: + dotnet-version: "8.x" + - name: Install WiX Toolset + run: dotnet tool install --global wix + - name: Add WiX UI extension + run: wix extension add --global WixToolset.UI.wixext - name: Build the MSI installer # NOTE: This also builds the .exe internally. run: poetry run .\install\windows\build-app.bat From 32626b4b99c4d973139af1fbc60d4afbec90f314 Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 22:44:46 +0300 Subject: [PATCH 19/22] Change: Update the `build-app.bat` script to work with WiX Toolset v5 - WiX Toolset v3 used to validate the msi package by default. In v5 that has moved to a new command, so add a new validation step to the script. - Also emove the step that uses `insignia.exe` to sign the Dangerzone.msi with the digital signatures from its external cab archives. In WiX Toolset v4 and newer, insignia is replaced with a new command `wix msi inscribe`, but we tell wix to embed the cabinets into the .msi (That's what`EmbedCab="yes"` in the Media / MediaTemplate element does) so singning them separately is not necessary. [0] [0] https://wixtoolset.org/docs/tools/signing/ --- install/windows/build-app.bat | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/install/windows/build-app.bat b/install/windows/build-app.bat index 1d2b770..44c8e81 100644 --- a/install/windows/build-app.bat +++ b/install/windows/build-app.bat @@ -17,22 +17,23 @@ signtool.exe sign /v /d "Dangerzone" /a /n "Freedom of the Press Foundation" /fd REM verify the signature of dangerzone-cli.exe signtool.exe verify /pa build\exe.win-amd64-3.12\dangerzone-cli.exe -REM build the wix file -python install\windows\build-wxs.py > build\Dangerzone.wxs +REM build the wxs file +python install\windows\build-wxs.py REM build the msi package cd build -candle.exe Dangerzone.wxs -light.exe -ext WixUIExtension Dangerzone.wixobj +wix build -ext WixToolset.UI.wixext .\Dangerzone.wxs -out Dangerzone.msi + +REM validate Dangerzone.msi +wix msi validate Dangerzone.msi REM code sign Dangerzone.msi -insignia.exe -im Dangerzone.msi signtool.exe sign /v /d "Dangerzone" /a /n "Freedom of the Press Foundation" /fd sha256 /t http://time.certum.pl/ Dangerzone.msi REM verify the signature of Dangerzone.msi signtool.exe verify /pa Dangerzone.msi -REM moving Dangerzone.msi to dist +REM move Dangerzone.msi to dist cd .. mkdir dist move build\Dangerzone.msi dist From 95239b5f88399d3fe6e5c8335c369993c1a28d0c Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 22:46:36 +0300 Subject: [PATCH 20/22] Docs: Update documentation for WiX Toolset 5 --- BUILD.md | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/BUILD.md b/BUILD.md index c395f50..de6c197 100644 --- a/BUILD.md +++ b/BUILD.md @@ -474,11 +474,24 @@ poetry shell .\dev_scripts\dangerzone.bat ``` -### If you want to build the installer +### If you want to build the Windows installer -* Go to https://dotnet.microsoft.com/download/dotnet-framework and download and install .NET Framework 3.5 SP1 Runtime. I downloaded `dotnetfx35.exe`. -* Go to https://wixtoolset.org/releases/ and download and install WiX toolset. I downloaded `wix314.exe`. -* Add `C:\Program Files (x86)\WiX Toolset v3.14\bin` to the path ([instructions](https://web.archive.org/web/20230221104142/https://windowsloop.com/how-to-add-to-windows-path/)). +Install [.NET SDK](https://dotnet.microsoft.com/en-us/download) version 6 or later. Then, open a terminal and install the latest version of [WiX Toolset .NET tool](https://wixtoolset.org/) **v5** with: + +```sh +dotnet tool install --global wix --version 5.* +``` + +Install the WiX UI extension. You may need to open a new terminal in order to use the newly installed `wix` .NET tool: + +```sh +wix extension add --global WixToolset.UI.wixext/5.x.y +``` + +> [!IMPORTANT] +> To avoid compatibility issues, ensure the WiX UI extension version matches the version of the WiX Toolset. +> +> Run `wix --version` to check the version of WiX Toolset you have installed and replace `5.x.y` with the full version number without the Git revision. ### If you want to sign binaries with Authenticode From ce85bde1094e330b68a6f72a13cf0151ef4cb217 Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 23:17:02 +0300 Subject: [PATCH 21/22] Change: Build a 64-bit installer --- install/windows/build-app.bat | 2 +- install/windows/build-wxs.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/install/windows/build-app.bat b/install/windows/build-app.bat index 44c8e81..ea74429 100644 --- a/install/windows/build-app.bat +++ b/install/windows/build-app.bat @@ -22,7 +22,7 @@ python install\windows\build-wxs.py REM build the msi package cd build -wix build -ext WixToolset.UI.wixext .\Dangerzone.wxs -out Dangerzone.msi +wix build -arch x64 -ext WixToolset.UI.wixext .\Dangerzone.wxs -out Dangerzone.msi REM validate Dangerzone.msi wix msi validate Dangerzone.msi diff --git a/install/windows/build-wxs.py b/install/windows/build-wxs.py index 7a15cdb..a195872 100644 --- a/install/windows/build-wxs.py +++ b/install/windows/build-wxs.py @@ -211,7 +211,7 @@ def main(): programfilesfolder_el = ET.SubElement( package_el, "StandardDirectory", - Id="ProgramFilesFolder", + Id="ProgramFiles64Folder", ) # Create the directory structure for the installed product From e6380f793b0a23edf07d0ef218b41e2dc6e8083c Mon Sep 17 00:00:00 2001 From: jkarasti Date: Wed, 20 Nov 2024 17:38:18 +0200 Subject: [PATCH 22/22] Fix: Dangerzone installed using an msi built with WiX Toolset v3 is not uninstalled by an msi built with WiX Toolset v5 Workaround for an issue after upgrading from WiX Toolset v3 to v5 where the previous version of Dangerzone is not uninstalled during the upgrade by checking if the older installation exists in "C:\Program Files (x86)\Dangerzone". Also handle a special case for Dangerzone 0.8.0 which allows choosing the install location during install by checking if the registry key for it exists. Note that this seems to allow installing Dangerzone 0.8.0 after installing Dangerzone from this branch. In this case the installer errors until Dangerzone 0.8.0 is uninstalled again --- install/windows/build-wxs.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/install/windows/build-wxs.py b/install/windows/build-wxs.py index a195872..33a4622 100644 --- a/install/windows/build-wxs.py +++ b/install/windows/build-wxs.py @@ -174,6 +174,41 @@ def main(): DowngradeErrorMessage="A newer version of [ProductName] is already installed. If you are sure you want to downgrade, remove the existing installation via Programs and Features.", ) + # Workaround for an issue after upgrading from WiX Toolset v3 to v5 where the previous + # version of Dangerzone is not uninstalled during the upgrade by checking if the older installation + # exists in "C:\Program Files (x86)\Dangerzone". + # + # Also handle a special case for Dangerzone 0.8.0 which allows choosing the install location + # during install by checking if the registry key for it exists. + # + # Note that this seems to allow installing Dangerzone 0.8.0 after installing Dangerzone from this branch. + # In this case the installer errors until Dangerzone 0.8.0 is uninstalled again + # + # TODO: Revert this once we are reasonably certain there aren't too many affected Dangerzone installations. + find_old_el = ET.SubElement(package_el, "Property", Id="OLDDANGERZONEFOUND") + directory_search_el = ET.SubElement( + find_old_el, + "DirectorySearch", + Id="dangerzone_install_folder", + Path="C:\\Program Files (x86)\\Dangerzone", + ) + ET.SubElement(directory_search_el, "FileSearch", Name="dangerzone.exe") + registry_search_el = ET.SubElement(package_el, "Property", Id="DANGERZONE080FOUND") + ET.SubElement( + registry_search_el, + "RegistrySearch", + Root="HKLM", + Key="SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{03C2D2B2-9955-4AED-831F-DA4E67FC0FDB}", + Name="DisplayName", + Type="raw", + ) + ET.SubElement( + package_el, + "Launch", + Condition="NOT OLDDANGERZONEFOUND AND NOT DANGERZONE080FOUND", + Message="A previous version of [ProductName] is already installed. Please uninstall it from Programs and Features before proceeding with the installation.", + ) + # Add the ProgramMenuFolder StandardDirectory programmenufolder_el = ET.SubElement( package_el,