fix(build): keep .py sources + make smoke test skip uninstalled modules
Lint & Test / test (push) Successful in 1m36s
Lint & Test / test (push) Successful in 1m36s
- compile_and_strip_sources: stop deleting .py files after compileall. OpenCV's loader does literal file I/O on cv2/config.py (not a Python import), so stripping it breaks `import cv2` with "missing configuration file: ['config.py']". Other packages may do similar file-based introspection tricks — the ~30% size win isn't worth playing whack-a-mole with broken installers. We already hit this with numpy.linalg and zeroconf._services; enough incidents. - smoke_test_imports: only assert importability for modules whose top-level dir actually exists in site-packages. Pillow for example is a Windows-only dep, and was failing the Linux build spuriously. Rewrote as a heredoc for readability.
This commit is contained in:
+58
-29
@@ -151,13 +151,20 @@ cleanup_site_packages() {
|
|||||||
echo " Site-packages after cleanup: $cleaned_size"
|
echo " Site-packages after cleanup: $cleaned_size"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ── Pre-compile .py → .pyc and strip sources ─────────────────
|
# ── Pre-compile .py → .pyc (keep sources) ────────────────────
|
||||||
#
|
#
|
||||||
# MUST run AFTER cleanup_site_packages (so we don't waste work compiling
|
# MUST run AFTER cleanup_site_packages. Speeds up first startup by
|
||||||
# files that are about to be deleted) and BEFORE any step that ships the
|
# producing .pyc alongside .py. We deliberately do NOT delete .py
|
||||||
# result. Uses `compileall -b` to produce legacy `foo.pyc` next to
|
# sources afterwards:
|
||||||
# `foo.py` (not `__pycache__/foo.cpython-XX.pyc`), which survives the
|
#
|
||||||
# __pycache__ cleanup and works without a matching .py file at import.
|
# 1. OpenCV's loader does literal file I/O on cv2/config.py (not
|
||||||
|
# an import) — stripping it breaks `import cv2` with:
|
||||||
|
# "OpenCV loader: missing configuration file: ['config.py']".
|
||||||
|
# 2. Other packages may do similar tricks (inspect.getsource,
|
||||||
|
# runtime introspection, __file__-relative data loading).
|
||||||
|
# 3. The size saving (~30%) isn't worth the whack-a-mole of
|
||||||
|
# shipping broken installers. We already hit this with
|
||||||
|
# numpy.linalg and zeroconf._services — enough incidents.
|
||||||
#
|
#
|
||||||
# Args:
|
# Args:
|
||||||
# $1 — directory to compile (site-packages or app/src)
|
# $1 — directory to compile (site-packages or app/src)
|
||||||
@@ -176,12 +183,9 @@ compile_and_strip_sources() {
|
|||||||
echo " ERROR: compileall failed for $target_dir — aborting"
|
echo " ERROR: compileall failed for $target_dir — aborting"
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
# Drop __pycache__ to save the duplicated PEP-3147 copies; the
|
||||||
echo " Removing .py source (keeping .pyc)..."
|
# `-b` flag above placed legacy .pyc next to each .py, so nothing
|
||||||
# Keep __init__.py so package discovery still works in edge cases
|
# of value is lost here.
|
||||||
# where namespace detection checks for the file.
|
|
||||||
find "$target_dir" -name "*.py" ! -name "__init__.py" -delete 2>/dev/null || true
|
|
||||||
# __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
|
find "$target_dir" -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,32 +213,57 @@ smoke_test_imports() {
|
|||||||
pypath="$extra_path:$sp_dir"
|
pypath="$extra_path:$sp_dir"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Modules that MUST import cleanly for the app to start.
|
# Modules that MUST import cleanly IF PRESENT. We don't enforce
|
||||||
# If you add a new top-level dependency, add it here.
|
# installation — Pillow for example is only a Windows dep. But if a
|
||||||
PYTHONPATH="$pypath" "$py_cmd" -c "
|
# module's top-level package dir exists in site-packages and we
|
||||||
|
# can't import it, that's a broken install and we abort.
|
||||||
|
PYTHONPATH="$pypath" "$py_cmd" - "$sp_dir" <<'PYEOF' || {
|
||||||
|
echo " ERROR: smoke test failed — site-packages is broken, aborting build"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
import importlib
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
modules = [
|
|
||||||
'numpy', 'numpy.linalg', 'numpy.lib', 'numpy.matrixlib',
|
sp_dir = sys.argv[1]
|
||||||
'cv2',
|
|
||||||
'fastapi', 'uvicorn', 'starlette', 'pydantic',
|
# (module_name, site-packages path to check for presence)
|
||||||
'zeroconf',
|
candidates = [
|
||||||
'PIL', 'PIL.Image',
|
('numpy', 'numpy'),
|
||||||
'yaml',
|
('numpy.linalg', 'numpy/linalg'),
|
||||||
|
('numpy.lib', 'numpy/lib'),
|
||||||
|
('numpy.matrixlib', 'numpy/matrixlib'),
|
||||||
|
('cv2', 'cv2'),
|
||||||
|
('fastapi', 'fastapi'),
|
||||||
|
('uvicorn', 'uvicorn'),
|
||||||
|
('starlette', 'starlette'),
|
||||||
|
('pydantic', 'pydantic'),
|
||||||
|
('zeroconf', 'zeroconf'),
|
||||||
|
('zeroconf._services', 'zeroconf/_services'),
|
||||||
|
('PIL', 'PIL'),
|
||||||
|
('PIL.Image', 'PIL'),
|
||||||
|
('yaml', 'yaml'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
tested = 0
|
||||||
|
skipped = 0
|
||||||
failed = []
|
failed = []
|
||||||
for mod in modules:
|
for mod, path in candidates:
|
||||||
|
if not os.path.exists(os.path.join(sp_dir, path)):
|
||||||
|
skipped += 1
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
__import__(mod)
|
importlib.import_module(mod)
|
||||||
|
tested += 1
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
failed.append(f'{mod}: {type(e).__name__}: {e}')
|
failed.append(f'{mod}: {type(e).__name__}: {e}')
|
||||||
|
|
||||||
if failed:
|
if failed:
|
||||||
print('SMOKE TEST FAILED:', file=sys.stderr)
|
print('SMOKE TEST FAILED:', file=sys.stderr)
|
||||||
for f in failed:
|
for f in failed:
|
||||||
print(f' {f}', file=sys.stderr)
|
print(f' {f}', file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
print(' Smoke test passed (imported', len(modules), 'modules)')
|
|
||||||
" || {
|
print(f' Smoke test passed ({tested} imported, {skipped} not installed)')
|
||||||
echo " ERROR: smoke test failed — site-packages is broken, aborting build"
|
PYEOF
|
||||||
return 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user