Skip to content

Commit 02bc232

Browse files
committed
Add macOS PHP CLI build CI
1 parent 828d74b commit 02bc232

4 files changed

Lines changed: 237 additions & 0 deletions

File tree

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#!/usr/bin/env bash
2+
3+
set -euo pipefail
4+
5+
WATCHED_FILES=(
6+
"scripts/build-php-cli-macos.sh"
7+
".buildkite/commands/run-php-cli-macos-arm64-build.sh"
8+
)
9+
10+
changed_files() {
11+
if [[ "${BUILDKITE_PULL_REQUEST:-false}" != "false" && -n "${BUILDKITE_PULL_REQUEST_BASE_BRANCH:-}" ]]; then
12+
local base_branch="$BUILDKITE_PULL_REQUEST_BASE_BRANCH"
13+
git fetch --quiet origin "$base_branch:refs/remotes/origin/$base_branch"
14+
git diff --name-only "origin/$base_branch"...HEAD
15+
return
16+
fi
17+
18+
git diff --name-only HEAD^ HEAD
19+
}
20+
21+
should_run=false
22+
while IFS= read -r file; do
23+
for watched_file in "${WATCHED_FILES[@]}"; do
24+
if [[ "$file" == "$watched_file" ]]; then
25+
should_run=true
26+
break 2
27+
fi
28+
done
29+
done < <(changed_files)
30+
31+
if [[ "$should_run" != "true" ]]; then
32+
echo "Skipping PHP CLI macOS arm64 build; watched files did not change."
33+
exit 0
34+
fi
35+
36+
if [[ "$(uname -m)" != "arm64" ]]; then
37+
echo "PHP CLI macOS arm64 build must run on an arm64 macOS agent." >&2
38+
exit 1
39+
fi
40+
41+
bash scripts/build-php-cli-macos.sh

.buildkite/pipeline.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,18 @@ steps:
7171
- github_commit_status:
7272
context: Performance Metrics
7373

74+
- label: ":elephant: PHP CLI macOS arm64 Build"
75+
key: php-cli-macos-arm64
76+
agents:
77+
queue: mac
78+
command: bash .buildkite/commands/run-php-cli-macos-arm64-build.sh
79+
artifact_paths:
80+
- out/php-binaries/*
81+
plugins: [$CI_TOOLKIT_PLUGIN]
82+
notify:
83+
- github_commit_status:
84+
context: PHP CLI macOS arm64 Build
85+
7486
- input: "🚦 Build for Mac?"
7587
prompt: "Do you want to build Mac dev binaries for this PR?"
7688
key: input-dev-mac
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Native PHP Binaries
2+
3+
Studio currently downloads native PHP binaries on demand from the upstream
4+
static-php-cli CDN. Custom Studio-built binaries are not bundled in the repo or
5+
uploaded by PR CI.
6+
7+
Use `scripts/build-php-cli-macos.sh` to build the macOS arm64 PHP 8.4.20 CLI
8+
artifact with the WordPress-recommended extension set plus Studio's SQLite/PDO
9+
runtime requirements. The script writes `php-8.4.20-cli-macos-aarch64.tar.gz`
10+
and its `.sha256` file to `out/php-binaries/`.
11+
12+
Buildkite runs this build only when the PHP build script or its CI wrapper
13+
changes, and uploads the generated files as Buildkite artifacts only.
14+
15+
Regular Studio app builds upload to the Apps CDN through `fastlane/Fastfile`:
16+
`upload_file_to_apps_cdn` wraps `upload_build_to_apps_cdn` from
17+
`fastlane-plugin-wpmreleasetoolkit`. That path requires `WPCOM_API_TOKEN`, the
18+
Studio Apps CDN site ID, build metadata, and a file path. A future PHP binary
19+
upload should be a separate manual Fastlane lane, dry-run by default, and should
20+
not run from PR CI.

scripts/build-php-cli-macos.sh

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
#!/usr/bin/env bash
2+
3+
set -euo pipefail
4+
5+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
6+
SPC_DIR="${SPC_DIR:-"$ROOT_DIR/.cache/static-php-cli"}"
7+
SPC_TAG="${SPC_TAG:-2.8.5}"
8+
PHP_VERSION="${PHP_VERSION:-8.4.20}"
9+
PHP_MINOR="${PHP_MINOR:-"${PHP_VERSION%.*}"}"
10+
OUTPUT_DIR="${OUTPUT_DIR:-"$ROOT_DIR/out/php-binaries"}"
11+
ARTIFACT_BASENAME="php-${PHP_VERSION}-cli-macos-aarch64"
12+
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"
13+
14+
export PATH="/opt/homebrew/opt/bison/bin:$PATH"
15+
16+
if [[ "$(uname -s)" != "Darwin" ]]; then
17+
echo "This script builds macOS binaries and must run on macOS." >&2
18+
exit 1
19+
fi
20+
21+
if [[ "$(uname -m)" != "arm64" ]]; then
22+
echo "This script must run on a macOS arm64 host." >&2
23+
exit 1
24+
fi
25+
26+
for command in git composer php python3 shasum tar file; do
27+
if ! command -v "$command" >/dev/null 2>&1; then
28+
echo "Missing required command: $command" >&2
29+
exit 1
30+
fi
31+
done
32+
33+
if [[ ! -d "$SPC_DIR/.git" ]]; then
34+
git clone --depth 1 --branch "$SPC_TAG" https://github.com/crazywhalecc/static-php-cli.git "$SPC_DIR"
35+
else
36+
git -C "$SPC_DIR" fetch --depth 1 origin "$SPC_TAG"
37+
git -C "$SPC_DIR" checkout --detach FETCH_HEAD
38+
git -C "$SPC_DIR" reset --hard
39+
fi
40+
41+
cd "$SPC_DIR"
42+
composer install --no-dev --no-interaction --prefer-dist
43+
44+
python3 - "$SPC_DIR" <<'PY'
45+
from pathlib import Path
46+
import sys
47+
48+
root = Path(sys.argv[1])
49+
50+
def replace_once(path, old, new):
51+
file = root / path
52+
text = file.read_text()
53+
if new in text:
54+
return
55+
if old not in text:
56+
raise SystemExit(f"Could not patch {path}")
57+
file.write_text(text.replace(old, new, 1))
58+
59+
def ensure_patch(path, marker, replacements):
60+
file = root / path
61+
text = file.read_text()
62+
if marker in text:
63+
return
64+
for old, new in replacements:
65+
if old in text:
66+
file.write_text(text.replace(old, new, 1))
67+
return
68+
raise SystemExit(f"Could not patch {path}")
69+
70+
replace_once(
71+
"src/SPC/util/executor/UnixCMakeExecutor.php",
72+
""" $target_arch = arch2gnu(php_uname('m'));
73+
$cflags = getenv('SPC_DEFAULT_C_FLAGS');
74+
""",
75+
""" $target_arch = arch2gnu(php_uname('m'));
76+
$target_mac_arch = match ($target_arch) {
77+
'aarch64' => 'arm64',
78+
default => $target_arch,
79+
};
80+
$cflags = getenv('SPC_DEFAULT_C_FLAGS');
81+
""",
82+
)
83+
ensure_patch(
84+
"src/SPC/util/executor/UnixCMakeExecutor.php",
85+
'CMAKE_OSX_ARCHITECTURES \\"{$target_mac_arch}\\"',
86+
[
87+
(
88+
' $toolchain .= "\\nset(CMAKE_OSX_ARCHITECTURES \\"{$target_arch}\\" CACHE STRING \\"\\" FORCE)";',
89+
' $toolchain .= "\\nset(CMAKE_OSX_ARCHITECTURES \\"{$target_mac_arch}\\" CACHE STRING \\"\\" FORCE)";',
90+
),
91+
(
92+
"""CMAKE;
93+
// Whoops, linux may need CMAKE_AR sometimes
94+
""",
95+
"""CMAKE;
96+
if (PHP_OS_FAMILY === 'Darwin') {
97+
$toolchain .= "\\nset(CMAKE_OSX_ARCHITECTURES \\"{$target_mac_arch}\\" CACHE STRING \\"\\" FORCE)";
98+
}
99+
// Whoops, linux may need CMAKE_AR sometimes
100+
""",
101+
),
102+
],
103+
)
104+
PY
105+
106+
php bin/spc download --with-php="$PHP_MINOR" --for-extensions="$EXTENSIONS" --retry=2
107+
108+
BUILD_ROOT="$SPC_DIR/buildroot-arm64"
109+
SOURCE_PATH="$SPC_DIR/source-arm64"
110+
PKG_ROOT="$SPC_DIR/pkgroot/aarch64-darwin"
111+
112+
rm -rf "$BUILD_ROOT" "$SOURCE_PATH"
113+
114+
BUILD_ROOT_PATH="$BUILD_ROOT" SOURCE_PATH="$SOURCE_PATH" PKG_ROOT_PATH="$PKG_ROOT" \
115+
php bin/spc doctor
116+
BUILD_ROOT_PATH="$BUILD_ROOT" SOURCE_PATH="$SOURCE_PATH" PKG_ROOT_PATH="$PKG_ROOT" \
117+
php bin/spc build "$EXTENSIONS" --build-cli --with-suggested-libs
118+
119+
PHP_BIN="$BUILD_ROOT/bin/php"
120+
121+
if [[ ! -x "$PHP_BIN" ]]; then
122+
echo "PHP binary was not built at $PHP_BIN" >&2
123+
exit 1
124+
fi
125+
126+
file "$PHP_BIN"
127+
"$PHP_BIN" --version | grep -q "PHP $PHP_VERSION "
128+
129+
modules="$("$PHP_BIN" -m)"
130+
expected_modules=(
131+
ctype curl dom exif fileinfo filter gd iconv imagick intl mbstring mysqli mysqlnd
132+
"Zend OPcache" openssl PDO pdo_sqlite Phar session SimpleXML sodium sqlite3
133+
tokenizer xml xmlreader xmlwriter zip zlib
134+
)
135+
excluded_modules=(redis apcu bcmath pdo_mysql pgsql imap soap sockets ftp)
136+
137+
for module in "${expected_modules[@]}"; do
138+
if ! grep -Fxq "$module" <<<"$modules"; then
139+
echo "Expected PHP module missing: $module" >&2
140+
exit 1
141+
fi
142+
done
143+
144+
for module in "${excluded_modules[@]}"; do
145+
if grep -Fxq "$module" <<<"$modules"; then
146+
echo "Unexpected PHP module present: $module" >&2
147+
exit 1
148+
fi
149+
done
150+
151+
mkdir -p "$OUTPUT_DIR"
152+
artifact_path="$OUTPUT_DIR/$ARTIFACT_BASENAME.tar.gz"
153+
hash_path="$artifact_path.sha256"
154+
package_dir="$(mktemp -d)"
155+
trap 'rm -rf "$package_dir"' EXIT
156+
157+
cp "$PHP_BIN" "$package_dir/php"
158+
chmod 755 "$package_dir/php"
159+
rm -f "$artifact_path" "$hash_path"
160+
tar -czf "$artifact_path" -C "$package_dir" php
161+
shasum -a 256 "$artifact_path" | awk '{print $1}' > "$hash_path"
162+
163+
echo "Created $artifact_path"
164+
echo "Created $hash_path"

0 commit comments

Comments
 (0)