mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-08-01 03:08:43 +08:00
✨ feat(release): 发布 Windows Portable 与 MSI 双产物
- Windows AMD64 与 ARM64 同时生成 Portable EXE 和 MSI 安装包 - 统一 stable 与 dev 的 MSI 产品身份并支持通道切换升级 - 发布清单同时包含两类资产及对应 SHA256 校验信息 - 在 Windows workflow 中执行双模式更新与 WiX 契约测试
This commit is contained in:
83
.github/workflows/dev-build.yml
vendored
83
.github/workflows/dev-build.yml
vendored
@@ -25,6 +25,11 @@ jobs:
|
||||
with:
|
||||
go-version-file: 'go.mod'
|
||||
|
||||
- name: Test release asset contracts
|
||||
run: |
|
||||
python3 tools/generate-update-latest-manifest.test.py
|
||||
python3 tools/windows-release-artifacts.test.py
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
@@ -391,6 +396,24 @@ jobs:
|
||||
}
|
||||
& $upxCmd --version
|
||||
|
||||
- name: Install WiX Toolset (Windows)
|
||||
if: contains(matrix.platform, 'windows')
|
||||
shell: pwsh
|
||||
run: |
|
||||
$wixVersion = "6.0.2"
|
||||
$wixTools = Join-Path $env:RUNNER_TEMP "wix-tools"
|
||||
dotnet tool install wix --tool-path $wixTools --version $wixVersion
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "WiX Toolset 安装失败,退出码: $LASTEXITCODE"
|
||||
}
|
||||
$wixTools | Out-File -FilePath $env:GITHUB_PATH -Append -Encoding utf8
|
||||
$env:PATH = "$wixTools;$env:PATH"
|
||||
wix extension add --global "WixToolset.UI.wixext/$wixVersion"
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "WiX UI 扩展安装失败,退出码: $LASTEXITCODE"
|
||||
}
|
||||
wix --version
|
||||
|
||||
- name: Install Linux Dependencies
|
||||
if: contains(matrix.platform, 'linux')
|
||||
run: |
|
||||
@@ -519,7 +542,7 @@ jobs:
|
||||
- name: Test Windows Online Updater (Windows AMD64)
|
||||
if: ${{ matrix.platform == 'windows/amd64' }}
|
||||
shell: pwsh
|
||||
run: go test ./internal/app -run '^TestWindowsPowerShellUpdater' -count=1 -timeout=3m
|
||||
run: go test ./internal/app -run '^(TestWindowsPowerShellUpdater|TestExpectedAssetNameForExecutableUsesWindowsPortableSuffix|TestShouldWindowsUpdateLaunchDownloadedAssetDirectly|TestResolveUpdateInstallMode|TestExpectedAssetNameForWindowsInstallMode|TestResolveUpdateWorkspaceDirForPlatformSeparatesMSIFromInstallDirectory|TestValidateUpdatePackageForCurrentInstallMode|TestInstallUpdateAndRestartMSI|TestBuildWindowsMSIUpdatePowerShellScript|TestBuildWindowsMSILaunchCommand|TestResolveWindowsMSIExecPath)' -count=1 -timeout=3m
|
||||
|
||||
# ---- 生成 dev 版本号 ----
|
||||
- name: Generate Dev Version
|
||||
@@ -861,14 +884,16 @@ jobs:
|
||||
mv "$DMG_NAME" "../../$FINAL_NAME"
|
||||
|
||||
# Windows Packaging
|
||||
- name: Package Windows EXE
|
||||
- name: Package Windows Portable EXE and MSI
|
||||
if: contains(matrix.platform, 'windows')
|
||||
shell: pwsh
|
||||
run: |
|
||||
Set-Location build/bin
|
||||
$version = "${{ steps.version.outputs.version }}"
|
||||
$target = "${{ matrix.build_name }}"
|
||||
$finalExeName = "GoNavi-$version-${{ matrix.os_name }}-${{ matrix.arch_name }}${{ matrix.artifact_suffix }}.exe"
|
||||
$archName = "${{ matrix.arch_name }}"
|
||||
$portableExeName = "GoNavi-$version-${{ matrix.os_name }}-$archName${{ matrix.artifact_suffix }}-Portable.exe"
|
||||
$msiName = "GoNavi-$version-${{ matrix.os_name }}-$archName${{ matrix.artifact_suffix }}-Installer.msi"
|
||||
|
||||
if (Test-Path "$target.exe") {
|
||||
$finalExe = "$target.exe"
|
||||
@@ -911,8 +936,55 @@ jobs:
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "📦 输出 Windows 可执行文件 $finalExeName..."
|
||||
Copy-Item -LiteralPath $finalExe -Destination "..\\..\\$finalExeName" -Force
|
||||
Write-Host "📦 输出 Windows Portable 可执行文件 $portableExeName..."
|
||||
Copy-Item -LiteralPath $finalExe -Destination "..\\..\\$portableExeName" -Force
|
||||
|
||||
$runNumber = [int64]$env:GITHUB_RUN_NUMBER
|
||||
if ($runNumber -le 0 -or $runNumber -gt 65535) {
|
||||
throw "GitHub run number 无法映射为 MSI 版本号: $runNumber"
|
||||
}
|
||||
$productVersion = "255.0.$runNumber"
|
||||
$wixArch = if ($archName -eq "Arm64") { "arm64" } else { "x64" }
|
||||
$componentGuid = if ($archName -eq "Arm64") {
|
||||
"7F4C7757-329F-42B0-B312-D4B8AD50415E"
|
||||
} else {
|
||||
"0BCEE70B-9CF2-449C-9ADE-190188493234"
|
||||
}
|
||||
|
||||
$licenseText = Get-Content -LiteralPath "..\\..\\LICENSE" -Raw
|
||||
$escapedLicense = $licenseText.Replace('\', '\\').Replace('{', '\{').Replace('}', '\}')
|
||||
$escapedLicense = $escapedLicense -replace "`r?`n", "\par`r`n"
|
||||
$licenseRtf = Join-Path $env:RUNNER_TEMP "GoNavi-License.rtf"
|
||||
$rtfContent = "{\rtf1\ansi\deff0{\fonttbl{\f0 Segoe UI;}}\f0\fs18 $escapedLicense}"
|
||||
Set-Content -LiteralPath $licenseRtf -Value $rtfContent -Encoding ascii
|
||||
|
||||
$sourceExe = (Resolve-Path -LiteralPath $finalExe).Path
|
||||
$wxsPath = (Resolve-Path -LiteralPath "..\\windows\\installer.wxs").Path
|
||||
$iconPath = (Resolve-Path -LiteralPath "..\\windows\\icon.ico").Path
|
||||
$installMarkerPath = Join-Path $env:RUNNER_TEMP ".gonavi-msi-install"
|
||||
Set-Content -LiteralPath $installMarkerPath -Value "MSI" -Encoding ascii -NoNewline
|
||||
$installMarkerPath = (Resolve-Path -LiteralPath $installMarkerPath).Path
|
||||
$wixIntermediateDir = Join-Path $env:RUNNER_TEMP "wixobj-$archName"
|
||||
Write-Host "📦 生成 Windows MSI 安装程序 $msiName (version=$productVersion arch=$wixArch)..."
|
||||
wix build $wxsPath `
|
||||
-arch $wixArch `
|
||||
-ext "WixToolset.UI.wixext/6.0.2" `
|
||||
-d "ProductName=GoNavi" `
|
||||
-d "ProductVersion=$productVersion" `
|
||||
-d "UpgradeCode=CDD6BF2F-ED1E-4345-A0AB-DCDB7E15FB23" `
|
||||
-d "ComponentGuid=$componentGuid" `
|
||||
-d "InstallFolderName=GoNavi" `
|
||||
-d "RegistryKeyName=GoNavi" `
|
||||
-d "SourceExe=$sourceExe" `
|
||||
-d "InstallMarker=$installMarkerPath" `
|
||||
-d "IconPath=$iconPath" `
|
||||
-d "LicenseRtf=$licenseRtf" `
|
||||
-pdbtype none `
|
||||
-intermediatefolder $wixIntermediateDir `
|
||||
-out "..\\..\\$msiName"
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "WiX MSI 构建失败,退出码: $LASTEXITCODE"
|
||||
}
|
||||
|
||||
# Linux Packaging
|
||||
- name: Package Linux
|
||||
@@ -1003,6 +1075,7 @@ jobs:
|
||||
path: |
|
||||
GoNavi-*.dmg
|
||||
GoNavi-*.exe
|
||||
GoNavi-*.msi
|
||||
GoNavi-*.tar.gz
|
||||
GoNavi-*.AppImage
|
||||
drivers/**
|
||||
|
||||
89
.github/workflows/release.yml
vendored
89
.github/workflows/release.yml
vendored
@@ -21,6 +21,11 @@ jobs:
|
||||
with:
|
||||
go-version-file: 'go.mod'
|
||||
|
||||
- name: Test release asset contracts
|
||||
run: |
|
||||
python3 tools/generate-update-latest-manifest.test.py
|
||||
python3 tools/windows-release-artifacts.test.py
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
@@ -343,6 +348,24 @@ jobs:
|
||||
}
|
||||
& $upxCmd --version
|
||||
|
||||
- name: Install WiX Toolset (Windows)
|
||||
if: contains(matrix.platform, 'windows')
|
||||
shell: pwsh
|
||||
run: |
|
||||
$wixVersion = "6.0.2"
|
||||
$wixTools = Join-Path $env:RUNNER_TEMP "wix-tools"
|
||||
dotnet tool install wix --tool-path $wixTools --version $wixVersion
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "WiX Toolset 安装失败,退出码: $LASTEXITCODE"
|
||||
}
|
||||
$wixTools | Out-File -FilePath $env:GITHUB_PATH -Append -Encoding utf8
|
||||
$env:PATH = "$wixTools;$env:PATH"
|
||||
wix extension add --global "WixToolset.UI.wixext/$wixVersion"
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "WiX UI 扩展安装失败,退出码: $LASTEXITCODE"
|
||||
}
|
||||
wix --version
|
||||
|
||||
# Linux Dependencies (GTK3, WebKit2GTK required by Wails)
|
||||
- name: Install Linux Dependencies
|
||||
if: contains(matrix.platform, 'linux')
|
||||
@@ -475,7 +498,7 @@ jobs:
|
||||
- name: Test Windows Online Updater (Windows AMD64)
|
||||
if: ${{ matrix.platform == 'windows/amd64' }}
|
||||
shell: pwsh
|
||||
run: go test ./internal/app -run '^TestWindowsPowerShellUpdater' -count=1 -timeout=3m
|
||||
run: go test ./internal/app -run '^(TestWindowsPowerShellUpdater|TestExpectedAssetNameForExecutableUsesWindowsPortableSuffix|TestShouldWindowsUpdateLaunchDownloadedAssetDirectly|TestResolveUpdateInstallMode|TestExpectedAssetNameForWindowsInstallMode|TestResolveUpdateWorkspaceDirForPlatformSeparatesMSIFromInstallDirectory|TestValidateUpdatePackageForCurrentInstallMode|TestInstallUpdateAndRestartMSI|TestBuildWindowsMSIUpdatePowerShellScript|TestBuildWindowsMSILaunchCommand|TestResolveWindowsMSIExecPath)' -count=1 -timeout=3m
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
@@ -806,7 +829,7 @@ jobs:
|
||||
mv "$DMG_NAME" "../../$FINAL_NAME"
|
||||
|
||||
# Windows Packaging
|
||||
- name: Package Windows EXE
|
||||
- name: Package Windows Portable EXE and MSI
|
||||
if: contains(matrix.platform, 'windows')
|
||||
shell: pwsh
|
||||
run: |
|
||||
@@ -816,7 +839,9 @@ jobs:
|
||||
$version = $version.Substring(1)
|
||||
}
|
||||
$target = "${{ matrix.build_name }}"
|
||||
$finalExeName = "GoNavi-$version-${{ matrix.os_name }}-${{ matrix.arch_name }}${{ matrix.artifact_suffix }}.exe"
|
||||
$archName = "${{ matrix.arch_name }}"
|
||||
$portableExeName = "GoNavi-$version-${{ matrix.os_name }}-$archName${{ matrix.artifact_suffix }}-Portable.exe"
|
||||
$msiName = "GoNavi-$version-${{ matrix.os_name }}-$archName${{ matrix.artifact_suffix }}-Installer.msi"
|
||||
|
||||
if (Test-Path "$target.exe") {
|
||||
$finalExe = "$target.exe"
|
||||
@@ -859,8 +884,61 @@ jobs:
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "📦 输出 Windows 可执行文件 $finalExeName..."
|
||||
Copy-Item -LiteralPath $finalExe -Destination "..\\..\\$finalExeName" -Force
|
||||
Write-Host "📦 输出 Windows Portable 可执行文件 $portableExeName..."
|
||||
Copy-Item -LiteralPath $finalExe -Destination "..\\..\\$portableExeName" -Force
|
||||
|
||||
if ($version -match '^(?<major>\d+)\.(?<minor>\d+)\.(?<build>\d+)(?:[-+].*)?$') {
|
||||
$major = [int]$Matches.major
|
||||
$minor = [int]$Matches.minor
|
||||
$build = [int]$Matches.build
|
||||
} else {
|
||||
throw "Windows MSI 版本必须是 major.minor.patch 格式,当前为: $version"
|
||||
}
|
||||
if ($major -gt 255 -or $minor -gt 255 -or $build -gt 65535) {
|
||||
throw "Windows MSI 版本超出范围 (major/minor <= 255, patch <= 65535): $version"
|
||||
}
|
||||
$productVersion = "$major.$minor.$build"
|
||||
$wixArch = if ($archName -eq "Arm64") { "arm64" } else { "x64" }
|
||||
$componentGuid = if ($archName -eq "Arm64") {
|
||||
"7F4C7757-329F-42B0-B312-D4B8AD50415E"
|
||||
} else {
|
||||
"0BCEE70B-9CF2-449C-9ADE-190188493234"
|
||||
}
|
||||
|
||||
$licenseText = Get-Content -LiteralPath "..\\..\\LICENSE" -Raw
|
||||
$escapedLicense = $licenseText.Replace('\', '\\').Replace('{', '\{').Replace('}', '\}')
|
||||
$escapedLicense = $escapedLicense -replace "`r?`n", "\par`r`n"
|
||||
$licenseRtf = Join-Path $env:RUNNER_TEMP "GoNavi-License.rtf"
|
||||
$rtfContent = "{\rtf1\ansi\deff0{\fonttbl{\f0 Segoe UI;}}\f0\fs18 $escapedLicense}"
|
||||
Set-Content -LiteralPath $licenseRtf -Value $rtfContent -Encoding ascii
|
||||
|
||||
$sourceExe = (Resolve-Path -LiteralPath $finalExe).Path
|
||||
$wxsPath = (Resolve-Path -LiteralPath "..\\windows\\installer.wxs").Path
|
||||
$iconPath = (Resolve-Path -LiteralPath "..\\windows\\icon.ico").Path
|
||||
$installMarkerPath = Join-Path $env:RUNNER_TEMP ".gonavi-msi-install"
|
||||
Set-Content -LiteralPath $installMarkerPath -Value "MSI" -Encoding ascii -NoNewline
|
||||
$installMarkerPath = (Resolve-Path -LiteralPath $installMarkerPath).Path
|
||||
$wixIntermediateDir = Join-Path $env:RUNNER_TEMP "wixobj-$archName"
|
||||
Write-Host "📦 生成 Windows MSI 安装程序 $msiName (version=$productVersion arch=$wixArch)..."
|
||||
wix build $wxsPath `
|
||||
-arch $wixArch `
|
||||
-ext "WixToolset.UI.wixext/6.0.2" `
|
||||
-d "ProductName=GoNavi" `
|
||||
-d "ProductVersion=$productVersion" `
|
||||
-d "UpgradeCode=CDD6BF2F-ED1E-4345-A0AB-DCDB7E15FB23" `
|
||||
-d "ComponentGuid=$componentGuid" `
|
||||
-d "InstallFolderName=GoNavi" `
|
||||
-d "RegistryKeyName=GoNavi" `
|
||||
-d "SourceExe=$sourceExe" `
|
||||
-d "InstallMarker=$installMarkerPath" `
|
||||
-d "IconPath=$iconPath" `
|
||||
-d "LicenseRtf=$licenseRtf" `
|
||||
-pdbtype none `
|
||||
-intermediatefolder $wixIntermediateDir `
|
||||
-out "..\\..\\$msiName"
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "WiX MSI 构建失败,退出码: $LASTEXITCODE"
|
||||
}
|
||||
|
||||
# Linux Packaging (tar.gz and AppImage)
|
||||
- name: Package Linux
|
||||
@@ -962,6 +1040,7 @@ jobs:
|
||||
path: |
|
||||
GoNavi-*.dmg
|
||||
GoNavi-*.exe
|
||||
GoNavi-*.msi
|
||||
GoNavi-*.tar.gz
|
||||
GoNavi-*.AppImage
|
||||
drivers/**
|
||||
|
||||
76
build/windows/installer.wxs
Normal file
76
build/windows/installer.wxs
Normal file
@@ -0,0 +1,76 @@
|
||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"
|
||||
xmlns:ui="http://wixtoolset.org/schemas/v4/wxs/ui">
|
||||
<Package Name="$(var.ProductName)"
|
||||
Manufacturer="Syngnat"
|
||||
Language="1033"
|
||||
Version="$(var.ProductVersion)"
|
||||
ProductCode="*"
|
||||
UpgradeCode="$(var.UpgradeCode)"
|
||||
Scope="perMachine"
|
||||
InstallerVersion="500"
|
||||
Compressed="yes">
|
||||
<MajorUpgrade AllowDowngrades="yes"
|
||||
Schedule="afterInstallInitialize" />
|
||||
<MediaTemplate EmbedCab="yes" CompressionLevel="high" />
|
||||
|
||||
<Icon Id="GoNaviIcon" SourceFile="$(var.IconPath)" />
|
||||
<Property Id="ARPPRODUCTICON" Value="GoNaviIcon" />
|
||||
<Property Id="ARPURLINFOABOUT" Value="https://github.com/Syngnat/GoNavi" />
|
||||
<SetProperty Id="ARPINSTALLLOCATION" Value="[INSTALLFOLDER]" After="CostFinalize" />
|
||||
<WixVariable Id="WixUILicenseRtf" Value="$(var.LicenseRtf)" />
|
||||
|
||||
<Feature Id="MainFeature" Title="$(var.ProductName)" Level="1">
|
||||
<ComponentRef Id="MainExecutable" />
|
||||
</Feature>
|
||||
|
||||
<Component Id="MainExecutable"
|
||||
Directory="INSTALLFOLDER"
|
||||
Guid="$(var.ComponentGuid)"
|
||||
Bitness="always64">
|
||||
<File Id="GoNaviExe"
|
||||
Source="$(var.SourceExe)"
|
||||
Name="GoNavi.exe"
|
||||
KeyPath="yes">
|
||||
<Shortcut Id="StartMenuShortcut"
|
||||
Directory="ProgramMenuFolder"
|
||||
Name="$(var.ProductName)"
|
||||
WorkingDirectory="INSTALLFOLDER"
|
||||
Icon="GoNaviIcon"
|
||||
Advertise="no" />
|
||||
<Shortcut Id="DesktopShortcut"
|
||||
Directory="DesktopFolder"
|
||||
Name="$(var.ProductName)"
|
||||
WorkingDirectory="INSTALLFOLDER"
|
||||
Icon="GoNaviIcon"
|
||||
Advertise="no" />
|
||||
</File>
|
||||
<File Id="GoNaviMsiInstallMarker"
|
||||
Source="$(var.InstallMarker)"
|
||||
Name=".gonavi-msi-install" />
|
||||
<RegistryValue Root="HKLM"
|
||||
Key="Software\Syngnat\$(var.RegistryKeyName)"
|
||||
Name="InstallType"
|
||||
Value="MSI"
|
||||
Type="string"
|
||||
KeyPath="no" />
|
||||
<RegistryValue Root="HKLM"
|
||||
Key="Software\Syngnat\$(var.RegistryKeyName)"
|
||||
Name="InstallPath"
|
||||
Value="[INSTALLFOLDER]GoNavi.exe"
|
||||
Type="string"
|
||||
KeyPath="no" />
|
||||
</Component>
|
||||
|
||||
<ui:WixUI Id="WixUI_InstallDir"
|
||||
InstallDirectory="INSTALLFOLDER"
|
||||
ExtendedPathValidation="yes" />
|
||||
</Package>
|
||||
|
||||
<Fragment>
|
||||
<StandardDirectory Id="ProgramFiles6432Folder">
|
||||
<Directory Id="INSTALLFOLDER" Name="$(var.InstallFolderName)" />
|
||||
</StandardDirectory>
|
||||
<StandardDirectory Id="ProgramMenuFolder" />
|
||||
<StandardDirectory Id="DesktopFolder" />
|
||||
</Fragment>
|
||||
</Wix>
|
||||
@@ -14,10 +14,13 @@ class GenerateUpdateLatestManifestTest(unittest.TestCase):
|
||||
def test_generates_manifest_with_sha256(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
assets = Path(tmp)
|
||||
exe = assets / "GoNavi-1.2.3-Windows-Amd64.exe"
|
||||
exe = assets / "GoNavi-1.2.3-Windows-Amd64-Portable.exe"
|
||||
msi = assets / "GoNavi-1.2.3-Windows-Amd64-Installer.msi"
|
||||
exe.write_bytes(b"fake-binary")
|
||||
msi.write_bytes(b"fake-installer")
|
||||
(assets / "SHA256SUMS").write_text(
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa GoNavi-1.2.3-Windows-Amd64.exe\n",
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa GoNavi-1.2.3-Windows-Amd64-Portable.exe\n"
|
||||
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb GoNavi-1.2.3-Windows-Amd64-Installer.msi\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
out = assets / "latest.json"
|
||||
@@ -43,14 +46,26 @@ class GenerateUpdateLatestManifestTest(unittest.TestCase):
|
||||
self.assertEqual(data["channel"], "latest")
|
||||
self.assertEqual(data["version"], "1.2.3")
|
||||
self.assertEqual(data["tagName"], "v1.2.3")
|
||||
self.assertEqual(len(data["assets"]), 1)
|
||||
asset = data["assets"][0]
|
||||
self.assertEqual(asset["name"], "GoNavi-1.2.3-Windows-Amd64.exe")
|
||||
assets_by_name = {asset["name"]: asset for asset in data["assets"]}
|
||||
self.assertEqual(
|
||||
asset["url"],
|
||||
"https://github.com/Syngnat/GoNavi/releases/download/v1.2.3/GoNavi-1.2.3-Windows-Amd64.exe",
|
||||
set(assets_by_name),
|
||||
{
|
||||
"GoNavi-1.2.3-Windows-Amd64-Portable.exe",
|
||||
"GoNavi-1.2.3-Windows-Amd64-Installer.msi",
|
||||
},
|
||||
)
|
||||
self.assertEqual(asset["sha256"], "a" * 64)
|
||||
portable_asset = assets_by_name["GoNavi-1.2.3-Windows-Amd64-Portable.exe"]
|
||||
self.assertEqual(
|
||||
portable_asset["url"],
|
||||
"https://github.com/Syngnat/GoNavi/releases/download/v1.2.3/GoNavi-1.2.3-Windows-Amd64-Portable.exe",
|
||||
)
|
||||
self.assertEqual(portable_asset["sha256"], "a" * 64)
|
||||
installer_asset = assets_by_name["GoNavi-1.2.3-Windows-Amd64-Installer.msi"]
|
||||
self.assertEqual(
|
||||
installer_asset["url"],
|
||||
"https://github.com/Syngnat/GoNavi/releases/download/v1.2.3/GoNavi-1.2.3-Windows-Amd64-Installer.msi",
|
||||
)
|
||||
self.assertEqual(installer_asset["sha256"], "b" * 64)
|
||||
self.assertNotIn("SHA256SUMS", [a["name"] for a in data["assets"]])
|
||||
|
||||
|
||||
|
||||
93
tools/windows-release-artifacts.test.py
Normal file
93
tools/windows-release-artifacts.test.py
Normal file
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env python3
|
||||
import unittest
|
||||
import xml.etree.ElementTree as ET
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
WORKFLOWS = (
|
||||
ROOT / ".github" / "workflows" / "release.yml",
|
||||
ROOT / ".github" / "workflows" / "dev-build.yml",
|
||||
)
|
||||
INSTALLER = ROOT / "build" / "windows" / "installer.wxs"
|
||||
WIX_NAMESPACE = "http://wixtoolset.org/schemas/v4/wxs"
|
||||
UPGRADE_CODE = "CDD6BF2F-ED1E-4345-A0AB-DCDB7E15FB23"
|
||||
AMD64_COMPONENT_GUID = "0BCEE70B-9CF2-449C-9ADE-190188493234"
|
||||
ARM64_COMPONENT_GUID = "7F4C7757-329F-42B0-B312-D4B8AD50415E"
|
||||
|
||||
|
||||
class WindowsReleaseArtifactsTest(unittest.TestCase):
|
||||
def test_release_workflows_publish_portable_and_msi_assets(self) -> None:
|
||||
for workflow in WORKFLOWS:
|
||||
with self.subTest(workflow=workflow.name):
|
||||
source = workflow.read_text(encoding="utf-8")
|
||||
self.assertIn("Package Windows Portable EXE and MSI", source)
|
||||
self.assertIn("-Portable.exe", source)
|
||||
self.assertIn("-Installer.msi", source)
|
||||
self.assertIn("GoNavi-*.msi", source)
|
||||
self.assertIn("dotnet tool install wix --tool-path $wixTools --version $wixVersion", source)
|
||||
self.assertIn('WixToolset.UI.wixext/$wixVersion', source)
|
||||
self.assertIn('WixToolset.UI.wixext/6.0.2', source)
|
||||
self.assertIn("-arch $wixArch", source)
|
||||
self.assertIn("TestExpectedAssetNameForWindowsInstallMode", source)
|
||||
self.assertIn("TestInstallUpdateAndRestartMSI", source)
|
||||
self.assertIn("TestBuildWindowsMSIUpdatePowerShellScript", source)
|
||||
self.assertIn('-d "ProductName=GoNavi"', source)
|
||||
self.assertIn(f'-d "UpgradeCode={UPGRADE_CODE}"', source)
|
||||
self.assertIn('-d "InstallFolderName=GoNavi"', source)
|
||||
self.assertIn('-d "RegistryKeyName=GoNavi"', source)
|
||||
self.assertIn(f'"{AMD64_COMPONENT_GUID}"', source)
|
||||
self.assertIn(f'"{ARM64_COMPONENT_GUID}"', source)
|
||||
self.assertIn('$installMarkerPath = Join-Path $env:RUNNER_TEMP ".gonavi-msi-install"', source)
|
||||
self.assertIn('-d "InstallMarker=$installMarkerPath"', source)
|
||||
self.assertNotIn('-d "ProductName=GoNavi Dev"', source)
|
||||
self.assertNotIn('-d "InstallFolderName=GoNavi Dev"', source)
|
||||
self.assertNotIn('-d "RegistryKeyName=GoNavi Dev"', source)
|
||||
self.assertNotIn("DA3DACF2-1E4B-428C-8E6F-A37D3A05CAF7", source)
|
||||
self.assertNotIn(
|
||||
'$finalExeName = "GoNavi-$version-${{ matrix.os_name }}-${{ matrix.arch_name }}${{ matrix.artifact_suffix }}.exe"',
|
||||
source,
|
||||
)
|
||||
|
||||
dev_source = WORKFLOWS[1].read_text(encoding="utf-8")
|
||||
self.assertIn('$productVersion = "255.0.$runNumber"', dev_source)
|
||||
self.assertNotIn('$productVersion = "0.0.$runNumber"', dev_source)
|
||||
|
||||
def test_installer_declares_upgrade_shortcuts_and_uninstall_metadata(self) -> None:
|
||||
root = ET.parse(INSTALLER).getroot()
|
||||
ns = {"wix": WIX_NAMESPACE}
|
||||
package = root.find("wix:Package", ns)
|
||||
self.assertIsNotNone(package)
|
||||
assert package is not None
|
||||
self.assertEqual(package.attrib["Scope"], "perMachine")
|
||||
self.assertEqual(package.attrib["InstallerVersion"], "500")
|
||||
self.assertEqual(package.attrib["ProductCode"], "*")
|
||||
major_upgrade = package.find("wix:MajorUpgrade", ns)
|
||||
self.assertIsNotNone(major_upgrade)
|
||||
assert major_upgrade is not None
|
||||
self.assertEqual(major_upgrade.attrib["AllowDowngrades"], "yes")
|
||||
self.assertEqual(major_upgrade.attrib["Schedule"], "afterInstallInitialize")
|
||||
self.assertNotIn("AllowSameVersionUpgrades", major_upgrade.attrib)
|
||||
self.assertNotIn("DowngradeErrorMessage", major_upgrade.attrib)
|
||||
self.assertIsNotNone(package.find("wix:MediaTemplate", ns))
|
||||
self.assertIsNotNone(package.find("wix:Property[@Id='ARPPRODUCTICON']", ns))
|
||||
self.assertIsNotNone(package.find("wix:SetProperty[@Id='ARPINSTALLLOCATION']", ns))
|
||||
self.assertIsNotNone(package.find(".//wix:Shortcut[@Id='StartMenuShortcut']", ns))
|
||||
self.assertIsNotNone(package.find(".//wix:Shortcut[@Id='DesktopShortcut']", ns))
|
||||
marker = package.find(".//wix:RegistryValue[@Name='InstallType']", ns)
|
||||
self.assertIsNotNone(marker)
|
||||
assert marker is not None
|
||||
self.assertEqual(marker.attrib["Value"], "MSI")
|
||||
install_path = package.find(".//wix:RegistryValue[@Name='InstallPath']", ns)
|
||||
self.assertIsNotNone(install_path)
|
||||
assert install_path is not None
|
||||
self.assertEqual(install_path.attrib["Value"], "[INSTALLFOLDER]GoNavi.exe")
|
||||
file_marker = package.find(".//wix:File[@Id='GoNaviMsiInstallMarker']", ns)
|
||||
self.assertIsNotNone(file_marker)
|
||||
assert file_marker is not None
|
||||
self.assertEqual(file_marker.attrib["Source"], "$(var.InstallMarker)")
|
||||
self.assertEqual(file_marker.attrib["Name"], ".gonavi-msi-install")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user