summaryrefslogtreecommitdiff
path: root/scripts/check_gcc.sh
diff options
context:
space:
mode:
authorFrédéric Desbiens <[email protected]>2026-10-02 10:42:44 -0400
committerGitHub <[email protected]>2026-10-02 10:42:44 -0400
commit93387b0a60382a29579bbad5045c68328e3751df (patch)
tree42be790d602cb76f55090e5a7c418df7ea2b3428 /scripts/check_gcc.sh
parent44d7c95c582d415c4ad84527180b29c93c3bf664 (diff)
parent28281cd5647d4671236ca08d19bd18ed15b2265a (diff)
Merge pull request #783 from eclipse-threadx/devv6.5.2.202603_rel
Merged changes ahead of the v6.5.2.202603 release
Diffstat (limited to 'scripts/check_gcc.sh')
-rwxr-xr-xscripts/check_gcc.sh748
1 files changed, 748 insertions, 0 deletions
diff --git a/scripts/check_gcc.sh b/scripts/check_gcc.sh
new file mode 100755
index 00000000..b8b2d794
--- /dev/null
+++ b/scripts/check_gcc.sh
@@ -0,0 +1,748 @@
+#!/bin/bash
+##############################################################################
+# Copyright (c) 2026 Eclipse ThreadX contributors
+#
+# This program and the accompanying materials are made available under the
+# terms of the MIT License which is available at
+# https://opensource.org/licenses/MIT.
+#
+# AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5).
+# The AI-generated portions may be considered public domain (CC0-1.0)
+# and not subject to the project's licence. The human contributor has
+# reviewed and verified that the code is correct.
+#
+# SPDX-License-Identifier: MIT and CC0-1.0
+##############################################################################
+
+# Builds the Arm ports with the GNU toolchain, in seven stages: assemble every
+# assembly source of every Arm gnu port, assemble again the parts guarded by
+# feature macros, compile the common C sources for one core per architecture
+# profile, compile the module manager C sources once per Arm module port, link
+# the example builds, both the script-driven ones and those driven by CMake,
+# and finally assert that the option combinations the Cortex-R52 port refuses
+# are in fact refused. Only the linking stages need a target C library.
+#
+# scripts/check_gcc.sh # both drivers from PATH
+# scripts/check_gcc.sh --arm-none-eabi /path/to/toolchain/bin \
+# --aarch64-none-elf /path/to/toolchain/bin
+#
+# Options:
+# --arm-none-eabi <path> arm-none-eabi-gcc, or the directory holding it.
+# Defaults to $ARM_NONE_EABI_GCC then to PATH.
+# --aarch64-none-elf <path> aarch64-none-elf-gcc, or its directory.
+# Defaults to $AARCH64_NONE_ELF_GCC then to PATH.
+# --asm-only Skip the C sources and the example builds.
+# --no-examples Skip the example builds.
+# --quiet Print only failures and the summary.
+#
+# Exit status is 0 when everything builds and 1 otherwise.
+#
+# Why this exists: GCC 14 on Linux is the project's default compiler, it is
+# what the gnu ports exist for, and it is what nearly every downstream user
+# builds with -- and until this script landed, nothing in CI compiled a line
+# of any port with it. The only cross-compilation check that ran was the LLVM
+# one, so the ATfE path was better guarded than the GNU one, on ports whose
+# directory is literally named gnu.
+#
+# This is the companion to scripts/check_clang.sh and deliberately mirrors it
+# stage for stage. They are two scripts rather than one with a --toolchain flag
+# because the flag surface differs (a prefixed driver against --target=), the
+# C library differs, and the set of examples that can link differs. Folding
+# them together makes it easy to weaken one check while working on the other.
+#
+# It compiles and links; it executes nothing. The Cortex-R52 FVP ctest suite is
+# a separate matter, and the RISC-V, MIPS, RX and ARC families are outside it
+# entirely -- every skip is printed by name below.
+#
+# Arm GNU toolchain releases:
+# https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads
+
+set -u
+
+cd "$(dirname "$(realpath "$0")")/.."
+
+# Two toolchains, not one. Arm ships arm-none-eabi (AArch32: every M and R
+# profile core and the A32 A-profile ports) and aarch64-none-elf as separate
+# downloads, and PORT_TARGET below maps every port to one of exactly those two
+# triples. Both are required: making a missing one a soft skip would let a run
+# cover half the tree and still say "all checks passed", which is the failure
+# this script was written to end.
+CC_ARM="${ARM_NONE_EABI_GCC:-}"
+CC_AARCH64="${AARCH64_NONE_ELF_GCC:-}"
+asm_only=0
+no_examples=0
+quiet=0
+
+while [ "$#" -gt 0 ]; do
+ case "$1" in
+ --arm-none-eabi)
+ [ "$#" -ge 2 ] || { echo "Error: --arm-none-eabi needs a path" >&2; exit 2; }
+ CC_ARM="$2"; shift 2 ;;
+ --aarch64-none-elf)
+ [ "$#" -ge 2 ] || { echo "Error: --aarch64-none-elf needs a path" >&2; exit 2; }
+ CC_AARCH64="$2"; shift 2 ;;
+ --asm-only) asm_only=1; no_examples=1; shift ;;
+ --no-examples) no_examples=1; shift ;;
+ --quiet) quiet=1; shift ;;
+ -h|--help) sed -n '17,36p' "$0"; exit 0 ;;
+ *) echo "Error: unknown option '$1'" >&2; exit 2 ;;
+ esac
+done
+
+say() { [ "$quiet" -eq 1 ] || echo "$@"; }
+fail() { echo " FAIL: $*"; }
+
+# The C stages treat any compiler output as a failure, and a #pragma message is
+# a deliberate notice to callers rather than a defect in the file that carries
+# it -- txm_module_manager_absolute_load.c deprecates itself in favour of the
+# extended entry point, and the module manager stage compiles it once per port.
+#
+# check_clang.sh suppresses these at the compiler with -Wno-#pragma-messages.
+# GCC has no equivalent: the note is unconditional, and neither -Wno-pragmas
+# nor any other -W option silences it -- verified with 14.3.rel1. So it is
+# filtered out of the output here instead, along with the source quote and
+# caret GCC prints beneath it. The skip ends at the next line that starts a
+# diagnostic of its own, so an error following a waived note is still reported.
+strip_pragma_messages() {
+ awk '
+ /note: .#pragma message:/ { skip = 1; next }
+ skip && /^ *[0-9]* *\|/ { next }
+ { skip = 0; print }
+ '
+}
+
+# Accept either the driver itself or the directory holding it, since a
+# toolchain is unpacked as a tree and naming its bin directory is the natural
+# thing to reach for. Resolve to an absolute path: the example stages run the
+# build scripts from inside their own directories, so a relative path would
+# stop resolving there.
+resolve_gcc() {
+ triple="$1"
+ given="$2"
+
+ if [ -z "$given" ]; then
+ command -v "${triple}-gcc" >/dev/null 2>&1 || return 1
+ command -v "${triple}-gcc"
+ return 0
+ fi
+ if [ -d "$given" ]; then
+ for candidate in "$given/${triple}-gcc" "$given/bin/${triple}-gcc"; do
+ [ -x "$candidate" ] && { realpath "$candidate"; return 0; }
+ done
+ return 1
+ fi
+ [ -x "$given" ] || return 1
+ realpath "$given"
+}
+
+missing=""
+CC_ARM="$(resolve_gcc arm-none-eabi "$CC_ARM")" || missing="$missing arm-none-eabi"
+CC_AARCH64="$(resolve_gcc aarch64-none-elf "$CC_AARCH64")" || missing="$missing aarch64-none-elf"
+
+if [ -n "$missing" ]; then
+ echo "Error: compiler(s) not found:$missing"
+ echo "Pass --arm-none-eabi and --aarch64-none-elf, set ARM_NONE_EABI_GCC and"
+ echo "AARCH64_NONE_ELF_GCC, or put both drivers on PATH. Downloads:"
+ echo " https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads"
+ exit 1
+fi
+
+say ""
+say "Using: $CC_ARM"
+say " $("$CC_ARM" --version | head -1)"
+say " $CC_AARCH64"
+say " $("$CC_AARCH64" --version | head -1)"
+
+# The example build scripts and the CMake toolchain file call the drivers by
+# their bare prefixed names, so both bin directories go on PATH for the linking
+# stages. Done once, here, rather than per stage: a second toolchain arriving
+# on PATH halfway through a run is exactly the kind of difference that makes a
+# failure irreproducible.
+PATH="$(dirname "$CC_ARM"):$(dirname "$CC_AARCH64"):$PATH"
+export PATH
+
+# Pick the driver for a port from its triple, so the map below stays the single
+# place a port's architecture is decided.
+cc_for() {
+ case "$1" in
+ aarch64-none-elf) echo "$CC_AARCH64" ;;
+ *) echo "$CC_ARM" ;;
+ esac
+}
+
+# Each port directory is mapped explicitly to a target triple and CPU. Do not
+# replace this with prefix matching: cortex_a5* also matches cortex_a53 and
+# cortex_a55, which are AArch64, and assembling those as ARM32 produces a flood
+# of misleading errors.
+#
+# Copied verbatim from scripts/check_clang.sh. Keep the two identical -- a port
+# covered by one check and not the other is worse than one covered by neither,
+# because the checks list implies parity that does not exist.
+declare -A PORT_TARGET=(
+ [cortex_m0]="arm-none-eabi cortex-m0 -mthumb"
+ [cortex_m0+]="arm-none-eabi cortex-m0plus -mthumb"
+ [cortex_m3]="arm-none-eabi cortex-m3 -mthumb"
+ [cortex_m4]="arm-none-eabi cortex-m4 -mthumb"
+ [cortex_m7]="arm-none-eabi cortex-m7 -mthumb"
+ [cortex_m23]="arm-none-eabi cortex-m23 -mthumb"
+ [cortex_m33]="arm-none-eabi cortex-m33 -mthumb"
+ [cortex_m52]="arm-none-eabi cortex-m52 -mthumb -mfloat-abi=hard"
+ [cortex_m55]="arm-none-eabi cortex-m55 -mthumb -mfloat-abi=hard"
+ [cortex_m85]="arm-none-eabi cortex-m85 -mthumb -mfloat-abi=hard"
+ [cortex_a5]="arm-none-eabi cortex-a5"
+ [cortex_a7]="arm-none-eabi cortex-a7"
+ [cortex_a8]="arm-none-eabi cortex-a8"
+ [cortex_a9]="arm-none-eabi cortex-a9"
+ [cortex_a12]="arm-none-eabi cortex-a12"
+ [cortex_a15]="arm-none-eabi cortex-a15"
+ [cortex_a17]="arm-none-eabi cortex-a17"
+ [cortex_a5_smp]="arm-none-eabi cortex-a5"
+ [cortex_a7_smp]="arm-none-eabi cortex-a7"
+ [cortex_a9_smp]="arm-none-eabi cortex-a9"
+ [cortex_r4]="arm-none-eabi cortex-r4"
+ [cortex_r5]="arm-none-eabi cortex-r5"
+ [cortex_r52]="arm-none-eabi cortex-r52"
+ [cortex_a34]="aarch64-none-elf cortex-a34"
+ [cortex_a35]="aarch64-none-elf cortex-a35"
+ [cortex_a53]="aarch64-none-elf cortex-a53"
+ [cortex_a55]="aarch64-none-elf cortex-a55"
+ [cortex_a57]="aarch64-none-elf cortex-a57"
+ [cortex_a65]="aarch64-none-elf cortex-a65"
+ [cortex_a65ae]="aarch64-none-elf cortex-a65ae"
+ [cortex_a72]="aarch64-none-elf cortex-a72"
+ [cortex_a73]="aarch64-none-elf cortex-a73"
+ [cortex_a75]="aarch64-none-elf cortex-a75"
+ [cortex_a76]="aarch64-none-elf cortex-a76"
+ [cortex_a76ae]="aarch64-none-elf cortex-a76ae"
+ [cortex_a77]="aarch64-none-elf cortex-a77"
+ [cortex_a34_smp]="aarch64-none-elf cortex-a34"
+ [cortex_a35_smp]="aarch64-none-elf cortex-a35"
+ [cortex_a53_smp]="aarch64-none-elf cortex-a53"
+ [cortex_a55_smp]="aarch64-none-elf cortex-a55"
+ [cortex_a57_smp]="aarch64-none-elf cortex-a57"
+ [cortex_a65_smp]="aarch64-none-elf cortex-a65"
+ [cortex_a65ae_smp]="aarch64-none-elf cortex-a65ae"
+ [cortex_a72_smp]="aarch64-none-elf cortex-a72"
+ [cortex_a73_smp]="aarch64-none-elf cortex-a73"
+ [cortex_a75_smp]="aarch64-none-elf cortex-a75"
+ [cortex_a76_smp]="aarch64-none-elf cortex-a76"
+ [cortex_a76ae_smp]="aarch64-none-elf cortex-a76ae"
+ [cortex_a77_smp]="aarch64-none-elf cortex-a77"
+ [cortex_a78_smp]="aarch64-none-elf cortex-a78"
+)
+
+# Assembly guarded by a feature macro is invisible to the stage above, which
+# assembles with default flags and so lets the preprocessor discard every #ifdef
+# block before the assembler sees it. These are the macros a user can turn on;
+# each file carrying one is assembled again with it defined.
+#
+# This is not hypothetical, and it is not only clang's finding. The Cortex-M23
+# module manager carried "POP {r0, lr}" in exactly these paths -- invalid on
+# Armv8-M Baseline, where the 16-bit POP takes r0-r7 and pc only -- for six
+# months after the identical fix landed in its non-module sibling, because no
+# glob in either script reached ports_module until #672.
+FEATURE_MACROS="TX_ENABLE_VFP_SUPPORT TX_ENABLE_FIQ_SUPPORT TX_LOW_POWER
+ TX_ENABLE_EXECUTION_CHANGE_NOTIFY TX_EXECUTION_PROFILE_ENABLE"
+
+# TX_ENABLE_IRQ_NESTING and TX_ENABLE_FIQ_NESTING are deliberately not here.
+# They guard no assembly in the trees this script walks: the nesting start and
+# end routines are separate files compiled unconditionally, and the macros only
+# feed the TX_PORT_SPECIFIC_BUILD_OPTIONS bitfield in tx_port.h. Adding them
+# would assemble nothing new and imply coverage that does not exist.
+
+# Extra flags for the VFP paths, per core.
+#
+# check_clang.sh's equivalent map has one entry and a comment saying "Do not
+# extend this to the A profile ports ... their defaults are already correct."
+# That is true of clang and false of GCC, so do not copy that comment here.
+# arm-none-eabi-gcc defaults to -mfloat-abi=soft, which disables the FPU
+# outright, and every VFP file then fails with
+#
+# selected processor does not support 'vmrs r1,FPSCR' in ARM mode
+#
+# -mfloat-abi=hard alone is the fix, and it is the right one: it selects the
+# core's own default FPU rather than naming one. Naming a -d16 FPU is the trap
+# the clang script warns about -- the A-profile paths save D16-D31, which exist
+# only on a 32-register FPU, so a -d16 choice turns 28 working files into
+# "register expected".
+#
+# Cortex-R4 is the exception, in both scripts and for the same reason: its FPU
+# is an option rather than part of the core, so -mfloat-abi=hard alone gives
+# "selected architecture lacks an FPU" and an explicit -mfpu is required. The
+# value matches check_clang.sh's, so the two scripts say the same thing about
+# the same port.
+#
+# Every entry below was measured against arm-gnu-toolchain 14.3.rel1. A core
+# that acquires a VFP-guarded file without an entry here fails loudly rather
+# than silently, which is the intended behaviour.
+declare -A VFP_EXTRA=(
+ [cortex_a5]="-mfloat-abi=hard"
+ [cortex_a7]="-mfloat-abi=hard"
+ [cortex_a8]="-mfloat-abi=hard"
+ [cortex_a9]="-mfloat-abi=hard"
+ [cortex_a12]="-mfloat-abi=hard"
+ [cortex_a15]="-mfloat-abi=hard"
+ [cortex_a17]="-mfloat-abi=hard"
+ [cortex_a5_smp]="-mfloat-abi=hard"
+ [cortex_a7_smp]="-mfloat-abi=hard"
+ [cortex_a9_smp]="-mfloat-abi=hard"
+ [cortex_r4]="-mfpu=vfpv3-d16 -mfloat-abi=softfp"
+ [cortex_r5]="-mfloat-abi=hard"
+ [cortex_r52]="-mfloat-abi=hard"
+)
+
+# One core per architecture profile for the C sources. Compiling all of them
+# for every core would multiply the run time without adding coverage, since the
+# port headers differ by profile rather than by core.
+#
+# cortex_r52 earns a slot of its own next to cortex_r5 because Armv8-R AArch32
+# is a separate profile rather than a variant of Armv7-R. That port is written
+# by hand instead of generated from ports_arch, and its tx_port.h differs
+# accordingly, so cortex_r5 does not stand in for it.
+C_CORES="cortex_m0 cortex_m4 cortex_m23 cortex_m33 cortex_m55 cortex_a7 cortex_a53 cortex_r5 cortex_r52"
+
+# Example builds driven by CMake rather than by a build_threadx.sh pair. These
+# are covered by their own stage below, so the script-driven loop passes over
+# them without reporting them as a gap.
+CMAKE_EXAMPLE_CORES="cortex_r52"
+
+# Example builds that are not expected to link, with the reason. Named by
+# their port directory, which covers both ports/ and ports_smp/. Listed
+# explicitly rather than silently skipped, so the gaps stay visible.
+#
+# The same four as check_clang.sh, and the reason there is stated in terms of
+# GNU tooling because that is where it was reproduced:
+#
+# their linker scripts define the .init and .fini sections but not the _init
+# and _fini symbols. Those come from crti.o and crtn.o, which -nostartfiles
+# leaves out, so newlib's fini.c cannot resolve them and the link ends with
+# "undefined reference to `_fini'".
+#
+# The 27 AArch64 examples failed the same way until #673, which links crti.o
+# and crtn.o back. They are deliberately absent from this list: they link.
+EXAMPLES_EXPECTED_TO_FAIL="arm9 arm11 cortex_r4 cortex_r5"
+
+failures=0
+skipped=""
+
+# --------------------------------------------------------------------------
+say ""
+say "== Assembly sources of every Arm gnu port =="
+
+total=0
+# The module ports keep their assembly in module_manager/src, not src.
+# check_clang.sh read ports_module/*/gnu/src until #672; that directory does
+# not exist, the [ -d ] guard skipped it in silence, and 116 files across nine
+# Arm module ports were assembled by no check with either compiler. This script
+# has never had that hole and must not acquire it.
+for dir in ports/*/gnu/src ports_smp/*/gnu/src ports_module/*/gnu/module_manager/src; do
+ [ -d "$dir" ] || continue
+ core="$(echo "$dir" | cut -d/ -f2)"
+ spec="${PORT_TARGET[$core]:-}"
+ if [ -z "$spec" ]; then
+ skipped="$skipped $core"
+ continue
+ fi
+ # shellcheck disable=SC2086
+ set -- $spec
+ target="$1"; cpu="$2"; shift 2; extra="$*"
+ CC="$(cc_for "$target")"
+
+ for src in "$dir"/*.S "$dir"/*.s; do
+ [ -f "$src" ] || continue
+ total=$((total + 1))
+ output="$("$CC" -mcpu="$cpu" $extra -c "$src" -o /dev/null 2>&1)"
+ if [ -n "$output" ]; then
+ fail "$src"
+ echo "$output" | sed 's/^/ /'
+ failures=$((failures + 1))
+ fi
+ done
+done
+say " $((total - failures)) of $total assembled"
+if [ -n "$skipped" ]; then
+ say " not Arm, skipped:$(echo $skipped | tr ' ' '\n' | sort -u | tr '\n' ' ')"
+ say " (arm9 and arm11 are Arm; they are skipped for having no PORT_TARGET entry)"
+fi
+
+# --------------------------------------------------------------------------
+say ""
+say "== Assembly behind feature macros =="
+
+for macro in $FEATURE_MACROS; do
+ macro_total=0
+ macro_bad=0
+ for src in $(grep -rl "$macro" ports/*/gnu/src/*.S ports_smp/*/gnu/src/*.S \
+ ports_module/*/gnu/module_manager/src/*.S \
+ 2>/dev/null | sort); do
+ core="$(echo "$src" | cut -d/ -f2)"
+ spec="${PORT_TARGET[$core]:-}"
+ [ -n "$spec" ] || continue
+ # shellcheck disable=SC2086
+ set -- $spec
+ target="$1"; cpu="$2"; shift 2; extra="$*"
+ CC="$(cc_for "$target")"
+
+ # The FPU flags apply to the VFP paths only; the other macros guard no
+ # floating-point code and do not need them.
+ fpu=""
+ if [ "$macro" = "TX_ENABLE_VFP_SUPPORT" ]; then
+ fpu="${VFP_EXTRA[$core]:-}"
+ fi
+
+ macro_total=$((macro_total + 1))
+ output="$("$CC" -mcpu="$cpu" $extra $fpu \
+ -D"$macro" -c "$src" -o /dev/null 2>&1)"
+ if [ -n "$output" ]; then
+ fail "$src with -D$macro"
+ # Not filtered on "error": GCC prefixes its diagnostics with an
+ # "Assembler messages:" line and a missing tool says nothing of the
+ # kind, so filtering would hide the case worth seeing.
+ echo "$output" | head -4 | sed 's/^/ /'
+ macro_bad=$((macro_bad + 1))
+ failures=$((failures + 1))
+ fi
+ done
+ if [ "$macro_total" -eq 0 ]; then
+ say " $macro: no assembly is guarded by it"
+ else
+ say " $macro: $((macro_total - macro_bad)) of $macro_total assembled"
+ fi
+done
+
+# --------------------------------------------------------------------------
+if [ "$asm_only" -eq 0 ]; then
+ say ""
+ say "== Common C sources, one core per architecture profile =="
+
+ for core in $C_CORES; do
+ spec="${PORT_TARGET[$core]:-}"
+ [ -n "$spec" ] || continue
+ # shellcheck disable=SC2086
+ set -- $spec
+ target="$1"; cpu="$2"; shift 2; extra="$*"
+ CC="$(cc_for "$target")"
+
+ count=0; bad=0
+ for src in common/src/*.c; do
+ count=$((count + 1))
+ output="$("$CC" -mcpu="$cpu" $extra \
+ -Iports/"$core"/gnu/inc -Icommon/inc -c "$src" -o /dev/null 2>&1)"
+ if [ -n "$output" ]; then
+ fail "$core: $src"
+ echo "$output" | head -4 | sed 's/^/ /'
+ bad=$((bad + 1)); failures=$((failures + 1))
+ fi
+ done
+ say " $core: $((count - bad)) of $count compiled"
+ done
+fi
+
+# --------------------------------------------------------------------------
+if [ "$asm_only" -eq 0 ]; then
+ say ""
+ say "== Module manager C sources, one per Arm module port =="
+
+ # #689 added this stage to check_clang.sh alone, so the module manager C
+ # stayed unbuilt by the project's declared default compiler: 28 files of
+ # portable module manager under common_modules, plus the three to nine
+ # per-port files under ports_module/<core>/gnu/module_manager/src. This is
+ # the GCC half, and it is deliberately the same stage -- same ports, same
+ # headers, same counts -- because a port covered by one check and not the
+ # other implies a parity the checks list does not have.
+ #
+ # Each module port ships its own tx_port.h and txm_module_port.h, carrying
+ # the control-block extensions the dispatch code needs, so a port is
+ # compiled against its own headers rather than the base port's.
+ module_skipped=""
+ for dir in ports_module/*/gnu/module_manager/src; do
+ [ -d "$dir" ] || continue
+ core="$(echo "$dir" | cut -d/ -f2)"
+ spec="${PORT_TARGET[$core]:-}"
+ if [ -z "$spec" ]; then
+ module_skipped="$module_skipped $core"
+ continue
+ fi
+
+ inc="ports_module/$core/gnu/inc"
+ if [ ! -f "$inc/tx_port.h" ] || [ ! -f "$inc/txm_module_port.h" ]; then
+ module_skipped="$module_skipped $core(headers)"
+ continue
+ fi
+
+ # shellcheck disable=SC2086
+ set -- $spec
+ target="$1"; cpu="$2"; shift 2; extra="$*"
+ CC="$(cc_for "$target")"
+
+ # An SMP port's control blocks come from common_smp; pairing it with the
+ # single-core headers hides _tx_thread_smp_protect behind an implicit
+ # declaration instead of compiling the port that is actually shipped.
+ case "$core" in
+ *_smp) kernel_inc="common_smp/inc" ;;
+ *) kernel_inc="common/inc" ;;
+ esac
+
+ # The TrustZone ports carry cmse_nonsecure_entry, which needs -mcmse to
+ # be honoured rather than ignored.
+ port_extra=""
+ if [ -f "$inc/tx_secure_interface.h" ]; then
+ port_extra="-mcmse"
+ fi
+
+ count=0; bad=0
+ for src in common_modules/module_manager/src/*.c "$dir"/*.c; do
+ [ -f "$src" ] || continue
+ count=$((count + 1))
+ output="$("$CC" -mcpu="$cpu" $extra $port_extra \
+ -I"$inc" -I"$kernel_inc" -Icommon_modules/inc \
+ -Icommon_modules/module_manager/inc \
+ -c "$src" -o /dev/null 2>&1 | strip_pragma_messages)"
+ if [ -n "$output" ]; then
+ fail "$core: $src"
+ # Show the error lines when there are any, and otherwise
+ # whatever the compiler did say -- a FAIL with nothing under it
+ # sends the reader off to reproduce the command by hand.
+ if echo "$output" | grep -q "error:"; then
+ echo "$output" | grep "error:" | head -3 | sed 's/^/ /'
+ else
+ echo "$output" | head -3 | sed 's/^/ /'
+ fi
+ bad=$((bad + 1)); failures=$((failures + 1))
+ fi
+ done
+ say " $core: $((count - bad)) of $count compiled"
+ done
+ if [ -n "$module_skipped" ]; then
+ say " no target mapping, skipped:$module_skipped"
+ fi
+fi
+
+# --------------------------------------------------------------------------
+if [ "$no_examples" -eq 0 ]; then
+ say ""
+ say "== Example builds, linked with GNU ld =="
+
+ example_ok=0
+ example_total=0
+ example_known=""
+ example_nodriver=""
+ example_nosample=""
+ for dir in ports/*/gnu/example_build ports_smp/*/gnu/example_build; do
+ [ -d "$dir" ] || continue
+ core="$(echo "$dir" | cut -d/ -f2)"
+
+ # Anything on the expected-to-fail list is reported before any other
+ # filter is applied, so a name placed there can never drop out of the
+ # output. arm9 and arm11 are the cases that matter: they are Arm ports
+ # with example drivers, but they carry no PORT_TARGET entry, so the
+ # Arm test below would discard them.
+ case " $EXAMPLES_EXPECTED_TO_FAIL " in
+ *" $core "*) example_known="$example_known $core"; continue ;;
+ esac
+
+ # Arm ports only, the same rule the assembly stage applies. Naming
+ # linux or mips32 as a gap here would be noise, not information.
+ [ -n "${PORT_TARGET[$core]:-}" ] || continue
+
+ # Covered by the CMake stage below rather than here.
+ case " $CMAKE_EXAMPLE_CORES " in
+ *" $core "*) continue ;;
+ esac
+
+ # A driverless example is not covered by this stage, so say so rather
+ # than dropping out in silence. A port that is simply absent from the
+ # count reads as covered.
+ if [ ! -f "$dir/build_threadx.sh" ]; then
+ example_nodriver="$example_nodriver $core"
+ continue
+ fi
+ if [ ! -f "$dir/build_threadx_sample.sh" ]; then
+ example_nosample="$example_nosample $core"
+ continue
+ fi
+ example_total=$((example_total + 1))
+
+ # Clean on both sides. A stale sample_threadx.out from a previous
+ # toolchain makes a failed link report success, because the success
+ # test below is the existence of the output file rather than the exit
+ # status of the driver.
+ rm -f "$dir"/*.o "$dir"/*.a "$dir"/*.out "$dir"/*.map 2>/dev/null || true
+
+ # TOOLCHAIN is *unset*, not set: the build scripts already default to
+ # GNU with `: "${TOOLCHAIN:=gnu}"`, and a stray TOOLCHAIN=atfe from a
+ # developer's shell or an earlier command would otherwise make this
+ # stage silently check the other compiler.
+ log="$(cd "$dir" && unset TOOLCHAIN && ./build_threadx.sh 2>&1 && \
+ ./build_threadx_sample.sh 2>&1)" || true
+
+ if [ -f "$dir/sample_threadx.out" ]; then
+ example_ok=$((example_ok + 1))
+ else
+ fail "$core: example build produced no image"
+ # Not filtered on "error": a missing tool reports "command not
+ # found" or "Permission denied", and filtering hid exactly that.
+ echo "$log" | tail -6 | sed 's/^/ /'
+ failures=$((failures + 1))
+ fi
+ rm -f "$dir"/*.o "$dir"/*.a "$dir"/*.out "$dir"/*.map 2>/dev/null || true
+ done
+ say " $example_ok of $example_total example builds linked"
+ if [ -n "$example_known" ]; then
+ say " known not to link, see the list at the top of this script:$example_known"
+ fi
+ if [ -n "$example_nosample" ]; then
+ say " has build_threadx.sh but no build_threadx_sample.sh, so not linked:$example_nosample"
+ fi
+ if [ -n "$example_nodriver" ]; then
+ say " no script driver, so outside this stage:$example_nodriver"
+ fi
+fi
+
+# --------------------------------------------------------------------------
+# The Cortex-R52 examples are built by CMake, so they need a toolchain file
+# rather than the build_threadx.sh pair. Same compiler, same purpose as the
+# stage above.
+if [ "$no_examples" -eq 0 ]; then
+ say ""
+ say "== CMake example builds, linked with GNU ld =="
+
+ if ! command -v cmake >/dev/null 2>&1 || ! command -v ninja >/dev/null 2>&1; then
+ say " skipped: cmake and ninja are both required"
+ else
+ for core in $CMAKE_EXAMPLE_CORES; do
+ build_dir="$(mktemp -d)"
+ # ARM_TOOLCHAIN_PATH follows --arm-none-eabi, so this stage uses
+ # the same compiler as every other stage rather than the absolute
+ # path cmake/cortex_r52.cmake pins by default.
+ if cmake -S . -B "$build_dir" -G Ninja \
+ -DCMAKE_TOOLCHAIN_FILE="cmake/${core}.cmake" \
+ -DARM_TOOLCHAIN_PATH="$(dirname "$CC_ARM")" \
+ -DTX_R52_BUILD_FVP_EXAMPLE=ON \
+ -DTX_R52_ENABLE_MPU=ON >"$build_dir/configure.log" 2>&1; then
+ # Read the image list from the generated graph instead of
+ # repeating it here, so adding a target cannot silently escape
+ # this check. The images are EXCLUDE_FROM_ALL, so "ninja" alone
+ # would build none of them.
+ #
+ # The cmake_object_order_depends_target_* entries are CMake's
+ # own ordering phonies, one per real image and named after it.
+ # Counting those doubled the total and reported ten images built
+ # where there are five.
+ images="$(ninja -C "$build_dir" -t targets all 2>/dev/null \
+ | grep -oE '^[A-Za-z0-9_]+\.elf' \
+ | grep -v '^cmake_' | sort -u)"
+ if [ -z "$images" ]; then
+ fail "$core: no .elf targets found in the CMake graph"
+ failures=$((failures + 1))
+ else
+ built=0; total=0
+ for image in $images; do
+ total=$((total + 1))
+ if ninja -C "$build_dir" "$image" \
+ >"$build_dir/$image.log" 2>&1; then
+ built=$((built + 1))
+ else
+ fail "$core: $image did not build"
+ tail -6 "$build_dir/$image.log" | sed 's/^/ /'
+ failures=$((failures + 1))
+ fi
+ done
+ say " $core: $built of $total images linked"
+ fi
+ else
+ fail "$core: CMake configure failed"
+ tail -6 "$build_dir/configure.log" | sed 's/^/ /'
+ failures=$((failures + 1))
+ fi
+ rm -rf "$build_dir"
+ done
+ fi
+fi
+
+# --------------------------------------------------------------------------
+# Option combinations the port refuses at configure time.
+#
+# ports/cortex_r52/gnu/CMakeLists.txt rejects two combinations outright rather
+# than letting them reach the compiler. Both are cheap to get wrong in a way
+# that is invisible: a typo in a variable name makes the condition constant,
+# and a guard that never fires is indistinguishable from one that is never
+# tripped. So these assert the exit status AND the text, because a guard that
+# fires for the wrong reason would otherwise pass.
+#
+# No example flags are needed: the port CMakeLists is included by the toolchain
+# file alone, so the guards are reached without configuring anything else. That
+# keeps the stage to three configures, two of which stop almost immediately.
+#
+# The third is the positive counterpart, and it is not decoration: a guard that
+# rejects everything would satisfy both negative cases on its own. Asserting
+# that the supported combination still configures is what distinguishes a guard
+# that works from one that is merely always on.
+#
+# This is the only negative check in the script. Everything else here asserts
+# that something builds; this stage asserts that two things refuse to, and that
+# a third still does not.
+if [ "$asm_only" -eq 0 ]; then
+ say ""
+ say "== Option combinations the Cortex-R52 port must refuse =="
+
+ if ! command -v cmake >/dev/null 2>&1; then
+ say " skipped: cmake is required"
+ else
+ # name | expected text in the error | options
+ refuse_case() {
+ case_name="$1"; want="$2"; shift 2
+ build_dir="$(mktemp -d)"
+ if cmake -S . -B "$build_dir" -G Ninja \
+ -DCMAKE_TOOLCHAIN_FILE=cmake/cortex_r52.cmake \
+ -DARM_TOOLCHAIN_PATH="$(dirname "$CC_ARM")" \
+ "$@" >"$build_dir/configure.log" 2>&1; then
+ fail "$case_name: configure succeeded, but this combination cannot build"
+ failures=$((failures + 1))
+ elif ! grep -q "$want" "$build_dir/configure.log"; then
+ fail "$case_name: configure failed, but not on the expected guard"
+ fail " wanted text: $want"
+ tail -6 "$build_dir/configure.log" | sed 's/^/ /'
+ failures=$((failures + 1))
+ else
+ say " $case_name: refused"
+ fi
+ rm -rf "$build_dir"
+ }
+
+ refuse_case "VFP without a hard float ABI" \
+ "TX_R52_ENABLE_VFP requires TX_R52_FLOAT_ABI=hard" \
+ -DTX_R52_ENABLE_VFP=ON
+
+ refuse_case "FIQ nesting without FIQ" \
+ "TX_R52_ENABLE_FIQ_NESTING requires TX_R52_ENABLE_FIQ" \
+ -DTX_R52_ENABLE_FIQ_NESTING=ON
+
+ # The counterpart: the supported spelling of the first case must still
+ # configure, so a guard cannot pass this stage by rejecting everything.
+ build_dir="$(mktemp -d)"
+ if cmake -S . -B "$build_dir" -G Ninja \
+ -DCMAKE_TOOLCHAIN_FILE=cmake/cortex_r52.cmake \
+ -DARM_TOOLCHAIN_PATH="$(dirname "$CC_ARM")" \
+ -DTX_R52_ENABLE_VFP=ON -DTX_R52_FLOAT_ABI=hard \
+ -DTX_R52_ENABLE_FIQ=ON -DTX_R52_ENABLE_FIQ_NESTING=ON \
+ >"$build_dir/configure.log" 2>&1; then
+ say " the supported combination: accepted"
+ else
+ fail "the supported combination was refused"
+ tail -6 "$build_dir/configure.log" | sed 's/^/ /'
+ failures=$((failures + 1))
+ fi
+ rm -rf "$build_dir"
+ fi
+fi
+
+# --------------------------------------------------------------------------
+say ""
+if [ "$failures" -eq 0 ]; then
+ say "All GNU toolchain checks passed."
+ exit 0
+fi
+
+echo "$failures GNU toolchain check(s) failed."
+exit 1