Find the ESP-IDF build directory under either cargo layout

2131e3c · Gabriel Morell · 2026-09-16 18:56

3 files +46 -19
Message
{commit_body(@commit)}

Files changed

modified CLAUDE.md
+16 −6
@@ -268,12 +268,22 @@ pulls a new one — v5.1.1 wanted 15.0.0, v5.2.3 wants 16.0.1).
268 268
269 269 ### Other build-cache traps
270 270
271 −- `cargo clean -p esp-idf-sys` does **not** clear embuild's CMake tree — it
272 − lives at `target/<triple>/release/build/esp-idf-sys/<hash>/` (nested dir, not
273 − cargo's `esp-idf-sys-<hash>`). A stale `CMakeCache.txt` there pinned gcc
274 − 12.2.0 after the ESP-IDF version bump and produced
275 − `Tool doesn't match supported version from list ['esp-13.2.0_20230928']`.
276 − Fix: `rm -rf target/<triple>/release/build/esp-idf-sys`.
271 +- `cargo clean -p esp-idf-sys` does **not** clear embuild's CMake tree. A stale
272 + `CMakeCache.txt` there pinned gcc 12.2.0 after the ESP-IDF version bump and
273 + produced `Tool doesn't match supported version from list
274 + ['esp-13.2.0_20230928']`.
275 +- **There are two possible paths for that tree, and this repo has both.** Older
276 + cargo writes `build/esp-idf-sys-<hash>/out`; the cargo in
277 + nightly-2026-04-27 writes `build/esp-idf-sys/<hash>/out`… and then writes the
278 + flat one again. Whatever the rule is, do not assume: `tools/idf-build-dir.sh`
279 + matches `*esp-idf-sys*/out/build` under either, and picks the one whose
280 + `bootloader/bootloader.bin` is **newest**, because cargo leaves the old
281 + directories in place and the stale one is indistinguishable by name. CI run
282 + #6 failed on exactly this — a fresh tree had only the flat layout, which the
283 + first version of that script did not match — and locally it was quietly
284 + resolving to a month-old build directory, which is how you flash a bootloader
285 + built from a different sdkconfig and get a boot loop instead of an error.
286 +- To clear it: `rm -rf target/<triple>/release/build/esp-idf-sys*`.
277 287 - ESP-IDF tool installation is not atomic; a mid-way failure leaves a partial
278 288 install that reports the same generic `Could not install all requested
279 289 Tools`. Re-running works, or drive it directly:
modified tools/idf-build-dir.sh
+30 −13
@@ -1,26 +1,43 @@
1 1 #!/usr/bin/env bash
2 −# Print the ESP-IDF CMake build directory for the release profile.
2 +# Print the ESP-IDF CMake build directory matching the ELF cargo just linked.
3 3 #
4 −# embuild puts it under a content hash, and cargo keeps the old directory when
5 −# the hash changes — so there can be several and only the newest is the one
6 −# that matches the ELF cargo just linked. Picking the first alphabetically
7 −# (which is what a glob does) is how you end up flashing a bootloader built
8 −# from a different sdkconfig, which fails as a boot loop rather than as an
9 −# error. Newest wins.
4 +# Two things make this more than a glob.
10 5 #
11 −# Prints nothing and exits 1 if the tree has not been built.
6 +# **Cargo has two layouts.** Older cargo puts a build script's output at
7 +# `build/<pkg>-<hash>/out`; newer cargo nests it as `build/<pkg>/<hash>/out`.
8 +# A tree that has been built by both — this one has — contains both, and CI
9 +# on a current nightly produces only the flat one. Match either.
10 +#
11 +# **Stale directories outlive the build that made them.** Cargo does not clean
12 +# up when a hash changes, so there can be several and only one matches the ELF
13 +# beside it. Taking the first alphabetically, which is what a glob does, is how
14 +# you flash a bootloader built from a different sdkconfig — which fails as a
15 +# boot loop rather than as an error. So: newest `bootloader/bootloader.bin`
16 +# wins, and a directory without one is not a candidate at all, since the
17 +# bootloader is the only reason anything asks for this path.
18 +#
19 +# Prints nothing and exits 1 if there is no usable directory, naming what it
20 +# did find on stderr.
12 21 set -euo pipefail
13 22
14 23 profile=${1:-release}
15 −root="target/riscv32imac-esp-espidf/${profile}/build/esp-idf-sys"
24 +root="target/riscv32imac-esp-espidf/${profile}/build"
16 25
17 26 newest=""
18 27 while IFS= read -r dir; do
19 − [ -d "$dir" ] || continue
20 − if [ -z "$newest" ] || [ "$dir" -nt "$newest" ]; then
28 + bootloader="$dir/bootloader/bootloader.bin"
29 + [ -f "$bootloader" ] || continue
30 + if [ -z "$newest" ] || [ "$bootloader" -nt "$newest/bootloader/bootloader.bin" ]; then
21 31 newest=$dir
22 32 fi
23 −done < <(find "$root" -maxdepth 3 -type d -path "*/out/build" 2>/dev/null)
33 +done < <(find "$root" -maxdepth 4 -type d -path "*esp-idf-sys*/out/build" 2>/dev/null)
34 +
35 +if [ -z "$newest" ]; then
36 + echo "idf-build-dir: no ESP-IDF build directory with a bootloader under $root" >&2
37 + echo "idf-build-dir: candidates seen:" >&2
38 + find "$root" -maxdepth 4 -type d -name "out" -path "*esp-idf-sys*" 2>/dev/null >&2 ||
39 + echo " (none — has this profile been built?)" >&2
40 + exit 1
41 +fi
24 42
25 −[ -n "$newest" ] || exit 1
26 43 printf '%s\n' "$newest"

Parents: 478e173