diff --git a/build_legacy.sh b/build_legacy.sh index 32bd0770..3b4eb5fc 100755 --- a/build_legacy.sh +++ b/build_legacy.sh @@ -5961,6 +5961,16 @@ run_full_only_esp32_profile() { return 0 } +is_ordinary_partition_migration_full_target() { + is_esp32_partition_migration_full_target "$1" || return 1 + # G2's plain identity is built only inside its migration package; the + # observer identity is the one ordinary Full release image for each role. + case "$1" in + Station_G2_repeater|Station_G2_room_server) return 1 ;; + esac + return 0 +} + run_partition_migration_full_esp32_profile() { local targets=("$@") local target @@ -5977,7 +5987,7 @@ run_partition_migration_full_esp32_profile() { local original_esp32_full_build=$ESP32_FULL_BUILD for target in "${targets[@]}"; do - if ! is_esp32_partition_migration_full_target "$target" \ + if ! is_ordinary_partition_migration_full_target "$target" \ || [ -n "${seen_migration_targets[$target]+x}" ]; then continue fi @@ -6130,7 +6140,7 @@ run_logging_matrix_build_targets() { echo "Publishing ${full_only_exact_count} audited ESP32 target(s) as their exact-identity FULL release only; explicit --standard remains available for recovery." fi for target in "${targets[@]}"; do - if is_esp32_partition_migration_full_target "$target"; then + if is_ordinary_partition_migration_full_target "$target"; then partition_migration_full_count=$((partition_migration_full_count + 1)) fi done diff --git a/scripts/build_local_release.py b/scripts/build_local_release.py index 5e010071..f35fa83e 100644 --- a/scripts/build_local_release.py +++ b/scripts/build_local_release.py @@ -97,7 +97,7 @@ def build_migration_artifacts(work: Path, version: str, short_source: str, raise RuntimeError(f"migration utility {bridge} failed; see {log}") -def stage_release(work: Path, destination: Path, version: str, +def stage_release(work: Path, migration_work: Path, destination: Path, version: str, source: str) -> None: if destination.exists(): raise FileExistsError(f"release already exists: {destination}") @@ -105,7 +105,10 @@ def stage_release(work: Path, destination: Path, version: str, short_source = source[:8] tooling_source = git("rev-parse", "HEAD") artifact_version = f"{version}-{short_source}" - records = collect_artifacts(work, artifact_version) + records = [record for record in collect_artifacts(work, artifact_version) + if not (record["manifest"]["build_profile"] == "full" + and record["manifest"]["target"] in { + "Station_G2_repeater", "Station_G2_room_server"})] if not records: raise ValueError("firmware matrix produced no qualified artifacts") @@ -134,7 +137,7 @@ def stage_release(work: Path, destination: Path, version: str, migrations.mkdir() with tempfile.TemporaryDirectory(prefix="migration-package-", dir=staging) as temp_packages: package_command = [sys.executable, "-B", "scripts/package_esp32_partition_migration.py", - "--build-dir", str(work), "--output-dir", temp_packages, + "--build-dir", str(migration_work), "--output-dir", temp_packages, "--version", version, "--source", short_source] package_log = work / "migration-packaging.log" with package_log.open("w", encoding="utf-8") as output: @@ -222,9 +225,15 @@ def main() -> None: "--radio-preset", "usa-cascade-fixed", "--profile", "cascade", "--require-ota", "--skip-kiss", "--resume", ], work / "release-build.log", environment) - build_migration_artifacts(work, args.firmware_version, short_source, - args.pio_jobs, environment) - stage_release(work, destination, args.firmware_version, source) + # Keep exact-identity migration inputs outside the ordinary firmware + # matrix. collect_artifacts() walks recursively, so nesting these under + # work would still publish a second Full image for the same G2 role. + migration_work = ROOT / ".releases" / f".migration-build-{label}" + migration_environment = environment.copy() + migration_environment["OUTPUT_DIR"] = str(migration_work) + build_migration_artifacts(migration_work, args.firmware_version, short_source, + args.pio_jobs, migration_environment) + stage_release(work, migration_work, destination, args.firmware_version, source) if __name__ == "__main__": diff --git a/test/test_full_infrastructure_selection.sh b/test/test_full_infrastructure_selection.sh index 5b79889c..27c56b74 100644 --- a/test/test_full_infrastructure_selection.sh +++ b/test/test_full_infrastructure_selection.sh @@ -41,6 +41,8 @@ assert_auto_unified heltec_v4_r8_tft_repeater heltec_v4_r8_tft_repeater_observer assert_auto_unified Heltec_T190_repeater_ Heltec_T190_repeater_observer_mqtt assert_auto_unified LilyGo_TLora_V2_1_1_6_room_server LilyGo_TLora_V2_1_1_6_room_server_observer_mqtt_ assert_auto_unified heltec_v4_r8_repeater_observer_mqtt heltec_v4_r8_repeater_observer_mqtt +assert_auto_unified Station_G2_repeater Station_G2_repeater_observer_mqtt +assert_auto_unified Station_G2_room_server Station_G2_room_server_observer_mqtt # A one-board migration package must retain the old repeater's mOTA identity, # even when its Full sources come from the matching observer environment. @@ -275,16 +277,32 @@ done || fail "matrix did not build exact FULL-only identities" ) +( + calls=() + run_logged_build_targets() { + calls+=("$BUILD_PROFILE_EFFECTIVE:$ESP32_FULL_BUILD:$FIRMWARE_FILENAME_INFIX:$*") + } + run_logging_matrix_build_targets Station_G2_repeater \ + Station_G2_repeater_observer_mqtt >/dev/null + full_calls=() + for call in "${calls[@]}"; do + [[ "$call" == full:1:* ]] && full_calls+=("$call") + done + [ "${#full_calls[@]}" -eq 1 ] || fail "G2 repeater matrix emitted two Full images" + [[ "${full_calls[0]}" == *:Station_G2_repeater_observer_mqtt ]] \ + || fail "G2 repeater matrix lost the observer OTA identity" +) + ( calls=() run_logged_build_targets() { calls+=("$BUILD_PROFILE_EFFECTIVE:$ESP32_FULL_BUILD:$FIRMWARE_FILENAME_INFIX:$*") } run_partition_migration_full_esp32_profile \ - Station_G2_repeater Station_G2_room_server Heltec_v3_repeater >/dev/null + Station_G2_repeater Station_G2_room_server ThinkNode_M2_Repeater >/dev/null [ "${#calls[@]}" -eq 1 ] || fail "partition-migration pass emitted the wrong number of builds" - [ "${calls[0]}" = 'full:1:full-logging:Station_G2_repeater Station_G2_room_server' ] \ - || fail "partition-migration pass lost canonical normal identities" + [ "${calls[0]}" = 'full:1:full-logging:ThinkNode_M2_Repeater' ] \ + || fail "partition-migration pass emitted duplicate G2 Full identities" ) # The default build settings retain power saving; this does not overwrite