3  * PEAR_DependencyDB, advanced installed packages dependency database
 
   9  * @author     Tomas V. V. Cox <cox@idecnet.com>
 
  10  * @author     Greg Beaver <cellog@php.net>
 
  11  * @copyright  1997-2009 The Authors
 
  12  * @license    http://opensource.org/licenses/bsd-license.php New BSD License
 
  13  * @link       http://pear.php.net/package/PEAR
 
  14  * @since      File available since Release 1.4.0a1
 
  18  * Needed for error handling
 
  20 require_once 'PEAR.php';
 
  21 require_once 'PEAR/Config.php';
 
  23 $GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'] = array();
 
  25  * Track dependency relationships between installed packages
 
  28  * @author     Greg Beaver <cellog@php.net>
 
  29  * @author     Tomas V.V.Cox <cox@idec.net.com>
 
  30  * @copyright  1997-2009 The Authors
 
  31  * @license    http://opensource.org/licenses/bsd-license.php New BSD License
 
  32  * @version    Release: 1.10.1
 
  33  * @link       http://pear.php.net/package/PEAR
 
  34  * @since      Class available since Release 1.4.0a1
 
  36 class PEAR_DependencyDB
 
  41      * This is initialized by {@link setConfig()}
 
  47      * This is initialized by {@link setConfig()}
 
  53      * Filename of the dependency DB (usually .depdb)
 
  59      * File name of the lockfile (usually .depdblock)
 
  63     var $_lockfile = false;
 
  65      * Open file resource for locking the lockfile
 
  71      * API version of this class, used to validate a file on-disk
 
  75     var $_version = '1.0';
 
  77      * Cached dependency database file
 
  87      * Get a raw dependency database.  Calls setConfig() and assertDepsDB()
 
  89      * @param string|false full path to the dependency database, or false to use default
 
  90      * @return PEAR_DependencyDB|PEAR_Error
 
  92     public static function &singleton(&$config, $depdb = false)
 
  94         $phpdir = $config->get('php_dir', null, 'pear.php.net');
 
  95         if (!isset($GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'][$phpdir])) {
 
  96             $a = new PEAR_DependencyDB;
 
  97             $GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'][$phpdir] = &$a;
 
  98             $a->setConfig($config, $depdb);
 
  99             $e = $a->assertDepsDB();
 
 100             if (PEAR::isError($e)) {
 
 105         return $GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'][$phpdir];
 
 109      * Set up the registry/location of dependency DB
 
 110      * @param PEAR_Config|false
 
 111      * @param string|false full path to the dependency database, or false to use default
 
 113     function setConfig(&$config, $depdb = false)
 
 116             $this->_config = &PEAR_Config::singleton();
 
 118             $this->_config = &$config;
 
 121         $this->_registry = &$this->_config->getRegistry();
 
 123             $dir = $this->_config->get('metadata_dir', null, 'pear.php.net');
 
 125                 $dir = $this->_config->get('php_dir', null, 'pear.php.net');
 
 127             $this->_depdb =  $dir . DIRECTORY_SEPARATOR . '.depdb';
 
 129             $this->_depdb = $depdb;
 
 132         $this->_lockfile = dirname($this->_depdb) . DIRECTORY_SEPARATOR . '.depdblock';
 
 136     function hasWriteAccess()
 
 138         if (!file_exists($this->_depdb)) {
 
 139             $dir = $this->_depdb;
 
 140             while ($dir && $dir != '.') {
 
 141                 $dir = dirname($dir); // cd ..
 
 142                 if ($dir != '.' && file_exists($dir)) {
 
 143                     if (is_writeable($dir)) {
 
 154         return is_writeable($this->_depdb);
 
 157     // {{{ assertDepsDB()
 
 160      * Create the dependency database, if it doesn't exist.  Error if the database is
 
 161      * newer than the code reading it.
 
 162      * @return void|PEAR_Error
 
 164     function assertDepsDB()
 
 166         if (!is_file($this->_depdb)) {
 
 171         $depdb = $this->_getDepDB();
 
 172         // Datatype format has been changed, rebuild the Deps DB
 
 173         if ($depdb['_version'] < $this->_version) {
 
 177         if ($depdb['_version']{0} > $this->_version{0}) {
 
 178             return PEAR::raiseError('Dependency database is version ' .
 
 179                 $depdb['_version'] . ', and we are version ' .
 
 180                 $this->_version . ', cannot continue');
 
 185      * Get a list of installed packages that depend on this package
 
 186      * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array
 
 187      * @return array|false
 
 189     function getDependentPackages(&$pkg)
 
 191         $data = $this->_getDepDB();
 
 192         if (is_object($pkg)) {
 
 193             $channel = strtolower($pkg->getChannel());
 
 194             $package = strtolower($pkg->getPackage());
 
 196             $channel = strtolower($pkg['channel']);
 
 197             $package = strtolower($pkg['package']);
 
 200         if (isset($data['packages'][$channel][$package])) {
 
 201             return $data['packages'][$channel][$package];
 
 208      * Get a list of the actual dependencies of installed packages that depend on
 
 210      * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array
 
 211      * @return array|false
 
 213     function getDependentPackageDependencies(&$pkg)
 
 215         $data = $this->_getDepDB();
 
 216         if (is_object($pkg)) {
 
 217             $channel = strtolower($pkg->getChannel());
 
 218             $package = strtolower($pkg->getPackage());
 
 220             $channel = strtolower($pkg['channel']);
 
 221             $package = strtolower($pkg['package']);
 
 224         $depend = $this->getDependentPackages($pkg);
 
 229         $dependencies = array();
 
 230         foreach ($depend as $info) {
 
 231             $temp = $this->getDependencies($info);
 
 232             foreach ($temp as $dep) {
 
 234                     isset($dep['dep'], $dep['dep']['channel'], $dep['dep']['name']) &&
 
 235                     strtolower($dep['dep']['channel']) == $channel &&
 
 236                     strtolower($dep['dep']['name']) == $package
 
 238                     if (!isset($dependencies[$info['channel']])) {
 
 239                         $dependencies[$info['channel']] = array();
 
 242                     if (!isset($dependencies[$info['channel']][$info['package']])) {
 
 243                         $dependencies[$info['channel']][$info['package']] = array();
 
 245                     $dependencies[$info['channel']][$info['package']][] = $dep;
 
 250         return $dependencies;
 
 254      * Get a list of dependencies of this installed package
 
 255      * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array
 
 256      * @return array|false
 
 258     function getDependencies(&$pkg)
 
 260         if (is_object($pkg)) {
 
 261             $channel = strtolower($pkg->getChannel());
 
 262             $package = strtolower($pkg->getPackage());
 
 264             $channel = strtolower($pkg['channel']);
 
 265             $package = strtolower($pkg['package']);
 
 268         $data = $this->_getDepDB();
 
 269         if (isset($data['dependencies'][$channel][$package])) {
 
 270             return $data['dependencies'][$channel][$package];
 
 277      * Determine whether $parent depends on $child, near or deep
 
 278      * @param array|PEAR_PackageFile_v2|PEAR_PackageFile_v2
 
 279      * @param array|PEAR_PackageFile_v2|PEAR_PackageFile_v2
 
 281     function dependsOn($parent, $child)
 
 285         return $this->_dependsOn($parent, $child, $c);
 
 288     function _dependsOn($parent, $child, &$checked)
 
 290         if (is_object($parent)) {
 
 291             $channel = strtolower($parent->getChannel());
 
 292             $package = strtolower($parent->getPackage());
 
 294             $channel = strtolower($parent['channel']);
 
 295             $package = strtolower($parent['package']);
 
 298         if (is_object($child)) {
 
 299             $depchannel = strtolower($child->getChannel());
 
 300             $deppackage = strtolower($child->getPackage());
 
 302             $depchannel = strtolower($child['channel']);
 
 303             $deppackage = strtolower($child['package']);
 
 306         if (isset($checked[$channel][$package][$depchannel][$deppackage])) {
 
 307             return false; // avoid endless recursion
 
 310         $checked[$channel][$package][$depchannel][$deppackage] = true;
 
 311         if (!isset($this->_cache['dependencies'][$channel][$package])) {
 
 315         foreach ($this->_cache['dependencies'][$channel][$package] as $info) {
 
 316             if (isset($info['dep']['uri'])) {
 
 317                 if (is_object($child)) {
 
 318                     if ($info['dep']['uri'] == $child->getURI()) {
 
 321                 } elseif (isset($child['uri'])) {
 
 322                     if ($info['dep']['uri'] == $child['uri']) {
 
 329             if (strtolower($info['dep']['channel']) == $depchannel &&
 
 330                   strtolower($info['dep']['name']) == $deppackage) {
 
 335         foreach ($this->_cache['dependencies'][$channel][$package] as $info) {
 
 336             if (isset($info['dep']['uri'])) {
 
 337                 if ($this->_dependsOn(array(
 
 338                         'uri' => $info['dep']['uri'],
 
 339                         'package' => $info['dep']['name']), $child, $checked)) {
 
 343                 if ($this->_dependsOn(array(
 
 344                         'channel' => $info['dep']['channel'],
 
 345                         'package' => $info['dep']['name']), $child, $checked)) {
 
 355      * Register dependencies of a package that is being installed or upgraded
 
 356      * @param PEAR_PackageFile_v2|PEAR_PackageFile_v2
 
 358     function installPackage(&$package)
 
 360         $data = $this->_getDepDB();
 
 361         unset($this->_cache);
 
 362         $this->_setPackageDeps($data, $package);
 
 363         $this->_writeDepDB($data);
 
 367      * Remove dependencies of a package that is being uninstalled, or upgraded.
 
 369      * Upgraded packages first uninstall, then install
 
 370      * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array If an array, then it must have
 
 371      *        indices 'channel' and 'package'
 
 373     function uninstallPackage(&$pkg)
 
 375         $data = $this->_getDepDB();
 
 376         unset($this->_cache);
 
 377         if (is_object($pkg)) {
 
 378             $channel = strtolower($pkg->getChannel());
 
 379             $package = strtolower($pkg->getPackage());
 
 381             $channel = strtolower($pkg['channel']);
 
 382             $package = strtolower($pkg['package']);
 
 385         if (!isset($data['dependencies'][$channel][$package])) {
 
 389         foreach ($data['dependencies'][$channel][$package] as $dep) {
 
 391             $depchannel = isset($dep['dep']['uri']) ? '__uri' : strtolower($dep['dep']['channel']);
 
 392             $depname    = strtolower($dep['dep']['name']);
 
 393             if (isset($data['packages'][$depchannel][$depname])) {
 
 394                 foreach ($data['packages'][$depchannel][$depname] as $i => $info) {
 
 395                     if ($info['channel'] == $channel && $info['package'] == $package) {
 
 403                 unset($data['packages'][$depchannel][$depname][$i]);
 
 404                 if (!count($data['packages'][$depchannel][$depname])) {
 
 405                     unset($data['packages'][$depchannel][$depname]);
 
 406                     if (!count($data['packages'][$depchannel])) {
 
 407                         unset($data['packages'][$depchannel]);
 
 410                     $data['packages'][$depchannel][$depname] =
 
 411                         array_values($data['packages'][$depchannel][$depname]);
 
 416         unset($data['dependencies'][$channel][$package]);
 
 417         if (!count($data['dependencies'][$channel])) {
 
 418             unset($data['dependencies'][$channel]);
 
 421         if (!count($data['dependencies'])) {
 
 422             unset($data['dependencies']);
 
 425         if (!count($data['packages'])) {
 
 426             unset($data['packages']);
 
 429         $this->_writeDepDB($data);
 
 433      * Rebuild the dependency DB by reading registry entries.
 
 434      * @return true|PEAR_Error
 
 438         $depdb = array('_version' => $this->_version);
 
 439         if (!$this->hasWriteAccess()) {
 
 440             // allow startup for read-only with older Registry
 
 444         $packages = $this->_registry->listAllPackages();
 
 445         if (PEAR::isError($packages)) {
 
 449         foreach ($packages as $channel => $ps) {
 
 450             foreach ($ps as $package) {
 
 451                 $package = $this->_registry->getPackage($package, $channel);
 
 452                 if (PEAR::isError($package)) {
 
 455                 $this->_setPackageDeps($depdb, $package);
 
 459         $error = $this->_writeDepDB($depdb);
 
 460         if (PEAR::isError($error)) {
 
 464         $this->_cache = $depdb;
 
 469      * Register usage of the dependency DB to prevent race conditions
 
 470      * @param int one of the LOCK_* constants
 
 471      * @return true|PEAR_Error
 
 474     function _lock($mode = LOCK_EX)
 
 476         if (stristr(php_uname(), 'Windows 9')) {
 
 480         if ($mode != LOCK_UN && is_resource($this->_lockFp)) {
 
 481             // XXX does not check type of lock (LOCK_SH/LOCK_EX)
 
 486         // XXX People reported problems with LOCK_SH and 'w'
 
 487         if ($mode === LOCK_SH) {
 
 488             if (!file_exists($this->_lockfile)) {
 
 489                 touch($this->_lockfile);
 
 490             } elseif (!is_file($this->_lockfile)) {
 
 491                 return PEAR::raiseError('could not create Dependency lock file, ' .
 
 492                     'it exists and is not a regular file');
 
 497         if (!is_resource($this->_lockFp)) {
 
 498             $this->_lockFp = @fopen($this->_lockfile, $open_mode);
 
 501         if (!is_resource($this->_lockFp)) {
 
 502             return PEAR::raiseError("could not create Dependency lock file" .
 
 503                                      (isset($php_errormsg) ? ": " . $php_errormsg : ""));
 
 506         if (!(int)flock($this->_lockFp, $mode)) {
 
 508                 case LOCK_SH: $str = 'shared';    break;
 
 509                 case LOCK_EX: $str = 'exclusive'; break;
 
 510                 case LOCK_UN: $str = 'unlock';    break;
 
 511                 default:      $str = 'unknown';   break;
 
 514             return PEAR::raiseError("could not acquire $str lock ($this->_lockfile)");
 
 521      * Release usage of dependency DB
 
 522      * @return true|PEAR_Error
 
 527         $ret = $this->_lock(LOCK_UN);
 
 528         if (is_resource($this->_lockFp)) {
 
 529             fclose($this->_lockFp);
 
 531         $this->_lockFp = null;
 
 536      * Load the dependency database from disk, or return the cache
 
 537      * @return array|PEAR_Error
 
 541         if (!$this->hasWriteAccess()) {
 
 542             return array('_version' => $this->_version);
 
 545         if (isset($this->_cache)) {
 
 546             return $this->_cache;
 
 549         if (!$fp = fopen($this->_depdb, 'r')) {
 
 550             $err = PEAR::raiseError("Could not open dependencies file `".$this->_depdb."'");
 
 556         $data = unserialize(file_get_contents($this->_depdb));
 
 557         $this->_cache = $data;
 
 562      * Write out the dependency database to disk
 
 563      * @param array the database
 
 564      * @return true|PEAR_Error
 
 567     function _writeDepDB(&$deps)
 
 569         if (PEAR::isError($e = $this->_lock(LOCK_EX))) {
 
 573         if (!$fp = fopen($this->_depdb, 'wb')) {
 
 575             return PEAR::raiseError("Could not open dependencies file `".$this->_depdb."' for writing");
 
 578         fwrite($fp, serialize($deps));
 
 581         $this->_cache = $deps;
 
 586      * Register all dependencies from a package in the dependencies database, in essence
 
 587      * "installing" the package's dependency information
 
 588      * @param array the database
 
 589      * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
 
 592     function _setPackageDeps(&$data, &$pkg)
 
 594         $pkg->setConfig($this->_config);
 
 595         if ($pkg->getPackagexmlVersion() == '1.0') {
 
 596             $gen = &$pkg->getDefaultGenerator();
 
 597             $deps = $gen->dependenciesToV2();
 
 599             $deps = $pkg->getDeps(true);
 
 606         if (!is_array($data)) {
 
 610         if (!isset($data['dependencies'])) {
 
 611             $data['dependencies'] = array();
 
 614         $channel = strtolower($pkg->getChannel());
 
 615         $package = strtolower($pkg->getPackage());
 
 617         if (!isset($data['dependencies'][$channel])) {
 
 618             $data['dependencies'][$channel] = array();
 
 621         $data['dependencies'][$channel][$package] = array();
 
 622         if (isset($deps['required']['package'])) {
 
 623             if (!isset($deps['required']['package'][0])) {
 
 624                 $deps['required']['package'] = array($deps['required']['package']);
 
 627             foreach ($deps['required']['package'] as $dep) {
 
 628                 $this->_registerDep($data, $pkg, $dep, 'required');
 
 632         if (isset($deps['optional']['package'])) {
 
 633             if (!isset($deps['optional']['package'][0])) {
 
 634                 $deps['optional']['package'] = array($deps['optional']['package']);
 
 637             foreach ($deps['optional']['package'] as $dep) {
 
 638                 $this->_registerDep($data, $pkg, $dep, 'optional');
 
 642         if (isset($deps['required']['subpackage'])) {
 
 643             if (!isset($deps['required']['subpackage'][0])) {
 
 644                 $deps['required']['subpackage'] = array($deps['required']['subpackage']);
 
 647             foreach ($deps['required']['subpackage'] as $dep) {
 
 648                 $this->_registerDep($data, $pkg, $dep, 'required');
 
 652         if (isset($deps['optional']['subpackage'])) {
 
 653             if (!isset($deps['optional']['subpackage'][0])) {
 
 654                 $deps['optional']['subpackage'] = array($deps['optional']['subpackage']);
 
 657             foreach ($deps['optional']['subpackage'] as $dep) {
 
 658                 $this->_registerDep($data, $pkg, $dep, 'optional');
 
 662         if (isset($deps['group'])) {
 
 663             if (!isset($deps['group'][0])) {
 
 664                 $deps['group'] = array($deps['group']);
 
 667             foreach ($deps['group'] as $group) {
 
 668                 if (isset($group['package'])) {
 
 669                     if (!isset($group['package'][0])) {
 
 670                         $group['package'] = array($group['package']);
 
 673                     foreach ($group['package'] as $dep) {
 
 674                         $this->_registerDep($data, $pkg, $dep, 'optional',
 
 675                             $group['attribs']['name']);
 
 679                 if (isset($group['subpackage'])) {
 
 680                     if (!isset($group['subpackage'][0])) {
 
 681                         $group['subpackage'] = array($group['subpackage']);
 
 684                     foreach ($group['subpackage'] as $dep) {
 
 685                         $this->_registerDep($data, $pkg, $dep, 'optional',
 
 686                             $group['attribs']['name']);
 
 692         if ($data['dependencies'][$channel][$package] == array()) {
 
 693             unset($data['dependencies'][$channel][$package]);
 
 694             if (!count($data['dependencies'][$channel])) {
 
 695                 unset($data['dependencies'][$channel]);
 
 701      * @param array the database
 
 702      * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
 
 703      * @param array the specific dependency
 
 704      * @param required|optional whether this is a required or an optional dep
 
 705      * @param string|false dependency group this dependency is from, or false for ordinary dep
 
 707     function _registerDep(&$data, &$pkg, $dep, $type, $group = false)
 
 715         $dep  = array_map('strtolower', $dep);
 
 716         $depchannel = isset($dep['channel']) ? $dep['channel'] : '__uri';
 
 717         if (!isset($data['dependencies'])) {
 
 718             $data['dependencies'] = array();
 
 721         $channel = strtolower($pkg->getChannel());
 
 722         $package = strtolower($pkg->getPackage());
 
 724         if (!isset($data['dependencies'][$channel])) {
 
 725             $data['dependencies'][$channel] = array();
 
 728         if (!isset($data['dependencies'][$channel][$package])) {
 
 729             $data['dependencies'][$channel][$package] = array();
 
 732         $data['dependencies'][$channel][$package][] = $info;
 
 733         if (isset($data['packages'][$depchannel][$dep['name']])) {
 
 735             foreach ($data['packages'][$depchannel][$dep['name']] as $i => $p) {
 
 736                 if ($p['channel'] == $channel && $p['package'] == $package) {
 
 742             if (!isset($data['packages'])) {
 
 743                 $data['packages'] = array();
 
 746             if (!isset($data['packages'][$depchannel])) {
 
 747                 $data['packages'][$depchannel] = array();
 
 750             if (!isset($data['packages'][$depchannel][$dep['name']])) {
 
 751                 $data['packages'][$depchannel][$dep['name']] = array();
 
 758             $data['packages'][$depchannel][$dep['name']][] = array(
 
 759                 'channel' => $channel,
 
 760                 'package' => $package