Compare commits

..

1 commit

Author SHA1 Message Date
e9c5b569a4
Merge 178364e3a7 into 60df4f7e35 2024-12-03 15:18:56 +01:00
5 changed files with 161 additions and 213 deletions

View file

@ -119,14 +119,10 @@ jobs:
key: v1-tessdata-${{ hashFiles('./install/common/download-tessdata.py') }} key: v1-tessdata-${{ hashFiles('./install/common/download-tessdata.py') }}
- name: Run CLI tests - name: Run CLI tests
run: poetry run make test run: poetry run make test
- name: Set up .NET CLI environment # Taken from: https://github.com/orgs/community/discussions/27149#discussioncomment-3254829
uses: actions/setup-dotnet@v4 - name: Set path for candle and light
with: run: echo "C:\Program Files (x86)\WiX Toolset v3.14\bin" >> $GITHUB_PATH
dotnet-version: "8.x" shell: bash
- 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 - name: Build the MSI installer
# NOTE: This also builds the .exe internally. # NOTE: This also builds the .exe internally.
run: poetry run .\install\windows\build-app.bat run: poetry run .\install\windows\build-app.bat

View file

@ -471,24 +471,11 @@ poetry shell
.\dev_scripts\dangerzone.bat .\dev_scripts\dangerzone.bat
``` ```
### If you want to build the Windows installer ### If you want to build the installer
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: * 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`.
```sh * 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/)).
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 ### If you want to sign binaries with Authenticode

View file

@ -16,11 +16,6 @@ since 0.4.1, and this project adheres to [Semantic Versioning](https://semver.or
- Platform support: Drop support for Fedora 39, since it's end-of-life ([#999](https://github.com/freedomofpress/dangerzone/pull/999)) - Platform support: Drop support for Fedora 39, since it's end-of-life ([#999](https://github.com/freedomofpress/dangerzone/pull/999))
### Development changes
- Build Dangerzone MSI with Wix Toolset 5 ([#929](https://github.com/freedomofpress/dangerzone/pull/929)).
Thanks [@jkarasti](https://github.com/jkarasti) for the contribution.
## [0.8.0](https://github.com/freedomofpress/dangerzone/compare/v0.8.0...0.7.1) ## [0.8.0](https://github.com/freedomofpress/dangerzone/compare/v0.8.0...0.7.1)
### Added ### Added

View file

@ -17,23 +17,22 @@ signtool.exe sign /v /d "Dangerzone" /a /n "Freedom of the Press Foundation" /fd
REM verify the signature of dangerzone-cli.exe REM verify the signature of dangerzone-cli.exe
signtool.exe verify /pa build\exe.win-amd64-3.12\dangerzone-cli.exe signtool.exe verify /pa build\exe.win-amd64-3.12\dangerzone-cli.exe
REM build the wxs file REM build the wix file
python install\windows\build-wxs.py python install\windows\build-wxs.py > build\Dangerzone.wxs
REM build the msi package REM build the msi package
cd build cd build
wix build -arch x64 -ext WixToolset.UI.wixext .\Dangerzone.wxs -out Dangerzone.msi candle.exe Dangerzone.wxs
light.exe -ext WixUIExtension Dangerzone.wixobj
REM validate Dangerzone.msi
wix msi validate Dangerzone.msi
REM code sign 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 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 REM verify the signature of Dangerzone.msi
signtool.exe verify /pa Dangerzone.msi signtool.exe verify /pa Dangerzone.msi
REM move Dangerzone.msi to dist REM moving Dangerzone.msi to dist
cd .. cd ..
mkdir dist mkdir dist
move build\Dangerzone.msi dist move build\Dangerzone.msi dist

View file

@ -4,75 +4,114 @@ import uuid
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
def build_data(base_path, path_prefix, dir_id, dir_name): def build_data(dirname, dir_prefix, id_, name):
data = { data = {
"directory_name": dir_name, "id": id_,
"directory_id": dir_id, "name": name,
"files": [], "files": [],
"dirs": [], "dirs": [],
} }
if dir_id == "INSTALLFOLDER": for basename in os.listdir(dirname):
data["component_id"] = "ApplicationFiles" filename = os.path.join(dirname, basename)
else: if os.path.isfile(filename):
data["component_id"] = "Component" + dir_id data["files"].append(os.path.join(dir_prefix, basename))
data["component_guid"] = str(uuid.uuid4()).upper() elif os.path.isdir(filename):
if id_ == "INSTALLDIR":
for entry in os.listdir(base_path): id_prefix = "Folder"
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: else:
next_dir_prefix = dir_id id_prefix = id_
# Skip lib/PySide6/examples folder due to ilegal file names # Skip lib/PySide6/examples folder due to ilegal file names
if "\\build\\exe.win-amd64-3.12\\lib\\PySide6\\examples" in base_path: if "\\build\\exe.win-amd64-3.12\\lib\\PySide6\\examples" in dirname:
continue continue
# Skip lib/PySide6/qml/QtQuick folder due to ilegal file names # Skip lib/PySide6/qml/QtQuick folder due to ilegal file names
# XXX Since we're not using Qml it should be no problem # XXX Since we're not using Qml it should be no problem
if "\\build\\exe.win-amd64-3.12\\lib\\PySide6\\qml\\QtQuick" in base_path: if "\\build\\exe.win-amd64-3.12\\lib\\PySide6\\qml\\QtQuick" in dirname:
continue continue
next_dir_id = next_dir_prefix + entry.capitalize().replace("-", "_") id_value = f"{id_prefix}{basename.capitalize().replace('-', '_')}"
subdata = build_data( data["dirs"].append(
os.path.join(base_path, entry), build_data(
os.path.join(path_prefix, entry), os.path.join(dirname, basename),
next_dir_id, os.path.join(dir_prefix, basename),
entry, id_value,
basename,
)
) )
# Add the subdirectory only if it contains files or subdirectories if len(data["files"]) > 0:
if subdata["files"] or subdata["dirs"]: if id_ == "INSTALLDIR":
data["dirs"].append(subdata) data["component_id"] = "ApplicationFiles"
else:
data["component_id"] = "FolderComponent" + id_[len("Folder") :]
data["component_guid"] = str(uuid.uuid4())
return data return data
def build_directory_xml(root, data): def build_dir_xml(root, data):
attrs = {} attrs = {}
attrs["Id"] = data["directory_id"] if "id" in data:
attrs["Name"] = data["directory_name"] attrs["Id"] = data["id"]
directory_el = ET.SubElement(root, "Directory", attrs) if "name" in data:
attrs["Name"] = data["name"]
el = ET.SubElement(root, "Directory", attrs)
for subdata in data["dirs"]: for subdata in data["dirs"]:
build_directory_xml(directory_el, subdata) 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="[INSTALLDIR]dangerzone.exe",
WorkingDirectory="INSTALLDIR",
)
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): def build_components_xml(root, data):
component_el = ET.SubElement( component_ids = []
root, if "component_id" in data:
"Component", component_ids.append(data["component_id"])
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"]: for subdata in data["dirs"]:
build_components_xml(root, subdata) if "component_guid" in subdata:
dir_ref_el = ET.SubElement(root, "DirectoryRef", Id=subdata["id"])
component_el = ET.SubElement(
dir_ref_el,
"Component",
Id=subdata["component_id"],
Guid=subdata["component_guid"],
)
for filename in subdata["files"]:
file_el = ET.SubElement(
component_el, "File", Source=filename, Id="file_" + uuid.uuid4().hex
)
component_ids += build_components_xml(root, subdata)
return component_ids
def main(): def main():
@ -86,188 +125,120 @@ def main():
# -rc markers. # -rc markers.
version = f.read().strip().split("-")[0] version = f.read().strip().split("-")[0]
build_dir = os.path.join( dist_dir = os.path.join(
os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))),
"build", "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): if not os.path.exists(dist_dir):
print("You must build the dangerzone binary before running this") print("You must build the dangerzone binary before running this")
return return
# Prepare data for WiX file harvesting from the output of cx_Freeze data = {
data = build_data( "id": "TARGETDIR",
dist_dir, "name": "SourceDir",
cx_freeze_dir, "dirs": [
"INSTALLFOLDER", {
"Dangerzone", "id": "ProgramFilesFolder",
"dirs": [],
},
{
"id": "ProgramMenuFolder",
"dirs": [],
},
],
}
data["dirs"][0]["dirs"].append(
build_data(
dist_dir,
"exe.win-amd64-3.12",
"INSTALLDIR",
"Dangerzone",
)
) )
# Add the Wix root element root_el = ET.Element("Wix", xmlns="http://schemas.microsoft.com/wix/2006/wi")
wix_el = ET.Element( product_el = ET.SubElement(
"Wix", root_el,
{ "Product",
"xmlns": "http://wixtoolset.org/schemas/v4/wxs",
"xmlns:ui": "http://wixtoolset.org/schemas/v4/wxs/ui",
},
)
# Add the Package element
package_el = ET.SubElement(
wix_el,
"Package",
Name="Dangerzone", Name="Dangerzone",
Manufacturer="Freedom of the Press Foundation", Manufacturer="Freedom of the Press Foundation",
UpgradeCode="12B9695C-965B-4BE0-BC33-21274E809576", Id="*",
UpgradeCode="$(var.ProductUpgradeCode)",
Language="1033", Language="1033",
Compressed="yes",
Codepage="1252", Codepage="1252",
Version=version, Version="$(var.ProductVersion)",
) )
ET.SubElement( ET.SubElement(
package_el, product_el,
"SummaryInformation", "Package",
Id="*",
Keywords="Installer", Keywords="Installer",
Description="Dangerzone " + version + " Installer", Description="Dangerzone $(var.ProductVersion) Installer",
Codepage="1252", Manufacturer="Freedom of the Press Foundation",
InstallerVersion="100",
Languages="1033",
Compressed="yes",
SummaryCodepage="1252",
) )
ET.SubElement(package_el, "MediaTemplate", EmbedCab="yes") ET.SubElement(product_el, "Media", Id="1", Cabinet="product.cab", EmbedCab="yes")
ET.SubElement( ET.SubElement(
package_el, "Icon", Id="ProductIcon", SourceFile="..\\share\\dangerzone.ico" product_el, "Icon", Id="ProductIcon", SourceFile="..\\share\\dangerzone.ico"
) )
ET.SubElement(package_el, "Property", Id="ARPPRODUCTICON", Value="ProductIcon") ET.SubElement(product_el, "Property", Id="ARPPRODUCTICON", Value="ProductIcon")
ET.SubElement( ET.SubElement(
package_el, product_el,
"Property", "Property",
Id="ARPHELPLINK", Id="ARPHELPLINK",
Value="https://dangerzone.rocks", Value="https://dangerzone.rocks",
) )
ET.SubElement( ET.SubElement(
package_el, product_el,
"Property", "Property",
Id="ARPURLINFOABOUT", Id="ARPURLINFOABOUT",
Value="https://freedom.press", Value="https://freedom.press",
) )
ET.SubElement( ET.SubElement(
package_el, "ui:WixUI", Id="WixUI_InstallDir", InstallDirectory="INSTALLFOLDER" product_el,
"Property",
Id="WIXUI_INSTALLDIR",
Value="INSTALLDIR",
) )
ET.SubElement(package_el, "UIRef", Id="WixUI_ErrorProgressText") ET.SubElement(product_el, "UIRef", Id="WixUI_InstallDir")
ET.SubElement(product_el, "UIRef", Id="WixUI_ErrorProgressText")
ET.SubElement( ET.SubElement(
package_el, product_el,
"WixVariable", "WixVariable",
Id="WixUILicenseRtf", Id="WixUILicenseRtf",
Value="..\\install\\windows\\license.rtf", Value="..\\install\\windows\\license.rtf",
) )
ET.SubElement( ET.SubElement(
package_el, product_el,
"WixVariable", "WixVariable",
Id="WixUIDialogBmp", Id="WixUIDialogBmp",
Value="..\\install\\windows\\dialog.bmp", Value="..\\install\\windows\\dialog.bmp",
) )
ET.SubElement( ET.SubElement(
package_el, product_el,
"MajorUpgrade", "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.", 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 build_dir_xml(product_el, data)
# version of Dangerzone is not uninstalled during the upgrade by checking if the older installation component_ids = build_components_xml(product_el, data)
# 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 feature_el = ET.SubElement(product_el, "Feature", Id="DefaultFeature", Level="1")
programmenufolder_el = ET.SubElement( for component_id in component_ids:
package_el, ET.SubElement(feature_el, "ComponentRef", Id=component_id)
"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",
)
# Add the ProgramFilesFolder StandardDirectory
programfilesfolder_el = ET.SubElement(
package_el,
"StandardDirectory",
Id="ProgramFiles64Folder",
)
# Create the directory structure for the installed product
build_directory_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")
ET.SubElement(feature_el, "ComponentGroupRef", Id="ApplicationComponents")
ET.SubElement(feature_el, "ComponentRef", Id="ApplicationShortcuts") ET.SubElement(feature_el, "ComponentRef", Id="ApplicationShortcuts")
ET.indent(wix_el, space=" ") print('<?xml version="1.0" encoding="windows-1252"?>')
print(f'<?define ProductVersion = "{version}"?>')
with open(os.path.join(build_dir, "Dangerzone.wxs"), "w") as wxs_file: print('<?define ProductUpgradeCode = "12b9695c-965b-4be0-bc33-21274e809576"?>')
wxs_file.write(ET.tostring(wix_el).decode()) ET.indent(root_el)
print(ET.tostring(root_el).decode())
if __name__ == "__main__": if __name__ == "__main__":