fix(packaging): dcs-serve.exe / dcs-client.exe never start — missing __main__ guard (LOT-020 #01) - #31
Merged
Merged
Conversation
…start (LOT-020 #1) Both PyInstaller specs pass an application module to `Analysis([...])`, which makes that module the frozen script: PyInstaller runs it top-to-bottom and exits. Neither `serve/app.py` nor `client/app.py` had an `if __name__ == "__main__": main()` block, so the built executables defined everything and quit — exit code 0, no output, nothing listening on 7777/8080. The Poetry console-scripts (`dcs-serve = ...app:main`) call main() for `poetry run`, which masked the gap. Two regression guards: - `test/test_packaging_entrypoints.py` parses each `.spec` as an AST, resolves the module it freezes as its script, and asserts the guard is present and calls main(). Runs in the normal quality gate, no PyInstaller needed. - `release.yml` smoke-tests the built exes before publishing: `dcs-serve.exe` must accept TCP connections on 127.0.0.1:7777 and 8080 within 30 s (both polled, since asyncio.gather binds them concurrently), and `dcs-client.exe --help` must list its subcommands. The smoke test refuses to run if either port is already bound — a stale listener would otherwise produce a meaningless pass — and tears the server down with `taskkill /T`: a one-file PyInstaller exe is a bootloader that re-launches itself as a child process which owns the sockets, so killing the PID returned by Start-Process orphans it and leaves the ports held. Verified against locally built executables on all three paths: the real exes pass and release their ports, an exe that never serves fails with an actionable message, and a pre-bound port is rejected. Also gitignore the `build_pyi/` and `test-mission/` build leftovers, and correct the stale status on the completed LOT-019 ticket. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
🧙 Sourcery is reviewing your pull request! Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
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.
Closes ticket 01 of LOT-020.
Problem
The packaged
dcs-serve.exeexits immediately with code 0: nothing listens on127.0.0.1:7777(DCS side) or0.0.0.0:8080(HTTP side), and it prints nothing — amission maker just sees a window flash and vanish.
Both specs pass an application module to
Analysis([...]), which makes that modulethe frozen script. PyInstaller runs it top-to-bottom — defining
_cli,main,_serve— and exits without ever callingmain(). The Poetry console-script(
dcs-serve = "dcs_bridge.serve.app:main") is what normally invokes it, and it doesnot exist inside the exe, so
poetry run dcs-servealways worked and hid the defect.client/app.pyhad the same defect, as the ticket suspected.Fix
An
if __name__ == "__main__": main()guard inserve/app.pyandclient/app.py.Regression guards
The ticket assumed a unit test could not catch a packaging defect. It can:
test/test_packaging_entrypoints.pyparses each.specas an AST, resolves themodule it freezes as its script, and asserts the guard exists and calls
main().Runs in the normal quality gate — no PyInstaller needed. Verified red without the
guard, green with it.
release.ymlsmoke-tests the built exes before publishing:dcs-serve.exemustaccept TCP connections on
127.0.0.1:7777and8080within 30 s (both polled,since
asyncio.gatherbinds them concurrently, with the server logs dumped onfailure);
dcs-client.exe --helpmust list its subcommands.A subtlety worth flagging in review
The smoke test tears the server down with
taskkill /F /T, notStop-Process. Aone-file PyInstaller exe is a bootloader that re-launches itself as a child
process, and the child owns the sockets — Windows says so explicitly
(
PID 32468 (child process of PID 24908)). Killing the PID returned byStart-Processorphans it and leaves 7777/8080 bound, so a later run would validatea phantom listener. The step also refuses to start if either port is already bound,
because a stale listener would otherwise yield a pass that proves nothing. Both
issues were found by actually running the gate, not by reading it.
Verification
Built both exes locally with PyInstaller 6.20 and ran the workflow's smoke-test
scripts verbatim:
exited with code 1 without serving — the frozen script never called main()Quality gate:
ruffclean,mypyclean on 22 files, 270 tests pass.Also in this PR
.gitignorefor thebuild_pyi/andtest-mission/build leftovers.capabilities.pyscratch edit that hardcoded framework versions to one runningmission (self-labelled
DO NOT COMMIT) is reverted, not committed. Loose versionmatching stays out of scope and deserves its own lot.
⬜status on the already-completed LOT-019 ticket.Not in this PR
Ticket 02 (publish the first release) is blocked by a finding recorded on the ticket:
RELEASE_NOTES.mdhas never been committed, yetrelease.ymlpassesbody_path: RELEASE_NOTES.mdtosoftprops/action-gh-release— the publish stepwould have failed. It will be authored via the project's own
/release-notesprocedure on a
release/1.0.0branch, which is also where the version bump to1.0.0belongs. This PR keeps the PATCH bump (0.8.3) per CLAUDE.md § 9.🤖 Generated with Claude Code
Summary by Sourcery
Ensure packaged dcs-serve.exe and dcs-client.exe actually start and are guarded against packaging regressions, and bump the project patch version.
Bug Fixes:
Enhancements:
CI:
Documentation:
Tests:
Chores: