From 5e10a319abed358064ffb9eae7689e31e5372c6a Mon Sep 17 00:00:00 2001 From: mikecarper Date: Sun, 13 Sep 2026 09:37:50 -0700 Subject: [PATCH] Repair constrained nRF52 release builds and retain size-failure diagnostics --- .github/workflows/run-unit-tests.yml | 1 + build.sh | 62 +++++++++++++----- docs/releases/1.17.1.6.md | 14 +++++ test/test_build_size_detection.sh | 94 ++++++++++++++++++++++++++++ 4 files changed, 157 insertions(+), 14 deletions(-) create mode 100644 test/test_build_size_detection.sh diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml index 8fa5e74d6..a2c37c720 100644 --- a/.github/workflows/run-unit-tests.yml +++ b/.github/workflows/run-unit-tests.yml @@ -116,6 +116,7 @@ jobs: - name: Verify firmware memory budgets and nRF52 Bluetooth startup run: | + bash test/test_build_size_detection.sh python3 -B test/test_firmware_ram.py python3 -B test/test_cascade_release_package.py python3 -B test/test_t096_full_memory.py diff --git a/build.sh b/build.sh index 9389001fb..73424b5f0 100755 --- a/build.sh +++ b/build.sh @@ -2696,6 +2696,9 @@ requires_dram_limited_neighbors() { # 8 KiB static reserve at 254 neighbors. Use their board-declared 50-entry # tables when the OTA manager is included. case "${1,,}" in + heltec_t096_repeater_lora_ota_no_external_sensors|\ + heltec_t1_repeater_lora_ota_no_external_sensors) + return 0 ;; generic_e22_sx1262_repeater_lora_ota_no_external_sensors|\ generic_e22_sx1268_repeater_lora_ota_no_external_sensors|\ heltec_v2_repeater_lora_ota_no_external_sensors|\ @@ -3266,7 +3269,8 @@ apply_nrf52_size_profile() { if [ "${PIO_ENV_PLATFORM_BY_NAME[$env_name]:-}" != "NRF52_PLATFORM" ]; then return 0 fi - if ! is_nrf52_companion_radio_full_target "$env_name" \ + if ! is_repeater_role_target "$env_name" \ + && ! is_nrf52_companion_radio_full_target "$env_name" \ && { ! is_lora_ota_build "$env_name" \ || ! is_lora_ota_only_target "$env_name"; }; then return 0 @@ -3276,10 +3280,26 @@ apply_nrf52_size_profile() { # runtime software Ed25519 fallback is linked alongside CC310, that setting # fully expands repeated Curve25519 arithmetic and wastes tens of kilobytes. # Keep hardware crypto, RNG mixing, the software fallback, and board features; - # select the size optimizer for both constrained self-updating images and - # Full Companion source images, especially their diagnostic profile. + # select the size optimizer for repeaters, constrained self-updating images + # and Full Companion source images, especially their diagnostic profile. + # Full-sensor repeaters also need this after adding the second radio profile; + # changing optimization preserves their sensors and protocol features. append_platformio_build_unflags "-Ofast" export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -Os" + + # These color-display repeaters reserve a 64 KiB reset-retained OTA arena + # plus a 25 KiB framebuffer. Keep their board-declared 50 neighbours and + # the complete flood-rule engine with a smaller table so runtime startup + # allocations also fit; do not lower the heap qualification requirement. + case "${env_name,,}" in + heltec_t096_repeater_lora_ota_no_external_sensors|\ + heltec_t1_repeater_lora_ota_no_external_sensors) + append_platformio_build_unflags "-DFLOOD_PACKET_FILTER_SLOTS=63" + export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DFLOOD_PACKET_FILTER_SLOTS=16" + record_build_reduction \ + "mesh.flood_rules limited to 16 by measured internal RAM; complete rule engine, color display, GPS, and OTA retained" + ;; + esac } apply_lora_ota_no_external_sensors_profile() { @@ -4029,6 +4049,29 @@ restore_platformio_build_flags() { fi } +run_pio_with_size_detection() { + local build_output_log + local build_status + local -a output_statuses + # SCons can delete the environment's build directory when flags change. + # A tee log inside that directory is then unlinked while still open, hiding + # the size error from the measured-size fallback after PlatformIO exits. + build_output_log=$(mktemp "${TMPDIR:-/tmp}/meshcore-build-XXXXXX.log") || return 1 + pio "$@" 2>&1 | tee "$build_output_log" + output_statuses=("${PIPESTATUS[@]}") + build_status=${output_statuses[0]} + if [ "${output_statuses[1]}" -ne 0 ]; then + build_status=1 + elif [ "$build_status" -ne 0 ] \ + && grep -Eiq \ + 'will not fit in region|region .+ overflowed by|section .+ will not fit|sketch too big|program size is greater than maximum|exceed(s|ing).*(flash|partition|app)' \ + "$build_output_log"; then + build_status=42 + fi + rm -f -- "$build_output_log" + return "$build_status" +} + build_firmware() { local env_name=$1 local pio_env_name @@ -4271,17 +4314,8 @@ build_firmware() { pio_run_args+=(-t mergebin) fi if [ "$build_status" -eq 0 ]; then - local build_output_dir="${PIO_BUILD_DIR_OVERRIDE:-${PLATFORMIO_BUILD_DIR:-.pio/build}}/${pio_env_name}" - local build_output_log="${build_output_dir}/.meshcore-build-output.log" - mkdir -p -- "$build_output_dir" - pio "${pio_run_args[@]}" 2>&1 | tee "$build_output_log" - build_status=${PIPESTATUS[0]} - if [ "$build_status" -ne 0 ] \ - && grep -Eiq \ - 'will not fit in region|region .+ overflowed by|section .+ will not fit|sketch too big|program size is greater than maximum|exceed(s|ing).*(flash|partition|app)' \ - "$build_output_log"; then - build_status=42 - fi + run_pio_with_size_detection "${pio_run_args[@]}" + build_status=$? fi if [ "$build_status" -eq 0 ]; then collect_build_artifacts "$env_name" "$env_platform" "$pio_env_name" "$firmware_filename" diff --git a/docs/releases/1.17.1.6.md b/docs/releases/1.17.1.6.md index 56a1415c4..3153d760f 100644 --- a/docs/releases/1.17.1.6.md +++ b/docs/releases/1.17.1.6.md @@ -95,6 +95,20 @@ before choosing an update method. ## Reproduce the release +Follow-up builds repair the constrained profiles without relaxing qualification: + +- nRF52 repeaters use `-Os` instead of `-Ofast`, retaining their sensor drivers + and protocol features while reducing flash use. +- T096 and T1 LoRa OTA repeaters retain their board-defined 50 neighbors and + use 16 flood-rule slots. The complete rule engine, color display, GPS and OTA + remain enabled. The T096's available startup heap rises from 72,492 to 81,468 + bytes, and the T1's from 72,868 to 81,844 bytes. Required startup allocations + fall from 88,410 to 79,010 bytes. These are linked capacity checks, not soak + measurements. Full Companion capacities are unchanged. +- Size-failure detection keeps its temporary output log outside PlatformIO's + build tree, so SCons cleanup cannot erase the evidence needed by the measured + size fallback. CI exercises cleanup, ordinary compiler failures and log errors. + Use a clean checkout of the release commit and a fresh output directory: ```bash diff --git a/test/test_build_size_detection.sh b/test/test_build_size_detection.sh new file mode 100644 index 000000000..38304a4d1 --- /dev/null +++ b/test/test_build_size_detection.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env bash +set -euo pipefail +cd "$(dirname "$0")/.." +source build.sh + +fail() { echo "test_build_size_detection: $*" >&2; exit 1; } +test_root=$(mktemp -d "${TMPDIR:-/tmp}/meshcore-size-tests.XXXXXX") +test_root=$(realpath "$test_root") +[[ "$test_root" == /*/meshcore-size-tests.* ]] || fail "unexpected temporary path" +trap 'rm -rf -- "$test_root"' EXIT +export TMPDIR=$test_root +managed_dir=$test_root/build/test +pio() { + # Model the real SCons cleanup that previously unlinked the tee log. + [[ "$managed_dir" == "$test_root/"* ]] || return 99 + rm -rf -- "$managed_dir" + touch "$test_root/pio-called" + printf '%s\n' "$pio_message" + return "$pio_result" +} + +check_status() { + local expected=$1 actual=0 + mkdir -p "$managed_dir" + if run_pio_with_size_detection run -e test > "$test_root/output" 2>&1; then + actual=0 + else + actual=$? + fi + [ "$actual" -eq "$expected" ] || fail "expected status $expected, got $actual" + [ ! -d "$managed_dir" ] || fail "fake PlatformIO did not clean its build directory" + if compgen -G "$test_root/meshcore-build-*.log" > /dev/null; then + fail "temporary build-output log was not released" + fi +} + +pio_result=1 +for pio_message in \ + "firmware.elf section .text will not fit in region FLASH" \ + "region FLASH overflowed by 43080 bytes" \ + "Error: The program size is greater than maximum allowed"; do + check_status 42 + grep -Fq "$pio_message" "$test_root/output" || fail "compiler output was lost" +done +pio_message='error: missing declaration' +check_status 1 +pio_result=2 +check_status 2 +pio_result=0 +pio_message='compilation succeeded' +check_status 0 + +# An output failure must not look like a successful or merely oversized build. +tee() { cat > /dev/null; return 1; } +check_status 1 +unset -f tee +rm -f "$test_root/pio-called" +TMPDIR=$test_root/missing +if run_pio_with_size_detection run -e test > /dev/null 2>&1; then + fail "missing log directory was accepted" +fi +[ ! -e "$test_root/pio-called" ] || fail "PIO ran without diagnostic storage" + +# Keep sensors and protocol features while shrinking nRF52 repeater code. +PIO_ENV_PLATFORM_BY_NAME[ikoka_nano_nrf_22dbm_repeater]=NRF52_PLATFORM +PLATFORMIO_BUILD_FLAGS='' +PLATFORMIO_BUILD_UNFLAGS='' +apply_nrf52_size_profile ikoka_nano_nrf_22dbm_repeater +[[ "$PLATFORMIO_BUILD_FLAGS" == *' -Os'* ]] || fail "full-sensor repeater omitted size optimization" +[[ "$PLATFORMIO_BUILD_UNFLAGS" == *'-Ofast'* ]] || fail "aggressive optimization was not removed" +[[ "$PLATFORMIO_BUILD_FLAGS" != *'-UENV_INCLUDE_'* ]] || fail "sensor support was reduced" +for target in Heltec_t096_repeater_lora_ota_no_external_sensors Heltec_t1_repeater_lora_ota_no_external_sensors; do + PIO_ENV_PLATFORM_BY_NAME[$target]=NRF52_PLATFORM + PLATFORMIO_BUILD_FLAGS='' + PLATFORMIO_BUILD_UNFLAGS='' + apply_repeater_neighbor_capacity "$target" + apply_nrf52_size_profile "$target" + [[ "$PLATFORMIO_BUILD_FLAGS" == *'-DMAX_NEIGHBOURS=50'* ]] || fail "$target omitted RAM-safe neighbours" + [[ "$PLATFORMIO_BUILD_FLAGS" == *'-DFLOOD_PACKET_FILTER_SLOTS=16'* ]] || fail "$target omitted RAM-safe rule table" + [[ "$PLATFORMIO_BUILD_FLAGS" != *'MESH_MIN_RUNTIME_HEAP'* ]] || fail "runtime RAM guard was overridden" +done +for target in Heltec_t096_companion_radio_full_femon RAK_4631_repeater; do + PIO_ENV_PLATFORM_BY_NAME[$target]=NRF52_PLATFORM + PLATFORMIO_BUILD_FLAGS='' + PLATFORMIO_BUILD_UNFLAGS='' + apply_nrf52_size_profile "$target" + [[ "$PLATFORMIO_BUILD_FLAGS" != *'FLOOD_PACKET_FILTER_SLOTS'* ]] || fail "$target received unrelated table reductions" + [[ "$PLATFORMIO_BUILD_FLAGS" != *'MAX_NEIGHBOURS'* ]] || fail "$target received unrelated neighbour reductions" +done +PIO_ENV_PLATFORM_BY_NAME[Heltec_v4_repeater]=ESP32_PLATFORM +PLATFORMIO_BUILD_FLAGS='' +apply_nrf52_size_profile Heltec_v4_repeater +[ -z "$PLATFORMIO_BUILD_FLAGS" ] || fail "nRF52 policy changed an ESP32 target" +echo 'Build-size diagnostics and constrained nRF52 recipes passed.'