From 9f71bebc6d88b270e28586fcfbeb71e40cd71953 Mon Sep 17 00:00:00 2001 From: Nikhil Dabas Date: Mon, 5 Oct 2026 09:56:53 +0100 Subject: [PATCH 1/3] Add Windows ARM64 builds --- .github/workflows/build.yml | 45 ++++++++++++++++++++++++------------- pyproject.toml | 3 +++ 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 00ad86a6..54e328c7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -241,29 +241,42 @@ jobs: arch: auto - os: windows-latest arch: auto + - os: windows-11-arm + arch: auto steps: - uses: actions/checkout@v7 with: submodules: true - - name: Set up MSVC for Windows - if: runner.os == 'Windows' - uses: egor-tensin/vs-shell@v2 - - - name: Install conda on Windows - if: runner.os == 'Windows' - uses: conda-incubator/setup-miniconda@v4 - with: - miniconda-version: "latest" - channels: conda-forge, anaconda - - - name: Install openblas from conda on Windows + - name: Install OpenBLAS on Windows + # meson finds OpenBLAS via its CMake config; delvewheel finds the DLL via PATH. + # 0.3.34 is skipped: its DGEMM kernels crash on AMD Zen 4/5 CPUs that + # expose AVX-512 (Fixed in OpenMathLib/OpenBLAS#6013. Bump with the next release.) if: runner.os == 'Windows' - # 0.3.33 pinned: conda-forge's win-64 0.3.34 build crashes inside its - # DGEMM kernels on AMD Zen 4/5 CPUs that expose AVX-512 - # (conda-forge/openblas-feedstock#196). Unpin once a fixed build ships. - run: conda install -y "openblas=0.3.33" pkgconfig + shell: pwsh + run: | + $version = '0.3.33' + $asset, $sha256 = @{ + X64 = 'x64', '7ad797ef0c9a5c42e28903bf726eaaaade307dafe187ff0e923d90cd4002780c' + ARM64 = 'woa64-dll', 'bdb1047eb24a17c38d20eda6aa3fe5a9d74fd4ad95432f452bfa2ae28a9227d0' + }[$env:RUNNER_ARCH] + $zip = Join-Path $env:RUNNER_TEMP 'openblas.zip' + $dest = Join-Path $env:RUNNER_TEMP 'openblas' + Invoke-WebRequest -OutFile $zip ` + -Uri "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/OpenMathLib/OpenBLAS/releases/download/v$version/OpenBLAS-$version-$asset.zip" + if ((Get-FileHash $zip -Algorithm SHA256).Hash -ne $sha256) { throw 'OpenBLAS checksum mismatch' } + Expand-Archive $zip -DestinationPath $dest + # The woa64 archive nests everything under OpenBLAS/; the x64 one does not. + $root = if (Test-Path (Join-Path $dest 'OpenBLAS')) { Join-Path $dest 'OpenBLAS' } else { $dest } + # The x64 (MinGW) CMake config points the linker at the DLL instead of its import library. + # (Fixed in OpenMathLib/OpenBLAS#6091. Can be removed with the next release.) + $cfg = Join-Path $root 'lib\cmake\openblas\OpenBLASConfig.cmake' + if (Test-Path $cfg) { + (Get-Content $cfg) -replace 'bin/libopenblas\.dll', 'lib/libopenblas.lib' | Set-Content $cfg + } + "CMAKE_PREFIX_PATH=$root" | Out-File -Append -Encoding utf8 $env:GITHUB_ENV + "$root\bin" | Out-File -Append -Encoding utf8 $env:GITHUB_PATH - name: Build wheels uses: pypa/cibuildwheel@v4.2.1 diff --git a/pyproject.toml b/pyproject.toml index 6925fa2e..6c98f2f2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,6 +41,8 @@ dependencies = [ [tool.cibuildwheel] skip = [ "*-win32", # fails on locating Python headers, probably meson.build is misconfigured + # there are no CPython win_arm64 releases before 3.11 + "cp39-win_arm64", "cp310-win_arm64", "*musllinux*aarch64*" # no Scipy wheels for this ] # Test @@ -61,6 +63,7 @@ archs = [ [tool.cibuildwheel.windows] before-build = "pip install delvewheel" +config-settings = {setup-args = "--vsenv"} # This will probably become default in newer cibuildwheels versions repair-wheel-command = "delvewheel repair -w {dest_dir} {wheel}" From a85d7ecb56359bccd007d598864da9439b0414a7 Mon Sep 17 00:00:00 2001 From: Brendan O'Donoghue Date: Mon, 5 Oct 2026 10:58:58 +0100 Subject: [PATCH 2/3] Build Windows ARM64 OpenBLAS for the ARMv8 baseline --- .github/scripts/install_openblas_arm64.ps1 | 58 ++++++++++++++++++++++ .github/workflows/build.yml | 21 ++++---- 2 files changed, 70 insertions(+), 9 deletions(-) create mode 100644 .github/scripts/install_openblas_arm64.ps1 diff --git a/.github/scripts/install_openblas_arm64.ps1 b/.github/scripts/install_openblas_arm64.ps1 new file mode 100644 index 00000000..28394bd6 --- /dev/null +++ b/.github/scripts/install_openblas_arm64.ps1 @@ -0,0 +1,58 @@ +# Follow OpenBLAS's Windows ARM64 CI recipe, using its portable ARMV8 target: +# https://github.com/OpenMathLib/OpenBLAS/blob/v0.3.33/.github/workflows/windows_arm64.yml +$ErrorActionPreference = 'Stop' +$PSNativeCommandUseErrorActionPreference = $true + +$version = '0.3.33' +$archive = Join-Path $env:RUNNER_TEMP 'openblas-source.tar.gz' +$source = Join-Path $env:RUNNER_TEMP "OpenBLAS-$version" +$build = Join-Path $env:RUNNER_TEMP 'openblas-build' +$prefix = Join-Path $env:RUNNER_TEMP 'openblas' +$llvm = Join-Path $env:RUNNER_TEMP 'openblas-llvm' +$installer = Join-Path $env:RUNNER_TEMP 'LLVM-20.1.8-woa64.exe' + +Invoke-WebRequest -OutFile $archive -Uri "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/OpenMathLib/OpenBLAS/releases/download/v$version/OpenBLAS-$version.tar.gz" +if ((Get-FileHash $archive -Algorithm SHA256).Hash -ne '6761af1d9f5d353ab4f0b7497be2643313b36c8f31caec0144bfef198e71e6ab') { + throw 'OpenBLAS source checksum mismatch' +} +tar -xf $archive -C $env:RUNNER_TEMP + +# Pin the C and Fortran toolchain used by the upstream recipe. Keep it separate +# from the runner's LLVM, which does not necessarily include flang-new. +Invoke-WebRequest -OutFile $installer -Uri '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/llvm/llvm-project/releases/download/llvmorg-20.1.8/LLVM-20.1.8-woa64.exe' +if ((Get-FileHash $installer -Algorithm SHA256).Hash -ne '7c4ac97eb2ae6b960ca5f9caf3ff6124c8d2a18cc07a7840a4d2ea15537bad8e') { + throw 'LLVM installer checksum mismatch' +} +$install = Start-Process -FilePath $installer -ArgumentList '/S', "/D=$llvm" -Wait -PassThru +if ($install.ExitCode -ne 0) { throw "LLVM installation failed: $($install.ExitCode)" } + +# Locate Visual Studio rather than depending on its version or edition. +$vswhere = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\vswhere.exe' +$vs = & $vswhere -latest -products '*' -requires Microsoft.VisualStudio.Component.VC.Tools.ARM64 -property installationPath +if (-not $vs) { throw 'Visual Studio ARM64 tools were not found' } +$vcvars = Join-Path $vs 'VC\Auxiliary\Build\vcvarsarm64.bat' +$environment = & cmd /d /c "`"`"$vcvars`" >nul && set`"" +foreach ($line in $environment) { + if ($line -match '^([^=]+)=(.*)$') { + [Environment]::SetEnvironmentVariable($matches[1], $matches[2], 'Process') + } +} +$env:PATH = "$llvm\bin;$env:PATH" + +cmake -S $source -B $build -G Ninja ` + -DCMAKE_BUILD_TYPE=Release ` + -DTARGET=ARMV8 ` + -DDYNAMIC_ARCH=OFF ` + -DBINARY=64 ` + -DCMAKE_C_COMPILER=clang-cl ` + -DCMAKE_Fortran_COMPILER=flang-new ` + -DBUILD_SHARED_LIBS=ON ` + -DCMAKE_SYSTEM_PROCESSOR=arm64 ` + -DCMAKE_SYSTEM_NAME=Windows ` + "-DCMAKE_INSTALL_PREFIX=$prefix" +cmake --build $build --parallel 4 +cmake --install $build + +# Meson uses the CMake package; delvewheel bundles the DLL found on PATH. +"CMAKE_PREFIX_PATH=$prefix" | Out-File -Append -Encoding utf8 $env:GITHUB_ENV +"$prefix\bin" | Out-File -Append -Encoding utf8 $env:GITHUB_PATH diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 54e328c7..c092d0a0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -249,26 +249,22 @@ jobs: with: submodules: true - - name: Install OpenBLAS on Windows + - name: Install OpenBLAS on Windows x64 # meson finds OpenBLAS via its CMake config; delvewheel finds the DLL via PATH. # 0.3.34 is skipped: its DGEMM kernels crash on AMD Zen 4/5 CPUs that # expose AVX-512 (Fixed in OpenMathLib/OpenBLAS#6013. Bump with the next release.) - if: runner.os == 'Windows' + if: runner.os == 'Windows' && runner.arch == 'X64' shell: pwsh run: | $version = '0.3.33' - $asset, $sha256 = @{ - X64 = 'x64', '7ad797ef0c9a5c42e28903bf726eaaaade307dafe187ff0e923d90cd4002780c' - ARM64 = 'woa64-dll', 'bdb1047eb24a17c38d20eda6aa3fe5a9d74fd4ad95432f452bfa2ae28a9227d0' - }[$env:RUNNER_ARCH] + $sha256 = '7ad797ef0c9a5c42e28903bf726eaaaade307dafe187ff0e923d90cd4002780c' $zip = Join-Path $env:RUNNER_TEMP 'openblas.zip' $dest = Join-Path $env:RUNNER_TEMP 'openblas' Invoke-WebRequest -OutFile $zip ` - -Uri "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/OpenMathLib/OpenBLAS/releases/download/v$version/OpenBLAS-$version-$asset.zip" + -Uri "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/OpenMathLib/OpenBLAS/releases/download/v$version/OpenBLAS-$version-x64.zip" if ((Get-FileHash $zip -Algorithm SHA256).Hash -ne $sha256) { throw 'OpenBLAS checksum mismatch' } Expand-Archive $zip -DestinationPath $dest - # The woa64 archive nests everything under OpenBLAS/; the x64 one does not. - $root = if (Test-Path (Join-Path $dest 'OpenBLAS')) { Join-Path $dest 'OpenBLAS' } else { $dest } + $root = $dest # The x64 (MinGW) CMake config points the linker at the DLL instead of its import library. # (Fixed in OpenMathLib/OpenBLAS#6091. Can be removed with the next release.) $cfg = Join-Path $root 'lib\cmake\openblas\OpenBLASConfig.cmake' @@ -278,6 +274,13 @@ jobs: "CMAKE_PREFIX_PATH=$root" | Out-File -Append -Encoding utf8 $env:GITHUB_ENV "$root\bin" | Out-File -Append -Encoding utf8 $env:GITHUB_PATH + - name: Build OpenBLAS for Windows ARM64 + # The upstream woa64 DLL targets NEOVERSEN1 and requires LSE atomics. + # Build for ARMV8 so the wheels also work on older Windows ARM64 CPUs. + if: runner.os == 'Windows' && runner.arch == 'ARM64' + shell: pwsh + run: ./.github/scripts/install_openblas_arm64.ps1 + - name: Build wheels uses: pypa/cibuildwheel@v4.2.1 env: From bcc94cc383010f3467069fcd20ab81c84d8aa332 Mon Sep 17 00:00:00 2001 From: Brendan O'Donoghue Date: Mon, 5 Oct 2026 11:05:27 +0100 Subject: [PATCH 3/3] Initialize Visual Studio through its PowerShell launcher --- .github/scripts/install_openblas_arm64.ps1 | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.github/scripts/install_openblas_arm64.ps1 b/.github/scripts/install_openblas_arm64.ps1 index 28394bd6..d6a657b8 100644 --- a/.github/scripts/install_openblas_arm64.ps1 +++ b/.github/scripts/install_openblas_arm64.ps1 @@ -30,13 +30,8 @@ if ($install.ExitCode -ne 0) { throw "LLVM installation failed: $($install.ExitC $vswhere = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\vswhere.exe' $vs = & $vswhere -latest -products '*' -requires Microsoft.VisualStudio.Component.VC.Tools.ARM64 -property installationPath if (-not $vs) { throw 'Visual Studio ARM64 tools were not found' } -$vcvars = Join-Path $vs 'VC\Auxiliary\Build\vcvarsarm64.bat' -$environment = & cmd /d /c "`"`"$vcvars`" >nul && set`"" -foreach ($line in $environment) { - if ($line -match '^([^=]+)=(.*)$') { - [Environment]::SetEnvironmentVariable($matches[1], $matches[2], 'Process') - } -} +# Initialize the current PowerShell process without passing paths through cmd. +& (Join-Path $vs 'Common7\Tools\Launch-VsDevShell.ps1') -Arch arm64 -HostArch arm64 -SkipAutomaticLocation $env:PATH = "$llvm\bin;$env:PATH" cmake -S $source -B $build -G Ninja `