@@ -249,6 +249,18 @@ jobs:
249249 family : " zts"
250250 artifact : " phpstan_turbo"
251251 suffix : " -zts"
252+ # The macOS slices are merged into one universal binary (and ad-hoc
253+ # signed) by the turbo-macos-universal job below, so their artifacts
254+ # carry the turbo-slice prefix that the phpstan_turbo-* download
255+ # patterns in the aggregate and commit jobs do not match.
256+ - name : " macos-x86_64"
257+ runs-on : " macos-15-intel" # the last x86_64 macOS image, available until August 2027
258+ family : " macos"
259+ artifact : " turbo-slice"
260+ - name : " macos-arm64"
261+ runs-on : " macos-latest"
262+ family : " macos"
263+ artifact : " turbo-slice"
252264
253265 steps :
254266 - name : Harden the runner (Audit all outbound calls)
@@ -395,6 +407,67 @@ jobs:
395407 path : " turbo-ext/phpstan_turbo.so"
396408 if-no-files-found : " error"
397409
410+ turbo-macos-universal :
411+ name : " Turbo macOS Universal Binary"
412+ needs : " turbo-compile"
413+ runs-on : " macos-latest"
414+ timeout-minutes : 30
415+
416+ strategy :
417+ fail-fast : false
418+ matrix :
419+ php-version : ["8.4", "8.5"]
420+
421+ steps :
422+ - name : Harden the runner (Audit all outbound calls)
423+ uses : step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
424+ with :
425+ egress-policy : audit
426+
427+ - name : " Download x86_64 slice"
428+ uses : actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
429+ with :
430+ name : " turbo-slice-macos-x86_64-php${{ matrix.php-version }}"
431+ path : " slice-x86_64"
432+
433+ - name : " Download arm64 slice"
434+ uses : actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
435+ with :
436+ name : " turbo-slice-macos-arm64-php${{ matrix.php-version }}"
437+ path : " slice-arm64"
438+
439+ - name : " Merge the slices into a universal binary"
440+ run : |
441+ lipo -create slice-x86_64/phpstan_turbo.so slice-arm64/phpstan_turbo.so -output phpstan_turbo.so
442+ lipo -info phpstan_turbo.so
443+
444+ # On arm64 AMFI refuses to map unsigned code, so the binary must carry
445+ # at least an ad-hoc signature. lipo preserves the linker's per-slice
446+ # ad-hoc signatures, but re-sign deliberately instead of relying on
447+ # that. Quarantine (and thus Gatekeeper/notarization) only applies to
448+ # browser downloads — composer/git/curl installs never see it — so an
449+ # ad-hoc signature is enough and needs no secrets.
450+ - name : " Ad-hoc sign the universal binary"
451+ run : |
452+ codesign --force --sign - phpstan_turbo.so
453+ codesign --verify --verbose=2 phpstan_turbo.so
454+
455+ - name : " Install PHP"
456+ uses : " shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240" # v2.37.2
457+ with :
458+ coverage : " none"
459+ php-version : " ${{ matrix.php-version }}"
460+
461+ - name : " Verify the universal binary loads"
462+ run : php -d extension="$PWD/phpstan_turbo.so" -m | grep phpstan_turbo
463+
464+ - name : " Upload universal binary artifact"
465+ uses : actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
466+ with :
467+ name : " phpstan_turbo-macos-php${{ matrix.php-version }}"
468+ path : " phpstan_turbo.so"
469+ if-no-files-found : " error"
470+
398471 turbo-compile-windows :
399472 name : " Compile Turbo Extension (Windows)"
400473 # windows-latest and windows-2025 serve the VS2026 image, whose 14.5x
@@ -518,6 +591,7 @@ jobs:
518591 needs :
519592 - turbo-compile
520593 - turbo-compile-windows
594+ - turbo-macos-universal
521595 runs-on : " ubuntu-latest"
522596 timeout-minutes : 5
523597
@@ -562,6 +636,7 @@ jobs:
562636 needs :
563637 - turbo-compile
564638 - turbo-compile-windows
639+ - turbo-macos-universal
565640 runs-on : ${{ matrix.operating-system }}
566641 timeout-minutes : 60
567642
@@ -580,6 +655,17 @@ jobs:
580655 php-version : ["8.4", "8.5"]
581656 ts : ["nts", "zts"]
582657 script : ["make tests", "make phpstan"]
658+ # macOS runs the shipped ad-hoc-signed universal binary; there is no
659+ # macOS zts build, and 8.4 coverage comes from the other legs.
660+ include :
661+ - operating-system : " macos-latest"
662+ php-version : " 8.5"
663+ ts : " nts"
664+ script : " make tests"
665+ - operating-system : " macos-latest"
666+ php-version : " 8.5"
667+ ts : " nts"
668+ script : " make phpstan"
583669
584670 steps :
585671 - name : Harden the runner (Audit all outbound calls)
@@ -603,7 +689,7 @@ jobs:
603689 - name : " Download extension artifact"
604690 uses : actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
605691 with :
606- name : " phpstan_turbo-${{ matrix.operating-system == 'windows-latest' && 'windows-x86_64' || 'linux-gnu-x86_64' }}-php${{ matrix.php-version }}${{ matrix.ts == 'zts' && '-zts' || '' }}"
692+ name : " phpstan_turbo-${{ matrix.operating-system == 'windows-latest' && 'windows-x86_64' || matrix.operating-system == 'macos-latest' && 'macos' || 'linux-gnu-x86_64' }}-php${{ matrix.php-version }}${{ matrix.ts == 'zts' && '-zts' || '' }}"
607693 path : " turbo-ext"
608694
609695 - name : " Install extension"
@@ -641,11 +727,12 @@ jobs:
641727 '
642728
643729 - name : " Run"
644- # The memo's per-worker footprint grows with files-per-worker; a
645- # future few-core leg (macOS runners) needs 1G where these many-worker
646- # runners stay under the default 450M.
730+ # The memo's per-worker footprint grows with files-per-worker; macOS
731+ # runners have few cores, so each of their few workers analyses enough
732+ # files to tip the default 450M limit that a many-worker runner
733+ # stays under.
647734 env :
648- PHPSTAN_MEMORY_LIMIT : " 450M"
735+ PHPSTAN_MEMORY_LIMIT : ${{ matrix.operating-system == 'macos-latest' && '1G' || ' 450M' }}
649736 # The Windows runner image ships GNU make only inside its MSYS2
650737 # install, which is not on PATH.
651738 run : |
@@ -836,6 +923,7 @@ jobs:
836923 - turbo-version
837924 - turbo-compile
838925 - turbo-compile-windows
926+ - turbo-macos-universal
839927 runs-on : " ubuntu-latest"
840928 timeout-minutes : 60
841929 steps :
0 commit comments