diff --git a/.github/scripts/install_openblas_arm64.ps1 b/.github/scripts/install_openblas_arm64.ps1 new file mode 100644 index 00000000..d6a657b8 --- /dev/null +++ b/.github/scripts/install_openblas_arm64.ps1 @@ -0,0 +1,53 @@ +# 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' } +# 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 ` + -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 00ad86a6..c092d0a0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -241,29 +241,45 @@ 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 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' && runner.arch == 'X64' + shell: pwsh + run: | + $version = '0.3.33' + $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-x64.zip" + if ((Get-FileHash $zip -Algorithm SHA256).Hash -ne $sha256) { throw 'OpenBLAS checksum mismatch' } + Expand-Archive $zip -DestinationPath $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' + 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: Install openblas from conda on Windows - 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 + - 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 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}"