From 498f4118f70f451b93c63c75a77fdd8abc8ee7c5 Mon Sep 17 00:00:00 2001
From: Mathias Schreiber <mathias.schreiber@typo3.org>
Date: Sat, 17 Feb 2018 21:40:05 +0100
Subject: [PATCH] [TASK] Add composer.json integrity check

Each composer.json file in a system extension now has its dependencies
checked against the root composer.json to avoid errors after the subtree split.

Resolves: #83957
Releases: master, 8.7
Change-Id: Ibf37bd56fd1534b14e714dfdfaaf6374a48978c4
Reviewed-on: https://review.typo3.org/55785
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
---
 Build/Scripts/checkIntegrityComposer.php      | 150 ++++++++++++++++++
 .../src/main/java/core/AbstractCoreSpec.java  |  11 +-
 typo3/sysext/core/composer.json               |   2 +-
 typo3/sysext/fluid/composer.json              |   2 +-
 typo3/sysext/install/composer.json            |   2 +-
 typo3/sysext/redirects/composer.json          |   2 +-
 6 files changed, 163 insertions(+), 6 deletions(-)
 create mode 100755 Build/Scripts/checkIntegrityComposer.php

diff --git a/Build/Scripts/checkIntegrityComposer.php b/Build/Scripts/checkIntegrityComposer.php
new file mode 100755
index 000000000000..a3f12f19e373
--- /dev/null
+++ b/Build/Scripts/checkIntegrityComposer.php
@@ -0,0 +1,150 @@
+#!/usr/bin/env php
+<?php
+declare(strict_types = 1);
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+require __DIR__ . '/../../vendor/autoload.php';
+
+if (PHP_SAPI !== 'cli') {
+    die('Script must be called from command line.' . chr(10));
+}
+
+/**
+ * Core integrity test script:
+ *
+ * Find all composer.json files in all system extensions and compare
+ * their dependencies against the defined dependencies of our root
+ * composer.json
+ */
+class checkIntegrityComposer
+{
+    /**
+     * @var array
+     */
+    private $rootComposerJson = [];
+
+    private $testResults = [];
+
+    /**
+     * Executes the composer integrity check.
+     * The return value is used directly in the ext() call outside this class.
+     *
+     * @return int
+     */
+    public function execute(): int
+    {
+        $rootComposerJson = __DIR__ . '/../../composer.json';
+        $this->rootComposerJson = json_decode(file_get_contents($rootComposerJson), true);
+        $filesToProcess = $this->findExtensionComposerJson();
+        $output = new \Symfony\Component\Console\Output\ConsoleOutput();
+
+        $resultAcrossAllFiles = 0;
+        /** @var \SplFileInfo $composerJsonFile */
+        foreach ($filesToProcess as $composerJsonFile) {
+            $fullFilePath = $composerJsonFile->getRealPath();
+            $this->validateComposerJson($fullFilePath);
+        }
+        if (!empty($this->testResults)) {
+            $table = new \Symfony\Component\Console\Helper\Table($output);
+            $table->setHeaders([
+                'EXT',
+                'type',
+                'Dependency',
+                'should be',
+                'actually is'
+            ]);
+            foreach ($this->testResults as $extKey => $results) {
+                foreach ($results as $result) {
+                    $table->addRow([
+                        $extKey,
+                        $result['type'],
+                        $result['dependency'],
+                        $result['shouldBe'],
+                        $result['actuallyIs']
+                    ]);
+                }
+            }
+            $table->render();
+            $resultAcrossAllFiles = 1;
+        }
+        return $resultAcrossAllFiles;
+    }
+
+    /**
+     * Finds all composer.json files in TYPO3s system extensions
+     *
+     * @return \Symfony\Component\Finder\Finder
+     */
+    private function findExtensionComposerJson(): \Symfony\Component\Finder\Finder
+    {
+        $finder = new Symfony\Component\Finder\Finder();
+        $composerFiles = $finder
+            ->files()
+            ->in(__DIR__ . '/../../typo3/sysext/*')
+            ->name('composer.json');
+        return $composerFiles;
+    }
+
+    /**
+     * Checks if the dependencies defined in $composerJsonFile are the same as
+     * in TYPO3s root composer.json file.
+     *
+     * @param string $composerJsonFile
+     */
+    private function validateComposerJson(string $composerJsonFile)
+    {
+        $extensionKey = $this->extractExtensionKey($composerJsonFile);
+        $extensionComposerJson = json_decode(file_get_contents($composerJsonFile), true);
+        // Check require section
+        foreach ($this->rootComposerJson['require'] as $requireKey => $requireItem) {
+            if (isset($extensionComposerJson['require'][$requireKey]) && $extensionComposerJson['require'][$requireKey] !== $requireItem) {
+                // log inconsistency
+                $this->testResults[$extensionKey][] = [
+                    'type' => 'require',
+                    'dependency' => $requireKey,
+                    'shouldBe' => $requireItem,
+                    'actuallyIs' => $extensionComposerJson['require'][$requireKey]
+                ];
+            }
+        }
+        // Check require-dev section
+        foreach ($this->rootComposerJson['require-dev'] as $requireDevKey => $requireDevItem) {
+            if (isset($extensionComposerJson['require-dev'][$requireDevKey]) && $extensionComposerJson['require-dev'][$requireDevKey] !== $requireDevItem) {
+                // log inconsistency
+                $this->testResults[$extensionKey][] = [
+                    'type' => 'require-dev',
+                    'dependency' => $requireDevKey,
+                    'shouldBe' => $requireDevItem,
+                    'actuallyIs' => $extensionComposerJson['require-dev'][$requireDevKey]
+                ];
+            }
+        }
+    }
+
+    /**
+     * Makes the output on CLI a bit more readable
+     *
+     * @param string $filename
+     * @return string
+     */
+    private function extractExtensionKey(string $filename): string
+    {
+        $pattern = '/typo3\/sysext\/(?<extName>[a-z].+?)\//';
+        preg_match_all($pattern, $filename, $matches, PREG_SET_ORDER, 0);
+        return $matches[0]['extName'];
+    }
+}
+
+$composerIntegrityChecker = new checkIntegrityComposer();
+exit($composerIntegrityChecker->execute());
diff --git a/Build/bamboo/src/main/java/core/AbstractCoreSpec.java b/Build/bamboo/src/main/java/core/AbstractCoreSpec.java
index 3c7e41607c5b..2b06cf814f17 100644
--- a/Build/bamboo/src/main/java/core/AbstractCoreSpec.java
+++ b/Build/bamboo/src/main/java/core/AbstractCoreSpec.java
@@ -516,8 +516,15 @@ abstract public class AbstractCoreSpec {
                     .inlineBody(
                         this.getScriptTaskBashInlineBody() +
                         "./Build/Scripts/checkIntegrityCsvFixtures.php"
-                    )
-            )
+                    ),
+                new ScriptTask()
+                    .description("Run composer.json integrity check")
+                    .interpreter(ScriptTaskProperties.Interpreter.BINSH_OR_CMDEXE)
+                    .inlineBody(
+                        this.getScriptTaskBashInlineBody() +
+                        "./Build/Scripts/checkIntegrityComposer.php"
+                   )
+           )
             .requirements(
                 this.getRequirementPhpVersion72()
             )
diff --git a/typo3/sysext/core/composer.json b/typo3/sysext/core/composer.json
index 450819e0e77e..ff3829cb2696 100644
--- a/typo3/sysext/core/composer.json
+++ b/typo3/sysext/core/composer.json
@@ -33,7 +33,7 @@
 		"mso/idna-convert": "^1.1.0",
 		"typo3fluid/fluid": "^2.4",
 		"guzzlehttp/guzzle": "^6.3.0",
-		"doctrine/dbal": "~2.6",
+		"doctrine/dbal": "^2.6",
 		"nikic/php-parser": "^3.1",
 		"symfony/polyfill-intl-icu": "^1.6"
 	},
diff --git a/typo3/sysext/fluid/composer.json b/typo3/sysext/fluid/composer.json
index 3cbcb3c16971..e17530b3dad6 100644
--- a/typo3/sysext/fluid/composer.json
+++ b/typo3/sysext/fluid/composer.json
@@ -13,7 +13,7 @@
 	"require": {
 		"typo3/cms-core": "9.2.*@dev",
 		"typo3/cms-extbase": "9.2.*@dev",
-		"typo3fluid/fluid": "^2.3"
+		"typo3fluid/fluid": "^2.4"
 	},
 	"conflict": {
 		"typo3/cms": "*"
diff --git a/typo3/sysext/install/composer.json b/typo3/sysext/install/composer.json
index e6afbd3e70f8..9e1e02446b77 100644
--- a/typo3/sysext/install/composer.json
+++ b/typo3/sysext/install/composer.json
@@ -14,7 +14,7 @@
 		"typo3/cms-core": "9.2.*@dev",
 		"typo3/cms-extbase": "9.2.*@dev",
 		"typo3/cms-fluid": "9.2.*@dev",
-		"nikic/php-parser": "~3.1",
+		"nikic/php-parser": "^3.1",
 		"symfony/finder": "^2.7 || ^3.0 || ^4.0"
 	},
 	"conflict": {
diff --git a/typo3/sysext/redirects/composer.json b/typo3/sysext/redirects/composer.json
index 592267c0bc18..8e360fed6ae0 100644
--- a/typo3/sysext/redirects/composer.json
+++ b/typo3/sysext/redirects/composer.json
@@ -12,7 +12,7 @@
 	"require": {
 		"typo3/cms-core": "9.2.*@dev",
 		"typo3/cms-backend": "9.2.*@dev",
-		"typo3fluid/fluid": "^2.3"
+		"typo3fluid/fluid": "^2.4"
 	},
 	"conflict": {
 		"typo3/cms": "*"
-- 
GitLab