1#!/usr/bin/env php 2<?php 3/* 4 +----------------------------------------------------------------------+ 5 | PHP Version 7 | 6 +----------------------------------------------------------------------+ 7 | Copyright (c) The PHP Group | 8 +----------------------------------------------------------------------+ 9 | This source file is subject to version 3.01 of the PHP license, | 10 | that is bundled with this package in the file LICENSE, and is | 11 | available through the world-wide-web at the following url: | 12 | http://www.php.net/license/3_01.txt | 13 | If you did not receive a copy of the PHP license and are unable to | 14 | obtain it through the world-wide-web, please send a note to | 15 | license@php.net so we can mail you a copy immediately. | 16 +----------------------------------------------------------------------+ 17 | Authors: Kalle Sommer Nielsen <kalle@php.net> | 18 +----------------------------------------------------------------------+ 19*/ 20 21/* $Id: ee274d10c3d48f7f979ea21ab174b76b0bd3c534 $ */ 22 23/* {{{ error 24 */ 25function error($message) { 26 printf('Error: %s%s', $message, PHP_EOL); 27 exit; 28} 29/* }}} */ 30 31/* {{{ print_help 32 */ 33function print_help() { 34 if (PHP_OS_FAMILY != 'Windows') { 35 $file_prefix = './'; 36 $make_prefix = ''; 37 } else { 38 $file_prefix = ''; 39 $make_prefix = 'n'; 40 } 41 42 echo <<<HELP 43WHAT IT IS 44 45 It's a tool for automatically creating the basic framework for a PHP extension. 46 47HOW TO USE IT 48 49 Very simple. First, change to the ext/ directory of the PHP sources. Then run 50 the following 51 52 php ext_skel.php --ext extension_name 53 54 and everything you need will be placed in directory ext/extension_name. 55 56 If you don't need to test the existence of any external header files, 57 libraries or functions in them, the extension is ready to be compiled in PHP. 58 To compile the extension run the following: 59 60 cd extension_name 61 phpize 62 {$file_prefix}configure 63 {$make_prefix}make 64 65 Don't forget to run tests once the compilation is done: 66 67 {$make_prefix}make test 68 69 Alternatively, to compile extension in the PHP: 70 71 cd /path/to/php-src 72 {$file_prefix}buildconf 73 {$file_prefix}configure --enable-extension_name 74 {$make_prefix}make 75 {$make_prefix}make test TESTS=ext/extension_name/tests 76 77 The definition of PHP_extension_NAME_VERSION will be present in the 78 php_extension_name.h and injected into the zend_extension_entry definition. 79 This is required by the PECL website for the version string conformity checks 80 against package.xml 81 82SOURCE AND HEADER FILE NAME 83 84 The ext_skel.php script generates 'extension_name.c' and 'php_extension_name.h' 85 as the main source and header files. Keep these names. 86 87 extension functions (User functions) must be named 88 89 extension_name_function() 90 91 When you need to expose extension functions to other extensions, expose 92 functions strictly needed by others. Exposed internal function must be named 93 94 php_extension_name_function() 95 96 See also CODING_STANDARDS.md. 97 98OPTIONS 99 100 php ext_skel.php --ext <name> [--experimental] [--author <name>] 101 [--dir <path>] [--std] [--onlyunix] 102 [--onlywindows] [--help] 103 104 --ext <name> The name of the extension defined as <name> 105 --experimental Passed if this extension is experimental, this creates 106 the EXPERIMENTAL file in the root of the extension 107 --author <name> Your name, this is used if --std is passed and for the 108 CREDITS file 109 --dir <path> Path to the directory for where extension should be 110 created. Defaults to the directory of where this script 111 lives 112 --std If passed, the standard header used in extensions that 113 is included in the core, will be used 114 --onlyunix Only generate configure scripts for Unix 115 --onlywindows Only generate configure scripts for Windows 116 --help This help 117 118HELP; 119 exit; 120} 121/* }}} */ 122 123/* {{{ task 124 */ 125function task($label, $callback) { 126 printf('%s... ', $label); 127 128 $callback(); 129 130 printf('done%s', PHP_EOL); 131} 132/* }}} */ 133 134/* {{{ print_success 135 */ 136function print_success() { 137 global $options; 138 139 if (PHP_OS_FAMILY != 'Windows') { 140 $file_prefix = './'; 141 $make_prefix = ''; 142 } else { 143 $file_prefix = ''; 144 $make_prefix = 'n'; 145 } 146 147 printf('%1$sSuccess. The extension is now ready to be compiled. To do so, use the%s', PHP_EOL); 148 printf('following steps:%1$s%1$s', PHP_EOL); 149 printf('cd /path/to/php-src/%s%s', $options['ext'], PHP_EOL); 150 printf('phpize%s', PHP_EOL); 151 printf('%sconfigure%s', $file_prefix, PHP_EOL); 152 printf('%smake%2$s%2$s', $make_prefix, PHP_EOL); 153 printf('Don\'t forget to run tests once the compilation is done:%s', PHP_EOL); 154 printf('%smake test%2$s%2$s', $make_prefix, PHP_EOL); 155 printf('Thank you for using PHP!%s', PHP_EOL); 156} 157/* }}} */ 158 159/* {{{ process_args 160 */ 161function process_args($argv, $argc) { 162 $options = [ 163 'unix' => true, 164 'windows' => true, 165 'ext' => '', 166 'dir' => __DIR__ . DIRECTORY_SEPARATOR, 167 'skel' => __DIR__ . DIRECTORY_SEPARATOR . 'skeleton' . DIRECTORY_SEPARATOR, 168 'author' => false, 169 'experimental' => false, 170 'std' => false 171 ]; 172 173 for($i = 1; $i < $argc; ++$i) 174 { 175 $val = $argv[$i]; 176 177 if($val[0] != '-' || $val[1] != '-') 178 { 179 continue; 180 } 181 182 switch($opt = strtolower(substr($val, 2))) 183 { 184 case 'help': { 185 print_help(); 186 } 187 case 'onlyunix': { 188 $options['windows'] = false; 189 } 190 break; 191 case 'onlywindows': { 192 $options['unix'] = false; 193 } 194 break; 195 case 'experimental': { 196 $options['experimental'] = true; 197 } 198 break; 199 case 'std': { 200 $options['std'] = true; 201 } 202 break; 203 case 'ext': 204 case 'dir': 205 case 'author': { 206 if (!isset($argv[$i + 1]) || ($argv[$i + 1][0] == '-' && $argv[$i + 1][1] == '-')) { 207 error('Argument "' . $val . '" expects a value, none passed'); 208 } else if ($opt == 'dir' && empty($argv[$i + 1])) { 209 continue 2; 210 } 211 212 $options[$opt] = ($opt == 'dir' ? realpath($argv[$i + 1]) . DIRECTORY_SEPARATOR : $argv[$i + 1]); 213 } 214 break; 215 default: { 216 error('Unsupported argument "' . $val . '" passed'); 217 } 218 } 219 } 220 221 if (empty($options['ext'])) { 222 error('No extension name passed, use "--ext <name>"'); 223 } else if (!$options['unix'] && !$options['windows']) { 224 error('Cannot pass both --onlyunix and --onlywindows'); 225 } else if (!is_dir($options['skel'])) { 226 error('The skeleton directory was not found'); 227 } 228 229 // Validate extension name 230 if (!preg_match('/^[a-z][a-z0-9_]+$/i', $options['ext'])) { 231 error('Invalid extension name. Valid names start with a letter,' 232 .' followed by any number of letters, numbers, or underscores.' 233 .' Using only lower case letters is preferred.'); 234 } 235 236 $options['ext'] = str_replace(['\\', '/'], '', strtolower($options['ext'])); 237 238 return $options; 239} 240/* }}} */ 241 242/* {{{ process_source_tags 243 */ 244function process_source_tags($file, $short_name) { 245 global $options; 246 247 $source = file_get_contents($file); 248 249 if ($source === false) { 250 error('Unable to open file for reading: ' . $short_name); 251 } 252 253 $source = str_replace('%EXTNAME%', $options['ext'], $source); 254 $source = str_replace('%EXTNAMECAPS%', strtoupper($options['ext']), $source); 255 256 if (strpos($short_name, '.c') !== false || strpos($short_name, '.h') !== false) { 257 static $header; 258 259 if (!$header) { 260 if ($options['std']) { 261 $author_len = strlen($options['author']); 262 $credits = $options['author'] . ($author_len && $author_len <= 60 ? str_repeat(' ', 60 - $author_len) : ''); 263 264 $header = <<<"HEADER" 265/* 266 +----------------------------------------------------------------------+ 267 | PHP Version 7 | 268 +----------------------------------------------------------------------+ 269 | Copyright (c) The PHP Group | 270 +----------------------------------------------------------------------+ 271 | This source file is subject to version 3.01 of the PHP license, | 272 | that is bundled with this package in the file LICENSE, and is | 273 | available through the world-wide-web at the following url: | 274 | http://www.php.net/license/3_01.txt | 275 | If you did not receive a copy of the PHP license and are unable to | 276 | obtain it through the world-wide-web, please send a note to | 277 | license@php.net so we can mail you a copy immediately. | 278 +----------------------------------------------------------------------+ 279 | Author: $credits | 280 +----------------------------------------------------------------------+ 281*/ 282HEADER; 283 } else { 284 if ($options['author']) { 285 $header = sprintf('/* %s extension for PHP (c) %d %s */', $options['ext'], date('Y'), $options['author']); 286 } else { 287 $header = sprintf('/* %s extension for PHP */', $options['ext']); 288 } 289 } 290 } 291 292 $source = str_replace('%HEADER%', $header, $source); 293 } 294 295 if (!file_put_contents($file, $source)) { 296 error('Unable to save contents to file: ' . $short_name); 297 } 298} 299/* }}} */ 300 301/* {{{ copy_config_scripts 302 */ 303function copy_config_scripts() { 304 global $options; 305 306 $files = []; 307 308 if ($options['unix']) { 309 $files[] = 'config.m4'; 310 } 311 312 if ($options['windows']) { 313 $files[] = 'config.w32'; 314 } 315 316 $files[] = '.gitignore'; 317 318 foreach($files as $config_script) { 319 $new_config_script = $options['dir'] . $options['ext'] . DIRECTORY_SEPARATOR . $config_script; 320 321 if (!copy($options['skel'] . $config_script . '.in', $new_config_script)) { 322 error('Unable to copy config script: ' . $config_script); 323 } 324 325 process_source_tags($new_config_script, $config_script); 326 } 327} 328/* }}} */ 329 330/* {{{ copy_sources 331 */ 332function copy_sources() { 333 global $options; 334 335 $files = [ 336 'skeleton.c' => $options['ext'] . '.c', 337 'php_skeleton.h' => 'php_' . $options['ext'] . '.h' 338 ]; 339 340 foreach ($files as $src_file => $dst_file) { 341 if (!copy($options['skel'] . $src_file, $options['dir'] . $options['ext'] . DIRECTORY_SEPARATOR . $dst_file)) { 342 error('Unable to copy source file: ' . $src_file); 343 } 344 345 process_source_tags($options['dir'] . $options['ext'] . DIRECTORY_SEPARATOR . $dst_file, $dst_file); 346 } 347} 348/* }}} */ 349 350/* {{{ copy_tests 351 */ 352function copy_tests() { 353 global $options; 354 355 $test_files = glob($options['skel'] . 'tests/*', GLOB_MARK); 356 357 if (!$test_files) { 358 return; 359 } 360 361 foreach ($test_files as $test) { 362 if (is_dir($test)) { 363 continue; 364 } 365 366 $new_test = str_replace([$options['skel'], '/'], ['', DIRECTORY_SEPARATOR], $test); 367 368 if (!copy($test, $options['dir'] . $options['ext'] . DIRECTORY_SEPARATOR . $new_test)) { 369 error('Unable to copy file: ' . $new_test); 370 } 371 372 process_source_tags($options['dir'] . $options['ext'] . DIRECTORY_SEPARATOR . $new_test, $new_test); 373 } 374} 375/* }}} */ 376 377 378if (PHP_SAPI != 'cli') { 379 error('This script is only suited for CLI'); 380} 381 382if ($argc < 1) { 383 print_help(); 384 exit; 385} 386 387$options = process_args($argv, $argc); 388 389if (!$options['dir'] || !is_dir($options['dir'])) { 390 error('The selected output directory does not exist'); 391} else if (is_dir($options['dir'] . $options['ext'])) { 392 error('There is already a folder named "' . $options['ext'] . '" in the output directory'); 393} else if (!mkdir($options['dir'] . $options['ext'])) { 394 error('Unable to create extension directory in the output directory'); 395} else if (!mkdir($options['dir'] . $options['ext'] . DIRECTORY_SEPARATOR . 'tests')) { 396 error('Unable to create the tests directory'); 397} 398 399if ($options['experimental']) { 400 print('Creating EXPERIMENTAL... '); 401 402 if (file_put_contents($options['dir'] . $options['ext'] . DIRECTORY_SEPARATOR . 'EXPERIMENTAL', '') === false) { 403 error('Unable to create the EXPERIMENTAL file'); 404 } 405 406 printf('done%s', PHP_EOL); 407} 408 409if (!empty($options['author'])) { 410 print('Creating CREDITS... '); 411 412 if (!file_put_contents($options['dir'] . $options['ext'] . DIRECTORY_SEPARATOR . 'CREDITS', $options['ext'] . PHP_EOL . $options['author'])) { 413 error('Unable to create the CREDITS file'); 414 } 415 416 printf('done%s', PHP_EOL); 417} 418 419date_default_timezone_set('UTC'); 420 421task('Copying config scripts', 'copy_config_scripts'); 422task('Copying sources', 'copy_sources'); 423task('Copying tests', 'copy_tests'); 424 425print_success(); 426