diff --git project.test project.test
index d9a55d1..8dc4d4e 100644
--- project.test
+++ project.test
@@ -5,13 +5,18 @@ class ProjectWebTestCase extends DrupalWebTestCase {
   /**
    * Setup basic tasks for all project issue tests.
    */
-  function setUp() {
+  public function setUp() {
     // Setup the required modules for all tests.
     $modules = func_get_args();
-    $modules = array_merge(array('project'), $modules);
+    $modules = array_merge(array('views', 'project'), $modules);
     // We can't call parent::setUp() with a single array argument, so we need
     // this ugly call_user_func_array().
-    call_user_func_array(array($this, 'parent::setUp'), $modules);
+    if (version_compare(PHP_VERSION, '5.3.0') >= 0) {
+      call_user_func_array('parent::setUp', $modules);
+    }
+    else {
+      call_user_func_array(array($this, 'parent::setUp'), $modules);
+    }
 
     $perms = array('maintain projects', 'access user profiles', 'access projects');
 
@@ -88,8 +93,11 @@ class ProjectWebTestCase extends DrupalWebTestCase {
    *
    * @param $edit array
    *   An array of form values to be passed to DrupalWebTestCase::drupalPost().
+   * @return
+   *    A node object.
+   * @see DrupalWebTestCase::drupalGetNodeByTitle()
    */
-  function createProject($edit = array()) {
+  protected function createProject($edit = array()) {
     $edit += array(
       'title' => $this->randomName(),
       'project[uri]' => $this->randomName(8),
@@ -108,6 +116,43 @@ class ProjectWebTestCase extends DrupalWebTestCase {
 
     return $this->drupalGetNodeByTitle($edit['title']);
   }
+
+  /**
+   * Helper function for creating a new project release.
+   *
+   * @param $project
+   *   Project node object to create release for.
+   * @param $edit array
+   *   An array of form values to be passed to DrupalWebTestCase::drupalPost().
+   * @return
+   *    A node object.
+   * @see DrupalWebTestCase::drupalGetNodeByTitle()
+   */
+  protected function createRelease($project, $edit = array()) {
+    $edit += array(
+      'project_release[version_major]' => 1,
+      'project_release[version_minor]' => '',
+      'project_release[version_patch]' =>  'x',
+      'project_release[version_extra]' => 'dev',
+      'body' => $this->randomString(128),
+    );
+    $this->drupalGet('node/add/project-release/' . $project->nid);
+    if (!$this->xpath('//input[@name="project_release[version_minor]"]')) {
+      unset($edit['project_release[version_minor]']);
+    }
+    $this->drupalPost('node/add/project-release/' . $project->nid, $edit, t('Save'));
+
+    $version = new stdClass();
+    foreach (array('major', 'minor', 'patch', 'extra') as $field) {
+      $field = 'version_' . $field;
+      $version->$field = isset($edit['project_release[' . $field . ']']) ? $edit['project_release[' . $field . ']'] : NULL;
+    }
+    $version = project_release_get_version($version, $project);
+    $title = $project->project['uri'] . ' ' . $version;
+    $this->assertRaw(t('!post %title has been created.', array('!post' => 'Project release', '%title' => $title)), t('Project release created.'));
+
+    return $this->drupalGetNodeByTitle($title);
+  }
 }
 
 
@@ -350,12 +395,11 @@ class ProjectDrupalOrgWebTestCase extends ProjectWebTestCase {
         'project_type' => $core ? 1 : 2,
         'title' => ucfirst($project),
         'project[uri]' => $project,
-        'body' => $this->randomString(64),
       );
       $this->projects[$project] = $this->createProject($edit);
     }
 
-    foreach ($this->projects as $project) {
+    foreach ($this->projects as $name => $project) {
       $core = ($project->title == 'Drupal') ? TRUE : FALSE;
       // Drupal core requires a special version format string.
       if ($core) {
@@ -369,13 +413,8 @@ class ProjectDrupalOrgWebTestCase extends ProjectWebTestCase {
       $edit = array(
         'taxonomy[3][]' => 8, // 7.x.
         'project_release[version_major]' => $core ? '7' : '1',
-        'project_release[version_patch]' => 'x',
-        'project_release[version_extra]' => 'dev',
-        'body' => $this->randomString(64),
       );
-      $this->drupalPost('node/add/project-release/' . $project->nid, $edit, t('Save'));
-
-      $this->releases[$project->title] = $this->drupalGetNodeByTitle($project->title . ' 7.x' . ($core ? '' : ' -1.x') . '-dev');
+      $this->releases[$name] = $this->createRelease($project, $edit);
     }
   }
 }
diff --git release/package-release-nodes.php release/package-release-nodes.php
index d99db67..b340627 100755
--- release/package-release-nodes.php
+++ release/package-release-nodes.php
@@ -316,6 +316,8 @@ function package_release_core($type, $nid, $project_short_name, $version, $tag)
   global $tmp_dir, $repositories, $dest_root, $dest_rel;
   global $cvs, $tar, $gzip, $rm;
 
+  module_load_include('drupal.inc', 'project_release');
+
   if (!drupal_chdir($tmp_dir)) {
     return 'error';
   }
@@ -353,6 +355,9 @@ function package_release_core($type, $nid, $project_short_name, $version, $tag)
     }
   }
 
+  // Process the module .info files.
+  package_release_info_process_all($nid, $info_files, $project_short_name, $version);
+
   if (!drupal_exec("$tar -c --file=- $release_file_id | $gzip -9 --no-name > $full_dest_tgz")) {
     return 'error';
   }
@@ -379,6 +384,8 @@ function package_release_contrib($type, $nid, $project_short_name, $version, $ta
   global $drush, $drush_make_dir;
   global $license, $trans_install;
 
+  module_load_include('drupal.inc', 'project_release');
+
   // Files to ignore when checking timestamps:
   $exclude = array('.', '..', 'LICENSE.txt');
 
@@ -433,6 +440,9 @@ function package_release_contrib($type, $nid, $project_short_name, $version, $ta
     }
   }
 
+  // Process the module .info files.
+  package_release_info_process_all($nid, $info_files, $project_short_name, $version);
+
   // Link not copy, since we want to preserve the date...
   if (!drupal_exec("$ln -sf $license $project_short_name/LICENSE.txt")) {
     return 'error';
diff --git release/project_release.batch.inc release/project_release.batch.inc
new file mode 100644
index 0000000..e66ea7a
--- /dev/null
+++ release/project_release.batch.inc
@@ -0,0 +1,203 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Parses a batch of releases to build project release info tables.
+ *
+ * @author Jimmy Berry ("boombatower", http://drupal.org/user/214218)
+ */
+
+/**
+ * Directory to checkout modules during processing.
+ *
+ * The directory should be empty since it will be cleared before each checkout.
+ */
+define('PROJECT_RELEASE_INFO_BATCH_DIRECTORY', '/tmp/project-info');
+
+/**
+ * Load Drupal implementation by default.
+ */
+module_load_include('drupal.inc', 'project_release');
+
+/**
+ * Legacy parsing form.
+ *
+ * Provide a form to select the API versions that releases must be compatible
+ * with and initiate the batch process to perform the parsing.
+ */
+function project_release_info_batch_form(&$form_state) {
+  // Ensure that the directory constant has been set.
+  if (empty(PROJECT_RELEASE_INFO_BATCH_DIRECTORY)) {
+    form_set_error('', t('The constant PROJECT_RELEASE_INFO_BATCH_DIRECTORY must be set.'));
+  }
+
+  $api_vid = _project_release_get_api_vid();
+  $form['filter'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Filter'),
+  );
+  $form['filter']['api'] = array(
+    '#type' => 'checkboxes',
+    '#title' => t('API'),
+    '#description' => t('Select the API versions that releases must be compatible with.'),
+    '#options' => array(),
+    '#default_value' => array(),
+  );
+  $terms = project_release_get_api_taxonomy();
+  foreach ($terms as $term) {
+    $form['filter']['api']['#options'][$term->tid] = $term->name;
+    // Defaultly select
+    if (($major = array_shift(explode('.', $term->name, 2))) && $major >= PROJECT_RELEASE_INFO_MINIMUM_API) {
+      $form['filter']['api']['#default_value'][] = $term->tid;
+    }
+  }
+  $form['submit'] = array('#type' => 'submit', '#value' => t('Submit'));
+
+  return $form;
+}
+
+/**
+ * Start the batch to process batch releases.
+ */
+function project_release_info_batch_form_submit($form, &$form_state) {
+  $tids = array_keys(array_filter($form_state['values']['api']));
+
+  $batch = array(
+    'title' => t('Processing releases'),
+    'operations' => array(
+      array('project_release_info_batch_batch_operation_parse', array($tids)),
+      array('project_release_info_batch_batch_operation_dependencies', array()),
+    ),
+    'finished' => 'project_release_info_batch_batch_finished',
+    'init_message' => t('Determining releases to parse...'),
+    'file' => drupal_get_path('module', 'project_release_info') . '/project_release_info.batch.inc',
+  );
+  batch_set($batch);
+}
+
+/**
+ * Parse the modules contained within a project and their dependencies.
+ */
+function project_release_info_batch_batch_operation_parse(array $tids, &$context) {
+  if (!isset($context['sandbox']['max'])) {
+    // First iteration, initialize working values.
+    $context['sandbox']['max'] = project_release_info_batch_releases($tids, 0, TRUE);
+    $context['sandbox']['release_id'] = 0;
+    $context['sandbox']['progress'] = 0;
+    $context['results']['dependencies'] = array();
+    $context['results']['count'] = 0;
+  }
+
+  // Load the package include that contains functions to save and parse data.
+  module_load_include('package.inc', 'project_release');
+
+  $releases = project_release_info_batch_releases($tids, $context['sandbox']['release_id']);
+  foreach ($releases as $release) {
+    if (($dependencies = project_release_info_batch_process_release($release)) === FALSE) {
+      $context['results']['error'] = $release;
+      $context['finished'] = 1;
+      return;
+    }
+    $context['results']['dependencies'][$release['nid']] = $dependencies;
+    $context['sandbox']['release_id'] = $release['nid'];
+    $context['sandbox']['progress']++;
+    $context['results']['count']++;
+  }
+
+  $context['message'] = t('Last parsed %module release %tag (@progress of @max).', array(
+    '%module' => $release['uri'],
+    '%tag' => $release['tag'],
+    '@progress' => number_format($context['sandbox']['progress']),
+    '@max' => number_format($context['sandbox']['max']),
+  ));
+
+  $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
+}
+
+/**
+ * Determine dependencies of releases.
+ */
+function project_release_info_batch_batch_operation_dependencies(&$context) {
+  if (!isset($context['sandbox']['max'])) {
+    $context['sandbox']['max'] = count($context['results']['dependencies']);
+    $context['sandbox']['progress'] = 0;
+  }
+
+  // Load the package include that contains functions to save and parse data.
+  module_load_include('package.inc', 'project_release');
+
+  $i = 0;
+  foreach ($context['results']['dependencies'] as $rid => $modules_dependencies) {
+    $modules = project_release_info_module_list_get($rid);
+    foreach ($modules_dependencies as $module => $dependencies) {
+      package_release_info_process_dependencies($rid, $modules[$module], $dependencies);
+    }
+
+    // Remove already processed releases.
+    unset($context['results']['dependencies'][$rid]);
+
+    $context['sandbox']['progress']++;
+
+    // Only process 20 releases in a single batch operation.
+    if (++$i < 20) {
+      break;
+    }
+  }
+
+  $context['message'] = t('Processed @progress of @max release dependencies.', array(
+    '@progress' => number_format($context['sandbox']['progress']),
+    '@max' => number_format($context['sandbox']['max']),
+  ));
+
+  $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
+}
+
+/**
+ * Display relevant finished message after batch run.
+ */
+function project_release_info_batch_batch_finished($success, $results, $operations) {
+  if ($success && !isset($results['error'])) {
+    drupal_set_message(t('Processed @count release(s).', array(
+      '@count' => number_format($results['count']),
+    )));
+  }
+  else {
+    drupal_set_message(t('Failed to process @module release #@rid.', array(
+      '@module' => $results['error']['uri'],
+      '@rid' => $results['error']['nid'],
+    )), 'error');
+  }
+}
+
+/**
+ * Select all releases that are compatible with the API tids.
+ *
+ * @return
+ *   List of release information: rid, project uri, and cvs directory.
+ */
+function project_release_info_batch_releases(array $tids, $release_id, $count = FALSE) {
+  $fields = ($count ? 'COUNT(r.nid)' : 'r.nid, r.tag, p.uri, cp.directory');
+  $placeholders = db_placeholders($tids, 'int');
+  $sql = "SELECT $fields
+    FROM {project_release_nodes} r
+    INNER JOIN {node} n ON r.nid = n.nid
+    INNER JOIN {term_node} t ON n.vid = t.vid
+    INNER JOIN {project_projects} p ON r.pid = p.nid
+    INNER JOIN {cvs_projects} cp ON p.nid = cp.nid
+    WHERE t.tid IN ($placeholders) AND r.nid > %d";
+  $args = array_merge($tids, array($release_id));
+
+  if ($count) {
+    return db_result(db_query($sql, $args));
+  }
+
+  $sql .= ' ORDER BY r.nid';
+  $result = db_query_range($sql, $args, 0, 10);
+  $releases = array();
+  while ($release = db_fetch_array($result)) {
+    $releases[] = $release;
+  }
+  return $releases;
+}
+
diff --git release/project_release.drupal.inc release/project_release.drupal.inc
new file mode 100644
index 0000000..07883a6
--- /dev/null
+++ release/project_release.drupal.inc
@@ -0,0 +1,387 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Provides Drupal implementation of project release info parsing and batch API.
+ *
+ * @author Jimmy Berry ("boombatower", http://drupal.org/user/214218)
+ */
+
+/**
+ * Regular expression to match PHP function names. (From Drupal 7)
+ *
+ * @see http://php.net/manual/en/language.functions.php
+ */
+define('DRUPAL_PHP_FUNCTION_PATTERN', '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*');
+
+module_load_include('package.inc', 'project_release');
+
+/**
+ * Minimum API compatibility to process.
+ */
+define('PROJECT_RELEASE_INFO_MINIMUM_API', 6);
+
+/**
+ * Parse Drupal info files for project information.
+ *
+ * @param $release_nid
+ *   The project release NID.
+ * @param $info_files
+ *   List of .info files to be processed.
+ * @param $uri
+ *   The short name of the project.
+ * @param $version
+ *   The version of the release being processed.
+ */
+function package_release_info_process_all($release_nid, array $info_files, $uri, $version) {
+  // Only process .info file for modules that meet the minimum API requirement.
+  if (($major = array_shift(explode('.', $version, 2))) && $major >= PROJECT_RELEASE_INFO_MINIMUM_API) {
+    // Clear previous records for the release.
+    project_release_info_package_clear($release_nid);
+
+    $info = package_release_info_parse($info_files);
+
+    // Store list of components.
+    $records = project_release_info_package_list_store($release_nid, $info);
+
+    // Process list of dependencies to determine the release related to each
+    // dependency.
+    package_release_info_process($release_nid, $records, $info);
+  }
+}
+
+/**
+ * Parse all the .info files.
+ *
+ * @param $info_files
+ *   List of component .info files.
+ *
+ * @return
+ *   Associative array of component information keyed by component name and
+ *   merged with an array of defaults.
+ */
+function package_release_info_parse(array $info_files) {
+  $defaults = array(
+    'name' => 'Unknown',
+    'description' => '',
+    'dependencies' => array(),
+    'test_dependencies' => array(),
+  );
+
+  $info = array();
+  foreach ($info_files as $file) {
+    $component = basename($file, '.info');
+    $info[$component] = drupal_parse_info_file($file) + $defaults;
+
+    // Change info keys to suite project_release_info.
+    $info[$component]['title'] = $info[$component]['name'];
+    $info[$component]['name'] = $component;
+  }
+  return $info;
+}
+
+/**
+ * Determine and store the list of dependencies for a release.
+ *
+ * @param $release_nid
+ *   The project release NID.
+ * @param $records
+ *   List of saved component information from
+ *   project_release_info_package_list_store().
+ * @param $info
+ *   Associative array of information from component .info files keyed by component
+ *   name and merged with a set of detaults from package_release_info_parse().
+ */
+function package_release_info_process($release_nid, array $records, array $info) {
+  foreach ($info as $component => $component_info) {
+    if ($component_info['dependencies']) {
+      package_release_info_process_dependencies($release_nid, $records[$component], PROJECT_RELEASE_DEPENDENCY_REQUIRED, $component_info['dependencies']);
+    }
+    if ($component_info['test_dependencies']) {
+      package_release_info_process_dependencies($release_nid, $records[$component], PROJECT_RELEASE_DEPENDENCY_RECOMMENDED, $component_info['test_dependencies']);
+    }
+  }
+}
+
+/**
+ * Store the list of dependencies for a component of a release.
+ *
+ * @param $release_nid
+ *   The project release NID.
+ * @param $component
+ *   Component information, returned from project_release_info_package_list_store().
+ * @param $dependency_type
+ *   Dependency type, PROJECT_RELEASE_DEPENDENCY_*.
+ * @param $dependencies
+ *   List of dependency component names.
+ */
+function package_release_info_process_dependencies($release_nid, array $component, $dependency_type, array $dependencies) {
+  // Load the release and determine is core API version.
+  $release = node_load($release_nid);
+  if (!($api_term = package_release_info_core_api($release))) {
+    wd_err('ERROR: No core release API term found.');
+    return;
+  }
+
+  // Preprocess dependencies looking for duplications, or overrides. Since test
+  // dependencies were appended to regular dependencies they should override.
+  $dependency_info = array();
+  foreach ($dependencies as $dependency) {
+    $info = drupal_parse_dependency($dependency, $api_term->name);
+    if (empty($info['name'])) {
+      wd_err('ERROR: Invalid dependency found [' . $dependency . '] of [' . $component['name'] . '].');
+      return;
+    }
+    $dependency_info[$info['name']] = $info;
+  }
+
+  $dependency_components = array();
+  foreach ($dependency_info as $dependency => $info) {
+    $releases = package_release_info_releases_get($dependency, $api_term->tid);
+
+    // Check for dependency version restriction information.
+    if (empty($info['versions'])) {
+      // Cycle through the releases made by the project until a dev branch is
+      // found of the latest stable series that matches the core API. The
+      // releases are in descending order from the largest release version to
+      // the smallest release version.
+      $best_release = NULL;
+      foreach ($releases as $release) {
+        $release_node = node_load($release['release_nid']);
+        if ($release_node->project_release['rebuild']) {
+          // Release represents a dev branch, store it.
+          $best_release = $release;
+        }
+        elseif ($best_release) {
+          // Release represents a stable branch, since a dev branch has already
+          // been found then stop and use the dev branch as the best branch.
+          $dependency_components[$dependency] = $best_release['component_id'];
+          break;
+        }
+      }
+
+      // If no releases found, then generate error.
+      if (empty($dependency_components[$dependency])) {
+        wd_err('ERROR: No development release with a corresponding stable release was found that ' .
+          'matches the requirements for dependency [' . $dependency . '] of [' . $component['name'] . '].');
+        return;
+      }
+    }
+    else {
+      // Cycle through each release from highest version number to lowest and
+      // select the first dev release that matches the criteria.
+      foreach ($releases as $release) {
+        $release_node = node_load($release['release_nid']);
+
+        // Get the release version string without the Drupal API part.
+        $parts = explode('-', $release_node->project_release['version']);
+        unset($parts[0]);
+        $current = implode('-', $parts);
+
+        // Compare the release version string against the requirements.
+        $valid = TRUE;
+        foreach ($info['versions'] as $required) {
+          if ((isset($required_version['op']) && !version_compare($required, $required['version'], $required['op']))) {
+            $valid = FALSE;
+            break;
+          }
+        }
+
+        // If all the requirements were met then use the release.
+        if ($valid) {
+          $dependency_components[$dependency] = $release['component_id'];
+        }
+      }
+
+      // If no releases found, then generate error.
+      if (empty($dependency_components[$dependency])) {
+        wd_err('ERROR: No release found that matches requirements [' . trim($info['original_version']) .
+          '] for dependency [' . $dependency . '] of [' . $component['name'] . '].');
+        return;
+      }
+    }
+  }
+
+  // Store dependencies for component.
+  project_release_info_package_dependencies_store($component['component_id'], $dependency_type, $dependency_components);
+}
+
+/**
+ * Get the releases that contain a component and are compatible with an API.
+ *
+ * @param $component
+ *   Component name.
+ * @param $api_tid
+ *   Core API compatibility tid.
+ *
+ * @return
+ *   List of releases with keys: 'component_id', 'release_nid'.
+ */
+function package_release_info_releases_get($component, $api_tid) {
+  $result = db_query("SELECT DISTINCT p.component_id, p.release_nid
+    FROM {project_release_component} p
+    INNER JOIN {project_release_nodes} r ON p.release_nid = r.nid
+    INNER JOIN {term_node} t ON p.release_nid = t.nid
+    WHERE t.tid = %d AND p.name = '%s'
+    ORDER BY r.version DESC, t.vid DESC
+  ", $api_tid, $component);
+
+  $releases = array();
+  while ($release = db_fetch_array($result)) {
+    $releases[] = $release;
+  }
+  return $releases;
+}
+
+/**
+ * Attempt to determine the Drupal core API term.
+ *
+ * @param $node
+ *   Node object.
+ *
+ * @return
+ *   Core API term to which the node belongs, otherwise FALSE.
+ */
+function package_release_info_core_api($node) {
+  static $api_terms = array();
+
+  if (!isset($api_terms[$node->nid])) {
+    $api_terms[$node->nid] = FALSE;
+
+    if (!empty($node->taxonomy)) {
+      // Relase API version vocabulary.
+      $api_vid = _project_release_get_api_vid();
+
+      foreach ($node->taxonomy as $tid => $term) {
+        if ($term->vid == $api_vid) {
+          $api_terms[$node->nid] = $term;
+        }
+      }
+    }
+  }
+  return $api_terms[$node->nid];
+}
+
+/**
+ * Parse a dependency for comparison by drupal_check_incompatibility().
+ *
+ * @param $dependency
+ *   A dependency string, for example 'foo (>=7.x-4.5-beta5, 3.x)'.
+ *
+ * @return
+ *   An associative array with three keys:
+ *   - 'name' includes the name of the thing to depend on (e.g. 'foo').
+ *   - 'original_version' contains the original version string (which can be
+ *     used in the UI for reporting incompatibilities).
+ *   - 'versions' is a list of associative arrays, each containing the keys
+ *     'op' and 'version'. 'op' can be one of: '=', '==', '!=', '<>', '<',
+ *     '<=', '>', or '>='. 'version' is one piece like '4.5-beta3'.
+ *   Callers should pass this structure to drupal_check_incompatibility().
+ *
+ * @see drupal_check_incompatibility()
+ */
+function drupal_parse_dependency($dependency, $core_api) {
+  // We use named subpatterns and support every op that version_compare
+  // supports. Also, op is optional and defaults to equals.
+  $p_op = '(?P<operation>!=|==|=|<|<=|>|>=|<>)?';
+  // Core version is always optional: 7.x-2.x and 2.x is treated the same.
+  $p_core = '(?:' . preg_quote($core_api . '.x') . '-)?';
+  $p_major = '(?P<major>\d+)';
+  // By setting the minor version to x, branches can be matched.
+  $p_minor = '(?P<minor>(?:\d+|x)(?:-[A-Za-z]+\d+)?)';
+  $value = array();
+  $parts = explode('(', $dependency, 2);
+  $value['name'] = trim($parts[0]);
+  if (isset($parts[1])) {
+    $value['original_version'] = ' (' . $parts[1];
+    foreach (explode(',', $parts[1]) as $version) {
+      if (preg_match("/^\s*$p_op\s*$p_core$p_major\.$p_minor/", $version, $matches)) {
+        $op = !empty($matches['operation']) ? $matches['operation'] : '=';
+        if ($matches['minor'] == 'x') {
+          // Drupal considers "2.x" to mean any version that begins with
+          // "2" (e.g. 2.0, 2.9 are all "2.x"). PHP's version_compare(),
+          // on the other hand, treats "x" as a string; so to
+          // version_compare(), "2.x" is considered less than 2.0. This
+          // means that >=2.x and <2.x are handled by version_compare()
+          // as we need, but > and <= are not.
+          if ($op == '>' || $op == '<=') {
+            $matches['major']++;
+          }
+          // Equivalence can be checked by adding two restrictions.
+          if ($op == '=' || $op == '==') {
+            $value['versions'][] = array('op' => '<', 'version' => ($matches['major'] + 1) . '.x');
+            $op = '>=';
+          }
+        }
+        $value['versions'][] = array('op' => $op, 'version' => $matches['major'] . '.' . $matches['minor']);
+      }
+    }
+  }
+  return $value;
+}
+
+/**
+ * Checkout a release and determine relevant information.
+ *
+ * Locate all .info files contained within the project, determine the names of
+ * the module(s) contained within the project and store the dependencies for
+ * later processing.
+ */
+function project_release_info_batch_process_release(array $release) {
+  $directory_original = getcwd();
+
+  // Use batch directory.
+  chdir(PROJECT_RELEASE_INFO_BATCH_DIRECTORY);
+
+  // Clean directory.
+  exec('rm -r *');
+
+  // Checkout release.
+  $release['tag'] = escapeshellcmd($release['tag']);
+  $release['uri'] = escapeshellcmd($release['uri']);
+
+  if ($release['uri'] == 'drupal') {
+    $url = ':pserver:anonymous:anonymous@cvs.drupal.org:/cvs/drupal';
+    $branch = $release['tag'];
+    $module = $release['uri'];
+    $directory = $release['uri'];
+  }
+  else {
+    $url = ':pserver:anonymous:anonymous@cvs.drupal.org:/cvs/drupal-contrib';
+    $branch = $release['tag'];
+    $module = 'contributions/modules/' . $release['uri'];
+    $directory = $release['uri'];
+  }
+  exec("cvs -z6 -Q -d$url checkout -d$directory -r $branch $module", $output, $status);
+
+  // Return to original directory.
+  chdir($directory_original);
+
+  // Check for CVS checkout failure.
+  if ($status != 0) {
+    return FALSE;
+  }
+
+  // Scan checkout for .info files and create a list in the same format that
+  // project release uses, so that standard API functions can be used.
+  $files = file_scan_directory(PROJECT_RELEASE_INFO_BATCH_DIRECTORY . '/' . $directory, '/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.info$/');
+  $info_files = array();
+  foreach ($files as $file) {
+    $info_files[] = $file->filename;
+  }
+  $info = project_release_info_package_parse($info_files);
+
+  $dependencies = array();
+  foreach ($info as $module => $module_info) {
+    $dependencies[$module] = $module_info['dependencies'];
+  }
+
+  // Clear previous records for the release.
+  project_release_info_package_clear($release['nid']);
+
+  // Store the list of modules contained by the project.
+  project_release_info_package_list_store($release['nid'], $info);
+
+  return $dependencies;
+}
diff --git release/project_release.info release/project_release.info
index d4f2591..f7fe1ff 100644
--- release/project_release.info
+++ release/project_release.info
@@ -7,3 +7,5 @@ dependencies[] = taxonomy
 dependencies[] = upload
 dependencies[] = views
 core = 6.x
+
+test_dependencies[] = pift
diff --git release/project_release.install release/project_release.install
index 941adc3..eefbe7c 100644
--- release/project_release.install
+++ release/project_release.install
@@ -380,6 +380,72 @@ function project_release_schema() {
     'primary key' => array('nid', 'uid'),
   );
 
+  $schema['project_release_component'] = array(
+    'description' => 'The component(s) contained by a project release.',
+    'fields' => array(
+      'component_id' => array(
+        'description' => 'Unique component ID.',
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'release_nid' => array(
+        'description' => 'The {node}.nid of the project_release node that includes a component.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'name' => array(
+        'description' => 'The machine readable name of a component.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'title' => array(
+        'description' => 'The human readable name of a component',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+      ),
+      'description' => array(
+        'description' => 'A description of a component.',
+        'type' => 'text',
+        'size' => 'medium'
+      ),
+    ),
+    'primary key' => array('component_id'),
+    'indexes' => array(
+      'release_nid' => array('release_nid'),
+      'name' => array('name'),
+    ),
+  );
+
+  $schema['project_release_dependency'] = array(
+    'description' => 'The dependencies of a component.',
+    'fields' => array(
+      'source_id' => array(
+        'description' => 'ID of a component.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'target_id' => array(
+        'description' => 'ID of a component that the component is dependent upon.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'dependency_type' => array(
+        'description' => 'Dependency type, PROJECT_RELEASE_DEPENDENCY_*.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+    ),
+    'primary key' => array('source_id', 'target_id'),
+  );
+
   return $schema;
 }
 
@@ -784,3 +850,79 @@ function project_release_update_6010() {
 
   return $ret;
 }
+
+/**
+ * Create project dependency info tables.
+ */
+function project_release_update_6011() {
+  $ret = array();
+  if (!db_table_exists('project_release_component')) {
+    db_create_table($ret, 'project_release_component', array(
+      'description' => 'The component(s) contained by a project release.',
+      'fields' => array(
+        'component_id' => array(
+          'description' => 'Unique component ID.',
+          'type' => 'serial',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+        ),
+        'release_nid' => array(
+          'description' => 'The {node}.nid of the project_release node that includes a component.',
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+        ),
+        'name' => array(
+          'description' => 'The machine readable name of a component.',
+          'type' => 'varchar',
+          'length' => 255,
+          'not null' => TRUE,
+          'default' => '',
+        ),
+        'title' => array(
+          'description' => 'The human readable name of a component',
+          'type' => 'varchar',
+          'length' => 255,
+          'not null' => TRUE,
+        ),
+        'description' => array(
+          'description' => 'A description of a component.',
+          'type' => 'text',
+          'size' => 'medium'
+        ),
+      ),
+      'primary key' => array('component_id'),
+      'indexes' => array(
+        'release_nid' => array('release_nid'),
+        'name' => array('name'),
+      ),
+    ));
+  }
+  if (!db_table_exists('project_release_dependency')) {
+    db_create_table($ret, 'project_release_dependency', array(
+      'description' => 'The dependencies of a component.',
+      'fields' => array(
+        'source_id' => array(
+          'description' => 'ID of a component.',
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+        ),
+        'target_id' => array(
+          'description' => 'ID of a component that the component is dependent upon.',
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+        ),
+        'dependency_type' => array(
+          'description' => 'Dependency type, PROJECT_RELEASE_DEPENDENCY_*.',
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+        ),
+      ),
+      'primary key' => array('source_id', 'target_id'),
+    ));
+  }
+  return $ret;
+}
diff --git release/project_release.module release/project_release.module
index 1d27a76..362849d 100644
--- release/project_release.module
+++ release/project_release.module
@@ -11,6 +11,12 @@ define('PROJECT_RELEASE_UPDATE_STATUS_CURRENT', 0);
 define('PROJECT_RELEASE_UPDATE_STATUS_NOT_CURRENT', 1);
 define('PROJECT_RELEASE_UPDATE_STATUS_NOT_SECURE', 2);
 
+/*
+ * Constants for the possible values of {project_release_dependency}.dependency_type.
+ */
+define('PROJECT_RELEASE_DEPENDENCY_REQUIRED', 0);
+define('PROJECT_RELEASE_DEPENDENCY_RECOMMENDED', 1);
+
 /**
  * @defgroup project_release_core Core Drupal hooks
  */
@@ -30,6 +36,15 @@ function project_release_init() {
 }
 
 /**
+ * Implementation of hook_perm().
+ */
+function project_release_perm() {
+  return array(
+    'parse project_release_info batch',
+  );
+}
+
+/**
  * Implementation of hook_menu()
  * @ingroup project_release_core
  */
@@ -66,6 +81,14 @@ function project_release_menu() {
     'type' => MENU_NORMAL_ITEM,
     'file' => 'includes/admin.settings.inc',
   );
+  $items['admin/project/project_release_info/batch'] = array(
+    'title' => 'Parse batch releases',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('project_release_info_batch_form'),
+    'access arguments' => array('parse project_release_info batch'),
+    'file' => 'project_release.batch.inc',
+    'type' => MENU_CALLBACK,
+  );
 
   // Redirect node/add/project_release/* to node/add/project-release.
   $items['node/add/project_release'] = array(
@@ -2177,3 +2200,125 @@ function project_release_views_default_views_alter(&$views) {
   _project_release_views_default_views_alter($views);
 }
 
+/**
+ * @defgroup project_release_info Project release information
+ * @{
+ */
+
+/**
+ * Loads a component.
+ *
+ * @param $component_id
+ *   Component ID to load.
+ *
+ * @return
+ *   Array of component information.
+ */
+function project_release_component_load($component_id) {
+  return db_fetch_array(db_query('SELECT * FROM {project_release_component} WHERE component_id = %d', $component_id));
+}
+
+/**
+ * Loads the list of components contained by a project release.
+ *
+ * @param $release_nid
+ *   Project release ID.
+ *
+ * @return
+ *   Associative array of components keyed by component name and containing
+ *   array keys: 'name', 'title', 'description'.
+ */
+function project_release_get_components($release_nid) {
+  $result = db_query('SELECT * FROM {project_release_component} WHERE release_nid = %d', $release_nid);
+  $components = array();
+  while ($component = db_fetch_array($result)) {
+    $components[$component['name']] = $component;
+  }
+  return $components;
+}
+
+/**
+ * Gets a list of dependency components for a component.
+ *
+ * @param $component_id
+ *   Component ID to get dependencies for.
+ * @param $dependency_type
+ *   (Optional) Minimum dependency type, PROJECT_RELEASE_DEPENDENCY_*. All
+ *   dependencies of equal or lesser stringency will be included.
+ *
+ * @return
+ *   Associative array of dependencies keyed by component name.
+ */
+function project_release_component_dependencies_load_all($component_id, $dependency_type = PROJECT_RELEASE_DEPENDENCY_REQUIRED) {
+  $result = db_query('SELECT c.*
+    FROM {project_release_dependency} d
+    JOIN {project_release_component} c
+      ON c.component_id = d.target_id
+    WHERE source_id = %d
+    AND dependency_type <= %d', $component_id, $dependency_type);
+  $dependencies = array();
+  while ($dependency = db_fetch_array($result)) {
+    $dependencies[$dependency['name']] = $dependency;
+  }
+  return $dependencies;
+}
+
+/**
+ * Recursively determines the list of dependencies of a release.
+ *
+ * @param $release_nid
+ *   Project release ID.
+ * @param $dependency_type
+ *   (Optional) Minimum dependency type, PROJECT_RELEASE_DEPENDENCY_*. All
+ *   dependencies of equal or lesser stringency will be included.
+ *
+ * @return
+ *   List of dependencies of the release.
+ */
+function project_release_get_dependencies($release_nid, $dependency_type = PROJECT_RELEASE_DEPENDENCY_REQUIRED) {
+  $components = project_release_get_components($release_nid);
+  return _project_release_get_component_dependencies($components, $dependency_type);
+}
+
+/**
+ * Recursively determines the list of dependencies for a list of components.
+ *
+ * @param array $components
+ *   List of components to determine the dependencies for.
+ * @param $dependency_type
+ *   (Optional) Minimum dependency type, PROJECT_RELEASE_DEPENDENCY_*. All
+ *   dependencies of equal or lesser stringency will be included.
+ * @param $dependencies
+ *   (Internal) Array of already determined dependencies.
+ *
+ * @return
+ *   Associative array of dependencies keyed by component name and containing
+ *   all component info keys: 'component_id', 'rid', 'name', 'title', and
+ *   'description'.
+ */
+function _project_release_get_component_dependencies(array $components, $dependency_type, array $dependencies = array()) {
+  foreach ($components as $component) {
+    // Get all dependencies of the component.
+    $dependencies_new = project_release_component_dependencies_load_all($component['component_id'], $dependency_type);
+
+    // Add the new dependencies to the list of dependencies and remove the
+    // dependencies that have already been added.
+    foreach ($dependencies_new as $name => $dependency_new) {
+      if (!isset($dependencies[$name])) {
+        $dependencies[$name] = $dependency_new;
+      }
+      else {
+        unset($dependencies_new[$name]);
+      }
+    }
+
+    // Process the dependencies of all the new dependency components.
+    $dependencies = _project_release_get_component_dependencies($dependencies_new, $dependency_type, $dependencies);
+  }
+  return $dependencies;
+}
+
+/**
+ * @} End of "defgroup project_release_info".
+ */
+
diff --git release/project_release.package.inc release/project_release.package.inc
new file mode 100644
index 0000000..24898e6
--- /dev/null
+++ release/project_release.package.inc
@@ -0,0 +1,72 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Provides storage functions for use during release packaging.
+ *
+ * @author Jimmy Berry ("boombatower", http://drupal.org/user/214218)
+ */
+
+/**
+ * Clear the list of components and dependencies for a release.
+ *
+ * @param $release_nid
+ *   The project release ID.
+ */
+function project_release_info_package_clear($release_nid) {
+  db_query('DELETE FROM {project_release_dependency}
+    WHERE source_id IN (SELECT component_id FROM {project_release_component} WHERE release_nid = %d)', $release_nid);
+
+  db_query('DELETE FROM {project_release_component} WHERE release_nid = %d', $release_nid);
+}
+
+/**
+ * Store the list of components for a release.
+ *
+ * @param $release_nid
+ *   The project release ID.
+ * @param $info
+ *   Associative array of information for component keyed by the component name
+ *   and providing the following keys: name, title, description.
+ *
+ * @return
+ *   Associative array of stored records keyed by the component name with all
+ *   the previous keys and the generated component_id.
+ */
+function project_release_info_package_list_store($release_nid, array $info) {
+  $records = array();
+  foreach ($info as $component => $component_info) {
+    $info = array(
+      'release_nid' => $release_nid,
+      'name' => $component_info['name'],
+      'title' => $component_info['title'],
+      'description' => $component_info['description'],
+    );
+    drupal_write_record('project_release_component', $info);
+    $records[$component] = $info;
+  }
+  return $records;
+}
+
+/**
+ * Store a list of dependencies for a component.
+ *
+ * @param $component_id
+ *   The component ID to which the dependencies relate.
+ * @param $dependency_type
+ *   Dependency type, PROJECT_RELEASE_DEPENDENCY_*.
+ * @param array $dependencies
+ *   List of dependencies component IDs.
+ */
+function project_release_info_package_dependencies_store($component_id, $dependency_type, array $dependencies) {
+  foreach ($dependencies as $dependency_id) {
+    // Store the dependency if a best release was found.
+    $info = array(
+      'source_id' => $component_id,
+      'target_id' => $dependency_id,
+      'dependency_type' => $dependency_type,
+    );
+    drupal_write_record('project_release_dependency', $info);
+  }
+}
diff --git release/project_release.test release/project_release.test
new file mode 100644
index 0000000..b8a3723
--- /dev/null
+++ release/project_release.test
@@ -0,0 +1,252 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Provide test of functionality.
+ *
+ * @author Jimmy Berry ("boombatower", http://drupal.org/user/214218)
+ */
+
+/**
+ * Base project_release info test case.
+ */
+class ProjectReleaseTestCase extends ProjectWebTestCase {
+
+  public function setUp() {
+    parent::setUp('project_release');
+  }
+}
+
+/**
+ * Ensure that the component storage and retrieval API works correctly..
+ */
+class ProjectReleaseComponentAPITestCase extends ProjectReleaseTestCase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Component API',
+      'description' => 'Ensure that the component storage and retrieval API works correctly.',
+      'group' => 'Project release',
+    );
+  }
+
+  protected function testComponentAPI() {
+    module_load_include('package.inc', 'project_release');
+
+    // Create a project and release.
+    $project = $this->createProject();
+    $release = $this->createRelease($project);
+
+    // Store two components for the release.
+    $components = array(
+      'foo' => array(
+        'name' => 'foo',
+        'title' => 'Foo',
+        'description' => 'Foo is not bar.',
+      ),
+      'bar' => array(
+        'name' => 'bar',
+        'title' => 'Bar',
+        'description' => 'Bar is not foo.',
+      ),
+    );
+    $components = project_release_info_package_list_store($release->nid, $components);
+    project_release_info_package_dependencies_store($components['foo']['component_id'], PROJECT_RELEASE_DEPENDENCY_REQUIRED, array($components['bar']['component_id']));
+
+    // Retrieve stored components and compared with components array.
+    $stored = project_release_get_components($release->nid);
+    $this->assertEqual($stored, $components, 'Components stored and retrieved successfully');
+
+    // Retrieve stored component dependency and compare with components array.
+    $dependencies = project_release_component_dependencies_load_all($components['foo']['component_id']);
+    $this->assertEqual(count($dependencies), 1, 'One dependency retrieved for foo');
+    $this->assertEqual(current($dependencies), $components['bar'], 'Component dependency stored and retrieved successfully');
+
+    // Retrieve stored release dependency and compare with components array.
+    $release_dependencies = project_release_get_dependencies($release->nid);
+    $this->assertEqual($release_dependencies, $dependencies, 'Release dependency stored and retrieved successfully');
+
+    // Load an individual component and ensure that it works properly.
+    $component = project_release_component_load($components['foo']['component_id']);
+    $this->assertEqual($component, $components['foo'], 'Component stored and retrieved successfully');
+
+    // Create a dependency tree and ensure that it is resolved properly.
+    // Foo -> Bar -> Baz.
+    $project2 = $this->createProject();
+    $release2 = $this->createRelease($project2);
+    $component = array(
+      'name' => 'baz',
+      'title' => 'Baz',
+      'description' => 'Baz is not bar.',
+    );
+    $component = current(project_release_info_package_list_store($release2->nid, array($component)));
+    project_release_info_package_dependencies_store($components['bar']['component_id'], PROJECT_RELEASE_DEPENDENCY_RECOMMENDED, array($component['component_id']));
+
+    // Ensure that the dependency is handled properly.
+    $dependencies = project_release_component_dependencies_load_all($components['bar']['component_id']);
+    $this->assertFalse($dependencies, 'Bar has no required dependencies');
+
+    // Retrieve stored component dependency and compare with component array.
+    $dependencies = project_release_component_dependencies_load_all($components['bar']['component_id'], PROJECT_RELEASE_DEPENDENCY_RECOMMENDED);
+    $this->assertEqual(count($dependencies), 1, 'One dependency retrieved for bar');
+    $this->assertEqual(current($dependencies), $component, 'Component dependency stored and retrieved successfully');
+
+    // Ensure that retrieval of the dependency tree respects type.
+    $dependencies = project_release_get_dependencies($release->nid);
+    $this->assertEqual(array_keys($dependencies), array('bar'), 'Foo requires bar');
+
+    // Ensure that retreval of the dependency tree is correct.
+    $dependencies = project_release_get_dependencies($release->nid, PROJECT_RELEASE_DEPENDENCY_RECOMMENDED);
+    $this->assertEqual(array_keys($dependencies), array('bar', 'baz'), 'Foo recommends bar and baz');
+  }
+}
+
+/**
+ * Ensure that the Drupal component parsing functions work properly.
+ */
+class ProjectReleaseComponentDrupalTestCase extends ProjectDrupalOrgWebTestCase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Drupal component parsing',
+      'description' => 'Ensure that the Drupal component parsing functions work properly.',
+      'group' => 'Project release',
+    );
+  }
+
+  protected function testDrupalComponentParsing() {
+    $this->process();
+
+    // Ensure that the component information is correct.
+    $components = project_release_get_components($this->releases['foo']->nid);
+    $this->assertEqual(array_keys($components), array('project_release_foo'), 'Project foo contains foo');
+    $this->assertEqual($components, array(
+      'project_release_foo' => array(
+        'component_id' => 1,
+        'release_nid' => $this->releases['foo']->nid,
+        'name' => 'project_release_foo',
+        'title' => 'Project release foo',
+        'description' => 'Test module for project_release. (do not enable)',
+      ),
+    ), 'Project foo components contain proper information');
+
+    $components = project_release_get_components($this->releases['bar']->nid);
+    $this->assertEqual(array_keys($components), array('project_release_bar', 'project_release_baz'), 'Project bar contains bar and baz');
+    $this->assertEqual($components, array(
+      'project_release_bar' => array(
+        'component_id' => 2,
+        'release_nid' => $this->releases['bar']->nid,
+        'name' => 'project_release_bar',
+        'title' => 'Project release bar',
+        'description' => 'Test module for project_release. (do not enable)',
+      ),
+      'project_release_baz' => array(
+        'component_id' => 3,
+        'release_nid' => $this->releases['bar']->nid,
+        'name' => 'project_release_baz',
+        'title' => 'Project release baz',
+        'description' => 'Test module for project_release. (do not enable)',
+      ),
+    ), 'Project bar components contain proper information');
+
+    // Ensure that dependencies were parsed properly.
+    $dependencies = project_release_get_dependencies($this->releases['foo']->nid);
+    $this->assertFalse($dependencies, 'No dependencies found for project foo');
+
+    $dependencies = project_release_get_dependencies($this->releases['bar']->nid); // @TODO Should pick up soft dependency.
+    $this->assertEqual(array_keys($dependencies), array('project_release_foo'), 'Project bar depends on component foo');
+
+    // Ensure that parsing creates an error due to the lack of a stable release.
+    $this->assertPackageError('ERROR: No development release with a corresponding stable release was found that matches the requirements for dependency [project_release_foo] of [project_release_bar].');
+
+  // @TODO Figure out what CVS/Version control API stuff needs to be in setup.
+//     $this->createTag('bar', 'DRUPAL-7--1-0');
+//     $this->drupalPost('node/add/project-release/' . $this->projects['bar']->nid, array(), t('Next'));
+//     $edit = array(
+//       'body' => $this->randomString(32),
+//     );
+//     $this->drupalPost(NULL, $edit, t('Save'));
+// 
+//     $this->process();
+// 
+//     $this->assertNoPackageErrors();
+  }
+
+  /**
+   * Process the test modules.
+   */
+  protected function process() {
+    // Remove all previously existing project_release_info data.
+    db_query('DELETE FROM {project_release_dependency}');
+    db_query('DELETE FROM {project_release_component}');
+
+    // Reset packaging errors.
+    wd_err(FALSE, TRUE);
+
+    // Load Drupal parsing implementation.
+    module_load_include('drupal.inc', 'project_release');
+
+    // Parse test modules related to projects foo and bar.
+    $info_files = array(
+      drupal_get_path('module', 'project_release_foo') . '/project_release_foo.info',
+    );
+    package_release_info_process_all($this->releases['foo']->nid, $info_files, 'foo', '7.x-1.0');
+
+    $info_files = array(
+      drupal_get_path('module', 'project_release_bar') . '/project_release_bar.info',
+      drupal_get_path('module', 'project_release_baz') . '/project_release_baz.info',
+    );
+    package_release_info_process_all($this->releases['bar']->nid, $info_files, 'bar', '7.x-1.2');
+  }
+
+  protected function createTag($project, $tag, $branch = FALSE) {
+    db_query("INSERT INTO {cvs_tags} VALUES (%d, '%s', %d, %d)", $this->projects[$project]->nid, $tag, $branch ? 1 : 0, time());
+  }
+
+  /**
+   * Assert that the package error were created.
+   *
+   * @param $message
+   *   The packaging error message.
+   * @return
+   *   TRUE if the assertion succeeded, FALSE otherwise.
+   */
+  protected function assertPackageError($message) {
+    $found = FALSE;
+    foreach (wd_err() as $error) {
+      if ($error == $message) {
+        $found = TRUE;
+        break;
+      }
+    }
+    return $this->assertTrue($found, 'Found package error [' . $message . ']');
+  }
+
+  /**
+   * Assert that no package errors were created.
+   *
+   * @return
+   *   TRUE if the assertion succeeded, FALSE otherwise.
+   */
+  protected function assertNoPackageErrors() {
+    if (!$this->assertFalse(wd_err(), 'No packaging errors')) {
+      foreach (wd_err() as $error) {
+        $this->fail($error);
+      }
+    }
+  }
+}
+
+function wd_err($message = FALSE, $reset = FALSE) {
+  static $messages = array();
+
+  if ($reset) {
+    $messages = array();
+  }
+
+  if ($message) {
+    $messages[] = $message;
+  }
+  return $messages;
+}
diff --git release/tests/bar/baz/project_release_baz.info release/tests/bar/baz/project_release_baz.info
new file mode 100644
index 0000000..ce52c6f
--- /dev/null
+++ release/tests/bar/baz/project_release_baz.info
@@ -0,0 +1,9 @@
+; $Id$
+name = Project release baz
+description = Test module for project_release. (do not enable)
+package = Project
+version = 1.2
+core = 7.x
+dependencies[] = project_release_bar
+
+test_dependencies[] = project_release_foo (1.x)
diff --git release/tests/bar/baz/project_release_baz.module release/tests/bar/baz/project_release_baz.module
new file mode 100644
index 0000000..bd4d750
--- /dev/null
+++ release/tests/bar/baz/project_release_baz.module
@@ -0,0 +1,9 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Dummy module file.
+ *
+ * @author Jimmy Berry ("boombatower", http://drupal.org/user/214218)
+ */
diff --git release/tests/bar/project_release_bar.info release/tests/bar/project_release_bar.info
new file mode 100644
index 0000000..f55b7ba
--- /dev/null
+++ release/tests/bar/project_release_bar.info
@@ -0,0 +1,9 @@
+; $Id$
+name = Project release bar
+description = Test module for project_release. (do not enable)
+package = Project
+version = 1.0
+core = 7.x
+dependencies[] = project_release_foo
+
+test_dependencies[] = project_release_baz
diff --git release/tests/bar/project_release_bar.module release/tests/bar/project_release_bar.module
new file mode 100644
index 0000000..bd4d750
--- /dev/null
+++ release/tests/bar/project_release_bar.module
@@ -0,0 +1,9 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Dummy module file.
+ *
+ * @author Jimmy Berry ("boombatower", http://drupal.org/user/214218)
+ */
diff --git release/tests/foo/project_release_foo.info release/tests/foo/project_release_foo.info
new file mode 100644
index 0000000..f672c40
--- /dev/null
+++ release/tests/foo/project_release_foo.info
@@ -0,0 +1,6 @@
+; $Id$
+name = Project release foo
+description = Test module for project_release. (do not enable)
+package = Project
+version = 1.0
+core = 7.x
diff --git release/tests/foo/project_release_foo.module release/tests/foo/project_release_foo.module
new file mode 100644
index 0000000..bd4d750
--- /dev/null
+++ release/tests/foo/project_release_foo.module
@@ -0,0 +1,9 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Dummy module file.
+ *
+ * @author Jimmy Berry ("boombatower", http://drupal.org/user/214218)
+ */
