Skip to content

Refactor Windows on ARM build script #193

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions .github/workflows/windows-arm.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
name: Windows-on-ARM

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

env:
OPENBLAS_COMMIT: "v0.3.29"
OPENBLAS_ROOT: "c:\\opt"
# Preserve working directory for calls into bash
# Without this, invoking bash will cd to the home directory
CHERE_INVOKING: "yes"
BASH_PATH: "C:\\Program Files\\Git\\bin\\bash.exe"
PLAT: arm64
INTERFACE64: 0
BUILD_BITS: 32

jobs:
build:
runs-on: windows-11-arm
timeout-minutes: 90

steps:

- uses: actions/[email protected]

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: 3.12
architecture: arm64

- name: Setup visual studio
uses: microsoft/setup-msbuild@v2

- name: Download, install 7zip.
run: |
Invoke-WebRequest https://www.7-zip.org/a/7z2409-arm64.exe -UseBasicParsing -OutFile 7z_arm.exe
Start-Process -FilePath ".\7z_arm.exe" -ArgumentList "/S" -Wait
echo "C:\Program Files\7-Zip" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append

- name: Download and install LLVM installer
run: |
Invoke-WebRequest https://github.com/llvm/llvm-project/releases/download/llvmorg-19.1.5/LLVM-19.1.5-woa64.exe -UseBasicParsing -OutFile LLVM-woa64.exe
Start-Process -FilePath ".\LLVM-woa64.exe" -ArgumentList "/S" -Wait
echo "C:\Program Files\LLVM\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append

- name: Update CMake for WoA
run: |
pip install cmake
get-command cmake

- name: Build
run: |
git submodule update --init --recursive
.\tools\build_steps_win_arm64.bat

- name: Pack
run: |
cd local
cp -r "scipy_openblas${env:BUILD_BITS}" $env:BUILD_BITS
7z a ../builds/openblas-${env:PLAT}-${env:INTERFACE64}.zip -tzip $env:BUILD_BITS

- name: Test 32-bit interface wheel
run: |
python -m pip install --no-index --find-links dist scipy_openblas32
python -m scipy_openblas32
python -c "import scipy_openblas32; print(scipy_openblas32.get_pkg_config())"

- uses: actions/[email protected]
with:
name: wheels-${{ env.PLAT }}-${{ env.INTERFACE64 }}
path: dist/scipy_openblas*.whl

- uses: actions/[email protected]
with:
name: openblas-${{ env.PLAT }}-${{ env.INTERFACE64 }}
path: builds/openblas*.zip

- name: Install Anaconda client
run: |
# Rust installation needed for rpds-py.
Invoke-WebRequest https://static.rust-lang.org/rustup/dist/aarch64-pc-windows-msvc/rustup-init.exe -UseBasicParsing -Outfile rustup-init.exe
.\rustup-init.exe -y
$env:PATH="$env:PATH;$env:USERPROFILE\.cargo\bin"
pip install anaconda-client

- name: Upload
# see https://github.com/marketplace/actions/setup-miniconda for why
# `-el {0}` is required.
shell: bash -el {0}
env:
ANACONDA_SCIENTIFIC_PYTHON_UPLOAD: ${{ secrets.ANACONDA_SCIENTIFIC_PYTHON_UPLOAD }}
run: |
source tools/upload_to_anaconda_staging.sh
upload_wheels
153 changes: 105 additions & 48 deletions tools/build_steps_win_arm64.bat
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,52 +1,83 @@
:: Build script for scipy_openblas wheel on Windows on ARM64

:: Usage: build_steps_win_arm64.bat [build_bits]
:: e.g build_steps_win_arm64.bat 64
:: Usage: build_steps_win_arm64.bat [build_bits] [if_bits]
:: e.g build_steps_win_arm64.bat 64 64

:: BUILD_BITS (default binary architecture, 32 or 64, unspec -> 64).
:: build_bits (default binary architecture, 32 or 64, unspec -> 64).
:: if_bits (default interface size, 32 or 64, unspec -> 32)
:: If INTERFACE64 environment variable is 1, then if_bits defaults to 64
:: Expects these binaries on the PATH:
:: clang-cl, flang-new, cmake, perl

:: First commit containing WoA build fixes.
:: Minimum OpenBLAS commit to build; we'll update to this if commit not
:: present.
set first_woa_buildable_commit="de2380e5a6149706a633322a16a0f66faa5591fc"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we in a position to update the submodule OpenBLAS to some commit that contains the WoA build updates as above? Otherwise - what should we do with the noted version if we have to do the update above?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not at the moment — we'll update the submodule once the SUFFIX64_UNDERSCORE issue is fixed upstream. That way, we produce the correct libname for 64-bit interface builds as scipy_openblas64_.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Harishmcw - yes, we do not use a compatible OpenBLAS at the moment, but the question was whether we could update to one as part of this change. Otherwise, I guess we could cherry-pick the build changes, so that the user would get the right version (e.g. v0.3.29) in terms of execution results.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Harishmcw - yes, we do not use a compatible OpenBLAS at the moment, but the question was whether we could update to one as part of this change. Otherwise, I guess we could cherry-pick the build changes, so that the user would get the right version (e.g. v0.3.29) in terms of execution results.

The current OpenBLAS submodule commit we're using already includes all the Win ARM64-related changes, so no update is needed at this point.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Harishmcw - if you look at the code in tools/build_openblas.sh, you will see that it does git checkout $OPENBLAS_COMMIT where OPENBLAS_COMMIT comes from the top of the the windows.yml and posix.yml Github workflow files. Currently this commit is v0.3.29. This is so we can assure ourselves that we are building a specific version, that is constant across the platforms. The WoA build recipe does not do this - but probably should. The problem is the lack of WoA build patches in OpenBLAS v0.3.29 - hence my question above.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is fine to change the OPENBLAS_COMMIT in all the workflow files to a newer commit. Then the version in the pyproject.toml should be adjusted to match, using `git describe --tags --abbrev=8 in the OpenBLAS directory. I thought I had a test to make sure the result of get_openblas_config() will match the pyproject.toml version, but it seems not. :(

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mattip - any preference for the newer (post WoA fixes) commit? Current develop for example?

And then we can change the logic to error if the OpenBLAS commit doesn't contain the WoA fixes.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any commit is fine, as long as the first fields of the version matches ``git describe --tags --abbrev=8, then tag on a 0` for the wheel version that we can update if there is a problem with the wheel


@echo off
setlocal enabledelayedexpansion

if "%1"=="" (
set BUILD_BIT=64
set build_bits=64
) else (
set build_bits=%1
)
if "%INTERFACE64%"=="1" (
set "if_default=64"
) else (
set "if_default=32"
)
if "%2"=="" (
set "if_bits=%if_default%"
) else (
set BUILD_BIT=%1
set "if_bits=%2"
)
echo Building for %BUILD_BIT%-bit configuration...
echo Building for %build_bits%-bit binary, %if_bits%-bit interface...

:: Define destination directory
move "..\local\scipy_openblas64" "..\local\scipy_openblas32"
set "DEST_DIR=%CD%\..\local\scipy_openblas32"
cd ..

:: Check if 'openblas' folder exists and is empty
if exist "openblas" (
dir /b "openblas" | findstr . >nul
if errorlevel 1 (
echo OpenBLAS folder exists but is empty. Deleting and recloning...
rmdir /s /q "openblas"
pushd "%~dp0\.."
set "ob_out_root=%CD%\local\scipy_openblas"
set "ob_64=%ob_out_root%64"
set "ob_32=%ob_out_root%32"
set "local_dir=%CD%\local"
for /d %%D in ("%local_dir%\*") do (
if /I not "%%~nxD"=="scipy_openblas64" (
rmdir /S /Q "%%D"
)
)

:: Clone OpenBLAS if not present
if not exist "openblas" (
echo Cloning OpenBLAS repository with submodules...
git clone --recursive https://github.com/OpenMathLib/OpenBLAS.git OpenBLAS
if errorlevel 1 exit /b 1
if "%if_bits%"=="64" (
set "DEST_DIR=%ob_64%"
) else (
if exist "%ob_64%" (
xcopy /Y /H "%ob_64%\*.py" "%CD%\ob64_backup\"
move "%ob_64%" "%ob_32%"
set "DEST_DIR=%ob_32%"
)
)

:: Clone OpenBLAS
echo Cloning OpenBLAS repository with submodules...
git submodule update --init --recursive OpenBLAS
if errorlevel 1 exit /b 1

:: Enter OpenBLAS directory and checkout develop branch
cd openblas
git checkout develop

echo Checked out to the latest branch of OpenBLAS.
:: Enter OpenBLAS directory and checkout buildable commit
cd OpenBLAS
git merge-base --is-ancestor %first_woa_buildable_commit% HEAD 2>NUL
if errorlevel 1 (
echo Updating to WoA buildable commit for OpenBLAS
git checkout %first_woa_buildable_commit%
)

:: Set suffixed-ILP64 flags
if "%if_bits%"=="64" (
set "interface_flags=-DINTERFACE64=1 -DSYMBOLSUFFIX=64_"
) else (
set "interface_flags="
)

:: Create build directory and navigate to it
if not exist build mkdir build
cd build
if exist build (rmdir /S /Q build || exit /b 1)
mkdir build || exit /b 1 & cd build || exit /b 1

echo Setting up ARM64 Developer Command Prompt and running CMake...

Expand All @@ -55,36 +86,42 @@ for /f "usebackq tokens=*" %%i in (`"C:\Program Files (x86)\Microsoft Visual Stu

:: Run CMake and Ninja build
cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DTARGET=ARMV8 -DBUILD_SHARED_LIBS=ON -DARCH=arm64 ^
-DBINARY=%BUILD_BIT% -DCMAKE_SYSTEM_PROCESSOR=ARM64 -DCMAKE_C_COMPILER=clang-cl ^
-DCMAKE_Fortran_COMPILER=flang-new -DSYMBOLPREFIX="scipy_" -DLIBNAMEPREFIX="scipy_"
-DBINARY=%build_bits% -DCMAKE_SYSTEM_PROCESSOR=ARM64 -DCMAKE_C_COMPILER=clang-cl ^
-DCMAKE_Fortran_COMPILER=flang-new -DSYMBOLPREFIX="scipy_" -DLIBNAMEPREFIX="scipy_" %interface_flags%
if errorlevel 1 exit /b 1

ninja
if errorlevel 1 exit /b 1

echo Build complete. Returning to Batch.

:: Rewrite the name of the project to scipy-openblas32
echo Rewrite to scipy_openblas32
cd ../..
powershell -Command "(Get-Content 'pyproject.toml') -replace 'openblas64', 'openblas32' | Set-Content 'pyproject.toml'"
powershell -Command "(Get-Content 'local\scipy_openblas32\__main__.py') -replace 'openblas64', 'openblas32' | Out-File 'local\scipy_openblas32\__main__.py' -Encoding utf8"
powershell -Command "(Get-Content 'local\scipy_openblas32\__init__.py') -replace 'openblas64', 'openblas32' | Out-File 'local\scipy_openblas32\__init__.py' -Encoding utf8"
powershell -Command "(Get-Content 'local\scipy_openblas32\__init__.py') -replace 'openblas_get_config64_', 'openblas_get_config' | Out-File 'local\scipy_openblas32\__init__.py' -Encoding utf8"
powershell -Command "(Get-Content 'local\scipy_openblas32\__init__.py') -replace 'cflags =.*', 'cflags = \"-DBLAS_SYMBOL_PREFIX=scipy_\"' | Out-File 'local\scipy_openblas32\__init__.py' -Encoding utf8"
if "%if_bits%"=="32" (
echo Rewrite to scipy_openblas32
cd ../..
set out_pyproject=pyproject_64_32.toml
powershell -Command "(Get-Content 'pyproject.toml') -replace 'openblas64', 'openblas32' | Set-Content !out_pyproject!"
powershell -Command "(Get-Content 'local\scipy_openblas32\__main__.py') -replace 'openblas64', 'openblas32' | Out-File 'local\scipy_openblas32\__main__.py' -Encoding utf8"
powershell -Command "(Get-Content 'local\scipy_openblas32\__init__.py') -replace 'openblas64', 'openblas32' | Out-File 'local\scipy_openblas32\__init__.py' -Encoding utf8"
powershell -Command "(Get-Content 'local\scipy_openblas32\__init__.py') -replace 'openblas_get_config64_', 'openblas_get_config' | Out-File 'local\scipy_openblas32\__init__.py' -Encoding utf8"
powershell -Command "(Get-Content 'local\scipy_openblas32\__init__.py') -replace 'cflags =.*', 'cflags = \"-DBLAS_SYMBOL_PREFIX=scipy_\"' | Out-File 'local\scipy_openblas32\__init__.py' -Encoding utf8"
)

:: Prepare destination directory
cd OpenBLAS/build
echo Preparing destination directory at %DEST_DIR%...
if not exist "%DEST_DIR%\lib\cmake\openblas" mkdir "%DEST_DIR%\lib\cmake\openblas"
if not exist "%DEST_DIR%\lib\cmake\OpenBLAS" mkdir "%DEST_DIR%\lib\cmake\OpenBLAS"
if not exist "%DEST_DIR%\include" mkdir "%DEST_DIR%\include"

:: Move library files
echo Moving library files...
if exist lib\release (
move /Y lib\release\*.dll "%DEST_DIR%\lib\"
if errorlevel 1 exit /b 1
move /Y lib\release\*.dll.a "%DEST_DIR%\lib\scipy_openblas.lib"
for %%f in (lib\release\*.dll.a) do (
set "orig_name=%%~nxf"
call set "base_name=%%orig_name:.dll.a=%%"
move /Y "%%f" "%DEST_DIR%\lib\!base_name!.lib"
)
if errorlevel 1 exit /b 1
) else (
echo Error: lib/release directory not found!
Expand All @@ -99,7 +136,7 @@ if exist openblasconfigversion.cmake copy /Y openblasconfigversion.cmake "%DEST_
:: Copy header files
echo Copying generated header files...
if exist generated xcopy /E /Y generated "%DEST_DIR%\include\"
if exist lapacke_mangling copy /Y lapacke_mangling "%DEST_DIR%\include\"
if exist lapacke_mangling.h copy /Y lapacke_mangling.h "%DEST_DIR%\include\"
if exist openblas_config.h copy /Y openblas_config.h "%DEST_DIR%\include\"


Expand All @@ -113,12 +150,32 @@ cd ../..

:: Build the Wheel & Install It
echo Running 'python -m build' to build the wheel...
python -m build
if errorlevel 1 exit /b 1

python -c "import build" 2>NUL || pip install build
if "%if_bits%"=="64" (
python -m build
if errorlevel 1 exit /b 1
) else (
move /Y pyproject.toml pyproject.toml.bak
move /Y %out_pyproject% pyproject.toml
python -m build
if errorlevel 1 exit /b 1
move /Y pyproject.toml.bak pyproject.toml
)
if "%if_bits%"=="32" (
move /Y "%CD%\ob64_backup" "%ob_64%"
)

:: Rename the wheel
for %%f in (dist\*any.whl) do (
set WHEEL_FILE=dist\%%f
set "filename=%%~nxf"
set "newname=!filename:any.whl=win_arm64.whl!"
ren "dist\!filename!" "!newname!"
)

:: Locate the built wheel
for /f %%f in ('dir /b dist\scipy_openblas*.whl 2^>nul') do set WHEEL_FILE=dist\%%f

if not defined WHEEL_FILE (
echo Error: No wheel file found in dist folder.
exit /b 1
Expand All @@ -129,4 +186,4 @@ pip install "%WHEEL_FILE%"
if errorlevel 1 exit /b 1

echo Done.
exit /b 0
exit /b 0
Loading