From: Gunnar Wrobel Date: Mon, 23 Aug 2010 07:42:38 +0000 (+0200) Subject: Added the Installer module to Horde_Element. It allows to install a PEAR based Horde... X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=b2861a78cba958722b201eccbd1354548b7f625e;p=horde.git Added the Installer module to Horde_Element. It allows to install a PEAR based Horde framework package from the repository tree, including its dependencies. Horde dependencies will also be pulled from the current tree, remote dependencies will be downloaded. This should now allow a continuous integration (CI) setup that tests the PEAR based Horde framework packages including their dependencies. While we will support PEAR based releases for Horde4 I assume from past experiences that the focus will remain on the applications. As those rely on a fully installed framework it will be quite easy to overlook PEAR dependency problems. So I feel it is necessary to include the full installation method via PEAR into the CI in order to spot problems early. The general idea of the Horde_Element package is to provide tools for managing Horde "Elements" which I consider to be PEAR based packages from the framework (and maybe later also the applications). While I'm currently focusing on the basics I think it would make sense to later add methods that allow publishing the elements better than via pear.horde.org. --- diff --git a/framework/Element/lib/Horde/Element/Module/Installer.php b/framework/Element/lib/Horde/Element/Module/Installer.php new file mode 100644 index 000000000..6478c7bf3 --- /dev/null +++ b/framework/Element/lib/Horde/Element/Module/Installer.php @@ -0,0 +1,154 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Element + */ + +/** + * Horde_Element_Module_Installer:: installs a Horde element including + * its dependencies. + * + * 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 Element + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Element + */ +class Horde_Element_Module_Installer +implements Horde_Element_Module +{ + public function getOptionGroupTitle() + { + return 'Installer'; + } + + public function getOptionGroupDescription() + { + return 'This module installs a Horde element including its dependencies.'; + } + + public function getOptionGroupOptions() + { + return array( + new Horde_Argv_Option( + '-i', + '--install', + array( + 'action' => 'store', + 'help' => 'install the element into the specified absolute INSTALL location' + ) + ), + ); + } + + public function handle(Horde_Element_Config $config) + { + $options = $config->getOptions(); + if (!empty($options['install'])) { + $this->run($config); + } + } + + public function run(Horde_Element_Config $config) + { + $options = $config->getOptions(); + + $pear = new PEAR(); + $pear->setErrorHandling(PEAR_ERROR_DIE); + + $pearrc = $options['install'] . DIRECTORY_SEPARATOR . '.pearrc'; + $command_config = new PEAR_Command_Config(new PEAR_Frontend_CLI(), new stdClass); + $command_config->doConfigCreate( + 'config-create', array(), array($options['install'], $pearrc) + ); + + $pear_config = new PEAR_Config($pearrc); + $GLOBALS['_PEAR_Config_instance'] = $pear_config; + + $channel = new PEAR_Command_Channels( + new PEAR_Frontend_CLI(), + $pear_config + ); + $channel->doDiscover('channel-discover', array(), array('pear.horde.org')); + $channel->doDiscover('channel-discover', array(), array('pear.phpunit.de')); + + $installer = new PEAR_Command_Install( + new PEAR_Frontend_CLI(), + $pear_config + ); + + $arguments = $config->getArguments(); + $element = basename(realpath($arguments[0])); + $root_path = dirname(realpath($arguments[0])); + + $this->_installHordeDependency( + $installer, + $pear_config, + $root_path, + $element + ); + } + + /** + * Install a Horde dependency from the current tree (the framework). + * + * @param PEAR_Command_Install $installer Installs the dependency. + * @param PEAR_Config $pear_config The configuration of the PEAR + * environment in which the + * dependency will be installed. + * @param string $root_path Root path to the Horde framework. + * @param string $dependency Package name of the dependency. + */ + private function _installHordeDependency( + PEAR_Command_Install $installer, + PEAR_Config $pear_config, + $root_path, + $dependency + ) { + $package_file = $root_path . DIRECTORY_SEPARATOR + . $dependency . DIRECTORY_SEPARATOR . 'package.xml'; + + $parser = new PEAR_PackageFile_Parser_v2(); + $parser->setConfig($pear_config); + $pkg = $parser->parse(file_get_contents($package_file), $package_file); + + $dependencies = $pkg->getDeps(); + foreach ($dependencies as $dependency) { + if (isset($dependency['channel']) && $dependency['channel'] != 'pear.horde.org') { + $installer->doInstall( + 'install', + array( + 'force' => true, + 'channel' => $dependency['channel'], + ), + array($dependency['name']) + ); + } else if (isset($dependency['channel'])) { + $this->_installHordeDependency( + $installer, + $pear_config, + $root_path, + $dependency['name'] + ); + } + } + $installer->doInstall( + 'install', + array('nodeps' => true), + array($package_file) + ); + } +} diff --git a/framework/Element/lib/Horde/Element/Module/PearPackageXml.php b/framework/Element/lib/Horde/Element/Module/PearPackageXml.php index e59b0af62..04b95b322 100644 --- a/framework/Element/lib/Horde/Element/Module/PearPackageXml.php +++ b/framework/Element/lib/Horde/Element/Module/PearPackageXml.php @@ -102,6 +102,9 @@ implements Horde_Element_Module ) ); + if ($package instanceOf PEAR_Error) { + throw new Horde_Element_Exception($package->getMessage()); + } $package->generateContents(); /** diff --git a/framework/Element/lib/Horde/Element/Modules.php b/framework/Element/lib/Horde/Element/Modules.php index 5633b7377..9c188f4bb 100644 --- a/framework/Element/lib/Horde/Element/Modules.php +++ b/framework/Element/lib/Horde/Element/Modules.php @@ -57,9 +57,6 @@ implements Iterator, Countable foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($module_directory)) as $file) { if ($file->isFile() && preg_match('/.php$/', $file->getFilename())) { $class = $base . preg_replace("/^(.*)\.php/", '\\1', $file->getFilename()); - if (!class_exists($class)) { - require $file->getPathname(); - } $this->_modules[$class] = new $class(); } } diff --git a/framework/Element/package.xml b/framework/Element/package.xml index aecbe7af9..22aaea7e5 100644 --- a/framework/Element/package.xml +++ b/framework/Element/package.xml @@ -24,8 +24,8 @@ jan@horde.org yes - 2010-07-05 - + 2010-08-22 + 0.0.1 0.0.1 @@ -47,6 +47,7 @@ + @@ -84,12 +85,16 @@ - 5.0.0 + 5.2.1 1.7.0 + PEAR_PackageFileManager2 + pear.php.net + + Autoloader pear.horde.org @@ -115,6 +120,7 @@ + @@ -135,7 +141,7 @@ alpha alpha - 2010-07-05 + 2010-08-22 LGPL * Initial release diff --git a/framework/Element/test/Horde/Element/Integration/ElementTest.php b/framework/Element/test/Horde/Element/Integration/ElementTest.php index b9e83826a..8c667df68 100644 --- a/framework/Element/test/Horde/Element/Integration/ElementTest.php +++ b/framework/Element/test/Horde/Element/Integration/ElementTest.php @@ -64,4 +64,48 @@ extends Horde_Element_StoryTestCase ->when('calling the package with the help option') ->then('the help will contain the "u" option.'); } + + /** + * @scenario + */ + public function theThePOptionProvidesAnUpdatedPackageXml() + { + $this->given('the default Element setup') + ->when('calling the package with the packagexml option and a Horde element') + ->then('the new package.xml of the Horde element will be printed.'); + } + + /** + * @todo Test (and fix) the reactions to three more scenarios: + * - invalid XML in the package.xml (e.g. tag missing) + * - empty file list + * - file list with just one entry. + * + * All three scenarios yield errors which are still hard to + * understand. + */ + + /** + * @scenario + */ + public function theInstallerModuleAddsTheIOptionInTheHelpOutput() + { + $this->given('the default Element setup') + ->when('calling the package with the help option') + ->then('the help will contain the "i" option.'); + } + + /** + * @scenario + */ + public function theTheIOptionInstallsThePackageFromTheCurrentTree() + { + $this->given('the default Element setup') + ->when('calling the package with the install option and a Horde element') + ->then('a new PEAR configuration file will be installed') + ->and('the PEAR package will be installed') + ->and('the non-Horde dependencies of the Horde element will get installed from the network.') + ->and('the Horde dependencies of the Horde element will get installed from the current tree.') + ->and('the Horde element will be installed'); + } } \ No newline at end of file diff --git a/framework/Element/test/Horde/Element/StoryTestCase.php b/framework/Element/test/Horde/Element/StoryTestCase.php index 7b41e0a8c..9059466a2 100644 --- a/framework/Element/test/Horde/Element/StoryTestCase.php +++ b/framework/Element/test/Horde/Element/StoryTestCase.php @@ -30,6 +30,13 @@ class Horde_Element_StoryTestCase extends PHPUnit_Extensions_Story_TestCase { + public function tearDown() + { + if (!empty($this->_temp_dir)) { + $this->_rrmdir($this->_temp_dir); + } + } + /** * Handle a "given" step. * @@ -65,12 +72,42 @@ extends PHPUnit_Extensions_Story_TestCase $_SERVER['argv'] = array( 'horde-element', '--help', - dirname(__FILE__) . '/fixture' + dirname(__FILE__) . '/fixture/empty' + ); + ob_start(); + $parameters = array(); + $parameters['cli']['parser']['class'] = 'Horde_Element_Stub_Parser'; + Horde_Element::main($parameters); + $world['output'] = ob_get_contents(); + ob_end_clean(); + break; + case 'calling the package with the packagexml option and a Horde element': + $_SERVER['argv'] = array( + 'horde-element', + '--packagexml', + dirname(__FILE__) . '/fixture/simple' + ); + ob_start(); + $parameters = array(); + $parameters['cli']['parser']['class'] = 'Horde_Element_Stub_Parser'; + $old_errorreporting = error_reporting(E_ALL & ~E_STRICT); + Horde_Element::main($parameters); + error_reporting($old_errorreporting); + $world['output'] = ob_get_contents(); + ob_end_clean(); + break; + case 'calling the package with the install option and a Horde element': + $_SERVER['argv'] = array( + 'horde-element', + '--install=' . $this->_getTemporaryDirectory(), + dirname(__FILE__) . '/../../../' ); ob_start(); $parameters = array(); $parameters['cli']['parser']['class'] = 'Horde_Element_Stub_Parser'; + $old_errorreporting = error_reporting(E_ALL & ~E_STRICT); Horde_Element::main($parameters); + error_reporting($old_errorreporting); $world['output'] = ob_get_contents(); ob_end_clean(); break; @@ -109,9 +146,95 @@ extends PHPUnit_Extensions_Story_TestCase $world['output'] ); break; + case 'the help will contain the "i" option.': + $this->assertRegExp( + '/-i\s*INSTALL,\s*--install=INSTALL/', + $world['output'] + ); + break; + case 'the new package.xml of the Horde element will be printed.': + $this->assertRegExp( + '//', + $world['output'] + ); + break; + case 'a new PEAR configuration file will be installed': + $this->assertTrue( + file_exists($this->_temp_dir . DIRECTORY_SEPARATOR . '.pearrc') + ); + break; + case 'the PEAR package will be installed': + $this->assertTrue( + file_exists( + $this->_temp_dir . DIRECTORY_SEPARATOR + . 'pear' . DIRECTORY_SEPARATOR + . 'php' . DIRECTORY_SEPARATOR + . 'PEAR.php' + ) + ); + break; + case 'the non-Horde dependencies of the Horde element will get installed from the network.': + var_dump($world['output']); + $this->assertTrue( + file_exists( + $this->_temp_dir . DIRECTORY_SEPARATOR + . 'pear' . DIRECTORY_SEPARATOR + . 'php' . DIRECTORY_SEPARATOR + . 'PEAR' . DIRECTORY_SEPARATOR + . 'PackageFileManager2.php' + ) + ); + break; + case 'the Horde dependencies of the Horde element will get installed from the current tree.': + $this->assertTrue( + file_exists( + $this->_temp_dir . DIRECTORY_SEPARATOR + . 'pear' . DIRECTORY_SEPARATOR + . 'php' . DIRECTORY_SEPARATOR + . 'Horde' . DIRECTORY_SEPARATOR + . 'Autoloader.php' + ) + ); + break; + case 'the Horde element will be installed': + $this->assertTrue( + file_exists( + $this->_temp_dir . DIRECTORY_SEPARATOR + . 'pear' . DIRECTORY_SEPARATOR + . 'php' . DIRECTORY_SEPARATOR + . 'Horde' . DIRECTORY_SEPARATOR + . 'Element.php' + ) + ); + break; default: return $this->notImplemented($action); } } + private function _getTemporaryDirectory() + { + $this->_temp_dir = sys_get_temp_dir() . DIRECTORY_SEPARATOR + . 'Horde_Element_' . mt_rand(); + mkdir($this->_temp_dir); + return $this->_temp_dir; + } + + private function _rrmdir($dir) + { + if (is_dir($dir)) { + $objects = scandir($dir); + foreach ($objects as $object) { + if ($object != '.' && $object != '..') { + if (filetype($dir . DIRECTORY_SEPARATOR . $object) == 'dir') { + $this->_rrmdir($dir . DIRECTORY_SEPARATOR . $object); + } else { + unlink($dir . DIRECTORY_SEPARATOR . $object); + } + } + } + reset($objects); + rmdir($dir); + } + } } \ No newline at end of file diff --git a/framework/Element/test/Horde/Element/fixture/empty/package.xml b/framework/Element/test/Horde/Element/fixture/empty/package.xml new file mode 100644 index 000000000..e69de29bb diff --git a/framework/Element/test/Horde/Element/fixture/package.xml b/framework/Element/test/Horde/Element/fixture/package.xml deleted file mode 100644 index e69de29bb..000000000 diff --git a/framework/Element/test/Horde/Element/fixture/simple/lib/New.php b/framework/Element/test/Horde/Element/fixture/simple/lib/New.php new file mode 100644 index 000000000..a81436628 --- /dev/null +++ b/framework/Element/test/Horde/Element/fixture/simple/lib/New.php @@ -0,0 +1 @@ + + + Fixture + pear.horde.org + Test fixture. + A dummy package.xml used for testing the Horde_Element package. + + Gunnar Wrobel + wrobel + p@rdus.de + yes + + 2010-08-22 + + + 0.0.1 + 0.0.1 + + + alpha + alpha + + LGPL + +* Initial release + + + + + + + + + + + + 5.0.0 + + + 1.7.0 + + + + + + + + + + + + 0.0.1 + 0.0.1 + + + alpha + alpha + + 2010-08-22 + LGPL + +* Initial release + + + +