feat(windows): ship MSIX instead of appx; add headless smoke test - #451
Merged
Conversation
- installer/appxmanifest.xml: ProcessorArchitecture amd64 (was x86, wrong for the 64-bit PyInstaller build), newer Windows 10 min/max baseline, updated comments to reflect MSIX. - install-windows.ps1: emit friture-$version.msix via MakeAppx (was .appx), save the manifest as AppxManifest.xml (MSIX canonical name). - build.yml: add 'Verify app launches' smoke-test step for Windows (frozen dist/friture/friture.exe, offscreen Qt, full-init assertion via the cross-platform smoke_test.py), a platformdirs-based log capture/upload, switch the artifact/upload from .appx to .msix, and add MSIX to the Store release files. (Store ingestion signs the package; CI builds an unsigned MSIX for packaging validation only.)
…back) os.killpg/signal.SIGKILL are POSIX-only; on Windows they raise AttributeError. Extract _kill_proc() that tries the process-group kill on POSIX and falls back to proc.kill() otherwise, so the Windows smoke test can terminate a hung release build instead of crashing the harness.
sounddevice.query_devices(kind='output') raises PortAudioError ('Error
querying device -1') on a headless host with no default output device,
which crashed AudioBackend() -> Friture() before init completed. Mirror
the existing guard in get_input_devices() so a missing default output
device degrades to an empty device list instead of raising.
…dless) On a host with no default input/output device (e.g. the Windows CI runner, unlike Linux which loads a PulseAudio monitor source), the frozen app could not start: * get_readable_devices_list() / get_readable_output_devices_list() called sounddevice.query_devices(kind=...) unconditionally and, when it raised PortAudioError, logged via logger.exception() -- emitting a 'Traceback' line that trips the smoke test's FATAL_MARKERS. Guard the kind= calls and log at debug instead; mirror get_input_devices()'s early return when there are no devices. * SettingsDialog popped a blocking QMessageBox.critical() + sys.exit(1) on the empty device list. Under the offscreen Qt platform (the CI smoke test / any headless host) that modal blocks forever with no user to dismiss it, preventing init from reaching 'Init finished' and before any QML is shown. In headless mode, log a warning and continue with an empty device set instead; interactive desktop users keep the existing message+exit behaviour. Neither change affects machines that have a real audio input device.
… the static call) PyQt6 moved QGuiApplication.platformName() from a static method to an instance method, so QtCore.QCoreApplication.platformName() raised AttributeError during SettingsDialog construction on a headless Windows runner, crashing Friture() before full init. Use the existing QApplication.instance() pattern (already used elsewhere in the codebase) to query the offscreen platform.
…rtup
SettingsDialog.restoreState() did themeButtonGroup.button(id).setChecked(True)
with the id read back from QSettings (AudioBackend group). On a system whose
stored value is absent, stale, or outside {0,1,2}, button() returns None and
QML/.setChecked raises AttributeError: 'NoneType' object has no attribute
'setChecked' -- crashing Friture() during restoreAppState() before
'Init finished'.
This reproduces on a fresh Windows CI runner with no audio (unlike Linux,
whose PulseAudio monitor source provides a default input): the headless
no-device path runs SettingsDialog.__init__ (which returns early, leaving
the comboBox empty) and restoreState is still invoked from the analyzer.
Guard the button(id) lookups (both theme and input-type groups): if no
button carries the stored id, fall back to the System (0) button instead
of crashing. Real interactive users see no behaviour change (their stored
0/1/2 still maps to a button); only a bad/sentinel id is rescued.
Root cause of the Windows headless startup crash (now visible in the smoke log as 'No theme button for id 0'): SettingsDialog.__init__ early-returns in the no-input-device / offscreen branch, which on a headless Windows runner is the common path (unlike Linux, which loads a PulseAudio monitor source as its default input). That early return happened BEFORE the themeButtonGroup.setId(...) calls, so on the no-device path the button group had no IDs -- restoreState()'s button(0) returned None, and .setChecked() raised AttributeError. Move the idToggled connect + the three setId(0/1/2) calls to right after setupUi(), before the device-list early return, so the button group is always populated. The defensive None-guard in restoreState() stays as defense-in-depth. No behaviour change for interactive desktop users (their button IDs were already set).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Build a msix package instead of appx: msix is a more modern, more generic packaging solution. Both can be published to the Microsoft store similarly.
Also add a headless smoke test like we did for AppImage in #450