Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 153 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,37 @@ jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
# windows-latest pinned to windows-2022 (Visual Studio 2022): GitHub moved
# windows-latest to Windows Server 2025 + Visual Studio 2026 in June 2026, and
# Nuitka 2.7.11's MSVC auto-detection doesn't recognize the VS2026 toolset yet
# ("Nuitka-Scons: MSVC version resolved to None" -> "cannot locate suitable C
# compiler"). windows-2022 is a GitHub-maintained stable image with the older,
# Nuitka-supported VS2022 toolset. Revisit once Nuitka supports VS2026.
os: [ubuntu-latest, macos-latest, windows-2022]

runs-on: ${{ matrix.os }}

env:
# Nuitka may need to download small helper tools on some platforms (e.g. ccache).
# It normally asks for interactive confirmation before doing so, which hangs/fails
# on CI's non-interactive stdin. This makes it assume "yes" everywhere. Windows no
# longer needs this for its compiler specifically -- it's forced onto the pre-
# installed MSVC via --msvc=latest below, since Nuitka's own MinGW64 auto-download
# is currently broken on Windows CI (see the "Generate deployment spec" step).
NUITKA_ASSUME_YES_FOR_DOWNLOADS: "1"

steps:
- uses: actions/checkout@v4

### DEBUG: SHOW CHECKED-OUT SOURCE TREE
- name: "Debug: list checked-out repo contents"
shell: bash
run: |
echo "--- repo root (top 2 levels) ---"
find . -maxdepth 2 -not -path '*/.git*' | sort
echo "--- app/ (full) ---"
find app -not -path '*/.git*' | sort

### INSTALL UV
- name: Install uv (Unix)
if: runner.os != 'Windows'
Expand Down Expand Up @@ -52,18 +76,111 @@ jobs:
-OutFile "$dest\depends.zip"
Expand-Archive -Path "$dest\depends.zip" -DestinationPath $dest -Force

### GENERATE A FRESH pysidedeploy.spec, WITHOUT NUITKA'S --quiet FLAG
# pysidedeploy.spec is intentionally not committed (see AGENTS.md), so pyside6-deploy
# would otherwise regenerate one from its own template on every run, which bakes in
# "extra_args = --quiet --noinclude-qt-translations". --quiet hides Nuitka's own
# compiler/Scons output -- which is exactly the output that revealed the Windows build's
# real failure after "Build app" below had already reported success. --init only writes
# the config file, it does not invoke Nuitka, so this is safe/fast to run separately first.
#
# On Windows we also force --msvc=latest. Nuitka's default Windows backend auto-downloads
# a pinned MinGW64 gcc, but that download/retry flow is currently broken on Windows CI
# (confirmed independently of any PATH conflict -- fails with "FATAL: Only this specific
# gcc is supported with Nuitka" / "Failed unexpectedly in Scons C backend compilation" even
# with zero gcc anywhere on PATH; matches the open regression nuitka/Nuitka#3309). windows-
# latest runners ship Visual Studio Build Tools already, so --msvc=latest sidesteps the
# whole MinGW64 download path and uses the compiler that's already there.
- name: Generate deployment spec (verbose Nuitka output)
shell: bash
run: |
uv run pyside6-deploy --init --name groupselect2 app/main.py
if [ "${{ runner.os }}" = "Windows" ]; then
extra_args="--msvc=latest --noinclude-qt-translations"
else
extra_args="--noinclude-qt-translations"
fi
sed -i.bak "s/^extra_args = .*/extra_args = $extra_args/" app/pysidedeploy.spec
rm -f app/pysidedeploy.spec.bak
echo "--- generated app/pysidedeploy.spec ---"
cat app/pysidedeploy.spec

### BUILD APP WITH PYSIDE6-DEPLOY AND UPLOAD AS ARTIFACT
- name: Build app
run: uv run pyside6-deploy --mode standalone --name groupselect2 app/main.py

### DEBUG: SHOW WHAT pyside6-deploy ACTUALLY PRODUCED
- name: "Debug: list build output"
shell: bash
run: |
echo "--- app/ (top level) ---"
ls -la app
for d in app/groupselect2.dist app/groupselect2.app app/deployment; do
if [ -e "$d" ]; then
echo "--- $d exists: $(find "$d" -type f | wc -l) files, $(du -sh "$d" | cut -f1) ---"
find "$d" -maxdepth 2 | sort
else
echo "--- $d does not exist ---"
fi
done

### HARD FAILURE GATE: pyside6-deploy can exit 0 even when the Nuitka compile it wraps
# failed (it catches the exception, logs it, and still reports "Executed file created"
# -- this is exactly how the Windows build silently shipped an empty package to the
# Store before). Fail the job loudly here instead of packaging whatever partial output
# (if any) exists.
- name: Verify build output exists
shell: bash
run: |
if [ "${{ runner.os }}" = "macOS" ]; then
dir="app/groupselect2.app/Contents/MacOS"
if [ ! -d "$dir" ] || [ -z "$(ls -A "$dir" 2>/dev/null)" ]; then
echo "::error::app/groupselect2.app/Contents/MacOS is missing or empty -- pyside6-deploy did not produce a real app bundle. Check the 'Build app' step's log above for a Nuitka error."
exit 1
fi
echo "Found app bundle contents: $(ls "$dir")"
else
if [ "${{ runner.os }}" = "Windows" ]; then
exe="app/groupselect2.dist/main.exe"
else
exe="app/groupselect2.dist/main.bin"
fi
if [ ! -f "$exe" ]; then
echo "::error::Expected built executable not found at $exe -- pyside6-deploy did not produce a real build. Check the 'Build app' step's log above for a Nuitka error (e.g. an unanswered interactive download prompt)."
exit 1
fi
echo "Found built executable: $exe ($(du -h "$exe" | cut -f1))"
fi

- name: Copy resources
run: python -c "import shutil; shutil.copytree('app/resources', 'app/groupselect2.dist/resources')"
shell: bash
run: |
if [ "${{ runner.os }}" = "macOS" ]; then
dest="app/groupselect2.app/Contents/Resources/resources"
else
dest="app/groupselect2.dist/resources"
fi
python -c "import shutil,sys; shutil.copytree(sys.argv[1], sys.argv[2])" app/resources "$dest"
echo "Copied app/resources into $dest"

### DEBUG: SHOW FINAL DIST/BUNDLE CONTENTS BEFORE UPLOAD
- name: "Debug: list final dist contents before upload"
shell: bash
run: |
if [ "${{ runner.os }}" = "macOS" ]; then
root="app/groupselect2.app"
else
root="app/groupselect2.dist"
fi
echo "--- $root: $(find "$root" -type f | wc -l) files, $(du -sh "$root" | cut -f1) ---"
find "$root" -maxdepth 2 | sort

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: groupselect2-dist-${{ matrix.os }}
path: app/groupselect2.dist
path: ${{ runner.os == 'macOS' && 'app/groupselect2.app' || 'app/groupselect2.dist' }}
retention-days: 15

### BUILD MSIX FOR WINDOWS
- name: Set version in manifest
Expand Down Expand Up @@ -98,6 +215,32 @@ jobs:
Copy-Item "packaging\licenses\PYSIDE6_LICENSES.txt" "$pkg\"
Copy-Item "packaging\licenses\QT_LIBRARY_REPLACEMENT.txt" "$pkg\"

### DEBUG: SHOW WHAT ENDED UP IN THE MSIX STAGING FOLDER
- name: "Debug: list MSIX staging contents"
if: runner.os == 'Windows'
shell: pwsh
run: |
Write-Host "--- msix_staging contents ---"
Get-ChildItem -Recurse msix_staging |
Select-Object FullName, Length | Format-Table -AutoSize
$totalSize = (Get-ChildItem -Recurse -File msix_staging |
Measure-Object -Property Length -Sum).Sum
Write-Host "Total size: $totalSize bytes"

### HARD FAILURE GATE: this is exactly the folder that got zipped into the .msix that
# shipped to the Store without the app in it -- re-check right here, closest to the
# actual packaging step, in addition to the "Verify build output exists" check above.
- name: Verify MSIX staging contains executable
if: runner.os == 'Windows'
shell: pwsh
run: |
$exe = "msix_staging\main.exe"
if (-not (Test-Path $exe)) {
Write-Error "$exe not found in msix_staging -- the MSIX would ship without the app executable (this is exactly the bug that broke the Store submission). Check the 'Build app' and 'Verify build output exists' steps' logs above."
exit 1
}
Write-Host "Found $exe ($((Get-Item $exe).Length) bytes)"

- name: Build MSIX with makeappx
if: runner.os == 'Windows'
shell: pwsh
Expand All @@ -109,12 +252,19 @@ jobs:

& $makeappx pack /d msix_staging /p groupselect2.msix /nv

### DEBUG: SHOW FINAL MSIX FILE SIZE
- name: "Debug: show final MSIX size"
if: runner.os == 'Windows'
shell: pwsh
run: Get-Item groupselect2.msix | Select-Object FullName, Length

- name: Upload MSIX artifact
if: runner.os == 'Windows'
uses: actions/upload-artifact@v4
with:
name: groupselect2-msix
path: groupselect2.msix
retention-days: 15

### ATTACH BUILDS TO THE GITHUB RELEASE AS DOWNLOADABLE ASSETS
publish-release-assets:
Expand Down
Loading