diff --git a/core/includes/project.inc b/core/includes/project.inc
new file mode 100644
index 0000000..fd1886f
--- /dev/null
+++ b/core/includes/project.inc
@@ -0,0 +1,271 @@
+<?php
+
+/**
+ * @file
+ * API for building lists of installed projects.
+ */
+
+/**
+ * Populates an array of project data.
+ *
+ * This iterates over a list of the installed modules or themes and groups
+ * them by project and status. A few parts of this function assume that
+ * enabled modules and themes are always processed first, and if disabled
+ * modules or themes are being processed (there is a setting to control if
+ * disabled code should be included in the Available updates report or not),
+ * those are only processed after $projects has been populated with
+ * information about the enabled code. 'Hidden' modules are always ignored.
+ * 'Hidden' themes are ignored only if they have no enabled sub-themes.
+ * This function also records the latest change time on the .info.yml
+ * files for each module or theme, which is important data which is used when
+ * deciding if the available update data should be invalidated.
+ *
+ * @param $projects
+ *   Reference to the array of project data of what's installed on this site.
+ * @param $list
+ *   Array of data to process to add the relevant info to the $projects array.
+ * @param $project_type
+ *   The kind of data in the list. Can be 'module' or 'theme'.
+ * @param $status
+ *   Boolean that controls what status (enabled or disabled) to process out of
+ *   the $list and add to the $projects array.
+ * @param $additional_whitelist
+ *   (optional) Array of additional elements to be collected from the .info.yml
+ *   file. Defaults to array().
+ *
+ * @see update_get_projects()
+ */
+function drupal_project_process_info_list(&$projects, $list, $project_type, $status, $additional_whitelist = array()) {
+  foreach ($list as $file) {
+    // A disabled or hidden base theme of an enabled sub-theme still has all
+    // of its code run by the sub-theme, so we include it in our "enabled"
+    // projects list.
+    if ($status && !empty($file->sub_themes)) {
+      foreach ($file->sub_themes as $key => $name) {
+        // Build a list of enabled sub-themes.
+        if ($list[$key]->status) {
+          $file->enabled_sub_themes[$key] = $name;
+        }
+      }
+      // If the theme is disabled and there are no enabled subthemes, we
+      // should ignore this base theme for the enabled case. If the site is
+      // trying to display disabled themes, we'll catch it then.
+      if (!$file->status && empty($file->enabled_sub_themes)) {
+        continue;
+      }
+    }
+    // Otherwise, just add projects of the proper status to our list.
+    elseif ($file->status != $status) {
+      continue;
+    }
+
+    // Skip if the .info.yml file is broken.
+    if (empty($file->info)) {
+      continue;
+    }
+
+    // Skip if it's a hidden module or hidden theme without enabled sub-themes.
+    if (!empty($file->info['hidden']) && empty($file->enabled_sub_themes)) {
+      continue;
+    }
+
+    // If the .info.yml doesn't define the 'project', try to figure it out.
+    if (!isset($file->info['project'])) {
+      $file->info['project'] = drupal_project_get_project_name($file);
+    }
+
+    // If we still don't know the 'project', give up.
+    if (empty($file->info['project'])) {
+      continue;
+    }
+
+    // If we don't already know it, grab the change time on the .info.yml file
+    // itself. Note: we need to use the ctime, not the mtime (modification
+    // time) since many (all?) tar implementations will go out of their way to
+    // set the mtime on the files it creates to the timestamps recorded in the
+    // tarball. We want to see the last time the file was changed on disk,
+    // which is left alone by tar and correctly set to the time the .info.yml
+    // file was unpacked.
+    if (!isset($file->info['_info_file_ctime'])) {
+      $info_filename = dirname($file->uri) . '/' . $file->name . '.info.yml';
+      $file->info['_info_file_ctime'] = filectime($info_filename);
+    }
+
+    if (!isset($file->info['datestamp'])) {
+      $file->info['datestamp'] = 0;
+    }
+
+    $project_name = $file->info['project'];
+
+    // Figure out what project type we're going to use to display this module
+    // or theme. If the project name is 'drupal', we don't want it to show up
+    // under the usual "Modules" section, we put it at a special "Drupal Core"
+    // section at the top of the report.
+    if ($project_name == 'drupal') {
+      $project_display_type = 'core';
+    }
+    else {
+      $project_display_type = $project_type;
+    }
+    if (empty($status) && empty($file->enabled_sub_themes)) {
+      // If we're processing disabled modules or themes, append a suffix.
+      // However, we don't do this to a a base theme with enabled
+      // subthemes, since we treat that case as if it is enabled.
+      $project_display_type .= '-disabled';
+    }
+    // Add a list of sub-themes that "depend on" the project and a list of base
+    // themes that are "required by" the project.
+    if ($project_name == 'drupal') {
+      // Drupal core is always required, so this extra info would be noise.
+      $sub_themes = array();
+      $base_themes = array();
+    }
+    else {
+      // Add list of enabled sub-themes.
+      $sub_themes = !empty($file->enabled_sub_themes) ? $file->enabled_sub_themes : array();
+      // Add list of base themes.
+      $base_themes = !empty($file->base_themes) ? $file->base_themes : array();
+    }
+    if (!isset($projects[$project_name])) {
+      // Only process this if we haven't done this project, since a single
+      // project can have multiple modules or themes.
+      $projects[$project_name] = array(
+        'name' => $project_name,
+        // Only save attributes from the .info.yml file we care about so we do
+        // not bloat our RAM usage needlessly.
+        'info' => drupal_project_filter_project_info($file->info, $additional_whitelist),
+        'datestamp' => $file->info['datestamp'],
+        'includes' => array($file->name => $file->info['name']),
+        'project_type' => $project_display_type,
+        'project_status' => $status,
+        'sub_themes' => $sub_themes,
+        'base_themes' => $base_themes,
+      );
+    }
+    elseif ($projects[$project_name]['project_type'] == $project_display_type) {
+      // Only add the file we're processing to the 'includes' array for this
+      // project if it is of the same type and status (which is encoded in the
+      // $project_display_type). This prevents listing all the disabled
+      // modules included with an enabled project if we happen to be checking
+      // for disabled modules, too.
+      $projects[$project_name]['includes'][$file->name] = $file->info['name'];
+      $projects[$project_name]['info']['_info_file_ctime'] = max($projects[$project_name]['info']['_info_file_ctime'], $file->info['_info_file_ctime']);
+      $projects[$project_name]['datestamp'] = max($projects[$project_name]['datestamp'], $file->info['datestamp']);
+      if (!empty($sub_themes)) {
+        $projects[$project_name]['sub_themes'] += $sub_themes;
+      }
+      if (!empty($base_themes)) {
+        $projects[$project_name]['base_themes'] += $base_themes;
+      }
+    }
+    elseif (empty($status)) {
+      // If we have a project_name that matches, but the project_display_type
+      // does not, it means we're processing a disabled module or theme that
+      // belongs to a project that has some enabled code. In this case, we add
+      // the disabled thing into a separate array for separate display.
+      $projects[$project_name]['disabled'][$file->name] = $file->info['name'];
+    }
+  }
+}
+
+/**
+ * Determines what project a given file object belongs to.
+ *
+ * @param $file
+ *   A file object as returned by system_get_files_database().
+ *
+ * @return
+ *   The canonical project short name.
+ *
+ * @see system_get_files_database()
+ */
+function drupal_project_get_project_name($file) {
+  $project_name = '';
+  if (isset($file->info['project'])) {
+    $project_name = $file->info['project'];
+  }
+  elseif (isset($file->filename) && (strpos($file->filename, 'core/modules') === 0)) {
+    $project_name = 'drupal';
+  }
+  return $project_name;
+}
+
+/**
+ * Determines version and type information for currently installed projects.
+ *
+ * Processes the list of projects on the system to figure out the currently
+ * installed versions, and other information that is required before we can
+ * compare against the available releases to produce the status report.
+ *
+ * @param $projects
+ *   Array of project information from update_get_projects().
+ */
+function drupal_project_process_project_info(&$projects) {
+  foreach ($projects as $key => $project) {
+    // Assume an official release until we see otherwise.
+    $install_type = 'official';
+
+    $info = $project['info'];
+
+    if (isset($info['version'])) {
+      // Check for development snapshots
+      if (preg_match('@(dev|HEAD)@', $info['version'])) {
+        $install_type = 'dev';
+      }
+
+      // Figure out what the currently installed major version is. We need
+      // to handle both contribution (e.g. "5.x-1.3", major = 1) and core
+      // (e.g. "5.1", major = 5) version strings.
+      $matches = array();
+      if (preg_match('/^(\d+\.x-)?(\d+)\..*$/', $info['version'], $matches)) {
+        $info['major'] = $matches[2];
+      }
+      elseif (!isset($info['major'])) {
+        // This would only happen for version strings that don't follow the
+        // drupal.org convention. We let contribs define "major" in their
+        // .info.yml in this case, and only if that's missing would we hit this.
+        $info['major'] = -1;
+      }
+    }
+    else {
+      // No version info available at all.
+      $install_type = 'unknown';
+      $info['version'] = t('Unknown');
+      $info['major'] = -1;
+    }
+
+    // Finally, save the results we care about into the $projects array.
+    $projects[$key]['existing_version'] = $info['version'];
+    $projects[$key]['existing_major'] = $info['major'];
+    $projects[$key]['install_type'] = $install_type;
+  }
+}
+
+/**
+ * Filters the project .info.yml data to only save attributes we need.
+ *
+ * @param array $info
+ *   Array of .info.yml file data as returned by drupal_parse_info_file().
+ * @param $additional_whitelist
+ *   (optional) Array of additional elements to be collected from the .info.yml
+ *   file. Defaults to array().
+ *
+ * @return
+ *   Array of .info.yml file data we need for the update manager.
+ *
+ * @see drupal_project_process_info_list(drupal_project_process_info_list
+ */
+function drupal_project_filter_project_info($info, $additional_whitelist = array()) {
+  $whitelist = array(
+    '_info_file_ctime',
+    'datestamp',
+    'major',
+    'name',
+    'package',
+    'project',
+    'project status url',
+    'version',
+  );
+  $whitelist = array_merge($whitelist, $additional_whitelist);
+  return array_intersect_key($info, drupal_map_assoc($whitelist));
+}
diff --git a/core/modules/locale/locale.compare.inc b/core/modules/locale/locale.compare.inc
index 3cbaac5..3288154 100644
--- a/core/modules/locale/locale.compare.inc
+++ b/core/modules/locale/locale.compare.inc
@@ -8,11 +8,12 @@
 use Drupal\Core\Cache;
 
 /**
- * Load the common translation API.
+ * Load common APIs.
  */
 // @todo Combine functions differently in files to avoid unnecessary includes.
 // Follow-up issue http://drupal.org/node/1834298
 require_once __DIR__ . '/locale.translation.inc';
+require_once DRUPAL_ROOT . '/core/includes/project.inc';
 
 /**
  * Clear the project data table.
@@ -126,11 +127,6 @@ function locale_translation_build_projects() {
  *   Array of project data including .info.yml file data.
  */
 function locale_translation_project_list() {
-  // This function depends on Update module. We degrade gracefully.
-  if (!Drupal::moduleHandler()->moduleExists('update')) {
-    return array();
-  }
-
   $projects = &drupal_static(__FUNCTION__, array());
   if (empty($projects)) {
     module_load_include('compare.inc', 'update');
@@ -143,11 +139,11 @@ function locale_translation_project_list() {
     );
     $module_data = _locale_translation_prepare_project_list(system_rebuild_module_data(), 'module');
     $theme_data = _locale_translation_prepare_project_list(system_rebuild_theme_data(), 'theme');
-    update_process_info_list($projects, $module_data, 'module', TRUE, $additional_whitelist);
-    update_process_info_list($projects, $theme_data, 'theme', TRUE, $additional_whitelist);
+    drupal_project_process_info_list($projects, $module_data, 'module', TRUE, $additional_whitelist);
+    drupal_project_process_info_list($projects, $theme_data, 'theme', TRUE, $additional_whitelist);
     if ($config->get('translation.check_disabled_modules')) {
-      update_process_info_list($projects, $module_data, 'module', FALSE, $additional_whitelist);
-      update_process_info_list($projects, $theme_data, 'theme', FALSE, $additional_whitelist);
+      drupal_project_process_info_list($projects, $module_data, 'module', FALSE, $additional_whitelist);
+      drupal_project_process_info_list($projects, $theme_data, 'theme', FALSE, $additional_whitelist);
     }
 
     // Allow other modules to alter projects before fetching and comparing.
@@ -159,9 +155,10 @@ function locale_translation_project_list() {
 /**
  * Prepare module and theme data.
  *
- * Modify .info.yml file data before it is processed by update_process_info_list().
- * In order for update_process_info_list() to recognize a project, it requires
- * the 'project' parameter in the .info.yml file data.
+ * Modify .info.yml file data before it is processed by
+ * drupal_project_process_info_list(). In order for
+ * drupal_project_process_info_list() to recognize a project, it requires the
+ * 'project' parameter in the .info.yml file data.
  *
  * Custom modules or themes can bring their own gettext translation file. To
  * enable import of this file the module or theme defines "interface translation
@@ -179,8 +176,9 @@ function locale_translation_project_list() {
 function _locale_translation_prepare_project_list($data, $type) {
   foreach ($data as $name => $file) {
     // Include interface translation projects. To allow
-    // update_process_info_list() to identify this as a project the 'project'
-    // property is filled with the 'interface translation project' value.
+    // drupal_project_process_info_list() to identify this as a project the
+    // 'project' property is filled with the 'interface translation project'
+    // value.
     if (isset($file->info['interface translation project'])) {
       $data[$name]->info['project'] = $file->info['interface translation project'];
     }
diff --git a/core/modules/update/lib/Drupal/update/Tests/UpdateContribTest.php b/core/modules/update/lib/Drupal/update/Tests/UpdateContribTest.php
index 1fdd0b3..9129466 100644
--- a/core/modules/update/lib/Drupal/update/Tests/UpdateContribTest.php
+++ b/core/modules/update/lib/Drupal/update/Tests/UpdateContribTest.php
@@ -287,7 +287,7 @@ function testUpdateHiddenBaseTheme() {
     config('update_test.settings')->set('system_info', $system_info)->save();
     $projects = update_get_projects();
     $theme_data = system_rebuild_theme_data();
-    update_process_info_list($projects, $theme_data, 'theme', TRUE);
+    drupal_project_process_info_list($projects, $theme_data, 'theme', TRUE);
 
     $this->assertTrue(!empty($projects['update_test_basetheme']), 'Valid base theme (update_test_basetheme) was found.');
   }
diff --git a/core/modules/update/update.api.php b/core/modules/update/update.api.php
index b5621dd..eb01a5b 100644
--- a/core/modules/update/update.api.php
+++ b/core/modules/update/update.api.php
@@ -28,11 +28,11 @@
  *   includes all the metadata documented in the comments below for each project
  *   (either module or theme) that is currently enabled. The array is initially
  *   populated inside update_get_projects() with the help of
- *   update_process_info_list(), so look there for examples of how to populate
- *   the array with real values.
+ *   drupal_project_process_info_list(), so look there for examples of how to
+ *   populate the array with real values.
  *
  * @see update_get_projects()
- * @see update_process_info_list()
+ * @see update_process_info_list(drupal_project_process_info_list
  */
 function hook_update_projects_alter(&$projects) {
   // Hide a site-specific module from the list.
diff --git a/core/modules/update/update.compare.inc b/core/modules/update/update.compare.inc
index c8f5958..072d67b 100644
--- a/core/modules/update/update.compare.inc
+++ b/core/modules/update/update.compare.inc
@@ -6,13 +6,18 @@
  */
 
 /**
+ * Load the project API.
+ */
+require_once DRUPAL_ROOT . '/core/includes/project.inc';
+
+/**
  * Fetches an array of installed and enabled projects.
  *
  * This is only responsible for generating an array of projects (taking into
  * account projects that include more than one module or theme). Other
  * information like the specific version and install type (official release,
- * dev snapshot, etc) is handled later in update_process_project_info() since
- * that logic is only required when preparing the status report, not for
+ * dev snapshot, etc) is handled later in drupal_project_process_project_info()
+ * since that logic is only required when preparing the status report, not for
  * fetching the available release data.
  *
  * This array is fairly expensive to construct, since it involves a lot of disk
@@ -48,7 +53,7 @@
  *   - base_themes: If the project is a theme it contains an associative array
  *     of all base-themes.
  *
- * @see update_process_project_info()
+ * @see drupal_project_process_project_info()
  * @see update_calculate_project_data()
  * @see update_project_storage()
  */
@@ -61,11 +66,11 @@ function update_get_projects() {
       // Still empty, so we have to rebuild.
       $module_data = system_rebuild_module_data();
       $theme_data = system_rebuild_theme_data();
-      update_process_info_list($projects, $module_data, 'module', TRUE);
-      update_process_info_list($projects, $theme_data, 'theme', TRUE);
+      drupal_project_process_info_list($projects, $module_data, 'module', TRUE);
+      drupal_project_process_info_list($projects, $theme_data, 'theme', TRUE);
       if (config('update.settings')->get('check.disabled_extensions')) {
-        update_process_info_list($projects, $module_data, 'module', FALSE);
-        update_process_info_list($projects, $theme_data, 'theme', FALSE);
+        drupal_project_process_info_list($projects, $module_data, 'module', FALSE);
+        drupal_project_process_info_list($projects, $theme_data, 'theme', FALSE);
       }
       // Allow other modules to alter projects before fetching and comparing.
       drupal_alter('update_projects', $projects);
@@ -76,241 +81,7 @@ function update_get_projects() {
   return $projects;
 }
 
-/**
- * Populates an array of project data.
- *
- * This iterates over a list of the installed modules or themes and groups
- * them by project and status. A few parts of this function assume that
- * enabled modules and themes are always processed first, and if disabled
- * modules or themes are being processed (there is a setting to control if
- * disabled code should be included in the Available updates report or not),
- * those are only processed after $projects has been populated with
- * information about the enabled code. 'Hidden' modules are always ignored.
- * 'Hidden' themes are ignored only if they have no enabled sub-themes.
- * This function also records the latest change time on the .info.yml
- * files for each module or theme, which is important data which is used when
- * deciding if the available update data should be invalidated.
- *
- * @param $projects
- *   Reference to the array of project data of what's installed on this site.
- * @param $list
- *   Array of data to process to add the relevant info to the $projects array.
- * @param $project_type
- *   The kind of data in the list. Can be 'module' or 'theme'.
- * @param $status
- *   Boolean that controls what status (enabled or disabled) to process out of
- *   the $list and add to the $projects array.
- * @param $additional_whitelist
- *   (optional) Array of additional elements to be collected from the .info.yml
- *   file. Defaults to array().
- *
- * @see update_get_projects()
- */
-function update_process_info_list(&$projects, $list, $project_type, $status, $additional_whitelist = array()) {
-  foreach ($list as $file) {
-    // A disabled or hidden base theme of an enabled sub-theme still has all
-    // of its code run by the sub-theme, so we include it in our "enabled"
-    // projects list.
-    if ($status && !empty($file->sub_themes)) {
-      foreach ($file->sub_themes as $key => $name) {
-        // Build a list of enabled sub-themes.
-        if ($list[$key]->status) {
-          $file->enabled_sub_themes[$key] = $name;
-        }
-      }
-      // If the theme is disabled and there are no enabled subthemes, we
-      // should ignore this base theme for the enabled case. If the site is
-      // trying to display disabled themes, we'll catch it then.
-      if (!$file->status && empty($file->enabled_sub_themes)) {
-        continue;
-      }
-    }
-    // Otherwise, just add projects of the proper status to our list.
-    elseif ($file->status != $status) {
-      continue;
-    }
-
-    // Skip if the .info.yml file is broken.
-    if (empty($file->info)) {
-      continue;
-    }
-
-    // Skip if it's a hidden module or hidden theme without enabled sub-themes.
-    if (!empty($file->info['hidden']) && empty($file->enabled_sub_themes)) {
-      continue;
-    }
-
-    // If the .info.yml doesn't define the 'project', try to figure it out.
-    if (!isset($file->info['project'])) {
-      $file->info['project'] = update_get_project_name($file);
-    }
-
-    // If we still don't know the 'project', give up.
-    if (empty($file->info['project'])) {
-      continue;
-    }
-
-    // If we don't already know it, grab the change time on the .info.yml file
-    // itself. Note: we need to use the ctime, not the mtime (modification
-    // time) since many (all?) tar implementations will go out of their way to
-    // set the mtime on the files it creates to the timestamps recorded in the
-    // tarball. We want to see the last time the file was changed on disk,
-    // which is left alone by tar and correctly set to the time the .info.yml
-    // file was unpacked.
-    if (!isset($file->info['_info_file_ctime'])) {
-      $info_filename = dirname($file->uri) . '/' . $file->name . '.info.yml';
-      $file->info['_info_file_ctime'] = filectime($info_filename);
-    }
-
-    if (!isset($file->info['datestamp'])) {
-      $file->info['datestamp'] = 0;
-    }
-
-    $project_name = $file->info['project'];
-
-    // Figure out what project type we're going to use to display this module
-    // or theme. If the project name is 'drupal', we don't want it to show up
-    // under the usual "Modules" section, we put it at a special "Drupal Core"
-    // section at the top of the report.
-    if ($project_name == 'drupal') {
-      $project_display_type = 'core';
-    }
-    else {
-      $project_display_type = $project_type;
-    }
-    if (empty($status) && empty($file->enabled_sub_themes)) {
-      // If we're processing disabled modules or themes, append a suffix.
-      // However, we don't do this to a a base theme with enabled
-      // subthemes, since we treat that case as if it is enabled.
-      $project_display_type .= '-disabled';
-    }
-    // Add a list of sub-themes that "depend on" the project and a list of base
-    // themes that are "required by" the project.
-    if ($project_name == 'drupal') {
-      // Drupal core is always required, so this extra info would be noise.
-      $sub_themes = array();
-      $base_themes = array();
-    }
-    else {
-      // Add list of enabled sub-themes.
-      $sub_themes = !empty($file->enabled_sub_themes) ? $file->enabled_sub_themes : array();
-      // Add list of base themes.
-      $base_themes = !empty($file->base_themes) ? $file->base_themes : array();
-    }
-    if (!isset($projects[$project_name])) {
-      // Only process this if we haven't done this project, since a single
-      // project can have multiple modules or themes.
-      $projects[$project_name] = array(
-        'name' => $project_name,
-        // Only save attributes from the .info.yml file we care about so we do
-        // not bloat our RAM usage needlessly.
-        'info' => update_filter_project_info($file->info, $additional_whitelist),
-        'datestamp' => $file->info['datestamp'],
-        'includes' => array($file->name => $file->info['name']),
-        'project_type' => $project_display_type,
-        'project_status' => $status,
-        'sub_themes' => $sub_themes,
-        'base_themes' => $base_themes,
-      );
-    }
-    elseif ($projects[$project_name]['project_type'] == $project_display_type) {
-      // Only add the file we're processing to the 'includes' array for this
-      // project if it is of the same type and status (which is encoded in the
-      // $project_display_type). This prevents listing all the disabled
-      // modules included with an enabled project if we happen to be checking
-      // for disabled modules, too.
-      $projects[$project_name]['includes'][$file->name] = $file->info['name'];
-      $projects[$project_name]['info']['_info_file_ctime'] = max($projects[$project_name]['info']['_info_file_ctime'], $file->info['_info_file_ctime']);
-      $projects[$project_name]['datestamp'] = max($projects[$project_name]['datestamp'], $file->info['datestamp']);
-      if (!empty($sub_themes)) {
-        $projects[$project_name]['sub_themes'] += $sub_themes;
-      }
-      if (!empty($base_themes)) {
-        $projects[$project_name]['base_themes'] += $base_themes;
-      }
-    }
-    elseif (empty($status)) {
-      // If we have a project_name that matches, but the project_display_type
-      // does not, it means we're processing a disabled module or theme that
-      // belongs to a project that has some enabled code. In this case, we add
-      // the disabled thing into a separate array for separate display.
-      $projects[$project_name]['disabled'][$file->name] = $file->info['name'];
-    }
-  }
-}
-
-/**
- * Determines what project a given file object belongs to.
- *
- * @param $file
- *   A file object as returned by system_get_files_database().
- *
- * @return
- *   The canonical project short name.
- *
- * @see system_get_files_database()
- */
-function update_get_project_name($file) {
-  $project_name = '';
-  if (isset($file->info['project'])) {
-    $project_name = $file->info['project'];
-  }
-  elseif (isset($file->filename) && (strpos($file->filename, 'core/modules') === 0)) {
-    $project_name = 'drupal';
-  }
-  return $project_name;
-}
-
-/**
- * Determines version and type information for currently installed projects.
- *
- * Processes the list of projects on the system to figure out the currently
- * installed versions, and other information that is required before we can
- * compare against the available releases to produce the status report.
- *
- * @param $projects
- *   Array of project information from update_get_projects().
- */
-function update_process_project_info(&$projects) {
-  foreach ($projects as $key => $project) {
-    // Assume an official release until we see otherwise.
-    $install_type = 'official';
 
-    $info = $project['info'];
-
-    if (isset($info['version'])) {
-      // Check for development snapshots
-      if (preg_match('@(dev|HEAD)@', $info['version'])) {
-        $install_type = 'dev';
-      }
-
-      // Figure out what the currently installed major version is. We need
-      // to handle both contribution (e.g. "5.x-1.3", major = 1) and core
-      // (e.g. "5.1", major = 5) version strings.
-      $matches = array();
-      if (preg_match('/^(\d+\.x-)?(\d+)\..*$/', $info['version'], $matches)) {
-        $info['major'] = $matches[2];
-      }
-      elseif (!isset($info['major'])) {
-        // This would only happen for version strings that don't follow the
-        // drupal.org convention. We let contribs define "major" in their
-        // .info.yml in this case, and only if that's missing would we hit this.
-        $info['major'] = -1;
-      }
-    }
-    else {
-      // No version info available at all.
-      $install_type = 'unknown';
-      $info['version'] = t('Unknown');
-      $info['major'] = -1;
-    }
-
-    // Finally, save the results we care about into the $projects array.
-    $projects[$key]['existing_version'] = $info['version'];
-    $projects[$key]['existing_major'] = $info['major'];
-    $projects[$key]['install_type'] = $install_type;
-  }
-}
 
 /**
  * Calculates the current update status of all projects on the site.
@@ -330,9 +101,9 @@ function update_process_project_info(&$projects) {
  * @return
  *   An array of installed projects with current update status information.
  *
+ * @see drupal_project_process_project_info()
  * @see update_get_available()
  * @see update_get_projects()
- * @see update_process_project_info()
  * @see update_project_storage()
  */
 function update_calculate_project_data($available) {
@@ -344,7 +115,7 @@ function update_calculate_project_data($available) {
     return $projects;
   }
   $projects = update_get_projects();
-  update_process_project_info($projects);
+  drupal_project_process_project_info($projects);
   foreach ($projects as $project => $project_info) {
     if (isset($available[$project])) {
       update_calculate_project_update_status($projects[$project], $available[$project]);
@@ -800,32 +571,3 @@ function update_project_storage($key) {
   }
   return $projects;
 }
-
-/**
- * Filters the project .info.yml data to only save attributes we need.
- *
- * @param array $info
- *   Array of .info.yml file data as returned by drupal_parse_info_file().
- * @param $additional_whitelist
- *   (optional) Array of additional elements to be collected from the .info.yml
- *   file. Defaults to array().
- *
- * @return
- *   Array of .info.yml file data we need for the update manager.
- *
- * @see update_process_info_list()
- */
-function update_filter_project_info($info, $additional_whitelist = array()) {
-  $whitelist = array(
-    '_info_file_ctime',
-    'datestamp',
-    'major',
-    'name',
-    'package',
-    'project',
-    'project status url',
-    'version',
-  );
-  $whitelist = array_merge($whitelist, $additional_whitelist);
-  return array_intersect_key($info, drupal_map_assoc($whitelist));
-}
