From a22d8443e826dff5dbf4720f017e7ef73bb16acb Mon Sep 17 00:00:00 2001 From: JKarasti Date: Thu, 12 Sep 2024 18:16:23 +0300 Subject: [PATCH 01/24] 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/24] 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/24] 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/24] 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/24] 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 3df5c0d3be5fffc147dbea9d98310bbf80d76cc9 Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 20:31:58 +0300 Subject: [PATCH 06/24] 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 9a87e64e69898764eaea3ff0722be0e40e65834a Mon Sep 17 00:00:00 2001 From: jkarasti Date: Mon, 11 Nov 2024 19:06:10 +0200 Subject: [PATCH 07/24] 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 28b555ac6f06d2e27ad97e6cd8f290da58098cd5 Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 20:43:31 +0300 Subject: [PATCH 08/24] 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 e6f5a258189bff545375957e64bee2865b06b6a2 Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 21:17:43 +0300 Subject: [PATCH 09/24] 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 68cd96a7d35dd6b48a34ba21e66a58755016d29d Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 21:21:00 +0300 Subject: [PATCH 10/24] 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 410beead11e4f93fe6b3f8e633a018b8598b54d2 Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 21:23:20 +0300 Subject: [PATCH 11/24] 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 c7e3f995c03417be3fb77c3efee32a1c62cb8c5f Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 21:34:05 +0300 Subject: [PATCH 12/24] 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 2b73427e1650ade015f83e02fa81b372c59600f9 Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 21:57:38 +0300 Subject: [PATCH 13/24] 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 02c8396a9aeb70b417154f792fe78e2afba6555c Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 22:12:40 +0300 Subject: [PATCH 14/24] 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 9da607093f9191c75ebb6fbf5b050ae899ac31ae Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 22:17:03 +0300 Subject: [PATCH 15/24] 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 39062804bfd21c284dd8ecde14e0fad515185097 Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 22:31:16 +0300 Subject: [PATCH 16/24] 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 ada9f47d655746efa79b25868a01732fb633202a Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 22:35:34 +0300 Subject: [PATCH 17/24] 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 0767e0105327e05b92b3814912766912862491d6 Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 22:38:47 +0300 Subject: [PATCH 18/24] 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 ca94580..20d63df 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 + - 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 63dbe6a037e6b8762c1e3b910c1fbef15f35f479 Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 22:44:46 +0300 Subject: [PATCH 19/24] 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 3eaa89fcce9dc278fccdc6fa143a16635a059e58 Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 22:46:36 +0300 Subject: [PATCH 20/24] 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 945f1846d1071ddba57a4d67e9535c657ca5efa2 Mon Sep 17 00:00:00 2001 From: JKarasti Date: Mon, 23 Sep 2024 23:17:02 +0300 Subject: [PATCH 21/24] 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 a900a7c42e39aadeab6c31bf830fe2a943a842f7 Mon Sep 17 00:00:00 2001 From: jkarasti Date: Tue, 29 Oct 2024 18:56:56 +0200 Subject: [PATCH 22/24] Change: Wrap installer ui related things in a `UI` element --- install/windows/build-wxs.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/install/windows/build-wxs.py b/install/windows/build-wxs.py index a195872..a08aaf8 100644 --- a/install/windows/build-wxs.py +++ b/install/windows/build-wxs.py @@ -152,10 +152,6 @@ def main(): Id="ARPURLINFOABOUT", Value="https://freedom.press", ) - ET.SubElement( - package_el, "ui:WixUI", Id="WixUI_InstallDir", InstallDirectory="INSTALLFOLDER" - ) - ET.SubElement(package_el, "UIRef", Id="WixUI_ErrorProgressText") ET.SubElement( package_el, "WixVariable", @@ -174,6 +170,13 @@ 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 UI element + ui_el = ET.SubElement(package_el, "UI") + ET.SubElement( + ui_el, "ui:WixUI", Id="WixUI_InstallDir", InstallDirectory="INSTALLFOLDER" + ) + ET.SubElement(ui_el, "UIRef", Id="WixUI_ErrorProgressText") + # Add the ProgramMenuFolder StandardDirectory programmenufolder_el = ET.SubElement( package_el, From 180b907cff178d1b3f73bc6b1a120fabc1e453ca Mon Sep 17 00:00:00 2001 From: jkarasti Date: Thu, 31 Oct 2024 18:45:47 +0200 Subject: [PATCH 23/24] Fix: Dangerzone installed using an msi built with WiX Toolset v3 is not uninstalled by an msi built with WiX Toolset v5 Fix 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 v3 2. Finds the older installation 3. And finally flips the scope back to "perMachine" which is the default in WiX v4 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 a08aaf8..18f09c9 100644 --- a/install/windows/build-wxs.py +++ b/install/windows/build-wxs.py @@ -177,6 +177,43 @@ def main(): ) ET.SubElement(ui_el, "UIRef", Id="WixUI_ErrorProgressText") + # Workaround for an issue after upgrading from WiX Toolset v3 to v5 where the older + # version of Dangerzone is not uninstalled during the upgrade + # + # Fix 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 v3 + # 2. Finds the older installation + # 3. And finally flips the scope back to "perMachine" which is the default in WiX v4 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", + ) + # Add the ProgramMenuFolder StandardDirectory programmenufolder_el = ET.SubElement( package_el, From da09566a3cf6a593789d84062bc1c2290f94e5c5 Mon Sep 17 00:00:00 2001 From: jkarasti Date: Mon, 11 Nov 2024 18:46:22 +0200 Subject: [PATCH 24/24] Fixup: Tweak wix install doc --- BUILD.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/BUILD.md b/BUILD.md index b727fc3..f6a527e 100644 --- a/BUILD.md +++ b/BUILD.md @@ -476,20 +476,21 @@ poetry shell ### If you want to build the Windows installer -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. +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.0.1 +dotnet tool install --global wix --version 5.* ``` -Install the WiX UI extension **in a new terminal**, in order to use the newly installed `wix` .NET tool: +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.0.1 +wix extension add --global WixToolset.UI.wixext/5.x.y ``` > [!IMPORTANT] -> To prevent compatibility issues, ensure that all WiX plugins you install match the version of WiX Toolset. +> 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