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