Skip to content

Commit dbab512

Browse files
ondrejmirtesclaude
andcommitted
Build the macOS turbo extension for arm64 only
setup-php no longer supports Intel macOS runners, so the x86_64 slice of the universal binary cannot be built any more. Drop the macos-15-intel compile leg and the turbo-macos-universal job that merged the two slices with lipo; the macos-arm64 leg now uploads a phpstan_turbo-* artifact directly, ad-hoc signed in place, and the dist layout gains a macos-arm64/ directory in place of macos/. TurboExtensionSelector resolves Darwin to that directory only when php_uname('m') reports arm64. An x86_64 PHP - native Intel, or an Intel build running under Rosetta on Apple Silicon, which reports x86_64 as well - gets no binary, as it could not load an arm64 one anyway. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PNSsTrrmeGmkCDA4VR72tt
1 parent e9d6cdd commit dbab512

4 files changed

Lines changed: 27 additions & 80 deletions

File tree

.github/workflows/phar.yml

Lines changed: 20 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -373,18 +373,14 @@ jobs:
373373
family: "zts"
374374
artifact: "phpstan_turbo"
375375
suffix: "-zts"
376-
# The macOS slices are merged into one universal binary (and ad-hoc
377-
# signed) by the turbo-macos-universal job below, so their artifacts
378-
# carry the turbo-slice prefix that the phpstan_turbo-* download
379-
# patterns in the aggregate and commit jobs do not match.
380-
- name: "macos-x86_64"
381-
runs-on: "macos-15-intel" # the last x86_64 macOS image, available until August 2027
382-
family: "macos"
383-
artifact: "turbo-slice"
376+
# arm64 (Apple Silicon) only: setup-php dropped Intel macOS. An
377+
# x86_64 PHP — native Intel, or Rosetta on Apple Silicon, which
378+
# reports x86_64 too — finds no binary: TurboExtensionSelector
379+
# resolves Darwin to macos-arm64 only when php_uname('m') says arm64.
384380
- name: "macos-arm64"
385381
runs-on: "macos-latest"
386382
family: "macos"
387-
artifact: "turbo-slice"
383+
artifact: "phpstan_turbo"
388384

389385
steps:
390386
- name: Harden the runner (Audit all outbound calls)
@@ -461,6 +457,19 @@ jobs:
461457
run: |
462458
make WARN_FLAGS="-Wall -Wextra -Werror -Wno-assume -Wno-unused-parameter -Wno-unicode" -j"$(getconf _NPROCESSORS_ONLN 2>/dev/null || nproc)"
463459
460+
# On arm64 AMFI refuses to map unsigned code, so the binary must carry
461+
# at least an ad-hoc signature. The linker applies one itself, but
462+
# re-sign deliberately instead of relying on that. Quarantine (and thus
463+
# Gatekeeper/notarization) only applies to browser downloads —
464+
# composer/git/curl installs never see it — so an ad-hoc signature is
465+
# enough and needs no secrets.
466+
- name: "Ad-hoc sign the extension (macOS)"
467+
if: matrix.target.family == 'macos'
468+
working-directory: "turbo-ext"
469+
run: |
470+
codesign --force --sign - phpstan_turbo.so
471+
codesign --verify --verbose=2 phpstan_turbo.so
472+
464473
- name: "Verify the built extension reports the expected version"
465474
run: |
466475
REPORTED="$(php -d extension="$PWD/turbo-ext/phpstan_turbo.so" -r 'echo phpversion("phpstan_turbo");')"
@@ -768,67 +777,6 @@ jobs:
768777
SCRIPT: ${{ matrix.script }}
769778
run: docker run --rm --shm-size=1g -v "$PWD:/work" -v "$RUNNER_TEMP/turbo-docker.ini:/usr/local/etc/php/conf.d/zz-turbo.ini" -w /work "$IMAGE" bash -c "$SCRIPT"
770779

771-
turbo-macos-universal:
772-
name: "Turbo macOS Universal Binary"
773-
needs: "turbo-compile"
774-
runs-on: "macos-latest"
775-
timeout-minutes: 30
776-
777-
strategy:
778-
fail-fast: false
779-
matrix:
780-
php-version: ["8.3", "8.4", "8.5"]
781-
782-
steps:
783-
- name: Harden the runner (Audit all outbound calls)
784-
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
785-
with:
786-
egress-policy: audit
787-
788-
- name: "Download x86_64 slice"
789-
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
790-
with:
791-
name: "turbo-slice-macos-x86_64-php${{ matrix.php-version }}"
792-
path: "slice-x86_64"
793-
794-
- name: "Download arm64 slice"
795-
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
796-
with:
797-
name: "turbo-slice-macos-arm64-php${{ matrix.php-version }}"
798-
path: "slice-arm64"
799-
800-
- name: "Merge the slices into a universal binary"
801-
run: |
802-
lipo -create slice-x86_64/phpstan_turbo.so slice-arm64/phpstan_turbo.so -output phpstan_turbo.so
803-
lipo -info phpstan_turbo.so
804-
805-
# On arm64 AMFI refuses to map unsigned code, so the binary must carry
806-
# at least an ad-hoc signature. lipo preserves the linker's per-slice
807-
# ad-hoc signatures, but re-sign deliberately instead of relying on
808-
# that. Quarantine (and thus Gatekeeper/notarization) only applies to
809-
# browser downloads — composer/git/curl installs never see it — so an
810-
# ad-hoc signature is enough and needs no secrets.
811-
- name: "Ad-hoc sign the universal binary"
812-
run: |
813-
codesign --force --sign - phpstan_turbo.so
814-
codesign --verify --verbose=2 phpstan_turbo.so
815-
816-
- name: "Install PHP"
817-
uses: "shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240" # v2.37.2
818-
with:
819-
coverage: "none"
820-
php-version: "${{ matrix.php-version }}"
821-
822-
- name: "Verify the universal binary loads"
823-
run: php -d extension="$PWD/phpstan_turbo.so" -m | grep phpstan_turbo
824-
825-
- name: "Upload universal binary artifact"
826-
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
827-
with:
828-
name: "phpstan_turbo-macos-php${{ matrix.php-version }}"
829-
path: "phpstan_turbo.so"
830-
if-no-files-found: "error"
831-
832780
turbo-compile-windows:
833781
name: "Compile Turbo Extension (Windows)"
834782
# windows-latest and windows-2025 serve the VS2026 image, whose 14.5x
@@ -982,7 +930,6 @@ jobs:
982930
- turbo-compile
983931
- turbo-compile-musl-arm64
984932
- turbo-compile-windows
985-
- turbo-macos-universal
986933
runs-on: "ubuntu-latest"
987934
timeout-minutes: 5
988935

@@ -1027,7 +974,6 @@ jobs:
1027974
needs:
1028975
- turbo-compile
1029976
- turbo-compile-windows
1030-
- turbo-macos-universal
1031977
runs-on: ${{ matrix.operating-system }}
1032978
timeout-minutes: 60
1033979

@@ -1046,7 +992,7 @@ jobs:
1046992
php-version: ["8.3", "8.4", "8.5"]
1047993
ts: ["nts", "zts"]
1048994
script: ["make tests", "make phpstan"]
1049-
# macOS runs the shipped ad-hoc-signed universal binary; there is no
995+
# macOS runs the shipped ad-hoc-signed arm64 binary; there is no
1050996
# macOS zts build, and 8.3/8.4 coverage comes from the other legs.
1051997
include:
1052998
- operating-system: "macos-latest"
@@ -1080,7 +1026,7 @@ jobs:
10801026
- name: "Download extension artifact"
10811027
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
10821028
with:
1083-
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' || '' }}"
1029+
name: "phpstan_turbo-${{ matrix.operating-system == 'windows-latest' && 'windows-x86_64' || matrix.operating-system == 'macos-latest' && 'macos-arm64' || 'linux-gnu-x86_64' }}-php${{ matrix.php-version }}${{ matrix.ts == 'zts' && '-zts' || '' }}"
10841030
path: "turbo-ext"
10851031

10861032
- name: "Install extension"
@@ -1539,7 +1485,6 @@ jobs:
15391485
- turbo-compile
15401486
- turbo-compile-musl-arm64
15411487
- turbo-compile-windows
1542-
- turbo-macos-universal
15431488
runs-on: "ubuntu-latest"
15441489
timeout-minutes: 60
15451490
steps:

src/Turbo/TurboExtensionSelector.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,10 @@ public static function findExtension(): ?string
109109
public static function resolvePlatformDirectory(string $osFamily, string $machine, bool $isMusl): ?string
110110
{
111111
if ($osFamily === 'Darwin') {
112-
// one universal binary covers x86_64 and arm64
113-
return 'macos';
112+
// arm64 (Apple Silicon) only - there is no Intel build. An x86_64
113+
// PHP under Rosetta reports x86_64 here as well, and cannot load
114+
// the arm64 binary either.
115+
return $machine === 'arm64' ? 'macos-arm64' : null;
114116
}
115117
if ($osFamily === 'Windows') {
116118
return $machine === 'AMD64' || $machine === 'x86_64' ? 'windows-x86_64' : null;

tests/PHPStan/Turbo/TurboExtensionSelectorTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ final class TurboExtensionSelectorTest extends PHPStanTestCase
1313
*/
1414
public static function dataResolvePlatformDirectory(): iterable
1515
{
16-
yield ['Darwin', 'arm64', false, 'macos'];
17-
yield ['Darwin', 'x86_64', false, 'macos'];
16+
yield ['Darwin', 'arm64', false, 'macos-arm64'];
17+
yield ['Darwin', 'x86_64', false, null];
1818
yield ['Linux', 'x86_64', false, 'linux-gnu-x86_64'];
1919
yield ['Linux', 'aarch64', false, 'linux-gnu-arm64'];
2020
yield ['Linux', 'arm64', false, 'linux-gnu-arm64'];

turbo-ext/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ self-analysis).
1414
**Most users do not need to install anything.** The
1515
[phpstan/phpstan](https://github.com/phpstan/phpstan) Composer package ships
1616
prebuilt binaries for the most common platforms — Linux (glibc and musl,
17-
x86_64 and arm64), macOS, and Windows (x86_64), for PHP 8.3 and newer — and
17+
x86_64 and arm64), macOS (arm64), and Windows (x86_64), for PHP 8.3 and newer — and
1818
PHPStan automatically loads the one matching your runtime into its worker
1919
processes.
2020

0 commit comments

Comments
 (0)