Skip to content

Build static macOS PHP CLI binaries#3297

Merged
bcotrim merged 28 commits intotrunkfrom
rsm-1650-codex-build-static-php-cli-binaries
May 7, 2026
Merged

Build static macOS PHP CLI binaries#3297
bcotrim merged 28 commits intotrunkfrom
rsm-1650-codex-build-static-php-cli-binaries

Conversation

@bcotrim
Copy link
Copy Markdown
Contributor

@bcotrim bcotrim commented Apr 30, 2026

Related issues

  • Related to RSM-1650

How AI was used in this PR

AI helped compare the Buildkite and GitHub Actions approaches, simplify the static-php-cli build, and validate the generated artifacts. I reviewed the changes and tested the resulting binaries locally.

Proposed Changes

  • Add a manual Build PHP CLI Binaries GitHub Actions workflow.
  • Build PHP CLI binaries with upstream static-php-cli instead of custom Buildkite scripts.
  • Default to PHP 8.4.20 and static-php-cli 2.8.5, with workflow inputs to override them.
  • Build the currently working targets: macos-aarch64, macos-x86_64, and windows-x86_64.
  • Upload packaged archives plus .sha256 sidecars as GitHub Actions artifacts.
  • Leave Apps CDN upload and manifest updates for a follow-up PR.

Testing Instructions

  • Confirm the macos-aarch64, macos-x86_64, and windows-x86_64 jobs pass.
  • Download the artifacts and verify the inner archive against its .sha256 file.
  • Extract the archive and confirm php -v reports PHP 8.4.20.
  • Move the archive to .studio folder - ~/.studio/php-bin/8.4 for example
  • npm run cli:build
  • STUDIO_RUNTIME=native-php node apps/cli/dist/cli/main.mjs site create
  • Confirm the WordPress site is running

Pre-merge Checklist

  • Have you checked for TypeScript, React or other console errors?

@bcotrim bcotrim force-pushed the rsm-1650-codex-build-static-php-cli-binaries branch from dcf22ae to 02bc232 Compare May 4, 2026 15:39
Copy link
Copy Markdown
Contributor

@fredrikekelund fredrikekelund left a comment

Choose a reason for hiding this comment

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

I know this isn't ready yet, but I left a few comments with preliminary observations

Comment thread scripts/build-php-cli-macos.sh Outdated
Comment on lines +135 to +150
php bin/spc download --with-php="$PHP_MINOR" --for-extensions="$EXTENSIONS" --retry=2

BUILD_ROOT="$SPC_DIR/buildroot-arm64"
SOURCE_PATH="$SPC_DIR/source-arm64"
PKG_ROOT="$SPC_DIR/pkgroot/aarch64-darwin"
SPC_ENV=(
"BUILD_ROOT_PATH=$BUILD_ROOT"
"SOURCE_PATH=$SOURCE_PATH"
"PKG_ROOT_PATH=$PKG_ROOT"
)

rm -rf "$BUILD_ROOT" "$SOURCE_PATH"

env "${SPC_ENV[@]}" php bin/spc install-pkg pkg-config
env "${SPC_ENV[@]}" php bin/spc doctor --auto-fix=never
env "${SPC_ENV[@]}" php bin/spc build "$EXTENSIONS" --build-cli --with-suggested-libs
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The spc craft command, with its more declarative approach, seems like an appealing option. AFAICT, it wraps spc download, spc install-pkg, and spc build into a single command.

Comment thread scripts/build-php-cli-macos.sh Outdated
Comment on lines +50 to +133
python3 - "$SPC_DIR" <<'PY'
from pathlib import Path
import sys

root = Path(sys.argv[1])

def replace_once(path, old, new):
file = root / path
text = file.read_text()
if new in text:
return
if old not in text:
raise SystemExit(f"Could not patch {path}")
file.write_text(text.replace(old, new, 1))

def ensure_patch(path, marker, replacements):
file = root / path
text = file.read_text()
if marker in text:
return
for old, new in replacements:
if old in text:
file.write_text(text.replace(old, new, 1))
return
raise SystemExit(f"Could not patch {path}")

replace_once(
"src/SPC/util/executor/UnixCMakeExecutor.php",
""" $target_arch = arch2gnu(php_uname('m'));
$cflags = getenv('SPC_DEFAULT_C_FLAGS');
""",
""" $target_arch = arch2gnu(php_uname('m'));
$target_mac_arch = match ($target_arch) {
'aarch64' => 'arm64',
default => $target_arch,
};
$cflags = getenv('SPC_DEFAULT_C_FLAGS');
""",
)
ensure_patch(
"src/SPC/util/executor/UnixCMakeExecutor.php",
'CMAKE_OSX_ARCHITECTURES \\"{$target_mac_arch}\\"',
[
(
' $toolchain .= "\\nset(CMAKE_OSX_ARCHITECTURES \\"{$target_arch}\\" CACHE STRING \\"\\" FORCE)";',
' $toolchain .= "\\nset(CMAKE_OSX_ARCHITECTURES \\"{$target_mac_arch}\\" CACHE STRING \\"\\" FORCE)";',
),
(
"""CMAKE;
// Whoops, linux may need CMAKE_AR sometimes
""",
"""CMAKE;
if (PHP_OS_FAMILY === 'Darwin') {
$toolchain .= "\\nset(CMAKE_OSX_ARCHITECTURES \\"{$target_mac_arch}\\" CACHE STRING \\"\\" FORCE)";
}
// Whoops, linux may need CMAKE_AR sometimes
""",
),
],
)
ensure_patch(
"src/SPC/util/SPCConfigUtil.php",
"$libs = str_replace('-lstdc++', '', $libs);",
[
(
""" if ($this->hasCpp($extensions, $libraries)) {
$libcpp = SPCTarget::getTargetOS() === 'Darwin' ? '-lc++' : '-lstdc++';
$libs = str_replace($libcpp, '', $libs) . " {$libcpp}";
}
""",
""" if ($this->hasCpp($extensions, $libraries)) {
$isDarwin = SPCTarget::getTargetOS() === 'Darwin';
$libcpp = $isDarwin ? '-lc++' : '-lstdc++';
$libs = str_replace($libcpp, '', $libs);
if ($isDarwin) {
$libs = str_replace('-lstdc++', '', $libs);
}
$libs .= " {$libcpp}";
}
""",
),
],
)
PY
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Two things:

  1. Can we add some comments to explain what's going on here?
  2. Why Python?

Comment thread scripts/build-php-cli-macos.sh Outdated
Comment on lines +159 to +182
file "$PHP_BIN"
"$PHP_BIN" --version | grep -q "PHP $PHP_VERSION "

modules="$("$PHP_BIN" -m)"
expected_modules=(
ctype curl dom exif fileinfo filter gd iconv imagick intl mbstring mysqli mysqlnd
"Zend OPcache" openssl PDO pdo_sqlite Phar session SimpleXML sodium sqlite3
tokenizer xml xmlreader xmlwriter zip zlib
)
excluded_modules=(redis apcu bcmath pdo_mysql pgsql imap soap sockets ftp)

for module in "${expected_modules[@]}"; do
if ! grep -Fxq "$module" <<<"$modules"; then
echo "Expected PHP module missing: $module" >&2
exit 1
fi
done

for module in "${excluded_modules[@]}"; do
if grep -Fxq "$module" <<<"$modules"; then
echo "Unexpected PHP module present: $module" >&2
exit 1
fi
done
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not bad, but strikes me as somewhat redundant…

Comment thread scripts/build-php-cli-macos.sh Outdated
PHP_MINOR="${PHP_MINOR:-"${PHP_VERSION%.*}"}"
OUTPUT_DIR="${OUTPUT_DIR:-"$ROOT_DIR/out/php-binaries"}"
ARTIFACT_BASENAME="php-${PHP_VERSION}-cli-macos-aarch64"
EXTENSIONS="ctype,curl,dom,exif,fileinfo,filter,gd,iconv,imagick,intl,mbregex,mbstring,mysqli,mysqlnd,opcache,openssl,pdo,pdo_sqlite,phar,session,simplexml,sodium,sqlite3,tokenizer,xml,xmlreader,xmlwriter,zip,zlib"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
EXTENSIONS="ctype,curl,dom,exif,fileinfo,filter,gd,iconv,imagick,intl,mbregex,mbstring,mysqli,mysqlnd,opcache,openssl,pdo,pdo_sqlite,phar,session,simplexml,sodium,sqlite3,tokenizer,xml,xmlreader,xmlwriter,zip,zlib"
EXTENSIONS="apcu,bcmath,calendar,ctype,curl,dba,dom,exif,fileinfo,filter,ftp,gd,gettext,iconv,igbinary,imagick,intl,mbregex,mbstring,mysqli,mysqlnd,opcache,openssl,pcntl,pdo,pdo_mysql,pdo_sqlite,phar,posix,readline,redis,session,shmop,simplexml,sockets,sodium,sqlite3,ssh2,tokenizer,xml,xmlreader,xmlwriter,xsl,yaml,zip,zlib"

Adds the following extensions: apcu, bcmath, calendar, dba, ftp, gettext, igbinary, pcntl, pdo_mysql, posix, readline, redis, shmop, sockets, ssh2, xsl, yaml.

This is quite a generous list, but I don't think that hurts. It's based on the official WordPress recommendation and static-php's "common extensions" list.

Moreover, per my other comment, it'd be nice to use the declarative config file craft.yml for clarity. I assume it also helps with cross-platform portability.

@bcotrim bcotrim marked this pull request as ready for review May 5, 2026 18:45
@bcotrim bcotrim requested a review from fredrikekelund May 5, 2026 18:45
@wpmobilebot
Copy link
Copy Markdown
Collaborator

wpmobilebot commented May 5, 2026

📊 Performance Test Results

Comparing 637658f vs trunk

app-size

Metric trunk 637658f Diff Change
App Size (Mac) 1404.87 MB 1454.05 MB +49.18 MB 🔴 3.5%

site-editor

Metric trunk 637658f Diff Change
load 1501 ms 1504 ms +3 ms ⚪ 0.0%

site-startup

Metric trunk 637658f Diff Change
siteCreation 8087 ms 8079 ms 8 ms ⚪ 0.0%
siteStartup 4936 ms 4949 ms +13 ms ⚪ 0.0%

Results are median values from multiple test runs.

Legend: 🟢 Improvement (faster) | 🔴 Regression (slower) | ⚪ No change (<50ms diff)

Copy link
Copy Markdown
Contributor

@fredrikekelund fredrikekelund left a comment

Choose a reason for hiding this comment

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

The build config looks fine, but I get the following warning when I try running the macOS arm64 binary:

Screenshot 2026-05-06 at 15 06 40

That smells like a code signing issue to me

artifact_arch: x86_64
archive_extension: zip
binary_name: php.exe
extensions: apcu,bcmath,calendar,ctype,curl,dba,dom,exif,fileinfo,filter,ftp,gd,iconv,igbinary,mbregex,mbstring,mysqli,mysqlnd,opcache,openssl,pdo,pdo_mysql,pdo_sqlite,phar,redis,session,shmop,simplexml,sockets,sqlite3,ssh2,tokenizer,xml,xmlreader,xmlwriter,yaml,zip,zlib
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No gettext, intl, sodium, or xsl on Windows?

Comment on lines +49 to +56
extensions: apcu,bcmath,calendar,ctype,curl,dba,dom,exif,fileinfo,filter,ftp,gd,gettext,iconv,igbinary,imagick,intl,mbregex,mbstring,mysqli,mysqlnd,opcache,openssl,pcntl,pdo,pdo_mysql,pdo_sqlite,phar,posix,readline,redis,session,shmop,simplexml,sockets,sodium,sqlite3,ssh2,tokenizer,xml,xmlreader,xmlwriter,xsl,yaml,zip,zlib
- target: macos-x86_64
runner: macos-15-intel
artifact_platform: macos
artifact_arch: x86_64
archive_extension: tar.gz
binary_name: php
extensions: apcu,bcmath,calendar,ctype,curl,dba,dom,exif,fileinfo,filter,ftp,gd,gettext,iconv,igbinary,imagick,intl,mbregex,mbstring,mysqli,mysqlnd,opcache,openssl,pcntl,pdo,pdo_mysql,pdo_sqlite,phar,posix,readline,redis,session,shmop,simplexml,sockets,sodium,sqlite3,ssh2,tokenizer,xml,xmlreader,xmlwriter,xsl,yaml,zip,zlib
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
extensions: apcu,bcmath,calendar,ctype,curl,dba,dom,exif,fileinfo,filter,ftp,gd,gettext,iconv,igbinary,imagick,intl,mbregex,mbstring,mysqli,mysqlnd,opcache,openssl,pcntl,pdo,pdo_mysql,pdo_sqlite,phar,posix,readline,redis,session,shmop,simplexml,sockets,sodium,sqlite3,ssh2,tokenizer,xml,xmlreader,xmlwriter,xsl,yaml,zip,zlib
- target: macos-x86_64
runner: macos-15-intel
artifact_platform: macos
artifact_arch: x86_64
archive_extension: tar.gz
binary_name: php
extensions: apcu,bcmath,calendar,ctype,curl,dba,dom,exif,fileinfo,filter,ftp,gd,gettext,iconv,igbinary,imagick,intl,mbregex,mbstring,mysqli,mysqlnd,opcache,openssl,pcntl,pdo,pdo_mysql,pdo_sqlite,phar,posix,readline,redis,session,shmop,simplexml,sockets,sodium,sqlite3,ssh2,tokenizer,xml,xmlreader,xmlwriter,xsl,yaml,zip,zlib
extensions: &php_extensions
apcu,bcmath,calendar,ctype,curl,dba,dom,exif,fileinfo,filter,ftp,gd,gettext,iconv,igbinary,imagick,intl,mbregex,mbstring,mysqli,mysqlnd,opcache,openssl,pcntl,pdo,pdo_mysql,pdo_sqlite,phar,posix,readline,redis,session,shmop,simplexml,sockets,sodium,sqlite3,ssh2,tokenizer,xml,xmlreader,xmlwriter,xsl,yaml,zip,zlib
- target: macos-x86_64
runner: macos-15-intel
artifact_platform: macos
artifact_arch: x86_64
archive_extension: tar.gz
binary_name: php
extensions: *php_extensions

We could explicitly reuse the extensions definition for both macOS architectures like so.


steps:
- name: Checkout Studio
uses: actions/checkout@v4
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
uses: actions/checkout@v4
uses: actions/checkout@v6

I'm not GitHub Actions wiz, but v6 is the latest version. Seems like we could use that.

uses: actions/checkout@v4

- name: Checkout static-php-cli
uses: actions/checkout@v4
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
uses: actions/checkout@v4
uses: actions/checkout@v6

chmod +x "$RUNNER_TEMP/php"
xattr -cr "$RUNNER_TEMP/php"
tar -czf "$out_dir/$artifact" -C "$RUNNER_TEMP" php
shasum -a 256 "$out_dir/$artifact" | awk '{print $1}' > "$out_dir/$artifact.sha256"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If we build and distribute the binaries ourselves, what do we do with the checksums?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

When we upload the binaries to the CDN and update the binary metadata, we can store the checksum alongside each CDN URL. It’s a good safety measure to verify the archive after download and before extraction.

Comment thread docs/design-docs/native-php-binaries.md Outdated
Comment on lines +3 to +6
Studio currently downloads native PHP binaries on demand from the upstream
static-php-cli CDN. Custom Studio-built binaries are not bundled in the repo or
uploaded by PR CI.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I say we can drop this part and just focus on how it'll work once this PR lands.

Comment thread docs/design-docs/native-php-binaries.md Outdated
Comment on lines +21 to +23
Do not patch `static-php-cli` by default. If the upstream build fails, use the
workflow logs to add the smallest targeted patch and document the exact upstream
failure that requires it.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This feel like it's written for AI. Why did we have to patch it before?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It was one of the attempts to build Mac Intel, but it no longer applies.

@fredrikekelund
Copy link
Copy Markdown
Contributor

Looking good! We're still missing xattr -d com.apple.quarantine here, though, right?

@bcotrim bcotrim merged commit 6d3e41a into trunk May 7, 2026
12 checks passed
@bcotrim bcotrim deleted the rsm-1650-codex-build-static-php-cli-binaries branch May 7, 2026 09:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants