From: Greg Beaver Date: Sun, 30 Nov 2003 05:39:02 +0000 (+0000) Subject: experimental code shuffling, moved download() into a new class, PEAR_Downloader. X-Git-Tag: php-5.0.0b3RC1~495 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=c7c5d7689d633508fde84ccd12327cc2a1b16901;p=php experimental code shuffling, moved download() into a new class, PEAR_Downloader. Reasons: - 7 parameters for a method with lots of pass by reference implies the need to save state, i.e. use an object - cleaner code. download() can be easily split into 4 separate but related methods - Installer.php is now exclusively used for installation - future extensibility: switching to channels or local package repositories from using PEAR_Remote is easy. Simply extend the API for PEAR_Downloader: no changes needed to the installer at all to install for testing purposes, use pear upgrade --force package-PEAR-new-Downloader.xml to uninstall, use pear upgrade --force package-PEAR.xml --- diff --git a/pear/PEAR/Command/Install-using-downloader.php b/pear/PEAR/Command/Install-using-downloader.php new file mode 100644 index 0000000000..06cde17846 --- /dev/null +++ b/pear/PEAR/Command/Install-using-downloader.php @@ -0,0 +1,535 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ + +require_once "PEAR/Command/Common.php"; +require_once "PEAR/Installer.php"; + +/** + * PEAR commands for installation or deinstallation/upgrading of + * packages. + * + */ +class PEAR_Command_Install extends PEAR_Command_Common +{ + // {{{ properties + + var $commands = array( + 'install' => array( + 'summary' => 'Install Package', + 'function' => 'doInstall', + 'shortcut' => 'i', + 'options' => array( + 'force' => array( + 'shortopt' => 'f', + 'doc' => 'will overwrite newer installed packages', + ), + 'nodeps' => array( + 'shortopt' => 'n', + 'doc' => 'ignore dependencies, install anyway', + ), + 'register-only' => array( + 'shortopt' => 'r', + 'doc' => 'do not install files, only register the package as installed', + ), + 'soft' => array( + 'shortopt' => 's', + 'doc' => 'soft install, fail silently, or upgrade if already installed', + ), + 'nobuild' => array( + 'shortopt' => 'B', + 'doc' => 'don\'t build C extensions', + ), + 'nocompress' => array( + 'shortopt' => 'Z', + 'doc' => 'request uncompressed files when downloading', + ), + 'installroot' => array( + 'shortopt' => 'R', + 'arg' => 'DIR', + 'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT)', + ), + 'ignore-errors' => array( + 'doc' => 'force install even if there were errors', + ), + 'alldeps' => array( + 'shortopt' => 'a', + 'doc' => 'install all required and optional dependencies', + ), + 'onlyreqdeps' => array( + 'shortopt' => 'o', + 'doc' => 'install all required dependencies', + ), + ), + 'doc' => ' ... +Installs one or more PEAR packages. You can specify a package to +install in four ways: + +"Package-1.0.tgz" : installs from a local file + +"http://example.com/Package-1.0.tgz" : installs from +anywhere on the net. + +"package.xml" : installs the package described in +package.xml. Useful for testing, or for wrapping a PEAR package in +another package manager such as RPM. + +"Package" : queries your configured server +({config master_server}) and downloads the newest package with +the preferred quality/state ({config preferred_state}). + +More than one package may be specified at once. It is ok to mix these +four ways of specifying packages. +'), + 'upgrade' => array( + 'summary' => 'Upgrade Package', + 'function' => 'doInstall', + 'shortcut' => 'up', + 'options' => array( + 'force' => array( + 'shortopt' => 'f', + 'doc' => 'overwrite newer installed packages', + ), + 'nodeps' => array( + 'shortopt' => 'n', + 'doc' => 'ignore dependencies, upgrade anyway', + ), + 'register-only' => array( + 'shortopt' => 'r', + 'doc' => 'do not install files, only register the package as upgraded', + ), + 'nobuild' => array( + 'shortopt' => 'B', + 'doc' => 'don\'t build C extensions', + ), + 'nocompress' => array( + 'shortopt' => 'Z', + 'doc' => 'request uncompressed files when downloading', + ), + 'installroot' => array( + 'shortopt' => 'R', + 'arg' => 'DIR', + 'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT)', + ), + 'ignore-errors' => array( + 'doc' => 'force install even if there were errors', + ), + 'alldeps' => array( + 'shortopt' => 'a', + 'doc' => 'install all required and optional dependencies', + ), + 'onlyreqdeps' => array( + 'shortopt' => 'o', + 'doc' => 'install all required dependencies', + ), + ), + 'doc' => ' ... +Upgrades one or more PEAR packages. See documentation for the +"install" command for ways to specify a package. + +When upgrading, your package will be updated if the provided new +package has a higher version number (use the -f option if you need to +upgrade anyway). + +More than one package may be specified at once. +'), + 'upgrade-all' => array( + 'summary' => 'Upgrade All Packages', + 'function' => 'doInstall', + 'shortcut' => 'ua', + 'options' => array( + 'nodeps' => array( + 'shortopt' => 'n', + 'doc' => 'ignore dependencies, upgrade anyway', + ), + 'register-only' => array( + 'shortopt' => 'r', + 'doc' => 'do not install files, only register the package as upgraded', + ), + 'nobuild' => array( + 'shortopt' => 'B', + 'doc' => 'don\'t build C extensions', + ), + 'nocompress' => array( + 'shortopt' => 'Z', + 'doc' => 'request uncompressed files when downloading', + ), + 'installroot' => array( + 'shortopt' => 'R', + 'arg' => 'DIR', + 'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT)', + ), + 'ignore-errors' => array( + 'doc' => 'force install even if there were errors', + ), + ), + 'doc' => ' +Upgrades all packages that have a newer release available. Upgrades are +done only if there is a release available of the state specified in +"preferred_state" (currently {config preferred_state}), or a state considered +more stable. +'), + 'uninstall' => array( + 'summary' => 'Un-install Package', + 'function' => 'doUninstall', + 'shortcut' => 'un', + 'options' => array( + 'nodeps' => array( + 'shortopt' => 'n', + 'doc' => 'ignore dependencies, uninstall anyway', + ), + 'register-only' => array( + 'shortopt' => 'r', + 'doc' => 'do not remove files, only register the packages as not installed', + ), + 'installroot' => array( + 'shortopt' => 'R', + 'arg' => 'DIR', + 'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT)', + ), + 'ignore-errors' => array( + 'doc' => 'force install even if there were errors', + ), + ), + 'doc' => ' ... +Uninstalls one or more PEAR packages. More than one package may be +specified at once. +'), + 'bundle' => array( + 'summary' => 'Unpacks a Pecl Package', + 'function' => 'doBundle', + 'shortcut' => 'bun', + 'options' => array( + 'destination' => array( + 'shortopt' => 'd', + 'arg' => 'DIR', + 'doc' => 'Optional destination directory for unpacking (defaults to current path or "ext" if exists)', + ), + 'force' => array( + 'shortopt' => 'f', + 'doc' => 'Force the unpacking even if there were errors in the package', + ), + ), + 'doc' => ' +Unpacks a Pecl Package into the selected location. It will download the +package if needed. +'), + 'revert' => array( + 'summary' => 'Revert the Most Recent Upgrade of a Package', + 'function' => 'doRevert', + 'shortcut' => 'r', + 'options' => array( + 'force' => array( + 'shortopt' => 'f', + 'doc' => 'Force the revert even if there were errors', + ), + 'installroot' => array( + 'shortopt' => 'R', + 'arg' => 'DIR', + 'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT)', + ), + 'deps' => array( + 'shortopt' => 'd', + 'doc' => 'revert any dependencies that are incompatible with earlier versions', + ), + ), + 'doc' => ' ... +This command may only be used after a pear upgrade has +been executed. pear revert will revert a package to the previously +installed version number. This command will only work once, and will +fail if any packages depend on the newer version.'), + ); + + // }}} + // {{{ constructor + + /** + * PEAR_Command_Install constructor. + * + * @access public + */ + function PEAR_Command_Install(&$ui, &$config) + { + parent::PEAR_Command_Common($ui, $config); + } + + // }}} + + // {{{ doInstall() + + function doInstall($command, $options, $params) + { + require_once 'PEAR/Downloader.php'; + if (empty($this->installer)) { + $this->installer = &new PEAR_Installer($this->ui); + } + if ($command == 'upgrade') { + $options['upgrade'] = true; + } + if ($command == 'upgrade-all') { + include_once "PEAR/Remote.php"; + $options['upgrade'] = true; + $remote = &new PEAR_Remote($this->config); + $state = $this->config->get('preferred_state'); + if (empty($state) || $state == 'any') { + $latest = $remote->call("package.listLatestReleases"); + } else { + $latest = $remote->call("package.listLatestReleases", $state); + } + if (PEAR::isError($latest)) { + return $latest; + } + $reg = new PEAR_Registry($this->config->get('php_dir')); + $installed = array_flip($reg->listPackages()); + $params = array(); + foreach ($latest as $package => $info) { + $package = strtolower($package); + if (!isset($installed[$package])) { + // skip packages we don't have installed + continue; + } + $inst_version = $reg->packageInfo($package, 'version'); + if (version_compare("$info[version]", "$inst_version", "le")) { + // installed version is up-to-date + continue; + } + $params[] = $package; + $this->ui->outputData(array('data' => "Will upgrade $package"), $command); + } + } + $this->downloader = &new PEAR_Downloader($this->ui, $options, $this->config); + $errors = array(); + $downloaded = array(); + $this->downloader->download($params); + if ($command != 'upgrade-all') { + for ($i = 0; $i < count($params); $i++) { + $params[$i] = $this->downloader->extractDownloadFileName($params[$i], $_tmp); + } + } + $errors = $this->downloader->getErrorMsgs(); + if (count($errors)) { + $err['data'] = array($errors); + $err['headline'] = 'Install Errors'; + $this->ui->outputData($err); + return $this->raiseError("$command failed"); + } + $downloaded = $this->downloader->getDownloadedPackages(); + $this->installer->sortPkgDeps($downloaded); + foreach ($downloaded as $pkg) { + $bn = basename($pkg['file']); + $info = $this->installer->install($pkg['file'], $options, $this->config); + if (is_array($info)) { + if ($this->config->get('verbose') > 0) { + $label = "$info[package] $info[version]"; + $out = array('data' => "$command ok: $label"); + if (isset($info['release_warnings'])) { + $out['release_warnings'] = $info['release_warnings']; + } + $this->ui->outputData($out, $command); + } + } else { + return $this->raiseError("$command failed"); + } + } + return true; + } + + // }}} + // {{{ doUninstall() + + function doUninstall($command, $options, $params) + { + if (empty($this->installer)) { + $this->installer = &new PEAR_Installer($this->ui); + } + if (sizeof($params) < 1) { + return $this->raiseError("Please supply the package(s) you want to uninstall"); + } + include_once 'PEAR/Registry.php'; + $reg = new PEAR_Registry($this->config->get('php_dir')); + $newparams = array(); + $badparams = array(); + foreach ($params as $pkg) { + $info = $reg->packageInfo($pkg); + if ($info === null) { + $badparams[] = $pkg; + } else { + $newparams[] = $info; + } + } + PEAR_Common::sortPkgDeps($newparams, true); + $params = array(); + foreach($newparams as $info) { + $params[] = $info['info']['package']; + } + $params = array_merge($params, $badparams); + foreach ($params as $pkg) { + if ($this->installer->uninstall($pkg, $options)) { + if ($this->config->get('verbose') > 0) { + $this->ui->outputData("uninstall ok: $pkg", $command); + } + } else { + return $this->raiseError("uninstall failed: $pkg"); + } + } + return true; + } + + // }}} + + + // }}} + // {{{ doBundle() + /* + (cox) It just downloads and untars the package, does not do + any check that the PEAR_Installer::_installFile() does. + */ + + function doBundle($command, $options, $params) + { + if (empty($this->installer)) { + $this->installer = &new PEAR_Downloader($this->ui); + } + $installer = &$this->installer; + if (sizeof($params) < 1) { + return $this->raiseError("Please supply the package you want to bundle"); + } + $pkgfile = $params[0]; + $need_download = false; + if (preg_match('#^(http|ftp)://#', $pkgfile)) { + $need_download = true; + } elseif (!@is_file($pkgfile)) { + if ($installer->validPackageName($pkgfile)) { + $pkgfile = $installer->getPackageDownloadUrl($pkgfile); + $need_download = true; + } else { + if (strlen($pkgfile)) { + return $this->raiseError("Could not open the package file: $pkgfile"); + } else { + return $this->raiseError("No package file given"); + } + } + } + + // Download package ----------------------------------------------- + if ($need_download) { + $downloaddir = $installer->config->get('download_dir'); + if (empty($downloaddir)) { + if (PEAR::isError($downloaddir = System::mktemp('-d'))) { + return $downloaddir; + } + $installer->log(2, '+ tmp dir created at ' . $downloaddir); + } + $callback = $this->ui ? array(&$installer, '_downloadCallback') : null; + $file = $installer->downloadHttp($pkgfile, $this->ui, $downloaddir, $callback); + if (PEAR::isError($file)) { + return $this->raiseError($file); + } + $pkgfile = $file; + } + + // Parse xml file ----------------------------------------------- + $pkginfo = $installer->infoFromTgzFile($pkgfile); + if (PEAR::isError($pkginfo)) { + return $this->raiseError($pkginfo); + } + $installer->validatePackageInfo($pkginfo, $errors, $warnings); + // XXX We allow warnings, do we have to do it? + if (count($errors)) { + if (empty($options['force'])) { + return $this->raiseError("The following errors where found:\n". + implode("\n", $errors)); + } else { + $this->log(0, "warning : the following errors were found:\n". + implode("\n", $errors)); + } + } + $pkgname = $pkginfo['package']; + + // Unpacking ------------------------------------------------- + + if (isset($options['destination'])) { + if (!is_dir($options['destination'])) { + System::mkdir('-p ' . $options['destination']); + } + $dest = realpath($options['destination']); + } else { + $pwd = getcwd(); + if (is_dir($pwd . DIRECTORY_SEPARATOR . 'ext')) { + $dest = $pwd . DIRECTORY_SEPARATOR . 'ext'; + } else { + $dest = $pwd; + } + } + $dest .= DIRECTORY_SEPARATOR . $pkgname; + $orig = $pkgname . '-' . $pkginfo['version']; + + $tar = new Archive_Tar($pkgfile); + if (!@$tar->extractModify($dest, $orig)) { + return $this->raiseError("unable to unpack $pkgfile"); + } + $this->ui->outputData("Package ready at '$dest'"); + // }}} + } + + // }}} + // {{{ doRevert() + function doRevert($command, $options, $params) + { + if (empty($this->installer)) { + $this->installer = &new PEAR_Installer($this->ui); + } + if (sizeof($params) < 1) { + return $this->raiseError("Please supply the package(s) you want to revert"); + } + include_once 'PEAR/Registry.php'; + $reg = new PEAR_Registry($this->config->get('php_dir')); + $newparams = array(); + $badparams = array(); + foreach ($params as $pkg) { + $info = $reg->packageInfo($pkg); + if ($info === null) { + $badparams[]['info']['package'] = $pkg; + } else { + $newparams = array_merge($newparams, + $this->installer->processRevertDeps($info, $options, $config, $reg)); + $newparams[] = $info; + } + } + PEAR_Common::sortPkgDeps($newparams, true); + $params = array(); + $params = array_merge($newparams, $badparams); + foreach ($params as $pkg) { + if (!PEAR::isError($this->installer->revert($pkg['info']['package'], + $options, $this->config))) { + if ($this->config->get('verbose') > 0) { + $this->ui->outputData('revert ok: ' . $pkg['info']['package'], $command); + } + } else { + $this->ui->outputData('revert failed: '); + return $this->raiseError($pkg); + } + } + return true; + } + + // }}} + +} +?> diff --git a/pear/PEAR/Downloader.php b/pear/PEAR/Downloader.php new file mode 100644 index 0000000000..2b3dec4e58 --- /dev/null +++ b/pear/PEAR/Downloader.php @@ -0,0 +1,588 @@ + | +// | Tomas V.V.Cox | +// | Martin Jansen | +// +----------------------------------------------------------------------+ +// +// $Id$ + +require_once 'PEAR/Common.php'; +require_once 'PEAR/Registry.php'; +require_once 'PEAR/Dependency.php'; +require_once 'PEAR/Remote.php'; +require_once 'System.php'; + +/** + * Administration class used to download PEAR packages and maintain the + * installed package database. + * + * @since PEAR 1.4 + * @author Greg Beaver + */ +class PEAR_Downloader extends PEAR_Common +{ + /** + * @var PEAR_Config + * @access private + */ + var $_config; + + /** + * @var PEAR_Registry + * @access private + */ + var $_registry; + + /** + * @var PEAR_Remote + * @access private + */ + var $_remote; + + /** + * Preferred Installation State (snapshot, devel, alpha, beta, stable) + * @var string|null + * @access private + */ + var $_preferredState; + + /** + * Options from command-line passed to Install. + * + * Recognized options:
+ * - onlyreqdeps : install all required dependencies as well + * - alldeps : install all dependencies, including optional + * - installroot : base relative path to install files in + * - force : force a download even if warnings would prevent it + * @see PEAR_Command_Install + * @access private + * @var array + */ + var $_options; + + /** + * Downloaded Packages after a call to download(). + * + * Format of each entry: + * + * + * array('pkg' => 'package_name', 'file' => '/path/to/local/file', + * 'info' => array() // parsed package.xml + * ); + * + * @access private + * @var array + */ + var $_downloadedPackages = array(); + + /** + * Packages slated for download. + * + * This is used to prevent downloading a package more than once should it be a dependency + * for two packages to be installed. + * Format of each entry: + * + *
+     * array('package_name1' => parsed package.xml, 'package_name2' => parsed package.xml,
+     * );
+     * 
+ * @access private + * @var array + */ + var $_toDownload = array(); + + /** + * Array of every package installed, with names lower-cased. + * + * Format: + * + * array('package1' => 0, 'package2' => 1, ); + * + * @var array + */ + var $_installed = array(); + + /** + * @var array + * @access private + */ + var $_errorStack = array(); + + // {{{ PEAR_Downloader() + + function PEAR_Downloader(&$ui, $options, &$config) + { + $this->_options = $options; + $this->_config = &$config; + $this->_preferredState = $this->_config->get('preferred_state'); + if (!$this->_preferredState) { + // don't inadvertantly use a non-set preferred_state + $this->_preferredState = null; + } + + $php_dir = $this->_config->get('php_dir'); + if (isset($this->_options['installroot'])) { + if (substr($this->_options['installroot'], -1) == DIRECTORY_SEPARATOR) { + $this->_options['installroot'] = substr($this->_options['installroot'], 0, -1); + } + $php_dir = $this->_prependPath($php_dir, $this->_options['installroot']); + } + $this->_registry = &new PEAR_Registry($php_dir); + $this->_remote = &new PEAR_Remote($config); + + if (isset($this->_options['alldeps']) || isset($this->_options['onlyreqdeps'])) { + $this->_installed = $this->_registry->listPackages(); + array_walk($this->_installed, create_function('&$v,$k','$v = strtolower($v);')); + $this->_installed = array_flip($this->_installed); + } + parent::PEAR_Common($ui); + } + + // }}} + // {{{ _downloadFile() + /** + * @param string filename to download + * @param string version/state + * @param string original value passed to command-line + * @param string|null preferred state (snapshot/devel/alpha/beta/stable) + * Defaults to configuration preferred state + * @return null|PEAR_Error|string + * @access private + */ + function _downloadFile($pkgfile, $version, $origpkgfile, $state = null) + { + if (is_null($state)) { + $state = $this->_preferredState; + } + // {{{ check the package filename, and whether it's already installed + $need_download = false; + if (preg_match('#^(http|ftp)://#', $pkgfile)) { + $need_download = true; + } elseif (!@is_file($pkgfile)) { + if ($this->validPackageName($pkgfile)) { + if ($this->_registry->packageExists($pkgfile)) { + if (empty($this->_options['upgrade']) && empty($this->_options['force'])) { + $errors[] = "$pkgfile already installed"; + return; + } + } + $pkgfile = $this->getPackageDownloadUrl($pkgfile, $version); + $need_download = true; + } else { + if (strlen($pkgfile)) { + $errors[] = "Could not open the package file: $pkgfile"; + } else { + $errors[] = "No package file given"; + } + return; + } + } + // }}} + + // {{{ Download package ----------------------------------------------- + if ($need_download) { + $downloaddir = $this->_config->get('download_dir'); + if (empty($downloaddir)) { + if (PEAR::isError($downloaddir = System::mktemp('-d'))) { + return $downloaddir; + } + $this->log(3, '+ tmp dir created at ' . $downloaddir); + } + $callback = $this->ui ? array(&$this, '_downloadCallback') : null; + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $file = $this->downloadHttp($pkgfile, $this->ui, $downloaddir, $callback); + $this->popErrorHandling(); + if (PEAR::isError($file)) { + if ($this->validPackageName($origpkgfile)) { + if (!PEAR::isError($info = $this->_remote->call('package.info', + $origpkgfile))) { + if (!count($info['releases'])) { + return $this->raiseError('Package ' . $origpkgfile . + ' has no releases'); + } else { + return $this->raiseError('No releases of preferred state "' + . $state . '" exist for package ' . $origpkgfile . + '. Use ' . $origpkgfile . '-state to install another' . + ' state (like ' . $origpkgfile .'-beta)', + PEAR_INSTALLER_ERROR_NO_PREF_STATE); + } + } else { + return $pkgfile; + } + } else { + return $this->raiseError($file); + } + } + $pkgfile = $file; + } + // }}} + return $pkgfile; + } + // }}} + // {{{ getPackageDownloadUrl() + + function getPackageDownloadUrl($package, $version = null) + { + if ($version) { + $package .= "-$version"; + } + if ($this === null || $this->_config === null) { + $package = "http://pear.php.net/get/$package"; + } else { + $package = "http://" . $this->_config->get('master_server') . + "/get/$package"; + } + if (!extension_loaded("zlib")) { + $package .= '?uncompress=yes'; + } + return $package; + } + + // }}} + // {{{ extractDownloadFileName($pkgfile, &$version) + + function extractDownloadFileName($pkgfile, &$version) + { + if (@is_file($pkgfile)) { + return $pkgfile; + } + // regex defined in Common.php + if (preg_match(PEAR_COMMON_PACKAGE_DOWNLOAD_PREG, $pkgfile, $m)) { + $version = (isset($m[3])) ? $m[3] : null; + return $m[1]; + } + $version = null; + return $pkgfile; + } + + // }}} + + // }}} + // {{{ getDownloadedPackages() + + /** + * Retrieve a list of downloaded packages after a call to {@link download()}. + * + * Also resets the list of downloaded packages. + * @return array + */ + function getDownloadedPackages() + { + $ret = $this->_downloadedPackages; + $this->_downloadedPackages = array(); + $this->_toDownload = array(); + return $ret; + } + + // }}} + // {{{ download() + + /** + * Download any files and their dependencies, if necessary + * + * @param array a mixed list of package names, local files, or package.xml + */ + function download($packages) + { + $mywillinstall = array(); + $state = $this->_preferredState; + + // {{{ download files in this list if necessary + foreach($packages as $pkgfile) { + if (!is_file($pkgfile)) { + $pkgfile = $this->_downloadNonFile($pkgfile); + if (PEAR::isError($pkgfile)) { + return $pkgfile; + } + if ($pkgfile === false) { + continue; + } + } // end is_file() + $tempinfo = $this->infoFromAny($pkgfile); + if (isset($this->_options['alldeps']) || isset($this->_options['onlyreqdeps'])) { + // ignore dependencies if there are any errors + if (!PEAR::isError($tempinfo)) { + $mywillinstall[strtolower($tempinfo['package'])] = @$tempinfo['release_deps']; + } + } + $this->_downloadedPackages[] = array('pkg' => $tempinfo['package'], + 'file' => $pkgfile, 'info' => $tempinfo); + } // end foreach($packages) + // }}} + + // {{{ extract dependencies from downloaded files and then download + // them if necessary + if (isset($this->_options['alldeps']) || isset($this->_options['onlyreqdeps'])) { + $deppackages = array(); + foreach ($mywillinstall as $package => $alldeps) { + if (!is_array($alldeps)) { + // there are no dependencies + continue; + } + foreach($alldeps as $info) { + if ($info['type'] != 'pkg') { + continue; + } + $ret = $this->_processDependency($info, $mywillinstall); + if ($ret === false) { + continue; + } + if (PEAR::isError($ret)) { + return $ret; + } + $deppackages[] = $ret; + } // foreach($alldeps + } + + if (count($deppackages)) { + // check dependencies' dependencies + // combine the list of packages to install + $temppack = array(); + foreach($this->_downloadedPackages as $p) { + $temppack[] = strtolower($p['info']['package']); + } + foreach($deppackages as $pack) { + $temppack[] = strtolower($pack); + } + $this->_toDownload = array_merge($this->_toDownload, $temppack); + $this->download($deppackages); + } + } // }}} if --alldeps or --onlyreqdeps + } + + // }}} + // {{{ _downloadNonFile($pkgfile) + + /** + * @return false|PEAR_Error|string false if loop should be broken out of, + * string if the file was downloaded, + * PEAR_Error on exception + * @access private + */ + function _downloadNonFile($pkgfile) + { + $origpkgfile = $pkgfile; + $state = null; + $pkgfile = $this->extractDownloadFileName($pkgfile, $version); + if (preg_match('#^(http|ftp)://#', $pkgfile)) { + $pkgfile = $this->_downloadFile($pkgfile, $version, $origpkgfile); + if (PEAR::isError($pkgfile)) { + return $pkgfile; + } + $tempinfo = $this->infoFromAny($pkgfile); + if (isset($this->_options['alldeps']) || isset($this->_options['onlyreqdeps'])) { + // ignore dependencies if there are any errors + if (!PEAR::isError($tempinfo)) { + $mywillinstall[strtolower($tempinfo['package'])] = @$tempinfo['release_deps']; + } + } + $this->_downloadedPackages[] = array('pkg' => $tempinfo['package'], + 'file' => $pkgfile, 'info' => $tempinfo); + return false; + } + if (!$this->validPackageName($pkgfile)) { + return $this->raiseError("Package name '$pkgfile' not valid"); + } + // ignore packages that are installed unless we are upgrading + $curinfo = $this->_registry->packageInfo($pkgfile); + if ($this->_registry->packageExists($pkgfile) + && empty($this->_options['upgrade']) && empty($this->_options['force'])) { + $this->log(0, "Package '{$curinfo['package']}' already installed, skipping"); + return false; + } + $curver = $curinfo['version']; + $releases = $this->_remote->call('package.info', $pkgfile, 'releases'); + if (!count($releases)) { + return $this->raiseError("No releases found for package '$pkgfile'"); + } + // Want a specific version/state + if ($version !== null) { + // Passed Foo-1.2 + if ($this->validPackageVersion($version)) { + if (!isset($releases[$version])) { + return $this->raiseError("No release with version '$version' found for '$pkgfile'"); + } + // Passed Foo-alpha + } elseif (in_array($version, $this->getReleaseStates())) { + $state = $version; + $version = 0; + foreach ($releases as $ver => $inf) { + if ($inf['state'] == $state && version_compare("$version", "$ver") < 0) { + $version = $ver; + } + } + if ($version == 0) { + return $this->raiseError("No release with state '$state' found for '$pkgfile'"); + } + // invalid postfix passed + } else { + return $this->raiseError("Invalid postfix '-$version', be sure to pass a valid PEAR ". + "version number or release state"); + } + // Guess what to download + } else { + $states = $this->betterStates($this->_preferredState, true); + $possible = false; + $version = 0; + foreach ($releases as $ver => $inf) { + if (in_array($inf['state'], $states) && version_compare("$version", "$ver") < 0) { + $version = $ver; + } + } + if ($version == 0 && !isset($this->_options['force'])) { + return $this->raiseError('No release with state equal to: \'' . implode(', ', $states) . + "' found for '$pkgfile'"); + } elseif ($version == 0) { + $this->log(0, "Warning: $pkgfile is state '$inf[state]' which is less stable " . + "than state '$state'"); + } + } + // Check if we haven't already the version + if (empty($this->_options['force'])) { + if ($curinfo['version'] == $version) { + $this->log(0, "Package '{$curinfo['package']}-{$curinfo['version']}' already installed, skipping"); + return false; + } elseif (version_compare("$version", "{$curinfo['version']}") < 0) { + $this->log(0, "Already got '{$curinfo['package']}-{$curinfo['version']}' greater than requested '$version', skipping"); + return false; + } + } + return $this->_downloadFile($pkgfile, $version, $origpkgfile, $state); + } + + // }}} + // {{{ _processDependency($info) + + /** + * Process a dependency, download if necessary + * @param array dependency information from PEAR_Remote call + * @param array packages that will be installed in this iteration + * @return false|string|PEAR_Error + * @access private + */ + function _processDependency($info, $mywillinstall) + { + $state = $this->_preferredState; + if (!isset($this->_options['alldeps']) && isset($info['optional']) && + $info['optional'] == 'yes') { + // skip optional deps + $this->log(0, "skipping Package $package optional dependency $info[name]"); + return false; + } + // {{{ get releases + $releases = $this->_remote->call('package.info', $info['name'], 'releases'); + if (PEAR::isError($releases)) { + return $releases; + } + if (!count($releases)) { + if (!isset($this->_installed[strtolower($info['name'])])) { + $this->pushError("Package $package dependency $info[name] ". + "has no releases"); + } + return false; + } + $found = false; + $save = $releases; + while(count($releases) && !$found) { + if (!empty($state) && $state != 'any') { + list($release_version, $release) = each($releases); + if ($state != $release['state'] && + !in_array($release['state'], $this->betterStates($state))) + { + // drop this release - it ain't stable enough + array_shift($releases); + } else { + $found = true; + } + } else { + $found = true; + } + } + if (!count($releases) && !$found) { + $get = array(); + foreach($save as $release) { + $get = array_merge($get, + $this->betterStates($release['state'], true)); + } + $savestate = array_shift($get); + $this->pushError( "Release for $package dependency $info[name] " . + "has state '$savestate', requires $state"); + continue; + } + if (in_array(strtolower($info['name']), $this->_toDownload) || + isset($mywillinstall[strtolower($info['name'])])) { + // skip upgrade check for packages we will install + continue; + } + if (!isset($this->_installed[strtolower($info['name'])])) { + // check to see if we can install the specific version required + if ($info['rel'] == 'eq') { + return $info['name'] . '-' . $info['version']; + } + // skip upgrade check for packages we don't have installed + return $info['name']; + } + // }}} + + // {{{ see if a dependency must be upgraded + $inst_version = $this->_registry->packageInfo($info['name'], 'version'); + if (!isset($info['version'])) { + // this is a rel='has' dependency, check against latest + if (version_compare($release_version, $inst_version, 'le')) { + return false; + } else { + return $info['name']; + } + } + if (version_compare($info['version'], $inst_version, 'le')) { + // installed version is up-to-date + return false; + } + return $info['name']; + } + + // }}} + // {{{ pushError($errmsg, $code) + + /** + * @param string + * @param integer + */ + function pushError($errmsg, $code = -1) + { + array_push($this->_errorStack, array($errmsg, $code)); + } + + // }}} + // {{{ getErrorMsgs() + + function getErrorMsgs() + { + $msgs = array(); + $errs = $this->_errorStack; + foreach ($errs as $err) { + $msgs[] = $err[0]; + } + $this->_errorStack = array(); + return $msgs; + } + + // }}} +} +// }}} + +?> diff --git a/pear/PEAR/Installer-minus-download.php b/pear/PEAR/Installer-minus-download.php new file mode 100644 index 0000000000..b966b51d7f --- /dev/null +++ b/pear/PEAR/Installer-minus-download.php @@ -0,0 +1,1023 @@ + | +// | Tomas V.V.Cox | +// | Martin Jansen | +// +----------------------------------------------------------------------+ +// +// $Id$ + +require_once 'PEAR/Common.php'; +require_once 'PEAR/Registry.php'; +require_once 'PEAR/Dependency.php'; +require_once 'System.php'; + +define('PEAR_INSTALLER_OK', 1); +define('PEAR_INSTALLER_FAILED', 0); +define('PEAR_INSTALLER_SKIPPED', -1); +define('PEAR_INSTALLER_ERROR_NO_PREF_STATE', 2); + +/** + * Administration class used to install PEAR packages and maintain the + * installed package database. + * + * TODO: + * - Check dependencies break on package uninstall (when no force given) + * - add a guessInstallDest() method with the code from _installFile() and + * use that method in Registry::_rebuildFileMap() & Command_Registry::doList(), + * others.. + * + * @since PHP 4.0.2 + * @author Stig Bakken + * @author Martin Jansen + */ +class PEAR_Installer extends PEAR_Common +{ + // {{{ properties + + /** name of the package directory, for example Foo-1.0 + * @var string + */ + var $pkgdir; + + /** directory where PHP code files go + * @var string + */ + var $phpdir; + + /** directory where PHP extension files go + * @var string + */ + var $extdir; + + /** directory where documentation goes + * @var string + */ + var $docdir; + + /** installation root directory (ala PHP's INSTALL_ROOT or + * automake's DESTDIR + * @var string + */ + var $installroot = ''; + + /** debug level + * @var int + */ + var $debug = 1; + + /** temporary directory + * @var string + */ + var $tmpdir; + + /** PEAR_Registry object used by the installer + * @var object + */ + var $registry; + + /** List of file transactions queued for an install/upgrade/uninstall. + * + * Format: + * array( + * 0 => array("rename => array("from-file", "to-file")), + * 1 => array("delete" => array("file-to-delete")), + * ... + * ) + * + * @var array + */ + var $file_operations = array(); + + // }}} + + // {{{ constructor + + /** + * PEAR_Installer constructor. + * + * @param object $ui user interface object (instance of PEAR_Frontend_*) + * + * @access public + */ + function PEAR_Installer(&$ui) + { + parent::PEAR_Common(); + $this->setFrontendObject($ui); + $this->debug = $this->config->get('verbose'); + //$this->registry = &new PEAR_Registry($this->config->get('php_dir')); + } + + // }}} + + // {{{ _deletePackageFiles() + + /** + * Delete a package's installed files, does not remove empty directories. + * + * @param string $package package name + * + * @return bool TRUE on success, or a PEAR error on failure + * + * @access private + */ + function _deletePackageFiles($package) + { + if (!strlen($package)) { + return $this->raiseError("No package to uninstall given"); + } + $filelist = $this->registry->packageInfo($package, 'filelist'); + if ($filelist == null) { + return $this->raiseError("$package not installed"); + } + foreach ($filelist as $file => $props) { + if (empty($props['installed_as'])) { + continue; + } + $path = $this->_prependPath($props['installed_as'], $this->installroot); + $this->addFileOperation('delete', array($path)); + } + return true; + } + + // }}} + // {{{ _installFile() + + /** + * @param string filename + * @param array attributes from tag in package.xml + * @param string path to install the file in + * @param array options from command-line + * @access private + */ + function _installFile($file, $atts, $tmp_path, $options) + { + // {{{ return if this file is meant for another platform + static $os; + if (isset($atts['platform'])) { + if (empty($os)) { + include_once "OS/Guess.php"; + $os = new OS_Guess(); + } + if (!$os->matchSignature($atts['platform'])) { + $this->log(3, "skipped $file (meant for $atts[platform], we are ".$os->getSignature().")"); + return PEAR_INSTALLER_SKIPPED; + } + } + // }}} + + // {{{ assemble the destination paths + switch ($atts['role']) { + case 'doc': + case 'data': + case 'test': + $dest_dir = $this->config->get($atts['role'] . '_dir') . + DIRECTORY_SEPARATOR . $this->pkginfo['package']; + unset($atts['baseinstalldir']); + break; + case 'ext': + case 'php': + $dest_dir = $this->config->get($atts['role'] . '_dir'); + break; + case 'script': + $dest_dir = $this->config->get('bin_dir'); + break; + case 'src': + case 'extsrc': + $this->source_files++; + return; + default: + return $this->raiseError("Invalid role `$atts[role]' for file $file"); + } + $save_destdir = $dest_dir; + if (!empty($atts['baseinstalldir'])) { + $dest_dir .= DIRECTORY_SEPARATOR . $atts['baseinstalldir']; + } + if (dirname($file) != '.' && empty($atts['install-as'])) { + $dest_dir .= DIRECTORY_SEPARATOR . dirname($file); + } + if (empty($atts['install-as'])) { + $dest_file = $dest_dir . DIRECTORY_SEPARATOR . basename($file); + } else { + $dest_file = $dest_dir . DIRECTORY_SEPARATOR . $atts['install-as']; + } + $orig_file = $tmp_path . DIRECTORY_SEPARATOR . $file; + + // Clean up the DIRECTORY_SEPARATOR mess + $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR; + list($dest_file, $orig_file) = preg_replace(array('!\\\\+!', '!/!', "!$ds2+!"), + DIRECTORY_SEPARATOR, + array($dest_file, $orig_file)); + $installed_as = $dest_file; + $final_dest_file = $this->_prependPath($dest_file, $this->installroot); + $dest_dir = dirname($final_dest_file); + $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file); + // }}} + + if (!@is_dir($dest_dir)) { + if (!$this->mkDirHier($dest_dir)) { + return $this->raiseError("failed to mkdir $dest_dir", + PEAR_INSTALLER_FAILED); + } + $this->log(3, "+ mkdir $dest_dir"); + } + if (empty($atts['replacements'])) { + if (!file_exists($orig_file)) { + return $this->raiseError("file does not exist", + PEAR_INSTALLER_FAILED); + } + if (!@copy($orig_file, $dest_file)) { + return $this->raiseError("failed to write $dest_file", + PEAR_INSTALLER_FAILED); + } + $this->log(3, "+ cp $orig_file $dest_file"); + if (isset($atts['md5sum'])) { + $md5sum = md5_file($dest_file); + } + } else { + // {{{ file with replacements + if (!file_exists($orig_file)) { + return $this->raiseError("file does not exist", + PEAR_INSTALLER_FAILED); + } + $fp = fopen($orig_file, "r"); + $contents = fread($fp, filesize($orig_file)); + fclose($fp); + if (isset($atts['md5sum'])) { + $md5sum = md5($contents); + } + $subst_from = $subst_to = array(); + foreach ($atts['replacements'] as $a) { + $to = ''; + if ($a['type'] == 'php-const') { + if (preg_match('/^[a-z0-9_]+$/i', $a['to'])) { + eval("\$to = $a[to];"); + } else { + $this->log(0, "invalid php-const replacement: $a[to]"); + continue; + } + } elseif ($a['type'] == 'pear-config') { + $to = $this->config->get($a['to']); + if (is_null($to)) { + $this->log(0, "invalid pear-config replacement: $a[to]"); + continue; + } + } elseif ($a['type'] == 'package-info') { + if (isset($this->pkginfo[$a['to']]) && is_string($this->pkginfo[$a['to']])) { + $to = $this->pkginfo[$a['to']]; + } else { + $this->log(0, "invalid package-info replacement: $a[to]"); + continue; + } + } + if (!is_null($to)) { + $subst_from[] = $a['from']; + $subst_to[] = $to; + } + } + $this->log(3, "doing ".sizeof($subst_from)." substitution(s) for $final_dest_file"); + if (sizeof($subst_from)) { + $contents = str_replace($subst_from, $subst_to, $contents); + } + $wp = @fopen($dest_file, "wb"); + if (!is_resource($wp)) { + return $this->raiseError("failed to create $dest_file: $php_errormsg", + PEAR_INSTALLER_FAILED); + } + if (!fwrite($wp, $contents)) { + return $this->raiseError("failed writing to $dest_file: $php_errormsg", + PEAR_INSTALLER_FAILED); + } + fclose($wp); + // }}} + } + // {{{ check the md5 + if (isset($md5sum)) { + if (strtolower($md5sum) == strtolower($atts['md5sum'])) { + $this->log(2, "md5sum ok: $final_dest_file"); + } else { + if (empty($options['force'])) { + // delete the file + @unlink($dest_file); + return $this->raiseError("bad md5sum for file $final_dest_file", + PEAR_INSTALLER_FAILED); + } else { + $this->log(0, "warning : bad md5sum for file $final_dest_file"); + } + } + } + // }}} + // {{{ set file permissions + if (!OS_WINDOWS) { + if ($atts['role'] == 'script') { + $mode = 0777 & ~(int)octdec($this->config->get('umask')); + $this->log(3, "+ chmod +x $dest_file"); + } else { + $mode = 0666 & ~(int)octdec($this->config->get('umask')); + } + $this->addFileOperation("chmod", array($mode, $dest_file)); + if (!@chmod($dest_file, $mode)) { + $this->log(0, "failed to change mode of $dest_file"); + } + } + // }}} + $this->addFileOperation("rename", array($dest_file, $final_dest_file)); + // Store the full path where the file was installed for easy unistall + $this->addFileOperation("installed_as", array($file, $installed_as, + $save_destdir, dirname(substr($dest_file, strlen($save_destdir))))); + + //$this->log(2, "installed: $dest_file"); + return PEAR_INSTALLER_OK; + } + + // }}} + // {{{ addFileOperation() + + /** + * Add a file operation to the current file transaction. + * + * @see startFileTransaction() + * @var string $type This can be one of: + * - rename: rename a file ($data has 2 values) + * - chmod: change permissions on a file ($data has 2 values) + * - delete: delete a file ($data has 1 value) + * - rmdir: delete a directory if empty ($data has 1 value) + * - installed_as: mark a file as installed ($data has 4 values). + * @var array $data For all file operations, this array must contain the + * full path to the file or directory that is being operated on. For + * the rename command, the first parameter must be the file to rename, + * the second its new name. + * + * The installed_as operation contains 4 elements in this order: + * 1. Filename as listed in the filelist element from package.xml + * 2. Full path to the installed file + * 3. Full path from the php_dir configuration variable used in this + * installation + * 4. Relative path from the php_dir that this file is installed in + */ + function addFileOperation($type, $data) + { + if (!is_array($data)) { + return $this->raiseError('Internal Error: $data in addFileOperation' + . ' must be an array, was ' . gettype($data)); + } + if ($type == 'chmod') { + $octmode = decoct($data[0]); + $this->log(3, "adding to transaction: $type $octmode $data[1]"); + } else { + $this->log(3, "adding to transaction: $type " . implode(" ", $data)); + } + $this->file_operations[] = array($type, $data); + } + + // }}} + // {{{ startFileTransaction() + + function startFileTransaction($rollback_in_case = false) + { + if (count($this->file_operations) && $rollback_in_case) { + $this->rollbackFileTransaction(); + } + $this->file_operations = array(); + } + + // }}} + // {{{ commitFileTransaction() + + function commitFileTransaction() + { + $n = count($this->file_operations); + $this->log(2, "about to commit $n file operations"); + // {{{ first, check permissions and such manually + $errors = array(); + foreach ($this->file_operations as $tr) { + list($type, $data) = $tr; + switch ($type) { + case 'rename': + if (!file_exists($data[0])) { + $errors[] = "cannot rename file $data[0], doesn't exist"; + } + // check that dest dir. is writable + if (!is_writable(dirname($data[1]))) { + $errors[] = "permission denied ($type): $data[1]"; + } + break; + case 'chmod': + // check that file is writable + if (!is_writable($data[1])) { + $errors[] = "permission denied ($type): $data[1] " . decoct($data[0]); + } + break; + case 'delete': + if (!file_exists($data[0])) { + $this->log(2, "warning: file $data[0] doesn't exist, can't be deleted"); + } + // check that directory is writable + if (file_exists($data[0]) && !is_writable(dirname($data[0]))) { + $errors[] = "permission denied ($type): $data[0]"; + } + break; + } + + } + // }}} + $m = sizeof($errors); + if ($m > 0) { + foreach ($errors as $error) { + $this->log(1, $error); + } + return false; + } + // {{{ really commit the transaction + foreach ($this->file_operations as $tr) { + list($type, $data) = $tr; + switch ($type) { + case 'rename': + @unlink($data[1]); + @rename($data[0], $data[1]); + $this->log(3, "+ mv $data[0] $data[1]"); + break; + case 'chmod': + @chmod($data[1], $data[0]); + $octmode = decoct($data[0]); + $this->log(3, "+ chmod $octmode $data[1]"); + break; + case 'delete': + @unlink($data[0]); + $this->log(3, "+ rm $data[0]"); + break; + case 'rmdir': + @rmdir($data[0]); + $this->log(3, "+ rmdir $data[0]"); + break; + case 'installed_as': + $this->pkginfo['filelist'][$data[0]]['installed_as'] = $data[1]; + if (!isset($this->pkginfo['filelist']['dirtree'][dirname($data[1])])) { + $this->pkginfo['filelist']['dirtree'][dirname($data[1])] = true; + while(!empty($data[3]) && $data[3] != '/' && $data[3] != '\\' + && $data[3] != '.') { + $this->pkginfo['filelist']['dirtree'] + [$this->_prependPath($data[3], $data[2])] = true; + $data[3] = dirname($data[3]); + } + } + break; + } + } + // }}} + $this->log(2, "successfully committed $n file operations"); + $this->file_operations = array(); + return true; + } + + // }}} + // {{{ rollbackFileTransaction() + + function rollbackFileTransaction() + { + $n = count($this->file_operations); + $this->log(2, "rolling back $n file operations"); + foreach ($this->file_operations as $tr) { + list($type, $data) = $tr; + switch ($type) { + case 'rename': + @unlink($data[0]); + $this->log(3, "+ rm $data[0]"); + break; + case 'mkdir': + @rmdir($data[0]); + $this->log(3, "+ rmdir $data[0]"); + break; + case 'chmod': + break; + case 'delete': + break; + case 'installed_as': + if (isset($this->pkginfo['filelist'])) { + unset($this->pkginfo['filelist'][$data[0]]['installed_as']); + } + if (isset($this->pkginfo['filelist']['dirtree'][dirname($data[1])])) { + unset($this->pkginfo['filelist']['dirtree'][dirname($data[1])]); + while(!empty($data[3]) && $data[3] != '/' && $data[3] != '\\' + && $data[3] != '.') { + unset($this->pkginfo['filelist']['dirtree'] + [$this->_prependPath($data[3], $data[2])]); + $data[3] = dirname($data[3]); + } + } + if (isset($this->pkginfo['filelist']['dirtree']) + && !count($this->pkginfo['filelist']['dirtree'])) { + unset($this->pkginfo['filelist']['dirtree']); + } + break; + } + } + $this->file_operations = array(); + } + + // }}} + // {{{ mkDirHier($dir) + + function mkDirHier($dir) + { + $this->addFileOperation('mkdir', array($dir)); + return parent::mkDirHier($dir); + } + + // }}} + // {{{ _prependPath($path, $prepend) + + function _prependPath($path, $prepend) + { + if (strlen($prepend) > 0) { + if (OS_WINDOWS && preg_match('/^[a-z]:/i', $path)) { + $path = $prepend . substr($path, 2); + } else { + $path = $prepend . $path; + } + } + return $path; + } + + // }}} + // {{{ install() + + /** + * Installs the files within the package file specified. + * + * @param string $pkgfile path to the package file + * @param array $options + * recognized options: + * - installroot : optional prefix directory for installation + * - force : force installation + * - register-only : update registry but don't install files + * - upgrade : upgrade existing install + * - soft : fail silently + * - nodeps : ignore dependency conflicts/missing dependencies + * - alldeps : install all dependencies + * - onlyreqdeps : install only required dependencies + * + * @return array|PEAR_Error package info if successful + */ + + function install($pkgfile, $options = array()) + { + $php_dir = $this->config->get('php_dir'); + if (isset($options['installroot'])) { + if (substr($options['installroot'], -1) == DIRECTORY_SEPARATOR) { + $options['installroot'] = substr($options['installroot'], 0, -1); + } + $php_dir = $this->_prependPath($php_dir, $options['installroot']); + $this->installroot = $options['installroot']; + } else { + $this->installroot = ''; + } + $this->registry = &new PEAR_Registry($php_dir); + // ==> XXX should be removed later on + $flag_old_format = false; + + if (substr($pkgfile, -4) == '.xml') { + $descfile = $pkgfile; + } else { + // {{{ Decompress pack in tmp dir ------------------------------------- + + // To allow relative package file names + $pkgfile = realpath($pkgfile); + + if (PEAR::isError($tmpdir = System::mktemp('-d'))) { + return $tmpdir; + } + $this->log(3, '+ tmp dir created at ' . $tmpdir); + + $tar = new Archive_Tar($pkgfile); + if (!@$tar->extract($tmpdir)) { + return $this->raiseError("unable to unpack $pkgfile"); + } + + // {{{ Look for existing package file + $descfile = $tmpdir . DIRECTORY_SEPARATOR . 'package.xml'; + + if (!is_file($descfile)) { + // ----- Look for old package archive format + // In this format the package.xml file was inside the + // Package-n.n directory + $dp = opendir($tmpdir); + do { + $pkgdir = readdir($dp); + } while ($pkgdir{0} == '.'); + + $descfile = $tmpdir . DIRECTORY_SEPARATOR . $pkgdir . DIRECTORY_SEPARATOR . 'package.xml'; + $flag_old_format = true; + $this->log(0, "warning : you are using an archive with an old format"); + } + // }}} + // <== XXX This part should be removed later on + // }}} + } + + if (!is_file($descfile)) { + return $this->raiseError("no package.xml file after extracting the archive"); + } + + // Parse xml file ----------------------------------------------- + $pkginfo = $this->infoFromDescriptionFile($descfile); + if (PEAR::isError($pkginfo)) { + return $pkginfo; + } + $this->validatePackageInfo($pkginfo, $errors, $warnings); + // XXX We allow warnings, do we have to do it? + if (count($errors)) { + if (empty($options['force'])) { + return $this->raiseError("The following errors where found (use force option to install anyway):\n". + implode("\n", $errors)); + } else { + $this->log(0, "warning : the following errors were found:\n". + implode("\n", $errors)); + } + } + + $pkgname = $pkginfo['package']; + + // {{{ Check dependencies ------------------------------------------- + if (isset($pkginfo['release_deps']) && empty($options['nodeps'])) { + $dep_errors = ''; + $error = $this->checkDeps($pkginfo, $dep_errors); + if ($error == true) { + if (empty($options['soft'])) { + $this->log(0, substr($dep_errors, 1)); + } + return $this->raiseError("$pkgname: Dependencies failed"); + } else if (!empty($dep_errors)) { + // Print optional dependencies + if (empty($options['soft'])) { + $this->log(0, $dep_errors); + } + } + } + // }}} + + // {{{ checks to do when not in "force" mode + if (empty($options['force'])) { + $test = $this->registry->checkFileMap($pkginfo); + if (sizeof($test)) { + $tmp = $test; + foreach ($tmp as $file => $pkg) { + if ($pkg == $pkgname) { + unset($test[$file]); + } + } + if (sizeof($test)) { + $msg = "$pkgname: conflicting files found:\n"; + $longest = max(array_map("strlen", array_keys($test))); + $fmt = "%${longest}s (%s)\n"; + foreach ($test as $file => $pkg) { + $msg .= sprintf($fmt, $file, $pkg); + } + return $this->raiseError($msg); + } + } + } + // }}} + + $this->startFileTransaction(); + + if (empty($options['upgrade'])) { + // checks to do only when installing new packages + if (empty($options['force']) && $this->registry->packageExists($pkgname)) { + return $this->raiseError("$pkgname already installed"); + } + } else { + if ($this->registry->packageExists($pkgname)) { + $v1 = $this->registry->packageInfo($pkgname, 'version'); + $v2 = $pkginfo['version']; + $cmp = version_compare("$v1", "$v2", 'gt'); + if (empty($options['force']) && !version_compare("$v2", "$v1", 'gt')) { + return $this->raiseError("upgrade to a newer version ($v2 is not newer than $v1)"); + } + if (empty($options['register-only'])) { + // when upgrading, remove old release's files first: + if (PEAR::isError($err = $this->_deletePackageFiles($pkgname))) { + return $this->raiseError($err); + } + } + } + } + + // {{{ Copy files to dest dir --------------------------------------- + + // info from the package it self we want to access from _installFile + $this->pkginfo = &$pkginfo; + // used to determine whether we should build any C code + $this->source_files = 0; + + if (empty($options['register-only'])) { + if (!is_dir($php_dir)) { + return $this->raiseError("no script destination directory\n", + null, PEAR_ERROR_DIE); + } + + $tmp_path = dirname($descfile); + if (substr($pkgfile, -4) != '.xml') { + $tmp_path .= DIRECTORY_SEPARATOR . $pkgname . '-' . $pkginfo['version']; + } + + // ==> XXX This part should be removed later on + if ($flag_old_format) { + $tmp_path = dirname($descfile); + } + // <== XXX This part should be removed later on + + // {{{ install files + foreach ($pkginfo['filelist'] as $file => $atts) { + $this->expectError(PEAR_INSTALLER_FAILED); + $res = $this->_installFile($file, $atts, $tmp_path, $options); + $this->popExpect(); + if (PEAR::isError($res)) { + if (empty($options['ignore-errors'])) { + $this->rollbackFileTransaction(); + if ($res->getMessage() == "file does not exist") { + $this->raiseError("file $file in package.xml does not exist"); + } + return $this->raiseError($res); + } else { + $this->log(0, "Warning: " . $res->getMessage()); + } + } + if ($res != PEAR_INSTALLER_OK) { + // Do not register files that were not installed + unset($pkginfo['filelist'][$file]); + } + } + // }}} + + // {{{ compile and install source files + if ($this->source_files > 0 && empty($options['nobuild'])) { + $this->log(1, "$this->source_files source files, building"); + $bob = &new PEAR_Builder($this->ui); + $bob->debug = $this->debug; + $built = $bob->build($descfile, array(&$this, '_buildCallback')); + if (PEAR::isError($built)) { + $this->rollbackFileTransaction(); + return $built; + } + $this->log(1, "\nBuild process completed successfully"); + foreach ($built as $ext) { + $bn = basename($ext['file']); + list($_ext_name, ) = explode('.', $bn); + if (extension_loaded($_ext_name)) { + $this->raiseError("Extension '$_ext_name' already loaded. Please unload it ". + "in your php.ini file prior to install or upgrade it."); + } + $dest = $this->config->get('ext_dir') . DIRECTORY_SEPARATOR . $bn; + $this->log(1, "Installing '$bn' at ext_dir ($dest)"); + $this->log(3, "+ cp $ext[file] ext_dir ($dest)"); + $copyto = $this->_prependPath($dest, $this->installroot); + if (!@copy($ext['file'], $copyto)) { + $this->rollbackFileTransaction(); + return $this->raiseError("failed to copy $bn to $copyto"); + } + $pkginfo['filelist'][$bn] = array( + 'role' => 'ext', + 'installed_as' => $dest, + 'php_api' => $ext['php_api'], + 'zend_mod_api' => $ext['zend_mod_api'], + 'zend_ext_api' => $ext['zend_ext_api'], + ); + } + } + // }}} + } + + if (!$this->commitFileTransaction()) { + $this->rollbackFileTransaction(); + return $this->raiseError("commit failed", PEAR_INSTALLER_FAILED); + } + // }}} + + $ret = false; + // {{{ Register that the package is installed ----------------------- + if (empty($options['upgrade'])) { + // if 'force' is used, replace the info in registry + if (!empty($options['force']) && $this->registry->packageExists($pkgname)) { + $this->registry->deletePackage($pkgname); + } + $ret = $this->registry->addPackage($pkgname, $pkginfo); + } else { + // new: upgrade installs a package if it isn't installed + if (!$this->registry->packageExists($pkgname)) { + $ret = $this->registry->addPackage($pkgname, $pkginfo); + } else { + $ret = $this->registry->updatePackage($pkgname, $pkginfo, false); + } + } + if (!$ret) { + return $this->raiseError("Adding package $pkgname to registry failed"); + } + // }}} + return $pkginfo; + } + + // }}} + // {{{ uninstall() + + /** + * Uninstall a package + * + * This method removes all files installed by the application, and then + * removes any empty directories. + * @param string package name + * @param array Command-line options. Possibilities include: + * + * - installroot: base installation dir, if not the default + */ + function uninstall($package, $options = array()) + { + $php_dir = $this->config->get('php_dir'); + if (isset($options['installroot'])) { + if (substr($options['installroot'], -1) == DIRECTORY_SEPARATOR) { + $options['installroot'] = substr($options['installroot'], 0, -1); + } + $this->installroot = $options['installroot']; + $php_dir = $this->_prependPath($php_dir, $this->installroot); + } else { + $this->installroot = ''; + } + $this->registry = &new PEAR_Registry($php_dir); + $filelist = $this->registry->packageInfo($package, 'filelist'); + if ($filelist == null) { + return $this->raiseError("$package not installed"); + } + if (empty($options['nodeps'])) { + $depchecker = &new PEAR_Dependency($this->registry); + $error = $depchecker->checkPackageUninstall($errors, $warning, $package); + if ($error) { + return $this->raiseError($errors . 'uninstall failed'); + } + if ($warning) { + $this->log(0, $warning); + } + } + // {{{ Delete the files + $this->startFileTransaction(); + if (PEAR::isError($err = $this->_deletePackageFiles($package))) { + $this->rollbackFileTransaction(); + return $this->raiseError($err); + } + if (!$this->commitFileTransaction()) { + $this->rollbackFileTransaction(); + return $this->raiseError("uninstall failed"); + } else { + $this->startFileTransaction(); + if (!isset($filelist['dirtree']) || !count($filelist['dirtree'])) { + return $this->registry->deletePackage($package); + } + // attempt to delete empty directories + uksort($filelist['dirtree'], array($this, '_sortDirs')); + foreach($filelist['dirtree'] as $dir => $notused) { + $this->addFileOperation('rmdir', array($dir)); + } + if (!$this->commitFileTransaction()) { + $this->rollbackFileTransaction(); + } + } + // }}} + + // Register that the package is no longer installed + return $this->registry->deletePackage($package); + } + + // }}} + // {{{ _sortDirs() + function _sortDirs($a, $b) + { + if (strnatcmp($a, $b) == -1) return 1; + if (strnatcmp($a, $b) == 1) return -1; + return 0; + } + + // }}} + // {{{ checkDeps() + + /** + * Check if the package meets all dependencies + * + * @param array Package information (passed by reference) + * @param string Error message (passed by reference) + * @return boolean False when no error occured, otherwise true + */ + function checkDeps(&$pkginfo, &$errors) + { + if (empty($this->registry)) { + $this->registry = &new PEAR_Registry($this->config->get('php_dir')); + } + $depchecker = &new PEAR_Dependency($this->registry); + $error = $errors = ''; + $failed_deps = $optional_deps = array(); + if (is_array($pkginfo['release_deps'])) { + foreach($pkginfo['release_deps'] as $dep) { + $code = $depchecker->callCheckMethod($error, $dep); + if ($code) { + if (isset($dep['optional']) && $dep['optional'] == 'yes') { + $optional_deps[] = array($dep, $code, $error); + } else { + $failed_deps[] = array($dep, $code, $error); + } + } + } + // {{{ failed dependencies + $n = count($failed_deps); + if ($n > 0) { + for ($i = 0; $i < $n; $i++) { + if (isset($failed_deps[$i]['type'])) { + $type = $failed_deps[$i]['type']; + } else { + $type = 'pkg'; + } + switch ($failed_deps[$i][1]) { + case PEAR_DEPENDENCY_MISSING: + if ($type == 'pkg') { + // install + } + $errors .= "\n" . $failed_deps[$i][2]; + break; + case PEAR_DEPENDENCY_UPGRADE_MINOR: + if ($type == 'pkg') { + // upgrade + } + $errors .= "\n" . $failed_deps[$i][2]; + break; + default: + $errors .= "\n" . $failed_deps[$i][2]; + break; + } + } + return true; + } + // }}} + + // {{{ optional dependencies + $count_optional = count($optional_deps); + if ($count_optional > 0) { + $errors = "Optional dependencies:"; + + for ($i = 0; $i < $count_optional; $i++) { + if (isset($optional_deps[$i]['type'])) { + $type = $optional_deps[$i]['type']; + } else { + $type = 'pkg'; + } + switch ($optional_deps[$i][1]) { + case PEAR_DEPENDENCY_MISSING: + case PEAR_DEPENDENCY_UPGRADE_MINOR: + default: + $errors .= "\n" . $optional_deps[$i][2]; + break; + } + } + return false; + } + // }}} + } + return false; + } + + // }}} + // {{{ _buildCallback() + + function _buildCallback($what, $data) + { + if (($what == 'cmdoutput' && $this->debug > 1) || + ($what == 'output' && $this->debug > 0)) { + $this->ui->outputData(rtrim($data), 'build'); + } + } + + // }}} +} + +// {{{ md5_file() utility function +if (!function_exists("md5_file")) { + function md5_file($filename) { + $fp = fopen($filename, "r"); + if (!$fp) return null; + $contents = fread($fp, filesize($filename)); + fclose($fp); + return md5($contents); + } +} +// }}} + +?> diff --git a/pear/package-PEAR-new-Downloader.xml b/pear/package-PEAR-new-Downloader.xml new file mode 100644 index 0000000000..9ecb1a5716 --- /dev/null +++ b/pear/package-PEAR-new-Downloader.xml @@ -0,0 +1,130 @@ + + + + PEAR + PEAR Base System + The PEAR package contains: + * the PEAR base class + * the PEAR_Error error handling mechanism + * the PEAR installer, for creating, distributing + and installing packages + * the OS_Guess class for retrieving info about the OS + where PHP is running on + * the System class for quick handling common operations + with files and directories + + PHP License + + + ssb + lead + Stig Sæther Bakken + stig@php.net + + + cox + lead + Tomas V.V.Cox + cox@idecnet.com + + + mj + developer + Martin Jansen + mj@php.net + + + pajoye + developer + Pierre-Alain Joye + pajoye@pearfr.org + + + cellog + developer + Greg Beaver + cellog@php.net + + + + 1.4a1 + 2003-11-17 + alpha + +PEAR Installer: + +* Bug #171 --alldeps with a rel="eq" should install the required version, if possible +* Bug #249 installing from an url doesnt work +* Bug #248 --force command does not work as expected +* Bug #293 [Patch] PEAR_Error not calling static method callbacks for error-handler +* Bug #324 pear -G gives Fatal Error (PHP-GTK not installed, but error is at engine level) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Archive_Tar + Console_Getopt + XML_RPC + xmlrpc + xml + + +