diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 27d4e05..914a211 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -2,73 +2,214 @@ name: Main workflow on: pull_request: + types: + - opened + - synchronize + - reopened + paths-ignore: + - "**.md" push: + branches: + - master + - main + paths-ignore: + - "**.md" schedule: - cron: 0 0 * * 5 -jobs: - plugin-test-docker: - strategy: - matrix: - container: - - ubuntu:latest - php-version: - - 7.4.14 - - 8.0.0 - - latest - - env: - DEBIAN_FRONTEND: noninteractive - - container: - image: ${{ matrix.container }} - - runs-on: ubuntu-latest - - steps: - - name: Install packages - run: apt-get update && apt-get install -y autoconf bison build-essential curl gettext git libgd-dev libcurl4-openssl-dev libedit-dev libicu-dev libjpeg-dev libmysqlclient-dev libonig-dev libpng-dev libpq-dev libreadline-dev libsqlite3-dev libssl-dev libxml2-dev libzip-dev openssl pkg-config re2c zlib1g-dev - - - name: asdf_plugin_test - uses: asdf-vm/actions/plugin-test@v4 - with: - command: php --version - version: ${{ matrix.php-version }} +env: + # Common dependencies for all builds + UBUNTU_DEPS: "autoconf bison build-essential curl gettext git libgd-dev libcurl4-openssl-dev libedit-dev libicu-dev libjpeg-dev libmysqlclient-dev libonig-dev libpng-dev libpq-dev libreadline-dev libsqlite3-dev libssl-dev libxml2-dev libzip-dev libtidy-dev openssl pkg-config re2c zlib1g-dev ca-certificates wget libtool automake gcc-9 g++-9 libxslt1.1 libxslt1-dev autotools-dev m4 libtool-bin" + MACOS_DEPS: "autoconf automake bison freetype gd gettext icu4c krb5 libedit libiconv libjpeg libpng libxml2 libzip openssl@3 pkg-config re2c zlib bzip2" - plugin-test-vm: +jobs: + plugin_test: strategy: + fail-fast: false matrix: - os: - - macos-latest - php-version: - - 7.4.14 - - 8.0.0 - - latest + include: + - os: ubuntu-latest + version: 8.0.0 + is_legacy: true + - os: ubuntu-latest + version: 8.3.29 + is_legacy: true + - os: ubuntu-latest + version: 7.4.14 + is_legacy: true + - os: ubuntu-latest + version: latest + is_legacy: false + - os: macos-latest + version: 8.3.29 + is_legacy: true + - os: macos-latest + version: 8.0.0 + is_legacy: true + # will uncomment later + # - os: macos-latest + # version: 7.4.14 + # is_legacy: true + # - os: ubuntu-latest + # version: latest + # is_legacy: false runs-on: ${{ matrix.os }} + timeout-minutes: 45 steps: - - name: Install packages - run: brew install autoconf automake bison freetype gd gettext icu4c krb5 libedit libiconv libjpeg libpng libxml2 libzip openssl@1.1 pkg-config re2c zlib - - - name: Install conflicting packages - run: brew install openssl@3 - - - name: asdf_plugin_test - uses: asdf-vm/actions/plugin-test@v4 - with: - command: php --version - version: ${{ matrix.php-version }} + - name: Checkout code + uses: actions/checkout@v4 + + - name: Initialize submodule and set permissions + run: | + git submodule update --init --recursive + chmod +x bin/* lib/* + + - name: Install system packages + run: | + set -e + if [ "${{ runner.os }}" = "Linux" ]; then + sudo apt-get update + sudo apt-get install -y ${{ env.UBUNTU_DEPS }} + elif [ "${{ runner.os }}" = "macOS" ]; then + brew install ${{ env.MACOS_DEPS }} + # Set up macOS-specific environment for PHP build + echo "CXXFLAGS=-std=c++17 -stdlib=libc++" >> $GITHUB_ENV + # Set up library paths for configure to find dependencies + echo "LDFLAGS=-L$(brew --prefix)/lib" >> $GITHUB_ENV + echo "CPPFLAGS=-I$(brew --prefix)/include" >> $GITHUB_ENV + # Set ICU4C prefix once for reuse + if brew --prefix icu4c@78 >/dev/null 2>&1; then + ICU_PREFIX="$(brew --prefix icu4c@78)" + else + ICU_PREFIX="$(brew --prefix icu4c)" + fi + echo "ICU_PREFIX=$ICU_PREFIX" >> $GITHUB_ENV + echo "PKG_CONFIG_PATH=$ICU_PREFIX/lib/pkgconfig:$(brew --prefix openssl@3)/lib/pkgconfig:$(brew --prefix libxml2)/lib/pkgconfig:$PKG_CONFIG_PATH" >> $GITHUB_ENV + + # Configure tidy once for reuse + TIDY_PREFIX="$(brew --prefix tidy-html5)" + if [ -f "$TIDY_PREFIX/lib/libtidy.dylib" ] || [ -f "$TIDY_PREFIX/lib/libtidy.a" ]; then + echo "✓ tidy library found, enabling tidy support" + echo "TIDY_CONFIG=--with-tidy=$TIDY_PREFIX" >> $GITHUB_ENV + else + echo "⚠ tidy library not found, disabling tidy support" + echo "TIDY_CONFIG=--without-tidy" >> $GITHUB_ENV + fi + fi + + - name: Setup asdf + uses: asdf-vm/actions/setup@v4 + + - name: Configure environment for PHP ${{ matrix.version }} + run: | + # Add plugin from current checkout + asdf plugin add php $GITHUB_WORKSPACE + + # Set common environment + export PHP_BUILD_XDEBUG_ENABLE=off + echo "PHP_BUILD_XDEBUG_ENABLE=off" >> $GITHUB_ENV + + if [ "${{ matrix.is_legacy }}" = "true" ]; then + echo "=== Configuring for legacy PHP ${{ matrix.version }} ===" + + # Use gcc-9 for legacy versions, fallback to gcc + if command -v gcc-9 >/dev/null 2>&1; then + COMPILER="gcc-9" + CXXCOMPILER="g++-9" + else + COMPILER="gcc" + CXXCOMPILER="g++" + fi + + # Set legacy-specific environment + echo "ASDF_PHP_OPENSSL_AUTO=yes" >> $GITHUB_ENV + echo "CC=$COMPILER" >> $GITHUB_ENV + echo "CXX=$CXXCOMPILER" >> $GITHUB_ENV + echo "ac_cv_prog_CC=$COMPILER" >> $GITHUB_ENV + echo "ac_cv_prog_CXX=$CXXCOMPILER" >> $GITHUB_ENV + echo "CONFIG_SHELL=/bin/bash" >> $GITHUB_ENV + + # Add macOS-specific PHP build configure options + if [ "${{ runner.os }}" = "macOS" ]; then + echo "PHP_BUILD_CONFIGURE_OPTS=--with-bz2=$(brew --prefix bzip2) --with-iconv=$(brew --prefix libiconv) --with-openssl=$(brew --prefix openssl@3) ${{ env.TIDY_CONFIG }}" >> $GITHUB_ENV + fi + + # Debug output + echo "Compiler: $COMPILER ($($COMPILER --version | head -1))" + echo 'int main() { return 0; }' > /tmp/test.c + $COMPILER -o /tmp/test /tmp/test.c && echo "✓ Compiler works" || echo "✗ Compiler failed" + rm -f /tmp/test /tmp/test.c + else + echo "=== Configuring for modern PHP ${{ matrix.version }} ===" + echo "ASDF_PHP_OPENSSL_AUTO=no" >> $GITHUB_ENV + echo "CC=gcc" >> $GITHUB_ENV + echo "CXX=g++" >> $GITHUB_ENV + + # Add macOS-specific PHP build configure options for modern versions + if [ "${{ runner.os }}" = "macOS" ]; then + echo "PHP_BUILD_CONFIGURE_OPTS=--with-bz2=$(brew --prefix bzip2) --with-iconv=$(brew --prefix libiconv) --with-openssl=$(brew --prefix openssl@3) ${{ env.TIDY_CONFIG }}" >> $GITHUB_ENV + fi + fi + + - name: Install PHP ${{ matrix.version }} + run: | + echo "Installing PHP ${{ matrix.version }}..." + + if ! asdf install php ${{ matrix.version }}; then + if [ "${{ matrix.is_legacy }}" = "true" ]; then + echo "=== Build failed - gathering diagnostics ===" + echo "Environment: CC=$CC, ac_cv_prog_CC=$ac_cv_prog_CC" + # On macOS, php-build writes its log to $TMPDIR (/var/folders/…), not /tmp + # Search both locations so diagnostics work on macOS and Linux + SEARCH_DIRS="${TMPDIR:-} /tmp /var/folders" + for _sdir in $SEARCH_DIRS; do + [ -d "$_sdir" ] || continue + find "$_sdir" -maxdepth 6 -name "php-build.${{ matrix.version }}.*.log" 2>/dev/null | while read -r _log; do + echo "=== $( basename \"$_log\" ) ===" + tail -150 "$_log" + done + done + find /var/tmp -maxdepth 6 -name "config.log" -path "*/${{ matrix.version }}/*" 2>/dev/null -exec tail -50 {} \; || true + fi + exit 1 + fi + + - name: Test PHP installation + run: | + asdf set php ${{ matrix.version }} + php --version + echo "Installation path: $(asdf where php)" + + # Test Composer if available + if command -v composer >/dev/null 2>&1; then + echo "Composer: $(composer --version)" + else + echo "Composer not available" + fi + + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y shellcheck make + - name: Run lint + run: make lint format: runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v5 - - - name: Install shfmt - run: brew install shfmt - - - name: Run shfmt + - uses: actions/checkout@v4 + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y make + curl -sL "https://github.com/mvdan/sh/releases/download/v3.8.0/shfmt_v3.8.0_linux_amd64" -o shfmt + chmod +x shfmt + sudo mv shfmt /usr/local/bin/ + - name: Check format run: make fmt-check diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..de30004 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# Ignore vendor directory (contains php-build submodule/clone) +vendor/ + +# Ignore temporary files +*.tmp +*.log +.DS_Store + +# Ignore editor files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# Ignore OS files +Thumbs.db diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..bedc473 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "vendor/php-build"] + path = vendor/php-build + url = https://github.com/php-build/php-build.git diff --git a/Makefile b/Makefile index 194601e..43fd986 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ SH_SRCFILES = $(shell git ls-files "bin/*") SHFMT_BASE_FLAGS = -s -i 2 -ci +TEST_FILES = $(wildcard tests/*.bats) fmt: shfmt -w $(SHFMT_BASE_FLAGS) $(SH_SRCFILES) @@ -12,3 +13,26 @@ fmt-check: lint: shellcheck $(SH_SRCFILES) .PHONY: lint + +test: + @if command -v bats >/dev/null 2>&1; then \ + bats $(TEST_FILES); \ + else \ + echo "Error: bats is not installed. Please install bats-core."; \ + echo "macOS: brew install bats-core"; \ + echo "Linux: apt-get install bats or yum install bats"; \ + exit 1; \ + fi +.PHONY: test + +test-verbose: + @if command -v bats >/dev/null 2>&1; then \ + bats --tap $(TEST_FILES); \ + else \ + echo "Error: bats is not installed. Please install bats-core."; \ + exit 1; \ + fi +.PHONY: test-verbose + +check: fmt-check lint test +.PHONY: check diff --git a/README.md b/README.md index e043afd..2530842 100644 --- a/README.md +++ b/README.md @@ -7,71 +7,191 @@ _Original version of this plugin created by ## Build History -[![Build history](https://buildstats.info/github/chart/asdf-community/asdf-php?branch=master)](https://github.com/asdf-community/asdf-php/actions) +[Build history](https://github.com/asdf-community/asdf-php/actions) -## Prerequirements +## Architecture -Check the [.github/workflows/workflow.yml](.github/workflows/workflow.yml) for -dependencies, paths, and environment variables needed to install the latest PHP -version. To be honest, supporting a major version other than the latest without -any extra work from the user is an endless endeavor that won't ever really work -too well. It's not that we don't support them at all, but it's almost impossible -for us to support them. +This plugin uses [php-build](https://github.com/php-build/php-build) as the underlying build system and provides: -## Installation +- **Legacy PHP Support**: Automatic OpenSSL 1.1 installation for PHP 7.4 and 8.0.x versions +- **Modern Compatibility**: Built-in patches for libxml2 2.12+ and ICU 74+ compatibility +- **Cross-Platform**: Works on Linux, macOS with proper dependency management + +## Dependencies + +### Linux - Ubuntu/Debian example +```bash +sudo apt-get install -y autoconf bison build-essential curl gettext git \ + libgd-dev libcurl4-openssl-dev libedit-dev libicu-dev libjpeg-dev \ + libmysqlclient-dev libonig-dev libpng-dev libpq-dev libreadline-dev \ + libsqlite3-dev libssl-dev libxml2-dev libzip-dev libtidy-dev openssl \ + pkg-config re2c zlib1g-dev ca-certificates wget libtool automake \ + gcc g++ libxslt1-dev m4 libtool-bin +``` + +### macOS +```bash +brew install autoconf automake bison freetype gd gettext icu4c krb5 \ + libedit libiconv libjpeg libpng libxml2 libzip openssl@1.1 openssl@3 \ + pkg-config re2c zlib bzip2 +``` +**Optional extensions:** ```bash -asdf plugin-add php https://github.com/asdf-community/asdf-php.git +brew install gmp libsodium imagemagick tidy-html5 ``` -## Note +## Installation -Composer is installed globally alongside PHP by default. If your application requires additional php extensions, you may need to install them via `pecl`. For example: +After installing [asdf](https://asdf-vm.com/guide/getting-started.html), install the plugin by running: ```bash -pecl install redis -pecl install imagick +asdf plugin add php https://github.com/asdf-community/asdf-php.git +``` -echo "extension=redis.so -extension=imagick.so" > $(asdf where php)/conf.d/php.ini +or update an existing installation: + +```bash +asdf plugin update php ``` -#### macOS +Then use `asdf-php` to manage php: -To install PHP on macOS, you'll need a set of packages [installed via homebrew](https://github.com/asdf-community/asdf-php/blob/248e9c6e2a7824510788f05e8cee848a62200b65/.github/workflows/workflow.yml#L52). +```bash +# Show all installable versions +asdf list all php -There's also a set of optional packages which enable additional extensions to be enabled: +# Install specific version +asdf install php 8.5.0 +# or install latest tagged version with +asdf install php latest + +# Set a version globally (in ~/.tool-versions) +asdf set php latest + +# Or set locally for a project (in ./.tool-versions) +asdf local php 8.3.29 ``` -brew install gmp libsodium imagemagick + +## Legacy PHP Support (7.4, 8.0.x) + +This plugin automatically handles legacy PHP versions that require OpenSSL 1.1: + +```bash +# Automatic OpenSSL 1.1 installation (recommended) +ASDF_PHP_OPENSSL_AUTO=yes asdf install php 7.4.33 +ASDF_PHP_OPENSSL_AUTO=yes asdf install php 8.0.30 + +# Manual OpenSSL 1.1 installation +./lib/install-openssl11.sh --auto +asdf install php 7.4.33 ``` -Note that the supported extension are not exhaustive, so you may need edit the `bin/install` script to support additional extension. Feel free to submit a PR for any missing extensions. +The plugin includes compatibility patches for: +- **libxml2 2.12+** compatibility for PHP 7.4 and 8.0.x +- **ICU 74+** compatibility for PHP 7.4 and 8.0.x -#### PHP-PEAR +## Environment Variables -If PHP PEAR is down you can install PHP without PEAR. Specify `PHP_WITHOUT_PEAR` variable with any value -(except no), eg: +### OpenSSL Configuration +```bash +# Enable automatic OpenSSL 1.1 installation for legacy PHP +export ASDF_PHP_OPENSSL_AUTO=yes + +# Manual OpenSSL paths (if needed) +export PKG_CONFIG_PATH="/path/to/openssl-1.1/lib/pkgconfig:$PKG_CONFIG_PATH" +export PHP_BUILD_CONFIGURE_OPTS="--with-openssl=/path/to/openssl-1.1" +``` +### Build Configuration ```bash -PHP_WITHOUT_PEAR=yes asdf install php +# Disable PEAR installation +export PHP_WITHOUT_PEAR=yes + +# Disable Xdebug during build +export PHP_BUILD_XDEBUG_ENABLE=off + +# Custom compiler flags +export CFLAGS="-Wno-error -O2" +export CXXFLAGS="-Wno-error -O2" ``` -## Usage +## Extensions and Composer -Check [asdf](https://github.com/asdf-vm/asdf) readme for instructions on how to -install & manage versions. +**Composer** is installed globally alongside PHP by default. -## Global Composer Dependencies +**PECL Extensions:** +```bash +pecl install redis imagick -Composer is installed globally by default. To use it, you'll need to reshim: +# Add to PHP configuration +echo "extension=redis.so +extension=imagick.so" >> $(asdf where php)/conf.d/extensions.ini +``` -```shell +**Global Composer packages:** +```bash composer global require friendsofphp/php-cs-fixer -asdf reshim +asdf reshim php php-cs-fixer --version ``` +## Usage + +### Version Management +```bash +# List installed versions +asdf list php + +# Show current version +asdf current php + +# Switch versions +asdf set php 8.3.29 # Set globally +asdf local php 7.4.33 # Set for current project + +# Uninstall a version +asdf uninstall php 8.0.30 +``` + +### Monitoring Installation +```bash +# Watch build progress +tail -f /tmp/php-build.*.log + +# Verbose installation +VERBOSE=y asdf install php 8.3.29 +``` + +## Troubleshooting + +### Legacy PHP Build Issues +```bash +# For PHP 7.4/8.0.x compilation errors: +export ASDF_PHP_OPENSSL_AUTO=yes +export CFLAGS="-Wno-error -Wno-deprecated-declarations -O2" +export CXXFLAGS="-Wno-error -Wno-deprecated-declarations -O2" +asdf install php 7.4.33 +``` + +### macOS Issues +```bash +# If configure can't find libraries: +export LDFLAGS="-L$(brew --prefix)/lib" +export CPPFLAGS="-I$(brew --prefix)/include" +export PKG_CONFIG_PATH="$(brew --prefix icu4c)/lib/pkgconfig:$PKG_CONFIG_PATH" +``` + +### Manual OpenSSL 1.1 Installation +```bash +# Install OpenSSL 1.1 manually if automatic installation fails +./lib/install-openssl11.sh --help +OPENSSL_SHA256=cf3098950... ./lib/install-openssl11.sh --auto +``` + +For more examples, see [docs/local-testing-manually.md](docs/local-testing-manually.md). + ## License Licensed under the diff --git a/bin/exec-env b/bin/exec-env index 012fc6f..c992a30 100755 --- a/bin/exec-env +++ b/bin/exec-env @@ -6,3 +6,62 @@ if [ "$ASDF_INSTALL_VERSION" != "system" ]; then export COMPOSER_HOME=$ASDF_INSTALL_PATH/.composer fi fi + +# Automatically set LD_LIBRARY_PATH for PHP versions that need OpenSSL 1.1 +check_and_set_openssl_path() { + local php_version="$ASDF_INSTALL_VERSION" + + # Skip for system version + if [ "$php_version" = "system" ]; then + return 0 + fi + + # Extract version components + local php_major + php_major=$(echo "$php_version" | cut -d. -f1) + local php_minor + php_minor=$(echo "$php_version" | cut -d. -f2) + local php_patch + php_patch=$(echo "$php_version" | cut -d. -f3 | sed 's/[^0-9].*//') + + # Determine if this PHP version needs OpenSSL 1.1 + local needs_openssl1=false + + if [ "$php_major" -eq 7 ]; then + needs_openssl1=true + elif [ "$php_major" -eq 8 ] && [ "$php_minor" -eq 0 ]; then + if [ "${php_patch:-0}" -lt 20 ]; then + needs_openssl1=true + fi + elif [ "$php_major" -eq 8 ] && [ "$php_minor" -eq 1 ]; then + if [ "${php_patch:-0}" -lt 6 ]; then + needs_openssl1=true + fi + fi + + if [ "$needs_openssl1" != true ]; then + return 0 + fi + + # Try to find OpenSSL 1.1 installation + local openssl11_paths=( + "$HOME/.local/openssl-1.1" + "/usr/local/openssl-1.1" + "/usr/local/openssl@1.1" + "/usr/local/opt/openssl@1.1" + "/opt/openssl-1.1" + "/usr/lib64/openssl11" + ) + + for path in "${openssl11_paths[@]}"; do + if [ -d "$path/lib" ]; then + # Add to LD_LIBRARY_PATH if not already present + if [[ ":$LD_LIBRARY_PATH:" != *":$path/lib:"* ]]; then + export LD_LIBRARY_PATH="$path/lib:${LD_LIBRARY_PATH}" + fi + break + fi + done +} + +check_and_set_openssl_path diff --git a/bin/install b/bin/install index c40be12..cba19b6 100755 --- a/bin/install +++ b/bin/install @@ -1,413 +1,825 @@ #!/usr/bin/env bash - set -eo pipefail -install_php() { - local install_type=$1 - local version=$2 - local install_path=$3 +# Get the plugin root directory +PLUGIN_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" - if [ "$TMPDIR" = "" ]; then - local tmp_download_dir=$(mktemp -d) - else - local tmp_download_dir=${TMPDIR%/} +# Setup php-build +PHP_BUILD_ROOT="$PLUGIN_DIR/vendor/php-build" + +# Check if php-build exists, if not try to initialize submodule +if [ ! -f "$PHP_BUILD_ROOT/bin/php-build" ]; then + echo "php-build not found, attempting to initialize submodule..." + + # Try to initialize the submodule if we're in a git repository + if [ -d "$PLUGIN_DIR/.git" ] || [ -f "$PLUGIN_DIR/.git" ]; then + ( + cd "$PLUGIN_DIR" + git submodule update --init --recursive vendor/php-build + ) fi - echo "Determining configuration options..." - local source_path=$(get_download_file_path $install_type $version $tmp_download_dir) - local configure_options="$(construct_configure_options $install_path)" - local make_flags="-j$ASDF_CONCURRENCY" + # Check again after submodule init + if [ ! -f "$PHP_BUILD_ROOT/bin/php-build" ]; then + echo "Error: php-build not found at $PHP_BUILD_ROOT" + echo "" + echo "This plugin requires php-build to be available." + echo "" + echo "If you cloned this plugin, run:" + echo " cd $PLUGIN_DIR" + echo " git submodule update --init --recursive" + echo "" + echo "If you used 'asdf plugin add php ', please reinstall using:" + echo " asdf plugin remove php" + echo " asdf plugin add php https://github.com/asdf-community/asdf-php.git" + exit 1 + fi +fi + +export PATH="$PHP_BUILD_ROOT/bin:$PATH" + +# Extract version from ASDF variables +version="${ASDF_INSTALL_VERSION}" +install_path="${ASDF_INSTALL_PATH}" + +# Handle "latest" version resolution +if [ "$version" = "latest" ]; then + version=$("${PLUGIN_DIR}/bin/latest-stable") + echo "Resolved 'latest' to version $version" +fi + +# Homebrew keg-only packages like bzip2 and libiconv may have a broken or empty opt +# symlink because Homebrew does not auto-link them; repairing it in-place ensures that +# both the build-time rpath and macOS's dyld use the same opt path at runtime, since +# the library's embedded install_name was set to the opt path when it was originally built +resolve_brew_package_prefix() { + local package=$1 + local opt_prefix + opt_prefix=$(brew --prefix "$package" 2>/dev/null || echo "") + + if [ -z "$opt_prefix" ]; then + echo "" + return 0 + fi - local operating_system=$(uname -a) + # When the opt symlink exists but its include directory is absent the symlink is stale; + # replacing it with a direct symlink to the versioned Cellar directory makes rpath in + # compiled binaries resolve correctly at runtime because macOS dyld follows the + # library's embedded install_name which was originally set to the opt path + if [ ! -d "${opt_prefix}/include" ]; then + local cellar_base + cellar_base=$(brew --cellar "$package" 2>/dev/null || echo "") + if [ -n "$cellar_base" ] && [ -d "$cellar_base" ]; then + local cellar_version_dir + cellar_version_dir=$(find "$cellar_base" -maxdepth 1 -mindepth 1 -type d | head -1) + if [ -n "$cellar_version_dir" ] && [ -d "${cellar_version_dir}/include" ]; then + # Remove the stale opt entry and replace it with a proper symlink so that the + # opt path used in -Wl,-rpath matches the install_name baked into the dylib + rm -rf "$opt_prefix" + ln -s "$cellar_version_dir" "$opt_prefix" + fi + fi + fi - if [[ $operating_system =~ "Darwin" ]]; then - exit_if_homebrew_not_installed + echo "$opt_prefix" +} - local bison_path=$(homebrew_package_path bison) - local icu4c_path=$(homebrew_package_path icu4c) - local krb5_path=$(homebrew_package_path krb5) - local libedit_path=$(homebrew_package_path libedit) - local libxml2_path=$(homebrew_package_path libxml2) - local openssl_path=$(homebrew_package_path openssl@1.1) +# Configure macOS-specific dependencies for all PHP versions +setup_macos_dependencies() { + local php_version=$1 - if [ -n "$bison_path" ]; then - export "PATH=${bison_path}/bin:${PATH}" - else - export ASDF_PKG_MISSING="$ASDF_PKG_MISSING bison" - fi + # Only apply on macOS + if [[ $OSTYPE != "darwin"* ]]; then + return 0 + fi - if [ -n "$icu4c_path" ]; then - export "PKG_CONFIG_PATH=${icu4c_path}/lib/pkgconfig:${PKG_CONFIG_PATH}" - else - export ASDF_PKG_MISSING="$ASDF_PKG_MISSING icu4c" - fi + # Check if Homebrew is available + if ! command -v brew &>/dev/null; then + return 0 + fi - if [ -n "$krb5_path" ]; then - export "PKG_CONFIG_PATH=${krb5_path}/lib/pkgconfig:${PKG_CONFIG_PATH}" - else - export ASDF_PKG_MISSING="$ASDF_PKG_MISSING krb5" + local brew_prefix + brew_prefix=$(brew --prefix) + local bzip2_prefix + bzip2_prefix=$(resolve_brew_package_prefix bzip2) + local iconv_prefix + iconv_prefix=$(resolve_brew_package_prefix libiconv) + + # Configure bzip2 if available (guard against duplication when already set by CI/env) + if [ -n "$bzip2_prefix" ] && [ -d "$bzip2_prefix" ]; then + if [[ ${PHP_BUILD_CONFIGURE_OPTS:-} != *"--with-bz2="* ]]; then + export PHP_BUILD_CONFIGURE_OPTS="${PHP_BUILD_CONFIGURE_OPTS} --with-bz2=${bzip2_prefix}" fi + fi - if [ -n "$libedit_path" ]; then - export "PKG_CONFIG_PATH=${libedit_path}/lib/pkgconfig:${PKG_CONFIG_PATH}" - else - export ASDF_PKG_MISSING="$ASDF_PKG_MISSING libedit" + # Configure libiconv if available (guard against duplication when already set by CI/env) + if [ -n "$iconv_prefix" ] && [ -d "$iconv_prefix" ]; then + if [[ ${PHP_BUILD_CONFIGURE_OPTS:-} != *"--with-iconv="* ]]; then + export PHP_BUILD_CONFIGURE_OPTS="${PHP_BUILD_CONFIGURE_OPTS} --with-iconv=${iconv_prefix}" fi + fi - if [ -n "$libxml2_path" ]; then - export "PKG_CONFIG_PATH=${libxml2_path}/lib/pkgconfig:${PKG_CONFIG_PATH}" - else - export ASDF_PKG_MISSING="$ASDF_PKG_MISSING libxml2" - fi + # Set library and include paths (idempotent: skip if already present to avoid duplicates) + if [[ ${LDFLAGS:-} != *"-L${brew_prefix}/lib"* ]]; then + export LDFLAGS="${LDFLAGS:+${LDFLAGS} }-L${brew_prefix}/lib" + fi + if [[ ${CPPFLAGS:-} != *"-I${brew_prefix}/include"* ]]; then + export CPPFLAGS="${CPPFLAGS:+${CPPFLAGS} }-I${brew_prefix}/include" + fi +} - if [ -n "$openssl_path" ]; then - export "PKG_CONFIG_PATH=${openssl_path}/lib/pkgconfig:${PKG_CONFIG_PATH}" - else - export ASDF_PKG_MISSING="$ASDF_PKG_MISSING openssl" - fi +# php-build's configure_package sets CXXFLAGS="-std=c++11 -stdlib=libc++ -DU_USING_ICU_NAMESPACE=1" +# when CXXFLAGS is empty and icu4c > 61 is present on macOS (see php-build#499). For PHP 8.5+ +# that -std=c++11 flag causes configure's C++17 feature check to fail because the last -std= flag +# wins when multiple ones are present in the compiler invocation. We must either strip the stale +# flag from an inherited CXXFLAGS, or — when CXXFLAGS is empty — pre-populate it with the same +# ICU compatibility flags php-build would use but with -std=c++17, so that php-build's +# [[ -z "$CXXFLAGS" ]] guard never fires and the c++11 override never happens. +sanitize_cxxflags_for_modern_php() { + local php_version=$1 + local php_major + php_major=$(echo "$php_version" | cut -d. -f1) + local php_minor + php_minor=$(echo "$php_version" | cut -d. -f2) + + # Only PHP 8.5+ introduced the C++17 build requirement + local requires_cxx17=false + if [ "$php_major" -gt 8 ]; then + requires_cxx17=true + elif [ "$php_major" -eq 8 ] && [ "$php_minor" -ge 5 ]; then + requires_cxx17=true fi - echo "Downloading source code..." - download_source $install_type $version $source_path + if [ "$requires_cxx17" != true ]; then + return 0 + fi - # Running this in a subshell because we don't to disturb the current - # working directory. - ( - echo "Extracting source code..." - cd $(dirname $source_path) - tar -zxf $source_path || exit 1 - - cd $(untar_path $install_type $version $tmp_download_dir) - - # Target is OS-specific - # target=$(get_target) - - # Build PHP - echo "Running buildconfig..." - ./buildconf --force || exit 1 - echo "Running ./configure $configure_options" - ./configure $configure_options || exit 1 - echo "Running make \"$make_flags\"" - make "$make_flags" || exit 1 - echo "Running make install..." - # make "$make_flags" test || exit 1 - make "$make_flags" install || exit 1 - ) + # Strip any existing -std=c++ flag to prevent old standards from blocking the C++17 check + local stripped_cxxflags + stripped_cxxflags=$(echo "${CXXFLAGS:-}" | sed 's/-std=c++[^ ]*//g' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + + # When CXXFLAGS was empty, php-build would normally inject "-std=c++11 -stdlib=libc++ + # -DU_USING_ICU_NAMESPACE=1" (for icu4c > 61) or "-std=c++11 -stdlib=libc++" (for icu4c > 59). + # Mirror that logic here with -std=c++17 so php-build's [[ -z "$CXXFLAGS" ]] guard is never + # triggered and the c++11 flag is never written into the configure environment. + if [ -z "$stripped_cxxflags" ] && [[ $OSTYPE == "darwin"* ]] && command -v brew >/dev/null 2>&1; then + local icu_prefix + icu_prefix=$(brew --prefix icu4c 2>/dev/null || echo "") + if [ -n "$icu_prefix" ] && [ -e "$icu_prefix" ]; then + local icu_version icu_major + icu_version=$("${icu_prefix}/bin/icu-config" --version 2>/dev/null || echo "0") + icu_major=$(echo "$icu_version" | cut -d. -f1) + if [ "$icu_major" -gt 61 ]; then + stripped_cxxflags="-stdlib=libc++ -DU_USING_ICU_NAMESPACE=1" + elif [ "$icu_major" -gt 59 ]; then + stripped_cxxflags="-stdlib=libc++" + fi + fi + fi - # it's not obvious where php.ini should be placed, let us make it easy for the user - mkdir -p $install_path/conf.d/ - echo "# add system-wide php configuration options here" >$install_path/conf.d/php.ini + # Set C++17 explicitly as it is the minimum standard PHP 8.5+ requires to build + if [ -n "$stripped_cxxflags" ]; then + export CXXFLAGS="${stripped_cxxflags} -std=c++17" + else + export CXXFLAGS="-std=c++17" + fi } -install_composer() { - local bin_path=$1/bin - local expected_signature="$(curl -sL https://composer.github.io/installer.sig)" +# Check OpenSSL version and PHP version compatibility +check_openssl_compatibility() { + local php_version=$1 - $bin_path/php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" - $bin_path/php -r "if (hash_file('sha384', 'composer-setup.php') === '${expected_signature}') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" - $bin_path/php composer-setup.php --install-dir=$bin_path --filename=composer - $bin_path/php -r "unlink('composer-setup.php');" -} + # Get system OpenSSL version + if ! command -v openssl &>/dev/null; then + echo "⚠ Warning: OpenSSL not found in PATH" + return 0 + fi -construct_configure_options() { - local install_path=$1 - - # many options included below are not applicable to newer PHP versions - # including these will trigger a build warning, but will not b - global_config="--prefix=$install_path \ - --enable-bcmath \ - --enable-calendar \ - --enable-dba \ - --enable-exif \ - --enable-fpm \ - --enable-ftp \ - --enable-gd \ - --enable-gd-native-ttf \ - --enable-intl \ - --enable-mbregex \ - --enable-mbstring \ - --enable-mysqlnd \ - --enable-pcntl \ - --enable-shmop \ - --enable-soap \ - --enable-sockets \ - --enable-sysvmsg \ - --enable-sysvsem \ - --enable-sysvshm \ - --enable-wddx \ - --enable-zip \ - --sysconfdir=$install_path \ - --with-config-file-path=$install_path \ - --with-config-file-scan-dir=$install_path/conf.d \ - --with-curl \ - --with-external-gd \ - --with-fpm-group=www-data \ - --with-fpm-user=www-data \ - --with-gd \ - --with-mhash \ - --with-mysql=mysqlnd \ - --with-mysqli=mysqlnd \ - --with-pdo-mysql=mysqlnd \ - --with-xmlrpc \ - --with-zip \ - --with-zlib \ - --without-snmp" - - if [ "$PHP_CONFIGURE_OPTIONS" = "" ]; then - local configure_options="$(os_based_configure_options) $global_config" - else - local configure_options="$PHP_CONFIGURE_OPTIONS $global_config" + local openssl_full_version + openssl_full_version=$(openssl version 2>/dev/null | awk '{print $2}') + local openssl_major + openssl_major=$(echo "$openssl_full_version" | cut -d. -f1) + + # Only check if OpenSSL 3.x is detected + if [ "$openssl_major" -lt 3 ]; then + return 0 fi - if [ "${PHP_WITHOUT_PEAR:-no}" != "no" ]; then - configure_options="$configure_options --without-pear" - else - configure_options="$configure_options --with-pear" + # Extract PHP version components + local php_major + php_major=$(echo "$php_version" | cut -d. -f1) + local php_minor + php_minor=$(echo "$php_version" | cut -d. -f2) + local php_patch + php_patch=$(echo "$php_version" | cut -d. -f3 | sed 's/[^0-9].*//') + + # Determine if this PHP version needs OpenSSL 1.1 + local needs_openssl1=false + local has_other_issues=false + + if [ "$php_major" -eq 7 ]; then + needs_openssl1=true + elif [ "$php_major" -eq 8 ] && [ "$php_minor" -eq 0 ]; then + if [ "${php_patch:-0}" -lt 20 ]; then + needs_openssl1=true + # PHP 8.0.0-8.0.5 have additional compatibility issues + if [ "${php_patch:-0}" -lt 6 ]; then + has_other_issues=true + fi + fi + elif [ "$php_major" -eq 8 ] && [ "$php_minor" -eq 1 ]; then + if [ "${php_patch:-0}" -lt 6 ]; then + needs_openssl1=true + fi fi - if [ "${PHP_WITHOUT_PDO_PGSQL:-no}" != "no" ]; then - configure_options="$configure_options" + if [ "$needs_openssl1" != true ]; then + return 0 + fi + + # Display compatibility warning + echo "⚠ PHP ${php_version} incompatible with OpenSSL ${openssl_full_version}" + + # Warn about other compatibility issues + if [ "$has_other_issues" = true ]; then + echo "⚠ PHP 8.0.0-8.0.5 have additional compatibility issues with modern systems" + echo " Recommended: Use PHP 8.0.30 (latest 8.0.x) instead" + fi + + # Try to find existing OpenSSL 1.1 installation + local openssl11_paths=( + "$HOME/.local/openssl-1.1" + "/usr/local/openssl-1.1" + "/usr/local/openssl@1.1" + "/usr/local/opt/openssl@1.1" + "/opt/openssl-1.1" + "/usr/lib64/openssl11" + ) + + local openssl11_path="" + for path in "${openssl11_paths[@]}"; do + if [ -f "$path/bin/openssl" ]; then + openssl11_path="$path" + break + fi + done + + if [ -n "$openssl11_path" ]; then + # Found OpenSSL 1.1 - configure build to use it + local found_version + found_version=$(LD_LIBRARY_PATH="${openssl11_path}/lib:${LD_LIBRARY_PATH}" "$openssl11_path/bin/openssl" version 2>/dev/null | awk '{print $2}') + echo "✓ Using OpenSSL 1.1 (${found_version}) at ${openssl11_path}" + export PKG_CONFIG_PATH="${openssl11_path}/lib/pkgconfig:${PKG_CONFIG_PATH}" + export PHP_BUILD_CONFIGURE_OPTS="${PHP_BUILD_CONFIGURE_OPTS} --with-openssl=${openssl11_path}" + export OPENSSL_CFLAGS="-I${openssl11_path}/include" + export OPENSSL_LIBS="-L${openssl11_path}/lib -lssl -lcrypto" + export LD_LIBRARY_PATH="${openssl11_path}/lib:${LD_LIBRARY_PATH}" else - local operating_system=$(uname -a) + # OpenSSL 1.1 not found - offer to install + echo "✗ OpenSSL 1.1 not found" + echo "" + echo "php-build's OpenSSL 3 patch is incomplete for PHP ${php_version}." + echo "OpenSSL 1.1 is required to build this PHP version." + echo "" - if [[ $operating_system =~ "Darwin" ]]; then - exit_if_homebrew_not_installed + # Debug environment variables + echo "=== OpenSSL Auto-install Debug ===" + echo "CI=${CI:-false}" + echo "GITHUB_ACTIONS=${GITHUB_ACTIONS:-false}" + echo "ASDF_PHP_OPENSSL_AUTO=${ASDF_PHP_OPENSSL_AUTO:-no}" - local libpq_path=$(homebrew_package_path libpq) + # Auto-install in CI environments or if ASDF_PHP_OPENSSL_AUTO is set + if [ "${CI:-false}" = "true" ] || [ "${GITHUB_ACTIONS:-false}" = "true" ] || [ "${ASDF_PHP_OPENSSL_AUTO:-no}" != "no" ]; then + echo "Auto-installing OpenSSL 1.1 (CI environment detected)..." + REPLY="y" + else + echo "No auto-install conditions met, prompting user..." + read -p "Install OpenSSL 1.1 automatically? [y/N] " -n 1 -r + echo + fi - if [ -n "$libpq_path" ]; then - configure_options="$configure_options --with-pdo-pgsql=$libpq_path" + if [[ $REPLY =~ ^[Yy]$ ]]; then + echo "Installing OpenSSL 1.1..." + echo "=== OpenSSL Installation Debug ===" + echo "PLUGIN_DIR=${PLUGIN_DIR}" + echo "Installation script: ${PLUGIN_DIR}/lib/install-openssl11.sh" + + # Check if installation script exists + if [ ! -f "${PLUGIN_DIR}/lib/install-openssl11.sh" ]; then + echo "✗ OpenSSL installation script not found: ${PLUGIN_DIR}/lib/install-openssl11.sh" + exit 1 + fi + + echo "Running OpenSSL 1.1 installation..." + if "${PLUGIN_DIR}/lib/install-openssl11.sh" --auto; then + echo "" + echo "✓ OpenSSL 1.1 installed successfully" + echo "Configuring build to use OpenSSL 1.1..." + + # Set environment variables for this build + openssl11_path="${INSTALL_PREFIX:-$HOME/.local/openssl-1.1}" + echo "OpenSSL 1.1 path: ${openssl11_path}" + + # Verify installation + if [ -f "${openssl11_path}/bin/openssl" ]; then + installed_version=$(LD_LIBRARY_PATH="${openssl11_path}/lib:${LD_LIBRARY_PATH}" "${openssl11_path}/bin/openssl" version 2>/dev/null | awk '{print $2}') + echo "✓ Verified OpenSSL 1.1 installation: ${installed_version}" + else + echo "✗ OpenSSL 1.1 binary not found after installation" + exit 1 + fi + + export PKG_CONFIG_PATH="${openssl11_path}/lib/pkgconfig:${PKG_CONFIG_PATH}" + export PHP_BUILD_CONFIGURE_OPTS="${PHP_BUILD_CONFIGURE_OPTS} --with-openssl=${openssl11_path}" + export OPENSSL_CFLAGS="-I${openssl11_path}/include" + export OPENSSL_LIBS="-L${openssl11_path}/lib -lssl -lcrypto" + export LD_LIBRARY_PATH="${openssl11_path}/lib:${LD_LIBRARY_PATH}" + echo "" else - export ASDF_PKG_MISSING="$ASDF_PKG_MISSING libpq" + echo "✗ OpenSSL 1.1 installation failed" + echo "Check the installation log above for details" + exit 1 fi else - configure_options="$configure_options --with-pdo-pgsql" + echo "" + echo "Cannot proceed without OpenSSL 1.1." + echo "To install manually, run: ${PLUGIN_DIR}/lib/install-openssl11.sh" + echo "" + exit 1 fi fi +} - if [ "${PHP_WITHOUT_PCRE_JIT:-no}" != "no" ]; then - configure_options="$configure_options" - else - configure_options="$configure_options --without-pcre-jit" +# Configure macOS dependencies first (applies to all PHP versions) +setup_macos_dependencies "$version" + +# Check compatibility before building +check_openssl_compatibility "$version" + +# Configure compiler for older PHP versions that need special handling +setup_compiler_for_old_php() { + local php_version=$1 + local php_major + php_major=$(echo "$php_version" | cut -d. -f1) + local php_minor + php_minor=$(echo "$php_version" | cut -d. -f2) + local php_patch + php_patch=$(echo "$php_version" | cut -d. -f3 | sed 's/[^0-9].*//') + + # Only apply special compiler setup for problematic old versions + if ! { [ "$php_major" -eq 7 ] && [ "$php_minor" -eq 4 ]; } && ! { [ "$php_major" -eq 8 ] && [ "$php_minor" -eq 0 ] && [ "${php_patch:-0}" -lt 20 ]; }; then + return 0 fi - echo "$configure_options" -} + echo "=== Configuring compiler for PHP ${php_version} ===" -homebrew_package_path() { - local package_name=$1 + # Try to use gcc-9 if available, otherwise fallback to system gcc + local target_cc="gcc" + local target_cxx="g++" - if [ "$(brew ls --versions $package_name)" = "" ]; then - echo "" + if command -v gcc-9 >/dev/null 2>&1 && command -v g++-9 >/dev/null 2>&1; then + target_cc="gcc-9" + target_cxx="g++-9" + echo "Using gcc-9/g++-9 for better compatibility" else - echo "$(brew --prefix $package_name)" + echo "gcc-9 not available, using system gcc" fi -} -exit_if_homebrew_not_installed() { - if [ "$(brew --version 2>/dev/null)" = "" ]; then - echo "ERROR: Please install homebrew for OSX" + # Set compiler environment variables + export CC="$target_cc" + export CXX="$target_cxx" + + # Set autoconf cache variables to ensure configure script uses our chosen compiler + export ac_cv_prog_CC="$target_cc" + export ac_cv_prog_CXX="$target_cxx" + + # Verify compiler works before proceeding + echo "Testing compiler functionality..." + if ! command -v "$target_cc" >/dev/null 2>&1; then + echo "✗ Compiler $target_cc not found" exit 1 fi -} -os_based_configure_options() { - local operating_system=$(uname -a) - local configure_options="" - - if [[ $operating_system =~ "Darwin" ]]; then - - exit_if_homebrew_not_installed - - local bison_path=$(homebrew_package_path bison) - local bzip2_path=$(homebrew_package_path bzip2) - local freetype_path=$(homebrew_package_path freetype) - local gettext_path=$(homebrew_package_path gettext) - local iconv_path=$(homebrew_package_path libiconv) - local icu4c_path=$(homebrew_package_path icu4c) - local jpeg_path=$(homebrew_package_path jpeg) - local libedit_path=$(homebrew_package_path libedit) - local libpng_path=$(homebrew_package_path libpng) - local libxml2_path=$(homebrew_package_path libxml2) - local libzip_path=$(homebrew_package_path libzip) - local openssl_path=$(homebrew_package_path openssl@1.1) - local readline_path=$(homebrew_package_path readline) - local webp_path=$(homebrew_package_path webp) - local zlib_path=$(homebrew_package_path zlib) - - # optional - # if these packages exist they are included in the php compilation - local gmp_path=$(homebrew_package_path gmp) - local sodium_path=$(homebrew_package_path libsodium) - - if [ -n "$gmp_path" ]; then - configure_options="--with-gmp=$gmp_path" - else - echo "gmp not found, not including in installation" - fi + # Test basic compilation + local test_c_file test_exe_file + test_c_file=$(mktemp) + test_exe_file="${test_c_file}_exe" + mv "$test_c_file" "${test_c_file}.c" + test_c_file="${test_c_file}.c" + echo 'int main() { return 0; }' >"$test_c_file" + + if "$target_cc" -o "$test_exe_file" "$test_c_file" 2>/dev/null; then + echo "✓ Compiler test successful" + rm -f "$test_c_file" "$test_exe_file" + else + echo "✗ Compiler test failed" + rm -f "$test_c_file" "$test_exe_file" + exit 1 + fi - if [ -n "$sodium_path" ]; then - configure_options="$configure_options --with-sodium=$sodium_path" + # Set conservative build flags for old PHP versions (only if not already set) + if [ "$php_major" -eq 7 ] && [ "$php_minor" -eq 4 ]; then + # PHP 7.4 specific flags + export CFLAGS="${CFLAGS:--Wno-error -Wno-deprecated-declarations -Wno-implicit-function-declaration -Wno-int-conversion -Wno-stringop-overflow -O0}" + export CXXFLAGS="${CXXFLAGS:--Wno-error -Wno-deprecated-declarations -O0}" + + # Platform-specific configuration + if [[ $OSTYPE == "darwin"* ]]; then + # macOS specific configuration (bzip2/iconv already configured by setup_macos_dependencies) + if command -v brew >/dev/null 2>&1; then + export PHP_BUILD_CONFIGURE_OPTS="${PHP_BUILD_CONFIGURE_OPTS} --disable-werror --with-external-pcre --disable-fileinfo" + # Library paths already set by setup_macos_dependencies + if [ -z "$LDFLAGS" ]; then + local brew_lib_path + brew_lib_path=$(brew --prefix) + export LDFLAGS="-L${brew_lib_path}/lib" + fi + if [ -z "$CPPFLAGS" ]; then + local brew_include_path + brew_include_path=$(brew --prefix) + export CPPFLAGS="-I${brew_include_path}/include" + fi + else + export PHP_BUILD_CONFIGURE_OPTS="${PHP_BUILD_CONFIGURE_OPTS} --disable-werror --with-external-pcre --disable-fileinfo" + fi else - echo "sodium not found, not including in installation" + # Linux specific configuration + export LDFLAGS="${LDFLAGS:+${LDFLAGS} }-Wl,--no-as-needed" + export PHP_BUILD_CONFIGURE_OPTS="${PHP_BUILD_CONFIGURE_OPTS} --disable-werror --with-external-pcre --disable-fileinfo --without-tidy" fi - - if [ -n "$freetype_path" ]; then - configure_options="$configure_options --with-freetype-dir=$freetype_path" + export MAKE_OPTS="${MAKE_OPTS:--j1}" + elif [ "$php_major" -eq 8 ] && [ "$php_minor" -eq 0 ] && [ "${php_patch:-0}" -lt 20 ]; then + # PHP 8.0.0-8.0.19 specific flags + # -Wno-incompatible-pointer-types-discards-qualifiers: Apple Clang 16 (Xcode 16 / + # macOS 15 GitHub runners) treats const-qualifier discards as a hard error. PHP 8.0.x + # openssl.c passes const RSA * where non-const RSA * is required with OpenSSL 3.6+. + export CFLAGS="${CFLAGS:--Wno-error -Wno-deprecated-declarations -Wno-implicit-function-declaration -Wno-int-conversion -Wno-stringop-overflow -Wno-incompatible-pointer-types-discards-qualifiers -O0}" + # Also append the flag when CFLAGS was preset externally without it + if [[ ${CFLAGS} != *"-Wno-incompatible-pointer-types-discards-qualifiers"* ]]; then + export CFLAGS="${CFLAGS} -Wno-incompatible-pointer-types-discards-qualifiers" + fi + export CXXFLAGS="${CXXFLAGS:--Wno-error -Wno-deprecated-declarations -O0}" + + # Platform-specific configuration + if [[ $OSTYPE == "darwin"* ]]; then + # macOS specific configuration (bzip2/iconv already configured by setup_macos_dependencies) + if command -v brew >/dev/null 2>&1; then + # ICU 74+ headers require C++17 features (enable_if_t, void_t, is_same_v, auto in + # template parameters). setup_compiler_for_old_php sets CXXFLAGS before php-build + # runs, so php-build's [[ -z "$CXXFLAGS" ]] guard never fires and its own + # "-std=c++11 -stdlib=libc++ -DU_USING_ICU_NAMESPACE=1" is never added. We must + # therefore inject the equivalent flags here, upgrading c++11 to c++17. + # Guard against duplication when CXXFLAGS was pre-set by CI workflow. + if [[ ${CXXFLAGS:-} != *"-std=c++17"* ]]; then + export CXXFLAGS="${CXXFLAGS:+${CXXFLAGS} }-std=c++17 -stdlib=libc++ -DU_USING_ICU_NAMESPACE=1" + elif [[ ${CXXFLAGS:-} != *"-DU_USING_ICU_NAMESPACE=1"* ]]; then + export CXXFLAGS="${CXXFLAGS} -DU_USING_ICU_NAMESPACE=1" + fi + + # --with-external-gd tells PHP to link Homebrew's pre-compiled libgd instead of + # compiling the bundled libgd source. Without this, CPPFLAGS=-I/usr/local/include + # causes Homebrew's gd.h (interpolation_method takes 2 doubles) to shadow the + # bundled libgd/gd.h (1 double), producing incompatible-function-pointer-types and + # too-few-arguments hard errors in gd_interpolation.c on Apple Clang 16+. + local gd_prefix + gd_prefix=$(brew --prefix gd 2>/dev/null || echo "") + if [ -n "$gd_prefix" ] && [ -d "$gd_prefix" ]; then + export PKG_CONFIG_PATH="${PKG_CONFIG_PATH:+${PKG_CONFIG_PATH}:}${gd_prefix}/lib/pkgconfig" + export PHP_BUILD_CONFIGURE_OPTS="${PHP_BUILD_CONFIGURE_OPTS} --disable-werror --with-external-pcre --disable-fileinfo --with-external-gd" + else + export PHP_BUILD_CONFIGURE_OPTS="${PHP_BUILD_CONFIGURE_OPTS} --disable-werror --with-external-pcre --disable-fileinfo" + fi + # Library paths already set by setup_macos_dependencies + if [ -z "$LDFLAGS" ]; then + local brew_lib_path + brew_lib_path=$(brew --prefix) + export LDFLAGS="-L${brew_lib_path}/lib" + fi + if [ -z "$CPPFLAGS" ]; then + local brew_include_path + brew_include_path=$(brew --prefix) + export CPPFLAGS="-I${brew_include_path}/include" + fi + else + export PHP_BUILD_CONFIGURE_OPTS="${PHP_BUILD_CONFIGURE_OPTS} --disable-werror --with-external-pcre --disable-fileinfo" + fi else - export ASDF_PKG_MISSING="freetype" + # Linux specific configuration + export LDFLAGS="${LDFLAGS:+${LDFLAGS} }-Wl,--no-as-needed" + export PHP_BUILD_CONFIGURE_OPTS="${PHP_BUILD_CONFIGURE_OPTS} --disable-werror --with-external-pcre --disable-fileinfo --without-tidy" fi + export MAKE_OPTS="${MAKE_OPTS:--j1}" + fi - if [ -n "$bison_path" ]; then - export ASDF_PKG_MISSING="$ASDF_PKG_MISSING bison" - fi + echo "Compiler configuration:" + echo " CC=$CC" + echo " CXX=$CXX" + echo " ac_cv_prog_CC=$ac_cv_prog_CC" + echo " CFLAGS=$CFLAGS" + echo " CXXFLAGS=$CXXFLAGS" + echo " LDFLAGS=$LDFLAGS" + echo " MAKE_OPTS=$MAKE_OPTS" +} - if [ -n "$gettext_path" ]; then - configure_options="$configure_options --with-gettext=$gettext_path" - else - export ASDF_PKG_MISSING="$ASDF_PKG_MISSING gettext" - fi +# Apply compiler setup for problematic PHP versions +setup_compiler_for_old_php "$version" + +# Disable Xdebug by default (prevents build failures) +if [ -z "$PHP_BUILD_XDEBUG_ENABLE" ]; then + export PHP_BUILD_XDEBUG_ENABLE=off +fi + +# Pass through custom configure options +if [ -n "$PHP_CONFIGURE_OPTIONS" ]; then + export PHP_BUILD_CONFIGURE_OPTS="${PHP_BUILD_CONFIGURE_OPTS:-} ${PHP_CONFIGURE_OPTIONS}" +fi + +# Pass through PEAR option +if [ "${PHP_WITHOUT_PEAR:-no}" != "no" ]; then + export PHP_BUILD_CONFIGURE_OPTS="${PHP_BUILD_CONFIGURE_OPTS} --without-pear" +fi + +# php-build does not read ASDF_CONCURRENCY directly, so it must be translated into +# PHP_BUILD_EXTRA_MAKE_ARGUMENTS; without this the build ignores the caller's intent +setup_build_concurrency() { + if [ -z "${ASDF_CONCURRENCY:-}" ]; then + return 0 + fi - if [ -n "$icu4c_path" ]; then - configure_options="$configure_options --with-icu-dir=$icu4c_path" - else - export ASDF_PKG_MISSING="$ASDF_PKG_MISSING icu4c" - fi + export PHP_BUILD_EXTRA_MAKE_ARGUMENTS="-j${ASDF_CONCURRENCY}" +} - if [ -n "$jpeg_path" ]; then - configure_options="$configure_options --with-jpeg-dir=$jpeg_path" - else - export ASDF_PKG_MISSING="$ASDF_PKG_MISSING jpeg" +setup_build_concurrency + +# Patch php-build definition files to include libxml2 compatibility +patch_php_build_definitions() { + local php_version=$1 + local php_major + php_major=$(echo "$php_version" | cut -d. -f1) + local php_minor + php_minor=$(echo "$php_version" | cut -d. -f2) + local php_patch + php_patch=$(echo "$php_version" | cut -d. -f3 | sed 's/[^0-9].*//') + local php_build_patches="$PHP_BUILD_ROOT/share/php-build/patches" + local definitions_dir="$PHP_BUILD_ROOT/share/php-build/definitions" + local def_file="$definitions_dir/$php_version" + + # Add libxml2 2.12+ and ICU 74+ compatibility patches for PHP 7.4 + if [ "$php_major" -eq 7 ] && [ "$php_minor" -eq 4 ]; then + # libxml2 2.12+ patch (php-build already has it) + if [ -f "$def_file" ] && ! grep -q "php-7.4-libxml2-2.12.patch" "$def_file"; then + # Add after the last patch_file line, or before install_package if no patches exist + if grep -q "patch_file" "$def_file"; then + # Find the last patch_file line and add after it + local last_patch_line + last_patch_line=$(grep -n "patch_file" "$def_file" | tail -1 | cut -d: -f1) + awk -v line="$last_patch_line" 'NR==line {print; print "patch_file \"php-7.4-libxml2-2.12.patch\""} NR!=line' "$def_file" >"$def_file.tmp" && mv "$def_file.tmp" "$def_file" + else + # Add before install_package + awk '/^install_package/ {print "patch_file \"php-7.4-libxml2-2.12.patch\""; print; next} {print}' "$def_file" >"$def_file.tmp" && mv "$def_file.tmp" "$def_file" + fi + echo "Added libxml2 compatibility patch to PHP $php_version definition" fi - if [ -n "$webp_path" ]; then - configure_options="$configure_options --with-webp-dir=$webp_path" - else - export ASDF_PKG_MISSING="$ASDF_PKG_MISSING webp" + # ICU 74+ patch (custom patch from plugin) + if [ -f "${PLUGIN_DIR}/patches/php-7.4-icu-74-compat.patch" ]; then + cp "${PLUGIN_DIR}/patches/php-7.4-icu-74-compat.patch" "$php_build_patches/" + if [ -f "$def_file" ] && ! grep -q "php-7.4-icu-74-compat.patch" "$def_file"; then + local last_patch_line + last_patch_line=$(grep -n "patch_file" "$def_file" | tail -1 | cut -d: -f1) + awk -v line="$last_patch_line" 'NR==line {print; print "patch_file \"php-7.4-icu-74-compat.patch\""} NR!=line' "$def_file" >"$def_file.tmp" && mv "$def_file.tmp" "$def_file" + echo "Added ICU 74+ compatibility patch to PHP $php_version definition" + fi fi + fi - if [ -n "$libpng_path" ]; then - configure_options="$configure_options --with-png-dir=$libpng_path" - else - export ASDF_PKG_MISSING="$ASDF_PKG_MISSING libpng" + # Add libxml2 2.12+ and ICU 74+ compatibility patches for PHP 8.0.0-8.0.19 + if [ "$php_major" -eq 8 ] && [ "$php_minor" -eq 0 ] && [ "${php_patch:-0}" -lt 20 ]; then + # libxml2 2.12+ patch + if [ -f "${PLUGIN_DIR}/patches/php-8.0-libxml2-2.12-compat.patch" ]; then + cp "${PLUGIN_DIR}/patches/php-8.0-libxml2-2.12-compat.patch" "$php_build_patches/" + if [ -f "$def_file" ] && ! grep -q "php-8.0-libxml2-2.12-compat.patch" "$def_file"; then + awk '/patch_file "php-8.0-support-openssl-3.patch"/ {print; print "patch_file \"php-8.0-libxml2-2.12-compat.patch\""; next} {print}' "$def_file" >"$def_file.tmp" && mv "$def_file.tmp" "$def_file" + echo "Added libxml2 compatibility patch to PHP $php_version definition" + fi fi - if [ -n "$openssl_path" ]; then - configure_options="$configure_options --with-openssl=$openssl_path" - else - export ASDF_PKG_MISSING="$ASDF_PKG_MISSING openssl" + # ICU 74+ patch + if [ -f "${PLUGIN_DIR}/patches/php-8.0-icu-74-compat.patch" ]; then + cp "${PLUGIN_DIR}/patches/php-8.0-icu-74-compat.patch" "$php_build_patches/" + if [ -f "$def_file" ] && ! grep -q "php-8.0-icu-74-compat.patch" "$def_file"; then + awk '/patch_file "php-8.0-libxml2-2.12-compat.patch"/ {print; print "patch_file \"php-8.0-icu-74-compat.patch\""; next} {print}' "$def_file" >"$def_file.tmp" && mv "$def_file.tmp" "$def_file" + echo "Added ICU 74+ compatibility patch to PHP $php_version definition" + fi fi - if [ -n "$libxml2_path" ]; then - configure_options="$configure_options --with-libxml-dir=$libxml2_path" - else - export ASDF_PKG_MISSING="$ASDF_PKG_MISSING libxml2" + # intl C++17 patch — ext/intl/config.m4 in PHP 8.0.x hardcodes PHP_CXX_COMPILE_STDCXX(11) + # which causes the generated Makefile to append -std=c++11 to every intl compile rule. + # That overrides any -std=c++17 in CXXFLAGS, so ICU 74+/78+ headers that use C++17 + # features (enable_if_t, void_t, is_same_v, if constexpr) fail to compile. + # Patching config.m4 to require C++17 fixes the generated Makefile rules; buildconf + # regenerates configure from the patched config.m4 before the build starts. + if [ -f "${PLUGIN_DIR}/patches/php-8.0-intl-cxx17.patch" ]; then + cp "${PLUGIN_DIR}/patches/php-8.0-intl-cxx17.patch" "$php_build_patches/" + if [ -f "$def_file" ] && ! grep -q "php-8.0-intl-cxx17.patch" "$def_file"; then + awk '/patch_file "php-8.0-icu-74-compat.patch"/ {print; print "patch_file \"php-8.0-intl-cxx17.patch\""; next} {print}' "$def_file" >"$def_file.tmp" && mv "$def_file.tmp" "$def_file" + echo "Added intl C++17 patch to PHP $php_version definition" + fi fi - if [ -n "$zlib_path" ]; then - configure_options="$configure_options --with-zlib-dir=$zlib_path" - else - export ASDF_PKG_MISSING="$ASDF_PKG_MISSING zlib" + # libxml2 const handler patch + if [ -f "${PLUGIN_DIR}/patches/php-8.0-libxml2-const-handler.patch" ]; then + cp "${PLUGIN_DIR}/patches/php-8.0-libxml2-const-handler.patch" "$php_build_patches/" + if [ -f "$def_file" ] && ! grep -q "php-8.0-libxml2-const-handler.patch" "$def_file"; then + awk '/patch_file "php-8.0-intl-cxx17.patch"/ {print; print "patch_file \"php-8.0-libxml2-const-handler.patch\""; next} {print}' "$def_file" >"$def_file.tmp" && mv "$def_file.tmp" "$def_file" + echo "Added libxml2 const handler patch to PHP $php_version definition" + fi fi - if [ -n "$libzip_path" ]; then - configure_options="$configure_options --with-libzip=$libzip_path" - else - export ASDF_PKG_MISSING="$ASDF_PKG_MISSING libzip" + # libxml2 ATTRIBUTE_UNUSED patch + if [ -f "${PLUGIN_DIR}/patches/php-8.0-libxml2-attribute-unused.patch" ]; then + cp "${PLUGIN_DIR}/patches/php-8.0-libxml2-attribute-unused.patch" "$php_build_patches/" + if [ -f "$def_file" ] && ! grep -q "php-8.0-libxml2-attribute-unused.patch" "$def_file"; then + awk '/patch_file "php-8.0-libxml2-const-handler.patch"/ {print; print "patch_file \"php-8.0-libxml2-attribute-unused.patch\""; next} {print}' "$def_file" >"$def_file.tmp" && mv "$def_file.tmp" "$def_file" + echo "Added libxml2 ATTRIBUTE_UNUSED compat patch to PHP $php_version definition" + fi fi - if [ -n "$readline_path" ]; then - configure_options="$configure_options --with-readline=$readline_path" - else - export ASDF_PKG_MISSING="$ASDF_PKG_MISSING readline" + # bcmath K&R to ANSI C patch + if [ -f "${PLUGIN_DIR}/patches/php-8.0-bcmath-knr-to-ansi.patch" ]; then + cp "${PLUGIN_DIR}/patches/php-8.0-bcmath-knr-to-ansi.patch" "$php_build_patches/" + if [ -f "$def_file" ] && ! grep -q "php-8.0-bcmath-knr-to-ansi.patch" "$def_file"; then + awk '/patch_file "php-8.0-libxml2-attribute-unused.patch"/ {print; print "patch_file \"php-8.0-bcmath-knr-to-ansi.patch\""; next} {print}' "$def_file" >"$def_file.tmp" && mv "$def_file.tmp" "$def_file" + echo "Added bcmath K&R to ANSI C patch to PHP $php_version definition" + fi fi + fi +} - if [ -n "$libedit_path" ]; then - configure_options="$configure_options --with-libedit=$libedit_path" - else - export ASDF_PKG_MISSING="$ASDF_PKG_MISSING libedit" - fi +patch_php_build_definitions "$version" - if [ -n "$bzip2_path" ]; then - configure_options="$configure_options --with-bz2=$bzip2_path" - else - export ASDF_PKG_MISSING="$ASDF_PKG_MISSING bzip2" - fi +# A stale CXXFLAGS=-std=c++11 from prior ICU-based PHP installations must be cleared +# before php-build runs, otherwise PHP 8.5+'s C++17 configure check fails despite +# having a capable compiler +sanitize_cxxflags_for_modern_php "$version" - if [ -n "$iconv_path" ]; then - configure_options="$configure_options --with-iconv=$iconv_path" - else - export ASDF_PKG_MISSING="$ASDF_PKG_MISSING libiconv" - fi - else - local jpeg_path=$(locate libjpeg.so | awk '{ print length(), $0 | "sort -n" }' | cut -d" " -f2- | head -n 1) - local libpng_path=$(locate libpng.so | awk '{ print length(), $0 | "sort -n" }' | cut -d" " -f2- | head -n 1) - configure_options="--with-openssl --with-curl --with-zlib --with-readline --with-gettext" +echo "Building PHP ${version}..." - if [ "$jpeg_path" = "" ]; then - export ASDF_PKG_MISSING="$ASDF_PKG_MISSING jpeg" - else - configure_options="$configure_options --with-jpeg-dir=$jpeg_path --with-jpeg" - fi +# For problematic PHP versions, use environment wrapper +php_major=$(echo "$version" | cut -d. -f1) +php_minor=$(echo "$version" | cut -d. -f2) +php_patch=$(echo "$version" | cut -d. -f3 | sed 's/[^0-9].*//') - if [ "$libpng_path" = "" ]; then - export ASDF_PKG_MISSING="$ASDF_PKG_MISSING libpng" - else - configure_options="$configure_options --with-png-dir=$libpng_path --with-png" - fi +if php-build "$version" "$install_path"; then + echo "✓ PHP ${version} built successfully" +else + echo "✗ Build failed. For OpenSSL issues: ${PLUGIN_DIR}/lib/install-openssl11.sh" + exit 1 +fi + +# Install Composer globally (php-build doesn't do this by default) +install_composer() { + local bin_path="$install_path/bin" + local temp_dir + temp_dir=$(mktemp -d) + + echo "=== Composer Installation Debug ===" + echo "PHP binary: $bin_path/php" + echo "Temp directory: $temp_dir" + + # Check if PHP has required extensions + if ! "$bin_path"/php -m | grep -q "openssl"; then + echo "⚠ PHP OpenSSL extension not available - required for Composer download" + rm -rf "$temp_dir" + return 1 fi - echo $configure_options -} + if ! "$bin_path"/php -m | grep -q "curl"; then + echo "⚠ PHP curl extension not available - using fallback download method" + fi -download_source() { - local install_type=$1 - local version=$2 - local download_path=$3 - local download_url=$(get_download_url $install_type $version) + # Try to fetch signature with better error handling + local expected_signature + expected_signature=$(curl -sL https://composer.github.io/installer.sig 2>&1) + local curl_exit_code=$? - # curl -Lo $download_path -C - $download_url - curl -Lo $download_path $download_url -} + if [ $curl_exit_code -ne 0 ] || [ -z "$expected_signature" ]; then + echo "⚠ Could not fetch Composer installer signature (curl exit: $curl_exit_code)" + echo "⚠ Signature response: '$expected_signature'" + echo "⚠ Skipping signature verification due to network issues" + expected_signature="" + else + echo "✓ Fetched Composer installer signature: ${expected_signature:0:20}..." + fi -get_download_file_path() { - local install_type=$1 - local version=$2 - local tmp_download_dir=$3 - local php_version=$(get_php_version $version) - local pkg_name="php-${php_version}.tar.gz" + ( + cd "$temp_dir" + echo "Downloading Composer installer..." + + # Try PHP download first, then fallback to curl + if "$bin_path"/php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" 2>/dev/null; then + echo "✓ Downloaded via PHP copy()" + elif command -v curl >/dev/null && curl -sL https://getcomposer.org/installer -o composer-setup.php; then + echo "✓ Downloaded via curl fallback" + elif command -v wget >/dev/null && wget -q https://getcomposer.org/installer -O composer-setup.php; then + echo "✓ Downloaded via wget fallback" + else + echo "⚠ Could not download Composer installer with any method" + cd - >/dev/null + rm -rf "$temp_dir" + return 1 + fi - echo "$tmp_download_dir/$pkg_name" -} + if [ ! -f "composer-setup.php" ]; then + echo "⚠ Composer installer not found after download" + cd - >/dev/null + rm -rf "$temp_dir" + return 1 + fi + + echo "✓ Composer installer downloaded ($(wc -c /dev/null) - local php_version=$(get_php_version $version) + if [ "$signature_check" != "OK" ]; then + echo "⚠ Composer installer signature verification failed" + echo "⚠ Expected: $expected_signature" + echo "⚠ Continuing anyway due to CI environment" + else + echo "✓ Signature verification passed" + fi + fi - local dir_name="php-src-php-${php_version}" + echo "Installing Composer..." + if "$bin_path"/php composer-setup.php --install-dir="$bin_path" --filename=composer 2>&1; then + echo "✓ Composer installation completed" + else + echo "⚠ Composer installation failed" + rm -f composer-setup.php + cd - >/dev/null + rm -rf "$temp_dir" + return 1 + fi - echo "$tmp_download_dir/$dir_name" -} + rm -f composer-setup.php + ) -get_download_url() { - local install_type=$1 - local version=$2 + rm -rf "$temp_dir" - echo "https://github.com/php/php-src/archive/php-${version}.tar.gz" + # Verify Composer was installed + if [ -f "$bin_path/composer" ] && [ -x "$bin_path/composer" ]; then + local composer_version + composer_version=$("$bin_path"/composer --version 2>/dev/null || echo "unknown") + echo "✓ Composer verified: $composer_version" + return 0 + else + echo "⚠ Composer binary not found after installation" + echo "Contents of $bin_path:" + ls -la "$bin_path" 2>/dev/null || echo "Directory not accessible" + return 1 + fi } -get_php_version() { - IFS='-' read -a version_info <<<"$1" +# Install Composer +echo "Installing Composer..." +if install_composer; then + echo "✓ Composer installed" +else + echo "⚠ Composer installation failed" +fi + +# Create conf.d directory for user configuration +mkdir -p "$install_path/conf.d" +if [ ! -f "$install_path/conf.d/php.ini" ]; then + echo "# Add custom PHP configuration here" >"$install_path/conf.d/php.ini" +fi + +# Install Xdebug if requested +install_xdebug() { + local bin_path="$install_path/bin" + local pecl_path="$bin_path/pecl" + + echo "Installing Xdebug..." + if ! command -v "$pecl_path" &>/dev/null; then + echo "⚠ PECL not found" + return 1 + fi - if [ "${#version_info[@]}" -eq 1 ]; then - echo "${version_info[0]}" + if "$pecl_path" install xdebug >/dev/null 2>&1; then + echo "zend_extension=xdebug.so" >>"$install_path/conf.d/xdebug.ini" + echo "xdebug.mode=debug" >>"$install_path/conf.d/xdebug.ini" + echo "✓ Xdebug installed" else - echo "${version_info[0]}-${version_info[1]}" + echo "⚠ Xdebug installation failed" + return 1 fi } -install_php "$ASDF_INSTALL_TYPE" "$ASDF_INSTALL_VERSION" "$ASDF_INSTALL_PATH" -install_composer "$ASDF_INSTALL_PATH" +# Check if user wants Xdebug installed +if [ "${PHP_WITH_XDEBUG:-no}" != "no" ]; then + install_xdebug || true +fi + +echo "✓ PHP ${version} installed at: ${install_path}" diff --git a/bin/latest-stable b/bin/latest-stable new file mode 100755 index 0000000..7cb804c --- /dev/null +++ b/bin/latest-stable @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Get all versions and filter for stable releases only +# Stable releases are in the format X.Y.Z without any alpha, beta, RC, or other suffixes + +sort_versions() { + sed 'h; s/[+-]/./g; s/.p\([[:digit:]]\)/.z\1/; s/$/.z/; G; s/\n/ /' | + LC_ALL=C sort -t. -k 1,1 -k 2,2n -k 3,3n -k 4,4n -k 5,5n | awk '{print $2}' +} + +# Fetch all tags from PHP repository +versions=$( + git ls-remote --tags https://github.com/php/php-src.git | + grep 'php-' | + awk '!/(\{.*\})/ {print $2}' | + sed 's/refs\/tags\/php-//' | + grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | + sort_versions +) + +# Get the latest version from the sorted list +latest_version=$(echo "$versions" | tail -n 1) + +# If a query version is provided (e.g., "8.4"), filter to that major.minor +if [ $# -eq 1 ] && [ -n "$1" ]; then + query="$1" + # Extract matching versions for the given major.minor + filtered_versions=$(echo "$versions" | grep -E "^${query}\." || echo "") + + if [ -n "$filtered_versions" ]; then + latest_version=$(echo "$filtered_versions" | tail -n 1) + else + # If no match found, return nothing (asdf will handle the error) + exit 1 + fi +fi + +echo "$latest_version" diff --git a/bin/post-plugin-add b/bin/post-plugin-add new file mode 100755 index 0000000..337afcb --- /dev/null +++ b/bin/post-plugin-add @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +set -eo pipefail + +# This hook is called after the plugin is added to asdf +# We use it to ensure php-build submodule is properly initialized + +PLUGIN_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +PHP_BUILD_DIR="$PLUGIN_DIR/vendor/php-build" + +echo "Initializing php-build..." + +# Check if vendor/php-build already exists and has content +if [ -f "$PHP_BUILD_DIR/bin/php-build" ]; then + echo "✓ php-build already initialized" + exit 0 +fi + +# Create vendor directory if it doesn't exist +mkdir -p "$PLUGIN_DIR/vendor" + +# Try to initialize submodule if we're in a git repository +if [ -d "$PLUGIN_DIR/.git" ] || [ -f "$PLUGIN_DIR/.git" ]; then + echo "Initializing php-build submodule..." + ( + cd "$PLUGIN_DIR" + if git submodule update --init --recursive vendor/php-build 2>/dev/null; then + echo "✓ php-build submodule initialized" + exit 0 + else + echo "⚠ Submodule initialization failed, trying direct clone..." + fi + ) +fi + +# Fallback: clone php-build directly +if [ ! -f "$PHP_BUILD_DIR/bin/php-build" ]; then + echo "Cloning php-build from GitHub..." + if git clone --depth 1 https://github.com/php-build/php-build.git "$PHP_BUILD_DIR"; then + echo "✓ php-build cloned successfully" + else + echo "✗ Failed to clone php-build" + echo "" + echo "Please manually clone php-build:" + echo " git clone https://github.com/php-build/php-build.git $PHP_BUILD_DIR" + exit 1 + fi +fi + +# Ensure all bin scripts have executable permissions +echo "Setting executable permissions on bin scripts..." +chmod +x "$PLUGIN_DIR"/bin/* + +echo "" +echo "✓ asdf-php plugin setup complete!" +echo "" +echo "You can now install PHP versions with:" +echo " asdf install php " diff --git a/docs/local-testing-manually.md b/docs/local-testing-manually.md new file mode 100644 index 0000000..50c952c --- /dev/null +++ b/docs/local-testing-manually.md @@ -0,0 +1,103 @@ +# local testing + +Below are the steps to install different versions of PHP using asdf in local environment. + +## install 8.5.4 + +```bash +asdf uninstall php 8.5.4 # currently 8.5.4 as of 2026-04-07 +asdf plugin remove php +asdf plugin add php $(pwd) +ASDF_CONCURRENCY=8 asdf install php 8.5.4 +``` + +## install 8.5.1 + +```bash +asdf uninstall php 8.5.1 +asdf plugin remove php +asdf plugin add php $(pwd) + +# normal installation +asdf install php 8.5.1 + +# verbose installation +VERBOSE=y ASDF_CONCURRENCY=4 asdf install php 8.5.1 + +# install with custom flags +export CFLAGS="-Wno-error -Wno-deprecated-declarations -Wno-implicit-function-declaration -O2" +export CXXFLAGS="-Wno-error -Wno-deprecated-declarations -O2" +export LDFLAGS="-Wl,--no-as-needed" +``` + +# install 8.3.29 + +to watch installation progress + +```bash +tail -f /tmp/php-build.8.3.29.*.log +``` + +```bash +asdf uninstall php 8.3.29 +asdf plugin remove php +asdf plugin add php $(pwd) +asdf install php 8.3.29 +➜ ~ asdf set php 8.3.29 +# the return should be like +# ➜ ~ php -v +# PHP 8.3.29 (cli) (built: Dec 25 2025 20:59:03) (NTS) +# Copyright (c) The PHP Group +# Zend Engine v4.3.29, Copyright (c) Zend Technologies +# with Zend OPcache v8.3.29, Copyright (c), by Zend Technologies + +# installation with custom flags +export CFLAGS="-Wno-error=dangling-pointer -Wno-error -O2" +export CXXFLAGS="-Wno-error=dangling-pointer -Wno-error -O2" +VERBOSE=y ASDF_CONCURRENCY=4 asdf install php 8.3.29 + +# monitor installation progress +tail -f /tmp/php-build.8.3.29.*.log + +# set as default +asdf set php 8.3.29 + +# verify installation +php -v +``` + +## install 8.0.0 + +```bash +asdf uninstall php 8.0.0 +asdf plugin remove php +asdf plugin add php $(pwd) +ASDF_CONCURRENCY=8 asdf install php 8.0.0 + +# monitor installation progress +tail -f /tmp/php-build.8.0.0.*.log + +# set as default +asdf set php 8.0.0 + +# verify installation +php -v +``` + +## install 7.4.14 + +```bash +asdf uninstall php 7.4.14 +asdf plugin remove php +asdf plugin add php $(pwd) +ASDF_CONCURRENCY=8 asdf install php 7.4.14 + +# monitor installation progress +tail -f /tmp/php-build.7.4.14.*.log + +# set as default +asdf set php 7.4.14 + +# verify installation +php -v +``` diff --git a/lib/install-openssl11.sh b/lib/install-openssl11.sh new file mode 100755 index 0000000..fe62854 --- /dev/null +++ b/lib/install-openssl11.sh @@ -0,0 +1,177 @@ +#!/usr/bin/env bash +set -eo pipefail + +OPENSSL_VERSION="1.1.1w" +# Optional: Set OPENSSL_SHA256 environment variable to verify download integrity +# If not set, the script will attempt to retrieve the official checksum automatically +INSTALL_PREFIX="${INSTALL_PREFIX:-$HOME/.local/openssl-1.1}" +TEMP_DIR="/tmp/openssl-1.1-build-$$" + +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +NC='\033[0m' + +# Check for flags +AUTO_MODE=false +SHOW_HELP=false +if [ "$1" = "--auto" ]; then + AUTO_MODE=true +elif [ "$1" = "--help" ] || [ "$1" = "-h" ]; then + SHOW_HELP=true +fi + +# Show help if requested +if [ "$SHOW_HELP" = true ]; then + echo "OpenSSL 1.1.1w Installation Script" + echo + echo "Usage: $0 [--auto|--help]" + echo + echo "Options:" + echo " --auto Skip confirmation prompt" + echo " --help Show this help message" + echo + echo "Security Verification:" + echo " The script verifies download integrity using SHA256 checksums:" + echo " 1. If OPENSSL_SHA256 is set, uses that checksum (recommended for CI)" + echo " 2. Otherwise, attempts to retrieve official checksum from OpenSSL.org" + echo " 3. If retrieval fails, proceeds with a warning" + echo + echo "Examples:" + echo " # With manual checksum verification:" + echo " OPENSSL_SHA256=cf3098950cb4d853ad95c0841f1f9c6d3dc102dccfcacd521d93925208b76ac8 $0" + echo + echo " # Automatic checksum retrieval:" + echo " $0" + echo + echo " # Silent installation with automatic verification:" + echo " $0 --auto" + exit 0 +fi + +# Check if already installed +if [ -f "$INSTALL_PREFIX/bin/openssl" ]; then + installed_version=$(LD_LIBRARY_PATH="$INSTALL_PREFIX/lib:$LD_LIBRARY_PATH" "$INSTALL_PREFIX/bin/openssl" version 2>/dev/null | awk '{print $2}') + if [ -n "$installed_version" ]; then + echo -e "${GREEN}✓${NC} OpenSSL 1.1 already installed: ${installed_version} at ${INSTALL_PREFIX}" + echo " export PKG_CONFIG_PATH=\"${INSTALL_PREFIX}/lib/pkgconfig:\$PKG_CONFIG_PATH\"" + echo " export PHP_BUILD_CONFIGURE_OPTS=\"--with-openssl=${INSTALL_PREFIX}\"" + echo " export LD_LIBRARY_PATH=\"${INSTALL_PREFIX}/lib:\$LD_LIBRARY_PATH\"" + exit 0 + fi +fi + +# Check dependencies +missing_deps=() +for cmd in gcc make perl; do + if ! command -v $cmd &> /dev/null; then + missing_deps+=("$cmd") + fi +done + +if [ ${#missing_deps[@]} -ne 0 ]; then + echo -e "${RED}✗${NC} Missing dependencies: ${missing_deps[*]}" + if command -v apt-get &> /dev/null; then + echo " sudo apt-get install -y build-essential perl" + elif command -v yum &> /dev/null; then + echo " sudo yum install -y gcc make perl-core" + fi + exit 1 +fi + +# Confirm (skip in auto mode) +if [ "$AUTO_MODE" = false ]; then + read -p "Install OpenSSL ${OPENSSL_VERSION} to ${INSTALL_PREFIX}? [y/N] " -n 1 -r + echo + [[ ! $REPLY =~ ^[Yy]$ ]] && exit 0 +fi + +# Cleanup on exit +trap "rm -rf $TEMP_DIR" EXIT + +# Download +mkdir -p "$TEMP_DIR" && cd "$TEMP_DIR" +echo "Downloading OpenSSL ${OPENSSL_VERSION}..." +curl -fsSL -o openssl.tar.gz "https://www.openssl.org/source/openssl-${OPENSSL_VERSION}.tar.gz" || exit 1 + +# Verify download integrity +echo "Verifying download integrity..." + +# Use environment variable if provided, otherwise try to get official checksum +if [ -n "$OPENSSL_SHA256" ]; then + echo "Using provided checksum: $OPENSSL_SHA256" +else + echo "Attempting to retrieve official checksum..." + for checksum_url in \ + "https://www.openssl.org/source/openssl-${OPENSSL_VERSION}.tar.gz.sha256" \ + "https://github.com/openssl/openssl/releases/download/OpenSSL_$(echo $OPENSSL_VERSION | tr '.' '_')/openssl-${OPENSSL_VERSION}.tar.gz.sha256"; do + + if RETRIEVED_CHECKSUM=$(curl -fsSL "$checksum_url" 2>/dev/null | head -1 | awk '{print $1}'); then + if [ -n "$RETRIEVED_CHECKSUM" ] && [ ${#RETRIEVED_CHECKSUM} -eq 64 ]; then + OPENSSL_SHA256="$RETRIEVED_CHECKSUM" + echo "Retrieved checksum from: $checksum_url" + break + fi + fi + done +fi + +# If we have a checksum, verify it +if [ -n "$OPENSSL_SHA256" ] && [ ${#OPENSSL_SHA256} -eq 64 ]; then + echo "Verifying SHA256: $OPENSSL_SHA256" + if command -v sha256sum >/dev/null 2>&1; then + echo "${OPENSSL_SHA256} openssl.tar.gz" | sha256sum -c - || { + echo -e "${RED}✗${NC} SHA256 checksum verification failed! File may be corrupted or tampered with." + exit 1 + } + elif command -v shasum >/dev/null 2>&1; then + echo "${OPENSSL_SHA256} openssl.tar.gz" | shasum -a 256 -c - || { + echo -e "${RED}✗${NC} SHA256 checksum verification failed! File may be corrupted or tampered with." + exit 1 + } + else + echo -e "${YELLOW}⚠${NC} Warning: No SHA256 verification tool available (sha256sum or shasum)." + fi + echo -e "${GREEN}✓${NC} Download integrity verified" +else + echo -e "${YELLOW}⚠${NC} Warning: Could not retrieve official SHA256 checksum." + echo -e "${YELLOW}⚠${NC} To enable verification, set OPENSSL_SHA256 environment variable with the official checksum." + echo -e "${YELLOW}⚠${NC} Proceeding without integrity check - use at your own risk." +fi + +# Extract & build +echo "Extracting and configuring..." +tar -xzf openssl.tar.gz && cd "openssl-${OPENSSL_VERSION}" + +CORES=$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4) +./config --prefix="$INSTALL_PREFIX" --openssldir="$INSTALL_PREFIX" shared zlib no-tests > /dev/null 2>&1 + +echo "Compiling with ${CORES} cores (2-5 minutes)..." +make -j"$CORES" > /dev/null 2>&1 + +echo "Installing to ${INSTALL_PREFIX}..." +mkdir -p "$INSTALL_PREFIX" +make install_sw install_ssldirs > /dev/null 2>&1 + +# Verify +if [ -f "$INSTALL_PREFIX/bin/openssl" ]; then + installed_version=$(LD_LIBRARY_PATH="$INSTALL_PREFIX/lib:$LD_LIBRARY_PATH" "$INSTALL_PREFIX/bin/openssl" version 2>/dev/null | awk '{print $2}') + if [ -n "$installed_version" ]; then + echo -e "${GREEN}✓${NC} OpenSSL ${installed_version} installed successfully" + + # Only show instructions in manual mode + if [ "$AUTO_MODE" = false ]; then + echo + echo "Add to your shell profile (~/.bashrc or ~/.zshrc):" + echo " export PKG_CONFIG_PATH=\"${INSTALL_PREFIX}/lib/pkgconfig:\$PKG_CONFIG_PATH\"" + echo " export PHP_BUILD_CONFIGURE_OPTS=\"--with-openssl=${INSTALL_PREFIX}\"" + echo " export LD_LIBRARY_PATH=\"${INSTALL_PREFIX}/lib:\$LD_LIBRARY_PATH\"" + fi + else + echo -e "${RED}✗${NC} OpenSSL installed but cannot verify version" + exit 1 + fi +else + echo -e "${RED}✗${NC} Installation failed" + exit 1 +fi diff --git a/patches/php-7.4-icu-74-compat.patch b/patches/php-7.4-icu-74-compat.patch new file mode 100644 index 0000000..2289b52 --- /dev/null +++ b/patches/php-7.4-icu-74-compat.patch @@ -0,0 +1,28 @@ +--- a/ext/intl/breakiterator/codepointiterator_internal.h ++++ b/ext/intl/breakiterator/codepointiterator_internal.h +@@ -39,7 +39,11 @@ namespace PHP { + + virtual ~CodePointBreakIterator(); + ++#if U_ICU_VERSION_MAJOR_NUM >= 74 ++ virtual bool operator==(const BreakIterator& that) const; ++#else + virtual UBool operator==(const BreakIterator& that) const; ++#endif + + virtual CodePointBreakIterator* clone(void) const; + +--- a/ext/intl/breakiterator/codepointiterator_internal.cpp ++++ b/ext/intl/breakiterator/codepointiterator_internal.cpp +@@ -49,7 +49,11 @@ CodePointBreakIterator::~CodePointBreakIterator() + clearCurrentCharIter(); + } + ++#if U_ICU_VERSION_MAJOR_NUM >= 74 ++bool CodePointBreakIterator::operator==(const BreakIterator& that) const ++#else + UBool CodePointBreakIterator::operator==(const BreakIterator& that) const ++#endif + { + if (typeid(*this) != typeid(that)) { + return FALSE; diff --git a/patches/php-8.0-bcmath-knr-to-ansi.patch b/patches/php-8.0-bcmath-knr-to-ansi.patch new file mode 100644 index 0000000..c24c139 --- /dev/null +++ b/patches/php-8.0-bcmath-knr-to-ansi.patch @@ -0,0 +1,258 @@ +diff --color -ru src.orig/add.c src/add.c +--- a/ext/bcmath/libbcmath/src/add.c 2026-06-29 00:18:40 ++++ b/ext/bcmath/libbcmath/src/add.c 2026-06-29 00:35:32 +@@ -43,9 +43,7 @@ + is the minimum scale for the result. */ + + void +-bc_add (n1, n2, result, scale_min) +- bc_num n1, n2, *result; +- int scale_min; ++bc_add (bc_num n1, bc_num n2, bc_num *result, int scale_min) + { + bc_num sum = NULL; + int cmp_res; +diff --color -ru src.orig/compare.c src/compare.c +--- a/ext/bcmath/libbcmath/src/compare.c 2026-06-29 00:18:40 ++++ b/ext/bcmath/libbcmath/src/compare.c 2026-06-29 00:27:13 +@@ -43,10 +43,7 @@ + compare the magnitudes. */ + + int +-_bc_do_compare (n1, n2, use_sign, ignore_last) +- bc_num n1, n2; +- int use_sign; +- int ignore_last; ++_bc_do_compare (bc_num n1, bc_num n2, int use_sign, int ignore_last) + { + char *n1ptr, *n2ptr; + int count; +@@ -152,8 +149,7 @@ + /* This is the "user callable" routine to compare numbers N1 and N2. */ + + int +-bc_compare (n1, n2) +- bc_num n1, n2; ++bc_compare (bc_num n1, bc_num n2) + { + return _bc_do_compare (n1, n2, TRUE, FALSE); + } +diff --color -ru src.orig/debug.c src/debug.c +--- a/ext/bcmath/libbcmath/src/debug.c 2026-06-29 00:18:40 ++++ b/ext/bcmath/libbcmath/src/debug.c 2026-06-29 00:35:09 +@@ -56,10 +56,7 @@ + + /* pv prints a character array as if it was a string of bcd digits. */ + void +-pv (name, num, len) +- char *name; +- unsigned char *num; +- int len; ++pv (char *name, unsigned char *num, int len) + { + int i; + printf ("%s=", name); +diff --color -ru src.orig/div.c src/div.c +--- a/ext/bcmath/libbcmath/src/div.c 2026-06-29 00:18:40 ++++ b/ext/bcmath/libbcmath/src/div.c 2026-06-29 00:29:09 +@@ -44,10 +44,7 @@ + the same pointers. */ + + static void +-_one_mult (num, size, digit, result) +- unsigned char *num; +- int size, digit; +- unsigned char *result; ++_one_mult (unsigned char *num, int size, int digit, unsigned char *result) + { + int carry, value; + unsigned char *nptr, *rptr; +diff --color -ru src.orig/doaddsub.c src/doaddsub.c +--- a/ext/bcmath/libbcmath/src/doaddsub.c 2026-06-29 00:18:40 ++++ b/ext/bcmath/libbcmath/src/doaddsub.c 2026-06-29 00:35:37 +@@ -43,9 +43,7 @@ + SCALE_MIN is to set the minimum scale of the result. */ + + bc_num +-_bc_do_add (n1, n2, scale_min) +- bc_num n1, n2; +- int scale_min; ++_bc_do_add (bc_num n1, bc_num n2, int scale_min) + { + bc_num sum; + int sum_scale, sum_digits; +@@ -135,9 +133,7 @@ + of the result. */ + + bc_num +-_bc_do_sub (n1, n2, scale_min) +- bc_num n1, n2; +- int scale_min; ++_bc_do_sub (bc_num n1, bc_num n2, int scale_min) + { + bc_num diff; + int diff_scale, diff_len; +diff --color -ru src.orig/init.c src/init.c +--- a/ext/bcmath/libbcmath/src/init.c 2026-06-29 00:18:40 ++++ b/ext/bcmath/libbcmath/src/init.c 2026-06-29 00:51:54 +@@ -40,8 +40,7 @@ + /* new_num allocates a number and sets fields to known values. */ + + bc_num +-_bc_new_num_ex (length, scale, persistent) +- int length, scale, persistent; ++_bc_new_num_ex (int length, int scale, int persistent) + { + bc_num temp; + /* PHP Change: malloc() -> pemalloc(), removed free_list code */ +@@ -62,9 +61,7 @@ + frees the storage if reference count is zero. */ + + void +-_bc_free_num_ex (num, persistent) +- bc_num *num; +- int persistent; ++_bc_free_num_ex (bc_num *num, int persistent) + { + if (*num == NULL) return; + (*num)->n_refs--; +diff --color -ru src.orig/int2num.c src/int2num.c +--- a/ext/bcmath/libbcmath/src/int2num.c 2026-06-29 00:18:40 ++++ b/ext/bcmath/libbcmath/src/int2num.c 2026-06-29 00:28:19 +@@ -41,9 +41,7 @@ + /* Convert an integer VAL to a bc number NUM. */ + + void +-bc_int2num (num, val) +- bc_num *num; +- int val; ++bc_int2num (bc_num *num, int val) + { + char buffer[30]; + char *bptr, *vptr; +diff --color -ru src.orig/nearzero.c src/nearzero.c +--- a/ext/bcmath/libbcmath/src/nearzero.c 2026-06-29 00:18:40 ++++ b/ext/bcmath/libbcmath/src/nearzero.c 2026-06-29 00:35:19 +@@ -42,9 +42,7 @@ + Last digit is defined by scale. */ + + char +-bc_is_near_zero (num, scale) +- bc_num num; +- int scale; ++bc_is_near_zero (bc_num num, int scale) + { + int count; + char *nptr; +diff --color -ru src.orig/neg.c src/neg.c +--- a/ext/bcmath/libbcmath/src/neg.c 2026-06-29 00:18:40 ++++ b/ext/bcmath/libbcmath/src/neg.c 2026-06-29 00:28:32 +@@ -40,8 +40,7 @@ + /* In some places we need to check if the number is negative. */ + + char +-bc_is_neg (num) +- bc_num num; ++bc_is_neg (bc_num num) + { + return num->n_sign == MINUS; + } +diff --color -ru src.orig/num2long.c src/num2long.c +--- a/ext/bcmath/libbcmath/src/num2long.c 2026-06-29 00:18:40 ++++ b/ext/bcmath/libbcmath/src/num2long.c 2026-06-29 00:35:20 +@@ -43,8 +43,7 @@ + the NUM for zero after having a zero returned. */ + + long +-bc_num2long (num) +- bc_num num; ++bc_num2long (bc_num num) + { + long val; + char *nptr; +diff --color -ru src.orig/num2str.c src/num2str.c +--- a/ext/bcmath/libbcmath/src/num2str.c 2026-06-29 00:18:40 ++++ b/ext/bcmath/libbcmath/src/num2str.c 2026-06-29 01:11:17 +@@ -40,9 +40,7 @@ + /* Convert a numbers to a string. Base 10 only.*/ + + zend_string +-*bc_num2str_ex (num, scale) +- bc_num num; +- int scale; ++*bc_num2str_ex (bc_num num, int scale) + { + zend_string *str; + char *sptr; +diff --color -ru src.orig/output.c src/output.c +--- a/ext/bcmath/libbcmath/src/output.c 2026-06-29 00:18:40 ++++ b/ext/bcmath/libbcmath/src/output.c 2026-06-29 00:35:45 +@@ -57,14 +57,7 @@ + is the actual routine for writing the characters. */ + + void +-bc_out_long (val, size, space, out_char) +- long val; +- int size, space; +-#ifdef __STDC__ +- void (*out_char)(int); +-#else +- void (*out_char)(); +-#endif ++bc_out_long (long val, int size, int space, void (*out_char)(int)) + { + char digits[40]; + int len, ix; +@@ -85,11 +78,7 @@ + as the routine to do the actual output of the characters. */ + + void +-#ifdef __STDC__ + bc_out_num (bc_num num, int o_base, void (*out_char)(int), int leading_zero) +-#else +-bc_out_num (bc_num num, int o_base, void (*out_char)(), int leading_zero) +-#endif + { + char *nptr; + int index, fdigit, pre_space; +diff --color -ru src.orig/recmul.c src/recmul.c +--- a/ext/bcmath/libbcmath/src/recmul.c 2026-06-29 00:18:40 ++++ b/ext/bcmath/libbcmath/src/recmul.c 2026-06-29 00:35:21 +@@ -51,9 +51,7 @@ + /* Multiply utility routines */ + + static bc_num +-new_sub_num (length, scale, value) +- int length, scale; +- char *value; ++new_sub_num (int length, int scale, char *value) + { + bc_num temp; + +diff --color -ru src.orig/rmzero.c src/rmzero.c +--- a/ext/bcmath/libbcmath/src/rmzero.c 2026-06-29 00:18:40 ++++ b/ext/bcmath/libbcmath/src/rmzero.c 2026-06-29 00:23:18 +@@ -42,8 +42,7 @@ + correct place and adjusts the length. */ + + void +-_bc_rm_leading_zeros (num) +- bc_num num; ++_bc_rm_leading_zeros (bc_num num) + { + /* We can move n_value to point to the first non zero digit! */ + while (*num->n_value == 0 && num->n_len > 1) { +diff --color -ru src.orig/sub.c src/sub.c +--- a/ext/bcmath/libbcmath/src/sub.c 2026-06-29 00:18:40 ++++ b/ext/bcmath/libbcmath/src/sub.c 2026-06-29 00:35:33 +@@ -43,9 +43,7 @@ + is the minimum scale for the result. */ + + void +-bc_sub (n1, n2, result, scale_min) +- bc_num n1, n2, *result; +- int scale_min; ++bc_sub (bc_num n1, bc_num n2, bc_num *result, int scale_min) + { + bc_num diff = NULL; + int cmp_res; diff --git a/patches/php-8.0-icu-74-compat.patch b/patches/php-8.0-icu-74-compat.patch new file mode 100644 index 0000000..dcce99b --- /dev/null +++ b/patches/php-8.0-icu-74-compat.patch @@ -0,0 +1,28 @@ +--- a/ext/intl/breakiterator/codepointiterator_internal.h ++++ b/ext/intl/breakiterator/codepointiterator_internal.h +@@ -37,7 +37,11 @@ namespace PHP { + + virtual ~CodePointBreakIterator(); + ++#if U_ICU_VERSION_MAJOR_NUM >= 74 ++ virtual bool operator==(const BreakIterator& that) const; ++#else + virtual UBool operator==(const BreakIterator& that) const; ++#endif + + virtual CodePointBreakIterator* clone(void) const; + +--- a/ext/intl/breakiterator/codepointiterator_internal.cpp ++++ b/ext/intl/breakiterator/codepointiterator_internal.cpp +@@ -49,7 +49,11 @@ CodePointBreakIterator::~CodePointBreakIterator() + clearCurrentCharIter(); + } + ++#if U_ICU_VERSION_MAJOR_NUM >= 74 ++bool CodePointBreakIterator::operator==(const BreakIterator& that) const ++#else + UBool CodePointBreakIterator::operator==(const BreakIterator& that) const ++#endif + { + if (typeid(*this) != typeid(that)) { + return FALSE; diff --git a/patches/php-8.0-intl-cxx17.patch b/patches/php-8.0-intl-cxx17.patch new file mode 100644 index 0000000..4b22e6f --- /dev/null +++ b/patches/php-8.0-intl-cxx17.patch @@ -0,0 +1,12 @@ +--- a/ext/intl/config.m4 ++++ b/ext/intl/config.m4 +@@ -83,7 +83,8 @@ + breakiterator/codepointiterator_methods.cpp" + + PHP_REQUIRE_CXX() +- PHP_CXX_COMPILE_STDCXX(11, mandatory, PHP_INTL_STDCXX) ++ dnl ICU 74+ headers use C++17 features (enable_if_t, void_t, is_same_v, if constexpr). ++ PHP_CXX_COMPILE_STDCXX(17, mandatory, PHP_INTL_STDCXX) + PHP_INTL_CXX_FLAGS="$INTL_COMMON_FLAGS $PHP_INTL_STDCXX $ICU_CXXFLAGS" + if test "$ext_shared" = "no"; then + PHP_ADD_SOURCES(PHP_EXT_DIR(intl), $PHP_INTL_CXX_SOURCES, $PHP_INTL_CXX_FLAGS) diff --git a/patches/php-8.0-libxml2-2.12-compat.patch b/patches/php-8.0-libxml2-2.12-compat.patch new file mode 100644 index 0000000..8daa361 --- /dev/null +++ b/patches/php-8.0-libxml2-2.12-compat.patch @@ -0,0 +1,29 @@ +--- a/ext/libxml/libxml.c ++++ b/ext/libxml/libxml.c +@@ -678,7 +678,7 @@ static void php_libxml_internal_error_handler(int error_type, const char *msg, + + PHP_LIBXML_API void php_libxml_structured_error_handler(void *userData, xmlErrorPtr error) + { +- _php_list_set_error_structure(error, NULL); ++ _php_list_set_error_structure((xmlError *)error, NULL); + + return; + } +@@ -968,7 +968,7 @@ PHP_FUNCTION(libxml_use_internal_errors) + LIBXML(error_list) = NULL; + } + } else { +- xmlSetStructuredErrorFunc(NULL, php_libxml_structured_error_handler); ++ xmlSetStructuredErrorFunc(NULL, (xmlStructuredErrorFunc)php_libxml_structured_error_handler); + if (LIBXML(error_list) == NULL) { + LIBXML(error_list) = (zend_llist *) emalloc(sizeof(zend_llist)); + zend_llist_init(LIBXML(error_list), sizeof(xmlError), _php_libxml_free_error, 0); +@@ -985,7 +985,7 @@ PHP_FUNCTION(libxml_get_last_error) + return; + } + +- error = xmlGetLastError(); ++ error = (xmlError *)xmlGetLastError(); + + if (error) { + object_init_ex(return_value, libxmlerror_class_entry); diff --git a/patches/php-8.0-libxml2-attribute-unused.patch b/patches/php-8.0-libxml2-attribute-unused.patch new file mode 100644 index 0000000..f01c8e6 --- /dev/null +++ b/patches/php-8.0-libxml2-attribute-unused.patch @@ -0,0 +1,11 @@ +--- a/ext/libxml/libxml.c 2026-06-28 20:11:37 ++++ b/ext/libxml/libxml.c 2026-06-28 20:11:37 +@@ -376,7 +376,7 @@ + static xmlOutputBufferPtr + php_libxml_output_buffer_create_filename(const char *URI, + xmlCharEncodingHandlerPtr encoder, +- int compression ATTRIBUTE_UNUSED) ++ int compression __attribute__((unused))) + { + xmlOutputBufferPtr ret; + xmlURIPtr puri; diff --git a/patches/php-8.0-libxml2-const-handler.patch b/patches/php-8.0-libxml2-const-handler.patch new file mode 100644 index 0000000..535ebed --- /dev/null +++ b/patches/php-8.0-libxml2-const-handler.patch @@ -0,0 +1,11 @@ +--- a/ext/libxml/libxml.c 2026-06-28 19:02:27 ++++ b/ext/libxml/libxml.c 2026-06-28 19:02:27 +@@ -947,7 +947,7 @@ + ZEND_PARSE_PARAMETERS_END(); + + current_handler = xmlStructuredError; +- if (current_handler && current_handler == php_libxml_structured_error_handler) { ++ if (current_handler && current_handler == (xmlStructuredErrorFunc)php_libxml_structured_error_handler) { + retval = 1; + } else { + retval = 0; diff --git a/tests/install_concurrency.bats b/tests/install_concurrency.bats new file mode 100644 index 0000000..3dee403 --- /dev/null +++ b/tests/install_concurrency.bats @@ -0,0 +1,70 @@ +#!/usr/bin/env bats + +# Tests for ASDF_CONCURRENCY pass-through to php-build, which does not read +# ASDF_CONCURRENCY natively and requires explicit translation to make arguments + +setup() { + export PLUGIN_DIR="${BATS_TEST_DIRNAME}/.." + export TEST_TEMP_DIR="$(mktemp -d)" + + # Extract and source only the target function to avoid install script side effects + sed -n '/^# php-build does not read ASDF_CONCURRENCY/,/^}$/p' \ + "${PLUGIN_DIR}/bin/install" > "${TEST_TEMP_DIR}/function.sh" + source "${TEST_TEMP_DIR}/function.sh" +} + +teardown() { + unset PHP_BUILD_EXTRA_MAKE_ARGUMENTS + unset ASDF_CONCURRENCY + rm -rf "${TEST_TEMP_DIR}" +} + +@test "should set PHP_BUILD_EXTRA_MAKE_ARGUMENTS to -j8 when ASDF_CONCURRENCY is 8" { + export ASDF_CONCURRENCY=8 + + setup_build_concurrency + + [ "${PHP_BUILD_EXTRA_MAKE_ARGUMENTS}" = "-j8" ] +} + +@test "should set PHP_BUILD_EXTRA_MAKE_ARGUMENTS to -j4 when ASDF_CONCURRENCY is 4" { + export ASDF_CONCURRENCY=4 + + setup_build_concurrency + + [ "${PHP_BUILD_EXTRA_MAKE_ARGUMENTS}" = "-j4" ] +} + +@test "should set PHP_BUILD_EXTRA_MAKE_ARGUMENTS to -j1 when ASDF_CONCURRENCY is 1" { + export ASDF_CONCURRENCY=1 + + setup_build_concurrency + + [ "${PHP_BUILD_EXTRA_MAKE_ARGUMENTS}" = "-j1" ] +} + +@test "should not set PHP_BUILD_EXTRA_MAKE_ARGUMENTS when ASDF_CONCURRENCY is not set" { + unset ASDF_CONCURRENCY + unset PHP_BUILD_EXTRA_MAKE_ARGUMENTS + + setup_build_concurrency + + [ -z "${PHP_BUILD_EXTRA_MAKE_ARGUMENTS:-}" ] +} + +@test "should not set PHP_BUILD_EXTRA_MAKE_ARGUMENTS when ASDF_CONCURRENCY is empty" { + export ASDF_CONCURRENCY="" + unset PHP_BUILD_EXTRA_MAKE_ARGUMENTS + + setup_build_concurrency + + [ -z "${PHP_BUILD_EXTRA_MAKE_ARGUMENTS:-}" ] +} + +@test "should produce a valid -j flag format that make accepts when ASDF_CONCURRENCY is set" { + export ASDF_CONCURRENCY=8 + + setup_build_concurrency + + [[ "${PHP_BUILD_EXTRA_MAKE_ARGUMENTS}" =~ ^-j[0-9]+$ ]] +} diff --git a/tests/install_cxxflags.bats b/tests/install_cxxflags.bats new file mode 100644 index 0000000..d76c585 --- /dev/null +++ b/tests/install_cxxflags.bats @@ -0,0 +1,117 @@ +#!/usr/bin/env bats + +# Tests for CXXFLAGS sanitization to prevent old C++ standard flags inherited +# from prior ICU-based PHP installations from blocking PHP 8.5+'s C++17 configure check + +setup() { + export PLUGIN_DIR="${BATS_TEST_DIRNAME}/.." + export TEST_TEMP_DIR="$(mktemp -d)" + + # Extract only the target function to avoid executing the rest of the install + # script which has side effects (php-build setup, version resolution, etc.) + sed -n '/^sanitize_cxxflags_for_modern_php()/,/^}$/p' \ + "${PLUGIN_DIR}/bin/install" > "${TEST_TEMP_DIR}/function.sh" + source "${TEST_TEMP_DIR}/function.sh" +} + +teardown() { + unset CXXFLAGS + rm -rf "${TEST_TEMP_DIR}" +} + +@test "should replace -std=c++11 with -std=c++17 for PHP 8.5.4" { + export CXXFLAGS="-std=c++11" + + sanitize_cxxflags_for_modern_php "8.5.4" + + [[ "${CXXFLAGS}" == *"-std=c++17"* ]] + [[ "${CXXFLAGS}" != *"-std=c++11"* ]] +} + +@test "should replace -std=c++14 with -std=c++17 for PHP 8.5.4" { + export CXXFLAGS="-std=c++14" + + sanitize_cxxflags_for_modern_php "8.5.4" + + [[ "${CXXFLAGS}" == *"-std=c++17"* ]] + [[ "${CXXFLAGS}" != *"-std=c++14"* ]] +} + +@test "should preserve -std=c++17 as it already satisfies the PHP 8.5 minimum requirement" { + export CXXFLAGS="-std=c++17" + + sanitize_cxxflags_for_modern_php "8.5.4" + + [[ "${CXXFLAGS}" == *"-std=c++17"* ]] +} + +@test "should set -std=c++17 when CXXFLAGS is unset for PHP 8.5.4" { + unset CXXFLAGS + + sanitize_cxxflags_for_modern_php "8.5.4" + + [[ "${CXXFLAGS}" == *"-std=c++17"* ]] +} + +@test "should set -std=c++17 when CXXFLAGS is empty for PHP 8.5.4" { + export CXXFLAGS="" + + sanitize_cxxflags_for_modern_php "8.5.4" + + [[ "${CXXFLAGS}" == *"-std=c++17"* ]] +} + +@test "should strip -std=c++11 and preserve all other flags matching the exact production failure scenario" { + # The production failure: old ICU pkg-config sets CXXFLAGS=-std=c++11 -stdlib=libc++ + # -DU_USING_ICU_NAMESPACE=1, which prevents PHP 8.5.4's C++17 configure check from passing + export CXXFLAGS="-std=c++11 -stdlib=libc++ -DU_USING_ICU_NAMESPACE=1" + + sanitize_cxxflags_for_modern_php "8.5.4" + + [[ "${CXXFLAGS}" == *"-std=c++17"* ]] + [[ "${CXXFLAGS}" == *"-stdlib=libc++"* ]] + [[ "${CXXFLAGS}" == *"-DU_USING_ICU_NAMESPACE=1"* ]] + [[ "${CXXFLAGS}" != *"-std=c++11"* ]] +} + +@test "should not modify CXXFLAGS for PHP 8.4.x because it does not require C++17" { + export CXXFLAGS="-std=c++11" + + sanitize_cxxflags_for_modern_php "8.4.3" + + [[ "${CXXFLAGS}" == "-std=c++11" ]] +} + +@test "should not modify CXXFLAGS for PHP 8.3.x because it does not require C++17" { + export CXXFLAGS="-std=c++11" + + sanitize_cxxflags_for_modern_php "8.3.15" + + [[ "${CXXFLAGS}" == "-std=c++11" ]] +} + +@test "should not modify CXXFLAGS for PHP 7.4.x because it does not require C++17" { + export CXXFLAGS="-std=c++11" + + sanitize_cxxflags_for_modern_php "7.4.33" + + [[ "${CXXFLAGS}" == "-std=c++11" ]] +} + +@test "should apply to PHP 8.5.0 as the boundary version that introduced the C++17 build requirement" { + export CXXFLAGS="-std=c++11" + + sanitize_cxxflags_for_modern_php "8.5.0" + + [[ "${CXXFLAGS}" == *"-std=c++17"* ]] + [[ "${CXXFLAGS}" != *"-std=c++11"* ]] +} + +@test "should apply to PHP 9.x to guard against future major versions that also require C++17 or higher" { + export CXXFLAGS="-std=c++11" + + sanitize_cxxflags_for_modern_php "9.0.0" + + [[ "${CXXFLAGS}" == *"-std=c++17"* ]] + [[ "${CXXFLAGS}" != *"-std=c++11"* ]] +} diff --git a/tests/install_dependencies.bats b/tests/install_dependencies.bats new file mode 100644 index 0000000..bba3cd1 --- /dev/null +++ b/tests/install_dependencies.bats @@ -0,0 +1,194 @@ +#!/usr/bin/env bats + +# Tests for macOS dependency configuration in the install script + +setup() { + export PLUGIN_DIR="${BATS_TEST_DIRNAME}/.." + export TEST_TEMP_DIR="$(mktemp -d)" + export ASDF_INSTALL_VERSION="8.5.4" + export ASDF_INSTALL_PATH="${TEST_TEMP_DIR}/install" + + # Mock brew command for macOS testing + export MOCK_BREW_PREFIX="/usr/local/opt" + mkdir -p "${TEST_TEMP_DIR}/bin" + # The include subdirectory must exist so resolve_brew_package_prefix treats the + # opt path as valid and returns it directly without falling back to the Cellar + mkdir -p "${TEST_TEMP_DIR}/opt/bzip2/include" + mkdir -p "${TEST_TEMP_DIR}/opt/libiconv/include" + + # The mock Cellar is isolated from the real system so tests never accidentally + # resolve headers from a pre-existing Homebrew installation on the host machine + mkdir -p "${TEST_TEMP_DIR}/cellar/bzip2/1.0.8/include" + mkdir -p "${TEST_TEMP_DIR}/cellar/libiconv/1.18/include" + + cat > "${TEST_TEMP_DIR}/bin/brew" < "${TEST_TEMP_DIR}/function.sh" + sed -n '/^# Configure macOS-specific dependencies for all PHP versions$/,/^}$/p' "${PLUGIN_DIR}/bin/install" >> "${TEST_TEMP_DIR}/function.sh" + source "${TEST_TEMP_DIR}/function.sh" +} + +teardown() { + rm -rf "${TEST_TEMP_DIR}" +} + +@test "should configure bzip2 paths on macOS for PHP 8.5.4" { + export OSTYPE="darwin23.0" + + setup_macos_dependencies "8.5.4" + + [[ "${PHP_BUILD_CONFIGURE_OPTS}" == *"--with-bz2=${TEST_TEMP_DIR}/opt/bzip2"* ]] +} + +@test "should configure bzip2 paths on macOS for PHP 8.4.0" { + export OSTYPE="darwin23.0" + + setup_macos_dependencies "8.4.0" + + [[ "${PHP_BUILD_CONFIGURE_OPTS}" == *"--with-bz2=${TEST_TEMP_DIR}/opt/bzip2"* ]] +} + +@test "should configure bzip2 paths on macOS for PHP 8.3.0" { + export OSTYPE="darwin23.0" + + setup_macos_dependencies "8.3.0" + + [[ "${PHP_BUILD_CONFIGURE_OPTS}" == *"--with-bz2=${TEST_TEMP_DIR}/opt/bzip2"* ]] +} + +@test "should configure libiconv paths on macOS for all PHP versions" { + export OSTYPE="darwin23.0" + + setup_macos_dependencies "8.5.4" + + [[ "${PHP_BUILD_CONFIGURE_OPTS}" == *"--with-iconv=${TEST_TEMP_DIR}/opt/libiconv"* ]] +} + +@test "should set LDFLAGS on macOS when brew is available" { + export OSTYPE="darwin23.0" + + setup_macos_dependencies "8.5.4" + + [[ "${LDFLAGS}" == *"-L${TEST_TEMP_DIR}/opt/lib"* ]] +} + +@test "should set CPPFLAGS on macOS when brew is available" { + export OSTYPE="darwin23.0" + + setup_macos_dependencies "8.5.4" + + [[ "${CPPFLAGS}" == *"-I${TEST_TEMP_DIR}/opt/include"* ]] +} + +@test "should not configure brew dependencies on Linux" { + export OSTYPE="linux-gnu" + + setup_macos_dependencies "8.5.4" + + [[ "${PHP_BUILD_CONFIGURE_OPTS}" != *"--with-bz2=/usr/local"* ]] +} + +@test "should handle missing brew command gracefully on macOS" { + export OSTYPE="darwin23.0" + export PATH="/usr/bin:/bin" + + setup_macos_dependencies "8.5.4" + + [ $? -eq 0 ] +} + +@test "should preserve existing PHP_BUILD_CONFIGURE_OPTS when adding bzip2" { + export OSTYPE="darwin23.0" + export PHP_BUILD_CONFIGURE_OPTS="--enable-mbstring --with-curl" + + setup_macos_dependencies "8.5.4" + + [[ "${PHP_BUILD_CONFIGURE_OPTS}" == *"--enable-mbstring"* ]] + [[ "${PHP_BUILD_CONFIGURE_OPTS}" == *"--with-curl"* ]] + [[ "${PHP_BUILD_CONFIGURE_OPTS}" == *"--with-bz2=${TEST_TEMP_DIR}/opt/bzip2"* ]] +} + +@test "should preserve existing LDFLAGS when adding brew lib path" { + export OSTYPE="darwin23.0" + export LDFLAGS="-L/custom/path" + + setup_macos_dependencies "8.5.4" + + [[ "${LDFLAGS}" == *"-L/custom/path"* ]] + [[ "${LDFLAGS}" == *"-L${TEST_TEMP_DIR}/opt/lib"* ]] +} + +@test "should preserve existing CPPFLAGS when adding brew include path" { + export OSTYPE="darwin23.0" + export CPPFLAGS="-I/custom/include" + + setup_macos_dependencies "8.5.4" + + [[ "${CPPFLAGS}" == *"-I/custom/include"* ]] + [[ "${CPPFLAGS}" == *"-I${TEST_TEMP_DIR}/opt/include"* ]] +} + +@test "should return opt prefix when include directory exists" { + # No setup needed — the shared setup already creates opt/bzip2/include + + result=$(resolve_brew_package_prefix bzip2) + + [ "$result" = "${TEST_TEMP_DIR}/opt/bzip2" ] +} + +@test "should repair the opt symlink in-place when its include directory is absent" { + # Simulate the production failure: opt dir exists but include/ is absent because + # Homebrew did not auto-link the keg-only package after an upgrade or reinstall + rm -rf "${TEST_TEMP_DIR}/opt/bzip2/include" + + resolve_brew_package_prefix bzip2 + + # The opt path must now resolve to the Cellar version so macOS dyld can follow + # the library's embedded install_name which was originally set to the opt path + [ -L "${TEST_TEMP_DIR}/opt/bzip2" ] + [ -d "${TEST_TEMP_DIR}/opt/bzip2/include" ] +} + +@test "should leave opt prefix unchanged when Cellar fallback also has no include directory" { + # When neither opt nor Cellar has a valid include dir, leave opt as-is and let + # the configure script decide whether to error or skip the dependency + rm -rf "${TEST_TEMP_DIR}/opt/bzip2/include" + rm -rf "${TEST_TEMP_DIR}/cellar/bzip2/1.0.8/include" + + result=$(resolve_brew_package_prefix bzip2) + + [ "$result" = "${TEST_TEMP_DIR}/opt/bzip2" ] +} + +@test "should return empty string when brew cannot find the package" { + cat > "${TEST_TEMP_DIR}/bin/brew" <<'EOF' +#!/bin/bash +exit 1 +EOF + chmod +x "${TEST_TEMP_DIR}/bin/brew" + + result=$(resolve_brew_package_prefix bzip2) + + [ -z "$result" ] +} diff --git a/tests/install_integration.bats b/tests/install_integration.bats new file mode 100644 index 0000000..f9c384d --- /dev/null +++ b/tests/install_integration.bats @@ -0,0 +1,243 @@ +#!/usr/bin/env bats + +# Integration tests for PHP installation with real-world scenarios + +setup() { + export PLUGIN_DIR="${BATS_TEST_DIRNAME}/.." + export TEST_TEMP_DIR="$(mktemp -d)" + export ASDF_INSTALL_VERSION="8.5.4" + export ASDF_INSTALL_PATH="${TEST_TEMP_DIR}/install" + + mkdir -p "${TEST_TEMP_DIR}/bin" + + # The include subdirectory must exist so resolve_brew_package_prefix treats the + # opt path as valid and returns it directly without falling back to the Cellar + mkdir -p "${TEST_TEMP_DIR}/opt/bzip2/include" + mkdir -p "${TEST_TEMP_DIR}/opt/libiconv/include" + + # The mock Cellar is isolated from the real system so tests never accidentally + # resolve headers from a pre-existing Homebrew installation on the host machine + mkdir -p "${TEST_TEMP_DIR}/cellar/bzip2/1.0.8/include" + mkdir -p "${TEST_TEMP_DIR}/cellar/libiconv/1.18/include" + + cat > "${TEST_TEMP_DIR}/bin/brew" < "${TEST_TEMP_DIR}/function.sh" + sed -n '/^# Configure macOS-specific dependencies for all PHP versions$/,/^}$/p' "${PLUGIN_DIR}/bin/install" >> "${TEST_TEMP_DIR}/function.sh" + source "${TEST_TEMP_DIR}/function.sh" +} + +teardown() { + rm -rf "${TEST_TEMP_DIR}" +} + +@test "PHP 8.5.4 installation should include bzip2 configuration on macOS" { + export OSTYPE="darwin23.0" + unset PHP_BUILD_CONFIGURE_OPTS + + setup_macos_dependencies "8.5.4" + + [[ "${PHP_BUILD_CONFIGURE_OPTS}" == *"--with-bz2=${TEST_TEMP_DIR}/opt/bzip2"* ]] +} + +@test "PHP 8.4.3 installation should include bzip2 configuration on macOS" { + export OSTYPE="darwin23.0" + unset PHP_BUILD_CONFIGURE_OPTS + + setup_macos_dependencies "8.4.3" + + [[ "${PHP_BUILD_CONFIGURE_OPTS}" == *"--with-bz2=${TEST_TEMP_DIR}/opt/bzip2"* ]] +} + +@test "PHP 8.3.15 installation should include bzip2 configuration on macOS" { + export OSTYPE="darwin23.0" + unset PHP_BUILD_CONFIGURE_OPTS + + setup_macos_dependencies "8.3.15" + + [[ "${PHP_BUILD_CONFIGURE_OPTS}" == *"--with-bz2=${TEST_TEMP_DIR}/opt/bzip2"* ]] +} + +@test "PHP 8.2.28 installation should include bzip2 configuration on macOS" { + export OSTYPE="darwin23.0" + unset PHP_BUILD_CONFIGURE_OPTS + + setup_macos_dependencies "8.2.28" + + [[ "${PHP_BUILD_CONFIGURE_OPTS}" == *"--with-bz2=${TEST_TEMP_DIR}/opt/bzip2"* ]] +} + +@test "PHP 8.1.31 installation should include bzip2 configuration on macOS" { + export OSTYPE="darwin23.0" + unset PHP_BUILD_CONFIGURE_OPTS + + setup_macos_dependencies "8.1.31" + + [[ "${PHP_BUILD_CONFIGURE_OPTS}" == *"--with-bz2=${TEST_TEMP_DIR}/opt/bzip2"* ]] +} + +@test "PHP 8.0.30 installation should include bzip2 configuration on macOS" { + export OSTYPE="darwin23.0" + unset PHP_BUILD_CONFIGURE_OPTS + + setup_macos_dependencies "8.0.30" + + [[ "${PHP_BUILD_CONFIGURE_OPTS}" == *"--with-bz2=${TEST_TEMP_DIR}/opt/bzip2"* ]] +} + +@test "PHP 7.4.33 installation should include bzip2 configuration on macOS" { + export OSTYPE="darwin23.0" + unset PHP_BUILD_CONFIGURE_OPTS + + setup_macos_dependencies "7.4.33" + + [[ "${PHP_BUILD_CONFIGURE_OPTS}" == *"--with-bz2=${TEST_TEMP_DIR}/opt/bzip2"* ]] +} + +@test "configuration should include both LDFLAGS and CPPFLAGS for Homebrew libraries" { + export OSTYPE="darwin23.0" + unset LDFLAGS + unset CPPFLAGS + + setup_macos_dependencies "8.5.4" + + [[ "${LDFLAGS}" == *"-L${TEST_TEMP_DIR}/opt/lib"* ]] + [[ "${CPPFLAGS}" == *"-I${TEST_TEMP_DIR}/opt/include"* ]] +} + +@test "should configure both bzip2 and libiconv on macOS" { + export OSTYPE="darwin23.0" + unset PHP_BUILD_CONFIGURE_OPTS + + setup_macos_dependencies "8.5.4" + + [[ "${PHP_BUILD_CONFIGURE_OPTS}" == *"--with-bz2=${TEST_TEMP_DIR}/opt/bzip2"* ]] + [[ "${PHP_BUILD_CONFIGURE_OPTS}" == *"--with-iconv=${TEST_TEMP_DIR}/opt/libiconv"* ]] +} + +@test "should not configure brew paths on Linux" { + export OSTYPE="linux-gnu" + unset PHP_BUILD_CONFIGURE_OPTS + + setup_macos_dependencies "8.5.4" + + [[ -z "${PHP_BUILD_CONFIGURE_OPTS}" ]] || [[ "${PHP_BUILD_CONFIGURE_OPTS}" != *"--with-bz2="* ]] +} + +@test "should not fail when brew is not available on macOS" { + export OSTYPE="darwin23.0" + export PATH="/usr/bin:/bin" + unset PHP_BUILD_CONFIGURE_OPTS + + run setup_macos_dependencies "8.5.4" + + [ "$status" -eq 0 ] +} + +@test "should append to existing configuration options" { + export OSTYPE="darwin23.0" + export PHP_BUILD_CONFIGURE_OPTS="--enable-mbstring --with-curl" + + setup_macos_dependencies "8.5.4" + + [[ "${PHP_BUILD_CONFIGURE_OPTS}" == *"--enable-mbstring"* ]] + [[ "${PHP_BUILD_CONFIGURE_OPTS}" == *"--with-curl"* ]] + [[ "${PHP_BUILD_CONFIGURE_OPTS}" == *"--with-bz2=${TEST_TEMP_DIR}/opt/bzip2"* ]] + [[ "${PHP_BUILD_CONFIGURE_OPTS}" == *"--with-iconv=${TEST_TEMP_DIR}/opt/libiconv"* ]] +} + +@test "should append to existing LDFLAGS" { + export OSTYPE="darwin23.0" + export LDFLAGS="-L/custom/lib -lz" + + setup_macos_dependencies "8.5.4" + + [[ "${LDFLAGS}" == *"-L/custom/lib"* ]] + [[ "${LDFLAGS}" == *"-lz"* ]] + [[ "${LDFLAGS}" == *"-L${TEST_TEMP_DIR}/opt/lib"* ]] +} + +@test "should append to existing CPPFLAGS" { + export OSTYPE="darwin23.0" + export CPPFLAGS="-I/custom/include -DCUSTOM_FLAG" + + setup_macos_dependencies "8.5.4" + + [[ "${CPPFLAGS}" == *"-I/custom/include"* ]] + [[ "${CPPFLAGS}" == *"-DCUSTOM_FLAG"* ]] + [[ "${CPPFLAGS}" == *"-I${TEST_TEMP_DIR}/opt/include"* ]] +} + +@test "should skip bzip2 configuration if directory does not exist" { + export OSTYPE="darwin23.0" + unset PHP_BUILD_CONFIGURE_OPTS + + # Remove both opt and Cellar paths so resolve_brew_package_prefix finds nothing valid + rm -rf "${TEST_TEMP_DIR}/opt/bzip2" + rm -rf "${TEST_TEMP_DIR}/cellar/bzip2" + + setup_macos_dependencies "8.5.4" + + [[ -z "${PHP_BUILD_CONFIGURE_OPTS}" ]] || [[ "${PHP_BUILD_CONFIGURE_OPTS}" != *"--with-bz2="* ]] +} + +@test "should skip libiconv configuration if directory does not exist" { + export OSTYPE="darwin23.0" + unset PHP_BUILD_CONFIGURE_OPTS + + # Remove both opt and Cellar paths so resolve_brew_package_prefix finds nothing valid + rm -rf "${TEST_TEMP_DIR}/opt/libiconv" + rm -rf "${TEST_TEMP_DIR}/cellar/libiconv" + + setup_macos_dependencies "8.5.4" + + [[ -z "${PHP_BUILD_CONFIGURE_OPTS}" ]] || [[ "${PHP_BUILD_CONFIGURE_OPTS}" != *"--with-iconv="* ]] +} + +@test "should handle version with RC suffix" { + export OSTYPE="darwin23.0" + unset PHP_BUILD_CONFIGURE_OPTS + + setup_macos_dependencies "8.5.0RC1" + + [[ "${PHP_BUILD_CONFIGURE_OPTS}" == *"--with-bz2=${TEST_TEMP_DIR}/opt/bzip2"* ]] +} + +@test "should handle version with beta suffix" { + export OSTYPE="darwin23.0" + unset PHP_BUILD_CONFIGURE_OPTS + + setup_macos_dependencies "8.5.0beta2" + + [[ "${PHP_BUILD_CONFIGURE_OPTS}" == *"--with-bz2=${TEST_TEMP_DIR}/opt/bzip2"* ]] +} + +@test "should handle version with alpha suffix" { + export OSTYPE="darwin23.0" + unset PHP_BUILD_CONFIGURE_OPTS + + setup_macos_dependencies "8.6.0alpha1" + + [[ "${PHP_BUILD_CONFIGURE_OPTS}" == *"--with-bz2=${TEST_TEMP_DIR}/opt/bzip2"* ]] +}