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