summaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'scripts')
-rwxr-xr-xscripts/check_ports.sh224
-rwxr-xr-xscripts/prepare_release.sh14
2 files changed, 238 insertions, 0 deletions
diff --git a/scripts/check_ports.sh b/scripts/check_ports.sh
new file mode 100755
index 00000000..5add21b7
--- /dev/null
+++ b/scripts/check_ports.sh
@@ -0,0 +1,224 @@
+#!/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
+ ./scripts/copy_armv7_m.sh >/dev/null 2>&1
+ ./scripts/copy_armv8_m.sh >/dev/null 2>&1
+ ./scripts/copy_module_armv7_m.sh >/dev/null 2>&1
+
+ 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
diff --git a/scripts/prepare_release.sh b/scripts/prepare_release.sh
index c33fa8f8..c6b1d93c 100755
--- a/scripts/prepare_release.sh
+++ b/scripts/prepare_release.sh
@@ -78,6 +78,20 @@ printf "\nThreadX release preparation\n"
printf " Repository : %s\n" "${REPO_ROOT}"
printf " Current version : %s\n" "${CURR_VER}"
printf " Target version : %s\n\n" "${VERSION}"
+
+# --------------------------------------------------------------------------
+# Port consistency checks
+# --------------------------------------------------------------------------
+# Run before anything is branched or rewritten, so a release is never cut on
+# top of ports that have drifted from ports_arch or headers that cannot
+# compile. Set SKIP_PORT_CHECKS=1 to proceed anyway.
+if [ "${SKIP_PORT_CHECKS:-0}" = "1" ]; then
+ printf "Skipping the port consistency checks (SKIP_PORT_CHECKS=1).\n\n"
+elif ! "${SCRIPT_DIR}/check_ports.sh"; then
+ printf "\nRelease preparation stopped: the port consistency checks failed.\n"
+ printf "Fix the problems above, or set SKIP_PORT_CHECKS=1 to proceed anyway.\n"
+ exit 1
+fi
printf "Proceed with update? [y/N] "
read -r CONFIRM
case "${CONFIRM}" in