← XeFM crftwr/xefm on GitHub · craftware

Windows Application Bundle — Build System

This document describes the windows_app/ build system that packages XeFM into a self-contained Windows application folder (XeFM.exe + embedded CPython + all dependencies), the direct counterpart of the macOS macos_app/ bundle.

It is the design-and-implementation reference; the source of truth is the code under windows_app/.


Goals

Why this mirrors macOS so closely

The macOS bundle embeds a Python.framework and drives it from a small Obj-C launcher (macos_app/src/{main.m,XeFMAppDelegate.m}) that:

  1. initializes CPython with PyConfig (Python home = the embedded runtime),
  2. points sys.path at the bundled XeFM source, PuiKit, and third-party packages,
  3. sets sys.argv = ["XeFM", "--backend", "gui"], and
  4. imports the xefm module and calls xefm.app.main().

The Windows launcher (windows_app/src/launcher.c) does the same four things against the CPython embeddable distribution. Crucially, there is nothing to compile in XeFM or PuiKit: PuiKit’s Windows backend (puikit/backends/windows_backend.py, _win32_native.py) is pure Python built on ctypes + numpy, exactly as the macOS backend is pure PyObjC. The only native code we build is the ~200-line launcher stub itself.


Bundle layout

The build produces windows_app/build/XeFM/, a self-contained folder (this whole folder is what gets zipped for distribution):

XeFM/                              <- bundle root = the .exe's directory (5 entries)
├── XeFM.exe                       compiled C launcher (this repo); static CRT, no DLL deps
├── LICENSE                       XeFM's own license
├── THIRD_PARTY_NOTICES.txt       aggregated license text for bundled components
├── runtime/                      the entire embedded CPython, kept out of the root
│   ├── python3XX.dll             CPython (delay-loaded by XeFM.exe)
│   ├── python3.dll               stable-ABI forwarder
│   ├── python3XX.zip             zipped standard library
│   ├── vcruntime140.dll, vcruntime140_1.dll   the CRT python3XX.dll needs
│   ├── _ctypes.pyd, _ssl.pyd, ...             stdlib C extensions
│   ├── libffi-8.dll, libssl-3.dll, ...        their support DLLs
│   ├── python.exe, pythonw.exe                (usable standalone for debugging)
│   └── LICENSE.txt               embedded CPython's PSF license
├── app/                          XeFM's own code (mirror of macOS Resources/)
│   ├── xefm/app.py                    entry script (imported as the `xefm` module)
│   ├── xefm/_bin/archive.dll     libarchive, for .7z .rar .iso .cab .cpio .rpm
│   ├── src/                      xefm_* business-logic modules
│   └── puikit/                   PuiKit toolkit (copied from the sibling repo)
└── Lib/
    └── site-packages/            third-party deps: numpy, pygments, boto3, watchdog, ...

The app icon is compiled into XeFM.exe as a resource (XeFM.rc) and the window icon loads from there, so no loose XeFM.ico ships in the bundle.

Path resolution at runtime

The launcher computes <root> from GetModuleFileNameW (the directory containing XeFM.exe) and configures CPython explicitly — it does not rely on a python3XX._pth file — so the layout is unambiguous:

sys.argv is set to ["XeFM", "--backend", "gui"] (via PyConfig.argv with parse_argv = 0) so xefm.app.main()’s argparse selects the Windows GUI backend — create_backend("gui") maps to WindowsBackend on sys.platform == "win32".


The launcher (src/launcher.c)


The embedded runtime — CPython “embeddable” package

build.ps1 downloads the official python-<X.Y.Z>-embed-amd64.zip from python.org matching the exact version of the developer’s .venv and extracts it into the bundle root. This is the minimal, redistributable CPython: the DLL, the zipped stdlib, and the stdlib C-extension .pyds + their support DLLs (libffi, libssl/libcrypto, etc.). ctypes (which the whole Windows backend rides on) and its libffi-8.dll are included.

Version lock: compiled wheels in .venv\Lib\site-packages (notably numpy) are built for the venv’s Python ABI (cp3XX). The embeddable must be the same X.Y, so build.ps1 derives the download version from the venv interpreter and refuses to mix ABIs.

Coverage note: the 3.14 embeddable ships a broad stdlib C-extension set — including _ctypes.pyd + libffi-8.dll (which the whole Windows backend rides on), _ssl/_hashlib + libssl/libcrypto, and _sqlite3.pyd + sqlite3.dll. It omits _tkinter (and Tcl/Tk); XeFM/PuiKit don’t use it, so that’s fine. If a future dependency needs a module the embeddable leaves out, copy its .pyd (+ any support DLL) from a full CPython install of the same version into the bundle root.


Dependency collection + license notices (shared with macOS)

The Windows build reuses the shared, platform-agnostic scripts rather than maintaining Windows-specific copies:


The build script (build.ps1)

PowerShell orchestrator (invoked from the Makefile windows-app target or directly). Steps:

  1. Locate the venv (.venv\Scripts\python.exe); derive Python version, base_prefix (for include/ + libs/), and the python3XX DLL/lib names.
  2. Locate the toolchain — cl.exe + rc.exe. If not already on PATH, find Visual Studio via vswhere and import VsDevCmd.bat’s environment. If neither MSVC nor the Windows SDK is present, fail with install instructions (this is the Windows analog of the macOS build’s Xcode Command Line Tools requirement).
  3. Fetch + extract the matching embeddable CPython into <root>\runtime (cached under windows_app/.cache/).
  4. Assemble app code: copy xefm/ (as app/xefm/), the resolved puikit/ package, and LICENSE into app/; compileall them. 4b. Fetch libarchive into app\xefm\_bin\archive.dll — see below.
  5. Collect dependencies into Lib\site-packages via the shared tools/collect_dependencies.py (--include-deps-of puikit), then generate THIRD_PARTY_NOTICES.txt at the bundle root via tools/generate_third_party_notices.py. See the section above.
  6. Generate resources: version_generated.h (from $VERSION / xefm/init.py’s __version__) and XeFM.ico (via make_icon.py); compile XeFM.rc → XeFM.res.
  7. Compile launcher.c + XeFM.res → XeFM.exe (GUI subsystem).
  8. (optional) -Zip → build\XeFM-<version>-win64.zip for distribution.

Step 4b: the bundled libarchive

XeFM reads .7z, .rar, .iso, .cab, .cpio and .rpm through libarchive, via the pure-ctypes libarchive-c binding that Step 5 collects. That binding carries no binary of its own: on macOS and Linux it finds a system library, and Windows has none, so the bundle must carry one.

The DLL comes from a release asset of crftwr/xefm-bin-deps, not from this repository. It statically links zlib, bzip2, liblzma and libzstd, four libraries with vulnerability cycles of their own; keeping them in a separate repository means a CVE in any of them is answered by re-releasing there rather than by cutting an XeFM release.

It is pinned by release tag and SHA-256, never “latest” — a Store submission has to be reproducible, and a pin that moves when nobody is looking is not a pin. Bumping it means editing $LibarchiveTag and $LibarchiveSha256 together.

It lands inside the copied package, at app\xefm\_bin\archive.dll, rather than beside the exe. xefm/archive_libarchive.py finds it relative to its own __file__, so it needs no knowledge of the bundle layout and the same lookup would work from a wheel. The launcher is not involved.

The step then loads the DLL and checks its codec list, and fails the build if zlib, liblzma, bz2lib or libzstd is missing. That check is not belt-and-braces: XeFM’s capability probe answers a missing codec by not offering the format, so a truncated download or a wrongly configured library would otherwise ship as a perfectly working XeFM with .7z quietly absent. The licenses shipped in the asset are fed to Step 5b as --extra entries, since none of those components has a .dist-info for the scanner to find.

Usage

# from the project root (or windows_app/)
powershell -ExecutionPolicy Bypass -File windows_app\build.ps1
powershell -ExecutionPolicy Bypass -File windows_app\build.ps1 -Version 1.0.0 -Zip
powershell -ExecutionPolicy Bypass -File windows_app\build.ps1 -Clean

# or via make (Git-Bash):
make windows-app
make windows-zip
make clean-windows

Build requirements


Open items / future work

Build gotchas (found live standing this up)