diff --git a/core/core.services.yml b/core/core.services.yml
index 3f68677200..e8704d3c6b 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -304,7 +304,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 5cc1236c1b..ca6b731da6 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -453,6 +453,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
@@ -1222,6 +1228,8 @@ function install_select_profile(&$install_state) {
  *   - For non-interactive installations via install_drupal() settings.
  * - A discovered profile that is a distribution. If multiple profiles are
  *   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
@@ -1244,11 +1252,8 @@ function _install_select_profile(&$install_state) {
     }
   }
   // Check for a distribution profile.
-  foreach ($install_state['profiles'] as $profile) {
-    $profile_info = install_profile_info($profile->getName());
-    if (!empty($profile_info['distribution'])) {
-      return $profile->getName();
-    }
+  if ($distribution = \Drupal::service('extension.list.profile')->selectDistribution(array_keys($install_state['profiles']))) {
+    return $distribution;
   }
 
   // Get all visible (not hidden) profiles.
@@ -1581,7 +1586,10 @@ function install_profile_themes(&$install_state) {
  *   An array of information about the current installation state.
  */
 function install_install_profile(&$install_state) {
-  \Drupal::service('module_installer')->install([drupal_get_profile()], FALSE);
+  // Install all the profiles.
+  $profiles = \Drupal::service('extension.list.profile')->getAncestors();
+  \Drupal::service('module_installer')->install(array_keys($profiles), FALSE);
+
   // Install all available optional config. During installation the module order
   // is determined by dependencies. If there are no dependencies between modules
   // then the order in which they are installed is dependent on random factors
diff --git a/core/includes/install.inc b/core/includes/install.inc
index 399476f102..62e52e2830 100644
--- a/core/includes/install.inc
+++ b/core/includes/install.inc
@@ -1056,6 +1056,9 @@ function drupal_check_module($module) {
  *   - install: Optional parameters to override the installer:
  *     - theme: The machine name of a theme to use in the installer instead of
  *       Drupal's default installer theme.
+ * - 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
@@ -1082,18 +1085,7 @@ function install_profile_info($profile, $langcode = 'en') {
   $cache = &drupal_static(__FUNCTION__, []);
 
   if (!isset($cache[$profile][$langcode])) {
-    // Set defaults for module info.
-    $defaults = [
-      'dependencies' => [],
-      'themes' => ['stark'],
-      'description' => '',
-      'version' => NULL,
-      'hidden' => FALSE,
-      'php' => DRUPAL_MINIMUM_PHP,
-    ];
-    $profile_file = drupal_get_path('profile', $profile) . "/$profile.info.yml";
-    $info = \Drupal::service('info_parser')->parse($profile_file);
-    $info += $defaults;
+    $info = \Drupal::service('extension.list.profile')->getExtensionInfo($profile);
 
     // drupal_required_modules() includes the current profile as a dependency.
     // Remove that dependency, since a module cannot depend on itself.
diff --git a/core/lib/Drupal/Core/Config/ConfigInstaller.php b/core/lib/Drupal/Core/Config/ConfigInstaller.php
index 242d92097e..1f7211506c 100644
--- a/core/lib/Drupal/Core/Config/ConfigInstaller.php
+++ b/core/lib/Drupal/Core/Config/ConfigInstaller.php
@@ -6,6 +6,8 @@
 use Drupal\Component\Utility\Unicode;
 use Drupal\Core\Config\Entity\ConfigDependencyManager;
 use Drupal\Core\Config\Entity\ConfigEntityDependency;
+use Drupal\Core\Extension\ProfileExtensionList;
+use Drupal\Core\Extension\ProfileHandlerInterface;
 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 
 class ConfigInstaller implements ConfigInstallerInterface {
@@ -52,6 +54,13 @@ class ConfigInstaller implements ConfigInstallerInterface {
    */
   protected $sourceStorage;
 
+  /**
+   * The profile list.
+   *
+   * @var \Drupal\Core\Extension\ProfileExtensionList
+   */
+  protected $profileList;
+
   /**
    * Is configuration being created as part of a configuration sync.
    *
@@ -81,14 +90,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');
   }
 
   /**
@@ -471,7 +483,8 @@ public function checkConfigurationToInstall($type, $name) {
 
     // Install profiles can not have config clashes. Configuration that
     // has the same name as a module's configuration will be used instead.
-    if ($name != $this->drupalGetProfile()) {
+    $profiles = $this->profileList->getAncestors($this->installProfile);
+    if (!isset($profiles[$name])) {
       // Throw an exception if the module being installed contains configuration
       // that already exists. Additionally, can not continue installing more
       // modules because those may depend on the current module being installed.
diff --git a/core/lib/Drupal/Core/Config/ExtensionInstallStorage.php b/core/lib/Drupal/Core/Config/ExtensionInstallStorage.php
index 9103ab8c07..430e3546a9 100644
--- a/core/lib/Drupal/Core/Config/ExtensionInstallStorage.php
+++ b/core/lib/Drupal/Core/Config/ExtensionInstallStorage.php
@@ -3,6 +3,8 @@
 namespace Drupal\Core\Config;
 
 use Drupal\Core\Extension\ExtensionDiscovery;
+use Drupal\Core\Extension\ProfileExtensionList;
+use Drupal\Core\Extension\ProfileHandlerInterface;
 
 /**
  * Storage to access configuration and schema in enabled extensions.
@@ -52,9 +54,11 @@ class ExtensionInstallStorage extends InstallStorage {
    *   (optional) The current installation profile. This parameter will be
    *   mandatory in Drupal 9.0.0. In Drupal 8.3.0 not providing this parameter
    *   will trigger a silenced deprecation warning.
+   * @param \Drupal\Core\Extension\ProfileExtensionList $profile_list
+   *   (optional) The profile list.
    */
-  public function __construct(StorageInterface $config_storage, $directory = self::CONFIG_INSTALL_DIRECTORY, $collection = StorageInterface::DEFAULT_COLLECTION, $include_profile = TRUE, $profile = NULL) {
-    parent::__construct($directory, $collection);
+  public function __construct(StorageInterface $config_storage, $directory = self::CONFIG_INSTALL_DIRECTORY, $collection = StorageInterface::DEFAULT_COLLECTION, $include_profile = TRUE, $profile = NULL, ProfileExtensionList $profile_list = NULL) {
+    parent::__construct($directory, $collection, $profile_list);
     $this->configStorage = $config_storage;
     $this->includeProfile = $include_profile;
     if (is_null($profile)) {
@@ -93,19 +97,11 @@ protected function getAllFolders() {
 
       $extensions = $this->configStorage->read('core.extension');
       // @todo Remove this scan as part of https://www.drupal.org/node/2186491
-      $listing = new ExtensionDiscovery(\Drupal::root());
+      $listing = new ExtensionDiscovery(\Drupal::root(), TRUE, NULL, NULL, $this->profileList);
       if (!empty($extensions['module'])) {
         $modules = $extensions['module'];
         // Remove the install profile as this is handled later.
         unset($modules[$this->installProfile]);
-        $profile_list = $listing->scan('profile');
-        if ($this->installProfile && isset($profile_list[$this->installProfile])) {
-          // Prime the drupal_get_filename() static cache with the profile info
-          // file location so we can use drupal_get_path() on the active profile
-          // during the module scan.
-          // @todo Remove as part of https://www.drupal.org/node/2186491
-          drupal_get_filename('profile', $this->installProfile, $profile_list[$this->installProfile]->getPathname());
-        }
         $module_list_scan = $listing->scan('module');
         $module_list = [];
         foreach (array_keys($modules) as $module) {
@@ -126,18 +122,11 @@ protected function getAllFolders() {
       }
 
       if ($this->includeProfile) {
-        // The install profile can override module default configuration. We do
-        // this by replacing the config file path from the module/theme with the
-        // install profile version if there are any duplicates.
-        if ($this->installProfile) {
-          if (!isset($profile_list)) {
-            $profile_list = $listing->scan('profile');
-          }
-          if (isset($profile_list[$this->installProfile])) {
-            $profile_folders = $this->getComponentNames([$profile_list[$this->installProfile]]);
-            $this->folders = $profile_folders + $this->folders;
-          }
-        }
+        // The install profile (and any parent profiles) can override module
+        // default configuration. We do this by replacing the config file path
+        // from the module/theme with the install profile version if there are
+        // any duplicates.
+        $this->folders += $this->getComponentNames($this->profileList->getAncestors($this->installProfile));
       }
     }
     return $this->folders;
diff --git a/core/lib/Drupal/Core/Config/InstallStorage.php b/core/lib/Drupal/Core/Config/InstallStorage.php
index c8d189e480..96a9898f05 100644
--- a/core/lib/Drupal/Core/Config/InstallStorage.php
+++ b/core/lib/Drupal/Core/Config/InstallStorage.php
@@ -4,6 +4,8 @@
 
 use Drupal\Core\Extension\ExtensionDiscovery;
 use Drupal\Core\Extension\Extension;
+use Drupal\Core\Extension\ProfileExtensionList;
+use Drupal\Core\Extension\ProfileHandlerInterface;
 
 /**
  * Storage used by the Drupal installer.
@@ -47,6 +49,13 @@ class InstallStorage extends FileStorage {
    */
   protected $directory;
 
+  /**
+   * The profile list, used to find additional folders to scan for config.
+   *
+   * @var \Drupal\Core\Extension\ProfileExtensionList
+   */
+  protected $profileList;
+
   /**
    * Constructs an InstallStorage object.
    *
@@ -56,9 +65,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');
+    }
   }
 
   /**
@@ -151,21 +165,12 @@ protected function getAllFolders() {
     if (!isset($this->folders)) {
       $this->folders = [];
       $this->folders += $this->getCoreNames();
+      // Get dependent profiles and add the extension components.
+      $this->folders += $this->getComponentNames($this->profileList->getAncestors());
       // 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
       $listing = new ExtensionDiscovery(\Drupal::root());
-      if ($profile = drupal_get_profile()) {
-        $profile_list = $listing->scan('profile');
-        if (isset($profile_list[$profile])) {
-          // Prime the drupal_get_filename() static cache with the profile info
-          // file location so we can use drupal_get_path() on the active profile
-          // 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]]);
-        }
-      }
       // @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'));
diff --git a/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php
index ea7d29fedf..245aeb1315 100644
--- a/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php
@@ -91,12 +91,18 @@ protected function validateModules(ConfigImporter $config_importer) {
       $config_importer->logError($this->t('Unable to install the %module module since it does not exist.', ['%module' => $module]));
     }
 
+    // Get a list of parent profiles and the main profile.
+    /* @var $profiles \Drupal\Core\Extension\Extension[] */
+    $profiles = \Drupal::service('extension.list.profile')->getAncestors();
+    /* @var $main_profile \Drupal\Core\Extension\Extension */
+    $main_profile = end($profiles);
+
     // Ensure that all modules being installed have their dependencies met.
     $installs = $config_importer->getExtensionChangelist('module', 'install');
     foreach ($installs as $module) {
       $missing_dependencies = [];
       foreach (array_keys($module_data[$module]->requires) as $required_module) {
-        if (!isset($core_extension['module'][$required_module])) {
+        if (!isset($core_extension['module'][$required_module]) && !array_key_exists($module, $profiles)) {
           $missing_dependencies[] = $module_data[$required_module]->info['name'];
         }
       }
@@ -111,32 +117,58 @@ protected function validateModules(ConfigImporter $config_importer) {
       }
     }
 
-    // Get the install profile from the site's configuration.
+    // Get the active install profile from the site's configuration.
     $current_core_extension = $config_importer->getStorageComparer()->getTargetStorage()->read('core.extension');
-    $install_profile = isset($current_core_extension['profile']) ? $current_core_extension['profile'] : NULL;
+    if (isset($current_core_extension['profile'])) {
+      // Ensure the active profile is not changing.
+      if ($current_core_extension['profile'] !== $core_extension['profile']) {
+        $config_importer->logError($this->t('Cannot change the install profile from %profile to %new_profile once Drupal is installed.', ['%profile' => $current_core_extension['profile'], '%new_profile' => $core_extension['profile']]));
+      }
+    }
 
     // Ensure that all modules being uninstalled are not required by modules
     // that will be installed after the import.
     $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
+            ]));
+          }
         }
       }
     }
 
-    // Ensure that the install profile is not being uninstalled.
-    if (in_array($install_profile, $uninstalls, TRUE)) {
-      $profile_name = $module_data[$install_profile]->info['name'];
-      $config_importer->logError($this->t('Unable to uninstall the %profile profile since it is the install profile.', ['%profile' => $profile_name]));
-    }
-
-    // Ensure the profile is not changing.
-    if ($install_profile !== $core_extension['profile']) {
-      $config_importer->logError($this->t('Cannot change the install profile from %profile to %new_profile once Drupal is installed.', ['%profile' => $install_profile, '%new_profile' => $core_extension['profile']]));
+    // Don't allow profiles to be uninstalled. It's possible for no profile to
+    // be set yet if the config is being imported during initial site install.
+    if ($main_profile instanceof \Drupal\core\Extension\Extension) {
+      if (in_array($main_profile->getName(), $uninstalls, TRUE)) {
+        // Ensure that the active profile is not being uninstalled.
+        $profile_name = $main_profile->info['name'];
+        $config_importer->logError($this->t('Unable to uninstall the %profile profile since it is the main install profile.', ['%profile' => $profile_name]));
+      }
+      if ($profile_uninstalls = array_intersect_key($profiles, array_flip($uninstalls))) {
+        // Ensure that none of the parent profiles are being uninstalled.
+        $profile_names = [];
+        foreach ($profile_uninstalls as $profile) {
+          if ($profile->getName() !== $main_profile->getName()) {
+            $profile_names[] = $module_data[$profile->getName()]->info['name'];
+          }
+        }
+        if (!empty($profile_names)) {
+          $message = $this->formatPlural(count($profile_names),
+            'Unable to uninstall the :profile profile since it is a parent of another installed profile.',
+            'Unable to uninstall the :profile profiles since they are parents of another installed profile.',
+            [':profile' => implode(', ', $profile_names)]
+          );
+          $config_importer->logError($message);
+        }
+      }
     }
   }
 
diff --git a/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php b/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php
index 17c0e5b4a3..8fea6212ce 100644
--- a/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php
+++ b/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php
@@ -91,6 +91,15 @@ class ExtensionDiscovery {
    */
   protected $sitePath;
 
+  /**
+   * The profile list.
+   *
+   * Used to determine the directories in which we want to scan for modules.
+   *
+   * @var \Drupal\Core\Extension\ProfileExtensionList
+   */
+  protected $profileList;
+
   /**
    * Constructs a new ExtensionDiscovery object.
    *
@@ -102,12 +111,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($root, $use_file_cache = TRUE, $profile_directories = NULL, $site_path = NULL) {
+  public function __construct($root, $use_file_cache = TRUE, $profile_directories = NULL, $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');
+    }
   }
 
   /**
@@ -241,7 +262,19 @@ public function setProfileDirectoriesFromSettings() {
     // In case both profile directories contain the same extension, the actual
     // profile always has precedence.
     if ($profile) {
-      $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/ExtensionList.php b/core/lib/Drupal/Core/Extension/ExtensionList.php
index 6201a08a11..da73b8c03d 100644
--- a/core/lib/Drupal/Core/Extension/ExtensionList.php
+++ b/core/lib/Drupal/Core/Extension/ExtensionList.php
@@ -342,7 +342,7 @@ public function getExtensionInfo($extension_name) {
     if (isset($all_info[$extension_name])) {
       return $all_info[$extension_name];
     }
-    throw new \InvalidArgumentException("The {$this->type} $extension_name does not exist or is not installed.");
+    throw new \InvalidArgumentException("The {$this->type} $extension_name is not installed or does not exist.");
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Extension/ModuleExtensionList.php b/core/lib/Drupal/Core/Extension/ModuleExtensionList.php
index 01fcf59095..62b5d92eed 100644
--- a/core/lib/Drupal/Core/Extension/ModuleExtensionList.php
+++ b/core/lib/Drupal/Core/Extension/ModuleExtensionList.php
@@ -35,7 +35,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;
 
@@ -56,14 +56,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;
@@ -139,13 +139,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/ModuleInstaller.php b/core/lib/Drupal/Core/Extension/ModuleInstaller.php
index 754be196c3..f1ffddb8d6 100644
--- a/core/lib/Drupal/Core/Extension/ModuleInstaller.php
+++ b/core/lib/Drupal/Core/Extension/ModuleInstaller.php
@@ -350,7 +350,7 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) {
     if ($uninstall_dependents) {
       // Add dependent modules to the list. The new modules will be processed as
       // the foreach loop continues.
-      $profile = drupal_get_profile();
+      $profiles = \Drupal::service('extension.list.profile')->getAncestors();
       foreach ($module_list as $module => $value) {
         foreach (array_keys($module_data[$module]->required_by) as $dependent) {
           if (!isset($module_data[$dependent])) {
@@ -358,8 +358,8 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) {
             return FALSE;
           }
 
-          // Skip already uninstalled modules.
-          if (isset($installed_modules[$dependent]) && !isset($module_list[$dependent]) && $dependent != $profile) {
+          // Skip already uninstalled modules and dependencies of profiles.
+          if (isset($installed_modules[$dependent]) && !isset($module_list[$dependent]) && (!array_key_exists($dependent, $profiles))) {
             $module_list[$dependent] = $dependent;
           }
         }
diff --git a/core/lib/Drupal/Core/Extension/ProfileExtensionList.php b/core/lib/Drupal/Core/Extension/ProfileExtensionList.php
index bb388b25fe..93ad20030a 100644
--- a/core/lib/Drupal/Core/Extension/ProfileExtensionList.php
+++ b/core/lib/Drupal/Core/Extension/ProfileExtensionList.php
@@ -16,13 +16,158 @@ 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_get_profile();
+    }
+    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() {
+    $extensions = parent::doList();
+
+    foreach ($extensions as $name => $extension) {
+      // List the profiles which depend on this one.
+      $extension->children = [];
+      // List the profiles that this one depends on, in order.
+      $extension->ancestors = [];
+      // Give the profile a heavy weight to ensure its hooks run last.
+      $extension->weight = 1000;
+
+      $info = &$extension->info;
+
+      if (!empty($info['base profile'])) {
+        $parent = $info['base profile'];
+        $extensions[$parent]->children[] = $name;
+        $extension->ancestors = $this->computeAncestry($extensions, $extension);
+        $extension->weight += count($extension->ancestors);
+      }
+
+      // Add all ancestral dependencies and themes.
+      foreach ($extension->ancestors as $ancestor) {
+        $info['dependencies'] = array_merge($info['dependencies'], $extensions[$ancestor]->info['dependencies']);
+        $info['themes'] = array_merge($info['themes'], $extensions[$ancestor]->info['themes']);
+      }
+      $info['dependencies'] = array_unique($info['dependencies']);
+      $info['themes'] = array_unique($info['themes']);
+    }
+    return $extensions;
+  }
+
+  /**
+   * 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/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/config/tests/src/Functional/ConfigImportInstallProfileTest.php b/core/modules/config/tests/src/Functional/ConfigImportInstallProfileTest.php
index 8b0787d6ee..d8d33da0e6 100644
--- a/core/modules/config/tests/src/Functional/ConfigImportInstallProfileTest.php
+++ b/core/modules/config/tests/src/Functional/ConfigImportInstallProfileTest.php
@@ -56,7 +56,7 @@ public function testInstallProfileValidation() {
 
     $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 config import profile since it is the install profile.');
+    $this->assertText('Unable to uninstall the Testing config import profile since it is the main install profile.');
 
     // Uninstall dependencies of testing_config_import.
     $core['module']['testing_config_import'] = 0;
diff --git a/core/modules/system/src/Form/ModulesUninstallForm.php b/core/modules/system/src/Form/ModulesUninstallForm.php
index 7949067925..2e196820f8 100644
--- a/core/modules/system/src/Form/ModulesUninstallForm.php
+++ b/core/modules/system/src/Form/ModulesUninstallForm.php
@@ -116,12 +116,15 @@ public function buildForm(array $form, FormStateInterface $form_state) {
       return $form;
     }
 
-    $profile = drupal_get_profile();
+    $profiles = \Drupal::service('extension.list.profile')->getAncestors();
 
     // Sort all modules by their name.
     uasort($uninstallable, 'system_sort_modules_by_info_name');
     $validation_reasons = $this->moduleInstaller->validateUninstall(array_keys($uninstallable));
 
+    // Remove any profiles from the list.
+    $uninstallable = array_diff_key($uninstallable, $profiles);
+
     $form['uninstall'] = ['#tree' => TRUE];
     foreach ($uninstallable as $module_key => $module) {
       $name = $module->info['name'] ?: $module->getName();
@@ -142,10 +145,10 @@ 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. (The installation profile
-      // is excluded from this list.)
+      // we can allow this module to be uninstalled. (Installation profiles are
+      // excluded from this list.)
       foreach (array_keys($module->required_by) as $dependent) {
-        if ($dependent != $profile && drupal_get_installed_schema_version($dependent) != SCHEMA_UNINSTALLED) {
+        if (!in_array($dependent, array_keys($profiles)) && drupal_get_installed_schema_version($dependent) != SCHEMA_UNINSTALLED) {
           $name = isset($modules[$dependent]->info['name']) ? $modules[$dependent]->info['name'] : $dependent;
           $form['modules'][$module->getName()]['#required_by'][] = $name;
           $form['uninstall'][$module->getName()]['#disabled'] = TRUE;
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/testing_inherited.info.yml b/core/profiles/testing_inherited/testing_inherited.info.yml
new file mode 100644
index 0000000000..099ecc55e7
--- /dev/null
+++ b/core/profiles/testing_inherited/testing_inherited.info.yml
@@ -0,0 +1,15 @@
+name: Testing Inherited
+type: profile
+description: 'Profile for testing base profile inheritance.'
+version: VERSION
+core: 8.x
+hidden: true
+
+base profile: testing
+
+dependencies:
+  - block
+  - config
+
+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..4e4bed9732
--- /dev/null
+++ b/core/profiles/testing_inherited/tests/src/Functional/InheritedProfileTest.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Drupal\Tests\testing_inherited\Functional;
+
+use Drupal\block\BlockInterface;
+use Drupal\block\Entity\Block;
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Tests inherited profiles.
+ *
+ * @group profiles
+ */
+class InheritedProfileTest extends BrowserTestBase {
+
+  /**
+   * {@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->assertEquals('stable', $this->config('system.theme')->get('default'));
+
+    // Check that parent dependencies are installed.
+    $this->assertTrue(\Drupal::moduleHandler()->moduleExists('config'));
+
+    // Check that all themes were installed.
+    $this->assertTrue(\Drupal::service('theme_handler')->themeExists('stable'));
+  }
+
+}
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..c65d3d3214
--- /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
+core: 8.x
+hidden: true
+
+base profile: testing_inherited
+
+dependencies:
+  - syslog
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..847c77fd84
--- /dev/null
+++ b/core/profiles/testing_subsubprofile/tests/src/Functional/DeepInheritedProfileTest.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Drupal\Tests\testing_subsubprofile\Functional;
+
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Tests inherited profiles.
+ *
+ * @group profiles
+ */
+class DeepInheritedProfileTest extends BrowserTestBase {
+
+  /**
+   * {@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->assertEquals('stable', $this->config('system.theme')->get('default'));
+
+    // page_cache was enabled in main profile.
+    $this->assertTrue(\Drupal::moduleHandler()->moduleExists('page_cache'));
+    // block was enabled in parent profile.
+    $this->assertTrue(\Drupal::moduleHandler()->moduleExists('block'));
+    // syslog was enabled in this profile.
+    $this->assertTrue(\Drupal::moduleHandler()->moduleExists('syslog'));
+  }
+
+}
diff --git a/core/tests/Drupal/KernelTests/Core/Config/ConfigImporterTest.php b/core/tests/Drupal/KernelTests/Core/Config/ConfigImporterTest.php
index 4024b7f9d9..c2f9919dc0 100644
--- a/core/tests/Drupal/KernelTests/Core/Config/ConfigImporterTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Config/ConfigImporterTest.php
@@ -690,9 +690,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.']);
     }
   }
 
diff --git a/core/tests/Drupal/KernelTests/Core/Extension/ModuleExtensionListTest.php b/core/tests/Drupal/KernelTests/Core/Extension/ModuleExtensionListTest.php
index 3ffef4a714..0a2faaf27e 100644
--- a/core/tests/Drupal/KernelTests/Core/Extension/ModuleExtensionListTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Extension/ModuleExtensionListTest.php
@@ -2,7 +2,6 @@
 
 namespace Drupal\KernelTests\Core\Extension;
 
-use Drupal\Core\Site\Settings;
 use Drupal\KernelTests\KernelTestBase;
 
 /**
@@ -15,10 +14,6 @@ class ModuleExtensionListTest extends KernelTestBase {
    * @covers ::getList
    */
   public function testGetlist() {
-    $settings = Settings::getAll();
-    $settings['install_profile'] = 'testing';
-    new Settings($settings);
-
     \Drupal::configFactory()->getEditable('core.extension')
       ->set('module.testing', 1000)
       ->save();
diff --git a/core/tests/Drupal/KernelTests/Core/Extension/ProfileExtensionListTest.php b/core/tests/Drupal/KernelTests/Core/Extension/ProfileExtensionListTest.php
new file mode 100644
index 0000000000..03ae12672f
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/Core/Extension/ProfileExtensionListTest.php
@@ -0,0 +1,145 @@
+<?php
+
+namespace Drupal\KernelTests\Core\Extension;
+
+use Drupal\Core\Extension\Extension;
+use Drupal\Core\Extension\ProfileExtensionList;
+use Drupal\KernelTests\KernelTestBase;
+
+/**
+ * Tests the ProfileExtensionList class.
+ *
+ * @coversDefaultClass \Drupal\Core\Extension\ProfileExtensionList
+ *
+ * @group Extension
+ */
+class ProfileExtensionListTest extends KernelTestBase {
+
+  /**
+   * Tests getting profile info.
+   *
+   * @covers ::getExtensionInfo
+   */
+  public function testGetExtensionInfo() {
+    /** @var \Drupal\Core\Extension\ProfileExtensionList $profile_list */
+    $profile_list = $this->container->get('extension.list.profile');
+
+    $info = $profile_list->getExtensionInfo('testing_inherited');
+    $this->assertNotEmpty($info);
+    $this->assertSame($info['name'], 'Testing Inherited');
+    $this->assertSame($info['base profile'], 'testing');
+    $this->assertContains('config', $info['dependencies'], 'config should be found in dependencies as it is a dependency of this profile.');
+    $this->assertContains('page_cache', $info['dependencies'], 'page_cache should be found in dependencies as it is a dependency of the parent profile.');
+    $this->assertTrue($info['hidden'], 'Profiles should be hidden');
+
+    // Test that profiles without any base return normalized info.
+    $info = $profile_list->getExtensionInfo('minimal');
+    $this->assertSame('', $info['base profile']);
+
+    // Tests three levels profile inheritance.
+    $info = $profile_list->getExtensionInfo('testing_subsubprofile');
+    $this->assertSame($info['base profile'], 'testing_inherited');
+  }
+
+  /**
+   * Tests getting profile dependency list.
+   *
+   * @covers ::getAncestors
+   */
+  public function testGetAncestors() {
+    /** @var \Drupal\Core\Extension\ProfileExtensionList $profile_list */
+    $profile_list = $this->container->get('extension.list.profile');
+
+    $profiles = $profile_list->getAncestors('testing');
+    $this->assertCount(1, $profiles);
+
+    $profiles = $profile_list->getAncestors('testing_inherited');
+    $this->assertCount(2, $profiles);
+
+    $profiles = $profile_list->getAncestors('testing_subsubprofile');
+    $this->assertCount(3, $profiles);
+
+    $first_profile = current($profiles);
+    $this->assertInstanceOf(Extension::class, $first_profile);
+    $this->assertSame($first_profile->getName(), 'testing');
+    $this->assertSame(1000, $first_profile->weight);
+    $this->assertObjectHasAttribute('origin', $first_profile);
+
+    $second_profile = next($profiles);
+    $this->assertInstanceOf(Extension::class, $second_profile);
+    $this->assertSame($second_profile->getName(), 'testing_inherited');
+    $this->assertSame(1001, $second_profile->weight);
+    $this->assertObjectHasAttribute('origin', $second_profile);
+
+    $third_profile = next($profiles);
+    $this->assertInstanceOf(Extension::class, $third_profile);
+    $this->assertSame($third_profile->getName(), 'testing_subsubprofile');
+    $this->assertSame(1002, $third_profile->weight);
+    $this->assertObjectHasAttribute('origin', $third_profile);
+  }
+
+  /**
+   * @covers ::selectDistribution
+   *
+   * @depends testGetExtensionInfo
+   */
+  public function testSelectDistribution() {
+    $profile_list = new TestProfileExtensionList(
+      $this->container->get('app.root'),
+      'profile',
+      $this->container->get('cache.default'),
+      $this->container->get('info_parser'),
+      $this->container->get('module_handler'),
+      $this->container->get('state'),
+      $this->container->getParameter('install_profile')
+    );
+
+    $profiles = ['testing', 'testing_inherited'];
+    $base_info = $profile_list->getExtensionInfo('minimal');
+    $profile_info = $profile_list->getExtensionInfo('testing_inherited');
+
+    // Neither profile has distribution set.
+    $distribution = $profile_list->selectDistribution($profiles);
+    $this->assertEmpty($distribution, 'No distribution should be selected');
+
+    // Set base profile distribution.
+    $base_info['distribution']['name'] = 'Minimal';
+    $profile_list->profileInfo['minimal'] = $base_info;
+    // Base profile distribution should not be selected.
+    $distribution = $profile_list->selectDistribution($profiles);
+    $this->assertEmpty($distribution, 'Base profile distribution should not be selected');
+
+    // Set main profile distribution.
+    $profile_info['distribution']['name'] = 'Testing Inherited';
+    $profile_list->profileInfo['testing_inherited'] = $profile_info;
+    // Main profile distribution should be selected.
+    $distribution = $profile_list->selectDistribution($profiles);
+    $this->assertEquals($distribution, 'testing_inherited');
+  }
+
+}
+
+final class TestProfileExtensionList extends ProfileExtensionList {
+
+  /**
+   * Overridden profile info, keyed by extension name.
+   *
+   * @var array
+   */
+  public $profileInfo = [];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getList() {
+    $extensions = parent::getList();
+
+    foreach ($extensions as $name => $extension) {
+      if (isset($this->profileInfo[$name])) {
+        $extension->info = $this->profileInfo[$name];
+      }
+    }
+    return $extensions;
+  }
+
+}
