-
Notifications
You must be signed in to change notification settings - Fork 0
731 lines (671 loc) · 34.7 KB
/
Copy pathbuild.yml
File metadata and controls
731 lines (671 loc) · 34.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
name: Build PHP SDK
run-name: "Build PHP ${{ inputs.php_version }}"
on:
workflow_dispatch:
inputs:
php_version:
description: "PHP version to build (e.g. 8.5.2, 8.4.7, 8.3.20)"
required: true
type: string
platform:
description: "Which platform(s) to build. Partial builds upload the
new tarball to the existing release; tarballs for unbuilt
platforms stay put."
required: false
type: choice
default: all
options:
- all
- linux-x86_64
- linux-aarch64
- macos-aarch64
- windows-x86_64
env:
SPC_VERSION: "v3.0.0-pgo-rc15"
# Linux + macOS extension set (sorted, including Unix-only IPC + process
# control extensions). Windows uses WIN_PHP_EXTENSIONS — keep the two
# in sync EXCEPT for the Unix-only entries (pcntl, posix, sysv*).
#
# NOTE on mbregex: spc builds mbstring with --disable-mbregex UNLESS the
# separate `mbregex` extension is also requested (it pulls in oniguruma).
# Without it, mb_split()/mb_ereg*() are undefined — which breaks Laravel's
# Str::studly() and any framework using the mbstring regex functions. Keep
# `mbregex` in BOTH lists alongside `mbstring`.
PHP_EXTENSIONS: "bcmath,bz2,calendar,ctype,curl,dom,exif,fileinfo,filter,ftp,gd,gettext,gmp,hash,iconv,intl,mbregex,mbstring,mysqli,mysqlnd,opcache,openssl,pcntl,pcre,pdo,pdo_mysql,pdo_pgsql,pdo_sqlite,pgsql,phar,posix,session,simplexml,soap,sodium,sqlite3,sysvmsg,sysvsem,sysvshm,tokenizer,xml,xmlreader,xmlwriter,xsl,zip,zlib"
jobs:
# Compute which jobs / matrix entries actually run based on the `platform`
# input. Each build job below reads from these outputs so we can target a
# single OS/arch (or all of them) from one workflow_dispatch invocation.
# Empty matrices => the job's matrix expands to zero runs and is skipped.
setup:
name: Setup (resolve target platforms)
runs-on: ubuntu-latest
outputs:
linux-matrix: ${{ steps.compute.outputs.linux-matrix }}
build-macos: ${{ steps.compute.outputs.build-macos }}
build-windows: ${{ steps.compute.outputs.build-windows }}
steps:
- id: compute
env:
PLATFORM: ${{ inputs.platform }}
run: |
linux_all='[{"arch":"x86_64","runner":["self-hosted","linux","x64"],"spc_arch":"x86_64"},{"arch":"aarch64","runner":["self-hosted","linux","arm64"],"spc_arch":"aarch64"}]'
linux_x64='[{"arch":"x86_64","runner":["self-hosted","linux","x64"],"spc_arch":"x86_64"}]'
linux_arm='[{"arch":"aarch64","runner":["self-hosted","linux","arm64"],"spc_arch":"aarch64"}]'
case "$PLATFORM" in
all) lm=$linux_all; mac=true; win=true ;;
linux-x86_64) lm=$linux_x64; mac=false; win=false ;;
linux-aarch64) lm=$linux_arm; mac=false; win=false ;;
macos-aarch64) lm='[]'; mac=true; win=false ;;
windows-x86_64) lm='[]'; mac=false; win=true ;;
*) echo "::error::unknown platform $PLATFORM"; exit 1 ;;
esac
echo "linux-matrix=$lm" >> "$GITHUB_OUTPUT"
echo "build-macos=$mac" >> "$GITHUB_OUTPUT"
echo "build-windows=$win" >> "$GITHUB_OUTPUT"
build-linux:
name: Linux (${{ matrix.arch }})
needs: setup
if: needs.setup.outputs.linux-matrix != '[]'
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include: ${{ fromJSON(needs.setup.outputs.linux-matrix) }}
steps:
- name: Clean up previous builds
run: rm -rf output sdk *.tar.gz
- name: Build inside Alpine container
run: |
docker run --name spc-build \
-e GITHUB_TOKEN="${GITHUB_TOKEN}" \
-e SPC_VERSION="${{ env.SPC_VERSION }}" \
-e PHP_VERSION="${{ inputs.php_version }}" \
-e PHP_EXTENSIONS="${{ env.PHP_EXTENSIONS }}" \
docker.io/library/alpine:3.20 sh -c '
set -eux
apk add --no-cache \
bash curl git build-base autoconf automake bison cmake flex \
libtool make pkgconf re2c linux-headers tar xz
# Use native gcc instead of zig-cc - Alpine already has musl
export SPC_TOOLCHAIN="StaticPHP\\Toolchain\\GccNativeToolchain"
mkdir -p /usr/local/bin
curl -fsSL "https://github.com/luthermonson/static-php-cli/releases/download/${SPC_VERSION}/spc-linux-$(uname -m).tar.gz" \
| tar xz -C /usr/local/bin
mkdir /build && cd /build
# Retry spc download to tolerate transient upstream flakes
# (gnu.org libiconv mirror, pecl.php.net, GitHub API rate
# limits). Up to 6 attempts with 2-3s jitter between (~15s).
i=0
until spc download \
--with-php=${PHP_VERSION} \
--for-extensions=${PHP_EXTENSIONS} \
--prefer-pre-built; do
i=$((i + 1))
if [ "$i" -ge 6 ]; then
echo "spc download failed after $i attempts" >&2
exit 1
fi
s=$(awk "BEGIN{srand(); print 2+int(rand()*2)}")
echo "spc download flaked (attempt $i), retrying in ${s}s..."
sleep "$s"
done
spc doctor --auto-fix || true
# --dl-with-php pins the build-phase artifact downloader to the
# requested version. Without it, spc build re-checks php-src
# against the dl-with-php DEFAULT (8.5), treats the cached
# 8.4.x/8.3.x source as a mismatch, silently re-downloads
# latest 8.5.x, and builds the wrong PHP (shipped mislabeled
# tarballs until the version guard below caught it).
# NOTE: this comment lives inside a single-quoted sh -c
# block; apostrophes here would terminate the string.
spc build ${PHP_EXTENSIONS} \
--dl-with-php=${PHP_VERSION} \
--build-embed \
--enable-zts \
--no-strip \
--debug
# Stage artifacts inside the container before it stops.
# spc nests install output under buildroot/lib at varying
# depths (lib/lib/, sometimes deeper). Find .a files and the
# php header dir wherever they landed and copy them into a
# flat layout. cp -L dereferences symlinks
# (e.g. libcurses.a → libncurses.a).
mkdir -p /output/lib /output/include
find /build/buildroot/lib -name "*.a" -type f -exec cp -Lf {} /output/lib/ \;
PHP_INC_DIR=$(find /build/buildroot/include -name php -type d | head -n 1)
if [ -z "$PHP_INC_DIR" ]; then
echo "ERROR: no php header dir found under buildroot/include" >&2
exit 1
fi
cp -RLf "$PHP_INC_DIR" /output/include/php
# Bundle the musl-built libstdc++.a so downstream consumers
# can link against the same C++ runtime spc used to compile
# libicu*, libxml2, etc. PHPs intl extension references C++
# symbols (std::__throw_bad_alloc, __cxa_begin_catch, ICU
# UnicodeString ctors, ...). Ubuntu/Debian build hosts have
# a glibc-flavored libstdc++ that is ABI-incompatible with
# musl PHP — without bundling this, downstream binaries
# either fail to link or silently mismix C++ runtimes.
libstdcxx_path=$(c++ -print-file-name=libstdc++.a)
if [ -f "$libstdcxx_path" ]; then
cp "$libstdcxx_path" /output/lib/
echo "==> Staged libstdc++.a from $libstdcxx_path"
else
echo "::error::libstdc++.a not found (c++ -print-file-name returned: $libstdcxx_path)"
exit 1
fi
'
# Copy staged artifacts out of the stopped container.
# `docker cp container:/output ./` copies /output as ./output (src
# dir → dest as src-named subdir). Avoid the `/.` "copy contents"
# form — observed to leave the source dir nested inside the
# destination on some runner Docker versions, producing
# ./lib/lib/*.a in the final tarball.
docker cp spc-build:/output ./
docker rm spc-build
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Package SDK
run: |
mkdir -p sdk
cp -r output/* sdk/
# Guard: the staged headers must be the PHP version this run was
# asked to build. spc has silently fallen back to "latest stable"
# before (--with-php=8.4.21 produced a PHP 8.5.7 tree, shipped as
# v8.4.21, and was only caught days later by ephpm's E2E version
# assertion). Fail here, loudly, instead of publishing a
# mislabeled SDK.
staged=$(sed -n 's/^#define PHP_VERSION "\(.*\)"/\1/p' sdk/include/php/main/php_version.h)
if [ "$staged" != "${{ inputs.php_version }}" ]; then
echo "::error::staged PHP is '${staged}' but this build was asked for '${{ inputs.php_version }}' - spc ignored --with-php"
exit 1
fi
echo "==> Version guard OK: staged PHP ${staged}"
tar czf php-sdk-${{ inputs.php_version }}-linux-${{ matrix.arch }}.tar.gz -C sdk .
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: php-sdk-${{ inputs.php_version }}-linux-${{ matrix.arch }}
path: php-sdk-${{ inputs.php_version }}-linux-${{ matrix.arch }}.tar.gz
build-macos:
name: macOS (${{ matrix.arch }})
needs: setup
if: needs.setup.outputs.build-macos == 'true'
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- arch: aarch64
runner: [self-hosted, macos, arm64]
spc_arch: aarch64
steps:
# spc on macOS is a self-contained micro-PHP binary (same as Windows
# spc.exe), so no host PHP install is needed. The runner image is bare,
# so we let `spc doctor --auto-fix` install Homebrew and the build deps
# it lists in MacOSToolCheckList::REQUIRED_COMMANDS (autoconf, automake,
# bison, cmake, flex, libtool, make, pkg-config, re2c, etc.).
- name: Download spc
run: |
mkdir -p "$HOME/bin"
curl -fsSL "https://github.com/luthermonson/static-php-cli/releases/download/${{ env.SPC_VERSION }}/spc-macos-${{ matrix.spc_arch }}.tar.gz" \
| tar xz -C "$HOME/bin"
echo "$HOME/bin" >> "$GITHUB_PATH"
# Pre-add Homebrew to PATH so spc doctor can use brew immediately
# after installing it (spc doesn't update PATH itself).
echo "/opt/homebrew/bin" >> "$GITHUB_PATH"
- name: Install Homebrew + build deps via spc doctor
# NONINTERACTIVE=1 is required so the Homebrew installer doesn't prompt
# for sudo confirmation (which would hang in CI).
env:
NONINTERACTIVE: "1"
run: spc doctor --auto-fix
# Apple clang crashes on PHP's JIT code (zend_jit_vm_helpers.c, exit 70),
# so use Homebrew LLVM instead. Doctor's REQUIRED_COMMANDS doesn't
# include llvm, so we install it explicitly after brew is up.
- name: Install LLVM and prepend to PATH
run: |
brew install llvm
echo "$(brew --prefix llvm)/bin" >> "$GITHUB_PATH"
- name: Download PHP sources
# Retry to tolerate transient upstream flakes (see linux build).
run: |
i=0
until spc download \
--with-php=${{ inputs.php_version }} \
--for-extensions=${{ env.PHP_EXTENSIONS }} \
--prefer-pre-built; do
i=$((i + 1))
if [ "$i" -ge 6 ]; then
echo "spc download failed after $i attempts" >&2
exit 1
fi
s=$((RANDOM % 2 + 2))
echo "spc download flaked (attempt $i), retrying in ${s}s..."
sleep "$s"
done
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build libphp.a
run: |
# PHP < 8.4 uses K&R function declarations removed in C23.
# Homebrew LLVM/clang defaults to -std=gnu23, so force gnu17.
php_minor=$(echo "${{ inputs.php_version }}" | cut -d. -f2)
if [ "$php_minor" -lt 4 ]; then
export CFLAGS="-std=gnu17"
fi
# --dl-with-php pins the build-phase downloader to the requested
# version (see linux build for the incident this prevents).
spc build ${{ env.PHP_EXTENSIONS }} \
--dl-with-php=${{ inputs.php_version }} \
--build-embed \
--enable-zts \
--no-strip \
--debug
- name: Package SDK
run: |
mkdir -p sdk/lib sdk/include
# spc nests install output at varying depths; discover by find
# (see linux build for rationale).
find buildroot/lib -name "*.a" -type f -exec cp -Lf {} sdk/lib/ \;
PHP_INC_DIR=$(find buildroot/include -name php -type d | head -n 1)
if [ -z "$PHP_INC_DIR" ]; then
echo "ERROR: no php header dir found under buildroot/include" >&2
exit 1
fi
cp -RLf "$PHP_INC_DIR" sdk/include/php
# Guard: staged headers must match the requested version (see the
# linux Package SDK step for the incident this prevents).
staged=$(sed -n 's/^#define PHP_VERSION "\(.*\)"/\1/p' sdk/include/php/main/php_version.h)
if [ "$staged" != "${{ inputs.php_version }}" ]; then
echo "::error::staged PHP is '${staged}' but this build was asked for '${{ inputs.php_version }}' - spc ignored --with-php"
exit 1
fi
echo "==> Version guard OK: staged PHP ${staged}"
tar czf php-sdk-${{ inputs.php_version }}-macos-${{ matrix.arch }}.tar.gz -C sdk .
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: php-sdk-${{ inputs.php_version }}-macos-${{ matrix.arch }}
path: php-sdk-${{ inputs.php_version }}-macos-${{ matrix.arch }}.tar.gz
build-windows:
name: Windows (x86_64)
needs: setup
if: needs.setup.outputs.build-windows == 'true'
runs-on: [self-hosted, windows, x64]
# Windows-specific extension set: PHP_EXTENSIONS minus the Unix-only
# entries (pcntl, posix, sysvmsg, sysvsem, sysvshm) and any extension
# that won't build under static-php-cli's current Windows toolchain:
# - gmp → no static-libs@windows in v3 gmp.yml
# - pgsql, pdo_pgsql → spc uses pre-built EnterpriseDB import lib
# (links against LIBPQ.DLL) instead of a static build
# Re-add as spc grows Windows support for its underlying library.
env:
WIN_PHP_EXTENSIONS: "bcmath,bz2,calendar,ctype,curl,dom,exif,fileinfo,filter,ftp,gd,gettext,hash,iconv,intl,mbregex,mbstring,mysqli,mysqlnd,opcache,openssl,pcre,pdo,pdo_mysql,pdo_sqlite,phar,session,simplexml,soap,sodium,sqlite3,tokenizer,xml,xmlreader,xmlwriter,xsl,zip,zlib"
steps:
- name: Clean up previous builds
shell: powershell
run: |
foreach ($dir in @('buildroot', 'source', 'downloads', 'sdk', 'php-sdk-binary-tools')) {
if (Test-Path $dir) { Remove-Item -Recurse -Force $dir }
}
Remove-Item -Force *.tar.gz -ErrorAction SilentlyContinue
- name: Install Git for Windows
shell: powershell
# Server Core has no git. spc needs:
# - git.exe: doctor's install-php-sdk auto-fix clones php-sdk-binary-tools
# - usr\bin\patch.exe: source patching during build
# Use PortableGit (self-extracting 7z) installed at the standard Git
# for Windows path so spc's expected layout matches.
run: |
$gitRoot = "C:\Program Files\Git"
if (-not (Test-Path "$gitRoot\cmd\git.exe")) {
$ver = "2.49.0"
$url = "https://github.com/git-for-windows/git/releases/download/v${ver}.windows.1/PortableGit-${ver}-64-bit.7z.exe"
Write-Host "Downloading PortableGit $ver..."
Invoke-WebRequest -Uri $url -OutFile portable-git.7z.exe
Write-Host "Extracting to $gitRoot..."
Start-Process -FilePath .\portable-git.7z.exe `
-ArgumentList @("-o`"$gitRoot`"", '-y') -Wait -NoNewWindow
Remove-Item portable-git.7z.exe
if (-not (Test-Path "$gitRoot\cmd\git.exe")) {
Write-Error "PortableGit extraction failed: $gitRoot\cmd\git.exe not found"
exit 1
}
}
"$gitRoot\cmd" | Out-File -Append -FilePath $env:GITHUB_PATH
"$gitRoot\usr\bin" | Out-File -Append -FilePath $env:GITHUB_PATH
- name: Install Visual Studio Build Tools
shell: powershell
# PHP requires MSVC to compile on Windows. Runner image is
# mcr.microsoft.com/windows/servercore:ltsc2025 with no compiler.
# Install Build Tools per-job (~10-15 min). Long-term: dedicated
# php-sdk runner image with VS pre-baked.
#
# Install path: C:\BuildTools (Microsoft's recommended path for
# Server Core containers; nested Program Files paths are flaky).
# spc's findVisualStudio() probes Community/Professional/Enterprise
# under standard Program Files locations, so we junction one of
# those to C:\BuildTools after install.
#
# The bootstrapper (vs_buildtools.exe) exits before the underlying
# installer engine actually finishes on Server Core, so we poll
# for MSBuild.exe rather than trusting its exit code.
run: |
$installPath = "C:\BuildTools"
$msbuild = "$installPath\MSBuild\Current\Bin\MSBuild.exe"
$communityPath = "C:\Program Files\Microsoft Visual Studio\2022\Community"
if (-not (Test-Path $msbuild)) {
Write-Host "Downloading VS Build Tools bootstrapper..."
Invoke-WebRequest -Uri "https://aka.ms/vs/17/release/vs_buildtools.exe" -OutFile vs_buildtools.exe
Write-Host "Launching install (bootstrapper may exit before install completes)..."
$proc = Start-Process -FilePath .\vs_buildtools.exe `
-ArgumentList @(
'--quiet', '--wait', '--norestart', '--nocache',
'--installPath', $installPath,
'--add', 'Microsoft.VisualStudio.Workload.VCTools',
'--includeRecommended'
) -Wait -PassThru -NoNewWindow
Write-Host "Bootstrapper exit code: $($proc.ExitCode)"
Remove-Item vs_buildtools.exe
# Poll for MSBuild.exe; bootstrapper exit is unreliable on Server Core
$sw = [System.Diagnostics.Stopwatch]::StartNew()
$timeoutMin = 20
while (-not (Test-Path $msbuild) -and $sw.Elapsed.TotalMinutes -lt $timeoutMin) {
Start-Sleep -Seconds 30
Write-Host (" ...waiting for VS install ({0:F0}s)" -f $sw.Elapsed.TotalSeconds)
}
if (-not (Test-Path $msbuild)) {
Write-Host "::error::VS install timed out after $timeoutMin min - $msbuild never appeared"
Write-Host "==> Searching for MSBuild.exe anywhere on disk:"
Get-ChildItem -Path C:\ -Recurse -Filter MSBuild.exe -ErrorAction SilentlyContinue |
Select-Object -First 10 |
ForEach-Object { Write-Host " $($_.FullName)" }
Write-Host "==> Recent VS install logs in `$env:TEMP (dd_*.log):"
Get-ChildItem -Path $env:TEMP -Filter 'dd_*.log' -ErrorAction SilentlyContinue |
Sort-Object LastWriteTime -Descending |
Select-Object -First 5 |
ForEach-Object {
Write-Host ('--- ' + $_.Name + ' ---')
Get-Content $_.FullName -Tail 80
}
exit 1
}
Write-Host ("MSBuild.exe found after {0:F0}s" -f $sw.Elapsed.TotalSeconds)
} else {
Write-Host "VS Build Tools already present at $installPath"
}
# Junction the path spc's hardcoded check expects -> our install path
if (-not (Test-Path $communityPath)) {
$parent = Split-Path $communityPath -Parent
if (-not (Test-Path $parent)) {
New-Item -ItemType Directory -Path $parent -Force | Out-Null
}
New-Item -ItemType Junction -Path $communityPath -Target $installPath | Out-Null
Write-Host "Junction $communityPath -> $installPath"
}
- name: Download spc
shell: powershell
run: |
Invoke-WebRequest -Uri "https://github.com/luthermonson/static-php-cli/releases/download/${{ env.SPC_VERSION }}/spc-windows-x64.exe" -OutFile spc.exe
"$PWD" | Out-File -Append -FilePath $env:GITHUB_PATH
- name: Download PHP sources
shell: powershell
# Retry to tolerate transient upstream flakes (see linux build).
run: |
$max = 6
for ($i = 1; $i -le $max; $i++) {
spc download `
--with-php=${{ inputs.php_version }} `
--for-extensions=${{ env.WIN_PHP_EXTENSIONS }} `
--prefer-pre-built
if ($LASTEXITCODE -eq 0) { break }
if ($i -eq $max) {
Write-Error "spc download failed after $max attempts"
exit 1
}
$s = Get-Random -Minimum 2 -Maximum 4
Write-Host "spc download flaked (attempt $i), retrying in ${s}s..."
Start-Sleep -Seconds $s
}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Run spc doctor
shell: powershell
# --auto-fix runs install-php-sdk (clones php-sdk-binary-tools, needed
# for 7za.exe at extraction time), install-nasm, install-perl.
run: |
spc doctor --auto-fix
exit 0
- name: Add Strawberry Perl to PATH
shell: powershell
# spc doctor installs Strawberry Perl into pkgroot but doesn't
# persist it in GITHUB_PATH. Git's perl is on PATH first and is
# missing modules openssl needs (Locale::Maketext::Simple).
# Prepend Strawberry Perl so it wins over Git's perl.
run: |
Write-Host "Searching for Strawberry Perl in: $PWD"
Get-ChildItem -Directory -Recurse -Filter "strawberry-perl*" -ErrorAction SilentlyContinue |
Select-Object -First 5 | ForEach-Object { Write-Host " Found dir: $($_.FullName)" }
$perlExe = Get-ChildItem -Recurse -Filter "perl.exe" -Path pkgroot -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -match "strawberry" } |
Select-Object -First 1
if ($perlExe) {
$perlBin = $perlExe.DirectoryName
Write-Host "Adding Strawberry Perl to PATH: $perlBin"
$perlBin | Out-File -Append -FilePath $env:GITHUB_PATH
} else {
Write-Host "::warning::Strawberry Perl not found in pkgroot - falling back to system perl"
}
- name: Build php8embed (patched spc with embed SAPI support)
shell: powershell
run: |
# Ensure Strawberry Perl precedes Git's perl in PATH for this step
$spDir = Get-ChildItem -Directory pkgroot -Recurse -Filter "strawberry-perl*" -ErrorAction SilentlyContinue |
Select-Object -First 1
if ($spDir -and (Test-Path "$($spDir.FullName)\perl\bin\perl.exe")) {
$env:PATH = "$($spDir.FullName)\perl\bin;$env:PATH"
Write-Host "Strawberry Perl active: $(& perl.exe -v 2>&1 | Select-String 'MSWin32|version')"
}
# --dl-with-php pins the build-phase downloader to the requested
# version (see linux build for the incident this prevents).
spc build ${{ env.WIN_PHP_EXTENSIONS }} `
--dl-with-php=${{ inputs.php_version }} `
--build-embed `
--enable-zts `
--no-strip `
--debug
- name: Find build outputs
shell: powershell
run: |
Write-Host "==> Searching for php8embed files..."
Get-ChildItem -Recurse -Filter "php8embed.*" | ForEach-Object { Write-Host $_.FullName }
Write-Host "==> Searching for php8ts files..."
Get-ChildItem -Recurse -Filter "php8ts.*" | ForEach-Object { Write-Host $_.FullName }
Write-Host "==> Searching for libphp files..."
Get-ChildItem -Recurse -Filter "libphp.*" -ErrorAction SilentlyContinue | ForEach-Object { Write-Host $_.FullName }
- name: Package SDK
shell: powershell
run: |
New-Item -ItemType Directory -Force -Path sdk/lib, sdk/include
# Find php8embed.lib (the embed SAPI static library)
$lib = Get-ChildItem -Recurse -Filter "php8embed.lib" -ErrorAction SilentlyContinue | Select-Object -First 1
if ($lib) {
Copy-Item $lib.FullName sdk/lib/
Write-Host "==> Found php8embed.lib at $($lib.FullName)"
} else {
Write-Error "php8embed.lib not found anywhere in build tree"
exit 1
}
# Ship php8embed.lib's static dependency libraries too. spc's
# --build-embed is a static build: php8embed.lib carries PHP's
# own objects, but the third-party deps it was compiled against
# (openssl/libcrypto, zlib, libxml2, ICU, oniguruma, sqlite3, ...)
# live as separate .lib files under spc's buildroot. ephpm links
# the embed lib STATICALLY (like libphp.a on Linux/macOS), so those
# dep libs must be in the tarball or the final MSVC link fails with
# unresolved externals. The Linux/macOS Package SDK steps already
# copy every *.a from buildroot/lib for the same reason; do the
# equivalent here.
#
# Sources, in priority order: every buildroot*/lib directory spc
# produced, plus any .lib sitting alongside php8embed.lib. We do NOT
# do a tree-wide *.lib copy -- that would sweep in MSVC/Windows-SDK
# import libs. Scope to spc's own output dirs only.
$depDirs = @()
Get-ChildItem -Recurse -Directory -ErrorAction SilentlyContinue |
Where-Object { $_.Name -eq "lib" -and $_.Parent.Name -like "buildroot*" } |
ForEach-Object { $depDirs += $_.FullName }
$depDirs += (Split-Path $lib.FullName -Parent)
$depDirs = $depDirs | Select-Object -Unique
$copied = 0
foreach ($dir in $depDirs) {
Get-ChildItem -Path $dir -Filter "*.lib" -File -ErrorAction SilentlyContinue |
ForEach-Object {
Copy-Item $_.FullName sdk/lib/ -Force
$copied++
}
}
Write-Host "==> Staged $copied .lib file(s) from spc buildroot dirs: $($depDirs -join ', ')"
# Diagnostic: full inventory of every .lib spc produced anywhere in
# the tree (path + size). If the downstream static link still hits
# unresolved externals, this log pinpoints which lib provides the
# missing symbols and whether it shipped.
Write-Host "==> Full .lib inventory in build tree:"
Get-ChildItem -Recurse -Filter "*.lib" -File -ErrorAction SilentlyContinue |
Sort-Object FullName |
ForEach-Object { Write-Host (" {0,12:N0} {1}" -f $_.Length, $_.FullName) }
Copy-Item -Recurse buildroot/include/php sdk/include/php
# spc's Windows build (nmake-driven) doesn't install the
# platform-specific PHP win32 headers into buildroot the way
# autoconf's `make install-headers` does on Linux/macOS. The
# generated headers (e.g. Zend/zend_stream.h:80) still expect
# `#include "win32/ioutil.h"` relative to the main/ dir, so
# without these consumers fail with `fatal error: 'win32/
# ioutil.h' file not found` at the first compile. Stage them
# from the PHP source tree directly so the published tarball
# is self-contained.
$win32SrcDir = Get-ChildItem -Directory source -ErrorAction SilentlyContinue |
Where-Object { Test-Path "$($_.FullName)/win32/ioutil.h" } |
Select-Object -First 1
if (-not $win32SrcDir) {
Write-Error "Could not find php-src/win32/ioutil.h under source/ - was the PHP source extracted?"
exit 1
}
$win32Dest = "sdk/include/php/main/win32"
New-Item -ItemType Directory -Force -Path $win32Dest | Out-Null
Copy-Item "$($win32SrcDir.FullName)/win32/*.h" $win32Dest
Write-Host "==> Staged win32 headers from $($win32SrcDir.FullName)/win32 -> $win32Dest"
Get-ChildItem $win32Dest | ForEach-Object { Write-Host " $($_.Name)" }
# main/php_streams.h includes streams/php_stream_context.h (plus
# several siblings: transport, filter_api, glob_wrapper, mmap,
# userspace, streams_int) unconditionally. On Linux and macOS,
# autoconf's `make install-headers` installs main/streams/*.h
# alongside main/*.h. spc's Windows build (nmake) doesn't run
# install-headers, so the streams/ subdirectory never makes it
# into buildroot/include and the published tarball ships without
# it - consumers fail to compile the first translation unit that
# pulls in main/php.h. Stage the same way we stage win32/*.h:
# copy the .h files directly from the extracted php-src tree.
$streamsSrcDir = Get-ChildItem -Directory source -ErrorAction SilentlyContinue |
Where-Object { Test-Path "$($_.FullName)/main/streams/php_stream_context.h" } |
Select-Object -First 1
if (-not $streamsSrcDir) {
Write-Error "Could not find php-src/main/streams/php_stream_context.h under source/ - was the PHP source extracted?"
exit 1
}
$streamsDest = "sdk/include/php/main/streams"
New-Item -ItemType Directory -Force -Path $streamsDest | Out-Null
Copy-Item "$($streamsSrcDir.FullName)/main/streams/*.h" $streamsDest
Write-Host "==> Staged streams headers from $($streamsSrcDir.FullName)/main/streams -> $streamsDest"
Get-ChildItem $streamsDest | ForEach-Object { Write-Host " $($_.Name)" }
# nmake install-headers on Windows doesn't recurse into ext/*
# subdirectories. PHP's own headers cross-reference each other
# with `#include "ext/standard/php_var.h"` from
# ext/session/php_session.h (and several others), so the entire
# ext/*/ header trees must be in the SDK tarball or downstream
# compiles fail at the first translation unit that pulls in an
# extension header.
#
# Recursive-by-default rather than per-subdir explicit list: we
# don't know in advance which ext/<name>/ headers each PHP
# release cross-references, and shipping more headers is cheap.
# Mirrors the main/streams/ pattern above.
$extSrcRoot = "$($streamsSrcDir.FullName)/ext"
if (-not (Test-Path "$extSrcRoot/standard/php_var.h")) {
Write-Error "Could not find php-src/ext/standard/php_var.h under $extSrcRoot"
exit 1
}
$extDest = "sdk/include/php/ext"
Get-ChildItem -Path $extSrcRoot -Recurse -Filter "*.h" |
ForEach-Object {
$rel = $_.FullName.Substring($extSrcRoot.Length).TrimStart('\','/')
$dest = Join-Path $extDest $rel
New-Item -ItemType Directory -Force -Path (Split-Path $dest -Parent) | Out-Null
Copy-Item $_.FullName $dest
}
$extHeaderCount = (Get-ChildItem -Path $extDest -Recurse -Filter "*.h").Count
Write-Host "==> Staged $extHeaderCount ext/*/*.h headers from $extSrcRoot -> $extDest"
Write-Host "==> SDK contents:"
Get-ChildItem -Recurse sdk/lib/
# Guard: staged headers must match the requested version (see the
# linux Package SDK step for the incident this prevents).
$versionHeader = "sdk/include/php/main/php_version.h"
$match = Select-String -Path $versionHeader -Pattern '#define PHP_VERSION "([^"]+)"'
if (-not $match) {
Write-Error "Could not read PHP_VERSION from $versionHeader"
exit 1
}
$staged = $match.Matches[0].Groups[1].Value
if ($staged -ne "${{ inputs.php_version }}") {
Write-Error "staged PHP is '$staged' but this build was asked for '${{ inputs.php_version }}' - spc ignored --with-php"
exit 1
}
Write-Host "==> Version guard OK: staged PHP $staged"
tar czf php-sdk-${{ inputs.php_version }}-windows-x86_64.tar.gz -C sdk .
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: php-sdk-${{ inputs.php_version }}-windows-x86_64
path: php-sdk-${{ inputs.php_version }}-windows-x86_64.tar.gz
release:
name: Create Release
needs: [build-linux, build-macos, build-windows]
# Partial rebuilds skip the un-targeted build jobs (skipped != failure),
# but a real failure should still abort the release.
if: |
always()
&& needs.build-linux.result != 'failure'
&& needs.build-macos.result != 'failure'
&& needs.build-windows.result != 'failure'
runs-on: [self-hosted, linux, x64]
permissions:
contents: write
steps:
- uses: actions/download-artifact@v4
with:
path: artifacts
merge-multiple: true
- name: List artifacts
run: ls -lh artifacts/ || { echo "::error::no artifacts produced"; exit 1; }
- name: Create release
uses: softprops/action-gh-release@v2
# action-gh-release replaces only assets whose filenames match what
# we upload. Tarballs for platforms we didn't rebuild stay attached
# to the existing release tag untouched.
with:
tag_name: v${{ inputs.php_version }}
name: PHP ${{ inputs.php_version }}
body: |
Pre-built PHP embed SAPI SDK for ephpm.
**PHP version:** ${{ inputs.php_version }}
**Extensions:** ${{ env.PHP_EXTENSIONS }}
## Artifacts
| File | Platform |
|------|----------|
| `php-sdk-${{ inputs.php_version }}-linux-x86_64.tar.gz` | Linux x86_64 (musl static) |
| `php-sdk-${{ inputs.php_version }}-linux-aarch64.tar.gz` | Linux aarch64 (musl static) |
| `php-sdk-${{ inputs.php_version }}-macos-aarch64.tar.gz` | macOS Apple Silicon |
| `php-sdk-${{ inputs.php_version }}-windows-x86_64.tar.gz` | Windows x86_64 (ZTS) |
files: artifacts/*.tar.gz