Files
computer-playbook/utils/install/php.py
T
kevinveenbirkenbachandClaude Opus 5 dca9db9283 fix(test): resolve the PHP vendor tree against a declared floor
ebb835d541 raised phpunit/phpunit from ^11.0 to ^13.3. PHPUnit 13 requires
PHP >=8.4.1 and ubuntu-latest ships 8.3.6, so composer install could not
resolve and test-unit-php failed on every CI run:

    - phpunit/phpunit[13.3.0, ..., 13.3.2] require php >=8.4.1
      -> your php version (8.3.6) does not satisfy that requirement.

Nothing in this repository installs a particular PHP. ensure_php_toolchain
takes whatever the distro php package is, so the vendor tree was resolved
against an ambient interpreter that no file named. A dependency floor above
that interpreter is then only discovered by the resolver, on the machine that
happens to have the oldest PHP.

Declare the floor and resolve against it. config.platform.php binds composer
everywhere, including Dependabot; require states the same for readers; the
phpunit constraint follows it. Packagist floors: 11 -> >=8.2, 12 -> >=8.3,
13 -> >=8.4.1.

Verified in the dev container: the pinned resolution downgrades phpunit
13.3.2 -> 12.5.34 and the suite passes unchanged, 26 tests, 56 assertions.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 02:15:56 +02:00

68 lines
2.2 KiB
Python

"""PHP toolchain provisioning for the PHP unit suite."""
from __future__ import annotations
import subprocess
from utils import PROJECT_ROOT
from utils.install.primitives import log
from utils.install.system_pkg import (
ensure_command_present,
ensure_php_extension_present,
)
_PHPUNIT = PROJECT_ROOT / "vendor" / "bin" / "phpunit"
_PHPUNIT_EXTENSIONS = ("dom", "mbstring", "xmlwriter")
def ensure_php_toolchain() -> None:
"""Install the PHP interpreter, Composer and the extensions PHPUnit needs.
``phpunit/phpunit`` declares ``_PHPUNIT_EXTENSIONS`` as platform
requirements and Composer refuses to resolve the vendor tree without them,
so they are part of the toolchain rather than of the suite consuming it.
A distro ships them as packages separate from the interpreter, which is why
a present ``php`` binary does not imply they are there.
Composer needs an archive extractor for its dist downloads. Without one it
silently falls back to ``--prefer-source`` and mirror-clones every
dependency, which blows its 300 s process timeout on ``phpunit.git``. Arch
ships no zip extension and no ``php-zip`` package, so the extractor has to
be the ``unzip`` binary rather than the extension.
Raises:
RuntimeError: php, composer, unzip or one of the extensions is still
absent.
"""
ensure_command_present("php")
ensure_command_present("composer")
ensure_command_present("unzip")
for extension in _PHPUNIT_EXTENSIONS:
ensure_php_extension_present(extension)
def ensure_php_present() -> None:
"""Install the PHP toolchain and the Composer vendor tree.
Raises:
RuntimeError: php, composer or phpunit is still absent afterwards.
subprocess.CalledProcessError: ``composer install`` failed.
"""
ensure_php_toolchain()
if not _PHPUNIT.is_file():
log("phpunit missing; running composer install.")
subprocess.run(
["composer", "install", "--no-interaction", "--no-progress"],
cwd=PROJECT_ROOT,
check=True,
)
if not _PHPUNIT.is_file():
raise RuntimeError(f"{_PHPUNIT} missing after composer install")
if __name__ == "__main__":
ensure_php_present()