Build and Release #86
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build and Release | |
| # Builds the native standalone MovingBoundarySolver executable (+ static lib) | |
| # and attaches them to a GitHub Release on each v* tag. | |
| # | |
| # Native source builds are exercised on Linux, macOS, and Windows here. | |
| # Python wheels are built/published separately by wheels.yml; Windows wheels | |
| # remain disabled there even though the source build is covered here. | |
| on: | |
| push: | |
| tags: | |
| - 'v*' | |
| workflow_dispatch: | |
| jobs: | |
| build: | |
| name: Build on ${{ matrix.os }} | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest, windows-latest] | |
| include: | |
| - os: ubuntu-latest | |
| artifact_prefix: linux | |
| - os: macos-latest | |
| artifact_prefix: macos | |
| - os: windows-latest | |
| artifact_prefix: windows | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: '24' | |
| - name: Configure git for private submodules | |
| shell: bash | |
| run: | | |
| git config --global url."https://x-access-token:${{ secrets.SUBMODULE_TOKEN }}@github.com/".insteadOf "https://github.com/" | |
| - name: Initialize submodules | |
| shell: bash | |
| run: git submodule update --init --recursive | |
| - name: Install Ubuntu dependencies | |
| if: runner.os == 'Linux' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y cmake libhdf5-dev python3-dev python3-pip pybind11-dev libboost-dev libcurl4-openssl-dev | |
| pip3 install pybind11 | |
| - name: Install macOS dependencies | |
| if: runner.os == 'macOS' | |
| run: | | |
| brew install cmake hdf5 python pybind11 boost curl | |
| - name: Install Windows dependencies | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| choco install strawberryperl -y | |
| $vcpkgRoot = if ($env:VCPKG_INSTALLATION_ROOT) { | |
| $env:VCPKG_INSTALLATION_ROOT | |
| } elseif (Test-Path 'C:\vcpkg\vcpkg.exe') { | |
| 'C:\vcpkg' | |
| } else { | |
| throw 'vcpkg.exe not found on the runner' | |
| } | |
| & "$vcpkgRoot\vcpkg.exe" install ` | |
| --triplet x64-windows ` | |
| curl ` | |
| hdf5[cpp] ` | |
| boost ` | |
| pybind11 | |
| "VCPKG_ROOT=$vcpkgRoot" >> $env:GITHUB_ENV | |
| "VCPKG_DEFAULT_TRIPLET=x64-windows" >> $env:GITHUB_ENV | |
| "CMAKE_TOOLCHAIN_FILE=$vcpkgRoot\scripts\buildsystems\vcpkg.cmake" >> $env:GITHUB_ENV | |
| "CMAKE_PREFIX_PATH=$vcpkgRoot\installed\x64-windows" >> $env:GITHUB_ENV | |
| - name: Verify Windows deps | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| Write-Host "Checking expected headers/libs from vcpkg..." | |
| if (!(Test-Path "$env:VCPKG_ROOT\installed\x64-windows\include\H5Cpp.h")) { | |
| throw "Missing H5Cpp.h at include\H5Cpp.h" | |
| } | |
| if (!(Test-Path "$env:VCPKG_ROOT\installed\x64-windows\lib\hdf5_cpp.lib")) { | |
| throw "Missing hdf5_cpp.lib" | |
| } | |
| if (!(Test-Path "$env:VCPKG_ROOT\installed\x64-windows\lib\libcurl.lib")) { | |
| throw "Missing libcurl.lib" | |
| } | |
| - name: Windows preflight (collision + header pollution) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| # ---------- scan roots / file sets ---------- | |
| $roots = @("Solver", "FronTierLib") | |
| $codeExt = @("*.h","*.hpp","*.hh","*.hxx","*.c","*.cc","*.cpp","*.cxx") | |
| $headerExt = @("*.h","*.hpp","*.hh","*.hxx") | |
| $codeFiles = foreach ($r in $roots) { | |
| if (Test-Path $r) { Get-ChildItem $r -Recurse -File -Include $codeExt } | |
| } | |
| $headerFiles = foreach ($r in $roots) { | |
| if (Test-Path $r) { Get-ChildItem $r -Recurse -File -Include $headerExt } | |
| } | |
| # ---------- check 1: dangerous global names (tight patterns) ---------- | |
| $collisionPatterns = @( | |
| '^\s*#\s*define\s+POINT\b', | |
| '^\s*typedef\s+.+\bPOINT\b\s*;', | |
| '^\s*using\s+POINT\s*=', | |
| '^\s*#\s*define\s+FIXED\b', | |
| '^\s*typedef\s+.+\bFIXED\b\s*;', | |
| '^\s*using\s+FIXED\s*=', | |
| '^\s*#\s*define\s+Sleep\b', | |
| '^\s*(extern\s+)?(inline\s+)?(static\s+)?\w[\w\s\*:&<>]*\bSleep\s*\(' | |
| ) | |
| $collisionHits = New-Object System.Collections.Generic.List[string] | |
| foreach ($f in $codeFiles) { | |
| $lines = Get-Content $f.FullName | |
| for ($i = 0; $i -lt $lines.Count; $i++) { | |
| $line = $lines[$i] | |
| # Skip intentional lowercase sleep aliases (e.g. #define sleep(x) FT_Sleep(x)) | |
| if ($line -cmatch '^\s*#\s*define\s+sleep\b' -or $line -cmatch '^\s*.*\bsleep\b\s*\(') { | |
| continue | |
| } | |
| foreach ($p in $collisionPatterns) { | |
| if ($line -cmatch $p) { | |
| $rel = Resolve-Path -Relative $f.FullName | |
| $collisionHits.Add("${rel}:$($i+1): $line") | |
| break | |
| } | |
| } | |
| } | |
| } | |
| # ---------- check 2: windows header pollution in project headers ---------- | |
| $winHeaderPatterns = @( | |
| '^\s*#\s*include\s*<windows\.h>', | |
| '^\s*#\s*include\s*<winsock2\.h>', | |
| '^\s*#\s*include\s*<windef\.h>', | |
| '^\s*#\s*include\s*<wingdi\.h>', | |
| '^\s*#\s*include\s*<winnt\.h>', | |
| '^\s*#\s*include\s*<synchapi\.h>' | |
| ) | |
| $headerPollutionHits = New-Object System.Collections.Generic.List[string] | |
| foreach ($h in $headerFiles) { | |
| $lines = Get-Content $h.FullName | |
| for ($i = 0; $i -lt $lines.Count; $i++) { | |
| $line = $lines[$i] | |
| foreach ($p in $winHeaderPatterns) { | |
| if ($line -match $p) { | |
| $rel = Resolve-Path -Relative $h.FullName | |
| $headerPollutionHits.Add("${rel}:$($i+1): $line") | |
| break | |
| } | |
| } | |
| } | |
| } | |
| # drop safe/local identifiers that are not Win32 global collisions | |
| $collisionHits = $collisionHits | Where-Object { | |
| ($_ -notmatch '::\s*typedef\s+TPoint<') -and | |
| ($_ -notmatch '\bPoint\b') -and | |
| ($_ -notmatch '\bFIXED_POINT\b') | |
| } | |
| # ---------- report both, fail once ---------- | |
| $hasCollision = $collisionHits.Count -gt 0 | |
| $hasPollution = $headerPollutionHits.Count -gt 0 | |
| if ($hasCollision) { | |
| Write-Host "=== Potential Windows symbol/macro collisions ===" | |
| $collisionHits | Sort-Object -Unique | ForEach-Object { Write-Host $_ } | |
| } else { | |
| Write-Host "No symbol/macro collision patterns found." | |
| } | |
| if ($hasPollution) { | |
| Write-Host "=== Windows headers included from project headers ===" | |
| $headerPollutionHits | Sort-Object -Unique | ForEach-Object { Write-Host $_ } | |
| } else { | |
| Write-Host "No Windows header pollution found in project headers." | |
| } | |
| if ($hasCollision -or $hasPollution) { | |
| throw "Windows preflight failed. Resolve reported issues before build." | |
| } | |
| Write-Host "Windows preflight passed." | |
| - name: Configure CMake (Unix) | |
| if: runner.os != 'Windows' | |
| run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DOPTION_TARGET_MESSAGING=ON | |
| - name: Configure CMake (Windows) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: > | |
| cmake -S . -B build -A x64 | |
| -DCMAKE_BUILD_TYPE=Release | |
| -DCMAKE_TOOLCHAIN_FILE="$env:CMAKE_TOOLCHAIN_FILE" | |
| -DVCPKG_TARGET_TRIPLET=x64-windows | |
| -DVCPKG_HOST_TRIPLET=x64-windows | |
| -DCMAKE_PREFIX_PATH="$env:VCPKG_ROOT\installed\x64-windows" | |
| -DHDF5_ROOT="$env:VCPKG_ROOT\installed\x64-windows" | |
| -DHDF5_INCLUDE_DIR="$env:VCPKG_ROOT\installed\x64-windows\include" | |
| -DHDF5_CXX_INCLUDE_DIR="$env:VCPKG_ROOT\installed\x64-windows\include" | |
| -DHDF5_hdf5_cpp_LIBRARY="$env:VCPKG_ROOT\installed\x64-windows\lib\hdf5_cpp.lib" | |
| -DHDF5_hdf5_LIBRARY="$env:VCPKG_ROOT\installed\x64-windows\lib\hdf5.lib" | |
| -DCURL_ROOT="$env:VCPKG_ROOT\installed\x64-windows" | |
| -DCURL_INCLUDE_DIR="$env:VCPKG_ROOT\installed\x64-windows\include" | |
| -DCURL_LIBRARY="$env:VCPKG_ROOT\installed\x64-windows\lib\libcurl.lib" | |
| -DOPTION_TARGET_MESSAGING=OFF | |
| - name: Build (Unix) | |
| if: runner.os != 'Windows' | |
| run: cmake --build build --parallel | |
| - name: Build (Windows) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: cmake --build build --config Release --parallel | |
| - name: Run tests | |
| shell: bash | |
| run: | | |
| cd build | |
| if [ "${{ runner.os }}" = "Windows" ]; then | |
| ctest -C Release --output-on-failure -j2 | |
| else | |
| ctest --output-on-failure -j"$(getconf _NPROCESSORS_ONLN)" | |
| fi | |
| - name: Prepare artifacts | |
| shell: bash | |
| run: | | |
| mkdir -p artifacts | |
| if [ "${{ runner.os }}" = "Windows" ]; then | |
| cp build/bin/Release/MovingBoundarySolver.exe artifacts/MovingBoundarySolver-${{ matrix.artifact_prefix }}.exe | |
| cp build/bin/Release/MovingBoundaryLib.lib artifacts/MovingBoundaryLib-${{ matrix.artifact_prefix }}.lib | |
| else | |
| cp build/bin/MovingBoundarySolver artifacts/MovingBoundarySolver-${{ matrix.artifact_prefix }} | |
| cp build/bin/libMovingBoundaryLib.a artifacts/libMovingBoundaryLib-${{ matrix.artifact_prefix }}.a | |
| fi | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ${{ matrix.artifact_prefix }}-artifacts | |
| path: artifacts/ | |
| retention-days: 1 | |
| release: | |
| name: Create Release | |
| needs: build | |
| runs-on: ubuntu-latest | |
| if: startsWith(github.ref, 'refs/tags/') | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: all-artifacts | |
| - name: Create Release | |
| uses: softprops/action-gh-release@v1 | |
| with: | |
| files: all-artifacts/**/* | |
| draft: false | |
| prerelease: false | |
| generate_release_notes: true | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Display release info | |
| run: | | |
| echo "Release created successfully!" | |
| find all-artifacts -type f -exec echo " - {}" \; |