mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-09-20 23:44:13 +02:00
b98589d3c7
windows-build.yml: - Remove the Qt5 build job (build-windows). Qt6 is now the sole Windows track built in CI. - publish-nightly-assets: only delete/replace .exe and .zip assets tagged "qt6" on the nightly release. Assets without "qt6" in the name (the last Qt5 build ever published) are left untouched and stay downloadable indefinitely as a frozen legacy build. - Release notes: drop the "Try Qt6 — soon the only track" notice, add a line explaining the Qt5 (frozen) vs Qt6 (maintained) split. windows-msi.yml: - Matrix reduced to the single "qt6" entry (flavor string kept as "qt6", not renamed, since it seeds the MSI ProductCode and a rename would break upgrade detection for existing installs). - Sign the Qt6 MSI via SignPath (previously Qt5-only). Guard is now just the upstream-repo fork check; no per-flavor exclusion left. - deploy-pages: also detect legacy (non-"qt6") release assets and pass them to generate-page.py as LEGACY_INSTALLER_URL / LEGACY_PORTABLE_URL / LEGACY_MSI_URL. generate-page.py: - Drop the old Qt5/Qt6 dual-track rendering; INSTALLER_URL / PORTABLE_URL / MSI_URL now point at the Qt6 build directly. - Add an optional "Windows — x86_64 — Qt5 (legacy, unmaintained)" card, rendered only when LEGACY_* URLs are set, with a frozen/ no-longer-updated notice. Before merging: manually trigger the current (pre-merge) "Windows Build" + "Windows MSI" workflows once to publish an up-to-date, signed Qt5 snapshot — that run becomes the frozen legacy reference, since the Qt5 job won't exist to re-run afterwards. No changes to QElectroTech.wxs (Qt5/Qt6-agnostic, only QtPlatformArgs varies and is already handled at the CI level).
430 lines
18 KiB
YAML
430 lines
18 KiB
YAML
name: Windows MSI (WiX v7)
|
|
|
|
on:
|
|
# Triggered automatically after a successful Windows build on master
|
|
workflow_run:
|
|
workflows: ["Windows Build"]
|
|
types: [completed]
|
|
branches: [master]
|
|
# Manual trigger still available (e.g. for a specific run_id)
|
|
workflow_dispatch:
|
|
inputs:
|
|
run_id:
|
|
description: "Run ID of 'Windows Build' (leave empty for automatic)"
|
|
required: false
|
|
default: ""
|
|
|
|
jobs:
|
|
build-msi:
|
|
name: Build MSI with WiX v7 (${{ matrix.flavor }})
|
|
runs-on: windows-latest
|
|
# Only runs if Windows Build succeeded (or triggered manually)
|
|
if: >
|
|
github.event_name == 'workflow_dispatch' ||
|
|
github.event.workflow_run.conclusion == 'success'
|
|
|
|
strategy:
|
|
fail-fast: false
|
|
# Single-entry matrix kept on purpose (rather than flattening the job):
|
|
# matrix.flavor is used as part of the MSI ProductCode seed, so changing
|
|
# it would generate a new ProductCode and break upgrade detection for
|
|
# existing installs. Keeping "qt6" here preserves continuity.
|
|
matrix:
|
|
include:
|
|
- flavor: qt6
|
|
portable_artifact: qelectrotech-windows-portable-qt6
|
|
version_source: hardcoded # see note in "Extract version" step
|
|
label_suffix: "-qt6"
|
|
experimental: false
|
|
|
|
permissions:
|
|
contents: write
|
|
pages: write
|
|
id-token: write # Required by SignPath
|
|
|
|
outputs:
|
|
qt6_msi: ${{ steps.export.outputs.msi_name_qt6 }}
|
|
|
|
steps:
|
|
# ----------------------------------------------------------------
|
|
# 1. Checkout (to retrieve QElectroTech.wxs and sources)
|
|
# ----------------------------------------------------------------
|
|
- name: Checkout
|
|
uses: actions/checkout@v7
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
# ----------------------------------------------------------------
|
|
# 2. Download the portable artifact for this flavor
|
|
# ----------------------------------------------------------------
|
|
- name: Download portable artifact
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: ${{ matrix.portable_artifact }}
|
|
path: artifact\files
|
|
# workflow_run => use the triggering run's ID
|
|
# workflow_dispatch => use input run_id if provided, otherwise current run
|
|
run-id: ${{ github.event.workflow_run.id || github.event.inputs.run_id || github.run_id }}
|
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
repository: ${{ github.repository }}
|
|
|
|
# ----------------------------------------------------------------
|
|
# 3. Extract version
|
|
# Qt6: hardcoded "0.200.1" here — deliberately NOT parsed from the
|
|
# binary/source, since Qt-version self-detection inside the
|
|
# compiled code proved unreliable (see commit e1aa65f). The CI
|
|
# matrix entry already knows for certain which flavor it is
|
|
# packaging, so the version label is set explicitly at the
|
|
# packaging level instead.
|
|
# ----------------------------------------------------------------
|
|
- name: Extract version
|
|
id: version
|
|
shell: pwsh
|
|
run: |
|
|
if ("${{ matrix.version_source }}" -eq "hardcoded") {
|
|
$ver = "0.200.1"
|
|
} else {
|
|
$src = Get-Content "sources\qetversion.cpp" -Raw -ErrorAction SilentlyContinue
|
|
if ($src -match 'return QVersionNumber\{([^}]+)\}') {
|
|
$ver = $Matches[1] -replace '\s','' -replace ',','.'
|
|
} else {
|
|
# Fallback: CMakeLists.txt
|
|
$cmake = Get-Content "CMakeLists.txt" -Raw
|
|
if ($cmake -match 'project\s*\([^)]*VERSION\s+([\d]+\.[\d]+\.[\d]+)') {
|
|
$ver = $Matches[1]
|
|
} else {
|
|
$ver = "0.0.0"
|
|
}
|
|
}
|
|
}
|
|
|
|
# Short SHA for the display version
|
|
$sha = git rev-parse --short HEAD 2>$null
|
|
if (-not $sha) { $sha = "unknown" }
|
|
|
|
# Cumulative revision number (same calculation as windows-build.yml)
|
|
$count = git rev-list HEAD --count 2>$null
|
|
$rev = [int]$count + 473
|
|
|
|
# MSI Version only compares Major.Minor.Build (the 4th field is
|
|
# ignored by Windows Installer for upgrade detection), so we
|
|
# inject rev into Build to force a real increase between
|
|
# nightlies and trigger MajorUpgrade's automatic uninstall.
|
|
$verParts = $ver -split '\.'
|
|
$verMsi = "$($verParts[0]).$($verParts[1]).$($rev % 65535)"
|
|
|
|
$flavorTag = "${{ matrix.label_suffix }}"
|
|
$verDisplay = "${ver}${flavorTag}-r${rev}-${sha}_x86_64-win64"
|
|
|
|
echo "VERSION_MSI=$verMsi" >> $env:GITHUB_OUTPUT
|
|
echo "VERSION_DISPLAY=$verDisplay" >> $env:GITHUB_OUTPUT
|
|
Write-Host "Version MSI : $verMsi"
|
|
Write-Host "Version display : $verDisplay"
|
|
|
|
# Qt platform argument: kept from the Qt5 era (QTBUG-83161
|
|
# font-rendering workaround, GDI backend). Qt6 uses DirectWrite by
|
|
# default and does not need it, so this always resolves empty now.
|
|
if ("${{ matrix.flavor }}" -eq "qt5") {
|
|
$qtArgs = "-platform windows:fontengine=freetype"
|
|
} else {
|
|
$qtArgs = ""
|
|
}
|
|
echo "QT_ARGS=$qtArgs" >> $env:GITHUB_OUTPUT
|
|
Write-Host "Qt platform args: '$qtArgs'"
|
|
|
|
# ----------------------------------------------------------------
|
|
# 4. Install WiX v7, accept EULA and install WixUI extension
|
|
# ----------------------------------------------------------------
|
|
- name: Install WiX v7
|
|
shell: pwsh
|
|
run: |
|
|
dotnet tool install --global wix --version 7.0.0
|
|
$toolsPath = [System.IO.Path]::Combine($env:USERPROFILE, '.dotnet', 'tools')
|
|
$env:PATH = "$toolsPath;$env:PATH"
|
|
echo $toolsPath >> $env:GITHUB_PATH
|
|
wix eula accept wix7
|
|
wix extension add WixToolset.UI.wixext/7.0.0
|
|
wix extension add WixToolset.Util.wixext/7.0.0
|
|
Write-Host "WiX v7 installed, EULA accepted, UI + Util extensions added."
|
|
|
|
# ----------------------------------------------------------------
|
|
# 5. Check that the WXS file exists in the repository
|
|
# ----------------------------------------------------------------
|
|
- name: Check WXS file
|
|
shell: pwsh
|
|
run: |
|
|
$wxs = "build-aux\windows\QElectroTech.wxs"
|
|
if (-not (Test-Path $wxs)) {
|
|
Write-Error "WXS file not found: $wxs"
|
|
exit 1
|
|
}
|
|
Write-Host "WXS found: $wxs"
|
|
|
|
# ----------------------------------------------------------------
|
|
# 6. Check the artifact structure and locate files/
|
|
# ----------------------------------------------------------------
|
|
- name: Check artifact structure
|
|
shell: pwsh
|
|
run: |
|
|
$exe = Get-ChildItem -Path "artifact\files" -Filter "qelectrotech.exe" -Recurse | Select-Object -First 1
|
|
if (-not $exe) {
|
|
$exe = Get-ChildItem -Path "artifact\files" -Filter "QElectroTech.exe" -Recurse | Select-Object -First 1
|
|
}
|
|
if (-not $exe) {
|
|
Write-Error "qelectrotech.exe not found in artifact"
|
|
exit 1
|
|
}
|
|
Write-Host "Executable: $($exe.FullName) ($([math]::Round($exe.Length/1MB,1)) MB)"
|
|
$binDir = $exe.Directory.FullName
|
|
$filesDir = Split-Path $binDir -Parent
|
|
echo "FILES_DIR=$filesDir" >> $env:GITHUB_ENV
|
|
Write-Host "FILES_DIR: $filesDir"
|
|
|
|
# ----------------------------------------------------------------
|
|
# 7. Convert LICENSE (GPL-2) to RTF for the WixUI licence screen
|
|
# ----------------------------------------------------------------
|
|
- name: Convert LICENSE to RTF
|
|
shell: pwsh
|
|
run: |
|
|
$licSrc = "LICENSE"
|
|
$licRtf = "$env:TEMP\License.rtf"
|
|
if (-not (Test-Path $licSrc)) {
|
|
Write-Error "LICENSE file not found in repository root"
|
|
exit 1
|
|
}
|
|
$lines = Get-Content $licSrc -Encoding UTF8
|
|
$rtf = New-Object System.Text.StringBuilder
|
|
[void]$rtf.AppendLine('{\rtf1\ansi\ansicpg1252\deff0')
|
|
[void]$rtf.AppendLine('{\fonttbl{\f0\fmodern\fprq1\fcharset0 Courier New;}}')
|
|
[void]$rtf.AppendLine('{\colortbl;\red0\green0\blue0;}')
|
|
[void]$rtf.AppendLine('\f0\fs18\cf1')
|
|
foreach ($line in $lines) {
|
|
$escaped = $line `
|
|
-replace '\\', '\\\\' `
|
|
-replace '\{', '\{' `
|
|
-replace '\}', '\}'
|
|
[void]$rtf.AppendLine("$escaped\par")
|
|
}
|
|
[void]$rtf.AppendLine('}')
|
|
[System.IO.File]::WriteAllText($licRtf, $rtf.ToString(), [System.Text.Encoding]::ASCII)
|
|
echo "LICENSE_RTF=$licRtf" >> $env:GITHUB_ENV
|
|
Write-Host "License.rtf generated: $licRtf"
|
|
|
|
# ----------------------------------------------------------------
|
|
# 8. Replace Lancer QET.bat with the MSI-specific version
|
|
# ----------------------------------------------------------------
|
|
- name: Replace Lancer QET.bat for MSI
|
|
shell: pwsh
|
|
run: |
|
|
$qtArgs = "${{ steps.version.outputs.QT_ARGS }}"
|
|
$bat = "$env:FILES_DIR\Lancer QET.bat"
|
|
$content = "@echo off`r`nstart `"`" `"%~dp0bin\qelectrotech.exe`" --common-elements-dir=`"%~dp0elements/`" --common-tbt-dir=`"%~dp0titleblocks/`" --lang-dir=`"%~dp0lang/`" $qtArgs`r`n"
|
|
[System.IO.File]::WriteAllText($bat, $content, [System.Text.Encoding]::ASCII)
|
|
Write-Host "Lancer QET.bat replaced for MSI installation (qtArgs: '$qtArgs')."
|
|
|
|
# ----------------------------------------------------------------
|
|
# 9. Build the MSI (unsigned at this stage — signing happens below)
|
|
# Qt6 keeps its own ProductCode seed (distinct from the retired Qt5
|
|
# MSI), so a machine that still has the old Qt5 MSI installed gets a
|
|
# separate, coexisting install rather than an unexpected upgrade.
|
|
# ----------------------------------------------------------------
|
|
- name: Build MSI
|
|
shell: pwsh
|
|
run: |
|
|
$version = "${{ steps.version.outputs.VERSION_MSI }}"
|
|
$verDisplay = "${{ steps.version.outputs.VERSION_DISPLAY }}"
|
|
$filesDir = $env:FILES_DIR
|
|
$licRtf = $env:LICENSE_RTF
|
|
$wxs = "build-aux\windows\QElectroTech.wxs"
|
|
$outputName = "QElectroTech-${verDisplay}.msi"
|
|
|
|
$seed = "qelectrotech-msi-${{ matrix.flavor }}-$version"
|
|
$sha1 = [System.Security.Cryptography.SHA1]::Create()
|
|
$hash = $sha1.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($seed))
|
|
$hash[6] = ($hash[6] -band 0x0F) -bor 0x50
|
|
$hash[8] = ($hash[8] -band 0x3F) -bor 0x80
|
|
$productCode = "{$([System.Guid]::new([byte[]]$hash[0..15]).ToString().ToUpper())}"
|
|
|
|
New-Item -ItemType Directory -Force -Path "dist" | Out-Null
|
|
|
|
Write-Host "=== wix build (${{ matrix.flavor }}) ==="
|
|
Write-Host " Version : $version"
|
|
Write-Host " ProductCode : $productCode"
|
|
Write-Host " Output : dist\$outputName"
|
|
|
|
$qtArgs = "${{ steps.version.outputs.QT_ARGS }}"
|
|
|
|
wix build $wxs `
|
|
-arch x64 `
|
|
-d "Version=$version" `
|
|
-d "ProductVersion=$verDisplay" `
|
|
-d "ProductCode=$productCode" `
|
|
-d "FilesDir=$filesDir" `
|
|
-d "LicenseRtf=$licRtf" `
|
|
-d QtPlatformArgs="$qtArgs" `
|
|
-ext WixToolset.UI.wixext `
|
|
-ext WixToolset.Util.wixext `
|
|
-o "dist\$outputName"
|
|
|
|
if (-not (Test-Path "dist\$outputName")) {
|
|
Write-Error "MSI not generated: dist\$outputName"
|
|
exit 1
|
|
}
|
|
|
|
$size = [math]::Round((Get-Item "dist\$outputName").Length / 1MB, 1)
|
|
Write-Host "MSI generated: dist\$outputName ($size MB) OK"
|
|
echo "MSI_NAME=$outputName" >> $env:GITHUB_ENV
|
|
|
|
- name: Upload unsigned MSI artifact (pre-signing)
|
|
id: upload_unsigned
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: qelectrotech-windows-msi-unsigned-${{ matrix.flavor }}
|
|
path: dist\*.msi
|
|
retention-days: 1
|
|
if-no-files-found: error
|
|
|
|
# Qt6 is now signed too (previously excluded while Qt5 was the stable
|
|
# track and Qt6 was experimental-only). The remaining guard is the fork
|
|
# check: forks never have the SignPath secrets, and a fork-originated
|
|
# PR/run must never attempt a signing request.
|
|
# (cf. DieterMayerOSS:fix/msi-signing-fork-guard, d3f60c88)
|
|
- name: Sign MSI via SignPath
|
|
if: github.repository == 'qelectrotech/qelectrotech-source-mirror'
|
|
uses: signpath/github-action-submit-signing-request@v2
|
|
with:
|
|
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
|
|
organization-id: ${{ secrets.SIGNPATH_ORGANIZATION_ID }}
|
|
project-slug: 'qelectrotech-source-mirror'
|
|
signing-policy-slug: 'test-signing'
|
|
artifact-configuration-slug: 'MSI'
|
|
github-artifact-id: ${{ steps.upload_unsigned.outputs.artifact-id }}
|
|
wait-for-completion: true
|
|
output-artifact-directory: 'dist\'
|
|
|
|
- name: Upload signed MSI artifact
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: qelectrotech-windows-msi-${{ matrix.flavor }}
|
|
path: dist\*.msi
|
|
retention-days: 40
|
|
if-no-files-found: error
|
|
|
|
- name: Delete old nightly .msi asset
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
REPO: ${{ github.repository }}
|
|
run: |
|
|
$pattern = "-qt6.*\\.msi$"
|
|
$names = gh release view nightly --repo $env:REPO --json assets --jq ".assets[] | select(.name | test(`"$pattern`")) | .name"
|
|
foreach ($name in ($names -split "`n" | Where-Object { $_ })) {
|
|
Write-Host "Deleting old asset: $name"
|
|
gh release delete-asset nightly $name --repo $env:REPO --yes
|
|
}
|
|
shell: pwsh
|
|
|
|
- name: Upload MSI to nightly release
|
|
uses: softprops/action-gh-release@v3
|
|
with:
|
|
tag_name: nightly
|
|
files: dist/*.msi
|
|
fail_on_unmatched_files: true
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Export MSI name for page generation
|
|
id: export
|
|
shell: pwsh
|
|
run: |
|
|
$name = "$env:MSI_NAME"
|
|
echo "msi_name_qt6=$name" >> $env:GITHUB_OUTPUT
|
|
|
|
- name: Summary
|
|
if: always()
|
|
shell: pwsh
|
|
run: |
|
|
Write-Host "=== MSI build summary (${{ matrix.flavor }}) ==="
|
|
Write-Host "Version : ${{ steps.version.outputs.VERSION_DISPLAY }}"
|
|
Write-Host "Signed : true"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Job 2 : Génère et déploie la page GitHub Pages une fois le MSI publié,
|
|
# pour que toutes les URLs soient connues.
|
|
# ---------------------------------------------------------------------------
|
|
deploy-pages:
|
|
needs: build-msi
|
|
runs-on: ubuntu-latest
|
|
if: always() && needs.build-msi.result == 'success'
|
|
permissions:
|
|
contents: write
|
|
pages: write
|
|
id-token: write
|
|
|
|
steps:
|
|
- name: Checkout master (for generate-page.py)
|
|
uses: actions/checkout@v7
|
|
with:
|
|
ref: master
|
|
path: source
|
|
sparse-checkout: build-aux/generate-page.py
|
|
sparse-checkout-cone-mode: false
|
|
|
|
- name: Generate download page (index.html)
|
|
shell: bash
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
set -euo pipefail
|
|
REPO="${{ github.repository }}"
|
|
|
|
ASSETS=$(gh release view nightly --repo "$REPO" --json assets --jq '.assets[].name')
|
|
|
|
EXE_NAME=$(echo "$ASSETS" | grep 'qt6' | grep '\.exe$' | head -1)
|
|
ZIP_NAME=$(echo "$ASSETS" | grep 'qt6' | grep '\.zip$' | head -1)
|
|
MSI_NAME=$(echo "$ASSETS" | grep 'qt6' | grep '\.msi$' | head -1 || echo "")
|
|
|
|
# Legacy Qt5 assets (no "qt6" in the name): last ever published,
|
|
# frozen — windows-build.yml/windows-msi.yml no longer delete or
|
|
# replace these, so they keep pointing at the same files release
|
|
# after release. Rendered as a separate "legacy" section if present.
|
|
LEGACY_EXE_NAME=$(echo "$ASSETS" | grep -v 'qt6' | grep '\.exe$' | head -1 || echo "")
|
|
LEGACY_ZIP_NAME=$(echo "$ASSETS" | grep -v 'qt6' | grep '\.zip$' | head -1 || echo "")
|
|
LEGACY_MSI_NAME=$(echo "$ASSETS" | grep -v 'qt6' | grep '\.msi$' | head -1 || echo "")
|
|
|
|
BASE="https://github.com/$REPO/releases/download/nightly"
|
|
INSTALLER_URL="$BASE/$EXE_NAME"
|
|
PORTABLE_URL="$BASE/$ZIP_NAME"
|
|
MSI_URL=""
|
|
[ -n "$MSI_NAME" ] && MSI_URL="$BASE/$MSI_NAME"
|
|
|
|
LEGACY_INSTALLER_URL=""
|
|
LEGACY_PORTABLE_URL=""
|
|
LEGACY_MSI_URL=""
|
|
[ -n "$LEGACY_EXE_NAME" ] && LEGACY_INSTALLER_URL="$BASE/$LEGACY_EXE_NAME"
|
|
[ -n "$LEGACY_ZIP_NAME" ] && LEGACY_PORTABLE_URL="$BASE/$LEGACY_ZIP_NAME"
|
|
[ -n "$LEGACY_MSI_NAME" ] && LEGACY_MSI_URL="$BASE/$LEGACY_MSI_NAME"
|
|
|
|
SHA="${{ github.event.workflow_run.head_sha || github.sha }}"
|
|
SHORT="${SHA:0:7}"
|
|
DATE=$(date -u '+%Y-%m-%d %H:%M UTC')
|
|
RUN_URL="https://github.com/$REPO/actions/runs/${{ github.run_id }}"
|
|
RUN_NUMBER="${{ github.run_number }}"
|
|
|
|
export DATE SHORT REPO SHA RUN_URL RUN_NUMBER
|
|
export INSTALLER_URL PORTABLE_URL MSI_URL
|
|
export LEGACY_INSTALLER_URL LEGACY_PORTABLE_URL LEGACY_MSI_URL
|
|
|
|
python3 source/build-aux/generate-page.py
|
|
|
|
- name: Add .nojekyll
|
|
shell: bash
|
|
run: touch gh-pages/.nojekyll
|
|
|
|
- name: Upload GitHub Pages artifact
|
|
uses: actions/upload-pages-artifact@v5
|
|
with:
|
|
path: gh-pages/
|
|
|
|
- name: Deploy to GitHub Pages
|
|
uses: actions/deploy-pages@v4
|