summaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'scripts')
-rw-r--r--scripts/build_smp.ps154
-rwxr-xr-xscripts/build_smp.sh11
-rw-r--r--scripts/build_tx.ps158
-rwxr-xr-xscripts/build_tx.sh11
-rwxr-xr-xscripts/build_tx_riscv.sh26
-rwxr-xr-xscripts/cmake_bootstrap.sh11
-rwxr-xr-xscripts/copy_armv7_m.sh11
-rwxr-xr-xscripts/copy_armv8_m.sh11
-rwxr-xr-xscripts/copy_module_armv7_m.sh11
-rwxr-xr-xscripts/install.sh11
-rwxr-xr-xscripts/install_riscv.sh43
-rwxr-xr-xscripts/prepare_release.sh172
-rwxr-xr-xscripts/sdl_check.sh11
-rw-r--r--scripts/test_smp.ps1120
-rwxr-xr-xscripts/test_smp.sh11
-rw-r--r--scripts/test_tx.ps1121
-rwxr-xr-xscripts/test_tx.sh11
-rwxr-xr-xscripts/test_tx_riscv.sh30
-rw-r--r--scripts/tx_windows_common.ps1941
19 files changed, 1675 insertions, 0 deletions
diff --git a/scripts/build_smp.ps1 b/scripts/build_smp.ps1
new file mode 100644
index 00000000..c66a2beb
--- /dev/null
+++ b/scripts/build_smp.ps1
@@ -0,0 +1,54 @@
+[CmdletBinding()]
+param(
+ [AllowNull()]
+ [object]$Configuration = 'all',
+
+ [int]$Parallel = [Math]::Max(1, [Environment]::ProcessorCount),
+
+ [int]$BuildTimeoutSeconds = 180,
+
+ [string]$BuildDir,
+
+ [switch]$Clean
+)
+
+$ErrorActionPreference = 'Stop'
+. (Join-Path $PSScriptRoot 'tx_windows_common.ps1')
+
+$repoRoot = Split-Path -Parent $PSScriptRoot
+
+if (-not $BuildDir) {
+ $BuildDir = Join-Path $repoRoot 'build\tests\win64_smp'
+}
+
+$selectedConfigurations = Resolve-RegressionConfigurations -RequestedConfigurations $Configuration
+Write-Host "Selected configurations: $($selectedConfigurations -join ', ')"
+
+Enter-VisualStudioDevShell -VsArch 'amd64'
+
+foreach ($currentConfiguration in $selectedConfigurations) {
+ $currentBuildDirName = Get-RegressionBuildDirectoryName -ConfigurationName $currentConfiguration
+ $currentBuildDir = Join-Path $BuildDir $currentBuildDirName
+
+ if ($Clean) {
+ Remove-BuildDirectory -Path $currentBuildDir -RepoRoot $repoRoot
+ }
+
+ Remove-NinjaLock -Path $currentBuildDir
+
+ Write-Host "Configuring win64_smp / $currentConfiguration"
+ Invoke-NativeCommand -FilePath 'cmake' -Arguments @(
+ '-S', (Join-Path $repoRoot 'test\smp\cmake'),
+ '-B', $currentBuildDir,
+ '-G', 'Ninja',
+ '-DCMAKE_C_COMPILER_FORCED=TRUE',
+ '-DCMAKE_C_COMPILER_WORKS=TRUE',
+ '-DCMAKE_C_ABI_COMPILED=TRUE',
+ "-DCMAKE_BUILD_TYPE=$currentConfiguration",
+ '-DTHREADX_ARCH=win64',
+ '-DTHREADX_TOOLCHAIN=vs_2022'
+ )
+
+ Write-Host "Building win64_smp / $currentConfiguration"
+ Invoke-CMakeBuild -BuildDir $currentBuildDir -Parallel $Parallel -TimeoutSeconds $BuildTimeoutSeconds
+}
diff --git a/scripts/build_smp.sh b/scripts/build_smp.sh
index 615a9be8..4c8f3b63 100755
--- a/scripts/build_smp.sh
+++ b/scripts/build_smp.sh
@@ -1,2 +1,13 @@
#!/bin/bash
+##############################################################################
+# Copyright (c) 2024 Microsoft Corporation
+# 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.
+#
+# SPDX-License-Identifier: MIT
+##############################################################################
+
$(dirname `realpath $0`)/../test/smp/cmake/run.sh build all
diff --git a/scripts/build_tx.ps1 b/scripts/build_tx.ps1
new file mode 100644
index 00000000..eeaa2222
--- /dev/null
+++ b/scripts/build_tx.ps1
@@ -0,0 +1,58 @@
+[CmdletBinding()]
+param(
+ [ValidateSet('win64', 'win32')]
+ [string]$Arch = 'win64',
+
+ [AllowNull()]
+ [object]$Configuration = 'all',
+
+ [int]$Parallel = [Math]::Max(1, [Environment]::ProcessorCount),
+
+ [int]$BuildTimeoutSeconds = 60,
+
+ [string]$BuildDir,
+
+ [switch]$Clean
+)
+
+$ErrorActionPreference = 'Stop'
+. (Join-Path $PSScriptRoot 'tx_windows_common.ps1')
+
+$repoRoot = Split-Path -Parent $PSScriptRoot
+$settings = Get-PortSettings -SelectedArch $Arch
+
+if (-not $BuildDir) {
+ $BuildDir = Join-Path $repoRoot "build\tests\$Arch"
+}
+
+$selectedConfigurations = Resolve-RegressionConfigurations -RequestedConfigurations $Configuration
+Write-Host "Selected configurations: $($selectedConfigurations -join ', ')"
+
+Enter-VisualStudioDevShell -VsArch $settings.VsArch
+
+foreach ($currentConfiguration in $selectedConfigurations) {
+ $currentBuildDirName = Get-RegressionBuildDirectoryName -ConfigurationName $currentConfiguration
+ $currentBuildDir = Join-Path $BuildDir $currentBuildDirName
+
+ if ($Clean) {
+ Remove-BuildDirectory -Path $currentBuildDir -RepoRoot $repoRoot
+ }
+
+ Remove-NinjaLock -Path $currentBuildDir
+
+ Write-Host "Configuring $Arch / $currentConfiguration"
+ Invoke-NativeCommand -FilePath 'cmake' -Arguments @(
+ '-S', (Join-Path $repoRoot 'test\tx\cmake'),
+ '-B', $currentBuildDir,
+ '-G', 'Ninja',
+ '-DCMAKE_C_COMPILER_FORCED=TRUE',
+ '-DCMAKE_C_COMPILER_WORKS=TRUE',
+ '-DCMAKE_C_ABI_COMPILED=TRUE',
+ "-DCMAKE_BUILD_TYPE=$currentConfiguration",
+ "-DTHREADX_ARCH=$($settings.ThreadXArch)",
+ "-DTHREADX_TOOLCHAIN=$($settings.ThreadXToolchain)"
+ )
+
+ Write-Host "Building $Arch / $currentConfiguration"
+ Invoke-CMakeBuild -BuildDir $currentBuildDir -Parallel $Parallel -TimeoutSeconds $BuildTimeoutSeconds
+}
diff --git a/scripts/build_tx.sh b/scripts/build_tx.sh
index a1773a1e..a904f909 100755
--- a/scripts/build_tx.sh
+++ b/scripts/build_tx.sh
@@ -1,2 +1,13 @@
#!/bin/bash
+##############################################################################
+# Copyright (c) 2024 Microsoft Corporation
+# 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.
+#
+# SPDX-License-Identifier: MIT
+##############################################################################
+
$(dirname `realpath $0`)/../test/tx/cmake/run.sh build all
diff --git a/scripts/build_tx_riscv.sh b/scripts/build_tx_riscv.sh
new file mode 100755
index 00000000..930bb9a3
--- /dev/null
+++ b/scripts/build_tx_riscv.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+##############################################################################
+# Copyright (c) 2024 Microsoft Corporation
+# 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.
+#
+# SPDX-License-Identifier: MIT
+##############################################################################
+
+# Build RISC-V regression tests for both RV32 and RV64.
+# Usage: build_tx_riscv.sh [all|<config>]
+
+SCRIPT_DIR="$(dirname "$(realpath "$0")")"
+RUN_SH="${SCRIPT_DIR}/../test/tx/cmake/riscv/run.sh"
+
+ARGS="${@:-all}"
+
+echo "=== Building RISC-V32 ==="
+"$RUN_SH" riscv32 build $ARGS
+
+echo ""
+echo "=== Building RISC-V64 ==="
+"$RUN_SH" riscv64 build $ARGS
diff --git a/scripts/cmake_bootstrap.sh b/scripts/cmake_bootstrap.sh
index b7b314fa..d66d668d 100755
--- a/scripts/cmake_bootstrap.sh
+++ b/scripts/cmake_bootstrap.sh
@@ -1,4 +1,15 @@
#!/bin/bash
+##############################################################################
+# Copyright (c) 2024 Microsoft Corporation
+# 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.
+#
+# SPDX-License-Identifier: MIT
+##############################################################################
+
set -e
diff --git a/scripts/copy_armv7_m.sh b/scripts/copy_armv7_m.sh
index 825979a2..e04ec2a7 100755
--- a/scripts/copy_armv7_m.sh
+++ b/scripts/copy_armv7_m.sh
@@ -1,4 +1,15 @@
#!/bin/bash
+##############################################################################
+# Copyright (c) 2024 Microsoft Corporation
+# 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.
+#
+# SPDX-License-Identifier: MIT
+##############################################################################
+
# There is only one tx_port.h file that covers three architectures: M3/M4/M7 and four tools: ac5/ac6/gnu/iar.
# This file is in threadx/ports/armv7-m/inc. We are going to ignore GHS for now, but I’d like to get GHS unified as well.
diff --git a/scripts/copy_armv8_m.sh b/scripts/copy_armv8_m.sh
index 0963ecbe..c66cc11e 100755
--- a/scripts/copy_armv8_m.sh
+++ b/scripts/copy_armv8_m.sh
@@ -1,4 +1,15 @@
#!/bin/bash
+##############################################################################
+# Copyright (c) 2024 Microsoft Corporation
+# 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.
+#
+# SPDX-License-Identifier: MIT
+##############################################################################
+
# There are two files tx_port.h and tx_secure_interface.h that cover three architectures: M33/M55/M85 and three tools: ac6/gnu/iar.
# These files are in threadx/ports/armv8-m/inc.
diff --git a/scripts/copy_module_armv7_m.sh b/scripts/copy_module_armv7_m.sh
index a0ddcd27..c2d19094 100755
--- a/scripts/copy_module_armv7_m.sh
+++ b/scripts/copy_module_armv7_m.sh
@@ -1,4 +1,15 @@
#!/bin/bash
+##############################################################################
+# Copyright (c) 2024 Microsoft Corporation
+# 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.
+#
+# SPDX-License-Identifier: MIT
+##############################################################################
+
# There is only one tx_port.h file that covers three architectures: M3/M4/M7 and four tools: ac5/ac6/gnu/iar.
# This file is in threadx/ports_module/armv7-m/inc. We are going to ignore GHS.
diff --git a/scripts/install.sh b/scripts/install.sh
index 752a03b4..bc868477 100755
--- a/scripts/install.sh
+++ b/scripts/install.sh
@@ -1,4 +1,15 @@
#!/bin/bash
+##############################################################################
+# Copyright (c) 2024 Microsoft Corporation
+# 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.
+#
+# SPDX-License-Identifier: MIT
+##############################################################################
+
#
# Install necessary softwares for Ubuntu.
diff --git a/scripts/install_riscv.sh b/scripts/install_riscv.sh
new file mode 100755
index 00000000..41642e70
--- /dev/null
+++ b/scripts/install_riscv.sh
@@ -0,0 +1,43 @@
+#!/bin/bash
+##############################################################################
+# Copyright (c) 2024 Microsoft Corporation
+# 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.
+#
+# SPDX-License-Identifier: MIT
+##############################################################################
+
+# Install RISC-V bare-metal cross-compiler toolchain and QEMU for CI.
+set -e
+
+RELEASE_TAG="2026.04.26"
+BASE_URL="https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/${RELEASE_TAG}"
+# Use ubuntu-24.04 binaries to match ubuntu-latest runners.
+RV32_TARBALL="riscv32-elf-ubuntu-24.04-gcc.tar.xz"
+RV64_TARBALL="riscv64-elf-ubuntu-24.04-gcc.tar.xz"
+
+echo "=== Installing QEMU and build tools ==="
+sudo apt-get update -qq
+sudo apt-get install -y -qq qemu-system-misc ninja-build cmake
+
+echo "=== Downloading RISC-V GCC toolchain (${RELEASE_TAG}) ==="
+
+# Both tarballs extract into riscv/ with non-overlapping prefixes
+# (riscv32-unknown-elf-* and riscv64-unknown-elf-*).
+for tarball in "$RV32_TARBALL" "$RV64_TARBALL"; do
+ echo "Downloading ${tarball} ..."
+ wget --no-verbose "${BASE_URL}/${tarball}" -O "/tmp/${tarball}"
+ sudo tar xJf "/tmp/${tarball}" -C /opt
+ rm "/tmp/${tarball}"
+done
+
+TOOLCHAIN_BIN=/opt/riscv/bin
+echo "$TOOLCHAIN_BIN" >> "$GITHUB_PATH"
+
+echo "=== Verifying installation ==="
+"$TOOLCHAIN_BIN/riscv32-unknown-elf-gcc" --version | head -1
+"$TOOLCHAIN_BIN/riscv64-unknown-elf-gcc" --version | head -1
+qemu-system-riscv64 --version | head -1
diff --git a/scripts/prepare_release.sh b/scripts/prepare_release.sh
new file mode 100755
index 00000000..c33fa8f8
--- /dev/null
+++ b/scripts/prepare_release.sh
@@ -0,0 +1,172 @@
+#!/usr/bin/env bash
+# prepare_release.sh
+# Prepares a ThreadX release by updating version constants and port version strings.
+#
+# Usage: prepare_release.sh <version>
+# Example: prepare_release.sh 6.5.1.202602
+# Hotfix: prepare_release.sh 6.5.1.202602a
+#
+# This script:
+# 1. Creates branch release-<version>-preparation from dev
+# 2. Updates version constants in common/inc/tx_api.h
+# (commit: "Updated version number constants")
+# 3. Updates port version strings in all tx_port.h files
+# (commit: "Updated port version strings")
+#
+# Copyright (C) 2026 Eclipse ThreadX contributors
+# SPDX-License-Identifier: MIT
+
+set -eu
+
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
+
+API_HEADER="${REPO_ROOT}/common/inc/tx_api.h"
+PORT_HEADER_NAME="tx_port.h"
+PORT_DIRS="ports ports_smp ports_arch ports_module"
+
+# --------------------------------------------------------------------------
+# Argument validation
+# --------------------------------------------------------------------------
+if [ "$#" -ne 1 ]; then
+ printf "Usage: %s <version>\n" "$(basename "$0")" >&2
+ printf "Example: %s 6.5.1.202602\n" "$(basename "$0")" >&2
+ exit 1
+fi
+
+VERSION="$1"
+
+if ! printf "%s" "${VERSION}" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+[a-z]?$'; then
+ printf "Error: Invalid version format '%s'.\n" "${VERSION}" >&2
+ printf "Expected: MAJOR.MINOR.PATCH.BUILD[hotfix_letter]\n" >&2
+ exit 1
+fi
+
+# --------------------------------------------------------------------------
+# Parse version components
+# --------------------------------------------------------------------------
+MAJOR=$(printf "%s" "${VERSION}" | cut -d. -f1)
+MINOR=$(printf "%s" "${VERSION}" | cut -d. -f2)
+PATCH=$(printf "%s" "${VERSION}" | cut -d. -f3)
+BUILD_AND_HOTFIX=$(printf "%s" "${VERSION}" | cut -d. -f4)
+BUILD=$(printf "%s" "${BUILD_AND_HOTFIX}" | sed -E 's/[a-z]+$//')
+HOTFIX=$(printf "%s" "${BUILD_AND_HOTFIX}" | sed -E 's/^[0-9]+//')
+
+if [ -z "${HOTFIX}" ]; then
+ HOTFIX_DEFINE="' '"
+else
+ HOTFIX_DEFINE="'${HOTFIX}'"
+fi
+
+# --------------------------------------------------------------------------
+# Read and display current version
+# --------------------------------------------------------------------------
+CURR_MAJOR=$(grep -E "^#define THREADX_MAJOR_VERSION" "${API_HEADER}" | awk '{print $NF}')
+CURR_MINOR=$(grep -E "^#define THREADX_MINOR_VERSION" "${API_HEADER}" | awk '{print $NF}')
+CURR_PATCH=$(grep -E "^#define THREADX_PATCH_VERSION" "${API_HEADER}" | awk '{print $NF}')
+CURR_BUILD=$(grep -E "^#define THREADX_BUILD_VERSION" "${API_HEADER}" | awk '{print $NF}')
+CURR_HOTFIX=$(grep -E "^#define THREADX_HOTFIX_VERSION" "${API_HEADER}" | \
+ sed -E "s/.*'([^']*)'.*/\1/" | tr -d ' ')
+
+if [ -z "${CURR_HOTFIX}" ]; then
+ CURR_VER="${CURR_MAJOR}.${CURR_MINOR}.${CURR_PATCH}.${CURR_BUILD}"
+else
+ CURR_VER="${CURR_MAJOR}.${CURR_MINOR}.${CURR_PATCH}.${CURR_BUILD}${CURR_HOTFIX}"
+fi
+
+printf "\nThreadX release preparation\n"
+printf " Repository : %s\n" "${REPO_ROOT}"
+printf " Current version : %s\n" "${CURR_VER}"
+printf " Target version : %s\n\n" "${VERSION}"
+printf "Proceed with update? [y/N] "
+read -r CONFIRM
+case "${CONFIRM}" in
+ y|Y) ;;
+ *)
+ printf "Aborted.\n"
+ exit 0
+ ;;
+esac
+
+# --------------------------------------------------------------------------
+# Pre-flight checks
+# --------------------------------------------------------------------------
+if ! git -C "${REPO_ROOT}" diff --quiet HEAD 2>/dev/null; then
+ printf "Error: Working tree has uncommitted changes. Commit or stash first.\n" >&2
+ exit 1
+fi
+
+# --------------------------------------------------------------------------
+# Create feature branch from dev
+# --------------------------------------------------------------------------
+BRANCH_NAME="release-${VERSION}-preparation"
+printf "\nChecking out dev and pulling latest changes...\n"
+git -C "${REPO_ROOT}" checkout dev
+git -C "${REPO_ROOT}" pull origin dev
+printf "Creating branch '%s'...\n" "${BRANCH_NAME}"
+git -C "${REPO_ROOT}" checkout -b "${BRANCH_NAME}"
+
+# --------------------------------------------------------------------------
+# Update version constants
+# --------------------------------------------------------------------------
+printf "\nUpdating version constants in %s...\n" "${API_HEADER}"
+
+sed -i -E "s|(#define THREADX_MAJOR_VERSION[[:space:]]+)[0-9]+|\1${MAJOR}|" "${API_HEADER}"
+sed -i -E "s|(#define THREADX_MINOR_VERSION[[:space:]]+)[0-9]+|\1${MINOR}|" "${API_HEADER}"
+sed -i -E "s|(#define THREADX_PATCH_VERSION[[:space:]]+)[0-9]+|\1${PATCH}|" "${API_HEADER}"
+sed -i -E "s|(#define THREADX_BUILD_VERSION[[:space:]]+)[0-9]+|\1${BUILD}|" "${API_HEADER}"
+sed -i -E "s|(#define THREADX_HOTFIX_VERSION[[:space:]]+)'[^']*'|\1${HOTFIX_DEFINE}|" "${API_HEADER}"
+
+git -C "${REPO_ROOT}" add "${API_HEADER}"
+git -C "${REPO_ROOT}" commit -F - <<'COMMIT_EOF'
+Updated version number constants
+
+Co-authored-by: Copilot <[email protected]>
+COMMIT_EOF
+
+printf "Committed version constant updates.\n"
+
+# --------------------------------------------------------------------------
+# Update port version strings
+# --------------------------------------------------------------------------
+printf "\nUpdating port version strings...\n"
+
+PORT_FILES=""
+for dir in ${PORT_DIRS}; do
+ if [ -d "${REPO_ROOT}/${dir}" ]; then
+ found=$(find "${REPO_ROOT}/${dir}" -name "${PORT_HEADER_NAME}" 2>/dev/null | sort)
+ if [ -n "${found}" ]; then
+ PORT_FILES="${PORT_FILES}${found}
+"
+ fi
+ fi
+done
+PORT_FILES=$(printf "%s" "${PORT_FILES}" | grep -v '^[[:space:]]*$' || true)
+
+if [ -z "${PORT_FILES}" ]; then
+ printf "Warning: No port header files found. Skipping port version string commit.\n"
+else
+ while IFS= read -r port_file; do
+ if [ -n "${port_file}" ] && grep -qE "Version [0-9]" "${port_file}" 2>/dev/null; then
+ sed -i -E "s/Version [0-9]+\.[0-9]+\.[0-9]+\.[0-9]+[a-z]*/Version ${VERSION}/g" "${port_file}"
+ printf " Updated: %s\n" "${port_file#${REPO_ROOT}/}"
+ fi
+ done <<EOF
+${PORT_FILES}
+EOF
+
+ git -C "${REPO_ROOT}" add -u
+ if git -C "${REPO_ROOT}" diff --cached --quiet; then
+ printf "No port version string changes staged. Skipping commit.\n"
+ else
+ git -C "${REPO_ROOT}" commit -F - <<'COMMIT_EOF'
+Updated port version strings
+
+Co-authored-by: Copilot <[email protected]>
+COMMIT_EOF
+ printf "Committed port version string updates.\n"
+ fi
+fi
+
+printf "\nRelease preparation complete.\n"
+printf "Branch '%s' is ready for review.\n" "${BRANCH_NAME}"
diff --git a/scripts/sdl_check.sh b/scripts/sdl_check.sh
index 77eeb5ce..4bcd888a 100755
--- a/scripts/sdl_check.sh
+++ b/scripts/sdl_check.sh
@@ -1,3 +1,14 @@
+##############################################################################
+# Copyright (c) 2024 Microsoft Corporation
+# 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.
+#
+# SPDX-License-Identifier: MIT
+##############################################################################
+
# !/bin/bash
dir_list="common common_smp common_modules ports ports_module ports_smp samples"
exclude_list="-path TX"
diff --git a/scripts/test_smp.ps1 b/scripts/test_smp.ps1
new file mode 100644
index 00000000..e71b1081
--- /dev/null
+++ b/scripts/test_smp.ps1
@@ -0,0 +1,120 @@
+[CmdletBinding()]
+param(
+ [AllowNull()]
+ [object]$Configuration = 'all',
+
+ [int]$Parallel = 1,
+
+ [int]$RepeatFailCount = 2,
+
+ [int]$TestTimeoutSeconds = 45,
+
+ [switch]$CollectFailureDiagnostics = $true,
+
+ [string]$TestRegex,
+
+ [switch]$RerunFailedOnly,
+
+ [string]$BuildDir,
+
+ [switch]$Clean
+)
+
+$ErrorActionPreference = 'Stop'
+. (Join-Path $PSScriptRoot 'tx_windows_common.ps1')
+
+$repoRoot = Split-Path -Parent $PSScriptRoot
+
+if (-not $BuildDir) {
+ $BuildDir = Join-Path $repoRoot 'build\tests\win64_smp'
+}
+
+$selectedConfigurations = Resolve-RegressionConfigurations -RequestedConfigurations $Configuration
+Write-Host "Selected configurations: $($selectedConfigurations -join ', ')"
+
+Enter-VisualStudioDevShell -VsArch 'amd64'
+
+if ($Parallel -ne 1) {
+ Write-Warning 'Windows SMP simulator regression tests are timing-sensitive. Forcing -Parallel 1.'
+ $Parallel = 1
+}
+
+if ($TestRegex -and -not $PSBoundParameters.ContainsKey('TestTimeoutSeconds')) {
+ $TestTimeoutSeconds = 60
+ Write-Host "Targeted run detected; using per-test timeout of $TestTimeoutSeconds seconds."
+}
+
+$failedConfigurations = @()
+
+foreach ($currentConfiguration in $selectedConfigurations) {
+ $currentBuildDirName = Get-RegressionBuildDirectoryName -ConfigurationName $currentConfiguration
+ $currentBuildDir = Join-Path $BuildDir $currentBuildDirName
+ $currentTestingTemporaryDir = Join-Path $currentBuildDir 'Testing\Temporary'
+
+ try {
+ if ($Clean) {
+ $currentTestingDir = Join-Path $currentBuildDir 'Testing'
+ Remove-CtestTestingDirectory -Path $currentTestingDir
+ }
+
+ if (-not (Test-Path -LiteralPath $currentBuildDir)) {
+ throw "Build directory does not exist for win64_smp / ${currentConfiguration}: $currentBuildDir"
+ }
+
+ Remove-NinjaLock -Path $currentBuildDir
+ if (Test-Path -LiteralPath $currentTestingTemporaryDir) {
+ Remove-Item -LiteralPath (Join-Path $currentTestingTemporaryDir 'LastTest.log') -Force -ErrorAction SilentlyContinue
+ Remove-Item -LiteralPath (Join-Path $currentTestingTemporaryDir 'LastTestsFailed.log') -Force -ErrorAction SilentlyContinue
+ }
+
+ Write-Host "Testing win64_smp / $currentConfiguration"
+ $ctestArguments = @(
+ '--test-dir', $currentBuildDir,
+ '--output-on-failure',
+ '--timeout', $TestTimeoutSeconds.ToString(),
+ '-j', $Parallel.ToString()
+ )
+
+ if ($RepeatFailCount -gt 1) {
+ $ctestArguments += @('--repeat', "until-pass:$RepeatFailCount")
+ }
+
+ if ($TestRegex) {
+ $ctestArguments += @('-R', $TestRegex)
+ }
+
+ if ($RerunFailedOnly) {
+ $ctestArguments += '--rerun-failed'
+ }
+
+ Invoke-NativeCommand -FilePath 'ctest' -Arguments $ctestArguments
+ }
+ catch {
+ if ($CollectFailureDiagnostics -and (Test-Path -LiteralPath $currentBuildDir)) {
+ try {
+ Invoke-CtestFailureDiagnostics -BuildDir $currentBuildDir -TestingTemporaryDir $currentTestingTemporaryDir `
+ -TimeoutSeconds $TestTimeoutSeconds
+ }
+ catch {
+ Write-Warning "Failure diagnostics collection failed for ${currentConfiguration}: $($_.Exception.Message)"
+ }
+ }
+
+ $failedConfigurations += @{
+ Configuration = $currentConfiguration
+ Message = $_.Exception.Message
+ }
+
+ Write-Warning "Configuration failed: $currentConfiguration"
+ }
+}
+
+if ($failedConfigurations.Count -gt 0) {
+ Write-Host ''
+ Write-Host 'Configuration failure summary:'
+ foreach ($failedConfiguration in $failedConfigurations) {
+ Write-Host "- $($failedConfiguration.Configuration): $($failedConfiguration.Message)"
+ }
+
+ throw "One or more configurations failed: $($failedConfigurations.Configuration -join ', ')"
+}
diff --git a/scripts/test_smp.sh b/scripts/test_smp.sh
index 135f0847..72be13c0 100755
--- a/scripts/test_smp.sh
+++ b/scripts/test_smp.sh
@@ -1,3 +1,14 @@
#!/bin/bash
+##############################################################################
+# Copyright (c) 2024 Microsoft Corporation
+# 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.
+#
+# SPDX-License-Identifier: MIT
+##############################################################################
+
CTEST_PARALLEL_LEVEL=4 $(dirname `realpath $0`)/../test/smp/cmake/run.sh test all
diff --git a/scripts/test_tx.ps1 b/scripts/test_tx.ps1
new file mode 100644
index 00000000..a5607ed7
--- /dev/null
+++ b/scripts/test_tx.ps1
@@ -0,0 +1,121 @@
+[CmdletBinding()]
+param(
+ [ValidateSet('win64', 'win32')]
+ [string]$Arch = 'win64',
+
+ [AllowNull()]
+ [object]$Configuration = 'all',
+
+ [int]$Parallel = [Math]::Max(1, [Environment]::ProcessorCount),
+
+ [int]$RepeatFailCount = 1,
+
+ [int]$TestTimeoutSeconds = 20,
+
+ [switch]$CollectFailureDiagnostics = $true,
+
+ [string]$TestRegex,
+
+ [switch]$RerunFailedOnly,
+
+ [string]$BuildDir,
+
+ [switch]$Clean
+)
+
+$ErrorActionPreference = 'Stop'
+. (Join-Path $PSScriptRoot 'tx_windows_common.ps1')
+
+$repoRoot = Split-Path -Parent $PSScriptRoot
+$settings = Get-PortSettings -SelectedArch $Arch
+
+if (-not $BuildDir) {
+ $BuildDir = Join-Path $repoRoot "build\tests\$Arch"
+}
+
+$selectedConfigurations = Resolve-RegressionConfigurations -RequestedConfigurations $Configuration
+Write-Host "Selected configurations: $($selectedConfigurations -join ', ')"
+
+Enter-VisualStudioDevShell -VsArch $settings.VsArch
+
+if (($settings.ThreadXArch -eq 'win32') -or ($settings.ThreadXArch -eq 'win64')) {
+ if ($Parallel -ne 1) {
+ Write-Warning "Windows simulator regression tests are timing-sensitive under concurrent ctest execution. Forcing -Parallel 1."
+ $Parallel = 1
+ }
+}
+
+$failedConfigurations = @()
+
+foreach ($currentConfiguration in $selectedConfigurations) {
+ $currentBuildDirName = Get-RegressionBuildDirectoryName -ConfigurationName $currentConfiguration
+ $currentBuildDir = Join-Path $BuildDir $currentBuildDirName
+ $currentTestingTemporaryDir = Join-Path $currentBuildDir 'Testing\Temporary'
+
+ try {
+ if ($Clean) {
+ $currentTestingDir = Join-Path $currentBuildDir 'Testing'
+ Remove-CtestTestingDirectory -Path $currentTestingDir
+ }
+
+ if (-not (Test-Path -LiteralPath $currentBuildDir)) {
+ throw "Build directory does not exist for $Arch / ${currentConfiguration}: $currentBuildDir"
+ }
+
+ Remove-NinjaLock -Path $currentBuildDir
+ if (Test-Path -LiteralPath $currentTestingTemporaryDir) {
+ Remove-Item -LiteralPath (Join-Path $currentTestingTemporaryDir 'LastTest.log') -Force -ErrorAction SilentlyContinue
+ Remove-Item -LiteralPath (Join-Path $currentTestingTemporaryDir 'LastTestsFailed.log') -Force -ErrorAction SilentlyContinue
+ }
+
+ Write-Host "Testing $Arch / $currentConfiguration"
+ $ctestArguments = @(
+ '--test-dir', $currentBuildDir,
+ '--output-on-failure',
+ '--timeout', $TestTimeoutSeconds.ToString(),
+ '-j', $Parallel.ToString()
+ )
+
+ if ($RepeatFailCount -gt 1) {
+ $ctestArguments += @('--repeat', "until-pass:$RepeatFailCount")
+ }
+
+ if ($TestRegex) {
+ $ctestArguments += @('-R', $TestRegex)
+ }
+
+ if ($RerunFailedOnly) {
+ $ctestArguments += '--rerun-failed'
+ }
+
+ Invoke-NativeCommand -FilePath 'ctest' -Arguments $ctestArguments
+ }
+ catch {
+ if ($CollectFailureDiagnostics -and (Test-Path -LiteralPath $currentBuildDir)) {
+ try {
+ Invoke-CtestFailureDiagnostics -BuildDir $currentBuildDir -TestingTemporaryDir $currentTestingTemporaryDir `
+ -TimeoutSeconds $TestTimeoutSeconds
+ }
+ catch {
+ Write-Warning "Failure diagnostics collection failed for ${currentConfiguration}: $($_.Exception.Message)"
+ }
+ }
+
+ $failedConfigurations += @{
+ Configuration = $currentConfiguration
+ Message = $_.Exception.Message
+ }
+
+ Write-Warning "Configuration failed: $currentConfiguration"
+ }
+}
+
+if ($failedConfigurations.Count -gt 0) {
+ Write-Host ''
+ Write-Host 'Configuration failure summary:'
+ foreach ($failedConfiguration in $failedConfigurations) {
+ Write-Host "- $($failedConfiguration.Configuration): $($failedConfiguration.Message)"
+ }
+
+ throw "One or more configurations failed: $($failedConfigurations.Configuration -join ', ')"
+}
diff --git a/scripts/test_tx.sh b/scripts/test_tx.sh
index 613b086c..3a219431 100755
--- a/scripts/test_tx.sh
+++ b/scripts/test_tx.sh
@@ -1,3 +1,14 @@
#!/bin/bash
+##############################################################################
+# Copyright (c) 2024 Microsoft Corporation
+# 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.
+#
+# SPDX-License-Identifier: MIT
+##############################################################################
+
CTEST_PARALLEL_LEVEL=4 $(dirname `realpath $0`)/../test/tx/cmake/run.sh test all
diff --git a/scripts/test_tx_riscv.sh b/scripts/test_tx_riscv.sh
new file mode 100755
index 00000000..950afb85
--- /dev/null
+++ b/scripts/test_tx_riscv.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+##############################################################################
+# Copyright (c) 2024 Microsoft Corporation
+# 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.
+#
+# SPDX-License-Identifier: MIT
+##############################################################################
+
+# Run RISC-V regression tests for both RV32 and RV64 on QEMU.
+# Usage: test_tx_riscv.sh [all|<config>]
+
+SCRIPT_DIR="$(dirname "$(realpath "$0")")"
+RUN_SH="${SCRIPT_DIR}/../test/tx/cmake/riscv/run.sh"
+
+ARGS="${@:-all}"
+
+exit_code=0
+
+echo "=== Testing RISC-V32 ==="
+CTEST_PARALLEL_LEVEL=4 "$RUN_SH" riscv32 test $ARGS || exit_code=$?
+
+echo ""
+echo "=== Testing RISC-V64 ==="
+CTEST_PARALLEL_LEVEL=4 "$RUN_SH" riscv64 test $ARGS || exit_code=$?
+
+exit $exit_code
diff --git a/scripts/tx_windows_common.ps1 b/scripts/tx_windows_common.ps1
new file mode 100644
index 00000000..7b712d4a
--- /dev/null
+++ b/scripts/tx_windows_common.ps1
@@ -0,0 +1,941 @@
+[CmdletBinding()]
+param()
+
+$ErrorActionPreference = 'Stop'
+
+function Invoke-NativeCommand {
+ param(
+ [Parameter(Mandatory = $true)]
+ [string]$FilePath,
+
+ [Parameter()]
+ [string[]]$Arguments = @()
+ )
+
+ & $FilePath @Arguments
+ if ($LASTEXITCODE -ne 0) {
+ throw "Command failed with exit code ${LASTEXITCODE}: $FilePath $($Arguments -join ' ')"
+ }
+}
+
+function Get-CommandPathIfAvailable {
+ param(
+ [Parameter(Mandatory = $true)]
+ [string]$CommandName
+ )
+
+ $command = Get-Command $CommandName -ErrorAction SilentlyContinue
+ if ($null -eq $command) {
+ return $null
+ }
+
+ return $command.Source
+}
+
+function Get-PortSettings {
+ param(
+ [Parameter(Mandatory = $true)]
+ [string]$SelectedArch
+ )
+
+ switch ($SelectedArch) {
+ 'win32' {
+ return @{
+ ThreadXArch = 'win32'
+ ThreadXToolchain = 'vs_2019'
+ VsArch = 'x86'
+ }
+ }
+ 'win64' {
+ return @{
+ ThreadXArch = 'win64'
+ ThreadXToolchain = 'vs_2022'
+ VsArch = 'amd64'
+ }
+ }
+ default {
+ throw "Unsupported architecture: $SelectedArch"
+ }
+ }
+}
+
+function Get-RegressionConfigurations {
+ return @(
+ 'default_build_coverage',
+ 'disable_notify_callbacks_build',
+ 'stack_checking_build',
+ 'stack_checking_rand_fill_build',
+ 'trace_build'
+ )
+}
+
+function Resolve-RegressionConfigurations {
+ param(
+ [Parameter(Mandatory = $false)]
+ [AllowNull()]
+ [AllowEmptyCollection()]
+ [object]$RequestedConfigurations = 'all'
+ )
+
+ $allConfigurations = Get-RegressionConfigurations
+ $resolvedConfigurations = @()
+
+ if ($null -eq $RequestedConfigurations) {
+ $resolvedConfigurations = @('all')
+ }
+ elseif ($RequestedConfigurations -is [System.Array]) {
+ foreach ($requestedConfiguration in $RequestedConfigurations) {
+ if ($null -ne $requestedConfiguration) {
+ $resolvedConfigurations += [string]$requestedConfiguration
+ }
+ }
+ }
+ else {
+ $resolvedConfigurations = @([string]$RequestedConfigurations)
+ }
+
+ $normalizedConfigurations = @()
+ foreach ($requestedConfiguration in $resolvedConfigurations) {
+ foreach ($configurationPart in ($requestedConfiguration -split ',')) {
+ $trimmedConfiguration = $configurationPart.Trim()
+ if ($trimmedConfiguration.Length -gt 0) {
+ $normalizedConfigurations += $trimmedConfiguration
+ }
+ }
+ }
+
+ if (($normalizedConfigurations.Count -eq 0) -or ($normalizedConfigurations -contains 'all')) {
+ return $allConfigurations
+ }
+
+ foreach ($normalizedConfiguration in $normalizedConfigurations) {
+ if ($allConfigurations -notcontains $normalizedConfiguration) {
+ throw "Unsupported configuration: $normalizedConfiguration"
+ }
+ }
+
+ return $normalizedConfigurations
+}
+
+function Get-RegressionBuildDirectoryName {
+ param(
+ [Parameter(Mandatory = $true)]
+ [string]$ConfigurationName
+ )
+
+ switch ($ConfigurationName) {
+ 'default_build_coverage' {
+ return 'dbc'
+ }
+ 'disable_notify_callbacks_build' {
+ return 'dnc'
+ }
+ 'stack_checking_build' {
+ return 'sc'
+ }
+ 'stack_checking_rand_fill_build' {
+ return 'scrf'
+ }
+ 'trace_build' {
+ return 'tr'
+ }
+ default {
+ throw "Unsupported configuration: $ConfigurationName"
+ }
+ }
+}
+
+function Enter-VisualStudioDevShell {
+ param(
+ [Parameter(Mandatory = $true)]
+ [string]$VsArch
+ )
+
+ $targetArch = switch ($VsArch) {
+ 'amd64' { 'x64' }
+ 'x86' { 'x86' }
+ default { $VsArch }
+ }
+
+ if ((Get-Command cl -ErrorAction SilentlyContinue) -and ($env:VSCMD_ARG_TGT_ARCH -eq $targetArch)) {
+ return
+ }
+
+ $vsWherePath = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\vswhere.exe'
+ if (-not (Test-Path -LiteralPath $vsWherePath)) {
+ throw "Unable to locate vswhere.exe at $vsWherePath"
+ }
+
+ $installationPath = & $vsWherePath -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath
+ if (-not $installationPath) {
+ throw 'Unable to locate a Visual Studio 2022 installation with MSVC build tools.'
+ }
+
+ $launchScript = Join-Path $installationPath 'Common7\Tools\Launch-VsDevShell.ps1'
+ if (-not (Test-Path -LiteralPath $launchScript)) {
+ throw "Unable to locate Launch-VsDevShell.ps1 at $launchScript"
+ }
+
+ $env:VSCMD_SKIP_SENDTELEMETRY = '1'
+ & $launchScript -VsInstallationPath $installationPath -Arch $VsArch -HostArch amd64 -SkipAutomaticLocation | Out-Null
+
+ if (-not (Get-Command cl -ErrorAction SilentlyContinue)) {
+ throw 'MSVC compiler environment was not activated successfully.'
+ }
+}
+
+function Remove-BuildDirectory {
+ param(
+ [Parameter(Mandatory = $true)]
+ [string]$Path,
+
+ [Parameter(Mandatory = $true)]
+ [string]$RepoRoot
+ )
+
+ $fullRepoRoot = [System.IO.Path]::GetFullPath($RepoRoot)
+ $fullPath = [System.IO.Path]::GetFullPath($Path)
+
+ if (-not $fullPath.StartsWith($fullRepoRoot, [System.StringComparison]::OrdinalIgnoreCase)) {
+ throw "Refusing to remove a directory outside the repository: $fullPath"
+ }
+
+ if (Test-Path -LiteralPath $fullPath) {
+ try {
+ Remove-Item -LiteralPath $fullPath -Recurse -Force -ErrorAction Stop
+ return
+ } catch {
+ Write-Warning "Failed to remove build directory '$fullPath': $($_.Exception.Message)"
+ }
+
+ Get-ChildItem -LiteralPath $fullPath -Force -Recurse -ErrorAction SilentlyContinue | ForEach-Object {
+ try {
+ if (($_.Attributes -band [System.IO.FileAttributes]::ReadOnly) -ne 0) {
+ $_.Attributes = ($_.Attributes -band (-bnot [System.IO.FileAttributes]::ReadOnly))
+ }
+ } catch {
+ }
+ }
+
+ try {
+ Remove-Item -LiteralPath $fullPath -Recurse -Force -ErrorAction Stop
+ } catch {
+ Write-Warning "Proceeding with partially cleaned build directory '$fullPath': $($_.Exception.Message)"
+ }
+ }
+}
+
+function Remove-CtestTestingDirectory {
+ param(
+ [Parameter(Mandatory = $true)]
+ [string]$Path
+ )
+
+ if (-not (Test-Path -LiteralPath $Path)) {
+ return
+ }
+
+ try {
+ Remove-Item -LiteralPath $Path -Recurse -Force -ErrorAction Stop
+ return
+ } catch {
+ Write-Warning "Failed to remove CTest directory '$Path': $($_.Exception.Message)"
+ }
+
+ Get-ChildItem -LiteralPath $Path -Force -Recurse -ErrorAction SilentlyContinue | ForEach-Object {
+ try {
+ if (($_.Attributes -band [System.IO.FileAttributes]::ReadOnly) -ne 0) {
+ $_.Attributes = ($_.Attributes -band (-bnot [System.IO.FileAttributes]::ReadOnly))
+ }
+ } catch {
+ }
+ }
+
+ try {
+ Remove-Item -LiteralPath $Path -Recurse -Force -ErrorAction Stop
+ } catch {
+ Write-Warning "Proceeding with partially cleaned CTest directory '$Path': $($_.Exception.Message)"
+ }
+}
+
+function Remove-NinjaLock {
+ param(
+ [Parameter(Mandatory = $true)]
+ [string]$Path
+ )
+
+ $ninjaLockPath = Join-Path $Path '.ninja_lock'
+ if (Test-Path -LiteralPath $ninjaLockPath) {
+ Remove-Item -LiteralPath $ninjaLockPath -Force
+ }
+}
+
+function Get-WindowsDebuggerPath {
+ $debuggerPath = Get-CommandPathIfAvailable -CommandName 'cdb.exe'
+ if ($debuggerPath) {
+ return $debuggerPath
+ }
+
+ $candidatePaths = @(
+ (Join-Path ${env:ProgramFiles(x86)} 'Windows Kits\10\Debuggers\x64\cdb.exe'),
+ (Join-Path ${env:ProgramFiles(x86)} 'Windows Kits\10\Debuggers\x86\cdb.exe')
+ )
+
+ foreach ($candidatePath in $candidatePaths) {
+ if (Test-Path -LiteralPath $candidatePath) {
+ return $candidatePath
+ }
+ }
+
+ return $null
+}
+
+function Get-SanitizedFileName {
+ param(
+ [Parameter(Mandatory = $true)]
+ [string]$Name
+ )
+
+ $safeName = [regex]::Replace($Name, '[<>:"/\\|?*]', '_')
+ $safeName = $safeName -replace '\s+', '_'
+ return $safeName
+}
+
+function Initialize-MinidumpSupport {
+ if ($null -ne ('ThreadX.WindowsMiniDump' -as [type])) {
+ return
+ }
+
+ Add-Type -TypeDefinition @'
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+
+namespace ThreadX
+{
+ public static class WindowsMiniDump
+ {
+ [DllImport("Dbghelp.dll", SetLastError = true)]
+ private static extern bool MiniDumpWriteDump(
+ IntPtr hProcess,
+ uint processId,
+ IntPtr hFile,
+ uint dumpType,
+ IntPtr exceptionParam,
+ IntPtr userStreamParam,
+ IntPtr callbackParam);
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ private static extern IntPtr OpenProcess(uint desiredAccess, bool inheritHandle, int processId);
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ private static extern bool CloseHandle(IntPtr handle);
+
+ private const uint ProcessQueryInformation = 0x0400U;
+ private const uint ProcessVmRead = 0x0010U;
+ private const uint ProcessDupHandle = 0x0040U;
+
+ public static bool WriteDump(int processId, string dumpPath, uint dumpType, out int errorCode)
+ {
+ IntPtr processHandle = OpenProcess(ProcessQueryInformation | ProcessVmRead | ProcessDupHandle, false, processId);
+ if (processHandle == IntPtr.Zero)
+ {
+ errorCode = Marshal.GetLastWin32Error();
+ return false;
+ }
+
+ try
+ {
+ using (FileStream dumpStream = new FileStream(dumpPath, FileMode.Create, FileAccess.ReadWrite, FileShare.Read))
+ {
+ bool success = MiniDumpWriteDump(
+ processHandle,
+ unchecked((uint)processId),
+ dumpStream.SafeFileHandle.DangerousGetHandle(),
+ dumpType,
+ IntPtr.Zero,
+ IntPtr.Zero,
+ IntPtr.Zero);
+
+ errorCode = success ? 0 : Marshal.GetLastWin32Error();
+ return success;
+ }
+ }
+ finally
+ {
+ CloseHandle(processHandle);
+ }
+ }
+ }
+}
+'@
+}
+
+function Wait-FileReadable {
+ param(
+ [Parameter(Mandatory = $true)]
+ [string]$Path,
+
+ [Parameter()]
+ [int]$TimeoutSeconds = 10
+ )
+
+ $deadline = (Get-Date).AddSeconds($TimeoutSeconds)
+ while ((Get-Date) -lt $deadline) {
+ if (-not (Test-Path -LiteralPath $Path)) {
+ Start-Sleep -Milliseconds 200
+ continue
+ }
+
+ try {
+ $fileStream = [System.IO.File]::Open($Path, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite)
+ $fileStream.Dispose()
+ return $true
+ }
+ catch {
+ Start-Sleep -Milliseconds 200
+ }
+ }
+
+ return $false
+}
+
+function Get-CtestTestMetadata {
+ param(
+ [Parameter(Mandatory = $true)]
+ [string]$BuildDir
+ )
+
+ $ctestOutput = & ctest --test-dir $BuildDir --show-only=json-v1
+ if ($LASTEXITCODE -ne 0) {
+ throw "Unable to enumerate ctest metadata in $BuildDir"
+ }
+
+ return (($ctestOutput -join [Environment]::NewLine) | ConvertFrom-Json).tests
+}
+
+function Get-CtestFailedTestNames {
+ param(
+ [Parameter(Mandatory = $true)]
+ [string]$TestingTemporaryDir
+ )
+
+ $lastFailedPath = Join-Path $TestingTemporaryDir 'LastTestsFailed.log'
+ if (-not (Test-Path -LiteralPath $lastFailedPath)) {
+ return @()
+ }
+
+ $failedTestNames = @()
+ foreach ($logLine in (Get-Content -LiteralPath $lastFailedPath)) {
+ if ([string]::IsNullOrWhiteSpace($logLine)) {
+ continue
+ }
+
+ if ($logLine -match '^\s*\d+:(?<name>.+)\s*$') {
+ $failedTestNames += $Matches['name'].Trim()
+ }
+ else {
+ $failedTestNames += $logLine.Trim()
+ }
+ }
+
+ return $failedTestNames
+}
+
+function Invoke-ProcessDumpCapture {
+ param(
+ [Parameter(Mandatory = $true)]
+ [int]$ProcessId,
+
+ [Parameter(Mandatory = $true)]
+ [string]$DumpPath,
+
+ [Parameter()]
+ [int]$TimeoutSeconds = 15
+ )
+
+ $outputDirectory = Split-Path -Parent $DumpPath
+ if (-not (Test-Path -LiteralPath $outputDirectory)) {
+ New-Item -ItemType Directory -Path $outputDirectory | Out-Null
+ }
+
+ Remove-Item -LiteralPath $DumpPath -Force -ErrorAction SilentlyContinue
+
+ Initialize-MinidumpSupport
+ $dumpType = [uint32]0x00001006
+ $errorCode = 0
+ $dumpCaptured = [ThreadX.WindowsMiniDump]::WriteDump($ProcessId, $DumpPath, $dumpType, [ref]$errorCode)
+
+ if (-not $dumpCaptured) {
+ Write-Warning "MiniDumpWriteDump failed for PID ${ProcessId} with Win32 error $errorCode"
+ return $false
+ }
+
+ return (Test-Path -LiteralPath $DumpPath)
+}
+
+function Invoke-DumpStackAnalysis {
+ param(
+ [Parameter(Mandatory = $true)]
+ [string]$DumpPath,
+
+ [Parameter(Mandatory = $true)]
+ [string]$OutputBasePath,
+
+ [Parameter()]
+ [string]$SymbolPath,
+
+ [Parameter()]
+ [int]$TimeoutSeconds = 15
+ )
+
+ if (-not (Test-Path -LiteralPath $DumpPath)) {
+ Write-Warning "Skipping dump analysis because the dump file was not created: $DumpPath"
+ return $false
+ }
+
+ if (-not (Wait-FileReadable -Path $DumpPath)) {
+ Write-Warning "Skipping dump analysis because the dump file is not readable yet: $DumpPath"
+ return $false
+ }
+
+ $debuggerPath = Get-WindowsDebuggerPath
+ if (-not $debuggerPath) {
+ Write-Warning 'Skipping dump analysis because cdb.exe is not available.'
+ return $false
+ }
+
+ $outputDirectory = Split-Path -Parent $OutputBasePath
+ if (-not (Test-Path -LiteralPath $outputDirectory)) {
+ New-Item -ItemType Directory -Path $outputDirectory | Out-Null
+ }
+
+ $stdoutPath = "${OutputBasePath}.stdout.txt"
+ $stderrPath = "${OutputBasePath}.stderr.txt"
+ $commandFilePath = "${OutputBasePath}.commands.txt"
+ Set-Content -LiteralPath $commandFilePath -Value @(
+ '!runaway 7'
+ '~* kb 200'
+ 'q'
+ ) -Encoding Ascii
+ $cdbArguments = @(
+ '-lines',
+ '-z', $DumpPath
+ )
+
+ if ($SymbolPath) {
+ $cdbArguments += @('-y', $SymbolPath)
+ }
+
+ $cdbArguments += @('-cf', $commandFilePath)
+ $cdbProcess = Start-Process -FilePath $debuggerPath -ArgumentList $cdbArguments -PassThru -NoNewWindow `
+ -RedirectStandardOutput $stdoutPath -RedirectStandardError $stderrPath
+
+ try {
+ $cdbProcess | Wait-Process -Timeout $TimeoutSeconds -ErrorAction Stop
+ }
+ catch {
+ if (-not $cdbProcess.HasExited) {
+ $null = Start-Process -FilePath 'taskkill.exe' -ArgumentList @('/PID', $cdbProcess.Id.ToString(), '/T', '/F') `
+ -WindowStyle Hidden -Wait -PassThru
+ }
+ }
+
+ return $true
+}
+
+function Invoke-ProcessWithTimeout {
+ param(
+ [Parameter(Mandatory = $true)]
+ [string]$FilePath,
+
+ [Parameter()]
+ [string[]]$Arguments = @(),
+
+ [Parameter()]
+ [int]$TimeoutSeconds = 0,
+
+ [Parameter()]
+ [string]$WorkingDirectory,
+
+ [Parameter()]
+ [string]$RedirectStandardOutputPath,
+
+ [Parameter()]
+ [string]$RedirectStandardErrorPath,
+
+ [Parameter()]
+ [scriptblock]$OnTimeout,
+
+ [Parameter()]
+ [scriptblock]$PostTimeout
+ )
+
+ $argumentList = @()
+ foreach ($argument in $Arguments) {
+ if ($argument -match '\s|"') {
+ $argumentList += '"' + ($argument -replace '"', '\"') + '"'
+ }
+ else {
+ $argumentList += $argument
+ }
+ }
+
+ $startProcessParameters = @{
+ FilePath = $FilePath
+ NoNewWindow = $true
+ PassThru = $true
+ }
+
+ if ($argumentList.Count -gt 0) {
+ $startProcessParameters['ArgumentList'] = $argumentList
+ }
+
+ if ($WorkingDirectory) {
+ $startProcessParameters['WorkingDirectory'] = $WorkingDirectory
+ }
+
+ if ($RedirectStandardOutputPath) {
+ $startProcessParameters['RedirectStandardOutput'] = $RedirectStandardOutputPath
+ }
+
+ if ($RedirectStandardErrorPath) {
+ $startProcessParameters['RedirectStandardError'] = $RedirectStandardErrorPath
+ }
+
+ $process = Start-Process @startProcessParameters
+ if ($TimeoutSeconds -le 0) {
+ $process | Wait-Process
+ $completed = $true
+ }
+ else {
+ try {
+ $process | Wait-Process -Timeout $TimeoutSeconds -ErrorAction Stop
+ $completed = $true
+ }
+ catch {
+ $completed = $false
+ }
+ }
+
+ if (-not $completed) {
+ if ($null -ne $OnTimeout) {
+ & $OnTimeout $process
+ }
+
+ $null = Start-Process -FilePath 'taskkill.exe' -ArgumentList @('/PID', $process.Id.ToString(), '/T', '/F') -WindowStyle Hidden -Wait -PassThru
+
+ if ($null -ne $PostTimeout) {
+ & $PostTimeout $process
+ }
+
+ return @{
+ Completed = $false
+ ExitCode = $null
+ ProcessId = $process.Id
+ }
+ }
+
+ $process.Refresh()
+ return @{
+ Completed = $true
+ ExitCode = $process.ExitCode
+ ProcessId = $process.Id
+ }
+}
+
+function Invoke-CtestFailureDiagnostics {
+ param(
+ [Parameter(Mandatory = $true)]
+ [string]$BuildDir,
+
+ [Parameter(Mandatory = $true)]
+ [string]$TestingTemporaryDir,
+
+ [Parameter(Mandatory = $true)]
+ [int]$TimeoutSeconds
+ )
+
+ $failedTestNames = Get-CtestFailedTestNames -TestingTemporaryDir $TestingTemporaryDir
+ if ($failedTestNames.Count -eq 0) {
+ Write-Warning "No failed tests were recorded in $TestingTemporaryDir"
+ return
+ }
+
+ $testMetadataList = Get-CtestTestMetadata -BuildDir $BuildDir
+ $testMetadataMap = @{}
+ foreach ($testMetadata in $testMetadataList) {
+ $testMetadataMap[$testMetadata.name] = $testMetadata
+ }
+
+ $diagnosticsRoot = Join-Path $TestingTemporaryDir 'FailureDiagnostics'
+ if (-not (Test-Path -LiteralPath $diagnosticsRoot)) {
+ New-Item -ItemType Directory -Path $diagnosticsRoot | Out-Null
+ }
+
+ foreach ($failedTestName in $failedTestNames) {
+ if (-not $testMetadataMap.ContainsKey($failedTestName)) {
+ Write-Warning "Unable to locate ctest metadata for failed test: $failedTestName"
+ continue
+ }
+
+ $testMetadata = $testMetadataMap[$failedTestName]
+ if (($null -eq $testMetadata.command) -or ($testMetadata.command.Count -eq 0)) {
+ Write-Warning "No executable command was recorded for failed test: $failedTestName"
+ continue
+ }
+
+ $testArguments = @()
+ if ($testMetadata.command.Count -gt 1) {
+ $testArguments = @($testMetadata.command[1..($testMetadata.command.Count - 1)])
+ }
+
+ $safeTestName = Get-SanitizedFileName -Name $failedTestName
+ $stdoutPath = Join-Path $diagnosticsRoot "${safeTestName}.stdout.txt"
+ $stderrPath = Join-Path $diagnosticsRoot "${safeTestName}.stderr.txt"
+ $debugOutputBasePath = Join-Path $diagnosticsRoot "${safeTestName}.cdb"
+ $workingDirectory = $null
+ $symbolDirectory = Split-Path -Parent $testMetadata.command[0]
+
+ if ($null -ne $testMetadata.properties) {
+ foreach ($testProperty in $testMetadata.properties) {
+ if ($testProperty.name -eq 'WORKING_DIRECTORY') {
+ $workingDirectory = $testProperty.value
+ break
+ }
+ }
+ }
+
+ Write-Warning "Collecting failure diagnostics for $failedTestName"
+ $dumpPath = '{0}.{1}.dmp' -f $debugOutputBasePath, ([DateTime]::UtcNow.ToString('yyyyMMddHHmmssfff'))
+ $testResult = Invoke-ProcessWithTimeout -FilePath $testMetadata.command[0] -Arguments $testArguments `
+ -TimeoutSeconds $TimeoutSeconds -WorkingDirectory $workingDirectory -RedirectStandardOutputPath $stdoutPath `
+ -RedirectStandardErrorPath $stderrPath -OnTimeout {
+ param($timedOutProcess)
+ Invoke-ProcessDumpCapture -ProcessId $timedOutProcess.Id -DumpPath $dumpPath | Out-Null
+ } -PostTimeout {
+ param($timedOutProcess)
+ if (Test-Path -LiteralPath $dumpPath) {
+ Invoke-DumpStackAnalysis -DumpPath $dumpPath -OutputBasePath $debugOutputBasePath -SymbolPath $symbolDirectory | Out-Null
+ }
+ }
+
+ if (-not $testResult.Completed) {
+ Write-Warning "Timeout diagnostics were captured for $failedTestName under $diagnosticsRoot"
+ continue
+ }
+
+ Write-Warning "Replay finished for $failedTestName with exit code $($testResult.ExitCode). Output was saved under $diagnosticsRoot"
+ }
+}
+
+function Test-IsNinjaBuildDirectory {
+ param(
+ [Parameter(Mandatory = $true)]
+ [string]$BuildDir
+ )
+
+ return (Test-Path -LiteralPath (Join-Path $BuildDir 'build.ninja'))
+}
+
+function Get-NinjaBuildStatements {
+ param(
+ [Parameter(Mandatory = $true)]
+ [string]$BuildDir
+ )
+
+ $buildNinjaPath = Join-Path $BuildDir 'build.ninja'
+ if (-not (Test-Path -LiteralPath $buildNinjaPath)) {
+ throw "Unable to locate build.ninja in $BuildDir"
+ }
+
+ return Get-Content -LiteralPath $buildNinjaPath
+}
+
+function New-NinjaRspFile {
+ param(
+ [Parameter(Mandatory = $true)]
+ [string]$BuildDir,
+
+ [Parameter(Mandatory = $true)]
+ [string]$RspRelativePath
+ )
+
+ $buildStatements = Get-NinjaBuildStatements -BuildDir $BuildDir
+ $rspLine = ' RSP_FILE = ' + $RspRelativePath
+ $rspIndex = -1
+
+ for ($index = 0; $index -lt $buildStatements.Count; $index++) {
+ if ($buildStatements[$index] -eq $rspLine) {
+ $rspIndex = $index
+ break
+ }
+ }
+
+ if ($rspIndex -lt 0) {
+ throw "Unable to locate RSP_FILE entry for $RspRelativePath in build.ninja."
+ }
+
+ $buildIndex = -1
+ for ($index = $rspIndex; $index -ge 0; $index--) {
+ if ($buildStatements[$index].StartsWith('build ')) {
+ $buildIndex = $index
+ break
+ }
+ }
+
+ if ($buildIndex -lt 0) {
+ throw "Unable to locate the build statement that owns $RspRelativePath."
+ }
+
+ $buildLine = $buildStatements[$buildIndex]
+ if ($buildLine -notmatch '^build\s+\S+:\s+\S+\s+(.+)$') {
+ throw "Unable to parse build statement for $RspRelativePath."
+ }
+
+ $rspContents = ($Matches[1] -split '\s+') -join [Environment]::NewLine
+ $rspPath = Join-Path $BuildDir $RspRelativePath
+ $rspParent = Split-Path -Parent $rspPath
+
+ if (-not (Test-Path -LiteralPath $rspParent)) {
+ New-Item -ItemType Directory -Path $rspParent | Out-Null
+ }
+
+ Set-Content -LiteralPath $rspPath -Value $rspContents
+}
+
+function Ensure-NinjaRspFiles {
+ param(
+ [Parameter(Mandatory = $true)]
+ [string]$BuildDir,
+
+ [Parameter(Mandatory = $true)]
+ [string]$CommandLine
+ )
+
+ $rspMatches = [regex]::Matches($CommandLine, '@(?<path>[^\s"]+\.rsp)')
+ foreach ($rspMatch in $rspMatches) {
+ $rspRelativePath = $rspMatch.Groups['path'].Value
+ $rspPath = Join-Path $BuildDir $rspRelativePath
+ if (-not (Test-Path -LiteralPath $rspPath)) {
+ New-NinjaRspFile -BuildDir $BuildDir -RspRelativePath $rspRelativePath
+ }
+ }
+}
+
+function Get-PendingNinjaCommands {
+ param(
+ [Parameter(Mandatory = $true)]
+ [string]$BuildDir
+ )
+
+ $commandLines = & ninja -C $BuildDir -t commands
+ if ($LASTEXITCODE -ne 0) {
+ throw "Unable to enumerate pending Ninja commands in $BuildDir"
+ }
+
+ return $commandLines | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
+}
+
+function Invoke-NinjaFallbackBuild {
+ param(
+ [Parameter(Mandatory = $true)]
+ [string]$BuildDir
+ )
+
+ $pendingCommands = Get-PendingNinjaCommands -BuildDir $BuildDir
+ if ($pendingCommands.Count -eq 0) {
+ return
+ }
+
+ Push-Location $BuildDir
+ try {
+ foreach ($pendingCommand in $pendingCommands) {
+ Ensure-NinjaRspFiles -BuildDir $BuildDir -CommandLine $pendingCommand
+
+ $commandToRun = $pendingCommand -replace '\s/showIncludes(?=\s|$)', ''
+
+ if ($commandToRun -match '^[^ ]*cmd(?:\.exe)?\s+/C\s+"(?<inner>.*)"\s*$') {
+ & cmd.exe /C $Matches['inner']
+ }
+ else {
+ & cmd.exe /C $commandToRun
+ }
+
+ if ($LASTEXITCODE -ne 0) {
+ throw "Fallback Ninja command failed with exit code ${LASTEXITCODE}: $pendingCommand"
+ }
+ }
+ }
+ finally {
+ Pop-Location
+ }
+}
+
+function Invoke-CMakeBuild {
+ param(
+ [Parameter(Mandatory = $true)]
+ [string]$BuildDir,
+
+ [Parameter(Mandatory = $true)]
+ [int]$Parallel,
+
+ [Parameter()]
+ [int]$TimeoutSeconds = 0
+ )
+
+ Remove-NinjaLock -Path $BuildDir
+ $isNinjaBuild = Test-IsNinjaBuildDirectory -BuildDir $BuildDir
+
+ if ($TimeoutSeconds -le 0) {
+ if ($isNinjaBuild) {
+ Invoke-NativeCommand -FilePath 'ninja' -Arguments @(
+ '-C', $BuildDir,
+ '-j', $Parallel.ToString()
+ )
+ }
+ else {
+ Invoke-NativeCommand -FilePath 'cmake' -Arguments @(
+ '--build', $BuildDir,
+ '--parallel', $Parallel.ToString()
+ )
+ }
+ return
+ }
+
+ if ($isNinjaBuild) {
+ $buildToolName = 'Ninja'
+ $buildResult = Invoke-ProcessWithTimeout -FilePath 'ninja' -Arguments @(
+ '-C', $BuildDir,
+ '-j', $Parallel.ToString()
+ ) -TimeoutSeconds $TimeoutSeconds
+ }
+ else {
+ $buildToolName = 'CMake'
+ $buildResult = Invoke-ProcessWithTimeout -FilePath 'cmake' -Arguments @(
+ '--build', $BuildDir,
+ '--parallel', $Parallel.ToString()
+ ) -TimeoutSeconds $TimeoutSeconds
+ }
+
+ if ($buildResult.Completed -and ($buildResult.ExitCode -eq 0)) {
+ return
+ }
+
+ if (-not $isNinjaBuild) {
+ if (-not $buildResult.Completed) {
+ throw "$buildToolName build timed out after $TimeoutSeconds seconds in $BuildDir"
+ }
+
+ throw "$buildToolName build failed with exit code $($buildResult.ExitCode) in $BuildDir"
+ }
+
+ if ($buildResult.Completed) {
+ throw "$buildToolName build failed with exit code $($buildResult.ExitCode) in $BuildDir"
+ }
+
+ Write-Warning "$buildToolName build timed out after $TimeoutSeconds seconds in $BuildDir. Replaying pending Ninja commands from PowerShell."
+
+ Remove-NinjaLock -Path $BuildDir
+ Invoke-NinjaFallbackBuild -BuildDir $BuildDir
+}