From 0afcc084aa584f5d50a839627379404078dfedc1 Mon Sep 17 00:00:00 2001
From: William Hearn <sylus1984@gmail.com>
Date: Fri, 11 Dec 2020 08:59:29 -0500
Subject: [PATCH] #1356276 adding patch in #622 plus a few changes

---
 core/core.services.yml                        |   2 +-
 core/includes/install.core.inc                |  67 +++++---
 core/includes/install.inc                     |  13 +-
 .../Drupal/Core/Config/ConfigInstaller.php    |   6 +-
 .../Core/Config/ExtensionInstallStorage.php   |  12 +-
 .../lib/Drupal/Core/Config/InstallStorage.php |  17 +-
 .../ConfigImportSubscriber.php                |  13 +-
 .../Core/Extension/ExtensionDiscovery.php     |  28 ++-
 .../Core/Extension/ModuleExtensionList.php    |  19 +--
 .../Core/Extension/ProfileExtensionList.php   | 161 +++++++++++++++++-
 .../InstallerProfileExtensionList.php         |  58 +++++++
 .../config_test/src/TestInstallStorage.php    |   7 +-
 .../ConfigImportBaseInstallProfileTest.php    |  96 +++++++++++
 .../system/src/Form/ModulesUninstallForm.php  |   3 +-
 .../install/block.block.stable_login.yml      |  19 +++
 .../config/install/system.theme.yml           |   2 +
 .../child_profile_module.info.yml             |   6 +
 .../contrib_child_profile_module.info.yml     |   6 +
 .../custom_child_profile_module.info.yml      |   6 +
 .../testing_inherited.info.yml                |  17 ++
 .../src/Functional/InheritedProfileTest.php   |  46 +++++
 .../testing_inherited_standard.info.yml       |  12 ++
 .../src/Functional/InheritedProfileTest.php   |  31 ++++
 .../config/install/system.theme.yml           |   2 +
 .../grandchild_profile_module.info.yml        |   6 +
 .../testing_subsubprofile.info.yml            |  11 ++
 .../Functional/DeepInheritedProfileTest.php   |  38 +++++
 .../Core/Config/ConfigImporterTest.php        |   4 +-
 28 files changed, 641 insertions(+), 67 deletions(-)
 create mode 100644 core/lib/Drupal/Core/Installer/InstallerProfileExtensionList.php
 create mode 100644 core/modules/config/tests/src/Functional/ConfigImportBaseInstallProfileTest.php
 create mode 100644 core/profiles/testing_inherited/config/install/block.block.stable_login.yml
 create mode 100644 core/profiles/testing_inherited/config/install/system.theme.yml
 create mode 100644 core/profiles/testing_inherited/modules/child_profile_module/child_profile_module.info.yml
 create mode 100644 core/profiles/testing_inherited/modules/contrib/contrib_child_profile_module/contrib_child_profile_module.info.yml
 create mode 100644 core/profiles/testing_inherited/modules/custom/custom_child_profile_module/custom_child_profile_module.info.yml
 create mode 100644 core/profiles/testing_inherited/testing_inherited.info.yml
 create mode 100644 core/profiles/testing_inherited/tests/src/Functional/InheritedProfileTest.php
 create mode 100644 core/profiles/testing_inherited_standard/testing_inherited_standard.info.yml
 create mode 100644 core/profiles/testing_inherited_standard/tests/src/Functional/InheritedProfileTest.php
 create mode 100644 core/profiles/testing_subsubprofile/config/install/system.theme.yml
 create mode 100644 core/profiles/testing_subsubprofile/modules/grandchild_profile_module/grandchild_profile_module.info.yml
 create mode 100644 core/profiles/testing_subsubprofile/testing_subsubprofile.info.yml
 create mode 100644 core/profiles/testing_subsubprofile/tests/src/Functional/DeepInheritedProfileTest.php

diff --git a/core/core.services.yml b/core/core.services.yml
index c42ca2dd21..baa7c7a306 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -314,7 +314,7 @@ services:
       - { name: event_subscriber }
   config.installer:
     class: Drupal\Core\Config\ConfigInstaller
-    arguments: ['@config.factory', '@config.storage', '@config.typed', '@config.manager', '@event_dispatcher', '%install_profile%']
+    arguments: ['@config.factory', '@config.storage', '@config.typed', '@config.manager', '@event_dispatcher', '%install_profile%', '@extension.list.profile']
     lazy: true
   config.storage:
     class: Drupal\Core\Config\CachedStorage
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index 52413b024e..b2d2e4693d 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -462,6 +462,12 @@ function install_begin_request($class_loader, &$install_state) {
     if (isset($install_state['profile_info']['distribution']['install']['theme'])) {
       $install_state['theme'] = $install_state['profile_info']['distribution']['install']['theme'];
     }
+    // Ensure all profile directories are registered.
+    $profiles = \Drupal::service('extension.list.profile')->getAncestors($profile);
+    $profile_directories = array_map(function($extension) {
+      return $extension->getPath();
+    }, $profiles);
+    $listing->setProfileDirectories($profile_directories);
   }
 
   // Before having installed the system module and being able to do a module
@@ -841,18 +847,22 @@ function install_tasks($install_state) {
 
   // Now add any tasks defined by the installation profile.
   if (!empty($install_state['parameters']['profile'])) {
-    // Load the profile install file, because it is not always loaded when
-    // hook_install_tasks() is invoked (e.g. batch processing).
-    $profile = $install_state['parameters']['profile'];
-    $profile_install_file = $install_state['profiles'][$profile]->getPath() . '/' . $profile . '.install';
-    if (file_exists($profile_install_file)) {
-      include_once \Drupal::root() . '/' . $profile_install_file;
-    }
-    $function = $install_state['parameters']['profile'] . '_install_tasks';
-    if (function_exists($function)) {
-      $result = $function($install_state);
-      if (is_array($result)) {
-        $tasks += $result;
+    $profiles = \Drupal::service('extension.list.profile')->getAncestors($install_state['parameters']['profile']);
+    foreach (array_keys($profiles) as $profile_name) {
+      $profile = $install_state['profiles'][$profile_name];
+      // Load the profile install file, because it is not always loaded when
+      // hook_install_tasks() is invoked (e.g. batch processing).
+      $profile_install_file = $profile->getPath() . '/' . $profile_name . '.install';
+      if (file_exists($profile_install_file)) {
+        include_once \Drupal::root() . '/' . $profile_install_file;
+      }
+      // If this is a base profile then the profile isn't loaded
+      $function = $profile_name . '_install_tasks';
+      if (function_exists($function)) {
+        $result = $function($install_state);
+        if (is_array($result)) {
+          $tasks += $result;
+        }
       }
     }
   }
@@ -870,11 +880,13 @@ function install_tasks($install_state) {
 
   // Allow the installation profile to modify the full list of tasks.
   if (!empty($install_state['parameters']['profile'])) {
-    $profile = $install_state['parameters']['profile'];
-    if ($install_state['profiles'][$profile]->load()) {
-      $function = $install_state['parameters']['profile'] . '_install_tasks_alter';
-      if (function_exists($function)) {
-        $function($tasks, $install_state);
+    $profiles = \Drupal::service('extension.list.profile')->getAncestors($install_state['parameters']['profile']);
+    foreach (array_keys($profiles) as $profile_name) {
+      if ($install_state['profiles'][$profile_name]->load()) {
+        $function = $profile_name . '_install_tasks_alter';
+        if (function_exists($function)) {
+          $function($tasks, $install_state);
+        }
       }
     }
   }
@@ -1264,7 +1276,9 @@ function install_select_profile(&$install_state) {
  *   - For interactive installations via request query parameters.
  *   - For non-interactive installations via install_drupal() settings.
  * - One of the available profiles is a distribution. If multiple profiles are
- *   distributions, then the first discovered profile will be selected.
+ *   distributions, then the first discovered profile will be selected. If an
+ *   inherited profile is detected that is a distribution, it will be chosen
+ *   over its base profile.
  * - Only one visible profile is available.
  *
  * @param array $install_state
@@ -1289,12 +1303,9 @@ function _install_select_profile(&$install_state) {
       return $profile;
     }
   }
-  // If any of the profiles are distribution profiles, return the first one.
-  foreach ($install_state['profiles'] as $profile) {
-    $profile_info = install_profile_info($profile->getName());
-    if (!empty($profile_info['distribution'])) {
-      return $profile->getName();
-    }
+  // Check for a distribution profile.
+  if ($distribution = \Drupal::service('extension.list.profile')->selectDistribution(array_keys($install_state['profiles']))) {
+    return $distribution;
   }
   // Get all visible (not hidden) profiles.
   $visible_profiles = array_filter($install_state['profiles'], function ($profile) {
@@ -1521,7 +1532,9 @@ function _install_get_version_info($version) {
  */
 function install_load_profile(&$install_state) {
   $profile = $install_state['parameters']['profile'];
-  $install_state['profiles'][$profile]->load();
+  foreach (array_keys(\Drupal::service('extension.list.profile')->getAncestors($profile)) as $profile_id) {
+    $install_state['profiles'][$profile_id]->load();
+  }
   $install_state['profile_info'] = install_profile_info($profile, isset($install_state['parameters']['langcode']) ? $install_state['parameters']['langcode'] : 'en');
 
   $sync_directory = Settings::get('config_sync_directory');
@@ -1654,6 +1667,10 @@ function install_install_profile(&$install_state) {
 
   \Drupal::service('module_installer')->install([$install_state['parameters']['profile']], FALSE);
 
+  // Install all the profiles.
+  $profiles = \Drupal::service('extension.list.profile')->getAncestors();
+  \Drupal::service('module_installer')->install(array_keys($profiles), FALSE);
+
   // Ensure that the install profile's theme is used.
   // @see _drupal_maintenance_theme()
   \Drupal::theme()->resetActiveTheme();
diff --git a/core/includes/install.inc b/core/includes/install.inc
index ce7df4759e..69a49d8f8c 100644
--- a/core/includes/install.inc
+++ b/core/includes/install.inc
@@ -531,7 +531,6 @@ function _drupal_rewrite_settings_dump_one(\stdClass $variable, $prefix = '', $s
  *   The list of modules to install.
  */
 function drupal_verify_profile($install_state) {
-  $profile = $install_state['parameters']['profile'];
   $info = $install_state['profile_info'];
 
   // Get the list of available modules for the selected installation profile.
@@ -540,10 +539,11 @@ function drupal_verify_profile($install_state) {
   foreach ($listing->scan('module') as $present_module) {
     $present_modules[] = $present_module->getName();
   }
-
-  // The installation profile is also a module, which needs to be installed
-  // after all the other dependencies have been installed.
-  $present_modules[] = $profile;
+  // Get the list of available profiles, which may be used as base profiles or
+  // ancestors of the selected installation profile.
+  foreach ($listing->scan('profile') as $present_profile) {
+    $present_modules[] = $present_profile->getName();
+  }
 
   // Verify that all of the profile's required modules are present.
   $missing_modules = array_diff($info['install'], $present_modules);
@@ -1064,6 +1064,9 @@ function drupal_check_module($module) {
  *       Drupal's default installer theme.
  *     - finish_url: A destination to visit after the installation of the
  *       distribution is finished
+ * - base profile: The shortname of the base installation profile. Existence of
+ *   this key denotes that the installation profile depends on a parent
+ *   installation profile.
  *
  * Note that this function does an expensive file system scan to get info file
  * information for dependencies. If you only need information from the info
diff --git a/core/lib/Drupal/Core/Config/ConfigInstaller.php b/core/lib/Drupal/Core/Config/ConfigInstaller.php
index 235a7c1a41..84eb5e1b31 100644
--- a/core/lib/Drupal/Core/Config/ConfigInstaller.php
+++ b/core/lib/Drupal/Core/Config/ConfigInstaller.php
@@ -4,6 +4,7 @@
 
 use Drupal\Component\Utility\Crypt;
 use Drupal\Core\Config\Entity\ConfigDependencyManager;
+use Drupal\Core\Extension\ProfileExtensionList;
 use Drupal\Core\Installer\InstallerKernel;
 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 
@@ -80,14 +81,17 @@ class ConfigInstaller implements ConfigInstallerInterface {
    *   The event dispatcher.
    * @param string $install_profile
    *   The name of the currently active installation profile.
+   * @param \Drupal\Core\Extension\ProfileExtensionList|null $profile_list
+   *   (optional) The profile list.
    */
-  public function __construct(ConfigFactoryInterface $config_factory, StorageInterface $active_storage, TypedConfigManagerInterface $typed_config, ConfigManagerInterface $config_manager, EventDispatcherInterface $event_dispatcher, $install_profile) {
+  public function __construct(ConfigFactoryInterface $config_factory, StorageInterface $active_storage, TypedConfigManagerInterface $typed_config, ConfigManagerInterface $config_manager, EventDispatcherInterface $event_dispatcher, $install_profile, ProfileExtensionList $profile_list = NULL) {
     $this->configFactory = $config_factory;
     $this->activeStorages[$active_storage->getCollectionName()] = $active_storage;
     $this->typedConfig = $typed_config;
     $this->configManager = $config_manager;
     $this->eventDispatcher = $event_dispatcher;
     $this->installProfile = $install_profile;
+    $this->profileList = $profile_list ?: \Drupal::service('extension.list.profile');
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Config/ExtensionInstallStorage.php b/core/lib/Drupal/Core/Config/ExtensionInstallStorage.php
index ccce5457c8..8f82334f69 100644
--- a/core/lib/Drupal/Core/Config/ExtensionInstallStorage.php
+++ b/core/lib/Drupal/Core/Config/ExtensionInstallStorage.php
@@ -50,9 +50,11 @@ class ExtensionInstallStorage extends InstallStorage {
    *   search and to get overrides from.
    * @param string $profile
    *   The current installation profile.
+   * @param \Drupal\Core\Extension\ProfileExtensionList $profile_list
+   *   (optional) The profile list.
    */
-  public function __construct(StorageInterface $config_storage, $directory, $collection, $include_profile, $profile) {
-    parent::__construct($directory, $collection);
+  public function __construct(StorageInterface $config_storage, $directory, $collection, $include_profile, $profile, ProfileExtensionList $profile_list = NULL) {
+    parent::__construct($directory, $collection, $profile_list);
     $this->configStorage = $config_storage;
     $this->includeProfile = $include_profile;
     $this->installProfile = $profile;
@@ -86,7 +88,7 @@ public function createCollection($collection) {
   protected function getAllFolders() {
     if (!isset($this->folders)) {
       $this->folders = [];
-      $this->folders += $this->getCoreNames();
+      $this->folders = $this->getCoreNames() + $this->folders;
 
       $extensions = $this->configStorage->read('core.extension');
       // @todo Remove this scan as part of https://www.drupal.org/node/2186491
@@ -110,7 +112,7 @@ protected function getAllFolders() {
             $module_list[$module] = $module_list_scan[$module];
           }
         }
-        $this->folders += $this->getComponentNames($module_list);
+        $this->folders = $this->getComponentNames($module_list) + $this->folders;
       }
       if (!empty($extensions['theme'])) {
         $theme_list_scan = $listing->scan('theme');
@@ -119,7 +121,7 @@ protected function getAllFolders() {
             $theme_list[$theme] = $theme_list_scan[$theme];
           }
         }
-        $this->folders += $this->getComponentNames($theme_list);
+        $this->folders = $this->getComponentNames($theme_list) + $this->folders;
       }
 
       if ($this->includeProfile) {
diff --git a/core/lib/Drupal/Core/Config/InstallStorage.php b/core/lib/Drupal/Core/Config/InstallStorage.php
index 1ad7017f5d..764d51ff46 100644
--- a/core/lib/Drupal/Core/Config/InstallStorage.php
+++ b/core/lib/Drupal/Core/Config/InstallStorage.php
@@ -56,9 +56,14 @@ class InstallStorage extends FileStorage {
    * @param string $collection
    *   (optional) The collection to store configuration in. Defaults to the
    *   default collection.
+   * @param \Drupal\Core\Extension\ProfileExtensionList $profile_list
+   *   (optional) The profile list.
    */
-  public function __construct($directory = self::CONFIG_INSTALL_DIRECTORY, $collection = StorageInterface::DEFAULT_COLLECTION) {
+  public function __construct($directory = self::CONFIG_INSTALL_DIRECTORY, $collection = StorageInterface::DEFAULT_COLLECTION, ProfileExtensionList $profile_list = NULL) {
     parent::__construct($directory, $collection);
+    if (\Drupal::hasService('extension.list.profile')) {
+      $this->profileList = $profile_list ?: \Drupal::service('extension.list.profile');
+    }
   }
 
   /**
@@ -150,7 +155,9 @@ public function listAll($prefix = '') {
   protected function getAllFolders() {
     if (!isset($this->folders)) {
       $this->folders = [];
-      $this->folders += $this->getCoreNames();
+      $this->folders = $this->getCoreNames() + $this->folders;
+      // Get dependent profiles and add the extension components.
+      $this->folders = $this->getComponentNames($this->profileList->getAncestors()) + $this->folders;
       // Perform an ExtensionDiscovery scan as we cannot use drupal_get_path()
       // yet because the system module may not yet be enabled during install.
       // @todo Remove as part of https://www.drupal.org/node/2186491
@@ -163,12 +170,12 @@ protected function getAllFolders() {
           // during the module scan.
           // @todo Remove as part of https://www.drupal.org/node/2186491
           drupal_get_filename('profile', $profile, $profile_list[$profile]->getPathname());
-          $this->folders += $this->getComponentNames([$profile_list[$profile]]);
+          $this->folders = $this->getComponentNames([$profile_list[$profile]]) + $this->folders;
         }
       }
       // @todo Remove as part of https://www.drupal.org/node/2186491
-      $this->folders += $this->getComponentNames($listing->scan('module'));
-      $this->folders += $this->getComponentNames($listing->scan('theme'));
+      $this->folders = $this->getComponentNames($listing->scan('module')) + $this->folders;
+      $this->folders = $this->getComponentNames($listing->scan('theme')) + $this->folders;
     }
     return $this->folders;
   }
diff --git a/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php
index dd0b355c79..186cf658d4 100644
--- a/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php
@@ -144,10 +144,15 @@ protected function validateModules(ConfigImporter $config_importer) {
     $uninstalls = $config_importer->getExtensionChangelist('module', 'uninstall');
     foreach ($uninstalls as $module) {
       foreach (array_keys($module_data[$module]->required_by) as $dependent_module) {
-        if ($module_data[$dependent_module]->status && !in_array($dependent_module, $uninstalls, TRUE) && $dependent_module !== $install_profile) {
-          $module_name = $module_data[$module]->info['name'];
-          $dependent_module_name = $module_data[$dependent_module]->info['name'];
-          $config_importer->logError($this->t('Unable to uninstall the %module module since the %dependent_module module is installed.', ['%module' => $module_name, '%dependent_module' => $dependent_module_name]));
+        if ($module_data[$dependent_module]->status && !in_array($dependent_module, $uninstalls, TRUE)) {
+          if (!array_key_exists($dependent_module, $profiles)) {
+            $module_name = $module_data[$module]->info['name'];
+            $dependent_module_name = $module_data[$dependent_module]->info['name'];
+            $config_importer->logError($this->t('Unable to uninstall the %module module since the %dependent_module module is installed.', [
+              '%module' => $module_name,
+              '%dependent_module' => $dependent_module_name
+            ]));
+          }
         }
       }
     }
diff --git a/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php b/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php
index 3dc28a8d42..5a85b94a36 100644
--- a/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php
+++ b/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php
@@ -102,12 +102,24 @@ class ExtensionDiscovery {
    *   The available profile directories
    * @param string $site_path
    *   The path to the site.
+   * @param \Drupal\Core\Extension\ProfileExtensionList|null $profile_list
+   *   (optional) The profile list.
    */
-  public function __construct(string $root, $use_file_cache = TRUE, array $profile_directories = NULL, string $site_path = NULL) {
+  public function __construct(string $root, $use_file_cache = TRUE, array $profile_directories = NULL, string $site_path = NULL, ProfileExtensionList $profile_list = NULL) {
     $this->root = $root;
     $this->fileCache = $use_file_cache ? FileCacheFactory::get('extension_discovery') : NULL;
     $this->profileDirectories = $profile_directories;
     $this->sitePath = $site_path;
+
+    // ExtensionDiscovery can be used without a service container
+    // (@drupalKernel::moduleData), so only use the profile list service if it
+    // is available to us.
+    if ($profile_list) {
+      $this->profileList = $profile_list;
+    }
+    elseif (\Drupal::hasService('extension.list.profile')) {
+      $this->profileList = \Drupal::service('extension.list.profile');
+    }
   }
 
   /**
@@ -229,7 +241,19 @@ public function scan($type, $include_tests = NULL) {
   public function setProfileDirectoriesFromSettings() {
     $this->profileDirectories = [];
     if ($profile = \Drupal::installProfile()) {
-      $this->profileDirectories[] = drupal_get_path('profile', $profile);
+      if ($this->profileList) {
+        $profiles = $this->profileList->getAncestors($profile);
+      }
+      else {
+        $profiles = [
+          $profile => new Extension($this->root, 'profile', drupal_get_path('profile', $profile)),
+        ];
+      }
+
+      $profile_directories = array_map(function(Extension $extension) {
+        return $extension->getPath();
+      }, $profiles);
+      $this->profileDirectories = array_unique(array_merge($profile_directories, $this->profileDirectories));
     }
     return $this;
   }
diff --git a/core/lib/Drupal/Core/Extension/ModuleExtensionList.php b/core/lib/Drupal/Core/Extension/ModuleExtensionList.php
index 60b08cc8b5..fa429b6212 100644
--- a/core/lib/Drupal/Core/Extension/ModuleExtensionList.php
+++ b/core/lib/Drupal/Core/Extension/ModuleExtensionList.php
@@ -41,7 +41,7 @@ class ModuleExtensionList extends ExtensionList {
   /**
    * The profile list needed by this module list.
    *
-   * @var \Drupal\Core\Extension\ExtensionList
+   * @var \Drupal\Core\Extension\ProfileExtensionList
    */
   protected $profileList;
 
@@ -62,14 +62,14 @@ class ModuleExtensionList extends ExtensionList {
    *   The state.
    * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
    *   The config factory.
-   * @param \Drupal\Core\Extension\ExtensionList $profile_list
+   * @param \Drupal\Core\Extension\ProfileExtensionList $profile_list
    *   The site profile listing.
    * @param string $install_profile
    *   The install profile used by the site.
    * @param array[] $container_modules_info
    *   (optional) The module locations coming from the compiled container.
    */
-  public function __construct($root, $type, CacheBackendInterface $cache, InfoParserInterface $info_parser, ModuleHandlerInterface $module_handler, StateInterface $state, ConfigFactoryInterface $config_factory, ExtensionList $profile_list, $install_profile, array $container_modules_info = []) {
+  public function __construct($root, $type, CacheBackendInterface $cache, InfoParserInterface $info_parser, ModuleHandlerInterface $module_handler, StateInterface $state, ConfigFactoryInterface $config_factory, ProfileExtensionList $profile_list, $install_profile, array $container_modules_info = []) {
     parent::__construct($root, $type, $cache, $info_parser, $module_handler, $state, $install_profile);
 
     $this->configFactory = $config_factory;
@@ -106,8 +106,7 @@ protected function getExtensionDiscovery() {
   protected function getProfileDirectories(ExtensionDiscovery $discovery) {
     $discovery->setProfileDirectories([]);
     $all_profiles = $discovery->scan('profile');
-    $active_profile = $all_profiles[$this->installProfile];
-    $profiles = array_intersect_key($all_profiles, $this->configFactory->get('core.extension')->get('module') ?: [$active_profile->getName() => 0]);
+    $profiles = $this->profileList->getAncestors($this->installProfile);
 
     $profile_directories = array_map(function (Extension $profile) {
       return $profile->getPath();
@@ -135,13 +134,9 @@ protected function getActiveProfile() {
    */
   protected function doScanExtensions() {
     $extensions = parent::doScanExtensions();
-
-    $profiles = $this->profileList->getList();
-    // Modify the active profile object that was previously added to the module
-    // list.
-    if ($this->installProfile && isset($profiles[$this->installProfile])) {
-      $extensions[$this->installProfile] = $profiles[$this->installProfile];
-    }
+    // Merge in the install profile and any profile ancestors.
+    $profiles = $this->profileList->getAncestors($this->installProfile);
+    $extensions = array_merge($extensions, $profiles);
 
     return $extensions;
   }
diff --git a/core/lib/Drupal/Core/Extension/ProfileExtensionList.php b/core/lib/Drupal/Core/Extension/ProfileExtensionList.php
index 7c415168d8..1ea6a5d303 100644
--- a/core/lib/Drupal/Core/Extension/ProfileExtensionList.php
+++ b/core/lib/Drupal/Core/Extension/ProfileExtensionList.php
@@ -23,13 +23,172 @@ class ProfileExtensionList extends ExtensionList {
     'package' => 'Other',
     'version' => NULL,
     'php' => DRUPAL_MINIMUM_PHP,
+    'themes' => ['stark'],
+    'hidden' => FALSE,
+    'base profile' => '',
   ];
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getExtensionInfo($extension_name) {
+    $all_info = $this->getAllAvailableInfo();
+    if (isset($all_info[$extension_name])) {
+      return $all_info[$extension_name];
+    }
+    throw new \InvalidArgumentException("The {$this->type} $extension_name does not exist.");
+  }
+
+  /**
+   * Returns a list comprised of the profile, its parent profile if it has one,
+   * and any further ancestors.
+   *
+   * @param string $profile
+   *   (optional) The name of profile. Defaults to the current install profile.
+   *
+   * @return \Drupal\Core\Extension\Extension[]
+   *   An associative array of Extension objects, keyed by profile name in
+   *   descending order of their dependencies (ancestors first). If the profile
+   *   is not given and cannot be determined, returns an empty array.
+   */
+  public function getAncestors($profile = NULL) {
+    $ancestors = [];
+
+    if (empty($profile)) {
+      $profile = $this->installProfile ?: \Drupal::installProfile();
+    }
+    if (empty($profile)) {
+      return $ancestors;
+    }
+
+    $extension = $this->get($profile);
+
+    foreach ($extension->ancestors as $ancestor) {
+      $ancestors[$ancestor] = $this->get($ancestor);
+    }
+    $ancestors[$profile] = $extension;
+
+    return $ancestors;
+  }
+
+  /**
+   * Returns all available profiles which are distributions.
+   *
+   * @return \Drupal\Core\Extension\Extension[]
+   *   Processed extension objects, keyed by machine name.
+   */
+  public function listDistributions() {
+    return array_filter($this->getList(), function (Extension $profile) {
+      return !empty($profile->info['distribution']);
+    });
+  }
+
+  /**
+   * Select the install distribution from the list of profiles.
+   *
+   * If there are multiple profiles marked as distributions, select the first.
+   * If there is an inherited profile marked as a distribution, select it over
+   * its base profile.
+   *
+   * @param string[] $profiles
+   *   List of profile names to search.
+   *
+   * @return string|null
+   *   The selected distribution profile name, or NULL if none is found.
+   */
+  public function selectDistribution(array $profiles = NULL) {
+    $distributions = $this->listDistributions();
+
+    if ($profiles) {
+      $distributions = array_intersect_key($distributions, array_flip($profiles));
+    }
+
+    // Remove any distributions which are extended by another one.
+    foreach ($distributions as $profile_name => $profile) {
+      if (!empty($profile->info['base profile'])) {
+        $base_profile = $profile->info['base profile'];
+        unset($distributions[$base_profile]);
+      }
+    }
+
+    return key($distributions) ?: NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function doList() {
+    $profiles = parent::doList();
+
+    // Compute the ancestry of each profile before any further processing.
+    foreach ($profiles as $profile) {
+      // Maintain a list of profiles which depend on this one.
+      $profile->children = [];
+
+      // Maintain a list of profiles that this one depends on, in reverse
+      // ancestral order (immediate parent first).
+      $profile->ancestors = $this->computeAncestry($profiles, $profile);
+
+      // Give the profile a heavy weight to ensure that its hooks run last.
+      $profile->weight = count($profile->ancestors) + 1000;
+    }
+
+    // For each profile, merge in ancestors' module and theme lists.
+    foreach ($profiles as $profile_name => $profile) {
+      if (empty($profile->ancestors)) {
+        continue;
+      }
+      // Reference the extension info here for readability.
+      $info = &$profile->info;
+
+      // Add the parent profile as a hard dependency.
+      $info['dependencies'][] = reset($profile->ancestors);
+
+      // Add all themes and extensions listed by ancestors.
+      foreach ($profile->ancestors as $ancestor) {
+        $ancestor = $profiles[$ancestor];
+
+        // Add the current profile as a child of the ancestor.
+        $ancestor->children[] = $profile_name;
+        $info['install'] = array_merge($info['install'], $ancestor->info['install']);
+        $info['themes'] = array_merge($info['themes'], $ancestor->info['themes']);
+        // Add ancestor dependencies as our dependencies.
+        $info['dependencies'] = array_merge($info['dependencies'], $ancestor->info['dependencies']);
+      }
+      $info['dependencies'] = array_unique($info['dependencies']);
+      $info['install'] = array_unique($info['install']);
+      $info['themes'] = array_unique($info['themes']);
+    }
+    return $profiles;
+  }
+
+  /**
+   * Computes and returns the ancestral lineage of a profile.
+   *
+   * @param \Drupal\Core\Extension\Extension[] $profiles
+   *   All discovered profiles.
+   * @param \Drupal\Core\Extension\Extension $profile
+   *   The profile for which to compute the ancestry.
+   *
+   * @return string[]
+   *   The names of the ancestors of the given profile, in order.
+   */
+  protected function computeAncestry(array $profiles, Extension $profile) {
+    $ancestors = [];
+
+    while (!empty($profile->info['base profile'])) {
+      array_unshift($ancestors, $profile->info['base profile']);
+      $profile = $profile->info['base profile'];
+      $profile = $profiles[$profile];
+    }
+    return $ancestors;
+  }
+
   /**
    * {@inheritdoc}
    */
   protected function getInstalledExtensionNames() {
-    return [$this->installProfile];
+    return array_keys($this->getAncestors());
   }
 
 }
diff --git a/core/lib/Drupal/Core/Installer/InstallerProfileExtensionList.php b/core/lib/Drupal/Core/Installer/InstallerProfileExtensionList.php
new file mode 100644
index 0000000000..d5c42adea3
--- /dev/null
+++ b/core/lib/Drupal/Core/Installer/InstallerProfileExtensionList.php
@@ -0,0 +1,58 @@
+<?php
+
+namespace Drupal\Core\Installer;
+
+use Drupal\Core\Extension\ProfileExtensionList;
+
+/**
+ * Overrides the profile extension list to have a static cache.
+ */
+class InstallerProfileExtensionList extends ProfileExtensionList {
+
+  /**
+   * Static version of the added file names during the installer.
+   *
+   * @var string[]
+   *
+   * @internal
+   */
+  protected static $staticAddedPathNames;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setPathname($extension_name, $pathname) {
+    parent::setPathname($extension_name, $pathname);
+
+    // In the early installer the container is rebuilt multiple times. Therefore
+    // we have to keep the added filenames across those rebuilds. This is not a
+    // final design, but rather just a workaround resolved at some point,
+    // hopefully.
+    // @todo Remove as part of https://drupal.org/project/drupal/issues/2934063
+    static::$staticAddedPathNames[$extension_name] = $pathname;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPathname($extension_name) {
+    if (isset($this->addedPathNames[$extension_name])) {
+      return $this->addedPathNames[$extension_name];
+    }
+    elseif (isset($this->pathNames[$extension_name])) {
+      return $this->pathNames[$extension_name];
+    }
+    elseif (isset(static::$staticAddedPathNames[$extension_name])) {
+      return static::$staticAddedPathNames[$extension_name];
+    }
+    elseif (($path_names = $this->getPathnames()) && isset($path_names[$extension_name])) {
+      // Ensure we don't have to do path scanning more than really needed.
+      foreach ($path_names as $extension => $path_name) {
+        static::$staticAddedPathNames[$extension] = $path_name;
+      }
+      return $path_names[$extension_name];
+    }
+    throw new \InvalidArgumentException("The {$this->type} $extension_name does not exist.");
+  }
+
+}
diff --git a/core/modules/config/tests/config_test/src/TestInstallStorage.php b/core/modules/config/tests/config_test/src/TestInstallStorage.php
index e3c4918df4..6d140815b7 100644
--- a/core/modules/config/tests/config_test/src/TestInstallStorage.php
+++ b/core/modules/config/tests/config_test/src/TestInstallStorage.php
@@ -21,9 +21,10 @@ protected function getAllFolders() {
       $this->folders = $this->getCoreNames();
       $listing = new ExtensionDiscovery(\Drupal::root());
       $listing->setProfileDirectories([]);
-      $this->folders += $this->getComponentNames($listing->scan('profile'));
-      $this->folders += $this->getComponentNames($listing->scan('module'));
-      $this->folders += $this->getComponentNames($listing->scan('theme'));
+      // @todo Remove as part of https://www.drupal.org/node/2186491
+      $this->folders = $this->getComponentNames($listing->scan('profile')) + $this->folders;
+      $this->folders = $this->getComponentNames($listing->scan('module')) + $this->folders;
+      $this->folders = $this->getComponentNames($listing->scan('theme')) + $this->folders;
     }
     return $this->folders;
   }
diff --git a/core/modules/config/tests/src/Functional/ConfigImportBaseInstallProfileTest.php b/core/modules/config/tests/src/Functional/ConfigImportBaseInstallProfileTest.php
new file mode 100644
index 0000000000..583c31153b
--- /dev/null
+++ b/core/modules/config/tests/src/Functional/ConfigImportBaseInstallProfileTest.php
@@ -0,0 +1,96 @@
+<?php
+
+namespace Drupal\Tests\config\Functional;
+
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Tests the importing/exporting configuration based on install sub-profile.
+ *
+ * @group config
+ */
+class ConfigImportBaseInstallProfileTest extends BrowserTestBase {
+
+  /**
+   * The profile to install as a basis for testing.
+   *
+   * @var string
+   */
+  protected $profile = 'testing_inherited';
+
+  /**
+   * A user with the 'synchronize configuration' permission.
+   *
+   * @var \Drupal\user\UserInterface
+   */
+  protected $webUser;
+
+  protected function setUp() {
+    parent::setUp();
+
+    $this->webUser = $this->drupalCreateUser(['synchronize configuration']);
+    $this->drupalLogin($this->webUser);
+    $this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.sync'));
+  }
+
+  /**
+   * Tests config importer cannot uninstall parent install profiles and
+   * dependencies of parent profiles can be uninstalled.
+   *
+   * @see \Drupal\Core\EventSubscriber\ConfigImportSubscriber
+   */
+  public function testInstallParentProfileValidation() {
+    $sync = $this->container->get('config.storage.sync');
+    $this->copyConfig($this->container->get('config.storage'), $sync);
+    $core = $sync->read('core.extension');
+
+    // Ensure that parent profile can not be uninstalled.
+    unset($core['module']['testing']);
+    $sync->write('core.extension', $core);
+
+    $this->drupalPostForm('admin/config/development/configuration', [], t('Import all'));
+    $this->assertText('The configuration cannot be imported because it failed validation for the following reasons:');
+    $this->assertText('Unable to uninstall the Testing profile since it is a parent of another installed profile.');
+
+    // Uninstall dependencies of parent profile.
+    $core['module']['testing'] = 0;
+    unset($core['module']['dynamic_page_cache']);
+    $sync->write('core.extension', $core);
+    $sync->deleteAll('dynamic_page_cache.');
+    $this->drupalPostForm('admin/config/development/configuration', [], t('Import all'));
+    $this->assertText('The configuration was imported successfully.');
+    $this->rebuildContainer();
+    $this->assertFalse(\Drupal::moduleHandler()->moduleExists('dynamic_page_cache'), 'The dynamic_page_cache module has been uninstalled.');
+  }
+
+  /**
+   * Tests config importer cannot uninstall sub-profiles and dependencies of
+   * sub-profiles can be uninstalled.
+   *
+   * @see \Drupal\Core\EventSubscriber\ConfigImportSubscriber
+   */
+  public function testInstallSubProfileValidation() {
+    $sync = $this->container->get('config.storage.sync');
+    $this->copyConfig($this->container->get('config.storage'), $sync);
+    $core = $sync->read('core.extension');
+
+    // Ensure install sub-profiles can not be uninstalled.
+    unset($core['module']['testing_inherited']);
+    $sync->write('core.extension', $core);
+
+    $this->drupalPostForm('admin/config/development/configuration', [], t('Import all'));
+    $this->assertText('The configuration cannot be imported because it failed validation for the following reasons:');
+    $this->assertText('Unable to uninstall the Testing Inherited profile since it is the main install profile.');
+
+    // Uninstall dependencies of main profile.
+    $core['module']['testing_inherited'] = 0;
+    unset($core['module']['syslog']);
+    $sync->write('core.extension', $core);
+    $sync->deleteAll('syslog.');
+    $this->drupalPostForm('admin/config/development/configuration', [], t('Import all'));
+    $this->assertText('The configuration was imported successfully.');
+    $this->rebuildContainer();
+    $this->assertFalse(\Drupal::moduleHandler()->moduleExists('syslog'), 'The syslog module has been uninstalled.');
+  }
+
+}
diff --git a/core/modules/system/src/Form/ModulesUninstallForm.php b/core/modules/system/src/Form/ModulesUninstallForm.php
index 52f378e709..3cdecfdf72 100644
--- a/core/modules/system/src/Form/ModulesUninstallForm.php
+++ b/core/modules/system/src/Form/ModulesUninstallForm.php
@@ -151,7 +151,8 @@ public function buildForm(array $form, FormStateInterface $form_state) {
         $form['uninstall'][$module->getName()]['#disabled'] = TRUE;
       }
       // All modules which depend on this one must be uninstalled first, before
-      // we can allow this module to be uninstalled.
+      // we can allow this module to be uninstalled. (Installation profiles are
+      // excluded from this list.)
       foreach (array_keys($module->required_by) as $dependent) {
         if (drupal_get_installed_schema_version($dependent) != SCHEMA_UNINSTALLED) {
           $form['modules'][$module->getName()]['#required_by'][] = $dependent;
diff --git a/core/profiles/testing_inherited/config/install/block.block.stable_login.yml b/core/profiles/testing_inherited/config/install/block.block.stable_login.yml
new file mode 100644
index 0000000000..3650c6c41a
--- /dev/null
+++ b/core/profiles/testing_inherited/config/install/block.block.stable_login.yml
@@ -0,0 +1,19 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - user
+  theme:
+    - stable
+id: stable_login
+theme: stable
+region: sidebar_first
+weight: 0
+provider: null
+plugin: user_login_block
+settings:
+  id: user_login_block
+  label: 'User login'
+  provider: user
+  label_display: visible
+visibility: {  }
diff --git a/core/profiles/testing_inherited/config/install/system.theme.yml b/core/profiles/testing_inherited/config/install/system.theme.yml
new file mode 100644
index 0000000000..67aeeeeac7
--- /dev/null
+++ b/core/profiles/testing_inherited/config/install/system.theme.yml
@@ -0,0 +1,2 @@
+# @todo: Remove this file in https://www.drupal.org/node/2352949
+default: stable
diff --git a/core/profiles/testing_inherited/modules/child_profile_module/child_profile_module.info.yml b/core/profiles/testing_inherited/modules/child_profile_module/child_profile_module.info.yml
new file mode 100644
index 0000000000..0d2dd2c288
--- /dev/null
+++ b/core/profiles/testing_inherited/modules/child_profile_module/child_profile_module.info.yml
@@ -0,0 +1,6 @@
+name: 'Child profile module'
+core_version_requirement: '*'
+type: module
+description: 'A module contained in a child profile, for testing.'
+package: Testing
+version: VERSION
diff --git a/core/profiles/testing_inherited/modules/contrib/contrib_child_profile_module/contrib_child_profile_module.info.yml b/core/profiles/testing_inherited/modules/contrib/contrib_child_profile_module/contrib_child_profile_module.info.yml
new file mode 100644
index 0000000000..25c89289bd
--- /dev/null
+++ b/core/profiles/testing_inherited/modules/contrib/contrib_child_profile_module/contrib_child_profile_module.info.yml
@@ -0,0 +1,6 @@
+name: 'Contrib child profile module'
+core_version_requirement: '*'
+type: module
+description: 'A contrib module contained in a child profile, for testing.'
+package: Testing
+version: VERSION
diff --git a/core/profiles/testing_inherited/modules/custom/custom_child_profile_module/custom_child_profile_module.info.yml b/core/profiles/testing_inherited/modules/custom/custom_child_profile_module/custom_child_profile_module.info.yml
new file mode 100644
index 0000000000..b6941a3a34
--- /dev/null
+++ b/core/profiles/testing_inherited/modules/custom/custom_child_profile_module/custom_child_profile_module.info.yml
@@ -0,0 +1,6 @@
+name: 'Custom child profile module'
+core_version_requirement: '*'
+type: module
+description: 'A custom module contained in a child profile, for testing.'
+package: Testing
+version: VERSION
diff --git a/core/profiles/testing_inherited/testing_inherited.info.yml b/core/profiles/testing_inherited/testing_inherited.info.yml
new file mode 100644
index 0000000000..92e183c86a
--- /dev/null
+++ b/core/profiles/testing_inherited/testing_inherited.info.yml
@@ -0,0 +1,17 @@
+name: Testing Inherited
+type: profile
+description: 'Profile for testing base profile inheritance.'
+version: VERSION
+hidden: true
+
+base profile: testing
+
+install:
+  - block
+  - config
+  - child_profile_module
+  - contrib_child_profile_module
+  - custom_child_profile_module
+
+themes:
+  - stable
diff --git a/core/profiles/testing_inherited/tests/src/Functional/InheritedProfileTest.php b/core/profiles/testing_inherited/tests/src/Functional/InheritedProfileTest.php
new file mode 100644
index 0000000000..0af6c4f68d
--- /dev/null
+++ b/core/profiles/testing_inherited/tests/src/Functional/InheritedProfileTest.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Drupal\Tests\testing_inherited\Functional;
+
+use Drupal\block\BlockInterface;
+use Drupal\block\Entity\Block;
+use Drupal\FunctionalTests\Installer\InstallerTestBase;
+
+/**
+ * Tests installing from an inherited profile.
+ *
+ * @group profiles
+ */
+class InheritedProfileTest extends InstallerTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $profile = 'testing_inherited';
+
+  /**
+   * Tests inherited installation profile.
+   */
+  public function testInheritedProfile() {
+    // Check that the stable_login block exists.
+    $this->assertInstanceOf(BlockInterface::class, Block::load('stable_login'));
+
+    // Check that stable is the default theme.
+    $this->assertSame('stable', $this->config('system.theme')->get('default'));
+
+    /** @var \Drupal\Core\Extension\ModuleHandlerInterface $module_handler */
+    $module_handler = $this->container->get('module_handler');
+    // Check that parent dependencies are installed.
+    $this->assertTrue($module_handler->moduleExists('page_cache'));
+    // Check that child profile dependencies are installed.
+    $this->assertTrue($module_handler->moduleExists('config'));
+    // Check that modules contained in the child profile are installed.
+    $this->assertTrue($module_handler->moduleExists('child_profile_module'));
+    $this->assertTrue($module_handler->moduleExists('contrib_child_profile_module'));
+    $this->assertTrue($module_handler->moduleExists('custom_child_profile_module'));
+
+    // Check that all themes were installed.
+    $this->assertTrue(\Drupal::service('theme_handler')->themeExists('stable'));
+  }
+
+}
diff --git a/core/profiles/testing_inherited_standard/testing_inherited_standard.info.yml b/core/profiles/testing_inherited_standard/testing_inherited_standard.info.yml
new file mode 100644
index 0000000000..0728ada2d1
--- /dev/null
+++ b/core/profiles/testing_inherited_standard/testing_inherited_standard.info.yml
@@ -0,0 +1,12 @@
+name: Testing Inherited Standard
+type: profile
+description: 'Profile for testing base profile inheritance.'
+version: VERSION
+hidden: true
+
+base profile: standard
+
+install: []
+
+themes:
+  - bartik
diff --git a/core/profiles/testing_inherited_standard/tests/src/Functional/InheritedProfileTest.php b/core/profiles/testing_inherited_standard/tests/src/Functional/InheritedProfileTest.php
new file mode 100644
index 0000000000..1bc6b51db0
--- /dev/null
+++ b/core/profiles/testing_inherited_standard/tests/src/Functional/InheritedProfileTest.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Drupal\Tests\testing_inherited_standard\Functional;
+
+use Drupal\FunctionalTests\Installer\InstallerTestBase;
+
+/**
+ * Tests installing from an inherited standard profile.
+ *
+ * @group profiles
+ */
+class InheritedProfileTest extends InstallerTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $profile = 'testing_inherited_standard';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stable';
+
+  /**
+   * Tests inherited installation profile.
+   */
+  public function testInheritedProfile() {
+    // Do nothing, simply install this profile.
+  }
+
+}
diff --git a/core/profiles/testing_subsubprofile/config/install/system.theme.yml b/core/profiles/testing_subsubprofile/config/install/system.theme.yml
new file mode 100644
index 0000000000..5cb3db7201
--- /dev/null
+++ b/core/profiles/testing_subsubprofile/config/install/system.theme.yml
@@ -0,0 +1,2 @@
+# @todo: Remove this file once https://www.drupal.org/node/2854576 is resolved.
+default: stable
diff --git a/core/profiles/testing_subsubprofile/modules/grandchild_profile_module/grandchild_profile_module.info.yml b/core/profiles/testing_subsubprofile/modules/grandchild_profile_module/grandchild_profile_module.info.yml
new file mode 100644
index 0000000000..f42abd66b5
--- /dev/null
+++ b/core/profiles/testing_subsubprofile/modules/grandchild_profile_module/grandchild_profile_module.info.yml
@@ -0,0 +1,6 @@
+name: 'Grandchild profile module'
+core_version_requirement: '*'
+type: module
+description: 'A module contained in a grandchild profile, for testing.'
+package: Testing
+version: VERSION
diff --git a/core/profiles/testing_subsubprofile/testing_subsubprofile.info.yml b/core/profiles/testing_subsubprofile/testing_subsubprofile.info.yml
new file mode 100644
index 0000000000..51d083d4e5
--- /dev/null
+++ b/core/profiles/testing_subsubprofile/testing_subsubprofile.info.yml
@@ -0,0 +1,11 @@
+name: Testing SubSubProfile
+type: profile
+description: 'Profile for testing deep profile inheritance.'
+version: VERSION
+hidden: true
+
+base profile: testing_inherited
+
+install:
+  - syslog
+  - grandchild_profile_module
diff --git a/core/profiles/testing_subsubprofile/tests/src/Functional/DeepInheritedProfileTest.php b/core/profiles/testing_subsubprofile/tests/src/Functional/DeepInheritedProfileTest.php
new file mode 100644
index 0000000000..7df6c46fc0
--- /dev/null
+++ b/core/profiles/testing_subsubprofile/tests/src/Functional/DeepInheritedProfileTest.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Drupal\Tests\testing_subsubprofile\Functional;
+
+use Drupal\FunctionalTests\Installer\InstallerTestBase;
+
+/**
+ * Tests installing from an inherited profile whose parent is also inherited.
+ *
+ * @group profiles
+ */
+class DeepInheritedProfileTest extends InstallerTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $profile = 'testing_subsubprofile';
+
+  /**
+   * Tests sub-sub-profile inherited installation.
+   */
+  public function testDeepInheritedProfile() {
+    // Check that stable is the default theme enabled in parent profile.
+    $this->assertSame('stable', $this->config('system.theme')->get('default'));
+
+    /** @var \Drupal\Core\Extension\ModuleHandlerInterface $module_handler */
+    $module_handler = $this->container->get('module_handler');
+    // page_cache was enabled in main profile.
+    $this->assertTrue($module_handler->moduleExists('page_cache'));
+    // block was enabled in parent profile.
+    $this->assertTrue($module_handler->moduleExists('block'));
+    // syslog was enabled in this profile.
+    $this->assertTrue($module_handler->moduleExists('syslog'));
+    // A module contained in this profile was installed too.
+    $this->assertTrue($module_handler->moduleExists('grandchild_profile_module'));
+  }
+
+}
diff --git a/core/tests/Drupal/KernelTests/Core/Config/ConfigImporterTest.php b/core/tests/Drupal/KernelTests/Core/Config/ConfigImporterTest.php
index 1e6c1b3ead..973d8d1660 100644
--- a/core/tests/Drupal/KernelTests/Core/Config/ConfigImporterTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Config/ConfigImporterTest.php
@@ -732,9 +732,9 @@ public function testInstallProfileMisMatch() {
       $error_log = $this->configImporter->getErrors();
       // Install profiles can not be changed. Note that KernelTestBase currently
       // does not use an install profile. This situation should be impossible
-      // to get in but site's can removed the install profile setting from
+      // to get into but sites can change the install profile value in config or
       // settings.php so the test is valid.
-      $this->assertEqual(['Cannot change the install profile from <em class="placeholder"></em> to <em class="placeholder">this_will_not_work</em> once Drupal is installed.'], $error_log);
+      $this->assertEqual($error_log, ['Cannot change the install profile from <em class="placeholder"></em> to <em class="placeholder">this_will_not_work</em> once Drupal is installed.']);
     }
   }
 
-- 
2.21.0 (Apple Git-122)

