summaryrefslogtreecommitdiff
path: root/.github/workflows
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 /.github/workflows
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 '.github/workflows')
-rw-r--r--.github/workflows/ci_cortex_m.yml110
-rw-r--r--.github/workflows/clang_check.yml74
-rw-r--r--.github/workflows/gcc_check.yml192
-rw-r--r--.github/workflows/ports_arch_check.yml79
-rw-r--r--.github/workflows/r52_fvp.yml335
-rw-r--r--.github/workflows/regression_template.yml201
-rw-r--r--.github/workflows/regression_test.yml129
-rw-r--r--.github/workflows/repo_checks.yml63
8 files changed, 1063 insertions, 120 deletions
diff --git a/.github/workflows/ci_cortex_m.yml b/.github/workflows/ci_cortex_m.yml
index 149490be..933c950f 100644
--- a/.github/workflows/ci_cortex_m.yml
+++ b/.github/workflows/ci_cortex_m.yml
@@ -1,14 +1,31 @@
-# This is a basic workflow to help you get started with Actions
-
name: cortex_m
-# Controls when the action will run. Triggers the workflow on push or pull request
-# events but only for the master branch
+# Cross-compiles the Cortex-M ports with GCC.
+#
+# Two things were wrong with this workflow and both are fixed here.
+#
+# It ran on master only, for both push and pull_request, while dev is the
+# integration branch -- the same defect ports_arch_check.yml carries a comment
+# about, where it cost eight months of ports drifting unnoticed. So this
+# gated no pull request that anybody opened.
+#
+# And it had not compiled anything since at least 2026-06-08. Every run since
+# then failed in six to eight seconds, at "Prepare all required actions",
+# before checkout: GitHub automatically fails any request using
+# actions/cache@v1. Nobody noticed, because of the first defect.
+#
+# The toolchain setup now follows clang_check.yml rather than third party
+# actions: a pinned release fetched directly, verified against Arm's published
+# checksum, and cached with actions/cache@v4. That also moves the compiler
+# from 9-2019-q4 to a version matching the project's stated GCC 14 default.
+#
+# This covers four ports of the forty under ports/ that have a gnu directory.
+# Widening that to every Arm gnu port is separate work.
on:
push:
- branches: [ master ]
+ branches: [ master, dev ]
pull_request:
- branches: [ master ]
+ branches: [ master, dev ]
paths:
- ".github/workflows/ci_cortex_m.yml"
- 'common/**'
@@ -18,60 +35,79 @@ on:
- 'ports/cortex_m4/gnu/**'
- 'ports/cortex_m7/gnu/**'
-# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
- # This workflow contains a single job called "build"
build:
- # The type of runner that the job will run on
- runs-on: ubuntu-latest
+ runs-on: ubuntu-24.04
strategy:
+ fail-fast: false
matrix:
port: [0, 3, 4, 7]
name: Cortex M${{ matrix.port }} build
- # Steps represent a sequence of tasks that will be executed as part of the job
+ env:
+ # Pinned deliberately, as the runner image is: a toolchain upgrade should
+ # be a reviewable commit rather than something that changes underneath the
+ # ports. Releases: https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads
+ GCC_VERSION: 14.3.rel1
+ GCC_TARGET: arm-none-eabi
+
steps:
- # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
+ # Actions are pinned to a commit SHA, with the version in the trailing
+ # comment. A tag can be moved; a SHA cannot, so this is what makes "which
+ # code ran in CI" answerable from the repository. Dependabot moves these
+ # pins and rewrites the comment with them -- see .github/dependabot.yml.
- name: Check out the repository
- uses: actions/checkout@v4
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: true
- # Store the arm compilers in the cache to speed up builds
- - name: Cache arm-none-eabi-gcc tools
+ - name: Cache the Arm GNU toolchain
id: cache-arm-gcc
- uses: actions/cache@v1
+ uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
- path: $HOME/arm-none-eabi-gcc-9-2019-q4
- key: ${{ runner.os }}-arm-gcc-9-2019-q4
+ path: toolchain
+ key: arm-gnu-toolchain-${{ env.GCC_VERSION }}-x86_64-${{ env.GCC_TARGET }}
- # Get the arm-non-eabi-gcc toolchain
- - name: Install arm-none-eabi-gcc
- uses: fiam/arm-none-eabi-gcc@v1
+ - name: Install the Arm GNU toolchain
if: steps.cache-arm-gcc.outputs.cache-hit != 'true'
- with:
- release: '9-2019-q4' # The arm-none-eabi-gcc release to use.
- directory: $HOME/arm-none-eabi-gcc-9-2019-q4
+ run: |
+ set -eu
+ base="https://developer.arm.com/-/media/Files/downloads/gnu/${GCC_VERSION}/binrel"
+ archive="arm-gnu-toolchain-${GCC_VERSION}-x86_64-${GCC_TARGET}.tar.xz"
+ mkdir -p toolchain && cd toolchain
+ curl -fsSLO "$base/$archive"
+ curl -fsSLO "$base/$archive.sha256asc"
+ sha256sum -c "$archive.sha256asc"
+ tar xf "$archive"
+ rm -f "$archive"
- # Get CMake into the environment
- - name: Install cmake 3.19.1
- uses: lukka/[email protected]
+ - name: Put the toolchain on PATH
+ run: |
+ set -eu
+ echo "$GITHUB_WORKSPACE/toolchain/arm-gnu-toolchain-${GCC_VERSION}-x86_64-${GCC_TARGET}/bin" >> "$GITHUB_PATH"
- # Get Ninja into the environment
- - name: Install ninja-build
- uses: seanmiddleditch/gha-setup-ninja@v3
+ # Only reaches apt if the runner image has stopped shipping ninja. This
+ # repository has already paid for unguarded apt calls: scripts/install.sh
+ # carries a long comment about apt-get update stalling for over two hours
+ # and taking whole regression runs with it. Do not turn this into an
+ # unconditional install.
+ - name: Ensure ninja is available
+ run: |
+ set -eu
+ if command -v ninja >/dev/null 2>&1; then
+ ninja --version
+ else
+ sudo apt-get update
+ sudo apt-get install -y --no-install-recommends ninja-build
+ fi
+
+ - name: Report the toolchain version
+ run: ${{ env.GCC_TARGET }}-gcc --version
- # Prepare the build system
- name: Prepare build system
run: cmake -Bbuild -DCMAKE_TOOLCHAIN_FILE=./cmake/cortex_m${{ matrix.port }}.cmake -GNinja .
- env:
- PATH: "$HOME/arm-none-eabi-gcc-9-2019-q4/bin:$PATH"
- name: Compile and link
run: cmake --build ./build
- env:
- PATH: "$HOME/arm-none-eabi-gcc-9-2019-q4/bin:$PATH"
-
-
diff --git a/.github/workflows/clang_check.yml b/.github/workflows/clang_check.yml
new file mode 100644
index 00000000..53b60bfe
--- /dev/null
+++ b/.github/workflows/clang_check.yml
@@ -0,0 +1,74 @@
+name: clang_check
+
+# Builds the Arm ports with Arm Toolchain for Embedded, which is LLVM based and
+# is the successor to Arm Compiler 6. The gnu ports are otherwise only ever
+# built with GNU tooling, and GNU as accepts several non-canonical forms that
+# LLVM's assembler rejects; this catches those before they accumulate.
+on:
+ pull_request:
+ branches: [ master, dev ]
+ paths:
+ - ".github/workflows/clang_check.yml"
+ - "scripts/check_clang.sh"
+ # CMakeLists.txt, cmake/ and common_smp/ were missing until 26 Aug 2026,
+ # so this check did not run when the files it reads changed. The
+ # ports_smp example builds compile common_smp/src and the CMake stage
+ # reads the toolchain file and the top-level project. utility/ is
+ # deliberately absent: the FreeRTOS and POSIX layers under it are not
+ # enabled by any target this workflow builds. Keep this list and
+ # gcc_check.yml's the same.
+ - "CMakeLists.txt"
+ - "cmake/**"
+ - "common/**"
+ # common_modules/ holds the portable module manager that the module
+ # manager stage compiles once per Arm module port -- the larger half of
+ # what that stage builds. Without it a change there leaves the stage
+ # unrun, which is the same "absent from the count reads as covered" the
+ # stage exists to close.
+ - "common_modules/**"
+ - "common_smp/**"
+ - "ports/**"
+ - "ports_arch/**"
+ - "ports_module/**"
+ - "ports_smp/**"
+
+jobs:
+ atfe:
+ runs-on: ubuntu-24.04
+
+ env:
+ # Pinned deliberately, as the runner image is: a toolchain upgrade should
+ # be a reviewable commit rather than something that changes underneath the
+ # ports. Releases: https://github.com/arm/arm-toolchain/releases
+ ATFE_VERSION: 22.1.0
+
+ steps:
+ # Actions are pinned to a commit SHA, with the version in the trailing
+ # comment. A tag can be moved; a SHA cannot, so this is what makes "which
+ # code ran in CI" answerable from the repository. Dependabot moves these
+ # pins and rewrites the comment with them -- see .github/dependabot.yml.
+ - name: Checkout sources
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+
+ - name: Cache Arm Toolchain for Embedded
+ id: cache-atfe
+ uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
+ with:
+ path: atfe
+ key: atfe-${{ env.ATFE_VERSION }}-linux-x86_64
+
+ - name: Install Arm Toolchain for Embedded
+ if: steps.cache-atfe.outputs.cache-hit != 'true'
+ run: |
+ set -eu
+ base="https://github.com/arm/arm-toolchain/releases/download/release-${ATFE_VERSION}-ATfE"
+ archive="ATfE-${ATFE_VERSION}-Linux-x86_64.tar.xz"
+ mkdir -p atfe && cd atfe
+ curl -fsSLO "$base/$archive"
+ curl -fsSLO "$base/$archive.sha256"
+ sha256sum -c "$archive.sha256"
+ tar xf "$archive"
+ rm -f "$archive"
+
+ - name: Build the Arm ports with clang
+ run: scripts/check_clang.sh --clang "atfe/ATfE-${ATFE_VERSION}-Linux-x86_64/bin/clang"
diff --git a/.github/workflows/gcc_check.yml b/.github/workflows/gcc_check.yml
new file mode 100644
index 00000000..cd340a0e
--- /dev/null
+++ b/.github/workflows/gcc_check.yml
@@ -0,0 +1,192 @@
+name: gcc_check
+
+# Builds the Arm ports with the Arm GNU toolchain, in six 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, then
+# link the example builds, both the script-driven ones and those driven by
+# CMake.
+#
+# Why this exists: GCC 14 on Linux is the project's default compiler, it is
+# what the gnu ports exist for, and until this workflow landed, nothing in CI
+# compiled a line of any port with it. The only cross-compilation check that
+# ran was clang_check, so the LLVM path was better guarded than the GNU one,
+# on ports whose directory is literally named gnu.
+#
+# What it covers: 840 assembly sources across 40 port families, 469 of them
+# again behind feature macros, common/src for nine cores, 302 module manager C
+# files across the nine Arm module ports, 42 script-driven example links and
+# the five Cortex-R52 CMake images. Every skip is printed by name with a reason
+# -- run scripts/check_gcc.sh --help, or read its header.
+#
+# What it does not cover: it compiles and links and **executes nothing**. The
+# Cortex-R52 FVP ctest suite is not part of it. RISC-V, MIPS, RX and ARC are
+# outside it entirely -- RISC-V deliberately, and now for a stronger reason
+# than when this was written: regression_test.yml builds both its ports and
+# runs 955 tests on them under QEMU, which is more than a compile check could
+# say. Adding them here would widen this workflow's toolchain download by about
+# a gigabyte to re-prove a subset of that.
+#
+# It does not supersede cortex_m. That workflow builds four ports *through
+# CMake*, which is the only thing exercising cmake/cortex_m*.cmake and the
+# top-level CMakeLists for the M profile; this script's CMake stage covers
+# cortex_r52 only. The overlap is the assembly and the C sources, not the build
+# system.
+on:
+ # push as well as pull_request, so dev's own history has a baseline and a bad
+ # squash-merge is caught rather than waiting for the next PR to notice. dev is
+ # named because it is the integration branch: a workflow that triggers only on
+ # master does not gate any pull request anybody opens, which is the defect
+ # ports_arch_check.yml carries a comment about and which cost cortex_m three
+ # months of failing in seven seconds unnoticed.
+ push:
+ branches: [ master, dev ]
+ paths:
+ - ".github/workflows/gcc_check.yml"
+ - "scripts/check_gcc.sh"
+ - "CMakeLists.txt"
+ - "cmake/**"
+ - "common/**"
+ # common_modules/ holds the portable module manager that the module
+ # manager stage compiles once per Arm module port -- the larger half of
+ # what that stage builds. Without it a change there leaves the stage
+ # unrun, which is the same "absent from the count reads as covered" the
+ # stage exists to close.
+ - "common_modules/**"
+ - "common_smp/**"
+ - "ports/**"
+ - "ports_arch/**"
+ - "ports_module/**"
+ - "ports_smp/**"
+ pull_request:
+ branches: [ master, dev ]
+ # The two lists are duplicated rather than shared through a YAML anchor.
+ # GitHub Actions' workflow parser does not dependably honour anchors, and
+ # the failure mode is the whole workflow refusing to parse -- which is the
+ # cortex_m failure again, a job that dies before it does anything. Ten
+ # duplicated lines are cheaper than that. **Edit both.**
+ #
+ # common_smp, CMakeLists.txt and cmake/ are here and are absent from
+ # clang_check.yml's otherwise identical list, which is a gap in that file
+ # and is fixed alongside this one: the ports_smp example builds compile
+ # common_smp/src, and the CMake stage reads the toolchain file and the
+ # top-level project. utility/ is deliberately absent -- the FreeRTOS and
+ # POSIX layers under it are not enabled by any target this workflow builds.
+ paths:
+ - ".github/workflows/gcc_check.yml"
+ - "scripts/check_gcc.sh"
+ - "CMakeLists.txt"
+ - "cmake/**"
+ - "common/**"
+ # common_modules/ holds the portable module manager that the module
+ # manager stage compiles once per Arm module port -- the larger half of
+ # what that stage builds. Without it a change there leaves the stage
+ # unrun, which is the same "absent from the count reads as covered" the
+ # stage exists to close.
+ - "common_modules/**"
+ - "common_smp/**"
+ - "ports/**"
+ - "ports_arch/**"
+ - "ports_module/**"
+ - "ports_smp/**"
+
+jobs:
+ gnu:
+ runs-on: ubuntu-24.04
+
+ env:
+ # Pinned deliberately, as the runner image is: a toolchain upgrade should
+ # be a reviewable commit rather than something that changes underneath the
+ # ports. 14.3.rel1 is the GCC 14 the project builds against, and the
+ # version cortex_m already pins.
+ # Releases: https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads
+ GCC_VERSION: 14.3.rel1
+
+ steps:
+ # Actions are pinned to a commit SHA, with the version in the trailing
+ # comment. A tag can be moved; a SHA cannot, so this is what makes "which
+ # code ran in CI" answerable from the repository. Dependabot moves these
+ # pins and rewrites the comment with them -- see .github/dependabot.yml.
+ - name: Check out the repository
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+
+ # Two toolchains, because Arm ships AArch32 and AArch64 as separate
+ # downloads and scripts/check_gcc.sh needs both -- every port maps to one of
+ # exactly those two triples. One job with two cache steps rather than two
+ # jobs, so the checks list stays short and a single script invocation sees
+ # both compilers.
+ #
+ # The AArch32 path and key match cortex_m's exactly, so the two workflows
+ # share one cache entry rather than each holding its own copy of the same
+ # 500MB archive. Change them together or the sharing silently stops and the
+ # only symptom is a slower run.
+ - name: Cache the AArch32 Arm GNU toolchain
+ id: cache-arm32
+ uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
+ with:
+ path: toolchain
+ key: arm-gnu-toolchain-${{ env.GCC_VERSION }}-x86_64-arm-none-eabi
+
+ - name: Cache the AArch64 Arm GNU toolchain
+ id: cache-arm64
+ uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
+ with:
+ path: toolchain64
+ key: arm-gnu-toolchain-${{ env.GCC_VERSION }}-x86_64-aarch64-none-elf
+
+ # The checksum suffix is .sha256asc and not .sha256, and that is not a
+ # stylistic choice. Arm publishes both for this release, and for
+ # arm-none-eabi the .sha256 file contains a 32-character MD5 rather than a
+ # SHA-256 -- verified 26 Aug 2026 -- so sha256sum -c on it fails with "no
+ # properly formatted checksum lines found". .sha256asc is a plain
+ # sha256sum-format line for both triples. Do not "simplify" the suffix.
+ - name: Install the AArch32 Arm GNU toolchain
+ if: steps.cache-arm32.outputs.cache-hit != 'true'
+ run: |
+ set -eu
+ base="https://developer.arm.com/-/media/Files/downloads/gnu/${GCC_VERSION}/binrel"
+ archive="arm-gnu-toolchain-${GCC_VERSION}-x86_64-arm-none-eabi.tar.xz"
+ mkdir -p toolchain && cd toolchain
+ curl -fsSLO "$base/$archive"
+ curl -fsSLO "$base/$archive.sha256asc"
+ sha256sum -c "$archive.sha256asc"
+ tar xf "$archive"
+ rm -f "$archive"
+
+ - name: Install the AArch64 Arm GNU toolchain
+ if: steps.cache-arm64.outputs.cache-hit != 'true'
+ run: |
+ set -eu
+ base="https://developer.arm.com/-/media/Files/downloads/gnu/${GCC_VERSION}/binrel"
+ archive="arm-gnu-toolchain-${GCC_VERSION}-x86_64-aarch64-none-elf.tar.xz"
+ mkdir -p toolchain64 && cd toolchain64
+ curl -fsSLO "$base/$archive"
+ curl -fsSLO "$base/$archive.sha256asc"
+ sha256sum -c "$archive.sha256asc"
+ tar xf "$archive"
+ rm -f "$archive"
+
+ # Only reaches apt if the runner image has stopped shipping ninja, which
+ # the CMake stage needs. This repository has already paid for unguarded apt
+ # calls: scripts/install.sh carries a long comment about apt-get update
+ # stalling for over two hours and taking whole regression runs with it. Do
+ # not turn this into an unconditional install.
+ - name: Ensure ninja is available
+ run: |
+ set -eu
+ if command -v ninja >/dev/null 2>&1; then
+ ninja --version
+ else
+ sudo apt-get update
+ sudo apt-get install -y --no-install-recommends ninja-build
+ fi
+
+ # The script is told where both toolchains are rather than being left to
+ # find them on PATH, so nothing about the runner image can decide which
+ # compiler is used. The script prints both versions it resolved, which is
+ # what makes "was this really 14.3.rel1?" answerable from the log.
+ - name: Build the Arm ports with GCC
+ run: |
+ scripts/check_gcc.sh \
+ --arm-none-eabi "$GITHUB_WORKSPACE/toolchain/arm-gnu-toolchain-${GCC_VERSION}-x86_64-arm-none-eabi/bin" \
+ --aarch64-none-elf "$GITHUB_WORKSPACE/toolchain64/arm-gnu-toolchain-${GCC_VERSION}-x86_64-aarch64-none-elf/bin"
diff --git a/.github/workflows/ports_arch_check.yml b/.github/workflows/ports_arch_check.yml
index 4a99c032..334a5386 100644
--- a/.github/workflows/ports_arch_check.yml
+++ b/.github/workflows/ports_arch_check.yml
@@ -6,7 +6,10 @@ name: ports_arch_check
# events but only for the master branch
on:
pull_request:
- branches: [ master ]
+ # dev is included as well as master. The check only ever ran against master,
+ # so eight months of port fixes merged into dev without it, and the ports
+ # drifted from ports_arch unnoticed.
+ branches: [ master, dev ]
paths:
- ".github/workflows/ports_arch_check.yml"
- 'common/**'
@@ -22,52 +25,60 @@ jobs:
# Check ports for cortex-m
cortex-m:
# The type of runner that the job will run on
- runs-on: ubuntu-latest
+ runs-on: ubuntu-24.04
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- - name: Checkout sources recursively
- uses: actions/checkout@v2
- with:
- token: ${{ secrets.REPO_SCOPED_TOKEN }}
- submodules: true
+ # No token input: secrets are not available to pull requests from forks, so
+ # passing one made this job fail at checkout with "Input required and not
+ # supplied: token" and the check never evaluated anything. The default
+ # GITHUB_TOKEN is enough to check out a public repository, and the
+ # repository has no submodules.
+ # Actions are pinned to a commit SHA, with the version in the trailing
+ # comment. A tag can be moved; a SHA cannot, so this is what makes "which
+ # code ran in CI" answerable from the repository. Dependabot moves these
+ # pins and rewrites the comment with them -- see .github/dependabot.yml.
+ - name: Checkout sources
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- # Copy ports arch
- - name: Copy ports arch
- run: |
- scripts/copy_armv7_m.sh && scripts/copy_armv8_m.sh && scripts/copy_module_armv7_m.sh
- if [[ -n $(git status --porcelain -uno) ]]; then
- echo "Ports for ARM architecture is not updated"
- git status
- exit 1
- fi
+ # Check the port trees: the generated ports must be reproducible from
+ # ports_arch, and no port header may be left unbalanced or carrying code
+ # outside a function. The same script runs locally, so a contributor sees
+ # exactly what CI sees.
+ - name: Check ports
+ run: scripts/check_ports.sh
cortex-a:
- # Check ports for cortex-a
- runs-on: windows-latest
+ # Check ports for cortex-a. This ran on Windows because the A profile
+ # tooling was PowerShell only; update.sh beside each update.ps1 means it
+ # runs on the same image as every other job now.
+ runs-on: ubuntu-24.04
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- - name: Checkout sources recursively
- uses: actions/checkout@v2
- with:
- token: ${{ secrets.REPO_SCOPED_TOKEN }}
- submodules: true
+ # No token input: secrets are not available to pull requests from forks, so
+ # passing one made this job fail at checkout with "Input required and not
+ # supplied: token" and the check never evaluated anything. The default
+ # GITHUB_TOKEN is enough to check out a public repository, and the
+ # repository has no submodules.
+ - name: Checkout sources
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- # Copy ports arch
- - name: Copy ports arch
+ # Regenerate the A profile ports and fail if anything changed, which means
+ # a generated port was edited directly instead of ports_arch.
+ - name: Check the Cortex-A ports
run: |
- cd ports_arch/ARMv7-A
- pwsh -Command ./update.ps1 -PortSets tx -CopyCommonFiles -CopyPortFiles -CopyExample -PatchFiles
- cd ../../ports_arch/ARMv8-A
- pwsh -Command ./update.ps1 -PortSets tx,tx_smp -CopyCommonFiles -CopyPortFiles -CopyExample -PatchFiles
- if ((git status --porcelain -uno) -ne $null) {
- Write-Host "Ports for ARM architecture is not updated"
- git status
- Exit 1
- }
+ (cd ports_arch/ARMv7-A && ./update.sh --port-sets tx \
+ --copy-common-files --copy-port-files --copy-example --patch-files)
+ (cd ports_arch/ARMv8-A && ./update.sh --port-sets tx,tx_smp \
+ --copy-common-files --copy-port-files --copy-example --patch-files)
+ if [[ -n $(git status --porcelain -uno) ]]; then
+ echo "The Cortex-A ports are not reproducible from ports_arch:"
+ git status --porcelain -uno
+ exit 1
+ fi
diff --git a/.github/workflows/r52_fvp.yml b/.github/workflows/r52_fvp.yml
new file mode 100644
index 00000000..60861d49
--- /dev/null
+++ b/.github/workflows/r52_fvp.yml
@@ -0,0 +1,335 @@
+name: r52_fvp
+
+# Builds every Cortex-R52 CMake image in both supported configurations and
+# EXECUTES them on the Armv8-R AEM FVP, asserting each image's self-reported
+# result.
+#
+# Why this exists: until this workflow landed, nothing in CI ran a single
+# instruction of any ThreadX port. gcc_check compiles and links -- its own
+# header says so, "it compiles and links and **executes nothing**" -- and its
+# CMake stage covers exactly one R52 configuration, the base FVP example with
+# -DTX_R52_ENABLE_MPU=ON. So a change that assembles cleanly and links cleanly
+# and then hangs on the first context switch passes every required check.
+#
+# That gap matters more for cortex_r52 than for the other ports, because the
+# R52 is the foundation the partitioning work is sequenced behind: the EL2 boot
+# path in entry.S, the MPU programming, and the generic-timer plumbing are all
+# things later work attaches to rather than replaces. A regression in any of
+# them has to be caught by an execution test; there is no static check for
+# "the scheduler still schedules".
+#
+# What it covers:
+#
+# - the default configuration -- the images gcc_check's CMake stage builds,
+# but run rather than merely linked;
+# - the feature configuration -- VFP with the hard float ABI, FIQ, IRQ
+# nesting and FIQ nesting. Those four options gate whole assembly blocks
+# (the VMRS/VSTMDB/VLDMIA sequences, the FIQ context paths, the nesting
+# start/end pairs) that the default configuration never assembles, let
+# alone runs.
+#
+# What it does not cover: the module manager port and the S32Z280 targets are
+# not on dev yet -- they are PR #639. When that merges, add their lanes here
+# rather than starting a second workflow; the matrix already has the shape.
+#
+# The S32Z280 targets will be build-only in CI regardless. They need silicon,
+# and a hosted runner has none.
+
+on:
+ # push as well as pull_request, for the reason gcc_check.yml states at
+ # length: dev needs its own baseline so a bad squash-merge is caught then
+ # rather than by whoever opens the next pull request.
+ push:
+ branches: [ master, dev ]
+ paths:
+ - ".github/workflows/r52_fvp.yml"
+ - "CMakeLists.txt"
+ - "cmake/cortex_r52*.cmake"
+ - "common/**"
+ - "ports/cortex_r52/**"
+ - "ports_module/cortex_r52/**"
+ pull_request:
+ branches: [ master, dev ]
+ # Duplicated rather than shared through a YAML anchor. GitHub Actions'
+ # parser does not dependably honour anchors and the failure mode is the
+ # whole workflow refusing to parse -- a job that dies before it does
+ # anything, which is the exact defect gcc_check.yml carries a comment
+ # about. **Edit both.**
+ paths:
+ - ".github/workflows/r52_fvp.yml"
+ - "CMakeLists.txt"
+ - "cmake/cortex_r52*.cmake"
+ - "common/**"
+ - "ports/cortex_r52/**"
+ - "ports_module/cortex_r52/**"
+
+jobs:
+ # One job rather than one per configuration, for the reason gcc_check.yml
+ # gives for folding its two toolchains together: the checks list stays short,
+ # and a single run sees every configuration so a cross-configuration
+ # regression shows up in one log instead of two.
+ r52:
+ runs-on: ubuntu-24.04
+
+ env:
+ # Pinned deliberately. Matches gcc_check.yml and ci_cortex_m.yml so all
+ # three share one cache entry rather than each holding its own copy of
+ # the same archive; change them together or the sharing silently stops.
+ # 14.3.rel1 is the project's GCC 14 default.
+ GCC_VERSION: 14.3.rel1
+
+ # Where the Armv8-R AEM FVP comes from.
+ #
+ # Arm distributes it free of charge but behind a click-through licence,
+ # with no stable unauthenticated download URL we can hard-code -- the
+ # permalink forms under developer.arm.com/-/cdn-downloads all 404 for
+ # this model. So the URL is a repository variable rather than a literal
+ # here, and the maintainer sets it once to a location the runner can
+ # reach (an Eclipse-hosted mirror, a release asset, or Arm's own URL if
+ # a durable one appears).
+ #
+ # Set FVP_AEMV8R_URL to a .tgz/.tar.gz of the model, and optionally
+ # FVP_AEMV8R_SHA256 to its checksum. With the checksum set the download
+ # is verified; without it the run says so rather than pretending.
+ #
+ # When the variable is unset the build lanes still run and still gate the
+ # pull request -- only execution is skipped, loudly, in the log and in
+ # the job summary. A silent green here would recreate the exact hole this
+ # workflow exists to close.
+ FVP_URL: ${{ vars.FVP_AEMV8R_URL }}
+ FVP_SHA256: ${{ vars.FVP_AEMV8R_SHA256 }}
+
+ steps:
+ # Actions are pinned to a commit SHA with the version in the trailing
+ # comment. A tag can be moved; a SHA cannot. Dependabot moves these pins
+ # and rewrites the comments -- see .github/dependabot.yml.
+ - name: Check out the repository
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+
+ # Path and key match gcc_check.yml's AArch32 entry exactly, so the two
+ # workflows share one cache entry. Only the AArch32 toolchain is needed
+ # here: cortex_r52 is Armv8-R AArch32, and nothing this workflow builds
+ # targets aarch64.
+ - name: Cache the AArch32 Arm GNU toolchain
+ id: cache-arm32
+ uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
+ with:
+ path: toolchain
+ key: arm-gnu-toolchain-${{ env.GCC_VERSION }}-x86_64-arm-none-eabi
+
+ # The checksum suffix is .sha256asc and not .sha256. Arm publishes both,
+ # and for arm-none-eabi the .sha256 file contains a 32-character MD5, so
+ # sha256sum -c on it fails with "no properly formatted checksum lines
+ # found". Do not "simplify" the suffix. (Same note as gcc_check.yml, which
+ # is where it was first paid for.)
+ - name: Install the AArch32 Arm GNU toolchain
+ if: steps.cache-arm32.outputs.cache-hit != 'true'
+ run: |
+ set -eu
+ base="https://developer.arm.com/-/media/Files/downloads/gnu/${GCC_VERSION}/binrel"
+ archive="arm-gnu-toolchain-${GCC_VERSION}-x86_64-arm-none-eabi.tar.xz"
+ mkdir -p toolchain && cd toolchain
+ curl -fsSLO "$base/$archive"
+ curl -fsSLO "$base/$archive.sha256asc"
+ sha256sum -c "$archive.sha256asc"
+ tar xf "$archive"
+ rm -f "$archive"
+
+ # Only reaches apt if the runner image has stopped shipping ninja. This
+ # repository has already paid for unguarded apt calls -- scripts/install.sh
+ # carries a long comment about apt-get update stalling for over two hours
+ # and taking whole regression runs with it. Do not make this
+ # unconditional.
+ - name: Ensure ninja is available
+ run: |
+ set -eu
+ if command -v ninja >/dev/null 2>&1; then
+ ninja --version
+ else
+ sudo apt-get update
+ sudo apt-get install -y --no-install-recommends ninja-build
+ fi
+
+ # The cache key has to move when the model does, and stay put when it does
+ # not. hashFiles() reads files, so it cannot hash a variable: pointing it
+ # at this workflow keyed the cache on the file's own contents, which is
+ # wrong twice over -- repointing FVP_AEMV8R_URL kept serving the old model,
+ # and editing anything here forced a needless refetch of a large archive.
+ # The URL is hashed here instead, so the key tracks exactly what it names.
+ - name: Key the FVP cache on the download URL
+ id: fvp-key
+ if: env.FVP_URL != ''
+ run: |
+ set -eu
+ printf 'hash=%s\n' "$(printf '%s' "$FVP_URL" | sha256sum | cut -d' ' -f1)" >>"$GITHUB_OUTPUT"
+
+ - name: Cache the Armv8-R AEM FVP
+ id: cache-fvp
+ if: env.FVP_URL != ''
+ uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
+ with:
+ path: fvp
+ key: fvp-aemv8r-${{ steps.fvp-key.outputs.hash }}-${{ env.FVP_SHA256 || 'nosum' }}
+
+ - name: Install the Armv8-R AEM FVP
+ if: env.FVP_URL != '' && steps.cache-fvp.outputs.cache-hit != 'true'
+ run: |
+ set -eu
+ mkdir -p fvp && cd fvp
+ curl -fsSL -o fvp.tgz "$FVP_URL"
+ if [ -n "${FVP_SHA256:-}" ]; then
+ echo "$FVP_SHA256 fvp.tgz" | sha256sum -c -
+ else
+ echo "::warning::FVP_AEMV8R_SHA256 is not set; the download was not verified."
+ fi
+ tar xf fvp.tgz
+ rm -f fvp.tgz
+
+ # The model links against libatomic. Everything else it needs
+ # (libstdc++, libgcc_s, librt, libpthread) is already on the runner image,
+ # and its own libscxframework/libsystemc ship inside the tarball. The
+ # runner usually has libatomic1 by way of gcc, so this is guarded for the
+ # same reason the ninja step is.
+ - name: Ensure the FVP's runtime dependencies are present
+ if: env.FVP_URL != ''
+ run: |
+ set -eu
+ if ldconfig -p | grep -q 'libatomic\.so\.1'; then
+ echo "libatomic1 present"
+ else
+ sudo apt-get update
+ sudo apt-get install -y --no-install-recommends libatomic1
+ fi
+
+ # Resolve the model once and hand its absolute path to CMake below.
+ #
+ # ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/CMakeLists.txt finds
+ # it with find_program(... HINTS $ENV{HOME}/FVP_Base_AEMv8R_11.32_19/bin),
+ # a hint that is right on a developer's machine and meaningless here, so
+ # the path is passed explicitly. find_program caches into FVP_BASER_AEMV8R,
+ # which means -DFVP_BASER_AEMV8R=<path> overrides it with no change to the
+ # CMake files.
+ #
+ # FVP_PRESENT, not a bare "did the variable exist", so that a URL which
+ # downloads something that is not the model fails here rather than
+ # silently skipping every test later.
+ - name: Locate the FVP
+ id: fvp
+ run: |
+ set -eu
+ if [ -z "${FVP_URL:-}" ]; then
+ echo "present=false" >>"$GITHUB_OUTPUT"
+ echo "::warning::FVP_AEMV8R_URL is not set for this repository, so the Cortex-R52 images were built but NOT executed. Set the repository variable to enable the execution lanes."
+ {
+ echo "### Cortex-R52 FVP execution: SKIPPED"
+ echo ""
+ echo "\`FVP_AEMV8R_URL\` is not set, so this run built the images but executed none of them."
+ echo "Set the repository variable (Settings -> Secrets and variables -> Actions -> Variables)"
+ echo "to a download URL for the Armv8-R AEM FVP to turn the execution lanes on."
+ } >>"$GITHUB_STEP_SUMMARY"
+ exit 0
+ fi
+ binary="$(find fvp -type f -name FVP_BaseR_AEMv8R -perm -u+x | head -1)"
+ if [ -z "$binary" ]; then
+ echo "FVP_AEMV8R_URL was set, but no FVP_BaseR_AEMv8R binary was found in the archive." >&2
+ find fvp -maxdepth 3 -type d | sed 's/^/ /' >&2
+ exit 1
+ fi
+ binary="$(cd "$(dirname "$binary")" && pwd)/$(basename "$binary")"
+ echo "present=true" >>"$GITHUB_OUTPUT"
+ echo "binary=$binary" >>"$GITHUB_OUTPUT"
+ "$binary" --version
+
+ # Both configurations are configured, built and (when the model is here)
+ # run by one script, so that adding a configuration is one line rather
+ # than three near-identical blocks of YAML.
+ #
+ # Every image is named explicitly from the generated ninja graph rather
+ # than from a list kept here. The images are EXCLUDE_FROM_ALL -- a bare
+ # "cmake --build" reports "no work to do" and would compare nothing --
+ # and reading the graph means a target added to CMakeLists.txt cannot
+ # escape this check by nobody remembering to list it. This is the same
+ # technique, and the same reasoning, as scripts/check_gcc.sh's CMake stage.
+ #
+ # TX_R52_FLOAT_ABI=hard is not optional alongside TX_R52_ENABLE_VFP: the
+ # port's CMakeLists.txt refuses the combination outright (#686), because
+ # TX_ENABLE_VFP_SUPPORT enables VMRS/VSTMDB/VLDMIA blocks that a soft
+ # float ABI leaves the assembler unable to accept.
+ - name: Build and run the Cortex-R52 images
+ run: |
+ set -eu
+ export PATH="$GITHUB_WORKSPACE/toolchain/arm-gnu-toolchain-${GCC_VERSION}-x86_64-arm-none-eabi/bin:$PATH"
+ arm-none-eabi-gcc --version | head -1
+
+ fvp_present='${{ steps.fvp.outputs.present }}'
+ fvp_binary='${{ steps.fvp.outputs.binary }}'
+
+ fvp_arg=""
+ if [ "$fvp_present" = "true" ]; then
+ fvp_arg="-DFVP_BASER_AEMV8R=$fvp_binary"
+ fi
+
+ status=0
+
+ run_configuration() {
+ name="$1"; shift
+ build="build/$name"
+
+ echo ""
+ echo "=============================================================="
+ echo "== $name"
+ echo "=============================================================="
+
+ if ! cmake -S . -B "$build" -G Ninja \
+ -DCMAKE_TOOLCHAIN_FILE=cmake/cortex_r52.cmake \
+ $fvp_arg "$@"; then
+ echo "::error::$name: CMake configure failed"
+ status=1
+ return
+ fi
+
+ images="$(ninja -C "$build" -t targets all 2>/dev/null \
+ | grep -oE '^[A-Za-z0-9_]+\.elf' \
+ | grep -v '^cmake_' | sort -u)"
+ if [ -z "$images" ]; then
+ echo "::error::$name: no .elf targets found in the CMake graph"
+ status=1
+ return
+ fi
+
+ echo "$name: building $(echo "$images" | wc -l) images"
+ # shellcheck disable=SC2086
+ if ! cmake --build "$build" --target $images; then
+ echo "::error::$name: one or more images did not build"
+ status=1
+ return
+ fi
+
+ if [ "$fvp_present" != "true" ]; then
+ echo "$name: built, not executed (no FVP configured)"
+ return
+ fi
+
+ # --no-tests=error, because ctest's default is to report success
+ # when it finds nothing to run. The example registers its tests
+ # inside if(FVP found) and if(Python3_FOUND), so either one going
+ # unmet would leave this stage green having executed nothing -- the
+ # very hole this workflow exists to close.
+ if ! ctest --test-dir "$build" --output-on-failure --no-tests=error; then
+ echo "::error::$name: one or more images failed on the FVP, or no tests were registered"
+ status=1
+ fi
+ }
+
+ run_configuration default \
+ -DTX_R52_BUILD_FVP_EXAMPLE=ON
+
+ run_configuration feature \
+ -DTX_R52_BUILD_FVP_EXAMPLE=ON \
+ -DTX_R52_ENABLE_VFP=ON \
+ -DTX_R52_FLOAT_ABI=hard \
+ -DTX_R52_ENABLE_FIQ=ON \
+ -DTX_R52_ENABLE_IRQ_NESTING=ON \
+ -DTX_R52_ENABLE_FIQ_NESTING=ON
+
+ exit $status
diff --git a/.github/workflows/regression_template.yml b/.github/workflows/regression_template.yml
index 205eb5a8..064f2ebe 100644
--- a/.github/workflows/regression_template.yml
+++ b/.github/workflows/regression_template.yml
@@ -21,6 +21,26 @@ on:
default: './test/cmake'
required: false
type: string
+ # Minutes for the install step. Ten suits a consumer that only installs
+ # packages, which is all of them bar GUIX: measured install steps run 0.2
+ # to 0.5 minutes, and NetX Duo's secure interoperability job, which builds
+ # OpenSSL and wolfSSL from source, takes 2.1 to 2.8. GUIX also builds a
+ # configuration here, because its regression suite is staged across the
+ # install, build and test steps to fit their limits, so it needs longer.
+ # The default keeps every other consumer exactly where it was.
+ install_timeout_minutes:
+ default: 10
+ required: false
+ type: number
+ # Minutes for the build step, on the same reasoning as the install step
+ # above. Fifteen suits a consumer that builds once: measured build steps
+ # are 0.5 minutes for LevelX, 1.1 for FileX and 6.4 for USBX. GUIX builds
+ # five configurations here as part of its staging and reaches 11.8, which
+ # is 79 per cent of the cap and shrinking as tests are added.
+ build_timeout_minutes:
+ default: 15
+ required: false
+ type: number
skip_test:
default: false
required: false
@@ -29,8 +49,40 @@ on:
default: false
required: false
type: boolean
+ # The merged report, not one configuration's. Every configuration is
+ # instrumented now (TX_COVERAGE below) and coverage.sh --merge unions
+ # them; default_build_coverage was one build of five, and the code the
+ # other four select was absent from its denominator rather than uncovered
+ # in it.
coverage_name:
- default: 'default_build_coverage'
+ default: 'merged'
+ required: false
+ type: string
+ # The lower and upper threshold percentages handed to
+ # CodeCoverageSummary, lower first. The lower one is a hard floor: the
+ # step below sets fail_below_min, so a run whose merged line rate falls
+ # under it turns the job red.
+ #
+ # Both must be whole numbers. Probed against the pinned action on a
+ # runner, 26 Aug 2026: '99.9 100' is rejected with
+ # 'System.ArgumentException - Threshold parameter set incorrectly.' and
+ # the step fails whether or not fail_below_min is set, because the action
+ # parses each half with an integer parse. So a floor can be 99 or 100 and
+ # nothing between, and the intended 99.9 is not expressible.
+ #
+ # The floor is compared against the LINE rate only -- not the branch
+ # rate, and not the lower of the two. Probed on the same run: the
+ # ThreadX report at 100.00% lines and 77.67% branches passes a floor of
+ # 99. Worth knowing, because branch coverage is around 78% in both
+ # suites while the project asks for 100%, and a floor set from the
+ # headline line figure says nothing about it.
+ #
+ # Each suite sets its own in regression_test.yml, because they do not
+ # sit at the same figure. The default here is the action's own, low
+ # enough to be no floor at all in practice -- a caller that collects
+ # coverage is expected to name its number.
+ coverage_thresholds:
+ default: '50 75'
required: false
type: string
skip_deploy:
@@ -58,26 +110,45 @@ jobs:
pull-requests: write
# The type of runner that the job will run on
- runs-on: ubuntu-latest
+ runs-on: ubuntu-24.04
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
+ # Actions are pinned to a commit SHA, with the version in the trailing
+ # comment. A tag can be moved; a SHA cannot, so this is what makes "which
+ # code ran in CI" answerable from the repository. Dependabot moves these
+ # pins and rewrites the comment with them -- see .github/dependabot.yml.
- name: Check out the repository
- uses: actions/[email protected]
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: true
+ # apt and PyPI have stalled this step twice, for 55 minutes and for over two
+ # hours, against a normal 27 to 152 seconds. Nothing here had a timeout, so a
+ # stall ran until the six hour job limit and cost a whole run rather than
+ # failing and being retried.
- name: Install softwares
+ timeout-minutes: ${{ inputs.install_timeout_minutes }}
run: ${{ inputs.install_script }}
+ # TX_COVERAGE instruments every build configuration rather than only the one
+ # whose name ends in _coverage. It has to be set for the build as well as the
+ # test: the build is where -fprofile-arcs is decided, and the test run is
+ # where the reports are collected and merged.
- name: Build
+ timeout-minutes: ${{ inputs.build_timeout_minutes }}
+ env:
+ TX_COVERAGE: ${{ inputs.skip_coverage && 'OFF' || 'ON' }}
run: ${{ inputs.build_script }}
- name: Test
+ timeout-minutes: 60
+ env:
+ TX_COVERAGE: ${{ inputs.skip_coverage && 'OFF' || 'ON' }}
run: ${{ inputs.test_script }}
- name: Publish Test Results
- uses: EnricoMi/[email protected]
+ uses: EnricoMi/publish-unit-test-result-action@d0a4676d0e0b938bc201470d88276b7c74c712b3 # v2.24.0
if: always()
with:
check_name: Test Results ${{ inputs.result_affix }}
@@ -86,7 +157,7 @@ jobs:
- name: Upload Test Results
if: success() || failure()
- uses: actions/[email protected]
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: test_reports ${{ inputs.result_affix }}
path: |
@@ -95,27 +166,48 @@ jobs:
${{ inputs.cmake_path }}/build/**/regression/output_files/*.bin
- name: Configure GitHub Pages
- uses: actions/[email protected]
+ uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
+ # The coverage steps below run even when a test failed. cmake_bootstrap.sh
+ # now produces the report in that case, and the run whose behaviour changed
+ # is the one whose coverage is worth reading; previously a single flaky test
+ # suppressed the report for the whole run. !cancelled() rather than always(),
+ # so a cancelled run still stops promptly -- the same idiom the
+ # deploy_code_coverage job below already uses. The ${{ }} is required: a bare
+ # ! opens a YAML tag, and the expression will not parse without it.
+ #
+ # fail_below_min turns the summary into a gate. Until now coverage could
+ # fall from any figure to any other and no check went red, against a
+ # project target of 100% -- a stated requirement measured with a gauge
+ # that could not fail.
+ #
+ # One thing the floor does not defend, and it is the likeliest way for
+ # coverage to break: an empty report reads as 100%. gcovr writes
+ # line-rate="1.0" beside lines-valid="0" when it finds no data, and the
+ # action reports 'Line Rate = 100% (0 / 0)' and passes any floor --
+ # probed on a runner, 26 Aug 2026. The check that catches that is the
+ # emptiness assertion in each suite's coverage.sh, not this one.
- name: Generate Code Coverage Results Summary
- if: (!inputs.skip_coverage)
- uses: irongut/[email protected]
+ if: ${{ !cancelled() && (!inputs.skip_coverage) }}
+ uses: irongut/CodeCoverageSummary@51cc3a756ddcd398d447c044c02cb6aa83fdae95 # v1.3.0
with:
filename: ${{ inputs.cmake_path }}/coverage_report/${{ inputs.coverage_name }}.xml
format: markdown
badge: true
hide_complexity: true
output: file
+ thresholds: ${{ inputs.coverage_thresholds }}
+ fail_below_min: true
- name: Write Code Coverage Summary
- if: (!inputs.skip_coverage)
+ if: ${{ !cancelled() && (!inputs.skip_coverage) }}
run: |
echo "## Coverage Report ${{ inputs.result_affix }}" >> $GITHUB_STEP_SUMMARY
cat code-coverage-results.md >> $GITHUB_STEP_SUMMARY
- name: Create CheckRun for Code Coverage
- if: ((github.event_name == 'push') || (github.event_name == 'workflow_dispatch') || (github.event.pull_request.head.repo.full_name == github.repository)) && (!inputs.skip_coverage)
- uses: LouisBrunner/[email protected]
+ if: ${{ !cancelled() && ((github.event_name == 'push') || (github.event_name == 'workflow_dispatch') || (github.event.pull_request.head.repo.full_name == github.repository)) && (!inputs.skip_coverage) }}
+ uses: LouisBrunner/checks-action@937cbbcde3259005b50746dc91cde29098aac2ff # v3.1.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
name: Code Coverage ${{ inputs.result_affix }}
@@ -125,15 +217,15 @@ jobs:
output_text_description_file: code-coverage-results.md
- name: Add Code Coverage PR Comment
- if: ((github.event_name == 'push') || (github.event.pull_request.head.repo.full_name == github.repository)) && (!inputs.skip_coverage)
- uses: marocchino/[email protected]
+ if: ${{ !cancelled() && ((github.event_name == 'push') || (github.event.pull_request.head.repo.full_name == github.repository)) && (!inputs.skip_coverage) }}
+ uses: marocchino/sticky-pull-request-comment@5770ad5eb8f42dd2c4f34da00c94c5381e49af88 # v3.0.5
with:
header: Code Coverage ${{ inputs.result_affix }}
path: code-coverage-results.md
# Add sudo to move coverage folder created by root user
- name: Prepare Coverage GitHub Pages
- if: (!inputs.skip_coverage)
+ if: ${{ !cancelled() && (!inputs.skip_coverage) }}
run: >-
if [ "${{ inputs.result_affix }}" != "" ] && ${{ inputs.skip_deploy }}; then
sudo mv ${{ inputs.cmake_path }}/coverage_report/${{ inputs.coverage_name }} \
@@ -142,25 +234,45 @@ jobs:
- name: Coverage Report name
id: artifact
- if: (!inputs.skip_coverage)
+ if: ${{ !cancelled() && (!inputs.skip_coverage) }}
run: echo "coverage_report=coverage_report-$(date +%s)" >> $GITHUB_OUTPUT
+ # per_configuration is excluded deliberately. deploy_code_coverage downloads
+ # every coverage_report-* artifact with merge-multiple, so whatever is in
+ # here lands on the published site -- and each suite's per-configuration
+ # directories carry the same names, so ThreadX's would overwrite SMP's.
+ # The top level therefore holds only the suite directory and the merged XML,
+ # which is the shape the deploy already expects.
- name: Upload Code Coverage Artifacts
- uses: actions/[email protected]
- if: (inputs.skip_deploy && !inputs.skip_coverage)
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ if: ${{ !cancelled() && (inputs.skip_deploy && !inputs.skip_coverage) }}
with:
name: ${{ steps.artifact.outputs.coverage_report }}
- path: ${{ inputs.cmake_path }}/coverage_report
+ path: |
+ ${{ inputs.cmake_path }}/coverage_report
+ !${{ inputs.cmake_path }}/coverage_report/per_configuration/**
+ retention-days: 1
+
+ # The per-configuration reports, kept separately so they are downloadable
+ # when the merged number moves and the question is which configuration moved
+ # it. The name deliberately does not match coverage_report-*, so the deploy
+ # job's pattern does not pick it up and it never reaches the published site.
+ - name: Upload Per-Configuration Coverage
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ if: ${{ !cancelled() && (!inputs.skip_coverage) }}
+ with:
+ name: coverage_detail ${{ inputs.result_affix }}
+ path: ${{ inputs.cmake_path }}/coverage_report/per_configuration
retention-days: 1
- name: Upload Code Coverage Pages
- uses: actions/[email protected]
- if: (!inputs.skip_deploy && !inputs.skip_coverage)
+ uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
+ if: ${{ !cancelled() && (!inputs.skip_deploy && !inputs.skip_coverage) }}
with:
path: ${{ inputs.cmake_path }}/coverage_report/${{ inputs.coverage_name }}
deploy_code_coverage:
- runs-on: ubuntu-latest
+ runs-on: ubuntu-24.04
if: ((github.event_name == 'push') || (github.event_name == 'workflow_dispatch')) && !inputs.skip_coverage && !inputs.skip_deploy && !failure() && !cancelled()
needs: run_tests
environment:
@@ -171,26 +283,61 @@ jobs:
id-token: write
steps:
- - uses: actions/[email protected]
+ # Selects the coverage artifacts by pattern rather than by name.
+ #
+ # This used to ask for ${{ steps.artifact.outputs.coverage_report }},
+ # which is set by the "Coverage Report name" step of run_tests -- a
+ # different job. The steps context does not cross jobs, so that
+ # expression evaluated to the empty string and the action fell back to
+ # its documented behaviour for an unspecified name: "No input name,
+ # artifact-ids or pattern filtered specified, downloading all
+ # artifacts". It pulled down four, the two coverage reports and the two
+ # test_reports bundles of JUnit XML, each into a directory named after
+ # the artifact -- so the published site carried the test reports as well
+ # as the coverage, under paths containing a run timestamp that changed
+ # on every publish.
+ #
+ # merge-multiple puts the contents of both coverage artifacts directly
+ # into coverage_report rather than under a directory named for each
+ # artifact. Each one holds a single directory named for its suite,
+ # ThreadX or SMP, renamed from merged by the "Prepare Coverage GitHub
+ # Pages" step, so the merge yields exactly the two suite directories the
+ # deploy expects, and the artifact name -- with its timestamp -- stops
+ # appearing in the published path at all.
+ #
+ # The two artifacts also each carry a merged.xml -- it was
+ # default_build_coverage.xml until #665 instrumented every configuration
+ # and unioned them -- and the merge means one overwrites the other.
+ # Verified on the runner: both hold merged.xml. That file is consumed by
+ # CodeCoverageSummary back in run_tests and is not read here, so this is
+ # untidy rather than wrong.
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
if: ${{ inputs.skip_test }}
with:
- name: ${{ steps.artifact.outputs.coverage_report }}
+ pattern: coverage_report-*
+ merge-multiple: true
path: ${{ inputs.cmake_path }}/coverage_report
- name: Upload Code Coverage Pages
- uses: actions/[email protected]
+ uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
if: ${{ inputs.skip_test }}
with:
path: .
+ # The artifacts are named coverage_report-<epoch> by run_tests, so the
+ # bare name matched nothing: useGlob defaults to true in this action, and
+ # as a glob "coverage_report" matches only the literal string. The step
+ # has therefore been deleting nothing. It does not fail on a miss, which
+ # is why that went unnoticed; retention-days: 1 on the upload is what has
+ # actually been clearing these up.
- name: Delete Duplicate Code Coverage Artifact
- uses: geekyeggo/[email protected]
+ uses: geekyeggo/delete-artifact@176a747ab7e287e3ff4787bf8a148716375ca118 # v6.0.0
with:
- name: coverage_report
+ name: coverage_report-*
- name: Deploy GitHub Pages site
id: deployment
- uses: actions/[email protected]
+ uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
- name: Write Code Coverage Report URL
run: >-
diff --git a/.github/workflows/regression_test.yml b/.github/workflows/regression_test.yml
index ee5519fd..7b5724ac 100644
--- a/.github/workflows/regression_test.yml
+++ b/.github/workflows/regression_test.yml
@@ -1,13 +1,24 @@
name: regression_test
-# Controls when the action will run. Triggers the workflow on push or pull request
-# events but only for the master branch
+# Runs the ThreadX, SMP and FreeRTOS-compatibility regression suites.
+#
+# dev is included as well as master, for the same reason ports_arch_check.yml
+# names it: dev is the integration branch, so a check that runs only against
+# master gates nothing that anybody opens. Until now these suites ran on no
+# pull request that anybody actually raised, and the last dev run of any kind
+# was a manual workflow_dispatch.
+#
+# That run, on 2026-08-18, failed two tests: threadx_timer_simple_test in the
+# ThreadX suite and threadx_thread_priority_change in the SMP one. Both are
+# fixed -- the first by running the suites one test at a time (#643), the
+# second by #647 -- and both suites now pass all 1030 tests across all ten
+# build configurations. The suites were never the problem; the trigger was.
on:
workflow_dispatch:
push:
- branches: [ master ]
+ branches: [ master, dev ]
pull_request:
- branches: [ master ]
+ branches: [ master, dev ]
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
@@ -26,6 +37,17 @@ jobs:
cmake_path: ./test/tx/cmake
result_affix: ThreadX
skip_deploy: true
+ # 99 is the tightest floor available below this suite's figure. The
+ # merged report reads 4503/4503 lines -- 100.00% -- on the dev push run
+ # of #666, and two local runs of the same green tree came in at 100.00%
+ # and 99.98%: tx_thread_system_resume.c:529 is reached by timing rather
+ # than by construction and flaps between runs, and #666 could not reach
+ # it deliberately. So a floor of 100 is a check that fails on a race --
+ # probed against the pinned action, 4502/4503 fails a floor of 100 and
+ # clears one of 99 -- and the thresholds are integers, so there is
+ # nothing between the two. Ratchet this to 100 if 529 is ever covered
+ # by construction.
+ coverage_thresholds: '99 100'
smp:
permissions:
contents: read
@@ -41,25 +63,88 @@ jobs:
cmake_path: ./test/smp/cmake
result_affix: SMP
skip_deploy: true
- # riscv: disabled — re-enable when RISC-V CI is ready
- # riscv:
- # permissions:
- # contents: read
- # issues: read
- # checks: write
- # pull-requests: write
- # pages: write
- # id-token: write
- # uses: ./.github/workflows/regression_template.yml
- # with:
- # install_script: ./scripts/install_riscv.sh
- # build_script: ./scripts/build_tx_riscv.sh
- # test_script: ./scripts/test_tx_riscv.sh
- # cmake_path: ./test/tx/cmake/riscv
- # result_affix: RISC-V
- # skip_deploy: true
- # skip_coverage: true
+ # Raised from 98 with the four tests that closed 53 of the 64 lines this
+ # comment used to list: the merged report goes from 5114/5178 -- 98.76% --
+ # to 5167/5178, 99.79%. A floor of 99 needs 5127, so there are 40 lines of
+ # headroom against a numerator measured flickering by two between runs.
+ #
+ # Still not 100, and not because the last eleven lines were skipped. Eight
+ # are tx_byte_pool_search.c's ownership delay loop, which needs another
+ # core to take the pool inside the window the search opens when it drops
+ # protection every twenty blocks; this port serialises all four cores on
+ # one pthread mutex and the thread that releases it wins the re-acquire
+ # every time -- measured over 180,003 windows, zero handovers. The other
+ # three are in tx_thread_smp_utilities.c: one is a range guard placed
+ # after the shift it is meant to guard, so reaching it needs undefined
+ # behaviour, and two are a priority-inheritance branch that needs the
+ # mutex owner genuinely executing on another core. See #677, which
+ # made these measurements and raised the floor.
+ coverage_thresholds: '99 100'
+ freertos:
+ permissions:
+ contents: read
+ issues: read
+ checks: write
+ pull-requests: write
+ pages: write
+ id-token: write
+ uses: ./.github/workflows/regression_template.yml
+ with:
+ build_script: ./scripts/build_freertos.sh
+ test_script: ./scripts/test_freertos.sh
+ cmake_path: ./test/freertos/cmake
+ result_affix: FreeRTOS
+ skip_deploy: true
+ # No coverage build configuration yet; the suite covers the creation
+ # paths of the layer rather than all of it.
+ skip_coverage: true
+ riscv:
+ permissions:
+ contents: read
+ issues: read
+ checks: write
+ pull-requests: write
+ pages: write
+ id-token: write
+ uses: ./.github/workflows/regression_template.yml
+ with:
+ install_script: ./scripts/install_riscv.sh
+ build_script: ./scripts/build_tx_riscv.sh
+ test_script: ./scripts/test_tx_riscv.sh
+ cmake_path: ./test/tx/cmake/riscv
+ result_affix: RISC-V
+ skip_deploy: true
+ # No coverage from this suite, and not for want of a build configuration.
+ # These tests are bare-metal images run under QEMU with -bios none, and
+ # test/tx/cmake/riscv/bsp/syscalls.c gives them _write to the UART, _exit
+ # through the sifive_test device, and stubs for _close, _fstat, _isatty,
+ # _lseek, _read and _sbrk -- but no _open. gcov emits a .gcda by opening
+ # a path, so instrumenting these builds would produce nothing to read.
+ #
+ # test/tx/cmake/riscv/CMakeLists.txt is its own top-level project and does
+ # not declare the TX_COVERAGE option that test/tx/cmake and test/smp/cmake
+ # do, so the OFF the template exports is inert here rather than the thing
+ # holding coverage off.
+ #
+ # Collecting it therefore needs a transport off the target -- gcov's dump
+ # routines over the UART the BSP already drives, or semihosting -- and not
+ # a default_build_coverage entry beside the other five configurations.
+ skip_coverage: true
deploy:
+ # Publishing the coverage report stays on master. Adding dev to this
+ # workflow's triggers was meant to run the suites on the branch the pull
+ # requests target, not to change what gets published -- and the first
+ # push to dev after that change failed here with 'Branch "dev" is not
+ # allowed to deploy to github-pages due to environment protection rules',
+ # while all three suites passed. The github-pages environment restricts
+ # deployments to master, so the job was rejected before it ran.
+ #
+ # Guarding here rather than relaxing the environment rule, because the
+ # rule is doing its job: it is a deliberate answer to the question of
+ # which branch the published report describes. Moving that answer to dev
+ # is a separate decision, and it needs the environment setting changed
+ # as well as this line removed.
+ if: github.ref == 'refs/heads/master'
permissions:
contents: read
issues: read
diff --git a/.github/workflows/repo_checks.yml b/.github/workflows/repo_checks.yml
new file mode 100644
index 00000000..955e6ddd
--- /dev/null
+++ b/.github/workflows/repo_checks.yml
@@ -0,0 +1,63 @@
+###############################################################################
+# 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
+###############################################################################
+
+name: repo_checks
+
+# Repository-wide text checks.
+#
+# THIS LIVES IN ITS OWN WORKFLOW BECAUSE IT MUST NOT BE PATH-FILTERED, and a
+# path filter is a property of a workflow rather than of a job. The check
+# selects its input with `git ls-files` and scans every tracked file, so any
+# file at all can carry a finding.
+#
+# That is not hypothetical here. The pass that introduced the fixed
+# disclosure line covered source files only, and the drift it was meant to end
+# survived for months afterwards in CMake files, toolchain files, shell and
+# PowerShell scripts, a GDB script and a Visual Studio manifest -- precisely
+# the files a source-path filter would have skipped. A workflow that gates no
+# pull request anybody opens is worse than no workflow, because it looks like
+# coverage.
+#
+# Cheap enough that running it on everything costs nothing worth measuring: it
+# is grep over a repository this size, with no toolchain, no build and no
+# cache.
+
+on:
+ # No `paths:` on either trigger, deliberately. See above.
+ push:
+ branches: [ dev, master ]
+ pull_request:
+ branches: [ dev, master ]
+
+# A second push to the same branch makes the first answer irrelevant.
+concurrency:
+ group: repo-checks-${{ github.ref }}
+ cancel-in-progress: true
+
+permissions:
+ contents: read
+
+jobs:
+ checks:
+ runs-on: ubuntu-24.04
+ steps:
+ # Actions are pinned to a commit SHA with the version in the trailing
+ # comment. A tag can be moved; a SHA cannot, which is what makes "which
+ # code ran in our CI" answerable from the repository.
+ - name: Check out the repository
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+
+ - name: Check the AI disclosure comments
+ run: scripts/check_ai_disclosure.sh