diff --git a/.github/smoke/linux-arm64/cpp-src/platform.cc b/.github/smoke/linux-arm64/cpp-src/platform.cc new file mode 100644 index 00000000..9814ae97 --- /dev/null +++ b/.github/smoke/linux-arm64/cpp-src/platform.cc @@ -0,0 +1,44 @@ +#include + +#include +#include +#include + +using namespace php; + +Int php_linux_current_process_id() +{ + return static_cast(getpid()); +} + +Int php_linux_online_processor_count() +{ + return static_cast(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 +} diff --git a/.github/smoke/linux-arm64/cpp-src/platform.stub.php b/.github/smoke/linux-arm64/cpp-src/platform.stub.php new file mode 100644 index 00000000..40f70422 --- /dev/null +++ b/.github/smoke/linux-arm64/cpp-src/platform.stub.php @@ -0,0 +1,12 @@ + 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'; +} diff --git a/.github/smoke/linux-arm64/project.yml b/.github/smoke/linux-arm64/project.yml new file mode 100644 index 00000000..93f035d9 --- /dev/null +++ b/.github/smoke/linux-arm64/project.yml @@ -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 diff --git a/.github/smoke/macos/cpp-src/platform.cc b/.github/smoke/macos/cpp-src/platform.cc new file mode 100644 index 00000000..239d4fb7 --- /dev/null +++ b/.github/smoke/macos/cpp-src/platform.cc @@ -0,0 +1,45 @@ +#include + +#include +#include +#include + +using namespace php; + +Int php_macos_current_process_id() +{ + return static_cast(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(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 +} diff --git a/.github/smoke/macos/cpp-src/platform.stub.php b/.github/smoke/macos/cpp-src/platform.stub.php new file mode 100644 index 00000000..e9799d58 --- /dev/null +++ b/.github/smoke/macos/cpp-src/platform.stub.php @@ -0,0 +1,12 @@ + 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'; +} diff --git a/.github/smoke/macos/project.yml b/.github/smoke/macos/project.yml new file mode 100644 index 00000000..457208cf --- /dev/null +++ b/.github/smoke/macos/project.yml @@ -0,0 +1,9 @@ +name: macos-smoke +mode: bin +build-dir: build +output: macos_smoke +cxx-std: c++17 + +sources: + - main.php + - cpp-src diff --git a/.github/workflows/platform-build.yml b/.github/workflows/platform-build.yml new file mode 100644 index 00000000..e11ac022 --- /dev/null +++ b/.github/workflows/platform-build.yml @@ -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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 09311a68..65010f8e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f7f7099f..5b567cd7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -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: | diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index 27cf8cf2..3545c251 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -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: | diff --git a/README-CN.md b/README-CN.md index b1cb0304..9bd4c11b 100644 --- a/README-CN.md +++ b/README-CN.md @@ -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。 diff --git a/README.md b/README.md index 599563bc..1249f7cc 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/package.php b/package.php index b80d27b5..5d0e430c 100755 --- a/package.php +++ b/package.php @@ -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);