Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions catalog/extensions/redis.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -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:
Expand Down
21 changes: 1 addition & 20 deletions cmd/phpup/main.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
Expand Down
30 changes: 30 additions & 0 deletions cmd/phpup/runtime_catalog.go
Original file line numberDiff line numberDiff line change
@@ -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"}}},
}
}
61 changes: 61 additions & 0 deletions cmd/phpup/runtime_catalog_test.go
Original file line numberDiff line numberDiff line change
@@ -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/<name>.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)
}
}
}
135 changes: 135 additions & 0 deletions docs/superpowers/specs/2026-04-24-redis-6.3.0-bump-design.md
Original file line numberDiff line numberDiff line change
@@ -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 `<min>7.4.0</min>`, 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 `<min>7.4.0</min>`; 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.
13 changes: 5 additions & 8 deletions test/compat/fixtures.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -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'

Expand DownExpand Up@@ -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'

Expand Down
Loading