ci: add ZTS ARM64 and macOS release builds

master
韩天峰 2 days ago
parent fef462af33
commit ee1d6e7b54
  1. 44
      .github/smoke/linux-arm64/cpp-src/platform.cc
  2. 12
      .github/smoke/linux-arm64/cpp-src/platform.stub.php
  3. 22
      .github/smoke/linux-arm64/main.php
  4. 9
      .github/smoke/linux-arm64/project.yml
  5. 45
      .github/smoke/macos/cpp-src/platform.cc
  6. 12
      .github/smoke/macos/cpp-src/platform.stub.php
  7. 22
      .github/smoke/macos/main.php
  8. 9
      .github/smoke/macos/project.yml
  9. 179
      .github/workflows/platform-build.yml
  10. 17
      .github/workflows/release.yml
  11. 87
      .github/workflows/tests.yml
  12. 33
      .github/workflows/windows-build.yml
  13. 14
      README-CN.md
  14. 16
      README.md
  15. 220
      package.php

@ -0,0 +1,44 @@
#include <phpx.h>
#include <cstring>
#include <sys/utsname.h>
#include <unistd.h>
using namespace php;
Int php_linux_current_process_id()
{
return static_cast<Int>(getpid());
}
Int php_linux_online_processor_count()
{
return static_cast<Int>(sysconf(_SC_NPROCESSORS_ONLN));
}
Bool php_linux_uname_machine_is_arm64()
{
utsname info{};
if (uname(&info) != 0) {
return false;
}
return std::strcmp(info.machine, "aarch64") == 0 || std::strcmp(info.machine, "arm64") == 0;
}
Bool php_linux_native_is_arm64()
{
#if defined(__aarch64__) || defined(__arm64__)
return true;
#else
return false;
#endif
}
Bool php_linux_native_php_is_zts()
{
#ifdef ZTS
return true;
#else
return false;
#endif
}

@ -0,0 +1,12 @@
<?php
/** Linux ARM64 platform declarations implemented by platform.cc. */
function linux_current_process_id(): int {}
function linux_online_processor_count(): int {}
function linux_uname_machine_is_arm64(): bool {}
function linux_native_is_arm64(): bool {}
function linux_native_php_is_zts(): bool {}

@ -0,0 +1,22 @@
<?php
function requireLinuxArm64Condition(bool $condition, string $message): void
{
if (!$condition) {
throw new RuntimeException($message);
}
}
function main(): void
{
requireLinuxArm64Condition(PHP_OS_FAMILY === 'Linux', 'Expected PHP_OS_FAMILY=Linux');
requireLinuxArm64Condition(DIRECTORY_SEPARATOR === '/', 'Expected the Unix directory separator');
requireLinuxArm64Condition(PHP_ZTS !== 0 && PHP_ZTS !== false, 'Expected a ZTS PHP runtime');
requireLinuxArm64Condition(linux_native_php_is_zts(), 'The native ZTS macro is not enabled');
requireLinuxArm64Condition(linux_native_is_arm64(), 'The native compiler target is not ARM64');
requireLinuxArm64Condition(linux_current_process_id() > 0, 'getpid() failed');
requireLinuxArm64Condition(linux_online_processor_count() > 0, 'sysconf() returned no processors');
requireLinuxArm64Condition(linux_uname_machine_is_arm64(), 'uname() did not report ARM64');
echo 'linux-arm64-smoke-ok:zts';
}

@ -0,0 +1,9 @@
name: linux-arm64-smoke
mode: bin
build-dir: build
output: linux_arm64_smoke
cxx-std: c++17
sources:
- main.php
- cpp-src

@ -0,0 +1,45 @@
#include <phpx.h>
#include <mach/mach.h>
#include <sys/sysctl.h>
#include <unistd.h>
using namespace php;
Int php_macos_current_process_id()
{
return static_cast<Int>(getpid());
}
Int php_macos_logical_processor_count()
{
int count = 0;
size_t size = sizeof(count);
if (sysctlbyname("hw.logicalcpu", &count, &size, nullptr, 0) != 0) {
return 0;
}
return static_cast<Int>(count);
}
Bool php_macos_has_mach_host_port()
{
return mach_host_self() != MACH_PORT_NULL;
}
Bool php_macos_native_is_arm64()
{
#if defined(__aarch64__) || defined(__arm64__)
return true;
#else
return false;
#endif
}
Bool php_macos_native_php_is_zts()
{
#ifdef ZTS
return true;
#else
return false;
#endif
}

@ -0,0 +1,12 @@
<?php
/** macOS platform declarations implemented by platform.cc. */
function macos_current_process_id(): int {}
function macos_logical_processor_count(): int {}
function macos_has_mach_host_port(): bool {}
function macos_native_is_arm64(): bool {}
function macos_native_php_is_zts(): bool {}

@ -0,0 +1,22 @@
<?php
function requireMacosCondition(bool $condition, string $message): void
{
if (!$condition) {
throw new RuntimeException($message);
}
}
function main(): void
{
requireMacosCondition(PHP_OS_FAMILY === 'Darwin', 'Expected PHP_OS_FAMILY=Darwin');
requireMacosCondition(DIRECTORY_SEPARATOR === '/', 'Expected the Unix directory separator');
requireMacosCondition(PHP_ZTS !== 0 && PHP_ZTS !== false, 'Expected a ZTS PHP runtime');
requireMacosCondition(macos_native_php_is_zts(), 'The native ZTS macro is not enabled');
requireMacosCondition(macos_native_is_arm64(), 'The native compiler target is not ARM64');
requireMacosCondition(macos_current_process_id() > 0, 'getpid() failed');
requireMacosCondition(macos_logical_processor_count() > 0, 'sysctl() returned no processors');
requireMacosCondition(macos_has_mach_host_port(), 'mach_host_self() failed');
echo 'macos-arm64-smoke-ok:zts';
}

@ -0,0 +1,9 @@
name: macos-smoke
mode: bin
build-dir: build
output: macos_smoke
cxx-std: c++17
sources:
- main.php
- cpp-src

@ -0,0 +1,179 @@
name: ARM64 and macOS tpc build
on:
push:
pull_request:
workflow_dispatch:
permissions:
contents: read
concurrency:
group: platform-tpc-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
COMPOSER_NO_INTERACTION: 1
COMPOSER_PROCESS_TIMEOUT: 0
jobs:
build-tpc:
name: ${{ matrix.target.label }} (PHP ${{ matrix.php }} ZTS)
runs-on: ${{ matrix.target.runner }}
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
php: ["8.4", "8.5"]
target:
- label: Linux ARM64
runner: ubuntu-22.04-arm
os: linux
arch: arm64
library_ext: so
smoke_dir: linux-arm64
smoke_binary: linux_arm64_smoke
smoke_output: linux-arm64-smoke-ok:zts
- label: macOS ARM64
runner: macos-15
os: macos
arch: arm64
library_ext: dylib
smoke_dir: macos
smoke_binary: macos_smoke
smoke_output: macos-arm64-smoke-ok:zts
env:
PHPX_HOME: ${{ github.workspace }}/vendor/swoole/phpx
steps:
- name: Checkout TypePHP
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
coverage: none
extensions: mbstring
ini-values: precision=17, memory_limit=4G, error_reporting=E_ERROR|E_WARNING, display_errors=1, display_startup_errors=1, log_errors=0
tools: composer:v2
env:
fail-fast: true
phpts: ts
update: true
- name: Install Linux build dependencies
if: matrix.target.os == 'linux'
shell: bash
run: |
sudo apt-get update
sudo apt-get install --yes build-essential cmake libgmp-dev libmpfr-dev pkg-config
- name: Install macOS build dependencies
if: matrix.target.os == 'macos'
shell: bash
run: brew install cmake gmp mpfr pkg-config
- name: Verify ZTS PHP and architecture
shell: bash
run: |
php -r 'if (!PHP_ZTS) { fwrite(STDERR, "Expected a ZTS PHP build\n"); exit(1); }'
case "$(php -r 'echo PHP_VERSION;')" in
"${{ matrix.php }}"*) ;;
*) echo "setup-php installed an unexpected PHP version" >&2; exit 1 ;;
esac
case "$(uname -m)" in
arm64|aarch64) ;;
*) echo "Expected an ARM64 runner, got $(uname -m)" >&2; exit 1 ;;
esac
php_home="$(php-config --prefix)"
embed_library="${php_home}/lib/libphp.${{ matrix.target.library_ext }}"
test -x "${php_home}/bin/php-config"
test -f "${embed_library}"
echo "PHP_HOME=${php_home}" >> "${GITHUB_ENV}"
if [[ '${{ matrix.target.os }}' == 'linux' ]]; then
echo "LD_LIBRARY_PATH=${PHPX_HOME}/lib:${php_home}/lib" >> "${GITHUB_ENV}"
else
echo "DYLD_LIBRARY_PATH=${PHPX_HOME}/lib:${php_home}/lib" >> "${GITHUB_ENV}"
fi
php -v
php-config --configure-options
- name: Patch php_hash.h C++ compatibility
uses: ./.github/actions/patch-php-headers
- name: Install Composer dependencies
run: composer install --prefer-dist --no-progress
- name: Build PHPX
shell: bash
run: |
cmake -S "${PHPX_HOME}" -B "${PHPX_HOME}/build" \
-D CMAKE_BUILD_TYPE=Release \
-D BUILD_TESTS=OFF \
-D BUILD_EXT=OFF \
-D GITHUB_ACTION=ON \
-D php_dir="${PHP_HOME}"
cmake --build "${PHPX_HOME}/build" --target phpx --parallel 2
test -f "${PHPX_HOME}/lib/libphpx.${{ matrix.target.library_ext }}"
- name: Build tpc
shell: bash
run: |
php bin/tpc.php project.yml --job 2 --no-progress
test -x ./tpc
file ./tpc
file ./tpc | grep -Eiq 'arm64|aarch64|ARM aarch64'
./tpc --version
- name: Run platform smoke test
shell: bash
run: |
smoke_root=".github/smoke/${{ matrix.target.smoke_dir }}"
./tpc "${smoke_root}/project.yml" --job 1 --no-progress
smoke_binary="${smoke_root}/${{ matrix.target.smoke_binary }}"
test -x "${smoke_binary}"
output="$("${smoke_binary}")"
test "${output}" = '${{ matrix.target.smoke_output }}'
- name: Show native dependencies
shell: bash
run: |
if [[ '${{ matrix.target.os }}' == 'linux' ]]; then
ldd ./tpc
else
otool -L ./tpc
fi
- name: Package tested compiler
if: startsWith(github.ref, 'refs/tags/') && matrix.php == '8.5'
shell: bash
run: |
composer install --no-dev --prefer-dist --no-progress --classmap-authoritative
export TYPEPHP_PACKAGE_VERSION="${GITHUB_REF_NAME}"
php package.php
test "$(find . -maxdepth 1 -name 'tpc_v*_${{ matrix.target.os }}_${{ matrix.target.arch }}.tar.gz' -type f | wc -l)" -eq 1
- name: Upload release package
if: startsWith(github.ref, 'refs/tags/') && matrix.php == '8.5'
uses: actions/upload-artifact@v4
with:
name: release-${{ matrix.target.os }}-${{ matrix.target.arch }}-php-${{ matrix.php }}-zts
if-no-files-found: error
retention-days: 1
path: tpc_v*_${{ matrix.target.os }}_${{ matrix.target.arch }}.tar.gz
- name: Upload platform build outputs
if: always()
uses: actions/upload-artifact@v4
with:
name: tpc-${{ matrix.target.os }}-${{ matrix.target.arch }}-php-${{ matrix.php }}-zts
include-hidden-files: true
if-no-files-found: warn
retention-days: 7
path: |
tpc
.github/smoke/${{ matrix.target.smoke_dir }}/${{ matrix.target.smoke_binary }}
.github/smoke/${{ matrix.target.smoke_dir }}/build/**/*.cc
.github/smoke/${{ matrix.target.smoke_dir }}/build/**/*.h

@ -79,23 +79,30 @@ jobs:
wait_for_workflow tests.yml TESTS_RUN_ID
wait_for_workflow windows-build.yml WINDOWS_RUN_ID
wait_for_workflow platform-build.yml PLATFORM_RUN_ID
- name: Download tested packages
shell: bash
run: |
set -euo pipefail
mkdir -p dist/linux dist/windows
mkdir -p dist/linux-x64 dist/windows dist/platforms
gh run download "${TESTS_RUN_ID}" \
--repo "${GITHUB_REPOSITORY}" \
--pattern 'release-linux-*' \
--dir dist/linux
--dir dist/linux-x64
gh run download "${WINDOWS_RUN_ID}" \
--repo "${GITHUB_REPOSITORY}" \
--pattern 'release-windows-*' \
--dir dist/windows
gh run download "${PLATFORM_RUN_ID}" \
--repo "${GITHUB_REPOSITORY}" \
--pattern 'release-*' \
--dir dist/platforms
test "$(find dist/linux -type f -name '*.tar.gz' | wc -l)" -eq 1
test "$(find dist/windows -type f -name '*.zip' | wc -l)" -eq 1
test "$(find dist -type f -name 'tpc_v*_linux_x64.tar.gz' | wc -l)" -eq 1
test "$(find dist -type f -name 'tpc_v*_linux_arm64.tar.gz' | wc -l)" -eq 1
test "$(find dist -type f -name 'tpc_v*_macos_arm64.tar.gz' | wc -l)" -eq 1
test "$(find dist -type f -name 'tpc_v*_windows_x64.zip' | wc -l)" -eq 1
- name: Prepare release assets
shell: bash
@ -104,7 +111,7 @@ jobs:
mkdir -p release-assets
find dist -type f \( -name '*.tar.gz' -o -name '*.zip' \) \
-exec cp '{}' release-assets/ \;
test "$(find release-assets -maxdepth 1 -type f \( -name '*.tar.gz' -o -name '*.zip' \) | wc -l)" -eq 2
test "$(find release-assets -maxdepth 1 -type f \( -name '*.tar.gz' -o -name '*.zip' \) | wc -l)" -eq 4
(
cd release-assets
sha256sum ./*.tar.gz ./*.zip > SHA256SUMS

@ -18,7 +18,7 @@ env:
jobs:
build-phpx:
name: Build PHPX (PHP ${{ matrix.php }})
name: Build PHPX (PHP ${{ matrix.php }} ZTS)
# Skip the whole test pipeline when the push commit message contains `--skip-tests`.
if: ${{ startsWith(github.ref, 'refs/tags/') || !contains(github.event.head_commit.message || '', '--skip-tests') }}
runs-on: ubuntu-22.04
@ -47,6 +47,19 @@ jobs:
coverage: none
ini-values: precision=17, memory_limit=4G, error_reporting=E_ERROR|E_WARNING, display_errors=1, display_startup_errors=1, log_errors=0
tools: composer:v2
env:
fail-fast: true
phpts: ts
update: true
- name: Verify ZTS PHP
shell: bash
run: |
php -r 'if (!PHP_ZTS) { fwrite(STDERR, "Expected a ZTS PHP build\n"); exit(1); }'
case "$(php -r 'echo PHP_VERSION;')" in
"${{ matrix.php }}"*) ;;
*) echo "setup-php installed an unexpected PHP version" >&2; exit 1 ;;
esac
- name: Patch php_hash.h C++ compatibility
uses: ./.github/actions/patch-php-headers
@ -81,7 +94,7 @@ jobs:
- name: Upload PHPX library
uses: actions/upload-artifact@v4
with:
name: libphpx-php-${{ matrix.php }}
name: libphpx-php-${{ matrix.php }}-zts
if-no-files-found: error
retention-days: 1
path: vendor/swoole/phpx/lib/libphpx.so
@ -89,13 +102,13 @@ jobs:
- name: Upload phpy extension
uses: actions/upload-artifact@v4
with:
name: phpy-php-${{ matrix.php }}
name: phpy-php-${{ matrix.php }}-zts
if-no-files-found: error
retention-days: 1
path: third_party/phpy/modules/phpy.so
phpunit:
name: PHPUnit (PHP ${{ matrix.php }})
name: PHPUnit (PHP ${{ matrix.php }} ZTS)
if: ${{ startsWith(github.ref, 'refs/tags/') || !contains(github.event.head_commit.message || '', '--skip-tests') }}
runs-on: ubuntu-22.04
timeout-minutes: 30
@ -119,6 +132,19 @@ jobs:
extensions: curl, redis, mbstring, ffi
ini-values: ffi.enable=1, phpy.enable_operator_overloading=0, precision=17, memory_limit=4G, error_reporting=E_ERROR|E_WARNING, display_errors=1, display_startup_errors=1, log_errors=0
tools: composer:v2
env:
fail-fast: true
phpts: ts
update: true
- name: Verify ZTS PHP
shell: bash
run: |
php -r 'if (!PHP_ZTS) { fwrite(STDERR, "Expected a ZTS PHP build\n"); exit(1); }'
case "$(php -r 'echo PHP_VERSION;')" in
"${{ matrix.php }}"*) ;;
*) echo "setup-php installed an unexpected PHP version" >&2; exit 1 ;;
esac
- name: Patch php_hash.h C++ compatibility
uses: ./.github/actions/patch-php-headers
@ -134,13 +160,13 @@ jobs:
- name: Download PHPX library
uses: actions/download-artifact@v4
with:
name: libphpx-php-${{ matrix.php }}
name: libphpx-php-${{ matrix.php }}-zts
path: vendor/swoole/phpx/lib
- name: Download phpy extension
uses: actions/download-artifact@v4
with:
name: phpy-php-${{ matrix.php }}
name: phpy-php-${{ matrix.php }}-zts
path: third_party/phpy/modules
- name: Enable phpy extension
@ -160,7 +186,7 @@ jobs:
run: vendor/bin/phpunit
phpt:
name: PHPT (PHP ${{ matrix.php }})
name: PHPT (PHP ${{ matrix.php }} ZTS)
if: ${{ startsWith(github.ref, 'refs/tags/') || !contains(github.event.head_commit.message || '', '--skip-tests') }}
runs-on: ubuntu-22.04
timeout-minutes: 180
@ -184,9 +210,22 @@ jobs:
with:
php-version: ${{ matrix.php }}
coverage: none
extensions: embed, curl, redis, mbstring, ffi
extensions: curl, redis, mbstring, ffi
ini-values: ffi.enable=1, phpy.enable_operator_overloading=0, opcache.jit=0, precision=17, memory_limit=4G, error_reporting=E_ERROR|E_WARNING, display_errors=1, display_startup_errors=1, log_errors=0
tools: composer:v2
env:
fail-fast: true
phpts: ts
update: true
- name: Verify ZTS PHP
shell: bash
run: |
php -r 'if (!PHP_ZTS) { fwrite(STDERR, "Expected a ZTS PHP build\n"); exit(1); }'
case "$(php -r 'echo PHP_VERSION;')" in
"${{ matrix.php }}"*) ;;
*) echo "setup-php installed an unexpected PHP version" >&2; exit 1 ;;
esac
- name: Patch php_hash.h C++ compatibility
uses: ./.github/actions/patch-php-headers
@ -196,29 +235,15 @@ jobs:
sudo apt-get update
sudo apt-get install --yes build-essential cmake libgmp-dev libmpfr-dev pkg-config python3-dev
- name: Configure version-matched PHP embed library
- name: Configure ZTS PHP embed library
shell: bash
run: |
embed_package="libphp${{ matrix.php }}-embed"
php_version="$(php-config --version)"
embed_version="$(dpkg-query --show --showformat='${Version}' "${embed_package}")"
case "${embed_version}" in
"${php_version}"*) ;;
*) echo "PHP embed package ${embed_version} does not match PHP ${php_version}" >&2; exit 1 ;;
esac
embed_library="$(dpkg-query --listfiles "${embed_package}" \
| sed -n '/\/libphp[0-9][^/]*\.so$/ { p; q; }')"
test -n "${embed_library}"
php_home="$(php-config --prefix)"
embed_library="${php_home}/lib/libphp.so"
test -x "${php_home}/bin/php-config"
test -f "${embed_library}"
php_home="${RUNNER_TEMP}/typephp-php-${{ matrix.php }}"
mkdir -p "${php_home}/bin" "${php_home}/include" "${php_home}/lib"
ln -s "$(command -v php-config)" "${php_home}/bin/php-config"
ln -s "$(php-config --include-dir)" "${php_home}/include/php"
ln -s "${embed_library}" "${php_home}/lib/libphp.so"
echo "PHP_HOME=${php_home}" >> "${GITHUB_ENV}"
echo "Using ${embed_library} from ${embed_package} ${embed_version} for PHP ${php_version}"
echo "Using ZTS PHP $(php-config --version) embed library: ${embed_library}"
- name: Install Composer dependencies
run: composer install --prefer-dist --no-progress
@ -226,13 +251,13 @@ jobs:
- name: Download PHPX library
uses: actions/download-artifact@v4
with:
name: libphpx-php-${{ matrix.php }}
name: libphpx-php-${{ matrix.php }}-zts
path: vendor/swoole/phpx/lib
- name: Download phpy extension
uses: actions/download-artifact@v4
with:
name: phpy-php-${{ matrix.php }}
name: phpy-php-${{ matrix.php }}-zts
path: third_party/phpy/modules
- name: Enable phpy extension
@ -284,7 +309,7 @@ jobs:
if: startsWith(github.ref, 'refs/tags/') && matrix.php == '8.5'
uses: actions/upload-artifact@v4
with:
name: release-linux-php-${{ matrix.php }}
name: release-linux-x64-php-${{ matrix.php }}-zts
if-no-files-found: error
retention-days: 1
path: tpc_v*_linux_*.tar.gz
@ -293,7 +318,7 @@ jobs:
if: failure()
uses: actions/upload-artifact@v4
with:
name: phpt-failures-php-${{ matrix.php }}
name: phpt-failures-linux-x64-php-${{ matrix.php }}-zts
if-no-files-found: ignore
retention-days: 7
path: |

@ -18,14 +18,13 @@ env:
jobs:
build-tpc:
name: tpc.exe (PHP ${{ matrix.php }} ${{ matrix.thread_safety }})
name: tpc.exe (PHP ${{ matrix.php }} ZTS)
runs-on: windows-2022
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
php: ["8.4", "8.5"]
thread_safety: [nts, zts]
steps:
- name: Checkout TypePHP
@ -40,7 +39,7 @@ jobs:
tools: composer:v2
env:
fail-fast: true
phpts: ${{ matrix.thread_safety == 'zts' && 'ts' || 'nts' }}
phpts: ts
update: true
- name: Resolve PHP build environment
@ -50,9 +49,12 @@ jobs:
$ErrorActionPreference = 'Stop'
$phpVersion = php -r 'echo PHP_VERSION;'
if (-not $phpVersion.StartsWith('${{ matrix.php }}.')) {
throw "setup-php installed PHP $phpVersion, expected ${{ matrix.php }}.x"
}
$threadSafety = php -r 'echo PHP_ZTS ? "zts" : "nts";'
if ($threadSafety -ne '${{ matrix.thread_safety }}') {
throw "setup-php installed $threadSafety PHP, expected ${{ matrix.thread_safety }}"
if ($threadSafety -ne 'zts') {
throw "setup-php installed $threadSafety PHP, expected zts"
}
$zipAvailable = php -r 'echo class_exists("ZipArchive") ? "yes" : "no";'
if ($zipAvailable -ne 'yes') {
@ -60,8 +62,7 @@ jobs:
}
$phpExe = (Get-Command php).Source
$phpHome = Split-Path -Parent $phpExe
$threadSuffix = if ($threadSafety -eq 'nts') { '-nts' } else { '' }
$archiveName = "php-devel-pack-$phpVersion$threadSuffix-Win32-vs17-x64.zip"
$archiveName = "php-devel-pack-$phpVersion-Win32-vs17-x64.zip"
$archive = Join-Path $env:RUNNER_TEMP $archiveName
"PHP_VERSION=$phpVersion" | Out-File $env:GITHUB_ENV -Append -Encoding utf8
@ -108,7 +109,7 @@ jobs:
}
}
if (-not $downloaded) {
throw "Unable to download the PHP $phpVersion NTS development pack"
throw "Unable to download the PHP $phpVersion ZTS development pack"
}
}
@ -133,8 +134,8 @@ jobs:
}
Copy-Item $embedLibrary $sdkLib -Force
$coreLibrary = if ($threadSafety -eq 'zts') { 'php8ts.lib' } else { 'php8.lib' }
$runtimeLibrary = if ($threadSafety -eq 'zts') { 'php8ts.dll' } else { 'php8.dll' }
$coreLibrary = 'php8ts.lib'
$runtimeLibrary = 'php8ts.dll'
# Temporary compatibility fix for PHP packages predating php/php-src#22940.
$hashHeader = Join-Path $sdkInclude 'ext\hash\php_hash.h'
@ -379,7 +380,7 @@ jobs:
$processInfo = [Diagnostics.ProcessStartInfo]::new()
$processInfo.FileName = $smokeExe
$processInfo.ArgumentList.Add('${{ matrix.thread_safety }}')
$processInfo.ArgumentList.Add('zts')
$processInfo.UseShellExecute = $false
$processInfo.RedirectStandardOutput = $true
$processInfo.RedirectStandardError = $true
@ -399,12 +400,12 @@ jobs:
if ($process.ExitCode -ne 0) {
throw "Windows smoke executable failed with exit code $($process.ExitCode)"
}
if ($stdout.Trim() -ne 'windows-smoke-ok:${{ matrix.thread_safety }}') {
if ($stdout.Trim() -ne 'windows-smoke-ok:zts') {
throw "Unexpected Windows smoke output: $stdout"
}
- name: Package tested Windows compiler
if: startsWith(github.ref, 'refs/tags/') && matrix.php == '8.5' && matrix.thread_safety == 'zts'
if: startsWith(github.ref, 'refs/tags/') && matrix.php == '8.5'
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
@ -425,10 +426,10 @@ jobs:
}
- name: Upload Windows release package
if: startsWith(github.ref, 'refs/tags/') && matrix.php == '8.5' && matrix.thread_safety == 'zts'
if: startsWith(github.ref, 'refs/tags/') && matrix.php == '8.5'
uses: actions/upload-artifact@v4
with:
name: release-windows-php-${{ matrix.php }}-${{ matrix.thread_safety }}
name: release-windows-x64-php-${{ matrix.php }}-zts
if-no-files-found: error
retention-days: 1
path: tpc_v*_windows_*.zip
@ -437,7 +438,7 @@ jobs:
if: always()
uses: actions/upload-artifact@v4
with:
name: tpc-windows-x64-php-${{ matrix.php }}-${{ matrix.thread_safety }}
name: tpc-windows-x64-php-${{ matrix.php }}-zts
if-no-files-found: warn
retention-days: 7
path: |

@ -83,7 +83,7 @@ AST,待全部项目符号就绪后再在 convert 阶段解析。这一两阶
`#[Printer]``#[Arrayable]` 根据属性声明生成类型安全的方法。
- **现代 PHP 支持** —— PHP 8.4 property hooks、非对称可见性、PHP 8.5
`clone()`-with 以及 `(void)` 丢弃表达式。
- **跨平台与 WASM** —— 面向 x86-64 和 ARM64 的 Linux、Windows、macOS 目标,
- **跨平台与 WASM** —— 面向 x64 和 ARM64 的 Linux、Windows、macOS 目标,
以及 WASI 0.2 和浏览器(Jco)输出。
- **Python 桥接** —— 为 Python 模块生成 IDE helper,并将 Python 脚本转换为 TypePHP。
@ -114,7 +114,8 @@ AST,待全部项目符号就绪后再在 convert 阶段解析。这一两阶
## 前置要求
- **PHP 8.4 – 8.5** CLI、开发头文件及 `php-config`
- 在类 Unix 系统构建二进制/共享库时,需要与 PHP 匹配的 **embed 库**(`libphp.so`)
- 在类 Unix 系统构建二进制/共享库时,需要与 PHP 匹配的 **embed 库**
(`libphp.so` 或 `libphp.dylib`
- **GCC 9+**(或 Clang),支持 **C++17**
- **CMake 3.24+**
- **Composer 2**
@ -134,10 +135,15 @@ sudo pacman -S base-devel cmake pkgconf gmp mpfr
> GMP 用于 `bigInt`,MPFR 用于 `bigFloat`。`decimal` 底层是 libmpdec,
> 已随 PHPX 内置,无需单独安装。
Linux 是主要开发和 CI 平台。编译器也提供 Windows、macOS、x86-64、ARM64 和
Linux x64 是主要开发及全量测试 CI 平台。编译器也提供 Windows、macOS、ARM64 和
WASI 后端;具体主机能否构建某个目标,仍取决于 PHP embed、工具链和第三方库是否
可用。
原生 Release Assets 默认使用 PHP 8.5 ZTS 的最新版本构建,提供 Linux x64、Linux
ARM64、macOS ARM64 和 Windows x64 四个平台包;不提供原生 NTS 或 32 位 x86 包。
Linux 与 macOS 包包含编译器和 production Composer 依赖,Windows 包则包含完整且
匹配的 PHP/PHPX 运行时与 SDK。
## 安装
### 通过 Composer
@ -601,7 +607,7 @@ source <(./tpc --generate-completion=bash)
## 常见问题
- **缺少 `libphp.so`:** 安装或编译与当前 PHP 匹配的 embed SAPI,设置
- **缺少 `libphp.so` / `libphp.dylib`:** 安装或编译与当前 PHP 匹配的 embed SAPI,设置
`PHP_HOME`,或使用 `bin/tpc.php` 在 Linux 上提供的交互式安装流程。
- **找不到 PHPX:**`PHPX_HOME` 指向包含 `include/`
`lib/libphpx.so`(或对应平台文件)的 PHPX 安装目录,并在编译项目前先构建 PHPX。

@ -97,7 +97,7 @@ This two-phase design keeps multi-file and self-hosted builds deterministic.
from property declarations.
- **Modern PHP support** — PHP 8.4 property hooks, asymmetric visibility,
PHP 8.5 `clone()`-with, and `(void)` discard expressions.
- **Cross-platform & WASM** — Linux, Windows, and macOS targets for x86-64 and
- **Cross-platform & WASM** — Linux, Windows, and macOS targets for x64 and
ARM64, plus WASI 0.2 and browser (Jco) output.
- **Python bridge** — generate IDE helpers for Python modules and convert
Python scripts to TypePHP.
@ -134,7 +134,7 @@ This two-phase design keeps multi-file and self-hosted builds deterministic.
## Requirements
- **PHP 8.4 – 8.5** CLI, development headers, and `php-config`
- The matching **PHP embed library** (`libphp.so`) for binary/shared-library
- The matching **PHP embed library** (`libphp.so` or `libphp.dylib`) for binary/shared-library
builds on Unix-like systems
- **GCC 9+** (or Clang) with **C++17**
- **CMake 3.24+**
@ -155,11 +155,17 @@ sudo pacman -S base-devel cmake pkgconf gmp mpfr
> GMP powers `bigInt` and MPFR powers `bigFloat`. The `decimal` type is backed
> by libmpdec, which is bundled with PHPX — no separate install required.
Linux is the primary development and CI platform. The compiler also has
Windows, macOS, x86-64, ARM64, and WASI backends; availability of PHP embed,
Linux x64 is the primary development and full-test CI platform. The compiler
also has Windows, macOS, ARM64, and WASI backends; availability of PHP embed,
toolchain, and third-party libraries still determines which target can be
built on a given host.
Native release assets are built with the latest PHP 8.5 ZTS release. TypePHP
publishes Linux x64, Linux ARM64, macOS ARM64, and Windows x64 packages. Native
NTS and 32-bit x86 packages are not provided. Linux and macOS archives contain
the compiler and production Composer dependencies, while the Windows archive
contains the complete matching PHP/PHPX runtime and SDK.
## Installation
### Via Composer
@ -644,7 +650,7 @@ source <(./tpc --generate-completion=bash)
## Troubleshooting
- **`libphp.so` is missing:** install/build the matching PHP embed SAPI, set
- **`libphp.so` / `libphp.dylib` is missing:** install/build the matching PHP embed SAPI, set
`PHP_HOME`, or let `bin/tpc.php` offer the interactive Linux installer.
- **PHPX cannot be found:** set `PHPX_HOME` to a PHPX installation containing
`include/` and `lib/libphpx.so` (or the platform equivalent), then build PHPX

@ -15,9 +15,9 @@ if (!chdir(__DIR__)) {
if (in_array('--help', $argv ?? [], true) || in_array('-h', $argv ?? [], true)) {
echo "Usage: php package.php\n\n";
echo "Windows: requires PHP_HOME and PHPX_HOME; creates a self-contained SDK package.\n";
echo "Linux: uses strip; packages the tested ELF and Composer vendor tree.\n";
echo "macOS: uses strip when available; packages the native binary and release metadata.\n";
echo "Supported architectures: 64-bit CPUs, including x86_64 and ARM64.\n";
echo "Linux: uses strip; packages the tested ELF and production Composer vendor tree.\n";
echo "macOS: uses strip; packages the tested Mach-O binary and production Composer vendor tree.\n";
echo "Supported architectures: 64-bit CPUs, including x64 and ARM64.\n";
exit(0);
}
@ -529,7 +529,7 @@ function normalizeArchitecture(string $architecture): string
{
$architecture = strtolower(trim($architecture));
$normalized = match ($architecture) {
'x86_64', 'x86-64', 'amd64', 'x64' => 'x86_64',
'x86_64', 'x86-64', 'amd64', 'x64' => 'x64',
'aarch64', 'arm64', 'arm64b', 'arm64e' => 'arm64',
'powerpc64', 'ppc64' => 'ppc64',
'powerpc64le', 'ppc64le' => 'ppc64le',
@ -568,54 +568,19 @@ function packageUnixLike(): void
$arch = normalizeArchitecture(php_uname('m'));
$binary = 'tpc';
$requiredFiles = [$binary];
if ($osType === 'linux') {
$requiredFiles[] = 'vendor/autoload.php';
$requiredFiles[] = 'vendor/composer/installed.php';
} else {
$phpxSourceDir = getenv('PHPX_HOME');
if (!is_string($phpxSourceDir) || $phpxSourceDir === '' || !is_dir($phpxSourceDir)) {
$phpxSourceDir = 'vendor/swoole/phpx';
}
$phpxSourceDir = realpath($phpxSourceDir) ?: rtrim($phpxSourceDir, '/\\');
$wasiSdkSourceRoot = $phpxSourceDir . '/wasm/wasm32-wasip2';
$wasiSdkPackageRoot = 'vendor/swoole/phpx/wasm/wasm32-wasip2';
array_push(
$requiredFiles,
'composer.json',
'README.md',
'LICENSE',
'examples/hello.php',
'completions/tpc.bash',
'wasm/build-program.sh',
"{$wasiSdkSourceRoot}/.typephp-wasi-sdk-abi",
"{$wasiSdkSourceRoot}/include/php/main/php.h",
"{$wasiSdkSourceRoot}/include/php/main/php_config.h",
"{$wasiSdkSourceRoot}/include/php/Zend/zend_config.h",
"{$wasiSdkSourceRoot}/include/php/ext/date/lib/timelib_config.h",
"{$wasiSdkSourceRoot}/include/phpx/phpx.h",
"{$wasiSdkSourceRoot}/include/phpx/phpx_python.h",
"{$wasiSdkSourceRoot}/include/phpx/typephp_helper.h",
"{$wasiSdkSourceRoot}/include/gmp.h",
"{$wasiSdkSourceRoot}/include/mpfr.h",
"{$wasiSdkSourceRoot}/include/decimal.hh",
);
foreach (['libphp.a', 'libphpx.a', 'libgmp.a', 'libgmpxx.a', 'libmpfr.a', 'libmpdec.a', 'libmpdec++.a'] as $library) {
$requiredFiles[] = "{$wasiSdkSourceRoot}/lib/{$library}";
}
}
$requiredFiles = [
$binary,
'vendor/autoload.php',
'vendor/composer/installed.php',
];
foreach ($requiredFiles as $file) {
if (!is_file($file)) {
throw new RuntimeException("Required package file not found: {$file}");
}
}
if ($osType !== 'linux' && !class_exists('ZipArchive')) {
throw new RuntimeException('The ZipArchive extension is required');
}
$versionId = resolvePackageVersion();
$topLevelDir = "tpc_v{$versionId}_{$osType}_{$arch}";
$outputFile = $topLevelDir . ($osType === 'linux' ? '.tar.gz' : '.zip');
$outputFile = $topLevelDir . '.tar.gz';
echo "========================================\n";
echo "TypePHP {$osType} package\n";
@ -669,129 +634,58 @@ function packageUnixLike(): void
throw new RuntimeException("strip failed:\n" . implode("\n", $stripOutput));
}
if ($osType === 'linux') {
// Linux release binaries intentionally use the target system's libphp,
// libphpx, and other native dependencies. Composer sources and headers
// are portable, but test-built host libraries must not enter the archive.
mustCreateDirectory("{$topLevelDir}/vendor");
copyDirectory(
'vendor',
"{$topLevelDir}/vendor",
[],
null,
['a', 'dll', 'dylib', 'exe', 'exp', 'lib', 'o', 'obj', 'pdb', 'so'],
);
} else {
mustCopy('composer.json', "{$topLevelDir}/composer.json");
mustCopy('README.md', "{$topLevelDir}/README.md");
mustCopy('LICENSE', "{$topLevelDir}/LICENSE");
mustCreateDirectory("{$topLevelDir}/examples");
mustCopy('examples/hello.php', "{$topLevelDir}/examples/hello.php");
mustCreateDirectory("{$topLevelDir}/completions");
mustCopy('completions/tpc.bash', "{$topLevelDir}/completions/tpc.bash");
copyDirectory('wasm', "{$topLevelDir}/wasm");
// Host tools are installed independently; only the target SDK is bundled.
copyDirectory(
"{$phpxSourceDir}/wasm",
"{$topLevelDir}/vendor/swoole/phpx/wasm",
['bin'],
);
}
// Linux and macOS releases intentionally use the target system's libphp,
// libphpx, and other native dependencies. Composer sources and headers are
// portable, but test-built host libraries must not enter the archive.
mustCreateDirectory("{$topLevelDir}/vendor");
copyDirectory(
'vendor',
"{$topLevelDir}/vendor",
[],
null,
['a', 'dll', 'dylib', 'exe', 'exp', 'lib', 'o', 'obj', 'pdb', 'so'],
);
$requiredEntries = ["{$topLevelDir}/{$binary}"];
if ($osType === 'linux') {
$requiredEntries[] = "{$topLevelDir}/vendor/autoload.php";
$requiredEntries[] = "{$topLevelDir}/vendor/composer/installed.php";
} else {
array_push(
$requiredEntries,
"{$topLevelDir}/composer.json",
"{$topLevelDir}/README.md",
"{$topLevelDir}/LICENSE",
"{$topLevelDir}/examples/hello.php",
"{$topLevelDir}/completions/tpc.bash",
"{$topLevelDir}/wasm/build-program.sh",
"{$topLevelDir}/{$wasiSdkPackageRoot}/.typephp-wasi-sdk-abi",
"{$topLevelDir}/{$wasiSdkPackageRoot}/include/php/main/php.h",
"{$topLevelDir}/{$wasiSdkPackageRoot}/include/phpx/phpx.h",
);
foreach (['libphp.a', 'libphpx.a', 'libgmp.a', 'libgmpxx.a', 'libmpfr.a', 'libmpdec.a', 'libmpdec++.a'] as $library) {
$requiredEntries[] = "{$topLevelDir}/{$wasiSdkPackageRoot}/lib/{$library}";
$requiredEntries = [
"{$topLevelDir}/{$binary}",
"{$topLevelDir}/vendor/autoload.php",
"{$topLevelDir}/vendor/composer/installed.php",
];
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator(
"{$topLevelDir}/vendor",
FilesystemIterator::SKIP_DOTS,
),
RecursiveIteratorIterator::LEAVES_ONLY,
);
foreach ($files as $file) {
if ($file->isFile() && isNativeLibraryPath($file->getPathname())) {
throw new RuntimeException(
"{$osType} package unexpectedly contains a native library: {$file->getPathname()}",
);
}
}
if ($osType === 'linux') {
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator(
"{$topLevelDir}/vendor",
FilesystemIterator::SKIP_DOTS,
),
RecursiveIteratorIterator::LEAVES_ONLY,
);
foreach ($files as $file) {
if ($file->isFile() && isNativeLibraryPath($file->getPathname())) {
throw new RuntimeException(
"Linux package unexpectedly contains a native library: {$file->getPathname()}",
);
}
}
exec('command -v tar 2>/dev/null', $tarPath, $tarStatus);
if ($tarStatus !== 0) {
throw new RuntimeException('tar is required for Linux packaging');
}
exec(
'tar -czf ' . escapeshellarg($outputFile) . ' ' . escapeshellarg($topLevelDir) . ' 2>&1',
$tarOutput,
$tarStatus,
);
if ($tarStatus !== 0) {
throw new RuntimeException("tar failed:\n" . implode("\n", $tarOutput));
}
exec('tar -tzf ' . escapeshellarg($outputFile) . ' 2>&1', $archiveEntries, $tarStatus);
if ($tarStatus !== 0) {
throw new RuntimeException("Unable to verify archive:\n" . implode("\n", $archiveEntries));
}
foreach ($requiredEntries as $entry) {
if (!in_array($entry, $archiveEntries, true)) {
throw new RuntimeException("Archive is missing required entry: {$entry}");
}
}
} else {
$zip = new ZipArchive();
if ($zip->open($outputFile, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
throw new RuntimeException("Unable to create archive: {$outputFile}");
}
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($topLevelDir, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::LEAVES_ONLY,
);
foreach ($files as $file) {
if (!$file->isFile()) {
continue;
}
$relativePath = str_replace('\\', '/', substr(
$file->getPathname(),
strlen($topLevelDir) + 1,
));
if (!$zip->addFile($file->getPathname(), "{$topLevelDir}/{$relativePath}")) {
throw new RuntimeException("Unable to add archive entry: {$file->getPathname()}");
}
}
if (!$zip->close()) {
throw new RuntimeException("Unable to finish archive: {$outputFile}");
}
$verificationZip = new ZipArchive();
if ($verificationZip->open($outputFile) !== true) {
throw new RuntimeException("Unable to verify archive: {$outputFile}");
}
foreach ($requiredEntries as $entry) {
if ($verificationZip->locateName($entry) === false) {
$verificationZip->close();
throw new RuntimeException("Archive is missing required entry: {$entry}");
}
exec('command -v tar 2>/dev/null', $tarPath, $tarStatus);
if ($tarStatus !== 0) {
throw new RuntimeException('tar is required for Unix-like packaging');
}
exec(
'tar -czf ' . escapeshellarg($outputFile) . ' ' . escapeshellarg($topLevelDir) . ' 2>&1',
$tarOutput,
$tarStatus,
);
if ($tarStatus !== 0) {
throw new RuntimeException("tar failed:\n" . implode("\n", $tarOutput));
}
exec('tar -tzf ' . escapeshellarg($outputFile) . ' 2>&1', $archiveEntries, $tarStatus);
if ($tarStatus !== 0) {
throw new RuntimeException("Unable to verify archive:\n" . implode("\n", $archiveEntries));
}
foreach ($requiredEntries as $entry) {
if (!in_array($entry, $archiveEntries, true)) {
throw new RuntimeException("Archive is missing required entry: {$entry}");
}
$verificationZip->close();
}
removeDirectory($topLevelDir);

Loading…
Cancel
Save