diff options
-rw-r--r-- | pear/PEAR/Command/Install.php | 51 | ||||
-rw-r--r-- | pear/PEAR/Common.php | 169 | ||||
-rw-r--r-- | pear/PEAR/Installer.php | 297 | ||||
-rw-r--r-- | pear/package-PEAR.xml | 14 |
4 files changed, 474 insertions, 57 deletions
diff --git a/pear/PEAR/Command/Install.php b/pear/PEAR/Command/Install.php index 8026a7bfb1..8d7f8d9f3d 100644 --- a/pear/PEAR/Command/Install.php +++ b/pear/PEAR/Command/Install.php @@ -69,6 +69,14 @@ class PEAR_Command_Install extends PEAR_Command_Common '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' => '<package> ... Installs one or more PEAR packages. You can specify a package to @@ -123,6 +131,14 @@ four ways of specifying packages. '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' => '<package> ... Upgrades one or more PEAR packages. See documentation for the @@ -273,9 +289,20 @@ package if needed. $this->ui->outputData(array('data' => "Will upgrade $package"), $command); } } - foreach ($params as $pkg) { - $bn = basename($pkg); - $info = $this->installer->install($pkg, $options, $this->config); + $errors = array(); + $downloaded = array(); + $this->installer->download($params, $options, $this->config, $downloaded, + $errors); + if (count($errors)) { + $err['data'] = array($errors); + $err['headline'] = 'Install Errors'; + $this->ui->outputData($err); + return $this->raiseError("$command failed"); + } + $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]"; @@ -303,6 +330,24 @@ package if needed. 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) { diff --git a/pear/PEAR/Common.php b/pear/PEAR/Common.php index 8f75a86085..2b416e020d 100644 --- a/pear/PEAR/Common.php +++ b/pear/PEAR/Common.php @@ -699,7 +699,7 @@ class PEAR_Common extends PEAR break; } } - $tmpdir = System::mkTemp('-d pear'); + $tmpdir = System::mkTemp(array('-d', 'pear')); $this->addTempFile($tmpdir); if (!$xml || !$tar->extractList(array($xml), $tmpdir)) { return $this->raiseError('could not extract the package.xml file'); @@ -1715,6 +1715,173 @@ class PEAR_Common extends PEAR } // }}} + // {{{ sortPkgDeps() + + /** + * Sort a list of arrays of array(downloaded packagefilename) by dependency. + * + * It also removes duplicate dependencies + * @param array + * @param boolean Sort packages in reverse order if true + * @return array array of array(packagefilename, package.xml contents) + */ + function sortPkgDeps(&$packages, $uninstall = false) + { + $ret = array(); + if ($uninstall) { + foreach($packages as $packageinfo) { + $ret[] = array('info' => $packageinfo); + } + } else { + foreach($packages as $packagefile) { + if (!is_array($packagefile)) { + $ret[] = array('file' => $packagefile, + 'info' => $a = $this->infoFromAny($packagefile), + 'pkg' => $a['package']); + } else { + $ret[] = $packagefile; + } + } + } + $checkdupes = array(); + $newret = array(); + foreach($ret as $i => $p) { + if (!isset($checkdupes[$p['info']['package']])) { + $checkdupes[$p['info']['package']][] = $i; + $newret[] = $p; + } + } + + $func = $uninstall ? '_sortPkgDepsRev' : '_sortPkgDeps'; + usort($newret, array('PEAR_Common', $func)); + $packages = $newret; + } + + // }}} + // {{{ _sortPkgDeps() + + /** + * Compare two package's package.xml, and sort + * so that dependencies are installed first + * + * This is a crude compare, real dependency checking is done on install. + * The only purpose this serves is to make the command-line + * order-independent (you can list a dependent package first, and + * installation occurs in the order required) + * @access private + */ + function _sortPkgDeps($p1, $p2) + { + $p1name = $p1['info']['package']; + $p2name = $p2['info']['package']; + $p1deps = PEAR_Common::_getPkgDeps($p1); + $p2deps = PEAR_Common::_getPkgDeps($p2); + if (!count($p1deps) && !count($p2deps)) { + return 0; // order makes no difference + } + if (!count($p1deps)) { + return -1; // package 2 has dependencies, package 1 doesn't + } + if (!count($p2deps)) { + return 1; // package 2 has dependencies, package 1 doesn't + } + // both have dependencies + if (in_array($p1name, $p2deps)) { + return -1; // put package 1 first + } + if (in_array($p2name, $p1deps)) { + return 1; // put package 2 first + } + // doesn't really matter if neither depends on the other + return 0; + } + + // }}} + // {{{ _sortPkgDepsRev() + + /** + * Compare two package's package.xml, and sort + * so that dependencies are uninstalled last + * + * This is a crude compare, real dependency checking is done on uninstall. + * The only purpose this serves is to make the command-line + * order-independent (you can list a dependency first, and + * uninstallation occurs in the order required) + * @access private + */ + function _sortPkgDepsRev($p1, $p2) + { + $p1name = $p1['info']['package']; + $p2name = $p2['info']['package']; + $p1deps = PEAR_Common::_getRevPkgDeps($p1); + $p2deps = PEAR_Common::_getRevPkgDeps($p2); + if (!count($p1deps) && !count($p2deps)) { + return 0; // order makes no difference + } + if (!count($p1deps)) { + return 1; // package 2 has dependencies, package 1 doesn't + } + if (!count($p2deps)) { + return -1; // package 2 has dependencies, package 1 doesn't + } + // both have dependencies + if (in_array($p1name, $p2deps)) { + return 1; // put package 1 last + } + if (in_array($p2name, $p1deps)) { + return -1; // put package 2 last + } + // doesn't really matter if neither depends on the other + return 0; + } + + // }}} + // {{{ _getPkgDeps() + + /** + * get an array of package dependency names + * @access private + */ + function _getPkgDeps($p) + { + if (!isset($p['info']['releases'])) { + return array(); + } + $rel = array_shift($p['info']['releases']); + if (!isset($rel['deps'])) { + return array(); + } + $ret = array(); + foreach($rel['deps'] as $dep) { + if ($dep['type'] == 'pkg') { + $ret[] = $dep['name']; + } + } + return $ret; + } + + // }}} + // {{{ _getRevPkgDeps() + + /** + * get an array of package dependency names for uninstall + * @access private + */ + function _getRevPkgDeps($p) + { + if (!isset($p['info']['release_deps'])) { + return array(); + } + $ret = array(); + foreach($p['info']['release_deps'] as $dep) { + if ($dep['type'] == 'pkg') { + $ret[] = $dep['name']; + } + } + return $ret; + } + + // }}} } ?> diff --git a/pear/PEAR/Installer.php b/pear/PEAR/Installer.php index 6fb653a370..4b66e2b4fc 100644 --- a/pear/PEAR/Installer.php +++ b/pear/PEAR/Installer.php @@ -464,7 +464,235 @@ class PEAR_Installer extends PEAR_Common } // }}} + // {{{ _downloadFile() + function _downloadFile($pkgfile, &$config, $options, &$errors) + { + $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($options['upgrade']) && empty($options['force'])) { + $errors[] = "$pkgfile already installed"; + return; + } + } + $pkgfile = $this->getPackageDownloadUrl($pkgfile); + $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 = $config->get('download_dir'); + if (empty($downloaddir)) { + if (PEAR::isError($downloaddir = System::mktemp('-d'))) { + return $downloaddir; + } + $this->log(2, '+ tmp dir created at ' . $downloaddir); + } + $callback = $this->ui ? array(&$this, '_downloadCallback') : null; + $file = $this->downloadHttp($pkgfile, $this->ui, $downloaddir, $callback); + if (PEAR::isError($file)) { + return $this->raiseError($file); + } + $pkgfile = $file; + } + return $pkgfile; + } + + // }}} + // {{{ download() + + /** + * Download any files and their dependencies, if necessary + * + * @param array a mixed list of package names, local files, or package.xml + * @param PEAR_Config + * @param array options from the command line + * @param array this is the array that will be populated with packages to + * install. Format of each entry: + * + * <code> + * array('pkg' => 'package_name', 'file' => '/path/to/local/file', + * 'info' => array() // parsed package.xml + * ); + * </code> + * @param array this will be populated with any error messages + * @param false private recursion variable + * @param false private recursion variable + * @param false private recursion variable + */ + function download($packages, $options, &$config, &$installpackages, + &$errors, $installed = false, $willinstall = false, $state = false) + { + // recognized options: + // - onlyreqdeps : install all required dependencies as well + // - alldeps : install all dependencies, including optional + // + if (!$willinstall) { + $willinstall = array(); + } + $mywillinstall = array(); + $php_dir = $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); + + // download files in this list if necessary + foreach($packages as $pkgfile) { + if ($this->validPackageName($pkgfile) && !isset($options['upgrade'])) { + if ($this->registry->packageExists($pkgfile)) { + // ignore dependencies that are installed unless we are upgrading + continue; + } + } + $pkgfile = $this->_downloadFile($pkgfile, $config, $options, $errors); + if (PEAR::isError($pkgfile)) { + return $pkgfile; + } + $tempinfo = $this->infoFromAny($pkgfile); + if (isset($options['alldeps']) || isset($options['onlyreqdeps'])) { + // ignore dependencies if there are any errors + if (!PEAR::isError($tempinfo)) { + $mywillinstall[strtolower($tempinfo['package'])] = @$tempinfo['release_deps']; + } + } + $installpackages[] = array('pkg' => $tempinfo['package'], + 'file' => $pkgfile, 'info' => $tempinfo); + } + + // extract dependencies from downloaded files and then download them + // if necessary + if (isset($options['alldeps']) || isset($options['onlyreqdeps'])) { + $reg = new PEAR_Registry($config->get('php_dir')); + if (!$installed) { + $state = $config->get('preferred_state'); + $installed = $reg->listPackages(); + array_walk($installed, create_function('&$v,$k','$v = strtolower($v);')); + $installed = array_flip($installed); + } + include_once "PEAR/Remote.php"; + $remote = new PEAR_Remote($config); + $deppackages = array(); + // construct the list of dependencies for each file + foreach ($mywillinstall as $package => $alldeps) { + if (!is_array($alldeps)) { + continue; + } + foreach($alldeps as $info) { + if ($info['type'] != 'pkg') { + continue; + } + if (!isset($options['alldeps']) && isset($info['optional']) && + $info['optional'] == 'yes') { + // skip optional deps + $this->log(0, "skipping Package $package optional dependency $info[name]"); + continue; + } + // get releases + $releases = $remote->call('package.info', $info['name'], 'releases'); + if (PEAR::isError($releases)) { + return $releases; + } + if (!count($releases)) { + if (!isset($installed[strtolower($info['name'])])) { + $errors[] = "Package $package dependency $info[name] ". + "has no releases"; + } + continue; + } + $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); + $errors[] = "Release for $package dependency $info[name] " . + "has state '$savestate', requires $state"; + continue; + } + if (in_array(strtolower($info['name']), $willinstall) || + isset($mywillinstall[strtolower($info['name'])])) { + // skip upgrade check for packages we will install + continue; + } + if (!isset($installed[strtolower($info['name'])])) { + // skip upgrade check for packages we don't have installed + $deppackages[] = $info['name']; + continue; + } + + // see if a dependency must be upgraded + $inst_version = $reg->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')) { + continue; + } else { + $deppackages[] = $info['name']; + continue; + } + } + if (version_compare($info['version'], $inst_version, 'le')) { + // installed version is up-to-date + continue; + } + $deppackages[] = $info['name']; + } // foreach($alldeps + } // foreach($willinstall + + if (count($deppackages)) { + // check dependencies' dependencies + // combine the list of packages to install + $temppack = array(); + foreach($installpackages as $p) { + $temppack[] = strtolower($p['info']['package']); + } + foreach($deppackages as $pack) { + $temppack[] = strtolower($pack); + } + $willinstall = array_merge($willinstall, $temppack); + $this->download($deppackages, $options, $config, $installpackages, + $errors, $installed, $willinstall, $state); + } + } // if --alldeps or --onlyreqdeps + } + // }}} // {{{ install() /** @@ -497,42 +725,6 @@ class PEAR_Installer extends PEAR_Common $need_download = false; // ==> XXX should be removed later on $flag_old_format = false; - if (preg_match('#^(http|ftp)://#', $pkgfile)) { - $need_download = true; - } elseif (!@is_file($pkgfile)) { - if ($this->validPackageName($pkgfile)) { - if ($this->registry->packageExists($pkgfile) && - empty($options['upgrade']) && empty($options['force'])) - { - return $this->raiseError("$pkgfile already installed"); - } - $pkgfile = $this->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 = $this->config->get('download_dir'); - if (empty($downloaddir)) { - if (PEAR::isError($downloaddir = System::mktemp('-d'))) { - return $downloaddir; - } - $this->log(2, '+ tmp dir created at ' . $downloaddir); - } - $callback = $this->ui ? array(&$this, '_downloadCallback') : null; - $file = $this->downloadHttp($pkgfile, $this->ui, $downloaddir, $callback); - if (PEAR::isError($file)) { - return $this->raiseError($file); - } - $pkgfile = $file; - } if (substr($pkgfile, -4) == '.xml') { $descfile = $pkgfile; @@ -644,19 +836,21 @@ class PEAR_Installer extends PEAR_Common } } else { // checks to do only when upgrading packages - if (!$this->registry->packageExists($pkgname)) { +/* if (!$this->registry->packageExists($pkgname)) { return $this->raiseError("$pkgname not installed"); - } - $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); + }*/ + 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); + } } } } @@ -750,7 +944,12 @@ class PEAR_Installer extends PEAR_Common } $ret = $this->registry->addPackage($pkgname, $pkginfo); } else { - $ret = $this->registry->updatePackage($pkgname, $pkginfo, false); + // 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 null; diff --git a/pear/package-PEAR.xml b/pear/package-PEAR.xml index 37fc9111b1..d2a1275f6c 100644 --- a/pear/package-PEAR.xml +++ b/pear/package-PEAR.xml @@ -37,15 +37,15 @@ </maintainer> <maintainer> <user>cellog</user> - <role>helper</role> + <role>developer</role> <name>Greg Beaver</name> <email>cellog@php.net</email> </maintainer> </maintainers> <release> - <version>1.2.2</version> - <date>2003-08-25</date> - <state>stable</state> + <version>1.3b1</version> + <date>2003-08-27</date> + <state>alpha</state> <notes> * Fixed #25131 - OS_Guess warnings on empty lines from popen("/usr/bin/cpp $tmpfile", "r"); @@ -54,6 +54,12 @@ * Added ability to use a static method callback for error-handling, and removed use of inadvisable @ in setErrorHandling (Greg) * Added dependency on XML_RPC, and optional dependency on xmlrpc extension (Greg) +* Added --alldeps and --onlyreqdeps options to pear install/pear upgrade (Greg) +* Sorting of installation/uninstallation so package order on the command-line is + insignificant (fixes upgrade-all if every package is installed) (Greg) +* pear upgrade will now install if the package is not installed (necessary for + pear upgrade --alldeps, as installation is often necessary for new + dependencies) (Greg) </notes> <provides type="class" name="OS_Guess" /> <provides type="class" name="System" /> |