? .git ? 900646_profiler_d7.patch ? PATCHES.txt ? profiler_d7_backport.patch Index: README.txt =================================================================== RCS file: /cvs/drupal-contrib/contributions/profiles/profiler/README.txt,v retrieving revision 1.2 diff -u -p -r1.2 README.txt --- README.txt 30 Aug 2010 16:30:34 -0000 1.2 +++ README.txt 2 Sep 2010 23:06:47 -0000 @@ -1,4 +1,4 @@ -# $Id: README.txt,v 1.2 2010/08/30 16:30:34 yhahn Exp $ +# $Id: README.txt,v 1.1 2010/03/23 23:42:57 q0rban Exp $ Profiler -------- @@ -74,51 +74,22 @@ modules to be included in your install p base = profile_foo -- `modules[core]` +- `dependencies` - An array of Drupal core modules to be enabled for this install profile. Need - not include the modules defined by `drupal_required_modules()`, ie. block, - filter, node, system and user. - - modules[core][] = book - modules[core][] = color - modules[core][] = comment + An array of Drupal core, contrib, or feature modules to be enabled for this + install profile. Need not include the modules defined by + `drupal_required_modules()`, ie. block, filter, node, system and user. Any + dependencies of the listed modules will also be detected and enabled. + + dependencies[] = book + dependencies[] = color + dependencies[] = views + dependencies[] = myblog The following syntax can be used to disable/exclude core modules that would otherwise be inherited from a base install profile: - modules[core][book] = 0 - -- `modules[contrib]` or optionally any `modules[x]` - - An array of non-core Drupal modules to be enabled for this install profile. - Any key(s) may be used to define one or more arrays of modules, though - `contrib` is standard. - - modules[contrib][] = content - modules[contrib][] = features - modules[contrib][] = token - modules[contrib][] = views - - The following syntax can be used to disable/exclude modules that would - otherwise be inherited from a base install profile: - - modules[contrib][content] = 0 - -- `features` - - An array of Drupal feature modules to be enabled for this install profile. - Requires: features. - - - features[] = myblog - features[] = mygallery - features[] = myvideos - - The following syntax can be used to disable/exclude features that would - otherwise be inherited from a base install profile: - - features[myblog] = 0 + dependencies[book] = 0 - `theme` Index: profiler_api.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/profiles/profiler/profiler_api.inc,v retrieving revision 1.3 diff -u -p -r1.3 profiler_api.inc --- profiler_api.inc 2 Sep 2010 20:20:27 -0000 1.3 +++ profiler_api.inc 2 Sep 2010 23:06:48 -0000 @@ -8,7 +8,8 @@ * The config array for an Install Profile. */ function profiler_profile_modules($config) { - $modules = isset($config['modules']['core']) ? profiler_config_reduce($config['modules']['core']) : array(); + // Retrieve install profile dependencies. + $modules = isset($config['dependencies']) ? profiler_config_reduce($config['dependencies']) : array(); // Add module dependencies for any install components. foreach (array_keys($config) as $name) { @@ -16,7 +17,37 @@ function profiler_profile_modules($confi $modules = array_merge($modules, $component['dependencies']); } } - return array_unique($modules); + + // Include code for building the module dependency tree. + require_once('profiler_module.inc'); + $files = profiler_module_rebuild_cache(); + + // Always install required modules first. Respect the dependencies between + // the modules. + $required = array(); + $non_required = array(); + // Although the profile module is marked as required, it needs to go after + // every dependency, including non-required ones. So clear its required + // flag for now to allow it to install late. + $files[$install_state['parameters']['profile']]->info['required'] = FALSE; + // Add modules that other modules depend on. + foreach ($modules as $module) { + if ($files[$module]->requires) { + $modules = array_merge($modules, array_keys($files[$module]->requires)); + } + } + $modules = array_unique($modules); + foreach ($modules as $module) { + if (!empty($files[$module]->info['required'])) { + $required[$module] = $files[$module]->sort; + } + else { + $non_required[$module] = $files[$module]->sort; + } + } + arsort($required); + arsort($non_required); + return array_unique(array_keys(array_merge($required, $non_required))); } /** @@ -26,13 +57,7 @@ function profiler_profile_modules($confi * The config array for an Install Profile. */ function profiler_profile_task_list($config) { - $tasks = array(); - $tasks['profiler-modules'] = st('Install Modules'); - if (!empty($config['features'])) { - $tasks['profiler-features'] = st('Install Features'); - } - $tasks['profiler-install'] = st('Additional Configuration'); - return $tasks; + return array(); } /** @@ -48,65 +73,7 @@ function profiler_profile_task_list($con * providing any, to allow the user to proceed with the installation. */ function profiler_profile_tasks($config, &$task, $url) { - // Just in case some of the future tasks adds some output - $output = ''; - if ($task == 'profile') { - // Move along, nothing to do here. - $task = 'profile-modules'; - } - - // Install some contrib modules. - if ($task == 'profile-modules') { - $modules = profiler_config_merge($config['modules'], array('core')); - - // Ensure the features module is here if there are features in the config. - if (!in_array('features', $modules) && !empty($config['features'])) { - $modules[] = 'features'; - } - - $files = module_rebuild_cache(); - $operations = array(); - foreach ($modules as $module) { - $operations[] = array('_install_module_batch', array($module, $files[$module]->info['name'])); - } - $batch = array( - 'operations' => $operations, - 'finished' => 'profiler_v1_batch_finished', - 'title' => st('Installing @drupal', array('@drupal' => drupal_install_profile_name())), - 'error_message' => st('The installation has encountered an error.'), - ); - // Start a batch, switch to 'profile-install-batch' task. We need to - // set the variable here, because batch_process() redirects. - variable_set('install_task', 'profile-install-batch'); - batch_set($batch); - batch_process($url, $url); - } - - // Install all features. - if ($task == 'profiler-features') { - if (!empty($config['features']) && $features = profiler_config_reduce($config['features'])) { - $operations = _profiler_features_batch_ops($features); - $batch = array( - 'operations' => $operations, - 'finished' => 'profiler_v1_batch_finished', - 'title' => st('Installing @drupal', array('@drupal' => drupal_install_profile_name())), - 'error_message' => st('The installation has encountered an error.'), - ); - // Start a batch, switch to 'profile-install-batch' task. We need to - // set the variable here, because batch_process() redirects. - variable_set('install_task', 'profile-install-batch'); - batch_set($batch); - batch_process($url, $url); - } - else { - profiler_v1_batch_finished(TRUE, array()); - $task = 'profiler-install'; - } - } - - // Installs the actual profile. - if ($task == 'profiler-install') { // If the install defines input formats, remove the default ones. if (!empty($config['input-formats'])) { $result = db_query("SELECT * FROM {filter_formats} WHERE name IN ('%s', '%s')", 'Filtered HTML', 'Full HTML'); @@ -134,6 +101,8 @@ function profiler_profile_tasks($config, $task = 'profile-finished'; } + // Just in case some of the future tasks adds some output. + $output = ''; return $output; } @@ -313,36 +282,3 @@ function profiler_install_configure($con variable_set('install_time', time()); } } - -/** - * Helper function to return an array of batch operations, or list of features - * and modules to install. Since a single feature could theoretically have quite - * a handful of dependencies, we detect the dependencies beforehand and add each - * one as a separate batch operation, so there is less of a chance of the batch - * timing out. - */ -function _profiler_features_batch_ops($features) { - module_load_include('inc', 'features', 'features.export'); - - $operations = $install = array(); - $files = module_rebuild_cache(); - - // Add any dependencies as separate items, so that the batch doesn't - // timeout trying to enable too many modules at once. - foreach ($features as $feature) { - if ($file = $files[$feature]) { - if (!empty($file->info['dependencies'])) { - $install = array_merge($install, _features_export_maximize_dependencies($file->info['dependencies'])); - } - $install[] = $feature; - } - } - // Now filter out any already enabled modules. - foreach ($install as $module) { - if (!module_exists($module)) { - $operations[] = array('_install_module_batch', array($module, $files[$module]->info['name'])); - } - } - - return $operations; -} Index: profiler_module.inc =================================================================== RCS file: profiler_module.inc diff -N profiler_module.inc --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ profiler_module.inc 2 Sep 2010 23:06:48 -0000 @@ -0,0 +1,273 @@ + array(), + 'dependents' => array(), + 'description' => '', + 'version' => NULL, + 'php' => DRUPAL_MINIMUM_PHP, + ); + + foreach ($files as $filename => $file) { + // Look for the info file. + $file->info = drupal_parse_info_file(dirname($file->filename) .'/'. $file->name .'.info'); + + // Skip modules that don't provide info. + if (empty($file->info)) { + unset($files[$filename]); + continue; + } + // Merge in defaults and save. + $files[$filename]->info = $file->info + $defaults; + } + $files = _profiler_module_build_dependencies($files); + return $files; +} + +/** + * Find dependencies any level deep and fill in required by information too. + * + * @param $files + * The array of filesystem objects used to rebuild the cache. + * + * @return + * The same array with the new keys for each module: + * - requires: An array with the keys being the modules that this module + * requires. + * - required_by: An array with the keys being the modules that will not work + * without this module. + */ +function _profiler_module_build_dependencies($files) { + // require_once DRUPAL_ROOT . '/includes/graph.inc'; + 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) { + $dependency_data = profiler_drupal_parse_dependency($dependency); + $graph[$file->name]['edges'][$dependency_data['name']] = $dependency_data; + } + } + } + profiler_drupal_depth_first_search($graph); + foreach ($graph as $module => $data) { + $files[$module]->required_by = isset($data['reverse_paths']) ? $data['reverse_paths'] : array(); + $files[$module]->requires = isset($data['paths']) ? $data['paths'] : array(); + $files[$module]->sort = $data['weight']; + } + return $files; +} + +/** + * 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 profiler_drupal_parse_dependency($dependency) { + // 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(DRUPAL_CORE_COMPATIBILITY) . '-)?'; + $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; +} + +/** + * Perform a depth first sort on a directed acyclic graph. + * + * @param $graph + * 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 are copied over. + * + * Example: + * @code + * $graph[1]['edges'][2] = 1; + * $graph[2]['edges'][3] = 1; + * $graph[2]['edges'][4] = 1; + * $graph[3]['edges'][4] = 1; + * @endcode + * + * On return you will also have: + * @code + * $graph[1]['paths'][2] = 1; + * $graph[1]['paths'][3] = 1; + * $graph[2]['reverse_paths'][1] = 1; + * $graph[3]['reverse_paths'][1] = 1; + * @endcode + * + * @return + * The passed in $graph with more secondary keys filled in: + * - 'paths': Contains a list of vertices than can be reached on a path from + * this vertex. + * - 'reverse_paths': Contains a list of vertices that has a path from them + * to this vertex. + * - 'weight': If there is a path from a vertex to another then the weight of + * the latter is higher. + * - 'component': Vertices in the same component have the same component + * identifier. + * + * @see _drupal_depth_first_search() + */ +function profiler_drupal_depth_first_search(&$graph) { + $state = array( + // The order of last visit of the depth first search. This is the reverse + // of the topological order if the graph is acyclic. + 'last_visit_order' => array(), + // The components of the graph. + 'components' => array(), + ); + // Perform the actual sort. + foreach ($graph as $start => $data) { + _profiler_drupal_depth_first_search($graph, $state, $start); + } + + // We do such a numbering that every component starts with 0. This is useful + // for module installs as we can install every 0 weighted module in one + // request, and then every 1 weighted etc. + $component_weights = array(); + + foreach ($state['last_visit_order'] as $vertex) { + $component = $graph[$vertex]['component']; + if (!isset($component_weights[$component])) { + $component_weights[$component] = 0; + } + $graph[$vertex]['weight'] = $component_weights[$component]--; + } +} + +/** + * Helper function to perform a depth first sort. + * + * @param &$graph + * A three dimensional associated graph array. + * @param &$state + * An associative array. The key 'last_visit_order' stores a list of the + * vertices visited. The key components stores list of vertices belonging + * to the same the component. + * @param $start + * An arbitrary vertex where we started traversing the graph. + * @param &$component + * The component of the last vertex. + * + * @see drupal_depth_first_search() + */ +function _profiler_drupal_depth_first_search(&$graph, &$state, $start, &$component = NULL) { + // Assign new component for each new vertex, i.e. when not called recursively. + if (!isset($component)) { + $component = $start; + } + // Nothing to do, if we already visited this vertex. + if (isset($graph[$start]['paths'])) { + return; + } + // Mark $start as visited. + $graph[$start]['paths'] = array(); + + // Assign $start to the current component. + $graph[$start]['component'] = $component; + $state['components'][$component][] = $start; + + // Visit edges of $start. + if (isset($graph[$start]['edges'])) { + foreach ($graph[$start]['edges'] as $end => $v) { + // Mark that $start can reach $end. + $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 + // reassign all the previously explored vertices. + $new_component = $graph[$end]['component']; + foreach ($state['components'][$component] as $vertex) { + $graph[$vertex]['component'] = $new_component; + $state['components'][$new_component][] = $vertex; + } + unset($state['components'][$component]); + $component = $new_component; + } + // Only visit existing vertices. + if (isset($graph[$end])) { + // Visit the connected vertex. + _profiler_drupal_depth_first_search($graph, $state, $end, $component); + + // All vertices reachable by $end are also reachable by $start. + $graph[$start]['paths'] += $graph[$end]['paths']; + } + } + } + + // Now that any other subgraph has been explored, add $start to all reverse + // paths. + foreach ($graph[$start]['paths'] as $end => $v) { + if (isset($graph[$end])) { + $graph[$end]['reverse_paths'][$start] = $v; + } + } + + // Record the order of the last visit. This is the reverse of the + // topological order if the graph is acyclic. + $state['last_visit_order'][] = $start; +}