diff options
| author | Frédéric Desbiens <[email protected]> | 2026-10-02 10:42:44 -0400 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-10-02 10:42:44 -0400 |
| commit | 93387b0a60382a29579bbad5045c68328e3751df (patch) | |
| tree | 42be790d602cb76f55090e5a7c418df7ea2b3428 /.github | |
| parent | 44d7c95c582d415c4ad84527180b29c93c3bf664 (diff) | |
| parent | 28281cd5647d4671236ca08d19bd18ed15b2265a (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')
| -rw-r--r-- | .github/dependabot.yml | 91 | ||||
| -rw-r--r-- | .github/workflows/ci_cortex_m.yml | 110 | ||||
| -rw-r--r-- | .github/workflows/clang_check.yml | 74 | ||||
| -rw-r--r-- | .github/workflows/gcc_check.yml | 192 | ||||
| -rw-r--r-- | .github/workflows/ports_arch_check.yml | 79 | ||||
| -rw-r--r-- | .github/workflows/r52_fvp.yml | 335 | ||||
| -rw-r--r-- | .github/workflows/regression_template.yml | 201 | ||||
| -rw-r--r-- | .github/workflows/regression_test.yml | 129 | ||||
| -rw-r--r-- | .github/workflows/repo_checks.yml | 63 |
9 files changed, 1154 insertions, 120 deletions
diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..1091a2d6 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,91 @@ +version: 2 + +# Keeps the pinned action SHAs moving. +# +# Every action reference under .github/workflows is a 40-character commit SHA +# with the version in a trailing comment. A SHA pin *without* this file is worse +# than a floating tag: it freezes CI on whatever was current the day it was +# written, which is how actions/cache@v1 came to sit in ci_cortex_m.yml until +# GitHub started auto-failing every request that used it. Measured on +# 2026-08-24, before the catch-up: download-artifact was four majors behind, +# checkout and upload-artifact three each, cache and upload-pages-artifact two. +# Nothing had ever reported that, because there was no configuration here -- nor +# in netxduo, filex, guix or rtos-docs-asciidoc, so this file sets the pattern +# rather than following one. +# +# Dependabot understands the SHA form and rewrites the trailing version comment +# together with the pin, so the comment cannot drift away from the SHA it +# describes. That is what keeps "which exact code ran in our CI" answerable from +# the repository, which the SBOM and certification work needs on its own. +# +# Two things this does not fix. It reports drift, not silence: a workflow that +# never triggers rots unnoticed no matter what is pinned in it, and the trigger +# fixes in ci_cortex_m.yml and regression_test.yml are the cure for that. And it +# does nothing at all until this file reaches the default branch -- see the note +# on target-branch below. +# +# There is no entry for any other ecosystem, and that is a decision rather than +# an oversight: the project forbids external dependencies, there are no +# submodules, and the one pinned tool -- gcovr in scripts/install.sh -- lives in +# a shell script that no Dependabot ecosystem can parse. That pin moves by hand. +updates: + - package-ecosystem: "github-actions" + # "/" is the only accepted value for this ecosystem; it covers + # .github/workflows and .github/actions. The five reusable-workflow + # references in regression_test.yml are local paths + # (./.github/workflows/regression_template.yml) and are correctly left + # alone -- they carry no version to move. + directory: "/" + + schedule: + interval: "weekly" + day: "monday" + time: "06:00" + timezone: "Etc/UTC" + + # Dependabot reads this file from the repository's DEFAULT branch, which is + # master. But master is deliberately kept behind dev, and pull requests + # belong on dev, where the regression suites gate them. target-branch sends + # the pull requests to dev and makes Dependabot read the workflows it is + # updating from dev as well. + # + # The consequence to plan for: landing this file on dev arms it, it does not + # fire it. Nothing happens until a release merge carries it to master. + # + # Setting target-branch also opts out of Dependabot *security* updates, + # which only ever run against the default branch. For this ecosystem the + # cost is small -- an action advisory arrives as an ordinary version bump on + # the weekly run -- but it is a real trade and not a detail to rediscover + # later. + target-branch: "dev" + + groups: + # Patch and minor arrive together in one pull request: they are the + # routine traffic, and reviewing them one at a time is how an update queue + # starts being ignored, which is the failure mode this file exists to + # prevent. Majors stay ungrouped, one pull request each, because every + # breaking change this repository has met in an action set has been a + # major -- download-artifact v8 defaulting digest-mismatch to error, and + # upload-artifact v6 requiring runner 2.327.1 or newer, are both from the + # last catch-up. + actions-minor-and-patch: + patterns: + - "*" + update-types: + - "minor" + - "patch" + + # Nine distinct third-party and first-party actions are in use. Dependabot's + # default limit of five would hold majors back with nothing saying that it + # had; ten leaves room for a wave without becoming a silent cap. + open-pull-requests-limit: 10 + + labels: + - "dependencies" + + # Reviewers are not listed here. .github/CODEOWNERS already routes every + # path to @eclipse-threadx/admins and Dependabot honours it. + # + # Commit subjects are left at Dependabot's own "Bump x from a to b" wording. + # The project asks for a past-tense subject and still gets one: these pull + # requests are squash-merged, and the subject is set at that point. 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 |
