Files
threadx/scripts/check_ports.sh
T
Frédéric Desbiens f23a1807a2 Added bash A profile update scripts and restored ports_arch as their source (#592)
The ARMv7-A and ARMv8-A ports are generated by update.ps1, which needs
PowerShell, so the cortex-a job in ports_arch_check ran on a Windows image and
nobody could reproduce it locally on Linux. Add update.sh beside each
update.ps1, with the same cores, compilers, copy sets and patches, and move the
job to the same Linux image as everything else.

The bash scripts were checked against the PowerShell ones by comparing what
each reports as drifted. They agree exactly on the 63 files the Windows job
last reported, and differ on 12 more, which turn out to be a defect in
update.ps1 rather than in the port. Its two .cproject patterns are written as
'value=`"cortex-a7`"' with backticks that survive into the pattern, so that
replacement has never matched, while the neighbouring Cortex-A7.NoFPU pattern
has no backticks and always worked. The result is that the AC6 example builds
for the A5, A8, A9, A12, A15 and A17 cores name cortex-a7 as their CPU while
their FPU string is correct. The bash scripts do what the PowerShell ones
intended, so regenerating corrects those twelve files.

Restore ports_arch as the source for the rest. The implementation of
_tx_thread_smp_time_get from #555 was applied to the twenty four generated SMP
ports and never to ports_arch, which still held MOV x0, #0 with a FIXME
comment, so regenerating would have replaced a working generic timer read with
a stub. That implementation now lives in the source. The remaining differences
are cosmetic and resolve in favour of the source: a trailing blank line in 38
copies of tx_thread_schedule.S and comment spacing in one tx_port.h.

Note that the Cortex-A VFP fix is already present in ports_arch and was never
at risk, contrary to what the description of the port consistency checks change
said before this was measured.

Extend scripts/check_ports.sh to run the A profile generators too, and make it
fail when a generator fails or is missing rather than reporting a clean tree,
which would have been a false pass.

Pin every workflow to ubuntu-24.04. ubuntu-latest already resolves to that
image, so nothing changes today, but a future migration becomes a deliberate
commit rather than something that happens underneath the -m32 builds.

Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
2026-08-09 10:55:29 -04:00

239 lines
9.2 KiB
Bash
Executable File

#!/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
##############################################################################
# Consistency checks for the port trees. Run it before cutting a release, or
# any time the ports have been touched:
#
# scripts/check_ports.sh
#
# Options:
# --no-regen Skip the reproducibility check, which needs a clean tree.
# --quiet Print only failures and the summary.
#
# Exit status is 0 when every check passes and 1 otherwise, so the same
# command serves CI and the command line.
#
# Each check exists because a real defect reached the repository through it:
#
# 1. Reproducibility. The Cortex-M ports are generated by the copy scripts,
# but fixes were applied to the generated copies instead of the source for
# eight months. The next run of those scripts would have reverted them.
#
# 2. Preprocessor balance. A fix left ports/cortex_m85/iar/inc/tx_port.h with
# one more #endif than #if, so that header could not compile.
#
# 3. Code at file scope. A fix left a second, headerless copy of a function
# body in ports/cortex_m4/ac6/inc/tx_port.h, which placed statements
# outside any function. See issue 569.
#
# The last section reports, without failing, on port families that have no copy
# script and so cannot be checked for reproducibility.
#
# Headers under example_build are skipped: those trees vendor third party SDK
# code, which is not ours to hold to these rules.
set -u
cd "$(dirname "$(realpath "$0")")/.."
regen=1
quiet=0
for arg in "$@"; do
case "$arg" in
--no-regen) regen=0 ;;
--quiet) quiet=1 ;;
-h|--help) sed -n '17,30p' "$0"; exit 0 ;;
*) echo "Unknown option: $arg" >&2; exit 2 ;;
esac
done
failures=0
say() { [ "$quiet" -eq 1 ] || echo "$@"; }
fail() { echo " FAIL: $*"; failures=$((failures + 1)); }
# --------------------------------------------------------------------------
# 1. The generated ports must be reproducible from ports_arch.
# --------------------------------------------------------------------------
say ""
say "== Generated ports are reproducible from ports_arch =="
if [ "$regen" -eq 0 ]; then
say " skipped (--no-regen)"
elif ! command -v git >/dev/null 2>&1; then
say " skipped (git not available)"
elif [ -n "$(git status --porcelain -uno)" ]; then
fail "the working tree has uncommitted changes; commit or stash them, or pass --no-regen"
else
# A generator that fails or is missing must not leave the tree looking
# clean, which would be a false pass.
generator_failed=0
run_generator() {
if ! "$@" >/dev/null 2>&1; then
fail "generator failed: $*"
generator_failed=1
fi
}
run_generator ./scripts/copy_armv7_m.sh
run_generator ./scripts/copy_armv8_m.sh
run_generator ./scripts/copy_module_armv7_m.sh
run_generator env -C ports_arch/ARMv7-A ./update.sh --port-sets tx \
--copy-common-files --copy-port-files --copy-example --patch-files
run_generator env -C ports_arch/ARMv8-A ./update.sh --port-sets tx,tx_smp \
--copy-common-files --copy-port-files --copy-example --patch-files
drift="$(git status --porcelain -uno)"
if [ -n "$drift" ]; then
fail "running the copy scripts changed $(echo "$drift" | wc -l) file(s)."
echo " The ports below were edited directly instead of through ports_arch."
echo " Re-apply the change to ports_arch and re-run the copy scripts."
echo "$drift" | sed 's/^/ /'
git checkout -- . >/dev/null 2>&1
else
say " ok: the copy scripts change nothing"
fi
fi
# --------------------------------------------------------------------------
# 2. Preprocessor directives must balance in every port header.
# --------------------------------------------------------------------------
say ""
say "== Preprocessor directives balance =="
unbalanced=0
while IFS= read -r f; do
result="$(awk '
/^[ \t]*#[ \t]*(if|ifdef|ifndef)/ { depth++ }
/^[ \t]*#[ \t]*endif/ {
depth--
if (depth < 0 && first == 0) { first = NR }
}
END { printf "%d %d", depth, first }
' "$f")"
depth="${result% *}"
first="${result#* }"
if [ "$depth" -ne 0 ]; then
if [ "$first" -ne 0 ]; then
fail "$f: #endif without a matching #if at line $first (final depth $depth)"
else
fail "$f: $depth unterminated #if (final depth $depth)"
fi
unbalanced=$((unbalanced + 1))
fi
done < <(find ports ports_arch ports_module ports_smp -name "*.h" -type f \
! -path "*/example_build/*" 2>/dev/null | sort)
[ "$unbalanced" -eq 0 ] && say " ok: every port header balances"
# --------------------------------------------------------------------------
# 3. No statements outside a function body in a port header.
# --------------------------------------------------------------------------
# Tracks brace depth, ignoring preprocessor lines, comments and strings, and
# reports any statement that lands at depth zero. Declarations, typedefs,
# externs and macro definitions are expected there; assignments, calls and
# dereferences are not, and an orphaned function body shows up as exactly that.
say ""
say "== No code at file scope in port headers =="
orphans=0
while IFS= read -r f; do
result="$(awk '
# Skip preprocessor lines, including multi-line macro bodies, whose
# continuations are statements by design.
/^[ \t]*#/ { if (/\\[ \t]*$/) { in_macro = 1 }; next }
in_macro { if (!/\\[ \t]*$/) { in_macro = 0 }; next }
# Skip comments, both kinds, including continuation lines.
in_comment { if (/\*\//) { in_comment = 0 }; next }
/^[ \t]*\/\// { next }
/\/\*/ { if (!/\*\//) { in_comment = 1 }; next }
{
line = $0
gsub(/"[^"]*"/, "", line)
gsub(/\/\/.*$/, "", line)
stripped = line
gsub(/^[ \t]+|[ \t]+$/, "", stripped)
# A statement at file scope ends in a semicolon and either assigns,
# dereferences or opens a control structure. Declarations and
# prototypes also end in a semicolon but match none of these.
if (depth == 0 && stripped ~ /;[ \t]*$/ &&
(stripped ~ /^\*\(/ ||
stripped ~ /^[A-Za-z_][A-Za-z0-9_]*([ \t]*(\[[^]]*\]|->[ \t]*[A-Za-z_][A-Za-z0-9_]*|\.[A-Za-z_][A-Za-z0-9_]*))*[ \t]*=[^=]/ ||
stripped ~ /^(return|if|while|for|switch|do)[ \t(]/)) {
print NR ": " stripped
found++
}
n = gsub(/{/, "{", line); m = gsub(/}/, "}", line)
depth += n - m
if (depth < 0) { depth = 0 }
}
END { exit (found > 0 ? 1 : 0) }
' "$f")"
if [ -n "$result" ]; then
fail "$f: statement(s) outside any function body"
echo "$result" | sed 's/^/ /'
orphans=$((orphans + 1))
fi
done < <(find ports ports_arch ports_module ports_smp -name "*.h" -type f \
! -path "*/example_build/*" 2>/dev/null | sort)
[ "$orphans" -eq 0 ] && say " ok: no port header carries code at file scope"
# --------------------------------------------------------------------------
# 4. Report only: families with no copy script.
# --------------------------------------------------------------------------
# These are maintained by hand, so a fix applied to one toolchain can silently
# miss the others. Nothing here fails the run; it is a prompt to look.
say ""
say "== Families with no copy script (report only) =="
if [ "$quiet" -eq 0 ]; then
for family in ports/cortex_m0 ports/cortex_m0+ ports/cortex_m23; do
[ -d "$family" ] || continue
for probe in "dsb 0xF" "isb 0xF"; do
have=""; missing=""
for header in "$family"/*/inc/tx_port.h; do
[ -f "$header" ] || continue
grep -q "0xE000ED04" "$header" || continue
tool="$(basename "$(dirname "$(dirname "$header")")")"
if grep -q "$probe" "$header"; then
have="$have $tool"
else
missing="$missing $tool"
fi
done
if [ -n "$have" ] && [ -n "$missing" ]; then
echo " $family: \"$probe\" present in$have but absent in$missing"
fi
done
done
say " (nothing above means the toolchains within each family agree)"
fi
# --------------------------------------------------------------------------
say ""
if [ "$failures" -eq 0 ]; then
say "All port consistency checks passed."
exit 0
fi
echo "$failures port consistency check(s) failed."
exit 1