Skip to content

build(python): evaluate uv_build as the build backend #3887

Description

@mcosgriff

What is the use case or problem this feature would solve?

openc3/python builds with setuptools while the rest of its own toolchain is uv: the images run uv sync --frozen, CI runs uv run, and uv.lock is committed. The release job at .github/workflows/release.yml:62-69 therefore sets up a second toolchain — setup-python plus pip install build — only to produce the wheel.

Three files exist solely to serve that backend: setup.cfg (which holds nothing but [bdist_wheel] universal=0, a Python 2 era flag), MANIFEST.in, and the [tool.setuptools] tables in pyproject.toml.

Describe the solution you'd like

Move the build backend to uv_build. This config builds the package today, verified against uv 0.12.15:

[build-system]
requires = ["uv_build>=0.12,<0.13"]
build-backend = "uv_build"

[tool.uv.build-backend]
module-root = ""
module-name = "openc3"
source-include = ["examples/**"]

One change has to land first. uv_build has no equivalent of setuptools py-modules, so openc3pycli.py cannot ship as a top-level single-file module. Naming it explicitly fails the build:

error: Expected a Python module at: openc3pycli/__init__.py

Leaving it out builds cleanly but omits the file, so the openc3pycli = "openc3pycli:main" console script installs and then fails on import. Either make it a package (openc3pycli/__init__.py) or move it under openc3/. Both change the published module layout, so it wants its own decision rather than riding along with the backend swap.

Then, with the switch:

  • Port MANIFEST.in's recursive-include examples * to source-include; uv_build ignores MANIFEST.in.
  • Delete setup.cfg.
  • Replace python -m build with uv build in release.yml, dropping the setup-python step.

Additional context

Diffing the two wheels built from the same tree, the only content differences are openc3pycli.py and the legacy top_level.txt, both absent from the uv_build wheel. The remaining 28 differing entries are directory records uv_build writes and setuptools does not. The package is pure Python with no tracked data files under openc3/, so nothing else depends on setuptools behavior.

The sdists differ slightly around examples/ and need a real comparison before the switch, not a spot check.

Two other pyprojects declare the setuptools backend: openc3-cosmos-init/plugins/packages/openc3-cosmos-demo/pyproject.toml and playwright/fixtures/pw-test-pyproject.toml. Both set packages = [] because they exist only to declare dependencies, so neither needs a build backend at all — [tool.uv] package = false with no [build-system] covers that case. Out of scope here: how openc3/bin/uvinstall consumes those files has to be checked first.

Activity

  1. crawlchat commented on Sep 17, 2026

    @crawlchat

    This is a well-researched proposal and matches what's in the repo today. A few observations, plus a suggested path forward:

    Confirmed current state
    • pyproject.toml has exactly the setuptools bits described: [build-system] → setuptools.build_meta, [tool.setuptools] py-modules = ["openc3pycli"], and [tool.setuptools.packages.find] include = ["openc3*"].
    • setup.cfg is only the universal=0 flag, as you said.
    • MANIFEST.in only adds LICENSE.md and recursive-include examples *.
    • openc3pycli.py deliberately lives outside the openc3 package — its docstring explains this is intentional, so that OPENC3_NO_STORE can be set before openc3/__init__.py (and therefore openc3.environment) is imported. That constraint matters for your migration decision.
    On the openc3pycli decision

    Given that comment, moving openc3pycli.py under openc3/ would need care — if openc3.pycli (or wherever it lands) is imported, Python will still execute openc3/__init__.py first as part of package initialization, which is exactly the ordering problem the current top-level placement avoids.

    The "make it a package" option (openc3pycli/__init__.py at the top level, sibling to openc3/) preserves the existing import-order guarantee without changing behavior, and is likely the lower-risk choice. It only changes packaging layout (single-file module → package with one __init__.py), not where it lives relative to openc3/__init__.py.

    If moving under openc3/ is still desired, the OPENC3_NO_STORE guard would need to move somewhere that runs before openc3/__init__.py's side effects — that's a separate design question worth its own discussion, not something to fold into this ticket.

    Not verified (tool limit reached)

    I wasn't able to read .github/workflows/release.yml in this pass to confirm the exact setup-python / python -m build block, so I can't give you a verified diff for that file right now. The described change (drop setup-python, replace python -m build with uv build) is consistent with how the rest of the workflows use uv, but please have a maintainer confirm the exact YAML before merging.

    Suggested sequencing, matching your writeup:

    1. Land the openc3pycli packaging change (package-style, keeping it top-level) as its own PR.
    2. Do the real sdist diff for examples/** under source-include vs. MANIFEST.in's recursive-include examples * — don't skip this, since sdist contents affect what installs from source distributions.
    3. Switch pyproject.toml to uv_build, delete setup.cfg, delete MANIFEST.in, update release.yml.

    Given the level of detail already here, I'd suggest opening the PR with these steps — it looks like solid, self-contained work that an engineer can review quickly.

  2. self-assigned this
    on Sep 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or requestpython:uvPull requests that update python:uv codetriageMore information is needed

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions