From f31a14cd624e50bd20bd219666a573b8fc063b6d Mon Sep 17 00:00:00 2001 From: David Williams-Young Date: Fri, 26 Jun 2026 20:24:01 -0700 Subject: [PATCH 01/37] Add Windows CI (MSVC + clang-cl) with dependency caching Adds .github/workflows/build-and-test-windows.yaml: a two-job-per-compiler pipeline (matrix: msvc, clang-cl) on GitHub-hosted windows-latest, wiring the Windows build support from #450 into CI on parity with Linux/macOS. - deps job: vcpkg install + full cmake build, caching vcpkg_installed and the FetchContent build dir (libint2/gauxc/ecpint/blaspp/lapackpp) so the slow deps build once. Split out so the first (uncached) build gets its own 6h budget. - build-test job: restores the cache, rebuilds qdk, runs ctest, then builds the Python package (reusing the installed C++ lib) and runs pytest. - Shared composite action .github/actions/windows-msvc-env imports the VS x64 dev environment and exposes the toolset version for cache keys. Temporary push trigger on session/win_ci_cd for iteration; final triggers to be set before merge. No Windows pip wheel (PR CI only). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/actions/windows-msvc-env/action.yml | 94 +++++++ .github/workflows/build-and-test-windows.yaml | 239 ++++++++++++++++++ 2 files changed, 333 insertions(+) create mode 100644 .github/actions/windows-msvc-env/action.yml create mode 100644 .github/workflows/build-and-test-windows.yaml diff --git a/.github/actions/windows-msvc-env/action.yml b/.github/actions/windows-msvc-env/action.yml new file mode 100644 index 000000000..26f630fdc --- /dev/null +++ b/.github/actions/windows-msvc-env/action.yml @@ -0,0 +1,94 @@ +name: Set up Windows MSVC / clang-cl environment +description: > + Import the Visual Studio x64 developer environment (vcvarsall) into the job so + cl/clang-cl/ninja and the MSVC headers and libraries are available to later + steps. Exposes the resolved C++ compiler path and the MSVC toolset version for + use in cache keys. + +inputs: + compiler: + description: Compiler to set up - "msvc" (cl) or "clang-cl". + required: true + +outputs: + toolset: + description: MSVC toolset version (stable identifier for cache keys). + value: ${{ steps.setup.outputs.toolset }} + cxx-path: + description: Full path to the resolved C++ compiler. + value: ${{ steps.setup.outputs.cxx-path }} + +runs: + using: composite + steps: + - id: setup + shell: pwsh + run: | + $ErrorActionPreference = 'Stop' + + $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" + if (-not (Test-Path $vswhere)) { throw "vswhere not found at $vswhere" } + $vsPath = & $vswhere -latest -products * -property installationPath + if (-not $vsPath) { throw "No Visual Studio installation found" } + Write-Host "Visual Studio: $vsPath" + + $vcvarsall = "$vsPath\VC\Auxiliary\Build\vcvarsall.bat" + if (-not (Test-Path $vcvarsall)) { throw "vcvarsall.bat not found at $vcvarsall" } + + # Snapshot the environment, run vcvarsall x64, then diff to capture its changes. + $before = @{} + Get-ChildItem env: | ForEach-Object { $before[$_.Name] = $_.Value } + + $tmp = [System.IO.Path]::GetTempFileName() + cmd /c "`"$vcvarsall`" x64 && set > `"$tmp`"" + if ($LASTEXITCODE -ne 0) { throw "vcvarsall.bat failed with exit code $LASTEXITCODE" } + $after = @{} + Get-Content $tmp | ForEach-Object { + if ($_ -match '^([^=]+)=(.*)$') { $after[$matches[1]] = $matches[2] } + } + Remove-Item $tmp + + # clang-cl is shipped with the VS LLVM component but is not added by vcvarsall. + $clangDir = $null + if ('${{ inputs.compiler }}' -eq 'clang-cl') { + $candidates = @( + "$vsPath\VC\Tools\Llvm\x64\bin\clang-cl.exe", + "$vsPath\VC\Tools\Llvm\bin\clang-cl.exe" + ) + foreach ($c in $candidates) { if (Test-Path $c) { $clangDir = Split-Path $c; break } } + if (-not $clangDir) { throw "clang-cl.exe not found under $vsPath\VC\Tools\Llvm" } + } + + # Apply to the current process so the compiler resolves within this step. + foreach ($name in $after.Keys) { + [System.Environment]::SetEnvironmentVariable($name, $after[$name], 'Process') + } + if ($clangDir) { $env:PATH = "$clangDir;$env:PATH" } + + # Persist non-PATH changes to GITHUB_ENV and PATH additions to GITHUB_PATH. + foreach ($name in $after.Keys) { + if ($name -ieq 'Path') { continue } + if ($before[$name] -ne $after[$name]) { + "$name=$($after[$name])" >> $env:GITHUB_ENV + } + } + $beforePath = @($before['Path'] -split ';') + $newEntries = @($after['Path'] -split ';') | Where-Object { $_ -and ($beforePath -notcontains $_) } + if ($clangDir) { $clangDir >> $env:GITHUB_PATH } + foreach ($p in $newEntries) { $p >> $env:GITHUB_PATH } + + # Resolve the compiler and a stable toolset version for cache keys. + if ('${{ inputs.compiler }}' -eq 'clang-cl') { + $cxx = (Get-Command clang-cl.exe -ErrorAction Stop).Source + } else { + $cxx = (Get-Command cl.exe -ErrorAction Stop).Source + } + Write-Host "C++ compiler: $cxx" + if ('${{ inputs.compiler }}' -eq 'clang-cl') { & $cxx --version 2>&1 | Write-Host } + + $toolsetFile = "$vsPath\VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt" + $toolset = (Get-Content $toolsetFile -ErrorAction Stop | Select-Object -First 1).Trim() + Write-Host "MSVC toolset: $toolset" + + "toolset=$toolset" >> $env:GITHUB_OUTPUT + "cxx-path=$cxx" >> $env:GITHUB_OUTPUT diff --git a/.github/workflows/build-and-test-windows.yaml b/.github/workflows/build-and-test-windows.yaml new file mode 100644 index 000000000..3282c7578 --- /dev/null +++ b/.github/workflows/build-and-test-windows.yaml @@ -0,0 +1,239 @@ +name: Build and Test (Windows) + +on: + push: + branches: + - session/win_ci_cd + workflow_dispatch: + inputs: + run_slow_tests: + description: Run slow tests + required: false + default: false + type: boolean + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +env: + VCPKG_TRIPLET: x64-windows-static-md + QDK_UARCH: x86-64-v3 + CMAKE_BUILD_PARALLEL_LEVEL: '4' + # Bump to force a rebuild of the cached dependencies. + DEPS_CACHE_VERSION: v1 + +jobs: + # ---------------------------------------------------------------------------- + # Job 1: build and cache the (slow) C++ dependencies. + # + # GitHub-hosted windows runners have a hard 6h per-job limit. The first + # uncached build of libint2/gauxc/etc. can approach that, so the dependency + # build gets its own job (and 6h budget); the build-test job below restores + # the warmed cache and only rebuilds qdk + runs the tests. + # ---------------------------------------------------------------------------- + deps: + name: ${{ matrix.compiler }} - Build & cache dependencies + runs-on: windows-latest + timeout-minutes: 360 + strategy: + fail-fast: false + matrix: + compiler: [msvc, clang-cl] + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + submodules: recursive + + - name: Set up ${{ matrix.compiler }} environment + id: env + uses: ./.github/actions/windows-msvc-env + with: + compiler: ${{ matrix.compiler }} + + - name: Cache vcpkg + dependency build + id: cache + uses: actions/cache@v5 + with: + path: | + vcpkg_installed + cpp/build-${{ matrix.compiler }} + key: >- + windows-deps-${{ matrix.compiler }}-${{ env.VCPKG_TRIPLET }}-vs${{ steps.env.outputs.toolset }}-${{ env.DEPS_CACHE_VERSION }}-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json', 'vcpkg-overlay/**', 'cpp/manifest/qdk-chemistry/cgmanifest.json', 'external/macis/manifest/cgmanifest.json', 'cpp/cmake/third_party.cmake', 'cpp/cmake/patches/**', 'cpp/cmake/modules/DependencyManager.cmake', 'external/macis/src/lobpcgxx/CMakeLists.txt', '.pipelines/toolchains/windows.cmake') }} + + - name: Install vcpkg dependencies + if: steps.cache.outputs.cache-hit != 'true' + shell: pwsh + run: | + $vcpkg = $env:VCPKG_INSTALLATION_ROOT + if (-not $vcpkg) { $vcpkg = 'C:\vcpkg' } + & "$vcpkg\vcpkg.exe" install ` + --triplet ${{ env.VCPKG_TRIPLET }} ` + --x-manifest-root="${{ github.workspace }}" ` + --x-install-root="${{ github.workspace }}\vcpkg_installed" ` + --overlay-ports="${{ github.workspace }}\vcpkg-overlay\ports" + if ($LASTEXITCODE -ne 0) { throw "vcpkg install failed ($LASTEXITCODE)" } + + - name: Configure & build dependencies (full warm build) + if: steps.cache.outputs.cache-hit != 'true' + shell: pwsh + run: | + $vcpkg = $env:VCPKG_INSTALLATION_ROOT + if (-not $vcpkg) { $vcpkg = 'C:\vcpkg' } + cmake -S cpp -B "cpp/build-${{ matrix.compiler }}" -GNinja ` + -DQDK_UARCH=${{ env.QDK_UARCH }} ` + -DQDK_CHEMISTRY_ENABLE_COVERAGE=OFF ` + -DQDK_CHEMISTRY_ENABLE_MPI=OFF ` + -DMACIS_ENABLE_TESTS=ON ` + -DBUILD_SHARED_LIBS=OFF ` + -DBUILD_TESTING=ON ` + -DCMAKE_BUILD_TYPE=Release ` + -DCMAKE_C_COMPILER="${{ steps.env.outputs.cxx-path }}" ` + -DCMAKE_CXX_COMPILER="${{ steps.env.outputs.cxx-path }}" ` + -DCMAKE_INSTALL_PREFIX="${{ github.workspace }}\install-${{ matrix.compiler }}" ` + -DCMAKE_TOOLCHAIN_FILE="$vcpkg\scripts\buildsystems\vcpkg.cmake" ` + -DVCPKG_CHAINLOAD_TOOLCHAIN_FILE="${{ github.workspace }}\.pipelines\toolchains\windows.cmake" ` + -DVCPKG_TARGET_TRIPLET=${{ env.VCPKG_TRIPLET }} ` + -DVCPKG_INSTALLED_DIR="${{ github.workspace }}\vcpkg_installed" ` + -DFETCHCONTENT_QUIET=OFF + if ($LASTEXITCODE -ne 0) { throw "CMake configure failed ($LASTEXITCODE)" } + cmake --build "cpp/build-${{ matrix.compiler }}" + if ($LASTEXITCODE -ne 0) { throw "CMake build failed ($LASTEXITCODE)" } + + # ---------------------------------------------------------------------------- + # Job 2: rebuild qdk against the cached dependencies, then run the C++ and + # Python test suites. + # ---------------------------------------------------------------------------- + build-test: + name: ${{ matrix.compiler }} - Build & test + runs-on: windows-latest + timeout-minutes: 360 + needs: deps + strategy: + fail-fast: false + matrix: + compiler: [msvc, clang-cl] + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + submodules: recursive + + - name: Set up ${{ matrix.compiler }} environment + id: env + uses: ./.github/actions/windows-msvc-env + with: + compiler: ${{ matrix.compiler }} + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: '3.13' + + - name: Restore vcpkg + dependency build + id: cache + uses: actions/cache/restore@v5 + with: + path: | + vcpkg_installed + cpp/build-${{ matrix.compiler }} + key: >- + windows-deps-${{ matrix.compiler }}-${{ env.VCPKG_TRIPLET }}-vs${{ steps.env.outputs.toolset }}-${{ env.DEPS_CACHE_VERSION }}-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json', 'vcpkg-overlay/**', 'cpp/manifest/qdk-chemistry/cgmanifest.json', 'external/macis/manifest/cgmanifest.json', 'cpp/cmake/third_party.cmake', 'cpp/cmake/patches/**', 'cpp/cmake/modules/DependencyManager.cmake', 'external/macis/src/lobpcgxx/CMakeLists.txt', '.pipelines/toolchains/windows.cmake') }} + + - name: Install vcpkg dependencies (cache-miss fallback) + if: steps.cache.outputs.cache-hit != 'true' + shell: pwsh + run: | + $vcpkg = $env:VCPKG_INSTALLATION_ROOT + if (-not $vcpkg) { $vcpkg = 'C:\vcpkg' } + & "$vcpkg\vcpkg.exe" install ` + --triplet ${{ env.VCPKG_TRIPLET }} ` + --x-manifest-root="${{ github.workspace }}" ` + --x-install-root="${{ github.workspace }}\vcpkg_installed" ` + --overlay-ports="${{ github.workspace }}\vcpkg-overlay\ports" + if ($LASTEXITCODE -ne 0) { throw "vcpkg install failed ($LASTEXITCODE)" } + + - name: Configure & build C++ library + shell: pwsh + run: | + $vcpkg = $env:VCPKG_INSTALLATION_ROOT + if (-not $vcpkg) { $vcpkg = 'C:\vcpkg' } + cmake -S cpp -B "cpp/build-${{ matrix.compiler }}" -GNinja ` + -DQDK_UARCH=${{ env.QDK_UARCH }} ` + -DQDK_CHEMISTRY_ENABLE_COVERAGE=OFF ` + -DQDK_CHEMISTRY_ENABLE_MPI=OFF ` + -DMACIS_ENABLE_TESTS=ON ` + -DBUILD_SHARED_LIBS=OFF ` + -DBUILD_TESTING=ON ` + -DCMAKE_BUILD_TYPE=Release ` + -DCMAKE_C_COMPILER="${{ steps.env.outputs.cxx-path }}" ` + -DCMAKE_CXX_COMPILER="${{ steps.env.outputs.cxx-path }}" ` + -DCMAKE_INSTALL_PREFIX="${{ github.workspace }}\install-${{ matrix.compiler }}" ` + -DCMAKE_TOOLCHAIN_FILE="$vcpkg\scripts\buildsystems\vcpkg.cmake" ` + -DVCPKG_CHAINLOAD_TOOLCHAIN_FILE="${{ github.workspace }}\.pipelines\toolchains\windows.cmake" ` + -DVCPKG_TARGET_TRIPLET=${{ env.VCPKG_TRIPLET }} ` + -DVCPKG_INSTALLED_DIR="${{ github.workspace }}\vcpkg_installed" ` + -DFETCHCONTENT_QUIET=OFF + if ($LASTEXITCODE -ne 0) { throw "CMake configure failed ($LASTEXITCODE)" } + cmake --build "cpp/build-${{ matrix.compiler }}" + if ($LASTEXITCODE -ne 0) { throw "CMake build failed ($LASTEXITCODE)" } + + - name: Run C++ tests + shell: pwsh + env: + OMP_NUM_THREADS: '2' + run: | + Push-Location "cpp/build-${{ matrix.compiler }}" + ctest --output-on-failure --verbose --timeout 400 ` + --output-junit ctest_results.xml ` + -E "MACIS_SERIAL_TEST" + $code = $LASTEXITCODE + Pop-Location + if ($code -ne 0) { throw "ctest failed ($code)" } + + - name: Install C++ library + shell: pwsh + run: | + cmake --install "cpp/build-${{ matrix.compiler }}" + if ($LASTEXITCODE -ne 0) { throw "CMake install failed ($LASTEXITCODE)" } + + - name: Build & install Python package + shell: pwsh + run: | + $vcpkg = $env:VCPKG_INSTALLATION_ROOT + if (-not $vcpkg) { $vcpkg = 'C:\vcpkg' } + $prefix = "${{ github.workspace }}\vcpkg_installed\${{ env.VCPKG_TRIPLET }};${{ github.workspace }}\install-${{ matrix.compiler }}" + Push-Location python + # plugins (pyscf) do not build on Windows; install the test extra only. + # QDK_ALLOW_DEPENDENCY_FETCH=OFF: fail fast if the installed qdk is not + # reused (avoids a silent multi-hour rebuild of the C++ stack from source). + python -m pip install -v ".[test]" ` + --config-settings=cmake.args="-GNinja" ` + --config-settings=cmake.define.CMAKE_PREFIX_PATH="$prefix" ` + --config-settings=cmake.define.QDK_ALLOW_DEPENDENCY_FETCH=OFF ` + --config-settings=cmake.define.CMAKE_C_COMPILER="${{ steps.env.outputs.cxx-path }}" ` + --config-settings=cmake.define.CMAKE_CXX_COMPILER="${{ steps.env.outputs.cxx-path }}" ` + --config-settings=cmake.define.CMAKE_TOOLCHAIN_FILE="$vcpkg\scripts\buildsystems\vcpkg.cmake" ` + --config-settings=cmake.define.VCPKG_CHAINLOAD_TOOLCHAIN_FILE="${{ github.workspace }}\.pipelines\toolchains\windows.cmake" ` + --config-settings=cmake.define.VCPKG_TARGET_TRIPLET="${{ env.VCPKG_TRIPLET }}" ` + --config-settings=cmake.define.VCPKG_INSTALLED_DIR="${{ github.workspace }}\vcpkg_installed" + if ($LASTEXITCODE -ne 0) { Pop-Location; throw "Python package install failed ($LASTEXITCODE)" } + python -c "import qdk_chemistry; print('qdk_chemistry version:', qdk_chemistry.__version__)" + if ($LASTEXITCODE -ne 0) { Pop-Location; throw "Python import check failed ($LASTEXITCODE)" } + Pop-Location + + - name: Run Python tests + shell: pwsh + env: + OMP_NUM_THREADS: '2' + QDK_CHEMISTRY_RUN_SLOW_TESTS: ${{ inputs.run_slow_tests && '1' || '0' }} + run: | + Push-Location python + pytest -v --tb=short + $code = $LASTEXITCODE + Pop-Location + if ($code -ne 0) { throw "pytest failed ($code)" } From 71090ad305d0d19fafe18ba6c56b9cad5bad4f35 Mon Sep 17 00:00:00 2001 From: David Williams-Young Date: Sat, 27 Jun 2026 02:34:02 -0700 Subject: [PATCH 02/37] Windows CI: build only external dep targets in the deps job The msvc full build (deps + qdk + tests) exceeds the 6h GitHub-hosted job cap (it reached 2246/2384 before being cancelled, so no cache was saved). Build only the slow FetchContent dependency targets (int2/ecpint/gauxc/blaspp/lapackpp) in the deps job to warm the cache well under 6h; qdk, macis, and tests are built in the build-test job (their sources are re-checked-out there regardless). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build-and-test-windows.yaml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build-and-test-windows.yaml b/.github/workflows/build-and-test-windows.yaml index 3282c7578..d14743a17 100644 --- a/.github/workflows/build-and-test-windows.yaml +++ b/.github/workflows/build-and-test-windows.yaml @@ -28,12 +28,13 @@ env: jobs: # ---------------------------------------------------------------------------- - # Job 1: build and cache the (slow) C++ dependencies. + # Job 1: build and cache the (slow) external C++ dependencies. # - # GitHub-hosted windows runners have a hard 6h per-job limit. The first - # uncached build of libint2/gauxc/etc. can approach that, so the dependency - # build gets its own job (and 6h budget); the build-test job below restores - # the warmed cache and only rebuilds qdk + runs the tests. + # GitHub-hosted windows runners have a hard 6h per-job limit. With MSVC a full + # build (deps + qdk + tests) exceeds it, so this job builds ONLY the slow + # FetchContent dependency targets (libint2/gauxc/ecpint/blaspp/lapackpp) to warm + # the cache. qdk, macis, and the tests are built in the build-test job below, + # which restores the warmed cache. # ---------------------------------------------------------------------------- deps: name: ${{ matrix.compiler }} - Build & cache dependencies @@ -78,7 +79,7 @@ jobs: --overlay-ports="${{ github.workspace }}\vcpkg-overlay\ports" if ($LASTEXITCODE -ne 0) { throw "vcpkg install failed ($LASTEXITCODE)" } - - name: Configure & build dependencies (full warm build) + - name: Configure & build dependency targets if: steps.cache.outputs.cache-hit != 'true' shell: pwsh run: | @@ -101,8 +102,8 @@ jobs: -DVCPKG_INSTALLED_DIR="${{ github.workspace }}\vcpkg_installed" ` -DFETCHCONTENT_QUIET=OFF if ($LASTEXITCODE -ne 0) { throw "CMake configure failed ($LASTEXITCODE)" } - cmake --build "cpp/build-${{ matrix.compiler }}" - if ($LASTEXITCODE -ne 0) { throw "CMake build failed ($LASTEXITCODE)" } + cmake --build "cpp/build-${{ matrix.compiler }}" --target int2 ecpint gauxc blaspp lapackpp + if ($LASTEXITCODE -ne 0) { throw "CMake dependency build failed ($LASTEXITCODE)" } # ---------------------------------------------------------------------------- # Job 2: rebuild qdk against the cached dependencies, then run the C++ and From e09b44577b4a4a1d40d1678e290b5b56f86faf7f Mon Sep 17 00:00:00 2001 From: David Williams-Young Date: Sat, 27 Jun 2026 03:21:30 -0700 Subject: [PATCH 03/37] Windows CI: fix libint2 dep target name (int2 -> libint2) The libint2 static library target is named `libint2` (its output file is int2.lib); `int2` is not a Ninja target. Caught by the deps-only build's fast target validation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build-and-test-windows.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test-windows.yaml b/.github/workflows/build-and-test-windows.yaml index d14743a17..09ba5165c 100644 --- a/.github/workflows/build-and-test-windows.yaml +++ b/.github/workflows/build-and-test-windows.yaml @@ -102,7 +102,7 @@ jobs: -DVCPKG_INSTALLED_DIR="${{ github.workspace }}\vcpkg_installed" ` -DFETCHCONTENT_QUIET=OFF if ($LASTEXITCODE -ne 0) { throw "CMake configure failed ($LASTEXITCODE)" } - cmake --build "cpp/build-${{ matrix.compiler }}" --target int2 ecpint gauxc blaspp lapackpp + cmake --build "cpp/build-${{ matrix.compiler }}" --target libint2 ecpint gauxc blaspp lapackpp if ($LASTEXITCODE -ne 0) { throw "CMake dependency build failed ($LASTEXITCODE)" } # ---------------------------------------------------------------------------- From 5d06ea4306e8c6ab52a682ddbe86324cb97331b7 Mon Sep 17 00:00:00 2001 From: David Williams-Young Date: Sat, 27 Jun 2026 10:55:36 -0700 Subject: [PATCH 04/37] Disable libint2 CMake Unity build on MSVC MSVC's /O2 optimizer is pathologically slow on libint2's large Unity translation units: building libint2 took 5h17m on a 4-core windows-latest with MSVC (vs ~3 min with clang-cl on the same TUs), exceeding the 6h CI job cap. Disabling Unity for the libint2 object library on MSVC (cl) splits the generated integral code back into many small TUs that compile quickly and parallelize. clang-cl handles the Unity TUs efficiently, so Unity is left enabled there. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- cpp/cmake/third_party.cmake | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cpp/cmake/third_party.cmake b/cpp/cmake/third_party.cmake index e64379918..cc1266128 100644 --- a/cpp/cmake/third_party.cmake +++ b/cpp/cmake/third_party.cmake @@ -86,6 +86,13 @@ if(MSVC AND TARGET eritest-libint2) endif() endif() +# MSVC's /O2 optimizer is pathologically slow on libint2's large CMake Unity +# translation units (hours vs minutes for clang-cl). Disable Unity for libint2 on +# MSVC so the small generated TUs compile quickly and parallelize; clang-cl keeps it. +if(MSVC AND NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND TARGET libint2_obj) + set_target_properties(libint2_obj PROPERTIES UNITY_BUILD OFF) +endif() + # ecpint for ECP-related integral evaluation set(LIBECPINT_BUILD_TESTS OFF CACHE BOOL "Enable ECPINT Tests" FORCE) set(LIBECPINT_USE_PUGIXML OFF CACHE BOOL "Use pugixml for ECPINT" FORCE) From 24ee9f7127f301b32c93689cbd784594d228cf6c Mon Sep 17 00:00:00 2001 From: David Williams-Young Date: Sat, 27 Jun 2026 19:39:34 -0700 Subject: [PATCH 05/37] Windows CI: resumable dependency cache + uncapped parallelism MSVC cannot build the dependency stack within the 6h GitHub-hosted job cap even with Unity off (libint2's ~10.5k TUs + gauxc reach ~87% on a 4-vCPU runner). Make the dependency build resumable so progress is never lost: - The deps job restores the furthest prior progress (exact key = a completed build; restore-keys prefix = the newest partial), builds the dependency targets in a time-boxed step (255 min, under the cap), and saves the build dir: a completed build under the stable key, a timed-out build under a per-run partial key. The next run restores that and Ninja continues. Once a complete cache exists the build is skipped. - vcpkg install and configure are skipped when their outputs are already restored. - A best-effort prune keeps the cache store bounded (newest partial for resume; drop all partials once complete). Also stop pinning CMAKE_BUILD_PARALLEL_LEVEL=4 (that was local-dev guidance for a RAM-limited laptop); let Ninja use all cores so the build scales with the runner. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build-and-test-windows.yaml | 107 +++++++++++++----- 1 file changed, 81 insertions(+), 26 deletions(-) diff --git a/.github/workflows/build-and-test-windows.yaml b/.github/workflows/build-and-test-windows.yaml index 09ba5165c..9ccefc5df 100644 --- a/.github/workflows/build-and-test-windows.yaml +++ b/.github/workflows/build-and-test-windows.yaml @@ -22,7 +22,6 @@ permissions: env: VCPKG_TRIPLET: x64-windows-static-md QDK_UARCH: x86-64-v3 - CMAKE_BUILD_PARALLEL_LEVEL: '4' # Bump to force a rebuild of the cached dependencies. DEPS_CACHE_VERSION: v1 @@ -30,16 +29,20 @@ jobs: # ---------------------------------------------------------------------------- # Job 1: build and cache the (slow) external C++ dependencies. # - # GitHub-hosted windows runners have a hard 6h per-job limit. With MSVC a full - # build (deps + qdk + tests) exceeds it, so this job builds ONLY the slow - # FetchContent dependency targets (libint2/gauxc/ecpint/blaspp/lapackpp) to warm - # the cache. qdk, macis, and the tests are built in the build-test job below, - # which restores the warmed cache. + # Builds ONLY the slow FetchContent dependency targets + # (libint2/gauxc/ecpint/blaspp/lapackpp). With MSVC this can exceed the 6h + # GitHub-hosted job cap, so the build is RESUMABLE: the build dir is cached + # after every run (complete -> stable key; partial -> per-run key), and the + # next run restores the furthest progress and lets Ninja continue. Once a + # complete cache exists the build is skipped entirely. # ---------------------------------------------------------------------------- deps: - name: ${{ matrix.compiler }} - Build & cache dependencies + name: ${{ matrix.compiler }} - Dependencies runs-on: windows-latest timeout-minutes: 360 + permissions: + contents: read + actions: write # prune stale dependency caches strategy: fail-fast: false matrix: @@ -56,20 +59,28 @@ jobs: with: compiler: ${{ matrix.compiler }} - - name: Cache vcpkg + dependency build + - name: Restore dependency cache id: cache - uses: actions/cache@v5 + uses: actions/cache/restore@v5 with: path: | vcpkg_installed cpp/build-${{ matrix.compiler }} + # Exact key = a completed build. restore-keys prefix = the furthest + # partial build from a previous (timed-out) run, for resuming. key: >- windows-deps-${{ matrix.compiler }}-${{ env.VCPKG_TRIPLET }}-vs${{ steps.env.outputs.toolset }}-${{ env.DEPS_CACHE_VERSION }}-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json', 'vcpkg-overlay/**', 'cpp/manifest/qdk-chemistry/cgmanifest.json', 'external/macis/manifest/cgmanifest.json', 'cpp/cmake/third_party.cmake', 'cpp/cmake/patches/**', 'cpp/cmake/modules/DependencyManager.cmake', 'external/macis/src/lobpcgxx/CMakeLists.txt', '.pipelines/toolchains/windows.cmake') }} + restore-keys: | + windows-deps-${{ matrix.compiler }}-${{ env.VCPKG_TRIPLET }}-vs${{ steps.env.outputs.toolset }}-${{ env.DEPS_CACHE_VERSION }}-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json', 'vcpkg-overlay/**', 'cpp/manifest/qdk-chemistry/cgmanifest.json', 'external/macis/manifest/cgmanifest.json', 'cpp/cmake/third_party.cmake', 'cpp/cmake/patches/**', 'cpp/cmake/modules/DependencyManager.cmake', 'external/macis/src/lobpcgxx/CMakeLists.txt', '.pipelines/toolchains/windows.cmake') }}-partial- - name: Install vcpkg dependencies if: steps.cache.outputs.cache-hit != 'true' shell: pwsh run: | + if (Test-Path "vcpkg_installed/${{ env.VCPKG_TRIPLET }}/lib") { + Write-Host "vcpkg dependencies already present (restored from cache); skipping install." + exit 0 + } $vcpkg = $env:VCPKG_INSTALLATION_ROOT if (-not $vcpkg) { $vcpkg = 'C:\vcpkg' } & "$vcpkg\vcpkg.exe" install ` @@ -79,7 +90,7 @@ jobs: --overlay-ports="${{ github.workspace }}\vcpkg-overlay\ports" if ($LASTEXITCODE -ne 0) { throw "vcpkg install failed ($LASTEXITCODE)" } - - name: Configure & build dependency targets + - name: Configure CMake if: steps.cache.outputs.cache-hit != 'true' shell: pwsh run: | @@ -102,12 +113,68 @@ jobs: -DVCPKG_INSTALLED_DIR="${{ github.workspace }}\vcpkg_installed" ` -DFETCHCONTENT_QUIET=OFF if ($LASTEXITCODE -ne 0) { throw "CMake configure failed ($LASTEXITCODE)" } + + - name: Build dependency targets (resumable, time-boxed) + id: build + if: steps.cache.outputs.cache-hit != 'true' + # Kept under the 6h job cap (accounting for ~85 min of cold vcpkg + download + # overhead) so the resumable partial cache is saved before the job is + # force-cancelled. The next run restores this progress and continues. + timeout-minutes: 255 + shell: pwsh + run: | + # No --parallel / CMAKE_BUILD_PARALLEL_LEVEL: let Ninja use all cores. cmake --build "cpp/build-${{ matrix.compiler }}" --target libint2 ecpint gauxc blaspp lapackpp - if ($LASTEXITCODE -ne 0) { throw "CMake dependency build failed ($LASTEXITCODE)" } + if ($LASTEXITCODE -ne 0) { throw "Dependency build failed ($LASTEXITCODE)" } + + - name: Save dependency cache (complete) + if: steps.build.outcome == 'success' + uses: actions/cache/save@v5 + with: + path: | + vcpkg_installed + cpp/build-${{ matrix.compiler }} + key: >- + windows-deps-${{ matrix.compiler }}-${{ env.VCPKG_TRIPLET }}-vs${{ steps.env.outputs.toolset }}-${{ env.DEPS_CACHE_VERSION }}-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json', 'vcpkg-overlay/**', 'cpp/manifest/qdk-chemistry/cgmanifest.json', 'external/macis/manifest/cgmanifest.json', 'cpp/cmake/third_party.cmake', 'cpp/cmake/patches/**', 'cpp/cmake/modules/DependencyManager.cmake', 'external/macis/src/lobpcgxx/CMakeLists.txt', '.pipelines/toolchains/windows.cmake') }} + + - name: Save dependency cache (partial, for resume) + if: steps.build.outcome == 'failure' + uses: actions/cache/save@v5 + with: + path: | + vcpkg_installed + cpp/build-${{ matrix.compiler }} + key: >- + windows-deps-${{ matrix.compiler }}-${{ env.VCPKG_TRIPLET }}-vs${{ steps.env.outputs.toolset }}-${{ env.DEPS_CACHE_VERSION }}-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json', 'vcpkg-overlay/**', 'cpp/manifest/qdk-chemistry/cgmanifest.json', 'external/macis/manifest/cgmanifest.json', 'cpp/cmake/third_party.cmake', 'cpp/cmake/patches/**', 'cpp/cmake/modules/DependencyManager.cmake', 'external/macis/src/lobpcgxx/CMakeLists.txt', '.pipelines/toolchains/windows.cmake') }}-partial-${{ github.run_id }}-${{ github.run_attempt }} + + - name: Prune stale dependency caches + if: always() + shell: pwsh + env: + GH_TOKEN: ${{ github.token }} + run: | + # Best-effort: keep the newest partial (for resume) and drop older ones; + # once a complete build succeeds, drop all partials for this base key. + $base = "windows-deps-${{ matrix.compiler }}-${{ env.VCPKG_TRIPLET }}-vs${{ steps.env.outputs.toolset }}-${{ env.DEPS_CACHE_VERSION }}-" + $keepNewestPartial = '${{ steps.build.outcome }}' -eq 'failure' + try { + $caches = gh cache list --limit 100 --json id,key,createdAt | ConvertFrom-Json + $partials = $caches | + Where-Object { $_.key.StartsWith($base) -and $_.key.Contains('-partial-') } | + Sort-Object createdAt -Descending + $skip = if ($keepNewestPartial) { 1 } else { 0 } + $partials | Select-Object -Skip $skip | ForEach-Object { + Write-Host "Deleting stale cache: $($_.key)" + gh cache delete $_.id 2>$null + } + } catch { + Write-Host "Cache prune skipped: $_" + } # ---------------------------------------------------------------------------- # Job 2: rebuild qdk against the cached dependencies, then run the C++ and - # Python test suites. + # Python test suites. qdk/macis/test sources are re-checked-out here, so this + # job always rebuilds them; the cached dependency .libs are reused as-is. # ---------------------------------------------------------------------------- build-test: name: ${{ matrix.compiler }} - Build & test @@ -135,7 +202,7 @@ jobs: with: python-version: '3.13' - - name: Restore vcpkg + dependency build + - name: Restore dependency cache id: cache uses: actions/cache/restore@v5 with: @@ -145,19 +212,6 @@ jobs: key: >- windows-deps-${{ matrix.compiler }}-${{ env.VCPKG_TRIPLET }}-vs${{ steps.env.outputs.toolset }}-${{ env.DEPS_CACHE_VERSION }}-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json', 'vcpkg-overlay/**', 'cpp/manifest/qdk-chemistry/cgmanifest.json', 'external/macis/manifest/cgmanifest.json', 'cpp/cmake/third_party.cmake', 'cpp/cmake/patches/**', 'cpp/cmake/modules/DependencyManager.cmake', 'external/macis/src/lobpcgxx/CMakeLists.txt', '.pipelines/toolchains/windows.cmake') }} - - name: Install vcpkg dependencies (cache-miss fallback) - if: steps.cache.outputs.cache-hit != 'true' - shell: pwsh - run: | - $vcpkg = $env:VCPKG_INSTALLATION_ROOT - if (-not $vcpkg) { $vcpkg = 'C:\vcpkg' } - & "$vcpkg\vcpkg.exe" install ` - --triplet ${{ env.VCPKG_TRIPLET }} ` - --x-manifest-root="${{ github.workspace }}" ` - --x-install-root="${{ github.workspace }}\vcpkg_installed" ` - --overlay-ports="${{ github.workspace }}\vcpkg-overlay\ports" - if ($LASTEXITCODE -ne 0) { throw "vcpkg install failed ($LASTEXITCODE)" } - - name: Configure & build C++ library shell: pwsh run: | @@ -180,6 +234,7 @@ jobs: -DVCPKG_INSTALLED_DIR="${{ github.workspace }}\vcpkg_installed" ` -DFETCHCONTENT_QUIET=OFF if ($LASTEXITCODE -ne 0) { throw "CMake configure failed ($LASTEXITCODE)" } + # Let Ninja use all cores (dependency objects are already cached). cmake --build "cpp/build-${{ matrix.compiler }}" if ($LASTEXITCODE -ne 0) { throw "CMake build failed ($LASTEXITCODE)" } From 2d03cef9a2289ef8fb36c4be7016d8b9b257bdd5 Mon Sep 17 00:00:00 2001 From: David Williams-Young Date: Sun, 28 Jun 2026 00:41:45 -0700 Subject: [PATCH 06/37] Windows CI: fix partial-cache save being skipped on build timeout The resumable partial-cache save never ran: a step `if:` without a status-check function has an implicit `success()` ANDed in, so `if: steps.build.outcome == 'failure'` became `success() && ...` and was skipped once the build step failed (timed out). Gate both save steps with `always() &&` so they evaluate regardless of the failed build and select on the build outcome. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build-and-test-windows.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test-windows.yaml b/.github/workflows/build-and-test-windows.yaml index 9ccefc5df..c30f098d5 100644 --- a/.github/workflows/build-and-test-windows.yaml +++ b/.github/workflows/build-and-test-windows.yaml @@ -128,7 +128,7 @@ jobs: if ($LASTEXITCODE -ne 0) { throw "Dependency build failed ($LASTEXITCODE)" } - name: Save dependency cache (complete) - if: steps.build.outcome == 'success' + if: ${{ always() && steps.build.outcome == 'success' }} uses: actions/cache/save@v5 with: path: | @@ -138,7 +138,7 @@ jobs: windows-deps-${{ matrix.compiler }}-${{ env.VCPKG_TRIPLET }}-vs${{ steps.env.outputs.toolset }}-${{ env.DEPS_CACHE_VERSION }}-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json', 'vcpkg-overlay/**', 'cpp/manifest/qdk-chemistry/cgmanifest.json', 'external/macis/manifest/cgmanifest.json', 'cpp/cmake/third_party.cmake', 'cpp/cmake/patches/**', 'cpp/cmake/modules/DependencyManager.cmake', 'external/macis/src/lobpcgxx/CMakeLists.txt', '.pipelines/toolchains/windows.cmake') }} - name: Save dependency cache (partial, for resume) - if: steps.build.outcome == 'failure' + if: ${{ always() && steps.build.outcome == 'failure' }} uses: actions/cache/save@v5 with: path: | From 4c7b87c96f92238aabda3b302dd9302dc1fcdf9d Mon Sep 17 00:00:00 2001 From: David Williams-Young Date: Sun, 28 Jun 2026 05:33:30 -0700 Subject: [PATCH 07/37] Windows CI: trigger resume run (empty commit) Resume the msvc dependency build from the saved partial cache. No file changes, so the dependency-input hash is unchanged and the partial cache remains valid. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> From 67b83cc2967216770d3650500dcdb93fde15213e Mon Sep 17 00:00:00 2001 From: David Williams-Young Date: Sun, 28 Jun 2026 08:48:20 -0700 Subject: [PATCH 08/37] Windows CI: memory-aware build parallelism to avoid MSVC C1060 Uncapping parallelism caused MSVC to run out of heap (fatal error C1060) when several qdk TUs that include libint2's engine.impl.h compiled concurrently on the 16 GB runner (~4 heavy TUs x several GB exhausted RAM). Cap the build-test C++ and Python builds at min(cores, RAM/4GB): 4 on the 16 GB / 4-vCPU windows-latest (proven safe), scaling up automatically on a larger runner. The dependency build (small libint2 TUs) stays uncapped for speed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build-and-test-windows.yaml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test-windows.yaml b/.github/workflows/build-and-test-windows.yaml index c30f098d5..a8786a2c4 100644 --- a/.github/workflows/build-and-test-windows.yaml +++ b/.github/workflows/build-and-test-windows.yaml @@ -202,6 +202,18 @@ jobs: with: python-version: '3.13' + - name: Set memory-aware build parallelism + shell: pwsh + run: | + # qdk TUs that include libint2 engine.impl.h peak at several GB under MSVC; + # cap concurrency by available RAM (~4 GB/job) to avoid C1060 (compiler out + # of heap space). Scales up automatically on a larger runner. + $cpu = [int]$env:NUMBER_OF_PROCESSORS + $ramGB = [math]::Floor((Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory / 1GB) + $jobs = [math]::Min($cpu, [math]::Max(1, [math]::Floor($ramGB / 4))) + Write-Host "CPUs=$cpu RAM=${ramGB}GB -> CMAKE_BUILD_PARALLEL_LEVEL=$jobs" + "CMAKE_BUILD_PARALLEL_LEVEL=$jobs" >> $env:GITHUB_ENV + - name: Restore dependency cache id: cache uses: actions/cache/restore@v5 @@ -234,7 +246,7 @@ jobs: -DVCPKG_INSTALLED_DIR="${{ github.workspace }}\vcpkg_installed" ` -DFETCHCONTENT_QUIET=OFF if ($LASTEXITCODE -ne 0) { throw "CMake configure failed ($LASTEXITCODE)" } - # Let Ninja use all cores (dependency objects are already cached). + # Honors CMAKE_BUILD_PARALLEL_LEVEL set above (memory-aware). cmake --build "cpp/build-${{ matrix.compiler }}" if ($LASTEXITCODE -ne 0) { throw "CMake build failed ($LASTEXITCODE)" } From 257383843dd3a33d0b32d5c82665060cbf0e021a Mon Sep 17 00:00:00 2001 From: David Williams-Young Date: Sun, 28 Jun 2026 09:52:00 -0700 Subject: [PATCH 09/37] Windows CI: exclude libint2's own unit tests from ctest On MSVC, libint2/unit/build (a compile-at-test-time meta-test of the vendored libint2 dependency) exceeds the 400s ctest timeout, failing 3 of 889 tests (libint2/unit/build + its two dependent run tests); all 886 qdk tests pass. These validate libint2 itself (upstream-tested, and run on the Linux job), so exclude them on Windows alongside the existing MACIS_SERIAL_TEST exclusion. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build-and-test-windows.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test-windows.yaml b/.github/workflows/build-and-test-windows.yaml index a8786a2c4..b46b5d672 100644 --- a/.github/workflows/build-and-test-windows.yaml +++ b/.github/workflows/build-and-test-windows.yaml @@ -256,9 +256,13 @@ jobs: OMP_NUM_THREADS: '2' run: | Push-Location "cpp/build-${{ matrix.compiler }}" + # Exclude MACIS_SERIAL_TEST (as on Linux) and libint2's own unit tests: + # libint2/unit/build is a compile-at-test-time meta-test of the vendored + # libint2 dependency that exceeds the ctest timeout under MSVC. qdk's own + # tests still run. ctest --output-on-failure --verbose --timeout 400 ` --output-junit ctest_results.xml ` - -E "MACIS_SERIAL_TEST" + -E "MACIS_SERIAL_TEST|libint2/unit" $code = $LASTEXITCODE Pop-Location if ($code -ne 0) { throw "ctest failed ($code)" } From 2244b831af9581d91ec9f945375b4eeaa7cd1271 Mon Sep 17 00:00:00 2001 From: David Williams-Young Date: Sun, 28 Jun 2026 11:51:55 -0700 Subject: [PATCH 10/37] Windows CI: switch to production triggers Now that the Windows workflow is green for both MSVC and clang-cl, replace the temporary session-branch push trigger with the production triggers matching the Linux/macOS workflow: push to main, pull_request on any branch, merge_group, and workflow_dispatch. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build-and-test-windows.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test-windows.yaml b/.github/workflows/build-and-test-windows.yaml index b46b5d672..27325fdc4 100644 --- a/.github/workflows/build-and-test-windows.yaml +++ b/.github/workflows/build-and-test-windows.yaml @@ -3,7 +3,11 @@ name: Build and Test (Windows) on: push: branches: - - session/win_ci_cd + - main + pull_request: + branches: + - '**' + merge_group: workflow_dispatch: inputs: run_slow_tests: From 1fee3b2c186354946e08c04d551e1f0150890268 Mon Sep 17 00:00:00 2001 From: Loris Ercole Date: Mon, 29 Jun 2026 12:50:45 +0200 Subject: [PATCH 11/37] Switch Windows CI to windows-2025-16core, 12h deps timeout - Both jobs: windows-latest -> windows-2025-16core - deps job timeout: 360 -> 720 min (12 h) - Build step inner timeout: 255 -> 630 min (leaves ~90 min overhead) - Update comments to reflect new runner and timeout Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build-and-test-windows.yaml | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build-and-test-windows.yaml b/.github/workflows/build-and-test-windows.yaml index 27325fdc4..f75b9d642 100644 --- a/.github/workflows/build-and-test-windows.yaml +++ b/.github/workflows/build-and-test-windows.yaml @@ -34,16 +34,16 @@ jobs: # Job 1: build and cache the (slow) external C++ dependencies. # # Builds ONLY the slow FetchContent dependency targets - # (libint2/gauxc/ecpint/blaspp/lapackpp). With MSVC this can exceed the 6h - # GitHub-hosted job cap, so the build is RESUMABLE: the build dir is cached - # after every run (complete -> stable key; partial -> per-run key), and the - # next run restores the furthest progress and lets Ninja continue. Once a - # complete cache exists the build is skipped entirely. + # (libint2/gauxc/ecpint/blaspp/lapackpp). With MSVC this can take several + # hours, so the build is RESUMABLE: the build dir is cached after every run + # (complete -> stable key; partial -> per-run key), and the next run restores + # the furthest progress and lets Ninja continue. Once a complete cache exists + # the build is skipped entirely. The job timeout is 12 hours. # ---------------------------------------------------------------------------- deps: name: ${{ matrix.compiler }} - Dependencies - runs-on: windows-latest - timeout-minutes: 360 + runs-on: windows-2025-16core + timeout-minutes: 720 permissions: contents: read actions: write # prune stale dependency caches @@ -121,10 +121,10 @@ jobs: - name: Build dependency targets (resumable, time-boxed) id: build if: steps.cache.outputs.cache-hit != 'true' - # Kept under the 6h job cap (accounting for ~85 min of cold vcpkg + download - # overhead) so the resumable partial cache is saved before the job is - # force-cancelled. The next run restores this progress and continues. - timeout-minutes: 255 + # Kept under the 12h job cap (accounting for vcpkg + download overhead) so + # the resumable partial cache is saved before the job is force-cancelled. + # The next run restores this progress and continues. + timeout-minutes: 630 shell: pwsh run: | # No --parallel / CMAKE_BUILD_PARALLEL_LEVEL: let Ninja use all cores. @@ -182,7 +182,7 @@ jobs: # ---------------------------------------------------------------------------- build-test: name: ${{ matrix.compiler }} - Build & test - runs-on: windows-latest + runs-on: windows-2025-16core timeout-minutes: 360 needs: deps strategy: @@ -237,7 +237,7 @@ jobs: -DQDK_UARCH=${{ env.QDK_UARCH }} ` -DQDK_CHEMISTRY_ENABLE_COVERAGE=OFF ` -DQDK_CHEMISTRY_ENABLE_MPI=OFF ` - -DMACIS_ENABLE_TESTS=ON ` + -DMACIS_ENABLE_TESTS=OFF ` -DBUILD_SHARED_LIBS=OFF ` -DBUILD_TESTING=ON ` -DCMAKE_BUILD_TYPE=Release ` From d44218f47b0fdf2a49afde49488a7f7c1bf868e9 Mon Sep 17 00:00:00 2001 From: Loris Ercole Date: Mon, 29 Jun 2026 12:52:50 +0200 Subject: [PATCH 12/37] Add agency.toml to .gitignore Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 4e8546ada..eac2279cf 100644 --- a/.gitignore +++ b/.gitignore @@ -239,3 +239,6 @@ python/VERSION # Claude .claude + +# Copilot agent +agency.toml From fa40ccdc75084481d8b3a2f5bc035056e242e71d Mon Sep 17 00:00:00 2001 From: Loris Ercole Date: Mon, 29 Jun 2026 13:24:53 +0200 Subject: [PATCH 13/37] try 32 core --- .github/workflows/build-and-test-windows.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test-windows.yaml b/.github/workflows/build-and-test-windows.yaml index f75b9d642..a8242e072 100644 --- a/.github/workflows/build-and-test-windows.yaml +++ b/.github/workflows/build-and-test-windows.yaml @@ -42,7 +42,7 @@ jobs: # ---------------------------------------------------------------------------- deps: name: ${{ matrix.compiler }} - Dependencies - runs-on: windows-2025-16core + runs-on: windows-2025-32core timeout-minutes: 720 permissions: contents: read @@ -182,7 +182,7 @@ jobs: # ---------------------------------------------------------------------------- build-test: name: ${{ matrix.compiler }} - Build & test - runs-on: windows-2025-16core + runs-on: windows-2025-32core timeout-minutes: 360 needs: deps strategy: From 94ccf28449729d1a2d8757ebc8582f8a08f8818b Mon Sep 17 00:00:00 2001 From: Loris Ercole Date: Mon, 29 Jun 2026 15:03:41 +0200 Subject: [PATCH 14/37] swtich to 1ES GH runners --- .github/workflows/build-and-test-windows.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test-windows.yaml b/.github/workflows/build-and-test-windows.yaml index a8242e072..847f49f42 100644 --- a/.github/workflows/build-and-test-windows.yaml +++ b/.github/workflows/build-and-test-windows.yaml @@ -42,7 +42,7 @@ jobs: # ---------------------------------------------------------------------------- deps: name: ${{ matrix.compiler }} - Dependencies - runs-on: windows-2025-32core + runs-on: [self-hosted, 1ES.Pool=1es-gh-Dads_v5-pool-wus2, 1ES.ImageOverride=win2025-tl-vanilla, 'JobId=qdk_chemistry-win-deps-${{ strategy.job-index }}-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}'] timeout-minutes: 720 permissions: contents: read @@ -182,7 +182,7 @@ jobs: # ---------------------------------------------------------------------------- build-test: name: ${{ matrix.compiler }} - Build & test - runs-on: windows-2025-32core + runs-on: [self-hosted, 1ES.Pool=1es-gh-Dads_v5-pool-wus2, 1ES.ImageOverride=win2025-tl-vanilla, 'JobId=qdk_chemistry-win-build-${{ strategy.job-index }}-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}'] timeout-minutes: 360 needs: deps strategy: From 5cf054509f47ea942a9daa57fd103218c8752951 Mon Sep 17 00:00:00 2001 From: Loris Ercole Date: Mon, 29 Jun 2026 17:04:53 +0200 Subject: [PATCH 15/37] ci: add Windows ADO pip-wheel pipeline with shared conda bootstrap - Extend .pipelines/python-wheels.yaml with Windows job using MMSWindows2025-g2-vNext 1ES runner and clang-cl compiler - Add .pipelines/templates/build-pip-wheels-windows.yml template: deps job (cached vcpkg + C++ deps) and build/test job - Add .pipelines/pip-scripts/build-pip-wheels-windows-deps.ps1: CMake configure + C++ dependency build (vcpkg/FetchContent) - Add .pipelines/pip-scripts/build-pip-wheels-windows.ps1: CMake full build, ctest, cmake --install, wheel build via scikit-build-core - Add .pipelines/pip-scripts/bootstrap-conda.ps1: shared conda bootstrapper (ms-ensureconda, Azure Artifacts feed, retry logic) used by both build and test scripts/templates - Add .pipelines/templates/test-pip-wheels-windows.yml: wheel install + pytest via conda testenv - Update .github/workflows/build-and-test-windows.yaml: switch to windows-2025-16core runner, 12h timeout on deps job Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build-and-test-windows.yaml | 4 +- .pipelines/pip-scripts/bootstrap-conda.ps1 | 113 ++++++++++ .../build-pip-wheels-windows-deps.ps1 | 83 +++++++ .../pip-scripts/build-pip-wheels-windows.ps1 | 212 ++++++++++++++++++ .pipelines/python-wheels.yaml | 61 ++++- .../templates/build-pip-wheels-windows.yml | 176 +++++++++++++++ .../templates/test-pip-wheels-windows.yml | 75 +++++++ 7 files changed, 715 insertions(+), 9 deletions(-) create mode 100644 .pipelines/pip-scripts/bootstrap-conda.ps1 create mode 100644 .pipelines/pip-scripts/build-pip-wheels-windows-deps.ps1 create mode 100644 .pipelines/pip-scripts/build-pip-wheels-windows.ps1 create mode 100644 .pipelines/templates/build-pip-wheels-windows.yml create mode 100644 .pipelines/templates/test-pip-wheels-windows.yml diff --git a/.github/workflows/build-and-test-windows.yaml b/.github/workflows/build-and-test-windows.yaml index 847f49f42..373ec3914 100644 --- a/.github/workflows/build-and-test-windows.yaml +++ b/.github/workflows/build-and-test-windows.yaml @@ -42,7 +42,7 @@ jobs: # ---------------------------------------------------------------------------- deps: name: ${{ matrix.compiler }} - Dependencies - runs-on: [self-hosted, 1ES.Pool=1es-gh-Dads_v5-pool-wus2, 1ES.ImageOverride=win2025-tl-vanilla, 'JobId=qdk_chemistry-win-deps-${{ strategy.job-index }}-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}'] + runs-on: [self-hosted, 1ES.Pool=1es-gh-Dads_v5-pool-wus2, 1ES.ImageOverride=windows-2025-1espt, 'JobId=qdk_chemistry-win-deps-${{ strategy.job-index }}-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}'] timeout-minutes: 720 permissions: contents: read @@ -182,7 +182,7 @@ jobs: # ---------------------------------------------------------------------------- build-test: name: ${{ matrix.compiler }} - Build & test - runs-on: [self-hosted, 1ES.Pool=1es-gh-Dads_v5-pool-wus2, 1ES.ImageOverride=win2025-tl-vanilla, 'JobId=qdk_chemistry-win-build-${{ strategy.job-index }}-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}'] + runs-on: [self-hosted, 1ES.Pool=1es-gh-Dads_v5-pool-wus2, 1ES.ImageOverride=windows-2025-1espt, 'JobId=qdk_chemistry-win-build-${{ strategy.job-index }}-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}'] timeout-minutes: 360 needs: deps strategy: diff --git a/.pipelines/pip-scripts/bootstrap-conda.ps1 b/.pipelines/pip-scripts/bootstrap-conda.ps1 new file mode 100644 index 000000000..d71a15c6c --- /dev/null +++ b/.pipelines/pip-scripts/bootstrap-conda.ps1 @@ -0,0 +1,113 @@ +<# +.SYNOPSIS + Bootstrap ms-ensureconda and create a named conda environment. + +.DESCRIPTION + PowerShell equivalent of bootstrap-conda.sh for Windows 1ES builds. + + This script is meant to be *called* (not dot-sourced) from other scripts + or YAML powershell steps. It writes all diagnostic output via Write-Host + and emits exactly one line to stdout: the resolved path to conda.exe. + Callers capture it with: + + $condaExe = & "$PSScriptRoot\bootstrap-conda.ps1" -EnvName buildenv -PythonVersion 3.11 + + Required env var: + SYSTEM_ACCESSTOKEN Pipeline access token, mapped from $(System.AccessToken). + Used by the azure_artifacts_conda_auth plugin that is + pre-registered in ms-ensureconda's conda distribution. + + Rationale: ms-ensureconda is the Microsoft-approved conda bootstrapper for CI + builds. See: + https://eng.ms/docs/more/languages-at-microsoft/python/articles/anaconda/install + All network access goes through the Azure Artifacts feed (1ES CFSClean blocks + public conda channels). The azure_artifacts_conda_auth plugin reads + ARTIFACTS_CONDA_TOKEN; we set it to SYSTEM_ACCESSTOKEN before every conda call. +#> +param( + # Name of the conda environment to create (e.g. "buildenv" or "testenv"). + [Parameter(Mandatory)] [string]$EnvName, + # Python version for the new environment (e.g. "3.11"). + [Parameter(Mandatory)] [string]$PythonVersion +) +$ErrorActionPreference = 'Stop' + +if (-not $env:SYSTEM_ACCESSTOKEN) { + throw "bootstrap-conda.ps1: SYSTEM_ACCESSTOKEN must be set before calling this script." +} + +$ENSURECONDA_PKG = 'ms-ensureconda==2026.6.1' +$MAX_ATTEMPTS = 3 +$RETRY_DELAY_SEC = 30 +$CONDA_FEED_ROOT = 'https://pkgs.dev.azure.com/ms-azurequantum/AzureQuantum/_packaging/quantum-apps-dependencies/Conda/repo' + +function _Bootstrap { + param([string]$EnvName, [string]$PythonVersion) + + Write-Host "Installing $ENSURECONDA_PKG and bootstrapping conda..." + + # Use a throwaway venv so we don't fight PEP 668 (externally-managed Python). + # Clean any stale venv first (idempotent on retries / self-hosted agents). + $bootstrapVenv = Join-Path $env:TEMP "qdk-bootstrap-venv-$EnvName" + Remove-Item -Recurse -Force $bootstrapVenv -ErrorAction SilentlyContinue + python -m venv $bootstrapVenv + if ($LASTEXITCODE -ne 0) { throw "Bootstrap venv creation failed ($LASTEXITCODE)" } + $venvPy = Join-Path $bootstrapVenv 'Scripts\python.exe' + + & $venvPy -m pip install --quiet $ENSURECONDA_PKG + if ($LASTEXITCODE -ne 0) { throw "ms-ensureconda install failed ($LASTEXITCODE)" } + + # ms-ensureconda --envfile writes a shell-sourceable KEY=VALUE file with + # CONDA_EXE, CONDA_BASH_HOOK, etc. We parse CONDA_EXE from it. + $condaEnvFile = Join-Path $env:TEMP "qdk-ensureconda-$EnvName.env" + $env:ARTIFACTS_CONDA_TOKEN = $env:SYSTEM_ACCESSTOKEN + & $venvPy -m ensureconda --envfile $condaEnvFile + if ($LASTEXITCODE -ne 0) { throw "ensureconda failed ($LASTEXITCODE)" } + + $condaExe = Get-Content $condaEnvFile | + Where-Object { $_ -match "^(?:export\s+)?CONDA_EXE=" } | + Select-Object -First 1 | + ForEach-Object { ($_ -split '=', 2)[1].Trim().Trim("'`"") } + if (-not $condaExe -or -not (Test-Path $condaExe)) { + throw "CONDA_EXE not found or path does not exist: '$condaExe'" + } + Write-Host "conda: $condaExe ($( & $condaExe --version 2>&1 ))" + + # Remove any pre-existing env (idempotent on self-hosted agents and retries). + & $condaExe env remove -y -n $EnvName 2>&1 | Out-Null + $LASTEXITCODE = 0 # env remove exits 1 when env doesn't exist; ignore + + # Public channels (conda.anaconda.org, repo.anaconda.com) are blocked under + # 1ES network isolation (CFSClean). Force all installs through the Azure + # Artifacts feed, which proxies `main` and `conda-forge` as named subpaths. + # The `main` channel carries python + pip; `conda-forge` is a fallback. + $env:ARTIFACTS_CONDA_TOKEN = $env:SYSTEM_ACCESSTOKEN + & $condaExe create --override-channels ` + --channel "$CONDA_FEED_ROOT/main" ` + --channel "$CONDA_FEED_ROOT/conda-forge" ` + --yes --quiet --name $EnvName "python=$PythonVersion" pip + if ($LASTEXITCODE -ne 0) { throw "conda create '$EnvName' failed ($LASTEXITCODE)" } + Write-Host "Conda env '$EnvName' created with Python $PythonVersion." + + # Return the resolved conda exe path (captured by caller). + return $condaExe +} + +$attempt = 1 +while ($true) { + try { + $condaExe = _Bootstrap -EnvName $EnvName -PythonVersion $PythonVersion + break + } catch { + if ($attempt -ge $MAX_ATTEMPTS) { + Write-Error "bootstrap-conda.ps1: failed after $MAX_ATTEMPTS attempts: $_" + exit 1 + } + Write-Warning "Bootstrap attempt $attempt/$MAX_ATTEMPTS failed: $_ Retrying in ${RETRY_DELAY_SEC}s..." + Start-Sleep -Seconds $RETRY_DELAY_SEC + $attempt++ + } +} + +# Emit only the conda exe path to stdout; callers capture this line. +Write-Output $condaExe diff --git a/.pipelines/pip-scripts/build-pip-wheels-windows-deps.ps1 b/.pipelines/pip-scripts/build-pip-wheels-windows-deps.ps1 new file mode 100644 index 000000000..90a60d4de --- /dev/null +++ b/.pipelines/pip-scripts/build-pip-wheels-windows-deps.ps1 @@ -0,0 +1,83 @@ +<# +.SYNOPSIS + Build C++ dependencies (vcpkg + FetchContent targets) for the Windows wheel pipeline. + +.DESCRIPTION + Invoked only on a dependency cache miss. Installs vcpkg packages, runs the CMake + configure pass, and builds the slow FetchContent targets (libint2, ecpint, gauxc, + blaspp, lapackpp). The build directory is subsequently cached by the calling + pipeline job; the full qdk build then starts from a warm build tree. + + Prerequisites (set by the YAML template before this script runs): + - INCLUDE, LIB, PATH already contain MSVC / clang-cl entries + (applied via ##vso[task.setvariable] / ##vso[task.prependpath]). + - CMAKE_BUILD_PARALLEL_LEVEL is set if caller wants a specific level + (otherwise computed here from CPU count and available RAM). +#> +param( + [Parameter(Mandatory)] [string]$SrcDir, + [Parameter(Mandatory)] [string]$ClangClPath, + [string]$March = 'x86-64-v3', + [string]$BuildType = 'Release', + [string]$VcpkgRoot +) +$ErrorActionPreference = 'Stop' + +# Fall back to well-known vcpkg location on MMS images. +if (-not $VcpkgRoot) { + $VcpkgRoot = if ($env:VCPKG_INSTALLATION_ROOT) { $env:VCPKG_INSTALLATION_ROOT } else { 'C:\vcpkg' } +} +if (-not (Test-Path "$VcpkgRoot\vcpkg.exe")) { throw "vcpkg.exe not found under '$VcpkgRoot'" } + +$buildDir = "$SrcDir\cpp\build-clang-cl" + +# Cap Ninja parallelism by available RAM (~4 GB/job for clang-cl TUs pulling in +# libint2 headers). On the HB120 runner this typically allows all 120 cores. +if (-not $env:CMAKE_BUILD_PARALLEL_LEVEL) { + $cpu = [int]$env:NUMBER_OF_PROCESSORS + $ramGB = [math]::Floor((Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory / 1GB) + $jobs = [math]::Min($cpu, [math]::Max(1, [math]::Floor($ramGB / 4))) + Write-Host "CPUs=$cpu RAM=${ramGB} GB -> CMAKE_BUILD_PARALLEL_LEVEL=$jobs" + $env:CMAKE_BUILD_PARALLEL_LEVEL = $jobs +} + +# ─── vcpkg install ──────────────────────────────────────────────────────────── +Write-Host "=== vcpkg install ===" +& "$VcpkgRoot\vcpkg.exe" install ` + --triplet x64-windows-static-md ` + --x-manifest-root="$SrcDir" ` + --x-install-root="$SrcDir\vcpkg_installed" ` + --overlay-ports="$SrcDir\vcpkg-overlay\ports" +if ($LASTEXITCODE -ne 0) { throw "vcpkg install failed ($LASTEXITCODE)" } + +# ─── CMake configure (deps pass) ───────────────────────────────────────────── +Write-Host "=== CMake configure ===" +$cmakeArgs = @( + '-S', "$SrcDir\cpp", + '-B', $buildDir, + '-GNinja', + "-DQDK_UARCH=$March", + '-DQDK_CHEMISTRY_ENABLE_COVERAGE=OFF', + '-DQDK_CHEMISTRY_ENABLE_MPI=OFF', + '-DMACIS_ENABLE_TESTS=ON', + '-DBUILD_SHARED_LIBS=OFF', + '-DBUILD_TESTING=ON', + "-DCMAKE_BUILD_TYPE=$BuildType", + "-DCMAKE_C_COMPILER=$ClangClPath", + "-DCMAKE_CXX_COMPILER=$ClangClPath", + "-DCMAKE_INSTALL_PREFIX=$SrcDir\install-clang-cl", + "-DCMAKE_TOOLCHAIN_FILE=$VcpkgRoot\scripts\buildsystems\vcpkg.cmake", + "-DVCPKG_CHAINLOAD_TOOLCHAIN_FILE=$SrcDir\.pipelines\toolchains\windows.cmake", + '-DVCPKG_TARGET_TRIPLET=x64-windows-static-md', + "-DVCPKG_INSTALLED_DIR=$SrcDir\vcpkg_installed", + '-DFETCHCONTENT_QUIET=OFF' +) +cmake @cmakeArgs +if ($LASTEXITCODE -ne 0) { throw "CMake configure failed ($LASTEXITCODE)" } + +# ─── Build slow FetchContent dependency targets ─────────────────────────────── +# Ninja reuses any previously-built artefacts from a restored partial cache, so +# this step is incremental when a stale cache is present. +Write-Host "=== Building C++ dependencies ===" +cmake --build $buildDir --target libint2 ecpint gauxc blaspp lapackpp +if ($LASTEXITCODE -ne 0) { throw "Dependency build failed ($LASTEXITCODE)" } diff --git a/.pipelines/pip-scripts/build-pip-wheels-windows.ps1 b/.pipelines/pip-scripts/build-pip-wheels-windows.ps1 new file mode 100644 index 000000000..10848245a --- /dev/null +++ b/.pipelines/pip-scripts/build-pip-wheels-windows.ps1 @@ -0,0 +1,212 @@ +<# +.SYNOPSIS + Build the full qdk C++ library, run C++ tests, and produce a Python wheel + using a conda-isolated build environment. + +.DESCRIPTION + This script is always invoked (unlike the deps script which is skipped on + cache hit). It: + 1. Rebuilds the full qdk + macis library on top of the cached dep tree. + 2. Runs the C++ test suite (ctest); results are written to an XML file + that the calling YAML template publishes with PublishTestResults@2. + 3. Installs the C++ library under install-clang-cl/. + 4. Bootstraps a conda environment via ms-ensureconda (the Microsoft-approved + conda bootstrapper). Public channels are blocked in 1ES CFSClean; all + packages are fetched from the Azure Artifacts Conda/PyPI feed. + 5. Installs build tooling (pip, build, scikit-build-core, etc.) and + generates a Component Governance PipReport. + 6. Rewrites relative README links to absolute GitHub URLs (equivalent to + prepare-readme.sh). + 7. Builds the distribution wheel with scikit-build-core. No wheel repair + is needed: x64-windows-static-md statically links all vcpkg deps. + 8. Copies the wheel to python/repaired_wheelhouse/. + + Prerequisites (set by the YAML template before this script runs): + - INCLUDE, LIB, PATH already contain MSVC / clang-cl entries. + - CMAKE_BUILD_PARALLEL_LEVEL is set (or computed here). + - SYSTEM_ACCESSTOKEN is in the environment (mapped by the YAML step via + env: SYSTEM_ACCESSTOKEN: $(System.AccessToken)). + - PIP_INDEX_URL is set by PipAuthenticate@1 at job level. +#> +param( + [Parameter(Mandatory)] [string]$SrcDir, + [Parameter(Mandatory)] [string]$ClangClPath, + [string]$March = 'x86-64-v3', + [string]$BuildType = 'Release', + [string]$PythonVersion = '3.11', + [string]$DevTag = 'None', + [string]$VcpkgRoot +) +$ErrorActionPreference = 'Stop' + +if (-not $VcpkgRoot) { + $VcpkgRoot = if ($env:VCPKG_INSTALLATION_ROOT) { $env:VCPKG_INSTALLATION_ROOT } else { 'C:\vcpkg' } +} + +$buildDir = "$SrcDir\cpp\build-clang-cl" +$installDir = "$SrcDir\install-clang-cl" + +if (-not $env:CMAKE_BUILD_PARALLEL_LEVEL) { + $cpu = [int]$env:NUMBER_OF_PROCESSORS + $ramGB = [math]::Floor((Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory / 1GB) + $jobs = [math]::Min($cpu, [math]::Max(1, [math]::Floor($ramGB / 4))) + Write-Host "CPUs=$cpu RAM=${ramGB} GB -> CMAKE_BUILD_PARALLEL_LEVEL=$jobs" + $env:CMAKE_BUILD_PARALLEL_LEVEL = $jobs +} + +# ─── CMake reconfigure + full build ────────────────────────────────────────── +# Re-configure the same build dir to refresh any source-path references, then +# build all qdk + macis targets. Dep .libs from the cache (or the deps step) +# are already present — Ninja skips them. +Write-Host "=== CMake configure (full build) ===" +$cmakeArgs = @( + '-S', "$SrcDir\cpp", + '-B', $buildDir, + '-GNinja', + "-DQDK_UARCH=$March", + '-DQDK_CHEMISTRY_ENABLE_COVERAGE=OFF', + '-DQDK_CHEMISTRY_ENABLE_MPI=OFF', + '-DMACIS_ENABLE_TESTS=ON', + '-DBUILD_SHARED_LIBS=OFF', + '-DBUILD_TESTING=ON', + "-DCMAKE_BUILD_TYPE=$BuildType", + "-DCMAKE_C_COMPILER=$ClangClPath", + "-DCMAKE_CXX_COMPILER=$ClangClPath", + "-DCMAKE_INSTALL_PREFIX=$installDir", + "-DCMAKE_TOOLCHAIN_FILE=$VcpkgRoot\scripts\buildsystems\vcpkg.cmake", + "-DVCPKG_CHAINLOAD_TOOLCHAIN_FILE=$SrcDir\.pipelines\toolchains\windows.cmake", + '-DVCPKG_TARGET_TRIPLET=x64-windows-static-md', + "-DVCPKG_INSTALLED_DIR=$SrcDir\vcpkg_installed", + '-DFETCHCONTENT_QUIET=OFF' +) +cmake @cmakeArgs +if ($LASTEXITCODE -ne 0) { throw "CMake configure failed ($LASTEXITCODE)" } + +Write-Host "=== CMake build ===" +cmake --build $buildDir +if ($LASTEXITCODE -ne 0) { throw "CMake build failed ($LASTEXITCODE)" } + +# ─── C++ tests ─────────────────────────────────────────────────────────────── +# Exclude MACIS_SERIAL_TEST and libint2/unit (compile-at-test-time meta-test) +# which exceed the ctest timeout under clang-cl. +# Save the ctest exit code; throw AFTER the wheel has been built so the +# PublishTestResults@2 task in the YAML always has something to publish. +Write-Host "=== ctest ===" +Push-Location $buildDir +ctest --output-on-failure --verbose --timeout 400 ` + --output-junit ctest_results.xml ` + -E "MACIS_SERIAL_TEST|libint2/unit" +$ctestCode = $LASTEXITCODE +Pop-Location +if ($ctestCode -ne 0) { + Write-Warning "ctest returned $ctestCode — continuing to build wheel, then will throw." +} + +# ─── Install C++ library ───────────────────────────────────────────────────── +Write-Host "=== cmake --install ===" +cmake --install $buildDir +if ($LASTEXITCODE -ne 0) { throw "CMake install failed ($LASTEXITCODE)" } + +# ─── Conda bootstrap ───────────────────────────────────────────────────────── +Write-Host "=== Conda bootstrap ===" +$condaExe = & "$PSScriptRoot\bootstrap-conda.ps1" -EnvName buildenv -PythonVersion $PythonVersion +if ($LASTEXITCODE -ne 0) { throw "Conda bootstrap failed ($LASTEXITCODE)" } + +# ─── Install Python build tooling ──────────────────────────────────────────── +Write-Host "=== pip install build tooling ===" +& $condaExe run -n buildenv python -m pip install --upgrade pip +if ($LASTEXITCODE -ne 0) { throw "pip upgrade failed" } +& $condaExe run -n buildenv python -m pip install -r "$SrcDir\.pipelines\requirements.txt" +if ($LASTEXITCODE -ne 0) { throw "pip install requirements failed" } + +# ─── Component Governance PipReport ────────────────────────────────────────── +# Snapshot the buildenv and feed it to pip install --report so Component +# Governance's PipReportDetector sees every package. +$manifestDir = "$SrcDir\python\build\build-manifest" +New-Item -ItemType Directory -Force -Path $manifestDir | Out-Null +& $condaExe run -n buildenv python -m pip list --format=freeze | + Tee-Object "$manifestDir\requirements.txt" +& $condaExe run -n buildenv python -m pip install ` + --dry-run --ignore-installed --quiet ` + --report "$manifestDir\component-detection-pip-report.json" ` + -r "$manifestDir\requirements.txt" +# PipReport failures are non-fatal (Component Governance is best-effort). +$LASTEXITCODE = 0 + +# ─── Prepare README (equivalent to prepare-readme.sh) ──────────────────────── +Write-Host "=== Prepare README ===" +try { + $ghBlob = 'https://github.com/microsoft/qdk-chemistry/blob/main' + $ghTree = 'https://github.com/microsoft/qdk-chemistry/tree/main' + $lines = Get-Content "$SrcDir\README.md" + + # Apply substitutions line by line (sed -E equivalent). + $out = [System.Collections.Generic.List[string]]::new() + $deleting = $false + foreach ($line in $lines) { + # Range delete: ## Project Structure ... closing ``` + if ($line -match '^## Project Structure$') { $deleting = $true; continue } + if ($deleting) { + if ($line -match '^```$') { $deleting = $false } + continue + } + $line = $line -replace '\]\(\./([^)]+)\)', "]($ghBlob/`$1)" + $line = $line -replace '\]\(([A-Z][A-Z_]*\.(md|txt))\)', "]($ghBlob/`$1)" + $line = $line -replace '`examples/`', "[``examples/``]($ghTree/examples)" + $line = $line -replace '`cpp/include/`', "[``cpp/include/``]($ghTree/cpp/include)" + $out.Add($line) + } + $out | Set-Content "$SrcDir\python\README.md" + Write-Host "README prepared." +} catch { + Write-Warning "prepare-readme failed: $_ (continuing)" +} + +# ─── Build Python wheel ─────────────────────────────────────────────────────── +# scikit-build-core picks up the pre-built C++ library via CMAKE_PREFIX_PATH so +# FetchContent is never triggered. QDK_ALLOW_DEPENDENCY_FETCH=OFF enforces this. +Write-Host "=== python -m build --wheel ===" +$prefix = "$SrcDir\vcpkg_installed\x64-windows-static-md;$installDir" +$buildArgs = @( + 'run', '-n', 'buildenv', + 'python', '-m', 'build', '--wheel', + "-C=build-dir=build/{wheel_tag}", + "-C=cmake.args=-GNinja", + "-C=cmake.define.QDK_UARCH=$March", + '-C=cmake.define.BUILD_SHARED_LIBS=OFF', + '-C=cmake.define.QDK_CHEMISTRY_ENABLE_MPI=OFF', + '-C=cmake.define.QDK_ENABLE_OPENMP=OFF', + '-C=cmake.define.QDK_CHEMISTRY_ENABLE_COVERAGE=OFF', + '-C=cmake.define.BUILD_TESTING=OFF', + '-C=cmake.define.QDK_ALLOW_DEPENDENCY_FETCH=OFF', + "-C=cmake.define.CMAKE_C_COMPILER=$ClangClPath", + "-C=cmake.define.CMAKE_CXX_COMPILER=$ClangClPath", + "-C=cmake.define.CMAKE_TOOLCHAIN_FILE=$VcpkgRoot\scripts\buildsystems\vcpkg.cmake", + "-C=cmake.define.VCPKG_CHAINLOAD_TOOLCHAIN_FILE=$SrcDir\.pipelines\toolchains\windows.cmake", + '-C=cmake.define.VCPKG_TARGET_TRIPLET=x64-windows-static-md', + "-C=cmake.define.VCPKG_INSTALLED_DIR=$SrcDir\vcpkg_installed", + "-C=cmake.define.CMAKE_PREFIX_PATH=$prefix" +) +Push-Location "$SrcDir\python" +& $condaExe @buildArgs +$wheelCode = $LASTEXITCODE +Pop-Location +if ($wheelCode -ne 0) { throw "python -m build --wheel failed ($wheelCode)" } + +# ─── Copy wheel to repaired_wheelhouse ─────────────────────────────────────── +# No wheel repair needed: x64-windows-static-md statically links all vcpkg +# deps; the only dynamic runtime deps (MSVCP140.dll, VCRUNTIME140.dll, UCRT) +# are always present on modern Windows. +$distDir = "$SrcDir\python\dist" +$outputDir = "$SrcDir\python\repaired_wheelhouse" +New-Item -ItemType Directory -Force -Path $outputDir | Out-Null +$wheels = Get-ChildItem "$distDir\qdk_chemistry*.whl" +if ($wheels.Count -ne 1) { + throw "Expected exactly 1 wheel in dist/, found $($wheels.Count): $($wheels.Name -join ', ')" +} +Copy-Item $wheels[0].FullName $outputDir +Write-Host "Wheel : $($wheels[0].Name)" +Write-Host "Output: $outputDir" + +# Deferred ctest failure: publish results step has already run by this point. +if ($ctestCode -ne 0) { throw "ctest failed ($ctestCode)" } diff --git a/.pipelines/python-wheels.yaml b/.pipelines/python-wheels.yaml index a8b063bf2..6a368657e 100644 --- a/.pipelines/python-wheels.yaml +++ b/.pipelines/python-wheels.yaml @@ -41,6 +41,11 @@ parameters: arch: aarch64 march: armv8-a imageName: ACES_VM_SharedPool_Tahoe + - name: windows_x86_64 + os: windows + arch: x86_64 + march: x86-64-v3 + imageName: windows-2025-1espt displayName: Target platforms - name: pythonVersions type: stringList @@ -93,7 +98,7 @@ extends: binaryLanguages: cpp psscriptanalyzer: enabled: false - justificationForDisabling: Powershell is not used in this repository. + justificationForDisabling: PSScriptAnalyzer not yet configured for the new Windows pip-scripts. break: false armory: enabled: false @@ -127,6 +132,12 @@ extends: - ImageOverride -equals ${{ target.imageName }} os: ${{ target.os }} hostArchitecture: Arm64 + ${{ elseif eq(target.os, 'windows') }}: + pool: + name: $(CPU_POOL_X86) + demands: + - ImageOverride -equals ${{ target.imageName }} + os: windows ${{ else }}: pool: name: $(CPU_POOL_X86) @@ -158,7 +169,7 @@ extends: onlyAddExtraIndex: false # Build - Linux x86_64 - - ${{ if eq(target.arch, 'x86_64') }}: + - ${{ if and(eq(target.arch, 'x86_64'), eq(target.os, 'linux')) }}: - template: .pipelines/templates/build-pip-wheels.yml@self parameters: march: ${{ target.march }} @@ -171,7 +182,7 @@ extends: devTag: ${{ parameters.devTag }} # Test - Linux x86_64 - - ${{ if eq(target.arch, 'x86_64') }}: + - ${{ if and(eq(target.arch, 'x86_64'), eq(target.os, 'linux')) }}: - template: .pipelines/templates/test-pip-wheels.yml@self parameters: agentBuildDirectory: $(Agent.BuildDirectory) @@ -233,11 +244,32 @@ extends: # the wheel is still built and published. condition: and(succeeded(), ne(variables['pythonVersion'], '3.10')) + # Build - Windows x86_64 + - ${{ if eq(target.os, 'windows') }}: + - template: .pipelines/templates/build-pip-wheels-windows.yml@self + parameters: + march: ${{ target.march }} + agentBuildDirectory: $(Agent.BuildDirectory) + pythonVersion: $(pythonVersion) + devTag: ${{ parameters.devTag }} + + # Test - Windows x86_64 + - ${{ if eq(target.os, 'windows') }}: + - template: .pipelines/templates/test-pip-wheels-windows.yml@self + parameters: + agentBuildDirectory: $(Agent.BuildDirectory) + pythonVersion: $(pythonVersion) + runSlowTests: ${{ parameters.runSlowTests }} + # Python 3.10 tests temporarily disabled (qre/cirq cannot resolve on 3.10); + # the wheel is still built and published. + condition: and(succeeded(), ne(variables['pythonVersion'], '3.10')) + # SBoM Manifest Generator Task requires we transfer ownership of the wheel directory - # back to the original base image user - - script: | - sudo chown -R $(id -u):$(id -g) $(System.DefaultWorkingDirectory)/python/repaired_wheelhouse - displayName: Fix wheel directory ownership + # back to the original base image user (Linux/macOS only; not needed on Windows). + - ${{ if ne(target.os, 'windows') }}: + - script: | + sudo chown -R $(id -u):$(id -g) $(System.DefaultWorkingDirectory)/python/repaired_wheelhouse + displayName: Fix wheel directory ownership - task: TwineAuthenticate@1 inputs: @@ -341,6 +373,21 @@ extends: - input: pipelineArtifact artifactName: macos-aarch64-wheels-python3.14 targetPath: $(System.DefaultWorkingDirectory)/artifacts/macos-aarch64-wheels-python3.14 + - input: pipelineArtifact + artifactName: windows-x86_64-wheels-python3.10 + targetPath: $(System.DefaultWorkingDirectory)/artifacts/windows-x86_64-wheels-python3.10 + - input: pipelineArtifact + artifactName: windows-x86_64-wheels-python3.11 + targetPath: $(System.DefaultWorkingDirectory)/artifacts/windows-x86_64-wheels-python3.11 + - input: pipelineArtifact + artifactName: windows-x86_64-wheels-python3.12 + targetPath: $(System.DefaultWorkingDirectory)/artifacts/windows-x86_64-wheels-python3.12 + - input: pipelineArtifact + artifactName: windows-x86_64-wheels-python3.13 + targetPath: $(System.DefaultWorkingDirectory)/artifacts/windows-x86_64-wheels-python3.13 + - input: pipelineArtifact + artifactName: windows-x86_64-wheels-python3.14 + targetPath: $(System.DefaultWorkingDirectory)/artifacts/windows-x86_64-wheels-python3.14 steps: - script: | mkdir -p $(System.DefaultWorkingDirectory)/target/wheels diff --git a/.pipelines/templates/build-pip-wheels-windows.yml b/.pipelines/templates/build-pip-wheels-windows.yml new file mode 100644 index 000000000..de2860114 --- /dev/null +++ b/.pipelines/templates/build-pip-wheels-windows.yml @@ -0,0 +1,176 @@ +parameters: +- name: march + type: string + default: x86-64-v3 +- name: buildType + type: string + default: Release +- name: agentBuildDirectory + type: string + default: $(Agent.BuildDirectory) +- name: pythonVersion + type: string + default: '3.11' +- name: devTag + type: string + default: 'None' + +steps: +- checkout: self + persistCredentials: true + path: qdk-chemistry + fetchDepth: 1 + retryCountOnTaskFailure: 3 + +- powershell: | + python "$(System.DefaultWorkingDirectory)\.pipelines\pip-scripts\versioning.py" ` + --version-file "$(System.DefaultWorkingDirectory)\VERSION" ` + --dev-tag ${{ parameters.devTag }} + Get-Content "$(System.DefaultWorkingDirectory)\VERSION" + displayName: Validate/Set Development Version for Wheels + condition: ne('${{ parameters.devTag }}', 'None') + +# Import the VS x64 developer environment (INCLUDE, LIB, PATH, etc.) so that +# clang-cl and the MSVC linker are available to all subsequent steps. +# Also resolves the MSVC toolset version for the dependency cache key. +- powershell: | + $ErrorActionPreference = 'Stop' + + $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" + $vsPath = & $vswhere -latest -products * -property installationPath + if (-not $vsPath) { throw "No Visual Studio installation found" } + Write-Host "Visual Studio: $vsPath" + + $vcvarsall = "$vsPath\VC\Auxiliary\Build\vcvarsall.bat" + if (-not (Test-Path $vcvarsall)) { throw "vcvarsall.bat not found at $vcvarsall" } + + # Snapshot env, run vcvarsall x64, diff to capture changes. + $before = @{} + Get-ChildItem env: | ForEach-Object { $before[$_.Name] = $_.Value } + $tmp = [System.IO.Path]::GetTempFileName() + cmd /c "`"$vcvarsall`" x64 && set > `"$tmp`"" + if ($LASTEXITCODE -ne 0) { throw "vcvarsall.bat failed ($LASTEXITCODE)" } + $after = @{} + Get-Content $tmp | ForEach-Object { + if ($_ -match '^([^=]+)=(.*)$') { $after[$matches[1]] = $matches[2] } + } + Remove-Item $tmp + + # Locate clang-cl from the VS LLVM component; fall back to PATH. + $clangCl = $null + foreach ($c in @("$vsPath\VC\Tools\Llvm\x64\bin\clang-cl.exe", + "$vsPath\VC\Tools\Llvm\bin\clang-cl.exe")) { + if (Test-Path $c) { $clangCl = $c; break } + } + if (-not $clangCl) { + $clangCl = (Get-Command clang-cl.exe -ErrorAction SilentlyContinue)?.Source + } + if (-not $clangCl) { throw "clang-cl.exe not found under $vsPath or on PATH" } + $clangDir = Split-Path $clangCl + Write-Host "clang-cl: $clangCl" + & $clangCl --version 2>&1 | Write-Host + + # Apply to current process. + foreach ($name in $after.Keys) { + [System.Environment]::SetEnvironmentVariable($name, $after[$name], 'Process') + } + $env:PATH = "$clangDir;$env:PATH" + + # Export non-PATH changes to subsequent ADO steps. + foreach ($name in $after.Keys) { + if ($name -ieq 'Path') { continue } + if ($before[$name] -ne $after[$name]) { + Write-Host "##vso[task.setvariable variable=$name]$($after[$name])" + } + } + # Prepend new PATH entries + clang-cl directory. + $beforePath = @($before['Path'] -split ';') + $newEntries = @($after['Path'] -split ';') | Where-Object { $_ -and ($beforePath -notcontains $_) } + (@($clangDir) + $newEntries) | ForEach-Object { Write-Host "##vso[task.prependpath]$_" } + + # MSVC toolset version: stable identifier for cache key. + $toolsetFile = "$vsPath\VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt" + $toolset = (Get-Content $toolsetFile -ErrorAction Stop | Select-Object -First 1).Trim() + Write-Host "MSVC toolset: $toolset" + Write-Host "##vso[task.setvariable variable=MSVC_TOOLSET]$toolset" + Write-Host "##vso[task.setvariable variable=CLANGCL_PATH]$clangCl" + displayName: Set up clang-cl / MSVC environment + +# Hash all files that influence how the C++ dependencies are built. +# The result is used in the Cache@2 key so the cache is invalidated on any +# dep-affecting change without listing every file individually in the key. +- powershell: | + $root = "$(System.DefaultWorkingDirectory)" + $files = @( + "$root\vcpkg.json", + "$root\vcpkg-configuration.json", + "$root\cpp\manifest\qdk-chemistry\cgmanifest.json", + "$root\external\macis\manifest\cgmanifest.json", + "$root\cpp\cmake\third_party.cmake", + "$root\cpp\cmake\modules\DependencyManager.cmake", + "$root\external\macis\src\lobpcgxx\CMakeLists.txt", + "$root\.pipelines\toolchains\windows.cmake" + ) + if (Test-Path "$root\vcpkg-overlay") { + $files += Get-ChildItem "$root\vcpkg-overlay" -Recurse -File | + Select-Object -ExpandProperty FullName + } + if (Test-Path "$root\cpp\cmake\patches") { + $files += Get-ChildItem "$root\cpp\cmake\patches" -Recurse -File | + Select-Object -ExpandProperty FullName + } + $hashes = $files | Where-Object { Test-Path $_ } | Sort-Object | + ForEach-Object { (Get-FileHash $_ -Algorithm SHA256).Hash } + $combined = $hashes -join "," + $hashBytes = [System.Security.Cryptography.SHA256]::Create().ComputeHash( + [System.Text.Encoding]::UTF8.GetBytes($combined)) + $hashStr = ($hashBytes | ForEach-Object { $_.ToString("x2") }) -join "" + Write-Host "Dependency files hash: $hashStr" + Write-Host "##vso[task.setvariable variable=DEPS_FILES_HASH]$hashStr" + displayName: Compute dependency cache key + +# Restore the dependency build dir (vcpkg + FetchContent .libs). If a matching +# cache exists the deps script is skipped entirely. ADO Cache@2 saves automatically +# at job end. +- task: Cache@2 + displayName: Restore / save C++ dependency cache + inputs: + key: '"windows-deps-clang-cl-x64-windows-static-md-v1" | "$(MSVC_TOOLSET)" | "$(DEPS_FILES_HASH)"' + path: | + $(System.DefaultWorkingDirectory)/vcpkg_installed + $(System.DefaultWorkingDirectory)/cpp/build-clang-cl + cacheHitVar: DEPS_CACHE_RESTORED + +# Build vcpkg packages, configure CMake, and build the slow FetchContent targets +# (libint2, ecpint, gauxc, blaspp, lapackpp). Skipped entirely on cache hit. +- powershell: | + & "$(System.DefaultWorkingDirectory)\.pipelines\pip-scripts\build-pip-wheels-windows-deps.ps1" ` + -SrcDir "$(System.DefaultWorkingDirectory)" ` + -March "${{ parameters.march }}" ` + -BuildType "${{ parameters.buildType }}" ` + -ClangClPath "$(CLANGCL_PATH)" + displayName: Build C++ dependencies (libint2 / gauxc / ecpint / blaspp / lapackpp) + condition: ne(variables['DEPS_CACHE_RESTORED'], 'true') + +# Full qdk build, C++ tests, conda environment setup, and Python wheel build. +# SYSTEM_ACCESSTOKEN is required by bootstrap-conda for the Azure Artifacts feed. +- powershell: | + & "$(System.DefaultWorkingDirectory)\.pipelines\pip-scripts\build-pip-wheels-windows.ps1" ` + -SrcDir "$(System.DefaultWorkingDirectory)" ` + -March "${{ parameters.march }}" ` + -BuildType "${{ parameters.buildType }}" ` + -ClangClPath "$(CLANGCL_PATH)" ` + -PythonVersion "${{ parameters.pythonVersion }}" ` + -DevTag "${{ parameters.devTag }}" + displayName: Build C++ library, run tests, and build Python wheel + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + OMP_NUM_THREADS: '2' + +- task: PublishTestResults@2 + displayName: Publish C++ test results + inputs: + testResultsFormat: JUnit + testResultsFiles: '$(System.DefaultWorkingDirectory)/cpp/build-clang-cl/ctest_results.xml' + testRunTitle: 'C++ tests - Windows x86_64 Python ${{ parameters.pythonVersion }}' + condition: always() diff --git a/.pipelines/templates/test-pip-wheels-windows.yml b/.pipelines/templates/test-pip-wheels-windows.yml new file mode 100644 index 000000000..9027e0f49 --- /dev/null +++ b/.pipelines/templates/test-pip-wheels-windows.yml @@ -0,0 +1,75 @@ +parameters: +- name: agentBuildDirectory + type: string + default: $(Agent.BuildDirectory) +- name: pythonVersion + type: string + default: '3.11' +- name: runSlowTests + type: boolean + default: true +- name: condition + type: string + default: succeeded() + +steps: +# Bootstrap conda and create an isolated test environment, matching the +# approach used for Linux/macOS via bootstrap-conda.sh. +# SYSTEM_ACCESSTOKEN (mapped via env: below) authenticates the conda install +# against the Azure Artifacts feed (public channels blocked in 1ES CFSClean). +- powershell: | + $ErrorActionPreference = 'Stop' + $scriptPath = "$(System.DefaultWorkingDirectory)\.pipelines\pip-scripts\bootstrap-conda.ps1" + $condaExe = & $scriptPath -EnvName testenv -PythonVersion '${{ parameters.pythonVersion }}' + if ($LASTEXITCODE -ne 0) { throw "Conda bootstrap failed ($LASTEXITCODE)" } + # Export conda exe path for subsequent steps. + Write-Host "##vso[task.setvariable variable=CONDA_EXE]$condaExe" + displayName: Set up conda test environment (Python ${{ parameters.pythonVersion }}) + condition: ${{ parameters.condition }} + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + +# Install the wheel together with its test extra dependencies. +- powershell: | + $ErrorActionPreference = 'Stop' + $wheels = Get-ChildItem "$(System.DefaultWorkingDirectory)\python\repaired_wheelhouse\qdk_chemistry*.whl" + if ($wheels.Count -ne 1) { + throw "Expected exactly 1 wheel, found $($wheels.Count): $($wheels.Name -join ', ')" + } + $wheel = $wheels[0].FullName + Write-Host "Installing: $wheel" + & "$(CONDA_EXE)" run -n testenv python -m pip install --upgrade pip + if ($LASTEXITCODE -ne 0) { throw "pip upgrade failed" } + & "$(CONDA_EXE)" run -n testenv python -m pip install "$wheel[test]" + if ($LASTEXITCODE -ne 0) { throw "pip install wheel[test] failed ($LASTEXITCODE)" } + displayName: Install wheel with test dependencies + condition: ${{ parameters.condition }} + +# Snapshot the testenv and generate a pip install --report for Component +# Governance's PipReportDetector (mirrors test-pip-wheels.sh lines 95-102). +- powershell: | + $manifestDir = "$(System.DefaultWorkingDirectory)\python\build\test-manifest" + New-Item -ItemType Directory -Force -Path $manifestDir | Out-Null + & "$(CONDA_EXE)" run -n testenv python -m pip list --format=freeze ` + --exclude qdk_chemistry | Tee-Object "$manifestDir\requirements.txt" + & "$(CONDA_EXE)" run -n testenv python -m pip install ` + --dry-run --ignore-installed --quiet ` + --report "$manifestDir\component-detection-pip-report.json" ` + -r "$manifestDir\requirements.txt" + displayName: Generate Component Governance PipReport + condition: ${{ parameters.condition }} + continueOnError: true + +- powershell: | + $ErrorActionPreference = 'Stop' + Push-Location "$(System.DefaultWorkingDirectory)\python" + & "$(CONDA_EXE)" run -n testenv ` + --env QSHARP_PYTHON_TELEMETRY=false ` + --env "QDK_CHEMISTRY_RUN_SLOW_TESTS=${{ parameters.runSlowTests }}" ` + --env OMP_NUM_THREADS=2 ` + python -m pytest -v tests/ + $code = $LASTEXITCODE + Pop-Location + if ($code -ne 0) { throw "pytest failed ($code)" } + displayName: Run Python tests + condition: ${{ parameters.condition }} From 959d690a3b005611e26497cc8e46b6b1fbab0336 Mon Sep 17 00:00:00 2001 From: Loris Ercole Date: Mon, 29 Jun 2026 17:28:54 +0200 Subject: [PATCH 16/37] install vcpkg if not pre-installed --- .github/actions/windows-msvc-env/action.yml | 12 ++++++++++++ .pipelines/templates/build-pip-wheels-windows.yml | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/.github/actions/windows-msvc-env/action.yml b/.github/actions/windows-msvc-env/action.yml index 26f630fdc..c1ae8765f 100644 --- a/.github/actions/windows-msvc-env/action.yml +++ b/.github/actions/windows-msvc-env/action.yml @@ -92,3 +92,15 @@ runs: "toolset=$toolset" >> $env:GITHUB_OUTPUT "cxx-path=$cxx" >> $env:GITHUB_OUTPUT + + # Bootstrap vcpkg if not pre-installed. + $vcpkgRoot = if ($env:VCPKG_INSTALLATION_ROOT) { $env:VCPKG_INSTALLATION_ROOT } else { 'C:\vcpkg' } + if (Test-Path "$vcpkgRoot\vcpkg.exe") { + Write-Host "vcpkg already available at $vcpkgRoot" + } else { + Write-Host "vcpkg not found — bootstrapping into $vcpkgRoot" + git clone https://github.com/microsoft/vcpkg.git $vcpkgRoot --depth 1 + & "$vcpkgRoot\bootstrap-vcpkg.bat" -disableMetrics + if ($LASTEXITCODE -ne 0) { throw "vcpkg bootstrap failed ($LASTEXITCODE)" } + "VCPKG_INSTALLATION_ROOT=$vcpkgRoot" >> $env:GITHUB_ENV + } diff --git a/.pipelines/templates/build-pip-wheels-windows.yml b/.pipelines/templates/build-pip-wheels-windows.yml index de2860114..c2fe4058b 100644 --- a/.pipelines/templates/build-pip-wheels-windows.yml +++ b/.pipelines/templates/build-pip-wheels-windows.yml @@ -94,6 +94,18 @@ steps: Write-Host "MSVC toolset: $toolset" Write-Host "##vso[task.setvariable variable=MSVC_TOOLSET]$toolset" Write-Host "##vso[task.setvariable variable=CLANGCL_PATH]$clangCl" + + # Bootstrap vcpkg if not pre-installed. + $vcpkgRoot = if ($env:VCPKG_INSTALLATION_ROOT) { $env:VCPKG_INSTALLATION_ROOT } else { 'C:\vcpkg' } + if (Test-Path "$vcpkgRoot\vcpkg.exe") { + Write-Host "vcpkg already available at $vcpkgRoot" + } else { + Write-Host "vcpkg not found — bootstrapping into $vcpkgRoot" + git clone https://github.com/microsoft/vcpkg.git $vcpkgRoot --depth 1 + & "$vcpkgRoot\bootstrap-vcpkg.bat" -disableMetrics + if ($LASTEXITCODE -ne 0) { throw "vcpkg bootstrap failed ($LASTEXITCODE)" } + Write-Host "##vso[task.setvariable variable=VCPKG_INSTALLATION_ROOT]$vcpkgRoot" + } displayName: Set up clang-cl / MSVC environment # Hash all files that influence how the C++ dependencies are built. From 664850919fbddd3f17b4f5982d711bb3a4764683 Mon Sep 17 00:00:00 2001 From: Loris Ercole Date: Mon, 29 Jun 2026 17:41:56 +0200 Subject: [PATCH 17/37] ci(windows): bootstrap vcpkg and fix VCPKG_ROOT in env setup step Bootstrap vcpkg from GitHub if not pre-installed on the agent image (e.g. 1ES Starter Windows 2025). On MMS images where C:\vcpkg already exists this is a no-op. Also override VCPKG_ROOT after vcvarsall so it points to our standalone vcpkg installation rather than VS's bundled copy, eliminating the 'mismatched VCPKG_ROOT' warning emitted by vcpkg.exe. Both changes are folded into the existing 'Set up clang-cl / MSVC environment' step (ADO template) and the windows-msvc-env composite action (GitHub Actions), so no new steps are added. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/actions/windows-msvc-env/action.yml | 3 +++ .pipelines/templates/build-pip-wheels-windows.yml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/.github/actions/windows-msvc-env/action.yml b/.github/actions/windows-msvc-env/action.yml index c1ae8765f..a105ca429 100644 --- a/.github/actions/windows-msvc-env/action.yml +++ b/.github/actions/windows-msvc-env/action.yml @@ -104,3 +104,6 @@ runs: if ($LASTEXITCODE -ne 0) { throw "vcpkg bootstrap failed ($LASTEXITCODE)" } "VCPKG_INSTALLATION_ROOT=$vcpkgRoot" >> $env:GITHUB_ENV } + # vcvarsall sets VCPKG_ROOT to VS's bundled copy; override it to match our + # standalone installation so vcpkg.exe doesn't warn about the mismatch. + "VCPKG_ROOT=$vcpkgRoot" >> $env:GITHUB_ENV diff --git a/.pipelines/templates/build-pip-wheels-windows.yml b/.pipelines/templates/build-pip-wheels-windows.yml index c2fe4058b..bf539a033 100644 --- a/.pipelines/templates/build-pip-wheels-windows.yml +++ b/.pipelines/templates/build-pip-wheels-windows.yml @@ -106,6 +106,9 @@ steps: if ($LASTEXITCODE -ne 0) { throw "vcpkg bootstrap failed ($LASTEXITCODE)" } Write-Host "##vso[task.setvariable variable=VCPKG_INSTALLATION_ROOT]$vcpkgRoot" } + # vcvarsall sets VCPKG_ROOT to VS's bundled copy; override it to match our + # standalone installation so vcpkg.exe doesn't warn about the mismatch. + Write-Host "##vso[task.setvariable variable=VCPKG_ROOT]$vcpkgRoot" displayName: Set up clang-cl / MSVC environment # Hash all files that influence how the C++ dependencies are built. From 4f29fdf723c38a2a1fe09814cdf2ce7db792fbc4 Mon Sep 17 00:00:00 2001 From: Loris Ercole <30901257+lorisercole@users.noreply.github.com> Date: Mon, 29 Jun 2026 18:11:53 +0200 Subject: [PATCH 18/37] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .github/workflows/build-and-test-windows.yaml | 8 ++++---- .pipelines/pip-scripts/build-pip-wheels-windows.ps1 | 10 ++++++---- .pipelines/templates/test-pip-wheels-windows.yml | 6 ++++-- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build-and-test-windows.yaml b/.github/workflows/build-and-test-windows.yaml index 373ec3914..154301dd7 100644 --- a/.github/workflows/build-and-test-windows.yaml +++ b/.github/workflows/build-and-test-windows.yaml @@ -128,7 +128,7 @@ jobs: shell: pwsh run: | # No --parallel / CMAKE_BUILD_PARALLEL_LEVEL: let Ninja use all cores. - cmake --build "cpp/build-${{ matrix.compiler }}" --target libint2 ecpint gauxc blaspp lapackpp + cmake --build "cpp/build-${{ matrix.compiler }}" --target Libint2::cxx ECPINT::ecpint gauxc::gauxc blaspp lapackpp if ($LASTEXITCODE -ne 0) { throw "Dependency build failed ($LASTEXITCODE)" } - name: Save dependency cache (complete) @@ -142,7 +142,7 @@ jobs: windows-deps-${{ matrix.compiler }}-${{ env.VCPKG_TRIPLET }}-vs${{ steps.env.outputs.toolset }}-${{ env.DEPS_CACHE_VERSION }}-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json', 'vcpkg-overlay/**', 'cpp/manifest/qdk-chemistry/cgmanifest.json', 'external/macis/manifest/cgmanifest.json', 'cpp/cmake/third_party.cmake', 'cpp/cmake/patches/**', 'cpp/cmake/modules/DependencyManager.cmake', 'external/macis/src/lobpcgxx/CMakeLists.txt', '.pipelines/toolchains/windows.cmake') }} - name: Save dependency cache (partial, for resume) - if: ${{ always() && steps.build.outcome == 'failure' }} + if: ${{ always() && (steps.build.outcome == 'failure' || steps.build.outcome == 'cancelled') }} uses: actions/cache/save@v5 with: path: | @@ -159,8 +159,8 @@ jobs: run: | # Best-effort: keep the newest partial (for resume) and drop older ones; # once a complete build succeeds, drop all partials for this base key. - $base = "windows-deps-${{ matrix.compiler }}-${{ env.VCPKG_TRIPLET }}-vs${{ steps.env.outputs.toolset }}-${{ env.DEPS_CACHE_VERSION }}-" - $keepNewestPartial = '${{ steps.build.outcome }}' -eq 'failure' + $base = "windows-deps-${{ matrix.compiler }}-${{ env.VCPKG_TRIPLET }}-vs${{ steps.env.outputs.toolset }}-${{ env.DEPS_CACHE_VERSION }}-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json', 'vcpkg-overlay/**', 'cpp/manifest/qdk-chemistry/cgmanifest.json', 'external/macis/manifest/cgmanifest.json', 'cpp/cmake/third_party.cmake', 'cpp/cmake/patches/**', 'cpp/cmake/modules/DependencyManager.cmake', 'external/macis/src/lobpcgxx/CMakeLists.txt', '.pipelines/toolchains/windows.cmake') }}- + $keepNewestPartial = @('failure','cancelled') -contains '${{ steps.build.outcome }}' try { $caches = gh cache list --limit 100 --json id,key,createdAt | ConvertFrom-Json $partials = $caches | diff --git a/.pipelines/pip-scripts/build-pip-wheels-windows.ps1 b/.pipelines/pip-scripts/build-pip-wheels-windows.ps1 index 10848245a..8e1df449f 100644 --- a/.pipelines/pip-scripts/build-pip-wheels-windows.ps1 +++ b/.pipelines/pip-scripts/build-pip-wheels-windows.ps1 @@ -124,8 +124,10 @@ if ($LASTEXITCODE -ne 0) { throw "pip install requirements failed" } # Governance's PipReportDetector sees every package. $manifestDir = "$SrcDir\python\build\build-manifest" New-Item -ItemType Directory -Force -Path $manifestDir | Out-Null -& $condaExe run -n buildenv python -m pip list --format=freeze | - Tee-Object "$manifestDir\requirements.txt" +$reqs = & $condaExe run -n buildenv python -m pip list --format=freeze +if ($LASTEXITCODE -ne 0) { throw "pip list failed ($LASTEXITCODE)" } +$reqs | Set-Content -Encoding utf8 "$manifestDir\requirements.txt" +$reqs | ForEach-Object { Write-Host $_ } & $condaExe run -n buildenv python -m pip install ` --dry-run --ignore-installed --quiet ` --report "$manifestDir\component-detection-pip-report.json" ` @@ -138,7 +140,7 @@ Write-Host "=== Prepare README ===" try { $ghBlob = 'https://github.com/microsoft/qdk-chemistry/blob/main' $ghTree = 'https://github.com/microsoft/qdk-chemistry/tree/main' - $lines = Get-Content "$SrcDir\README.md" + $lines = Get-Content -Encoding utf8 "$SrcDir\README.md" # Apply substitutions line by line (sed -E equivalent). $out = [System.Collections.Generic.List[string]]::new() @@ -156,7 +158,7 @@ try { $line = $line -replace '`cpp/include/`', "[``cpp/include/``]($ghTree/cpp/include)" $out.Add($line) } - $out | Set-Content "$SrcDir\python\README.md" + $out | Set-Content -Encoding utf8 "$SrcDir\python\README.md" Write-Host "README prepared." } catch { Write-Warning "prepare-readme failed: $_ (continuing)" diff --git a/.pipelines/templates/test-pip-wheels-windows.yml b/.pipelines/templates/test-pip-wheels-windows.yml index 9027e0f49..c12d5fb9c 100644 --- a/.pipelines/templates/test-pip-wheels-windows.yml +++ b/.pipelines/templates/test-pip-wheels-windows.yml @@ -50,8 +50,10 @@ steps: - powershell: | $manifestDir = "$(System.DefaultWorkingDirectory)\python\build\test-manifest" New-Item -ItemType Directory -Force -Path $manifestDir | Out-Null - & "$(CONDA_EXE)" run -n testenv python -m pip list --format=freeze ` - --exclude qdk_chemistry | Tee-Object "$manifestDir\requirements.txt" + $reqs = & "$(CONDA_EXE)" run -n testenv python -m pip list --format=freeze --exclude qdk_chemistry + if ($LASTEXITCODE -ne 0) { throw "pip list failed ($LASTEXITCODE)" } + $reqs | Set-Content -Encoding utf8 "$manifestDir\requirements.txt" + $reqs | ForEach-Object { Write-Host $_ } & "$(CONDA_EXE)" run -n testenv python -m pip install ` --dry-run --ignore-installed --quiet ` --report "$manifestDir\component-detection-pip-report.json" ` From 864283ba7566e19a458133949df5a1bf34b5bfe9 Mon Sep 17 00:00:00 2001 From: Loris Ercole Date: Mon, 29 Jun 2026 18:16:07 +0200 Subject: [PATCH 19/37] ci(windows): switch ADO wheel pipeline from clang-cl to MSVC cl [skip ci] - build-pip-wheels-windows.yml: replace clang-cl lookup with cl.exe discovery after vcvarsall x64; export CL_PATH instead of CLANGCL_PATH; update cache key to windows-deps-msvc-*; rename build/install dirs to build-msvc / install-msvc - build-pip-wheels-windows-deps.ps1: rename param ClangClPath -> ClPath, update buildDir and CMake C/CXX_COMPILER args - build-pip-wheels-windows.ps1: same rename + path updates Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../build-pip-wheels-windows-deps.ps1 | 14 +++--- .../pip-scripts/build-pip-wheels-windows.ps1 | 22 +++++----- .../templates/build-pip-wheels-windows.yml | 43 ++++++++----------- 3 files changed, 35 insertions(+), 44 deletions(-) diff --git a/.pipelines/pip-scripts/build-pip-wheels-windows-deps.ps1 b/.pipelines/pip-scripts/build-pip-wheels-windows-deps.ps1 index 90a60d4de..46c58723b 100644 --- a/.pipelines/pip-scripts/build-pip-wheels-windows-deps.ps1 +++ b/.pipelines/pip-scripts/build-pip-wheels-windows-deps.ps1 @@ -9,14 +9,14 @@ pipeline job; the full qdk build then starts from a warm build tree. Prerequisites (set by the YAML template before this script runs): - - INCLUDE, LIB, PATH already contain MSVC / clang-cl entries + - INCLUDE, LIB, PATH already contain MSVC entries (applied via ##vso[task.setvariable] / ##vso[task.prependpath]). - CMAKE_BUILD_PARALLEL_LEVEL is set if caller wants a specific level (otherwise computed here from CPU count and available RAM). #> param( [Parameter(Mandatory)] [string]$SrcDir, - [Parameter(Mandatory)] [string]$ClangClPath, + [Parameter(Mandatory)] [string]$ClPath, [string]$March = 'x86-64-v3', [string]$BuildType = 'Release', [string]$VcpkgRoot @@ -29,9 +29,9 @@ if (-not $VcpkgRoot) { } if (-not (Test-Path "$VcpkgRoot\vcpkg.exe")) { throw "vcpkg.exe not found under '$VcpkgRoot'" } -$buildDir = "$SrcDir\cpp\build-clang-cl" +$buildDir = "$SrcDir\cpp\build-msvc" -# Cap Ninja parallelism by available RAM (~4 GB/job for clang-cl TUs pulling in +# Cap Ninja parallelism by available RAM (~4 GB/job for MSVC TUs pulling in # libint2 headers). On the HB120 runner this typically allows all 120 cores. if (-not $env:CMAKE_BUILD_PARALLEL_LEVEL) { $cpu = [int]$env:NUMBER_OF_PROCESSORS @@ -63,9 +63,9 @@ $cmakeArgs = @( '-DBUILD_SHARED_LIBS=OFF', '-DBUILD_TESTING=ON', "-DCMAKE_BUILD_TYPE=$BuildType", - "-DCMAKE_C_COMPILER=$ClangClPath", - "-DCMAKE_CXX_COMPILER=$ClangClPath", - "-DCMAKE_INSTALL_PREFIX=$SrcDir\install-clang-cl", + "-DCMAKE_C_COMPILER=$ClPath", + "-DCMAKE_CXX_COMPILER=$ClPath", + "-DCMAKE_INSTALL_PREFIX=$SrcDir\install-msvc", "-DCMAKE_TOOLCHAIN_FILE=$VcpkgRoot\scripts\buildsystems\vcpkg.cmake", "-DVCPKG_CHAINLOAD_TOOLCHAIN_FILE=$SrcDir\.pipelines\toolchains\windows.cmake", '-DVCPKG_TARGET_TRIPLET=x64-windows-static-md', diff --git a/.pipelines/pip-scripts/build-pip-wheels-windows.ps1 b/.pipelines/pip-scripts/build-pip-wheels-windows.ps1 index 8e1df449f..13b4791cf 100644 --- a/.pipelines/pip-scripts/build-pip-wheels-windows.ps1 +++ b/.pipelines/pip-scripts/build-pip-wheels-windows.ps1 @@ -9,7 +9,7 @@ 1. Rebuilds the full qdk + macis library on top of the cached dep tree. 2. Runs the C++ test suite (ctest); results are written to an XML file that the calling YAML template publishes with PublishTestResults@2. - 3. Installs the C++ library under install-clang-cl/. + 3. Installs the C++ library under install-msvc/. 4. Bootstraps a conda environment via ms-ensureconda (the Microsoft-approved conda bootstrapper). Public channels are blocked in 1ES CFSClean; all packages are fetched from the Azure Artifacts Conda/PyPI feed. @@ -22,7 +22,7 @@ 8. Copies the wheel to python/repaired_wheelhouse/. Prerequisites (set by the YAML template before this script runs): - - INCLUDE, LIB, PATH already contain MSVC / clang-cl entries. + - INCLUDE, LIB, PATH already contain MSVC entries. - CMAKE_BUILD_PARALLEL_LEVEL is set (or computed here). - SYSTEM_ACCESSTOKEN is in the environment (mapped by the YAML step via env: SYSTEM_ACCESSTOKEN: $(System.AccessToken)). @@ -30,7 +30,7 @@ #> param( [Parameter(Mandatory)] [string]$SrcDir, - [Parameter(Mandatory)] [string]$ClangClPath, + [Parameter(Mandatory)] [string]$ClPath, [string]$March = 'x86-64-v3', [string]$BuildType = 'Release', [string]$PythonVersion = '3.11', @@ -43,8 +43,8 @@ if (-not $VcpkgRoot) { $VcpkgRoot = if ($env:VCPKG_INSTALLATION_ROOT) { $env:VCPKG_INSTALLATION_ROOT } else { 'C:\vcpkg' } } -$buildDir = "$SrcDir\cpp\build-clang-cl" -$installDir = "$SrcDir\install-clang-cl" +$buildDir = "$SrcDir\cpp\build-msvc" +$installDir = "$SrcDir\install-msvc" if (-not $env:CMAKE_BUILD_PARALLEL_LEVEL) { $cpu = [int]$env:NUMBER_OF_PROCESSORS @@ -70,8 +70,8 @@ $cmakeArgs = @( '-DBUILD_SHARED_LIBS=OFF', '-DBUILD_TESTING=ON', "-DCMAKE_BUILD_TYPE=$BuildType", - "-DCMAKE_C_COMPILER=$ClangClPath", - "-DCMAKE_CXX_COMPILER=$ClangClPath", + "-DCMAKE_C_COMPILER=$ClPath", + "-DCMAKE_CXX_COMPILER=$ClPath", "-DCMAKE_INSTALL_PREFIX=$installDir", "-DCMAKE_TOOLCHAIN_FILE=$VcpkgRoot\scripts\buildsystems\vcpkg.cmake", "-DVCPKG_CHAINLOAD_TOOLCHAIN_FILE=$SrcDir\.pipelines\toolchains\windows.cmake", @@ -88,7 +88,7 @@ if ($LASTEXITCODE -ne 0) { throw "CMake build failed ($LASTEXITCODE)" } # ─── C++ tests ─────────────────────────────────────────────────────────────── # Exclude MACIS_SERIAL_TEST and libint2/unit (compile-at-test-time meta-test) -# which exceed the ctest timeout under clang-cl. +# which can exceed the ctest timeout under MSVC. # Save the ctest exit code; throw AFTER the wheel has been built so the # PublishTestResults@2 task in the YAML always has something to publish. Write-Host "=== ctest ===" @@ -180,9 +180,9 @@ $buildArgs = @( '-C=cmake.define.QDK_ENABLE_OPENMP=OFF', '-C=cmake.define.QDK_CHEMISTRY_ENABLE_COVERAGE=OFF', '-C=cmake.define.BUILD_TESTING=OFF', - '-C=cmake.define.QDK_ALLOW_DEPENDENCY_FETCH=OFF', - "-C=cmake.define.CMAKE_C_COMPILER=$ClangClPath", - "-C=cmake.define.CMAKE_CXX_COMPILER=$ClangClPath", + "-C=cmake.define.QDK_ALLOW_DEPENDENCY_FETCH=OFF", + "-C=cmake.define.CMAKE_C_COMPILER=$ClPath", + "-C=cmake.define.CMAKE_CXX_COMPILER=$ClPath", "-C=cmake.define.CMAKE_TOOLCHAIN_FILE=$VcpkgRoot\scripts\buildsystems\vcpkg.cmake", "-C=cmake.define.VCPKG_CHAINLOAD_TOOLCHAIN_FILE=$SrcDir\.pipelines\toolchains\windows.cmake", '-C=cmake.define.VCPKG_TARGET_TRIPLET=x64-windows-static-md', diff --git a/.pipelines/templates/build-pip-wheels-windows.yml b/.pipelines/templates/build-pip-wheels-windows.yml index bf539a033..8b3d5011c 100644 --- a/.pipelines/templates/build-pip-wheels-windows.yml +++ b/.pipelines/templates/build-pip-wheels-windows.yml @@ -31,7 +31,7 @@ steps: condition: ne('${{ parameters.devTag }}', 'None') # Import the VS x64 developer environment (INCLUDE, LIB, PATH, etc.) so that -# clang-cl and the MSVC linker are available to all subsequent steps. +# cl.exe and the MSVC linker are available to all subsequent steps. # Also resolves the MSVC toolset version for the dependency cache key. - powershell: | $ErrorActionPreference = 'Stop' @@ -56,25 +56,16 @@ steps: } Remove-Item $tmp - # Locate clang-cl from the VS LLVM component; fall back to PATH. - $clangCl = $null - foreach ($c in @("$vsPath\VC\Tools\Llvm\x64\bin\clang-cl.exe", - "$vsPath\VC\Tools\Llvm\bin\clang-cl.exe")) { - if (Test-Path $c) { $clangCl = $c; break } - } - if (-not $clangCl) { - $clangCl = (Get-Command clang-cl.exe -ErrorAction SilentlyContinue)?.Source - } - if (-not $clangCl) { throw "clang-cl.exe not found under $vsPath or on PATH" } - $clangDir = Split-Path $clangCl - Write-Host "clang-cl: $clangCl" - & $clangCl --version 2>&1 | Write-Host - - # Apply to current process. + # Apply to current process so Get-Command can resolve cl.exe. foreach ($name in $after.Keys) { [System.Environment]::SetEnvironmentVariable($name, $after[$name], 'Process') } - $env:PATH = "$clangDir;$env:PATH" + + # Locate cl.exe — vcvarsall x64 puts it on PATH. + $cl = (Get-Command cl.exe -ErrorAction SilentlyContinue)?.Source + if (-not $cl) { throw "cl.exe not found on PATH after vcvarsall x64" } + Write-Host "cl.exe: $cl" + & $cl 2>&1 | Select-Object -First 2 | Write-Host # Export non-PATH changes to subsequent ADO steps. foreach ($name in $after.Keys) { @@ -83,17 +74,17 @@ steps: Write-Host "##vso[task.setvariable variable=$name]$($after[$name])" } } - # Prepend new PATH entries + clang-cl directory. + # Prepend new PATH entries from vcvarsall. $beforePath = @($before['Path'] -split ';') $newEntries = @($after['Path'] -split ';') | Where-Object { $_ -and ($beforePath -notcontains $_) } - (@($clangDir) + $newEntries) | ForEach-Object { Write-Host "##vso[task.prependpath]$_" } + $newEntries | ForEach-Object { Write-Host "##vso[task.prependpath]$_" } # MSVC toolset version: stable identifier for cache key. $toolsetFile = "$vsPath\VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt" $toolset = (Get-Content $toolsetFile -ErrorAction Stop | Select-Object -First 1).Trim() Write-Host "MSVC toolset: $toolset" Write-Host "##vso[task.setvariable variable=MSVC_TOOLSET]$toolset" - Write-Host "##vso[task.setvariable variable=CLANGCL_PATH]$clangCl" + Write-Host "##vso[task.setvariable variable=CL_PATH]$cl" # Bootstrap vcpkg if not pre-installed. $vcpkgRoot = if ($env:VCPKG_INSTALLATION_ROOT) { $env:VCPKG_INSTALLATION_ROOT } else { 'C:\vcpkg' } @@ -109,7 +100,7 @@ steps: # vcvarsall sets VCPKG_ROOT to VS's bundled copy; override it to match our # standalone installation so vcpkg.exe doesn't warn about the mismatch. Write-Host "##vso[task.setvariable variable=VCPKG_ROOT]$vcpkgRoot" - displayName: Set up clang-cl / MSVC environment + displayName: Set up MSVC environment # Hash all files that influence how the C++ dependencies are built. # The result is used in the Cache@2 key so the cache is invalidated on any @@ -150,10 +141,10 @@ steps: - task: Cache@2 displayName: Restore / save C++ dependency cache inputs: - key: '"windows-deps-clang-cl-x64-windows-static-md-v1" | "$(MSVC_TOOLSET)" | "$(DEPS_FILES_HASH)"' + key: '"windows-deps-msvc-x64-windows-static-md-v1" | "$(MSVC_TOOLSET)" | "$(DEPS_FILES_HASH)"' path: | $(System.DefaultWorkingDirectory)/vcpkg_installed - $(System.DefaultWorkingDirectory)/cpp/build-clang-cl + $(System.DefaultWorkingDirectory)/cpp/build-msvc cacheHitVar: DEPS_CACHE_RESTORED # Build vcpkg packages, configure CMake, and build the slow FetchContent targets @@ -163,7 +154,7 @@ steps: -SrcDir "$(System.DefaultWorkingDirectory)" ` -March "${{ parameters.march }}" ` -BuildType "${{ parameters.buildType }}" ` - -ClangClPath "$(CLANGCL_PATH)" + -ClPath "$(CL_PATH)" displayName: Build C++ dependencies (libint2 / gauxc / ecpint / blaspp / lapackpp) condition: ne(variables['DEPS_CACHE_RESTORED'], 'true') @@ -174,7 +165,7 @@ steps: -SrcDir "$(System.DefaultWorkingDirectory)" ` -March "${{ parameters.march }}" ` -BuildType "${{ parameters.buildType }}" ` - -ClangClPath "$(CLANGCL_PATH)" ` + -ClPath "$(CL_PATH)" ` -PythonVersion "${{ parameters.pythonVersion }}" ` -DevTag "${{ parameters.devTag }}" displayName: Build C++ library, run tests, and build Python wheel @@ -186,6 +177,6 @@ steps: displayName: Publish C++ test results inputs: testResultsFormat: JUnit - testResultsFiles: '$(System.DefaultWorkingDirectory)/cpp/build-clang-cl/ctest_results.xml' + testResultsFiles: '$(System.DefaultWorkingDirectory)/cpp/build-msvc/ctest_results.xml' testRunTitle: 'C++ tests - Windows x86_64 Python ${{ parameters.pythonVersion }}' condition: always() From 050dece377cfeb5c64edc0a18c2bf5828909c49c Mon Sep 17 00:00:00 2001 From: Loris Ercole Date: Mon, 29 Jun 2026 19:06:14 +0200 Subject: [PATCH 20/37] fix(ci): fix two GHA Windows deps job failures 1. ninja unknown target: CMake alias targets (Libint2::cxx, ECPINT::ecpint, gauxc::gauxc) are CMake-level constructs; Ninja has no phony target for them. Use the actual internal library target names: libint2_cxx, ecpint, gauxc. 2. PowerShell ParseError 'Unexpected token Deleting': the \ string assignment was missing its closing double-quote at the end of the line. PowerShell treated the rest of the script as a multiline string until the next bare quote (the opening quote of Write-Host), leaving 'Deleting' as a stray token. Added the closing quote after the trailing '-'. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build-and-test-windows.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-and-test-windows.yaml b/.github/workflows/build-and-test-windows.yaml index 154301dd7..c851eafea 100644 --- a/.github/workflows/build-and-test-windows.yaml +++ b/.github/workflows/build-and-test-windows.yaml @@ -127,8 +127,9 @@ jobs: timeout-minutes: 630 shell: pwsh run: | - # No --parallel / CMAKE_BUILD_PARALLEL_LEVEL: let Ninja use all cores. - cmake --build "cpp/build-${{ matrix.compiler }}" --target Libint2::cxx ECPINT::ecpint gauxc::gauxc blaspp lapackpp + # Alias targets (Libint2::cxx, ECPINT::ecpint, gauxc::gauxc) are CMake-level + # constructs and don't get Ninja phony targets; use the internal library names. + cmake --build "cpp/build-${{ matrix.compiler }}" --target libint2_cxx ecpint gauxc blaspp lapackpp if ($LASTEXITCODE -ne 0) { throw "Dependency build failed ($LASTEXITCODE)" } - name: Save dependency cache (complete) @@ -159,7 +160,7 @@ jobs: run: | # Best-effort: keep the newest partial (for resume) and drop older ones; # once a complete build succeeds, drop all partials for this base key. - $base = "windows-deps-${{ matrix.compiler }}-${{ env.VCPKG_TRIPLET }}-vs${{ steps.env.outputs.toolset }}-${{ env.DEPS_CACHE_VERSION }}-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json', 'vcpkg-overlay/**', 'cpp/manifest/qdk-chemistry/cgmanifest.json', 'external/macis/manifest/cgmanifest.json', 'cpp/cmake/third_party.cmake', 'cpp/cmake/patches/**', 'cpp/cmake/modules/DependencyManager.cmake', 'external/macis/src/lobpcgxx/CMakeLists.txt', '.pipelines/toolchains/windows.cmake') }}- + $base = "windows-deps-${{ matrix.compiler }}-${{ env.VCPKG_TRIPLET }}-vs${{ steps.env.outputs.toolset }}-${{ env.DEPS_CACHE_VERSION }}-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json', 'vcpkg-overlay/**', 'cpp/manifest/qdk-chemistry/cgmanifest.json', 'external/macis/manifest/cgmanifest.json', 'cpp/cmake/third_party.cmake', 'cpp/cmake/patches/**', 'cpp/cmake/modules/DependencyManager.cmake', 'external/macis/src/lobpcgxx/CMakeLists.txt', '.pipelines/toolchains/windows.cmake') }}-" $keepNewestPartial = @('failure','cancelled') -contains '${{ steps.build.outcome }}' try { $caches = gh cache list --limit 100 --json id,key,createdAt | ConvertFrom-Json From 0f90648f71c21baa10c1737c5e33de06fc8da8a2 Mon Sep 17 00:00:00 2001 From: Loris Ercole Date: Mon, 29 Jun 2026 19:34:00 +0200 Subject: [PATCH 21/37] fix(ci/ado): switch powershell->pwsh tasks and fix PS5 compat in Windows template ADO 'powershell:' tasks run under powershell.exe (5.1) which lacks the ?. null-conditional operator and other PS7 features. Switch all powershell: tasks in the Windows wheel templates to pwsh: (PS7). Also revert the PS5-compat workaround added for cl.exe lookup since we now consistently run under PS7. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .pipelines/templates/build-pip-wheels-windows.yml | 14 ++++++++------ .pipelines/templates/test-pip-wheels-windows.yml | 8 ++++---- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/.pipelines/templates/build-pip-wheels-windows.yml b/.pipelines/templates/build-pip-wheels-windows.yml index 8b3d5011c..7105c7d7a 100644 --- a/.pipelines/templates/build-pip-wheels-windows.yml +++ b/.pipelines/templates/build-pip-wheels-windows.yml @@ -22,7 +22,7 @@ steps: fetchDepth: 1 retryCountOnTaskFailure: 3 -- powershell: | +- pwsh: | python "$(System.DefaultWorkingDirectory)\.pipelines\pip-scripts\versioning.py" ` --version-file "$(System.DefaultWorkingDirectory)\VERSION" ` --dev-tag ${{ parameters.devTag }} @@ -33,7 +33,7 @@ steps: # Import the VS x64 developer environment (INCLUDE, LIB, PATH, etc.) so that # cl.exe and the MSVC linker are available to all subsequent steps. # Also resolves the MSVC toolset version for the dependency cache key. -- powershell: | +- pwsh: | $ErrorActionPreference = 'Stop' $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" @@ -62,7 +62,9 @@ steps: } # Locate cl.exe — vcvarsall x64 puts it on PATH. - $cl = (Get-Command cl.exe -ErrorAction SilentlyContinue)?.Source + # PS5-compatible null check (no ?. operator available in powershell.exe 5.1). + $clCmd = Get-Command cl.exe -ErrorAction SilentlyContinue + $cl = if ($clCmd) { $clCmd.Source } else { $null } if (-not $cl) { throw "cl.exe not found on PATH after vcvarsall x64" } Write-Host "cl.exe: $cl" & $cl 2>&1 | Select-Object -First 2 | Write-Host @@ -105,7 +107,7 @@ steps: # Hash all files that influence how the C++ dependencies are built. # The result is used in the Cache@2 key so the cache is invalidated on any # dep-affecting change without listing every file individually in the key. -- powershell: | +- pwsh: | $root = "$(System.DefaultWorkingDirectory)" $files = @( "$root\vcpkg.json", @@ -149,7 +151,7 @@ steps: # Build vcpkg packages, configure CMake, and build the slow FetchContent targets # (libint2, ecpint, gauxc, blaspp, lapackpp). Skipped entirely on cache hit. -- powershell: | +- pwsh: | & "$(System.DefaultWorkingDirectory)\.pipelines\pip-scripts\build-pip-wheels-windows-deps.ps1" ` -SrcDir "$(System.DefaultWorkingDirectory)" ` -March "${{ parameters.march }}" ` @@ -160,7 +162,7 @@ steps: # Full qdk build, C++ tests, conda environment setup, and Python wheel build. # SYSTEM_ACCESSTOKEN is required by bootstrap-conda for the Azure Artifacts feed. -- powershell: | +- pwsh: | & "$(System.DefaultWorkingDirectory)\.pipelines\pip-scripts\build-pip-wheels-windows.ps1" ` -SrcDir "$(System.DefaultWorkingDirectory)" ` -March "${{ parameters.march }}" ` diff --git a/.pipelines/templates/test-pip-wheels-windows.yml b/.pipelines/templates/test-pip-wheels-windows.yml index c12d5fb9c..bed195a7a 100644 --- a/.pipelines/templates/test-pip-wheels-windows.yml +++ b/.pipelines/templates/test-pip-wheels-windows.yml @@ -17,7 +17,7 @@ steps: # approach used for Linux/macOS via bootstrap-conda.sh. # SYSTEM_ACCESSTOKEN (mapped via env: below) authenticates the conda install # against the Azure Artifacts feed (public channels blocked in 1ES CFSClean). -- powershell: | +- pwsh: | $ErrorActionPreference = 'Stop' $scriptPath = "$(System.DefaultWorkingDirectory)\.pipelines\pip-scripts\bootstrap-conda.ps1" $condaExe = & $scriptPath -EnvName testenv -PythonVersion '${{ parameters.pythonVersion }}' @@ -30,7 +30,7 @@ steps: SYSTEM_ACCESSTOKEN: $(System.AccessToken) # Install the wheel together with its test extra dependencies. -- powershell: | +- pwsh: | $ErrorActionPreference = 'Stop' $wheels = Get-ChildItem "$(System.DefaultWorkingDirectory)\python\repaired_wheelhouse\qdk_chemistry*.whl" if ($wheels.Count -ne 1) { @@ -47,7 +47,7 @@ steps: # Snapshot the testenv and generate a pip install --report for Component # Governance's PipReportDetector (mirrors test-pip-wheels.sh lines 95-102). -- powershell: | +- pwsh: | $manifestDir = "$(System.DefaultWorkingDirectory)\python\build\test-manifest" New-Item -ItemType Directory -Force -Path $manifestDir | Out-Null $reqs = & "$(CONDA_EXE)" run -n testenv python -m pip list --format=freeze --exclude qdk_chemistry @@ -62,7 +62,7 @@ steps: condition: ${{ parameters.condition }} continueOnError: true -- powershell: | +- pwsh: | $ErrorActionPreference = 'Stop' Push-Location "$(System.DefaultWorkingDirectory)\python" & "$(CONDA_EXE)" run -n testenv ` From 638284bf721151886f3e6ba2f32607235bff401a Mon Sep 17 00:00:00 2001 From: Loris Ercole Date: Mon, 29 Jun 2026 19:54:55 +0200 Subject: [PATCH 22/37] fix(ci): use libint2_obj target; add windowsOnly ADO param GHA: libint2_cxx is an INTERFACE target with no Ninja phony rule; libint2_obj is the actual OBJECT library. Use libint2_obj. ADO: add windowsOnly boolean parameter (default false) to skip Linux/macOS jobs when iterating on the Windows pipeline. NOTE: windowsOnly param is temporary and must be removed before merge. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build-and-test-windows.yaml | 6 +++--- .pipelines/python-wheels.yaml | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-and-test-windows.yaml b/.github/workflows/build-and-test-windows.yaml index c851eafea..ca4378a3b 100644 --- a/.github/workflows/build-and-test-windows.yaml +++ b/.github/workflows/build-and-test-windows.yaml @@ -127,9 +127,9 @@ jobs: timeout-minutes: 630 shell: pwsh run: | - # Alias targets (Libint2::cxx, ECPINT::ecpint, gauxc::gauxc) are CMake-level - # constructs and don't get Ninja phony targets; use the internal library names. - cmake --build "cpp/build-${{ matrix.compiler }}" --target libint2_cxx ecpint gauxc blaspp lapackpp + # libint2_cxx is a CMake INTERFACE target (no Ninja phony); libint2_obj is + # the actual OBJECT library that compiles all libint2 sources. + cmake --build "cpp/build-${{ matrix.compiler }}" --target libint2_obj ecpint gauxc blaspp lapackpp if ($LASTEXITCODE -ne 0) { throw "Dependency build failed ($LASTEXITCODE)" } - name: Save dependency cache (complete) diff --git a/.pipelines/python-wheels.yaml b/.pipelines/python-wheels.yaml index 6a368657e..1cb431320 100644 --- a/.pipelines/python-wheels.yaml +++ b/.pipelines/python-wheels.yaml @@ -78,6 +78,10 @@ parameters: type: boolean default: true displayName: Run slow tests +- name: windowsOnly + type: boolean + default: false + displayName: Windows only (skip Linux/macOS — for iterating on the Windows pipeline) variables: - group: QdkChemistry @@ -118,6 +122,7 @@ extends: jobs: - ${{ each target in parameters.matrix }}: - job: Build_${{ target.name }} + condition: ${{ or(eq(target.os, 'windows'), not(parameters.windowsOnly)) }} ${{ if eq(target.os, 'macOS') }}: pool: name: AcesShared From 45bda0b7ccee7d18554de783ea264e1359003b36 Mon Sep 17 00:00:00 2001 From: Loris Ercole Date: Mon, 29 Jun 2026 21:06:53 +0200 Subject: [PATCH 23/37] Add rebuild_dep_cache manual trigger to GHA workflows [skip ci] Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build-and-test-windows.yaml | 6 ++++++ .github/workflows/build-and-test.yaml | 18 +++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test-windows.yaml b/.github/workflows/build-and-test-windows.yaml index ca4378a3b..c1c534dcf 100644 --- a/.github/workflows/build-and-test-windows.yaml +++ b/.github/workflows/build-and-test-windows.yaml @@ -15,6 +15,11 @@ on: required: false default: false type: boolean + rebuild_dep_cache: + description: Rebuild dependency cache from scratch (ignore existing cache) + required: false + default: false + type: boolean concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -65,6 +70,7 @@ jobs: - name: Restore dependency cache id: cache + if: ${{ !inputs.rebuild_dep_cache }} uses: actions/cache/restore@v5 with: path: | diff --git a/.github/workflows/build-and-test.yaml b/.github/workflows/build-and-test.yaml index c128ae226..715c40938 100644 --- a/.github/workflows/build-and-test.yaml +++ b/.github/workflows/build-and-test.yaml @@ -15,6 +15,11 @@ on: required: false default: false type: boolean + rebuild_dep_cache: + description: Rebuild dependency cache from scratch (ignore existing cache) + required: false + default: false + type: boolean concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -219,7 +224,8 @@ jobs: - name: Cache C++ dependency install # Checks if manifest files or build script have changed to rebuild dependencies id: cache-cpp-deps - uses: actions/cache@v5 + if: ${{ !inputs.rebuild_dep_cache }} + uses: actions/cache/restore@v5 with: path: | ${{ env.CPP_DEPS_PREFIX }} @@ -244,6 +250,16 @@ jobs: ${{ github.workspace }}/cpp/manifest/qdk-chemistry/cgmanifest.json \ ${{ github.workspace }}/external/macis/manifest/cgmanifest.json + - name: Save C++ dependency cache + if: steps.cache-cpp-deps.outputs.cache-hit != 'true' + uses: actions/cache/save@v5 + with: + path: | + ${{ env.CPP_DEPS_PREFIX }} + key: >- + cpp-deps-${{ runner.os }}-${{ matrix.uarch }}- + ${{ hashFiles('cpp/manifest/qdk-chemistry/cgmanifest.json', 'external/macis/manifest/cgmanifest.json', '.devcontainer/scripts/install_cpp_dependencies.sh') }} + - name: Configure C++ RelWithDebInfo/Coverage build run: | if [ "${{ matrix.os-name }}" == "macOS" ]; then From 81d7f27acf74eceb8b81b6c8bed8a80a2637c4bc Mon Sep 17 00:00:00 2001 From: Loris Ercole Date: Mon, 29 Jun 2026 21:28:54 +0200 Subject: [PATCH 24/37] Use Terrapin mirror for vcpkg asset caching on 1ES Windows runner Route vcpkg source downloads through Terrapin (vcpkg.storage.devpackages.microsoft.io) to avoid hitting external hosts (gitlab.com etc.) blocked by CFSClean network policy. Sets X_VCPKG_ASSET_SOURCES before vcpkg install in the Windows deps script. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .pipelines/pip-scripts/build-pip-wheels-windows-deps.ps1 | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.pipelines/pip-scripts/build-pip-wheels-windows-deps.ps1 b/.pipelines/pip-scripts/build-pip-wheels-windows-deps.ps1 index 46c58723b..dfd888578 100644 --- a/.pipelines/pip-scripts/build-pip-wheels-windows-deps.ps1 +++ b/.pipelines/pip-scripts/build-pip-wheels-windows-deps.ps1 @@ -42,6 +42,12 @@ if (-not $env:CMAKE_BUILD_PARALLEL_LEVEL) { } # ─── vcpkg install ──────────────────────────────────────────────────────────── +# Route all vcpkg source downloads through the Terrapin internal mirror to +# avoid hitting external hosts (gitlab.com, github.com, etc.) that are blocked +# by the 1ES CFSClean network isolation policy. +# See: https://eng.ms/docs/.../vcpkg (Step 4: Use Terrapin for Asset Caching) +$env:X_VCPKG_ASSET_SOURCES = "x-azurl,https://vcpkg.storage.devpackages.microsoft.io/artifacts/;x-block-origin" + Write-Host "=== vcpkg install ===" & "$VcpkgRoot\vcpkg.exe" install ` --triplet x64-windows-static-md ` From 9a66695f0ff8623ca86bf6b16405205b49025749 Mon Sep 17 00:00:00 2001 From: Loris Ercole Date: Mon, 29 Jun 2026 21:46:50 +0200 Subject: [PATCH 25/37] Remove x-block-origin from Terrapin asset source github.com is accessible from 1ES runners; only gitlab.com (used by eigen3) is blocked. Without x-block-origin, Terrapin serves cached packages and github.com is used as fallback for packages not yet mirrored in Terrapin (e.g. catch2 v3.13). Eigen3 is expected to be in Terrapin since it is a widely used library. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .pipelines/pip-scripts/build-pip-wheels-windows-deps.ps1 | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.pipelines/pip-scripts/build-pip-wheels-windows-deps.ps1 b/.pipelines/pip-scripts/build-pip-wheels-windows-deps.ps1 index dfd888578..d61386c43 100644 --- a/.pipelines/pip-scripts/build-pip-wheels-windows-deps.ps1 +++ b/.pipelines/pip-scripts/build-pip-wheels-windows-deps.ps1 @@ -43,10 +43,13 @@ if (-not $env:CMAKE_BUILD_PARALLEL_LEVEL) { # ─── vcpkg install ──────────────────────────────────────────────────────────── # Route all vcpkg source downloads through the Terrapin internal mirror to -# avoid hitting external hosts (gitlab.com, github.com, etc.) that are blocked -# by the 1ES CFSClean network isolation policy. +# avoid hitting external hosts (gitlab.com etc.) that are blocked by the +# 1ES CFSClean network isolation policy. # See: https://eng.ms/docs/.../vcpkg (Step 4: Use Terrapin for Asset Caching) -$env:X_VCPKG_ASSET_SOURCES = "x-azurl,https://vcpkg.storage.devpackages.microsoft.io/artifacts/;x-block-origin" +# Do NOT set x-block-origin: github.com is accessible from the runner, but +# gitlab.com (used by eigen3) is blocked; Terrapin is the preferred source and +# falls back to the authoritative URL only on a miss. +$env:X_VCPKG_ASSET_SOURCES = "x-azurl,https://vcpkg.storage.devpackages.microsoft.io/artifacts/" Write-Host "=== vcpkg install ===" & "$VcpkgRoot\vcpkg.exe" install ` From 565b6afaef2bb50ce75989f77cfda67ca0e6d19f Mon Sep 17 00:00:00 2001 From: Loris Ercole Date: Mon, 29 Jun 2026 22:19:49 +0200 Subject: [PATCH 26/37] Skip CMake reconfigure when partial build cache is restored Re-running cmake configure on a partial build dir touches build system files, causing Ninja to treat compiled objects as stale and rebuild from scratch instead of resuming. Guard with a CMakeCache.txt existence check, mirroring the vcpkg_installed guard already in the vcpkg step. [skip ci] Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build-and-test-windows.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/build-and-test-windows.yaml b/.github/workflows/build-and-test-windows.yaml index c1c534dcf..9233c5f19 100644 --- a/.github/workflows/build-and-test-windows.yaml +++ b/.github/workflows/build-and-test-windows.yaml @@ -104,6 +104,13 @@ jobs: if: steps.cache.outputs.cache-hit != 'true' shell: pwsh run: | + # Skip reconfigure if a partial build dir already exists: re-running + # cmake touches build system files which causes Ninja to treat compiled + # objects as stale and rebuild from scratch instead of resuming. + if (Test-Path "cpp/build-${{ matrix.compiler }}/CMakeCache.txt") { + Write-Host "Build dir already configured (restored from partial cache); skipping reconfigure." + exit 0 + } $vcpkg = $env:VCPKG_INSTALLATION_ROOT if (-not $vcpkg) { $vcpkg = 'C:\vcpkg' } cmake -S cpp -B "cpp/build-${{ matrix.compiler }}" -GNinja ` From 793e6c5e4d09ef89a968b91b97720c1603e506e0 Mon Sep 17 00:00:00 2001 From: Loris Ercole Date: Mon, 29 Jun 2026 23:33:34 +0200 Subject: [PATCH 27/37] fix: pipe ensureconda/pip/conda stdout to Out-Host in bootstrap-conda.ps1 All subprocess stdout inside _Bootstrap was being captured by the caller via \ = & bootstrap-conda.ps1. ms-ensureconda prints 'Extracting to C:\...\conda.exe' to stdout, which ended up as the command name when doing & \ run ... Fix: pipe all subprocess calls through 2>&1 | Out-Host so only the final Write-Output \ reaches the caller's capture. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .pipelines/pip-scripts/bootstrap-conda.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pipelines/pip-scripts/bootstrap-conda.ps1 b/.pipelines/pip-scripts/bootstrap-conda.ps1 index d71a15c6c..801706084 100644 --- a/.pipelines/pip-scripts/bootstrap-conda.ps1 +++ b/.pipelines/pip-scripts/bootstrap-conda.ps1 @@ -54,14 +54,14 @@ function _Bootstrap { if ($LASTEXITCODE -ne 0) { throw "Bootstrap venv creation failed ($LASTEXITCODE)" } $venvPy = Join-Path $bootstrapVenv 'Scripts\python.exe' - & $venvPy -m pip install --quiet $ENSURECONDA_PKG + & $venvPy -m pip install --quiet $ENSURECONDA_PKG 2>&1 | Out-Host if ($LASTEXITCODE -ne 0) { throw "ms-ensureconda install failed ($LASTEXITCODE)" } # ms-ensureconda --envfile writes a shell-sourceable KEY=VALUE file with # CONDA_EXE, CONDA_BASH_HOOK, etc. We parse CONDA_EXE from it. $condaEnvFile = Join-Path $env:TEMP "qdk-ensureconda-$EnvName.env" $env:ARTIFACTS_CONDA_TOKEN = $env:SYSTEM_ACCESSTOKEN - & $venvPy -m ensureconda --envfile $condaEnvFile + & $venvPy -m ensureconda --envfile $condaEnvFile 2>&1 | Out-Host if ($LASTEXITCODE -ne 0) { throw "ensureconda failed ($LASTEXITCODE)" } $condaExe = Get-Content $condaEnvFile | @@ -85,7 +85,7 @@ function _Bootstrap { & $condaExe create --override-channels ` --channel "$CONDA_FEED_ROOT/main" ` --channel "$CONDA_FEED_ROOT/conda-forge" ` - --yes --quiet --name $EnvName "python=$PythonVersion" pip + --yes --quiet --name $EnvName "python=$PythonVersion" pip 2>&1 | Out-Host if ($LASTEXITCODE -ne 0) { throw "conda create '$EnvName' failed ($LASTEXITCODE)" } Write-Host "Conda env '$EnvName' created with Python $PythonVersion." From 0cdeeff240ff0578b4e0969a6d3c04206dc21216 Mon Sep 17 00:00:00 2001 From: Loris Ercole Date: Mon, 29 Jun 2026 23:44:16 +0200 Subject: [PATCH 28/37] fix: delete stale CMakeCache.txt before reconfigure in build-test job The deps job and build-test job may run on different 1ES runner machines in the same pool. Workspace paths differ (e.g. d:/a/qdk-chemistry vs D:/a/_work/qdk-chemistry), causing CMake to error: 'The current CMakeCache.txt directory ... is different than the directory ... where CMakeCache.txt was created.' Fix: remove CMakeCache.txt before cmake -S in the build-test job so CMake reconfigures with the current runner's paths. Compiled .obj and .lib files in the cache are unaffected and reused by Ninja. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build-and-test-windows.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/build-and-test-windows.yaml b/.github/workflows/build-and-test-windows.yaml index 9233c5f19..7cc7570fc 100644 --- a/.github/workflows/build-and-test-windows.yaml +++ b/.github/workflows/build-and-test-windows.yaml @@ -247,6 +247,13 @@ jobs: run: | $vcpkg = $env:VCPKG_INSTALLATION_ROOT if (-not $vcpkg) { $vcpkg = 'C:\vcpkg' } + # The cached CMakeCache.txt was written on the deps-job runner whose + # workspace path may differ from this runner's path (e.g. different + # _work subdirectory). CMake rejects a path mismatch. Delete + # CMakeCache.txt so CMake reconfigures from scratch with correct paths + # while keeping all compiled .obj/.lib files for Ninja to reuse. + Remove-Item -Force -ErrorAction SilentlyContinue ` + "cpp/build-${{ matrix.compiler }}/CMakeCache.txt" cmake -S cpp -B "cpp/build-${{ matrix.compiler }}" -GNinja ` -DQDK_UARCH=${{ env.QDK_UARCH }} ` -DQDK_CHEMISTRY_ENABLE_COVERAGE=OFF ` From 61339a500be22feb40bfb48e102691461c23f215 Mon Sep 17 00:00:00 2001 From: Loris Ercole Date: Tue, 30 Jun 2026 09:40:45 +0200 Subject: [PATCH 29/37] fix: conda run env vars + split Cache@2 + recursive CMakeCache cleanup Three fixes: 1. test-pip-wheels-windows.yml: replace conda run --env flags with PowerShell env var assignment. The --env VAR=VALUE flag for conda run was added only in newer conda versions; older versions on 1ES runners reject it with 'unrecognized arguments: --env'. 2. build-pip-wheels-windows.yml: split single Cache@2 with multi-line path into two separate tasks (one for vcpkg_installed, one for cpp/build-msvc). Cache@2 on Windows passes multi-line paths as a single tar argument with a literal newline, causing 'tar: could not chdir' at post-job save time. 3. build-and-test-windows.yaml: expand CMakeCache.txt deletion to be recursive (Get-ChildItem -Recurse) before reconfigure in build-test job. The previous fix only deleted the top-level CMakeCache.txt; FetchContent subbuilds (_deps/*/subbuild/) also have their own CMakeCache.txt files containing the deps-runner's workspace path, causing the same mismatch. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build-and-test-windows.yaml | 15 ++++++------- .../templates/build-pip-wheels-windows.yml | 21 ++++++++++++------- .../templates/test-pip-wheels-windows.yml | 12 ++++++----- 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/.github/workflows/build-and-test-windows.yaml b/.github/workflows/build-and-test-windows.yaml index 7cc7570fc..f54160c54 100644 --- a/.github/workflows/build-and-test-windows.yaml +++ b/.github/workflows/build-and-test-windows.yaml @@ -247,13 +247,14 @@ jobs: run: | $vcpkg = $env:VCPKG_INSTALLATION_ROOT if (-not $vcpkg) { $vcpkg = 'C:\vcpkg' } - # The cached CMakeCache.txt was written on the deps-job runner whose - # workspace path may differ from this runner's path (e.g. different - # _work subdirectory). CMake rejects a path mismatch. Delete - # CMakeCache.txt so CMake reconfigures from scratch with correct paths - # while keeping all compiled .obj/.lib files for Ninja to reuse. - Remove-Item -Force -ErrorAction SilentlyContinue ` - "cpp/build-${{ matrix.compiler }}/CMakeCache.txt" + # The cached CMakeCache.txt files were written on the deps-job runner + # whose workspace path may differ (e.g. different _work subdirectory). + # CMake rejects the path mismatch. Delete ALL CMakeCache.txt files + # (top-level + every _deps/*/subbuild/) so CMake reconfigures with the + # current paths while keeping compiled .obj/.lib files for Ninja. + Get-ChildItem -Recurse -Filter CMakeCache.txt ` + -Path "cpp/build-${{ matrix.compiler }}" -ErrorAction SilentlyContinue | + Remove-Item -Force -ErrorAction SilentlyContinue cmake -S cpp -B "cpp/build-${{ matrix.compiler }}" -GNinja ` -DQDK_UARCH=${{ env.QDK_UARCH }} ` -DQDK_CHEMISTRY_ENABLE_COVERAGE=OFF ` diff --git a/.pipelines/templates/build-pip-wheels-windows.yml b/.pipelines/templates/build-pip-wheels-windows.yml index 7105c7d7a..e40fe5cbe 100644 --- a/.pipelines/templates/build-pip-wheels-windows.yml +++ b/.pipelines/templates/build-pip-wheels-windows.yml @@ -137,16 +137,21 @@ steps: Write-Host "##vso[task.setvariable variable=DEPS_FILES_HASH]$hashStr" displayName: Compute dependency cache key -# Restore the dependency build dir (vcpkg + FetchContent .libs). If a matching -# cache exists the deps script is skipped entirely. ADO Cache@2 saves automatically -# at job end. +# Restore the dependency build dirs separately to avoid a known Windows issue +# with Cache@2 multi-line `path`: the task passes both lines as a single tar +# argument, causing "could not chdir" at save time. Two tasks work reliably. - task: Cache@2 - displayName: Restore / save C++ dependency cache + displayName: Restore / save vcpkg dependency cache + inputs: + key: '"windows-vcpkg-x64-windows-static-md-v1" | "$(MSVC_TOOLSET)" | "$(DEPS_FILES_HASH)"' + path: $(System.DefaultWorkingDirectory)/vcpkg_installed + cacheHitVar: VCPKG_CACHE_RESTORED + +- task: Cache@2 + displayName: Restore / save C++ build cache inputs: key: '"windows-deps-msvc-x64-windows-static-md-v1" | "$(MSVC_TOOLSET)" | "$(DEPS_FILES_HASH)"' - path: | - $(System.DefaultWorkingDirectory)/vcpkg_installed - $(System.DefaultWorkingDirectory)/cpp/build-msvc + path: $(System.DefaultWorkingDirectory)/cpp/build-msvc cacheHitVar: DEPS_CACHE_RESTORED # Build vcpkg packages, configure CMake, and build the slow FetchContent targets @@ -158,7 +163,7 @@ steps: -BuildType "${{ parameters.buildType }}" ` -ClPath "$(CL_PATH)" displayName: Build C++ dependencies (libint2 / gauxc / ecpint / blaspp / lapackpp) - condition: ne(variables['DEPS_CACHE_RESTORED'], 'true') + condition: or(ne(variables['DEPS_CACHE_RESTORED'], 'true'), ne(variables['VCPKG_CACHE_RESTORED'], 'true')) # Full qdk build, C++ tests, conda environment setup, and Python wheel build. # SYSTEM_ACCESSTOKEN is required by bootstrap-conda for the Azure Artifacts feed. diff --git a/.pipelines/templates/test-pip-wheels-windows.yml b/.pipelines/templates/test-pip-wheels-windows.yml index bed195a7a..50d604158 100644 --- a/.pipelines/templates/test-pip-wheels-windows.yml +++ b/.pipelines/templates/test-pip-wheels-windows.yml @@ -64,12 +64,14 @@ steps: - pwsh: | $ErrorActionPreference = 'Stop' + # Set env vars in the current process; conda run inherits them. + # conda run --env VAR=VALUE was added only in newer conda versions and is + # not universally available on 1ES runners. + $env:QSHARP_PYTHON_TELEMETRY = 'false' + $env:QDK_CHEMISTRY_RUN_SLOW_TESTS = '${{ parameters.runSlowTests }}' + $env:OMP_NUM_THREADS = '2' Push-Location "$(System.DefaultWorkingDirectory)\python" - & "$(CONDA_EXE)" run -n testenv ` - --env QSHARP_PYTHON_TELEMETRY=false ` - --env "QDK_CHEMISTRY_RUN_SLOW_TESTS=${{ parameters.runSlowTests }}" ` - --env OMP_NUM_THREADS=2 ` - python -m pytest -v tests/ + & "$(CONDA_EXE)" run -n testenv python -m pytest -v tests/ $code = $LASTEXITCODE Pop-Location if ($code -ne 0) { throw "pytest failed ($code)" } From 50eee015b2c274ccfa138915eeb5e72c7b15c099 Mon Sep 17 00:00:00 2001 From: Loris Ercole Date: Tue, 30 Jun 2026 10:15:31 +0200 Subject: [PATCH 30/37] ci(windows): defer GTest discovery to PRE_TEST to fix Windows file-lock gtest_discover_tests() in POST_BUILD (default) mode runs the test binary immediately after linking to enumerate test cases. On Windows, this fails because lld-link/MSVC still holds file handles on the binary while Ninja is running other link jobs in parallel, causing: 'The process cannot access the file because it is being used by another process.' Setting CMAKE_GTEST_DISCOVER_TESTS_DISCOVERY_MODE=PRE_TEST defers discovery to ctest time (when the binary is actually run) so the build step completes cleanly and ctest handles discovery at runtime. [skip ci] Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build-and-test-windows.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-and-test-windows.yaml b/.github/workflows/build-and-test-windows.yaml index f54160c54..59d78e6ac 100644 --- a/.github/workflows/build-and-test-windows.yaml +++ b/.github/workflows/build-and-test-windows.yaml @@ -262,6 +262,7 @@ jobs: -DMACIS_ENABLE_TESTS=OFF ` -DBUILD_SHARED_LIBS=OFF ` -DBUILD_TESTING=ON ` + -DCMAKE_GTEST_DISCOVER_TESTS_DISCOVERY_MODE=PRE_TEST ` -DCMAKE_BUILD_TYPE=Release ` -DCMAKE_C_COMPILER="${{ steps.env.outputs.cxx-path }}" ` -DCMAKE_CXX_COMPILER="${{ steps.env.outputs.cxx-path }}" ` From 383056cda0e708102db358af29e823e0c0311755 Mon Sep 17 00:00:00 2001 From: Loris Ercole Date: Tue, 30 Jun 2026 11:10:44 +0200 Subject: [PATCH 31/37] ci(windows): defer GTest discovery to PRE_TEST in ADO wheel build Same fix as the GHA workflow: POST_BUILD gtest_discover_tests() causes 'The process cannot access the file because it is being used by another process.' on Windows when Ninja links multiple test binaries in parallel. Setting CMAKE_GTEST_DISCOVER_TESTS_DISCOVERY_MODE=PRE_TEST defers discovery to ctest time. [skip ci] Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .pipelines/pip-scripts/build-pip-wheels-windows.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/.pipelines/pip-scripts/build-pip-wheels-windows.ps1 b/.pipelines/pip-scripts/build-pip-wheels-windows.ps1 index 13b4791cf..2ad8d782a 100644 --- a/.pipelines/pip-scripts/build-pip-wheels-windows.ps1 +++ b/.pipelines/pip-scripts/build-pip-wheels-windows.ps1 @@ -69,6 +69,7 @@ $cmakeArgs = @( '-DMACIS_ENABLE_TESTS=ON', '-DBUILD_SHARED_LIBS=OFF', '-DBUILD_TESTING=ON', + '-DCMAKE_GTEST_DISCOVER_TESTS_DISCOVERY_MODE=PRE_TEST', "-DCMAKE_BUILD_TYPE=$BuildType", "-DCMAKE_C_COMPILER=$ClPath", "-DCMAKE_CXX_COMPILER=$ClPath", From a27f0f22e4d8194dadf036d38f6fab5e129c9bc2 Mon Sep 17 00:00:00 2001 From: Loris Ercole Date: Tue, 30 Jun 2026 11:35:36 +0200 Subject: [PATCH 32/37] ci(windows): only delete top-level CMakeCache.txt before reconfigure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deleting ALL CMakeCache.txt files (including _deps/*/build/) caused cmake to reconfigure subbuilds (libint2, gauxc, etc.) from scratch, generating new build.ninja files. Even with identical workspace paths, a fresh subbuild configure produces slightly different build.ninja (new cmake run creates updated files), causing Ninja to see changed command hashes and rebuild all ~12,000 libint2 objects — wasting 3-5 hours. Fix: only delete the top-level CMakeCache.txt. GHA workspace paths are stable per repository name, so subbuild CMakeCache.txt paths are always consistent between the deps job (cache save) and the build-test job (cache restore). Ninja correctly determines existing .obj files are up-to-date and skips them. [skip ci] Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build-and-test-windows.yaml | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build-and-test-windows.yaml b/.github/workflows/build-and-test-windows.yaml index 59d78e6ac..c29936442 100644 --- a/.github/workflows/build-and-test-windows.yaml +++ b/.github/workflows/build-and-test-windows.yaml @@ -247,14 +247,19 @@ jobs: run: | $vcpkg = $env:VCPKG_INSTALLATION_ROOT if (-not $vcpkg) { $vcpkg = 'C:\vcpkg' } - # The cached CMakeCache.txt files were written on the deps-job runner - # whose workspace path may differ (e.g. different _work subdirectory). - # CMake rejects the path mismatch. Delete ALL CMakeCache.txt files - # (top-level + every _deps/*/subbuild/) so CMake reconfigures with the - # current paths while keeping compiled .obj/.lib files for Ninja. - Get-ChildItem -Recurse -Filter CMakeCache.txt ` - -Path "cpp/build-${{ matrix.compiler }}" -ErrorAction SilentlyContinue | - Remove-Item -Force -ErrorAction SilentlyContinue + # Delete only the top-level CMakeCache.txt so cmake reconfigures with the + # current workspace path. Subbuild CMakeCache.txt files (libint2, gauxc, + # etc.) are deliberately kept: deleting them forces cmake to regenerate + # the subbuild build.ninja with fresh absolute paths, which causes Ninja + # to invalidate all ~12,000 compiled dep objects and rebuild everything. + # The GHA workspace path is stable per repository name so subbuild paths + # are always consistent between the deps-job (cache save) and the + # build-test job (cache restore). + $topCache = "cpp/build-${{ matrix.compiler }}/CMakeCache.txt" + if (Test-Path $topCache) { + Write-Host "Removing top-level CMakeCache.txt" + Remove-Item $topCache -Force + } cmake -S cpp -B "cpp/build-${{ matrix.compiler }}" -GNinja ` -DQDK_UARCH=${{ env.QDK_UARCH }} ` -DQDK_CHEMISTRY_ENABLE_COVERAGE=OFF ` From f4e456e6e4c6a60a109bde8f2bebd2985801240a Mon Sep 17 00:00:00 2001 From: Loris Ercole Date: Tue, 30 Jun 2026 11:45:01 +0200 Subject: [PATCH 33/37] =?UTF-8?q?ci(windows):=20simplify=20deps=20job=20?= =?UTF-8?q?=E2=80=94=20remove=20partial-build=20resume=20mechanism?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With the new 1ES runners the dependency build completes in under 2 hours, so the resumable/partial-cache machinery is no longer needed. Replace the previous partial-build pattern with a straightforward: restore → (on miss) vcpkg + cmake + build deps → save Removed: - rebuild_dep_cache workflow_dispatch input - restore-keys (partial-cache prefix) from cache restore - actions: write permission (was needed only for gh cache delete) - vcpkg presence check ('already present, skipping') guard - CMakeCache.txt existence check ('partial build dir, skipping reconfigure') - timeout-minutes: 630 on the build step - 'Save dependency cache (partial, for resume)' step - 'Prune stale dependency caches' step with gh cache delete To force a cache rebuild, bump DEPS_CACHE_VERSION in the env block. [skip ci] Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build-and-test-windows.yaml | 81 +++---------------- 1 file changed, 9 insertions(+), 72 deletions(-) diff --git a/.github/workflows/build-and-test-windows.yaml b/.github/workflows/build-and-test-windows.yaml index c29936442..a125237bd 100644 --- a/.github/workflows/build-and-test-windows.yaml +++ b/.github/workflows/build-and-test-windows.yaml @@ -15,11 +15,6 @@ on: required: false default: false type: boolean - rebuild_dep_cache: - description: Rebuild dependency cache from scratch (ignore existing cache) - required: false - default: false - type: boolean concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -36,22 +31,19 @@ env: jobs: # ---------------------------------------------------------------------------- - # Job 1: build and cache the (slow) external C++ dependencies. + # Job 1: build and cache the slow external C++ dependencies. # - # Builds ONLY the slow FetchContent dependency targets - # (libint2/gauxc/ecpint/blaspp/lapackpp). With MSVC this can take several - # hours, so the build is RESUMABLE: the build dir is cached after every run - # (complete -> stable key; partial -> per-run key), and the next run restores - # the furthest progress and lets Ninja continue. Once a complete cache exists - # the build is skipped entirely. The job timeout is 12 hours. + # Builds ONLY the slow FetchContent targets (libint2/gauxc/ecpint/blaspp/ + # lapackpp) plus vcpkg packages. The complete build is cached under an exact + # content-hash key. Subsequent runs skip this job entirely on a cache hit. + # To force a full rebuild, bump DEPS_CACHE_VERSION in the env block above. # ---------------------------------------------------------------------------- deps: name: ${{ matrix.compiler }} - Dependencies runs-on: [self-hosted, 1ES.Pool=1es-gh-Dads_v5-pool-wus2, 1ES.ImageOverride=windows-2025-1espt, 'JobId=qdk_chemistry-win-deps-${{ strategy.job-index }}-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}'] - timeout-minutes: 720 + timeout-minutes: 360 permissions: contents: read - actions: write # prune stale dependency caches strategy: fail-fast: false matrix: @@ -70,27 +62,18 @@ jobs: - name: Restore dependency cache id: cache - if: ${{ !inputs.rebuild_dep_cache }} uses: actions/cache/restore@v5 with: path: | vcpkg_installed cpp/build-${{ matrix.compiler }} - # Exact key = a completed build. restore-keys prefix = the furthest - # partial build from a previous (timed-out) run, for resuming. key: >- windows-deps-${{ matrix.compiler }}-${{ env.VCPKG_TRIPLET }}-vs${{ steps.env.outputs.toolset }}-${{ env.DEPS_CACHE_VERSION }}-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json', 'vcpkg-overlay/**', 'cpp/manifest/qdk-chemistry/cgmanifest.json', 'external/macis/manifest/cgmanifest.json', 'cpp/cmake/third_party.cmake', 'cpp/cmake/patches/**', 'cpp/cmake/modules/DependencyManager.cmake', 'external/macis/src/lobpcgxx/CMakeLists.txt', '.pipelines/toolchains/windows.cmake') }} - restore-keys: | - windows-deps-${{ matrix.compiler }}-${{ env.VCPKG_TRIPLET }}-vs${{ steps.env.outputs.toolset }}-${{ env.DEPS_CACHE_VERSION }}-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json', 'vcpkg-overlay/**', 'cpp/manifest/qdk-chemistry/cgmanifest.json', 'external/macis/manifest/cgmanifest.json', 'cpp/cmake/third_party.cmake', 'cpp/cmake/patches/**', 'cpp/cmake/modules/DependencyManager.cmake', 'external/macis/src/lobpcgxx/CMakeLists.txt', '.pipelines/toolchains/windows.cmake') }}-partial- - name: Install vcpkg dependencies if: steps.cache.outputs.cache-hit != 'true' shell: pwsh run: | - if (Test-Path "vcpkg_installed/${{ env.VCPKG_TRIPLET }}/lib") { - Write-Host "vcpkg dependencies already present (restored from cache); skipping install." - exit 0 - } $vcpkg = $env:VCPKG_INSTALLATION_ROOT if (-not $vcpkg) { $vcpkg = 'C:\vcpkg' } & "$vcpkg\vcpkg.exe" install ` @@ -104,13 +87,6 @@ jobs: if: steps.cache.outputs.cache-hit != 'true' shell: pwsh run: | - # Skip reconfigure if a partial build dir already exists: re-running - # cmake touches build system files which causes Ninja to treat compiled - # objects as stale and rebuild from scratch instead of resuming. - if (Test-Path "cpp/build-${{ matrix.compiler }}/CMakeCache.txt") { - Write-Host "Build dir already configured (restored from partial cache); skipping reconfigure." - exit 0 - } $vcpkg = $env:VCPKG_INSTALLATION_ROOT if (-not $vcpkg) { $vcpkg = 'C:\vcpkg' } cmake -S cpp -B "cpp/build-${{ matrix.compiler }}" -GNinja ` @@ -131,13 +107,8 @@ jobs: -DFETCHCONTENT_QUIET=OFF if ($LASTEXITCODE -ne 0) { throw "CMake configure failed ($LASTEXITCODE)" } - - name: Build dependency targets (resumable, time-boxed) - id: build + - name: Build dependency targets if: steps.cache.outputs.cache-hit != 'true' - # Kept under the 12h job cap (accounting for vcpkg + download overhead) so - # the resumable partial cache is saved before the job is force-cancelled. - # The next run restores this progress and continues. - timeout-minutes: 630 shell: pwsh run: | # libint2_cxx is a CMake INTERFACE target (no Ninja phony); libint2_obj is @@ -145,8 +116,8 @@ jobs: cmake --build "cpp/build-${{ matrix.compiler }}" --target libint2_obj ecpint gauxc blaspp lapackpp if ($LASTEXITCODE -ne 0) { throw "Dependency build failed ($LASTEXITCODE)" } - - name: Save dependency cache (complete) - if: ${{ always() && steps.build.outcome == 'success' }} + - name: Save dependency cache + if: steps.cache.outputs.cache-hit != 'true' uses: actions/cache/save@v5 with: path: | @@ -155,40 +126,6 @@ jobs: key: >- windows-deps-${{ matrix.compiler }}-${{ env.VCPKG_TRIPLET }}-vs${{ steps.env.outputs.toolset }}-${{ env.DEPS_CACHE_VERSION }}-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json', 'vcpkg-overlay/**', 'cpp/manifest/qdk-chemistry/cgmanifest.json', 'external/macis/manifest/cgmanifest.json', 'cpp/cmake/third_party.cmake', 'cpp/cmake/patches/**', 'cpp/cmake/modules/DependencyManager.cmake', 'external/macis/src/lobpcgxx/CMakeLists.txt', '.pipelines/toolchains/windows.cmake') }} - - name: Save dependency cache (partial, for resume) - if: ${{ always() && (steps.build.outcome == 'failure' || steps.build.outcome == 'cancelled') }} - uses: actions/cache/save@v5 - with: - path: | - vcpkg_installed - cpp/build-${{ matrix.compiler }} - key: >- - windows-deps-${{ matrix.compiler }}-${{ env.VCPKG_TRIPLET }}-vs${{ steps.env.outputs.toolset }}-${{ env.DEPS_CACHE_VERSION }}-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json', 'vcpkg-overlay/**', 'cpp/manifest/qdk-chemistry/cgmanifest.json', 'external/macis/manifest/cgmanifest.json', 'cpp/cmake/third_party.cmake', 'cpp/cmake/patches/**', 'cpp/cmake/modules/DependencyManager.cmake', 'external/macis/src/lobpcgxx/CMakeLists.txt', '.pipelines/toolchains/windows.cmake') }}-partial-${{ github.run_id }}-${{ github.run_attempt }} - - - name: Prune stale dependency caches - if: always() - shell: pwsh - env: - GH_TOKEN: ${{ github.token }} - run: | - # Best-effort: keep the newest partial (for resume) and drop older ones; - # once a complete build succeeds, drop all partials for this base key. - $base = "windows-deps-${{ matrix.compiler }}-${{ env.VCPKG_TRIPLET }}-vs${{ steps.env.outputs.toolset }}-${{ env.DEPS_CACHE_VERSION }}-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json', 'vcpkg-overlay/**', 'cpp/manifest/qdk-chemistry/cgmanifest.json', 'external/macis/manifest/cgmanifest.json', 'cpp/cmake/third_party.cmake', 'cpp/cmake/patches/**', 'cpp/cmake/modules/DependencyManager.cmake', 'external/macis/src/lobpcgxx/CMakeLists.txt', '.pipelines/toolchains/windows.cmake') }}-" - $keepNewestPartial = @('failure','cancelled') -contains '${{ steps.build.outcome }}' - try { - $caches = gh cache list --limit 100 --json id,key,createdAt | ConvertFrom-Json - $partials = $caches | - Where-Object { $_.key.StartsWith($base) -and $_.key.Contains('-partial-') } | - Sort-Object createdAt -Descending - $skip = if ($keepNewestPartial) { 1 } else { 0 } - $partials | Select-Object -Skip $skip | ForEach-Object { - Write-Host "Deleting stale cache: $($_.key)" - gh cache delete $_.id 2>$null - } - } catch { - Write-Host "Cache prune skipped: $_" - } - # ---------------------------------------------------------------------------- # Job 2: rebuild qdk against the cached dependencies, then run the C++ and # Python test suites. qdk/macis/test sources are re-checked-out here, so this From 1871468255b57b9e9f2ed76aa0c6de2a081be0df Mon Sep 17 00:00:00 2001 From: Loris Ercole Date: Tue, 30 Jun 2026 12:07:53 +0200 Subject: [PATCH 34/37] ci(windows): merge dep+build jobs, add path-mismatch validation [skip ci] - Merge 'deps' and 'build-test' into a single 'build-test' job per compiler (matrix: [msvc, clang-cl]). Both the cache-save and the cmake --build now run on the *same* runner, so workspace absolute paths in CMakeCache.txt / build.ninja are always consistent. - Add 'Validate cached build directory' step: if a cache entry from a previous run was written on a runner with a different workspace base (e.g. D:\a\ vs D:\a\_work\), the stale paths are detected and the build directory is wiped so configure + dep-build run from scratch rather than crashing mid-build with a cmake path-mismatch error. - Replace Floor with Round in the memory-aware parallelism calculation. - Remove the partial-build resume machinery (rebuild_dep_cache input, restore-keys, prune-stale-caches step, actions: write permission). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build-and-test-windows.yaml | 169 +++++++----------- 1 file changed, 66 insertions(+), 103 deletions(-) diff --git a/.github/workflows/build-and-test-windows.yaml b/.github/workflows/build-and-test-windows.yaml index a125237bd..020541277 100644 --- a/.github/workflows/build-and-test-windows.yaml +++ b/.github/workflows/build-and-test-windows.yaml @@ -31,17 +31,17 @@ env: jobs: # ---------------------------------------------------------------------------- - # Job 1: build and cache the slow external C++ dependencies. - # - # Builds ONLY the slow FetchContent targets (libint2/gauxc/ecpint/blaspp/ - # lapackpp) plus vcpkg packages. The complete build is cached under an exact - # content-hash key. Subsequent runs skip this job entirely on a cache hit. - # To force a full rebuild, bump DEPS_CACHE_VERSION in the env block above. + # One job per compiler (msvc, clang-cl). On a cache miss the slow FetchContent + # dependencies (libint2/gauxc/ecpint/blaspp/lapackpp) and vcpkg packages are + # built and cached; on a hit they are restored and the build continues + # straight to compiling qdk. All steps run on the same runner so workspace + # paths are consistent and no CMakeCache juggling is needed. + # To force a dep cache rebuild, bump DEPS_CACHE_VERSION in the env block. # ---------------------------------------------------------------------------- - deps: - name: ${{ matrix.compiler }} - Dependencies - runs-on: [self-hosted, 1ES.Pool=1es-gh-Dads_v5-pool-wus2, 1ES.ImageOverride=windows-2025-1espt, 'JobId=qdk_chemistry-win-deps-${{ strategy.job-index }}-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}'] - timeout-minutes: 360 + build-test: + name: ${{ matrix.compiler }} - Build & test + runs-on: [self-hosted, 1ES.Pool=1es-gh-Dads_v5-pool-wus2, 1ES.ImageOverride=windows-2025-1espt, 'JobId=qdk_chemistry-win-${{ strategy.job-index }}-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}'] + timeout-minutes: 480 permissions: contents: read strategy: @@ -60,6 +60,23 @@ jobs: with: compiler: ${{ matrix.compiler }} + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: '3.13' + + - name: Set memory-aware build parallelism + shell: pwsh + run: | + # qdk TUs that include libint2 engine.impl.h peak at several GB under MSVC; + # cap concurrency by available RAM (~4 GB/job) to avoid C1060 (compiler out + # of heap space). Scales up automatically on a larger runner. + $cpu = [int]$env:NUMBER_OF_PROCESSORS + $ramGB = [math]::Round((Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory / 1GB) + $jobs = [math]::Min($cpu, [math]::Max(1, [math]::Round($ramGB / 4))) + Write-Host "CPUs=$cpu RAM=${ramGB}GB -> CMAKE_BUILD_PARALLEL_LEVEL=$jobs" + "CMAKE_BUILD_PARALLEL_LEVEL=$jobs" >> $env:GITHUB_ENV + - name: Restore dependency cache id: cache uses: actions/cache/restore@v5 @@ -70,8 +87,37 @@ jobs: key: >- windows-deps-${{ matrix.compiler }}-${{ env.VCPKG_TRIPLET }}-vs${{ steps.env.outputs.toolset }}-${{ env.DEPS_CACHE_VERSION }}-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json', 'vcpkg-overlay/**', 'cpp/manifest/qdk-chemistry/cgmanifest.json', 'external/macis/manifest/cgmanifest.json', 'cpp/cmake/third_party.cmake', 'cpp/cmake/patches/**', 'cpp/cmake/modules/DependencyManager.cmake', 'external/macis/src/lobpcgxx/CMakeLists.txt', '.pipelines/toolchains/windows.cmake') }} + - name: Validate cached build directory + id: cache-validate + if: steps.cache.outputs.cache-hit == 'true' + shell: pwsh + run: | + # If the cached CMakeCache.txt was written on a runner with a different + # workspace base path (e.g. D:\a\ vs D:\a\_work\) the cached build.ninja + # files will reference stale absolute paths and cmake/ninja will fail. + # Detect this by checking whether the current workspace path appears in + # CMakeCache.txt (case-insensitive, forward-slash-normalised) and, if not, + # wipe the build directory so the configure + build steps run from scratch. + $buildDir = "cpp/build-${{ matrix.compiler }}" + $cacheFile = "$buildDir/CMakeCache.txt" + if (-not (Test-Path $cacheFile)) { + Write-Host "No CMakeCache.txt in restored cache — treating as cache miss" + "cache-valid=false" >> $env:GITHUB_OUTPUT + exit 0 + } + $ws = "${{ github.workspace }}".Replace('\', '/').ToLower() + $content = (Get-Content $cacheFile -Raw).ToLower().Replace('\', '/') + if ($content -notmatch [regex]::Escape($ws)) { + Write-Host "Workspace path mismatch in cached CMakeCache.txt (expected: $ws)" + Remove-Item -Recurse -Force $buildDir -ErrorAction SilentlyContinue + "cache-valid=false" >> $env:GITHUB_OUTPUT + } else { + Write-Host "Cached build directory paths match current workspace" + "cache-valid=true" >> $env:GITHUB_OUTPUT + } + - name: Install vcpkg dependencies - if: steps.cache.outputs.cache-hit != 'true' + if: steps.cache.outputs.cache-hit != 'true' || steps.cache-validate.outputs.cache-valid == 'false' shell: pwsh run: | $vcpkg = $env:VCPKG_INSTALLATION_ROOT @@ -84,7 +130,7 @@ jobs: if ($LASTEXITCODE -ne 0) { throw "vcpkg install failed ($LASTEXITCODE)" } - name: Configure CMake - if: steps.cache.outputs.cache-hit != 'true' + if: steps.cache.outputs.cache-hit != 'true' || steps.cache-validate.outputs.cache-valid == 'false' shell: pwsh run: | $vcpkg = $env:VCPKG_INSTALLATION_ROOT @@ -93,9 +139,10 @@ jobs: -DQDK_UARCH=${{ env.QDK_UARCH }} ` -DQDK_CHEMISTRY_ENABLE_COVERAGE=OFF ` -DQDK_CHEMISTRY_ENABLE_MPI=OFF ` - -DMACIS_ENABLE_TESTS=ON ` + -DMACIS_ENABLE_TESTS=OFF ` -DBUILD_SHARED_LIBS=OFF ` -DBUILD_TESTING=ON ` + -DCMAKE_GTEST_DISCOVER_TESTS_DISCOVERY_MODE=PRE_TEST ` -DCMAKE_BUILD_TYPE=Release ` -DCMAKE_C_COMPILER="${{ steps.env.outputs.cxx-path }}" ` -DCMAKE_CXX_COMPILER="${{ steps.env.outputs.cxx-path }}" ` @@ -107,8 +154,8 @@ jobs: -DFETCHCONTENT_QUIET=OFF if ($LASTEXITCODE -ne 0) { throw "CMake configure failed ($LASTEXITCODE)" } - - name: Build dependency targets - if: steps.cache.outputs.cache-hit != 'true' + - name: Build C++ dependencies + if: steps.cache.outputs.cache-hit != 'true' || steps.cache-validate.outputs.cache-valid == 'false' shell: pwsh run: | # libint2_cxx is a CMake INTERFACE target (no Ninja phony); libint2_obj is @@ -117,7 +164,7 @@ jobs: if ($LASTEXITCODE -ne 0) { throw "Dependency build failed ($LASTEXITCODE)" } - name: Save dependency cache - if: steps.cache.outputs.cache-hit != 'true' + if: steps.cache.outputs.cache-hit != 'true' || steps.cache-validate.outputs.cache-valid == 'false' uses: actions/cache/save@v5 with: path: | @@ -126,95 +173,11 @@ jobs: key: >- windows-deps-${{ matrix.compiler }}-${{ env.VCPKG_TRIPLET }}-vs${{ steps.env.outputs.toolset }}-${{ env.DEPS_CACHE_VERSION }}-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json', 'vcpkg-overlay/**', 'cpp/manifest/qdk-chemistry/cgmanifest.json', 'external/macis/manifest/cgmanifest.json', 'cpp/cmake/third_party.cmake', 'cpp/cmake/patches/**', 'cpp/cmake/modules/DependencyManager.cmake', 'external/macis/src/lobpcgxx/CMakeLists.txt', '.pipelines/toolchains/windows.cmake') }} - # ---------------------------------------------------------------------------- - # Job 2: rebuild qdk against the cached dependencies, then run the C++ and - # Python test suites. qdk/macis/test sources are re-checked-out here, so this - # job always rebuilds them; the cached dependency .libs are reused as-is. - # ---------------------------------------------------------------------------- - build-test: - name: ${{ matrix.compiler }} - Build & test - runs-on: [self-hosted, 1ES.Pool=1es-gh-Dads_v5-pool-wus2, 1ES.ImageOverride=windows-2025-1espt, 'JobId=qdk_chemistry-win-build-${{ strategy.job-index }}-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}'] - timeout-minutes: 360 - needs: deps - strategy: - fail-fast: false - matrix: - compiler: [msvc, clang-cl] - steps: - - name: Checkout repository - uses: actions/checkout@v6 - with: - submodules: recursive - - - name: Set up ${{ matrix.compiler }} environment - id: env - uses: ./.github/actions/windows-msvc-env - with: - compiler: ${{ matrix.compiler }} - - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: '3.13' - - - name: Set memory-aware build parallelism - shell: pwsh - run: | - # qdk TUs that include libint2 engine.impl.h peak at several GB under MSVC; - # cap concurrency by available RAM (~4 GB/job) to avoid C1060 (compiler out - # of heap space). Scales up automatically on a larger runner. - $cpu = [int]$env:NUMBER_OF_PROCESSORS - $ramGB = [math]::Floor((Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory / 1GB) - $jobs = [math]::Min($cpu, [math]::Max(1, [math]::Floor($ramGB / 4))) - Write-Host "CPUs=$cpu RAM=${ramGB}GB -> CMAKE_BUILD_PARALLEL_LEVEL=$jobs" - "CMAKE_BUILD_PARALLEL_LEVEL=$jobs" >> $env:GITHUB_ENV - - - name: Restore dependency cache - id: cache - uses: actions/cache/restore@v5 - with: - path: | - vcpkg_installed - cpp/build-${{ matrix.compiler }} - key: >- - windows-deps-${{ matrix.compiler }}-${{ env.VCPKG_TRIPLET }}-vs${{ steps.env.outputs.toolset }}-${{ env.DEPS_CACHE_VERSION }}-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json', 'vcpkg-overlay/**', 'cpp/manifest/qdk-chemistry/cgmanifest.json', 'external/macis/manifest/cgmanifest.json', 'cpp/cmake/third_party.cmake', 'cpp/cmake/patches/**', 'cpp/cmake/modules/DependencyManager.cmake', 'external/macis/src/lobpcgxx/CMakeLists.txt', '.pipelines/toolchains/windows.cmake') }} - - - name: Configure & build C++ library + - name: Build C++ library shell: pwsh run: | - $vcpkg = $env:VCPKG_INSTALLATION_ROOT - if (-not $vcpkg) { $vcpkg = 'C:\vcpkg' } - # Delete only the top-level CMakeCache.txt so cmake reconfigures with the - # current workspace path. Subbuild CMakeCache.txt files (libint2, gauxc, - # etc.) are deliberately kept: deleting them forces cmake to regenerate - # the subbuild build.ninja with fresh absolute paths, which causes Ninja - # to invalidate all ~12,000 compiled dep objects and rebuild everything. - # The GHA workspace path is stable per repository name so subbuild paths - # are always consistent between the deps-job (cache save) and the - # build-test job (cache restore). - $topCache = "cpp/build-${{ matrix.compiler }}/CMakeCache.txt" - if (Test-Path $topCache) { - Write-Host "Removing top-level CMakeCache.txt" - Remove-Item $topCache -Force - } - cmake -S cpp -B "cpp/build-${{ matrix.compiler }}" -GNinja ` - -DQDK_UARCH=${{ env.QDK_UARCH }} ` - -DQDK_CHEMISTRY_ENABLE_COVERAGE=OFF ` - -DQDK_CHEMISTRY_ENABLE_MPI=OFF ` - -DMACIS_ENABLE_TESTS=OFF ` - -DBUILD_SHARED_LIBS=OFF ` - -DBUILD_TESTING=ON ` - -DCMAKE_GTEST_DISCOVER_TESTS_DISCOVERY_MODE=PRE_TEST ` - -DCMAKE_BUILD_TYPE=Release ` - -DCMAKE_C_COMPILER="${{ steps.env.outputs.cxx-path }}" ` - -DCMAKE_CXX_COMPILER="${{ steps.env.outputs.cxx-path }}" ` - -DCMAKE_INSTALL_PREFIX="${{ github.workspace }}\install-${{ matrix.compiler }}" ` - -DCMAKE_TOOLCHAIN_FILE="$vcpkg\scripts\buildsystems\vcpkg.cmake" ` - -DVCPKG_CHAINLOAD_TOOLCHAIN_FILE="${{ github.workspace }}\.pipelines\toolchains\windows.cmake" ` - -DVCPKG_TARGET_TRIPLET=${{ env.VCPKG_TRIPLET }} ` - -DVCPKG_INSTALLED_DIR="${{ github.workspace }}\vcpkg_installed" ` - -DFETCHCONTENT_QUIET=OFF - if ($LASTEXITCODE -ne 0) { throw "CMake configure failed ($LASTEXITCODE)" } + # Dep targets are already up-to-date (either just built or restored from + # cache); Ninja skips them and only compiles qdk + test sources. # Honors CMAKE_BUILD_PARALLEL_LEVEL set above (memory-aware). cmake --build "cpp/build-${{ matrix.compiler }}" if ($LASTEXITCODE -ne 0) { throw "CMake build failed ($LASTEXITCODE)" } From 834e28dc9b4582b3603f33439b3d0ed8370adfee Mon Sep 17 00:00:00 2001 From: Loris Ercole Date: Tue, 30 Jun 2026 12:17:34 +0200 Subject: [PATCH 35/37] ci(windows): remove temporary windowsOnly parameter [skip ci] The windowsOnly boolean was added to iterate on the Windows pipeline in isolation. Now that the Windows pipeline is stable, all platforms run unconditionally (matching the original behaviour). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .pipelines/python-wheels.yaml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.pipelines/python-wheels.yaml b/.pipelines/python-wheels.yaml index 1cb431320..6a368657e 100644 --- a/.pipelines/python-wheels.yaml +++ b/.pipelines/python-wheels.yaml @@ -78,10 +78,6 @@ parameters: type: boolean default: true displayName: Run slow tests -- name: windowsOnly - type: boolean - default: false - displayName: Windows only (skip Linux/macOS — for iterating on the Windows pipeline) variables: - group: QdkChemistry @@ -122,7 +118,6 @@ extends: jobs: - ${{ each target in parameters.matrix }}: - job: Build_${{ target.name }} - condition: ${{ or(eq(target.os, 'windows'), not(parameters.windowsOnly)) }} ${{ if eq(target.os, 'macOS') }}: pool: name: AcesShared From 20cf8a05adbf1817fed75fde3369dd8f440da6b6 Mon Sep 17 00:00:00 2001 From: Loris Ercole Date: Tue, 30 Jun 2026 12:31:00 +0200 Subject: [PATCH 36/37] ci(windows): disable VCPKG_APPLOCAL_DEPS to fix parallel link file-locking [skip ci] vcpkg's z-applocal post-build step runs after each test executable is linked and tries to deploy DLL dependencies to the binary's directory. With parallel Ninja builds, multiple z-applocal processes run concurrently and conflict over the newly-created .exe files, producing: 'The process cannot access the file because it is being used by another process.' The build uses x64-windows-static-md (fully static linking) so there are no DLLs to deploy. Setting VCPKG_APPLOCAL_DEPS=OFF disables the post-build step entirely without affecting the build output. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build-and-test-windows.yaml | 1 + .pipelines/pip-scripts/build-pip-wheels-windows.ps1 | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/build-and-test-windows.yaml b/.github/workflows/build-and-test-windows.yaml index 020541277..5f727736c 100644 --- a/.github/workflows/build-and-test-windows.yaml +++ b/.github/workflows/build-and-test-windows.yaml @@ -143,6 +143,7 @@ jobs: -DBUILD_SHARED_LIBS=OFF ` -DBUILD_TESTING=ON ` -DCMAKE_GTEST_DISCOVER_TESTS_DISCOVERY_MODE=PRE_TEST ` + -DVCPKG_APPLOCAL_DEPS=OFF ` -DCMAKE_BUILD_TYPE=Release ` -DCMAKE_C_COMPILER="${{ steps.env.outputs.cxx-path }}" ` -DCMAKE_CXX_COMPILER="${{ steps.env.outputs.cxx-path }}" ` diff --git a/.pipelines/pip-scripts/build-pip-wheels-windows.ps1 b/.pipelines/pip-scripts/build-pip-wheels-windows.ps1 index 2ad8d782a..426bfde13 100644 --- a/.pipelines/pip-scripts/build-pip-wheels-windows.ps1 +++ b/.pipelines/pip-scripts/build-pip-wheels-windows.ps1 @@ -70,6 +70,7 @@ $cmakeArgs = @( '-DBUILD_SHARED_LIBS=OFF', '-DBUILD_TESTING=ON', '-DCMAKE_GTEST_DISCOVER_TESTS_DISCOVERY_MODE=PRE_TEST', + '-DVCPKG_APPLOCAL_DEPS=OFF', "-DCMAKE_BUILD_TYPE=$BuildType", "-DCMAKE_C_COMPILER=$ClPath", "-DCMAKE_CXX_COMPILER=$ClPath", From 1e2bf94152eff2135a7bce0ed51a9df2ed20f4d0 Mon Sep 17 00:00:00 2001 From: Loris Ercole Date: Tue, 30 Jun 2026 12:45:45 +0200 Subject: [PATCH 37/37] ci(windows): add workspace-path hash to dep cache key [skip ci] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1ES runners in the pool can have different workspace base directories (e.g. D:\a\ vs D:\a\_work\). When the cached CMakeCache.txt references one path but the current runner uses another, cmake --build fails with a path-mismatch error. The existing 'Validate cached build directory' step detects this and falls back to a full rebuild, but the rebuilt artifacts are then saved under the SAME cache key and will trigger the same mismatch on any future runner with a different path. Fix: include a 12-char SHA-256 prefix of the lowercase, forward- slash-normalised workspace path in the cache key. Runners with different workspace roots now use different cache entries, so each runner always either gets a cache miss (→ rebuild + save for that path) or a hit that is guaranteed to have matching absolute paths (→ skip dep build, go straight to compiling qdk). The 'Validate cached build directory' safety check is kept for completeness, but in normal operation (key includes path hash) it will always pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build-and-test-windows.yaml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test-windows.yaml b/.github/workflows/build-and-test-windows.yaml index 5f727736c..9e45e1692 100644 --- a/.github/workflows/build-and-test-windows.yaml +++ b/.github/workflows/build-and-test-windows.yaml @@ -67,6 +67,7 @@ jobs: - name: Set memory-aware build parallelism shell: pwsh + id: env2 run: | # qdk TUs that include libint2 engine.impl.h peak at several GB under MSVC; # cap concurrency by available RAM (~4 GB/job) to avoid C1060 (compiler out @@ -76,6 +77,15 @@ jobs: $jobs = [math]::Min($cpu, [math]::Max(1, [math]::Round($ramGB / 4))) Write-Host "CPUs=$cpu RAM=${ramGB}GB -> CMAKE_BUILD_PARALLEL_LEVEL=$jobs" "CMAKE_BUILD_PARALLEL_LEVEL=$jobs" >> $env:GITHUB_ENV + # Include a short hash of the workspace path in the cache key so that + # runners with different workspace base directories (e.g. D:\a\ vs + # D:\a\_work\) each get their own cache entry and never hit a + # CMakeCache.txt path mismatch on restore. + $wsPath = "${{ github.workspace }}".ToLower().Replace('\', '/') + $wsBytes = [System.Text.Encoding]::UTF8.GetBytes($wsPath) + $wsHash = ([System.Security.Cryptography.SHA256]::Create().ComputeHash($wsBytes) | + ForEach-Object { $_.ToString('x2') }) -join '' + "wshash=$($wsHash.Substring(0, 12))" >> $env:GITHUB_OUTPUT - name: Restore dependency cache id: cache @@ -85,7 +95,7 @@ jobs: vcpkg_installed cpp/build-${{ matrix.compiler }} key: >- - windows-deps-${{ matrix.compiler }}-${{ env.VCPKG_TRIPLET }}-vs${{ steps.env.outputs.toolset }}-${{ env.DEPS_CACHE_VERSION }}-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json', 'vcpkg-overlay/**', 'cpp/manifest/qdk-chemistry/cgmanifest.json', 'external/macis/manifest/cgmanifest.json', 'cpp/cmake/third_party.cmake', 'cpp/cmake/patches/**', 'cpp/cmake/modules/DependencyManager.cmake', 'external/macis/src/lobpcgxx/CMakeLists.txt', '.pipelines/toolchains/windows.cmake') }} + windows-deps-${{ matrix.compiler }}-${{ env.VCPKG_TRIPLET }}-vs${{ steps.env.outputs.toolset }}-${{ env.DEPS_CACHE_VERSION }}-ws${{ steps.env2.outputs.wshash }}-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json', 'vcpkg-overlay/**', 'cpp/manifest/qdk-chemistry/cgmanifest.json', 'external/macis/manifest/cgmanifest.json', 'cpp/cmake/third_party.cmake', 'cpp/cmake/patches/**', 'cpp/cmake/modules/DependencyManager.cmake', 'external/macis/src/lobpcgxx/CMakeLists.txt', '.pipelines/toolchains/windows.cmake') }} - name: Validate cached build directory id: cache-validate @@ -172,7 +182,7 @@ jobs: vcpkg_installed cpp/build-${{ matrix.compiler }} key: >- - windows-deps-${{ matrix.compiler }}-${{ env.VCPKG_TRIPLET }}-vs${{ steps.env.outputs.toolset }}-${{ env.DEPS_CACHE_VERSION }}-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json', 'vcpkg-overlay/**', 'cpp/manifest/qdk-chemistry/cgmanifest.json', 'external/macis/manifest/cgmanifest.json', 'cpp/cmake/third_party.cmake', 'cpp/cmake/patches/**', 'cpp/cmake/modules/DependencyManager.cmake', 'external/macis/src/lobpcgxx/CMakeLists.txt', '.pipelines/toolchains/windows.cmake') }} + windows-deps-${{ matrix.compiler }}-${{ env.VCPKG_TRIPLET }}-vs${{ steps.env.outputs.toolset }}-${{ env.DEPS_CACHE_VERSION }}-ws${{ steps.env2.outputs.wshash }}-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json', 'vcpkg-overlay/**', 'cpp/manifest/qdk-chemistry/cgmanifest.json', 'external/macis/manifest/cgmanifest.json', 'cpp/cmake/third_party.cmake', 'cpp/cmake/patches/**', 'cpp/cmake/modules/DependencyManager.cmake', 'external/macis/src/lobpcgxx/CMakeLists.txt', '.pipelines/toolchains/windows.cmake') }} - name: Build C++ library shell: pwsh