xref: /PHP-7.4/ext/ext_skel.php (revision 58b17906)
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