fix(esp32): wipe persistent build/ when the resolved library set changes
The persistent per-target build/ caches ESP-IDF's cmake configuration, ninja's
incremental graph and ccache-backed objects, all assuming a stable component
set. When consecutive compiles on the same dir have a DIFFERENT resolved
user_libs set (a different project/user, or a different library manifest) that
cache is inconsistent and produces two real failures:
- cmake reconfigure intermittently fails ('cmake configure failed') even
though each manifest compiles fine on a clean dir;
- ninja/ccache reuse a previous compile's objects/headers, letting a
now-absent library slip through as a false-positive success against a lib
the current sketch/manifest no longer includes.
Fingerprint the materialized user_libs/ (sorted relative paths + sizes) and,
when it changes vs the last compile on this dir, wipe build/ to force a clean
configure. ccache (enabled) refills the objects so the rebuild stays cheap.
No-op on the ephemeral path and the first compile. Fixes both symptoms; will be
superseded by the fully ephemeral per-compile workspace (P1).
This commit is contained in:
parent
c671c9b21c
commit
161deb93e2
|
|
@ -779,6 +779,24 @@ class ESPIDFCompiler:
|
||||||
out[h] = cands
|
out[h] = cands
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _fingerprint_dir(d: Path) -> str:
|
||||||
|
"""Stable fingerprint of a directory's file set: sorted relative paths
|
||||||
|
+ sizes. Captures libraries added / removed / version-changed without
|
||||||
|
reading file contents. Empty / missing dir -> a stable constant."""
|
||||||
|
h = hashlib.sha256()
|
||||||
|
if d.is_dir():
|
||||||
|
for f in sorted(d.rglob('*')):
|
||||||
|
if f.is_file():
|
||||||
|
h.update(f.relative_to(d).as_posix().encode())
|
||||||
|
h.update(b'\0')
|
||||||
|
try:
|
||||||
|
h.update(str(f.stat().st_size).encode())
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
h.update(b'\0')
|
||||||
|
return h.hexdigest()
|
||||||
|
|
||||||
def _resolve_library_components(
|
def _resolve_library_components(
|
||||||
self,
|
self,
|
||||||
ext_headers: list[str],
|
ext_headers: list[str],
|
||||||
|
|
@ -1945,6 +1963,37 @@ class ESPIDFCompiler:
|
||||||
if main_cpp.exists():
|
if main_cpp.exists():
|
||||||
main_cpp.unlink()
|
main_cpp.unlink()
|
||||||
|
|
||||||
|
# ── Persistent-build-dir staleness guard ─────────────────────────
|
||||||
|
# The persistent build/ caches ESP-IDF's cmake configuration, ninja's
|
||||||
|
# incremental graph and (via ccache) compiled objects, all assuming a
|
||||||
|
# STABLE component set. When the resolved user_libs set changes between
|
||||||
|
# consecutive compiles on this dir (a different project/user, or a
|
||||||
|
# different library manifest) that cache is inconsistent: cmake
|
||||||
|
# reconfigure can fail ("cmake configure failed"), or ninja/ccache can
|
||||||
|
# reuse a previous compile's objects/headers and let a now-absent
|
||||||
|
# library slip through — a false-positive success against a lib the
|
||||||
|
# current sketch/manifest no longer includes. Force a clean configure
|
||||||
|
# by wiping build/ whenever the user_libs fingerprint changes. ccache
|
||||||
|
# (enabled) refills the objects so the rebuild stays cheap. No-op on
|
||||||
|
# the ephemeral path (fresh dir, no build/) and the first compile.
|
||||||
|
if _USE_PERSISTENT_DIR:
|
||||||
|
ul_fp = self._fingerprint_dir(project_dir / 'user_libs')
|
||||||
|
fp_sentinel = project_dir / '.user_libs_fingerprint'
|
||||||
|
prior_fp = (
|
||||||
|
fp_sentinel.read_text(encoding='utf-8').strip()
|
||||||
|
if fp_sentinel.exists() else ''
|
||||||
|
)
|
||||||
|
if ul_fp != prior_fp:
|
||||||
|
_bd = project_dir / 'build'
|
||||||
|
if _bd.exists():
|
||||||
|
logger.info(
|
||||||
|
'[espidf] resolved library set changed (%s -> %s); '
|
||||||
|
'wiping build/ for a clean configure',
|
||||||
|
prior_fp[:12] or 'none', ul_fp[:12],
|
||||||
|
)
|
||||||
|
shutil.rmtree(_bd, ignore_errors=True)
|
||||||
|
fp_sentinel.write_text(ul_fp, encoding='utf-8')
|
||||||
|
|
||||||
# Build using cmake + ninja (more portable than idf.py on Windows)
|
# Build using cmake + ninja (more portable than idf.py on Windows)
|
||||||
build_dir = project_dir / 'build'
|
build_dir = project_dir / 'build'
|
||||||
build_dir.mkdir(exist_ok=True)
|
build_dir.mkdir(exist_ok=True)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue