From a22d8443e826dff5dbf4720f017e7ef73bb16acb 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 99f231b46a55ed0381a70a1c6289c49378b17996 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 6e2659bc0e3f6ed796ea2d97dded9bee892f9d45 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 b2c085c812a46effd46fb25f83156a61d336b55d 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 3357b30edb3f0991195f8c140a1a39f3f1db1b75 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 8c26f3512b218d4b8be69daa699d1aa4a9fe5d0a 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 | 49 +++++++++++++++++------------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/install/windows/build-wxs.py b/install/windows/build-wxs.py index 49cc83a..3b688b3 100644 --- a/install/windows/build-wxs.py +++ b/install/windows/build-wxs.py @@ -160,77 +160,74 @@ 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)", + Version=dangerzone_version, ) 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") + 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 f7f81c59602a2b0f53f31acf1db6267d658bbcff Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 20:43:31 +0300 Subject: [PATCH 07/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 3b688b3..b1ca5fc 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": [], - }, ], } @@ -224,6 +192,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", + ) + + 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 965210dee5bbb713b8b172e5d3ec13a9a816c89b Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 21:17:43 +0300 Subject: [PATCH 08/22] Change: Wrap `ProgramFilesFolder` component with a `StandardDirectory` component --- install/windows/build-wxs.py | 37 ++++++++++++------------------------ 1 file changed, 12 insertions(+), 25 deletions(-) diff --git a/install/windows/build-wxs.py b/install/windows/build-wxs.py index b1ca5fc..0cf7106 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 From aab5bd08d7b4f849761f685816816a14c873a092 Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 21:21:00 +0300 Subject: [PATCH 09/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 0cf7106..bafc557 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 a1dba4a098c86bfd763a4999cb810bcb91a958e7 Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 21:23:20 +0300 Subject: [PATCH 10/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 bafc557..335e9eb 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 ea3f4c88a5b990debd5f5fe0759c09d7be5c9bc4 Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 21:34:05 +0300 Subject: [PATCH 11/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 335e9eb..345fe9f 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 9035497da38083ba9fab4eb6744f5afabe9464cc Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 21:57:38 +0300 Subject: [PATCH 12/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 | 50 +++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/install/windows/build-wxs.py b/install/windows/build-wxs.py index 345fe9f..b336ac9 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(): @@ -215,12 +206,25 @@ 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", + ) + build_dir_xml(programfilesfolder_el, data) + + applicationcomponents_el = ET.SubElement( + package_el, "ComponentGroup", Id="ApplicationComponents" + ) + + # Generate the components for the installed product + build_components_xml(applicationcomponents_el, data) + + # 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 4f97ed41773e3adeac4d49adf2824c5c40387477 Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 22:12:40 +0300 Subject: [PATCH 13/22] Refactor: `build_dir_xml()` function - rename for clarity - remove unnecessary checks --- install/windows/build-wxs.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/install/windows/build-wxs.py b/install/windows/build-wxs.py index b336ac9..cc1e565 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,8 @@ def main(): Id="ProgramFilesFolder", ) - build_dir_xml(programfilesfolder_el, data) + # Generate the directory layout for the installed product + build_directory_xml(programfilesfolder_el, data) applicationcomponents_el = ET.SubElement( package_el, "ComponentGroup", Id="ApplicationComponents" From d61f8667ae42d2190b78d9951c821d4255f0ef57 Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 22:17:03 +0300 Subject: [PATCH 14/22] Change: Write dangerzone version and upgradecode into Package and SummaryInformation elements directly --- install/windows/build-wxs.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/install/windows/build-wxs.py b/install/windows/build-wxs.py index cc1e565..9029e92 100644 --- a/install/windows/build-wxs.py +++ b/install/windows/build-wxs.py @@ -84,7 +84,9 @@ def main(): with open(version_filename) as f: # Read the Dangerzone version from share/version.txt, and remove any potential # -rc markers. - version = f.read().strip().split("-")[0] + dangerzone_version = f.read().strip().split("-")[0] + + dangerzone_product_upgrade_code = "12b9695c-965b-4be0-bc33-21274e809576" dist_dir = os.path.join( os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), @@ -118,7 +120,7 @@ def main(): "Package", Name="Dangerzone", Manufacturer="Freedom of the Press Foundation", - UpgradeCode="$(var.ProductUpgradeCode)", + UpgradeCode=dangerzone_product_upgrade_code, Language="1033", Compressed="yes", Codepage="1252", @@ -128,7 +130,7 @@ def main(): package_el, "SummaryInformation", Keywords="Installer", - Description="Dangerzone $(var.ProductVersion) Installer", + Description="Dangerzone " + dangerzone_version + " Installer", Codepage="1252", ) ET.SubElement(package_el, "MediaTemplate", EmbedCab="yes") @@ -226,8 +228,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 5d47984e4572c3196698f6f693949344d318de91 Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 22:31:16 +0300 Subject: [PATCH 15/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 9029e92..a2b1835 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) @@ -86,7 +86,7 @@ def main(): # -rc markers. dangerzone_version = f.read().strip().split("-")[0] - dangerzone_product_upgrade_code = "12b9695c-965b-4be0-bc33-21274e809576" + dangerzone_product_upgrade_code = "12B9695C-965B-4BE0-BC33-21274E809576" dist_dir = os.path.join( os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), @@ -184,7 +184,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 6e2a95326d27dfc43d4e0732c99705db2ed99737 Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 22:35:34 +0300 Subject: [PATCH 16/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 | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/install/windows/build-wxs.py b/install/windows/build-wxs.py index a2b1835..bb7fa52 100644 --- a/install/windows/build-wxs.py +++ b/install/windows/build-wxs.py @@ -88,11 +88,15 @@ def main(): dangerzone_product_upgrade_code = "12B9695C-965B-4BE0-BC33-21274E809576" - 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 @@ -100,7 +104,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", ) @@ -126,6 +130,7 @@ def main(): Codepage="1252", Version=dangerzone_version, ) + ET.SubElement( package_el, "SummaryInformation", @@ -229,7 +234,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 c1dc2490b530f01550e0dbbc2159d927ff1f82e0 Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 22:38:47 +0300 Subject: [PATCH 17/22] Change: Use WiX Toolset v5 to build the msi in CI --- .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 ca94580..888bb58 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -110,10 +110,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 --version 5.0.1 + - name: Add WiX UI extension + run: wix extension add --global WixToolset.UI.wixext/5.0.1 - name: Build the MSI installer # NOTE: This also builds the .exe internally. run: poetry run .\install\windows\build-app.bat From a0f99f89f73e19fa540f0b4ed03e886844fd0472 Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 22:44:46 +0300 Subject: [PATCH 18/22] Change: Make `build-app.bat` script 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. --- install/windows/build-app.bat | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/install/windows/build-app.bat b/install/windows/build-app.bat index 1d2b770..75c9b0d 100644 --- a/install/windows/build-app.bat +++ b/install/windows/build-app.bat @@ -17,22 +17,24 @@ 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 +wix msi inscribe 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 dba5b7a3ac79bde916628ee98e449bcc547a320e Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 22:46:36 +0300 Subject: [PATCH 19/22] Docs: Documentation for WiX Toolset 5 --- BUILD.md | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/BUILD.md b/BUILD.md index c395f50..b727fc3 100644 --- a/BUILD.md +++ b/BUILD.md @@ -474,11 +474,22 @@ 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 [WiX Toolset .NET tool](https://wixtoolset.org/) v5.0.1. + +```sh +dotnet tool install --global wix --version 5.0.1 +``` + +Install the WiX UI extension **in a new terminal**, in order to use the newly installed `wix` .NET tool: + +```sh +wix extension add --global WixToolset.UI.wixext/5.0.1 +``` + +> [!IMPORTANT] +> To prevent compatibility issues, ensure that all WiX plugins you install match the version of WiX Toolset. ### If you want to sign binaries with Authenticode From 7e4346a3061cca2e64ff849bb19684bf5b790309 Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 23:17:02 +0300 Subject: [PATCH 20/22] Change: Build 64bit 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 75c9b0d..0e30505 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 bb7fa52..71e3564 100644 --- a/install/windows/build-wxs.py +++ b/install/windows/build-wxs.py @@ -215,7 +215,7 @@ def main(): programfilesfolder_el = ET.SubElement( package_el, "StandardDirectory", - Id="ProgramFilesFolder", + Id="ProgramFiles64Folder", ) # Generate the directory layout for the installed product From 8603cd3b8637b71c8aa0217ecebdbb89541b81c9 Mon Sep 17 00:00:00 2001 From: jkarasti Date: Tue, 29 Oct 2024 18:56:56 +0200 Subject: [PATCH 21/22] Change: Wrap installer ui related things in a `UI` element fix --- install/windows/build-wxs.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/install/windows/build-wxs.py b/install/windows/build-wxs.py index 71e3564..489f43f 100644 --- a/install/windows/build-wxs.py +++ b/install/windows/build-wxs.py @@ -155,10 +155,14 @@ def main(): Id="ARPURLINFOABOUT", Value="https://freedom.press", ) + + ui_el = ET.SubElement(package_el, "UI") + ET.SubElement( - package_el, "ui:WixUI", Id="WixUI_InstallDir", InstallDirectory="INSTALLFOLDER" + ui_el, "ui:WixUI", Id="WixUI_InstallDir", InstallDirectory="INSTALLFOLDER" ) - ET.SubElement(package_el, "UIRef", Id="WixUI_ErrorProgressText") + ET.SubElement(ui_el, "UIRef", Id="WixUI_ErrorProgressText") + ET.SubElement( package_el, "WixVariable", From 223fb0f1b95c7b8daa62525378c662261038d4b5 Mon Sep 17 00:00:00 2001 From: jkarasti Date: Tue, 29 Oct 2024 18:58:00 +0200 Subject: [PATCH 22/22] Fix: Dangerzone installed using an msi built with WiX Toolset 3 is not uninstalled by an msi built with WiX Toolset 5 Work around the issue by adding some extra functionality to the "Next" button on the welcome screen of the installer. When the user clicks it to proceed with the installation this: 1. Flips the install scope to "perUser" which is the default in WiX 3 2. Finds the older installation 3. And finally flips the scope back to "perMachine" which is the default in WiX 4 and newer TODO: Revert this once we are reasonably certain there are no affected Dangerzone Installations? --- install/windows/build-wxs.py | 37 ++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/install/windows/build-wxs.py b/install/windows/build-wxs.py index 489f43f..fd1e154 100644 --- a/install/windows/build-wxs.py +++ b/install/windows/build-wxs.py @@ -163,6 +163,43 @@ def main(): ) ET.SubElement(ui_el, "UIRef", Id="WixUI_ErrorProgressText") + # Workaround for an issue after upgrading from WiX Toolset 3 to 5 where the older + # version of Dangerzone is not uninstalled during the upgrade + # + # Work around the issue by adding some extra functionality to the "Next" button on the welcome screen + # of the installer. When the user clicks it to proceed with the installation this: + # 1. Flips the install scope to "perUser" which is the default in WiX 3 + # 2. Finds the older installation + # 3. And finally flips the scope back to "perMachine" which is the default in WiX 4 and newer + # + # Adapted from this stack overflow answer: https://stackoverflow.com/a/35064434 + # + # TODO: Revert this once we are reasonably certain there are no affected Dangerzone Installations? + ET.SubElement( + ui_el, + "Publish", + Dialog="WelcomeDlg", + Control="Next", + Property="ALLUSERS", + Value="{}", + ) + ET.SubElement( + ui_el, + "Publish", + Dialog="WelcomeDlg", + Control="Next", + Event="DoAction", + Value="FindRelatedProducts", + ) + ET.SubElement( + ui_el, + "Publish", + Dialog="WelcomeDlg", + Control="Next", + Property="ALLUSERS", + Value="1", + ) + ET.SubElement( package_el, "WixVariable",