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