diff --git a/.github/workflows/test_php.yml b/.github/workflows/test_php.yml index 8da15266de7df..02d5eeebcd609 100644 --- a/.github/workflows/test_php.yml +++ b/.github/workflows/test_php.yml @@ -32,7 +32,7 @@ jobs: - name: 8.2 Optimized version: "8.2.2" version-short: "8.2" - command: composer test && composer test_c + command: composer test && composer test_c && composer test_pie - name: 8.2 Debug version: 8.2.2-dbg version-short: "8.2" @@ -51,7 +51,7 @@ jobs: - name: 8.5 Optimized version: "8.5.2" version-short: "8.5" - command: composer test && composer test_c + command: composer test && composer test_c && composer test_pie name: ${{ matrix.continuous-only && inputs.continuous-prefix || '' }} Linux ${{ matrix.name}} runs-on: ubuntu-22-4core diff --git a/.github/workflows/update_php_repo.yml b/.github/workflows/update_php_repo.yml index 29ac26479fcb0..5bb507e2c9ded 100644 --- a/.github/workflows/update_php_repo.yml +++ b/.github/workflows/update_php_repo.yml @@ -1,4 +1,4 @@ -name: Update protobuf-php Repo +name: Update protobuf-php and protobuf-php-ext Repos on: push: @@ -10,8 +10,8 @@ permissions: contents: read # to fetch code in 'Clone protobuf' (actions/checkout) jobs: - update-repo: - name: Update PHP Repo + update-php-repo: + name: Update protobuf-php Repo runs-on: ubuntu-latest steps: - name: Checkout protobuf-php @@ -47,3 +47,40 @@ jobs: git push --force origin master git tag -a ${{ env.VERSION_TAG }} -m "Tag release ${{ env.VERSION_TAG }}" git push origin ${{ env.VERSION_TAG }} + + update-php-ext-repo: + name: Update protobuf-php-ext Repo + runs-on: ubuntu-latest + steps: + - name: Checkout protobuf-php-ext + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + repository: protocolbuffers/protobuf-php-ext + token: ${{ secrets.BOT_ACCESS_TOKEN }} + - name: Clone protobuf + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + path: protobuf + - name: Configure Git Bot + run: | + git config user.name "Protobuf Team Bot" + git config user.email "protobuf-team-bot@google.com" + - name: Get PHP Version + run: | + unformatted_version=$( cat protobuf/version.json | jq -r '.[].languages.php' ) + version=${unformatted_version/-rc/RC} + version_tag=v$version + echo "VERSION=$version" >> $GITHUB_ENV + echo "VERSION_TAG=$version_tag" >> $GITHUB_ENV + - name: Copy files + run: | + find . -maxdepth 1 ! -name '.git' ! -name '.gitignore' ! -name 'protobuf' ! -name '.' -exec rm -rf {} + + python3 protobuf/php/ext/google/protobuf/package_mirror.py protobuf . + rm -rf protobuf + - name: Push Changes + run: | + git add --all + git commit --allow-empty -m "${{ env.VERSION }} sync" + git push --force origin master + git tag -a ${{ env.VERSION_TAG }} -m "Tag release ${{ env.VERSION_TAG }}" + git push origin ${{ env.VERSION_TAG }} diff --git a/.gitignore b/.gitignore index a54510bef76f7..b3890a877e3e6 100644 --- a/.gitignore +++ b/.gitignore @@ -150,6 +150,7 @@ php/ext/google/protobuf/Makefile.fragments php/ext/google/protobuf/Makefile.global php/ext/google/protobuf/Makefile.objects php/ext/google/protobuf/acinclude.m4 +php/ext/google/protobuf/autom4te.cache/ php/ext/google/protobuf/build/ php/ext/google/protobuf/bundled_php.c php/ext/google/protobuf/config.h diff --git a/php/composer.json b/php/composer.json index 28ee767ab6b85..23dfb87293363 100644 --- a/php/composer.json +++ b/php/composer.json @@ -30,7 +30,8 @@ "test_c": "./generate_test_protos.sh && ./tests/compile_extension.sh && php -dextension=ext/google/protobuf/modules/protobuf.so vendor/bin/phpunit --bootstrap tests/force_c_ext.php tests", "test_valgrind": "./generate_test_protos.sh && ./tests/compile_extension.sh && ZEND_DONT_UNLOAD_MODULES=1 USE_VALGRIND=1 USE_ZEND_ALLOC=0 valgrind --leak-check=full --error-exitcode=1 php -dextension=ext/google/protobuf/modules/protobuf.so vendor/bin/phpunit --bootstrap tests/force_c_ext.php tests", "test": "./generate_test_protos.sh && vendor/bin/phpunit tests", - "aggregate_metadata_test": "./generate_test_protos.sh --aggregate_metadata && vendor/bin/phpunit tests" + "aggregate_metadata_test": "./generate_test_protos.sh --aggregate_metadata && vendor/bin/phpunit tests", + "test_pie": "./ext/google/protobuf/test_pie_package.sh" }, "config": { "process-timeout": 1200 diff --git a/php/ext/google/protobuf/README.md b/php/ext/google/protobuf/README.md new file mode 100644 index 0000000000000..8c666609acad9 --- /dev/null +++ b/php/ext/google/protobuf/README.md @@ -0,0 +1,15 @@ +# Protocol Buffers PHP Extension Mirror (PIE) + +This repository is a **read-only mirror** of the Protocol Buffers PHP extension C source code, optimized for installation using **PIE (PHP Installer for Extensions)**. + +Development of the Protocol Buffers PHP extension takes place inside the main repository at [protocolbuffers/protobuf](https://github.com/protocolbuffers/protobuf). Please submit all issues, feature requests, and pull requests to the main repository. + +## Installation via PIE + +You can install this extension using PIE: + +```bash +pie install google/protobuf-ext +``` + +Ensure your system has the required build tools (such as `make`, `gcc`, `autoconf`, and `phpize`) installed prior to running the installation command. diff --git a/php/ext/google/protobuf/composer.json b/php/ext/google/protobuf/composer.json new file mode 100644 index 0000000000000..ef28e2dde6a6d --- /dev/null +++ b/php/ext/google/protobuf/composer.json @@ -0,0 +1,14 @@ +{ + "name": "google/protobuf-ext", + "type": "php-ext", + "description": "Protocol Buffers PHP Extension", + "keywords": ["proto", "protobuf"], + "homepage": "https://developers.google.com/protocol-buffers/", + "license": "BSD-3-Clause", + "require": { + "php": ">=8.2.0" + }, + "php-ext": { + "extension-name": "protobuf" + } +} diff --git a/php/ext/google/protobuf/package_mirror.py b/php/ext/google/protobuf/package_mirror.py new file mode 100755 index 0000000000000..9c0a327a01065 --- /dev/null +++ b/php/ext/google/protobuf/package_mirror.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +# Protocol Buffers - Google's data interchange format +# Copyright 2026 Google Inc. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +import glob +import os +import shutil +import sys + +def copy_files(src_dir, dst_dir, patterns): + os.makedirs(dst_dir, exist_ok=True) + for pattern in patterns: + for src_path in glob.glob(os.path.join(src_dir, pattern)): + if os.path.isfile(src_path): + filename = os.path.basename(src_path) + dst_path = os.path.join(dst_dir, filename) + shutil.copy2(src_path, dst_path) + print(f"Copied: {src_path} -> {dst_path}") + +def main(): + src_root = sys.argv[1] if len(sys.argv) > 1 else 'protobuf' + dst_root = sys.argv[2] if len(sys.argv) > 2 else '.' + + ext_src_dir = os.path.join(src_root, 'php', 'ext', 'google', 'protobuf') + utf8_range_src_dir = os.path.join(src_root, 'third_party', 'utf8_range') + + if not os.path.exists(ext_src_dir): + print(f"Error: Extension source directory not found: {ext_src_dir}") + sys.exit(1) + + if not os.path.exists(utf8_range_src_dir): + fallback_dir = os.path.join(ext_src_dir, 'third_party', 'utf8_range') + if os.path.exists(fallback_dir): + utf8_range_src_dir = fallback_dir + else: + print(f"Warning: utf8_range source directory not found at {utf8_range_src_dir}") + + print(f"Syncing PHP extension files from {src_root} to {dst_root}...") + + # Copy root extension files (.c, .h, .inc, config files, composer.json, README.md) + copy_files(ext_src_dir, dst_root, [ + '*.c', '*.h', '*.inc', '*.m4', '*.w32', + 'composer.json', 'README.md', 'LICENSE' + ]) + + # Copy third_party/utf8_range dependencies + if os.path.exists(utf8_range_src_dir): + utf8_range_dst_dir = os.path.join(dst_root, 'third_party', 'utf8_range') + copy_files(utf8_range_src_dir, utf8_range_dst_dir, [ + '*.c', '*.h', '*.inc', 'LICENSE', 'README.md' + ]) + + # Copy root LICENSE if not already copied from ext_src_dir + root_license = os.path.join(src_root, 'LICENSE') + if os.path.exists(root_license) and not os.path.exists(os.path.join(dst_root, 'LICENSE')): + shutil.copy2(root_license, os.path.join(dst_root, 'LICENSE')) + print(f"Copied: {root_license} -> {os.path.join(dst_root, 'LICENSE')}") + + print("Sync completed successfully.") + +if __name__ == '__main__': + main() diff --git a/php/ext/google/protobuf/test_pie_package.sh b/php/ext/google/protobuf/test_pie_package.sh new file mode 100755 index 0000000000000..e5548c0de0cbb --- /dev/null +++ b/php/ext/google/protobuf/test_pie_package.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# Protocol Buffers - Google's data interchange format +# Copyright 2026 Google Inc. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +set -ex + +# Ensure we are running from inside the php/ directory +if [ ! -f "ext/google/protobuf/package_mirror.py" ]; then + echo "This script must be run from inside the php/ directory." + exit 1 +fi + +PIE_PHAR_URL="https://github.com/php/pie/releases/latest/download/pie.phar" +TEST_DIR=$(mktemp -d -t pie_test_XXXXXX) +PIE_BIN="$TEST_DIR/pie.phar" + +export XDG_CONFIG_HOME="$TEST_DIR/config" +export COMPOSER_HOME="$TEST_DIR/composer" +export PIE_HOME="$TEST_DIR/pie" +mkdir -p "$XDG_CONFIG_HOME" "$COMPOSER_HOME" "$PIE_HOME" + +cleanup() { + rm -rf "$TEST_DIR" +} +trap cleanup EXIT + +echo "Downloading latest PIE installer..." +curl -sSL -o "$PIE_BIN" "$PIE_PHAR_URL" + +echo "Syncing package mirror structure into temporary test directory..." +python3 ext/google/protobuf/package_mirror.py .. "$TEST_DIR/mirror" + +echo "Adding local path repository to PIE..." +php "$PIE_BIN" repository:add path "$TEST_DIR/mirror" + +echo "Testing PIE build for google/protobuf-ext..." +php "$PIE_BIN" build google/protobuf-ext:@dev + +echo "PIE package build test completed successfully!" diff --git a/src/google/protobuf/compiler/php/generator_unittest.cc b/src/google/protobuf/compiler/php/generator_unittest.cc index 79d4b10faaeaf..6c2fe73059067 100644 --- a/src/google/protobuf/compiler/php/generator_unittest.cc +++ b/src/google/protobuf/compiler/php/generator_unittest.cc @@ -46,6 +46,43 @@ TEST_F(PhpGeneratorTest, Basic) { ExpectNoErrors(); } +TEST_F(PhpGeneratorTest, Formatting) { + CreateTempFile("google/protobuf/wrappers.proto", + R"schema( + syntax = "proto3"; + package google.protobuf; + message Int32Value { + int32 value = 1; + })schema"); + + CreateTempFile("foo.proto", + R"schema( + syntax = "proto3"; + import "google/protobuf/wrappers.proto"; + message Foo { + optional int32 bar = 1; + google.protobuf.Int32Value wrapped_val = 2; + })schema"); + + RunProtoc( + "protocol_compiler --proto_path=$tmpdir --php_out=$tmpdir foo.proto"); + + ExpectNoErrors(); + + ExpectFileContentContainsSubstring( + "Foo.php", + " public function __construct($data = null)\n" + " {\n"); + + ExpectFileContentContainsSubstring( + "Foo.php", + " public function setWrappedValUnwrapped($var)\n" + " {\n" + " $this->writeWrapperValue(\"wrapped_val\", $var);\n" + " return $this;\n" + " }\n"); +} + TEST_F(PhpGeneratorTest, Proto2File) { CreateTempFile("foo.proto", R"schema(