Skip to content

fix(build): repair the add-on image build (wheel collision + Python 3.14 base) - #34

Merged
cayossarian merged 2 commits into
mainfrom
fix/wheel-force-include-collision
Jul 31, 2026
Merged

fix(build): repair the add-on image build (wheel collision + Python 3.14 base)#34
cayossarian merged 2 commits into
mainfrom
fix/wheel-force-include-collision

Conversation

@cayossarian

@cayossarian cayossarian commented Jul 31, 2026

Copy link
Copy Markdown
Member

Fixes the Build HA Add-on Image failure on main (run 30606212505). Two independent problems blocked it; the first masked the second.

Problem 1 — duplicate file in the wheel

The job summary blames pip install --no-cache-dir . && apk del .build-deps exiting 1, which reads like a dependency problem. The real error is further up the log:

ValueError: A second file is being added to the wheel archive at the same path:
  `span_panel_simulator/dashboard/static/dashboard.css`

pyproject.toml had both:

[tool.hatch.build.targets.wheel]
packages = ["src/span_panel_simulator"]

[tool.hatch.build.targets.wheel.force-include]
"src/span_panel_simulator/dashboard/templates" = "span_panel_simulator/dashboard/templates"
"src/span_panel_simulator/dashboard/static"    = "span_panel_simulator/dashboard/static"

packages already ships every file under src/span_panel_simulator. The force-include re-added those two directories at the same destination paths, so hatchling was asked to write each of those 21 files into the wheel twice — its own error message names force-include as the likely cause.

Nothing in this repo changed to trigger it. requires = ["hatchling"] is unpinned and build isolation resolves the backend fresh each time, so the build picked up a newer hatchling that rejects duplicate paths. The force-include dates to 284167e; the add-on build last passed 2026-04-27. The duplicate was always wrong — the new backend stopped hiding it. Editable installs use a different build path, which is why local dev never saw it.

Problem 2 — base image is Python 3.13, project requires 3.14

Fixing the wheel exposes the next wall. The base image is base-python:3.13-alpine3.21, but requires-python = ">=3.14" (pinned deliberately in b195c86, dbd6ddd). The old build died during metadata generation and never got far enough to report this. Against a real 3.13 interpreter:

Because the current Python version (3.13.2) does not satisfy Python>=3.14
and span-panel-simulator==1.0.12 depends on Python>=3.14, we can conclude
that span-panel-simulator==1.0.12 cannot be used.

So the force-include fix alone would have produced a green-looking change that still failed CI.

Fix

  1. Remove the redundant force-include table.
  2. Move the add-on base image to base-python:3.14-alpine3.23 — confirmed present for both build arches by querying the GHCR tag list. Updated in all five places the old tag appeared: the workflow matrix (both arches), the Dockerfile BUILD_FROM default, build.yaml (both arches), and the DEVELOPER.md build example.

Bumping the base is the right side to change: the 3.14 pin is deliberate and recent, and relaxing it would undo that decision.

Verification

Everything below was run locally, since build-addon.yaml only triggers on push to main — these PR checks do not exercise the Docker build at all.

  • uv build --wheel fails with the identical ValueError before the change, succeeds after
  • the built wheel contains 2 static assets and 19 templates, matching find over the source tree exactly — removing the force-include drops nothing
  • installing that wheel into a 3.13 venv reproduces the Requires-Python rejection above
  • installing into a 3.14 venv succeeds; the package imports, reports 1.0.12, and carries all 21 dashboard assets
  • full suite: 241 passed; ruff clean

Worth considering separately

requires = ["hatchling"] is unbounded, so the build backend can change under this project at any time — which is exactly what happened, and the second unpinned-dependency drift to break this repo this week (the first being ebus-sdk 0.12.0 in #30). An upper bound on hatchling would make add-on builds reproducible. Not done here: it trades away backend fixes and deserves its own decision.

Also note build-addon.yaml runs only on push to main, so a broken image build cannot be caught before merge. Running it on PRs (build only, no push to GHCR) would have caught both of these.

…-on image

The add-on Docker build failed on `pip install .`:

    ValueError: A second file is being added to the wheel archive at the
    same path: `span_panel_simulator/dashboard/static/dashboard.css`

`packages = ["src/span_panel_simulator"]` already ships every file under the
package, dashboard/templates and dashboard/static included. The
`force-include` table re-added those two directories at the same destination
paths, so hatchling was asked to write each of those 21 files into the wheel
twice. Older hatchling tolerated it; the current one refuses.

Nothing in this repo changed to trigger it — `requires = ["hatchling"]` is
unpinned, and pip's build isolation resolves the backend fresh on every build,
so the tolerant version this was authored against is gone for good. The
duplicate was always wrong; the newer backend just stopped hiding it.

Verified by building the wheel locally: it failed with the identical error
before this change, succeeds after, and the built wheel still contains all 2
static assets and all 19 templates — matching the source tree exactly, so
removing the force-include drops nothing.
Removing the duplicate wheel entry was necessary but not sufficient. The
add-on base image is `base-python:3.13-alpine3.21`, while the project pinned
`requires-python = ">=3.14"` (b195c86, dbd6ddd). The previous build never got
far enough to report it — it died during metadata generation — but once the
wheel builds, `pip install .` refuses it on 3.13:

    Because the current Python version (3.13.2) does not satisfy
    Python>=3.14 and span-panel-simulator==1.0.12 depends on Python>=3.14,
    we can conclude that span-panel-simulator==1.0.12 cannot be used.

Reproduced against a real 3.13 interpreter, not inferred from metadata.

Home Assistant publishes 3.14 base images for both build arches; confirmed
`3.14-alpine3.23` exists for aarch64 and amd64 by querying the GHCR tag list.
Bumping the base is the right side to change — the 3.14 pin is deliberate and
recent, and relaxing it would undo that decision.

Updated in all five places the old tag appeared: the build workflow matrix
(both arches), the Dockerfile's BUILD_FROM default, build.yaml (both arches),
and the DEVELOPER.md build example.

Verified end to end: the wheel installs into a 3.14 venv, imports, reports
version 1.0.12, and carries all 2 static assets and 19 templates.
@cayossarian cayossarian changed the title fix(build): drop the redundant wheel force-include that broke the add-on image fix(build): repair the add-on image build (wheel collision + Python 3.14 base) Jul 31, 2026
@cayossarian
cayossarian merged commit cab9e4c into main Jul 31, 2026
2 checks passed
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.

1 participant