diff options
-rw-r--r-- | CHANGELOG.txt | 2 | ||||
-rw-r--r-- | includes/graph.inc | 6 | ||||
-rw-r--r-- | includes/module.inc | 51 | ||||
-rw-r--r-- | modules/simpletest/tests/system_test.module | 20 | ||||
-rw-r--r-- | modules/system/system.admin.inc | 29 | ||||
-rw-r--r-- | modules/system/system.test | 55 |
6 files changed, 150 insertions, 13 deletions
diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 7a863b1f2..360c663b9 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -145,6 +145,8 @@ Drupal 7.0, xxxx-xx-xx (development version) * Upgraded the jQuery Forms library to 2.21. * Added jQuery UI 1.7.2, which allows improvements to Drupal's user experience. +- Better module version support. + * Modules now can specify which version of another module they depend on. Drupal 6.0, 2008-02-13 ---------------------- diff --git a/includes/graph.inc b/includes/graph.inc index e9fffc587..aaaa19abb 100644 --- a/includes/graph.inc +++ b/includes/graph.inc @@ -14,7 +14,7 @@ * A three dimensional associated array, with the first keys being the names * of the vertices, these can be strings or numbers. The second key is * 'edges' and the third one are again vertices, each such key representing - * an edge. Values of array elements do not matter. + * an edge. Values of array elements are copied over. * * Example: * @code @@ -108,7 +108,7 @@ function _drupal_depth_first_search(&$graph, &$state, $start, &$component = NULL if (isset($graph[$start]['edges'])) { foreach ($graph[$start]['edges'] as $end => $v) { // Mark that $start can reach $end. - $graph[$start]['paths'][$end] = TRUE; + $graph[$start]['paths'][$end] = $v; if (isset($graph[$end]['component']) && $component != $graph[$end]['component']) { // This vertex already has a component, use that from now on and @@ -136,7 +136,7 @@ function _drupal_depth_first_search(&$graph, &$state, $start, &$component = NULL // paths. foreach ($graph[$start]['paths'] as $end => $v) { if (isset($graph[$end])) { - $graph[$end]['reverse_paths'][$start] = TRUE; + $graph[$end]['reverse_paths'][$start] = $v; } } diff --git a/includes/module.inc b/includes/module.inc index 4d25994f7..8fd4273ef 100644 --- a/includes/module.inc +++ b/includes/module.inc @@ -95,11 +95,52 @@ function module_list($refresh = FALSE, $sort = FALSE, $fixed_list = NULL) { function _module_build_dependencies($files) { require_once DRUPAL_ROOT . '/includes/graph.inc'; $roots = $files; + // 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(DRUPAL_CORE_COMPATIBILITY) . '-)?'; + $p_major = '(?P<major>\d+)'; + // By setting the minor version to x, branches can be matched. + $p_minor = '(?P<minor>\d+|x)'; foreach ($files as $filename => $file) { $graph[$file->name]['edges'] = array(); if (isset($file->info['dependencies']) && is_array($file->info['dependencies'])) { foreach ($file->info['dependencies'] as $dependency_name) { - $graph[$file->name]['edges'][$dependency_name] = 1; + $value = array(); + $parts = explode('(', $dependency_name, 2); + $dependency_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') { + // If a module is newer than 2.x then it's at least 3.0. + $matches['minor'] = 0; + if ($op == '>') { + $matches['major']++; + $op = '>='; + } + // If a module is older or equivalent than 2.x then it is older + // than 3.0. + if ($op == '<=') { + $matches['major']++; + $op = '<'; + } + // Equivalence is checked by preg. + if ($op == '=' || $op == '==') { + $value['versions'][] = array('preg' => '/^' . $matches['major'] . '\./'); + $op = ''; + } + } + if ($op) { + $value['versions'][] = array('op' => $op, 'version' => $matches['major'] . '.' . $matches['minor']); + } + } + } + } + $graph[$file->name]['edges'][$dependency_name] = $value; unset($roots[$dependency_name]); } } @@ -138,13 +179,13 @@ function module_load_install($module) { /** * Load a module include file. - * + * * Examples: * @code * // Load node.admin.inc from the node module. * module_load_include('inc', 'node', 'node.admin'); * // Load content_types.inc from the node module. - * module_load_include('inc', 'node', 'content_types'); + * module_load_include('inc', 'node', 'content_types'); * @endcode * * Do not use this function to load an install file. Use module_load_install() @@ -155,7 +196,7 @@ function module_load_install($module) { * @param $module * The module to which the include file belongs. * @param $name - * Optionally, specify the base file name (without the $type extension). + * Optionally, specify the base file name (without the $type extension). * If not set, $module is used. */ function module_load_include($type, $module, $name = NULL) { @@ -194,7 +235,7 @@ function module_load_all_includes($type, $name = NULL) { */ function module_enable($module_list, $disable_modules_installed_hook = FALSE) { $invoke_modules = array(); - + // Try to install the enabled modules and collect which were installed. // $module_list is not changed and already installed modules are ignored. $modules_installed = array_filter($module_list, '_drupal_install_module'); diff --git a/modules/simpletest/tests/system_test.module b/modules/simpletest/tests/system_test.module index 07a86afdb..cab3b9e57 100644 --- a/modules/simpletest/tests/system_test.module +++ b/modules/simpletest/tests/system_test.module @@ -165,3 +165,23 @@ function system_test_exit() { watchdog('system_test', 'hook_exit'); } +/** + * Implement hook_system_info_alter(). + */ +function system_test_system_info_alter(&$info, $file) { + // We need a static otherwise the last test will fail to alter common_test. + static $test; + if (($dependencies = variable_get('dependencies', array())) || $test) { + if ($file->name == 'module_test') { + $info['hidden'] = FALSE; + $info['dependencies'][] = array_shift($dependencies); + variable_set('dependencies', $dependencies); + $test = TRUE; + } + if ($file->name == 'common_test') { + $info['hidden'] = FALSE; + $info['version'] = '7.x-2.4'; + } + } +} + diff --git a/modules/system/system.admin.inc b/modules/system/system.admin.inc index 834cea968..284d02eec 100644 --- a/modules/system/system.admin.inc +++ b/modules/system/system.admin.inc @@ -598,13 +598,32 @@ function system_modules($form_state = array()) { foreach ($module->requires as $requires => $v) { if (!isset($files[$requires])) { $extra['requires'][$requires] = t('@module (<span class="admin-missing">missing</span>)', array('@module' => drupal_ucfirst($requires))); - $extra['disabled'] = TRUE; + $extra['disabled'] = TRUE; + } + else { + $requires_name = $files[$requires]->info['name']; + if ($v) { + $requires_name .= $v['original_version']; + $current_version = str_replace(DRUPAL_CORE_COMPATIBILITY . '-', '', $files[$requires]->info['version']); + foreach ($v['versions'] as $required_version) { + if ((isset($required_version['op']) && !version_compare($current_version, $required_version['version'], $required_version['op'])) || + (isset($required_version['preg']) && !preg_match($required_version['preg'], $current_version))) { + $extra['requires'][$requires] = t('@module (<span class="admin-missing">incompatible with</span> version @version)', array( + '@module' => $requires_name, + '@version' => $files[$requires]->info['version'], + )); + $extra['disabled'] = TRUE; + } + } } - elseif (!$files[$requires]->status) { - $extra['requires'][$requires] = t('@module (<span class="admin-disabled">disabled</span>)', array('@module' => $files[$requires]->info['name'])); + if (!isset($extra['requires'][$requires])) { + if ($files[$requires]->status) { + $extra['requires'][$requires] = t('@module (<span class="admin-enabled">enabled</span>)', array('@module' => $requires_name)); + } + else { + $extra['requires'][$requires] = t('@module (<span class="admin-disabled">disabled</span>)', array('@module' => $requires_name)); + } } - else { - $extra['requires'][$requires] = t('@module (<span class="admin-enabled">enabled</span>)', array('@module' => $files[$requires]->info['name'])); } } // Generate link for module's help page, if there is one. diff --git a/modules/system/system.test b/modules/system/system.test index a48cbd95a..09f49b9f5 100644 --- a/modules/system/system.test +++ b/modules/system/system.test @@ -198,6 +198,61 @@ class ModuleDependencyTestCase extends ModuleTestCase { } /** + * Test module dependency on specific versions. + */ +class ModuleVersionTestCase extends ModuleTestCase { + public static function getInfo() { + return array( + 'name' => 'Module versions', + 'description' => 'Check module version dependencies.', + 'group' => 'Module', + ); + } + + function setup() { + parent::setUp('module_test'); + } + + /** + * Test version dependencies. + */ + function testModuleVersions() { + $dependencies = array( + // Alternating between being compatible and incompatible with 7.x-2.4. + // The first is always a compatible. + 'common_test', + // Branch incompatibility. + 'common_test (1.x)', + // Branch compatibility. + 'common_test (2.x)', + // Another branch incompatibility. + 'common_test (>2.x)', + // Another branch compatibility. + 'common_test (<=2.x)', + // Another branch incompatibility. + 'common_test (<2.x)', + // Another branch compatibility. + 'common_test (>=2.x)', + // Nonsense, misses a dash. Incompatible with everything. + 'common_test (=7.x2.x, >=2.4)', + // Core version is optional. Compatible. + 'common_test (=7.x-2.x, >=2.4)', + // Test !=, explicitly incompatible. + 'common_test (=2.x, !=2.4)', + // Three operations. Compatible. + 'common_test (=2.x, !=2.3, <2.5)', + ); + variable_set('dependencies', $dependencies); + $n = count($dependencies); + for ($i = 0; $i < $n; $i++) { + $this->drupalGet('admin/structure/modules'); + $checkbox = $this->xpath('//input[@id="edit-modules-Testing-module-test-enable"]'); + $this->assertEqual(!empty($checkbox[0]['disabled']), $i % 2, $dependencies[$i]); + } + } +} + +/** * Test required modules functionality. */ class ModuleRequiredTestCase extends ModuleTestCase { |