Skip to content

Building Artifacts

How beetkeeper's build artifacts are produced with Pants. All commands run from the repo root.

Artifact Pants target Notes
beetkeeper wheel src/python:beetkeeper-whl pure-python, py3-none-any
beetkeeper-plugin wheel src/beetsplug:plugin-whl pure-python
image PEXes //:beetkeeper-linux-amd64, //:beetkeeper-linux-arm64 thin, single-arch; bundled into the image
server image //:beetkeeper-server-image GHCR (db upgrade / run)
pants package //:beetkeeper-server-image   # builds the host-arch image (+ its PEX)
pants package src/python:beetkeeper-whl

The image PEXes (//:beetkeeper-linux-<arch>)

The Docker image bundles a thin, single-arch PEX built for its target Linux arch. Each is a pex_binary pinned via complete_platforms to one platform (linux-amd64 or linux-aarch64), so it carries only that arch's native wheels (e.g. llvmlite, pulled in via beets → numba). complete_platforms lets PEX resolve those foreign wheels locally — on a macOS dev machine or a native CI runner — without a foreign interpreter or emulator, so the same pants package works everywhere. The Dockerfile's ARG TARGETARCH selects the matching PEX at COPY time.

A complete platform is a JSON description of a target interpreter — its PEP 508 marker environment plus the full list of wheel tags it accepts — stored under 3rdparty/platforms/.

Only linux-amd64 / linux-aarch64 are targeted — the images are Linux-only. The musl-linux-* (Alpine) JSONs are kept for reference but unused: numba/llvmlite ship no cp314 musl wheel.

Regenerating the complete-platform JSONs

Regenerate whenever the target interpreter version changes (e.g. a Python minor bump). The files are tied to a specific cp314 interpreter; do not hand-edit or hand-copy subsets — a truncated tag list silently drops generic tags like py3-none-any and breaks pure-python wheel resolution.

The generator is pex3 interpreter inspect --markers --tags, run once per target. Its raw output includes a machine-specific path key that must be stripped; the jq filter below drops it and prepends the __meta_data__ note. Repeat steps 1–2 for each SLUG (linux-amd64, linux-aarch64).

  1. Generate the interpreter inspection inside a matching-architecture python:3.14-slim container — the non-native arch runs under QEMU on an Apple-silicon host:
    docker run --rm --platform linux/amd64 python:3.14-slim \
      sh -c 'pip install -q pex && pex3 interpreter inspect --markers --tags' > raw.json
    
  2. Strip path and write the committed file:
    SLUG=linux-amd64   # or linux-aarch64
    jq --arg m "Complete platform for cp314 ${SLUG}; generated by \`pex3 interpreter inspect --markers --tags\`." \
       '{__meta_data__: $m, marker_environment, compatible_tags}' raw.json \
       > "3rdparty/platforms/${SLUG}.json"
    
  3. Rebuild and confirm the arch's native llvmlite wheel is bundled:
    pants package //:beetkeeper-linux-amd64
    unzip -l dist/beetkeeper-linux-amd64.pex | grep -o 'llvmlite-[^/]*\.whl' | sort -u
    

FAQ

The JSON files list py38-* and cp310-* tags — does the PEX run on Python 3.8 / 3.10? No. compatible_tags lists the wheels a cp314 interpreter is willing to install, ordered by preference — not interpreters that can run the PEX. The list runs downward for backward compatibility: py38-none-* / py3-none-any are pure-Python wheels (a "3.8+, no C" wheel also runs on 3.14), and cp310-abi3-* are stable-ABI wheels (abi3 is forward-compatible, so a 3.10-ABI extension loads on 3.14). Note the files contain only cp310-abi3, never cp310-cp310 — a genuine 3.10-only binary wheel is not accepted. The actual runtime floor is 3.14, pinned by the JSON's marker_environment.python_full_version and the target's interpreter_constraints = [">=3.14,<3.15"] (pants.toml); the PEX rejects a 3.8/3.10 interpreter at launch. The older tags simply let the 3.14 build reuse older pure-python/abi3 wheels when no cp314-specific wheel exists — which is exactly why the full tag list matters (drop py3-none-any and pure-python deps like aiosqlite stop resolving).

Why not one fat multi-platform PEX for all arches? A single PEX bundling every arch's wheels would carry ~100 MB of foreign-platform llvmlite/numba that a fixed-arch container can never use. Since each image is one arch, a per-arch PEX is strictly smaller. The complete_platforms mechanism could produce a fat PEX (list all platforms on one pex_binary), but nothing consumes one today.

With execution_mode="venv", does the PEX pick up libraries from the virtualenv it's invoked from? No. venv mode is an internal startup optimization: on first run PEX materializes its own bundled distributions into a private venv under PEX_ROOT and re-execs into it for faster imports. It does not see the ambient environment's site-packages. PEXes are hermetic by default. Ambient-library visibility is a separate knob — inherit_path (PEX_INHERIT_PATH): false (default, isolated), fallback (PEX wins, ambient fills gaps), or prefer (ambient wins). The image PEXes set inherit_path="fallback" so a derived image can pip install an extra beets plugin and have the PEX pick it up.

Generating OpenAPI Spec JSON

To regenerate the OpenAPI spec JSON for the user reference site, run the following command:

pants run build_scripts:openapi-json-exporter