Source for file Installer.php
Documentation is available at Installer.php
* @author Stig Bakken <ssb@php.net>
* @author Tomas V.V. Cox <cox@idecnet.com>
* @author Martin Jansen <mj@php.net>
* @author Greg Beaver <cellog@php.net>
* @copyright 1997-2009 The Authors
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @version CVS: $Id: Installer.php 313024 2011-07-06 19:51:24Z dufuz $
* @link http://pear.php.net/package/PEAR
* @since File available since Release 0.1
* Used for installation groups in package.xml 2.0 and platform exceptions
require_once 'OS/Guess.php';
require_once 'PEAR/Downloader.php';
define('PEAR_INSTALLER_NOBINARY', -240 );
* Administration class used to install PEAR packages and maintain the
* installed package database.
* @author Stig Bakken <ssb@php.net>
* @author Tomas V.V. Cox <cox@idecnet.com>
* @author Martin Jansen <mj@php.net>
* @author Greg Beaver <cellog@php.net>
* @copyright 1997-2009 The Authors
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @version Release: 1.9.4
* @link http://pear.php.net/package/PEAR
* @since Class available since Release 0.1
/** name of the package directory, for example Foo-1.0
/** directory where PHP code files go
/** directory where PHP extension files go
/** directory where documentation goes
/** installation root directory (ala PHP's INSTALL_ROOT or
* PEAR_Registry object used by the installer
* array of PEAR_Downloader_Packages
var $_downloadedPackages;
/** List of file transactions queued for an install/upgrade/uninstall.
* 0 => array("rename => array("from-file", "to-file")),
* 1 => array("delete" => array("file-to-delete")),
* PEAR_Installer constructor.
* @param object $ui user interface object (instance of PEAR_Frontend_*)
$this->_options = $options;
$this->_registry = &$config->getRegistry ();
function _removeBackups ($files)
foreach ($files as $path) {
// {{{ _deletePackageFiles()
* Delete a package's installed files, does not remove empty directories.
* @param string package name
* @param string channel name
* @param bool if true, then files are backed up first
* @return bool TRUE on success, or a PEAR error on failure
$channel = 'pear.php.net';
return $this->raiseError("No package to uninstall given");
if (strtolower($package) == 'pear' && $channel == 'pear.php.net') {
// to avoid race conditions, include all possible needed files
require_once 'PEAR/Task/Common.php';
require_once 'PEAR/Task/Replace.php';
require_once 'PEAR/Task/Unixeol.php';
require_once 'PEAR/Task/Windowseol.php';
require_once 'PEAR/PackageFile/v1.php';
require_once 'PEAR/PackageFile/v2.php';
require_once 'PEAR/PackageFile/Generator/v1.php';
require_once 'PEAR/PackageFile/Generator/v2.php';
$filelist = $this->_registry->packageInfo ($package, 'filelist', $channel);
return $this->raiseError(" $channel/$package not installed" );
foreach ($filelist as $file => $props) {
if (empty ($props['installed_as'])) {
$path = $props['installed_as'];
* @param array attributes from <file> tag in package.xml
* @param string path to install the file in
* @param array options from command-line
function _installFile ($file, $atts, $tmp_path, $options)
// {{{ return if this file is meant for another platform
if (!isset ($this->_registry)) {
$this->_registry = &$this->config->getRegistry ();
if (isset ($atts['platform'])) {
if (strlen($atts['platform']) && $atts['platform']{0 } == '!') {
$platform = substr($atts['platform'], 1 );
$platform = $atts['platform'];
if ((bool) $os->matchSignature ($platform) === $negate) {
$this->log(3 , " skipped $file (meant for $atts[platform], we are ". $os->getSignature (). ")");
$channel = $this->pkginfo->getChannel ();
// {{{ assemble the destination paths
$dest_dir = $this->config->get ($atts['role'] . '_dir', null , $channel) .
DIRECTORY_SEPARATOR . $this->pkginfo->getPackage ();
unset ($atts['baseinstalldir']);
$dest_dir = $this->config->get ($atts['role'] . '_dir', null , $channel);
$dest_dir = $this->config->get ('bin_dir', null , $channel);
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);
$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+!" ),
array (DIRECTORY_SEPARATOR ,
array ($dest_file, $orig_file));
$final_dest_file = $installed_as = $dest_file;
if (isset ($this->_options['packagingroot'])) {
$installedas_dest_dir = dirname($final_dest_file);
$installedas_dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
$final_dest_file = $this->_prependPath ($final_dest_file, $this->_options['packagingroot']);
$installedas_dest_dir = dirname($final_dest_file);
$installedas_dest_file = $installedas_dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
$dest_dir = dirname($final_dest_file);
$dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
if (empty ($this->_options['register-only']) &&
return $this->raiseError(" failed to mkdir $dest_dir" ,
$this->log(3 , " + mkdir $dest_dir" );
// pretty much nothing happens if we are only registering the install
if (empty ($this->_options['register-only'])) {
if (empty ($atts['replacements'])) {
return $this->raiseError(" file $orig_file does not exist" ,
if (!@copy($orig_file, $dest_file)) {
return $this->raiseError(" failed to write $dest_file: $php_errormsg" ,
$this->log(3 , " + cp $orig_file $dest_file" );
if (isset ($atts['md5sum'])) {
// {{{ file with replacements
if ($contents === false ) {
if (isset ($atts['md5sum'])) {
$md5sum = md5($contents);
$subst_from = $subst_to = array ();
foreach ($atts['replacements'] as $a) {
if ($a['type'] == 'php-const') {
if (!isset ($options['soft'])) {
$this->log(0 , " invalid php-const replacement: $a[to]" );
} elseif ($a['type'] == 'pear-config') {
if ($a['to'] == 'master_server') {
$chan = $this->_registry->getChannel ($channel);
$to = $chan->getServer ();
$to = $this->config->get ($a['to'], null , $channel);
$to = $this->config->get ($a['to'], null , $channel);
if (!isset ($options['soft'])) {
$this->log(0 , " invalid pear-config replacement: $a[to]" );
} elseif ($a['type'] == 'package-info') {
if ($t = $this->pkginfo->packageInfo ($a['to'])) {
if (!isset ($options['soft'])) {
$this->log(0 , " invalid package-info replacement: $a[to]" );
$subst_from[] = $a['from'];
$this->log(3 , "doing ". sizeof($subst_from)." substitution(s) for $final_dest_file" );
$contents = str_replace($subst_from, $subst_to, $contents);
$wp = @fopen($dest_file, "wb");
return $this->raiseError(" failed to create $dest_file: $php_errormsg" ,
if (@fwrite($wp, $contents) === false ) {
return $this->raiseError(" failed writing to $dest_file: $php_errormsg" ,
$this->log(2 , " md5sum ok: $final_dest_file" );
if (empty ($options['force'])) {
if (!isset ($options['ignore-errors'])) {
return $this->raiseError(" bad md5sum for file $final_dest_file" ,
if (!isset ($options['soft'])) {
$this->log(0 , " warning : bad md5sum for file $final_dest_file" );
if (!isset ($options['soft'])) {
$this->log(0 , " warning : bad md5sum for file $final_dest_file" );
// {{{ set file permissions
if ($atts['role'] == 'script') {
$this->log(3 , " + chmod +x $dest_file" );
if ($atts['role'] != 'src') {
if (!@chmod($dest_file, $mode)) {
if (!isset ($options['soft'])) {
$this->log(0 , " failed to change mode of $dest_file: $php_errormsg" );
if ($atts['role'] == 'src') {
rename($dest_file, $final_dest_file);
$this->log(2 , " renamed source file $dest_file to $final_dest_file" );
$atts['role'] == 'ext'));
// Store the full path where the file was installed for easy unistall
if ($atts['role'] != 'script') {
$loc = $this->config->get ($atts['role'] . '_dir');
$loc = $this->config->get ('bin_dir');
if ($atts['role'] != 'src') {
//$this->log(2, "installed: $dest_file");
* @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
* @param array attributes from <file> tag in package.xml
* @param string path to install the file in
* @param array options from command-line
function _installFile2 (&$pkg, $file, &$real_atts, $tmp_path, $options)
if (!isset ($this->_registry)) {
$this->_registry = &$this->config->getRegistry ();
$channel = $pkg->getChannel ();
// {{{ assemble the destination paths
return $this->raiseError('Invalid role `' . $atts['attribs']['role'] .
$err = $role->setup ($this, $pkg, $atts['attribs'], $file);
if (!$role->isInstallable ()) {
$info = $role->processInstallation ($pkg, $atts['attribs'], $file, $tmp_path);
list ($save_destdir, $dest_dir, $dest_file, $orig_file) = $info;
$final_dest_file = $installed_as = $dest_file;
if (isset ($this->_options['packagingroot'])) {
$final_dest_file = $this->_prependPath ($final_dest_file,
$this->_options['packagingroot']);
$dest_dir = dirname($final_dest_file);
$dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
if (empty ($this->_options['register-only'])) {
return $this->raiseError(" failed to mkdir $dest_dir" ,
$this->log(3 , " + mkdir $dest_dir" );
$attribs = $atts['attribs'];
// pretty much nothing happens if we are only registering the install
if (empty ($this->_options['register-only'])) {
if (!count($atts)) { // no tasks
return $this->raiseError(" file $orig_file does not exist" ,
if (!@copy($orig_file, $dest_file)) {
return $this->raiseError(" failed to write $dest_file: $php_errormsg" ,
$this->log(3 , " + cp $orig_file $dest_file" );
if (isset ($attribs['md5sum'])) {
} else { // file with tasks
return $this->raiseError(" file $orig_file does not exist" ,
if ($contents === false ) {
if (isset ($attribs['md5sum'])) {
$md5sum = md5($contents);
foreach ($atts as $tag => $raw) {
$tag = str_replace(array ($pkg->getTasksNs () . ':', '-'), array ('', '_'), $tag);
$task = " PEAR_Task_$tag";
if (!$task->isScript ()) { // scripts are only handled after installation
$task->init ($raw, $attribs, $pkg->getLastInstalledVersion ());
$res = $task->startSession ($pkg, $contents, $final_dest_file);
continue; // skip this file
$contents = $res; // save changes
$wp = @fopen($dest_file, "wb");
return $this->raiseError(" failed to create $dest_file: $php_errormsg" ,
if (fwrite($wp, $contents) === false ) {
return $this->raiseError(" failed writing to $dest_file: $php_errormsg" ,
// Make sure the original md5 sum matches with expected
$this->log(2 , " md5sum ok: $final_dest_file" );
// set md5 sum based on $content in case any tasks were run.
$real_atts['attribs']['md5sum'] = md5($contents);
if (empty ($options['force'])) {
if (!isset ($options['ignore-errors'])) {
return $this->raiseError(" bad md5sum for file $final_dest_file" ,
if (!isset ($options['soft'])) {
$this->log(0 , " warning : bad md5sum for file $final_dest_file" );
if (!isset ($options['soft'])) {
$this->log(0 , " warning : bad md5sum for file $final_dest_file" );
$real_atts['attribs']['md5sum'] = md5_file($dest_file);
// {{{ set file permissions
if ($role->isExecutable ()) {
$this->log(3 , " + chmod +x $dest_file" );
if ($attribs['role'] != 'src') {
if (!@chmod($dest_file, $mode)) {
if (!isset ($options['soft'])) {
$this->log(0 , " failed to change mode of $dest_file: $php_errormsg" );
if ($attribs['role'] == 'src') {
rename($dest_file, $final_dest_file);
$this->log(2 , " renamed source file $dest_file to $final_dest_file" );
$this->addFileOperation("rename", array ($dest_file, $final_dest_file, $role->isExtension ()));
// Store the full path where the file was installed for easy uninstall
if ($attribs['role'] != 'src') {
$loc = $this->config->get ($role->getLocationConfig (), null , $channel);
//$this->log(2, "installed: $dest_file");
// {{{ addFileOperation()
* Add a file operation to the current file transaction.
* @see startFileTransaction()
* @param string $type This can be one of:
* - rename: rename a file ($data has 3 values)
* - backup: backup an existing file ($data has 1 value)
* - removebackup: clean up backups created during install ($data has 1 value)
* - 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).
* @param 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 third whether this is a PHP extension.
* 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
* 4. Relative path from the php_dir that this file is installed in
return $this->raiseError('Internal Error: $data in addFileOperation'
. ' must be an array, was ' . gettype($data));
$this->log(3 , " adding to transaction: $type $octmode $data[1]" );
$this->log(3 , " adding to transaction: $type " . implode(" ", $data));
// {{{ startFileTransaction()
// {{{ commitFileTransaction()
// {{{ first, check permissions and such manually
list ($type, $data) = $tr;
$errors[] = " cannot rename file $data[0], doesn't exist";
// check that dest dir. is writable
$errors[] = " permission denied ($type): $data[1]";
// check that file is writable
$errors[] = " permission denied ($type): $data[1] " . decoct($data[0 ]);
$this->log(2 , " warning: file $data[0] doesn't exist, can't be deleted" );
// check that directory is writable
$errors[] = " permission denied ($type): $data[0]";
// make sure the file to be deleted can be opened for writing
$errors[] = " permission denied ($type): $data[0]";
/* Verify we are not deleting a file owned by another package
* This can happen when a file moves from package A to B in
* an upgrade ala http://pear.php.net/17986
$result = $this->_registry->checkFileMap ($data[0 ], $info, '1.1');
$new = $this->_registry->getPackage ($result[1 ], $result[0 ]);
$this->log(3 , " file $data[0] was scheduled for removal from {$this->pkginfo->getName ()} but is owned by { $new->getChannel ()}/{ $new->getName ()}, removal has been cancelled." );
$this->log(2 , " about to commit $n file operations for " . $this->pkginfo->getName ());
foreach ($errors as $error) {
if (!isset ($this->_options['soft'])) {
if (!isset ($this->_options['ignore-errors'])) {
$this->_dirtree = array ();
// {{{ really commit the transaction
// support removal of non-existing backups
list ($type, $data) = $tr;
if (!@copy($data[0 ], $data[0 ] . '.bak')) {
$this->log(1 , 'Could not copy ' . $data[0 ] . ' to ' . $data[0 ] .
'.bak ' . $php_errormsg);
$this->log(3 , " + backup $data[0] to $data[0].bak" );
$this->log(3 , " + rm backup of $data[0] ($data[0].bak)" );
$extra = ', this extension must be installed manually. Rename to "' .
if (!isset ($this->_options['soft'])) {
$this->log(1 , 'Could not delete ' . $data[1 ] . ', cannot rename ' .
if (!isset ($this->_options['ignore-errors'])) {
// permissions issues with rename - copy() is far superior
if (!@copy($data[0 ], $data[1 ])) {
$this->log(1 , 'Could not rename ' . $data[0 ] . ' to ' . $data[1 ] .
// copy over permissions, otherwise they are lost
@chmod($data[1 ], $perms);
$this->log(3 , " + mv $data[0] $data[1]" );
if (!@chmod($data[1 ], $data[0 ])) {
$this->log(1 , 'Could not chmod ' . $data[1 ] . ' to ' .
decoct($data[0 ]) . ' ' . $php_errormsg);
$this->log(3 , " + chmod $octmode $data[1]" );
$this->log(1 , 'Could not delete ' . $data[0 ] . ' ' .
$this->log(3 , " + rm $data[0]" );
while (false !== ($entry = readdir($testme))) {
if ($entry == '.' || $entry == '..') {
break 2; // this directory is not empty and can't be
$this->log(1 , 'Could not rmdir ' . $data[0 ] . ' ' .
$this->log(3 , " + rmdir $data[0]" );
$this->pkginfo->setInstalledAs ($data[0 ], $data[1 ]);
if (!isset ($this->_dirtree[dirname($data[1 ])])) {
$this->_dirtree[dirname($data[1 ])] = true;
while (!empty ($data[3 ]) && dirname($data[3 ]) != $data[3 ] &&
$data[3 ] != '/' && $data[3 ] != '\\') {
$this->_prependPath ($data[3 ], $data[2 ]));
$this->_dirtree[$pp] = true;
$this->log(2 , " successfully committed $n file operations" );
// {{{ rollbackFileTransaction()
$this->log(2 , " rolling back $n file operations" );
list ($type, $data) = $tr;
@copy($data[0 ] . '.bak', $data[0 ]);
$this->log(3 , " + restore $data[0] from $data[0].bak" );
$this->log(3 , " + rm backup of $data[0] ($data[0].bak)" );
$this->log(3 , " + rm $data[0]" );
$this->log(3 , " + rmdir $data[0]" );
$this->pkginfo->setInstalledAs ($data[0 ], false );
return parent ::mkDirHier ($dir);
* Download any files and their dependencies, if necessary
* @param array a mixed list of package names, local files, or package.xml
* @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:
* array('pkg' => 'package_name', 'file' => '/path/to/local/file',
* 'info' => array() // parsed package.xml
* @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
* @deprecated in favor of PEAR_Downloader
function download($packages, $options, &$config, &$installpackages,
&$errors, $installed = false , $willinstall = false , $state = false )
// trickiness: initialize here
trigger_error("PEAR Warning: PEAR_Installer::download() is deprecated " .
"in favor of PEAR_Downloader class", E_USER_WARNING );
// {{{ _parsePackageXml()
function _parsePackageXml (&$descfile)
// Parse xml file -----------------------------------------------
foreach ($p->getUserInfo () as $err) {
$loglevel = $err['level'] == 'error' ? 0 : 1;
if (!isset ($this->_options['soft'])) {
$this->log($loglevel, ucfirst($err['level']) . ': ' . $err['message']);
return $this->raiseError('Installation failed: invalid package file');
$descfile = $p->getPackageFile ();
* Set the list of PEAR_Downloader_Package objects to allow more sane
$this->_downloadedPackages = &$pkgs;
* Set the list of PEAR_Downloader_Package objects to allow more sane
$this->_downloadedPackages = &$pkgs;
return $this->_downloadedPackages;
* Installs the files within the package file specified.
* @param string|PEAR_Downloader_Package$pkgfile path to the package file,
* or a pre-initialized packagefile object
* - installroot : optional prefix directory for installation
* - force : force installation
* - register-only : update registry but don't install files
* - upgrade : upgrade existing install
* - nodeps : ignore dependency conflicts/missing dependencies
* - alldeps : install all dependencies
* - onlyreqdeps : install only required dependencies
* @return array|PEAR_Errorpackage info if successful
function install($pkgfile, $options = array ())
$this->_options = $options;
$this->_registry = &$this->config->getRegistry ();
$pkg = $pkgfile->getPackageFile ();
$pkgfile = $pkg->getArchiveFile ();
$descfile = $pkg->getPackageFile ();
$pkg = $this->_parsePackageXml ($descfile);
// Use the temp_dir since $descfile can contain the download dir path
$tmpdir = $this->config->get ('temp_dir', null , 'pear.php.net');
$tar = new Archive_Tar ($pkgfile);
if (!$tar->extract ($tmpdir)) {
return $this->raiseError(" unable to unpack $pkgfile" );
$pkgname = $pkg->getName ();
$channel = $pkg->getChannel ();
if (isset ($this->_options['packagingroot'])) {
$regdir = $this->_prependPath (
$this->config->get ('php_dir', null , 'pear.php.net'),
$this->_options['packagingroot']);
$packrootphp_dir = $this->_prependPath (
$this->config->get ('php_dir', null , $channel),
$this->_options['packagingroot']);
if (isset ($options['installroot'])) {
$this->config->setInstallRoot ($options['installroot']);
$this->_registry = &$this->config->getRegistry ();
$installregistry = &$this->_registry;
$php_dir = $this->config->get ('php_dir', null , $channel);
$this->config->setInstallRoot (false );
$this->_registry = &$this->config->getRegistry ();
if (isset ($this->_options['packagingroot'])) {
if (!$installregistry->channelExists ($channel, true )) {
// we need to fake a channel-discover of this channel
$chanobj = $this->_registry->getChannel ($channel, true );
$installregistry->addChannel ($chanobj);
$php_dir = $packrootphp_dir;
$installregistry = &$this->_registry;
$php_dir = $this->config->get ('php_dir', null , $channel);
// {{{ checks to do when not in "force" mode
if (empty ($options['force']) &&
$testp = $channel == 'pear.php.net' ? $pkgname : array ($channel, $pkgname);
$instfilelist = $pkg->getInstallationFileList (true );
// ensure we have the most accurate registry
$installregistry->flushFileMap ();
$test = $installregistry->checkFileMap ($instfilelist, $testp, '1.1');
foreach ($pkgs as $param) {
if ($pkg->isSubpackageOf ($param)) {
// subpackages can conflict with earlier versions of parent packages
$parentreg = $installregistry->packageInfo ($param->getPackage (), null , $param->getChannel ());
foreach ($tmp as $file => $info) {
if (isset ($parentreg['filelist'][$file])) {
unset ($parentreg['filelist'][$file]);
$basedir = substr($file, 0 , $pos);
$file2 = substr($file, $pos + 1 );
if (isset ($parentreg['filelist'][$file2]['baseinstalldir'])
&& $parentreg['filelist'][$file2]['baseinstalldir'] === $basedir
unset ($parentreg['filelist'][$file2]);
if (strtolower($param->getChannel ()) != 'pear.php.net') {
if (isset ($parentreg['filelist'][$file])) {
unset ($parentreg['filelist'][$file]);
$basedir = substr($file, 0 , $pos);
$file2 = substr($file, $pos + 1 );
if (isset ($parentreg['filelist'][$file2]['baseinstalldir'])
&& $parentreg['filelist'][$file2]['baseinstalldir'] === $basedir
unset ($parentreg['filelist'][$file2]);
$parentpkg = &$pfk->fromArray ($parentreg);
$installregistry->updatePackage2 ($parentpkg);
if ($param->getChannel () == 'pecl.php.net' && isset ($options['upgrade'])) {
foreach ($tmp as $file => $info) {
// pear.php.net packages are always stored as strings
// upgrading existing package
$msg = " $channel/$pkgname: conflicting files found:\n";
$fmt = " %${longest}s (%s)\n";
foreach ($test as $file => $info) {
$info = array ('pear.php.net', $info);
$info = $info[0 ] . '/' . $info[1 ];
$msg .= sprintf($fmt, $file, $info);
if (!isset ($options['ignore-errors'])) {
if (!isset ($options['soft'])) {
$this->log(0 , " WARNING: $msg" );
if ($channel == 'pecl.php.net') {
$test = $installregistry->packageExists ($pkgname, $channel);
$test = $installregistry->packageExists ($pkgname, 'pear.php.net');
$usechannel = 'pear.php.net';
$test = $installregistry->packageExists ($pkgname, $channel);
if (empty ($options['upgrade']) && empty ($options['soft'])) {
// checks to do only when installing new packages
if (empty ($options['force']) && $test) {
return $this->raiseError(" $channel/$pkgname is already installed" );
$v1 = $installregistry->packageInfo ($pkgname, 'version', $usechannel);
$v2 = $pkg->getVersion ();
return $this->raiseError(" upgrade to a newer version ($v2 is not newer than $v1)" );
// Do cleanups for upgrade and install, remove old release's files first
if ($test && empty ($options['register-only'])) {
// when upgrading, remove old release's files first:
if (!isset ($options['ignore-errors'])) {
if (!isset ($options['soft'])) {
$this->log(0 , 'WARNING: ' . $err->getMessage ());
// {{{ Copy files to dest dir ---------------------------------------
// info from the package it self we want to access from _installFile
// used to determine whether we should build any C code
$savechannel = $this->config->get ('default_channel');
if (empty ($options['register-only']) && !is_dir($php_dir)) {
return $this->raiseError(" no installation destination directory '$php_dir'\n" );
if (substr($pkgfile, -4 ) != '.xml') {
$tmpdir .= DIRECTORY_SEPARATOR . $pkgname . '-' . $pkg->getVersion ();
$this->configSet('default_channel', $channel);
$ver = $pkg->getPackagexmlVersion ();
$filelist = $pkg->getInstallationFilelist ();
$filelist = $pkg->getFileList ();
$p = &$installregistry->getPackage ($pkgname, $channel);
$dirtree = (empty ($options['register-only']) && $p) ? $p->getDirTree () : false;
$pkg->setLastInstalledVersion ($installregistry->packageInfo ($pkg->getPackage (),
'version', $pkg->getChannel ()));
foreach ($filelist as $file => $atts) {
if ($pkg->getPackagexmlVersion () == '1.0') {
$res = $this->_installFile ($file, $atts, $tmpdir, $options);
$res = $this->_installFile2 ($pkg, $file, $atts, $tmpdir, $options);
if (empty ($options['ignore-errors'])) {
if ($res->getMessage () == "file does not exist") {
$this->raiseError(" file $file in package.xml does not exist" );
if (!isset ($options['soft'])) {
$this->log(0 , "Warning: " . $res->getMessage ());
$real = isset ($atts['attribs']) ? $atts['attribs'] : $atts;
// Register files that were installed
$pkg->installedFile ($file, $atts);
// {{{ compile and install source files
if ($this->source_files > 0 && empty ($options['nobuild'])) {
$this->_compileSourceFiles ($savechannel, $pkg))) {
$this->_removeBackups ($backedup);
$this->configSet('default_channel', $savechannel);
$installphase = 'install';
// {{{ Register that the package is installed -----------------------
if (empty ($options['upgrade'])) {
// if 'force' is used, replace the info in registry
if ($channel == 'pecl.php.net') {
$test = $installregistry->packageExists ($pkgname, $channel);
$test = $installregistry->packageExists ($pkgname, 'pear.php.net');
$usechannel = 'pear.php.net';
$test = $installregistry->packageExists ($pkgname, $channel);
if (!empty ($options['force']) && $test) {
$oldversion = $installregistry->packageInfo ($pkgname, 'version', $usechannel);
$installregistry->deletePackage ($pkgname, $usechannel);
$ret = $installregistry->addPackage2 ($pkg);
// attempt to delete empty directories
uksort($dirtree, array ($this, '_sortDirs'));
foreach($dirtree as $dir => $notused) {
if ($channel == 'pecl.php.net') {
$test = $installregistry->packageExists ($pkgname, $channel);
$test = $installregistry->packageExists ($pkgname, 'pear.php.net');
$usechannel = 'pear.php.net';
$test = $installregistry->packageExists ($pkgname, $channel);
// new: upgrade installs a package if it isn't installed
$ret = $installregistry->addPackage2 ($pkg);
if ($usechannel != $channel) {
$installregistry->deletePackage ($pkgname, $usechannel);
$ret = $installregistry->addPackage2 ($pkg);
$ret = $installregistry->updatePackage2 ($pkg);
$installphase = 'upgrade';
$this->configSet('default_channel', $savechannel);
return $this->raiseError(" Adding package $channel/$pkgname to registry failed" );
$this->configSet('default_channel', $savechannel);
if (class_exists('PEAR_Task_Common')) { // this is auto-included if any tasks exist
return $pkg->toArray (true );
// {{{ _compileSourceFiles()
* @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
function _compileSourceFiles ($savechannel, &$filelist)
require_once 'PEAR/Builder.php';
$this->log(1 , " $this->source_files source files, building" );
$bob->debug = $this->debug;
$built = $bob->build ($filelist, array (&$this, '_buildCallback'));
$this->configSet('default_channel', $savechannel);
$this->log(1 , "\nBuild process completed successfully");
foreach ($built as $ext) {
list ($_ext_name, $_ext_suff) = explode('.', $bn);
if ($_ext_suff == '.so' || $_ext_suff == '.dll') {
$this->raiseError(" Extension '$_ext_name' already loaded. " .
'Please unload it in your php.ini file ' .
'prior to install or upgrade');
if (isset ($this->_options['packagingroot'])) {
$packagingroot = $this->_options['packagingroot'];
$copyto = $this->_prependPath ($dest, $packagingroot);
$extra = $copyto != $dest ? " as '$copyto'" : '';
$this->log(1 , " Installing '$dest'$extra" );
// pretty much nothing happens if we are only registering the install
if (empty ($this->_options['register-only'])) {
return $this->raiseError(" failed to mkdir $copydir" ,
$this->log(3 , " + mkdir $copydir" );
if (!@copy($ext['file'], $copyto)) {
$this->log(3 , " + cp $ext[file] $copyto" );
if (!@chmod($copyto, $mode)) {
$this->log(0 , " failed to change mode of $copyto ($php_errormsg)" );
'php_api' => $ext['php_api'],
'zend_mod_api' => $ext['zend_mod_api'],
'zend_ext_api' => $ext['zend_ext_api'],
if ($filelist->getPackageXmlVersion () == '1.0') {
$filelist->installedFile ($bn, $data);
$filelist->installedFile ($bn, array ('attribs' => $data));
return $this->_downloadedPackages;
* 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
* - register-only : update registry but don't remove files
* - nodeps: do not process dependencies of other packages to ensure
* uninstallation does not break things
function uninstall($package, $options = array ())
$installRoot = isset ($options['installroot']) ? $options['installroot'] : '';
$this->config->setInstallRoot ($installRoot);
$this->_registry = &$this->config->getRegistry ();
$channel = $package->getChannel ();
$package = $pkg->getPackage ();
$info = $this->_registry->parsePackageName ($package,
$this->config->get ('default_channel'));
$channel = $info['channel'];
$package = $info['package'];
$savechannel = $this->config->get ('default_channel');
$this->configSet('default_channel', $channel);
$pkg = $this->_registry->getPackage ($package, $channel);
$this->configSet('default_channel', $savechannel);
return $this->raiseError($this->_registry->parsedPackageNameToString (
), true ) . ' not installed');
if ($pkg->getInstalledBinary ()) {
// this is just an alias for a binary package
return $this->_registry->deletePackage ($package, $channel);
$filelist = $pkg->getFilelist ();
require_once 'PEAR/Dependency2.php';
array ('channel' => $channel, 'package' => $package),
$e = $depchecker->validatePackageUninstall ($this);
if (!isset ($options['ignore-errors'])) {
if (!isset ($options['soft'])) {
$this->log(0 , 'WARNING: ' . $e->getMessage ());
if (!isset ($options['soft'])) {
// pretty much nothing happens if we are only registering the uninstall
if (empty ($options['register-only'])) {
$this->configSet('default_channel', $savechannel);
if (!isset ($options['ignore-errors'])) {
if (!isset ($options['soft'])) {
$this->log(0 , 'WARNING: ' . $err->getMessage ());
if (!isset ($options['ignore-errors'])) {
if (!isset ($options['soft'])) {
$this->log(0 , 'WARNING: uninstall failed');
$dirtree = $pkg->getDirTree ();
if ($dirtree === false ) {
$this->configSet('default_channel', $savechannel);
return $this->_registry->deletePackage ($package, $channel);
// attempt to delete empty directories
uksort($dirtree, array ($this, '_sortDirs'));
foreach($dirtree as $dir => $notused) {
if (!isset ($options['ignore-errors'])) {
if (!isset ($options['soft'])) {
$this->log(0 , 'WARNING: uninstall failed');
$this->configSet('default_channel', $savechannel);
// Register that the package is no longer installed
return $this->_registry->deletePackage ($package, $channel);
* Sort a list of arrays of array(downloaded packagefilename) by dependency.
* It also removes duplicate dependencies
* @param array an array of PEAR_PackageFile_v[1/2] objects
* @return array|PEAR_Errorarray of array(packagefilename, package.xml contents)
return $this->_dependencyDB;
usort($packages, array (&$this, '_sortUninstall'));
function _sortUninstall ($a, $b)
if (!$a->getDeps () && !$b->getDeps ()) {
return 0; // neither package has dependencies, order is insignificant
if ($a->getDeps () && !$b->getDeps ()) {
return -1; // $a must be installed after $b because $a has dependencies
if (!$a->getDeps () && $b->getDeps ()) {
return 1; // $b must be installed after $a because $b has dependencies
// both packages have dependencies
if ($this->_dependencyDB->dependsOn ($a, $b)) {
if ($this->_dependencyDB->dependsOn ($b, $a)) {
function _sortDirs ($a, $b)
function _buildCallback ($what, $data)
if (($what == 'cmdoutput' && $this->debug > 1 ) ||
($what == 'output' && $this->debug > 0 )) {
$this->ui->outputData (rtrim($data), 'build');
Documentation generated on Wed, 06 Jul 2011 23:30:55 +0000 by phpDocumentor 1.4.3. PEAR Logo Copyright © PHP Group 2004.
|