PEAR
[ class tree: PEAR ] [ index: PEAR ] [ all elements ]

Source for file Builder.php

Documentation is available at Builder.php

  1. <?php
  2. /**
  3.  * PEAR_Builder for building PHP extensions (PECL packages)
  4.  *
  5.  * PHP versions 4 and 5
  6.  *
  7.  * @category   pear
  8.  * @package    PEAR
  9.  * @author     Stig Bakken <ssb@php.net>
  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.  * @version    CVS: $Id: Builder.php 313024 2011-07-06 19:51:24Z dufuz $
  14.  * @link       http://pear.php.net/package/PEAR
  15.  * @since      File available since Release 0.1
  16.  *
  17.  *  TODO: log output parameters in PECL command line
  18.  *  TODO: msdev path in configuration
  19.  */
  20.  
  21. /**
  22.  * Needed for extending PEAR_Builder
  23.  */
  24. require_once 'PEAR/Common.php';
  25. require_once 'PEAR/PackageFile.php';
  26.  
  27. /**
  28.  * Class to handle building (compiling) extensions.
  29.  *
  30.  * @category   pear
  31.  * @package    PEAR
  32.  * @author     Stig Bakken <ssb@php.net>
  33.  * @author     Greg Beaver <cellog@php.net>
  34.  * @copyright  1997-2009 The Authors
  35.  * @license    http://opensource.org/licenses/bsd-license.php New BSD License
  36.  * @version    Release: 1.9.4
  37.  * @link       http://pear.php.net/package/PEAR
  38.  * @since      Class available since PHP 4.0.2
  39.  * @see        http://pear.php.net/manual/en/core.ppm.pear-builder.php
  40.  */
  41. class PEAR_Builder extends PEAR_Common
  42. {
  43.     var $php_api_version = 0;
  44.     var $zend_module_api_no = 0;
  45.     var $zend_extension_api_no = 0;
  46.  
  47.     var $extensions_built = array();
  48.  
  49.     /**
  50.      * @var string Used for reporting when it is not possible to pass function
  51.      *              via extra parameter, e.g. log, msdevCallback
  52.      */
  53.     var $current_callback = null;
  54.  
  55.     // used for msdev builds
  56.     var $_lastline = null;
  57.     var $_firstline = null;
  58.  
  59.     /**
  60.      * PEAR_Builder constructor.
  61.      *
  62.      * @param object $ui user interface object (instance of PEAR_Frontend_*)
  63.      *
  64.      * @access public
  65.      */
  66.     function PEAR_Builder(&$ui)
  67.     {
  68.         parent::PEAR_Common();
  69.         $this->setFrontendObject($ui);
  70.     }
  71.  
  72.     /**
  73.      * Build an extension from source on windows.
  74.      * requires msdev
  75.      */
  76.     function _build_win32($descfile$callback = null)
  77.     {
  78.         if (is_object($descfile)) {
  79.             $pkg $descfile;
  80.             $descfile $pkg->getPackageFile();
  81.         else {
  82.             $pf &new PEAR_PackageFile($this->config$this->debug);
  83.             $pkg &$pf->fromPackageFile($descfilePEAR_VALIDATE_NORMAL);
  84.             if (PEAR::isError($pkg)) {
  85.                 return $pkg;
  86.             }
  87.         }
  88.         $dir dirname($descfile);
  89.         $old_cwd getcwd();
  90.  
  91.         if (!file_exists($dir|| !is_dir($dir|| !chdir($dir)) {
  92.             return $this->raiseError("could not chdir to $dir");
  93.         }
  94.  
  95.         // packages that were in a .tar have the packagefile in this directory
  96.         $vdir $pkg->getPackage('-' $pkg->getVersion();
  97.         if (file_exists($dir&& is_dir($vdir)) {
  98.             if (!chdir($vdir)) {
  99.                 return $this->raiseError("could not chdir to " realpath($vdir));
  100.             }
  101.  
  102.             $dir getcwd();
  103.         }
  104.  
  105.         $this->log(2"building in $dir");
  106.  
  107.         $dsp $pkg->getPackage().'.dsp';
  108.         if (!file_exists("$dir/$dsp")) {
  109.             return $this->raiseError("The DSP $dsp does not exist.");
  110.         }
  111.         // XXX TODO: make release build type configurable
  112.         $command 'msdev '.$dsp.' /MAKE "'.$pkg->getPackage()' - Release"';
  113.  
  114.         $err $this->_runCommand($commandarray(&$this'msdevCallback'));
  115.         if (PEAR::isError($err)) {
  116.             return $err;
  117.         }
  118.  
  119.         // figure out the build platform and type
  120.         $platform 'Win32';
  121.         $buildtype 'Release';
  122.         if (preg_match('/.*?'.$pkg->getPackage().'\s-\s(\w+)\s(.*?)-+/i',$this->_firstline,$matches)) {
  123.             $platform $matches[1];
  124.             $buildtype $matches[2];
  125.         }
  126.  
  127.         if (preg_match('/(.*)?\s-\s(\d+).*?(\d+)/'$this->_lastline$matches)) {
  128.             if ($matches[2]{
  129.                 // there were errors in the build
  130.                 return $this->raiseError("There were errors during compilation.");
  131.             }
  132.             $out $matches[1];
  133.         else {
  134.             return $this->raiseError("Did not understand the completion status returned from msdev.exe.");
  135.         }
  136.  
  137.         // msdev doesn't tell us the output directory :/
  138.         // open the dsp, find /out and use that directory
  139.         $dsptext join(file($dsp),'');
  140.  
  141.         // this regex depends on the build platform and type having been
  142.         // correctly identified above.
  143.         $regex ='/.*?!IF\s+"\$\(CFG\)"\s+==\s+("'.
  144.                     $pkg->getPackage().'\s-\s'.
  145.                     $platform.'\s'.
  146.                     $buildtype.'").*?'.
  147.                     '\/out:"(.*?)"/is';
  148.  
  149.         if ($dsptext && preg_match($regex$dsptext$matches)) {
  150.             // what we get back is a relative path to the output file itself.
  151.             $outfile realpath($matches[2]);
  152.         else {
  153.             return $this->raiseError("Could not retrieve output information from $dsp.");
  154.         }
  155.         // realpath returns false if the file doesn't exist
  156.         if ($outfile && copy($outfile"$dir/$out")) {
  157.             $outfile = "$dir/$out";
  158.         }
  159.  
  160.         $built_files[= array(
  161.             'file' => "$outfile",
  162.             'php_api' => $this->php_api_version,
  163.             'zend_mod_api' => $this->zend_module_api_no,
  164.             'zend_ext_api' => $this->zend_extension_api_no,
  165.             );
  166.  
  167.         return $built_files;
  168.     }
  169.     // }}}
  170.  
  171.     // {{{ msdevCallback()
  172.     function msdevCallback($what$data)
  173.     {
  174.         if (!$this->_firstline)
  175.             $this->_firstline $data;
  176.         $this->_lastline $data;
  177.         call_user_func($this->current_callback$what$data);
  178.     }
  179.  
  180.     /**
  181.      * @param string 
  182.      * @param string 
  183.      * @param array 
  184.      * @access private
  185.      */
  186.     function _harvestInstDir($dest_prefix$dirname&$built_files)
  187.     {
  188.         $d opendir($dirname);
  189.         if (!$d)
  190.             return false;
  191.  
  192.         $ret = true;
  193.         while (($ent readdir($d)) !== false{
  194.             if ($ent{0== '.')
  195.                 continue;
  196.  
  197.             $full $dirname . DIRECTORY_SEPARATOR . $ent;
  198.             if (is_dir($full)) {
  199.                 if (!$this->_harvestInstDir(
  200.                         $dest_prefix . DIRECTORY_SEPARATOR . $ent,
  201.                         $full$built_files)) {
  202.                     $ret = false;
  203.                     break;
  204.                 }
  205.             else {
  206.                 $dest $dest_prefix . DIRECTORY_SEPARATOR . $ent;
  207.                 $built_files[= array(
  208.                         'file' => $full,
  209.                         'dest' => $dest,
  210.                         'php_api' => $this->php_api_version,
  211.                         'zend_mod_api' => $this->zend_module_api_no,
  212.                         'zend_ext_api' => $this->zend_extension_api_no,
  213.                         );
  214.             }
  215.         }
  216.         closedir($d);
  217.         return $ret;
  218.     }
  219.  
  220.     /**
  221.      * Build an extension from source.  Runs "phpize" in the source
  222.      * directory, but compiles in a temporary directory
  223.      * (TMPDIR/pear-build-USER/PACKAGE-VERSION).
  224.      *
  225.      * @param string|PEAR_PackageFile_v*$descfile path to XML package description file, or
  226.      *                a PEAR_PackageFile object
  227.      *
  228.      * @param mixed $callback callback function used to report output,
  229.      *  see PEAR_Builder::_runCommand for details
  230.      *
  231.      * @return array an array of associative arrays with built files,
  232.      *  format:
  233.      *  array( array( 'file' => '/path/to/ext.so',
  234.      *                'php_api' => YYYYMMDD,
  235.      *                'zend_mod_api' => YYYYMMDD,
  236.      *                'zend_ext_api' => YYYYMMDD ),
  237.      *         ... )
  238.      *
  239.      * @access public
  240.      *
  241.      * @see PEAR_Builder::_runCommand
  242.      */
  243.     function build($descfile$callback = null)
  244.     {
  245.         if (preg_match('/(\\/|\\\\|^)([^\\/\\\\]+)?php(.+)?$/',
  246.                        $this->config->get('php_bin')$matches)) {
  247.             if (isset($matches[2]&& strlen($matches[2]&&
  248.                 trim($matches[2]!= trim($this->config->get('php_prefix'))) {
  249.                 $this->log(0'WARNING: php_bin ' $this->config->get('php_bin'.
  250.                            ' appears to have a prefix ' $matches[2', but' .
  251.                            ' config variable php_prefix does not match');
  252.             }
  253.  
  254.             if (isset($matches[3]&& strlen($matches[3]&&
  255.                 trim($matches[3]!= trim($this->config->get('php_suffix'))) {
  256.                 $this->log(0'WARNING: php_bin ' $this->config->get('php_bin'.
  257.                            ' appears to have a suffix ' $matches[3', but' .
  258.                            ' config variable php_suffix does not match');
  259.             }
  260.         }
  261.  
  262.         $this->current_callback = $callback;
  263.         if (PEAR_OS == "Windows"{
  264.             return $this->_build_win32($descfile$callback);
  265.         }
  266.  
  267.         if (PEAR_OS != 'Unix'{
  268.             return $this->raiseError("building extensions not supported on this platform");
  269.         }
  270.  
  271.         if (is_object($descfile)) {
  272.             $pkg $descfile;
  273.             $descfile $pkg->getPackageFile();
  274.             if (is_a($pkg'PEAR_PackageFile_v1')) {
  275.                 $dir dirname($descfile);
  276.             else {
  277.                 $dir $pkg->_config->get('temp_dir''/' $pkg->getName();
  278.                 // automatically delete at session end
  279.                 $this->addTempFile($dir);
  280.             }
  281.         else {
  282.             $pf &new PEAR_PackageFile($this->config);
  283.             $pkg &$pf->fromPackageFile($descfilePEAR_VALIDATE_NORMAL);
  284.             if (PEAR::isError($pkg)) {
  285.                 return $pkg;
  286.             }
  287.             $dir dirname($descfile);
  288.         }
  289.  
  290.         // Find config. outside of normal path - e.g. config.m4
  291.         foreach (array_keys($pkg->getInstallationFileList()) as $item{
  292.           if (stristr(basename($item)'config.m4'&& dirname($item!= '.'{
  293.             $dir .= DIRECTORY_SEPARATOR . dirname($item);
  294.             break;
  295.           }
  296.         }
  297.  
  298.         $old_cwd getcwd();
  299.         if (!file_exists($dir|| !is_dir($dir|| !chdir($dir)) {
  300.             return $this->raiseError("could not chdir to $dir");
  301.         }
  302.  
  303.         $vdir $pkg->getPackage('-' $pkg->getVersion();
  304.         if (is_dir($vdir)) {
  305.             chdir($vdir);
  306.         }
  307.  
  308.         $dir getcwd();
  309.         $this->log(2"building in $dir");
  310.         putenv('PATH=' $this->config->get('bin_dir'':' getenv('PATH'));
  311.         $err $this->_runCommand($this->config->get('php_prefix')
  312.                                 . "phpize" .
  313.                                 $this->config->get('php_suffix'),
  314.                                 array(&$this'phpizeCallback'));
  315.         if (PEAR::isError($err)) {
  316.             return $err;
  317.         }
  318.  
  319.         if (!$err{
  320.             return $this->raiseError("`phpize' failed");
  321.         }
  322.  
  323.         // {{{ start of interactive part
  324.         $configure_command = "$dir/configure";
  325.         $configure_options $pkg->getConfigureOptions();
  326.         if ($configure_options{
  327.             foreach ($configure_options as $o{
  328.                 $default array_key_exists('default'$o$o['default': null;
  329.                 list($r$this->ui->userDialog('build',
  330.                                                  array($o['prompt']),
  331.                                                  array('text'),
  332.                                                  array($default));
  333.                 if (substr($o['name']05== 'with-' &&
  334.                     ($r == 'yes' || $r == 'autodetect')) {
  335.                     $configure_command .= " --$o[name]";
  336.                 else {
  337.                     $configure_command .= " --$o[name]=".trim($r);
  338.                 }
  339.             }
  340.         }
  341.         // }}} end of interactive part
  342.  
  343.         // FIXME make configurable
  344.         if (!$user=getenv('USER')) {
  345.             $user='defaultuser';
  346.         }
  347.  
  348.         $tmpdir $this->config->get('temp_dir');
  349.         $build_basedir System::mktemp(' -t "' $tmpdir '" -d "pear-build-' $user '"');
  350.         $build_dir = "$build_basedir/$vdir";
  351.         $inst_dir = "$build_basedir/install-$vdir";
  352.         $this->log(1"building in $build_dir");
  353.         if (is_dir($build_dir)) {
  354.             System::rm(array('-rf'$build_dir));
  355.         }
  356.  
  357.         if (!System::mkDir(array('-p'$build_dir))) {
  358.             return $this->raiseError("could not create build dir: $build_dir");
  359.         }
  360.  
  361.         $this->addTempFile($build_dir);
  362.         if (!System::mkDir(array('-p'$inst_dir))) {
  363.             return $this->raiseError("could not create temporary install dir: $inst_dir");
  364.         }
  365.         $this->addTempFile($inst_dir);
  366.  
  367.         $make_command getenv('MAKE'getenv('MAKE''make';
  368.  
  369.         $to_run = array(
  370.             $configure_command,
  371.             $make_command,
  372.             "$make_command INSTALL_ROOT=\"$inst_dir\" install",
  373.             "find \"$inst_dir\" | xargs ls -dils"
  374.             );
  375.         if (!file_exists($build_dir|| !is_dir($build_dir|| !chdir($build_dir)) {
  376.             return $this->raiseError("could not chdir to $build_dir");
  377.         }
  378.         putenv('PHP_PEAR_VERSION=1.9.4');
  379.         foreach ($to_run as $cmd{
  380.             $err $this->_runCommand($cmd$callback);
  381.             if (PEAR::isError($err)) {
  382.                 chdir($old_cwd);
  383.                 return $err;
  384.             }
  385.             if (!$err{
  386.                 chdir($old_cwd);
  387.                 return $this->raiseError("`$cmd' failed");
  388.             }
  389.         }
  390.         if (!($dp opendir("modules"))) {
  391.             chdir($old_cwd);
  392.             return $this->raiseError("no `modules' directory found");
  393.         }
  394.         $built_files = array();
  395.         $prefix exec($this->config->get('php_prefix')
  396.                         . "php-config" .
  397.                        $this->config->get('php_suffix'" --prefix");
  398.         $this->_harvestInstDir($prefix$inst_dir . DIRECTORY_SEPARATOR . $prefix$built_files);
  399.         chdir($old_cwd);
  400.         return $built_files;
  401.     }
  402.  
  403.     /**
  404.      * Message callback function used when running the "phpize"
  405.      * program.  Extracts the API numbers used.  Ignores other message
  406.      * types than "cmdoutput".
  407.      *
  408.      * @param string $what the type of message
  409.      * @param mixed $data the message
  410.      *
  411.      * @return void 
  412.      *
  413.      * @access public
  414.      */
  415.     function phpizeCallback($what$data)
  416.     {
  417.         if ($what != 'cmdoutput'{
  418.             return;
  419.         }
  420.         $this->log(1rtrim($data));
  421.         if (preg_match('/You should update your .aclocal.m4/'$data)) {
  422.             return;
  423.         }
  424.         $matches = array();
  425.         if (preg_match('/^\s+(\S[^:]+):\s+(\d{8})/'$data$matches)) {
  426.             $member preg_replace('/[^a-z]/''_'strtolower($matches[1]));
  427.             $apino = (int)$matches[2];
  428.             if (isset($this->$member)) {
  429.                 $this->$member $apino;
  430.                 //$msg = sprintf("%-22s : %d", $matches[1], $apino);
  431.                 //$this->log(1, $msg);
  432.             }
  433.         }
  434.     }
  435.  
  436.     /**
  437.      * Run an external command, using a message callback to report
  438.      * output.  The command will be run through popen and output is
  439.      * reported for every line with a "cmdoutput" message with the
  440.      * line string, including newlines, as payload.
  441.      *
  442.      * @param string $command the command to run
  443.      *
  444.      * @param mixed $callback (optional) function to use as message
  445.      *  callback
  446.      *
  447.      * @return bool whether the command was successful (exit code 0
  448.      *  means success, any other means failure)
  449.      *
  450.      * @access private
  451.      */
  452.     function _runCommand($command$callback = null)
  453.     {
  454.         $this->log(1"running: $command");
  455.         $pp popen("$command 2>&1""r");
  456.         if (!$pp{
  457.             return $this->raiseError("failed to run `$command'");
  458.         }
  459.         if ($callback && $callback[0]->debug == 1{
  460.             $olddbg $callback[0]->debug;
  461.             $callback[0]->debug = 2;
  462.         }
  463.  
  464.         while ($line fgets($pp1024)) {
  465.             if ($callback{
  466.                 call_user_func($callback'cmdoutput'$line);
  467.             else {
  468.                 $this->log(2rtrim($line));
  469.             }
  470.         }
  471.         if ($callback && isset($olddbg)) {
  472.             $callback[0]->debug = $olddbg;
  473.         }
  474.  
  475.         $exitcode is_resource($pppclose($pp: -1;
  476.         return ($exitcode == 0);
  477.     }
  478.  
  479.     function log($level$msg)
  480.     {
  481.         if ($this->current_callback{
  482.             if ($this->debug >= $level{
  483.                 call_user_func($this->current_callback'output'$msg);
  484.             }
  485.             return;
  486.         }
  487.         return PEAR_Common::log($level$msg);
  488.     }
  489. }

Documentation generated on Wed, 06 Jul 2011 23:30:26 +0000 by phpDocumentor 1.4.3. PEAR Logo Copyright © PHP Group 2004.