fix(build): stop stripping zeroconf/_services + add import smoke test
Lint & Test / test (push) Successful in 2m39s

- build-common.sh: remove zeroconf/_services from the strip list.
  zeroconf's compiled Cython _listener.pyd imports from _services
  internally, so stripping it broke `import zeroconf` at runtime with
  ModuleNotFoundError — same class of bug as the numpy.linalg strip.
- build-common.sh: add smoke_test_imports() that imports every top-level
  dependency against the stripped site-packages. Catches "we stripped
  something that was actually needed" regressions at build time instead
  of on a user's machine after install.
- build-dist.sh: wire smoke test into the Linux flow (runs real imports).
- build-dist-windows.sh: cross-build can't load win_amd64 .pyd files with
  the host python, so instead verify that the known-required submodule
  dirs (numpy.linalg/lib/matrixlib/ma, zeroconf._services) exist after
  cleanup. Fails loud if any future strip-rule removes them.
This commit is contained in:
2026-04-07 23:32:50 +03:00
parent 9f34ffb0a0
commit fd6776aeac
3 changed files with 77 additions and 1 deletions
+59 -1
View File
@@ -131,7 +131,11 @@ cleanup_site_packages() {
done
# ── zeroconf ─────────────────────────────────────────────
rm -rf "$sp_dir/zeroconf/_services" 2>/dev/null || true
# DO NOT strip zeroconf/_services — the compiled Cython _listener.pyd
# imports from it, and the import fails at runtime with:
# ModuleNotFoundError: No module named 'zeroconf._services'
# Same class of bug as numpy — "presumed unused" submodule is actually
# imported internally by the package's own compiled code.
# ── Strip debug symbols ──────────────────────────────────
if command -v strip &>/dev/null; then
@@ -180,3 +184,57 @@ compile_and_strip_sources() {
# __pycache__ dirs are redundant now that we have legacy .pyc files
find "$target_dir" -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
}
# ── Import smoke test ────────────────────────────────────────
#
# Verifies that every top-level dependency that wled_controller actually
# uses can be imported from the stripped site-packages. Catches regressions
# where cleanup_site_packages removes a submodule that turns out to be
# imported internally by the package (e.g. numpy.linalg, zeroconf._services).
# Failing here is cheap; failing on a user's machine after install is not.
#
# Args:
# $1 — path to site-packages to test against
# $2 — python executable
# $3 — (optional) extra PYTHONPATH entry (e.g. app/src for wled_controller)
smoke_test_imports() {
local sp_dir="$1"
local py_cmd="${2:-python3}"
local extra_path="${3:-}"
echo " Running import smoke test..."
local pypath="$sp_dir"
if [ -n "$extra_path" ]; then
pypath="$extra_path:$sp_dir"
fi
# Modules that MUST import cleanly for the app to start.
# If you add a new top-level dependency, add it here.
PYTHONPATH="$pypath" "$py_cmd" -c "
import sys
modules = [
'numpy', 'numpy.linalg', 'numpy.lib', 'numpy.matrixlib',
'cv2',
'fastapi', 'uvicorn', 'starlette', 'pydantic',
'zeroconf',
'PIL', 'PIL.Image',
'yaml',
]
failed = []
for mod in modules:
try:
__import__(mod)
except Exception as e:
failed.append(f'{mod}: {type(e).__name__}: {e}')
if failed:
print('SMOKE TEST FAILED:', file=sys.stderr)
for f in failed:
print(f' {f}', file=sys.stderr)
sys.exit(1)
print(' Smoke test passed (imported', len(modules), 'modules)')
" || {
echo " ERROR: smoke test failed — site-packages is broken, aborting build"
return 1
}
}
+15
View File
@@ -279,6 +279,21 @@ find "$SITE_PACKAGES/winrt" -name "*.pyi" -delete 2>/dev/null || true
# the generated .pyc will ImportError on the target.
compile_and_strip_sources "$SITE_PACKAGES" "python"
# Windows cross-build: host python can't load win_amd64 .pyd files, so
# we can't `import numpy` for real. Instead, check that the submodules
# known to be imported internally exist on disk — the same landmines that
# cost users a broken v0.0.0.dev0 installer.
echo " Verifying required submodules exist after cleanup..."
for required in \
"numpy/linalg" "numpy/lib" "numpy/matrixlib" "numpy/ma" \
"zeroconf/_services"; do
if [ ! -d "$SITE_PACKAGES/$required" ] && [ ! -f "$SITE_PACKAGES/$required.py" ] && [ ! -f "$SITE_PACKAGES/$required.pyc" ]; then
echo " ERROR: $required missing from site-packages — cleanup_site_packages removed something required. Aborting."
exit 1
fi
done
echo " All required submodules present."
WHEEL_COUNT=$(ls "$WHEEL_DIR"/*.whl 2>/dev/null | wc -l)
echo " Installed $WHEEL_COUNT packages"
+3
View File
@@ -56,6 +56,9 @@ cleanup_site_packages "$SITE_PACKAGES" "so" "so"
# Pre-compile and strip .py sources (must happen AFTER cleanup)
compile_and_strip_sources "$SITE_PACKAGES" "python"
# Fail loud if cleanup broke any required import
smoke_test_imports "$SITE_PACKAGES" "python"
# ── Build frontend ───────────────────────────────────────────
echo "[4/7] Building frontend..."