Repository: protocolbuffers/protobuf (language: python)

Issue:
Clean up PHP CI
This PR does the following:
* Special-case descriptor.proto to allow for codegen despite being proto2
* Fix a pre-existing bug that gets exercised now during descriptor builds
* Expand test coverage of conformance tests, and fix pre-existing issues
* Properly hook up gencode to staleness infrastructure for automated regen



Discussion / hints:
Nope, as discussed off line stale file regen is handled automatically post-submit.  Tests don't run against the stale checked-in code

Fix patch:
diff --git a/conformance/BUILD.bazel b/conformance/BUILD.bazel
index 9d7938d75db9..5e0da1924aa2 100644
--- a/conformance/BUILD.bazel
+++ b/conformance/BUILD.bazel
@@ -346,7 +346,7 @@ inline_sh_binary(
         "conformance_php.php",
     ],
     cmd = """
-        php -d include_path=conformance:src/google/protobuf \\
+        php -d include_path=php/generated:conformance:src/google/protobuf:editions/golden \\
             -d auto_prepend_file=$(rootpath autoload.php) \\
             $(rootpath conformance_php.php)
     """,
@@ -354,6 +354,7 @@ inline_sh_binary(
     deps = [
         ":conformance_php_proto",
         "//:test_messages_proto3_php_proto",
+        "//editions:test_messages_proto3_editions_php_proto",
         "//php:source_files",
     ],
 )
diff --git a/conformance/autoload.php b/conformance/autoload.php
index 7f04cc008c37..6a115e7d0245 100644
--- a/conformance/autoload.php
+++ b/conformance/autoload.php
@@ -7,8 +7,12 @@
 function protobuf_autoloader_impl($class, $prefix) {
     $length = strlen($prefix);
     if ((substr($class, 0, $length) === $prefix)) {
-        $path = 'php/src/' . implode('/', array_map('ucwords', explode('\\', $class))) . '.php';
-        include_once $path;
+        $path = 'src/' . implode('/', array_map('ucwords', explode('\\', $class))) . '.php';
+        if (file_exists('php/' . $path)) {
+            include_once 'php/' . $path;
+        } else {
+            include_once 'php/generated/' . $path;
+        }
     }
 }
 
diff --git a/conformance/conformance_php.php b/conformance/conformance_php.php
index a75376114756..683eae537a61 100644
--- a/conformance/conformance_php.php
+++ b/conformance/conformance_php.php
@@ -86,6 +86,7 @@ function doTest($request)
                 case 'protobuf_test_messages.editions.proto3.TestAllTypesProto3':
                     $test_message = new TestAllTypesProto3Editions();
                     break;
+                case 'protobuf_test_messages.proto2.TestAllTypesProto2':
                 case 'protobuf_test_messages.editions.proto2.TestAllTypesProto2':
                     $response->setSkipped('PHP doesn\'t support proto2');
                     return $response;
diff --git a/conformance/failure_list_php.txt b/conformance/failure_list_php.txt
index 5c09f3fc2f3e..eca30a6c97cd 100644
--- a/conformance/failure_list_php.txt
+++ b/conformance/failure_list_php.txt
@@ -1,42 +1,36 @@
-Recommended.FieldMaskNumbersDontRoundTrip.JsonOutput
-Recommended.FieldMaskPathsDontRoundTrip.JsonOutput
-Recommended.FieldMaskTooManyUnderscore.JsonOutput
-Recommended.Proto2.JsonInput.FieldNameExtension.Validator
-Recommended.Proto3.JsonInput.BytesFieldBase64Url.JsonOutput
-Recommended.Proto3.JsonInput.BytesFieldBase64Url.ProtobufOutput
-Recommended.Proto3.JsonInput.FieldMaskInvalidCharacter
-Recommended.Proto3.JsonInput.IgnoreUnknownEnumStringValueInMapValue.ProtobufOutput
-Recommended.Proto3.JsonInput.IgnoreUnknownEnumStringValueInRepeatedField.ProtobufOutput
-Recommended.Editions_Proto2.JsonInput.IgnoreUnknownEnumStringValueInRepeatedPart.ProtobufOutput
-Recommended.Editions_Proto3.JsonInput.IgnoreUnknownEnumStringValueInRepeatedPart.ProtobufOutput
-Recommended.Proto2.JsonInput.IgnoreUnknownEnumStringValueInRepeatedPart.ProtobufOutput
-Recommended.Proto3.JsonInput.IgnoreUnknownEnumStringValueInRepeatedPart.ProtobufOutput
-Recommended.Editions_Proto2.JsonInput.IgnoreUnknownEnumStringValueInMapPart.ProtobufOutput
-Recommended.Editions_Proto3.JsonInput.IgnoreUnknownEnumStringValueInMapPart.ProtobufOutput
-Recommended.Proto2.JsonInput.IgnoreUnknownEnumStringValueInMapPart.ProtobufOutput
-Recommended.Proto3.JsonInput.IgnoreUnknownEnumStringValueInMapPart.ProtobufOutput
-Recommended.Proto3.ProtobufInput.ValidDataOneofBinary.MESSAGE.Merge.ProtobufOutput
-Required.Proto2.JsonInput.StoresDefaultPrimitive.Validator
-Required.Proto3.JsonInput.DoubleFieldTooSmall
-Required.Proto3.JsonInput.DurationNegativeNanos.JsonOutput
-Required.Proto3.JsonInput.DurationNegativeNanos.ProtobufOutput
-Required.Proto3.JsonInput.FloatFieldTooLarge
-Required.Proto3.JsonInput.FloatFieldTooSmall
-Required.Proto3.JsonInput.Int32FieldNotInteger
-Required.Proto3.JsonInput.Int64FieldNotInteger
-Required.Proto3.JsonInput.OneofFieldDuplicate
-Required.Proto3.JsonInput.OneofFieldNullSecond.JsonOutput
-Required.Proto3.JsonInput.OneofFieldNullSecond.ProtobufOutput
-Required.Proto3.JsonInput.RepeatedFieldWrongElementTypeExpectingStringsGotInt
-Required.Proto3.JsonInput.RepeatedListValue.JsonOutput
-Required.Proto3.JsonInput.RepeatedListValue.ProtobufOutput
-Required.Proto3.JsonInput.StringFieldNotAString
-Required.Proto3.JsonInput.Uint32FieldNotInteger
-Required.Proto3.JsonInput.Uint64FieldNotInteger
-Required.Proto3.ProtobufInput.RepeatedScalarMessageMerge.JsonOutput
-Required.Proto3.ProtobufInput.RepeatedScalarMessageMerge.ProtobufOutput
-Required.Proto3.ProtobufInput.ValidDataOneof.MESSAGE.Merge.JsonOutput
-Required.Proto3.ProtobufInput.ValidDataOneof.MESSAGE.Merge.ProtobufOutput
-Required.Proto3.ProtobufInput.ValidDataRepeated.FLOAT.PackedInput.JsonOutput
-Required.Proto3.ProtobufInput.ValidDataRepeated.FLOAT.UnpackedInput.JsonOutput
-Required.Proto3.ProtobufInput.ValidDataScalar.FLOAT[2].JsonOutput
+Recommended.*.FieldMaskNumbersDontRoundTrip.JsonOutput
+Recommended.*.FieldMaskPathsDontRoundTrip.JsonOutput
+Recommended.*.FieldMaskTooManyUnderscore.JsonOutput
+Recommended.*.JsonInput.BytesFieldBase64Url.JsonOutput
+Recommended.*.JsonInput.BytesFieldBase64Url.ProtobufOutput
+Recommended.*.JsonInput.FieldMaskInvalidCharacter
+Recommended.*.JsonInput.IgnoreUnknownEnumStringValueInMapValue.ProtobufOutput
+Recommended.*.JsonInput.IgnoreUnknownEnumStringValueInRepeatedField.ProtobufOutput
+Recommended.*.JsonInput.IgnoreUnknownEnumStringValueInRepeatedPart.ProtobufOutput
+Recommended.*.JsonInput.IgnoreUnknownEnumStringValueInMapPart.ProtobufOutput
+Recommended.*.ProtobufInput.ValidDataOneofBinary.MESSAGE.Merge.ProtobufOutput
+Recommended.*.ValueRejectInfNumberValue.JsonOutput # Should have failed to serialize, but didn't.
+Recommended.*.ValueRejectNanNumberValue.JsonOutput # Should have failed to serialize, but didn't.
+Required.*.JsonInput.DoubleFieldTooSmall
+Required.*.JsonInput.DurationNegativeNanos.JsonOutput
+Required.*.JsonInput.DurationNegativeNanos.ProtobufOutput
+Required.*.JsonInput.FloatFieldTooLarge
+Required.*.JsonInput.FloatFieldTooSmall
+Required.*.JsonInput.Int32FieldNotInteger
+Required.*.JsonInput.Int64FieldNotInteger
+Required.*.JsonInput.OneofFieldDuplicate
+Required.*.JsonInput.OneofFieldNullSecond.JsonOutput
+Required.*.JsonInput.OneofFieldNullSecond.ProtobufOutput
+Required.*.JsonInput.RepeatedFieldWrongElementTypeExpectingStringsGotInt
+Required.*.JsonInput.RepeatedListValue.JsonOutput
+Required.*.JsonInput.RepeatedListValue.ProtobufOutput
+Required.*.JsonInput.StringFieldNotAString
+Required.*.JsonInput.Uint32FieldNotInteger
+Required.*.JsonInput.Uint64FieldNotInteger
+Required.*.ProtobufInput.RepeatedScalarMessageMerge.JsonOutput
+Required.*.ProtobufInput.RepeatedScalarMessageMerge.ProtobufOutput
+Required.*.ProtobufInput.ValidDataOneof.MESSAGE.Merge.JsonOutput
+Required.*.ProtobufInput.ValidDataOneof.MESSAGE.Merge.ProtobufOutput
+Required.*.ProtobufInput.ValidDataRepeated.FLOAT.PackedInput.JsonOutput
+Required.*.ProtobufInput.ValidDataRepeated.FLOAT.UnpackedInput.JsonOutput
+Required.*.ProtobufInput.ValidDataScalar.FLOAT[2].JsonOutput
diff --git a/conformance/text_format_failure_list_php.txt b/conformance/text_format_failure_list_php.txt
index e69de29bb2d1..7cc5260cca00 100644
--- a/conformance/text_format_failure_list_php.txt
+++ b/conformance/text_format_failure_list_php.txt
@@ -0,0 +1,4 @@
+Recommended.*.ProtobufInput.GroupUnknownFields_Drop.TextFormatOutput # Failed to parse input or produce output.
+Recommended.*.ProtobufInput.GroupUnknownFields_Print.TextFormatOutput # Failed to parse input or produce output.
+Recommended.*.ProtobufInput.RepeatedUnknownFields_Drop.TextFormatOutput # Failed to parse input or produce output.
+Recommended.*.ProtobufInput.RepeatedUnknownFields_Print.TextFormatOutput # Failed to parse input or produce output.
diff --git a/php/BUILD.bazel b/php/BUILD.bazel
index cc0ba5b174a4..66adeb7922f9 100644
--- a/php/BUILD.bazel
+++ b/php/BUILD.bazel
@@ -9,12 +9,163 @@ load("//build_defs:internal_shell.bzl", "inline_sh_binary")
 load("//conformance:defs.bzl", "conformance_test")
 load("//upb/cmake:build_defs.bzl", "staleness_test")
 
+# We must explicitly classify every checked-in php file because we can't
+# distinguish gencode from hand-written code.  The former must be kept
+# up-to-date though, using our staleness infrastructure.
+
+_WKT_GENERATED_FILES = glob([
+    "src/Google/Protobuf/Any.php",
+    "src/Google/Protobuf/Api.php",
+    "src/Google/Protobuf/BoolValue.php",
+    "src/Google/Protobuf/BytesValue.php",
+    "src/Google/Protobuf/DoubleValue.php",
+    "src/Google/Protobuf/Duration.php",
+    "src/Google/Protobuf/Enum.php",
+    "src/Google/Protobuf/EnumValue.php",
+    "src/Google/Protobuf/Field.php",
+    "src/Google/Protobuf/Field/Cardinality.php",
+    "src/Google/Protobuf/Field/Kind.php",
+    "src/Google/Protobuf/FieldMask.php",
+    "src/Google/Protobuf/FloatValue.php",
+    "src/Google/Protobuf/GPBEmpty.php",
+    "src/Google/Protobuf/Int32Value.php",
+    "src/Google/Protobuf/Int64Value.php",
+    "src/Google/Protobuf/ListValue.php",
+    "src/Google/Protobuf/Method.php",
+    "src/Google/Protobuf/Mixin.php",
+    "src/Google/Protobuf/NullValue.php",
+    "src/Google/Protobuf/Option.php",
+    "src/Google/Protobuf/SourceContext.php",
+    "src/Google/Protobuf/StringValue.php",
+    "src/Google/Protobuf/Struct.php",
+    "src/Google/Protobuf/Syntax.php",
+    "src/Google/Protobuf/Timestamp.php",
+    "src/Google/Protobuf/Type.php",
+    "src/Google/Protobuf/UInt32Value.php",
+    "src/Google/Protobuf/UInt64Value.php",
+    "src/Google/Protobuf/Value.php",
+    "src/GPBMetadata/Google/Protobuf/Any.php",
+    "src/GPBMetadata/Google/Protobuf/Api.php",
+    "src/GPBMetadata/Google/Protobuf/Duration.php",
+    "src/GPBMetadata/Google/Protobuf/FieldMask.php",
+    "src/GPBMetadata/Google/Protobuf/GPBEmpty.php",
+    "src/GPBMetadata/Google/Protobuf/SourceContext.php",
+    "src/GPBMetadata/Google/Protobuf/Struct.php",
+    "src/GPBMetadata/Google/Protobuf/Timestamp.php",
+    "src/GPBMetadata/Google/Protobuf/Type.php",
+    "src/GPBMetadata/Google/Protobuf/Wrappers.php",
+])
+
+_DESCRIPTOR_GENERATED_FILES = [
+    "src/Google/Protobuf/Internal/DescriptorProto.php",
+    "src/Google/Protobuf/Internal/DescriptorProto/ExtensionRange.php",
+    "src/Google/Protobuf/Internal/DescriptorProto/ReservedRange.php",
+    "src/Google/Protobuf/Internal/Edition.php",
+    "src/Google/Protobuf/Internal/EnumDescriptorProto.php",
+    "src/Google/Protobuf/Internal/EnumDescriptorProto/EnumReservedRange.php",
+    "src/Google/Protobuf/Internal/EnumOptions.php",
+    "src/Google/Protobuf/Internal/EnumValueDescriptorProto.php",
+    "src/Google/Protobuf/Internal/EnumValueOptions.php",
+    "src/Google/Protobuf/Internal/ExtensionRangeOptions.php",
+    "src/Google/Protobuf/Internal/ExtensionRangeOptions/Declaration.php",
+    "src/Google/Protobuf/Internal/ExtensionRangeOptions/VerificationState.php",
+    "src/Google/Protobuf/Internal/FeatureSet.php",
+    "src/Google/Protobuf/Internal/FeatureSet/EnumType.php",
+    "src/Google/Protobuf/Internal/FeatureSet/FieldPresence.php",
+    "src/Google/Protobuf/Internal/FeatureSet/JsonFormat.php",
+    "src/Google/Protobuf/Internal/FeatureSet/MessageEncoding.php",
+    "src/Google/Protobuf/Internal/FeatureSet/RepeatedFieldEncoding.php",
+    "src/Google/Protobuf/Internal/FeatureSet/Utf8Validation.php",
+    "src/Google/Protobuf/Internal/FeatureSetDefaults.php",
+    "src/Google/Protobuf/Internal/FeatureSetDefaults/FeatureSetEditionDefault.php",
+    "src/Google/Protobuf/Internal/FieldDescriptorProto.php",
+    "src/Google/Protobuf/Internal/FieldDescriptorProto/Label.php",
+    "src/Google/Protobuf/Internal/FieldDescriptorProto/Type.php",
+    "src/Google/Protobuf/Internal/FieldOptions.php",
+    "src/Google/Protobuf/Internal/FieldOptions/CType.php",
+    "src/Google/Protobuf/Internal/FieldOptions/EditionDefault.php",
+    "src/Google/Protobuf/Internal/FieldOptions/FeatureSupport.php",
+    "src/Google/Protobuf/Internal/FieldOptions/JSType.php",
+    "src/Google/Protobuf/Internal/FieldOptions/OptionRetention.php",
+    "src/Google/Protobuf/Internal/FieldOptions/OptionTargetType.php",
+    "src/Google/Protobuf/Internal/FileDescriptorProto.php",
+    "src/Google/Protobuf/Internal/FileDescriptorSet.php",
+    "src/Google/Protobuf/Internal/FileOptions.php",
+    "src/Google/Protobuf/Internal/FileOptions/OptimizeMode.php",
+    "src/Google/Protobuf/Internal/GeneratedCodeInfo.php",
+    "src/Google/Protobuf/Internal/GeneratedCodeInfo/Annotation.php",
+    "src/Google/Protobuf/Internal/GeneratedCodeInfo/Annotation/Semantic.php",
+    "src/Google/Protobuf/Internal/MessageOptions.php",
+    "src/Google/Protobuf/Internal/MethodDescriptorProto.php",
+    "src/Google/Protobuf/Internal/MethodOptions.php",
+    "src/Google/Protobuf/Internal/MethodOptions/IdempotencyLevel.php",
+    "src/Google/Protobuf/Internal/OneofDescriptorProto.php",
+    "src/Google/Protobuf/Internal/OneofOptions.php",
+    "src/Google/Protobuf/Internal/ServiceDescriptorProto.php",
+    "src/Google/Protobuf/Internal/ServiceOptions.php",
+    "src/Google/Protobuf/Internal/SourceCodeInfo.php",
+    "src/Google/Protobuf/Internal/SourceCodeInfo/Location.php",
+    "src/Google/Protobuf/Internal/UninterpretedOption.php",
+    "src/Google/Protobuf/Internal/UninterpretedOption/NamePart.php",
+    "src/GPBMetadata/Google/Protobuf/Internal/Descriptor.php",
+]
+
+_RUNTIME_SOURCES = [
+    "src/Google/Protobuf/Descriptor.php",
+    "src/Google/Protobuf/DescriptorPool.php",
+    "src/Google/Protobuf/EnumDescriptor.php",
+    "src/Google/Protobuf/EnumValueDescriptor.php",
+    "src/Google/Protobuf/Field_Cardinality.php",
+    "src/Google/Protobuf/Field_Kind.php",
+    "src/Google/Protobuf/FieldDescriptor.php",
+    "src/Google/Protobuf/Internal/AnyBase.php",
+    "src/Google/Protobuf/Internal/CodedInputStream.php",
+    "src/Google/Protobuf/Internal/CodedOutputStream.php",
+    "src/Google/Protobuf/Internal/Descriptor.php",
+    "src/Google/Protobuf/Internal/DescriptorPool.php",
+    "src/Google/Protobuf/Internal/EnumDescriptorProto_EnumReservedRange.php",
+    "src/Google/Protobuf/Internal/DescriptorProto_ExtensionRange.php",
+    "src/Google/Protobuf/Internal/DescriptorProto_ReservedRange.php",
+    "src/Google/Protobuf/Internal/EnumBuilderContext.php",
+    "src/Google/Protobuf/Internal/EnumDescriptor.php",
+    "src/Google/Protobuf/Internal/FieldDescriptor.php",
+    "src/Google/Protobuf/Internal/FieldDescriptorProto_Label.php",
+    "src/Google/Protobuf/Internal/FieldDescriptorProto_Type.php",
+    "src/Google/Protobuf/Internal/FieldOptions_CType.php",
+    "src/Google/Protobuf/Internal/FieldOptions_JSType.php",
+    "src/Google/Protobuf/Internal/FileDescriptor.php",
+    "src/Google/Protobuf/Internal/FileOptions_OptimizeMode.php",
+    "src/Google/Protobuf/Internal/GeneratedCodeInfo_Annotation.php",
+    "src/Google/Protobuf/Internal/GetPublicDescriptorTrait.php",
+    "src/Google/Protobuf/Internal/GPBDecodeException.php",
+    "src/Google/Protobuf/Internal/GPBJsonWire.php",
+    "src/Google/Protobuf/Internal/GPBLabel.php",
+    "src/Google/Protobuf/Internal/GPBType.php",
+    "src/Google/Protobuf/Internal/GPBUtil.php",
+    "src/Google/Protobuf/Internal/GPBWire.php",
+    "src/Google/Protobuf/Internal/GPBWireType.php",
+    "src/Google/Protobuf/Internal/HasPublicDescriptorTrait.php",
+    "src/Google/Protobuf/Internal/MapEntry.php",
+    "src/Google/Protobuf/Internal/MapField.php",
+    "src/Google/Protobuf/Internal/MapFieldIter.php",
+    "src/Google/Protobuf/Internal/Message.php",
+    "src/Google/Protobuf/Internal/MessageBuilderContext.php",
+    "src/Google/Protobuf/Internal/MethodOptions_IdempotencyLevel.php",
+    "src/Google/Protobuf/Internal/OneofDescriptor.php",
+    "src/Google/Protobuf/Internal/OneofField.php",
+    "src/Google/Protobuf/Internal/RawInputStream.php",
+    "src/Google/Protobuf/Internal/RepeatedField.php",
+    "src/Google/Protobuf/Internal/RepeatedFieldIter.php",
+    "src/Google/Protobuf/Internal/SourceCodeInfo_Location.php",
+    "src/Google/Protobuf/Internal/TimestampBase.php",
+    "src/Google/Protobuf/Internal/UninterpretedOption_NamePart.php",
+    "src/Google/Protobuf/OneofDescriptor.php",
+]
+
 filegroup(
     name = "source_files",
-    srcs = glob([
-        "src/GPBMetadata/Google/Protobuf/**/*.php",
-        "src/Google/Protobuf/**/*.php",
-    ]) + [
+    srcs = _RUNTIME_SOURCES +
+           ["generated/" + f for f in _DESCRIPTOR_GENERATED_FILES + _WKT_GENERATED_FILES] + [
         "composer.json",
         ":php_ext_source_files",
     ],
@@ -47,9 +198,16 @@ genrule(
     srcs = [
         ":source_files",
         "//third_party/utf8_range:utf8_range_srcs",
+        "generated/ext/google/protobuf/wkt.inc",
+        "generated/ext/google/protobuf/php-upb.h",
+        "generated/ext/google/protobuf/php-upb.c",
     ],
     outs = ["protobuf.so"],
     cmd = """
+        rm php/ext/google/protobuf/wkt.inc php/ext/google/protobuf/php-upb.h php/ext/google/protobuf/php-upb.c
+        cp $(location generated/ext/google/protobuf/wkt.inc) php/ext/google/protobuf
+        cp $(location generated/ext/google/protobuf/php-upb.h) php/ext/google/protobuf
+        cp $(location generated/ext/google/protobuf/php-upb.c) php/ext/google/protobuf
         ./$(execpath :build_extension) $@
         cp php/ext/google/protobuf/modules/protobuf.so $(OUTS)
     """,
@@ -65,10 +223,6 @@ conformance_test(
     name = "conformance_test",
     failure_list = "//conformance:failure_list_php.txt",
     maximum_edition = "2023",
-    target_compatible_with = select({
-        "@platforms//os:osx": ["@platforms//:incompatible"],
-        "//conditions:default": [],
-    }),
     testee = "//conformance:conformance_php",
     text_format_failure_list = "//conformance:text_format_failure_list_php.txt",
 )
@@ -77,10 +231,6 @@ conformance_test(
     name = "conformance_test_c",
     failure_list = "//conformance:failure_list_php_c.txt",
     maximum_edition = "2023",
-    target_compatible_with = select({
-        "@platforms//os:osx": [],
-        "//conditions:default": ["@platforms//:incompatible"],
-    }),
     testee = "//conformance:conformance_php_c",
     text_format_failure_list = "//conformance:text_format_failure_list_php.txt",
 )
@@ -88,14 +238,14 @@ conformance_test(
 genrule(
     name = "copy_php_amalgamation_h",
     srcs = ["//upb:php-upb.h"],
-    outs = ["generated-in/ext/google/protobuf/php-upb.h"],
+    outs = ["generated/ext/google/protobuf/php-upb.h"],
     cmd = "cp $< $@",
 )
 
 genrule(
     name = "copy_php_amalgamation_c",
     srcs = ["//upb:php-upb.c"],
-    outs = ["generated-in/ext/google/protobuf/php-upb.c"],
+    outs = ["generated/ext/google/protobuf/php-upb.c"],
     cmd = "cp $< $@",
 )
 
@@ -104,8 +254,50 @@ staleness_test(
     outs = [
         "ext/google/protobuf/php-upb.c",
         "ext/google/protobuf/php-upb.h",
+        "ext/google/protobuf/wkt.inc",
     ],
-    generated_pattern = "generated-in/%s",
+    generated_pattern = "generated/%s",
+    tags = ["manual"],
+)
+
+# The WKTs have to be checked in to support the Composer builds. This
+# generule and test ensure the source are current.
+
+# Make sure we error if new files start showing up, since that may go unnoticed.
+_CHECK_GENCODE = """
+for f in $$(find $(@D) -name "*.php"); do
+    local=$$(echo $$f | sed "s:bazel-out.*.bin.php.::g");
+    if [[ "$(OUTS)" != *$$local* ]]; then
+        echo "Extra generated file $$local!  Please update the lists in php/BUILD.bazel" && exit 1;
+    fi
+done
+"""
+
+# These must be combined due to windows sandbox isolation issues.
+genrule(
+    name = "gen_wkt_sources",
+    srcs = [
+        "//src/google/protobuf:well_known_type_protos",
+        "//src/google/protobuf:descriptor_proto_srcs",
+    ],
+    outs = ["generated/" + wkt for wkt in _WKT_GENERATED_FILES + _DESCRIPTOR_GENERATED_FILES] + [
+        "generated/ext/google/protobuf/wkt.inc",
+    ],
+    cmd = """
+        $(execpath //:protoc) --php_out=internal_generate_c_wkt:$(RULEDIR)/generated/src --proto_path=src $(locations //src/google/protobuf:well_known_type_protos);
+        $(execpath //:protoc) --php_out=internal:$(RULEDIR)/generated/src --proto_path=src $(location //src/google/protobuf:descriptor_proto_srcs);
+    """ + _CHECK_GENCODE,
+    tags = ["manual"],
+    tools = ["//:protoc"],
+)
+
+staleness_test(
+    name = "proto_staleness_test",
+    outs = glob(
+        ["src/**/*.php"],
+        exclude = _RUNTIME_SOURCES,
+    ),
+    generated_pattern = "generated/%s",
     tags = ["manual"],
 )
 
diff --git a/php/ext/google/protobuf/wkt.inc b/php/ext/google/protobuf/wkt.inc
index 799a48fd4656..6902c4f0f039 100644
--- a/php/ext/google/protobuf/wkt.inc
+++ b/php/ext/google/protobuf/wkt.inc
@@ -12,8 +12,8 @@ static void google_protobuf_empty_proto_AddDescriptor();
 static void google_protobuf_field_mask_proto_AddDescriptor();
 static void google_protobuf_source_context_proto_AddDescriptor();
 static void google_protobuf_struct_proto_AddDescriptor();
-static void google_protobuf_type_proto_AddDescriptor();
 static void google_protobuf_timestamp_proto_AddDescriptor();
+static void google_protobuf_type_proto_AddDescriptor();
 static void google_protobuf_wrappers_proto_AddDescriptor();
 /* google/protobuf/any.proto */
 
@@ -1497,6 +1497,127 @@ static void google_protobuf_NullValue_ModuleInit() {
                                    strlen("NULL_VALUE"), 0);
 }
 
+/* google/protobuf/timestamp.proto */
+
+zend_class_entry* GPBMetadata_Google_Protobuf_Timestamp_ce;
+
+const char google_protobuf_timestamp_proto_descriptor [239] = {
+'\n', '\037', 'g', 'o', 'o', 'g', 'l', 'e', '/', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '/', 't', 'i', 'm', 'e', 's', 't', 'a', 
+'m', 'p', '.', 'p', 'r', 'o', 't', 'o', '\022', '\017', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', 
+'\"', '+', '\n', '\t', 'T', 'i', 'm', 'e', 's', 't', 'a', 'm', 'p', '\022', '\017', '\n', '\007', 's', 'e', 'c', 'o', 'n', 'd', 's', '\030', 
+'\001', ' ', '\001', '(', '\003', '\022', '\r', '\n', '\005', 'n', 'a', 'n', 'o', 's', '\030', '\002', ' ', '\001', '(', '\005', 'B', '\205', '\001', '\n', '\023', 
+'c', 'o', 'm', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', 'B', '\016', 'T', 'i', 'm', 'e', 
+'s', 't', 'a', 'm', 'p', 'P', 'r', 'o', 't', 'o', 'P', '\001', 'Z', '2', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'g', 'o', 'l', 'a', 
+'n', 'g', '.', 'o', 'r', 'g', '/', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '/', 't', 'y', 'p', 'e', 's', '/', 'k', 'n', 'o', 
+'w', 'n', '/', 't', 'i', 'm', 'e', 's', 't', 'a', 'm', 'p', 'p', 'b', '\370', '\001', '\001', '\242', '\002', '\003', 'G', 'P', 'B', '\252', '\002', 
+'\036', 'G', 'o', 'o', 'g', 'l', 'e', '.', 'P', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'W', 'e', 'l', 'l', 'K', 'n', 'o', 'w', 
+'n', 'T', 'y', 'p', 'e', 's', 'b', '\006', 'p', 'r', 'o', 't', 'o', '3', 
+};
+
+static void google_protobuf_timestamp_proto_AddDescriptor() {
+  if (DescriptorPool_HasFile("google/protobuf/timestamp.proto")) return;
+  DescriptorPool_AddDescriptor("google/protobuf/timestamp.proto", google_protobuf_timestamp_proto_descriptor,
+                               sizeof(google_protobuf_timestamp_proto_descriptor));
+}
+
+static PHP_METHOD(GPBMetadata_Google_Protobuf_Timestamp, initOnce) {
+  google_protobuf_timestamp_proto_AddDescriptor();
+}
+
+static zend_function_entry GPBMetadata_Google_Protobuf_Timestamp_methods[] = {
+  PHP_ME(GPBMetadata_Google_Protobuf_Timestamp, initOnce, arginfo_void, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
+  ZEND_FE_END
+};
+
+static void GPBMetadata_Google_Protobuf_Timestamp_ModuleInit() {
+  zend_class_entry tmp_ce;
+
+  INIT_CLASS_ENTRY(tmp_ce, "GPBMetadata\\Google\\Protobuf\\Timestamp",
+                   GPBMetadata_Google_Protobuf_Timestamp_methods);
+
+  GPBMetadata_Google_Protobuf_Timestamp_ce = zend_register_internal_class(&tmp_ce);
+}
+
+/* google_protobuf_Timestamp */
+
+zend_class_entry* google_protobuf_Timestamp_ce;
+
+static PHP_METHOD(google_protobuf_Timestamp, __construct) {
+  google_protobuf_timestamp_proto_AddDescriptor();
+  zim_Message___construct(INTERNAL_FUNCTION_PARAM_PASSTHRU);
+}
+
+static PHP_METHOD(google_protobuf_Timestamp, getSeconds) {
+  Message* intern = (Message*)Z_OBJ_P(getThis());
+  const upb_FieldDef *f = upb_MessageDef_FindFieldByName(
+      intern->desc->msgdef, "seconds");
+  zval ret;
+  Message_get(intern, f, &ret);
+  RETURN_COPY_VALUE(&ret);
+}
+
+static PHP_METHOD(google_protobuf_Timestamp, setSeconds) {
+  Message* intern = (Message*)Z_OBJ_P(getThis());
+  const upb_FieldDef *f = upb_MessageDef_FindFieldByName(
+      intern->desc->msgdef, "seconds");
+  zval *val;
+  if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &val)
+      == FAILURE) {
+    return;
+  }
+  Message_set(intern, f, val);
+  RETURN_COPY(getThis());
+}
+
+static PHP_METHOD(google_protobuf_Timestamp, getNanos) {
+  Message* intern = (Message*)Z_OBJ_P(getThis());
+  const upb_FieldDef *f = upb_MessageDef_FindFieldByName(
+      intern->desc->msgdef, "nanos");
+  zval ret;
+  Message_get(intern, f, &ret);
+  RETURN_COPY_VALUE(&ret);
+}
+
+static PHP_METHOD(google_protobuf_Timestamp, setNanos) {
+  Message* intern = (Message*)Z_OBJ_P(getThis());
+  const upb_FieldDef *f = upb_MessageDef_FindFieldByName(
+      intern->desc->msgdef, "nanos");
+  zval *val;
+  if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &val)
+      == FAILURE) {
+    return;
+  }
+  Message_set(intern, f, val);
+  RETURN_COPY(getThis());
+}
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_timestamp_fromdatetime, 0, 0, 1)
+  ZEND_ARG_INFO(0, datetime)
+ZEND_END_ARG_INFO()
+
+static zend_function_entry google_protobuf_Timestamp_phpmethods[] = {
+  PHP_ME(google_protobuf_Timestamp, __construct, arginfo_construct, ZEND_ACC_PUBLIC)
+  PHP_ME(google_protobuf_Timestamp, getSeconds, arginfo_void, ZEND_ACC_PUBLIC)
+  PHP_ME(google_protobuf_Timestamp, setSeconds, arginfo_setter, ZEND_ACC_PUBLIC)
+  PHP_ME(google_protobuf_Timestamp, getNanos, arginfo_void, ZEND_ACC_PUBLIC)
+  PHP_ME(google_protobuf_Timestamp, setNanos, arginfo_setter, ZEND_ACC_PUBLIC)
+  PHP_ME(google_protobuf_Timestamp, fromDateTime, arginfo_timestamp_fromdatetime, ZEND_ACC_PUBLIC)
+  PHP_ME(google_protobuf_Timestamp, toDateTime, arginfo_void, ZEND_ACC_PUBLIC)
+  ZEND_FE_END
+};
+
+static void google_protobuf_Timestamp_ModuleInit() {
+  zend_class_entry tmp_ce;
+
+  INIT_CLASS_ENTRY(tmp_ce, "Google\\Protobuf\\Timestamp",
+                   google_protobuf_Timestamp_phpmethods);
+
+  google_protobuf_Timestamp_ce = zend_register_internal_class(&tmp_ce);
+  google_protobuf_Timestamp_ce->ce_flags |= ZEND_ACC_FINAL;
+  google_protobuf_Timestamp_ce->create_object = Message_create;
+  zend_do_inheritance(google_protobuf_Timestamp_ce, message_ce);
+}
+
 /* google/protobuf/type.proto */
 
 zend_class_entry* GPBMetadata_Google_Protobuf_Type_ce;
@@ -2584,127 +2705,6 @@ static void google_protobuf_Syntax_ModuleInit() {
                                    strlen("SYNTAX_PROTO3"), 1);
 }
 
-/* google/protobuf/timestamp.proto */
-
-zend_class_entry* GPBMetadata_Google_Protobuf_Timestamp_ce;
-
-const char google_protobuf_timestamp_proto_descriptor [239] = {
-'\n', '\037', 'g', 'o', 'o', 'g', 'l', 'e', '/', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '/', 't', 'i', 'm', 'e', 's', 't', 'a', 
-'m', 'p', '.', 'p', 'r', 'o', 't', 'o', '\022', '\017', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', 
-'\"', '+', '\n', '\t', 'T', 'i', 'm', 'e', 's', 't', 'a', 'm', 'p', '\022', '\017', '\n', '\007', 's', 'e', 'c', 'o', 'n', 'd', 's', '\030', 
-'\001', ' ', '\001', '(', '\003', '\022', '\r', '\n', '\005', 'n', 'a', 'n', 'o', 's', '\030', '\002', ' ', '\001', '(', '\005', 'B', '\205', '\001', '\n', '\023', 
-'c', 'o', 'm', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', 'B', '\016', 'T', 'i', 'm', 'e', 
-'s', 't', 'a', 'm', 'p', 'P', 'r', 'o', 't', 'o', 'P', '\001', 'Z', '2', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'g', 'o', 'l', 'a', 
-'n', 'g', '.', 'o', 'r', 'g', '/', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '/', 't', 'y', 'p', 'e', 's', '/', 'k', 'n', 'o', 
-'w', 'n', '/', 't', 'i', 'm', 'e', 's', 't', 'a', 'm', 'p', 'p', 'b', '\370', '\001', '\001', '\242', '\002', '\003', 'G', 'P', 'B', '\252', '\002', 
-'\036', 'G', 'o', 'o', 'g', 'l', 'e', '.', 'P', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'W', 'e', 'l', 'l', 'K', 'n', 'o', 'w', 
-'n', 'T', 'y', 'p', 'e', 's', 'b', '\006', 'p', 'r', 'o', 't', 'o', '3', 
-};
-
-static void google_protobuf_timestamp_proto_AddDescriptor() {
-  if (DescriptorPool_HasFile("google/protobuf/timestamp.proto")) return;
-  DescriptorPool_AddDescriptor("google/protobuf/timestamp.proto", google_protobuf_timestamp_proto_descriptor,
-                               sizeof(google_protobuf_timestamp_proto_descriptor));
-}
-
-static PHP_METHOD(GPBMetadata_Google_Protobuf_Timestamp, initOnce) {
-  google_protobuf_timestamp_proto_AddDescriptor();
-}
-
-static zend_function_entry GPBMetadata_Google_Protobuf_Timestamp_methods[] = {
-  PHP_ME(GPBMetadata_Google_Protobuf_Timestamp, initOnce, arginfo_void, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
-  ZEND_FE_END
-};
-
-static void GPBMetadata_Google_Protobuf_Timestamp_ModuleInit() {
-  zend_class_entry tmp_ce;
-
-  INIT_CLASS_ENTRY(tmp_ce, "GPBMetadata\\Google\\Protobuf\\Timestamp",
-                   GPBMetadata_Google_Protobuf_Timestamp_methods);
-
-  GPBMetadata_Google_Protobuf_Timestamp_ce = zend_register_internal_class(&tmp_ce);
-}
-
-/* google_protobuf_Timestamp */
-
-zend_class_entry* google_protobuf_Timestamp_ce;
-
-static PHP_METHOD(google_protobuf_Timestamp, __construct) {
-  google_protobuf_timestamp_proto_AddDescriptor();
-  zim_Message___construct(INTERNAL_FUNCTION_PARAM_PASSTHRU);
-}
-
-static PHP_METHOD(google_protobuf_Timestamp, getSeconds) {
-  Message* intern = (Message*)Z_OBJ_P(getThis());
-  const upb_FieldDef *f = upb_MessageDef_FindFieldByName(
-      intern->desc->msgdef, "seconds");
-  zval ret;
-  Message_get(intern, f, &ret);
-  RETURN_COPY_VALUE(&ret);
-}
-
-static PHP_METHOD(google_protobuf_Timestamp, setSeconds) {
-  Message* intern = (Message*)Z_OBJ_P(getThis());
-  const upb_FieldDef *f = upb_MessageDef_FindFieldByName(
-      intern->desc->msgdef, "seconds");
-  zval *val;
-  if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &val)
-      == FAILURE) {
-    return;
-  }
-  Message_set(intern, f, val);
-  RETURN_COPY(getThis());
-}
-
-static PHP_METHOD(google_protobuf_Timestamp, getNanos) {
-  Message* intern = (Message*)Z_OBJ_P(getThis());
-  const upb_FieldDef *f = upb_MessageDef_FindFieldByName(
-      intern->desc->msgdef, "nanos");
-  zval ret;
-  Message_get(intern, f, &ret);
-  RETURN_COPY_VALUE(&ret);
-}
-
-static PHP_METHOD(google_protobuf_Timestamp, setNanos) {
-  Message* intern = (Message*)Z_OBJ_P(getThis());
-  const upb_FieldDef *f = upb_MessageDef_FindFieldByName(
-      intern->desc->msgdef, "nanos");
-  zval *val;
-  if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &val)
-      == FAILURE) {
-    return;
-  }
-  Message_set(intern, f, val);
-  RETURN_COPY(getThis());
-}
-
-ZEND_BEGIN_ARG_INFO_EX(arginfo_timestamp_fromdatetime, 0, 0, 1)
-  ZEND_ARG_INFO(0, datetime)
-ZEND_END_ARG_INFO()
-
-static zend_function_entry google_protobuf_Timestamp_phpmethods[] = {
-  PHP_ME(google_protobuf_Timestamp, __construct, arginfo_construct, ZEND_ACC_PUBLIC)
-  PHP_ME(google_protobuf_Timestamp, getSeconds, arginfo_void, ZEND_ACC_PUBLIC)
-  PHP_ME(google_protobuf_Timestamp, setSeconds, arginfo_setter, ZEND_ACC_PUBLIC)
-  PHP_ME(google_protobuf_Timestamp, getNanos, arginfo_void, ZEND_ACC_PUBLIC)
-  PHP_ME(google_protobuf_Timestamp, setNanos, arginfo_setter, ZEND_ACC_PUBLIC)
-  PHP_ME(google_protobuf_Timestamp, fromDateTime, arginfo_timestamp_fromdatetime, ZEND_ACC_PUBLIC)
-  PHP_ME(google_protobuf_Timestamp, toDateTime, arginfo_void, ZEND_ACC_PUBLIC)
-  ZEND_FE_END
-};
-
-static void google_protobuf_Timestamp_ModuleInit() {
-  zend_class_entry tmp_ce;
-
-  INIT_CLASS_ENTRY(tmp_ce, "Google\\Protobuf\\Timestamp",
-                   google_protobuf_Timestamp_phpmethods);
-
-  google_protobuf_Timestamp_ce = zend_register_internal_class(&tmp_ce);
-  google_protobuf_Timestamp_ce->ce_flags |= ZEND_ACC_FINAL;
-  google_protobuf_Timestamp_ce->create_object = Message_create;
-  zend_do_inheritance(google_protobuf_Timestamp_ce, message_ce);
-}
-
 /* google/protobuf/wrappers.proto */
 
 zend_class_entry* GPBMetadata_Google_Protobuf_Wrappers_ce;
@@ -3226,6 +3226,8 @@ static void WellKnownTypes_ModuleInit() {
   google_protobuf_Value_ModuleInit();
   google_protobuf_ListValue_ModuleInit();
   google_protobuf_NullValue_ModuleInit();
+  GPBMetadata_Google_Protobuf_Timestamp_ModuleInit();
+  google_protobuf_Timestamp_ModuleInit();
   GPBMetadata_Google_Protobuf_Type_ModuleInit();
   google_protobuf_Type_ModuleInit();
   google_protobuf_Field_ModuleInit();
@@ -3235,8 +3237,6 @@ static void WellKnownTypes_ModuleInit() {
   google_protobuf_EnumValue_ModuleInit();
   google_protobuf_Option_ModuleInit();
   google_protobuf_Syntax_ModuleInit();
-  GPBMetadata_Google_Protobuf_Timestamp_ModuleInit();
-  google_protobuf_Timestamp_ModuleInit();
   GPBMetadata_Google_Protobuf_Wrappers_ModuleInit();
   google_protobuf_DoubleValue_ModuleInit();
   google_protobuf_FloatValue_ModuleInit();
diff --git a/php/src/Google/Protobuf/Internal/DescriptorPool.php b/php/src/Google/Protobuf/Internal/DescriptorPool.php
index 621e2e6b1733..4f01180d78c3 100644
--- a/php/src/Google/Protobuf/Internal/DescriptorPool.php
+++ b/php/src/Google/Protobuf/Internal/DescriptorPool.php
@@ -20,6 +20,7 @@ class DescriptorPool
     private static $pool;
     // Map from message names to sub-maps, which are maps from field numbers to
     // field descriptors.
+    private $unique_descs = [];
     private $class_to_desc = [];
     private $class_to_enum_desc = [];
     private $proto_to_class = [];
@@ -71,6 +72,8 @@ public function addDescriptor($descriptor)
     {
         $this->proto_to_class[$descriptor->getFullName()] =
             $descriptor->getClass();
+        $this->unique_descs[$descriptor->getFullName()] =
+            $descriptor;
         $this->class_to_desc[$descriptor->getClass()] = $descriptor;
         $this->class_to_desc[$descriptor->getLegacyClass()] = $descriptor;
         $this->class_to_desc[$descriptor->getPreviouslyUnreservedClass()] = $descriptor;
@@ -163,7 +166,7 @@ private function crossLink(Descriptor $desc)
 
     public function finish()
     {
-        foreach ($this->class_to_desc as $klass => $desc) {
+        foreach ($this->unique_descs as $klass => $desc) {
             $this->crossLink($desc);
         }
         unset($desc);
diff --git a/regenerate_stale_files.sh b/regenerate_stale_files.sh
index b7322b5c708a..f5940b824dd7 100755
--- a/regenerate_stale_files.sh
+++ b/regenerate_stale_files.sh
@@ -19,6 +19,7 @@ STALENESS_TESTS=(
   "src/google/protobuf:well_known_types_staleness_test"
   "objectivec:well_known_types_staleness_test"
   "php:test_amalgamation_staleness"
+  "php:proto_staleness_test"
   "ruby/ext/google/protobuf_c:test_amalgamation_staleness"
   "upb/cmake:test_generated_files"
   "upb/reflection:descriptor_upb_proto_staleness_test"
diff --git a/src/google/protobuf/compiler/php/php_generator.cc b/src/google/protobuf/compiler/php/php_generator.cc
index d7f5fa65e0b0..f9b27a8ea5f3 100644
--- a/src/google/protobuf/compiler/php/php_generator.cc
+++ b/src/google/protobuf/compiler/php/php_generator.cc
@@ -541,7 +541,7 @@ std::string BinaryToPhpString(const std::string& src) {
 
 bool GenerateField(const FieldDescriptor* field, io::Printer* printer,
                    const Options& options, std::string* error) {
-  if (field->is_required()) {
+  if (!options.is_descriptor && field->is_required()) {
     *error = absl::StrCat("Can't generate PHP code for required field ",
                           field->full_name(), ".\n");
     return false;
@@ -1155,7 +1155,7 @@ void GenerateMetadataFile(const FileDescriptor* file, const Options& options,
 bool GenerateEnumFile(const FileDescriptor* file, const EnumDescriptor* en,
                       const Options& options,
                       GeneratorContext* generator_context, std::string* error) {
-  if (en->is_closed()) {
+  if (!options.is_descriptor && en->is_closed()) {
     *error = absl::StrCat("Can't generate PHP code for closed enum ",
                           en->full_name(),
                           ".  Please use either proto3 or editions without "


Test patch:
diff --git a/.github/workflows/test_php.yml b/.github/workflows/test_php.yml
index a2d6814aa218..2fae1826c057 100644
--- a/.github/workflows/test_php.yml
+++ b/.github/workflows/test_php.yml
@@ -77,6 +77,15 @@ jobs:
           extra-flags: -e COMPOSER_HOME=/workspace/composer-cache
           command: ${{ matrix.command }}
 
+      - name: Run conformance tests
+        if: ${{ !matrix.continuous-only || inputs.continuous-run }}
+        uses: protocolbuffers/protobuf-ci/bazel@v3
+        with:
+          image: us-docker.pkg.dev/protobuf-build/containers/test/linux/php:6.4.0-${{ matrix.version }}-27cf7b86212020d7e552bc13b1e084abb971da75
+          credentials: ${{ secrets.GAR_SERVICE_ACCOUNT }}
+          bazel-cache: php_linux/${{ matrix.version }}
+          bazel: test //php:conformance_test //php:conformance_test_c --action_env=PATH --test_env=PATH
+
   linux-32bit:
     strategy:
       fail-fast: false   # Don't cancel all jobs if one fails.
@@ -176,6 +185,13 @@ jobs:
             composer test;
             composer test_c'
 
+      - name: Run conformance tests
+        uses: protocolbuffers/protobuf-ci/bazel@v3
+        with:
+          credentials: ${{ secrets.GAR_SERVICE_ACCOUNT }}
+          bazel-cache: php_linux/${{ matrix.version }}
+          bazel: test //php:conformance_test //php:conformance_test_c --action_env=PATH --test_env=PATH
+
   macos:
     strategy:
       fail-fast: false   # Don't cancel all jobs if one fails.
@@ -237,4 +253,4 @@ jobs:
         with:
           credentials: ${{ secrets.GAR_SERVICE_ACCOUNT }}
           bazel-cache: php_macos/${{ matrix.version }}
-          bazel: test //php:conformance_test_c --action_env=PATH --test_env=PATH
+          bazel: test //php:conformance_test //php:conformance_test_c --action_env=PATH --test_env=PATH
diff --git a/upb/cmake/staleness_test_lib.py b/upb/cmake/staleness_test_lib.py
index d9b78a6eb7eb..3b32405b3584 100644
--- a/upb/cmake/staleness_test_lib.py
+++ b/upb/cmake/staleness_test_lib.py
@@ -121,7 +121,7 @@ def _GetMissingAndStaleFiles(file_pairs):
       missing_files.append(pair)
       continue
 
-    with open(pair.generated) as g, open(pair.target) as t:
+    with open(pair.generated, 'rb') as g, open(pair.target, 'rb') as t:
       if g.read() != t.read():
         stale_files.append(pair)