From f2aa0ca8f365fd548bb7ae5fe2c91b8766d93604 Mon Sep 17 00:00:00 2001 From: mattcapa Date: Wed, 24 Jun 2026 17:44:15 +0200 Subject: [PATCH] ci(windows): add Windows build workflow with openblas overlay and Fortran env passthrough - .github/workflows/windows-build.yml: build seahowl on windows-latest via vcpkg manifest mode. Uses the runner's bundled mingw gfortran (no MSYS2 install needed), strips every flang*.exe directory from PATH (discovered via Get-Command -All so we are not fooled by reinjection from vcvars), exports FC and the sanitized PATH for downstream steps and vcpkg port builds. Caches vcpkg binaries via x-gha. Collects per-port vcpkg logs on failure. - external/vcpkg/ports/blas, external/vcpkg/ports/lapack: empty overlay metaports that route BLAS/LAPACK to openblas instead of lapack-reference, so we do not need a Fortran compiler just to satisfy find_package(BLAS). - external/vcpkg/triplets/x64-windows.cmake: overlay triplet identical to upstream x64-windows but with VCPKG_ENV_PASSTHROUGH=FC PATH so the Fortran configuration propagates into every port's inner build (openfast). --- .github/workflows/windows-build.yml | 219 +++++++++++++++++++++ external/vcpkg/ports/blas/portfile.cmake | 5 + external/vcpkg/ports/blas/vcpkg.json | 10 + external/vcpkg/ports/lapack/portfile.cmake | 7 + external/vcpkg/ports/lapack/vcpkg.json | 10 + external/vcpkg/triplets/x64-windows.cmake | 16 ++ 6 files changed, 267 insertions(+) create mode 100644 .github/workflows/windows-build.yml create mode 100644 external/vcpkg/ports/blas/portfile.cmake create mode 100644 external/vcpkg/ports/blas/vcpkg.json create mode 100644 external/vcpkg/ports/lapack/portfile.cmake create mode 100644 external/vcpkg/ports/lapack/vcpkg.json create mode 100644 external/vcpkg/triplets/x64-windows.cmake diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml new file mode 100644 index 00000000..22b8717d --- /dev/null +++ b/.github/workflows/windows-build.yml @@ -0,0 +1,219 @@ +name: Windows build + +on: + push: + branches: [main, dev] + pull_request: + branches: [main, dev] + workflow_dispatch: + +# Cancel in-progress runs on the same ref when a new commit arrives. +concurrency: + group: windows-build-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + name: Build (x64-windows) + runs-on: windows-latest + timeout-minutes: 180 + + env: + VCPKG_ROOT: ${{ github.workspace }}\external\vcpkg + # Cache built vcpkg packages in the GH Actions cache. + VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite" + VCPKG_DISABLE_METRICS: "1" + # Overlay triplet sets VCPKG_ENV_PASSTHROUGH=FC PATH so the Fortran + # compiler we configure below reaches openfast's inner build. + VCPKG_OVERLAY_TRIPLETS: ${{ github.workspace }}\external\vcpkg\triplets + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Set up MSVC dev environment + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: x64 + + - name: Install Ninja + uses: seanmiddleditch/gha-setup-ninja@v5 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + # The windows-latest runner already ships a usable mingw gfortran (at + # C:\mingw64\bin and/or via Strawberry Perl at C:\Strawberry\c\bin). + # We do NOT install MSYS2 to keep the workflow minimal and avoid flaky + # install steps. We just locate the existing gfortran, strip every + # flang directory from PATH, and prepend the gfortran directory. + - name: Configure Fortran toolchain on PATH + shell: pwsh + run: | + $ErrorActionPreference = 'Stop' + + # 1. Locate gfortran from well-known runner locations, then PATH. + $candidates = @( + 'C:\mingw64\bin\gfortran.exe', + 'C:\msys64\mingw64\bin\gfortran.exe', + 'C:\Strawberry\c\bin\gfortran.exe' + ) + $gfortran = $null + foreach ($c in $candidates) { + if (Test-Path $c) { $gfortran = $c; break } + } + if (-not $gfortran) { + $cmd = Get-Command gfortran -ErrorAction SilentlyContinue | + Select-Object -First 1 + if ($cmd) { $gfortran = $cmd.Source } + } + if (-not $gfortran) { + throw "gfortran.exe not found. Checked: $($candidates -join ', '). Also not on PATH." + } + $mingwBin = Split-Path $gfortran -Parent + Write-Host "Using gfortran: $gfortran" + + # 2. Discover every directory hosting a flang*.exe on the current + # PATH (regardless of folder name). Strip them. + $flangDirs = @() + foreach ($exe in 'flang.exe','flang-new.exe','flang-classic.exe') { + $found = Get-Command $exe -All -ErrorAction SilentlyContinue + if ($found) { + $flangDirs += $found | ForEach-Object { Split-Path $_.Source -Parent } + } + } + $flangDirs = @($flangDirs | Sort-Object -Unique) + if ($flangDirs.Count -gt 0) { + Write-Host "Stripping flang directories from PATH:" + $flangDirs | ForEach-Object { Write-Host " $_" } + } else { + Write-Host "No flang directories detected on PATH." + } + + # 3. Build sanitized PATH: mingw bin first. + $kept = $env:PATH.Split(';') | Where-Object { + $_ -and + ($flangDirs -notcontains $_.TrimEnd('\')) -and + ($flangDirs -notcontains $_) + } + $newPath = "$mingwBin;" + ($kept -join ';') + + # 4. Persist for subsequent steps + every vcpkg port build (via the + # overlay triplet's VCPKG_ENV_PASSTHROUGH=FC PATH). + "PATH=$newPath" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 + "FC=$gfortran" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 + + - name: Verify Fortran configuration + shell: pwsh + run: | + $ErrorActionPreference = 'Stop' + + Write-Host "FC = $env:FC" + if (-not (Test-Path $env:FC)) { + throw "FC path '$env:FC' does not exist" + } + $ver = & $env:FC --version | Select-Object -First 1 + Write-Host " $ver" + + $stillFlang = foreach ($exe in 'flang.exe','flang-new.exe','flang-classic.exe') { + Get-Command $exe -ErrorAction SilentlyContinue + } + $stillFlang = @($stillFlang | Where-Object { $_ }) + if ($stillFlang.Count -gt 0) { + Write-Host "::warning::flang still visible on PATH:" + $stillFlang | ForEach-Object { Write-Host " $($_.Source)" } + } else { + Write-Host "OK: no flang*.exe on PATH" + } + + - name: Bootstrap vcpkg + shell: pwsh + run: | + $ErrorActionPreference = 'Stop' + if (-not (Test-Path "$env:VCPKG_ROOT\vcpkg.exe")) { + & "$env:VCPKG_ROOT\bootstrap-vcpkg.bat" -disableMetrics + if ($LASTEXITCODE -ne 0) { throw "vcpkg bootstrap failed (exit $LASTEXITCODE)" } + } + & "$env:VCPKG_ROOT\vcpkg.exe" version | Select-Object -First 1 + + # Required for VCPKG_BINARY_SOURCES=x-gha + - name: Export GitHub Actions cache env for vcpkg + uses: actions/github-script@v7 + with: + script: | + core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); + core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); + + - name: Configure (default preset) + shell: pwsh + run: cmake --preset default + + - name: Build + shell: pwsh + run: cmake --build build --config Release + + - name: Test + shell: pwsh + working-directory: build + run: ctest --output-on-failure -C Release + + # --- Diagnostics on failure --------------------------------------------- + + - name: Collect failure logs + if: failure() + shell: pwsh + run: | + $ErrorActionPreference = 'SilentlyContinue' + New-Item -ItemType Directory -Force -Path ci-logs | Out-Null + + # Per-port vcpkg build logs and issue body + $blds = 'build\vcpkg_installed\vcpkg\blds' + if (Test-Path $blds) { + Get-ChildItem $blds -Recurse -File -Include ` + 'install-*.log','config-*.log','build-*.log','issue_body.md' | + ForEach-Object { + $rel = $_.FullName.Substring((Resolve-Path '.').Path.Length + 1) + $dest = Join-Path 'ci-logs' $rel + New-Item -ItemType Directory -Force -Path (Split-Path $dest -Parent) | Out-Null + Copy-Item $_.FullName $dest + } + } + + # CMake configure-time logs + @('build\CMakeFiles\CMakeOutput.log', + 'build\CMakeFiles\CMakeError.log', + 'build\CMakeCache.txt', + 'build\vcpkg-manifest-install.log') | + Where-Object { Test-Path $_ } | + ForEach-Object { + $dest = Join-Path 'ci-logs' $_ + New-Item -ItemType Directory -Force -Path (Split-Path $dest -Parent) | Out-Null + Copy-Item $_ $dest + } + + Write-Host "Collected log files:" + Get-ChildItem ci-logs -Recurse -File | Select-Object -ExpandProperty FullName + + - name: Upload failure logs + if: failure() + uses: actions/upload-artifact@v4 + with: + name: windows-build-failure-logs + path: ci-logs + if-no-files-found: ignore + retention-days: 7 + + - name: Upload build artifacts + if: success() + uses: actions/upload-artifact@v4 + with: + name: seahowl-windows-x64 + path: | + build/**/*.exe + build/**/*.dll + build/**/*.pyd + if-no-files-found: ignore diff --git a/external/vcpkg/ports/blas/portfile.cmake b/external/vcpkg/ports/blas/portfile.cmake new file mode 100644 index 00000000..c561f247 --- /dev/null +++ b/external/vcpkg/ports/blas/portfile.cmake @@ -0,0 +1,5 @@ +# Empty overlay metaport: the actual BLAS implementation is provided by the +# `openblas` dependency declared in vcpkg.json. This overlay overrides vcpkg's +# default `blas` metaport so it does not pull in `lapack-reference` (which +# would require a working Fortran compiler on Windows). +set(VCPKG_POLICY_EMPTY_PACKAGE enabled) diff --git a/external/vcpkg/ports/blas/vcpkg.json b/external/vcpkg/ports/blas/vcpkg.json new file mode 100644 index 00000000..268b8ffc --- /dev/null +++ b/external/vcpkg/ports/blas/vcpkg.json @@ -0,0 +1,10 @@ +{ + "name": "blas", + "version-date": "2024-01-01", + "port-version": 0, + "description": "Overlay metaport that routes BLAS to OpenBLAS so the build does not require a Fortran compiler for lapack-reference on Windows.", + "homepage": "https://github.com/microsoft/vcpkg", + "dependencies": [ + "openblas" + ] +} diff --git a/external/vcpkg/ports/lapack/portfile.cmake b/external/vcpkg/ports/lapack/portfile.cmake new file mode 100644 index 00000000..8dacf824 --- /dev/null +++ b/external/vcpkg/ports/lapack/portfile.cmake @@ -0,0 +1,7 @@ +# Empty overlay metaport: the actual LAPACK implementation is provided by the +# `openblas` dependency declared in vcpkg.json. OpenBLAS bundles the reference +# LAPACK Fortran sources compiled with its own toolchain, so consumers of +# `find_package(LAPACK)` get a working LAPACK without seahowl needing to build +# `lapack-reference` (which would require a working Fortran compiler on +# Windows). +set(VCPKG_POLICY_EMPTY_PACKAGE enabled) diff --git a/external/vcpkg/ports/lapack/vcpkg.json b/external/vcpkg/ports/lapack/vcpkg.json new file mode 100644 index 00000000..ffca704c --- /dev/null +++ b/external/vcpkg/ports/lapack/vcpkg.json @@ -0,0 +1,10 @@ +{ + "name": "lapack", + "version-date": "2024-01-01", + "port-version": 0, + "description": "Overlay metaport that routes LAPACK to OpenBLAS so the build does not require a Fortran compiler for lapack-reference on Windows.", + "homepage": "https://github.com/microsoft/vcpkg", + "dependencies": [ + "openblas" + ] +} diff --git a/external/vcpkg/triplets/x64-windows.cmake b/external/vcpkg/triplets/x64-windows.cmake new file mode 100644 index 00000000..973f84a1 --- /dev/null +++ b/external/vcpkg/triplets/x64-windows.cmake @@ -0,0 +1,16 @@ +# Overlay triplet for x64-windows used by this project's CI. +# +# Identical to the upstream `x64-windows` triplet, except it forwards the +# Fortran compiler (FC) and PATH from the runner's environment into every +# vcpkg port's inner build. Without this passthrough, vcpkg sanitizes the +# environment between ports, which is what causes the VS-bundled LLVMFlang +# to be picked up for openfast even after we have configured a different +# Fortran compiler on the outer PATH. +# +# Activated by setting VCPKG_OVERLAY_TRIPLETS to this folder in the workflow. + +set(VCPKG_TARGET_ARCHITECTURE x64) +set(VCPKG_CRT_LINKAGE dynamic) +set(VCPKG_LIBRARY_LINKAGE dynamic) + +set(VCPKG_ENV_PASSTHROUGH FC PATH)