Skip to content

Merge branch 'main' of https://github.com/wsollers/utf_strings #70

Merge branch 'main' of https://github.com/wsollers/utf_strings

Merge branch 'main' of https://github.com/wsollers/utf_strings #70

Workflow file for this run

name: CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
release:
types: [published]
env:
CONAN_USER_HOME: "${{ github.workspace }}/conan-cache"
jobs:
# ============================================================================
# Linux x64 - GCC 13
# ============================================================================
linux-gcc:
name: "Linux GCC 13 (x64)"
runs-on: ubuntu-24.04
strategy:
matrix:
build_type: [Debug, Release]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install system dependencies
run: |
sudo apt update
sudo apt install -y cmake python3-pip git clang-format
# Install exact GCC version 13.3.0 to match local development environment
sudo apt install -y gcc-13 g++-13
# Set up alternatives to ensure we use GCC 13
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 100
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-13 100
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: "3.11"
- name: Install Conan
run: |
pip3 install --user conan
echo "$HOME/.local/bin" >> $GITHUB_PATH
- name: Setup Conan profile
run: |
conan profile detect --force
# Use static profile for GCC 13 with C++23
sed -i 's/compiler=.*/compiler=gcc/' ~/.conan2/profiles/default
sed -i 's/compiler.version=.*/compiler.version=13/' ~/.conan2/profiles/default
sed -i 's/compiler.libcxx=.*/compiler.libcxx=libstdc++11/' ~/.conan2/profiles/default
sed -i 's/compiler.cppstd=.*/compiler.cppstd=23/' ~/.conan2/profiles/default
sed -i 's/os=.*/os=Linux/' ~/.conan2/profiles/default
sed -i 's/arch=.*/arch=x86_64/' ~/.conan2/profiles/default
echo "=== Conan Profile ==="
conan profile show --profile:host=default
- name: Cache Conan packages
uses: actions/cache@v4
with:
path: ~/.conan2
key: conan-linux-gcc-${{ matrix.build_type }}-${{ hashFiles('conanfile.py') }}
restore-keys: |
conan-linux-gcc-${{ matrix.build_type }}-
conan-linux-gcc-
- name: Install dependencies
run: |
export CC=gcc
export CXX=g++
conan install . -s os=Linux -s build_type=${{ matrix.build_type }} --output-folder=build --build=missing -o with_gperftools=True
- name: Configure CMake
run: |
export CC=gcc
export CXX=g++
if [[ "${{ matrix.build_type }}" == "Debug" ]]; then
cmake --preset conan-debug \
-DCOMPILER_TYPE=GCC \
-DUSE_LTO=ON \
-DUSE_NATIVE_ARCH=ON \
-DENABLE_SHARED_LIBRARY=ON
else
cmake --preset conan-release \
-DCOMPILER_TYPE=GCC \
-DUSE_LTO=ON \
-DUSE_NATIVE_ARCH=ON \
-DENABLE_SHARED_LIBRARY=ON
fi
- name: Build
run: |
if [[ "${{ matrix.build_type }}" == "Debug" ]]; then
cmake --build --preset conan-debug --parallel
else
cmake --build --preset conan-release --parallel
fi
- name: Test
run: |
# Both Debug and Release builds use build/build directory with Conan presets
cd build/build
if [[ "${{ matrix.build_type }}" == "Debug" ]]; then
./utf_strings-tests --gtest_output=xml:test_results_gcc_debug.xml
else
./utf_strings-tests --gtest_output=xml:test_results_gcc_release.xml
fi
- name: Benchmark (Release only)
if: matrix.build_type == 'Release'
run: |
cd build/build
./utf_strings-bench --benchmark_min_time=0.1s --benchmark_format=json --benchmark_out=benchmark_results_gcc.json
- name: Check code formatting (Release only)
if: matrix.build_type == 'Release'
run: cmake --build --preset conan-release --target format-check
- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
name: test-results-linux-gcc-${{ matrix.build_type }}
path: |
build/build/test_results_gcc_debug.xml
build/build/test_results_gcc_release.xml
build/build/benchmark_results_gcc.json
# ============================================================================
# Linux x64 - Clang 18
# ============================================================================
linux-clang:
name: "Linux Clang 18 (x64)"
runs-on: ubuntu-24.04
strategy:
matrix:
build_type: [Debug, Release]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install system dependencies
timeout-minutes: 15
run: |
set -e # Exit on any error
# Update package list first
sudo apt update
# Install basic dependencies first (these are fast and reliable)
sudo apt install -y cmake python3-pip git wget curl
# Install Clang 18 with timeout and retry logic
echo "Installing Clang 18..."
for i in {1..3}; do
if timeout 10m bash -c "
wget -q https://apt.llvm.org/llvm.sh &&
chmod +x llvm.sh &&
sudo ./llvm.sh 18
"; then
echo "LLVM installation successful on attempt $i"
break
else
echo "LLVM installation attempt $i failed, retrying..."
rm -f llvm.sh
if [ $i -eq 3 ]; then
echo "All LLVM installation attempts failed"
exit 1
fi
sleep 30
fi
done
# Install additional tools
sudo apt install -y clang-format-18 || {
echo "clang-format-18 not available, trying alternatives..."
sudo apt install -y clang-format || echo "clang-format installation failed"
}
# Set up symlinks
sudo ln -sf /usr/bin/clang-18 /usr/bin/clang
sudo ln -sf /usr/bin/clang++-18 /usr/bin/clang++
if command -v clang-format-18 >/dev/null 2>&1; then
sudo ln -sf /usr/bin/clang-format-18 /usr/bin/clang-format
fi
# Verify installation
clang --version
clang++ --version
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: "3.11"
- name: Install Conan
run: |
pip3 install --user conan
echo "$HOME/.local/bin" >> $GITHUB_PATH
- name: Setup Conan profile
run: |
conan profile detect --force
# Use static profile for Clang 18 with C++23
sed -i 's/compiler=.*/compiler=clang/' ~/.conan2/profiles/default
sed -i 's/compiler.version=.*/compiler.version=18/' ~/.conan2/profiles/default
sed -i 's/compiler.libcxx=.*/compiler.libcxx=libstdc++11/' ~/.conan2/profiles/default
sed -i 's/compiler.cppstd=.*/compiler.cppstd=23/' ~/.conan2/profiles/default
sed -i 's/os=.*/os=Linux/' ~/.conan2/profiles/default
sed -i 's/arch=.*/arch=x86_64/' ~/.conan2/profiles/default
echo "=== Conan Profile ==="
conan profile show --profile:host=default
- name: Cache Conan packages
uses: actions/cache@v4
with:
path: ~/.conan2
key: conan-linux-clang-${{ matrix.build_type }}-${{ hashFiles('conanfile.py') }}
restore-keys: |
conan-linux-clang-${{ matrix.build_type }}-
conan-linux-clang-
- name: Install dependencies
run: |
export CC=clang
export CXX=clang++
conan install . -s os=Linux -s build_type=${{ matrix.build_type }} --output-folder=build --build=missing -o with_gperftools=True
- name: Configure CMake
run: |
export CC=clang
export CXX=clang++
if [[ "${{ matrix.build_type }}" == "Debug" ]]; then
cmake --preset conan-debug \
-DCOMPILER_TYPE=CLANG \
-DUSE_LTO=ON \
-DUSE_NATIVE_ARCH=ON \
-DUSE_LIBC_PLUS_PLUS=OFF \
-DENABLE_SHARED_LIBRARY=ON
else
cmake --preset conan-release \
-DCOMPILER_TYPE=CLANG \
-DUSE_LTO=ON \
-DUSE_NATIVE_ARCH=ON \
-DUSE_LIBC_PLUS_PLUS=OFF \
-DENABLE_SHARED_LIBRARY=ON
fi
- name: Build
run: |
if [[ "${{ matrix.build_type }}" == "Debug" ]]; then
cmake --build --preset conan-debug --parallel
else
cmake --build --preset conan-release --parallel
fi
- name: Test
run: |
# Both Debug and Release builds use build/build directory with Conan presets
cd build/build
if [[ "${{ matrix.build_type }}" == "Debug" ]]; then
./utf_strings-tests --gtest_output=xml:test_results_clang_debug.xml
else
./utf_strings-tests --gtest_output=xml:test_results_clang_release.xml
fi
- name: Benchmark (Release only)
if: matrix.build_type == 'Release'
run: |
cd build/build
./utf_strings-bench --benchmark_min_time=0.1s --benchmark_format=json --benchmark_out=benchmark_results_clang.json
- name: Check code formatting (Release only)
if: matrix.build_type == 'Release'
run: cmake --build --preset conan-release --target format-check
- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
name: test-results-linux-clang-${{ matrix.build_type }}
path: |
build/build/test_results_clang_debug.xml
build/build/test_results_clang_release.xml
build/build/benchmark_results_clang.json
# ============================================================================
# Linux x64 - Clang with Fuzz Testing
# ============================================================================
linux-clang-fuzz:
name: "Linux Clang Fuzz Testing (x64)"
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install system dependencies
timeout-minutes: 15
run: |
set -e # Exit on any error
# Update package list first
sudo apt update
# Install basic dependencies first (these are fast and reliable)
sudo apt install -y cmake python3-pip git wget curl
# Install Clang 18 with timeout and retry logic
echo "Installing Clang 18..."
for i in {1..3}; do
if timeout 10m bash -c "
wget -q https://apt.llvm.org/llvm.sh &&
chmod +x llvm.sh &&
sudo ./llvm.sh 18
"; then
echo "LLVM installation successful on attempt $i"
break
else
echo "LLVM installation attempt $i failed, retrying..."
rm -f llvm.sh
if [ $i -eq 3 ]; then
echo "All LLVM installation attempts failed"
exit 1
fi
sleep 30
fi
done
# Install additional tools
sudo apt install -y clang-format-18 || {
echo "clang-format-18 not available, trying alternatives..."
sudo apt install -y clang-format || echo "clang-format installation failed"
}
# Set up symlinks
sudo ln -sf /usr/bin/clang-18 /usr/bin/clang
sudo ln -sf /usr/bin/clang++-18 /usr/bin/clang++
if command -v clang-format-18 >/dev/null 2>&1; then
sudo ln -sf /usr/bin/clang-format-18 /usr/bin/clang-format
fi
# Verify installation
clang --version
clang++ --version
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: "3.11"
- name: Install Conan
run: |
pip3 install --user conan
echo "$HOME/.local/bin" >> $GITHUB_PATH
- name: Setup Conan profile
run: |
conan profile detect --force
# Use static profile for Clang 18 with C++23 (fuzz testing)
sed -i 's/compiler=.*/compiler=clang/' ~/.conan2/profiles/default
sed -i 's/compiler.version=.*/compiler.version=18/' ~/.conan2/profiles/default
sed -i 's/compiler.libcxx=.*/compiler.libcxx=libstdc++11/' ~/.conan2/profiles/default
sed -i 's/compiler.cppstd=.*/compiler.cppstd=23/' ~/.conan2/profiles/default
sed -i 's/os=.*/os=Linux/' ~/.conan2/profiles/default
sed -i 's/arch=.*/arch=x86_64/' ~/.conan2/profiles/default
echo "=== Conan Profile ==="
conan profile show --profile:host=default
- name: Cache Conan packages
uses: actions/cache@v4
with:
path: ~/.conan2
key: conan-linux-clang-fuzz-${{ hashFiles('conanfile.py') }}
restore-keys: |
conan-linux-clang-fuzz-
conan-linux-clang-
- name: Install dependencies
run: |
export CC=clang
export CXX=clang++
conan install . -s os=Linux -s build_type=Debug --output-folder=build --build=missing -o with_gperftools=True
- name: Configure CMake with Fuzz Testing
run: |
export CC=clang
export CXX=clang++
cmake -S . -B build/Fuzz \
-DCMAKE_TOOLCHAIN_FILE=build/conan_toolchain.cmake \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_CXX_COMPILER=clang++ \
-DCOMPILER_TYPE=CLANG \
-DUSE_LTO=OFF \
-DUSE_NATIVE_ARCH=OFF \
-DENABLE_SHARED_LIBRARY=OFF \
-DUTF_STRINGS_BUILD_TESTS=OFF \
-DUTF_STRINGS_BUILD_BENCHMARKS=OFF \
-DUTF_STRINGS_BUILD_FUZZ_TESTS=ON \
-DUTF_STRINGS_WITH_GPERFTOOLS=OFF
- name: Build Fuzz Targets
run: cmake --build build/Fuzz --parallel
- name: Run Fuzz Tests (Short Duration for CI)
run: |
cd build/Fuzz
echo "Running UTF-8 fuzz tests..."
timeout 60s ./fuzz_utf8 -max_total_time=30 -print_final_stats=1 || true
echo "Running UTF-16 BE fuzz tests..."
timeout 60s ./fuzz_utf16_be -max_total_time=30 -print_final_stats=1 || true
echo "Running UTF-16 LE fuzz tests..."
timeout 60s ./fuzz_utf16_le -max_total_time=30 -print_final_stats=1 || true
echo "Running UTF-32 BE fuzz tests..."
timeout 60s ./fuzz_utf32_be -max_total_time=30 -print_final_stats=1 || true
echo "Running UTF-32 LE fuzz tests..."
timeout 60s ./fuzz_utf32_le -max_total_time=30 -print_final_stats=1 || true
- name: Upload fuzz artifacts
uses: actions/upload-artifact@v4
if: always()
with:
name: fuzz-results-linux-clang
path: |
build/Fuzz/*.log
build/Fuzz/crash-*
build/Fuzz/leak-*
build/Fuzz/timeout-*
# ============================================================================
# Linux x64 - Sanitizer Testing (GCC & Clang)
# ============================================================================
linux-sanitizers:
name: "Linux Sanitizer Testing (x64)"
runs-on: ubuntu-24.04
strategy:
matrix:
sanitizer: [asan, tsan]
compiler: [clang]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install system dependencies
timeout-minutes: 15
run: |
set -e # Exit on any error
# Update package list first
sudo apt update
# Install basic dependencies first
sudo apt install -y cmake python3-pip git wget curl
# Install Clang 18 with retry logic (sanitizers use Clang only)
echo "Installing Clang 18..."
for i in {1..3}; do
if timeout 10m bash -c "
wget -q https://apt.llvm.org/llvm.sh &&
chmod +x llvm.sh &&
sudo ./llvm.sh 18
"; then
echo "LLVM installation successful on attempt $i"
sudo ln -sf /usr/bin/clang-18 /usr/bin/clang
sudo ln -sf /usr/bin/clang++-18 /usr/bin/clang++
break
else
echo "LLVM installation attempt $i failed, retrying..."
rm -f llvm.sh
if [ $i -eq 3 ]; then
echo "All LLVM installation attempts failed"
exit 1
fi
sleep 30
fi
done
# Verify Clang installation
clang --version
clang++ --version
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: "3.11"
- name: Install Conan
run: |
pip3 install --user conan
echo "$HOME/.local/bin" >> $GITHUB_PATH
- name: Setup Conan profile
run: |
conan profile detect --force
# Use static profile for Clang 18 with C++23 (sanitizers use Clang only)
sed -i 's/compiler=.*/compiler=clang/' ~/.conan2/profiles/default
sed -i 's/compiler.version=.*/compiler.version=18/' ~/.conan2/profiles/default
sed -i 's/compiler.libcxx=.*/compiler.libcxx=libstdc++11/' ~/.conan2/profiles/default
sed -i 's/compiler.cppstd=.*/compiler.cppstd=23/' ~/.conan2/profiles/default
sed -i 's/os=.*/os=Linux/' ~/.conan2/profiles/default
sed -i 's/arch=.*/arch=x86_64/' ~/.conan2/profiles/default
echo "=== Conan Profile ==="
conan profile show --profile:host=default
- name: Cache Conan packages
uses: actions/cache@v4
with:
path: ~/.conan2
key: conan-linux-clang-${{ matrix.sanitizer }}-${{ hashFiles('conanfile.py') }}
restore-keys: |
conan-linux-clang-${{ matrix.sanitizer }}-
- name: Install dependencies
run: |
export CC=clang
export CXX=clang++
conan install . -s os=Linux -s build_type=Debug --output-folder=build --build=missing -o with_gperftools=True
- name: Configure CMake with Sanitizers
run: |
export CC=clang
export CXX=clang++
if [ "${{ matrix.sanitizer }}" = "asan" ]; then
cmake --preset conan-debug \
-DCOMPILER_TYPE=CLANG \
-DUSE_LTO=OFF \
-DUSE_NATIVE_ARCH=OFF \
-DENABLE_SHARED_LIBRARY=ON \
-DUTF_STRINGS_ENABLE_SANITIZERS=ON
else
cmake --preset conan-debug \
-DCOMPILER_TYPE=CLANG \
-DUSE_LTO=OFF \
-DUSE_NATIVE_ARCH=OFF \
-DENABLE_SHARED_LIBRARY=ON \
-DUTF_STRINGS_ENABLE_THREAD_SANITIZER=ON
fi
- name: Build with Sanitizers
run: cmake --build --preset conan-debug --parallel
- name: Test with Sanitizers
run: |
# Debug builds use build/build directory with Conan presets
cd build/build
./utf_strings-tests --gtest_output=xml:test_results_clang_${{ matrix.sanitizer }}.xml
- name: Upload sanitizer results
uses: actions/upload-artifact@v4
if: always()
with:
name: sanitizer-results-clang-${{ matrix.sanitizer }}
path: build/build/test_results_clang_${{ matrix.sanitizer }}.xml
# ============================================================================
# Windows x64 - MSVC 2022
# ============================================================================
windows-msvc:
name: "Windows ${{ matrix.compiler_name }} (x64)"
runs-on: windows-2022
timeout-minutes: 30
strategy:
matrix:
build_type: [Release] # Debug builds timeout on Windows CI
compiler: [msvc, clang-cl]
include:
- compiler: msvc
compiler_name: "MSVC 2022"
conan_compiler: msvc
conan_version: 193
runtime_version: ""
cmake_args: "-DUSE_MSVC_LTO=ON -DENABLE_SHARED_LIBRARY=ON"
- compiler: clang-cl
compiler_name: "Clang-CL"
conan_compiler: clang
conan_version: 16
runtime_version: "v143"
cmake_args: "-DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_C_COMPILER=clang-cl -DUSE_MSVC_LTO=OFF -DENABLE_SHARED_LIBRARY=OFF"
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: "3.11"
- name: Install Conan
run: |
pip install conan
- name: Setup Conan profile
run: |
conan profile detect --force
# Configure profile for ${{ matrix.compiler_name }} with C++23
$profile = "$env:USERPROFILE\.conan2\profiles\default"
(Get-Content $profile) -replace 'compiler=.*', 'compiler=${{ matrix.conan_compiler }}' | Set-Content $profile
(Get-Content $profile) -replace 'compiler\.version=.*', 'compiler.version=${{ matrix.conan_version }}' | Set-Content $profile
(Get-Content $profile) -replace 'compiler\.runtime=.*', 'compiler.runtime=dynamic' | Set-Content $profile
(Get-Content $profile) -replace 'compiler\.cppstd=.*', 'compiler.cppstd=23' | Set-Content $profile
(Get-Content $profile) -replace 'os=.*', 'os=Windows' | Set-Content $profile
(Get-Content $profile) -replace 'arch=.*', 'arch=x86_64' | Set-Content $profile
# Add runtime_version for Clang builds (required for Windows Clang-CL)
if ("${{ matrix.runtime_version }}" -ne "") {
Add-Content $profile "compiler.runtime_version=${{ matrix.runtime_version }}"
}
Write-Host "=== ${{ matrix.compiler_name }} Conan Profile ==="
conan profile show --profile:host=default
shell: pwsh
- name: Cache Conan packages
uses: actions/cache@v4
with:
path: ~/.conan2
key: conan-windows-${{ matrix.compiler }}-${{ matrix.build_type }}-${{ hashFiles('conanfile.py') }}
restore-keys: |
conan-windows-${{ matrix.compiler }}-${{ matrix.build_type }}-
conan-windows-${{ matrix.compiler }}-
- name: Install LLVM (for clang-format)
run: |
choco install llvm -y
- name: Setup MSVC environment
uses: ilammy/msvc-dev-cmd@v1
with:
arch: x64
- name: Clean build directory
run: |
# Clean build directory to prevent configuration conflicts between matrix jobs
if (Test-Path "build") {
Remove-Item -Recurse -Force "build"
}
shell: pwsh
- name: Install dependencies
timeout-minutes: 20
run: |
# Install Conan dependencies for Windows ${{ matrix.compiler_name }}
conan install . -s os=Windows -s build_type=${{ matrix.build_type }} --output-folder=build --build=missing -o with_gperftools=False
- name: Configure CMake
run: |
# Windows: Configure with ${{ matrix.compiler_name }} compiler using compiler-specific preset logic
if ("${{ matrix.compiler }}" -eq "msvc") {
# MSVC always uses conan-default for configure
$preset = "conan-default"
} else {
# Clang-CL uses build-type specific presets
$preset = if ("${{ matrix.build_type }}" -eq "Debug") { "conan-debug" } else { "conan-release" }
}
# Override generator for Windows builds to ensure Visual Studio generator is used
cmake --preset $preset -G "Visual Studio 17 2022" -A x64 ${{ matrix.cmake_args }}
- name: Build
run: |
# Windows: Use build-type specific build preset
$preset = if ("${{ matrix.build_type }}" -eq "Debug") { "conan-debug" } else { "conan-release" }
cmake --build --preset $preset --config ${{ matrix.build_type }} --parallel
- name: Test
run: |
# Windows builds use configuration-specific subdirectories
cd build\build\${{ matrix.build_type }}
if ("${{ matrix.build_type }}" -eq "Debug") {
.\utf_strings-tests.exe --gtest_output=xml:test_results_${{ matrix.compiler }}_debug.xml
} else {
.\utf_strings-tests.exe --gtest_output=xml:test_results_${{ matrix.compiler }}_release.xml
}
- name: Benchmark (Release only)
if: matrix.build_type == 'Release'
run: |
cd build\build\Release
.\utf_strings-bench.exe --benchmark_min_time=0.1s --benchmark_format=json --benchmark_out=benchmark_results_${{ matrix.compiler }}.json
- name: Check code formatting (Release only)
if: matrix.build_type == 'Release'
run: |
$env:PATH = "C:\Program Files\LLVM\bin;$env:PATH"
cmake --build --preset conan-release --config Release --target format-check
- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
name: test-results-windows-${{ matrix.compiler }}-${{ matrix.build_type }}
path: |
build\build\Debug\test_results_${{ matrix.compiler }}_debug.xml
build\build\Release\test_results_${{ matrix.compiler }}_release.xml
build\build\Release\benchmark_results_${{ matrix.compiler }}.json
# ============================================================================
# Windows - MSVC with AddressSanitizer
# ============================================================================
windows-msvc-debug:
name: "Windows MSVC Debug (x64)"
runs-on: windows-2022
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: "3.11"
- name: Install Conan
run: |
pip install conan
- name: Setup Conan profile
run: |
conan profile detect --force
# Use static profile for MSVC 2022 with C++23 (sanitizer)
$profile = "$env:USERPROFILE\.conan2\profiles\default"
(Get-Content $profile) -replace 'compiler=.*', 'compiler=msvc' | Set-Content $profile
(Get-Content $profile) -replace 'compiler\.version=.*', 'compiler.version=193' | Set-Content $profile
(Get-Content $profile) -replace 'compiler\.runtime=.*', 'compiler.runtime=dynamic' | Set-Content $profile
(Get-Content $profile) -replace 'compiler\.cppstd=.*', 'compiler.cppstd=23' | Set-Content $profile
(Get-Content $profile) -replace 'os=.*', 'os=Windows' | Set-Content $profile
(Get-Content $profile) -replace 'arch=.*', 'arch=x86_64' | Set-Content $profile
Write-Host "=== Conan Profile ==="
conan profile show --profile:host=default
shell: pwsh
- name: Cache Conan packages
uses: actions/cache@v4
with:
path: ~/.conan2
key: conan-windows-msvc-asan-${{ hashFiles('conanfile.py') }}
restore-keys: |
conan-windows-msvc-asan-
conan-windows-msvc-
- name: Setup MSVC environment
uses: ilammy/msvc-dev-cmd@v1
with:
arch: x64
- name: Clean build directory
run: |
# Clean build directory to prevent configuration conflicts
if (Test-Path "build") {
Remove-Item -Recurse -Force "build"
}
shell: pwsh
- name: Install dependencies
timeout-minutes: 20
run: |
# Optimize Conan builds for CI
conan config set tools.build:jobs=2
conan config set tools.build:verbosity=minimal
conan install . -s os=Windows -s build_type=Debug --output-folder=build --build=missing -o with_gperftools=False
- name: Configure CMake (Debug build without AddressSanitizer)
run: |
# Note: Windows AddressSanitizer disabled due to Conan dependency linking issues
# (annotate_string/annotate_vector mismatch between ASAN and non-ASAN builds)
# AddressSanitizer testing is comprehensively covered by Linux CI jobs
# Override generator for Windows builds to ensure Visual Studio generator is used
cmake --preset conan-default -G "Visual Studio 17 2022" -A x64
- name: Build Debug
run: cmake --build --preset conan-debug --config Debug --parallel
- name: Test Debug Build
run: |
cd build\build\Debug
.\utf_strings-tests.exe --gtest_output=xml:test_results_msvc_debug.xml
- name: Upload debug test results
uses: actions/upload-artifact@v4
if: always()
with:
name: test-results-windows-msvc-debug
path: build\build\Debug\test_results_msvc_debug.xml
# ============================================================================
# Performance Baseline Tracking
# ============================================================================
performance-tracking:
name: "Performance Baseline Tracking"
runs-on: ubuntu-24.04
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
permissions:
contents: write
pages: write
id-token: write
actions: read
steps:
- name: Checkout
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0
- name: Install system dependencies
run: |
sudo apt update
sudo apt install -y cmake python3-pip git
# Install Clang 18 for high-performance release builds
sudo apt install -y clang-18 libc++-18-dev libc++abi-18-dev
# Install profiling tools for flame graph generation
sudo apt install -y linux-tools-generic linux-tools-common perf-tools-unstable
# Install FlameGraph tools
git clone https://github.com/brendangregg/FlameGraph.git /tmp/FlameGraph
sudo cp /tmp/FlameGraph/*.pl /usr/local/bin/
sudo chmod +x /usr/local/bin/*.pl
# Set up alternatives to use Clang 18
sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-18 100
sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-18 100
- name: Set perf permissions
run: |
# Allow perf to run without root for non-paranoid mode
sudo sysctl -w kernel.perf_event_paranoid=-1
sudo sysctl -w kernel.kptr_restrict=0
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: "3.11"
- name: Install Conan
run: |
pip3 install --user conan
echo "$HOME/.local/bin" >> $GITHUB_PATH
- name: Setup Conan profile
run: |
conan profile detect --force
# Use Clang 18 with libstdc++ for performance builds (avoids GoogleTest linking issues)
sed -i 's/compiler=.*/compiler=clang/' ~/.conan2/profiles/default
sed -i 's/compiler.version=.*/compiler.version=18/' ~/.conan2/profiles/default
sed -i 's/compiler.libcxx=.*/compiler.libcxx=libstdc++11/' ~/.conan2/profiles/default
sed -i 's/compiler.cppstd=.*/compiler.cppstd=23/' ~/.conan2/profiles/default
sed -i 's/os=.*/os=Linux/' ~/.conan2/profiles/default
sed -i 's/arch=.*/arch=x86_64/' ~/.conan2/profiles/default
echo "=== Conan Profile ==="
conan profile show --profile:host=default
- name: Cache Conan packages
uses: actions/cache@v4
with:
path: ~/.conan2
key: conan-performance-baseline-${{ hashFiles('conanfile.py') }}
restore-keys: |
conan-performance-baseline-
- name: Install dependencies
run: |
export CC=clang-18
export CXX=clang++-18
conan install . -s os=Linux -s build_type=Release --output-folder=build --build=missing -o with_gperftools=True
- name: Configure and Build (Release)
run: |
export CC=clang-18
export CXX=clang++-18
cmake --preset conan-release \
-DCOMPILER_TYPE=CLANG \
-DUSE_LTO=OFF \
-DUSE_NATIVE_ARCH=ON \
-DUSE_LIBC_PLUS_PLUS=OFF \
-DENABLE_SHARED_LIBRARY=ON
cmake --build --preset conan-release --parallel
- name: Run Performance Benchmarks and Generate Flame Graphs
run: |
cd build/build
# Create docs/performance directory for all performance reports
mkdir -p ../../docs/performance
# 1. Standard benchmark run for historical tracking
echo "Running standard benchmarks..."
./utf_strings-bench --benchmark_min_time=1.0s --benchmark_format=json --benchmark_out=performance_baseline.json
# 2. Extended benchmark run for detailed analysis
echo "Running extended benchmarks..."
./utf_strings-bench --benchmark_min_time=3.0s --benchmark_format=json --benchmark_out=../../docs/performance/detailed_benchmark.json
# 3. Generate flame graphs with perf (if available)
echo "Generating flame graphs..."
if command -v perf >/dev/null 2>&1; then
# Try to run perf - may fail in containerized environment
echo "Attempting perf record with diagnostic output..."
if perf record -F 99 -g --call-graph dwarf -- ./utf_strings-bench --benchmark_min_time=2.0s --benchmark_repetitions=5; then
echo "Perf recording successful, checking file format..."
# Check perf file format and try to process
if perf script -v > /dev/null 2>&1; then
echo "Perf file format compatible, generating flame graph..."
perf script > ../../docs/performance/perf.script
if stackcollapse-perf.pl ../../docs/performance/perf.script > ../../docs/performance/perf.folded; then
flamegraph.pl ../../docs/performance/perf.folded > ../../docs/performance/flamegraph.svg
echo "Flame graph generated successfully"
else
echo "Stack collapse failed, creating placeholder"
echo "Perf data processing failed during stack collapse step. File format may be incompatible." > ../../docs/performance/flamegraph_unavailable.txt
fi
else
echo "Perf file format incompatible, creating placeholder with diagnostic info"
perf script -v 2>&1 | head -20 > ../../docs/performance/perf_debug.txt || true
echo "Perf file format incompatible with current perf version. This can happen in containerized environments. Debug info saved to perf_debug.txt" > ../../docs/performance/flamegraph_unavailable.txt
fi
else
echo "Perf recording failed (likely due to container restrictions), creating placeholder"
echo "Flame graph generation requires privileged container access. Run locally with: perf record -g ./utf_strings-bench && perf script | stackcollapse-perf.pl | flamegraph.pl > flamegraph.svg" > ../../docs/performance/flamegraph_unavailable.txt
fi
else
echo "Perf not available, skipping flame graph generation"
echo "Perf tools not found. Install with: sudo apt install linux-tools-generic" > ../../docs/performance/flamegraph_unavailable.txt
fi
# 4. Generate comprehensive benchmark report with multiple formats
echo "Generating comprehensive reports..."
./utf_strings-bench --benchmark_format=console --benchmark_min_time=1.0s > ../../docs/performance/benchmark_console.txt
./utf_strings-bench --benchmark_format=csv --benchmark_min_time=1.0s --benchmark_out=../../docs/performance/benchmark_results.csv
# 5. System information for performance context
echo "Collecting system information..."
cat > ../../docs/performance/system_info.txt << EOF
Performance Benchmark System Information
Generated: $(date)
Build: Clang 18 Release with libc++
CPU Information:
$(cat /proc/cpuinfo | grep "model name" | head -1)
$(cat /proc/cpuinfo | grep "cpu MHz" | head -1)
$(cat /proc/cpuinfo | grep "cache size" | head -1)
CPU Cores: $(nproc)
Memory Information:
$(free -h)
Compiler Version:
$(clang++-18 --version)
Build Flags Used:
Release build with -O3 -march=native -mtune=native -flto
EOF
echo "Performance analysis complete!"
ls -la ../../docs/performance/
- name: Setup docs directory and create performance index
run: |
# Configure git identity for GitHub Actions
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git config --global user.name "github-actions[bot]"
# Set up authentication for git push
git remote set-url origin "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git"
# Ensure docs directories exist
mkdir -p docs/dev/bench
mkdir -p docs/performance
# Ensure .nojekyll exists to disable Jekyll processing
touch docs/.nojekyll
# Create performance index page
cat > docs/performance/index.html << 'EOF'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>UTF Strings - Performance Analysis</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
line-height: 1.6;
color: #333;
}
.header {
text-align: center;
margin-bottom: 3rem;
padding-bottom: 2rem;
border-bottom: 2px solid #eee;
}
.card {
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 8px;
padding: 1.5rem;
margin: 1rem 0;
}
.card h2 {
margin-top: 0;
color: #495057;
}
.btn {
display: inline-block;
padding: 0.5rem 1rem;
background: #007bff;
color: white;
text-decoration: none;
border-radius: 4px;
margin: 0.5rem 0.5rem 0.5rem 0;
}
.btn:hover {
background: #0056b3;
}
.flame-btn {
background: #fd7e14;
}
.flame-btn:hover {
background: #e8690a;
}
.data-btn {
background: #28a745;
}
.data-btn:hover {
background: #1e7e34;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1rem;
}
.build-info {
background: #e7f3ff;
border-left: 4px solid #007bff;
padding: 1rem;
margin: 1rem 0;
}
iframe {
width: 100%;
height: 600px;
border: 1px solid #ddd;
border-radius: 4px;
}
</style>
</head>
<body>
<div class="header">
<h1>UTF Strings Performance Analysis</h1>
<p>Comprehensive performance benchmarks and profiling results</p>
<div class="build-info">
<strong>Build Configuration:</strong> Clang 18 Release with libc++, -O3 -march=native -flto
<br><strong>Last Updated:</strong> <span id="timestamp"></span>
</div>
</div>
<div class="grid">
<div class="card">
<h2>Flame Graph Analysis</h2>
<p>Interactive flame graph showing CPU time distribution and hot paths in the UTF string processing functions.</p>
<a href="flamegraph.svg" class="btn flame-btn">View Flame Graph</a>
<a href="perf.script" class="btn">Raw Perf Data</a>
</div>
<div class="card">
<h2>Historical Benchmarks</h2>
<p>Interactive charts showing performance trends over time with automatic regression detection.</p>
<a href="../dev/bench/" class="btn">View Historical Charts</a>
</div>
<div class="card">
<h2>Detailed Results</h2>
<p>Comprehensive benchmark results in multiple formats for analysis and comparison.</p>
<a href="detailed_benchmark.json" class="btn data-btn">JSON Results</a>
<a href="benchmark_results.csv" class="btn data-btn">CSV Data</a>
<a href="benchmark_console.txt" class="btn">Console Output</a>
</div>
<div class="card">
<h2>System Information</h2>
<p>Hardware and build environment details for performance context.</p>
<a href="system_info.txt" class="btn">System Info</a>
</div>
</div>
<div class="card">
<h2>Interactive Flame Graph</h2>
<p>Embedded flame graph showing function call patterns and CPU time distribution:</p>
<iframe src="flamegraph.svg" title="Performance Flame Graph"></iframe>
</div>
<script>
document.getElementById('timestamp').textContent = new Date().toLocaleString();
</script>
</body>
</html>
EOF
# Create a basic README for docs if it doesn't exist
if [ ! -f docs/README.md ]; then
echo "# UTF Strings Documentation" > docs/README.md
echo "This directory contains automated performance benchmark results and documentation." >> docs/README.md
fi
- name: Commit Performance Analysis Reports
run: |
# Add all generated performance files to git
git add docs/performance/
# Check if there are changes to commit
if ! git diff --cached --quiet; then
git commit -m "Update performance analysis reports
- Detailed benchmark results (JSON, CSV, console)
- Flame graph analysis (if available)
- System information and build context
- Generated on: $(date)
- Build: Clang 18 Release with libc++"
# Push the performance reports
git push origin main
echo "Performance reports committed and pushed successfully"
else
echo "No changes in performance reports to commit"
fi
- name: Store Performance Results
uses: benchmark-action/github-action-benchmark@v1
with:
tool: "googlecpp"
output-file-path: build/build/performance_baseline.json
github-token: ${{ secrets.GITHUB_TOKEN }}
auto-push: true
alert-threshold: "150%"
comment-on-alert: true
fail-on-alert: false
# Use gh-pages branch for benchmark data storage
gh-pages-branch: "gh-pages"
benchmark-data-dir-path: "dev/bench"
- name: Setup Pages
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: actions/configure-pages@v4
- name: Upload Pages Artifact
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: actions/upload-pages-artifact@v3
with:
path: docs/
- name: Deploy to GitHub Pages
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: actions/deploy-pages@v4
# ============================================================================
# Publish Test Results
# ============================================================================
publish-test-results:
name: "Publish Test Results"
runs-on: ubuntu-latest
needs:
[
linux-gcc,
linux-clang,
windows-msvc,
linux-sanitizers,
windows-msvc-debug,
]
if: always()
permissions:
contents: read
issues: read
checks: write
pull-requests: write
steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
- name: Publish Unit Test Results
uses: EnricoMi/publish-unit-test-result-action@v2
if: always()
with:
files: "**/*.xml"
comment_mode: always
check_name: "Unit Test Results"
report_individual_runs: true
deduplicate_classes_by_file_name: false