diff --git a/.github/scripts/install-openblas.ps1 b/.github/scripts/install-openblas.ps1 new file mode 100644 index 000000000..b253d73c3 --- /dev/null +++ b/.github/scripts/install-openblas.ps1 @@ -0,0 +1,112 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +param( + [string]$InstallPath = "C:\openblas" +) + +$ErrorActionPreference = "Stop" +# Windows PowerShell 5.1 redraws the progress bar on every read block, which +# costs an order of magnitude of wall-clock on a download this size. +$ProgressPreference = "SilentlyContinue" +Set-StrictMode -Version Latest + +# Bumping OpenBLAS means changing $openBlasVersion and every Hash below. +# Regenerate a hash with: Get-FileHash -Algorithm SHA256 +$openBlasVersion = "0.3.30" +$packages = @{ + arm64 = @{ + Asset = "woa64-dll" + Hash = "5BBD8C6CA5A4C415FF58D5E18378B35BC1E81F46E6C385753EFF367FF7474819" + Subdirectory = "OpenBLAS" + } + x64 = @{ + Asset = "x64" + Hash = "8B04387766EFC05C627E26D24797EC0D4ED4C105EC14FA7400AA84A02DB22B66" + Subdirectory = "" + } +} + +$nativeArchitectures = @( + $env:PROCESSOR_ARCHITECTURE + $env:PROCESSOR_ARCHITEW6432 +) + +if ($nativeArchitectures -contains "ARM64") { + $architecture = "arm64" +} elseif ($nativeArchitectures -contains "AMD64") { + $architecture = "x64" +} else { + throw "Unsupported Windows architecture: $($nativeArchitectures -join ', ')" +} + +$package = $packages[$architecture] +$archiveUrl = "https://github.com/OpenMathLib/OpenBLAS/releases/download/v$openBlasVersion/OpenBLAS-$openBlasVersion-$($package.Asset).zip" +$expectedHash = $package.Hash +$archiveSubdirectory = $package.Subdirectory + +$workingPath = Join-Path ([System.IO.Path]::GetTempPath()) "faiss-openblas-$architecture-$PID" +$archivePath = Join-Path $workingPath "openblas.zip" +$extractPath = Join-Path $workingPath "extract" + +New-Item -ItemType Directory -Path $workingPath | Out-Null + +try { + # A blip here aborts before-all, losing every interpreter's wheel in this + # job. Retrying is safe: the hash check below rejects a partial download. + # -UseBasicParsing keeps PS 5.1 off the Internet Explorer parsing engine, + # which is absent on freshly provisioned images. + for ($attempt = 1; $attempt -le 3; $attempt++) { + try { + Invoke-WebRequest -Uri $archiveUrl -OutFile $archivePath -UseBasicParsing + break + } catch { + if ($attempt -eq 3) { + throw + } + Start-Sleep -Seconds (5 * $attempt) + } + } + + $archiveStream = [System.IO.File]::OpenRead($archivePath) + try { + $hasher = [System.Security.Cryptography.SHA256]::Create() + try { + $actualHash = [System.BitConverter]::ToString( + $hasher.ComputeHash($archiveStream) + ).Replace("-", "") + } finally { + $hasher.Dispose() + } + } finally { + $archiveStream.Dispose() + } + + if ($actualHash -ne $expectedHash) { + throw "OpenBLAS archive hash mismatch: expected $expectedHash, got $actualHash" + } + + Expand-Archive -LiteralPath $archivePath -DestinationPath $extractPath + $sourcePath = if ($archiveSubdirectory) { + Join-Path $extractPath $archiveSubdirectory + } else { + $extractPath + } + + foreach ($subdirectory in @("bin", "include", "lib")) { + if (-not (Test-Path -LiteralPath (Join-Path $sourcePath $subdirectory))) { + throw "OpenBLAS archive is missing the '$subdirectory' directory" + } + } + + if (Test-Path -LiteralPath $InstallPath) { + Remove-Item -LiteralPath $InstallPath -Recurse -Force + } + Move-Item -LiteralPath $sourcePath -Destination $InstallPath +} finally { + if (Test-Path -LiteralPath $workingPath) { + Remove-Item -LiteralPath $workingPath -Recurse -Force + } +} diff --git a/.github/workflows/build-pip.yml b/.github/workflows/build-pip.yml index e89dd1ee6..d8483d82b 100644 --- a/.github/workflows/build-pip.yml +++ b/.github/workflows/build-pip.yml @@ -39,7 +39,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, macos-14, macos-15-intel, windows-2022, 2-core-ubuntu-arm] + os: [ubuntu-latest, macos-14, macos-15-intel, windows-2022, windows-11-arm, 2-core-ubuntu-arm] steps: - name: Checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 diff --git a/pyproject.toml b/pyproject.toml index 1585fc987..384b3bddc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -119,9 +119,11 @@ cmake.define.FAISS_OPT_LEVEL = "generic" [tool.cibuildwheel] # Build all Python versions on Windows (no abi3); cp310 only on Linux/macOS (abi3). -# Skip 32-bit and free-threaded builds. musllinux is built (see override below). +# Skip Windows arm64 for Python 3.10 because Python 3.10 is not available as a +# supported Windows arm64 target. Also skip 32-bit and free-threaded builds. +# musllinux is built below. build = "cp310-* cp311-* cp312-* cp313-* cp314-*" -skip = "*-win32 *-manylinux_i686 cp3*t-*" +skip = "cp310-win_arm64 *-win32 *-manylinux_i686 cp3*t-*" test-requires = ["numpy", "pytest", "scipy"] test-command = "python -m pytest {project}/tests/test_wheel_smoke.py -v" @@ -167,14 +169,26 @@ environment = {MACOSX_DEPLOYMENT_TARGET = "14.0"} select = "*-macosx_x86_64" environment = {MACOSX_DEPLOYMENT_TARGET = "15.0"} +# Windows arm64: drop scipy for the same reason as musllinux above — the smoke +# test does not import it, and a missing win_arm64 scipy wheel would force a +# failing sdist build (needs a Fortran toolchain and a BLAS on the runner). +[[tool.cibuildwheel.overrides]] +select = "*-win_arm64" +test-requires = ["numpy", "pytest"] + [tool.cibuildwheel.windows] -# Install prebuilt OpenBLAS (statically-linked MinGW runtime, no external deps). -before-all = "powershell -Command \"Invoke-WebRequest -Uri 'https://github.com/OpenMathLib/OpenBLAS/releases/download/v0.3.30/OpenBLAS-0.3.30-x64.zip' -OutFile openblas.zip; Expand-Archive openblas.zip -DestinationPath C:/openblas -Force\"" +# Install the native x64 or arm64 OpenBLAS package for the runner. +# delvewheel only has to vendor the OpenBLAS DLL itself, because the x64 package +# links the MinGW runtime statically. Re-check that property when bumping the +# OpenBLAS version or adding an architecture: `delvewheel show` on a repaired +# wheel lists what was vendored, and the PATH below hides a missed DLL from the +# smoke test, so a wheel broken this way still passes CI. +before-all = 'powershell -NoProfile -ExecutionPolicy Bypass -File "{package}\.github\scripts\install-openblas.ps1"' repair-wheel-command = "pip install delvewheel && delvewheel repair -w {dest_dir} {wheel} --add-path C:/openblas/bin --ignore-in-wheel" [tool.cibuildwheel.windows.environment] # BLAS_ROOT tells CMake's FindBLAS (via CMP0074) where to find OpenBLAS. -# PATH includes the bin/ dir so delvewheel and tests can find libopenblas.dll. +# PATH includes the bin/ dir so delvewheel and tests can find the OpenBLAS DLL. BLAS_ROOT = "C:/openblas" LAPACK_ROOT = "C:/openblas" PATH = "C:/openblas/bin;$PATH"