diff --git a/.github/actions/php/post-merge/action.yml b/.github/actions/php/post-merge/action.yml new file mode 100644 index 0000000000..740df528cf --- /dev/null +++ b/.github/actions/php/post-merge/action.yml @@ -0,0 +1,193 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +name: php-post-merge +description: PHP post-merge GitHub release publishing with pre-built PIE extension archives + +inputs: + version: + description: "Version for publishing" + required: true + tag: + description: "Git tag that identifies this PHP SDK release" + required: true + commit: + description: "Commit SHA being published" + required: true + dry_run: + description: "Dry run mode" + required: false + default: "false" + packages_artifact: + description: "Name of the artifact containing PHP extension archives" + required: false + default: "php-extensions-all" + packages_path: + description: "Path where PHP packages should be downloaded" + required: false + default: "dist" + +runs: + using: "composite" + steps: + - name: Validate version format + run: | + VERSION="${{ inputs.version }}" + + if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$'; then + echo "Invalid PHP SDK version format: $VERSION" + echo "Expected Composer/Cargo-compatible SemVer: X.Y.Z[-prerelease][+build]" + exit 1 + fi + + echo "Version format valid: $VERSION" + shell: bash + + - name: Download pre-built PHP packages + uses: actions/download-artifact@v8 + with: + name: ${{ inputs.packages_artifact }} + path: ${{ inputs.packages_path }} + + - name: Validate downloaded PHP packages + run: | + set -euo pipefail + + PACKAGES_PATH="${{ inputs.packages_path }}" + VERSION="${{ inputs.version }}" + + if [ ! -d "$PACKAGES_PATH" ]; then + echo "PHP packages directory not found: $PACKAGES_PATH" + exit 1 + fi + + expected=( + "php_iggy_php-${VERSION}_php8.3-x86_64-linux-glibc-nts.zip" + "php_iggy_php-${VERSION}_php8.4-x86_64-linux-glibc-nts.zip" + "php_iggy_php-${VERSION}_php8.3-arm64-linux-glibc-nts.zip" + "php_iggy_php-${VERSION}_php8.4-arm64-linux-glibc-nts.zip" + "php_iggy_php-${VERSION}_php8.3-x86_64-linux-musl-nts.zip" + "php_iggy_php-${VERSION}_php8.4-x86_64-linux-musl-nts.zip" + "php_iggy_php-${VERSION}_php8.3-arm64-linux-musl-nts.zip" + "php_iggy_php-${VERSION}_php8.4-arm64-linux-musl-nts.zip" + "php_iggy_php-${VERSION}_php8.3-x86_64-darwin-bsdlibc-nts.zip" + "php_iggy_php-${VERSION}_php8.4-x86_64-darwin-bsdlibc-nts.zip" + "php_iggy_php-${VERSION}_php8.3-arm64-darwin-bsdlibc-nts.zip" + "php_iggy_php-${VERSION}_php8.4-arm64-darwin-bsdlibc-nts.zip" + "apache-iggy-php-${VERSION}-source.tar.gz" + ) + + missing=0 + for filename in "${expected[@]}"; do + if [ ! -f "$PACKAGES_PATH/$filename" ]; then + echo "Missing expected package: $filename" + missing=1 + fi + done + if [ "$missing" -ne 0 ]; then + exit 1 + fi + + echo "Validating PHP extension archive contents:" + for archive in "$PACKAGES_PATH"/*.zip; do + filename="$(basename "$archive")" + echo " - $filename" + members="$(unzip -Z1 "$archive")" + for member in iggy_php.so LICENSE NOTICE LICENSE-binary; do + if ! grep -qx "$member" <<< "$members"; then + echo "Archive $filename is missing $member" + exit 1 + fi + done + done + + echo "" + echo "Packages ready for publishing:" + find "$PACKAGES_PATH" -maxdepth 1 -type f -print | sort | while IFS= read -r file; do + size="$(du -h "$file" | cut -f1)" + echo " - $(basename "$file") ($size)" + done + shell: bash + + - name: Display publishing information (dry run) + if: inputs.dry_run == 'true' + run: | + PACKAGES_PATH="${{ inputs.packages_path }}" + VERSION="${{ inputs.version }}" + TAG="${{ inputs.tag }}" + + echo "DRY RUN - Would publish PHP SDK:" + echo "" + echo "Package: apache/iggy-php" + echo "Version: $VERSION" + echo "Tag: $TAG" + echo "Registry: GitHub Releases" + echo "" + echo "Release assets that would be uploaded:" + find "$PACKAGES_PATH" -maxdepth 1 -type f -print | sort | sed 's|.*/| - |' + echo "" + echo "Packagist publishing requires a split repository with composer.json at the repository root." + shell: bash + + - name: Publish GitHub release assets + if: inputs.dry_run == 'false' + run: | + set -euo pipefail + + PACKAGES_PATH="${{ inputs.packages_path }}" + VERSION="${{ inputs.version }}" + TAG="${{ inputs.tag }}" + COMMIT="${{ inputs.commit }}" + + if [ -z "$TAG" ]; then + echo "PHP release tag input is empty" + exit 1 + fi + if ! git ls-remote --tags --exit-code origin "refs/tags/${TAG}" >/dev/null 2>&1; then + echo "PHP release tag $TAG does not exist on origin" + echo "Create the annotated PHP SDK tag before publishing GitHub release assets." + exit 1 + fi + + release_notes="$(mktemp)" + trap 'rm -f "$release_notes"' EXIT + { + echo "Apache Iggy PHP SDK ${VERSION}" + echo "" + echo "This GitHub release contains downstream convenience binaries for the PHP extension." + echo "The canonical Apache source release remains the ASF release artifact." + } > "$release_notes" + + prerelease_flag=() + if [ "$(scripts/extract-version.sh sdk-php --is-pre-release)" = "true" ]; then + prerelease_flag=(--prerelease) + fi + + if gh release view "$TAG" >/dev/null 2>&1; then + echo "GitHub release $TAG exists, uploading assets with clobber" + gh release upload "$TAG" "$PACKAGES_PATH"/* --clobber + else + echo "Creating GitHub release $TAG" + gh release create "$TAG" "$PACKAGES_PATH"/* \ + --target "$COMMIT" \ + --title "$TAG" \ + --notes-file "$release_notes" \ + "${prerelease_flag[@]}" + fi + shell: bash + env: + GH_TOKEN: ${{ github.token }} diff --git a/.github/actions/php/setup-release-tools/action.yml b/.github/actions/php/setup-release-tools/action.yml new file mode 100644 index 0000000000..257b01c364 --- /dev/null +++ b/.github/actions/php/setup-release-tools/action.yml @@ -0,0 +1,100 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +name: php-setup-release-tools +description: Install release helpers and resolve the PHP SDK version + +inputs: + version: + description: "Explicit PHP SDK version. When empty, the version is read from publish config." + required: false + default: "" + +outputs: + version: + description: "Resolved PHP SDK version" + value: ${{ steps.version.outputs.version }} + +runs: + using: "composite" + steps: + - name: Setup yq + shell: bash + run: | + set -euo pipefail + + if command -v yq >/dev/null 2>&1; then + yq --version + exit 0 + fi + + YQ_VERSION="v4.47.1" + case "$(uname -s)-$(uname -m)" in + Linux-x86_64) + YQ_BINARY="yq_linux_amd64" + YQ_CHECKSUM="0fb28c6680193c41b364193d0c0fc4a03177aecde51cfc04d506b1517158c2fb" + ;; + Linux-aarch64) + YQ_BINARY="yq_linux_arm64" + YQ_CHECKSUM="b7f7c991abe262b0c6f96bbcb362f8b35429cefd59c8b4c2daa4811f1e9df599" + ;; + Darwin-x86_64) + YQ_BINARY="yq_darwin_amd64" + YQ_CHECKSUM="a9b5ca36f7750576c6ace3cc7193349cd676b3a6bf30193fb2773ff45f5af5c2" + ;; + Darwin-arm64) + YQ_BINARY="yq_darwin_arm64" + YQ_CHECKSUM="99aae3a7c9ddfe76bb339f0e7acd8224324b6527436fb6a5d890079bf5fcc590" + ;; + *) + echo "Unsupported yq platform: $(uname -s)-$(uname -m)" + exit 1 + ;; + esac + + yq_tmp="$(mktemp -d)" + trap 'rm -rf "$yq_tmp"' EXIT + curl -sSL -o "${yq_tmp}/${YQ_BINARY}" "https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/${YQ_BINARY}" + if command -v sha256sum >/dev/null 2>&1; then + echo "${YQ_CHECKSUM} ${yq_tmp}/${YQ_BINARY}" | sha256sum -c - + else + echo "${YQ_CHECKSUM} ${yq_tmp}/${YQ_BINARY}" | shasum -a 256 -c - + fi + + mkdir -p "${RUNNER_TEMP}/yq-bin" + chmod +x "${yq_tmp}/${YQ_BINARY}" + mv "${yq_tmp}/${YQ_BINARY}" "${RUNNER_TEMP}/yq-bin/yq" + echo "${RUNNER_TEMP}/yq-bin" >> "$GITHUB_PATH" + + - name: Resolve version + id: version + shell: bash + run: | + set -euo pipefail + + if [ -d "${RUNNER_TEMP}/yq-bin" ]; then + export PATH="${RUNNER_TEMP}/yq-bin:${PATH}" + fi + + if [ -n "${{ inputs.version }}" ]; then + version="${{ inputs.version }}" + else + chmod +x scripts/extract-version.sh + version="$(scripts/extract-version.sh sdk-php)" + fi + + echo "version=${version}" >> "$GITHUB_OUTPUT" diff --git a/.github/config/publish.yml b/.github/config/publish.yml index 36376d34c6..cae4893cde 100644 --- a/.github/config/publish.yml +++ b/.github/config/publish.yml @@ -123,6 +123,12 @@ components: version_file: "foreign/python/pyproject.toml" version_regex: '(?m)^\s*version\s*=\s*"([^"]+)"' + sdk-php: + tag_pattern: "^php-sdk-([0-9]+\\.[0-9]+\\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\\+[0-9A-Za-z.-]+)?)$" + registry: github-release + version_file: "foreign/php/Cargo.toml" + version_regex: '(?m)^\s*version\s*=\s*"([^"]+)"' + sdk-node: tag_pattern: "^node-sdk-([0-9]+\\.[0-9]+\\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\\+[0-9A-Za-z.-]+)?)$" registry: npm diff --git a/.github/workflows/_build_php_extensions.yml b/.github/workflows/_build_php_extensions.yml new file mode 100644 index 0000000000..fad80cd9ff --- /dev/null +++ b/.github/workflows/_build_php_extensions.yml @@ -0,0 +1,469 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +name: _build_php_extensions +on: + workflow_call: + inputs: + version: + type: string + required: false + default: "" + upload_artifacts: + type: boolean + required: false + default: true + use_latest_ci: + type: boolean + required: false + default: true + description: "Use latest CI configuration and scripts from master branch" + commit: + type: string + required: false + default: "" + description: "Specific commit to checkout for building PHP extensions" + outputs: + artifact_name: + description: "Name of the uploaded artifact containing PHP extension archives" + value: ${{ jobs.collect.outputs.artifact_name }} + +permissions: + contents: read + +jobs: + license-manifest: + name: Generate PHP binary license manifest + runs-on: ubuntu-latest + steps: + - name: Download latest copy script from master + if: inputs.use_latest_ci + run: | + curl -sSL "https://raw.githubusercontent.com/${{ github.repository }}/master/scripts/copy-latest-from-master.sh" \ + -o /tmp/copy-latest-from-master.sh + chmod +x /tmp/copy-latest-from-master.sh + + - uses: actions/checkout@v6.0.2 + with: + ref: ${{ inputs.commit }} + + - name: Save and apply latest CI from master + if: inputs.use_latest_ci + run: | + /tmp/copy-latest-from-master.sh save .github scripts + /tmp/copy-latest-from-master.sh apply + + - name: Install cargo-about + run: | + mkdir -p "$HOME/.cargo/bin" + TARGET="$(uname -m)-unknown-linux-musl" + curl -sSfL "https://github.com/EmbarkStudios/cargo-about/releases/download/0.9.0/cargo-about-0.9.0-${TARGET}.tar.gz" \ + | tar -xz -C "$HOME/.cargo/bin" --strip-components=1 "cargo-about-0.9.0-${TARGET}/cargo-about" + + - name: Generate third-party license manifest + run: ./scripts/ci/third-party-licenses.sh --generate --manifest foreign/php/Cargo.toml --output foreign/php/LICENSE-binary + + - name: Upload license manifest + uses: actions/upload-artifact@v7 + with: + name: php-license-binary + path: foreign/php/LICENSE-binary + retention-days: 7 + + linux: + name: PHP ${{ matrix.php }} ${{ matrix.arch }} linux-${{ matrix.libc }} + needs: license-manifest + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + include: + - php: "8.3" + arch: x86_64 + libc: glibc + runner: ubuntu-latest + image: php:8.3-cli-bookworm + - php: "8.4" + arch: x86_64 + libc: glibc + runner: ubuntu-latest + image: php:8.4-cli-bookworm + - php: "8.3" + arch: arm64 + libc: glibc + runner: ubuntu-24.04-arm + image: php:8.3-cli-bookworm + - php: "8.4" + arch: arm64 + libc: glibc + runner: ubuntu-24.04-arm + image: php:8.4-cli-bookworm + - php: "8.3" + arch: x86_64 + libc: musl + runner: ubuntu-latest + image: php:8.3-cli-alpine + - php: "8.4" + arch: x86_64 + libc: musl + runner: ubuntu-latest + image: php:8.4-cli-alpine + - php: "8.3" + arch: arm64 + libc: musl + runner: ubuntu-24.04-arm + image: php:8.3-cli-alpine + - php: "8.4" + arch: arm64 + libc: musl + runner: ubuntu-24.04-arm + image: php:8.4-cli-alpine + steps: + - name: Download latest copy script from master + if: inputs.use_latest_ci + run: | + curl -sSL "https://raw.githubusercontent.com/${{ github.repository }}/master/scripts/copy-latest-from-master.sh" \ + -o /tmp/copy-latest-from-master.sh + chmod +x /tmp/copy-latest-from-master.sh + + - uses: actions/checkout@v6.0.2 + with: + ref: ${{ inputs.commit }} + + - name: Save and apply latest CI from master + if: inputs.use_latest_ci + run: | + /tmp/copy-latest-from-master.sh save .github scripts + /tmp/copy-latest-from-master.sh apply + + - name: Download license manifest + uses: actions/download-artifact@v8 + with: + name: php-license-binary + path: foreign/php + + - name: Setup PHP release tools + id: release + uses: ./.github/actions/php/setup-release-tools + with: + version: ${{ inputs.version }} + + - name: Cache Linux PHP cargo state + uses: actions/cache@v5 + with: + path: | + target/php-cargo-home/registry + target/php-cargo-home/git + target/php-rustup-home + key: php-release-linux-${{ matrix.arch }}-${{ matrix.libc }}-php${{ matrix.php }}-${{ hashFiles('Cargo.lock', 'foreign/php/Cargo.toml') }} + restore-keys: | + php-release-linux-${{ matrix.arch }}-${{ matrix.libc }}-php${{ matrix.php }}- + php-release-linux-${{ matrix.arch }}-${{ matrix.libc }}- + php-release-linux-${{ matrix.arch }}- + + - name: Build and package PHP extension + env: + VERSION: ${{ steps.release.outputs.version }} + PHP_VERSION: ${{ matrix.php }} + PHP_ARCH: ${{ matrix.arch }} + PHP_LIBC: ${{ matrix.libc }} + PHP_IMAGE: ${{ matrix.image }} + run: | + set -euo pipefail + + mkdir -p foreign/php/dist + + docker run --rm \ + -e CARGO_TERM_COLOR=always \ + -e CARGO_HOME=/workspace/target/php-cargo-home \ + -e RUSTUP_HOME=/workspace/target/php-rustup-home \ + -e "CARGO_TARGET_DIR=/workspace/target/php-release-${PHP_VERSION}-${PHP_ARCH}-${PHP_LIBC}" \ + -e VERSION="${VERSION}" \ + -e PHP_VERSION="${PHP_VERSION}" \ + -e PHP_ARCH="${PHP_ARCH}" \ + -e PHP_LIBC="${PHP_LIBC}" \ + -v "$PWD:/workspace" \ + -w /workspace \ + "${PHP_IMAGE}" \ + sh -lc ' + set -eu + + if command -v apt-get >/dev/null 2>&1; then + apt-get update + apt-get install -y --no-install-recommends \ + build-essential \ + ca-certificates \ + clang \ + curl \ + git \ + libclang-dev \ + libhwloc-dev \ + libssl-dev \ + libudev-dev \ + pkg-config \ + zip + rm -rf /var/lib/apt/lists/* + else + apk add --no-cache \ + build-base \ + ca-certificates \ + clang \ + clang-dev \ + curl \ + eudev-dev \ + git \ + hwloc-dev \ + linux-headers \ + openssl-dev \ + pkgconf \ + zip + fi + + mkdir -p "$CARGO_HOME" "$RUSTUP_HOME" + if [ ! -x "$CARGO_HOME/bin/rustup" ]; then + curl -sSf https://sh.rustup.rs -o /tmp/rustup-init.sh + sh /tmp/rustup-init.sh -y --profile minimal --default-toolchain none + fi + . "$CARGO_HOME/env" + rustup show + + php --version + php-config --version + cargo build --release --manifest-path foreign/php/Cargo.toml + + extension="$(find "${CARGO_TARGET_DIR}/release" -maxdepth 1 -name "libiggy_php.so" -print -quit)" + if [ -z "$extension" ]; then + echo "PHP extension was not produced" + exit 1 + fi + + package_dir="$(mktemp -d)" + cp "$extension" "$package_dir/iggy_php.so" + cp foreign/php/LICENSE "$package_dir/LICENSE" + cp foreign/php/NOTICE "$package_dir/NOTICE" + cp foreign/php/LICENSE-binary "$package_dir/LICENSE-binary" + + archive="php_iggy_php-${VERSION}_php${PHP_VERSION}-${PHP_ARCH}-linux-${PHP_LIBC}-nts.zip" + (cd "$package_dir" && zip -9 "/workspace/foreign/php/dist/${archive}" iggy_php.so LICENSE NOTICE LICENSE-binary) + rm -rf "$package_dir" + ' + + ls -lh foreign/php/dist + + - name: Upload PHP extension archive + if: inputs.upload_artifacts + uses: actions/upload-artifact@v7 + with: + name: php-package-linux-${{ matrix.arch }}-${{ matrix.libc }}-php${{ matrix.php }} + path: foreign/php/dist/*.zip + retention-days: 7 + + macos: + name: PHP ${{ matrix.php }} ${{ matrix.arch }} macos + needs: license-manifest + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + include: + - php: "8.3" + arch: x86_64 + runner: macos-15-intel + - php: "8.4" + arch: x86_64 + runner: macos-15-intel + - php: "8.3" + arch: arm64 + runner: macos-15 + - php: "8.4" + arch: arm64 + runner: macos-15 + steps: + - name: Download latest copy script from master + if: inputs.use_latest_ci + run: | + curl -sSL "https://raw.githubusercontent.com/${{ github.repository }}/master/scripts/copy-latest-from-master.sh" \ + -o /tmp/copy-latest-from-master.sh + chmod +x /tmp/copy-latest-from-master.sh + + - uses: actions/checkout@v6.0.2 + with: + ref: ${{ inputs.commit }} + + - name: Save and apply latest CI from master + if: inputs.use_latest_ci + run: | + /tmp/copy-latest-from-master.sh save .github scripts + /tmp/copy-latest-from-master.sh apply + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + tools: none + + - name: Setup Rust with cache + uses: ./.github/actions/utils/setup-rust-with-cache + with: + shared-key: php-release + save-cache: "false" + + - name: Download license manifest + uses: actions/download-artifact@v8 + with: + name: php-license-binary + path: foreign/php + + - name: Setup PHP release tools + id: release + uses: ./.github/actions/php/setup-release-tools + with: + version: ${{ inputs.version }} + + - name: Build and package PHP extension + env: + VERSION: ${{ steps.release.outputs.version }} + PHP_VERSION: ${{ matrix.php }} + PHP_ARCH: ${{ matrix.arch }} + CARGO_TARGET_DIR: ${{ github.workspace }}/target/php-release-${{ matrix.php }}-${{ matrix.arch }}-macos + run: | + set -euo pipefail + + php --version + php-config --version + cargo build --release --manifest-path foreign/php/Cargo.toml + + extension="$(find "${CARGO_TARGET_DIR}/release" -maxdepth 1 \( -name 'libiggy_php.so' -o -name 'libiggy_php.dylib' \) -print -quit)" + if [ -z "$extension" ]; then + echo "PHP extension was not produced" + exit 1 + fi + + mkdir -p foreign/php/dist + package_dir="$(mktemp -d)" + cp "$extension" "$package_dir/iggy_php.so" + cp foreign/php/LICENSE "$package_dir/LICENSE" + cp foreign/php/NOTICE "$package_dir/NOTICE" + cp foreign/php/LICENSE-binary "$package_dir/LICENSE-binary" + + archive="php_iggy_php-${VERSION}_php${PHP_VERSION}-${PHP_ARCH}-darwin-bsdlibc-nts.zip" + (cd "$package_dir" && zip -9 "${{ github.workspace }}/foreign/php/dist/${archive}" iggy_php.so LICENSE NOTICE LICENSE-binary) + rm -rf "$package_dir" + + ls -lh foreign/php/dist + + - name: Upload PHP extension archive + if: inputs.upload_artifacts + uses: actions/upload-artifact@v7 + with: + name: php-package-macos-${{ matrix.arch }}-php${{ matrix.php }} + path: foreign/php/dist/*.zip + retention-days: 7 + + source: + name: Build PHP source archive + runs-on: ubuntu-latest + steps: + - name: Download latest copy script from master + if: inputs.use_latest_ci + run: | + curl -sSL "https://raw.githubusercontent.com/${{ github.repository }}/master/scripts/copy-latest-from-master.sh" \ + -o /tmp/copy-latest-from-master.sh + chmod +x /tmp/copy-latest-from-master.sh + + - uses: actions/checkout@v6.0.2 + with: + ref: ${{ inputs.commit }} + + - name: Save and apply latest CI from master + if: inputs.use_latest_ci + run: | + /tmp/copy-latest-from-master.sh save .github scripts + /tmp/copy-latest-from-master.sh apply + + - name: Setup PHP release tools + id: release + uses: ./.github/actions/php/setup-release-tools + with: + version: ${{ inputs.version }} + + - name: Build source archive + run: | + set -euo pipefail + + version="${{ steps.release.outputs.version }}" + mkdir -p foreign/php/dist + git archive \ + --format=tar.gz \ + --prefix="apache-iggy-php-${version}/" \ + --output="foreign/php/dist/apache-iggy-php-${version}-source.tar.gz" \ + HEAD:foreign/php + + ls -lh foreign/php/dist + + - name: Upload PHP source archive + if: inputs.upload_artifacts + uses: actions/upload-artifact@v7 + with: + name: php-package-source + path: foreign/php/dist/*.tar.gz + retention-days: 7 + + collect: + name: Collect PHP packages + needs: [linux, macos, source] + if: ${{ !cancelled() }} + runs-on: ubuntu-latest + outputs: + artifact_name: ${{ steps.output.outputs.artifact_name }} + steps: + - name: Download PHP packages + uses: actions/download-artifact@v8 + with: + pattern: php-package-* + merge-multiple: true + path: dist + + - name: List PHP packages + run: | + echo "## Built PHP packages" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Kind | File |" >> $GITHUB_STEP_SUMMARY + echo "|------|------|" >> $GITHUB_STEP_SUMMARY + + while IFS= read -r package; do + filename="$(basename "$package")" + case "$filename" in + *.zip) kind="PIE binary" ;; + *.tar.gz) kind="Source" ;; + *) kind="Other" ;; + esac + echo "| $kind | \`$filename\` |" >> $GITHUB_STEP_SUMMARY + done < <(find dist -type f | sort) + + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Total packages built:** $(find dist -type f | wc -l)" >> $GITHUB_STEP_SUMMARY + + - name: Upload combined artifact + uses: actions/upload-artifact@v7 + with: + name: php-extensions-all + path: dist + retention-days: 30 + + - id: output + run: echo "artifact_name=php-extensions-all" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/_common.yml b/.github/workflows/_common.yml index 616008abcb..f3071bb764 100644 --- a/.github/workflows/_common.yml +++ b/.github/workflows/_common.yml @@ -42,6 +42,15 @@ jobs: - name: Check Python SDK versions are synchronized run: ./scripts/ci/python-sdk-version-sync.sh --check + php-versions: + name: Check PHP SDK metadata + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6.0.2 + + - name: Check PHP SDK metadata + run: ./scripts/ci/php-sdk-version-sync.sh --check + python-interpreter-versions: name: Check Python interpreter versions sync runs-on: ubuntu-latest @@ -214,6 +223,7 @@ jobs: [ rust-versions, python-versions, + php-versions, python-interpreter-versions, python-lockfiles, version-consistency, @@ -240,6 +250,7 @@ jobs: # Always-run checks RUST_VERSIONS="${{ needs.rust-versions.result }}" PYTHON_VERSIONS="${{ needs.python-versions.result }}" + PHP_VERSIONS="${{ needs.php-versions.result }}" PYTHON_INTERPRETER_VERSIONS="${{ needs.python-interpreter-versions.result }}" PYTHON_LOCKFILES="${{ needs.python-lockfiles.result }}" VERSION_CONSISTENCY="${{ needs.version-consistency.result }}" @@ -263,6 +274,14 @@ jobs: echo "| ⏭️ Python SDK Versions | $PYTHON_VERSIONS | Check skipped |" >> "$GITHUB_STEP_SUMMARY" fi + if [ "$PHP_VERSIONS" = "success" ]; then + echo "| ✅ PHP SDK Metadata | success | Rust SDK dependency and Composer metadata synchronized |" >> "$GITHUB_STEP_SUMMARY" + elif [ "$PHP_VERSIONS" = "failure" ]; then + echo "| ❌ PHP SDK Metadata | failure | PHP SDK metadata mismatch detected |" >> "$GITHUB_STEP_SUMMARY" + else + echo "| ⏭️ PHP SDK Metadata | $PHP_VERSIONS | Check skipped |" >> "$GITHUB_STEP_SUMMARY" + fi + if [ "$PYTHON_INTERPRETER_VERSIONS" = "success" ]; then echo "| ✅ Python Interpreter Versions | success | Python interpreter versions synchronized |" >> "$GITHUB_STEP_SUMMARY" elif [ "$PYTHON_INTERPRETER_VERSIONS" = "failure" ]; then diff --git a/.github/workflows/post-merge.yml b/.github/workflows/post-merge.yml index 5de7a96ec1..5625d0bb78 100644 --- a/.github/workflows/post-merge.yml +++ b/.github/workflows/post-merge.yml @@ -16,7 +16,8 @@ # under the License. # Auto-publish: detects pre-release crate/SDK versions without tags -# and publishes them to crates.io, Docker Hub, PyPI, npm, Maven, NuGet. +# and publishes them to crates.io, Docker Hub, PyPI, npm, Maven, NuGet, +# and GitHub Releases. # Runs on every push to master. name: Post-merge @@ -107,7 +108,7 @@ jobs: # Check SDKs for pre-release versions without tags SDKS_TO_PUBLISH="" - for sdk in sdk-python sdk-node sdk-java sdk-csharp sdk-go; do + for sdk in sdk-python sdk-php sdk-node sdk-java sdk-csharp sdk-go; do VERSION=$(scripts/extract-version.sh "$sdk") TAG=$(scripts/extract-version.sh "$sdk" --tag) @@ -163,4 +164,3 @@ jobs: publish_other: ${{ needs.check-auto-publish.outputs.sdks_to_publish }} create_edge_docker_tag: true secrets: inherit - diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index aa28ccd10c..48621be3f9 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -49,7 +49,7 @@ on: required: false default: "" publish_other: - description: "Other SDKs to publish (comma-separated: python, node, java, csharp, go)" + description: "Other SDKs to publish (comma-separated: python, php, node, java, csharp, go)" type: string required: false default: "" @@ -119,7 +119,7 @@ permissions: id-token: write # Static group so two concurrent release dispatches cannot race the -# crates.io / Maven Central / npm / NuGet / DockerHub / PyPI uploads. +# crates.io / Maven Central / npm / NuGet / DockerHub / PyPI / GitHub release uploads. # `run_id` in the group was effectively a no-op because it is unique # per dispatch. `cancel-in-progress: false` keeps any in-flight upload # running to completion rather than leaving partial state in registries. @@ -260,10 +260,12 @@ jobs: targets: ${{ steps.mk.outputs.targets }} non_rust_targets: ${{ steps.mk.outputs.non_rust_targets }} non_docker_targets: ${{ steps.mk.outputs.non_docker_targets }} + php_targets: ${{ steps.mk.outputs.php_targets }} docker_matrix: ${{ steps.mk.outputs.docker_matrix }} docker_components: ${{ steps.mk.outputs.docker_components }} count: ${{ steps.mk.outputs.count }} has_python: ${{ steps.mk.outputs.has_python }} + has_php: ${{ steps.mk.outputs.has_php }} has_rust_crates: ${{ steps.mk.outputs.has_rust_crates }} has_docker: ${{ steps.mk.outputs.has_docker }} steps: @@ -327,7 +329,7 @@ jobs: // Parse other SDKs ('${{ inputs.publish_other }}').split(',').map(s => s.trim()).filter(Boolean).forEach(sdk => { - if (['python','node','java','csharp','go'].includes(sdk)) { + if (['python','php','node','java','csharp','go'].includes(sdk)) { wants.push(`sdk-${sdk}`); } else { core.warning(`Unknown SDK: ${sdk}`); @@ -338,6 +340,7 @@ jobs: dockerhub: 'docker', crates: 'rust', pypi: 'python', + 'github-release': 'php', npm: 'node', maven: 'java', nuget: 'csharp', @@ -384,11 +387,13 @@ jobs: // Separate Docker targets from other non-Rust targets const dockerTargets = nonRustTargets.filter(t => t.type === 'docker'); const nonDockerTargets = nonRustTargets.filter(t => t.type !== 'docker'); + const phpTargets = nonDockerTargets.filter(t => t.type === 'php'); + const otherSdkTargets = nonDockerTargets.filter(t => t.type !== 'php'); console.log(`Publishing ${targets.length} components:`); targets.forEach(t => console.log(` - ${t.name} (${t.type}) -> ${t.registry || 'N/A'}`)); console.log(` (${nonRustTargets.length} non-Rust, ${targets.length - nonRustTargets.length} Rust crates)`); - console.log(` (${dockerTargets.length} Docker, ${nonDockerTargets.length} other SDKs)`); + console.log(` (${dockerTargets.length} Docker, ${otherSdkTargets.length} other SDKs, ${phpTargets.length} PHP SDK)`); // Output all targets for reference and tag creation core.setOutput('targets', JSON.stringify(targets.length ? { include: targets } : { include: [{ key: 'noop', type: 'noop' }] })); @@ -396,8 +401,10 @@ jobs: // Output only non-Rust targets for the parallel publish job core.setOutput('non_rust_targets', JSON.stringify(nonRustTargets.length ? { include: nonRustTargets } : { include: [{ key: 'noop', type: 'noop' }] })); - // Output non-Docker, non-Rust targets (SDKs only) - core.setOutput('non_docker_targets', JSON.stringify(nonDockerTargets.length ? { include: nonDockerTargets } : { include: [{ key: 'noop', type: 'noop' }] })); + // Output non-Docker, non-Rust, non-PHP targets. PHP needs its + // pre-built binary artifact, so it has a separate publish job. + core.setOutput('non_docker_targets', JSON.stringify(otherSdkTargets.length ? { include: otherSdkTargets } : { include: [{ key: 'noop', type: 'noop' }] })); + core.setOutput('php_targets', JSON.stringify(phpTargets.length ? { include: phpTargets } : { include: [{ key: 'noop', type: 'noop' }] })); // Build Docker matrix: components × platforms for native runner builds const platforms = [ @@ -428,6 +435,13 @@ jobs: core.setOutput('has_python', 'false'); } + const phpTarget = targets.find(t => t.key === 'sdk-php'); + if (phpTarget) { + core.setOutput('has_php', 'true'); + } else { + core.setOutput('has_php', 'false'); + } + check-tags: name: Check existing tags needs: [validate, plan] @@ -537,7 +551,7 @@ jobs: # Versioned git tags require manual publish (workflow_dispatch). REGISTRY=$(_jq '.registry') if [ "${{ inputs.create_edge_docker_tag }}" = "true" ] && [ "$REGISTRY" = "dockerhub" ]; then - if [[ ! "$VERSION" =~ -(edge|rc) ]]; then + if [ "$(scripts/extract-version.sh "$KEY" --is-pre-release 2>/dev/null || echo false)" != "true" ]; then echo "⏭️ $NAME: Stable Docker version in auto-publish mode, tag will be skipped" echo "| $NAME | $VERSION | $TAG | ⏭️ Stable (manual publish only) |" >> $GITHUB_STEP_SUMMARY continue @@ -580,8 +594,8 @@ jobs: echo "| $NAME | $VERSION | $TAG | ℹ️ Already released ($SHORT_SHA) - :edge refresh only |" >> $GITHUB_STEP_SUMMARY # Fail-fast on wrong-target for MANUAL publish. A wrong-target tag # means create-git-tag would hard-fail 20-40 minutes later after - # publishing artifacts to crates.io / PyPI / npm / Maven / - # NuGet / DockerHub. Catching it at check-tags converts that + # publishing artifacts to crates.io / PyPI / npm / + # Maven / NuGet / DockerHub. Catching it at check-tags converts that # into a fast, cheap failure at the top of the run. # Same-target is still benign (rerun convergence). # @@ -711,6 +725,18 @@ jobs: use_latest_ci: ${{ inputs.use_latest_ci }} commit: ${{ needs.validate.outputs.commit }} + build-php-extensions: + name: Build PHP extensions + needs: [validate, plan, check-tags] + if: | + needs.validate.outputs.has_targets == 'true' && + needs.plan.outputs.has_php == 'true' + uses: ./.github/workflows/_build_php_extensions.yml + with: + upload_artifacts: true + use_latest_ci: ${{ inputs.use_latest_ci }} + commit: ${{ needs.validate.outputs.commit }} + # Sequential Rust crate publishing to handle dependencies properly publish-rust-crates: name: Publish Rust crates @@ -982,13 +1008,13 @@ jobs: fi else # Manual publish: always push the versioned manifest, plus :latest - # for stable (non edge/rc) releases. + # for stable releases. docker buildx imagetools create \ -t "${IMAGE}:${VERSION}" \ $(printf "${IMAGE}@sha256:%s " *) echo "✅ Pushed manifest: ${IMAGE}:${VERSION}" - if [[ ! "$VERSION" =~ -(edge|rc) ]]; then + if [ "$(scripts/extract-version.sh "${{ matrix.key }}" --is-pre-release)" != "true" ]; then echo "Creating 'latest' manifest" docker buildx imagetools create \ -t "${IMAGE}:latest" \ @@ -1314,6 +1340,101 @@ jobs: # above ensure the tag is never pushed before the registry is serving # the artifact (Go is exempt: the tag IS the release). - name: Tag SDK release (${{ matrix.key }}) + if: | + success() && + inputs.dry_run == false && + matrix.type != 'php' && + inputs.skip_tag_creation == false && + steps.ver.outputs.should_tag == 'true' + uses: ./.github/actions/utils/create-git-tag + with: + tag: ${{ steps.ver.outputs.tag }} + commit: ${{ needs.validate.outputs.commit }} + message: | + Release ${{ matrix.key }} ${{ steps.ver.outputs.version }} + + Component: ${{ matrix.key }} + Tag: ${{ steps.ver.outputs.tag }} + Commit: ${{ needs.validate.outputs.commit }} + Released by: GitHub Actions (workflow ${{ github.run_id }}) + + - name: Set status output + id: status + if: always() + run: echo "status=${{ job.status }}" >> "$GITHUB_OUTPUT" + + publish-php: + name: PHP SDK + needs: [validate, plan, check-tags, build-php-extensions] + if: | + !cancelled() && + needs.validate.outputs.has_targets == 'true' && + needs.plan.outputs.has_php == 'true' && + fromJson(needs.plan.outputs.php_targets).include[0].key != 'noop' && + (needs.build-php-extensions.result == 'success' || needs.build-php-extensions.result == 'skipped') + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.plan.outputs.php_targets) }} + outputs: + status: ${{ steps.status.outputs.status }} + version: ${{ steps.ver.outputs.version }} + tag: ${{ steps.ver.outputs.tag }} + steps: + - name: Download latest copy script from master + if: inputs.use_latest_ci + run: | + curl -sSL "https://raw.githubusercontent.com/${{ github.repository }}/master/scripts/copy-latest-from-master.sh" \ + -o /tmp/copy-latest-from-master.sh + chmod +x /tmp/copy-latest-from-master.sh + echo "✅ Downloaded latest copy script from master" + + - name: Checkout at commit + uses: actions/checkout@v6.0.2 + with: + ref: ${{ needs.validate.outputs.commit }} + fetch-depth: 1 + + - name: Save and apply latest CI from master + if: inputs.use_latest_ci + run: | + /tmp/copy-latest-from-master.sh save \ + .github \ + scripts + + /tmp/copy-latest-from-master.sh apply + + - name: Ensure version extractor is executable + run: | + test -x scripts/extract-version.sh || chmod +x scripts/extract-version.sh + + - name: Extract version & tag + id: ver + shell: bash + env: + MATRIX_KEY: ${{ matrix.key }} + MATRIX_TAG_PATTERN: ${{ matrix.tag_pattern }} + CREATE_EDGE_DOCKER_TAG: ${{ inputs.create_edge_docker_tag }} + run: | + set -euo pipefail + VERSION=$(scripts/extract-version.sh "$MATRIX_KEY") + TAG=$(scripts/extract-version.sh "$MATRIX_KEY" --tag) + SHOULD_TAG=$(scripts/extract-version.sh "$MATRIX_KEY" --should-tag) + + if [ "$SHOULD_TAG" = "true" ] \ + && [ "$CREATE_EDGE_DOCKER_TAG" = "true" ] \ + && [ "$(scripts/extract-version.sh "$MATRIX_KEY" --is-pre-release)" != "true" ]; then + SHOULD_TAG=false + fi + + { + echo "version=$VERSION" + echo "tag=$TAG" + echo "should_tag=$SHOULD_TAG" + } >> "$GITHUB_OUTPUT" + echo "✅ Resolved $MATRIX_KEY -> version=$VERSION tag=$TAG should_tag=$SHOULD_TAG" + + - name: Tag PHP SDK release (${{ matrix.key }}) if: | success() && inputs.dry_run == false && @@ -1331,6 +1452,36 @@ jobs: Commit: ${{ needs.validate.outputs.commit }} Released by: GitHub Actions (workflow ${{ github.run_id }}) + - name: Publish PHP SDK + if: | + success() && + ( + inputs.dry_run == true || + ( + inputs.skip_tag_creation == false && + steps.ver.outputs.should_tag == 'true' + ) + ) + uses: ./.github/actions/php/post-merge + with: + version: ${{ steps.ver.outputs.version }} + tag: ${{ steps.ver.outputs.tag }} + commit: ${{ needs.validate.outputs.commit }} + dry_run: ${{ inputs.dry_run }} + packages_artifact: php-extensions-all + packages_path: dist + + - name: Skip PHP SDK publishing without tag + if: | + success() && + inputs.dry_run == false && + ( + inputs.skip_tag_creation == true || + steps.ver.outputs.should_tag != 'true' + ) + run: | + echo "PHP GitHub release publishing is skipped because it requires a taggable php-sdk version." + - name: Set status output id: status if: always() @@ -1345,10 +1496,12 @@ jobs: plan, check-tags, build-python-wheels, + build-php-extensions, publish-rust-crates, publish-docker, docker-manifests, publish, + publish-php, ] if: ${{ !cancelled() && needs.validate.outputs.has_targets == 'true' }} runs-on: ubuntu-latest @@ -1432,6 +1585,7 @@ jobs: crates) REGISTRY_DISPLAY="crates.io" ;; dockerhub) REGISTRY_DISPLAY="Docker Hub" ;; pypi) REGISTRY_DISPLAY="PyPI" ;; + github-release) REGISTRY_DISPLAY="GitHub Releases" ;; npm) REGISTRY_DISPLAY="npm" ;; maven) REGISTRY_DISPLAY="Maven" ;; nuget) REGISTRY_DISPLAY="NuGet" ;; @@ -1477,6 +1631,28 @@ jobs: echo fi + # PHP extension building status + if [ "${{ needs.plan.outputs.has_php }}" = "true" ]; then + echo "### PHP Extension Building" + case "${{ needs.build-php-extensions.result }}" in + success) echo "✅ **PHP extension packages built successfully for all platforms**" ;; + failure) echo "❌ **PHP extension package building failed**" ;; + skipped) echo "⏭️ **PHP extension package building was skipped**" ;; + esac + echo + fi + + if [ "${{ needs.plan.outputs.has_php }}" = "true" ]; then + echo "### PHP SDK Publishing" + case "${{ needs.publish-php.result }}" in + success) echo "✅ **PHP SDK GitHub release assets published successfully**" ;; + failure) echo "❌ **PHP SDK publishing failed - check logs for details**" ;; + cancelled) echo "🚫 **PHP SDK publishing was cancelled**" ;; + skipped) echo "⏭️ **PHP SDK publishing was skipped**" ;; + esac + echo + fi + # Rust crates publishing status if [ -n "${{ inputs.publish_crates }}" ]; then echo "### Rust Crates Publishing (Sequential)" @@ -1536,10 +1712,12 @@ jobs: validate, plan, build-python-wheels, + build-php-extensions, publish-rust-crates, publish-docker, docker-manifests, publish, + publish-php, summary, ] if: failure() && inputs.dry_run == false diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 67ecaaaa0e..5a50b43e54 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -103,6 +103,14 @@ repos: files: ^foreign/python/(Cargo\.toml|pyproject\.toml)$ pass_filenames: false + - id: php-sdk-version-sync + name: php sdk version sync + entry: ./scripts/ci/php-sdk-version-sync.sh + args: ["--fix"] + language: system + files: ^foreign/php/(Cargo\.toml|composer\.json)$ + pass_filenames: false + - id: python-interpreter-version-sync name: python interpreter version sync entry: ./scripts/ci/sync-python-interpreter-version.sh @@ -124,7 +132,7 @@ repos: entry: ./scripts/extract-version.sh args: ["--check"] language: system - files: (^Cargo\.toml$|^(core|foreign)/.*Cargo\.toml$|^foreign/node/package\.json$|^foreign/python/pyproject\.toml$|^foreign/csharp/.*\.csproj$|^foreign/java/gradle\.properties$|^foreign/go/contracts/version\.go$|^\.github/config/publish\.yml$) + files: (^Cargo\.toml$|^(core|foreign)/.*Cargo\.toml$|^foreign/node/package\.json$|^foreign/python/pyproject\.toml$|^foreign/php/composer\.json$|^foreign/csharp/.*\.csproj$|^foreign/java/gradle\.properties$|^foreign/go/contracts/version\.go$|^\.github/config/publish\.yml$) pass_filenames: false - id: trailing-whitespace diff --git a/foreign/php/Cargo.toml b/foreign/php/Cargo.toml index 0384b65633..554b47f4fc 100644 --- a/foreign/php/Cargo.toml +++ b/foreign/php/Cargo.toml @@ -32,7 +32,7 @@ crate-type = ["cdylib"] bytes = "1.11.1" futures = "0.3.32" ext-php-rs = "=0.15.14" -iggy = { path = "../../core/sdk" } +iggy = { path = "../../core/sdk", version = "0.10.1-edge.2" } tokio = "1.52.3" [profile.release] diff --git a/scripts/bump-version.sh b/scripts/bump-version.sh index aa4b086518..46c5a4d401 100755 --- a/scripts/bump-version.sh +++ b/scripts/bump-version.sh @@ -33,7 +33,7 @@ Usage: bump-version.sh --status [] Components: - rust-sdk core/sdk + workspace dep + python iggy dep + rust-sdk core/sdk + workspace dep + python/php iggy deps rust-common core/common + workspace dep rust-binary-protocol core/binary_protocol + workspace dep rust-server core/server @@ -53,6 +53,7 @@ Components: connectors-all runtime + all sink + all source crates --all All components (Rust + connectors + SDKs + web-ui) sdk-python foreign/python/Cargo.toml + foreign/python/pyproject.toml + sdk-php foreign/php/Cargo.toml sdk-node foreign/node/package.json sdk-go foreign/go/contracts/version.go sdk-csharp foreign/csharp/Iggy_SDK/Iggy_SDK.csproj @@ -90,11 +91,11 @@ RUST_COMPONENTS="rust-sdk rust-common rust-binary-protocol rust-server rust-cli CONNECTOR_SINK_COMPONENTS="rust-connector-delta-sink rust-connector-elasticsearch-sink rust-connector-http-sink rust-connector-iceberg-sink rust-connector-influxdb-sink rust-connector-mongodb-sink rust-connector-postgres-sink rust-connector-quickwit-sink rust-connector-stdout-sink" CONNECTOR_SOURCE_COMPONENTS="rust-connector-elasticsearch-source rust-connector-influxdb-source rust-connector-postgres-source rust-connector-random-source" CONNECTOR_COMPONENTS="rust-connector-runtime ${CONNECTOR_SINK_COMPONENTS} ${CONNECTOR_SOURCE_COMPONENTS}" -SDK_COMPONENTS="sdk-python sdk-node sdk-go sdk-csharp sdk-java" +SDK_COMPONENTS="sdk-python sdk-php sdk-node sdk-go sdk-csharp sdk-java" ALL_COMPONENTS="${RUST_COMPONENTS} ${CONNECTOR_COMPONENTS} ${SDK_COMPONENTS} web-ui" # Returns "file:format" lines per component. -# Format keys: cargo, cargo-ws-dep:PKG, cargo-dep:PKG, python-cargo, pyproject, json, csproj, gradle, go +# Format keys: cargo, cargo-ws-dep:PKG, cargo-dep:PKG, python-cargo, pyproject, php-cargo, json, csproj, gradle, go get_version_files() { local component="$1" case "$component" in @@ -102,6 +103,7 @@ get_version_files() { echo "core/sdk/Cargo.toml:cargo" echo "Cargo.toml:cargo-ws-dep:iggy" echo "foreign/python/Cargo.toml:cargo-dep:iggy" + echo "foreign/php/Cargo.toml:cargo-dep:iggy" ;; rust-common) echo "core/common/Cargo.toml:cargo" @@ -152,6 +154,9 @@ get_version_files() { echo "foreign/python/Cargo.toml:python-cargo" echo "foreign/python/pyproject.toml:pyproject" ;; + sdk-php) + echo "foreign/php/Cargo.toml:php-cargo" + ;; sdk-node) echo "foreign/node/package.json:json" ;; @@ -245,7 +250,7 @@ translate_version() { read -r base pre_type pre_num <<< "$parsed" case "$format" in - cargo|cargo-ws-dep:*|cargo-dep:*|json|csproj|go) + cargo|cargo-ws-dep:*|cargo-dep:*|php-cargo|json|csproj|go) echo "$canonical" ;; python-cargo) case "$pre_type" in @@ -272,7 +277,7 @@ translate_version() { canonicalize_version() { local raw="$1" format="$2" case "$format" in - cargo|cargo-ws-dep:*|cargo-dep:*|json|csproj|go) + cargo|cargo-ws-dep:*|cargo-dep:*|php-cargo|json|csproj|go) echo "$raw" ;; python-cargo) # Handle both old (0.7.2-dev.1) and new (0.7.2-dev1) formats @@ -319,6 +324,10 @@ read_current_version() { grep '^version = ' "$abs_file" | head -1 | sed 's/version = "\(.*\)"/\1/' ;; pyproject) grep '^version = ' "$abs_file" | head -1 | sed 's/version = "\(.*\)"/\1/' ;; + php-cargo) + grep '^version = ' "$abs_file" | head -1 | sed 's/version = "\(.*\)"/\1/' ;; + composer) + grep '"version"' "$abs_file" | head -1 | sed 's/.*"version": *"\([^"]*\)".*/\1/' ;; json) grep '"version"' "$abs_file" | head -1 | sed 's/.*"version": *"\([^"]*\)".*/\1/' ;; csproj) @@ -363,6 +372,10 @@ write_version() { sedi "1,/^version = \".*\"/s/^version = \".*\"/version = \"${translated}\"/" "$abs_file" ;; pyproject) sedi '/^\[project\]/,/^\[/{s/^version = ".*"/version = "'"${translated}"'"/;}' "$abs_file" ;; + php-cargo) + sedi "1,/^version = \".*\"/s/^version = \".*\"/version = \"${translated}\"/" "$abs_file" ;; + composer) + sedi "1,/\"version\": *\"[^\"]*\"/{s/\"version\": *\"[^\"]*\"/\"version\": \"${translated}\"/;}" "$abs_file" ;; json) sedi "1,/\"version\": *\"[^\"]*\"/{s/\"version\": *\"[^\"]*\"/\"version\": \"${translated}\"/;}" "$abs_file" ;; csproj) diff --git a/scripts/ci/php-sdk-version-sync.sh b/scripts/ci/php-sdk-version-sync.sh new file mode 100755 index 0000000000..9b93fc3089 --- /dev/null +++ b/scripts/ci/php-sdk-version-sync.sh @@ -0,0 +1,172 @@ +#!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +set -euo pipefail + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +MODE="" + +while [[ $# -gt 0 ]]; do + case $1 in + --check) + MODE="check" + shift + ;; + --fix) + MODE="fix" + shift + ;; + --help|-h) + echo "Usage: $0 [--check|--fix]" + echo "" + echo "Validate PHP SDK Cargo, Rust SDK dependency, and Composer metadata" + echo "" + echo "Options:" + echo " --check Check Cargo dependency and Composer version metadata" + echo " --fix Sync the Rust SDK dependency and remove a manual Composer version field" + echo " --help Show this help message" + exit 0 + ;; + *) + echo -e "${RED}Error: Unknown option $1${NC}" + echo "Use --help for usage information" + exit 1 + ;; + esac +done + +if [ -z "$MODE" ]; then + echo -e "${RED}Error: Please specify either --check or --fix${NC}" + echo "Use --help for usage information" + exit 1 +fi + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$REPO_ROOT" + +RUST_SDK_CARGO_TOML="core/sdk/Cargo.toml" +PHP_CARGO_TOML="foreign/php/Cargo.toml" +COMPOSER_JSON="foreign/php/composer.json" + +PHP_CARGO_VERSION=$(grep '^version = ' "$PHP_CARGO_TOML" | head -1 | sed 's/version = "\(.*\)"/\1/') +RUST_SDK_VERSION=$(grep '^version = ' "$RUST_SDK_CARGO_TOML" | head -1 | sed 's/version = "\(.*\)"/\1/') +PHP_IGGY_DEP_LINE=$(grep '^iggy = ' "$PHP_CARGO_TOML" | head -1 || true) +PHP_IGGY_DEP_VERSION="" +if [[ "$PHP_IGGY_DEP_LINE" == *'version = "'* ]]; then + PHP_IGGY_DEP_VERSION=$(printf '%s\n' "$PHP_IGGY_DEP_LINE" | sed 's/.*version = "\([^"]*\)".*/\1/') +fi +COMPOSER_VERSION_LINE=$(grep '"version"' "$COMPOSER_JSON" | head -1 || true) +COMPOSER_VERSION="" +if [ -n "$COMPOSER_VERSION_LINE" ]; then + COMPOSER_VERSION=$(printf '%s\n' "$COMPOSER_VERSION_LINE" | sed 's/.*"version": *"\([^"]*\)".*/\1/') +fi + +if [ -z "$PHP_CARGO_VERSION" ]; then + echo -e "${RED}Error: Could not extract version from $PHP_CARGO_TOML${NC}" + exit 1 +fi + +if [ -z "$RUST_SDK_VERSION" ]; then + echo -e "${RED}Error: Could not extract version from $RUST_SDK_CARGO_TOML${NC}" + exit 1 +fi + +echo "PHP SDK version check:" +echo " PHP Cargo.toml: $PHP_CARGO_VERSION" +echo " core/sdk Cargo.toml: $RUST_SDK_VERSION" +if [ -n "$PHP_IGGY_DEP_VERSION" ]; then + echo " PHP iggy dependency: $PHP_IGGY_DEP_VERSION" +else + echo " PHP iggy dependency: missing version" +fi +if [ -n "$COMPOSER_VERSION" ]; then + echo " composer.json: $COMPOSER_VERSION (must be removed)" +else + echo " composer.json: no manual version" +fi +echo "" + +if [ "$MODE" = "check" ]; then + if [ "$PHP_IGGY_DEP_VERSION" = "$RUST_SDK_VERSION" ] && [ -z "$COMPOSER_VERSION" ]; then + echo -e "${GREEN}✓ PHP SDK metadata is synchronized${NC}" + exit 0 + fi + + echo -e "${RED}✗ PHP SDK metadata is NOT synchronized${NC}" + echo "" + if [ "$PHP_IGGY_DEP_VERSION" != "$RUST_SDK_VERSION" ]; then + echo "The PHP iggy dependency must match $RUST_SDK_CARGO_TOML." + fi + if [ -n "$COMPOSER_VERSION" ]; then + echo "Packagist/Composer derive VCS package versions from tags." + echo "Remove the version field from $COMPOSER_JSON." + fi + echo "" + echo -e "${YELLOW}Run '$0 --fix' to sync PHP SDK metadata${NC}" + exit 1 +fi + +changed=0 + +sedi() { + if sed --version 2>/dev/null | grep -q 'GNU'; then + sed -i "$@" + else + sed -i '' "$@" + fi +} + +if [ "$PHP_IGGY_DEP_VERSION" != "$RUST_SDK_VERSION" ]; then + if grep -q '^iggy = .*version = ' "$PHP_CARGO_TOML"; then + sedi "s/^\(iggy = .*version = \"\)[^\"]*/\1${RUST_SDK_VERSION}/" "$PHP_CARGO_TOML" + else + sedi "s/^\(iggy = {[^}]*\) }/\1, version = \"${RUST_SDK_VERSION}\" }/" "$PHP_CARGO_TOML" + fi + changed=1 +fi + +if [ -n "$COMPOSER_VERSION" ]; then + sedi '/^[[:space:]]*"version"[[:space:]]*:/d' "$COMPOSER_JSON" + changed=1 +fi + +if [ "$changed" -eq 0 ]; then + echo -e "${GREEN}✓ PHP SDK metadata already synchronized${NC}" + exit 0 +fi + +if grep -q '"version"' "$COMPOSER_JSON"; then + echo -e "${RED}Error: Failed to remove version field from $COMPOSER_JSON${NC}" + exit 1 +fi + +PHP_IGGY_DEP_LINE=$(grep '^iggy = ' "$PHP_CARGO_TOML" | head -1 || true) +PHP_IGGY_DEP_VERSION="" +if [[ "$PHP_IGGY_DEP_LINE" == *'version = "'* ]]; then + PHP_IGGY_DEP_VERSION=$(printf '%s\n' "$PHP_IGGY_DEP_LINE" | sed 's/.*version = "\([^"]*\)".*/\1/') +fi +if [ "$PHP_IGGY_DEP_VERSION" != "$RUST_SDK_VERSION" ]; then + echo -e "${RED}Error: Failed to sync PHP iggy dependency in $PHP_CARGO_TOML${NC}" + exit 1 +fi + +echo -e "${GREEN}✓ Synchronized PHP SDK metadata${NC}" diff --git a/scripts/extract-version.sh b/scripts/extract-version.sh index 6253db9342..51548347dd 100755 --- a/scripts/extract-version.sh +++ b/scripts/extract-version.sh @@ -260,6 +260,22 @@ handle_check() { echo "" + # --- Check 3: PHP SDK metadata --- + echo "=== PHP SDK metadata ===" + local php_script="$SCRIPT_DIR/ci/php-sdk-version-sync.sh" + if [[ -x "$php_script" ]]; then + if "$php_script" --check; then + passes=$((passes + 1)) + else + errors=$((errors + 1)) + fi + else + echo -e " ${RED}FAIL${NC} php-sdk-version-sync.sh not found or not executable" + errors=$((errors + 1)) + fi + + echo "" + # --- Summary --- local total=$((passes + errors)) echo "=== Summary ===" @@ -406,12 +422,11 @@ fi # version would be auto-published to PyPI but never git-tagged). # # Matches (any of): -# -edge[.N] (rust crates, docker, node SDK) -# -rc[.N] (all SDKs) +# - (rust crates, docker, node/PHP SDKs) # .devN (Python SDK PEP 440 development markers) # rcN$ (legacy bare rcN, retained for compatibility) if [[ "$RETURN_IS_PRE_RELEASE" == "true" ]]; then - if [[ "$VERSION" =~ -(edge|rc) ]] \ + if [[ "$VERSION" =~ -[0-9A-Za-z] ]] \ || [[ "$VERSION" =~ \.dev[0-9]+$ ]] \ || [[ "$VERSION" =~ rc[0-9]+$ ]]; then echo "true"