Skip to content
Open
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
75 changes: 63 additions & 12 deletions drupal-dev/composer-plugin/src/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,31 @@ public function activate(Composer $composer, IOInterface $io): void
$this->composer = $composer;
$this->io = $io;

// Seed installer-paths from core's composer.json into the root package
// extra so the fallback in getInstallPath() works before the merge
// plugin has had a chance to merge core's extra.
$rootExtra = $composer->getPackage()->getExtra();
if (empty($rootExtra['installer-paths'])) {
$coreFile = getcwd() . '/composer.json';
if (file_exists($coreFile)) {
$coreConfig = json_decode(file_get_contents($coreFile), true);
if (!empty($coreConfig['extra']['installer-paths'])) {
$rootExtra['installer-paths'] = $coreConfig['extra']['installer-paths'];
$composer->getPackage()->setExtra($rootExtra);
}
// Seed installer-paths and config.platform from core's composer.json
// into the root package so the fallback in getInstallPath() works
// before the merge plugin has had a chance to merge core's extra, and
// so PHPStan sees the correct platform PHP version.
//
// config.platform must also be written to the root composer file on
// disk because PHPStan reads it directly from that file (via the
// $COMPOSER env var) in a separate process — the in-memory Config
// merge is not visible to it.
$coreFile = getcwd() . '/composer.json';
if (file_exists($coreFile)) {
$coreConfig = json_decode(file_get_contents($coreFile), true);

$rootExtra = $composer->getPackage()->getExtra();
if (empty($rootExtra['installer-paths']) && !empty($coreConfig['extra']['installer-paths'])) {
$rootExtra['installer-paths'] = $coreConfig['extra']['installer-paths'];
$composer->getPackage()->setExtra($rootExtra);
}

if (!empty($coreConfig['config']['platform'])) {
$composer->getConfig()->merge(
['config' => ['platform' => $coreConfig['config']['platform']]],
$coreFile
);
$this->syncPlatformToRootFile($coreConfig['config']['platform']);
}
}

Expand Down Expand Up @@ -120,6 +133,44 @@ private function registerInstaller(): void
$this->composer->getInstallationManager()->addInstaller($installer);
}

/**
* Write config.platform into the root composer file (e.g. composer.local.json)
* so that tools like PHPStan, which read that file directly in a separate
* process, pick up the correct platform PHP version.
*
* Only writes when the value differs from what is already on disk to avoid
* unnecessary file churn.
*
* @param array<string, string> $platform
*/
private function syncPlatformToRootFile(array $platform): void
{
$envComposer = getenv('COMPOSER');
$rootFileName = is_string($envComposer) && $envComposer !== '' ? basename($envComposer) : 'composer.json';
$rootFile = getcwd() . '/' . $rootFileName;

// Only act when we're actually running under an overlay root file that
// is separate from the core composer.json we just read.
if ($rootFile === getcwd() . '/composer.json' || !file_exists($rootFile)) {
return;
}

$rootData = json_decode(file_get_contents($rootFile), true);
if (!is_array($rootData)) {
return;
}

if (($rootData['config']['platform'] ?? null) === $platform) {
return;
}

$rootData['config']['platform'] = $platform;
file_put_contents(
$rootFile,
json_encode($rootData, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n"
);
}

public function deactivate(Composer $composer, IOInterface $io): void
{
}
Expand Down
18 changes: 18 additions & 0 deletions tests/test.bats
Original file line number Diff line number Diff line change
Expand Up @@ -359,3 +359,21 @@ PY
[ "${core_sha}" = "${overlay_sha}" ]
fi
}

@test "config-platform" {
set -eu -o pipefail
addon_setup

# The plugin must sync config.platform from composer.json into composer.local.json
# so that tools like PHPStan, which read the root composer file directly, pick
# up the correct platform PHP version instead of falling back to the runtime.
core_platform=$(python3 -c "import json; print(json.load(open('${TESTDIR}/composer.json'))['config']['platform']['php'])")
overlay_platform=$(python3 -c "import json; print(json.load(open('${TESTDIR}/composer.local.json'))['config']['platform']['php'])")
[ "${core_platform}" = "${overlay_platform}" ]

# PHPStan must report the platform version, not the runtime version.
# -vvv emits "PHP version for analysis: X.Y (from config.platform.php in composer.json)"
run ddev exec vendor/bin/phpstan -vvv analyze --configuration=core/phpstan.neon.dist -- core/lib/Drupal/Core/Entity/EntityInterface.php
assert_success
assert_output --partial "from config.platform.php"
}