Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
179 changes: 148 additions & 31 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ on:
branches: [main, development]

jobs:
test-php-matrix:
test-php-matrix-linux:
name: Test PHP ${{ matrix.php_version }} on Linux
timeout-minutes: 60
runs-on: ubuntu-latest
strategy:
Expand All @@ -26,7 +27,6 @@ jobs:

env:
PHPV_ROOT: ${{ github.workspace }}/phpv-root-${{ matrix.php_version }}
PHPV_EXTENSIONS: "opcache,mbstring,curl,pdo_mysql,openssl,intl,bcmath,zip,xml,gd,pdo,zlib,json,phar,filter,dom,fileinfo,session,tokenizer,xmlwriter,simplexml,xmlreader"

steps:
- uses: actions/checkout@v4
Expand All @@ -37,17 +37,20 @@ jobs:
go-version: "1.25"
cache: true

- name: Install system dependencies
uses: awalsh128/cache-apt-pkgs-action@v1
with:
packages: >-
build-essential autoconf automake libtool m4 re2c bison
pkg-config libssl-dev libxml2-dev libsqlite3-dev
libgd-dev libonig-dev libzip-dev libicu-dev
libcurl4-openssl-dev libpng-dev libjpeg-dev
libwebp-dev libxpm-dev libfreetype6-dev
- name: Install system dependencies (apt)
run: |
sudo apt-get update
sudo apt-get install -y \
build-essential autoconf automake libtool m4 re2c bison \
pkg-config libssl-dev libxml2-dev libsqlite3-dev \
libgd-dev libonig-dev libzip-dev libicu-dev \
libcurl4-openssl-dev libpng-dev libjpeg-dev \
libwebp-dev libxpm-dev libfreetype6-dev \
libmariadb-dev gettext
version: 1.0

- name: Set up PKG_CONFIG_PATH
run: |
echo "PKG_CONFIG_PATH=/usr/lib/x86_64-linux-gnu/pkgconfig:/usr/share/pkgconfig:/usr/lib/pkgconfig" >> $GITHUB_ENV

- name: Compute cache key
id: cache-key
Expand All @@ -69,14 +72,14 @@ jobs:
go.sum \
.github/workflows/test.yaml \
| sha256sum | cut -d' ' -f1)
echo "key=phpv-${{ matrix.php_version }}-$HASH" >> $GITHUB_OUTPUT
echo "restore-key=phpv-${{ matrix.php_version }}-" >> $GITHUB_OUTPUT
echo "key=phpv-caches-${{ matrix.php_version }}-$HASH" >> $GITHUB_OUTPUT
echo "restore-key=phpv-caches-${{ matrix.php_version }}-" >> $GITHUB_OUTPUT

- name: Cache phpv build artifacts
- name: Cache downloaded archives
id: cache-phpv
uses: actions/cache@v4
with:
path: ${{ env.PHPV_ROOT }}
path: ${{ env.PHPV_ROOT }}/caches
key: ${{ steps.cache-key.outputs.key }}
restore-keys: |
${{ steps.cache-key.outputs.restore-key }}
Expand All @@ -87,28 +90,142 @@ jobs:
go build -o $PHPV_ROOT/bin/phpv ./app/phpv.go
echo "$PHPV_ROOT/bin" >> $GITHUB_PATH

- name: Get installed PHP version
id: php-state
- name: Install PHP ${{ matrix.php_version }}
run: |
PATTERN="$PHPV_ROOT/packages/php"
INSTALLED=$(ls -d $PATTERN/*/bin/php 2>/dev/null || echo "")
if [ -n "$INSTALLED" ]; then
echo "cached=true" >> $GITHUB_OUTPUT
else
echo "cached=false" >> $GITHUB_OUTPUT
fi
eval "$($PHPV_ROOT/bin/phpv init bash)"
phpv install ${{ matrix.php_version }}

- name: Install PHP ${{ matrix.php_version }}
if: steps.php-state.outputs.cached == 'false'
- name: Verify PHP ${{ matrix.php_version }}
run: |
eval "$($PHPV_ROOT/bin/phpv init bash)"
phpv install ${{ matrix.php_version }} --ext $PHPV_EXTENSIONS -v
phpv use ${{ matrix.php_version }}

PHP_PATH=$(which php)
echo "PHP binary: $PHP_PATH"

if [[ "$PHP_PATH" != "$PHPV_ROOT"* ]]; then
echo "Error: PHP is not running from the isolated root!"
exit 1
fi

php -v
echo ""
echo "=== Installed extensions ==="
php -m | sort
echo ""

MISSING=""
for ext in dom gd intl mbstring pdo_mysql zip curl json phar opcache openssl simplexml xml xmlreader xmlwriter tokenizer session fileinfo filter bcmath pdo zlib; do
if php -m | grep -qi -w "$ext"; then
echo "✓ $ext"
elif [ "$ext" = "opcache" ] && \
php -r '
if (extension_loaded("Zend OPcache")) exit(0);
$f = ini_get("extension_dir") . "/opcache.so";
exit(file_exists($f) ? 0 : 1);
' 2>/dev/null; then
echo "✓ $ext (shared)"
else
echo "✗ $ext MISSING"
MISSING="$MISSING $ext"
fi
done

if [ -n "$MISSING" ]; then
echo ""
echo "FAILED: Missing extensions:$MISSING"
exit 1
fi

echo ""
echo "All critical extensions present ✓"

test-php-matrix-macos:
name: Test PHP ${{ matrix.php_version }} on macOS
timeout-minutes: 120
runs-on: macos-14
strategy:
fail-fast: false
matrix:
php_version:
- "7.4"
- "8.5"
continue-on-error: true

- name: Rebuild PHP ${{ matrix.php_version }}
if: steps.php-state.outputs.cached == 'true'
env:
PHPV_ROOT: ${{ github.workspace }}/phpv-root-${{ matrix.php_version }}

steps:
- uses: actions/checkout@v4

- name: Install Go
uses: actions/setup-go@v5
with:
go-version: "1.25"
cache: true

- name: Install Homebrew dependencies
run: |
brew install \
autoconf automake libtool m4 re2c bison \
pkg-config openssl@3 libxml2 sqlite3 \
oniguruma icu4c libpng jpeg-turbo \
freetype libzip mariadb-connector-c \
gettext webp libxpm libiconv

- name: Set up environment variables
run: |
echo "PKG_CONFIG_PATH=/opt/homebrew/opt/openssl@3/lib/pkgconfig:/opt/homebrew/opt/libxml2/lib/pkgconfig:/opt/homebrew/opt/sqlite3/lib/pkgconfig:/opt/homebrew/opt/oniguruma/lib/pkgconfig:/opt/homebrew/opt/icu4c/lib/pkgconfig:/opt/homebrew/opt/libpng/lib/pkgconfig:/opt/homebrew/opt/jpeg-turbo/lib/pkgconfig:/opt/homebrew/opt/freetype/lib/pkgconfig:/opt/homebrew/opt/libzip/lib/pkgconfig:/opt/homebrew/opt/mariadb-connector-c/lib/pkgconfig:/opt/homebrew/opt/webp/lib/pkgconfig:/opt/homebrew/opt/libxpm/lib/pkgconfig:/opt/homebrew/opt/zlib/lib/pkgconfig:/opt/homebrew/opt/bison/lib/pkgconfig:/opt/homebrew/opt/libiconv/lib/pkgconfig" >> $GITHUB_ENV
echo "PATH=/opt/homebrew/opt/bison/bin:$PATH" >> $GITHUB_ENV

- name: Build phpv binary
run: |
mkdir -p $PHPV_ROOT/bin
go build -o $PHPV_ROOT/bin/phpv ./app/phpv.go
echo "$PHPV_ROOT/bin" >> $GITHUB_PATH

- name: Compute cache key
id: cache-key
run: |
HASH=$(sha256sum \
internal/repository/memory/graph.go \
internal/repository/memory/compiler.go \
internal/repository/memory/registry.go \
internal/repository/memory/patcher.go \
internal/repository/disk/silo.go \
internal/repository/disk/forge.go \
internal/repository/disk/config.go \
internal/repository/disk/doctor.go \
internal/repository/disk/update.go \
assembler/service.go \
bundle/service.go \
app/phpv.go \
go.mod \
go.sum \
.github/workflows/test.yaml \
| sha256sum | cut -d' ' -f1)
echo "key=phpv-caches-macos-${{ matrix.php_version }}-$HASH" >> $GITHUB_OUTPUT
echo "restore-key=phpv-caches-macos-${{ matrix.php_version }}-" >> $GITHUB_OUTPUT

- name: Cache downloaded archives
id: cache-phpv
uses: actions/cache@v4
with:
path: ${{ env.PHPV_ROOT }}/caches
key: ${{ steps.cache-key.outputs.key }}
restore-keys: |
${{ steps.cache-key.outputs.restore-key }}

- name: Build phpv binary
run: |
mkdir -p $PHPV_ROOT/bin
go build -o $PHPV_ROOT/bin/phpv ./app/phpv.go
echo "$PHPV_ROOT/bin" >> $GITHUB_PATH

- name: Install PHP ${{ matrix.php_version }}
run: |
eval "$($PHPV_ROOT/bin/phpv init bash)"
phpv install ${{ matrix.php_version }} --ext $PHPV_EXTENSIONS --force -v
phpv install ${{ matrix.php_version }}

- name: Verify PHP ${{ matrix.php_version }}
run: |
Expand Down
41 changes: 32 additions & 9 deletions assembler/resolve_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package assembler
import (
"os"
"path/filepath"
"runtime"
"sort"
"strings"

Expand All @@ -15,16 +16,18 @@ import (
// to inject dependency paths (e.g., curl's --with-openssl={{dep:openssl}}).
//
// The placeholder is resolved for every dependency that appears in the build
// plan, even if its install directory does not exist yet. The assembler builds
// dependencies in dependency order, so the directory will be created before any
// dependent package is configured. This keeps fresh-state builds from passing a
// literal {{dep:NAME}} string to ./configure.
// plan, but only if the dependency was actually built locally (not when
// satisfied by a system package). If the local install directory doesn't exist,
// the placeholder is removed entirely, letting the package's configure script
// find the system version via pkg-config or default search paths.
func (s *Service) resolveDepPlaceholders(flags []string, deps []domain.Dependency) []string {
result := make([]string, len(flags))
for i, flag := range flags {
result := make([]string, 0, len(flags))
for _, flag := range flags {
resolved := flag
skip := false
for _, dep := range deps {
placeholder := "{{dep:" + dep.Name + "}}"
if !strings.Contains(flag, placeholder) {
if !strings.Contains(resolved, placeholder) {
continue
}
ver := dep.Version
Expand All @@ -35,9 +38,17 @@ func (s *Service) resolveDepPlaceholders(flags []string, deps []domain.Dependenc
continue
}
prefix := s.silo.PackagePrefix(dep.Name, ver)
flag = strings.ReplaceAll(flag, placeholder, prefix)
// Check if the dependency was actually built locally
if fi, err := os.Stat(prefix); err != nil || !fi.IsDir() {
// Dependency was satisfied by system package, skip this flag
skip = true
break
}
resolved = strings.ReplaceAll(resolved, placeholder, prefix)
}
if !skip {
result = append(result, resolved)
}
result[i] = flag
}
return result
}
Expand Down Expand Up @@ -128,6 +139,18 @@ func (s *Service) resolveDependencyFlags(name, phpVersion string, flags []string
result = append(result, "--with-icu-dir="+path)
}

case flag == "--with-iconv":
if runtime.GOOS == "darwin" {
brewIconv := "/opt/homebrew/opt/libiconv"
if fi, err := os.Stat(filepath.Join(brewIconv, "include", "iconv.h")); err == nil && !fi.IsDir() {
result = append(result, "--with-iconv="+brewIconv)
} else {
result = append(result, flag)
}
} else {
result = append(result, flag)
}

default:
result = append(result, flag)
}
Expand Down
Loading
Loading