diff --git a/composer.json b/composer.json
index 727e031..58d0b1e 100644
--- a/composer.json
+++ b/composer.json
@@ -39,6 +39,8 @@
         "pre-autoload-dump": "Drupal\\Core\\Composer\\Composer::preAutoloadDump",
         "post-autoload-dump": "Drupal\\Core\\Composer\\Composer::ensureHtaccess",
         "post-package-install": "Drupal\\Core\\Composer\\Composer::vendorTestCodeCleanup",
-        "post-package-update": "Drupal\\Core\\Composer\\Composer::vendorTestCodeCleanup"
+        "post-package-update": "Drupal\\Core\\Composer\\Composer::vendorTestCodeCleanup",
+        "post-install-cmd": "Drupal\\Core\\Composer\\Composer::dumpInstalledPackages",
+        "post-update-cmd": "Drupal\\Core\\Composer\\Composer::dumpInstalledPackages"
     }
 }
diff --git a/composer.lock b/composer.lock
index 90b375c..3d51a89 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,10 +4,142 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "This file is @generated automatically"
     ],
-    "hash": "7d101b08e5ae002d827cd42ae9a4e344",
+    "hash": "930c63e1e223ea769335d0baa19400c4",
     "content-hash": "60f7057617c6d995bf9946d0b12f0b5d",
     "packages": [
         {
+            "name": "composer/ca-bundle",
+            "version": "1.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/composer/ca-bundle.git",
+                "reference": "a2995e5fe351055f2c7630166af12ce8fd03edfc"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/composer/ca-bundle/zipball/a2995e5fe351055f2c7630166af12ce8fd03edfc",
+                "reference": "a2995e5fe351055f2c7630166af12ce8fd03edfc",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.3.2 || ^7.0"
+            },
+            "require-dev": {
+                "symfony/process": "^2.5 || ^3.0"
+            },
+            "suggest": {
+                "symfony/process": "This is necessary to reliably check whether openssl_x509_parse is vulnerable on older php versions, but can be ignored on PHP 5.5.6+"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Composer\\CaBundle\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jordi Boggiano",
+                    "email": "j.boggiano@seld.be",
+                    "homepage": "http://seld.be"
+                }
+            ],
+            "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.",
+            "keywords": [
+                "cabundle",
+                "cacert",
+                "certificate",
+                "ssl",
+                "tls"
+            ],
+            "time": "2016-04-13 10:13:24"
+        },
+        {
+            "name": "composer/composer",
+            "version": "1.1.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/composer/composer.git",
+                "reference": "30ab6f1c1753267d181839142fafe022313c3c9a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/composer/composer/zipball/30ab6f1c1753267d181839142fafe022313c3c9a",
+                "reference": "30ab6f1c1753267d181839142fafe022313c3c9a",
+                "shasum": ""
+            },
+            "require": {
+                "composer/ca-bundle": "^1.0",
+                "composer/semver": "^1.0",
+                "composer/spdx-licenses": "^1.0",
+                "justinrainbow/json-schema": "^1.6",
+                "php": "^5.3.2 || ^7.0",
+                "psr/log": "^1.0",
+                "seld/cli-prompt": "^1.0",
+                "seld/jsonlint": "^1.4",
+                "seld/phar-utils": "^1.0",
+                "symfony/console": "^2.5 || ^3.0",
+                "symfony/filesystem": "^2.5 || ^3.0",
+                "symfony/finder": "^2.2 || ^3.0",
+                "symfony/process": "^2.1 || ^3.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.5 || ^5.0.5",
+                "phpunit/phpunit-mock-objects": "2.3.0 || ^3.0"
+            },
+            "suggest": {
+                "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages",
+                "ext-zip": "Enabling the zip extension allows you to unzip archives",
+                "ext-zlib": "Allow gzip compression of HTTP requests"
+            },
+            "bin": [
+                "bin/composer"
+            ],
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Composer\\": "src/Composer"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nils Adermann",
+                    "email": "naderman@naderman.de",
+                    "homepage": "http://www.naderman.de"
+                },
+                {
+                    "name": "Jordi Boggiano",
+                    "email": "j.boggiano@seld.be",
+                    "homepage": "http://seld.be"
+                }
+            ],
+            "description": "Composer helps you declare, manage and install dependencies of PHP projects, ensuring you have the right stack everywhere.",
+            "homepage": "https://getcomposer.org/",
+            "keywords": [
+                "autoload",
+                "dependency",
+                "package"
+            ],
+            "time": "2016-06-26 14:42:08"
+        },
+        {
             "name": "composer/installers",
             "version": "v1.0.21",
             "source": {
@@ -164,6 +296,67 @@
             "time": "2015-09-21 09:42:36"
         },
         {
+            "name": "composer/spdx-licenses",
+            "version": "1.1.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/composer/spdx-licenses.git",
+                "reference": "88c26372b1afac36d8db601cdf04ad8716f53d88"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/88c26372b1afac36d8db601cdf04ad8716f53d88",
+                "reference": "88c26372b1afac36d8db601cdf04ad8716f53d88",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.3.2 || ^7.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.5 || ^5.0.5",
+                "phpunit/phpunit-mock-objects": "2.3.0 || ^3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Composer\\Spdx\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nils Adermann",
+                    "email": "naderman@naderman.de",
+                    "homepage": "http://www.naderman.de"
+                },
+                {
+                    "name": "Jordi Boggiano",
+                    "email": "j.boggiano@seld.be",
+                    "homepage": "http://seld.be"
+                },
+                {
+                    "name": "Rob Bast",
+                    "email": "rob.bast@gmail.com",
+                    "homepage": "http://robbast.nl"
+                }
+            ],
+            "description": "SPDX licenses list and validation library.",
+            "keywords": [
+                "license",
+                "spdx",
+                "validator"
+            ],
+            "time": "2016-05-04 12:27:30"
+        },
+        {
             "name": "doctrine/annotations",
             "version": "v1.2.7",
             "source": {
@@ -890,6 +1083,72 @@
             "time": "2014-11-20 16:49:30"
         },
         {
+            "name": "justinrainbow/json-schema",
+            "version": "1.6.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/justinrainbow/json-schema.git",
+                "reference": "cc84765fb7317f6b07bd8ac78364747f95b86341"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/cc84765fb7317f6b07bd8ac78364747f95b86341",
+                "reference": "cc84765fb7317f6b07bd8ac78364747f95b86341",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.29"
+            },
+            "require-dev": {
+                "json-schema/json-schema-test-suite": "1.1.0",
+                "phpdocumentor/phpdocumentor": "~2",
+                "phpunit/phpunit": "~3.7"
+            },
+            "bin": [
+                "bin/validate-json"
+            ],
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.6.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "JsonSchema\\": "src/JsonSchema/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Bruno Prieto Reis",
+                    "email": "bruno.p.reis@gmail.com"
+                },
+                {
+                    "name": "Justin Rainbow",
+                    "email": "justin.rainbow@gmail.com"
+                },
+                {
+                    "name": "Igor Wiedler",
+                    "email": "igor@wiedler.ch"
+                },
+                {
+                    "name": "Robert Schönthal",
+                    "email": "seroscho@googlemail.com"
+                }
+            ],
+            "description": "A library to validate a json schema.",
+            "homepage": "https://github.com/justinrainbow/json-schema",
+            "keywords": [
+                "json",
+                "schema"
+            ],
+            "time": "2016-01-25 15:43:01"
+        },
+        {
             "name": "masterminds/html5",
             "version": "2.2.1",
             "source": {
@@ -1057,12 +1316,12 @@
             "source": {
                 "type": "git",
                 "url": "https://github.com/php-fig/log.git",
-                "reference": "1.0.0"
+                "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/php-fig/log/zipball/1.0.0",
-                "reference": "1.0.0",
+                "url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b",
+                "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b",
                 "shasum": ""
             },
             "type": "library",
@@ -1090,6 +1349,144 @@
             "time": "2012-12-21 11:40:51"
         },
         {
+            "name": "seld/cli-prompt",
+            "version": "1.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Seldaek/cli-prompt.git",
+                "reference": "8cbe10923cae5bcd7c5a713f6703fc4727c8c1b4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Seldaek/cli-prompt/zipball/8cbe10923cae5bcd7c5a713f6703fc4727c8c1b4",
+                "reference": "8cbe10923cae5bcd7c5a713f6703fc4727c8c1b4",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Seld\\CliPrompt\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jordi Boggiano",
+                    "email": "j.boggiano@seld.be"
+                }
+            ],
+            "description": "Allows you to prompt for user input on the command line, and optionally hide the characters they type",
+            "keywords": [
+                "cli",
+                "console",
+                "hidden",
+                "input",
+                "prompt"
+            ],
+            "time": "2016-04-18 09:31:41"
+        },
+        {
+            "name": "seld/jsonlint",
+            "version": "1.4.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Seldaek/jsonlint.git",
+                "reference": "66834d3e3566bb5798db7294619388786ae99394"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/66834d3e3566bb5798db7294619388786ae99394",
+                "reference": "66834d3e3566bb5798db7294619388786ae99394",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.3 || ^7.0"
+            },
+            "bin": [
+                "bin/jsonlint"
+            ],
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Seld\\JsonLint\\": "src/Seld/JsonLint/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jordi Boggiano",
+                    "email": "j.boggiano@seld.be",
+                    "homepage": "http://seld.be"
+                }
+            ],
+            "description": "JSON Linter",
+            "keywords": [
+                "json",
+                "linter",
+                "parser",
+                "validator"
+            ],
+            "time": "2015-11-21 02:21:41"
+        },
+        {
+            "name": "seld/phar-utils",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Seldaek/phar-utils.git",
+                "reference": "7009b5139491975ef6486545a39f3e6dad5ac30a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/7009b5139491975ef6486545a39f3e6dad5ac30a",
+                "reference": "7009b5139491975ef6486545a39f3e6dad5ac30a",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Seld\\PharUtils\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jordi Boggiano",
+                    "email": "j.boggiano@seld.be"
+                }
+            ],
+            "description": "PHAR file format utilities, for when PHP phars you up",
+            "keywords": [
+                "phra"
+            ],
+            "time": "2015-10-13 18:44:15"
+        },
+        {
             "name": "stack/builder",
             "version": "v1.0.4",
             "source": {
@@ -1487,6 +1884,104 @@
             "time": "2016-03-07 14:04:32"
         },
         {
+            "name": "symfony/filesystem",
+            "version": "v3.1.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/filesystem.git",
+                "reference": "5751e80d6f94b7c018f338a4a7be0b700d6f3058"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/filesystem/zipball/5751e80d6f94b7c018f338a4a7be0b700d6f3058",
+                "reference": "5751e80d6f94b7c018f338a4a7be0b700d6f3058",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.5.9"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.1-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Filesystem\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony Filesystem Component",
+            "homepage": "https://symfony.com",
+            "time": "2016-04-12 18:27:47"
+        },
+        {
+            "name": "symfony/finder",
+            "version": "v3.1.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/finder.git",
+                "reference": "40d17ed287bf51a2f884c4619ce8ff2a1c5cd219"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/finder/zipball/40d17ed287bf51a2f884c4619ce8ff2a1c5cd219",
+                "reference": "40d17ed287bf51a2f884c4619ce8ff2a1c5cd219",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.5.9"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.1-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Finder\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony Finder Component",
+            "homepage": "https://symfony.com",
+            "time": "2016-05-13 18:06:41"
+        },
+        {
             "name": "symfony/http-foundation",
             "version": "v2.8.4",
             "source": {
diff --git a/core/composer.json b/core/composer.json
index c11f6b2..41c96ca 100644
--- a/core/composer.json
+++ b/core/composer.json
@@ -30,7 +30,9 @@
         "masterminds/html5": "~2.1",
         "symfony/psr-http-message-bridge": "v0.2",
         "zendframework/zend-diactoros": "~1.1",
+        "composer/composer": "~1.0",
         "composer/semver": "~1.0",
+        "justinrainbow/json-schema": "~1.0",
         "paragonie/random_compat": "~1.0"
     },
     "require-dev": {
@@ -159,6 +161,8 @@
     },
     "scripts": {
         "pre-autoload-dump": "Drupal\\Core\\Composer\\Composer::preAutoloadDump",
-        "post-autoload-dump": "Drupal\\Core\\Composer\\Composer::ensureHtaccess"
+        "post-autoload-dump": "Drupal\\Core\\Composer\\Composer::ensureHtaccess",
+        "post-install-cmd": "Drupal\\Core\\Composer\\Composer::dumpInstalledPackages",
+        "post-update-cmd": "Drupal\\Core\\Composer\\Composer::dumpInstalledPackages"
     }
 }
diff --git a/core/composer/installed-packages.json b/core/composer/installed-packages.json
new file mode 100644
index 0000000..40ed9c4
--- /dev/null
+++ b/core/composer/installed-packages.json
@@ -0,0 +1,710 @@
+[
+    {
+        "name": "wikimedia/composer-merge-plugin",
+        "version_constraint": "1.3.1.0"
+    },
+    {
+        "name": "symfony/finder",
+        "version_constraint": "3.1.1.0"
+    },
+    {
+        "name": "symfony/filesystem",
+        "version_constraint": "3.1.1.0"
+    },
+    {
+        "name": "seld/phar-utils",
+        "version_constraint": "1.0.1.0"
+    },
+    {
+        "name": "seld/jsonlint",
+        "version_constraint": "1.4.0.0"
+    },
+    {
+        "name": "seld/cli-prompt",
+        "version_constraint": "1.0.2.0"
+    },
+    {
+        "name": "psr/log",
+        "version_constraint": "1.0.0.0"
+    },
+    {
+        "name": "composer/spdx-licenses",
+        "version_constraint": "1.1.4.0"
+    },
+    {
+        "name": "composer/ca-bundle",
+        "version_constraint": "1.0.2.0"
+    },
+    {
+        "name": "doctrine/lexer",
+        "version_constraint": "1.0.1.0"
+    },
+    {
+        "name": "doctrine/collections",
+        "version_constraint": "1.3.0.0"
+    },
+    {
+        "name": "doctrine/annotations",
+        "version_constraint": "1.2.7.0"
+    },
+    {
+        "name": "easyrdf/easyrdf",
+        "version_constraint": "0.9.1.0"
+    },
+    {
+        "name": "psr/http-message",
+        "version_constraint": "1.0.0.0"
+    },
+    {
+        "name": "ircmaxell/password-compat",
+        "version_constraint": "1.0.4.0"
+    },
+    {
+        "name": "masterminds/html5",
+        "version_constraint": "2.2.1.0"
+    },
+    {
+        "name": "stack/builder",
+        "version_constraint": "1.0.4.0"
+    },
+    {
+        "name": "symfony-cmf/routing",
+        "version_constraint": "1.4.0.0"
+    },
+    {
+        "name": "symfony/psr-http-message-bridge",
+        "version_constraint": "0.2.0.0"
+    },
+    {
+        "name": "zendframework/zend-escaper",
+        "version_constraint": "2.5.1.0"
+    },
+    {
+        "name": "fabpot/goutte",
+        "version_constraint": "3.1.2.0"
+    },
+    {
+        "name": "behat/mink",
+        "version_constraint": "1.7.1.0"
+    },
+    {
+        "name": "behat/mink-browserkit-driver",
+        "version_constraint": "1.3.2.0"
+    },
+    {
+        "name": "behat/mink-goutte-driver",
+        "version_constraint": "1.2.1.0"
+    },
+    {
+        "name": "jcalderonzumba/mink-phantomjs-driver",
+        "version_constraint": "0.3.1.0"
+    },
+    {
+        "name": "phpunit/php-token-stream",
+        "version_constraint": "1.4.8.0"
+    },
+    {
+        "name": "sebastian/version",
+        "version_constraint": "1.0.6.0"
+    },
+    {
+        "name": "sebastian/comparator",
+        "version_constraint": "1.2.0.0"
+    },
+    {
+        "name": "phpunit/php-text-template",
+        "version_constraint": "1.2.1.0"
+    },
+    {
+        "name": "doctrine/instantiator",
+        "version_constraint": "1.0.5.0"
+    },
+    {
+        "name": "phpunit/phpunit-mock-objects",
+        "version_constraint": "2.3.8.0"
+    },
+    {
+        "name": "phpunit/php-file-iterator",
+        "version_constraint": "1.4.1.0"
+    },
+    {
+        "name": "phpunit/php-code-coverage",
+        "version_constraint": "2.2.4.0"
+    },
+    {
+        "name": "justinrainbow/json-schema",
+        "version_constraint": "1.6.1.0"
+    },
+    {
+        "name": "composer/composer",
+        "version_constraint": "1.1.3.0"
+    },
+    {
+        "name": "composer/installers",
+        "version_constraint": "1.0.21.0"
+    },
+    {
+        "name": "roundcube/plugin-installer",
+        "version_constraint": "*"
+    },
+    {
+        "name": "shama/baton",
+        "version_constraint": "*"
+    },
+    {
+        "name": "symfony/polyfill-apcu",
+        "version_constraint": "1.1.1.0"
+    },
+    {
+        "name": "symfony/class-loader",
+        "version_constraint": "2.8.4.0"
+    },
+    {
+        "name": "symfony/polyfill-mbstring",
+        "version_constraint": "1.1.0.0"
+    },
+    {
+        "name": "symfony/console",
+        "version_constraint": "2.8.4.0"
+    },
+    {
+        "name": "symfony/dependency-injection",
+        "version_constraint": "2.8.4.0"
+    },
+    {
+        "name": "symfony/polyfill-php55",
+        "version_constraint": "1.1.0.0"
+    },
+    {
+        "name": "symfony/polyfill-php54",
+        "version_constraint": "1.1.0.0"
+    },
+    {
+        "name": "symfony/http-foundation",
+        "version_constraint": "2.8.4.0"
+    },
+    {
+        "name": "symfony/event-dispatcher",
+        "version_constraint": "2.8.4.0"
+    },
+    {
+        "name": "symfony/debug",
+        "version_constraint": "2.7.6.0"
+    },
+    {
+        "name": "symfony/http-kernel",
+        "version_constraint": "2.8.4.0"
+    },
+    {
+        "name": "symfony/routing",
+        "version_constraint": "2.8.4.0"
+    },
+    {
+        "name": "symfony/serializer",
+        "version_constraint": "2.8.4.0"
+    },
+    {
+        "name": "symfony/translation",
+        "version_constraint": "2.8.4.0"
+    },
+    {
+        "name": "symfony/validator",
+        "version_constraint": "2.8.4.0"
+    },
+    {
+        "name": "symfony/process",
+        "version_constraint": "2.8.4.0"
+    },
+    {
+        "name": "symfony/polyfill-iconv",
+        "version_constraint": "1.1.1.0"
+    },
+    {
+        "name": "twig/twig",
+        "version_constraint": "1.24.0.0"
+    },
+    {
+        "name": "doctrine/inflector",
+        "version_constraint": "1.0.1.0"
+    },
+    {
+        "name": "doctrine/cache",
+        "version_constraint": "1.4.2.0"
+    },
+    {
+        "name": "doctrine/common",
+        "version_constraint": "2.5.1.0"
+    },
+    {
+        "name": "zendframework/zend-hydrator",
+        "version_constraint": "1.0.0.0"
+    },
+    {
+        "name": "zendframework/zend-stdlib",
+        "version_constraint": "2.7.3.0"
+    },
+    {
+        "name": "zendframework/zend-feed",
+        "version_constraint": "2.5.2.0"
+    },
+    {
+        "name": "egulias/email-validator",
+        "version_constraint": "1.2.9.0"
+    },
+    {
+        "name": "zendframework/zend-diactoros",
+        "version_constraint": "1.1.3.0"
+    },
+    {
+        "name": "composer/semver",
+        "version_constraint": "1.0.0.0"
+    },
+    {
+        "name": "paragonie/random_compat",
+        "version_constraint": "1.1.1.0"
+    },
+    {
+        "name": "guzzlehttp/psr7",
+        "version_constraint": "1.2.0.0"
+    },
+    {
+        "name": "guzzlehttp/promises",
+        "version_constraint": "1.0.2.0"
+    },
+    {
+        "name": "guzzlehttp/guzzle",
+        "version_constraint": "6.1.0.0"
+    },
+    {
+        "name": "jcalderonzumba/gastonjs",
+        "version_constraint": "1.0.2.0"
+    },
+    {
+        "name": "mikey179/vfsstream",
+        "version_constraint": "1.6.0.0"
+    },
+    {
+        "name": "symfony/yaml",
+        "version_constraint": "2.8.4.0"
+    },
+    {
+        "name": "sebastian/global-state",
+        "version_constraint": "1.0.0.0"
+    },
+    {
+        "name": "sebastian/recursion-context",
+        "version_constraint": "1.0.1.0"
+    },
+    {
+        "name": "sebastian/exporter",
+        "version_constraint": "1.2.1.0"
+    },
+    {
+        "name": "sebastian/environment",
+        "version_constraint": "1.3.2.0"
+    },
+    {
+        "name": "sebastian/diff",
+        "version_constraint": "1.3.0.0"
+    },
+    {
+        "name": "phpunit/php-timer",
+        "version_constraint": "1.0.7.0"
+    },
+    {
+        "name": "phpdocumentor/reflection-docblock",
+        "version_constraint": "2.0.4.0"
+    },
+    {
+        "name": "phpspec/prophecy",
+        "version_constraint": "1.5.0.0"
+    },
+    {
+        "name": "phpunit/phpunit",
+        "version_constraint": "4.8.11.0"
+    },
+    {
+        "name": "symfony/css-selector",
+        "version_constraint": "2.8.4.0"
+    },
+    {
+        "name": "symfony/dom-crawler",
+        "version_constraint": "2.7.6.0"
+    },
+    {
+        "name": "symfony/browser-kit",
+        "version_constraint": "2.7.6.0"
+    },
+    {
+        "name": "drupal/drupal",
+        "version_constraint": "dev-2494073-8.2.x"
+    },
+    {
+        "name": "drupal/core",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/action",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/aggregator",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/automated_cron",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/bartik",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/ban",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/basic_auth",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/big_pipe",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/block",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/block_content",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/block_place",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/book",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/breakpoint",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/ckeditor",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/classy",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/color",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/comment",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/config",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/config_translation",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/contact",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/content_translation",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/contextual",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/core-annotation",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/core-bridge",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/core-datetime",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/core-dependency-injection",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/core-diff",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/core-discovery",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/core-event-dispatcher",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/core-file-cache",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/core-filesystem",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/core-gettext",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/core-graph",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/core-php-storage",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/core-plugin",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/core-proxy-builder",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/core-serialization",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/core-transliteration",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/core-utility",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/core-uuid",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/datetime",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/dblog",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/dynamic_page_cache",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/editor",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/entity_reference",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/field",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/field_ui",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/file",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/filter",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/forum",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/hal",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/help",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/history",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/image",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/inline_form_errors",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/language",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/link",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/locale",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/minimal",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/menu_link_content",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/menu_ui",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/migrate",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/migrate_drupal",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/migrate_drupal_ui",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/node",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/options",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/page_cache",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/path",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/quickedit",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/rdf",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/responsive_image",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/rest",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/search",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/serialization",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/seven",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/shortcut",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/simpletest",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/standard",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/stark",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/statistics",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/syslog",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/system",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/taxonomy",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/telephone",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/text",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/toolbar",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/tour",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/tracker",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/update",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/user",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/views",
+        "version_constraint": "~8.2"
+    },
+    {
+        "name": "drupal/views_ui",
+        "version_constraint": "~8.2"
+    }
+]
\ No newline at end of file
diff --git a/core/composer/installed-packages.schema.json b/core/composer/installed-packages.schema.json
new file mode 100644
index 0000000..7a5658a
--- /dev/null
+++ b/core/composer/installed-packages.schema.json
@@ -0,0 +1,22 @@
+{
+  "description": "Installed Composer packages",
+  "type": "array",
+  "items": [
+    {
+      "description": "Package",
+      "type": "object",
+      "required": ["name", "version_constraint"],
+      "additionalProperties": false,
+      "properties": {
+        "name": {
+          "type": "string",
+          "required": true
+        },
+        "version_constraint": {
+          "type": "string",
+          "required": true
+        }
+      }
+    }
+  ]
+}
diff --git a/core/core.services.yml b/core/core.services.yml
index 1ee07fe..f39fcaa 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -34,6 +34,14 @@ parameters:
     - webcal
     - rtsp
 services:
+  # Composer integration.
+  composer.extension_dependency_checker:
+    class: Drupal\Core\Composer\ExtensionDependencyChecker
+    arguments: ['@app.root']
+  composer.extension_dependency_requirements:
+    class: Drupal\Core\Composer\ExtensionDependencyRequirements
+    arguments: ['@composer.extension_dependency_checker', '@string_translation']
+
   # Simple cache contexts, directly derived from the request context.
   cache_context.ip:
     class: Drupal\Core\Cache\Context\IpCacheContext
@@ -478,7 +486,7 @@ services:
     class: Drupal\Core\Extension\ModuleInstaller
     tags:
       - { name: service_collector, tag: 'module_install.uninstall_validator', call: addUninstallValidator }
-    arguments: ['@app.root', '@module_handler', '@kernel', '@router.builder']
+    arguments: ['@app.root', '@module_handler', '@kernel', '@composer.extension_dependency_checker']
     lazy: true
   content_uninstall_validator:
     class: Drupal\Core\Entity\ContentUninstallValidator
diff --git a/core/includes/install.inc b/core/includes/install.inc
index 3a9c2bc..48c1a3a 100644
--- a/core/includes/install.inc
+++ b/core/includes/install.inc
@@ -10,6 +10,7 @@
 use Drupal\Component\Utility\Crypt;
 use Drupal\Component\Utility\OpCodeCache;
 use Drupal\Component\Utility\UrlHelper;
+use Drupal\Core\Extension\Extension;
 use Drupal\Core\Extension\ExtensionDiscovery;
 use Drupal\Core\Site\Settings;
 
@@ -985,9 +986,20 @@ function drupal_requirements_severity(&$requirements) {
  */
 function drupal_check_module($module) {
   module_load_install($module);
-  // Check requirements
-  $requirements = \Drupal::moduleHandler()->invoke($module, 'requirements', array('install'));
-  if (is_array($requirements) && drupal_requirements_severity($requirements) == REQUIREMENT_ERROR) {
+  // Check requirements based on hook_requirements(). Make sure the requirements
+  // are an array, even if the hook invocation returns NULL if the module does
+  // not implement hook_requirements().
+  $requirements = \Drupal::moduleHandler()->invoke($module, 'requirements', array('install')) ?: [];
+
+  // We can't use the module handler here because it only gives us enabled
+  // extensions.
+  $extension = new Extension(DRUPAL_ROOT, 'module', drupal_get_filename('module', $module));
+  // Add Composer dependencies to requirements.
+  /** @var \Drupal\Core\Composer\ExtensionDependencyRequirements $dependency_requirements */
+  $dependency_requirements = \Drupal::service('composer.extension_dependency_requirements');
+  $requirements = array_merge($requirements, $dependency_requirements->buildRequirements([$extension]));
+
+  if (drupal_requirements_severity($requirements) == REQUIREMENT_ERROR) {
     // Print any error messages
     foreach ($requirements as $requirement) {
       if (isset($requirement['severity']) && $requirement['severity'] == REQUIREMENT_ERROR) {
diff --git a/core/lib/Drupal/Core/Composer/Composer.php b/core/lib/Drupal/Core/Composer/Composer.php
index 51a4917..c614582 100644
--- a/core/lib/Drupal/Core/Composer/Composer.php
+++ b/core/lib/Drupal/Core/Composer/Composer.php
@@ -2,6 +2,10 @@
 
 namespace Drupal\Core\Composer;
 
+use Composer\Json\JsonFile;
+use Composer\Package\Link;
+use Composer\Package\PackageInterface;
+use Composer\Repository\InstalledFilesystemRepository;
 use Drupal\Component\PhpStorage\FileStorage;
 use Composer\Script\Event;
 use Composer\Installer\PackageEvent;
@@ -246,4 +250,46 @@ protected static function deleteRecursive($path) {
     return rmdir($path) && $success;
   }
 
+  /**
+   * Dumps a list of all installed packages.
+   *
+   * @param \Composer\Script\Event
+   */
+  public static function dumpInstalledPackages(Event $event) {
+    $composer = $event->getComposer();
+
+    // Get the packages Composer itself says have been installed.
+    $vendor_directory = $composer->getConfig()->get('vendor-dir');
+    $composer_installed_json_path = $vendor_directory . '/composer/installed.json';
+    $composer_installed_repository = new InstalledFilesystemRepository(new JsonFile($composer_installed_json_path));
+    $installed_packages = $composer_installed_repository->getPackages();
+
+    // Mark the root package as having been installed.
+    $installed_packages[] = $composer->getPackage();
+
+    // Convert the list of installed packages to a list of constraints.
+    $installed_packages_constraints = array_reduce($installed_packages, function ($installed_packages_constraints, PackageInterface $package) {
+      $installed_packages_constraints[$package->getName()] = $package->getVersion();
+
+      // Add replaced packages.
+      $installed_packages_constraints = array_reduce($package->getReplaces(), function ($installed_packages_constraints, Link $link) {
+        $installed_packages_constraints[$link->getTarget()] = $link->getConstraint()->getPrettyString();
+
+        return $installed_packages_constraints;
+      }, $installed_packages_constraints);
+
+      return $installed_packages_constraints;
+    }, []);
+
+    // Create and store the dump.
+    $installed_packages_dump = array_map(function ($package_name, $package_version) {
+      return (object) [
+        'name' => $package_name,
+        'version_constraint' => $package_version,
+      ];
+    }, array_keys($installed_packages_constraints), $installed_packages_constraints);
+    $installed_dump_file_path = __DIR__ . '/../../../../composer/installed-packages.json';
+    file_put_contents($installed_dump_file_path, json_encode($installed_packages_dump, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Composer/ComposerIntegrationException.php b/core/lib/Drupal/Core/Composer/ComposerIntegrationException.php
new file mode 100644
index 0000000..88fa389
--- /dev/null
+++ b/core/lib/Drupal/Core/Composer/ComposerIntegrationException.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Drupal\Core\Composer;
+
+/**
+ * Exception thrown when Drupal's Composer integration is missing.
+ */
+class ComposerIntegrationException extends \Exception {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct($message = '', $code = 0, \Exception $previous = NULL) {
+    $message .= ' ' . sprintf('The Drupal installation has not been fully integrated with Composer yet. Add %s::dumpInstalledPackages as "post-install-cmd" and "post-update-cmd" scripts to composer.json and run `composer update`.', Composer::class);
+    parent::__construct($message, 0, $previous);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Composer/ExtensionDependencyChecker.php b/core/lib/Drupal/Core/Composer/ExtensionDependencyChecker.php
new file mode 100644
index 0000000..fa091b1
--- /dev/null
+++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyChecker.php
@@ -0,0 +1,180 @@
+<?php
+
+namespace Drupal\Core\Composer;
+
+use Composer\Semver\VersionParser;
+use Drupal\Core\Extension\Extension;
+use JsonSchema\RefResolver;
+use JsonSchema\Uri\UriRetriever;
+use JsonSchema\Validator;
+
+/**
+ * Checks whether an extension's Composer dependencies are met.
+ */
+class ExtensionDependencyChecker {
+
+  /**
+   * The path to the application's root directory.
+   *
+   * @var string
+   */
+  protected $appRoot;
+
+  /**
+   * The Composer packages that are currently installed.
+   *
+   * @var null|string[]
+   *   Either NULL when the installed packages have not been computed yet, or an
+   *   array of which keys are package names and values are package version
+   *   constraints. Example: ['crell/api-problem' => '1.7.0'].
+   *
+   * @see self::getInstalledPackages()
+   */
+  protected $installedPackageConstraints;
+
+  /**
+   * Creates a new instance.
+   *
+   * @param string $app_root
+   *   The path to the application's root directory.
+   */
+  public function __construct($app_root) {
+    $this->appRoot = $app_root;
+  }
+
+  /**
+   * Determines whether the Composer-based dependencies of an extension are met.
+   *
+   * Platform requirements and require-dev are ignored.
+   *
+   * @param \Drupal\Core\Extension\Extension $extension
+   *   The extension to check.
+   *
+   * @return bool
+   *   TRUE if this extension's Composer dependencies are met, FALSE otherwise.
+   *
+   * @throws \Drupal\Core\Composer\ComposerIntegrationException
+   *   Thrown if this Drupal installation has not been fully integrated with
+   *   Composer yet.
+   */
+  public function dependenciesAreMet(Extension $extension) {
+    return empty($this->getUnmetDependencies($extension));
+  }
+
+  /**
+   * Gets a list of all the unmet Composer-based dependencies for an extension.
+   *
+   * @param \Drupal\Core\Extension\Extension $extension
+   *   An extension to check.
+   *
+   * @return string[]
+   *   An array of unmet dependencies, or empty array. Keys are package names,
+   *   values are version constraints. Similar to the 'require' section of a
+   *   composer.json file.
+   *
+   * @throws \Drupal\Core\Composer\ComposerIntegrationException
+   *   Thrown if this Drupal installation has not been fully integrated with
+   *   Composer yet.
+   */
+  public function getUnmetDependencies(Extension $extension) {
+    $composer_file_path = $this->appRoot . '/' . $extension->getPath() . '/composer.json';
+
+    // If the extension has no Composer file, it has no unmet dependencies.
+    if (!file_exists($composer_file_path)) {
+      return [];
+    }
+
+    $composer_file = json_decode(file_get_contents($composer_file_path));
+
+    // If the extension has no Composer dependencies at all, it has no unmet
+    // dependencies.
+    if (empty($composer_file->require)) {
+      return [];
+    }
+
+    $requirements = (array) $composer_file->require;
+    $installed_packages = $this->getInstalledPackages();
+    $semver_version_parser = new VersionParser();
+
+    // Check each required package against the list of installed packages.
+    $unmet_dependencies = array_filter($requirements, function($constraint, $package_name) use ($installed_packages, $semver_version_parser) {
+      // If the package is a platform requirement, we ignore it.
+      if (strpos($package_name, '/') === FALSE) {
+        return FALSE;
+      }
+
+      // If a dependency is not installed at all, it is unmet.
+      if (empty($installed_packages[$package_name])) {
+        return TRUE;
+      }
+
+      // If the wrong version of a dependency is installed, it is unmet.
+      $requirement_constraints = $semver_version_parser->parseConstraints($constraint);
+      $installed_package_version_constraints = $semver_version_parser->parseConstraints($installed_packages[$package_name]);
+      if (!$requirement_constraints->matches($installed_package_version_constraints)) {
+        return TRUE;
+      }
+    }, ARRAY_FILTER_USE_BOTH);
+
+    return $unmet_dependencies;
+  }
+
+  /**
+   * Gets information about installed Composer packages.
+   *
+   * @return string[]
+   *   Keys are package names and values are package version constraints.
+   *   Example: ['crell/api-problem' => '1.7.0'].
+   *
+   * @throws \Drupal\Core\Composer\ComposerIntegrationException
+   *   Thrown if this Drupal installation has not been fully integrated with
+   *   Composer yet.
+   */
+  protected function getInstalledPackages() {
+    // Quickly return the cached list, if it exists.
+    if (is_array($this->installedPackageConstraints)) {
+      return $this->installedPackageConstraints;
+    }
+
+    $installed_packages_json_path = $this->appRoot . '/core/composer/installed-packages.json';
+
+    // If the JSON file does not exist, `composer install/update` failed or was
+    // never executed.
+    if (!file_exists($installed_packages_json_path)) {
+      throw new ComposerIntegrationException();
+    }
+
+    $installed_packages_json = file_get_contents($installed_packages_json_path, FALSE);
+    $installed_packages = json_decode($installed_packages_json);
+
+    // Check if the content is valid JSON.
+    if ($installed_packages_json !== 'null' && is_null($installed_packages)) {
+      throw new ComposerIntegrationException(sprintf('%s does not contain valid JSON.', $installed_packages_json_path));
+    }
+
+    // Validate the message against its JSON schema.
+    $installed_packages_json_schema_path = $this->appRoot . '/core/composer/installed-packages.schema.json';
+    $schema_uri = parse_url($installed_packages_json_schema_path, PHP_URL_SCHEME) ? $installed_packages_json_schema_path : 'file://' . $installed_packages_json_schema_path;
+    $schema_retriever = new UriRetriever();
+    $schema = $schema_retriever->retrieve($schema_uri);
+    $schema_reference_resolver = new RefResolver($schema_retriever);
+    $schema_reference_resolver->resolve($schema, $schema_uri);
+    $schema_validator = new Validator();
+    $schema_validator->check($installed_packages, $schema);
+    if (!$schema_validator->isValid()) {
+      $errors = $schema_validator->getErrors();
+      $error = reset($errors);
+      throw new ComposerIntegrationException(sprintf('%s contains valid JSON, but does not follow the schema described in %s: [%s] %s.', $installed_packages_json_path, $installed_packages_json_schema_path, $error['property'], $error['message']));
+    }
+
+    // Extract the data to a simple array.
+    $this->installedPackageConstraints = array_reduce($installed_packages, function ($installed_package_constraints, \stdClass $package) {
+      $installed_package_constraints[$package->name] = $package->version_constraint;
+
+      return $installed_package_constraints;
+    }, []);
+
+    return $this->installedPackageConstraints;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Composer/ExtensionDependencyRequirements.php b/core/lib/Drupal/Core/Composer/ExtensionDependencyRequirements.php
new file mode 100644
index 0000000..0f1f0a2
--- /dev/null
+++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyRequirements.php
@@ -0,0 +1,95 @@
+<?php
+
+namespace Drupal\Core\Composer;
+
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\StringTranslation\TranslationInterface;
+
+/**
+ * Builds hook_requirements() info about extensions' Composer dependencies.
+ */
+class ExtensionDependencyRequirements {
+
+  use StringTranslationTrait;
+
+  /**
+   * The extension Composer dependency checker.
+   *
+   * @var \Drupal\Core\Composer\ExtensionDependencyChecker
+   */
+  protected $dependencyChecker;
+
+  /**
+   * Creates a new instance.
+   *
+   * @param \Drupal\Core\Composer\ExtensionDependencyChecker $dependency_checker
+   *   The extension Composer dependency checker.
+   * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
+   *   The string translator.
+   */
+  public function __construct(ExtensionDependencyChecker $dependency_checker, TranslationInterface $string_translation) {
+    $this->dependencyChecker = $dependency_checker;
+    $this->stringTranslation = $string_translation;
+  }
+
+  /**
+   * Builds the Composer requirements for extensions.
+   *
+   * @param \Drupal\Core\Extension\Extension[] $extensions
+   *   The extensions to build the requirements for.
+   *
+   * @return array[]
+   *   Requirements array, suitable for hook_requirements().
+   *
+   * @see hook_requirements()
+   */
+  public function buildRequirements(array $extensions) {
+    $requirements = [
+      'composer_dependencies' => [
+        'title' => $this->t('Composer dependencies'),
+      ],
+    ];
+    $unmet_dependencies = [];
+    foreach ($extensions as $extension) {
+      $unmet = $this->dependencyChecker->getUnmetDependencies($extension);
+      if (!empty($unmet)) {
+        $unmet_dependencies[] = $extension->getName() . ' (' . $this->formatComposerDependencies($unmet) . ')';
+      }
+    }
+    if (!empty($unmet_dependencies)) {
+      $requirements['composer_dependencies'] += [
+        'description' => $this->t('The following extensions have unmet Composer-based dependencies: @extensions. Read the <a href=":documentation">documentation on drupal.org</a> on how to install them.', [
+          '@extensions' => implode(', ', $unmet_dependencies),
+          ':documentation' => 'https://www.drupal.org/documentation/install/composer-dependencies',
+        ]),
+        'severity' => REQUIREMENT_ERROR,
+      ];
+    }
+    else {
+      $requirements['composer_dependencies'] += [
+        'description' => $this->t('All Composer dependencies have been met.'),
+        'severity' => REQUIREMENT_OK,
+      ];
+    }
+    return $requirements;
+  }
+
+  /**
+   * Format some dependencies so the user can understand them.
+   *
+   * @param string[] $dependencies
+   *   An array where the key is the name of the package and the value is the
+   *   constraint.
+   *
+   * @return string
+   *   User-readable string.
+   */
+  protected function formatComposerDependencies(array $dependencies) {
+    $result = [];
+    foreach($dependencies as $name => $constraint) {
+      $result[] = $name . ': ' . $constraint;
+    }
+    return implode(', ', $result);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Extension/ExtensionComposerRequirementsException.php b/core/lib/Drupal/Core/Extension/ExtensionComposerRequirementsException.php
new file mode 100644
index 0000000..24808b9
--- /dev/null
+++ b/core/lib/Drupal/Core/Extension/ExtensionComposerRequirementsException.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace Drupal\Core\Extension;
+
+use Drupal\Core\StringTranslation\TranslationInterface;
+
+/**
+ * Exception thrown when an extension's Composer requirements are not available.
+ */
+class ExtensionComposerRequirementsException extends \Exception {
+
+  /**
+   * The machine name of the extension with unmet dependencies.
+   *
+   * @var string
+   */
+  protected $extensionName;
+
+  /**
+   * Constructs a new instance.
+   *
+   * @param string $extension_name
+   *   The machine name of the extension with unmet dependencies.
+   */
+  public function __construct($extension_name) {
+    $this->extensionName = $extension_name;
+    // Construct a message string which can be used without a translation
+    // service.
+    parent::__construct("Extension $extension_name has unmet Composer dependencies.");
+  }
+
+  /**
+   * Gets a translated message from the exception.
+   *
+   * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
+   *   The string translation service.
+   *
+   * @return string
+   *   Translated string.
+   */
+  public function getTranslatedMessage(TranslationInterface $string_translation) {
+    return $string_translation->translate("Extension @extension has unmet Composer dependencies.", ['@extension' => $this->extensionName]);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Extension/ModuleInstaller.php b/core/lib/Drupal/Core/Extension/ModuleInstaller.php
index a903b8a..4395d1d 100644
--- a/core/lib/Drupal/Core/Extension/ModuleInstaller.php
+++ b/core/lib/Drupal/Core/Extension/ModuleInstaller.php
@@ -5,6 +5,7 @@
 use Drupal\Component\Serialization\Yaml;
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Composer\ExtensionDependencyChecker;
 use Drupal\Core\DrupalKernelInterface;
 use Drupal\Core\Entity\EntityStorageException;
 use Drupal\Core\Entity\FieldableEntityInterface;
@@ -18,6 +19,13 @@
 class ModuleInstaller implements ModuleInstallerInterface {
 
   /**
+   * The extension Composer dependency checker.
+   *
+   * @var \Drupal\Core\Composer\ExtensionDependencyChecker
+   */
+  protected $extensionComposerDependencyChecker;
+
+  /**
    * The module handler.
    *
    * @var \Drupal\Core\Extension\ModuleHandlerInterface
@@ -54,11 +62,14 @@ class ModuleInstaller implements ModuleInstallerInterface {
    *   The module handler.
    * @param \Drupal\Core\DrupalKernelInterface $kernel
    *   The drupal kernel.
+   * @param \Drupal\Core\Composer\ExtensionDependencyChecker $extension_composer_dependency_checker
+   *   The extension Composer dependency checker.
    *
    * @see \Drupal\Core\DrupalKernel
    * @see \Drupal\Core\CoreServiceProvider
    */
-  public function __construct($root, ModuleHandlerInterface $module_handler, DrupalKernelInterface $kernel) {
+  public function __construct($root, ModuleHandlerInterface $module_handler, DrupalKernelInterface $kernel, ExtensionDependencyChecker $extension_composer_dependency_checker) {
+    $this->extensionComposerDependencyChecker = $extension_composer_dependency_checker;
     $this->root = $root;
     $this->moduleHandler = $module_handler;
     $this->kernel = $kernel;
@@ -93,9 +104,15 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
       }
 
       // Add dependencies to the list. The new modules will be processed as
-      // the while loop continues.
+      // the while loop continues. We will check for Composer-based dependencies
+      // as we go.
       while (list($module) = each($module_list)) {
-        foreach (array_keys($module_data[$module]->requires) as $dependency) {
+        $module_extension = $module_data[$module];
+        // Throw an exception if there are unmet Composer-based dependencies.
+        if (!$this->extensionComposerDependencyChecker->dependenciesAreMet($module_extension)) {
+          throw new ExtensionComposerRequirementsException($module);
+        }
+        foreach (array_keys($module_extension->requires) as $dependency) {
           if (!isset($module_data[$dependency])) {
             // The dependency does not exist.
             throw new MissingDependencyException("Unable to install modules: module '$module' is missing its dependency module $dependency.");
@@ -128,6 +145,7 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
       $source_storage = $config_installer->getSourceStorage();
     }
     $modules_installed = array();
+
     foreach ($module_list as $module) {
       $enabled = $extension_config->get("module.$module") !== NULL;
       if (!$enabled) {
diff --git a/core/lib/Drupal/Core/Extension/ModuleInstallerInterface.php b/core/lib/Drupal/Core/Extension/ModuleInstallerInterface.php
index c8221a1..ab714bb 100644
--- a/core/lib/Drupal/Core/Extension/ModuleInstallerInterface.php
+++ b/core/lib/Drupal/Core/Extension/ModuleInstallerInterface.php
@@ -37,6 +37,9 @@
    * @throws \Drupal\Core\Extension\MissingDependencyException
    *   Thrown when a requested module, or a dependency of one, can not be found.
    *
+   * @throws \Drupal\Core\Extension\ExtensionComposerRequirementsException
+   *   Thrown when Composer-based dependencies are not met.
+   *
    * @see hook_module_preinstall()
    * @see hook_install()
    * @see hook_modules_installed()
diff --git a/core/modules/system/src/Form/ModulesListConfirmForm.php b/core/modules/system/src/Form/ModulesListConfirmForm.php
index 88452e0..1cf85ef 100644
--- a/core/modules/system/src/Form/ModulesListConfirmForm.php
+++ b/core/modules/system/src/Form/ModulesListConfirmForm.php
@@ -4,6 +4,7 @@
 
 use Drupal\Core\Config\PreExistingConfigException;
 use Drupal\Core\Config\UnmetDependenciesException;
+use Drupal\Core\Extension\ExtensionComposerRequirementsException;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Extension\ModuleInstallerInterface;
 use Drupal\Core\Form\ConfirmFormBase;
@@ -192,6 +193,13 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
         );
         return;
       }
+      catch (ExtensionComposerRequirementsException $e) {
+        drupal_set_message(
+          $e->getTranslatedMessage($this->getStringTranslation()),
+          'error'
+        );
+        return;
+      }
 
       $module_names = array_values($this->modules['install']);
       drupal_set_message($this->formatPlural(count($module_names), 'Module %name has been enabled.', '@count modules have been enabled: %names.', array(
diff --git a/core/modules/system/src/Form/ModulesListForm.php b/core/modules/system/src/Form/ModulesListForm.php
index 7831c38..1903fba 100644
--- a/core/modules/system/src/Form/ModulesListForm.php
+++ b/core/modules/system/src/Form/ModulesListForm.php
@@ -7,6 +7,7 @@
 use Drupal\Core\Config\UnmetDependenciesException;
 use Drupal\Core\Access\AccessManagerInterface;
 use Drupal\Core\Extension\Extension;
+use Drupal\Core\Extension\ExtensionComposerRequirementsException;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Extension\ModuleInstallerInterface;
 use Drupal\Core\Form\FormBase;
@@ -476,6 +477,13 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
         );
         return;
       }
+      catch (ExtensionComposerRequirementsException $e) {
+        drupal_set_message(
+          $e->getTranslatedMessage($this->getStringTranslation()),
+          'error'
+        );
+        return;
+      }
     }
   }
 
diff --git a/core/modules/system/src/Tests/Module/HookRequirementsTest.php b/core/modules/system/src/Tests/Module/HookRequirementsTest.php
index ccbbff9..760e423 100644
--- a/core/modules/system/src/Tests/Module/HookRequirementsTest.php
+++ b/core/modules/system/src/Tests/Module/HookRequirementsTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\system\Tests\Module;
 
+use Drupal\Core\Extension\ExtensionComposerRequirementsException;
+
 /**
  * Attempts enabling a module that fails hook_requirements('install').
  *
@@ -24,4 +26,49 @@ function testHookRequirementsFailure() {
     $this->assertModules(array('requirements1_test'), FALSE);
   }
 
+  /**
+   * Tests that a module with uninstalled dependencies is not installable.
+   */
+  public function testComposerDependenciesFailure() {
+    $this->assertModules(['composer_uninstallable'], FALSE);
+
+    // Attempt to install the composer_uninstallable module using internals.
+    // This should throw a
+    // \Drupal\Core\Extension\ExtensionComposerRequirementsException.
+    $message = sprintf('Attempting to install composer_uninstallable threw %s.', ExtensionComposerRequirementsException::class);
+    try {
+      $installer = $this->container->get('module_installer');
+      $installer->install(['composer_uninstallable'], TRUE);
+      $this->fail($message);
+    }
+    catch (ExtensionComposerRequirementsException $e) {
+      $this->pass($message);
+    }
+
+    // Attempt to install the composer_uninstallable module using the module
+    // list form. This should fail with a message to the user.
+    $edit = [];
+    $edit['modules[Testing][composer_uninstallable][enable]'] = 'composer_uninstallable';
+    $this->drupalPostForm('admin/modules', $edit, t('Install'));
+
+    $this->assertText('The following extensions have unmet Composer-based dependencies: composer_uninstallable (scalopus/empty: ^1.1, jellyfish/empty: ~2.0, ordinal/empty: 4.0.*). Read the documentation on drupal.org on how to install them.');
+    // Makes sure the module was NOT installed.
+    $this->assertModules(['composer_uninstallable'], FALSE);
+  }
+
+  /**
+   * Tests that a module with installed dependencies is installable.
+   */
+  public function testComposerDependenciesSuccess() {
+    $this->assertModules(['composer_installable'], FALSE);
+
+    // Attempt to install the composer_installable module.
+    $edit = [];
+    $edit['modules[Testing][composer_installable][enable]'] = 'composer_installable';
+    $this->drupalPostForm('admin/modules', $edit, t('Install'));
+
+    // Makes sure the module was installed.
+    $this->assertModules(['composer_installable'], TRUE);
+  }
+
 }
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index 1f4c3ad..9b68b03 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -240,6 +240,11 @@ function system_requirements($phase) {
     $requirements['php_extensions']['value'] = t('Enabled');
   }
 
+  // Check Composer dependencies during runtime, because drupal_check_module()
+  // does it when installing modules already.
+  $dependency_requirements = \Drupal::service('composer.extension_dependency_requirements');
+  $requirements = array_merge($requirements, $dependency_requirements->buildRequirements(\Drupal::moduleHandler()->getModuleList()));
+
   if ($phase == 'install' || $phase == 'runtime') {
     // Check to see if OPcache is installed.
     $opcache_enabled = (function_exists('opcache_get_status') && opcache_get_status()['opcache_enabled']);
diff --git a/core/modules/system/tests/modules/composer_installable/composer.json b/core/modules/system/tests/modules/composer_installable/composer.json
new file mode 100644
index 0000000..04a5827
--- /dev/null
+++ b/core/modules/system/tests/modules/composer_installable/composer.json
@@ -0,0 +1,6 @@
+{
+  "name": "drupal_test/composer_installable",
+  "require": {
+    "psr/log": "~1.0"
+  }
+}
diff --git a/core/modules/system/tests/modules/composer_installable/composer_installable.info.yml b/core/modules/system/tests/modules/composer_installable/composer_installable.info.yml
new file mode 100644
index 0000000..399e267
--- /dev/null
+++ b/core/modules/system/tests/modules/composer_installable/composer_installable.info.yml
@@ -0,0 +1,6 @@
+name: 'Composer Installable'
+type: module
+description: 'Test module that is installable because its composer dependencies have already been installed'
+package: Testing
+version: VERSION
+core: 8.x
diff --git a/core/modules/system/tests/modules/composer_uninstallable/composer.json b/core/modules/system/tests/modules/composer_uninstallable/composer.json
new file mode 100644
index 0000000..9e56b74
--- /dev/null
+++ b/core/modules/system/tests/modules/composer_uninstallable/composer.json
@@ -0,0 +1,8 @@
+{
+  "name": "drupal_test/composer_uninstallable",
+  "require": {
+    "scalopus/empty": "^1.1",
+    "jellyfish/empty": "~2.0",
+    "ordinal/empty": "4.0.*"
+  }
+}
diff --git a/core/modules/system/tests/modules/composer_uninstallable/composer_uninstallable.info.yml b/core/modules/system/tests/modules/composer_uninstallable/composer_uninstallable.info.yml
new file mode 100644
index 0000000..a7f734e
--- /dev/null
+++ b/core/modules/system/tests/modules/composer_uninstallable/composer_uninstallable.info.yml
@@ -0,0 +1,6 @@
+name: 'Composer Uninstallable'
+type: module
+description: 'Test module that is not installable because of missing composer dependencies.'
+package: Testing
+version: VERSION
+core: 8.x
diff --git a/core/tests/Drupal/Tests/Core/Composer/ExtensionDependencyCheckerTest.php b/core/tests/Drupal/Tests/Core/Composer/ExtensionDependencyCheckerTest.php
new file mode 100644
index 0000000..5a70c3c
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Composer/ExtensionDependencyCheckerTest.php
@@ -0,0 +1,151 @@
+<?php
+
+namespace Drupal\Tests\Core\Composer;
+
+use Drupal\Core\Composer\ExtensionDependencyChecker;
+use Drupal\Core\Extension\Extension;
+use Drupal\Tests\UnitTestCase;
+use org\bovigo\vfs\vfsStream;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Composer\ExtensionDependencyChecker
+ *
+ * @group Composer
+ */
+class ExtensionDependencyCheckerTest extends UnitTestCase {
+
+  /**
+   * Provides data to self::testDependenciesAreMet().
+   *
+   * @return array[]
+   *   Every item is an array with the following items:
+   *   - A boolean TRUE if dependencies are expected to be met, FALSE otherwise.
+   *   - Contents of module's composer.json file, or NULL if there is no file.
+   *   - Contents of project's installed.json file, or NULL if there is no file.
+   */
+  public function providerDependenciesAreMet() {
+    return [
+      // No Composer dependencies.
+      'no dependencies' => [
+        TRUE,
+        NULL,
+        NULL,
+      ],
+      // No Composer dependencies, without any installed packages.
+      'no dependencies without installed packages' => [
+        TRUE,
+        NULL,
+        '[]',
+      ],
+      // No Composer dependencies, with an empty extension Composer file.
+      'no dependencies with empty composer.json' => [
+        TRUE,
+        '{}',
+        '[]',
+      ],
+      // One met dependency using different version constraints.
+      'one met dependency with specific version constraint' => [
+        TRUE,
+        '{"require": {"vendor/package":"1.0.0"}}',
+        '[{"name":"vendor/package","version_constraint":"1.0.0"}]'
+      ],
+      'one met dependency with compatible version constraint' => [
+        TRUE,
+        '{"require": {"vendor/package":"~1.0"}}',
+        '[{"name":"vendor/package","version_constraint":"1.0.0"}]'
+      ],
+      // One unmet dependency, without any installed packages.
+      'one unmet dependency dependency without installed packages' => [
+        FALSE,
+        '{"require": {"vendor/not-installed":"1.0.0"}}',
+        '[]',
+      ],
+      // One unmet dependency, with another package installed.
+      'one unmet dependency with installed packages' => [
+        FALSE,
+        '{"require": {"vendor/not-installed":"1.0.0"}}',
+        '[{"name":"vendor/package","version_constraint":"1.0.0"}]'
+      ],
+      // One unmet dependency using different version constraints.
+      'one unmet dependency with specific version constraint' => [
+        FALSE,
+        '{"require": {"vendor/package":"1.1.1"}}',
+        '[{"name":"vendor/package","version_constraint":"1.0.0"}]'
+      ],
+      'one unmet dependency with compatible version constraint' => [
+        FALSE,
+        '{"require": {"vendor/package":"~1.1"}}',
+        '[{"name":"vendor/package","version_constraint":"1.0.0"}]'
+      ],
+      // One unmet require-dev dependency, which is ignored, because it is not
+      // required for normal site operation.
+      'one unmet require-dev dependency with specific version constraint without installed packages' => [
+        TRUE,
+        '{"require-dev": {"vendor/package":"~1.1"}}',
+        NULL,
+      ],
+      'one unmet require-dev dependency with compatible version constraint with installed packages' => [
+        TRUE,
+        '{"require-dev": {"vendor/package":"1.1.1"}}',
+        '[{"name":"vendor/package","version_constraint":"1.0.0"}]'
+      ],
+      'one unmet require-dev dependency with compatible version constraint' => [
+        TRUE,
+        '{"require-dev": {"vendor/package":"~1.1"}}',
+        '[{"name":"vendor/package","version_constraint":"1.0.0"}]'
+      ],
+      // Platform dependencies are ignored.
+      'ignore platform dependencies' => [
+        TRUE,
+        '{"require": {"php":">10.0"}}',
+        '[{"name":"php","version_constraint":"1.0.0"}]'
+      ],
+      // Replaced dependencies use version constraints.
+      'one met replaced dependency' => [
+        TRUE,
+        '{"require": {"drupal/core":"~8.0"}}',
+        '[{"name":"drupal/core","version_constraint":"~8.2"}]'
+      ],
+      'one unmet replaced dependency' => [
+        FALSE,
+        '{"require": {"drupal/core":"~8.0"}}',
+        '[{"name":"drupal/core","version_constraint":"~9.0"}]'
+      ],
+    ];
+  }
+
+  /**
+   * @covers ::dependenciesAreMet
+   * @covers ::getUnmetDependencies
+   * @covers ::getInstalledPackages
+   *
+   * @dataProvider providerDependenciesAreMet
+   */
+  public function testDependenciesAreMet($expected, $composer_json, $installed_packages_json) {
+    $root_directory_structure = [
+      'core' => [
+        'composer' => [
+          'installed-packages.schema.json' => file_get_contents(__DIR__ . '/../../../../../composer/installed-packages.schema.json'),
+        ],
+      ],
+      'modules' => [
+        'some_module' => [
+          'some_module.info.yml' => '',
+        ],
+      ],
+    ];
+    if ($composer_json !== NULL) {
+      $root_directory_structure['modules']['some_module']['composer.json'] = $composer_json;
+    }
+    if ($installed_packages_json !== NULL) {
+      $root_directory_structure['core']['composer']['installed-packages.json'] = $installed_packages_json;
+    }
+    vfsStream::setup('root', NULL, $root_directory_structure);
+
+    $extension = new Extension(vfsStream::url('root'), 'module', 'modules/some_module/some_module.info.yml');
+    $sut = new ExtensionDependencyChecker(vfsStream::url('root'));
+
+    $this->assertEquals($expected, $sut->dependenciesAreMet($extension));
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Composer/ExtensionDependencyRequirementsTest.php b/core/tests/Drupal/Tests/Core/Composer/ExtensionDependencyRequirementsTest.php
new file mode 100644
index 0000000..dd4fc1f
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Composer/ExtensionDependencyRequirementsTest.php
@@ -0,0 +1,74 @@
+<?php
+
+namespace Drupal\Tests\Core\Composer;
+
+use Drupal\Core\Composer\ExtensionDependencyChecker;
+use Drupal\Core\Composer\ExtensionDependencyRequirements;
+use Drupal\Core\Extension\Extension;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\KernelTests\KernelTestBase;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Composer\ExtensionDependencyRequirements
+ *
+ * @group Composer
+ */
+class ExtensionDependencyRequirementsTest extends KernelTestBase {
+
+  /**
+   * Provides data to self::testBuildRequirements().
+   *
+   * @return array[]
+   *   Every item is an array with the following items:
+   *   - One of the REQUIREMENT_* constants.
+   *   - An unmet dependency with a package name as key and version constraint
+   *     as value.
+   */
+  public function providerBuildRequirements() {
+    // We cannot use any of the REQUIREMENT_* constants here, because providers
+    // are run before environments are booted.
+    return [
+      // REQUIREMENT_ERROR is 2.
+      'REQUIREMENT_ERROR' => [
+        2,
+        [
+          'scalopus/empty' => '^1.1',
+        ],
+      ],
+      // REQUIREMENT_OK is 0.
+      'REQUIREMENT_OK' => [
+        0,
+        [],
+      ],
+    ];
+  }
+
+  /**
+   * @covers ::buildRequirements
+   *
+   * @dataProvider providerBuildRequirements
+   *
+   * @param int $expected_severity
+   *   One of the REQUIREMENT_* constants.
+   * @param string[] $unmet_dependencies
+   *   An array of unmet dependences, with the package name as key and version
+   *   constraint as value.
+   */
+  public function testBuildRequirements($expected_severity, $unmet_dependencies) {
+    $extension = $this->prophesize(Extension::class);
+
+    $dependency_checker = $this->prophesize(ExtensionDependencyChecker::class);
+    $dependency_checker->getUnmetDependencies($extension->reveal())->willReturn($unmet_dependencies);
+
+    $string_translation = $this->getMock(TranslationInterface::class);
+
+    $sut = new ExtensionDependencyRequirements($dependency_checker->reveal(), $string_translation);
+
+    $requirements = $sut->buildRequirements([$extension->reveal()]);
+    $this->assertCount(1, $requirements);
+    $this->assertArrayHasKey('composer_dependencies', $requirements);
+    $this->assertArrayHasKey('severity', $requirements['composer_dependencies']);
+    $this->assertSame($expected_severity, $requirements['composer_dependencies']['severity']);
+  }
+
+}
