fix(build): repair the add-on image build (wheel collision + Python 3.14 base) - #34
Merged
Merged
Conversation
…-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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the
Build HA Add-on Imagefailure onmain(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-depsexiting 1, which reads like a dependency problem. The real error is further up the log:pyproject.tomlhad both:packagesalready ships every file undersrc/span_panel_simulator. Theforce-includere-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 namesforce-includeas 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. Theforce-includedates to284167e; 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, butrequires-python = ">=3.14"(pinned deliberately inb195c86,dbd6ddd). The old build died during metadata generation and never got far enough to report this. Against a real 3.13 interpreter:So the force-include fix alone would have produced a green-looking change that still failed CI.
Fix
force-includetable.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), theDockerfileBUILD_FROMdefault,build.yaml(both arches), and theDEVELOPER.mdbuild 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.yamlonly triggers onpushtomain— these PR checks do not exercise the Docker build at all.uv build --wheelfails with the identicalValueErrorbefore the change, succeeds afterfindover the source tree exactly — removing the force-include drops nothingRequires-Pythonrejection above1.0.12, and carries all 21 dashboard assetsruffcleanWorth 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 beingebus-sdk0.12.0 in #30). An upper bound onhatchlingwould make add-on builds reproducible. Not done here: it trades away backend fixes and deserves its own decision.Also note
build-addon.yamlruns only onpushtomain, 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.