Viewing File: /usr/local/cpanel/3rdparty/php/83/lib/php/PEAR/Command/Packaging.php

<?php
/**
 * PEAR_Command_Packaging (make-rpm-spec commands)
 *
 * PHP version 5
 *
 * LICENSE: This source file is subject to version 3.01 of the PHP license
 * that is available through the world-wide-web at the following URI:
 * http://www.php.net/license/3_01.txt.  If you did not receive a copy of
 * the PHP License and are unable to obtain it through the web, please
 * send a note to license@php.net so we can mail you a copy immediately.
 *
 * @category   pear
 * @package    PEAR
 * @author     Tim Jackson <tim@timj.co.uk>
 * @author     Greg Beaver <cellog@php.net>
 * @copyright  2006 The PHP Group
 * @license    http://www.php.net/license/3_01.txt  PHP License 3.01
 * @version    CVS: $Id$
 * @link       http://pear.php.net/package/PEAR_Command_Packaging
 * @since      File available since Release 0.1.0
 */

/**
 * base class
 */
require_once 'PEAR/Command/Common.php';

/**
 * PEAR commands for RPM management
 *
 * @category   pear
 * @package    PEAR
 * @author     Tim Jackson <tim@timj.co.uk>
 * @author     Greg Beaver <cellog@php.net>
 * @copyright  2006 The PHP Group
 * @license    http://www.php.net/license/3_01.txt  PHP License 3.01
 * @version    Release: 0.3.0
 * @link       http://pear.php.net/package/PEAR_Command_Packaging
 * @since      Class available since Release 0.1.0
 */

class PEAR_Command_Packaging extends PEAR_Command_Common
{
    var $commands = array(

        'make-rpm-spec' => array(
            'summary' => 'Builds an RPM spec file from a PEAR package or channel XML file',
            'function' => 'doMakeRPM',
            'shortcut' => 'rpm',
            'options' => array(
                'spec-template' => array(
                    'shortopt' => 't',
                    'arg' => 'FILE',
                    'doc' => 'Use FILE as RPM spec file template'
                    ),
                'rpm-release' => array(
                    'shortopt' => 'r',
                    'arg' => 'RELEASE',
                    'doc' => 'RPM release version. Defaults to "1".'
                    ),
                'rpm-pkgname' => array(
                    'shortopt' => 'p',
                    'arg' => 'FORMAT',
                    'doc' => 'Use FORMAT as format string for RPM package name. Substitutions
are as follows:
%s = PEAR package name
%l = PEAR package name (lowercased)
%S = PEAR package name (with underscores replaced with hyphens)
%C = Channel alias
%c = Channel alias, lowercased
%n = Channel name (full) e.g. pear.example.com
%N = Non standard channel name followed by a "/" (e.g. "pear.example.com/")

Defaults to "%C::%s" for library/application packages and "php-channel-%c" for 
channel packages.',
                    ),
                'rpm-depname' => array(
                    'shortopt' => 'd',
                    'arg' => 'FORMAT',
                    'doc' => 'Use FORMAT as format string for naming RPM dependencies. Substitutions
are as for the --rpm-pkgname option. Defaults to be the same as
the format defined by the --rpm-pkgname option.',
                   ),
                ),
            'doc' => '<package-file>

Creates an RPM .spec file for wrapping a PEAR package or channel definition 
inside an RPM package.  Intended to be used from the SPECS directory, with the 
PEAR package tarball in the SOURCES directory:

$ cd /path/to/rpm-build-tree/SPECS
$ pear make-rpm-spec ../SOURCES/Net_Socket-1.0.tgz
Wrote RPM spec file PEAR::Net_Socket-1.0.spec
$ rpm -bb PEAR::Net_Socket-1.0.spec
...
Wrote: /path/to/rpm-build-tree/RPMS/noarch/PEAR::Net_Socket-1.0-1.noarch.rpm
',
            ),
        );

    var $output;
    
    // ------------------------------------------------------------------------
    // BEGIN DISTRIBUTION CONFIG
    // This is the start of configuration options that might need to be patched
    // by downstream distributors
    // TODO: all this stuff should be settable via $options really
    // ------------------------------------------------------------------------
    
    /**
     * The default format of the RPM package name. See above for possible
     * substitution variables.
     *
     * There are two elements in the array:
     *
     * pkg - used when generating a spec file for an actual library/application
     * chan - used when generating a spec file for a channel
     *
     * If you change these, you will want to patch the documentation in the
     * $commands array above and in Packaging.xml so that it is consistent.
     */
    var $_rpm_pkgname_format = array(
        'pkg'  => '%C::%s',
        'chan' => 'php-channel-%c',
    );
    
    /**
     * The default format of various dependencies that might be generated in the
     * spec file. The currently-handled dependency types are:
     *
     * pkg  = another PEAR package
     * ext  = a PHP extension
     * php  = PHP itself
     * chan = a PEAR installer-based channel
     *
     * In each one:
     *
     * NULL = don't generate a dependency
     * %P   = use the same as whatever rpm_pkgname_format is set to be
     */
    var $_rpm_depname_format = array(
        'pkg'  => '%P',
        'ext'  => 'php-%l',
        'php'  => 'php',
        'chan' => 'php-channel(%n)',
    );
    
    /**
     * Format of the filename for the output spec file. Substitutions are as per 
     * the rpm-pkgname format string, with the addition of:
     *
     * %v = package version
     * %P = use the same as whatever rpm_pkgname_format is set to be
     *
     * There are two elements in the array:
     *
     * pkg  - used when generating a spec file for an actual library/application
     * chan - used when generating a spec file for a channel
     */
    var $_rpm_specname_format = array(
        'pkg'  => '%P-%v.spec',
        'chan' => 'php-channel-%c.spec'
    );
    
    /**
     * File prefixes to use for the standard file roles. Used when generating
     * specs for packages only. It's OK to use RPM macros here; these file
     * paths are not used internally, only for putting in the output spec file.
     *
     * Don't include trailing slashes.
     *
     * The %s (PEAR package name) substitution is understood, and if this is
     * used *and* it is at the end of the string, then make-rpm-spec will be
     * intelligent and include just the top level directory in the corresponding 
     * "[role]_files_statement" macro instead of listing *all* the files in that
     * subdirectory. That makes for cleaner specs and means that the output
     * package will own that directory.
     *
     * NB that 'doc' is handled specially and should normally be empty
     * Files with role='src' should not appear in final package so do not
     * need to be listed here
     */
    var $_file_prefixes = array(
        'php' => '%{_libdir}/php/pear',
        'doc' => '',
        'ext' => '%{_libdir}/php',
        'test' => '%{_libdir}/php/tests/%s',
        'data' => '%{_libdir}/php/data/%s',
        'script' => '%{_bindir}',
        'cfg' => '%{_sysconfdir}/pear',
        'www' => '%{_datadir}/pear/www'
    );
    
    /**
     * The format to use when adding new RPM header lines to the spec file, in
     * printf format. The first '%s' is the RPM header name followed by a colon,
     * the second is the header value.
     */
    var $_spec_line_format = '%s %s';
    
    // ------------------------------------------------------------------------
    // --- END DISTRIBUTION CONFIG
    // ------------------------------------------------------------------------
    
    /**
     * The final output substitutions which will be put into the RPM spec 
     * template. Common to the generation of specs for both channels and packages 
     */
    var $_output = array(
        'channel_alias' => '',   // channel alias
        'master_server' => '',   // download server for package/channel
        'pear_rpm_name' => '',   // RPM name of the core PEAR package
        'possible_channel' => '',// channel name e.g. pear.example.com
        'release' => 1,          // RPM release number
        'release_license' => '', // license
        'rpm_package' => '',     // the output RPM package name (RPMified)
        'version' => '',         // the (source) package version
    );
    
    /**
     * Final output substitutions that are only used when generating specs for
     * packages (not channels)
     */
    var $_output_package = array(
        'arch_statement' => '',  // empty string, or "BuildArchitecture: noarch" if definitely a noarch package
        'bin_dir' => '',
        'cfg_files_statement' => '', // empty string, or newline-separated list of files with "cfg" role
        'customrole_files_statement' => '',// empty string, or list of files with custom roles
        'data_dir' => '',
        'data_files_statement' => '',// empty string, or list of data files
        'doc_dir' => '',
        'doc_files' => '',
        'doc_files_relocation_script' => '', // doc files relocation script, if needed
        'doc_files_statement' => '', // empty string, or list of doc files preceded with %doc
        'ext_dir' => '',
        'extra_config' => '',
        'extra_headers' => '',
        'files' => '',           // list of files in the package, newline-separated
        'package' => '',         // the (source) package name
        'package2xml' => '',     // either empty string, or number "2" if using package.xml v2
        'php_dir' => '',
        'php_files_statement' => '', // empty string, or list of php files
        'release_state' => '',   // stable, unstable etc
        'script_files_statement' => '',
        'summary' => '',
        'test_dir' => '',
        'test_files_statement' => '',// empty string, or list of test files
        'www_files_statement' => '', // empty string, or newline-separated list of files with "www" role
    );
    
    // The name of the template spec file to use
    var $_template_spec_name = '';
    
    /**
     * List of "standard" channels used by PEAR/PECL
     * 
     * @var array
     */
    var $_standard_channels = array('pear.php.net','pecl.php.net');
    
    /**
     * PEAR_Command_Packaging constructor.
     *
     * @access public
     */
    function PEAR_Command_Packaging($ui, $config)
    {
        parent::PEAR_Command_Common($ui, $config);
    }

    /**
     * Get a PEAR_PackageFile object based on the provided config options
     */
    function getPackageFile($config, $debug = false, $tmpdir = null)
    {
        if (!class_exists('PEAR_Common')) {
            require_once 'PEAR/Common.php';
        }
        if (!class_exists('PEAR_PackageFile')) {
            require_once 'PEAR/PackageFile.php';
        }
        $a = new PEAR_PackageFile($config, $debug, $tmpdir);
        $common = new PEAR_Common;
        $common->ui = $this->ui;
        $a->setLogger($common);
        return $a;
    }

    /**
     * Abstraction for unit testing purposes
     */
    function getInstaller($ui)
    {
        if (!class_exists('PEAR_Installer')) {
            require_once 'PEAR/Installer.php';
        }
        $a = new PEAR_Installer($ui);
        return $a;
    }
    
    /**
     * Abstraction for unit testing purposes
     */
    function makeTempDir()
    {
        return System::mktemp(array('-d', 'pear2rpm'));
    }

    /**
     * The make-rpm-spec command - create an RPM spec file from package source
     * @uses _doMakeRPMFromChannel() for channel sources
     * @uses _doMakeRPMFromPackage() for package sources
     */
    function doMakeRPM($command, $options, $params)
    {
        require_once 'System.php';
        require_once 'Archive/Tar.php';
        
        if (sizeof($params) != 1) {
            return $this->raiseError("Bad parameter(s); please try \"help $command\"");
        }
        
        $source_file = $params[0];
        
        // Check source file exists
        if (!file_exists($source_file)) {
            return $this->raiseError("Source file '$source_file' does not exist");
        }
        
        // Initialise the RPM package/dep naming format options
        $this->_initialiseNamingOptions($options);        
        
        // Set the RPM release version
        if (isset($options['rpm-release'])) {
            $this->_output['release'] = $options['rpm-release'];
        }
        
        // Set the PEAR RPM name for the PEAR core package
        $this->_output['pear_rpm_name'] = $this->_getRPMName('PEAR', 'pear.php.net', null, 'pkgdep');
        
        // If source file ends in ".xml" we assume we are creating an RPM spec
        // for a channel rather than an actual package
        if (substr(strtolower($source_file), -4) == '.xml') {
            $this->_doMakeRPMFromChannel($source_file, $options, $params);
            $type = 'chan';
        } else {
            $this->_doMakeRPMFromPackage($source_file, $command, $options, $params);
            $type = 'pkg';
        }
        
        // Work out where we are loading the specfile template from
        if (isset($options['spec-template'])) {
            $spec_template = $options['spec-template'];
        } else {
            if (file_exists('/usr/local/cpanel/3rdparty/php/83/lib/php/data/PEAR_Command_Packaging/')) {
                $spec_template = '/usr/local/cpanel/3rdparty/php/83/lib/php/data/PEAR_Command_Packaging/' . $this->_template_spec_name;
            } else {
                $spec_template = dirname(dirname(dirname(__FILE__))) . '/' . $this->_template_spec_name;
            }
        }
        
        // Open the template spec file
        $spec_contents = @file_get_contents($spec_template);
        if ($spec_contents == false) {
            return $this->raiseError("Could not open RPM spec file template '$spec_template': $php_errormsg");
        }
        
        // Do the actual macro substitutions
        $spec_contents = preg_replace_callback(
            '/@([a-z0-9_-]+)@/', 
            array($this, '_replaceOutputMacro'),
            $spec_contents
        );
        
        // Write the output spec file
        $this->_writeSpecFile($spec_contents, $type, $command);
        
        return true;
    }
    
    /**
     * Format an RPM header line to be added to the spec file
     * @param  string $header The name of the RPM header to be added 
     * @param  string $value  The contents of the RPM header
     * @return string
     */
    function _formatRpmHeader($header, $value)
    {
        return sprintf($this->_spec_line_format, $header . ':', $value);
    }
    
    /**
     * Replace a macro in the output spec file
     * @return string
     */
    function _replaceOutputMacro($matches)
    {
        if (!isset($this->_output[$matches[1]])) {
            $this->raiseError("Replacement macro '$matches[1]' does not exist");
            return '@' . $matches[1] . '@'; // return original string
        }
        return $this->_output[$matches[1]];
    }
    
    /**
     * Write the output spec file
     * @return string
     */
    function _writeSpecFile($spec_contents, $type, $command)
    {
        if (isset($this->_output['package'])) {
            $package_name = $this->_output['package'];
        } else {
            $package_name = null;
        }
        
        // Work out the name of the output spec file
        $spec_file = $this->_getRPMNameFromFormat(
            $this->_rpm_specname_format[$type],
            $package_name,
            $this->_output['possible_channel'],
            $this->_output['channel_alias'],
            $this->_output['version']
        );
        
        // Write the actual file
        $wp = fopen($spec_file, 'wb');
        if (!$wp) {
            return $this->raiseError("Could not write RPM spec file '$spec_file': $php_errormsg");
        }
        fwrite($wp, $spec_contents);
        fclose($wp);
        
        // Notify user
        $this->ui->outputData("Wrote RPM spec file $spec_file", $command);
    }

    /**
     * Set the output macros based on a channel source
     */    
    function _doMakeRPMFromChannel($source_file, $options, $params)
    {
        // Set the name of the template spec file to use by default
        $this->_template_spec_name = 'template-channel.spec';
        
        // Create a PEAR_ChannelFile object
        if (!class_exists('PEAR_ChannelFile')) {
            require_once 'PEAR/ChannelFile.php';
        }
        $cf = new PEAR_ChannelFile();
        
        // Load in the channel.xml file from the source XML
        $cf->fromXmlFile($source_file);
        
        // Set the output macros
        $this->_output['channel_alias'] = $cf->getAlias();
        $this->_output['master_server'] = $cf->getName();
        $this->_output['possible_channel'] = $cf->getName();
        $this->_output['rpm_package'] = $this->_getRPMName(null, $cf->getName(), $cf->getAlias(), 'chan');
        
        $rpmdep = $this->_getRPMName(null, $cf->getName(), $cf->getAlias(), 'chandep');
        if (!empty($rpmdep) && $rpmdep != $this->_output['rpm_package']) {
            $this->_output['extra_headers'] = $this->_formatRpmHeader('Provides', "$rpmdep") . "\n";
        }
            
        // Channels don't really have version numbers; this will need to be
        // hand-maintained in the spec
        $this->_output['version'] = '1.0';
    }
    
    /**
     * Set the output macros based on a package source
     */ 
    function _doMakeRPMFromPackage($source_file, $command, $options, $params) {
        // Merge the "core" output macros with those for packages only
        $this->_output += $this->_output_package;
        
        // Set the name of the template spec file to use by default
        $this->_template_spec_name = 'template.spec';
    
        // Create a PEAR_PackageFile object and fill it with info from the
        // source package
        $reg = $this->config->getRegistry();
        $pkg = $this->getPackageFile($this->config, $this->_debug);
        $pf = $pkg->fromAnyFile($source_file, PEAR_VALIDATE_NORMAL);
        if (PEAR::isError($pf)) {
            $u = $pf->getUserinfo();
            if (is_array($u)) {
                foreach ($u as $err) {
                    if (is_array($err)) {
                        $err = $err['message'];
                    }
                    $this->ui->outputData($err);
                }
            }
            return $this->raiseError("$source_file is not a valid package");
        }
        
        // Install the package into a temporary directory
        $tmpdir = $this->makeTempDir();
        $instroot = $this->makeTempDir();
        
        // Save a channel object for the channel our package is part of
        // We will need this later to stick back into the temporary
        // installation directory
        $chan = $reg->getChannel($pf->getChannel(), true);
        if (PEAR::isError($chan)) {
            $this->ui->outputData($chan->getMessage());
            return $this->raiseError("Could not find channel data for channel '" .
                $pf->getChannel() . " - you need to channel-discover or channel-add ".
                "the channel before building packages based on it.");
        }
        
        // Set the role prefixes - package won't actually be installed here
        // but we can pull the final install paths back out later to put into
        // our spec
        foreach ($this->_file_prefixes as $role => $prefix) {
            // if role is 'script' the corresponding option is bin_dir
            if ($role == 'script') $role = 'bin';
            
            // save the original config options to restore later
            $orig_config_options["${role}_dir"] = $this->config->get("${role}_dir");
            
            // Substitute the package name into the file prefix
            $prefix = str_replace('%s', $pf->getPackage(), $prefix);
            
            // Set the temporary role prefix for installation
            $this->config->set("${role}_dir", $prefix);
        }
        
        // Construct a fake registry inside the ultimate destination
        // temporary directory, and load the necessary channel into it
        $regdir = $instroot . $this->config->get('php_dir');
        $fakereg = new PEAR_Registry($regdir);
        $fakereg->addChannel($chan);

        $tmp = $this->config->get('verbose');
        $this->config->set('verbose', 0);
        $installer = $this->getInstaller($this->ui);
        $installer->setConfig($this->config);
        require_once 'PEAR/Downloader/Package.php';
        $pack = new PEAR_Downloader_Package($installer);
        $pack->setPackageFile($pf);
        $params[0] = $pack;
        $installer->setOptions(array('packagingroot' => $instroot,
                                            'nodeps' => true, 'soft' => true));
        $installer->setDownloadedPackages($params);
        
        // Don't change $params[0] below to $source_file - it's not the same
        // any more (see $params[0] a few lines above here)
        $package_info = $installer->install($params[0],
                                    array('packagingroot' => $instroot,
                                            'nodeps' => true, 'soft' => true));
        if (PEAR::isError($package_info)) {
            $this->ui->outputData($package_info->getMessage());
            return $this->raiseError('Failed to do a temporary installation of the package');
        }
                                            
        // Restore the original config options
        foreach ($orig_config_options as $key => $val) 
        {
            $this->config->set($key, $val);
        }
        
        // Restore the original config verbosity
        $this->config->set('verbose', $tmp);
        
        // Set up some of the basic macros
        $this->_output['rpm_package'] = $this->_getRPMName($pf->getPackage(), $pf->getChannel(), null, 'pkg');
        $this->_output['description'] = wordwrap($package_info['description']);
        $this->_output['summary'] = trim($package_info['summary']);
        $this->_output['possible_channel'] = $pf->getChannel();
        $this->_output['channel_alias'] = $this->_getChannelAlias($pf->getPackage(), $pf->getChannel());
        $this->_output['package'] = $pf->getPackage();
        $this->_output['version'] = $pf->getVersion();
        $this->_output['release_license'] = $pf->getLicense();
        $this->_output['release_state'] = $pf->getState();

        // Remove trailing dots from summaries
        if (substr($this->_output['summary'], -1) == '.') {
            $this->_output['summary'] = substr($this->_output['summary'], 0, -1);
        }
        
        // Figure out the master server for the package's channel
        $chan = $reg->getChannel($pf->getChannel());
        $this->_output['master_server'] = $chan->getServer();

        // Put some standard PEAR config options into the output macros. These
        // will probably be deprecated in future
        $cfg = array('php_dir', 'ext_dir', 'doc_dir', 'bin_dir', 'data_dir', 'test_dir');
        foreach ($cfg as $k) {
            $this->_output[$k] = $this->config->get($k);
        }

        // Generate the Requires and Conflicts for the RPM
        if ($pf->getDeps()) {
            $this->_generatePackageDeps($pf);
        }
    
        // Hook to support virtual Provides, where the dependency name differs
        // from the package name
        $rpmdep = $this->_getRPMName($pf->getPackage(), $pf->getChannel(), null, 'pkgdep');
        if (!empty($rpmdep) && $rpmdep != $this->_output['rpm_package']) {
            $this->_output['extra_headers'] .= $this->_formatRpmHeader('Provides', "$rpmdep = %{version}") . "\n";
        }
        
        // Create the list of files in the package
        foreach ($package_info['filelist'] as $filename => $attr) {
            // Ignore files with no role set or that didn't get installed
            if (!isset($attr['role']) || !isset($attr['installed_as'])) {
                continue;
            }
            $installed_filename = $attr['installed_as'];
            $role = $attr['role'];
            
            // Handle custom roles; set prefix
            // TODO: This is not tested and probably doesn't work, because:
            // a) package probably needs a custom file role package to build
            // b) [role]_dir wasn't set earlier before we did the test install
            if (!isset($this->_file_prefixes[$role])) {
                $this->_file_prefixes[$role] = $this->_file_prefixes['php'] . "/$role/%s";
                $this->_output['extra_config'] .=
                    "\n        -d ${role}_dir=" . $this->_file_prefixes[$role] . "\\";
                $this->ui->outputData("WARNING: role '$role' used, " .
                    'and will be installed in "' . str_replace('%s', $pf->getPackage(), $this->_file_prefixes[$role]) .
                    ' - hand-edit the final .spec if this is wrong', $command);
            }
            
            // Add to master file list
            $file_list[$role][] = $installed_filename;
        }
        
        // Build the master file lists
        foreach ($file_list as $role => $files) {
            // Docs are handled separately below; 'src' shouldn't be in RPM
            if ($role == 'doc' || $role == 'src') continue; 
            
            // Master file list @files@ - recommended not to use
            $this->_output['files'] .= implode("\n", $files) . "\n";
            
            // Handle other roles specially: if the role puts files in a subdir
            // dedicated to the package in question (i.e. the prefix ends with 
            // %s) we don't need to specify all the individual files
            if (in_array($role, array('php','test','data','script','cfg','www'))) {
                $macro_name = "${role}_files_statement";
            } else {
                $macro_name = 'customrole_files_statement';
            }
            if (substr($this->_file_prefixes[$role], -2) == '%s') {
                $this->_output[$macro_name] = str_replace('%s', $pf->getPackage(), $this->_file_prefixes[$role]);
            } else {
                if ($role == 'cfg') {
                    $this->_output[$macro_name] = '%config(noreplace) ' . implode("\n%config(noreplace) ", $files);
                } else {
                    $this->_output[$macro_name] = implode("\n", $files);
                }
            }
        }
        $this->_output['files'] = trim($this->_output['files']);
        
        // Handle doc files
        if (isset($file_list['doc'])) {
            $this->_output['doc_files'] = 'docs/' . $pf->getPackage() . '/*';
            $this->_output['doc_files_statement'] = '%doc ' . $this->_output['doc_files'];
            $this->_output['doc_files_relocation_script'] = "mv %{buildroot}/docs .\n";
        }
        
        // Work out architecture
        // If there are 1 or more files with role="src", something needs compiling
        // and this is not a noarch package
        if (!isset($file_list['src'])) {
            $this->_output['arch_statement'] = $this->_formatRpmHeader('BuildArch', 'noarch') . "\n";
        }
        
        
        // If package is not from pear.php.net or pecl.php.net, we will need
        // to BuildRequire/Require a channel RPM
        if (!empty($this->_output['possible_channel']) && !in_array($this->_output['possible_channel'], $this->_standard_channels)) {
            $channel_dep = $this->_getRPMName($this->_output['package'], $this->_output['possible_channel'], null, 'chandep');
            $this->_output['extra_headers'] .= $this->_formatRpmHeader('BuildRequires', $channel_dep) . "\n";
            $this->_output['extra_headers'] .= $this->_formatRpmHeader('Requires', $channel_dep) . "\n";
        }
        
        // Remove any trailing newline from extra_headers
        $this->_output['extra_headers'] = trim($this->_output['extra_headers']);
    }
    

    function _generatePackageDeps($pf)
    {
        $requires = $conflicts = array();
        if ($pf->getPackagexmlVersion() == '1.0') {
            foreach ($pf->getDeps() as $dep) {
                if (isset($dep['optional']) && $dep['optional'] == 'yes') {
                    continue;
                }
                
                if (!isset($dep['type']) || $dep['type'] == 'pkg') {
                    $type = 'pkgdep';
                } else {
                    $type = $dep['type'];
                }
                
                if (!isset($dep['channel'])) $dep['channel'] = null;
                if (!isset($dep['name'])) $dep['name'] = ''; //e.g. "php" dep
                
                // $package contains the *dependency name* here, which may or may
                // not be the same as the package name
                $package = $this->_getRPMName($dep['name'], $dep['channel'], null, $type);

                // If we could not find an RPM namespace equivalent, don't add the dependency
                if (empty($package)) {
                    continue;
                }

                $trans = array(
                    '>' => '>',
                    '<' => '<',
                    '>=' => '>=',
                    '<=' => '<=',
                    '=' => '=',
                    'gt' => '>',
                    'lt' => '<',
                    'ge' => '>=',
                    'le' => '<=',
                    'eq' => '=',
                );
                if ($dep['rel'] == 'has') {
                    // We use $package as the index to the $requires array to de-duplicate deps.
                    // Note that in the case of duplicate deps, versioned deps will "win" - see several lines down.
                    $requires[] = $package;
                } elseif ($dep['rel'] == 'not') {
                    $conflicts[] = $package;
                } elseif ($dep['rel'] == 'ne') {
                    $conflicts[] = $package . ' = ' . $dep['version'];
                } elseif (isset($trans[$dep['rel']])) {
                    $requires[] = $package . ' ' . $trans[$dep['rel']] . ' ' . $dep['version'];
                }
            }
            if (count($requires)) {
                $this->_output['extra_headers'] .= $this->_formatRpmHeader('Requires', implode(', ', array_unique($requires))) . "\n";
            }
            if (count($conflicts)) {
                $this->_output['extra_headers'] .= $this->_formatRpmHeader('Conflicts', implode(', ', array_unique($conflicts))) . "\n";
            }
        } else {
            $this->_output['package2xml'] = '2'; // tell the spec to use package2.xml
            $deps = $pf->getDeps(true);
            if (isset($deps['required']['package'])) {
                if (!isset($deps['required']['package'][0])) {
                    $deps['required']['package'] = array($deps['required']['package']);
                }
                foreach ($deps['required']['package'] as $dep) {

                    if (!isset($dep['type']) || $dep['type'] == 'pkg') {
                        $type = 'pkgdep';
                    } else {
                        $type = $dep['type'];
                    }

                    if (!isset($dep['channel'])) $dep['channel'] = null;
                    
                    // $package contains the *dependency name* here, which may or may
                    // not be the same as the package name
                    $package = $this->_getRPMName($dep['name'], $dep['channel'], null, $type);
                    
                    if (empty($package)) {
                        continue;
                    }
                    
                    if (isset($dep['conflicts']) && (isset($dep['min']) ||
                            isset($dep['max']))) {
                        $deprange = array();
                        if (isset($dep['min'])) {
                            $deprange[] = array($dep['min'],'>=');
                        }
                        if (isset($dep['max'])) {
                            $deprange[] = array($dep['max'], '<=');
                        }
                        if (isset($dep['exclude'])) {
                            if (!is_array($dep['exclude']) ||
                                    !isset($dep['exclude'][0])) {
                                $dep['exclude'] = array($dep['exclude']);
                            }
                            if (count($deprange)) {
                                $excl = $dep['exclude'];
                                // change >= to > if excluding the min version
                                // change <= to < if excluding the max version
                                for($i = 0; $i < count($excl); $i++) {
                                    if (isset($deprange[0]) &&
                                            $excl[$i] == $deprange[0][0]) {
                                        $deprange[0][1] = '<';
                                        unset($dep['exclude'][$i]);
                                    }
                                    if (isset($deprange[1]) &&
                                            $excl[$i] == $deprange[1][0]) {
                                        $deprange[1][1] = '>';
                                        unset($dep['exclude'][$i]);
                                    }
                                }
                            }
                            if (count($dep['exclude'])) {
                                $dep['exclude'] = array_values($dep['exclude']);
                                $newdeprange = array();
                                // remove excludes that are outside the existing range
                                for ($i = 0; $i < count($dep['exclude']); $i++) {
                                    if ($dep['exclude'][$i] < $dep['min'] ||
                                            $dep['exclude'][$i] > $dep['max']) {
                                        unset($dep['exclude'][$i]);
                                    }
                                }
                                $dep['exclude'] = array_values($dep['exclude']);
                                usort($dep['exclude'], 'version_compare');
                                // take the remaining excludes and
                                // split the dependency into sub-ranges
                                $lastmin = $deprange[0];
                                for ($i = 0; $i < count($dep['exclude']) - 1; $i++) {
                                    $newdeprange[] = '(' .
                                        $package . " {$lastmin[1]} {$lastmin[0]} and " .
                                        $package . ' < ' . $dep['exclude'][$i] . ')';
                                    $lastmin = array($dep['exclude'][$i], '>');
                                }
                                if (isset($dep['max'])) {
                                    $newdeprange[] = '(' . $package .
                                        " {$lastmin[1]} {$lastmin[0]} and " .
                                        $package . ' < ' . $dep['max'] . ')';
                                }
                                $conflicts[] = implode(' or ', $deprange);
                            } else {
                                $conflicts[] = $package .
                                    " {$deprange[0][1]} {$deprange[0][0]}" .
                                    (isset($deprange[1]) ? 
                                    " and $package {$deprange[1][1]} {$deprange[1][0]}"
                                    : '');
                            }
                        }
                        continue;
                    }
                    if (!isset($dep['min']) && !isset($dep['max']) &&
                            !isset($dep['exclude'])) {
                        if (isset($dep['conflicts'])) {
                            $conflicts[] = $package;
                        } else {
                            $requires[] = $package;
                        }
                    } else {
                        if (isset($dep['min'])) {
                            $requires[] = $package . ' >= ' . $dep['min'];
                        }
                        if (isset($dep['max'])) {
                            if (isset($dep['exclude']) && !is_array($dep['exclude']) && $dep['exclude']=$dep['max']) {
                                $requires[] = $package . ' < ' . $dep['max'];
                                unset($dep['exclude']);
                            } else {
                                $requires[] = $package . ' <= ' . $dep['max'];
                            }
                        }
                        if (isset($dep['exclude'])) {
                            $ex = $dep['exclude'];
                            if (!is_array($ex)) {
                                $ex = array($ex);
                            }
                            foreach ($ex as $ver) {
                                $conflicts[] = $package . ' = ' . $ver;
                            }
                        }
                    }
                }
                require_once 'Archive/Tar.php';
                $tar = new Archive_Tar($pf->getArchiveFile());
                $tar->pushErrorHandling(PEAR_ERROR_RETURN);
                $a = $tar->extractInString('package2.xml');
                $tar->popErrorHandling();
                if ($a === null || PEAR::isError($a)) {
                    $this->_output['package2xml'] = '';
                    // this doesn't have a package.xml version 1.0
                    $requires[$this->_output['pear_rpm_name']] = $this->_output['pear_rpm_name'] . ' >= ' .
                        $deps['required']['pearinstaller']['min'];
                }
                if (count($requires)) {
                    foreach(array_unique($requires) as $req) {
                        $this->_output['extra_headers'] .= $this->_formatRpmHeader('Requires', $req) . "\n";
                    }
                }
                if (count($conflicts)) {
                    $this->_output['extra_headers'] .= $this->_formatRpmHeader('Conflicts', implode(', ', array_unique($conflicts))) . "\n";
                }
            }
        }
    }

    // }}}
    // {{{ _initialiseNamingOptions()
    /*
     * Initialise the RPM naming options
     *
     * @param  array $options    Standard options array
     * @return void
     */    
    function _initialiseNamingOptions($options)
    {
        if (isset($options['rpm-pkgname'])) {
            $this->_rpm_pkgname_format['pkg'] = $options['rpm-pkgname'];
        }
        
        if (isset($options['rpm-depname'])) {
            $this->_rpm_depname_format['pkg'] = $options['rpm-depname'];
        }
    }
    
    
    // }}}
    // {{{ _getRPMName()
    /*
     * Return an RPM name
     *
     * @param  string $package_name Package name
     * @param  string $chan_name    Optional channel name
     * @param  string $chang_alias  Optional channel alias
     * @param  string $type         Optional type (e.g. 'pkg', 'ext'). Defaults to 'pkg'.
     *                              'pkgdep' is a special case that means 'pkg', but it's a 
     *                              dependency rather than the package itself
     *
     * @return string RPM name. If empty, assume there is no equivalent in RPM namespace.
     */
    function _getRPMName($package_name, $chan_name=null, $chan_alias=null, $type='pkg')
    {
        if ($chan_alias === null) {
            $chan_alias = $this->_getChannelAlias($package_name, $chan_name);
        }
        
        switch ($type) {
            case 'chan':
                return $this->_getRPMNameFromFormat($this->_rpm_pkgname_format['chan'], null, $chan_name, $chan_alias);
            case 'chandep':
                return $this->_getRPMNameFromFormat($this->_rpm_depname_format['chan'], null, $chan_name, $chan_alias);
            case 'pkg':
                return $this->_getRPMNameFromFormat($this->_rpm_pkgname_format['pkg'], $package_name, $chan_name, $chan_alias);
            case 'pkgdep':
                $type = 'pkg';
                // let it drop through...
            default:
                if (isset($this->_rpm_depname_format[$type]) && !empty($this->_rpm_depname_format[$type])) {
                    return $this->_getRPMNameFromFormat($this->_rpm_depname_format[$type], $package_name, $chan_name, $chan_alias);
                }
                return '';
        }
    }
    
    // }}}
    // {{{ _getChannelAlias()
    /*
     * Return a channel alias from a channel name
     *
     * @param  string $chan_name    Channel name (e.g. 'pecl.php.net')
     * @param  string $package_name Optional name of the PEAR package to which $chan_name relates.
     *                              Assists when "guessing" channel aliases for PEAR/PECL
     * @return string Channel alias (e.g. 'PECL')
     */

    function _getChannelAlias($package_name, $chan_name = null)
    {    
        switch($chan_name) {
            case null:
            case '':
                // If channel name not supplied, it is presumably
                // either PEAR or PECL. There's no sure-fire way of
                // telling between the two, but we try to make an
                // intelligent guess: if the package name is supplied
                // and starts with a lowercase letter, it's PECL.
                if (preg_match('/^[a-z]/', $package_name)) {
                    $alias = 'PECL';
                } else {
                    $alias = 'PEAR';
                }
                break;
            case 'pear.php.net':
                $alias = 'PEAR';
                break;
            case 'pecl.php.net':
                $alias = 'PECL';
                break;
            default:
                $reg = $this->config->getRegistry();
                $alias = $reg->channelAlias($chan_name);
                
                // fallback
                if (empty($alias)) {
                    $alias = $chan_name;
                }
                break;
         }
         return $alias;
    }

    
    // }}}
    // {{{ _getRPMNameFromFormat()
    /*
     * Get an RPM package or dependency name from a format string
     *
     * This method generates an RPM package or dependency name based on
     * a format string containing substitution variables, rather like
     * sprintf(). It supports the following substitution variables:
     * %s = package name
     * %l = package name, lowercased
     * %S = package name, with underscores replaced with hyphens
     * %v = package version
     * %C = channel alias
     * %c = channel alias, lowercased
     * %n = channel name
     * %P = whatever the rpm pkgname format is set to be
     *
     * @param  string $format          Format string
     * @param  string $package_name    Package name (e.g. 'Example_Package')
     * @param  string $channel_alias   Channel alias (e.g. 'PEAR', 'PECL')
     * @param  string $version         Package version (e.g. '1.2.3')
     * @return string RPM package/dependency name
     */

    function _getRPMNameFromFormat($format, $package_name, $channel, $channel_alias, $version=null)
    {
        $name = $format;
        if (empty($channel_alias)) $channel_alias = $channel;
    
        // pkgname_format
        $name = str_replace('%P', $this->_rpm_pkgname_format['pkg'], $name);
    
        // Package name
        $name = str_replace('%s', $package_name, $name);
        
        // Package name, lowercased
        $name = str_replace('%l', strtolower($package_name), $name);
        
        // Package name, with underscores replaced with hyphens
        $name = str_replace('%S', str_replace('_', '-', $package_name), $name);

        // Channel alias
        $name = str_replace('%C', $channel_alias, $name);
        
        // Channel alias, lowercased
        $name = str_replace('%c', strtolower($channel_alias), $name);

        // Channel name, full
        $name = str_replace('%n', $channel, $name);

        // Non standard Channel name with /
        if (empty($channel) || in_array($channel, $this->_standard_channels)) {
	         $name = str_replace('%N', '', $name);
        } else {
            $name = str_replace('%N', $channel.'/', $name);
        }
        
        // Version
        $name = str_replace('%v', $version, $name);
        
        return $name;
    }

}

?>
Back to Directory File Manager