Added ability to include/exclude optional dependencies during the installation. Refac...
authorGunnar Wrobel <p@rdus.de>
Thu, 28 Oct 2010 16:09:02 +0000 (18:09 +0200)
committerGunnar Wrobel <p@rdus.de>
Thu, 28 Oct 2010 19:33:57 +0000 (21:33 +0200)
18 files changed:
components/TODO
components/lib/Components/Helper/InstallationRun.php
components/lib/Components/Helper/ListRun.php
components/lib/Components/Helper/Tree.php
components/lib/Components/Helper/Tree/Element.php [deleted file]
components/lib/Components/Module/Installer.php
components/lib/Components/Pear/Dependencies.php [new file with mode: 0644]
components/lib/Components/Pear/Dependency.php [new file with mode: 0644]
components/lib/Components/Pear/Factory.php
components/lib/Components/Pear/InstallLocation.php
components/lib/Components/Pear/Package.php
components/lib/Components/Runner/Installer.php
components/test/Components/Integration/Components/Module/DependenciesTest.php
components/test/Components/Integration/Components/Module/InstallerTest.php
components/test/Components/StoryTestCase.php
components/test/Components/fixture/framework/Dependency/package.xml
components/test/Components/fixture/framework/Optional/lib/Optional.php [new file with mode: 0644]
components/test/Components/fixture/framework/Optional/package.xml [new file with mode: 0644]

index c8a07de..294802f 100644 (file)
@@ -6,8 +6,6 @@
 
  - Add module for generating component documentation.
 
- - Allow activating/deactivating optional install elements
-
  - Allow handling various Horde package locations (horde/, horde/framework/, Horde_SQL -> horde/framework/SQL)
 
  - Allow linking files during the installation.
index 24d22ed..eca2d05 100644 (file)
@@ -37,6 +37,20 @@ class Components_Helper_InstallationRun
     private $_environment;
 
     /**
+     * The tree for this run.
+     *
+     * @var Components_Helper_Tree
+     */
+    private $_tree;
+
+    /**
+     * The output handler.
+     *
+     * @param Component_Output
+     */
+    private $_output;
+
+    /**
      * The list of channels already installed.
      *
      * @var array
@@ -55,11 +69,56 @@ class Components_Helper_InstallationRun
      *
      * @param Components_Pear_InstallLocation $environment The environment we
      *                                                     establish the tree for.
+     * @param Components_Helper_Tree          $tree        The tree for this run.
+     * @param Component_Output                $output      The output handler.
+     * @param array                           $options     Options for this installation.
      */
     public function __construct(
-        Components_Pear_InstallLocation $environment
+        Components_Pear_InstallLocation $environment,
+        Components_Helper_Tree $tree,
+        Components_Output $output,
+        array $options
     ) {
         $this->_environment = $environment;
+        $this->_tree = $tree;
+        $this->_output = $output;
+        $this->_options = $options;
+    }
+
+    /**
+     * Install a package into the environment.
+     *
+     * @param Components_Pear_Package $package The package that should be installed.
+     * @param string                            $reason  Optional reason for adding the package.
+     *
+     * @return NULL
+     */
+    public function install(
+        Components_Pear_Package $package,
+        $reason = ''
+    ) {
+        $this->_installDependencies(
+            $package->getDependencyHelper(),
+            sprintf(' [required by %s]', $package->getName())
+        );
+        $this->_installHordePackageOnce($package->getPackageXml(), $reason);
+    }
+
+    /**
+     * Install a list of dependencies.
+     *
+     * @param Components_Pear_Dependencies $dependencies The package dependencies.
+     * @param string                       $reason  Optional reason for adding the dependencies.
+     *
+     * @return NULL
+     */
+    private function _installDependencies(
+        Components_Pear_Dependencies $dependencies,
+        $reason = ''
+    ) {
+        $this->_installChannels($dependencies, $reason);
+        $this->_installExternalPackages($dependencies, $reason);
+        $this->_installHordeDependencies($dependencies, $reason);
     }
 
     /**
@@ -67,59 +126,136 @@ class Components_Helper_InstallationRun
      * environment. The channels are only going to be installed once during the
      * installation run represented by this instance.
      *
-     * @param array  $channels The channels to install.
-     * @param string $reason   Optional reason for adding the channels.
+     * @param Components_Pear_Dependencies $dependencies The package dependencies.
+     * @param string                       $reason       Optional reason for adding the channels.
      *
      * @return NULL
      */
-    public function installChannelsOnce(array $channels, $reason = '')
-    {
-        foreach ($channels as $channel) {
+    private function _installChannels(
+        Components_Pear_Dependencies $dependencies,
+        $reason = ''
+    ) {
+        foreach ($dependencies->listAllChannels() as $channel) {
             if (!in_array($channel, $this->_installed_channels)) {
-                $this->_environment->provideChannel($channel, $reason);
+                if (empty($this->_options['pretend'])) {
+                    $this->_environment->provideChannel($channel, $reason);
+                } else {
+                    $this->_output->ok(
+                        sprintf('Would install channel %s%s.', $channel, $reason)
+                    );
+                }
                 $this->_installed_channels[] = $channel;
             }
         }
     }
 
     /**
+     * Ensure that the external dependencies are available within the
+     * installation environment.
+     *
+     * @param Components_Pear_Dependencies $dependencies The package dependencies.
+     * @param string                       $reason       Optional reason for adding the package.
+     *
+     * @return NULL
+     */
+    private function _installExternalPackages(
+        Components_Pear_Dependencies $dependencies,
+        $reason = ''
+    ) {
+        foreach (
+            $dependencies->listExternalDependencies(
+                $this->_options['include'], $this->_options['exclude']
+            ) as $dependency
+        ) {
+            // Refrain from installing optional pecl packages
+            if ($dependency->isPackage()) {
+                $this->_installExternalPackageOnce(
+                    $dependency, $reason
+                );
+            }
+        }
+    }
+
+    /**
      * Ensure that the external package is available within the installation
      * environment. The package is only going to be installed once during the
      * installation run represented by this instance.
      *
-     * @param string $package The package that should be installed.
-     * @param string $channel The channel of the package.
-     * @param string $reason  Optional reason for adding the package.
-     * @param array  $to_add  The local packages currently being added.
+     * @param Components_Pear_Dependency $dependency The package dependency.
+     * @param string                     $reason     Optional reason for adding the package.
+     * @param array                      $to_add     The local packages currently being added.
      *
      * @return NULL
      */
-    public function installExternalPackageOnce($channel, $package, $reason = '', array &$to_add = null)
-    {
-        $key = $channel . '/' . $package;
-        if (!in_array($key, $this->_installed_packages)) {
+    private function _installExternalPackageOnce(
+        Components_Pear_Dependency $dependency,
+        $reason = '',
+        array &$to_add = null
+    ) {
+        if (!in_array($dependency->key(), $this->_installed_packages)) {
             if (empty($to_add)) {
-                $to_add = array($key);
+                $to_add = array($dependency->key());
             }
-            foreach ($this->_environment->identifyRequiredLocalDependencies($channel, $package) as $required) {
-                $rkey = $required['channel'] . '/' . $required['name'];
-                if (in_array($rkey, $to_add)) {
+            foreach (
+                $this->_environment->identifyRequiredLocalDependencies(
+                    $dependency, $this->_options['include'], $this->_options['exclude']
+                ) as $required
+            ) {
+                if (in_array($required->key(), $to_add)) {
                     continue;
                 }
-                $to_add[] = $rkey;
-                $rreason = sprintf(' [required by %s]', $package);
-                $this->installExternalPackageOnce(
-                    $required['channel'], $required['name'], $rreason, &$to_add
+                $to_add[] = $required->key();
+                $this->_installExternalPackageOnce(
+                    $required, sprintf(' [required by %s]', $dependency->name()), &$to_add
                 );
             }
                 
-            $this->_environment->addPackageFromPackage(
-                $channel, $package, $reason
-            );
-            $this->_installed_packages[] = $key;
+            if (empty($this->_options['pretend'])) {
+                $this->_environment->addPackageFromPackage(
+                    $dependency, $reason
+                );
+            } else {
+                $this->_output->ok(
+                    sprintf(
+                        'Would install external package %s%s.',
+                        $dependency->key(),
+                        $reason
+                    )
+                );
+            }
+            $this->_installed_packages[] = $dependency->key();
         }
     }
-    
+
+    /**
+     * Ensure that the horde package is available within the installation
+     * environment. The package is only going to be installed once during the
+     * installation run represented by this instance.
+     *
+     * @param Components_Pear_Dependencies $dependencies The package dependencies.
+     * @param string                       $reason       Optional reason for adding the package.
+     *
+     * @return NULL
+     */
+    private function _installHordeDependencies(
+        Components_Pear_Dependencies $dependencies,
+        $reason = ''
+    ) {
+        foreach (
+            $this->_tree->getChildren(
+                $dependencies->listHordeDependencies(
+                    $this->_options['include'], $this->_options['exclude']
+                )
+            ) as $child
+        ) {
+            if (in_array($child->getName(), $this->_installed_packages)) {
+                continue;
+            }
+            $this->_installed_packages[] = $child->getName();
+            $this->install($child, $reason);
+        }
+    }
+  
     /**
      * Ensure that the horde package is available within the installation
      * environment. The package is only going to be installed once during the
@@ -131,13 +267,20 @@ class Components_Helper_InstallationRun
      *
      * @return NULL
      */
-    public function installHordePackageOnce($package_file, $reason = '')
+    private function _installHordePackageOnce($package_file, $reason = '')
     {
-        if (!in_array($package_file, $this->_installed_packages)) {
+        if (empty($this->_options['pretend'])) {
             $this->_environment->addPackageFromSource(
                 $package_file, $reason
             );
-            $this->_installed_packages[] = $package_file;
+        } else {
+            $this->_output->ok(
+                sprintf(
+                    'Would install package %s%s.',
+                    $package_file,
+                    $reason
+                )
+            );
         }
     }
 }
\ No newline at end of file
index 2ccf452..3448614 100644 (file)
@@ -37,6 +37,13 @@ class Components_Helper_ListRun
     private $_output;
 
     /**
+     * The tree for this run.
+     *
+     * @var Components_Helper_Tree
+     */
+    private $_tree;
+
+    /**
      * The list of dependencies already displayed.
      *
      * @var array
@@ -53,40 +60,60 @@ class Components_Helper_ListRun
     /**
      * Constructor.
      *
-     * @param Component_Output $output The output handler.
+     * @param Component_Output       $output The output handler.
+     * @param Components_Helper_Tree $tree   The tree for this run.
      */
-    public function __construct(Components_Output $output)
-    {
+    public function __construct(
+        Components_Output $output,
+        Components_Helper_Tree $tree
+    ) {
         $this->_output = $output;
+        $this->_tree = $tree;
         if ($this->_output->isVerbose()) {
-            $output->bold('List contains optional dependencies!');
+            $output->bold('The list contains optional dependencies!');
         } else {
-            $output->bold('List only contains required dependencies!');
+            $output->bold('The list only contains required dependencies!');
         }
         $output->blue('Dependencies on PEAR itself are not displayed.');
         $output->bold('');
     }
 
     /**
+     * List the dependency tree for this package.
+     *
+     * @param Components_Pear_Package $package The package that should be installed.
+     * @param int                     $level  The current list level.
+     * @param string                  $parent The name of the parent element.
+     *
+     * @return NULL
+     */
+    public function listTree(
+        Components_Pear_Package $package,
+        $level = 0,
+        $parent = '',
+        $required = true
+    ) {
+        if ($this->_listHordePackage($package, $level, $parent, $required)) {
+            $this->_listExternalPackages($package->getDependencyHelper(), $level + 1);
+            $this->_listHordeDependencies($package->getDependencyHelper(), $level + 1);
+        }
+    }
+
+    /**
      * List a Horde component as dependency.
      *
      * @param Components_Pear_Package $package The package that should be listed.
      * @param int                     $level   The current list level.
      * @param string                  $parent  Name of the parent element.
-     * @param boolean                 $reqired Is this a required element?
      *
      * @return boolean True in case listing should continue.
      */
-    public function listHordePackage(
+    private function _listHordePackage(
         Components_Pear_Package $package,
         $level,
-        $parent,
-        $required
+        $parent
     ) {
-        if (!$this->_output->isVerbose() && !$required) {
-            return false;
-        }
-        $key = $package->getName() . '@pear.horde.org';
+        $key = $package->getName() . '/pear.horde.org';
         if (!$this->_output->isQuiet()) {
             if (in_array($key, array_keys($this->_displayed_dependencies))) {
                 if (empty($this->_displayed_dependencies[$key])) {
@@ -123,37 +150,66 @@ class Components_Helper_ListRun
     }
 
     /**
-     * List an external package as dependency.
+     * List the horde dependencies.
      *
-     * @param array $dependency The dependency that should be listed.
-     * @param int   $level     The current list level.
+     * @param Components_Pear_Dependencies $dependencies The package dependencies.
+     * @param int                          $level        The current list level.
      *
      * @return NULL
      */
-    public function listExternalPackage(array $dependency, $level)
-    {
-        // Showing PEAR does not make much sense.
-        if ($dependency['name'] == 'PEAR'
-            && $dependency['channel'] == 'pear.php.net') {
-            return;
+    private function _listHordeDependencies(
+        Components_Pear_Dependencies $dependencies,
+        $level
+    ) {
+        if ($this->_output->isVerbose()) {
+            $list = $dependencies->listAllHordeDependencies();
+        } else {
+            $list = $dependencies->listRequiredHordeDependencies();
+        }
+        foreach ($this->_tree->getChildren($list) as $child) {
+            $this->listTree($child, $level, $child->getName());
         }
+    }
 
-        $key = $dependency['name'] . '@' . $dependency['channel'];
-        if (!$this->_output->isQuiet()) {
-            $this->_output->yellow(
-                Horde_String::pad(
-                    $this->_listLevel($level) . '|_' 
-                    . $dependency['name'], 40
-                )
-                . Horde_String::pad(' [' . $dependency['channel'] . ']', 20)
-                . ' (EXTERNAL) ***STOP***'
-            );
+    /**
+     * List an external package as dependency.
+     *
+     * @param Components_Pear_Dependencies $dependencies The package dependencies.
+     * @param int                          $level        The current list level.
+     *
+     * @return NULL
+     */
+    private function _listExternalPackages(
+        Components_Pear_Dependencies $dependencies,
+        $level
+    ) {
+        if ($this->_output->isVerbose()) {
+            $list = $dependencies->listAllExternalDependencies();
         } else {
-            $this->_quiet_list[$key] = array(
-                'channel' => $dependency['channel'],
-                'name' => $dependency['name'],
-                'color' => 'yellow'
-            );
+            $list = $dependencies->listRequiredExternalDependencies();
+        }
+        foreach ($list as $dependency) {
+            if ($dependency->isPearBase()) {
+                // Showing PEAR does not make much sense.
+                continue;
+            }
+
+            if (!$this->_output->isQuiet()) {
+                $this->_output->yellow(
+                    Horde_String::pad(
+                        $this->_listLevel($level) . '|_' 
+                        . $dependency->name(), 40
+                    )
+                    . Horde_String::pad(' [' . $dependency->channelOrType() . ']', 20)
+                    . ' (EXTERNAL) ***STOP***'
+                );
+            } else {
+                $this->_quiet_list[$dependency->key()] = array(
+                    'channel' => $dependency->channelOrType(),
+                    'name' => $dependency->name(),
+                    'color' => 'yellow'
+                );
+            }
         }
     }
 
index f902d14..65b3e24 100644 (file)
@@ -73,16 +73,20 @@ class Components_Helper_Tree
     /**
      * Install the tree of packages into the specified environment.
      *
-     * @param string $package_file Path to the package file representing the element
-     *                             at the root of the dependency tree.
+     * @param string           $package_file Path to the package file representing the element
+     *                                       at the root of the dependency tree.
+     * @param Component_Output $output       The output handler.
+     * @param array            $options      Options for this installation.
      *
      * @return NULL
      */
-    public function installTreeInEnvironment($package_file) {
-        $this->_getHordeChildElement($package_file)
-            ->installInTree(
-                new Components_Helper_InstallationRun($this->_environment)
-            );
+    public function installTreeInEnvironment(
+        $package_file,
+        Components_Output $output,
+        array $options)
+    {
+        $run = new Components_Helper_InstallationRun($this->_environment, $this, $output, $options);
+        $run->install($this->_getHordeChildElement($package_file));
     }
 
     /**
@@ -99,9 +103,8 @@ class Components_Helper_Tree
         $package_file,
         Components_Output $output
     ) {
-        $run = new Components_Helper_ListRun($output);
-        $this->_getHordeChildElement($package_file)
-            ->listDependencies($run, 0);
+        $run = new Components_Helper_ListRun($output, $this);
+        $run->listTree($this->_getHordeChildElement($package_file));
         $run->finish();
     }
 
@@ -110,22 +113,21 @@ class Components_Helper_Tree
      *
      * @param array $dependencies The dependencies of a package to be
      *                            transformed in elements.
-     * @return array The list of children elements.
+     * @return array The list of children.
      */
     public function getChildren(array $dependencies)
     {
         $children = array();
         foreach ($dependencies as $dependency) {
             $package_file = $this->_root_path . DIRECTORY_SEPARATOR
-                 . $dependency['name'] . DIRECTORY_SEPARATOR . 'package.xml';
+                . $dependency->name() . DIRECTORY_SEPARATOR . 'package.xml';
             if (!file_exists($package_file)) {
                 $package_file = $this->_root_path . DIRECTORY_SEPARATOR
-                    . 'framework' . DIRECTORY_SEPARATOR . $dependency['name']
+                    . 'framework' . DIRECTORY_SEPARATOR . $dependency->name()
                     . DIRECTORY_SEPARATOR . 'package.xml';
             }
-            $children[] = $this->_getHordeChildElement(
-                $package_file,
-                isset($dependency['optional']) && $dependency['optional'] == 'no'
+            $children[$dependency->key()] = $this->_getHordeChildElement(
+                $package_file
             );
         }
         return $children;
@@ -136,18 +138,13 @@ class Components_Helper_Tree
      *
      * @param string  $package_file Path to the package file representing the
      *                              element at the root of the dependency tree.
-     * @param boolean $required     Is this a required element?
-     * @return NULL
+     *
+     * @return Components_Pear_Package The child package.
      */
-    private function _getHordeChildElement($package_file, $required = true)
+    private function _getHordeChildElement($package_file)
     {
-        return new Components_Helper_Tree_Element(
-            $this->_factory->createPackageForEnvironment(
-                $package_file, $this->_environment
-            ),
-            $package_file,
-            $required,
-            $this
+        return $this->_factory->createPackageForEnvironment(
+            $package_file, $this->_environment
         );
     }
 
diff --git a/components/lib/Components/Helper/Tree/Element.php b/components/lib/Components/Helper/Tree/Element.php
deleted file mode 100644 (file)
index 650cef9..0000000
+++ /dev/null
@@ -1,149 +0,0 @@
-<?php
-/**
- * Components_Helper_Tree_Element:: provides utility methods for a single
- * element of the tree.
- *
- * PHP version 5
- *
- * @category Horde
- * @package  Components
- * @author   Gunnar Wrobel <wrobel@pardus.de>
- * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
- * @link     http://pear.horde.org/index.php?package=Components
- */
-
-/**
- * Components_Helper_Tree_Element:: provides utility methods for a single
- * element of the tree.
- *
- * Copyright 2010 The Horde Project (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (LGPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
- *
- * @category Horde
- * @package  Components
- * @author   Gunnar Wrobel <wrobel@pardus.de>
- * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
- * @link     http://pear.horde.org/index.php?package=Components
- */
-class Components_Helper_Tree_Element
-{
-    /**
-     * The package represented by this element.
-     *
-     * @var Components_Pear_Package
-     */
-    private $_package;
-
-    /**
-     * The path to the package file defining this element.
-     *
-     * @var string
-     */
-    private $_package_file;
-
-    /**
-     * Is this a required element?
-     *
-     * @var boolean
-     */
-    private $_required;
-
-    /**
-     * The parent tree for this child.
-     *
-     * @var Components_Helper_Tree
-     */
-    private $_tree;
-
-    /**
-     * Constructor.
-     *
-     * @param Components_Pear_Package $package      The root of the dependency tree.
-     * @param string                  $package_file The path to the package file.
-     * @param boolean                 $required     Is this a required element?
-     * @param Components_Helper_Tree  $tree         The parent tree for this child.
-     */
-    public function __construct(
-        Components_Pear_Package $package,
-        $package_file,
-        $required,
-        Components_Helper_Tree $tree
-    ) {
-        $this->_package = $package;
-        $this->_package_file = $package_file;
-        $this->_required = $required;
-        $this->_tree = $tree;
-    }
-
-    /**
-     * Install the tree of packages into the environment.
-     *
-     * @param Components_Helper_InstallationRun $run     The current installation run.
-     * @param array                             $visited The packages already visited.
-     * @param string                            $reason  Optional reason for adding the package.
-     *
-     * @return NULL
-     */
-    public function installInTree(
-        Components_Helper_InstallationRun $run,
-        array $visited = array(),
-        $reason = ''
-    ) {
-        if (in_array($this->_package_file, $visited)) {
-            return;
-        }
-        $visited[] = $this->_package_file;
-        $new_reason = sprintf(' [required by %s]', $this->_package->getName());
-        $run->installChannelsOnce($this->_package->listAllRequiredChannels(), $new_reason);
-        foreach ($this->_package->listAllExternalDependencies() as $dependency) {
-            // Refrain from installing optional pecl packages
-            if (isset($dependency['optional'])
-                && $dependency['optional'] != 'no'
-                && $dependency['channel'] == 'pecl.php.net') {
-                continue;
-            }
-            $run->installExternalPackageOnce(
-                $dependency['channel'], $dependency['name'], $new_reason
-            );
-        }
-        foreach (
-            $this->_tree->getChildren(
-                $this->_package->listAllHordeDependencies()
-            ) as $child
-        ) {
-            $child->installInTree($run, $visited, $new_reason);
-        }
-        $run->installHordePackageOnce($this->_package_file, $reason);
-    }
-
-    /**
-     * List the dependency tree for this package.
-     *
-     * @param Components_Helper_ListRun $run    The current listing run.
-     * @param int                       $level  The current list level.
-     * @param string                    $parent The name of the parent element.
-     *
-     * @return NULL
-     */
-    public function listDependencies(
-        Components_Helper_ListRun $run,
-        $level,
-        $parent = ''
-    ) {
-        if ($run->listHordePackage($this->_package, $level, $parent, $this->_required)) {
-            foreach ($this->_package->listAllExternalDependencies() as $dependency) {
-                $run->listExternalPackage($dependency, $level + 1);
-            }
-            foreach (
-                $this->_tree->getChildren(
-                    $this->_package->listAllHordeDependencies()
-                ) as $child
-            ) {
-                $child->listDependencies($run, $level + 1, $this->_package->getName());
-            }
-        }
-    }
-
-}
\ No newline at end of file
index 9b6f06e..a8e4af1 100644 (file)
@@ -47,7 +47,7 @@ extends Components_Module_Base
      */
     public function getOptionGroupDescription()
     {
-        return 'This module installs a Horde element including its dependencies.';
+        return 'This module installs a Horde component including its dependencies.';
     }
 
     /**
@@ -82,6 +82,34 @@ extends Components_Module_Base
                     'help'   => 'Location of static channel XML descriptions. These files need to be named CHANNEL.channel.xml (e.g. pear.php.net.channel.xml). Specifying this path allows you to avoid accessing the network for installing new channels. If this is not specified but SOURCEPATH is given then SOURCEPATH will be checked for such channel XML files.'
                 )
             ),
+            new Horde_Argv_Option(
+                '-I',
+                '--include',
+                array(
+                    'action' => 'store',
+                    'help'   => 'The list of optional dependencies that should be included in the installation. You can either specify packages by name (e.g. PEAR), by a combination of channel and name (e.g. pear.php.net/PEAR), a channel name (e.g. channel:pear.php.net), or all packages by the special keyword ALL. Several entries need to be separated by ",". The default for this option is "ALL".',
+                    'default' => 'ALL',
+                    'dest' => 'include',
+                )
+            ),
+            new Horde_Argv_Option(
+                '-E',
+                '--exclude',
+                array(
+                    'action' => 'store',
+                    'help'   => 'The list of optional dependencies that should be excluded during the installation. You can either specify packages by name (e.g. PEAR), by a combination of channel and name (e.g. pear.php.net/PEAR), a channel name (e.g. channel:pear.php.net), or all packages by the special keyword ALL. Several entries need to be separated by ",". The default for this option is "channel:pecl.php.net".',
+                    'default' => 'channel:pecl.php.net',
+                    'dest' => 'exclude',
+                )
+            ),
+            new Horde_Argv_Option(
+                '-P',
+                '--pretend',
+                array(
+                    'action' => 'store_true',
+                    'help'   => 'Just indicate what would be installed.',
+                )
+            ),
         );
     }
 
diff --git a/components/lib/Components/Pear/Dependencies.php b/components/lib/Components/Pear/Dependencies.php
new file mode 100644 (file)
index 0000000..d134ad4
--- /dev/null
@@ -0,0 +1,220 @@
+<?php
+/**
+ * Components_Pear_Dependencies:: provides dependency handling mechanisms.
+ *
+ * PHP version 5
+ *
+ * @category Horde
+ * @package  Components
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Components
+ */
+
+/**
+ * Components_Pear_Dependencies:: provides dependency handling mechanisms.
+ *
+ * Copyright 2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @category Horde
+ * @package  Components
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Components
+ */
+class Components_Pear_Dependencies
+{
+    /**
+     * The package.
+     *
+     * @param Components_Pear_Package
+     */
+    private $_package;
+
+    /**
+     * This helper will handle the dependencies for the package provided here.
+     *
+     * @param Components_Pear_Package
+     *
+     * @return NULL
+     */
+    public function setPackage(Components_Pear_Package $package)
+    {
+        $this->_package = $package;
+    }
+
+    /**
+     * Return the PEAR package for this package.
+     *
+     * @return Components_Pear_Package
+     */
+    public function getPackage()
+    {
+        if ($this->_package === null) {
+            throw new Component_Exception('You need to set the package first!');
+        }
+        return $this->_package;
+    }
+
+    /**
+     * Return all channels required for the package and its dependencies.
+     *
+     * @return array The list of channels.
+     */
+    public function listAllChannels()
+    {
+        $channel = array();
+        foreach ($this->_getDependencies() as $dependency) {
+            if (isset($dependency['channel'])) {
+                $channel[] = $dependency['channel'];
+            }
+        }
+        $channel[] = $this->getPackage()->getChannel();
+        return array_unique($channel);
+    }    
+
+    /**
+     * Return all dependencies required for this package.
+     *
+     * @return array The list of required dependencies.
+     */
+    public function listAllRequiredDependencies()
+    {
+        $dependencies = array();
+        foreach ($this->_getDependencies() as $dependency) {
+            $d = new Components_Pear_Dependency($dependency);
+            if ($d->isRequired()) {
+                $dependencies[$d->key()] = $d;
+            }
+        }
+        return $dependencies;
+    }
+
+    /**
+     * Return all Horde dependencies required for this package.
+     *
+     * @return array The list of Horde dependencies.
+     */
+    public function listAllHordeDependencies()
+    {
+        $dependencies = array();
+        foreach ($this->_getDependencies() as $dependency) {
+            $d = new Components_Pear_Dependency($dependency);
+            if ($d->isHorde()) {
+                $dependencies[$d->key()] = $d;
+            }
+        }
+        return $dependencies;
+    }    
+
+    /**
+     * Return the Horde dependencies required for this package.
+     *
+     * @param string $include Optional dependencies to include.
+     * @param string $exclude Optional dependencies to exclude.
+     *
+     * @return array The list of Horde dependencies.
+     */
+    public function listHordeDependencies($include, $exclude)
+    {
+        $dependencies = array();
+        foreach ($this->_getDependencies() as $dependency) {
+            $d = new Components_Pear_Dependency($dependency);
+            if ($d->isHorde()
+                && ($d->isRequired()
+                    || ($d->matches($include) && !$d->matches($exclude))
+                )
+            ) {
+                $dependencies[$d->key()] = $d;
+            }
+        }
+        return $dependencies;
+    }    
+
+    /**
+     * Return the Horde dependencies absolutely required for this package.
+     *
+     * @return array The list of Horde dependencies.
+     */
+    public function listRequiredHordeDependencies()
+    {
+        $dependencies = array();
+        foreach ($this->_getDependencies() as $dependency) {
+            $d = new Components_Pear_Dependency($dependency);
+            if ($d->isHorde() && $d->isRequired()) {
+                $dependencies[$d->key()] = $d;
+            }
+        }
+        return $dependencies;
+    }    
+
+    /**
+     * Return all external non Horde package dependencies required for this package.
+     *
+     * @return array The list of external dependencies.
+     */
+    public function listAllExternalDependencies()
+    {
+        $dependencies = array();
+        foreach ($this->_getDependencies() as $dependency) {
+            $d = new Components_Pear_Dependency($dependency);
+            if (!$d->isHorde() && !$d->isPhp()) {
+                $dependencies[$d->key()] = $d;
+            }
+        }
+        return $dependencies;
+    }
+
+    /**
+     * Return the external non Horde package dependencies for this package.
+     *
+     * @param string $include Optional dependencies to include.
+     * @param string $exclude Optional dependencies to exclude.
+     *
+     * @return array The list of external dependencies.
+     */
+    public function listExternalDependencies($include, $exclude)
+    {
+        $dependencies = array();
+        foreach ($this->_getDependencies() as $dependency) {
+            $d = new Components_Pear_Dependency($dependency);
+            if (!$d->isHorde() && !$d->isPhp() && $d->isPackage()
+                && ($d->isRequired()
+                    || ($d->matches($include) && !$d->matches($exclude))
+                )
+            ) {
+                $dependencies[$d->key()] = $d;
+            }
+        }
+        return $dependencies;
+    }
+
+    /**
+     * Return the required external dependencies.
+     *
+     * @return array The list of external dependencies.
+     */
+    public function listRequiredExternalDependencies()
+    {
+        $dependencies = array();
+        foreach ($this->_getDependencies() as $dependency) {
+            $d = new Components_Pear_Dependency($dependency);
+            if (!$d->isHorde() && !$d->isPhp() && $d->isRequired()) {
+                $dependencies[$d->key()] = $d;
+            }
+        }
+        return $dependencies;
+    }
+
+    private function _getDependencies()
+    {
+        $dependencies = $this->getPackage()->getDependencies();
+        if (empty($dependencies)) {
+            return array();
+        }
+        return $dependencies;
+    }
+}
\ No newline at end of file
diff --git a/components/lib/Components/Pear/Dependency.php b/components/lib/Components/Pear/Dependency.php
new file mode 100644 (file)
index 0000000..5e6c696
--- /dev/null
@@ -0,0 +1,249 @@
+<?php
+/**
+ * Components_Pear_Dependency:: wraps PEAR dependency information.
+ *
+ * PHP version 5
+ *
+ * @category Horde
+ * @package  Components
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Components
+ */
+
+/**
+ * Components_Pear_Dependency:: wraps PEAR dependency information.
+ *
+ * Copyright 2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @category Horde
+ * @package  Components
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Components
+ */
+class Components_Pear_Dependency
+{
+    /**
+     * The name of the dependency.
+     *
+     * @var string
+     */
+    private $_name = '';
+
+    /**
+     * The channel of the dependency.
+     *
+     * @var string
+     */
+    private $_channel = '';
+
+    /**
+     * The type of the dependency.
+     *
+     * @var string
+     */
+    private $_type = '';
+
+    /**
+     * Indicates if this is an optional dependency.
+     *
+     * @var boolean
+     */
+    private $_optional = true;
+
+    /**
+     * Indicates if this is a package dependency.
+     *
+     * @var boolean
+     */
+    private $_package = false;
+
+    /**
+     * Constructor.
+     *
+     * @param array $dependency The dependency information.
+     */
+    public function __construct($dependency)
+    {
+        if (isset($dependency['name'])) {
+            $this->_name = $dependency['name'];
+        }
+        if (isset($dependency['channel'])) {
+            $this->_channel = $dependency['channel'];
+        }
+        if (isset($dependency['optional'])
+            && $dependency['optional'] == 'no') {
+            $this->_optional = false;
+        }
+        if (isset($dependency['type'])) {
+            $this->_type = $dependency['type'];
+        }
+        if (isset($dependency['type'])
+            && $dependency['type'] == 'pkg') {
+            $this->_package = true;
+        }
+    }
+
+    /**
+     * Is the dependency required?
+     *
+     * @return boolen True if the dependency is required.
+     */
+    public function isRequired()
+    {
+        if (!$this->_package) {
+            return false;
+        }
+        return !$this->_optional;
+    }
+
+    /**
+     * Is this a pacakge dependency?
+     *
+     * @return boolen True if the dependency is a package.
+     */
+    public function isPackage()
+    {
+        return $this->_package;
+    }
+
+    /**
+     * Is the dependency a Horde dependency?
+     *
+     * @return boolen True if it is a Horde dependency.
+     */
+    public function isHorde()
+    {
+        if (empty($this->_channel)) {
+            return false;
+        }
+        if ($this->_channel != 'pear.horde.org') {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Is this the PHP dependency?
+     *
+     * @return boolen True if it is the PHP dependency.
+     */
+    public function isPhp()
+    {
+        if ($this->_type != 'php') {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Is this a PHP extension dependency?
+     *
+     * @return boolen True if it is a PHP extension dependency.
+     */
+    public function isExtension()
+    {
+        if ($this->_type != 'ext') {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Is the dependency the PEAR base package?
+     *
+     * @return boolen True if it is the PEAR base package.
+     */
+    public function isPearBase()
+    {
+        if ($this->_name == 'PEAR' && $this->_channel == 'pear.php.net') {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Does the dependency match the given selector?
+     *
+     * @param string $selector The selector.
+     *
+     * @return boolen True if the dependency matches.
+     */
+    public function matches($selector)
+    {
+        $selectors = split(',', $selector);
+        if (in_array('ALL', $selectors)) {
+            return true;
+        }
+        foreach ($selectors as $selector) {
+            if (empty($selector)) {
+                continue;
+            }
+            if (strpos($selector, '/') !== false) {
+                list($channel, $name) = split('/', $selector, 2);
+                if ($this->_channel == $channel && $this->_name == $name) {
+                    return true;
+                }
+                continue;
+            }
+            if (substr($selector, 0, 8) == 'channel:') {
+                if ($this->_channel == substr($selector, 8)) {
+                    return true;
+                }
+                continue;
+            }
+            if ($this->_name == $selector) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Return the package name for the dependency
+     *
+     * @return string The package name.
+     */
+    public function name()
+    {
+        return $this->_name;
+    }
+
+    /**
+     * Return the package channel for the dependency
+     *
+     * @return string The package channel.
+     */
+    public function channel()
+    {
+        return $this->_channel;
+    }
+
+    /**
+     * Return the package channel or the type description for the dependency.
+     *
+     * @return string The package channel.
+     */
+    public function channelOrType()
+    {
+        if ($this->isExtension()) {
+            return 'PHP Extension';
+        } else {
+            return $this->_channel;
+        }
+    }
+
+    /**
+     * Return the key for the dependency
+     *
+     * @return string The uniqe key for this dependency.
+     */
+    public function key()
+    {
+        return $this->_channel . '/' . $this->_name;
+    }
+}
index ffcbebd..70d525e 100644 (file)
@@ -49,11 +49,12 @@ class Components_Pear_Factory
      *
      * @param string $config_file The path to the configuration file.
      *
-     * @return NULL
+     * @return Components_Pear_InstallLocation The PEAR environment
      */
     public function createInstallLocation($config_file)
     {
         $install_location = $this->_dependencies->createInstance('Components_Pear_InstallLocation');
+        $install_location->setFactory($this);
         $install_location->setLocation(
             dirname($config_file),
             basename($config_file)
@@ -67,15 +68,13 @@ class Components_Pear_Factory
      * @param string                          $package_file The path of the package XML file.
      * @param Components_Pear_InstallLocation $environment  The PEAR environment.
      *
-     * @return NULL
+     * @return Components_Pear_Package The PEAR package.
      */
     public function createPackageForEnvironment(
         $package_file,
         Components_Pear_InstallLocation $environment
     ) {
-        $package = $this->_dependencies->createInstance('Components_Pear_Package');
-        $package->setFactory($this);
-        $package->setEnvironment($environment);
+        $package = $this->_createPackage($environment);
         $package->setPackageXml($package_file);
         return $package;
     }
@@ -86,15 +85,13 @@ class Components_Pear_Factory
      * @param string $package_file The path of the package XML file.
      * @param string $config_file  The path to the configuration file.
      *
-     * @return NULL
+     * @return Components_Pear_Package The PEAR package.
      */
     public function createPackageForInstallLocation($package_file, $config_file)
     {
-        $package = $this->_dependencies->createInstance('Components_Pear_Package');
-        $package->setFactory($this);
-        $package->setEnvironment($this->createInstallLocation($config_file));
-        $package->setPackageXml($package_file);
-        return $package;
+        return $this->createPackageForEnvironment(
+            $package_file, $this->createInstallLocation($config_file)
+        );
     }
 
     /**
@@ -102,14 +99,44 @@ class Components_Pear_Factory
      *
      * @param string $package_file The path of the package XML file.
      *
-     * @return NULL
+     * @return Components_Pear_Package The PEAR package.
      */
     public function createPackageForDefaultLocation($package_file)
     {
+        return $this->createPackageForEnvironment(
+            $package_file, $this->_dependencies->getInstance('Components_Pear_InstallLocation')
+        );
+    }
+
+    /**
+     * Create a package representation for a specific PEAR environment based on a *.tgz archive.
+     *
+     * @param string                          $package_file The path of the package *.tgz file.
+     * @param Components_Pear_InstallLocation $environment  The environment for the package file.
+     *
+     * @return Components_Pear_Package The PEAR package.
+     */
+    public function createTgzPackageForInstallLocation(
+        $package_file,
+        Components_Pear_InstallLocation $environment
+    ) {
+        $package = $this->_createPackage($environment);
+        $package->setPackageTgz($package_file);
+        return $package;
+    }
+
+    /**
+     * Create a generic package representation for a specific PEAR environment.
+     *
+     * @param Components_Pear_InstallLocation $environment  The PEAR environment.
+     *
+     * @return Components_Pear_Package The generic PEAR package.
+     */
+    private function _createPackage(Components_Pear_InstallLocation $environment)
+    {
         $package = $this->_dependencies->createInstance('Components_Pear_Package');
         $package->setFactory($this);
-        $package->setEnvironment($this->_dependencies->getInstance('Components_Pear_InstallLocation'));
-        $package->setPackageXml($package_file);
+        $package->setEnvironment($environment);
         return $package;
     }
 
@@ -125,6 +152,7 @@ class Components_Pear_Factory
     public function createTreeHelper($config_file, $root_path, array $options)
     {
         $environment = $this->_dependencies->createInstance('Components_Pear_InstallLocation');
+        $environment->setFactory($this);
         $environment->setLocation(
             dirname($config_file),
             basename($config_file)
@@ -171,6 +199,25 @@ class Components_Pear_Factory
     }
 
     /**
+     * Return the PEAR Package representation based on a local *.tgz archive.
+     *
+     * @param string                          $package_tgz_path Path to the *.tgz file.
+     * @param Components_Pear_InstallLocation $environment      The PEAR environment.
+     *
+     * @return PEAR_PackageFile
+     */
+    public function getPackageFileFromTgz(
+        $package_tgz_path,
+        Components_Pear_InstallLocation $environment
+    )
+    {
+        $pkg = new PEAR_PackageFile($environment->getPearConfig());
+        return Components_Exception_Pear::catchError(
+            $pkg->fromTgzFile($package_tgz_path, PEAR_VALIDATE_NORMAL)
+        );
+    }
+
+    /**
      * Create a new PEAR Package representation.
      *
      * @param string                          $package_xml_dir Path to the parent directory of the package.xml file.
@@ -271,4 +318,18 @@ class Components_Pear_Factory
             )
         );
     }
+
+    /**
+     * Create a package dependency helper.
+     *
+     * @param Components_Pear_Package $package The package.
+     *
+     * @return Components_Pear_Dependencies The dependency helper.
+     */
+    public function createDependencies(Components_Pear_Package $package)
+    {
+        $dependencies = $this->_dependencies->createInstance('Components_Pear_Dependencies');
+        $dependencies->setPackage($package);
+        return $dependencies;
+    }
 }
\ No newline at end of file
index 511385a..7eca2d6 100644 (file)
@@ -37,6 +37,13 @@ class Components_Pear_InstallLocation
     private $_output;
 
     /**
+     * The factory for PEAR class instances.
+     *
+     * @param Components_Pear_Factory
+     */
+    private $_factory;
+
+    /**
      * The base directory for the PEAR install location.
      *
      * @param string
@@ -69,12 +76,23 @@ class Components_Pear_InstallLocation
      *
      * @param Component_Output $output The output handler.
      */
-    public function __construct(Components_Output $output)
-    {
+    public function __construct(Components_Output $output) {
         $this->_output = $output;
     }
 
     /**
+     * Define the factory that creates our PEAR dependencies.
+     *
+     * @param Components_Pear_Factory
+     *
+     * @return NULL
+     */
+    public function setFactory(Components_Pear_Factory $factory)
+    {
+        $this->_factory = $factory;
+    }
+
+    /**
      * Set the path to the install location.
      *
      * @param string $base_directory The base directory for the PEAR install location.
@@ -352,57 +370,27 @@ class Components_Pear_InstallLocation
     }
 
     /**
-     * Identify any dependencies we need when installing via downloaded packages.
-     *
-     * @param string $channel The channel name for the package.
-     * @param string $package The name of the package of the path of the tarball.
-     *
-     * @return array The added packages.
-     */
-    public function identifyRequiredLocalDependencies($channel, $package)
-    {
-        if ($local = $this->_identifyMatchingLocalPackage($package)) {
-            $dependencies = array();
-            $pkg = new PEAR_PackageFile($this->getPearConfig());
-            $pkg = $pkg->fromTgzFile($local, PEAR_VALIDATE_NORMAL);
-            $deps = $pkg->getDeps();
-            if (empty($deps)) {
-                return array();
-            }
-            foreach ($deps as $dependency) {
-                if ($dependency['type'] != 'pkg') {
-                    continue;
-                }
-                if (isset($dependency['optional']) && $dependency['optional'] == 'no') {
-                    $dependencies[] = $dependency;
-                }
-            }
-            return $dependencies;
-        }
-        return array();
-    }
-
-    /**
-     * Add a package based on a package name or package tarball.
+     * Add an external dependency based on a package name or package tarball.
      *
-     * @param string $channel The channel name for the package.
+     * @param Components_Pear_Dependency $dependency The package dependency.
      * @param string $package The name of the package of the path of the tarball.
      * @param string $reason  Optional reason for adding the package.
      *
      * @return NULL
      */
-    public function addPackageFromPackage($channel, $package, $reason = '')
-    {
+    public function addPackageFromPackage(
+        Components_Pear_Dependency $dependency,
+        $reason = ''
+    ) {
         $installer = $this->getInstallationHandler();
         $this->_output->ok(
             sprintf(
-                'About to add package %s/%s%s',
-                $channel,
-                $package,
+                'About to add external package %s%s',
+                $dependency->key(),
                 $reason
             )
         );
-        if ($local = $this->_identifyMatchingLocalPackage($package)) {
+        if ($local = $this->_identifyMatchingLocalPackage($dependency->name())) {
             ob_start();
             Components_Exception_Pear::catchError(
                 $installer->doInstall(
@@ -417,8 +405,8 @@ class Components_Pear_InstallLocation
         } else {
             $this->_output->warn(
                 sprintf(
-                    'Adding package %s via network.',
-                    $package
+                    'Adding external package %s via network.',
+                    $dependency->key()
                 )
             );
             ob_start();
@@ -426,23 +414,51 @@ class Components_Pear_InstallLocation
                 $installer->doInstall(
                     'install',
                     array(
-                        'channel' => $channel,
+                        'channel' => $dependency->channel(),
                     ),
-                    array($package)
+                    array($dependency->name())
                 )
             );
             $this->_output->pear(ob_get_clean());
         }
         $this->_output->ok(
             sprintf(
-                'Successfully added package %s/%s%s',
-                $channel,
-                $package,
+                'Successfully added external package %s%s',
+                $dependency->key(),
                 $reason
             )
         );
     }
 
+    /**
+     * Identify any dependencies we need when installing via downloaded packages.
+     *
+     * @param Components_Pear_Dependency $dependency The package dependency.
+     * @param string                     $include    Optional dependencies to include.
+     * @param string                     $exclude    Optional dependencies to exclude.
+     *
+     * @return array The added packages.
+     */
+    public function identifyRequiredLocalDependencies(
+        Components_Pear_Dependency $dependency, $include, $exclude
+    ) {
+        if ($local = $this->_identifyMatchingLocalPackage($dependency->name())) {
+            $this->_checkSetup();
+            return $this->_factory
+                ->createTgzPackageForInstallLocation($local, $this)
+                ->getDependencyHelper()
+                ->listExternalDependencies($include, $exclude);
+        }
+        return array();
+    }
+
+    /**
+     * Identify a dependency that is available via a downloaded *.tgz archive.
+     *
+     * @param string $package The package name.
+     *
+     * @return string A path to the local archive if it was found.
+     */
     private function _identifyMatchingLocalPackage($package)
     {
         if (empty($this->_source_directory)) {
@@ -455,4 +471,19 @@ class Components_Pear_InstallLocation
         }
         return false;
     }
+
+    /**
+     * Validate that the required instance parameters are set.
+     *
+     * @return NULL
+     *
+     * @throws Components_Exception In case some settings are missing.
+     */
+    private function _checkSetup()
+    {
+        if ($this->_factory === null) {
+            throw new Components_Exception('You need to set the factory, the environment and the path to the package file first!');
+        }
+    }
+
 }
index c2ec3b8..8eba628 100644 (file)
@@ -56,6 +56,13 @@ class Components_Pear_Package
     private $_package_xml_path;
 
     /**
+     * The path to the package *.tgz file.
+     *
+     * @param string
+     */
+    private $_package_tgz_path;
+
+    /**
      * The package representation.
      *
      * @param PEAR_PackageFile_v2
@@ -129,6 +136,31 @@ class Components_Pear_Package
     }
 
     /**
+     * Return the path to the package.xml.
+     *
+     * @return string
+     */
+    public function getPackageXml()
+    {
+        if ($this->_package_xml_path === null) {
+            throw new Component_Exception('You need to set the package.xml path first!');
+        }
+        return $this->_package_xml_path;
+    }
+
+    /**
+     * Define the package to work on.
+     *
+     * @param string $package_tgz_path Path to the *.tgz file.
+     *
+     * @return NULL
+     */
+    public function setPackageTgz($package_tgz_path)
+    {
+        $this->_package_tgz_path = $package_tgz_path;
+    }
+
+    /**
      * Return the PEAR Package representation.
      *
      * @return PEAR_PackageFile
@@ -137,10 +169,17 @@ class Components_Pear_Package
     {
         $this->_checkSetup();
         if ($this->_package_file === null) {
-            $this->_package_file = $this->_factory->getPackageFile(
-                $this->_package_xml_path,
-                $this->getEnvironment()
-            );
+            if (!empty($this->_package_xml_path)) {
+                $this->_package_file = $this->_factory->getPackageFile(
+                    $this->_package_xml_path,
+                    $this->getEnvironment()
+                );
+            } else {
+                $this->_package_file = $this->_factory->getPackageFileFromTgz(
+                    $this->_package_tgz_path,
+                    $this->getEnvironment()
+                );
+            }
         }
         return $this->_package_file;
     }
@@ -172,7 +211,8 @@ class Components_Pear_Package
     private function _checkSetup()
     {
         if ($this->_environment === null
-            || $this->_package_xml_path === null
+            || ($this->_package_xml_path === null
+                && $this->_package_tgz_path === null)
             || $this->_factory === null) {
             throw new Components_Exception('You need to set the factory, the environment and the path to the package file first!');
         }
@@ -189,6 +229,16 @@ class Components_Pear_Package
     }
 
     /**
+     * Return the channel for the package.
+     *
+     * @return string The package channel.
+     */
+    public function getChannel()
+    {
+        return $this->_getPackageFile()->getChannel();
+    }
+
+    /**
      * Return the description for this package.
      *
      * @return string The package description.
@@ -406,53 +456,25 @@ class Components_Pear_Package
     }    
 
     /**
-     * Return all channels required for this package and its dependencies.
+     * Return the dependencies for the package.
      *
-     * @return array The list of channels.
+     * @return array The list of dependencies.
      */
-    public function listAllRequiredChannels()
+    public function getDependencies()
     {
-        $dependencies = array();
-        foreach ($this->_getPackageFile()->getDeps() as $dependency) {
-            if (isset($dependency['channel'])) {
-                $dependencies[] = $dependency['channel'];
-            }
-        }
-        $dependencies[] = $this->_getPackageFile()->getChannel();
-        return array_unique($dependencies);
-    }    
-
-    /**
-     * Return all channels required for this package and its dependencies.
-     *
-     * @return array The list of channels.
-     */
-    public function listAllExternalDependencies()
-    {
-        $dependencies = array();
-        foreach ($this->_getPackageFile()->getDeps() as $dependency) {
-            if (isset($dependency['channel']) && $dependency['channel'] != 'pear.horde.org') {
-                $dependencies[] = $dependency;
-            }
-        }
-        return $dependencies;
-    }    
+        return $this->_getPackageFile()->getDeps();
+    }
 
     /**
-     * Return all channels required for this package and its dependencies.
+     * Return the dependency helper for the package.
      *
-     * @return array The list of channels.
+     * @return Components_Pear_Dependencies The dependency helper.
      */
-    public function listAllHordeDependencies()
+    public function getDependencyHelper()
     {
-        $dependencies = array();
-        foreach ($this->_getPackageFile()->getDeps() as $dependency) {
-            if (isset($dependency['channel']) && $dependency['channel'] == 'pear.horde.org') {
-                $dependencies[] = $dependency;
-            }
-        }
-        return $dependencies;
-    }    
+        $this->_checkSetup();
+        return $this->_factory->createDependencies($this);
+    }
 
     /**
      * Generate a snapshot of the package using the provided version number.
index 39511d6..16a79c7 100644 (file)
@@ -44,19 +44,29 @@ class Components_Runner_Installer
     private $_factory;
 
     /**
+     * The output handler.
+     *
+     * @param Component_Output
+     */
+    private $_output;
+
+    /**
      * Constructor.
      *
      * @param Components_Config       $config  The configuration for the current
      *                                         job.
      * @param Components_Pear_Factory $factory The factory for PEAR
      *                                         dependencies.
+     * @param Component_Output        $output  The output handler.
      */
     public function __construct(
         Components_Config $config,
-        Components_Pear_Factory $factory
+        Components_Pear_Factory $factory,
+        Components_Output $output
     ) {
         $this->_config = $config;
         $this->_factory = $factory;
+        $this->_output = $output;
     }
 
     public function run()
@@ -73,7 +83,9 @@ class Components_Runner_Installer
             );
         $tree->getEnvironment()->provideChannel('pear.horde.org');
         $tree->installTreeInEnvironment(
-            realpath($arguments[0]) . DIRECTORY_SEPARATOR . 'package.xml'
+            realpath($arguments[0]) . DIRECTORY_SEPARATOR . 'package.xml',
+            $this->_output,
+            $options
         );
     }
 }
index 92855c0..db60448 100644 (file)
@@ -52,7 +52,7 @@ extends Components_StoryTestCase
     {
         $this->given('the default Components setup')
             ->when('calling the package with the list dependencies option and a path to a Horde framework component')
-            ->then('the non-Horde dependencies of the component will be listed')
+            ->then('the non-Horde dependencies of the component will not be listed')
             ->and('the Horde dependencies of the component will be listed');
     }
 
@@ -63,7 +63,7 @@ extends Components_StoryTestCase
     {
         $this->given('the default Components setup')
             ->when('calling the package with the list dependencies option, the nocolor option and a path to a Horde framework component')
-            ->then('the non-Horde dependencies of the component will be listed')
+            ->then('the non-Horde dependencies of the component will not be listed')
             ->and('the Horde dependencies of the component will be listed');
     }
 
@@ -85,7 +85,7 @@ extends Components_StoryTestCase
     {
         $this->given('the default Components setup')
             ->when('calling the package with the quiet list dependencies option and a path to a Horde framework component')
-            ->then('the non-Horde dependencies of the component will be listed')
+            ->then('the non-Horde dependencies of the component will not be listed')
             ->and('the Horde dependencies of the component will be listed');
     }
 }
\ No newline at end of file
index f9600bd..ced13e4 100644 (file)
@@ -48,6 +48,91 @@ extends Components_StoryTestCase
     /**
      * @scenario
      */
+    public function theTheIOptionListsThePackagesToBeInstalledWhenPretendHasBeenSelected()
+    {
+        $this->given('the default Components setup')
+            ->when('calling the package with the install option, the pretend option and a path to a Horde framework component')
+            ->then('the dummy PEAR package will be listed')
+            ->and('the non-Horde dependencies of the component would be installed')
+            ->and('the Horde dependencies of the component would be installed')
+            ->and('the component will be listed');
+    }
+
+    /**
+     * @scenario
+     */
+    public function theTheIOptionAllowsToAvoidIncludingAllOptionalPackages()
+    {
+        $this->given('the default Components setup')
+            ->when('calling the package with the install option, a path to a Horde framework component, and the following include/exclude options', '', '')
+            ->then('the Optional package will not be listed')
+            ->and('the Console_Getopt package will not be listed')
+            ->and('the PECL will package will not be listed');
+    }
+
+    /**
+     * @scenario
+     */
+    public function theTheIOptionAllowsToIncludeSpecificChannels()
+    {
+        $this->given('the default Components setup')
+            ->when('calling the package with the install option, a path to a Horde framework component, and the following include/exclude options', 'channel:pear.php.net,channel:pear.horde.org', '')
+            ->then('the Optional package will be listed')
+            ->and('the Console_Getopt package will be listed')
+            ->and('the PECL will package will not be listed');
+    }
+
+    /**
+     * @scenario
+     */
+    public function theTheIOptionAllowsToIncludeSpecificPackages()
+    {
+        $this->given('the default Components setup')
+            ->when('calling the package with the install option, a path to a Horde framework component, and the following include/exclude options', 'Console_Getopt,Optional', '')
+            ->then('the Optional package will be listed')
+            ->and('the Console_Getopt package will be listed')
+            ->and('the PECL will package will not be listed');
+    }
+
+    /**
+     * @scenario
+     */
+    public function theTheIOptionAllowsToExcludeAllOptionalPackages()
+    {
+        $this->given('the default Components setup')
+            ->when('calling the package with the install option, a path to a Horde framework component, and the following include/exclude options', 'channel:pear.horde.org,channel:pear.php.net', 'ALL')
+            ->then('the Optional package will not be listed')
+            ->and('the Console_Getopt package will not be listed')
+            ->and('the PECL will package will not be listed');
+    }
+
+    /**
+     * @scenario
+     */
+    public function theTheIOptionAllowsToExcludeSpecificChannels()
+    {
+        $this->given('the default Components setup')
+            ->when('calling the package with the install option, a path to a Horde framework component, and the following include/exclude options', 'channel:pear.php.net,channel:pear.horde.org', 'channel:pecl.php.net')
+            ->then('the Optional package will be listed')
+            ->and('the Console_Getopt package will be listed')
+            ->and('the PECL will package will not be listed');
+    }
+
+    /**
+     * @scenario
+     */
+    public function theTheIOptionAllowsToExcludeSpecificPackages()
+    {
+        $this->given('the default Components setup')
+            ->when('calling the package with the install option, a path to a Horde framework component, and the following include/exclude options', 'ALL', 'pecl.php.net/PECL')
+            ->then('the Optional package will be listed')
+            ->and('the Console_Getopt package will be listed')
+            ->and('the PECL will package will not be listed');
+    }
+
+    /**
+     * @scenario
+     */
     public function theTheIOptionInstallsThePackageFromTheCurrentTree()
     {
         $this->given('the default Components setup')
index fde1239..2bafe62 100644 (file)
@@ -196,6 +196,30 @@ extends PHPUnit_Extensions_Story_TestCase
             );
             $world['output'] = $this->_callUnstrictComponents();
             break;
+        case 'calling the package with the install option, the pretend option and a path to a Horde framework component':
+            $_SERVER['argv'] = array(
+                'horde-components',
+                '--channelxmlpath=' . dirname(__FILE__) . '/fixture/channels',
+                '--sourcepath=' . dirname(__FILE__) . '/fixture/packages',
+                '--pretend',
+                '--install=' . $this->_getTemporaryDirectory() . DIRECTORY_SEPARATOR . '.pearrc',
+                dirname(__FILE__) . '/fixture/framework/Install'
+            );
+            $world['output'] = $this->_callUnstrictComponents();
+            break;
+        case 'calling the package with the install option, a path to a Horde framework component, and the following include/exclude options':
+            $_SERVER['argv'] = array(
+                'horde-components',
+                '--channelxmlpath=' . dirname(__FILE__) . '/fixture/channels',
+                '--sourcepath=' . dirname(__FILE__) . '/fixture/packages',
+                '--pretend',
+                '--include=' . $arguments[0],
+                '--exclude=' . $arguments[1],
+                '--install=' . $this->_getTemporaryDirectory() . DIRECTORY_SEPARATOR . '.pearrc',
+                dirname(__FILE__) . '/fixture/framework/Install'
+            );
+            $world['output'] = $this->_callUnstrictComponents();
+            break;
         case 'calling the package with the list dependencies option and a path to a Horde framework component':
             $_SERVER['argv'] = array(
                 'horde-components',
@@ -394,6 +418,70 @@ extends PHPUnit_Extensions_Story_TestCase
                 )
             );
             break;
+        case 'the dummy PEAR package will be listed':
+            $this->assertContains(
+                'Would install external package pear.php.net/PEAR',
+                $world['output']
+            );
+            break;
+        case 'the non-Horde dependencies of the component would be installed':
+            $this->assertContains(
+                'Would install external package pear.php.net/Console_Getopt',
+                $world['output']
+            );
+            break;
+        case 'the PECL will package will be listed':
+            $this->assertContains(
+                'Would install external package pecl.php.net/PECL',
+                $world['output']
+            );
+            break;
+        case 'the PECL will package will not be listed':
+            $this->assertNotContains(
+                'Would install external package pecl.php.net/PECL',
+                $world['output']
+            );
+            break;
+        case 'the Console_Getopt package will be listed':
+            $this->assertContains(
+                'Would install external package pear.php.net/Console_Getopt',
+                $world['output']
+            );
+            break;
+        case 'the Console_Getopt package will not be listed':
+            $this->assertNotContains(
+                'Would install external package pear.php.net/Console_Getopt',
+                $world['output']
+            );
+            break;
+        case 'the Horde dependencies of the component would be installed':
+            $trimmed = strtr($world['output'], array(' ' => '', "\n" => ''));
+            $this->assertRegExp(
+                '#Wouldinstallpackage.*Dependency/package.xml#',
+                $trimmed
+            );
+            break;
+        case 'the Optional package will be listed':
+            $trimmed = strtr($world['output'], array(' ' => '', "\n" => ''));
+            $this->assertRegExp(
+                '#Wouldinstallpackage.*Optional/package.xml#',
+                $trimmed
+            );
+            break;
+        case 'the Optional package will not be listed':
+            $trimmed = strtr($world['output'], array(' ' => '', "\n" => ''));
+            $this->assertNotRegExp(
+                '#Wouldinstallpackage.*Optional/package.xml#',
+                $trimmed
+            );
+            break;
+        case 'the component will be listed':
+            $trimmed = strtr($world['output'], array(' ' => '', "\n" => ''));
+            $this->assertRegExp(
+                '#Wouldinstallpackage.*Install/package.xml#',
+                $trimmed
+            );
+            break;
         case 'the CI configuration will be installed.':
             $this->assertTrue(
                 file_exists(
@@ -440,6 +528,12 @@ extends PHPUnit_Extensions_Story_TestCase
                 $world['output']
             );
             break;
+        case 'the non-Horde dependencies of the component will not be listed':
+            $this->assertNotContains(
+                'Console_Getopt',
+                $world['output']
+            );
+            break;
         case 'the non-Horde dependencies of the component will be listed':
             $this->assertContains(
                 'Console_Getopt',
index 0cf1e60..720ec57 100644 (file)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <package packagerversion="1.9.0" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0 http://pear.php.net/dtd/tasks-1.0.xsd http://pear.php.net/dtd/package-2.0 http://pear.php.net/dtd/package-2.0.xsd">
  <name>Dependency</name>
- <channel>pear.php.net</channel>
+ <channel>pear.horde.org</channel>
  <summary>Test fixture.</summary>
  <description>A dummy package.xml used for testing the Components package.</description>
  <lead>
    <pearinstaller>
     <min>1.7.0</min>
    </pearinstaller>
-   <package>
-    <name>Console_Getopt</name>
-    <channel>pear.php.net</channel>
-   </package>
   </required>
   <optional>
    <package>
-    <name>Install</name>
+    <name>Optional</name>
     <channel>pear.horde.org</channel>
    </package>
+   <package>
+    <name>Console_Getopt</name>
+    <channel>pear.php.net</channel>
+   </package>
   </optional>
  </dependencies>
  <phprelease>
diff --git a/components/test/Components/fixture/framework/Optional/lib/Optional.php b/components/test/Components/fixture/framework/Optional/lib/Optional.php
new file mode 100644 (file)
index 0000000..a814366
--- /dev/null
@@ -0,0 +1 @@
+<?php
\ No newline at end of file
diff --git a/components/test/Components/fixture/framework/Optional/package.xml b/components/test/Components/fixture/framework/Optional/package.xml
new file mode 100644 (file)
index 0000000..9c560aa
--- /dev/null
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<package packagerversion="1.9.0" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0 http://pear.php.net/dtd/tasks-1.0.xsd http://pear.php.net/dtd/package-2.0 http://pear.php.net/dtd/package-2.0.xsd">
+ <name>Optional</name>
+ <channel>pear.horde.org</channel>
+ <summary>Test fixture.</summary>
+ <description>A dummy package.xml used for testing the Components package.</description>
+ <lead>
+  <name>Gunnar Wrobel</name>
+  <user>wrobel</user>
+  <email>p@rdus.de</email>
+  <active>yes</active>
+ </lead>
+ <date>2010-08-22</date>
+ <time>21:12:03</time>
+ <version>
+  <release>0.0.1</release>
+  <api>0.0.1</api>
+ </version>
+ <stability>
+  <release>alpha</release>
+  <api>alpha</api>
+ </stability>
+ <license uri="http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html">LGPL</license>
+ <notes>
+* Initial release
+ </notes>
+ <contents>
+  <dir baseinstalldir="/" name="/">
+   <dir name="lib">
+    <file name="Optional.php" role="php" />
+   </dir> <!-- /lib -->
+  </dir> <!-- / -->
+ </contents>
+ <dependencies>
+  <required>
+   <php>
+    <min>5.0.0</min>
+   </php>
+   <pearinstaller>
+    <min>1.7.0</min>
+   </pearinstaller>
+   <package>
+    <name>Console_Getopt</name>
+    <channel>pear.php.net</channel>
+   </package>
+  </required>
+  <optional>
+   <package>
+    <name>PECL</name>
+    <channel>pecl.php.net</channel>
+   </package>
+  </optional>
+ </dependencies>
+ <phprelease>
+  <filelist>
+   <install as="Optional.php" name="lib/Optional.php" />
+  </filelist>
+ </phprelease>
+ <changelog>
+  <release>
+   <version>
+    <release>0.0.1</release>
+    <api>0.0.1</api>
+   </version>
+   <stability>
+    <release>alpha</release>
+    <api>alpha</api>
+   </stability>
+   <date>2010-08-22</date>
+   <license uri="http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html">LGPL</license>
+   <notes>
+* Initial release
+   </notes>
+  </release>
+ </changelog>
+</package>