arithmetic operations improved #76
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| pull_request: | |
| branches: [main] | |
| push: | |
| branches: [main] | |
| workflow_dispatch: | |
| # Cancel in-progress runs on the same branch to save CI minutes. | |
| concurrency: | |
| group: ci-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| # ========================================================================= | |
| # Linux matrix: Ubuntu (22.04 / 24.04) × PHP (8.1–8.5) × CPU / CUDA | |
| # 20 combinations. | |
| # ========================================================================= | |
| test: | |
| name: "PHP ${{ matrix.php }} / ${{ matrix.os }} / ${{ matrix.cuda && 'CUDA' || 'CPU' }}" | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-22.04, ubuntu-24.04] | |
| php: ['8.1', '8.2', '8.3', '8.4', '8.5'] | |
| cuda: [false, true] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup PHP ${{ matrix.php }} | |
| uses: shivammathur/setup-php@v2 | |
| with: | |
| php-version: ${{ matrix.php }} | |
| extensions: gd | |
| tools: none | |
| coverage: none | |
| - name: Install system dependencies | |
| run: | | |
| sudo apt-get update -qq | |
| sudo apt-get install -y \ | |
| build-essential \ | |
| autoconf \ | |
| liblapacke-dev \ | |
| libopenblas-dev \ | |
| libjpeg-dev \ | |
| libpng-dev \ | |
| libwebp-dev \ | |
| libfreetype6-dev \ | |
| libxpm-dev | |
| # nvidia-cuda-toolkit provides libcublas + development headers on Ubuntu 22/24/26. | |
| # GPU tests are skipped at runtime (no hardware on standard runners); this job | |
| # validates that the CUDA build path compiles and links correctly. | |
| - name: Install CUDA toolkit | |
| if: matrix.cuda | |
| run: sudo apt-get install -y nvidia-cuda-toolkit | |
| - name: Build extension | |
| run: | | |
| phpize | |
| ./configure ${{ matrix.cuda && '--with-cuda' || '' }} | |
| make -j$(nproc) | |
| # When cublas is detected, the standard PHP build links ndarray.so without | |
| # the .cu objects (cuda_math.cu / cuda_dnn.cu) — only install-cuda does that, | |
| # and it targets the system extension dir. cuda-modules rebuilds everything | |
| # with nvcc and drops the result into modules/ndarray.so so that make test | |
| # picks it up without a system install. | |
| - name: Link CUDA objects into extension | |
| if: matrix.cuda | |
| run: | | |
| if grep -q "HAVE_CUBLAS 1" config.h; then | |
| echo "cublas detected — rebuilding modules/ndarray.so with CUDA objects" | |
| make cuda-modules | |
| else | |
| echo "::warning::cublas not found; extension built without GPU support" | |
| fi | |
| # `make test` builds tmp-php.ini from the host PHP's loaded ini + conf.d, | |
| # but the upstream phpize Makefile filters out every `^extension=` line via | |
| # PHP_DEPRECATED_DIRECTIVES_REGEX — so even though setup-php enabled `gd` | |
| # globally, the resulting tmp-php.ini lands GD-less and every | |
| # `tests/image/*.phpt` skips with "GD extension not loaded". | |
| # | |
| # Fix: bypass the Makefile's tmp-php.ini layer for GD by injecting it via | |
| # PHP_TEST_SHARED_EXTENSIONS (the variable run-tests.php propagates as `-d` | |
| # to each child test process). We must also re-include the project's own | |
| # `-d extension=ndarray.so` because we're replacing the default value of | |
| # the variable, not appending to it. | |
| # | |
| # The probe matters: Ubuntu's shivammathur PHP loads GD dynamically via | |
| # `/etc/php/.../conf.d/20-gd.ini`, so `php -n -m` does NOT list `gd` and | |
| # we inject the absolute `$extension_dir/gd.so` path. macOS Homebrew PHP | |
| # links GD statically into the binary, so `php -n -m` lists `gd` and | |
| # `extension_dir` points at the PECL dir where no separate gd.so file | |
| # exists — injecting the path there triggers a startup warning that | |
| # corrupts every test's expected output. Detecting "already loaded" | |
| # keeps both matrices clean. | |
| # | |
| # REPORT_EXIT_STATUS=1 is required: without it run-tests.php always exits 0. | |
| - name: Run tests | |
| run: | | |
| EXTRA_EXT="" | |
| if ! php -n -m 2>/dev/null | grep -qi '^gd$'; then | |
| GD_PATH=$(php -r 'echo ini_get("extension_dir") . "/gd.so";') | |
| if [ -f "$GD_PATH" ]; then | |
| EXTRA_EXT=" -d extension=$GD_PATH" | |
| fi | |
| fi | |
| REPORT_EXIT_STATUS=1 make test \ | |
| PHP_TEST_SHARED_EXTENSIONS="$EXTRA_EXT -d extension=ndarray.so" \ | |
| TESTS="-q --show-diff" | |
| - name: Upload failure diffs | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: "diffs-php${{ matrix.php }}-${{ matrix.os }}-${{ matrix.cuda && 'cuda' || 'cpu' }}" | |
| path: "**/*.diff" | |
| if-no-files-found: ignore | |
| # ========================================================================= | |
| # macOS matrix: macos-14 / macos-15 (both Apple Silicon ARM64) × PHP 8.1–8.5. | |
| # 10 combinations. CPU only — CUDA is not available on macOS. | |
| # | |
| # Apple clang on ARM64 falls through the long-double fallback in | |
| # src/ndarray_types.h (no __float128, no libquadmath). The HAVE_AVX2 macro | |
| # is left undefined on non-x86 hosts by config.m4, so the AVX2 kernels are | |
| # excluded from the build. OpenBLAS + LAPACK come from Homebrew. | |
| # ========================================================================= | |
| macos-test: | |
| name: "PHP ${{ matrix.php }} / ${{ matrix.os }}" | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [macos-14, macos-15] | |
| php: ['8.1', '8.2', '8.3', '8.4', '8.5'] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup PHP ${{ matrix.php }} | |
| uses: shivammathur/setup-php@v2 | |
| with: | |
| php-version: ${{ matrix.php }} | |
| extensions: gd | |
| tools: none | |
| coverage: none | |
| # Homebrew ships its own openblas + lapack kegs that include LAPACKE | |
| # headers and the libopenblas.dylib symbol set we need (cblas_sdot, | |
| # LAPACKE_sgesdd). Apple's vecLib/Accelerate framework would be lighter | |
| # weight but doesn't export the LAPACKE_* C bindings. | |
| - name: Install OpenBLAS + LAPACK (Homebrew) | |
| run: | | |
| brew update | |
| brew install openblas lapack autoconf | |
| - name: Build extension | |
| run: | | |
| OPENBLAS_PREFIX=$(brew --prefix openblas) | |
| LAPACK_PREFIX=$(brew --prefix lapack) | |
| export CPPFLAGS="-I${OPENBLAS_PREFIX}/include -I${LAPACK_PREFIX}/include ${CPPFLAGS}" | |
| export LDFLAGS="-L${OPENBLAS_PREFIX}/lib -L${LAPACK_PREFIX}/lib ${LDFLAGS}" | |
| export LIBRARY_PATH="${OPENBLAS_PREFIX}/lib:${LAPACK_PREFIX}/lib:${LIBRARY_PATH}" | |
| export PKG_CONFIG_PATH="${OPENBLAS_PREFIX}/lib/pkgconfig:${LAPACK_PREFIX}/lib/pkgconfig:${PKG_CONFIG_PATH}" | |
| phpize | |
| ./configure | |
| make -j$(sysctl -n hw.ncpu) | |
| # Same GD-loading workaround as the Linux job — see the long comment | |
| # there for why `make test`'s tmp-php.ini lands GD-less and how | |
| # PHP_TEST_SHARED_EXTENSIONS gets it back in front of each child test. | |
| # On macOS, Homebrew PHP links GD statically; the probe below skips | |
| # the inject so we don't fight a non-existent /opt/homebrew/.../gd.so. | |
| - name: Run tests | |
| run: | | |
| EXTRA_EXT="" | |
| if ! php -n -m 2>/dev/null | grep -qi '^gd$'; then | |
| GD_PATH=$(php -r 'echo ini_get("extension_dir") . "/gd.so";') | |
| if [ -f "$GD_PATH" ]; then | |
| EXTRA_EXT=" -d extension=$GD_PATH" | |
| fi | |
| fi | |
| REPORT_EXIT_STATUS=1 make test \ | |
| PHP_TEST_SHARED_EXTENSIONS="$EXTRA_EXT -d extension=ndarray.so" \ | |
| TESTS="-q --show-diff" | |
| - name: Upload failure diffs | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: "diffs-php${{ matrix.php }}-${{ matrix.os }}" | |
| path: "**/*.diff" | |
| if-no-files-found: ignore | |
| # ========================================================================= | |
| # Windows matrix: Windows Server 2022 / 2025 × PHP 8.1–8.5 × CPU / CUDA. | |
| # 20 combinations. CUDA jobs install the NVIDIA Toolkit via Jimver's action, | |
| # run build-cuda-windows.bat to pre-compile the .cu sources, then nmake | |
| # links them in. GitHub-hosted Windows runners have no GPU, so the GPU | |
| # PHPT tests SKIP at runtime — same behaviour as the Linux CUDA matrix. | |
| # ========================================================================= | |
| windows-test: | |
| name: "PHP ${{ matrix.php }} / ${{ matrix.os }} / ${{ matrix.cuda && 'CUDA' || 'CPU' }}" | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [windows-2022, windows-2025] | |
| php: ['8.1', '8.2', '8.3', '8.4', '8.5'] | |
| cuda: [false, true] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| # Sets %PHP_SDK_ROOT% / %PHP_PREFIX%, downloads the matching PHP devel | |
| # pack, configures the right Visual Studio toolset (vs16/vs17) and | |
| # exposes phpize.bat + nmake on PATH for subsequent steps. | |
| - name: Set up PHP build environment | |
| uses: php/setup-php-sdk@v0.12 | |
| id: php-sdk | |
| with: | |
| version: ${{ matrix.php }} | |
| arch: x64 | |
| ts: nts | |
| - name: Set up MSVC developer prompt | |
| uses: ilammy/msvc-dev-cmd@v1 | |
| with: | |
| arch: x64 | |
| toolset: ${{ steps.php-sdk.outputs.toolset }} | |
| # OpenBLAS for Windows: download the upstream pre-built x64 release. | |
| # The ZIP contains include/, lib/libopenblas.lib + libopenblas.dll.a, | |
| # bin/libopenblas.dll — config.w32 finds these via --with-openblas-dir. | |
| - name: Cache OpenBLAS | |
| id: openblas-cache | |
| uses: actions/cache@v4 | |
| with: | |
| path: openblas | |
| key: openblas-0.3.30-windows-x64 | |
| - name: Download OpenBLAS prebuilt | |
| if: steps.openblas-cache.outputs.cache-hit != 'true' | |
| shell: pwsh | |
| run: | | |
| $url = "https://github.com/OpenMathLib/OpenBLAS/releases/download/v0.3.30/OpenBLAS-0.3.30-x64.zip" | |
| Invoke-WebRequest -Uri $url -OutFile openblas.zip | |
| New-Item -ItemType Directory -Path openblas -Force | Out-Null | |
| Expand-Archive -Path openblas.zip -DestinationPath openblas -Force | |
| # CUDA Toolkit (network installer with a minimal sub-package list so the | |
| # runner only downloads ~1.5 GB instead of the full 3+ GB). The action | |
| # exposes %CUDA_PATH% for the rest of the steps. cuDNN is not installed | |
| # by default — the GPU DNN tests will skip, matching the cudnn-less | |
| # build path on the Linux CUDA matrix runners. | |
| - name: Install CUDA Toolkit | |
| if: matrix.cuda | |
| uses: Jimver/cuda-toolkit@v0.2.19 | |
| id: cuda-toolkit | |
| with: | |
| cuda: '12.5.0' | |
| method: 'network' | |
| sub-packages: '["nvcc", "cublas", "cublas_dev", "cudart", "thrust", "curand", "curand_dev", "cusolver", "cusolver_dev", "cusparse", "nvjitlink", "visual_studio_integration"]' | |
| - name: phpize | |
| shell: cmd | |
| run: | | |
| phpize | |
| - name: configure (CPU) | |
| if: '!matrix.cuda' | |
| shell: cmd | |
| run: | | |
| configure --enable-ndarray --with-openblas-dir=%CD%\openblas --with-prefix=${{ steps.php-sdk.outputs.prefix }} | |
| - name: configure (CUDA) | |
| if: matrix.cuda | |
| shell: cmd | |
| env: | |
| PHP_PREFIX: ${{ steps.php-sdk.outputs.prefix }} | |
| run: | | |
| configure --enable-ndarray --with-openblas-dir=%CD%\openblas --with-cuda --with-cuda-dir="%CUDA_PATH%" --with-prefix=%PHP_PREFIX% | |
| # Pre-compile the .cu sources into ndarray_cuda.lib. Must run BEFORE | |
| # nmake, since config.w32 added ndarray_cuda.lib to LIBS_NDARRAY and | |
| # nmake will fail at link time if the file is missing. The .bat reads | |
| # %CUDA_PATH% (set by the cuda-toolkit action) and %PHP_PREFIX% (set | |
| # below) directly from the environment — passing them as CLI flags | |
| # would hit cmd.exe's quirk of treating `=` as an argument delimiter. | |
| - name: Build CUDA objects | |
| if: matrix.cuda | |
| shell: cmd | |
| env: | |
| PHP_PREFIX: ${{ steps.php-sdk.outputs.prefix }} | |
| run: | | |
| call build-cuda-windows.bat | |
| - name: nmake | |
| shell: cmd | |
| run: | | |
| nmake | |
| # The PHP test runner loads the freshly-built php_ndarray.dll from the | |
| # build output dir; the OpenBLAS DLL (and on CUDA builds the cublas / | |
| # cudart / cudnn DLLs) have to be co-located or on PATH for the loader | |
| # to resolve cblas_* / LAPACKE_* / cublas* / cudnn* at runtime. | |
| - name: Stage runtime DLLs next to extension | |
| shell: pwsh | |
| run: | | |
| # PHP on Windows calls SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_DEFAULT_DIRS) | |
| # early in startup, which kills PATH-based search for extension transitive | |
| # deps. After that, dependency resolution only looks in: | |
| # 1. The directory of php.exe (= %PHP_PREFIX% = php-bin\) | |
| # 2. C:\Windows\System32 | |
| # 3. Dirs added via AddDllDirectory() — PHP adds none. | |
| # So every runtime DLL must end up in php-bin\. The copies into x64\Release\ | |
| # are belt & suspenders for tools that bypass the hardened loader. | |
| $buildDir = if (Test-Path 'x64\Release') { 'x64\Release' } else { 'x64\Release_TS' } | |
| $phpBin = "${{ steps.php-sdk.outputs.prefix }}" | |
| Write-Host "=== Staging targets ===" | |
| Write-Host " buildDir = $buildDir" | |
| Write-Host " phpBin = $phpBin" | |
| Write-Host "=== Copying libopenblas.dll ===" | |
| Copy-Item openblas\bin\libopenblas.dll "$buildDir\" -Force -Verbose | |
| Copy-Item openblas\bin\libopenblas.dll "$phpBin\" -Force -Verbose | |
| if ($env:RUNNER_CUDA -eq 'true') { | |
| $cudaBin = Join-Path $env:CUDA_PATH 'bin' | |
| Write-Host "=== Source CUDA bin: $cudaBin ===" | |
| Get-ChildItem -Path $cudaBin -Filter '*.dll' | Select-Object -ExpandProperty Name | Sort-Object | Out-Host | |
| $filters = @( | |
| 'cublas64_*.dll', # direct link dep | |
| 'cudart64_*.dll', # direct link dep | |
| 'cublasLt64_*.dll', # transitive dep of cublas | |
| 'cusolver64_*.dll', # direct link dep | |
| 'curand64_*.dll', # direct link dep | |
| 'cusparse64_*.dll', # transitive dep of cusolver | |
| 'nvJitLink_*.dll', # transitive dep of cublas/cusolver on CUDA 12+ | |
| 'cudnn64_*.dll' # optional, when cuDNN sub-package was installed | |
| ) | |
| foreach ($filter in $filters) { | |
| $matched = Get-ChildItem -Path $cudaBin -Filter $filter -ErrorAction SilentlyContinue | |
| if (-not $matched) { | |
| Write-Host " $filter -> no match" | |
| continue | |
| } | |
| foreach ($f in $matched) { | |
| Write-Host " Copying $($f.Name) -> $buildDir\ and $phpBin\" | |
| Copy-Item -Path $f.FullName -Destination "$buildDir\" -Force | |
| Copy-Item -Path $f.FullName -Destination "$phpBin\" -Force | |
| } | |
| } | |
| } | |
| Write-Host "" | |
| Write-Host "=== Files in $phpBin (final) ===" | |
| Get-ChildItem -Path $phpBin -Filter '*.dll' | Select-Object -ExpandProperty Name | Sort-Object | Out-Host | |
| env: | |
| RUNNER_CUDA: ${{ matrix.cuda }} | |
| - name: Show extension's transitive DLL deps | |
| shell: pwsh | |
| run: | | |
| $buildDir = if (Test-Path 'x64\Release') { 'x64\Release' } else { 'x64\Release_TS' } | |
| $dll = Join-Path $buildDir 'php_ndarray.dll' | |
| Write-Host "=== dumpbin /dependents $dll ===" | |
| dumpbin /dependents $dll | |
| if ($env:RUNNER_CUDA -eq 'true') { | |
| # Also dump deps of the CUDA libs themselves — these are transitive | |
| # deps of php_ndarray.dll that the loader needs to resolve too. | |
| # If something here is missing from php-bin\ or System32, that's | |
| # the source of "module could not be found". | |
| $phpBin = "${{ steps.php-sdk.outputs.prefix }}" | |
| foreach ($name in @('cublas64_12.dll', 'cusolver64_11.dll', 'cudart64_12.dll', 'curand64_10.dll')) { | |
| $p = Join-Path $phpBin $name | |
| if (Test-Path $p) { | |
| Write-Host "" | |
| Write-Host "=== dumpbin /dependents $p ===" | |
| dumpbin /dependents $p | |
| } | |
| } | |
| } | |
| env: | |
| RUNNER_CUDA: ${{ matrix.cuda }} | |
| - name: Run tests | |
| shell: cmd | |
| env: | |
| REPORT_EXIT_STATUS: 1 | |
| run: | | |
| nmake test TESTS="-q --show-diff" | |
| - name: Upload failure diffs | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: "diffs-php${{ matrix.php }}-${{ matrix.os }}-win-x64-${{ matrix.cuda && 'cuda' || 'cpu' }}" | |
| path: "**/*.diff" | |
| if-no-files-found: ignore | |
| # ========================================================================= | |
| # Gate job — the ONLY required status check for branch protection. | |
| # ========================================================================= | |
| all-pass: | |
| name: All required tests passed | |
| needs: [test, macos-test, windows-test] | |
| runs-on: ubuntu-latest | |
| if: always() | |
| steps: | |
| - name: Check required matrix results | |
| run: | | |
| fail=0 | |
| for label in "linux:${{ needs.test.result }}" \ | |
| "macos:${{ needs.macos-test.result }}" \ | |
| "windows:${{ needs.windows-test.result }}"; do | |
| name="${label%%:*}" | |
| result="${label##*:}" | |
| echo "${name}: ${result}" | |
| if [[ "${result}" != "success" ]]; then | |
| fail=1 | |
| fi | |
| done | |
| if [[ $fail -ne 0 ]]; then | |
| echo "::error::One or more required matrices failed or were cancelled." | |
| exit 1 | |
| fi | |
| echo "All required Linux + macOS + Windows combinations passed." |