Made the example builds work with LLVM, and fixed four that were broken on Linux (#594)

* Made the example builds work with LLVM, and fixed four that were broken on Linux

The gnu example builds are the natural LLVM path: Arm Toolchain for Embedded is
LLVM based, consumes GNU ld linker scripts, and needs none of the scatter files
or Arm DS projects the ac6 examples carry. So rather than port the ac6 examples,
teach the gnu scripts to drive either toolchain.

Each script now selects the toolchain from a TOOLCHAIN variable that defaults to
gnu, so every existing invocation behaves exactly as before. TOOLCHAIN=atfe
switches the compiler, adds the target triple, passes the entry symbol through
to the linker rather than to the driver, and links the toolchain's own
semihosting library in place of --specs=nosys.specs, which is a GCC spec file
mechanism with no LLVM equivalent. That last choice avoids adding a syscall stub
source to every example.

The scripts are parameterised rather than duplicated. Copies of build scripts
would drift the first time one side was edited, which is the failure this
repository has just spent several changes recovering from.

Four sample scripts could not run on a case-sensitive filesystem at all. They
compiled MP_PrivateTimer.s and V7.s while the files on disk are
MP_PrivateTimer.S and v7.s, and the Cortex-A5 and A9 scripts named MP_GIC.s
where the file is MP_GIC.S. The link lines named V7.o accordingly. Corrected to
match the files, which is why the Cortex-A5, A7, A8 and A9 examples now build on
Linux where before they could not.

Extend scripts/check_clang.sh to link the example builds as well as compile the
sources, since compiling proves the sources parse while only linking exercises
entry symbols, linker scripts and the C library together.

Eight examples are listed as not expected to link, each with its reason, so the
gaps stay visible rather than being silently skipped. Five of them fail with the
GNU toolchain too and are therefore not LLVM problems: the Cortex-M0 example's
crt0 references __text_load_start__, __text_start__ and __text_end__, which its
linker script never defines, while the Cortex-M4 script defines the equivalents;
the arm9, arm11, Cortex-R4 and Cortex-R5 examples need newlib multilib variants
that are not present in every GNU toolchain packaging. The Cortex-A12, A15 and
A17 examples fail only with LLVM, because their link line omits -nostartfiles so
the toolchain's own crt0 is linked and wants picolibc's __data_start,
__data_source, __data_size and __bss_size, which their linker script does not
define.

Verified by building every example with both toolchains. Seven link with both:
Cortex-A5, A7, A8, A9, M3, M4 and M7. The GNU results are unchanged where they
worked before, and now also succeed for the four scripts with the case bug.

Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>

* Resolved the compiler path before running the example builds

The example stage runs each build script from inside its own directory, so a
relative path passed with --clang stopped resolving there and every example
build failed instantly. Local runs passed an absolute path and did not show it;
the CI job passes a path relative to the workspace root, which did.

Resolve the compiler to an absolute path once, before any directory change, and
print it so the toolchain in use is visible in the log.

Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>

* Parameterised the archiver as well, and stopped the check hiding failures

The toolchain selection covered the compiler but not arm-none-eabi-ar, which the
scripts invoke 628 times to assemble the library archive. A machine with an LLVM
toolchain and no GNU one therefore could not build any example, which is exactly
the situation in CI: the runner has no Arm GNU toolchain installed. Local runs
had one, so this only appeared once the job ran.

Select the archiver alongside the compiler, taking llvm-ar from beside clang in
the toolchain. The four scripts that call arm-none-eabi-ld directly are left
alone: they are arm9, arm11, Cortex-R4 and Cortex-R5, all already listed as not
expected to link, and their invocations are specific to GNU ld in ways that
parameterising would not resolve.

The check reported "example build produced no image" and then filtered the log
for lines containing "error", which hid the actual cause, since a missing tool
reports "command not found" or "No such file or directory". It now prints the
tail of the log. That filtering cost two CI round trips to diagnose something
the first run already knew.

Verified by shadowing arm-none-eabi-gcc, arm-none-eabi-ar, arm-none-eabi-ld and
the aarch64 equivalents with stubs that fail loudly, then running the whole
check: all 711 sources assemble, all eight profiles compile and all seven
example builds link without any GNU tool being invoked. The GNU default path
still produces an identical image. The diagnostics were confirmed by pointing
the archiver at a name that does not exist and checking that the reason appears.

Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
This commit is contained in:
Frédéric Desbiens
2026-08-10 15:10:34 -04:00
committed by GitHub
parent 68eb205768
commit f3ab36dacc
36 changed files with 5814 additions and 4488 deletions
+72 -4
View File
@@ -17,7 +17,7 @@
# Builds the Arm ports with an LLVM based toolchain: every assembly source of
# every Arm gnu port, and the common C sources for one core per architecture
# profile. Compilation only, no linking, so no target C library is required
# beyond whatever the toolchain ships.
# profile, then links the example builds that have a script driver.
#
# scripts/check_clang.sh # clang from PATH
# scripts/check_clang.sh --clang /path/to/clang
@@ -25,7 +25,8 @@
#
# Options:
# --clang <path> Compiler to use; defaults to $CLANG then to clang.
# --asm-only Skip the C sources.
# --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.
@@ -45,12 +46,14 @@ cd "$(dirname "$(realpath "$0")")/.."
CC="${CLANG:-clang}"
asm_only=0
no_examples=0
quiet=0
while [ "$#" -gt 0 ]; do
case "$1" in
--clang) [ "$#" -ge 2 ] || { echo "Error: --clang needs a path" >&2; exit 2; }; CC="$2"; shift 2 ;;
--asm-only) asm_only=1; shift ;;
--asm-only) asm_only=1; no_examples=1; shift ;;
--no-examples) no_examples=1; shift ;;
--quiet) quiet=1; shift ;;
-h|--help) sed -n '17,33p' "$0"; exit 0 ;;
*) echo "Error: unknown option '$1'" >&2; exit 2 ;;
@@ -67,8 +70,18 @@ if ! command -v "$CC" >/dev/null 2>&1 && [ ! -x "$CC" ]; then
exit 1
fi
# Resolve to an absolute path. The example stage runs the build scripts from
# inside their own directories, so a relative compiler path would stop
# resolving there.
if [ -e "$CC" ]; then
CC="$(realpath "$CC")"
else
CC="$(command -v "$CC")"
fi
say ""
say "Using: $("$CC" --version | head -1)"
say "Using: $CC"
say " $("$CC" --version | head -1)"
# 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
@@ -131,6 +144,24 @@ declare -A PORT_TARGET=(
# port headers differ by profile rather than by core.
C_CORES="cortex_m0 cortex_m4 cortex_m23 cortex_m33 cortex_m55 cortex_a7 cortex_a53 cortex_r5"
# Example builds that are not expected to link, with the reason. Listed
# explicitly rather than silently skipped, so the gaps stay visible.
#
# These fail with the GNU toolchain too, so they are not LLVM problems:
# cortex_m0 cortexm0_crt0.S references __text_load_start__,
# __text_start__ and __text_end__, which its linker
# script never defines. The Cortex-M4 script defines the
# equivalents, so this is a gap in that one example.
# arm9 arm11 need newlib multilib variants for those CPUs, which are
# cortex_r4 cortex_r5 not present in every GNU toolchain packaging.
#
# This one is specific to LLVM:
# cortex_a12 a15 a17 their link line omits -nostartfiles, so the toolchain's
# own crt0 is linked, and it needs picolibc's
# __data_start, __data_source, __data_size and __bss_size,
# which the example's linker script does not define.
EXAMPLES_EXPECTED_TO_FAIL="arm9 arm11 cortex_m0 cortex_r4 cortex_r5 cortex_a12 cortex_a15 cortex_a17"
failures=0
skipped=""
@@ -194,6 +225,43 @@ if [ "$asm_only" -eq 0 ]; then
done
fi
# --------------------------------------------------------------------------
if [ "$no_examples" -eq 0 ]; then
say ""
say "== Example builds, linked with lld =="
example_ok=0
example_total=0
example_known=""
for dir in ports/*/gnu/example_build; do
[ -f "$dir/build_threadx.sh" ] && [ -f "$dir/build_threadx_sample.sh" ] || continue
core="$(echo "$dir" | cut -d/ -f2)"
case " $EXAMPLES_EXPECTED_TO_FAIL " in
*" $core "*) example_known="$example_known $core"; continue ;;
esac
example_total=$((example_total + 1))
rm -f "$dir"/*.o "$dir"/*.a "$dir"/*.out "$dir"/*.map 2>/dev/null || true
log="$(cd "$dir" && TOOLCHAIN=atfe ATFE_CLANG="$CC" ./build_threadx.sh 2>&1 && \
TOOLCHAIN=atfe ATFE_CLANG="$CC" ./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
fi
# --------------------------------------------------------------------------
say ""
if [ "$failures" -eq 0 ]; then