? 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 15:36:37 -0000
@@ -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.1
diff -u -p -r1.1 profiler_api.inc
--- profiler_api.inc	30 Aug 2010 16:30:34 -0000	1.1
+++ profiler_api.inc	2 Sep 2010 15:36:37 -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,66 +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.
-  // @todo Review all the cache/rebuild options at the end, some of them may not be needed
-  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');
@@ -137,6 +103,8 @@ function profiler_profile_tasks($config,
     $task = 'profile-finished';
   }
 
+  // Just in case some of the future tasks adds some output.
+  $output = '';
   return $output;
 }
 
@@ -316,36 +284,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 15:36:37 -0000
@@ -0,0 +1,273 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Contains backports of Drupal 7 install profile and graph builder functions.
+ */
+
+/**
+ * Generate a cache of module files with proper dependency tree relationships
+ * fully built. Modeled after module_rebuild_cache() but without requiring a
+ * database to be present.
+ *
+ * @return
+ *   The array of filesystem objects used to rebuild the cache.
+ */
+function profiler_module_rebuild_cache() {
+  // Get current list of modules
+  $files = drupal_system_listing('\.module$', 'modules', 'name', 0);
+  ksort($files);
+
+  // Set defaults for module info
+  $defaults = array(
+    'dependencies' => 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<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)(?:-[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;
+}
