diff --git a/catalog/extensions/redis.yaml b/catalog/extensions/redis.yaml index 53e4814..b9d6773 100644 --- a/catalog/extensions/redis.yaml +++ b/catalog/extensions/redis.yaml @@ -3,18 +3,12 @@ kind: pecl source: pecl_package: redis versions: - - "6.2.0" + - "6.3.0" abi_matrix: php: ["8.1", "8.2", "8.3", "8.4", "8.5"] os: ["linux"] arch: ["x86_64", "aarch64"] ts: ["nts"] -exclude: - # redis 6.2.0 references ext/standard/php_smart_string.h which was removed - # in PHP 8.5; compilation fails. Upstream redis 6.3.0 ships the fix, but a - # version bump is its own single-purpose slice per the version-expansion - # spec non-goals. Until that slice lands, redis is unavailable on 8.5. - - { php: "8.5" } runtime_deps: linux: [] ini: diff --git a/cmd/phpup/main.go b/cmd/phpup/main.go index 622b110..5662ecb 100644 --- a/cmd/phpup/main.go +++ b/cmd/phpup/main.go @@ -217,26 +217,7 @@ func main() { p.PHPVersion: {BundledExtensions: bundled}, }, }, - Extensions: map[string]*catalog.ExtensionSpec{ - "redis": {Name: "redis", Kind: catalog.ExtensionKindPECL, Versions: []string{"6.2.0"}}, - "xdebug": {Name: "xdebug", Kind: catalog.ExtensionKindPECL, Versions: []string{"3.5.1"}, Ini: []string{"zend_extension=xdebug"}}, - "pcov": {Name: "pcov", Kind: catalog.ExtensionKindPECL, Versions: []string{"1.0.12"}, Ini: []string{"extension=pcov"}}, - "apcu": {Name: "apcu", Kind: catalog.ExtensionKindPECL, Versions: []string{"5.1.28"}}, - "igbinary": {Name: "igbinary", Kind: catalog.ExtensionKindPECL, Versions: []string{"3.2.16"}}, - "msgpack": {Name: "msgpack", Kind: catalog.ExtensionKindPECL, Versions: []string{"3.0.0"}}, - "uuid": {Name: "uuid", Kind: catalog.ExtensionKindPECL, Versions: []string{"1.3.0"}, RuntimeDeps: map[string][]string{"linux": {"libuuid1"}}}, - "ssh2": {Name: "ssh2", Kind: catalog.ExtensionKindPECL, Versions: []string{"1.5.0"}, RuntimeDeps: map[string][]string{"linux": {"libssh2-1"}}}, - "yaml": {Name: "yaml", Kind: catalog.ExtensionKindPECL, Versions: []string{"2.3.0"}, RuntimeDeps: map[string][]string{"linux": {"libyaml-0-2"}}}, - "memcached": {Name: "memcached", Kind: catalog.ExtensionKindPECL, Versions: []string{"3.4.0"}, RuntimeDeps: map[string][]string{"linux": {"libmemcached11", "libsasl2-2"}}}, - "amqp": {Name: "amqp", Kind: catalog.ExtensionKindPECL, Versions: []string{"2.2.0"}, RuntimeDeps: map[string][]string{"linux": {"librabbitmq4"}}}, - "event": {Name: "event", Kind: catalog.ExtensionKindPECL, Versions: []string{"3.1.4"}, RuntimeDeps: map[string][]string{"linux": {"libevent-2.1-7", "libevent-extra-2.1-7", "libevent-openssl-2.1-7"}}}, - "rdkafka": {Name: "rdkafka", Kind: catalog.ExtensionKindPECL, Versions: []string{"6.0.5"}, RuntimeDeps: map[string][]string{"linux": {"librdkafka1"}}}, - "protobuf": {Name: "protobuf", Kind: catalog.ExtensionKindPECL, Versions: []string{"5.34.1"}}, - "imagick": {Name: "imagick", Kind: catalog.ExtensionKindPECL, Versions: []string{"3.8.1"}, RuntimeDeps: map[string][]string{"linux": {"libfontconfig1", "libx11-6", "libxext6", "liblcms2-2", "liblqr-1-0", "libfftw3-double3", "libbz2-1.0"}}}, - "mongodb": {Name: "mongodb", Kind: catalog.ExtensionKindPECL, Versions: []string{"2.2.1"}, RuntimeDeps: map[string][]string{"linux": {"libssl3", "libsasl2-2"}}}, - "swoole": {Name: "swoole", Kind: catalog.ExtensionKindPECL, Versions: []string{"6.2.0"}, RuntimeDeps: map[string][]string{"linux": {"libssl3", "libcurl4"}}}, - "grpc": {Name: "grpc", Kind: catalog.ExtensionKindPECL, Versions: []string{"1.80.0"}, RuntimeDeps: map[string][]string{"linux": {"libssl3"}}}, - }, + Extensions: runtimeExtensionSpecs(), } // 4. Resolve plan against lockfile diff --git a/cmd/phpup/runtime_catalog.go b/cmd/phpup/runtime_catalog.go new file mode 100644 index 0000000..0025f3b --- /dev/null +++ b/cmd/phpup/runtime_catalog.go @@ -0,0 +1,30 @@ +package main + +import "github.com/buildrush/setup-php/internal/catalog" + +// runtimeExtensionSpecs returns the extension specs used by `phpup install` +// at runtime. These MUST stay in sync with catalog/extensions/*.yaml — see +// TestRuntimeExtensionSpecsMatchYAML in runtime_catalog_test.go for the +// drift guard. +func runtimeExtensionSpecs() map[string]*catalog.ExtensionSpec { + return map[string]*catalog.ExtensionSpec{ + "redis": {Name: "redis", Kind: catalog.ExtensionKindPECL, Versions: []string{"6.3.0"}}, + "xdebug": {Name: "xdebug", Kind: catalog.ExtensionKindPECL, Versions: []string{"3.5.1"}, Ini: []string{"zend_extension=xdebug"}}, + "pcov": {Name: "pcov", Kind: catalog.ExtensionKindPECL, Versions: []string{"1.0.12"}, Ini: []string{"extension=pcov"}}, + "apcu": {Name: "apcu", Kind: catalog.ExtensionKindPECL, Versions: []string{"5.1.28"}}, + "igbinary": {Name: "igbinary", Kind: catalog.ExtensionKindPECL, Versions: []string{"3.2.16"}}, + "msgpack": {Name: "msgpack", Kind: catalog.ExtensionKindPECL, Versions: []string{"3.0.0"}}, + "uuid": {Name: "uuid", Kind: catalog.ExtensionKindPECL, Versions: []string{"1.3.0"}, RuntimeDeps: map[string][]string{"linux": {"libuuid1"}}}, + "ssh2": {Name: "ssh2", Kind: catalog.ExtensionKindPECL, Versions: []string{"1.5.0"}, RuntimeDeps: map[string][]string{"linux": {"libssh2-1"}}}, + "yaml": {Name: "yaml", Kind: catalog.ExtensionKindPECL, Versions: []string{"2.3.0"}, RuntimeDeps: map[string][]string{"linux": {"libyaml-0-2"}}}, + "memcached": {Name: "memcached", Kind: catalog.ExtensionKindPECL, Versions: []string{"3.4.0"}, RuntimeDeps: map[string][]string{"linux": {"libmemcached11", "libsasl2-2"}}}, + "amqp": {Name: "amqp", Kind: catalog.ExtensionKindPECL, Versions: []string{"2.2.0"}, RuntimeDeps: map[string][]string{"linux": {"librabbitmq4"}}}, + "event": {Name: "event", Kind: catalog.ExtensionKindPECL, Versions: []string{"3.1.4"}, RuntimeDeps: map[string][]string{"linux": {"libevent-2.1-7", "libevent-extra-2.1-7", "libevent-openssl-2.1-7"}}}, + "rdkafka": {Name: "rdkafka", Kind: catalog.ExtensionKindPECL, Versions: []string{"6.0.5"}, RuntimeDeps: map[string][]string{"linux": {"librdkafka1"}}}, + "protobuf": {Name: "protobuf", Kind: catalog.ExtensionKindPECL, Versions: []string{"5.34.1"}}, + "imagick": {Name: "imagick", Kind: catalog.ExtensionKindPECL, Versions: []string{"3.8.1"}, RuntimeDeps: map[string][]string{"linux": {"libfontconfig1", "libx11-6", "libxext6", "liblcms2-2", "liblqr-1-0", "libfftw3-double3", "libbz2-1.0"}}}, + "mongodb": {Name: "mongodb", Kind: catalog.ExtensionKindPECL, Versions: []string{"2.2.1"}, RuntimeDeps: map[string][]string{"linux": {"libssl3", "libsasl2-2"}}}, + "swoole": {Name: "swoole", Kind: catalog.ExtensionKindPECL, Versions: []string{"6.2.0"}, RuntimeDeps: map[string][]string{"linux": {"libssl3", "libcurl4"}}}, + "grpc": {Name: "grpc", Kind: catalog.ExtensionKindPECL, Versions: []string{"1.80.0"}, RuntimeDeps: map[string][]string{"linux": {"libssl3"}}}, + } +} diff --git a/cmd/phpup/runtime_catalog_test.go b/cmd/phpup/runtime_catalog_test.go new file mode 100644 index 0000000..ce00100 --- /dev/null +++ b/cmd/phpup/runtime_catalog_test.go @@ -0,0 +1,61 @@ +package main + +import ( + "fmt" + "path/filepath" + "runtime" + "slices" + "sort" + "testing" + + "github.com/buildrush/setup-php/internal/catalog" +) + +// TestRuntimeExtensionSpecsMatchYAML guards against drift between the inline +// extension specs in runtimeExtensionSpecs() and the authoritative YAML files +// at catalog/extensions/.yaml. Mirrors the pattern used by +// TestCatalogBundledMatchesCompat in internal/catalog/catalog_compat_test.go. +// +// Only version pins and kind are checked; RuntimeDeps and Ini intentionally +// differ in shape between the runtime catalog and the YAML. +func TestRuntimeExtensionSpecsMatchYAML(t *testing.T) { + // Resolve the catalog/extensions directory relative to this test file. + _, thisFile, _, ok := runtime.Caller(0) + if !ok { + t.Fatal("runtime.Caller failed") + } + extDir := filepath.Join(filepath.Dir(thisFile), "..", "..", "catalog", "extensions") + + specs := runtimeExtensionSpecs() + + // Sort keys for deterministic output. + names := make([]string, 0, len(specs)) + for name := range specs { + names = append(names, name) + } + sort.Strings(names) + + for _, name := range names { + inline := specs[name] + yamlPath := filepath.Join(extDir, fmt.Sprintf("%s.yaml", name)) + yamlSpec, err := catalog.LoadExtensionSpec(yamlPath) + if err != nil { + t.Errorf("extension %s: LoadExtensionSpec(%s): %v", name, yamlPath, err) + continue + } + + // Check kind. + if inline.Kind != yamlSpec.Kind { + t.Errorf("extension %s: kind mismatch: inline=%q yaml=%q", name, inline.Kind, yamlSpec.Kind) + } + + // Check versions. + inlineVersions := append([]string(nil), inline.Versions...) + yamlVersions := append([]string(nil), yamlSpec.Versions...) + sort.Strings(inlineVersions) + sort.Strings(yamlVersions) + if !slices.Equal(inlineVersions, yamlVersions) { + t.Errorf("extension %s: versions mismatch: inline=%v yaml=%v", name, inlineVersions, yamlVersions) + } + } +} diff --git a/docs/superpowers/specs/2026-04-24-redis-6.3.0-bump-design.md b/docs/superpowers/specs/2026-04-24-redis-6.3.0-bump-design.md new file mode 100644 index 0000000..493a7c1 --- /dev/null +++ b/docs/superpowers/specs/2026-04-24-redis-6.3.0-bump-design.md @@ -0,0 +1,135 @@ +# redis PECL 6.2.0 → 6.3.0 bump (PHP 8.5 restoration) + +**Issue:** [#38](https://github.com/buildrush/setup-php/issues/38) +**Status:** Design approved 2026-04-24 +**Depends on:** PR #37 (Phase-2 version expansion) — merged + +## 1. Goal & scope + +Restore redis on PHP 8.5 by bumping the catalog pin from `6.2.0` to `6.3.0`. 6.3.0 is the upstream release that drops the reference to `ext/standard/php_smart_string.h`, an internal PHP header removed in PHP 8.5. redis 6.2.0 still includes that header and fails to compile against 8.5 with: + +``` +/tmp/ext-src/common.h:14:10: fatal error: ext/standard/php_smart_string.h: No such file or directory +``` + +This is a single-purpose slice per the version-expansion spec (`docs/superpowers/specs/2026-04-21-phase2-version-expansion-design.md` §2 non-goals): "Bumping PECL extension versions ... is its own single-purpose slice." + +### In scope + +- Catalog pin bump and removal of the `exclude: [{ php: "8.5" }]` block in `catalog/extensions/redis.yaml`. +- Compat-harness fixture cleanup: revert the apcu substitutions in three 8.5 input-shape fixtures back to redis, and restore redis to the top14 8.5 jumbo fixture. +- `bundles.lock` refresh: PR self-publish CI rebuilds all 10 redis bundles (5 PHP ABIs × 2 arches) and commits the lockfile back to the PR head. + +### Out of scope + +- igbinary 3.2.16 → 3.2.17 bump (tracked in issue #43 — blocked on a stable igbinary release; only 3.2.17RC1 exists at time of writing). +- Any other PECL extension version bumps. +- Runtime Go code changes, builder script changes, workflow YAML changes. +- Backfilling historical design docs or the product-vision example text that still reference `redis 6.2.0`. + +## 2. Changes + +### 2.1 `catalog/extensions/redis.yaml` + +- Change `versions: ["6.2.0"]` to `versions: ["6.3.0"]`. +- Remove the `exclude:` block and its explanatory comment: + +```yaml +exclude: + # redis 6.2.0 references ext/standard/php_smart_string.h which was removed + # in PHP 8.5; compilation fails. Upstream redis 6.3.0 ships the fix, but a + # version bump is its own single-purpose slice per the version-expansion + # spec non-goals. Until that slice lands, redis is unavailable on 8.5. + - { php: "8.5" } +``` + +After the change, the file's `abi_matrix.php` stays `["8.1", "8.2", "8.3", "8.4", "8.5"]` and there are no excludes — redis 6.3.0 is expected to compile cleanly across all supported PHP ABIs. + +### 2.2 `test/compat/fixtures.yaml` + +Three fixtures in the 8.5 input-shape block were substituted from redis to apcu in PR #37 as a temporary workaround. Revert them, and drop the accompanying comment. + +- `multi-ext-85`: `extensions: 'apcu, xdebug'` → `'redis, xdebug'` +- `none-reset-85`: `extensions: 'none, apcu'` → `'none, redis'` +- `single-ext-85`: `extensions: 'apcu'` → `'redis'` +- Remove the 3-line "# redis is excluded on 8.5 (see catalog/extensions/redis.yaml) ..." comment that introduces that fixture block. + +The `multi-ext-top14-arm64-85` fixture (line ~422) currently omits `redis` from its extension list. Restore it: + +- Prepend `redis, ` to its `extensions:` field. +- Replace the preceding "# 8.5 omits redis + igbinary (php_smart_string.h upstream issue; see #38, #43)." comment with "# 8.5 omits igbinary (php_smart_string.h upstream issue; see #43)." + +Rationale for reverting to redis rather than keeping apcu + adding new redis-85 fixtures: + +- PR #37's comment explicitly framed the apcu swap as a substitution ("substitute apcu to cover the same input-shape axes"), not a permanent coverage expansion. +- apcu retains coverage on PHP 8.5 via the top14 jumbo fixture (which lists apcu). +- Keeping apcu would add three new redis-85 fixtures instead of reverting three, growing CI cost without net coverage gain. + +### 2.3 `bundles.lock` + +The spec_hash for every `ext:redis:*` entry changes when the pinned version changes. The PR self-publish flow (same as PR #37) handles this: + +1. Planner detects 10 redis cells with drifted spec_hashes. +2. `build-extension.yml` builds redis 6.3.0 for each cell and pushes the bundle to GHCR. +3. `lockfile-update` job commits the 10 refreshed `bundles.lock` entries back to the PR head. +4. Compat harness then runs against the freshly published bundles. + +Existing lockfile shape: `ext:redis:6.2.0:{8.1,8.2,8.3,8.4}:linux:{x86_64,aarch64}:nts` (8 entries, no 8.5). +After the slice: `ext:redis:6.3.0:{8.1,8.2,8.3,8.4,8.5}:linux:{x86_64,aarch64}:nts` (10 entries). + +### 2.4 Files deliberately not touched + +- `catalog/extensions/igbinary.yaml` — comment says "same class of upstream incompatibility as redis 6.2.0"; that is still accurate historical framing, and #43 is the slice that will revise it. +- `docs/product-vision.md` — example snippets mention `redis:6.2.0`; vision doc is illustrative, not authoritative, and churn here has no functional impact. +- `docs/superpowers/specs/*.md` — frozen-in-time design docs. Do not backfill. + +## 3. Verification + +Per project CLAUDE.md "CI failures: reproduce locally first" rule — we do not iterate by push-and-observe. + +### 3.1 Pre-push local checks + +1. `make check` — Go/JS lint, unit tests, formatting (seconds to minutes). +2. `make ci-cell OS=jammy ARCH=x86_64 PHP=8.5` — the riskiest cell (net-new build); ~20 min. Confirms redis 6.3.0 compiles against PHP 8.5 and the fixtures pass inside bare-ubuntu docker. +3. Optionally `make ci-cell OS=jammy ARCH=x86_64 PHP=8.1` — regression check for the oldest supported PHP, since 6.3.0 could in principle drop older-version support. PECL `package.xml` declares `7.4.0`, so this is a low-probability check; if 8.5 passes locally, defer the remaining 8 cells to PR CI. + +### 3.2 PR CI expectations + +- Planner emits 10 redis rebuild cells. No other extensions share spec_hash inputs with redis, so no ripple. +- `build-extension.yml` succeeds for all 10 cells; bundles pushed to GHCR. +- `lockfile-update` commits refreshed `bundles.lock` to the PR head. +- Compat harness green for every fixture containing redis: + - 8.1–8.4 input-shape fixtures (`multi-ext`, `none-reset`, `single-ext`) — no change, but rebuilt under new spec_hash. + - 8.5 input-shape fixtures (`multi-ext-85`, `none-reset-85`, `single-ext-85`) — newly redis-bearing. + - Top14 multi-ext variants (x86_64 and arm64, all versions including -85). + - Jumbo fixtures containing redis. + +### 3.3 Release-notes review + +Skim the redis PECL 6.2.0 → 6.3.0 changelog (upstream GitHub release notes) for: + +- BC breaks or removed APIs. +- Default-value changes. +- Deprecations users should know about. + +Surface any notable items in the PR body so consumers upgrading the action see them. + +## 4. Risk & rollback + +### 4.1 Risks + +- **6.3.0 introduces a behavior change that breaks consumer code.** Mitigation: minor version bump per semver intent; changelog reviewed upstream; compat harness exercises the existing redis-bearing fixtures. +- **6.3.0 fails to build against an older PHP ABI (8.1–8.4).** Mitigation: PECL metadata shows `7.4.0`; all supported ABIs are above that floor. Local `make ci-cell` covers the riskiest cell pre-push. +- **Spec_hash ripple forces large rebuild.** Expected: 10 redis cells rebuild. Not a regression — it's the intended outcome of a version bump. + +### 4.2 Rollback + +Revert the PR. Old `ext:redis:6.2.0:*` bundles remain content-addressed on GHCR; lockfile entries revert with the PR revert. PHP 8.5 loses redis again (pre-slice state). + +## 5. Success criteria + +- `catalog/extensions/redis.yaml` pins version `6.3.0` with no `exclude:` block. +- `bundles.lock` contains `ext:redis:6.3.0:*` entries for all 10 (ABI × arch) combinations and no `ext:redis:6.2.0:*` entries. +- Compat harness passes with redis present in every fixture that had it pre-PR-#37, plus redis restored in all 8.5 redis-bearing fixtures. +- `make check` and PR CI are green. +- PR body calls out any notable 6.2.0 → 6.3.0 upstream behavior changes for consumers. diff --git a/test/compat/fixtures.yaml b/test/compat/fixtures.yaml index 1300c55..2dba903 100644 --- a/test/compat/fixtures.yaml +++ b/test/compat/fixtures.yaml @@ -85,24 +85,21 @@ fixtures: coverage: 'none' ini-file: 'development' - # redis is excluded on 8.5 (see catalog/extensions/redis.yaml); these three - # fixtures substitute apcu to cover the same input-shape axes (multi-ext, - # none-reset, single-ext) against an extension that does build on 8.5. - name: multi-ext-85 php-version: '8.5' - extensions: 'apcu, xdebug' + extensions: 'redis, xdebug' ini-values: '' coverage: 'none' - name: none-reset-85 php-version: '8.5' - extensions: 'none, apcu' + extensions: 'none, redis' ini-values: '' coverage: 'none' - name: single-ext-85 php-version: '8.5' - extensions: 'apcu' + extensions: 'redis' ini-values: '' coverage: 'none' @@ -416,11 +413,11 @@ fixtures: ini-values: '' coverage: 'none' - # 8.5 omits redis + igbinary (php_smart_string.h upstream issue; see #38, #43). + # 8.5 omits igbinary (php_smart_string.h upstream issue; see #43). - name: multi-ext-top14-arm64-85 php-version: '8.5' arch: 'aarch64' - extensions: 'xdebug, pcov, apcu, msgpack, uuid, ssh2, yaml, memcached, amqp, event, rdkafka, protobuf' + extensions: 'redis, xdebug, pcov, apcu, msgpack, uuid, ssh2, yaml, memcached, amqp, event, rdkafka, protobuf' ini-values: '' coverage: 'none'