Skip to content

Fix PyInstaller hook for CGX files inside packages - #184

Merged
berendkleinhaneveld merged 1 commit into
masterfrom
fix/pyinstaller-hook-package-modules
Jul 7, 2026
Merged

Fix PyInstaller hook for CGX files inside packages#184
berendkleinhaneveld merged 1 commit into
masterfrom
fix/pyinstaller-hook-package-modules

Conversation

@berendkleinhaneveld

Copy link
Copy Markdown
Collaborator

Fixes #178

Root cause

The PyInstaller hook registered CGX modules under their bare file stem (cgx_path.stem). That only works when CGX files sit flat next to the entry script — which is exactly what the integration test app looked like. In a real app where CGX files live inside packages, a file like myapp/components/button.cgx is imported as myapp.components.button, but the hook looked for a graph node named just button. The MissingModule node was never cleaned up, graph.import_hook("button", ...) raised an ImportError that was silently swallowed, and the compiled module never made it into the bundle.

The spec-file workaround from #178 worked because pre-generated .py files go through PyInstaller's normal analysis, which resolves package-qualified names correctly. So the files weren't generated too late — they were registered under the wrong names.

Changes

  • qualified_module_name() derives the fully qualified module name by walking up the directory tree while parent directories are packages (contain __init__.py), and is used everywhere the hook previously used the bare stem.
  • ImportsCollector now resolves relative imports (from .todo_item import ...) in CGX scripts against the containing package instead of emitting them as bogus top-level hidden imports.

Tests

Restructured tests/pyinstaller/todo_app so the existing integration test guards the regression: todo_item.cgx and helpers.py moved into a components package while todo_list.cgx stays flat. One build now exercises a flat CGX module, an absolute import of a packaged CGX module, and a relative import of a plain Python module.

Verified locally: reproduced the failure (ModuleNotFoundError: No module named 'myapp.components.todo_list') with a package-structured app before the fix, and confirmed the same bundle runs correctly after. test_pyinstaller.sh passes, as do the unit tests and ruff.

Known limitation

Qualified-name detection relies on __init__.py files, so CGX files inside namespace packages (no __init__.py) still fall back to the old bare-name behavior.

🤖 Generated with Claude Code

The hook registered CGX modules under their bare file stem, so a CGX
file inside a package (e.g. myapp/components/button.cgx, imported as
myapp.components.button) never matched its MissingModule node in the
module graph and graph.import_hook() failed silently, leaving the
generated module out of the bundle.

Derive the fully qualified module name by walking up the directory
tree while parent directories are packages, and resolve relative
imports in CGX scripts against the containing package when collecting
hidden imports.

The todo_app integration test now covers both cases: a flat CGX module
importing a CGX module from a package, which in turn uses a relative
import to a plain Python module.

Fixes #178

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@berendkleinhaneveld
berendkleinhaneveld merged commit 7ac00c1 into master Jul 7, 2026
9 checks passed
@berendkleinhaneveld
berendkleinhaneveld deleted the fix/pyinstaller-hook-package-modules branch July 7, 2026 16:29
berendkleinhaneveld added a commit that referenced this pull request Jul 21, 2026
Features:
- Add pure-Python view API as alternative to cgx templates (#193)
- Support text elements for PySide widgets that display text (#191)

Fixes & internals:
- Fragment parenting overhaul (#162)
- Fix PyInstaller hook for CGX files inside packages (#184)
- Write compiled AST to temp file when CGX_DEBUG is set (#175)

Performance:
- Speed up mount path: cheap arity check, reuse first(), leaner emit (#186)
- Cache Fragment._component_parent() lookups (#187)
- Avoid redundant anchor lookups in Fragment.anchor() and unkeyed v-for (#188)

Documentation:
- Add MkDocs documentation with GitHub Pages deployment (#176)
- Add internals architecture documentation page (#194)
- Add docs badge and links to README (#192)

Tooling & CI:
- Add benchmark suite and per-PR benchmark CI workflow (#185)
- Make benchmark CI guard robust against run-to-run noise (#196)
- Update GitHub actions from Node 20 to Node 24 (#190)
- Migrate from pre-commit to prek (#195)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PyInstaller hooks seem to generate the python files too late

1 participant