Skip to content

P3-A1: native ARStack Studio binary profile deployment and installer candidate #465

P3-A1: native ARStack Studio binary profile deployment and installer candidate

P3-A1: native ARStack Studio binary profile deployment and installer candidate #465

name: ARStack Studio Release
on:
workflow_dispatch:
pull_request:
paths:
- 'apps/arstack_studio/**'
- 'embedded/esp32p4_smv_injector/**'
- 'include/ariec61850/**'
- 'src/**'
- '.github/workflows/arstack-studio-release.yml'
- '.github/workflows/esp32p4-smv-injector.yml'
push:
tags:
- 'v*'
permissions:
contents: read
concurrency:
group: arstack-studio-release-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
env:
ARSTACK_VERSION: '0.1.1'
QT_VERSION: '6.10.3'
ESPFLASH_VERSION: '4.5.0'
ESPFLASH_WINDOWS_ZIP_SHA256: '854c82c947c20e7f337f120f0398042ed1760f2269cb0f9be3d2beae3b66fbfb'
jobs:
firmware:
uses: ./.github/workflows/esp32p4-smv-injector.yml
windows-package:
name: Windows x64 package
needs: firmware
runs-on: windows-2022
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Validate release version contract
shell: powershell
run: |
$cmake = Get-Content 'apps/arstack_studio/CMakeLists.txt' -Raw
if ($cmake -notmatch "project\(ARStackStudio VERSION $([regex]::Escape($env:ARSTACK_VERSION)) ") {
throw "CMake project version does not match ARSTACK_VERSION=$env:ARSTACK_VERSION"
}
if ($env:GITHUB_REF -like 'refs/tags/*') {
$expected = "v$env:ARSTACK_VERSION"
if ($env:GITHUB_REF_NAME -ne $expected) {
throw "P0 release only accepts tag $expected; got $env:GITHUB_REF_NAME"
}
}
- name: Download verified ESP32-P4 firmware package
uses: actions/download-artifact@v4
with:
name: arstack-esp32p4-smv-firmware
path: firmware-package
- name: Install Qt
uses: jurplel/install-qt-action@v4
with:
version: ${{ env.QT_VERSION }}
arch: win64_msvc2022_64
modules: 'qtserialport'
cache: true
- name: Configure native Release build
shell: powershell
run: |
cmake -S apps/arstack_studio -B build-arstack-studio -G "Visual Studio 17 2022" -A x64 `
-DARIEC61850_WARNINGS_AS_ERRORS=ON
- name: Build native Release
shell: powershell
run: |
cmake --build build-arstack-studio --config Release `
--target arstack_studio arstack_session_harness arstack_windows_ownership_harness --parallel
- name: S8A/S8B native Windows integration gates
timeout-minutes: 2
shell: powershell
run: |
$sessionHarness = Join-Path $PWD 'build-arstack-studio\Release\arstack_session_harness.exe'
& $sessionHarness
$sessionExit = $LASTEXITCODE
if ($sessionExit -ne 0) {
throw "S8A deterministic session harness on Windows failed with exit code $sessionExit"
}
$ownershipHarness = Join-Path $PWD 'build-arstack-studio\Release\arstack_windows_ownership_harness.exe'
& $ownershipHarness
$ownershipExit = $LASTEXITCODE
if ($ownershipExit -ne 0) {
throw "S8B Windows ownership harness failed with exit code $ownershipExit"
}
- name: Stage Qt application
shell: powershell
run: |
$stage = Join-Path $PWD 'dist\ARStack-Studio'
New-Item -ItemType Directory -Force $stage | Out-Null
$sourceExe = Join-Path $PWD 'build-arstack-studio\Release\arstack_studio.exe'
if (-not (Test-Path $sourceExe)) { throw "Release executable missing: $sourceExe" }
$stageExe = Join-Path $stage 'ARStackStudio.exe'
Copy-Item $sourceExe $stageExe
windeployqt --release --qmldir "$PWD\apps\arstack_studio\qml" --compiler-runtime $stageExe
Copy-Item LICENSE, NOTICE, THIRD_PARTY_NOTICES.md, COPYRIGHT.md -Destination $stage
New-Item -ItemType Directory -Force (Join-Path $stage 'firmware') | Out-Null
Copy-Item 'firmware-package\*' -Destination (Join-Path $stage 'firmware') -Recurse
- name: Bundle pinned standalone espflash
shell: powershell
run: |
$url = "https://github.com/esp-rs/espflash/releases/download/v$env:ESPFLASH_VERSION/espflash-x86_64-pc-windows-msvc.zip"
$zip = Join-Path $env:RUNNER_TEMP 'espflash.zip'
Invoke-WebRequest -Uri $url -OutFile $zip
$actual = (Get-FileHash $zip -Algorithm SHA256).Hash.ToLowerInvariant()
if ($actual -ne $env:ESPFLASH_WINDOWS_ZIP_SHA256) {
throw "espflash asset SHA-256 mismatch: $actual"
}
$extract = Join-Path $env:RUNNER_TEMP 'espflash'
Expand-Archive -Path $zip -DestinationPath $extract -Force
$exe = Get-ChildItem $extract -Filter espflash.exe -Recurse | Select-Object -First 1
if (-not $exe) { throw 'espflash.exe not found in verified archive' }
$tools = Join-Path $PWD 'dist\ARStack-Studio\tools'
New-Item -ItemType Directory -Force $tools | Out-Null
Copy-Item $exe.FullName (Join-Path $tools 'espflash.exe')
$licenses = Join-Path $PWD 'dist\ARStack-Studio\licenses\espflash'
New-Item -ItemType Directory -Force $licenses | Out-Null
Invoke-WebRequest -Uri "https://raw.githubusercontent.com/esp-rs/espflash/v$env:ESPFLASH_VERSION/LICENSE-APACHE" -OutFile (Join-Path $licenses 'LICENSE-APACHE')
Invoke-WebRequest -Uri "https://raw.githubusercontent.com/esp-rs/espflash/v$env:ESPFLASH_VERSION/LICENSE-MIT" -OutFile (Join-Path $licenses 'LICENSE-MIT')
- name: Package contract acceptance
shell: powershell
run: |
function Assert-ExitZero([string]$FilePath, [string[]]$Arguments, [string]$Label) {
$process = Start-Process -FilePath $FilePath -ArgumentList $Arguments -Wait -PassThru
if ($process.ExitCode -ne 0) {
throw "$Label failed with exit code $($process.ExitCode)"
}
}
$stage = Join-Path $PWD 'dist\ARStack-Studio'
$studio = Join-Path $stage 'ARStackStudio.exe'
$espflash = Join-Path $stage 'tools\espflash.exe'
$env:ARSTACK_STUDIO_FIRMWARE_DIR = Join-Path $stage 'firmware'
$env:ARSTACK_STUDIO_ESPFLASH = $espflash
$env:ESPFLASH_SKIP_UPDATE_CHECK = 'true'
Assert-ExitZero $studio @('--check-reference-template') 'Packaged reference-template regression'
Assert-ExitZero $studio @('--check-firmware-contract') 'Packaged firmware contract'
Assert-ExitZero $studio @('--check-p0-policy') 'Packaged native policy check'
Assert-ExitZero $studio @('--check-app-lifecycle') 'Packaged primary-window shutdown check'
& "$PWD\apps\arstack_studio\packaging\verify-single-instance.ps1" -Executable $studio
Assert-ExitZero $espflash @('--version') 'Bundled espflash execution'
Assert-ExitZero $espflash @('board-info', '--non-interactive', '--help') 'espflash board-info recovery CLI contract'
Assert-ExitZero $espflash @('write-bin', '--chip', 'esp32p4', '--non-interactive', '--help') 'espflash write-bin recovery CLI contract'
Assert-ExitZero $espflash @('reset', '--non-interactive', '--help') 'espflash reset recovery CLI contract'
- name: Build portable ZIP
shell: powershell
run: |
$zip = Join-Path $PWD "dist\ARStack-Studio-$env:ARSTACK_VERSION-win-x64-portable.zip"
Compress-Archive -Path 'dist\ARStack-Studio\*' -DestinationPath $zip -CompressionLevel Optimal -Force
- name: Install NSIS
shell: powershell
run: choco install nsis -y --no-progress
- name: Build Windows installer
shell: powershell
run: |
$stage = (Resolve-Path 'dist\ARStack-Studio').Path
$out = Join-Path $PWD "dist\ARStack-Studio-$env:ARSTACK_VERSION-win-x64-setup.exe"
$script = (Resolve-Path 'apps\arstack_studio\packaging\ARStackStudio.nsi').Path
& makensis "/DSOURCE_DIR=$stage" "/DOUTPUT_FILE=$out" "/DAPP_VERSION=$env:ARSTACK_VERSION" $script
if ($LASTEXITCODE -ne 0 -or -not (Test-Path $out)) { throw 'NSIS installer build failed' }
- name: Generate release checksums
shell: powershell
run: |
$files = @(
"dist\ARStack-Studio-$env:ARSTACK_VERSION-win-x64-portable.zip",
"dist\ARStack-Studio-$env:ARSTACK_VERSION-win-x64-setup.exe"
)
$lines = foreach ($file in $files) {
$hash = (Get-FileHash $file -Algorithm SHA256).Hash.ToLowerInvariant()
"$hash $([IO.Path]::GetFileName($file))"
}
$lines | Set-Content 'dist\SHA256SUMS.txt' -Encoding ascii
Get-Content 'dist\SHA256SUMS.txt'
- name: Upload Windows release candidates
uses: actions/upload-artifact@v4
with:
name: arstack-studio-windows-x64
path: |
dist/ARStack-Studio-${{ env.ARSTACK_VERSION }}-win-x64-portable.zip
dist/ARStack-Studio-${{ env.ARSTACK_VERSION }}-win-x64-setup.exe
dist/SHA256SUMS.txt
if-no-files-found: error
retention-days: 14
clean-windows-smoke:
name: Fresh Windows package smoke
needs: windows-package
runs-on: windows-2022
steps:
- name: Checkout ownership probe
uses: actions/checkout@v4
- name: Download release candidates only
uses: actions/download-artifact@v4
with:
name: arstack-studio-windows-x64
path: release-candidates
- name: Verify release checksums
shell: powershell
run: |
$expected = @{}
Get-Content 'release-candidates\SHA256SUMS.txt' | ForEach-Object {
if ($_ -match '^([0-9a-f]{64})\s+(.+)$') { $expected[$Matches[2]] = $Matches[1] }
}
foreach ($name in @(
"ARStack-Studio-$env:ARSTACK_VERSION-win-x64-portable.zip",
"ARStack-Studio-$env:ARSTACK_VERSION-win-x64-setup.exe")) {
$path = Join-Path 'release-candidates' $name
if (-not (Test-Path $path)) { throw "Missing release candidate: $name" }
$actual = (Get-FileHash $path -Algorithm SHA256).Hash.ToLowerInvariant()
if ($expected[$name] -ne $actual) { throw "Release checksum mismatch: $name" }
}
- name: Portable package smoke with restricted PATH
shell: powershell
run: |
function Assert-ExitZero([string]$FilePath, [string[]]$Arguments, [string]$Label) {
$process = Start-Process -FilePath $FilePath -ArgumentList $Arguments -Wait -PassThru
if ($process.ExitCode -ne 0) {
throw "$Label failed with exit code $($process.ExitCode)"
}
}
$root = Join-Path $env:RUNNER_TEMP 'ARStackPortable'
Expand-Archive -Path "release-candidates\ARStack-Studio-$env:ARSTACK_VERSION-win-x64-portable.zip" -DestinationPath $root -Force
$exe = Join-Path $root 'ARStackStudio.exe'
$espflash = Join-Path $root 'tools\espflash.exe'
$env:ARSTACK_STUDIO_FIRMWARE_DIR = Join-Path $root 'firmware'
$env:ARSTACK_STUDIO_ESPFLASH = $espflash
$env:ESPFLASH_SKIP_UPDATE_CHECK = 'true'
$savedPath = $env:PATH
$env:PATH = "$env:SystemRoot\System32;$env:SystemRoot"
try {
Assert-ExitZero $exe @('--check-reference-template') 'Portable reference-template check'
Assert-ExitZero $exe @('--check-firmware-contract') 'Portable firmware contract'
Assert-ExitZero $exe @('--check-p0-policy') 'Portable P0 policy'
Assert-ExitZero $exe @('--check-app-lifecycle') 'Portable primary-window shutdown check'
& "$PWD\apps\arstack_studio\packaging\verify-single-instance.ps1" -Executable $exe
Assert-ExitZero $espflash @('--version') 'Portable espflash'
} finally {
$env:PATH = $savedPath
}
- name: Installer smoke with restricted PATH
shell: powershell
run: |
function Assert-ExitZero([string]$FilePath, [string[]]$Arguments, [string]$Label) {
$process = Start-Process -FilePath $FilePath -ArgumentList $Arguments -Wait -PassThru
if ($process.ExitCode -ne 0) {
throw "$Label failed with exit code $($process.ExitCode)"
}
}
$installer = (Resolve-Path "release-candidates\ARStack-Studio-$env:ARSTACK_VERSION-win-x64-setup.exe").Path
$installDir = Join-Path $env:RUNNER_TEMP 'ARStackStudioInstall'
$savedPath = $env:PATH
$env:PATH = "$env:SystemRoot\System32;$env:SystemRoot"
try {
Assert-ExitZero $installer @('/S', "/D=$installDir") 'Silent installer'
$exe = Join-Path $installDir 'ARStackStudio.exe'
if (-not (Test-Path $exe)) { throw 'Installer did not deploy ARStackStudio.exe' }
$espflash = Join-Path $installDir 'tools\espflash.exe'
$env:ARSTACK_STUDIO_FIRMWARE_DIR = Join-Path $installDir 'firmware'
$env:ARSTACK_STUDIO_ESPFLASH = $espflash
$env:ESPFLASH_SKIP_UPDATE_CHECK = 'true'
Assert-ExitZero $exe @('--check-reference-template') 'Installed reference-template check'
Assert-ExitZero $exe @('--check-firmware-contract') 'Installed firmware contract'
Assert-ExitZero $exe @('--check-p0-policy') 'Installed P0 policy'
Assert-ExitZero $exe @('--check-app-lifecycle') 'Installed primary-window shutdown check'
& "$PWD\apps\arstack_studio\packaging\verify-single-instance.ps1" -Executable $exe
Assert-ExitZero $espflash @('--version') 'Installed espflash'
$uninstaller = Join-Path $installDir 'Uninstall.exe'
if (Test-Path $uninstaller) {
Assert-ExitZero $uninstaller @('/S') 'Silent uninstaller'
}
} finally {
$env:PATH = $savedPath
}
github-release:
name: Publish tagged release
if: startsWith(github.ref, 'refs/tags/')
needs: clean-windows-smoke
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Download release candidates
uses: actions/download-artifact@v4
with:
name: arstack-studio-windows-x64
path: release
- name: Publish GitHub Release
uses: softprops/action-gh-release@v2
with:
name: ARStack Studio v${{ env.ARSTACK_VERSION }}
prerelease: true
generate_release_notes: true
files: |
release/ARStack-Studio-${{ env.ARSTACK_VERSION }}-win-x64-portable.zip
release/ARStack-Studio-${{ env.ARSTACK_VERSION }}-win-x64-setup.exe
release/SHA256SUMS.txt