From b989e91894ca4f2573728b7b9e36836dc3138f95 Mon Sep 17 00:00:00 2001 From: Michael Wegener Date: Tue, 21 Jul 2026 15:27:46 +0200 Subject: [PATCH 1/6] refactor: out-of-tree build architecture + fix .deb version detection Source tree is never written to during builds. All build artifacts go to BUILD_DIR (default /tmp/build-firebird), which is copied from source and worked from. This prevents stale .so files, .deb version mismatches, and source tree pollution. Changes: - build-extension action: copy source to BUILD_DIR, all steps cd $BUILD_DIR - ci.yml: pdo_fbird build + test reference BUILD_DIR - coverage.yml/sanitizers.yml: extension-path references BUILD_DIR - release-linux.yml/release-macos.yml: out-of-tree build pattern - build-precompiled.sh: --build-dir parameter - packaging/debian/build.sh: BUILD_DIR copy, REPO_ROOT repoint, fix find command (-maxdepth 1, ! -name *dbgsym*) to prevent stale .deb selection - packaging/debian/build-matrix.sh: source mounted /src:ro, build in /build (ephemeral --rm container), Sury PPA quoting fix ($distro not $(lsb_release -sc)) Fixes: PHP 8.4 .deb version bug (stale 13.0.0-1 file picked by find instead of freshly built 13.0.1-rc.1-1) Verified: 4/4 PHP versions (8.2-8.5) x bookworm pass with correct version --- .github/actions/build-extension/action.yml | 38 +++++++++++++++------- .github/workflows/ci.yml | 9 +++-- .github/workflows/coverage.yml | 6 ++-- .github/workflows/release-linux.yml | 27 +++++++-------- .github/workflows/release-macos.yml | 18 ++++++---- .github/workflows/sanitizers.yml | 14 ++++---- packaging/debian/build-matrix.sh | 18 ++++++++-- packaging/debian/build.sh | 35 ++++++++++++++++---- scripts/build-precompiled.sh | 32 ++++++++++++++---- 9 files changed, 134 insertions(+), 63 deletions(-) diff --git a/.github/actions/build-extension/action.yml b/.github/actions/build-extension/action.yml index f93e8632..57dab90b 100644 --- a/.github/actions/build-extension/action.yml +++ b/.github/actions/build-extension/action.yml @@ -1,5 +1,5 @@ name: 'Build PHP Extension' -description: 'Run phpize, configure, and make for the firebird and optional pdo_fbird extensions' +description: 'Run phpize, configure, and make for the firebird and optional pdo_fbird extensions. Out-of-tree build: source is copied to a build directory, keeping the source tree pristine.' inputs: firebird-path: description: 'Path to Firebird client installation' @@ -38,7 +38,7 @@ inputs: required: false default: '' phpize-clean: - description: 'Run phpize --clean and remove stale .so files before phpize. Prevents header/library version mismatches when the source tree is shared between containers with different Firebird client versions (e.g. FB 3.0 vs FB 5.0).' + description: 'Run phpize --clean before phpize (in the build directory)' required: false default: 'true' php-bin-dir: @@ -53,10 +53,30 @@ inputs: description: 'Extra paths to prepend to LD_LIBRARY_PATH' required: false default: '' + build-dir: + description: 'Directory for out-of-tree build (source is copied here). Set to BUILD_DIR env var for subsequent steps.' + required: false + default: '/tmp/build-firebird' runs: using: composite steps: + - name: Prepare build directory + shell: bash + env: + BUILD_DIR: ${{ inputs.build-dir }} + run: | + # Out-of-tree build: copy source to BUILD_DIR, keep source tree pristine. + # jane: Source tree is NEVER written to. All artifacts (modules/*.so, + # config.h, Makefile, debian/, autom4te.cache/) stay in BUILD_DIR. + # This prevents stale .so between containers (SIGSEGV fix), + # .deb version mismatches, and ensures idempotent builds. + rm -rf "$BUILD_DIR" + mkdir -p "$BUILD_DIR" + cp -a . "$BUILD_DIR/" + echo "BUILD_DIR=$BUILD_DIR" >> "$GITHUB_ENV" + echo "Source copied to $BUILD_DIR" + - name: phpize shell: bash env: @@ -66,20 +86,12 @@ runs: if [ -n "$PHP_BIN_DIR" ]; then export PATH="$PHP_BIN_DIR:$PATH" fi + cd "${BUILD_DIR}" if [ "$PHPIZE_CLEAN" = 'true' ]; then - # Remove stale .so files BEFORE phpize --clean. - # jane: The /ext directory is a shared bind mount across all Docker - # containers (PHP 8.2-8.5 × FB 3/4/5). If a previous container compiled - # against FB 5.0 headers (FB_API_VER=50) and the current container has - # FB 3.0 (FB_API_VER=30), the stale .so calls IResultSet::close() which - # dispatches through a vtable slot that doesn't exist in FB 3.0's - # libfbclient → NULL function pointer → SIGSEGV during shutdown. - # See: src/cpp/fb_statement.hpp:409 (#if FB_API_VER >= 40 guard). rm -f modules/firebird.so pdo_fbird/modules/pdo_fbird.so 2>/dev/null || true phpize --clean fi phpize - # Clean .dep files between PHP versions (absolute paths differ) find . -name '*.dep' -delete 2>/dev/null || true - name: Configure @@ -104,6 +116,8 @@ runs: export LD_LIBRARY_PATH="$LD_PATH_EXTRA:${LD_LIBRARY_PATH:-}" fi + cd "${BUILD_DIR}" + ARGS="--with-firebird=$FB_PATH" if [ "$BUILD_PDO" = 'true' ]; then ARGS="$ARGS --with-pdo-fbird=$FB_PATH" @@ -133,6 +147,7 @@ runs: env: PRE_BUILD: ${{ inputs.pre-build-script }} run: | + cd "${BUILD_DIR}" eval "$PRE_BUILD" - name: Build @@ -147,4 +162,5 @@ runs: if [ -n "$LD_PATH_EXTRA" ]; then export LD_LIBRARY_PATH="$LD_PATH_EXTRA:${LD_LIBRARY_PATH:-}" fi + cd "${BUILD_DIR}" make -j"$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bc7f99d3..72df8ef0 100755 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -174,8 +174,7 @@ jobs: shell: bash run: | set -eu - cd pdo_fbird - # Remove stale .so before phpize (same rationale as build-extension action) + cd "${BUILD_DIR}/pdo_fbird" rm -f modules/pdo_fbird.so 2>/dev/null || true phpize --clean phpize @@ -188,7 +187,7 @@ jobs: uses: ./.github/actions/verify-extension with: mode: full - extension-path: modules/firebird.so + extension-path: ${{ env.BUILD_DIR }}/modules/firebird.so ld-library-path-extra: /opt/firebird-client/lib - name: Cache Composer dependencies @@ -218,8 +217,8 @@ jobs: run: | set -eu - EXTENSION_PATH="$(pwd)/modules/firebird.so" - PDO_FBIRD_PATH="$(pwd)/pdo_fbird/modules/pdo_fbird.so" + EXTENSION_PATH="${BUILD_DIR}/modules/firebird.so" + PDO_FBIRD_PATH="${BUILD_DIR}/pdo_fbird/modules/pdo_fbird.so" # Build -d flags: load both extensions explicitly. # jane: make test only loads the main extension; run-tests.php with diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index e634a4a3..1d7375be 100755 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -98,7 +98,7 @@ jobs: uses: ./.github/actions/verify-extension with: mode: full - extension-path: modules/firebird.so + extension-path: ${{ env.BUILD_DIR }}/modules/firebird.so ld-library-path-extra: /opt/firebird-client/lib - name: Install Composer dependencies @@ -120,8 +120,8 @@ jobs: run: | set -euo pipefail - EXTENSION_PATH="$(pwd)/modules/firebird.so" - PDO_FBIRD_PATH="$(pwd)/pdo_fbird/modules/pdo_fbird.so" + EXTENSION_PATH="${BUILD_DIR}/modules/firebird.so" + PDO_FBIRD_PATH="${BUILD_DIR}/pdo_fbird/modules/pdo_fbird.so" # Build extension flags - load both firebird and pdo_fbird explicitly. # Per AGENTS.md: `make test` only loads firebird.so via the Makefile, diff --git a/.github/workflows/release-linux.yml b/.github/workflows/release-linux.yml index 6ccf6b48..e4a55121 100644 --- a/.github/workflows/release-linux.yml +++ b/.github/workflows/release-linux.yml @@ -396,27 +396,24 @@ jobs: export FB_ROOT=/opt/firebird # Ensure Firebird libraries are discoverable regardless of PHP_LIBDIR (lib vs lib64) - # config.m4 line 64 uses $PHP_FIREBIRD/$PHP_LIBDIR which may be lib64 on x86_64 - # Force lib64 -> lib symlink (remove any pre-existing empty dir from source builds) rm -rf "${FB_ROOT}/lib64" 2>/dev/null || true ln -sf lib "${FB_ROOT}/lib64" + # Out-of-tree build: copy source to BUILD_DIR, keep source tree pristine + export BUILD_DIR="/tmp/build-firebird" + rm -rf "$BUILD_DIR" + mkdir -p "$BUILD_DIR" + cp -a . "$BUILD_DIR/" + cd "$BUILD_DIR" + echo "BUILD_DIR=$BUILD_DIR" >> $GITHUB_ENV + echo "Building with:" php -v | head -1 echo "Firebird: ${FB_ROOT}" + echo "BUILD_DIR: ${BUILD_DIR}" - # Clean any previous builds - phpize --clean 2>/dev/null || true - - # Build with LTO for optimized release binaries - # LTO is now handled by config.m4 (default: enabled, --disable-fbird-lto to opt out) - # -rpath-link helps the linker resolve DT_NEEDED transitive dependencies - # (e.g. libtommath.so needed by libfbclient.so) during configure link tests export LDFLAGS="${LDFLAGS:-} -Wl,-rpath-link,${FB_ROOT}/lib" - # musl source-built libfbclient is a C++ library; PHP's configure tests - # link with cc (C compiler) so we must explicitly add -lstdc++ and the - # transitive deps that libfbclient.so DT_NEEDS. if [ "${{ matrix.libc }}" = "musl" ]; then export LIBS="-lstdc++ -lm" echo "musl: added LIBS='${LIBS}' for C++ libfbclient linking" @@ -450,13 +447,13 @@ jobs: echo "FB_ROOT: ${FB_ROOT}" echo "EXT_VERSION: ${EXT_VERSION}" ls -la "${FB_ROOT}/lib/" || echo "FB_ROOT/lib not found" - ls -la modules/ || echo "modules/ not found" + ls -la "${BUILD_DIR}/modules/" || echo "modules/ not found" which patchelf && patchelf --version || echo "patchelf not found" echo "=== End Debug ===" - # Run build script (skip compilation since already done) + # Run build script (skip compilation since already done in BUILD_DIR) chmod +x scripts/build-precompiled.sh - bash -x scripts/build-precompiled.sh --skip-build --checksums --verbose \ + bash -x scripts/build-precompiled.sh --skip-build --build-dir "${BUILD_DIR}" --checksums --verbose \ "${{ matrix.php }}" "${{ matrix.variant }}" "${{ matrix.arch }}" 2>&1 # Get output filename diff --git a/.github/workflows/release-macos.yml b/.github/workflows/release-macos.yml index d7623d14..c910ce5d 100644 --- a/.github/workflows/release-macos.yml +++ b/.github/workflows/release-macos.yml @@ -225,13 +225,19 @@ jobs: export PATH="/opt/php/${{ matrix.php }}-${{ matrix.variant }}/bin:${PATH}" export FB_ROOT=/opt/firebird + # Out-of-tree build: copy source to BUILD_DIR, keep source tree pristine + export BUILD_DIR="/tmp/build-firebird" + rm -rf "$BUILD_DIR" + mkdir -p "$BUILD_DIR" + cp -a . "$BUILD_DIR/" + cd "$BUILD_DIR" + echo "BUILD_DIR=$BUILD_DIR" >> $GITHUB_ENV + echo "Building with:" php -v | head -1 echo "Firebird: ${FB_ROOT}" + echo "BUILD_DIR: ${BUILD_DIR}" - phpize --clean 2>/dev/null || true - - # LTO for optimized release binaries export CFLAGS="${CFLAGS:-} -O2" export LDFLAGS="${LDFLAGS:-} -L${FB_ROOT}/lib" phpize @@ -266,10 +272,10 @@ jobs: mkdir -p "${DIST_DIR}/lib" # Copy extension - cp modules/firebird.so "${DIST_DIR}/firebird.so" + cp "${BUILD_DIR}/modules/firebird.so" "${DIST_DIR}/firebird.so" # Copy pdo_fbird.so if built - if [ -f pdo_fbird/modules/pdo_fbird.so ]; then - cp pdo_fbird/modules/pdo_fbird.so "${DIST_DIR}/pdo_fbird.so" + if [ -f "${BUILD_DIR}/pdo_fbird/modules/pdo_fbird.so" ]; then + cp "${BUILD_DIR}/pdo_fbird/modules/pdo_fbird.so" "${DIST_DIR}/pdo_fbird.so" fi # Bundle libfbclient.dylib and its dependencies diff --git a/.github/workflows/sanitizers.yml b/.github/workflows/sanitizers.yml index 3e9caf7c..b7e9fb15 100755 --- a/.github/workflows/sanitizers.yml +++ b/.github/workflows/sanitizers.yml @@ -97,7 +97,7 @@ jobs: uses: ./.github/actions/verify-extension with: mode: build-only - extension-path: modules/firebird.so + extension-path: ${{ env.BUILD_DIR }}/modules/firebird.so ld-library-path-extra: /opt/firebird-client/lib sanitizer-type: asan @@ -209,7 +209,7 @@ jobs: uses: ./.github/actions/verify-extension with: mode: full - extension-path: modules/firebird.so + extension-path: ${{ env.BUILD_DIR }}/modules/firebird.so ld-library-path-extra: /opt/firebird-client/lib - name: Create LSan suppressions file @@ -256,8 +256,8 @@ jobs: echo "=== Running tests with LeakSanitizer ===" - EXTENSION_PATH="$(pwd)/modules/firebird.so" - PDO_FBIRD_PATH="$(pwd)/pdo_fbird/modules/pdo_fbird.so" + EXTENSION_PATH="${BUILD_DIR}/modules/firebird.so" + PDO_FBIRD_PATH="${BUILD_DIR}/pdo_fbird/modules/pdo_fbird.so" # Per AGENTS.md: `make test` only loads firebird.so and ignores # PHP_TEST_SHARED_EXTENSIONS, causing ALL pdo_fbird tests to SKIP. @@ -429,7 +429,7 @@ jobs: uses: ./.github/actions/verify-extension with: mode: best-effort - extension-path: modules/firebird.so + extension-path: ${{ env.BUILD_DIR }}/modules/firebird.so ld-library-path-extra: /opt/firebird-client/lib php-bin: /opt/php-tsan/bin/php sanitizer-type: tsan @@ -470,8 +470,8 @@ jobs: echo "=== Running tests with ThreadSanitizer (ZTS) ===" - EXTENSION_PATH="$(pwd)/modules/firebird.so" - PDO_FBIRD_PATH="$(pwd)/pdo_fbird/modules/pdo_fbird.so" + EXTENSION_PATH="${BUILD_DIR}/modules/firebird.so" + PDO_FBIRD_PATH="${BUILD_DIR}/pdo_fbird/modules/pdo_fbird.so" # Per AGENTS.md: `make test` only loads firebird.so and ignores # PHP_TEST_SHARED_EXTENSIONS, causing ALL pdo_fbird tests to SKIP. diff --git a/packaging/debian/build-matrix.sh b/packaging/debian/build-matrix.sh index a8d2bc3a..f8d7a1af 100755 --- a/packaging/debian/build-matrix.sh +++ b/packaging/debian/build-matrix.sh @@ -207,9 +207,15 @@ build_package() { local fb_vol="" [ -d "$fb_host_dir" ] && fb_vol="-v ${fb_host_dir}:${fb_mount}" + # Out-of-tree build: source mounted READ-ONLY, build happens in /build (ephemeral) + # jane: No Docker volume for /build — the container is --rm, so /build is + # automatically cleaned when the container exits. The source tree at /src + # is never written to. + local build_dir="/build" + docker run --rm \ --platform "$platform" \ - -v "${REPO_ROOT}:/ext" \ + -v "${REPO_ROOT}:/src:ro" \ -v "${abs_output}:/output" \ $fb_vol \ -e PHP_VERSION="$php_ver" \ @@ -217,6 +223,8 @@ build_package() { -e ARCH="$arch" \ -e OUTPUT_DIR="/output" \ -e FB_ROOT="${fb_mount}" \ + -e BUILD_DIR="${build_dir}" \ + -e SOURCE_DIR="/src" \ "$docker_image" \ bash -c "export DEBIAN_FRONTEND=noninteractive && \ $(if [ "$(is_ubuntu "$distro")" = "true" ]; then @@ -224,7 +232,11 @@ build_package() { apt-get install -y -qq lsb-release ca-certificates curl && \ curl -sSLo /tmp/keyring.deb https://packages.sury.org/debsuryorg-archive-keyring.deb && \ dpkg -i /tmp/keyring.deb && \ - printf 'Types: deb\nURIs: https://packages.sury.org/php/\nSuites: \$(lsb_release -sc)\nComponents: main\nSigned-By: /usr/share/keyrings/debsuryorg-archive-keyring.gpg\n' > /etc/apt/sources.list.d/php.sources && \ + echo 'Types: deb' > /etc/apt/sources.list.d/php.sources && \ + echo 'URIs: https://packages.sury.org/php/' >> /etc/apt/sources.list.d/php.sources && \ + echo 'Suites: $distro' >> /etc/apt/sources.list.d/php.sources && \ + echo 'Components: main' >> /etc/apt/sources.list.d/php.sources && \ + echo 'Signed-By: /usr/share/keyrings/debsuryorg-archive-keyring.gpg' >> /etc/apt/sources.list.d/php.sources && \ apt-get update -qq && \ apt-get install -y -qq --no-install-recommends \ php${php_ver}-cli php${php_ver}-dev \ @@ -238,7 +250,7 @@ build_package() { libicu-dev libxml2-dev libtommath1 patchelf dpkg-dev debhelper \ jq curl wget git ca-certificates &&" fi) \ - cd /ext && bash packaging/debian/build.sh --php-version $php_ver --distro $distro --arch $arch --output-dir /output" \ + bash /src/packaging/debian/build.sh --php-version $php_ver --distro $distro --arch $arch --output-dir /output --build-dir ${build_dir}" \ > "$result_log" 2>&1 || exit_code=$? if [ $exit_code -eq 0 ]; then diff --git a/packaging/debian/build.sh b/packaging/debian/build.sh index e65b0e4e..79b02852 100755 --- a/packaging/debian/build.sh +++ b/packaging/debian/build.sh @@ -34,6 +34,7 @@ DISTRO="bookworm" ARCH="x86_64" FB_ROOT="${FB_ROOT:-/opt/firebird}" OUTPUT_DIR="${OUTPUT_DIR:-dist/deb}" +BUILD_DIR="${BUILD_DIR:-}" VERBOSE=0 # ----------------------------------------------------------------------------- @@ -64,6 +65,8 @@ while [ $# -gt 0 ]; do --output-dir) OUTPUT_DIR="$2"; shift 2 ;; --output-dir=*) OUTPUT_DIR="${1#--output-dir=}"; shift ;; --verbose|-v) VERBOSE=1; shift ;; + --build-dir) BUILD_DIR="$2"; shift 2 ;; + --build-dir=*) BUILD_DIR="${1#--build-dir=}"; shift ;; --help|-h) usage ;; *) echo "ERROR: Unknown argument: $1" >&2; exit 1 ;; esac @@ -102,7 +105,22 @@ log "Using: $PHPIZE, $PHPCONFIG" # ----------------------------------------------------------------------------- SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +SOURCE_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" + +# Out-of-tree build: if BUILD_DIR is set, copy source there and work from it. +# jane: REPO_ROOT is repointed to BUILD_DIR so all cd "$REPO_ROOT" calls and +# packaging/debian/* path references resolve to the build copy, not the +# read-only source. This prevents build artifacts from polluting the source +# tree and fixes .deb version mismatches (stale debian/changelog template). +if [ -n "$BUILD_DIR" ] && [ "$BUILD_DIR" != "$SOURCE_ROOT" ]; then + log_info "Out-of-tree build: copying source to ${BUILD_DIR}" + mkdir -p "$BUILD_DIR" + rm -rf "${BUILD_DIR}"/* "${BUILD_DIR}"/.[!.]* 2>/dev/null || true + cp -a "$SOURCE_ROOT"/* "$BUILD_DIR/" + REPO_ROOT="$BUILD_DIR" +else + REPO_ROOT="$SOURCE_ROOT" +fi cd "$REPO_ROOT" # ----------------------------------------------------------------------------- @@ -349,18 +367,23 @@ dpkg-buildpackage -us -uc -b -d 2>&1 || { # Step 6: Find and report the .deb file # ----------------------------------------------------------------------------- -# dpkg-buildpackage puts the .deb in the parent directory -DEB_FILE=$(find "${REPO_ROOT}/.." -name "php${PHP_VERSION}-firebird_*.deb" -type f | head -n 1) +# dpkg-buildpackage puts the .deb in the parent directory of REPO_ROOT. +# jane: -maxdepth 1 is critical - without it, find searches the ENTIRE +# filesystem (including /src/dist/ which has stale .deb files from previous +# test runs). head -n 1 would pick the stale 13.0.0-1 file instead of the +# freshly built 13.0.1-rc.1-1 file. ! -name "*dbgsym*" excludes debug symbol +# packages that dpkg-buildpackage also produces. +DEB_FILE=$(find "${REPO_ROOT}/.." -maxdepth 1 -name "php${PHP_VERSION}-firebird_*.deb" -type f ! -name "*dbgsym*" | head -n 1) if [ -z "$DEB_FILE" ]; then - # Fallback: search in current directory - DEB_FILE=$(find . -name "php${PHP_VERSION}-firebird_*.deb" -type f | head -n 1) + # Fallback: search in current directory (same constraints) + DEB_FILE=$(find . -maxdepth 1 -name "php${PHP_VERSION}-firebird_*.deb" -type f ! -name "*dbgsym*" | head -n 1) fi if [ -z "$DEB_FILE" ]; then log_error "No .deb file found after build" log_error "Searching for any .deb files..." - find "${REPO_ROOT}/.." . -name "*.deb" -type f 2>/dev/null | head -10 + find "${REPO_ROOT}/.." -maxdepth 1 -name "*.deb" -type f ! -name "*dbgsym*" 2>/dev/null | head -10 exit 1 fi diff --git a/scripts/build-precompiled.sh b/scripts/build-precompiled.sh index 4dbd8078..9f46c7a5 100755 --- a/scripts/build-precompiled.sh +++ b/scripts/build-precompiled.sh @@ -50,6 +50,7 @@ SKIP_BUILD=false GENERATE_CHECKSUMS=true VERBOSE=0 RUN_VERIFY=false +BUILD_DIR="${BUILD_DIR:-}" # Set via --build-dir or env var; empty = current dir # Auto-detect platform case "$(uname -s)" in @@ -356,6 +357,14 @@ while [[ $# -gt 0 ]]; do RUN_VERIFY=true shift ;; + --build-dir) + BUILD_DIR="$2" + shift 2 + ;; + --build-dir=*) + BUILD_DIR="${1#--build-dir=}" + shift + ;; --help|-h) usage ;; @@ -385,6 +394,15 @@ PHP_VERSION="${PHP_VERSION:-8.4}" VARIANT="${VARIANT:-nts}" ARCH="${ARCH:-x86_64}" +# Resolve BUILD_DIR: if set, .so files are there; otherwise current directory +if [ -n "$BUILD_DIR" ]; then + FIREBIRD_SO="${BUILD_DIR}/modules/firebird.so" + PDO_FBIRD_SO="${BUILD_DIR}/pdo_fbird/modules/pdo_fbird.so" +else + FIREBIRD_SO="modules/firebird.so" + PDO_FBIRD_SO="pdo_fbird/modules/pdo_fbird.so" +fi + # ============================================================================= # Distribution Naming # ============================================================================= @@ -482,8 +500,8 @@ if [ "$SKIP_BUILD" = false ]; then fi # Verify build -if [ "$DRY_RUN" = 0 ] && [ ! -f "modules/firebird.so" ]; then - log_error "Build failed - modules/firebird.so not found" +if [ "$DRY_RUN" = 0 ] && [ ! -f "$FIREBIRD_SO" ]; then + log_error "Build failed - $FIREBIRD_SO not found" exit 1 fi @@ -505,12 +523,12 @@ fi log_info "Copying extension..." if [ "$DRY_RUN" = 1 ]; then - log_dry_run "Would copy: modules/firebird.so -> ${DIST_DIR}/" + log_dry_run "Would copy: $FIREBIRD_SO -> ${DIST_DIR}/" else - cp modules/firebird.so "${DIST_DIR}/" - # Copy pdo_fbird.so if it was built as a separate extension - if [ -f pdo_fbird/modules/pdo_fbird.so ]; then - cp pdo_fbird/modules/pdo_fbird.so "${DIST_DIR}/" + cp "$FIREBIRD_SO" "${DIST_DIR}/" + # Copy pdo_fbird.so if built + if [ -f "$PDO_FBIRD_SO" ]; then + cp "$PDO_FBIRD_SO" "${DIST_DIR}/" log_info "Copied pdo_fbird.so" fi fi From a2ed0ab91f9392cb1c96d681d3fe3b91833b126b Mon Sep 17 00:00:00 2001 From: Michael Wegener Date: Tue, 21 Jul 2026 15:43:11 +0200 Subject: [PATCH 2/6] fix(ci): cd BUILD_DIR before run-tests.php (out-of-tree test fix) phpize generates run-tests.php in the directory where it runs. With out-of-tree builds, that's BUILD_DIR, not GITHUB_WORKSPACE. Test steps must cd to BUILD_DIR so both run-tests.php and tests/ are found. Fixes: 'Could not open input file: run-tests.php' in CI/coverage/sanitizers Affected test steps: - ci.yml: Run PHPT tests - coverage.yml: Run PHPT tests (+ cd BUILD_DIR/pdo_fbird for build) - sanitizers.yml: LSan tests + TSan tests (+ cd BUILD_DIR/pdo_fbird for build) --- .github/workflows/ci.yml | 4 ++++ .github/workflows/coverage.yml | 6 +++++- .github/workflows/sanitizers.yml | 10 +++++++++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 72df8ef0..c5c010b6 100755 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -217,6 +217,10 @@ jobs: run: | set -eu + # jane: run-tests.php is generated by phpize in BUILD_DIR (out-of-tree + # build). cd there so both run-tests.php and tests/ are found. + cd "${BUILD_DIR}" + EXTENSION_PATH="${BUILD_DIR}/modules/firebird.so" PDO_FBIRD_PATH="${BUILD_DIR}/pdo_fbird/modules/pdo_fbird.so" diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 1d7375be..84f289fd 100755 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -88,7 +88,7 @@ jobs: shell: bash run: | set -eu - cd pdo_fbird + cd "${BUILD_DIR}/pdo_fbird" phpize ./configure --with-pdo-fbird --with-firebird=/opt/firebird-client make -j"$(nproc)" @@ -120,6 +120,10 @@ jobs: run: | set -euo pipefail + # jane: run-tests.php is generated by phpize in BUILD_DIR (out-of-tree + # build). cd there so both run-tests.php and tests/ are found. + cd "${BUILD_DIR}" + EXTENSION_PATH="${BUILD_DIR}/modules/firebird.so" PDO_FBIRD_PATH="${BUILD_DIR}/pdo_fbird/modules/pdo_fbird.so" diff --git a/.github/workflows/sanitizers.yml b/.github/workflows/sanitizers.yml index b7e9fb15..f7ed55f3 100755 --- a/.github/workflows/sanitizers.yml +++ b/.github/workflows/sanitizers.yml @@ -87,7 +87,7 @@ jobs: export CFLAGS="-I/opt/firebird-client/include ${SANITIZE_FLAGS}" export CXXFLAGS="-I/opt/firebird-client/include ${SANITIZE_FLAGS}" export LDFLAGS="-L/opt/firebird-client/lib -fsanitize=address,undefined" - cd pdo_fbird + cd "${BUILD_DIR}/pdo_fbird" phpize ./configure --with-pdo-fbird --with-firebird=/opt/firebird-client make -j"$(nproc)" @@ -251,6 +251,10 @@ jobs: export LD_LIBRARY_PATH="/opt/firebird-client/lib:${LD_LIBRARY_PATH:-}" + # jane: run-tests.php is generated by phpize in BUILD_DIR (out-of-tree + # build). cd there so both run-tests.php and tests/ are found. + cd "${BUILD_DIR}" + # Note: Standalone LSan (-fsanitize=leak) links directly into the extension # No LD_PRELOAD needed (unlike ASan which needs runtime library preloading) @@ -468,6 +472,10 @@ jobs: export PATH="/opt/php-tsan/bin:$PATH" export LD_LIBRARY_PATH="/opt/firebird-client/lib:/usr/lib:${LD_LIBRARY_PATH:-}" + # jane: run-tests.php is generated by phpize in BUILD_DIR (out-of-tree + # build). cd there so both run-tests.php and tests/ are found. + cd "${BUILD_DIR}" + echo "=== Running tests with ThreadSanitizer (ZTS) ===" EXTENSION_PATH="${BUILD_DIR}/modules/firebird.so" From 404602cb296e19112064d476f4ff9a8a67ddbc1c Mon Sep 17 00:00:00 2001 From: Michael Wegener Date: Tue, 21 Jul 2026 15:51:45 +0200 Subject: [PATCH 3/6] fix(ci): use ${BUILD_DIR}/run-tests.php from GITHUB_WORKSPACE Tests use __DIR__/../vendor/autoload.php which resolves to GITHUB_WORKSPACE/vendor/ (where composer install runs). With cd BUILD_DIR, vendor/ was not found. Fix: reference run-tests.php by full path instead of cd-ing to BUILD_DIR. Reverts: cd ${BUILD_DIR} in ci.yml/coverage.yml/sanitizers.yml test steps Replaces: php run-tests.php -> php ${BUILD_DIR}/run-tests.php --- .github/workflows/ci.yml | 7 +++---- .github/workflows/coverage.yml | 6 ++---- .github/workflows/sanitizers.yml | 10 ++++------ 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c5c010b6..4736f8c2 100755 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -218,9 +218,8 @@ jobs: set -eu # jane: run-tests.php is generated by phpize in BUILD_DIR (out-of-tree - # build). cd there so both run-tests.php and tests/ are found. - cd "${BUILD_DIR}" - + # build). Run it from $GITHUB_WORKSPACE so vendor/autoload.php (from + # composer install) is found by tests that use __DIR__/../vendor/. EXTENSION_PATH="${BUILD_DIR}/modules/firebird.so" PDO_FBIRD_PATH="${BUILD_DIR}/pdo_fbird/modules/pdo_fbird.so" @@ -250,7 +249,7 @@ jobs: echo "Running all PHPT tests..." TEST_PHP_EXECUTABLE="$(which php)" \ - php run-tests.php \ + php "${BUILD_DIR}/run-tests.php" \ ${EXT_FLAGS} \ -p "$(which php)" \ --set-timeout 15 \ diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 84f289fd..d3bd5a99 100755 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -121,9 +121,7 @@ jobs: set -euo pipefail # jane: run-tests.php is generated by phpize in BUILD_DIR (out-of-tree - # build). cd there so both run-tests.php and tests/ are found. - cd "${BUILD_DIR}" - + # build). Run it from $GITHUB_WORKSPACE so vendor/autoload.php is found. EXTENSION_PATH="${BUILD_DIR}/modules/firebird.so" PDO_FBIRD_PATH="${BUILD_DIR}/pdo_fbird/modules/pdo_fbird.so" @@ -148,7 +146,7 @@ jobs: echo "Running all PHPT tests..." # Use run-tests.php directly (not make test) so both extensions load. # --set-timeout 15 kills hanging tests in 15s instead of default 60s. - php run-tests.php ${EXT_FLAGS} \ + php "${BUILD_DIR}/run-tests.php" ${EXT_FLAGS} \ -p "$(which php)" \ --set-timeout 15 \ tests/ 2>&1 | tee test_output.txt || true diff --git a/.github/workflows/sanitizers.yml b/.github/workflows/sanitizers.yml index f7ed55f3..81be7a80 100755 --- a/.github/workflows/sanitizers.yml +++ b/.github/workflows/sanitizers.yml @@ -252,8 +252,7 @@ jobs: export LD_LIBRARY_PATH="/opt/firebird-client/lib:${LD_LIBRARY_PATH:-}" # jane: run-tests.php is generated by phpize in BUILD_DIR (out-of-tree - # build). cd there so both run-tests.php and tests/ are found. - cd "${BUILD_DIR}" + # build). Run it from $GITHUB_WORKSPACE so vendor/autoload.php is found. # Note: Standalone LSan (-fsanitize=leak) links directly into the extension # No LD_PRELOAD needed (unlike ASan which needs runtime library preloading) @@ -279,7 +278,7 @@ jobs: fi fi - php run-tests.php ${EXT_FLAGS} \ + php "${BUILD_DIR}/run-tests.php" ${EXT_FLAGS} \ -p "$(which php)" \ --set-timeout 15 \ tests/ 2>&1 | tee lsan_output.txt || true @@ -473,8 +472,7 @@ jobs: export LD_LIBRARY_PATH="/opt/firebird-client/lib:/usr/lib:${LD_LIBRARY_PATH:-}" # jane: run-tests.php is generated by phpize in BUILD_DIR (out-of-tree - # build). cd there so both run-tests.php and tests/ are found. - cd "${BUILD_DIR}" + # build). Run it from $GITHUB_WORKSPACE so vendor/autoload.php is found. echo "=== Running tests with ThreadSanitizer (ZTS) ===" @@ -495,7 +493,7 @@ jobs: EXT_FLAGS="${EXT_FLAGS} -d extension=${PCNTL_SO}" fi - php run-tests.php ${EXT_FLAGS} \ + php "${BUILD_DIR}/run-tests.php" ${EXT_FLAGS} \ -p "$(which php)" \ --set-timeout 15 \ tests/ 2>&1 | tee tsan_output.txt || true From 09eae9289f2f47440e7cce7dc2163e671598d2d4 Mon Sep 17 00:00:00 2001 From: Michael Wegener Date: Tue, 21 Jul 2026 16:10:27 +0200 Subject: [PATCH 4/6] fix(ci): copy run-tests.php from BUILD_DIR to GITHUB_WORKSPACE Instead of running run-tests.php from BUILD_DIR (which caused path resolution issues with vendor/autoload.php and tests/), copy it to GITHUB_WORKSPACE and run it there. This matches the original in-source build behavior where phpize generates run-tests.php in the source tree. Fixes: event test failures + 40 fewer tests running vs main --- .github/workflows/ci.yml | 8 +++++--- .github/workflows/coverage.yml | 6 ++++-- .github/workflows/sanitizers.yml | 10 ++++++---- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4736f8c2..42a17019 100755 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -218,8 +218,10 @@ jobs: set -eu # jane: run-tests.php is generated by phpize in BUILD_DIR (out-of-tree - # build). Run it from $GITHUB_WORKSPACE so vendor/autoload.php (from - # composer install) is found by tests that use __DIR__/../vendor/. + # build). Copy it to GITHUB_WORKSPACE so tests run from the source + # tree where vendor/autoload.php and tests/ are located. + cp "${BUILD_DIR}/run-tests.php" . + EXTENSION_PATH="${BUILD_DIR}/modules/firebird.so" PDO_FBIRD_PATH="${BUILD_DIR}/pdo_fbird/modules/pdo_fbird.so" @@ -249,7 +251,7 @@ jobs: echo "Running all PHPT tests..." TEST_PHP_EXECUTABLE="$(which php)" \ - php "${BUILD_DIR}/run-tests.php" \ + php run-tests.php \ ${EXT_FLAGS} \ -p "$(which php)" \ --set-timeout 15 \ diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index d3bd5a99..937013c6 100755 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -121,7 +121,9 @@ jobs: set -euo pipefail # jane: run-tests.php is generated by phpize in BUILD_DIR (out-of-tree - # build). Run it from $GITHUB_WORKSPACE so vendor/autoload.php is found. + # build). Copy it to GITHUB_WORKSPACE so tests run from the source tree. + cp "${BUILD_DIR}/run-tests.php" . + EXTENSION_PATH="${BUILD_DIR}/modules/firebird.so" PDO_FBIRD_PATH="${BUILD_DIR}/pdo_fbird/modules/pdo_fbird.so" @@ -146,7 +148,7 @@ jobs: echo "Running all PHPT tests..." # Use run-tests.php directly (not make test) so both extensions load. # --set-timeout 15 kills hanging tests in 15s instead of default 60s. - php "${BUILD_DIR}/run-tests.php" ${EXT_FLAGS} \ + php run-tests.php ${EXT_FLAGS} \ -p "$(which php)" \ --set-timeout 15 \ tests/ 2>&1 | tee test_output.txt || true diff --git a/.github/workflows/sanitizers.yml b/.github/workflows/sanitizers.yml index 81be7a80..6dcdf46a 100755 --- a/.github/workflows/sanitizers.yml +++ b/.github/workflows/sanitizers.yml @@ -252,7 +252,8 @@ jobs: export LD_LIBRARY_PATH="/opt/firebird-client/lib:${LD_LIBRARY_PATH:-}" # jane: run-tests.php is generated by phpize in BUILD_DIR (out-of-tree - # build). Run it from $GITHUB_WORKSPACE so vendor/autoload.php is found. + # build). Copy it to GITHUB_WORKSPACE so tests run from the source tree. + cp "${BUILD_DIR}/run-tests.php" . # Note: Standalone LSan (-fsanitize=leak) links directly into the extension # No LD_PRELOAD needed (unlike ASan which needs runtime library preloading) @@ -278,7 +279,7 @@ jobs: fi fi - php "${BUILD_DIR}/run-tests.php" ${EXT_FLAGS} \ + php run-tests.php ${EXT_FLAGS} \ -p "$(which php)" \ --set-timeout 15 \ tests/ 2>&1 | tee lsan_output.txt || true @@ -472,7 +473,8 @@ jobs: export LD_LIBRARY_PATH="/opt/firebird-client/lib:/usr/lib:${LD_LIBRARY_PATH:-}" # jane: run-tests.php is generated by phpize in BUILD_DIR (out-of-tree - # build). Run it from $GITHUB_WORKSPACE so vendor/autoload.php is found. + # build). Copy it to GITHUB_WORKSPACE so tests run from the source tree. + cp "${BUILD_DIR}/run-tests.php" . echo "=== Running tests with ThreadSanitizer (ZTS) ===" @@ -493,7 +495,7 @@ jobs: EXT_FLAGS="${EXT_FLAGS} -d extension=${PCNTL_SO}" fi - php "${BUILD_DIR}/run-tests.php" ${EXT_FLAGS} \ + php run-tests.php ${EXT_FLAGS} \ -p "$(which php)" \ --set-timeout 15 \ tests/ 2>&1 | tee tsan_output.txt || true From 9421b4c273bec58fa6d1d4b307421a9cd9e9d691 Mon Sep 17 00:00:00 2001 From: Michael Wegener Date: Tue, 21 Jul 2026 16:30:49 +0200 Subject: [PATCH 5/6] fix(tests): use FBIRD_SO env var for child process extension path Event tests spawn child PHP processes with realpath('modules/firebird.so') which resolves relative to CWD. With out-of-tree builds, the .so is in BUILD_DIR/modules/, not CWD. Fix: use getenv('FBIRD_SO') with fallback to realpath() for backward compatibility. Set FBIRD_SO in ci.yml, coverage.yml, sanitizers.yml test steps. --- .github/workflows/ci.yml | 1 + .github/workflows/coverage.yml | 1 + .github/workflows/sanitizers.yml | 2 ++ tests/fbird_event_live_001.phpt | 4 ++-- tests/oop_event_methods_001.phpt | 2 +- 5 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 42a17019..a8525d4b 100755 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -214,6 +214,7 @@ jobs: env: NO_INTERACTION: 1 REPORT_EXIT_STATUS: 1 + FBIRD_SO: ${{ env.BUILD_DIR }}/modules/firebird.so run: | set -eu diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 937013c6..f0460e23 100755 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -116,6 +116,7 @@ jobs: NO_INTERACTION: 1 REPORT_EXIT_STATUS: 1 TEST_PHP_EXECUTABLE: php + FBIRD_SO: ${{ env.BUILD_DIR }}/modules/firebird.so shell: bash run: | set -euo pipefail diff --git a/.github/workflows/sanitizers.yml b/.github/workflows/sanitizers.yml index 6dcdf46a..66efe7f5 100755 --- a/.github/workflows/sanitizers.yml +++ b/.github/workflows/sanitizers.yml @@ -246,6 +246,7 @@ jobs: NO_INTERACTION: 1 REPORT_EXIT_STATUS: 1 TEST_PHP_EXECUTABLE: php + FBIRD_SO: ${{ env.BUILD_DIR }}/modules/firebird.so run: | set -eu @@ -466,6 +467,7 @@ jobs: env: NO_INTERACTION: 1 REPORT_EXIT_STATUS: 1 + FBIRD_SO: ${{ env.BUILD_DIR }}/modules/firebird.so run: | set -eu diff --git a/tests/fbird_event_live_001.phpt b/tests/fbird_event_live_001.phpt index 4617368e..2558ebbc 100644 --- a/tests/fbird_event_live_001.phpt +++ b/tests/fbird_event_live_001.phpt @@ -45,7 +45,7 @@ var_dump($event instanceof \Firebird\Event || is_resource($event)); // Spawn child process to insert (fires trigger on commit) $child = proc_open( - ['php', '-d', 'extension=' . realpath('modules/firebird.so')], + ['php', '-d', 'extension=' . (getenv('FBIRD_SO') ?: realpath('modules/firebird.so'))], [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes ); @@ -85,7 +85,7 @@ $cmd2 = sprintf( ); $child2 = proc_open( - ['php', '-d', 'extension=' . realpath('modules/firebird.so')], + ['php', '-d', 'extension=' . (getenv('FBIRD_SO') ?: realpath('modules/firebird.so'))], [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes2 ); diff --git a/tests/oop_event_methods_001.phpt b/tests/oop_event_methods_001.phpt index 48b48d7a..33ca755e 100644 --- a/tests/oop_event_methods_001.phpt +++ b/tests/oop_event_methods_001.phpt @@ -40,7 +40,7 @@ $cmd = sprintf( ); $child = proc_open( - ['php', '-d', 'extension=' . realpath('modules/firebird.so')], + ['php', '-d', 'extension=' . (getenv('FBIRD_SO') ?: realpath('modules/firebird.so'))], [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes ); From 1ea82a679fbfb0c39cfc5ad5c54502c686dc8afa Mon Sep 17 00:00:00 2001 From: Michael Wegener Date: Tue, 21 Jul 2026 16:41:31 +0200 Subject: [PATCH 6/6] fix(coverage): capture from BUILD_DIR where .gcno/.gcda files are Out-of-tree build puts .gcno/.gcda files in BUILD_DIR, not GITHUB_WORKSPACE. Coverage step must cd to BUILD_DIR and use GITHUB_WORKSPACE/coverage/ for output paths. Also: PHP 8.2/FB 4.0 'unbound variable' is a pre-existing transient issue in install-firebird-client (get-latest-firebird.sh), not related to our changes. --- .github/workflows/coverage.yml | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index f0460e23..59a010b4 100755 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -183,7 +183,12 @@ jobs: mkdir -p coverage echo "=== Debug: Listing gcno/gcda files ===" - find . -name "*.gcno" -o -name "*.gcda" 2>/dev/null | head -20 || true + find "${BUILD_DIR}" -name "*.gcno" -o -name "*.gcda" 2>/dev/null | head -20 || true + + # jane: coverage data (.gcno/.gcda) is in BUILD_DIR (out-of-tree build). + # Use lcov --directory BUILD_DIR to capture from the correct location. + # Run gcov from BUILD_DIR where source files (.c) and .gcda files are. + cd "${BUILD_DIR}" # Run gcov on source files echo "Running gcov on source files..." @@ -206,40 +211,40 @@ jobs: # Capture coverage using lcov echo "Capturing coverage data with lcov..." - lcov --directory . --capture --output-file coverage/lcov.info \ + lcov --directory . --capture --output-file "${GITHUB_WORKSPACE}/coverage/lcov.info" \ --rc lcov_branch_coverage=0 \ --no-external \ --ignore-errors gcov \ 2>&1 || true - if [ ! -s coverage/lcov.info ]; then + if [ ! -s "${GITHUB_WORKSPACE}/coverage/lcov.info" ]; then echo "Warning: No coverage data captured" - echo "TN:" > coverage/lcov.info + echo "TN:" > "${GITHUB_WORKSPACE}/coverage/lcov.info" fi # Filter out system headers, PHP headers, and tests - lcov --remove coverage/lcov.info \ + lcov --remove "${GITHUB_WORKSPACE}/coverage/lcov.info" \ '/usr/*' \ '*/php-src/*' \ '*/tests/*' \ '*/build/*' \ '*/modules/*' \ - --output-file coverage/lcov_filtered.info \ + --output-file "${GITHUB_WORKSPACE}/coverage/lcov_filtered.info" \ --rc lcov_branch_coverage=0 \ 2>&1 || true - if [ ! -s coverage/lcov_filtered.info ]; then + if [ ! -s "${GITHUB_WORKSPACE}/coverage/lcov_filtered.info" ]; then echo "Warning: No coverage data after filtering" - echo "TN:" > coverage/lcov_filtered.info + echo "TN:" > "${GITHUB_WORKSPACE}/coverage/lcov_filtered.info" fi # Generate HTML report - genhtml coverage/lcov_filtered.info --output-directory coverage/html \ + genhtml "${GITHUB_WORKSPACE}/coverage/lcov_filtered.info" --output-directory "${GITHUB_WORKSPACE}/coverage/html" \ --rc lcov_branch_coverage=0 \ 2>&1 || true echo "Coverage summary:" - COVERAGE_OUTPUT=$(lcov --summary coverage/lcov_filtered.info) + COVERAGE_OUTPUT=$(lcov --summary "${GITHUB_WORKSPACE}/coverage/lcov_filtered.info") echo "$COVERAGE_OUTPUT" COVERAGE=$(echo "$COVERAGE_OUTPUT" | grep -oP "lines\.*: \K[0-9.]+")