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 132fba5..97661ba 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "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": [
         {
@@ -890,6 +890,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": {
diff --git a/core/composer.json b/core/composer.json
index 3109649..d72e7c5 100644
--- a/core/composer.json
+++ b/core/composer.json
@@ -31,7 +31,8 @@
         "symfony/psr-http-message-bridge": "v0.2",
         "zendframework/zend-diactoros": "~1.1",
         "composer/semver": "~1.0",
-        "paragonie/random_compat": "~1.0"
+        "paragonie/random_compat": "~1.0",
+        "justinrainbow/json-schema": "~1.0"
     },
     "require-dev": {
         "behat/mink": "~1.7",
@@ -162,6 +163,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-extensions.schema.json b/core/composer/installed-extensions.schema.json
new file mode 100644
index 0000000..2f5462c
--- /dev/null
+++ b/core/composer/installed-extensions.schema.json
@@ -0,0 +1,12 @@
+{
+  "description": "Drupal extensions installed through Composer. Properties are package names and values are their installation paths, relative to Drupal's app root.",
+  "type": "object",
+  "items": [
+    {
+      "description": "A package's installation path, relative to the app root, without leading slashes, and with a trailing slash.",
+      "type": "string",
+      "required": true,
+      "pattern": "^[^/]{1}.+?\/$"
+    }
+  ]
+}
diff --git a/core/composer/installed-modules.json b/core/composer/installed-modules.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/core/composer/installed-modules.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/core/core.services.yml b/core/core.services.yml
index 2f7da56..91c6beb 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..711d3ad 100644
--- a/core/lib/Drupal/Core/Composer/Composer.php
+++ b/core/lib/Drupal/Core/Composer/Composer.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\Core\Composer;
 
+use Composer\Package\PackageInterface;
 use Drupal\Component\PhpStorage\FileStorage;
 use Composer\Script\Event;
 use Composer\Installer\PackageEvent;
@@ -246,4 +247,34 @@ 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) {
+    $app_root = realpath(__DIR__ . '/../../../../..');
+    $composer = $event->getComposer();
+
+    $installed_packages = $composer->getRepositoryManager()->getLocalRepository()->getPackages();
+    $installed_module_packages = array_filter($installed_packages, function (PackageInterface $package) {
+      return in_array($package->getType(), ['drupal-module', 'drupal-profile', 'drupal-theme']);
+    });
+
+    $installed_module_paths = array_reduce($installed_module_packages, function ($installed_module_paths, PackageInterface $package) use ($app_root, $composer) {
+      $absolute_installation_path = getcwd() . '/' . $composer->getInstallationManager()->getInstaller($package->getType())->getInstallPath($package);
+      // Drupal modules must be placed in one of several subdirectories of the
+      // app root.
+      if (strpos($absolute_installation_path, $app_root) !== 0) {
+        throw new ComposerIntegrationException(sprintf('Composer package "%s" is a Drupal module and must be installed in a subdirectory of %s, but was installed in %s. You may need to use the "composer/installers" package (https://packagist.org/packages/composer/installers).', $package->getName(), $app_root, $absolute_installation_path));
+      }
+      $installed_module_paths[$package->getName()] = trim(substr($absolute_installation_path, strlen($app_root)), '/');
+
+      return $installed_module_paths;
+    }, []);
+
+    $installed_dump_file_path = $app_root . '/core/composer/installed-extensions.json';
+    file_put_contents($installed_dump_file_path, json_encode((object) $installed_module_paths, 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..592b076
--- /dev/null
+++ b/core/lib/Drupal/Core/Composer/ComposerIntegrationException.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace Drupal\Core\Composer;
+
+/**
+ * Exception thrown when Drupal's Composer integration is missing.
+ */
+class ComposerIntegrationException extends \Exception {
+}
diff --git a/core/lib/Drupal/Core/Composer/ExtensionDependencyChecker.php b/core/lib/Drupal/Core/Composer/ExtensionDependencyChecker.php
new file mode 100644
index 0000000..36e107e
--- /dev/null
+++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyChecker.php
@@ -0,0 +1,164 @@
+<?php
+
+namespace Drupal\Core\Composer;
+
+use Drupal\Core\Extension\Extension;
+use JsonSchema\RefResolver;
+use JsonSchema\Uri\UriRetriever;
+use JsonSchema\Validator;
+
+/**
+ * Checks Drupal modules's Composer integration.
+ *
+ * Note: "installation" in the context of Composer means to add a package to the
+ * code base. In Drupal, it means to enable an extension's runtime integration
+ * with Drupal.
+ */
+class ExtensionDependencyChecker {
+
+  /**
+   * The path to the application's root directory.
+   *
+   * @var string
+   */
+  protected $appRoot;
+
+  /**
+   * The installation paths of Drupal modules included through Composer.
+   *
+   * @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 their installation
+   *   paths.
+   *
+   * @see self::getModulePackageInstallationPaths()
+   */
+  protected $modulePackageInstallationPaths;
+
+  /**
+   * 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 a Drupal extension must be installed through Composer.
+   *
+   * @param \Drupal\Core\Extension\Extension $extension
+   *   The extension to check.
+   *
+   * @return bool
+   *   TRUE if this extension has been installed through Composer, or FALSE
+   *   otherwise.
+   *
+   * @throws \Drupal\Core\Composer\ComposerIntegrationException
+   *   Thrown if this Drupal installation has not been fully integrated with
+   *   Composer yet.
+   */
+  public function requiresInstallation(Extension $extension) {
+    // Find all directories of potential Composer packages this extension can
+    // belong to.
+    $directory = $this->appRoot . '/' . $extension->getPath();
+    $directories = [$directory];
+    while (($directory = dirname($directory)) && !in_array($directory, $directories)) {
+      $directories[] = $directory;
+    }
+
+    // Filter out any directories that are no subdirectories of the app root and
+    // therefore cannot contain Composer packages.
+    $potential_package_directories = array_filter($directories, function ($directory) {
+      return preg_match('#^' . preg_quote($this->appRoot) . '/.+?$#', $directory) === 1;
+    });
+
+    // Filter out any directories without Composer files.
+    $package_directories = array_filter($potential_package_directories, function ($directory) {
+      return file_exists($directory . '/composer.json');
+    });
+
+    // Filter out drupal/core, because drupal/drupal uses
+    // wikimedia/composer-merge-plugin.
+    $package_directories = array_filter($package_directories, function ($directory) {
+      return $directory !== $this->appRoot . '/core';
+    });
+
+    // If no directories with Composer files are found, this extension is not
+    // part of a Composer package, and it does not need to be installed through
+    // Composer.
+    if (empty($package_directories)) {
+      return FALSE;
+    }
+
+    // If none of the directories of Composer packages the extension belongs to
+    // is present in the list of directories of installed packages, that means
+    // none of the packages the extension belongs to has been installed.
+    $requires_installation = empty(array_intersect($this->getModulePackageInstallationPaths(), $package_directories));
+
+    return $requires_installation;
+  }
+
+  /**
+   * Gets information about installed Composer packages.
+   *
+   * @return string[]
+   *   Keys are package names and values are their installation paths.
+   *
+   * @throws \Drupal\Core\Composer\ComposerIntegrationException
+   *   Thrown if this Drupal installation has not been fully integrated with
+   *   Composer yet.
+   */
+  protected function getModulePackageInstallationPaths() {
+    // Quickly return the cached list, if it exists.
+    if (is_array($this->modulePackageInstallationPaths)) {
+      return $this->modulePackageInstallationPaths;
+    }
+
+    $installed_module_packages_json_path = $this->appRoot . '/core/composer/installed-extensions.json';
+
+    // If the JSON file does not exist, `composer install/update` failed or was
+    // never executed.
+    if (!file_exists($installed_module_packages_json_path)) {
+      throw new ComposerIntegrationException(sprintf('%s does not exist.', $installed_module_packages_json_path));
+    }
+
+    $module_package_installation_paths_json = file_get_contents($installed_module_packages_json_path, FALSE);
+    $module_package_installation_paths_data = json_decode($module_package_installation_paths_json);
+
+    // Check if the content is valid JSON.
+    if ($module_package_installation_paths_json !== 'null' && is_null($module_package_installation_paths_data)) {
+      throw new ComposerIntegrationException(sprintf('%s does not contain valid JSON.', $installed_module_packages_json_path));
+    }
+
+    // Validate the message against its JSON schema.
+    $installed_packages_json_schema_path = $this->appRoot . '/core/composer/installed-extensions.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($module_package_installation_paths_data, $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_module_packages_json_path, $installed_packages_json_schema_path, $error['property'], $error['message']));
+    }
+
+    // Because our data is a hash/dictionary, we use an object in JSON which we
+    // need to cast to an associative array in PHP.
+    $module_package_installation_paths = (array) $module_package_installation_paths_data;
+
+    // Make all paths absolute.
+    $module_package_installation_paths = array_map(function ($path) {
+      return $this->appRoot . '/' . $path;
+    }, $module_package_installation_paths);
+
+    $this->modulePackageInstallationPaths = $module_package_installation_paths;
+
+    return $this->modulePackageInstallationPaths;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Composer/ExtensionDependencyRequirements.php b/core/lib/Drupal/Core/Composer/ExtensionDependencyRequirements.php
new file mode 100644
index 0000000..ca8e91c
--- /dev/null
+++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyRequirements.php
@@ -0,0 +1,77 @@
+<?php
+
+namespace Drupal\Core\Composer;
+
+use Drupal\Core\Extension\Extension;
+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'),
+      ],
+    ];
+    $uninstalled_extensions = array_filter($extensions, function (Extension $extension) {
+      return $this->dependencyChecker->requiresInstallation($extension);
+    });
+    $uninstalled_extension_names = array_map(function (Extension $extension) {
+      return $extension->getName();
+    }, $uninstalled_extensions);
+    if ($uninstalled_extension_names) {
+      $requirements['composer_dependencies'] += [
+        'description' => $this->t('The following extensions must be added through Composer: @extensions. Read the <a href=":documentation">documentation on drupal.org</a> on how to install them.', [
+          '@extensions' => implode(', ', $uninstalled_extension_names),
+          ':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;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Extension/ExtensionComposerRequirementsException.php b/core/lib/Drupal/Core/Extension/ExtensionComposerRequirementsException.php
new file mode 100644
index 0000000..9949804
--- /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 translator.
+   *
+   * @return \Drupal\Core\StringTranslation\TranslatableMarkup
+   *   The translatable exception message.
+   */
+  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..c7197b6 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->requiresInstallation($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..9404271 100644
--- a/core/lib/Drupal/Core/Extension/ModuleInstallerInterface.php
+++ b/core/lib/Drupal/Core/Extension/ModuleInstallerInterface.php
@@ -36,6 +36,8 @@
    *
    * @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()
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/system.install b/core/modules/system/system.install
index b3a0ec1..983f956 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..60a0842
--- /dev/null
+++ b/core/modules/system/tests/modules/composer_uninstallable/composer.json
@@ -0,0 +1,3 @@
+{
+  "name": "drupal_test/composer_uninstallable"
+}
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/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php b/core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php
index 9beac7a..7a3e607 100644
--- a/core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php
+++ b/core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php
@@ -95,7 +95,7 @@ protected function assertModuleList(array $expected_values, $condition) {
    * Tests dependency resolution.
    *
    * Intentionally using fake dependencies added via hook_system_info_alter()
-   * for modules that normally do not have any dependencies.
+   * for modules that normally do not have any module-based dependencies.
    *
    * To simplify things further, all of the manipulated modules are either
    * purely UI-facing or live at the "bottom" of all dependency chains.
@@ -120,13 +120,13 @@ function testDependencyResolution() {
 
     try {
       $result = $this->moduleInstaller()->install(array('color'));
-      $this->fail(t('ModuleInstaller::install() throws an exception if dependencies are missing.'));
+      $this->fail(t('ModuleInstaller::install() throws an exception if module-based dependencies are missing.'));
     }
     catch (MissingDependencyException $e) {
-      $this->pass(t('ModuleInstaller::install() throws an exception if dependencies are missing.'));
+      $this->pass(t('ModuleInstaller::install() throws an exception if module-based dependencies are missing.'));
     }
 
-    $this->assertFalse($this->moduleHandler()->moduleExists('color'), 'ModuleHandler::install() aborts if dependencies are missing.');
+    $this->assertFalse($this->moduleHandler()->moduleExists('color'), 'ModuleHandler::install() aborts if module-based dependencies are missing.');
 
     // Fix the missing dependency.
     // Color module depends on Config. Config depends on Help module.
@@ -140,7 +140,7 @@ function testDependencyResolution() {
     $this->assertTrue($this->moduleHandler()->moduleExists('config') && $this->moduleHandler()->moduleExists('help'), 'Dependency chain was installed.');
 
     // Verify that the original module was installed.
-    $this->assertTrue($this->moduleHandler()->moduleExists('color'), 'Module installation with dependencies succeeded.');
+    $this->assertTrue($this->moduleHandler()->moduleExists('color'), 'Module installation with module-based dependencies succeeded.');
 
     // Verify that the modules were enabled in the correct order.
     $module_order = \Drupal::state()->get('module_test.install_order') ?: array();
@@ -172,7 +172,7 @@ function testDependencyResolution() {
     $this->assertTrue($this->moduleHandler()->moduleExists('config') && $this->moduleHandler()->moduleExists('help'), 'Dependency chain was installed.');
 
     // Verify that the original module was installed.
-    $this->assertTrue($this->moduleHandler()->moduleExists('color'), 'Module installation with version dependencies succeeded.');
+    $this->assertTrue($this->moduleHandler()->moduleExists('color'), 'Module installation with versioned module-based dependencies succeeded.');
 
     // Finally, verify that the modules were enabled in the correct order.
     $enable_order = \Drupal::state()->get('module_test.install_order') ?: array();
diff --git a/core/tests/Drupal/KernelTests/Core/Extension/ModuleInstallerTest.php b/core/tests/Drupal/KernelTests/Core/Extension/ModuleInstallerTest.php
index c15e7fb..f43f300 100644
--- a/core/tests/Drupal/KernelTests/Core/Extension/ModuleInstallerTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Extension/ModuleInstallerTest.php
@@ -3,6 +3,7 @@
 namespace Drupal\KernelTests\Core\Extension;
 
 use Drupal\Core\Database\Database;
+use Drupal\Core\Extension\ExtensionComposerRequirementsException;
 use Drupal\KernelTests\KernelTestBase;
 use Symfony\Component\Routing\Exception\RouteNotFoundException;
 
@@ -44,4 +45,15 @@ public function testRouteRebuild() {
     $this->container->get('router.route_provider')->getRouteByName('router_test.1');
   }
 
+  /**
+   * @covers ::install
+   */
+  public function testInstallWithModuleWithoutRequiredComposerInstallation() {
+    $module_name = 'composer_uninstallable';
+    /** @var \Drupal\Core\Extension\ModuleInstallerInterface $installer */
+    $installer = $this->container->get('module_installer');
+    $this->setExpectedException(ExtensionComposerRequirementsException::class);
+    $installer->install([$module_name], TRUE);
+  }
+
 }
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..39ec1a0
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Composer/ExtensionDependencyCheckerTest.php
@@ -0,0 +1,157 @@
+<?php
+
+namespace Drupal\Tests\Core\Composer;
+
+use Drupal\Component\Utility\NestedArray;
+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::testRequiresInstallation().
+   *
+   * @return array[]
+   */
+  public function providerRequiresInstallation() {
+    return [
+      'The module is no Composer package. No modules have been installed through Composer.' => [
+        FALSE,
+        '{}',
+        'modules/some_module',
+        FALSE,
+      ],
+      'The module is no Composer package. Some modules have been installed through Composer.' => [
+        FALSE,
+        '{"drupal/foo":"modules/foo"}',
+        'modules/some_module',
+        FALSE,
+      ],
+      'The module is a Composer package and has been installed through Composer.' => [
+        FALSE,
+        '{"drupal/some_module":"modules/some_module"}',
+        'modules/some_module',
+        TRUE,
+      ],
+      'The module is a Composer package. No modules have been installed through Composer.' => [
+        TRUE,
+        '{}',
+        'modules/some_module',
+        TRUE,
+      ],
+      'The module is a Composer package. Some modules have been installed through Composer.' => [
+        TRUE,
+        '{"drupal/foo":"modules/foo"}',
+        'modules/some_module',
+        TRUE,
+      ],
+      'The module is a submodule and no Composer package. Its parent module is no Composer package. No modules have been installed through Composer.' => [
+        FALSE,
+        '{}',
+        'modules/some_parent_module/modules/some_module',
+        FALSE,
+        'modules/some_parent_module',
+        FALSE,
+      ],
+      'The module is a submodule and no Composer package. Its parent module is no Composer package. Some modules have been installed through Composer.' => [
+        FALSE,
+        '{"drupal/foo":"modules/foo"}',
+        'modules/some_parent_module/modules/some_module',
+        FALSE,
+        'modules/some_parent_module',
+        FALSE,
+      ],
+      'The module is a submodule and no Composer package. Its parent module is a Composer package. No modules have been installed through Composer.' => [
+        TRUE,
+        '{}',
+        'modules/some_parent_module/modules/some_module',
+        FALSE,
+        'modules/some_parent_module',
+        TRUE,
+      ],
+      'The module is a submodule and no Composer package. Its parent module is a Composer package. Some modules have been installed through Composer.' => [
+        TRUE,
+        '{"drupal/foo":"modules/foo"}',
+        'modules/some_parent_module/modules/some_module',
+        FALSE,
+        'modules/some_parent_module',
+        TRUE,
+      ],
+      'The module is a submodule and a Composer package. Its parent module is a Composer package. No modules have been installed through Composer.' => [
+        TRUE,
+        '{}',
+        'modules/some_parent_module/modules/some_module',
+        TRUE,
+        'modules/some_parent_module',
+        TRUE,
+      ],
+      'The module is a submodule and a Composer package. Its parent module is a Composer package. Some modules have been installed through Composer.' => [
+        TRUE,
+        '{"drupal/foo":"modules/foo"}',
+        'modules/some_parent_module/modules/some_module',
+        TRUE,
+        'modules/some_parent_module',
+        TRUE,
+      ],
+      'The module is a submodule and no Composer package. Its parent module is a Composer package and has been installed through Composer.' => [
+        FALSE,
+        '{"drupal/some_parent_module":"modules/some_parent_module"}',
+        'modules/some_parent_module/modules/some_module',
+        FALSE,
+        'modules/some_parent_module',
+        TRUE,
+      ],
+      'The module is a submodule and a Composer package. Its parent module is a Composer package and has been installed through Composer.' => [
+        FALSE,
+        '{"drupal/some_parent_module":"modules/some_parent_module"}',
+        'modules/some_parent_module/modules/some_module',
+        TRUE,
+        'modules/some_parent_module',
+        TRUE,
+      ],
+    ];
+  }
+
+  /**
+   * @covers ::requiresInstallation
+   * @covers ::getModulePackageInstallationPaths
+   *
+   * @dataProvider providerRequiresInstallation
+   */
+  public function testRequiresInstallation($expected, $installed_modules_json, $module_root_path, $module_has_composer_json, $parent_module_root_path = NULL, $parent_module_has_composer_json = FALSE) {
+    $root_directory_structure = [
+      'core' => [
+        'composer' => [
+          'installed-extensions.schema.json' => file_get_contents(__DIR__ . '/../../../../../composer/installed-extensions.schema.json'),
+        ],
+      ],
+    ];
+    NestedArray::setValue($root_directory_structure, explode('/', $module_root_path . '/some_module.info.yml'), '');
+    if ($module_has_composer_json) {
+      NestedArray::setValue($root_directory_structure, explode('/', $module_root_path . '/composer.json'), '{}');
+    }
+    if ($parent_module_root_path) {
+      NestedArray::setValue($root_directory_structure, explode('/', $parent_module_root_path . '/some_parent_module.info.yml'), '');
+      if ($parent_module_has_composer_json) {
+        NestedArray::setValue($root_directory_structure, explode('/', $parent_module_root_path . '/composer.json'), '{}');
+      }
+    }
+    if ($installed_modules_json) {
+      $root_directory_structure['core']['composer']['installed-extensions.json'] = $installed_modules_json;
+    }
+    vfsStream::setup('root', NULL, $root_directory_structure);
+
+    $extension = new Extension(vfsStream::url('root'), 'module', $module_root_path . '/some_module.info.yml');
+    $sut = new ExtensionDependencyChecker(vfsStream::url('root'));
+
+    $this->assertEquals($expected, $sut->requiresInstallation($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..90921c2
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Composer/ExtensionDependencyRequirementsTest.php
@@ -0,0 +1,71 @@
+<?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,
+        TRUE,
+      ],
+      // REQUIREMENT_OK is 0.
+      'REQUIREMENT_OK' => [
+        0,
+        FALSE,
+      ],
+    ];
+  }
+
+  /**
+   * @covers ::buildRequirements
+   *
+   * @dataProvider providerBuildRequirements
+   *
+   * @param int $expected_severity
+   *   One of the \REQUIREMENT_* constants.
+   * @param bool $requires_installation
+   *   TRUE if the extension needs to be installed through Composer.
+   */
+  public function testBuildRequirements($expected_severity, $requires_installation) {
+    $extension = $this->prophesize(Extension::class);
+    $extension->getName()->willReturn('some_module');
+
+    $dependency_checker = $this->prophesize(ExtensionDependencyChecker::class);
+    $dependency_checker->requiresInstallation($extension->reveal())->willReturn($requires_installation);
+
+    $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']);
+  }
+
+}
