diff options
Diffstat (limited to 'scripts')
| -rw-r--r-- | scripts/build_smp.ps1 | 54 | ||||
| -rwxr-xr-x | scripts/build_smp.sh | 11 | ||||
| -rw-r--r-- | scripts/build_tx.ps1 | 58 | ||||
| -rwxr-xr-x | scripts/build_tx.sh | 11 | ||||
| -rwxr-xr-x | scripts/build_tx_riscv.sh | 26 | ||||
| -rwxr-xr-x | scripts/cmake_bootstrap.sh | 11 | ||||
| -rwxr-xr-x | scripts/copy_armv7_m.sh | 11 | ||||
| -rwxr-xr-x | scripts/copy_armv8_m.sh | 11 | ||||
| -rwxr-xr-x | scripts/copy_module_armv7_m.sh | 11 | ||||
| -rwxr-xr-x | scripts/install.sh | 11 | ||||
| -rwxr-xr-x | scripts/install_riscv.sh | 43 | ||||
| -rwxr-xr-x | scripts/prepare_release.sh | 172 | ||||
| -rwxr-xr-x | scripts/sdl_check.sh | 11 | ||||
| -rw-r--r-- | scripts/test_smp.ps1 | 120 | ||||
| -rwxr-xr-x | scripts/test_smp.sh | 11 | ||||
| -rw-r--r-- | scripts/test_tx.ps1 | 121 | ||||
| -rwxr-xr-x | scripts/test_tx.sh | 11 | ||||
| -rwxr-xr-x | scripts/test_tx_riscv.sh | 30 | ||||
| -rw-r--r-- | scripts/tx_windows_common.ps1 | 941 |
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 +} |
