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
13 changes: 13 additions & 0 deletions ext/dom/Module.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,19 @@
*/
class Module extends ModuleAbstract
{

/**
* php-src ext/dom builds on ext/libxml (libxml2).
*
* Runtime::loadCoreModules() already loads them in this order; declaring it makes the
* constraint checkable instead of remembered (RELEASE-PLAN Phase 2.5).
*
* @return list<string>
*/
public function getExtensionDependencies(): array
{
return ['libxml'];
}
/** php-src ext/dom/php_dom.h DOM_API_VERSION — libxml DOM module version (#15439). */
private const DOM_API_VERSION = '20031129';

Expand Down
13 changes: 13 additions & 0 deletions ext/simplexml/Module.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,19 @@
*/
class Module extends ModuleAbstract
{

/**
* php-src ext/simplexml builds on ext/libxml (libxml2).
*
* Runtime::loadCoreModules() already loads them in this order; declaring it makes the
* constraint checkable instead of remembered (RELEASE-PLAN Phase 2.5).
*
* @return list<string>
*/
public function getExtensionDependencies(): array
{
return ['libxml'];
}
public function init(Runtime $runtime): void
{
parent::init($runtime);
Expand Down
13 changes: 13 additions & 0 deletions ext/xml/Module.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,19 @@
*/
class Module extends ModuleAbstract
{

/**
* php-src ext/xml builds on ext/libxml (libxml2).
*
* Runtime::loadCoreModules() already loads them in this order; declaring it makes the
* constraint checkable instead of remembered (RELEASE-PLAN Phase 2.5).
*
* @return list<string>
*/
public function getExtensionDependencies(): array
{
return ['libxml'];
}
public function init(Runtime $runtime): void
{
parent::init($runtime);
Expand Down
13 changes: 13 additions & 0 deletions ext/xmlreader/Module.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,19 @@
*/
class Module extends ModuleAbstract
{

/**
* php-src ext/xmlreader builds on ext/libxml (libxml2).
*
* Runtime::loadCoreModules() already loads them in this order; declaring it makes the
* constraint checkable instead of remembered (RELEASE-PLAN Phase 2.5).
*
* @return list<string>
*/
public function getExtensionDependencies(): array
{
return ['libxml'];
}
public function init(Runtime $runtime): void
{
parent::init($runtime);
Expand Down
13 changes: 13 additions & 0 deletions ext/xmlwriter/Module.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,19 @@
*/
class Module extends ModuleAbstract
{

/**
* php-src ext/xmlwriter builds on ext/libxml (libxml2).
*
* Runtime::loadCoreModules() already loads them in this order; declaring it makes the
* constraint checkable instead of remembered (RELEASE-PLAN Phase 2.5).
*
* @return list<string>
*/
public function getExtensionDependencies(): array
{
return ['libxml'];
}
public function init(Runtime $runtime): void
{
parent::init($runtime);
Expand Down
13 changes: 13 additions & 0 deletions ext/xsl/Module.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,19 @@
*/
class Module extends ModuleAbstract
{

/**
* php-src ext/xsl builds on ext/libxml (libxml2) and ext/dom.
*
* Runtime::loadCoreModules() already loads them in this order; declaring it makes the
* constraint checkable instead of remembered (RELEASE-PLAN Phase 2.5).
*
* @return list<string>
*/
public function getExtensionDependencies(): array
{
return ['libxml', 'dom'];
}
public function getExtensionVersion(): string
{
if (XsltHostBridge::available()) {
Expand Down
22 changes: 22 additions & 0 deletions lib/Module.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,28 @@ public function getExtensionVersion(): string;
*/
public function getAdditionalExtensionNames(): array;

/**
* Extensions that must be loaded before this one (RELEASE-PLAN Phase 2.5).
*
* Today the load order is a hand-maintained list in Runtime::loadCoreModules(), where the
* ordering constraints are real but implicit — libxml before dom, dom before xsl/simplexml.
* Declaring them here lets the order be derived and checked instead of remembered.
*
* Names are extension names as returned by getExtensionName() (e.g. 'libxml'), lowercase.
*
* @return list<string>
*/
public function getExtensionDependencies(): array;

/**
* Is this extension part of the default build set?
*
* Every extension returns true today, which is exactly the current behaviour: all 76 are loaded
* unconditionally and a script that never touches an extension still pays for it. This is the
* declaration a per-build extension set will select on; nothing consumes it for that yet.
*/
public function isDefaultEnabled(): bool;

/**
* Logical extension versions bundled with this module (e.g. pcre in standard).
*
Expand Down
25 changes: 25 additions & 0 deletions lib/ModuleAbstract.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,31 @@ public function getAdditionalExtensionNames(): array
return [];
}

/**
* No declared dependencies by default (RELEASE-PLAN Phase 2.5).
*
* Overriding this is how an extension states an ordering constraint that is currently only
* implicit in Runtime::loadCoreModules() — e.g. ext/dom depends on libxml. Defaulting to none
* keeps every existing module behaving exactly as before.
*
* @return list<string>
*/
public function getExtensionDependencies(): array
{
return [];
}

/**
* Default-enabled, matching today's behaviour: all 76 extensions load unconditionally.
*
* An extension that should be opt-in overrides this to false. Nothing selects on it yet — the
* declaration comes first so the set can be made selectable without a flag day.
*/
public function isDefaultEnabled(): bool
{
return true;
}

/**
* @return array<string, string>
*/
Expand Down
107 changes: 107 additions & 0 deletions script/check-extension-dependencies.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<?php

declare(strict_types=1);

/**
* Checks declared extension dependencies against the real load order (RELEASE-PLAN Phase 2.5).
*
* Module::getExtensionDependencies() lets an extension state an ordering constraint that is
* currently only implicit in the hand-maintained list in Runtime::loadCoreModules(). A declaration
* nobody verifies is worse than none — it reads as authoritative while being free to drift — so
* this asserts two things:
*
* 1. every declared dependency names an extension that actually exists;
* 2. every declared dependency is loaded BEFORE its dependent in Runtime::loadCoreModules().
*
* It deliberately does not reorder anything. Its job is to prove the declarations describe reality,
* which is the precondition for later deriving the order from them instead of hand-maintaining it.
*
* Usage: php script/check-extension-dependencies.php [--json]
*/

$root = dirname(__DIR__);
$json = in_array('--json', $argv, true);

// The real order, as Runtime loads it.
$runtimeSrc = (string) file_get_contents($root.'/lib/Runtime.php');
$order = [];
if (preg_match('/private function loadCoreModules\(\): void \{(.*?)\n \}/s', $runtimeSrc, $m)
&& preg_match_all('/\$this->load\(new ext\\\\([A-Za-z0-9_]+)\\\\Module\)/', $m[1], $mm)
) {
$order = $mm[1];
}
if ([] === $order) {
fwrite(STDERR, "check-extension-dependencies: could not parse Runtime::loadCoreModules()\n");
exit(2);
}
$position = array_flip($order);

// Declared dependencies, read from source rather than by loading 76 modules (which would need a
// full Runtime and is far more than this check needs).
$declared = [];
foreach (glob($root.'/ext/*/Module.php') ?: [] as $path) {
$ext = basename(dirname($path));
$src = (string) file_get_contents($path);
if (!preg_match('/function getExtensionDependencies\(\): array\s*\{\s*return \[(.*?)\];/s', $src, $dm)) {
continue;
}
if (!preg_match_all("/'([^']+)'/", $dm[1], $names)) {
continue;
}
$declared[$ext] = array_map('strtolower', $names[1]);
}

$problems = [];
foreach ($declared as $ext => $deps) {
foreach ($deps as $dep) {
if (!is_dir($root.'/ext/'.$dep)) {
$problems[] = sprintf('%s declares dependency "%s" but ext/%s does not exist', $ext, $dep, $dep);
continue;
}
if (!isset($position[$dep])) {
$problems[] = sprintf('%s declares dependency "%s" but %s is never loaded', $ext, $dep, $dep);
continue;
}
if (!isset($position[$ext])) {
continue; // the dependent itself is not loaded; not this check's concern
}
if ($position[$dep] > $position[$ext]) {
$problems[] = sprintf(
'%s (load #%d) declares dependency "%s" but %s loads later at #%d',
$ext,
$position[$ext],
$dep,
$dep,
$position[$dep]
);
}
}
}

if ($json) {
echo json_encode([
'status' => [] === $problems ? 'ok' : 'fail',
'declared_count' => count($declared),
'edge_count' => array_sum(array_map('count', $declared)),
'loaded_count' => count($order),
'problems' => $problems,
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES), "\n";
exit([] === $problems ? 0 : 1);
}

$edges = array_sum(array_map('count', $declared));
if ([] === $problems) {
printf(
"check-extension-dependencies: ok — %d extension(s) declare %d dependency edge(s), all satisfied by the load order\n",
count($declared),
$edges
);
exit(0);
}

fwrite(STDERR, "check-extension-dependencies: FAILED\n");
foreach ($problems as $p) {
fwrite(STDERR, ' '.$p."\n");
}
fwrite(STDERR, "\nEither the declaration is wrong or Runtime::loadCoreModules() is ordered wrongly.\n");
exit(1);
Loading