diff --git info/project_info.batch.inc info/project_info.batch.inc new file mode 100644 index 0000000..acb3e1c --- /dev/null +++ info/project_info.batch.inc @@ -0,0 +1,215 @@ + '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_INFO_MINIMUM_API) { + $form['filter']['api']['#default_value'][] = $term->tid; + } + } + + $form['op'] = array( + '#type' => 'submit', + '#value' => t('Submit'), + ); + + return $form; +} + +/** + * Ensure that the directory constant has been set. + */ +function project_info_batch_form_validate($form, &$form_state) { + if (!PROJECT_INFO_BATCH_DIRECTORY) { + form_set_error('op', t('The constant PROJECT_INFO_BATCH_DIRECTORY must be set.')); + } +} + +/** + * Start the batch to process batch releases. + */ +function project_info_batch_form_submit($form, &$form_state) { + $tids = array(); + foreach ($form_state['values']['api'] as $tid => $selected) { + if ($selected) { + $tids[] = $tid; + } + } + + $batch = array( + 'title' => t('Processing releases'), + 'operations' => array( + array('project_info_batch_batch_operation_parse', array($tids)), + array('project_info_batch_batch_operation_dependencies', array()), + ), + 'finished' => 'project_info_batch_batch_finished', + 'init_message' => t('Determining releases to parse...'), + 'file' => drupal_get_path('module', 'project_info') . '/project_info.batch.inc', + ); + batch_set($batch); +} + +/** + * Parse the modules contained within a project and their dependencies. + */ +function project_info_batch_batch_operation_parse(array $tids, &$context) { + if (!isset($context['sandbox']['max'])) { + // First iteration, initialize working values. + $context['sandbox']['max'] = project_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_info'); + + $releases = project_info_batch_releases($tids, $context['sandbox']['release_id']); + foreach ($releases as $release) { + if (($dependencies = project_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_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_info'); + + $i = 0; + foreach ($context['results']['dependencies'] as $rid => $modules_dependencies) { + $modules = project_info_module_list_get($rid); + foreach ($modules_dependencies as $module => $dependencies) { + project_info_batch_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_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_info_batch_releases(array $tids, $release_id, $count = FALSE) { + $sql = 'SELECT ' . ($count ? 'COUNT(r.nid)' : 'r.nid, r.tag, p.uri, cp.directory') . ' + 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 (' . db_placeholders($tids, 'int') . ') + 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 info/project_info.drupal.inc info/project_info.drupal.inc new file mode 100644 index 0000000..61cceb2 --- /dev/null +++ info/project_info.drupal.inc @@ -0,0 +1,377 @@ += PROJECT_INFO_MINIMUM_API) { + // Clear previous records for the release. + project_info_package_clear($rid); + + $info = package_release_info_parse($info_files); + + // Store list of components. + $records = project_info_package_list_store($rid, $info); + + // Process list of dependencies to determine the release related to each + // dependency. + package_release_info_process($rid, $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_info. + $info[$component]['title'] = $info[$component]['name']; + $info[$component]['name'] = $component; + } + return $info; +} + +/** + * Determine and store the list of dependencies for a release. + * + * @param $rid + * The project release ID. + * @param $records + * List of saved component information from + * project_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($rid, array $records, array $info) { + foreach ($info as $component => $component_info) { + // Merge required dependencies and test dependencies into one array. + $dependencies = array_merge($component_info['dependencies'], $component_info['test_dependencies']); + package_release_info_process_dependencies($rid, $records[$component], $dependencies); + } +} + +/** + * Store the list of dependencies for a component of a release. + * + * @param $rid + * The project release ID. + * @param $component + * Component information, returned from project_info_package_list_store(). + * @param $dependencies + * List of dependency component names. + */ +function package_release_info_process_dependencies($rid, array $component, array $dependencies) { + // Load the release and determine is core API version. + $release = node_load($rid); + 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; + } + + $dependecy_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['rid']); + 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. + $dependecy_components[$dependency] = $best_release['component_id']; + break; + } + } + + // If no releases found, then generate error. + if (empty($dependecy_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['rid']); + + // 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) { + $dependecy_components[$dependency] = $release['component_id']; + } + } + + // If no releases found, then generate error. + if (empty($dependecy_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_info_package_dependencies_store($component['component_id'], $dependecy_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', 'rid'. + */ +function package_release_info_releases_get($component, $api_tid) { + $result = db_query("SELECT DISTINCT p.component_id, p.rid + FROM {project_info_component} p + INNER JOIN {project_release_nodes} r + ON p.rid = r.nid + INNER JOIN {term_node} t + ON p.rid = 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!=|==|=|<|<=|>|>=|<>)?'; + // 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\d+)'; + // By setting the minor version to x, branches can be matched. + $p_minor = '(?P(?:\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_info_batch_process_release(array $release) { + $directory_original = getcwd(); + + // Use batch directory. + chdir(PROJECT_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_INFO_BATCH_DIRECTORY . '/' . $directory, '\.info$'); + $info_files = array(); + foreach ($files as $file) { + $info_files[] = $file->filename; + } + $info = project_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_info_package_clear($release['nid']); + + // Store the list of modules contained by the project. + project_info_package_list_store($release['nid'], $info); + + return $dependencies; +} + +function project_info_batch_process_dependencies($rid, array $component, array $dependencies) { + package_release_info_process_dependencies($rid, $component, $dependencies); +} diff --git info/project_info.info info/project_info.info new file mode 100644 index 0000000..3305fc0 --- /dev/null +++ info/project_info.info @@ -0,0 +1,8 @@ +; $Id$ +name = Project info +description = Provides component list and component-component dependecy information for releases. +package = Project +core = 6.x +dependencies[] = project_release + +test_dependencies[] = pift diff --git info/project_info.install info/project_info.install new file mode 100644 index 0000000..6a3d9f0 --- /dev/null +++ info/project_info.install @@ -0,0 +1,98 @@ + 'The component(s) contained by a project release.', + 'fields' => array( + 'component_id' => array( + 'description' => 'Unique component ID.', + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'rid' => 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( + 'rid' => array('rid'), + 'name' => array('name'), + ), + ); + + $schema['project_info_dependency'] = array( + 'description' => 'The dependencies of a component.', + 'fields' => array( + 'component_id' => array( + 'description' => 'ID of a component.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'dependency_id' => array( + 'description' => 'ID of a component that the component is dependent upon.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'required' => array( + 'description' => 'ID of a component that the component is dependent upon.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + ), + 'primary key' => array('component_id', 'dependency_id'), + ); + + return $schema; +} + +/** + * Implementation of hook_install(). + */ +function project_info_install() { + drupal_install_schema('project_info'); +} + +/** + * Implementation of hook_uninstall(). + */ +function project_info_uninstall() { + drupal_uninstall_schema('project_info'); +} diff --git info/project_info.module info/project_info.module new file mode 100644 index 0000000..76380ac --- /dev/null +++ info/project_info.module @@ -0,0 +1,136 @@ + 'Parse batch releases', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('project_info_batch_form'), + 'access arguments' => array('parse project_info batch'), + 'file' => 'project_info.batch.inc', + 'type' => MENU_CALLBACK, + ); + + return $items; +} + +/** + * Implementation of hook_perm(). + */ +function project_info_perm() { + return array( + 'parse project_info batch', + ); +} + +/** + * Load a component. + * + * @param $component_id + * Component ID to load. + * @return + * Array of component information. + */ +function project_info_component_get($component_id) { + static $components = array(); + + if (!isset($components[$component_id])) { + $result = db_query('SELECT * FROM {project_info_component} WHERE component_id = %d', $component_id); + $components[$component_id] = db_fetch_array($result); + } + return $components[$component_id]; +} + +/** + * Load the list of components contained by a project release. + * + * @param $rid + * Project release ID. + * @return + * Associative array of components keyed by component name and containing + * array keys: 'name', 'title', 'description'. + */ +function project_info_component_list_get($rid) { + $result = db_query('SELECT * FROM {project_info_component} WHERE rid = %d', $rid); + $components = array(); + while ($component = db_fetch_array($result)) { + $components[$component['name']] = $component; + } + return $components; +} + +/** + * Get a list of dependency components for a component. + * + * @param $component_id + * Component ID to get dependencies for. + * @return + * List of dependencies. + */ +function project_info_component_dependencies_get($component_id) { + $result = db_query('SELECT dependency_id FROM {project_info_dependency} WHERE component_id = %d', $component_id); + $dependencies = array(); + while ($dependency = db_result($result)) { + $dependencies[] = project_info_component_get($dependency); + } + return $dependencies; +} + +/** + * Recursively determine the list of dependencies of a release. + * + * @param $rid + * Project release ID. + * @return + * List of dependencies of the release. + */ +function project_info_dependency_list_get($rid) { + $components = project_info_component_list_get($rid); + return project_info_dependency_list_get_components($components); +} + +/** + * Recursively determine the list of dependencies for a list of components. + * + * @param array $components + * List of components to determine the dependencies for. + * @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_info_dependency_list_get_components(array $components, array $dependencies = array()) { + foreach ($components as $component) { + // Get all dependencies of the component. + $dependencies_new = project_info_component_dependencies_get($component['component_id']); + + // Add the new dependencies to the list of dependencies and remove the + // dependencies that have already been added. + foreach ($dependencies_new as $component_name => $dependency_new) { + if (!isset($dependencies[$component_name])) { + $dependencies[$component_name] = $dependency_new; + } + else { + unset($dependencies_new[$component_name]); + } + } + + // Process the dependencies of all the new dependency components. + $dependencies = project_info_dependency_list_get_components($dependencies_new, $dependencies); + } + return $dependencies; +} diff --git info/project_info.package.inc info/project_info.package.inc new file mode 100644 index 0000000..37e731e --- /dev/null +++ info/project_info.package.inc @@ -0,0 +1,72 @@ + $component_info) { + $info = array( + 'rid' => $rid, + 'name' => $component_info['name'], + 'title' => $component_info['title'], + 'description' => $component_info['description'], + ); + drupal_write_record('project_info_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 array $dependencies + * List of dependencies component IDs. + */ +function project_info_package_dependencies_store($component_id, array $dependencies) { + foreach ($dependencies as $dependency_id) { + // Store the dependency if a best release was found. + $info = array( + 'component_id' => $component_id, + 'dependency_id' => $dependency_id, + ); + drupal_write_record('project_info_dependency', $info); + } +} diff --git info/project_info.test info/project_info.test new file mode 100644 index 0000000..fe5d8f9 --- /dev/null +++ info/project_info.test @@ -0,0 +1,210 @@ +assertEqual($component['component_id'], $component_id, 'Proper component ID found'); + $this->assertEqual($component['rid'], $rid, 'Proper release ID found'); + $this->assertEqual($component['name'], $project, 'Proper name found'); + $this->assertEqual($component['title'], ucfirst(str_replace('_', ' ', $project)), 'Proper title found'); + $this->assertTrue($component['description'], 'Description found'); + } +} + +/** + * Ensure that the storage and retrieval API works correctly. + */ +class ProjectInfoAPITestCase extends ProjectInfoTestCase { + + public static function getInfo() { + return array( + 'name' => 'API', + 'description' => 'Ensure that the storage and retrieval API works correctly.', + 'group' => 'Project info', + ); + } + + /** + * Ensure that the storage and retrieval API works correctly. + */ + protected function testAPI() { + module_load_include('package.inc', 'project_info'); + + // Ensure that default data is provided properly. + $component_id = 1; + foreach ($this->releases as $project => $release) { + $components = project_info_component_list_get($release->nid); + $this->assertEqual(count($components), 2, 'Two components found'); + + $name = $project; + foreach ($components as $component) { + $this->assertComponent($component, $component_id, $release->nid, $name); + + $component = project_info_component_get($component['component_id']); + $this->assertComponent($component, $component_id++, $release->nid, $name); + + $name .= $project; + } + + // Ensure that dependencies are provided properly. + $dependencies = project_info_dependency_list_get($release->nid); + if ($project == 'drupal') { + $this->assertFalse($dependencies, 'Drupal core has no dependencies'); + } + else if ($project == 'foo') { + $this->assertEqual($dependencies[0]['component_id'], 5, 'Foo depends on bar'); + $dependencies = project_info_component_dependencies_get(3); + $this->assertEqual($dependencies[0]['component_id'], 5, 'Foo depends on bar'); + } + else { + $this->assertEqual($dependencies[0]['component_id'], 4, 'Bar depends on foo'); + $dependencies = project_info_component_dependencies_get(6); + $this->assertEqual($dependencies[0]['component_id'], 4, 'Bar depends on foo'); + } + } + } +} + +/** + * Ensure that the Drupal parsing functions work properly. + */ +class ProjectInfoDrupalTestCase extends ProjectInfoTestCase { + + public static function getInfo() { + return array( + 'name' => 'Drupal', + 'description' => 'Ensure that the Drupal parsing functions work properly.', + 'group' => 'Project info', + ); + } + + /** + * Ensure that the Drupal parsing functions work properly. + */ + protected function testDrupalParsing() { + $this->process(); + + // Ensure that the components were parsed properly. + $component_id = 7; + $components = project_info_component_list_get($this->releases['foo']->nid); + $this->assertComponent($components['project_info_foo'], $component_id++, $this->releases['foo']->nid, 'project_info_foo'); + + $components = project_info_component_list_get($this->releases['bar']->nid); + $this->assertComponent($components['project_info_bar'], $component_id++, $this->releases['bar']->nid, 'project_info_bar'); + $this->assertComponent($components['project_info_barbar'], $component_id++, $this->releases['bar']->nid, 'project_info_barbar'); + + debug(project_info_component_list_get($this->releases['foo']->nid)); + debug(project_info_component_list_get($this->releases['bar']->nid)); + + debug(project_info_component_dependencies_get(7)); + debug(project_info_component_dependencies_get(8)); + debug(project_info_component_dependencies_get(9)); + + // Ensure that dependencies were parsed properly. + debug(project_info_dependency_list_get($this->releases['foo']->nid)); + debug(project_info_dependency_list_get($this->releases['bar']->nid)); + + // Should have error since there is no stable release of project_info_foo. + $this->assertPackageError('ERROR: No development release with a corresponding stable release was ' . + 'found that matches the requirements for dependency [project_info_foo] of [project_info_bar].'); + + $this->createTag('bar', 'DRUPAL-6--1-0'); +// $this->drupalGet('node/add/project-release/' . $this->projects['bar']->nid); + + $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(); + } + + protected function process() { + // Remove all previously existing project_info data. + db_query('DELETE FROM {project_info_dependency}'); + db_query('DELETE FROM {project_info_component}'); + + // Reset packaging errors. + wd_err(FALSE, TRUE); + + // Load Drupal specific implementation. + module_load_include('drupal.inc', 'project_info'); + + // Parse test modules related to foo and bar projects. + $info_files = array( + drupal_get_path('module', 'project_info_foo') . '/project_info_foo.info', + ); + package_release_info_process_all($this->releases['foo']->nid, $info_files, 'foo', '6.x-1.0'); + + $info_files = array( + drupal_get_path('module', 'project_info_bar') . '/project_info_bar.info', + drupal_get_path('module', 'project_info_barbar') . '/project_info_barbar.info', + ); + package_release_info_process_all($this->releases['bar']->nid, $info_files, 'bar', '6.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()); + } + + 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 . ']'); + } + + 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 info/tests/bar/barbar/project_info_barbar.info info/tests/bar/barbar/project_info_barbar.info new file mode 100644 index 0000000..dbca2a2 --- /dev/null +++ info/tests/bar/barbar/project_info_barbar.info @@ -0,0 +1,9 @@ +; $Id$ +name = Project info barbar +description = Test module for project_info. (do not enable) +package = Project +version = 1.2 +core = 6.x +dependencies[] = project_info_foo + +test_dependencies[] = project_info_foo (1.x) diff --git info/tests/bar/barbar/project_info_barbar.module info/tests/bar/barbar/project_info_barbar.module new file mode 100644 index 0000000..bd4d750 --- /dev/null +++ info/tests/bar/barbar/project_info_barbar.module @@ -0,0 +1,9 @@ + $full_dest_tgz")) { return 'error'; @@ -384,7 +390,11 @@ 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'); + // If project_info module exists then load package.inc. + static $project_info; + if (!isset($project_info) && ($project_info = module_exists('project_info'))) { + module_load_include('drupal.inc', 'project_info'); + } // Files to ignore when checking timestamps: $exclude = array('.', '..', 'LICENSE.txt'); @@ -440,8 +450,10 @@ 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); + // Allow project_info to process the module .info files. + if ($project_info) { + 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")) { diff --git release/project_release.batch.inc release/project_release.batch.inc deleted file mode 100644 index c930421..0000000 --- release/project_release.batch.inc +++ /dev/null @@ -1,203 +0,0 @@ - '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['op'] = 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) { - project_release_info_batch_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 deleted file mode 100644 index de08940..0000000 --- release/project_release.drupal.inc +++ /dev/null @@ -1,380 +0,0 @@ -= PROJECT_RELEASE_INFO_MINIMUM_API) { - // Clear previous records for the release. - project_release_info_package_clear($rid); - - $info = package_release_info_parse($info_files); - - // Store list of components. - $records = project_release_info_package_list_store($rid, $info); - - // Process list of dependencies to determine the release related to each - // dependency. - package_release_info_process($rid, $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 $rid - * The project release ID. - * @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($rid, array $records, array $info) { - foreach ($info as $component => $component_info) { - // Merge required dependencies and test dependencies into one array. - $dependencies = array_merge($component_info['dependencies'], $component_info['test_dependencies']); - package_release_info_process_dependencies($rid, $records[$component], $dependencies); - } -} - -/** - * Store the list of dependencies for a component of a release. - * - * @param $rid - * The project release ID. - * @param $component - * Component information, returned from project_release_info_package_list_store(). - * @param $dependencies - * List of dependency component names. - */ -function package_release_info_process_dependencies($rid, array $component, array $dependencies) { - // Load the release and determine is core API version. - $release = node_load($rid); - 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['rid']); - 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['rid']); - - // 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_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', 'rid'. - */ -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!=|==|=|<|<=|>|>=|<>)?'; - // 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\d+)'; - // By setting the minor version to x, branches can be matched. - $p_minor = '(?P(?:\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, '\.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; -} - -function project_release_info_batch_process_dependencies($rid, array $component, array $dependencies) { - package_release_info_process_dependencies($rid, $component, $dependencies); -} - diff --git release/project_release.info release/project_release.info index f7fe1ff..d4f2591 100644 --- release/project_release.info +++ release/project_release.info @@ -7,5 +7,3 @@ dependencies[] = taxonomy dependencies[] = upload dependencies[] = views core = 6.x - -test_dependencies[] = pift diff --git release/project_release.install release/project_release.install index 0ed69e0..941adc3 100644 --- release/project_release.install +++ release/project_release.install @@ -380,72 +380,6 @@ 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' => '@todo', - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - ), - ), - 'primary key' => array('source_id', 'target_id'), - ); - return $schema; } @@ -850,77 +784,3 @@ 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( - '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( - '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' => '@todo', - '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 d6db967..1d27a76 100644 --- release/project_release.module +++ release/project_release.module @@ -30,15 +30,6 @@ 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 */ @@ -75,14 +66,6 @@ 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( @@ -2194,115 +2177,3 @@ 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. - * - * @return - * List of dependencies. - */ -function project_release_component_dependencies_load_all($component_id) { - $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', $component_id); - $dependencies = array(); - while ($dependency = db_fetch_array($result)) { - $dependencies[] = $dependency; - } - return $dependencies; -} - -/** - * Recursively determines the list of dependencies of a release. - * - * @param $release_nid - * Project release ID. - * - * @return - * List of dependencies of the release. - */ -function project_release_get_dependencies($release_nid) { - $components = project_release_get_components($release_nid); - return _project_release_get_component_dependencies($components); -} - -/** - * Recursively determines the list of dependencies for a list of components. - * - * @param array $components - * List of components to determine the dependencies for. - * @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, array $dependencies = array()) { - foreach ($components as $component) { - // Get all dependencies of the component. - $dependencies_new = project_release_component_dependencies_load_all($component['component_id']); - - // Add the new dependencies to the list of dependencies and remove the - // dependencies that have already been added. - foreach ($dependencies_new as $component_name => $dependency_new) { - if (!isset($dependencies[$component_name])) { - $dependencies[$component_name] = $dependency_new; - } - else { - unset($dependencies_new[$component_name]); - } - } - - // Process the dependencies of all the new dependency components. - $dependencies = _project_release_get_component_dependencies($dependencies_new, $dependencies); - } - return $dependencies; -} - -/** - * @} End of "defgroup project_release_info". - */ - diff --git release/project_release.package.inc release/project_release.package.inc deleted file mode 100644 index bb839c9..0000000 --- release/project_release.package.inc +++ /dev/null @@ -1,69 +0,0 @@ - $component_info) { - $info = array( - 'rid' => $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 array $dependencies - * List of dependencies component IDs. - */ -function project_release_info_package_dependencies_store($component_id, 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, - ); - drupal_write_record('project_release_dependency', $info); - } -} diff --git release/project_release.test release/project_release.test deleted file mode 100644 index d502e3c..0000000 --- release/project_release.test +++ /dev/null @@ -1,210 +0,0 @@ -assertEqual($component['component_id'], $component_id, 'Proper component ID found'); - $this->assertEqual($component['rid'], $rid, 'Proper release ID found'); - $this->assertEqual($component['name'], $project, 'Proper name found'); - $this->assertEqual($component['title'], ucfirst(str_replace('_', ' ', $project)), 'Proper title found'); - $this->assertTrue($component['description'], 'Description found'); - } -} - -/** - * Ensure that the storage and retrieval API works correctly. - */ -class ProjectInfoAPITestCase extends ProjectInfoTestCase { - - public static function getInfo() { - return array( - 'name' => 'API', - 'description' => 'Ensure that the storage and retrieval API works correctly.', - 'group' => 'Project info', - ); - } - - /** - * Ensure that the storage and retrieval API works correctly. - */ - protected function testAPI() { - module_load_include('package.inc', 'project_release'); - - // Ensure that default data is provided properly. - $component_id = 1; - foreach ($this->releases as $project => $release) { - $components = project_release_get_components($release->nid); - $this->assertEqual(count($components), 2, 'Two components found'); - - $name = $project; - foreach ($components as $component) { - $this->assertComponent($component, $component_id, $release->nid, $name); - - $component = project_release_component_load($component['component_id']); - $this->assertComponent($component, $component_id++, $release->nid, $name); - - $name .= $project; - } - - // Ensure that dependencies are provided properly. - $dependencies = project_release_get_dependencies($release->nid); - if ($project == 'drupal') { - $this->assertFalse($dependencies, 'Drupal core has no dependencies'); - } - else if ($project == 'foo') { - $this->assertEqual($dependencies[0]['component_id'], 5, 'Foo depends on bar'); - $dependencies = project_release_component_dependencies_load_all(3); - $this->assertEqual($dependencies[0]['component_id'], 5, 'Foo depends on bar'); - } - else { - $this->assertEqual($dependencies[0]['component_id'], 4, 'Bar depends on foo'); - $dependencies = project_release_component_dependencies_load_all(6); - $this->assertEqual($dependencies[0]['component_id'], 4, 'Bar depends on foo'); - } - } - } -} - -/** - * Ensure that the Drupal parsing functions work properly. - */ -class ProjectInfoDrupalTestCase extends ProjectInfoTestCase { - - public static function getInfo() { - return array( - 'name' => 'Drupal', - 'description' => 'Ensure that the Drupal parsing functions work properly.', - 'group' => 'Project info', - ); - } - - /** - * Ensure that the Drupal parsing functions work properly. - */ - protected function testDrupalParsing() { - $this->process(); - - // Ensure that the components were parsed properly. - $component_id = 7; - $components = project_release_get_components($this->releases['foo']->nid); - $this->assertComponent($components['project_release_info_foo'], $component_id++, $this->releases['foo']->nid, 'project_release_info_foo'); - - $components = project_release_get_components($this->releases['bar']->nid); - $this->assertComponent($components['project_release_info_bar'], $component_id++, $this->releases['bar']->nid, 'project_release_info_bar'); - $this->assertComponent($components['project_release_info_barbar'], $component_id++, $this->releases['bar']->nid, 'project_release_info_barbar'); - - debug(project_release_get_components($this->releases['foo']->nid)); - debug(project_release_get_components($this->releases['bar']->nid)); - - debug(project_release_component_dependencies_load_all(7)); - debug(project_release_component_dependencies_load_all(8)); - debug(project_release_component_dependencies_load_all(9)); - - // Ensure that dependencies were parsed properly. - debug(project_release_get_dependencies($this->releases['foo']->nid)); - debug(project_release_get_dependencies($this->releases['bar']->nid)); - - // Should have error since there is no stable release of project_release_info_foo. - $this->assertPackageError('ERROR: No development release with a corresponding stable release was ' . - 'found that matches the requirements for dependency [project_release_info_foo] of [project_release_info_bar].'); - - $this->createTag('bar', 'DRUPAL-6--1-0'); -// $this->drupalGet('node/add/project-release/' . $this->projects['bar']->nid); - - $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(); - } - - 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 specific implementation. - module_load_include('drupal.inc', 'project_release'); - - // Parse test modules related to foo and bar projects. - $info_files = array( - drupal_get_path('module', 'project_release_info_foo') . '/project_release_info_foo.info', - ); - package_release_info_process_all($this->releases['foo']->nid, $info_files, 'foo', '6.x-1.0'); - - $info_files = array( - drupal_get_path('module', 'project_release_info_bar') . '/project_release_info_bar.info', - drupal_get_path('module', 'project_release_info_barbar') . '/project_release_info_barbar.info', - ); - package_release_info_process_all($this->releases['bar']->nid, $info_files, 'bar', '6.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()); - } - - 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 . ']'); - } - - 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/barbar/project_release_info_barbar.info release/tests/bar/barbar/project_release_info_barbar.info deleted file mode 100644 index 2c215e8..0000000 --- release/tests/bar/barbar/project_release_info_barbar.info +++ /dev/null @@ -1,9 +0,0 @@ -; $Id$ -name = Project release info barbar -description = Test module for project_release_info. (do not enable) -package = Project -version = 1.2 -core = 6.x -dependencies[] = project_release_info_foo - -test_dependencies[] = project_release_info_foo (1.x) diff --git release/tests/bar/barbar/project_release_info_barbar.module release/tests/bar/barbar/project_release_info_barbar.module deleted file mode 100644 index bd4d750..0000000 --- release/tests/bar/barbar/project_release_info_barbar.module +++ /dev/null @@ -1,9 +0,0 @@ -