Files
2026-10-18 13:55:08 -07:00

261 lines
9.5 KiB
YAML

name: 'Flutter Build Windows'
description: 'Builds Windows Flutter desktop apps, compiles Inno Setup (.exe) and MSIX (.msix) installers, signs updates for WinSparkle (DSA), and generates release metadata.'
inputs:
working_directory:
description: 'Path to the Flutter app package directory (e.g., packages/sfw_desktop)'
required: false
default: '.'
arch:
description: 'Target Windows architecture (x64 or arm64)'
required: false
default: 'x64'
build_args:
description: 'Extra arguments passed to flutter build windows (e.g., --dart-define=...)'
required: false
default: ''
build_inno_setup:
description: 'Whether to compile an Inno Setup installer (.exe)'
required: false
default: 'true'
inno_setup_script:
description: 'Path to Inno Setup .iss script relative to working_directory'
required: false
default: 'windows/installer/inno_setup.iss'
output_dir:
description: 'Output directory for compiled Inno Setup installer'
required: false
default: 'dist'
exe_filename:
description: 'Expected installer filename for metadata JSON (e.g., slides-for-worship-windows-x64-setup.exe)'
required: false
default: ''
build_msix:
description: 'Whether to build an MSIX package using msix:create'
required: false
default: 'true'
msix_output_name:
description: 'Output base name for the MSIX package (e.g., slides_for_worship_x64)'
required: false
default: ''
pfx_base64:
description: 'Base64-encoded Windows Authenticode .pfx certificate'
required: false
default: ''
pfx_password:
description: 'Password for the Windows .pfx certificate'
required: false
default: ''
sparkle_dsa_private_key:
description: 'WinSparkle DSA private key content (PEM) for signing installer'
required: false
default: ''
sparkle_dsa_key_file:
description: 'Fallback path to WinSparkle DSA private key file in repository'
required: false
default: 'signing/sparkle/dsa_priv.pem'
generate_metadata_json:
description: 'Whether to generate windows_<arch>_metadata.json and windows_signature.txt'
required: false
default: 'false'
outputs:
version:
description: 'Resolved package version from pubspec.yaml'
value: ${{ steps.build.outputs.version }}
exe_path:
description: 'Path to compiled Inno Setup .exe installer'
value: ${{ steps.package_meta.outputs.exe_path }}
msix_path:
description: 'Path to generated .msix installer'
value: ${{ steps.package_meta.outputs.msix_path }}
signature:
description: 'WinSparkle DSA signature if generated'
value: ${{ steps.package_meta.outputs.signature }}
metadata_path:
description: 'Path to generated windows_<arch>_metadata.json'
value: ${{ steps.package_meta.outputs.metadata_path }}
runs:
using: 'composite'
steps:
- name: Configure Windows Tooling PATH
shell: powershell
run: |
$toolPaths = @(
"C:\Program Files\Git\bin",
"C:\Program Files\Git\usr\bin",
"C:\Git\bin",
"C:\Git\usr\bin",
"C:\Users\rodyd\AppData\Local\Programs\Inno Setup 6",
"C:\Program Files (x86)\Inno Setup 6",
"C:\Program Files\Inno Setup 6",
"C:\flutter\bin"
)
foreach ($p in $toolPaths) {
if (Test-Path $p) {
$env:PATH = "$p;$env:PATH"
if (Test-Path env:GITHUB_PATH) {
[System.IO.File]::AppendAllText($env:GITHUB_PATH, "$p`r`n")
}
}
}
- name: Decode Windows PFX Certificate
if: inputs.pfx_base64 != ''
shell: powershell
env:
WINDOWS_PFX_BASE64: ${{ inputs.pfx_base64 }}
run: |
if ($env:WINDOWS_PFX_BASE64) {
$pfx_cert_byte = [System.Convert]::FromBase64String($env:WINDOWS_PFX_BASE64)
[IO.File]::WriteAllBytes("cert.pfx", $pfx_cert_byte)
Write-Output "Decoded cert.pfx"
}
- name: Build Windows Release
id: build
shell: bash
env:
WORK_DIR: ${{ inputs.working_directory }}
ARCH: ${{ inputs.arch }}
BUILD_ARGS: ${{ inputs.build_args }}
run: |
ROOT_DIR="$(pwd)"
VERSION=$(grep '^version: ' "$WORK_DIR/pubspec.yaml" | head -n 1 | cut -d ' ' -f 2 | tr -d '\r')
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
cd "$WORK_DIR"
if [ "$ARCH" = "arm64" ]; then
# shellcheck disable=SC2086
flutter build windows --release --target-platform=windows-arm64 $BUILD_ARGS
else
# shellcheck disable=SC2086
flutter build windows --release $BUILD_ARGS
fi
cd "$ROOT_DIR"
- name: Compile Inno Setup Installer
if: inputs.build_inno_setup == 'true'
shell: powershell
env:
WORK_DIR: ${{ inputs.working_directory }}
ARCH: ${{ inputs.arch }}
ISS_REL: ${{ inputs.inno_setup_script }}
OUT_DIR: ${{ inputs.output_dir }}
VERSION: ${{ steps.build.outputs.version }}
run: |
New-Item -ItemType Directory -Force -Path $env:OUT_DIR | Out-Null
$sourceRel = Join-Path $env:WORK_DIR "build\windows\$($env:ARCH)\runner\Release"
if (-not (Test-Path $sourceRel)) {
$sourceRel = Join-Path $env:WORK_DIR "build\windows\x64\runner\Release"
}
$sourceDir = (Resolve-Path $sourceRel).Path
$outputDir = (Resolve-Path $env:OUT_DIR).Path
$issScript = Join-Path $env:WORK_DIR $env:ISS_REL
if (-not (Test-Path $issScript)) {
Write-Warning "Inno Setup script not found at $issScript; skipping Inno Setup compilation."
exit 0
}
$iscc = (Get-Command iscc.exe -ErrorAction SilentlyContinue).Source
if (-not $iscc -and (Test-Path "C:\Users\rodyd\AppData\Local\Programs\Inno Setup 6\ISCC.exe")) {
$iscc = "C:\Users\rodyd\AppData\Local\Programs\Inno Setup 6\ISCC.exe"
}
if ($iscc) {
& $iscc /DMyAppVersion="$($env:VERSION)" /DAppArch="$($env:ARCH)" /DSourceDir="$sourceDir" /DOutputDir="$outputDir" "$issScript"
} else {
Write-Warning "ISCC.exe not found on runner; skipping Inno Setup installer."
}
- name: Create MSIX Package
if: inputs.build_msix == 'true'
shell: bash
env:
WORK_DIR: ${{ inputs.working_directory }}
ARCH: ${{ inputs.arch }}
MSIX_NAME: ${{ inputs.msix_output_name }}
PFX_PASS: ${{ inputs.pfx_password }}
run: |
ROOT_DIR="$(pwd)"
OUT_NAME="${MSIX_NAME:-slides_for_worship_${ARCH}}"
cd "$WORK_DIR"
if [ -f "$ROOT_DIR/cert.pfx" ]; then
flutter pub run msix:create --certificate-path "$ROOT_DIR/cert.pfx" --certificate-password "$PFX_PASS" --install-certificate false --output-name "$OUT_NAME"
else
flutter pub run msix:create --install-certificate false --output-name "$OUT_NAME"
fi
cd "$ROOT_DIR"
- name: Sign for WinSparkle & Generate Metadata
id: package_meta
shell: bash
env:
WORK_DIR: ${{ inputs.working_directory }}
ARCH: ${{ inputs.arch }}
OUT_DIR: ${{ inputs.output_dir }}
VERSION: ${{ steps.build.outputs.version }}
EXE_FILENAME_INPUT: ${{ inputs.exe_filename }}
SPARKLE_DSA_KEY: ${{ inputs.sparkle_dsa_private_key }}
SPARKLE_DSA_FILE: ${{ inputs.sparkle_dsa_key_file }}
GEN_META: ${{ inputs.generate_metadata_json }}
run: |
EXE_FILE=$(find "$OUT_DIR" -name "*.exe" 2>/dev/null | head -n 1 || true)
MSIX_FILE=$(find "$WORK_DIR/build/windows" -name "*.msix" 2>/dev/null | head -n 1 || true)
echo "exe_path=$EXE_FILE" >> "$GITHUB_OUTPUT"
echo "msix_path=$MSIX_FILE" >> "$GITHUB_OUTPUT"
SETUP_FILE="${EXE_FILE:-$MSIX_FILE}"
if [ -z "$SPARKLE_DSA_KEY" ] && [ -n "$SPARKLE_DSA_FILE" ] && [ -f "$SPARKLE_DSA_FILE" ]; then
SPARKLE_DSA_KEY=$(cat "$SPARKLE_DSA_FILE")
fi
WIN_SIG=""
if [ -n "$SPARKLE_DSA_KEY" ] && [ -n "$SETUP_FILE" ] && [ -f "$SETUP_FILE" ]; then
echo "$SPARKLE_DSA_KEY" > dsa_priv.pem
echo "Signing $SETUP_FILE with DSA key..."
WIN_SIG=$(openssl dgst -sha1 -binary < "$SETUP_FILE" | openssl dgst -sha1 -sign dsa_priv.pem | openssl enc -base64 | tr -d '\n\r' || true)
if [ -n "$WIN_SIG" ]; then
echo "$WIN_SIG" > windows_signature.txt
echo "signature=$WIN_SIG" >> "$GITHUB_OUTPUT"
fi
rm -f dsa_priv.pem
fi
if [ "$GEN_META" = "true" ]; then
SIZE="0"
if [ -n "$EXE_FILE" ] && [ -f "$EXE_FILE" ]; then
SIZE=$(stat -c%s "$EXE_FILE" 2>/dev/null || stat -f%z "$EXE_FILE" 2>/dev/null || echo "0")
fi
if [ -n "$EXE_FILENAME_INPUT" ]; then
FILENAME="$EXE_FILENAME_INPUT"
elif [ -n "$EXE_FILE" ]; then
FILENAME=$(basename "$EXE_FILE")
else
FILENAME="slides-for-worship-windows-${ARCH}-setup.exe"
fi
META_FILE="windows_${ARCH}_metadata.json"
cat <<EOF > "$META_FILE"
{
"version": "$VERSION",
"platform": "windows",
"arch": "$ARCH",
"filename": "$FILENAME",
"size": $SIZE,
"signature": "$WIN_SIG",
"signatureType": "dsaSignature"
}
EOF
cat "$META_FILE"
echo "metadata_path=$META_FILE" >> "$GITHUB_OUTPUT"
fi
- name: Clean up PFX Certificate
if: always()
shell: powershell
run: |
if (Test-Path "cert.pfx") {
Remove-Item -Force "cert.pfx" -ErrorAction SilentlyContinue
}