diff --git a/.gdbinit b/.gdbinit index dfe56360ca..4477828265 100644 --- a/.gdbinit +++ b/.gdbinit @@ -42,7 +42,7 @@ define print_cvs printf "Compiled variables count: %d\n\n", $cv_count while $cv_idx < $cv_count - printf "[%d] '%s'\n", $cv_idx, $cv[$cv_idx].val + printf "[%d] '$%s'\n", $cv_idx, $cv[$cv_idx].val@$cv[$cv_idx].len set $zvalue = ((zval *) $cv_ex_ptr) + $callFrameSize + $cv_idx printzv $zvalue set $cv_idx = $cv_idx + 1 @@ -66,18 +66,18 @@ define dump_bt if $func if $ex->This->value.obj if $func->common.scope - printf "%s->", $func->common.scope->name->val + printf "%s->", (char*)$func->common.scope->name->val else - printf "%s->", $ex->This->value.obj->ce.name->val + printf "%s->", (char*)$ex->This->value.obj->ce.name->val end else if $func->common.scope - printf "%s::", $func->common.scope->name->val + printf "%s::", (char*)$func->common.scope->name->val end end if $func->common.function_name - printf "%s(", $func->common.function_name->val + printf "%s(", (char*)$func->common.function_name->val else printf "(main" end @@ -109,7 +109,7 @@ define dump_bt printf "%f", $zvalue->value.dval end if $type == 6 - ____print_str $zvalue->value.str->val $zvalue->value.str->len + ____print_str (char*)$zvalue->value.str->val $zvalue->value.str->len end if $type == 7 printf "array(%d)[%p]", $zvalue->value.arr->nNumOfElements, $zvalue @@ -135,7 +135,7 @@ define dump_bt end if $func != 0 if $func->type == 2 - printf "%s:%d ", $func->op_array.filename->val, $ex->opline->lineno + printf "%s:%d ", (char*)$func->op_array.filename->val, $ex->opline->lineno else printf "[internal function]" end @@ -186,7 +186,7 @@ define ____printzv_contents printf "double: %f", $zvalue->value.dval end if $type == 6 - printf "string: %s", $zvalue->value.str->val + printf "string: %s", (char*)$zvalue->value.str->val end if $type == 7 printf "array: " @@ -208,7 +208,7 @@ define ____printzv_contents set $handle = $zvalue->value.obj.handle set $handlers = $zvalue->value.obj.handlers set $zobj = $zvalue->value.obj - set $cname = $zobj->ce->name->val + set $cname = (char*)$zobj->ce->name->val printf "(%s) #%d", $cname, $handle if ! $arg1 if $handlers->get_properties == &zend_std_get_properties @@ -233,7 +233,7 @@ define ____printzv_contents set $name = $p->key set $prop = (zend_property_info*)$p->val.value.ptr set $val = (zval*)((char*)$zobj + $prop->offset) - printf "%s => ", $name->val + printf "%s => ", (char*)$name->val printzv $val set $k = $k + 1 end @@ -318,7 +318,8 @@ define ____print_ht set $n = $n - 1 end - if $ht->u.v.flags & 4 + set $packed = $ht->u.v.flags & 4 + if $packed printf "Packed" else printf "Hash" @@ -329,36 +330,45 @@ define ____print_ht set $i = 0 set $ind = $ind + 1 while $i < $num - set $p = (Bucket*)($ht->arData + $i) + if $packed + set $val = (zval*)($ht->arPacked + $i) + set $key = (zend_string*)0 + set $h = $i + else + set $bucket = (Bucket*)($ht->arData + $i) + set $val = &$bucket->val + set $key = $bucket->key + set $h = $bucket->h + end set $n = $ind - if $p->val.u1.v.type > 0 + if $val->u1.v.type > 0 while $n > 0 printf " " set $n = $n - 1 end printf "[%d] ", $i - if $p->key - ____print_str $p->key->val $p->key->len + if $key + ____print_str (char*)$key->val $key->len printf " => " else - printf "%d => ", $p->h + printf "%d => ", $h end if $arg1 == 0 - printf "%p\n", (zval *)&$p->val + printf "%p\n", $val end if $arg1 == 1 - set $zval = (zval *)&$p->val + set $zval = $val ____printzv $zval 1 end if $arg1 == 2 - printf "%s\n", (char*)$p->val.value.ptr + printf "%s\n", (char*)$val->value.ptr end if $arg1 == 3 - set $func = (zend_function*)$p->val.value.ptr - printf "\"%s\"\n", $func->common.function_name->val + set $func = (zend_function*)$val->value.ptr + printf "\"%s\"\n", (char*)$func->common.function_name->val end if $arg1 == 4 - set $const = (zend_constant *)$p->val.value.ptr + set $const = (zend_constant *)$val->value.ptr ____printzv $const 1 end end @@ -413,15 +423,15 @@ define ____print_inh_class printf "final " end end - printf "class %s", $ce->name->val + printf "class %s", (char*)$ce->name->val if $ce->parent != 0 - printf " extends %s", $ce->parent->name->val + printf " extends %s", (char*)$ce->parent->name->val end if $ce->num_interfaces != 0 printf " implements" set $tmp = 0 while $tmp < $ce->num_interfaces - printf " %s", $ce->interfaces[$tmp]->name->val + printf " %s", (char*)$ce->interfaces[$tmp]->name->val set $tmp = $tmp + 1 if $tmp < $ce->num_interfaces printf "," @@ -433,10 +443,10 @@ end define ____print_inh_iface set $ce = $arg0 - printf "interface %s", $ce->name->val + printf "interface %s", (char*)$ce->name->val if $ce->num_interfaces != 0 set $ce = $ce->interfaces[0] - printf " extends %s", $ce->name->val + printf " extends %s", (char*)$ce->name->val else set $ce = 0 end @@ -472,11 +482,11 @@ end define print_pi set $pi = (zend_property_info *)$arg0 - set $initial_offset = ((uint32_t)(zend_uintptr_t)(&((zend_object*)0)->properties_table[(0)])) + set $initial_offset = ((uint32_t)(uintptr_t)(&((zend_object*)0)->properties_table[(0)])) set $ptr_to_val = (zval*)((char*)$pi->ce->default_properties_table + $pi->offset - $initial_offset) printf "[%p] {\n", $pi printf " offset = %p\n", $pi->offset - printf " ce = [%p] %s\n", $pi->ce, $pi->ce->name->val + printf " ce = [%p] %s\n", $pi->ce, (char*)$pi->ce->name->val printf " flags = 0x%x (", $pi->flags if $pi->flags & 0x100 printf "ZEND_ACC_PUBLIC" @@ -598,7 +608,7 @@ define print_zstr set $maxlen = $zstr->len end printf "string(%d) ", $zstr->len - ____print_str $zstr->val $zstr->len $maxlen + ____print_str (char*)$zstr->val $zstr->len $maxlen printf "\n" end diff --git a/.github/workflows/macos-aarch64.yml b/.github/workflows/macos-aarch64.yml index 535a502a9f..4e2a29ac2d 100644 --- a/.github/workflows/macos-aarch64.yml +++ b/.github/workflows/macos-aarch64.yml @@ -7,8 +7,7 @@ env: jobs: macos-aarch64: - if: 1 - runs-on: macos-14 + runs-on: macos-15 # macos-latest (macos-14) 变更了 CPU 架构,由 x86_64 变更为 arm64 # macos-14 CPU 架构 arm64 # macos-13 CPU 架构 x86_64 diff --git a/.github/workflows/macos-x86_64.yml b/.github/workflows/macos-x86_64.yml index 4c95c0251b..c59f9ad0af 100644 --- a/.github/workflows/macos-x86_64.yml +++ b/.github/workflows/macos-x86_64.yml @@ -10,7 +10,7 @@ env: jobs: macos-x86_64: if: 1 - runs-on: macos-13 + runs-on: macos-15-intel # macos-latest (macos-14) 变更了 CPU 架构,由 x86_64 变更为 arm64 # macos-14 CPU 架构 arm64 # macos-13 CPU 架构 x86_64 diff --git a/.github/workflows/windows-cygwin.yml b/.github/workflows/windows-cygwin.yml index 49261424a1..db2a92ed48 100644 --- a/.github/workflows/windows-cygwin.yml +++ b/.github/workflows/windows-cygwin.yml @@ -47,15 +47,6 @@ jobs: run: | echo "BUILD_PHP_VERSION=${{ matrix.php-version }}" >> $GITHUB_ENV - - name: Cache cygwin packages - id: cache-cygwin - uses: actions/cache@v4 - env: - cache-name: cache-cygwin-packages - with: - path: C:\cygwin-packages - key: "${{ runner.os }}-build-${{ env.cache-name }}" - - name: Cache pool id: cache-cygwin-pool uses: actions/cache@v4 diff --git a/.github/workflows/windows-msys2.yml b/.github/workflows/windows-msys2.yml index 6029d731c2..1f6128cdf0 100644 --- a/.github/workflows/windows-msys2.yml +++ b/.github/workflows/windows-msys2.yml @@ -15,7 +15,7 @@ jobs: matrix: php-version: - "8.2.29" - - "8.1.33" + # - "8.1.33" - "8.3.23" - "8.4.10" steps: diff --git a/.gitignore b/.gitignore index f669141531..e0f0057a80 100644 --- a/.gitignore +++ b/.gitignore @@ -311,6 +311,7 @@ tmp-php.ini /ext/inotify /ext/swoole_tracker_agent /ext/loader8 +/ext/swoole /ext/ds /ext/xlswriter /ext/uuid diff --git a/README.md b/README.md index d757978f98..a00194d311 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ curl -fSL https://github.com/swoole/build-static-php/blob/main/setup-php-cli-run - [php-cli 构建选项文档](docs/options.md) - [php-cli 搭建依赖库镜像服务](sapi/download-box/README.md) - [quickstart](sapi/quickstart/README.md) +- [常见问题解答](https://github.com/swoole/swoole-cli/blob/main/docs/FAQ.md) ## Clone diff --git a/docs/ChangeLog.md b/docs/ChangeLog.md new file mode 100644 index 0000000000..5d8437d24d --- /dev/null +++ b/docs/ChangeLog.md @@ -0,0 +1,34 @@ +# [v6.1.0.0-rc1](https://github.com/swoole/swoole-cli/releases/tag/v6.1.0.0-rc1) + +| item | value | +|----------------|--------------| +| branch | main | +| tag | v6.1.0.0-rc1 | +| swoole version | v6.1.0-rc1 | +| php version | 8.1.29 | +| release date | 2025-08-31 | +| status | ok | + +## [change info](https://github.com/swoole/swoole-cli/compare/v6.0.2.0...v6.1.0.0-rc1) + +1. swoole version v6.0.2.0 upgrade to + v6.1.0.0-rc1 , [swoole v6.1.0-rc1 info](https://github.com/swoole/swoole-src/releases/tag/v6.1.0-rc1) + +# [v5.1.8.0](https://github.com/swoole/swoole-cli/releases/tag/v5.1.8.0) + +| item | value | +|----------------|------------| +| branch | v5.1.x | +| tag | v5.1.8.0 | +| swoole version | v5.1.8 | +| php version | 8.1.29 | +| release date | 2025-08-23 | +| status | ok | + +## [change info](https://github.com/swoole/swoole-cli/compare/v5.1.7.0...v5.1.8.0) + +1. swoole version v5.1.7 upgrade to + v5.1.8 , [swoole v5.1.8 info](https://github.com/swoole/swoole-src/releases/tag/v5.1.8) + + + diff --git a/docs/FAQ.md b/docs/FAQ.md new file mode 100644 index 0000000000..94836593cd --- /dev/null +++ b/docs/FAQ.md @@ -0,0 +1,22 @@ +## download swoole-cli + +```shell + +curl -fSL https://github.com/swoole/swoole-cli/blob/main/setup-swoole-cli-runtime.sh?raw=true | bash -s -- --version v5.1.8.0 + +# from https://www.swoole.com/download +curl -fSL https://github.com/swoole/swoole-cli/blob/main/setup-swoole-cli-runtime.sh?raw=true | bash -s -- --version v5.1.8.0 --mirror china + +``` + +## 备注: macos环境下 首次运行提示无权限 ,解决方法 + +note : macos clearing the com.apple.quarantine extended attribute + +``` +xattr ./runtime/swoole-cli/swoole-cli + +sudo xattr -rd com.apple.quarantine ./runtime/swoole-cli/swoole-cli + +``` + diff --git a/docs/diff.md b/docs/diff.md index 729f336278..14606ff8b5 100644 --- a/docs/diff.md +++ b/docs/diff.md @@ -1,14 +1,14 @@ -Added(6) -=============================================================== -+ ds + +# Extensions + +## added(6) + imagick + mongodb + redis + swoole + yaml -Removed(26) -============================================================== +## Removed(26) - calendar - com_dotnet - dba @@ -36,3 +36,13 @@ Removed(26) - sysvshm - tidy +# SAPI +Only CLI SAPI(`sapi/cli`) is available. FPM module(`sapi/fpm`) and cli are merged into one(`sapi/cli/fpm`). + +## List of files maintained by us +- Makefile.frag +- config.m4 +- php_cli.c +- php_cli_server.h +- php_cli_server.c + diff --git a/prepare.php b/prepare.php index 5f1eee1109..be67076ec7 100755 --- a/prepare.php +++ b/prepare.php @@ -164,16 +164,14 @@ //$p->setExtraLdflags(' -framework CoreFoundation'); $p->setExtraLdflags(' '); $homebrew_prefix = trim(shell_exec('brew --prefix')); - $p->withBinPath($homebrew_prefix . '/opt/llvm/bin') - ->withBinPath($homebrew_prefix . '/opt/flex/bin') + $p->withBinPath($homebrew_prefix . '/opt/flex/bin') ->withBinPath($homebrew_prefix . '/opt/bison/bin') ->withBinPath($homebrew_prefix . '/opt/libtool/bin') ->withBinPath($homebrew_prefix . '/opt/m4/bin') ->withBinPath($homebrew_prefix . '/opt/automake/bin/') ->withBinPath($homebrew_prefix . '/opt/autoconf/bin/') ->withBinPath($homebrew_prefix . '/opt/gettext/bin') - ->setLinker('ld64.lld'); - + ->setLinker('ld'); $p->setLogicalProcessors('$(sysctl -n hw.ncpu)'); } else { $p->setLinker('ld.lld'); diff --git a/run-tests.php b/run-tests.php index 39621a39aa..c80dd71688 100755 --- a/run-tests.php +++ b/run-tests.php @@ -7,7 +7,7 @@ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | - | https://php.net/license/3_01.txt | + | https://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | @@ -23,18 +23,10 @@ +----------------------------------------------------------------------+ */ -/* $Id$ */ - -/* Temporary variables while this file is being refactored. */ -/** @var ?JUnit */ -$junit = null; - -/* End temporary variables. */ - /* Let there be no top-level code beyond this point: * Only functions and classes, thanks! * - * Minimum required PHP version: 7.4.0 + * Minimum required PHP version: 8.0.0 */ function show_usage(): void @@ -83,9 +75,11 @@ function show_usage(): void -s Write output to . - -x Sets 'SKIP_SLOW_TESTS' environmental variable. + -x Sets 'SKIP_SLOW_TESTS' environment variable. - --offline Sets 'SKIP_ONLINE_TESTS' environmental variable. + --online Prevents setting the 'SKIP_ONLINE_TESTS' environment variable. + + --offline Sets 'SKIP_ONLINE_TESTS' environment variable (default). --verbose -v Verbose mode. @@ -127,6 +121,9 @@ function show_usage(): void --color --no-color Do/Don't colorize the result type in the test result. + --progress + --no-progress Do/Don't show the current progress. + --repeat [n] Run the tests multiple times in the same process and check the output of the last execution (CLI SAPI only). @@ -149,23 +146,22 @@ function main(): void * looks like it doesn't belong, it probably doesn't; cull at will. */ global $DETAILED, $PHP_FAILED_TESTS, $SHOW_ONLY_GROUPS, $argc, $argv, $cfg, - $cfgfiles, $cfgtypes, $conf_passed, $end_time, $environment, + $end_time, $environment, $exts_skipped, $exts_tested, $exts_to_test, $failed_tests_file, - $ignored_by_ext, $ini_overwrites, $is_switch, $colorize, - $just_save_results, $log_format, $matches, $no_clean, $no_file_cache, - $optionals, $output_file, $pass_option_n, $pass_options, - $pattern_match, $php, $php_cgi, $phpdbg, $preload, $redir_tests, - $repeat, $result_tests_file, $slow_min_ms, $start_time, $switch, - $temp_source, $temp_target, $test_cnt, $test_dirs, - $test_files, $test_idx, $test_list, $test_results, $testfile, - $user_tests, $valgrind, $sum_results, $shuffle, $file_cache, $num_repeats, - $bless; + $ignored_by_ext, $ini_overwrites, $colorize, + $log_format, $no_clean, $no_file_cache, + $pass_options, $php, $php_cgi, $preload, + $result_tests_file, $slow_min_ms, $start_time, + $temp_source, $temp_target, $test_cnt, + $test_files, $test_idx, $test_results, $testfile, + $valgrind, $sum_results, $shuffle, $file_cache, $num_repeats, + $show_progress; // Parallel testing global $workers, $workerID; global $context_line_count; // Temporary for the duration of refactoring - /** @var JUnit */ + /** @var JUnit $junit */ global $junit; define('IS_WINDOWS', substr(PHP_OS, 0, 3) == "WIN"); @@ -228,13 +224,13 @@ function main(): void // fail to reattach to the OpCache because it will be using the // wrong path. die("TEMP environment is NOT set"); - } else { - if (count($environment) == 1) { - // Not having other environment variables, only having TEMP, is - // probably ok, but strange and may make a difference in the - // test pass rate, so warn the user. - echo "WARNING: Only 1 environment variable will be available to tests(TEMP environment variable)" . PHP_EOL; - } + } + + if (count($environment) == 1) { + // Not having other environment variables, only having TEMP, is + // probably ok, but strange and may make a difference in the + // test pass rate, so warn the user. + echo "WARNING: Only 1 environment variable will be available to tests(TEMP environment variable)" , PHP_EOL; } } @@ -242,10 +238,6 @@ function main(): void $environment["SystemRoot"] = getenv("SystemRoot"); } - $php = null; - $php_cgi = null; - $phpdbg = null; - if (getenv('TEST_PHP_LOG_FORMAT')) { $log_format = strtoupper(getenv('TEST_PHP_LOG_FORMAT')); } else { @@ -268,10 +260,9 @@ function main(): void } // Check whether user test dirs are requested. + $user_tests = []; if (getenv('TEST_PHP_USER')) { $user_tests = explode(',', getenv('TEST_PHP_USER')); - } else { - $user_tests = []; } $exts_to_test = []; @@ -305,6 +296,10 @@ function main(): void 'opcache.jit_hot_func=1', 'opcache.jit_hot_return=1', 'opcache.jit_hot_side_exit=1', + 'opcache.jit_max_root_traces=100000', + 'opcache.jit_max_side_traces=100000', + 'opcache.jit_max_exit_counters=100000', + 'opcache.protect_memory=1', 'zend.assertions=1', 'zend.exception_ignore_args=0', 'zend.exception_string_param_max_len=15', @@ -313,11 +308,6 @@ function main(): void $no_file_cache = '-d opcache.file_cache= -d opcache.file_cache_only=0'; - define('PHP_QA_EMAIL', 'qa-reports@lists.php.net'); - define('QA_SUBMISSION_PAGE', 'http://qa.php.net/buildtest-process.php'); - define('QA_REPORTS_PAGE', 'http://qa.php.net/reports'); - define('TRAVIS_CI', (bool) getenv('TRAVIS')); - // Determine the tests to be run. $test_files = []; @@ -363,6 +353,9 @@ function main(): void $workers = null; $context_line_count = 3; $num_repeats = 1; + $show_progress = true; + $ignored_by_ext = []; + $online = null; $cfgtypes = ['show', 'keep']; $cfgfiles = ['skip', 'php', 'clean', 'out', 'diff', 'exp', 'mem']; @@ -398,15 +391,13 @@ function main(): void $is_switch = true; - if ($repeat) { - foreach ($cfgtypes as $type) { - if (strpos($switch, '--' . $type) === 0) { - foreach ($cfgfiles as $file) { - if ($switch == '--' . $type . '-' . $file) { - $cfg[$type][$file] = true; - $is_switch = false; - break; - } + foreach ($cfgtypes as $type) { + if (strpos($switch, '--' . $type) === 0) { + foreach ($cfgfiles as $file) { + if ($switch == '--' . $type . '-' . $file) { + $cfg[$type][$file] = true; + $is_switch = false; + break; } } } @@ -422,7 +413,7 @@ function main(): void switch ($switch) { case 'j': $workers = substr($argv[$i], 2); - if (!preg_match('/^\d+$/', $workers) || $workers == 0) { + if ($workers == 0 || !preg_match('/^\d+$/', $workers)) { error("'$workers' is not a valid number of workers, try e.g. -j16 for 16 workers"); } $workers = intval($workers, 10); @@ -439,10 +430,8 @@ function main(): void $matches = []; if (preg_match('/^#.*\[(.*)\]\:\s+(.*)$/', $test, $matches)) { $redir_tests[] = [$matches[1], $matches[2]]; - } else { - if (strlen($test)) { - $test_files[] = trim($test); - } + } elseif (strlen($test)) { + $test_files[] = trim($test); } } } @@ -469,13 +458,11 @@ function main(): void case 'g': $SHOW_ONLY_GROUPS = explode(",", $argv[++$i]); break; - //case 'h' case '--keep-all': foreach ($cfgfiles as $file) { $cfg['keep'][$file] = true; } break; - //case 'l' case 'm': $valgrind = new RuntestsValgrind($environment); break; @@ -524,7 +511,6 @@ function main(): void putenv('NO_INTERACTION=1'); $environment['NO_INTERACTION'] = 1; break; - //case 'r' case 's': $output_file = $argv[++$i]; $just_save_results = true; @@ -568,8 +554,11 @@ function main(): void case 'x': $environment['SKIP_SLOW_TESTS'] = 1; break; + case '--online': + $online = true; + break; case '--offline': - $environment['SKIP_ONLINE_TESTS'] = 1; + $online = false; break; case '--shuffle': $shuffle = true; @@ -598,7 +587,6 @@ function main(): void case '--bless': $bless = true; break; - //case 'w' case '-': // repeat check with full switch $switch = $argv[$i]; @@ -606,6 +594,12 @@ function main(): void $repeat = true; } break; + case '--progress': + $show_progress = true; + break; + case '--no-progress': + $show_progress = false; + break; case '--version': echo '$Id$' . "\n"; exit(1); @@ -628,63 +622,62 @@ function main(): void if (!$testfile && strpos($argv[$i], '*') !== false && function_exists('glob')) { if (substr($argv[$i], -5) == '.phpt') { $pattern_match = glob($argv[$i]); + } elseif (preg_match("/\*$/", $argv[$i])) { + $pattern_match = glob($argv[$i] . '.phpt'); } else { - if (preg_match("/\*$/", $argv[$i])) { - $pattern_match = glob($argv[$i] . '.phpt'); - } else { - die('Cannot find test file "' . $argv[$i] . '".' . PHP_EOL); - } + die('Cannot find test file "' . $argv[$i] . '".' . PHP_EOL); } if (is_array($pattern_match)) { $test_files = array_merge($test_files, $pattern_match); } + } elseif (is_dir($testfile)) { + find_files($testfile); + } elseif (substr($testfile, -5) == '.phpt') { + $test_files[] = $testfile; } else { - if (is_dir($testfile)) { - find_files($testfile); - } else { - if (substr($testfile, -5) == '.phpt') { - $test_files[] = $testfile; - } else { - die('Cannot find test file "' . $argv[$i] . '".' . PHP_EOL); - } - } + die('Cannot find test file "' . $argv[$i] . '".' . PHP_EOL); } } } + if ($online === null && !isset($environment['SKIP_ONLINE_TESTS'])) { + $online = false; + } + if ($online !== null) { + $environment['SKIP_ONLINE_TESTS'] = $online ? '0' : '1'; + } + + if (!defined('STDIN') || !stream_isatty(STDIN) + || !defined('STDOUT') || !stream_isatty(STDOUT) + || !defined('STDERR') || !stream_isatty(STDERR)) { + $environment['SKIP_IO_CAPTURE_TESTS'] = '1'; + } + if ($selected_tests && count($test_files) === 0) { echo "No tests found.\n"; return; } if (!$php) { - $php = getenv('TEST_PHP_EXECUTABLE'); - } - if (!$php) { - $php = PHP_BINARY; - } - - if (!$php_cgi) { - $php_cgi = getenv('TEST_PHP_CGI_EXECUTABLE'); - } - if (!$php_cgi) { - $php_cgi = get_binary($php, 'php-cgi', 'sapi/cgi/php-cgi'); + $php = getenv('TEST_PHP_EXECUTABLE') ?: PHP_BINARY; } - if (!$phpdbg) { - $phpdbg = getenv('TEST_PHPDBG_EXECUTABLE'); - } - if (!$phpdbg) { - $phpdbg = get_binary($php, 'phpdbg', 'sapi/phpdbg/phpdbg'); - } + $php_cgi = getenv('TEST_PHP_CGI_EXECUTABLE') ?: get_binary($php, 'php-cgi', 'sapi/cgi/php-cgi'); + $phpdbg = getenv('TEST_PHPDBG_EXECUTABLE') ?: get_binary($php, 'phpdbg', 'sapi/phpdbg/phpdbg'); putenv("TEST_PHP_EXECUTABLE=$php"); $environment['TEST_PHP_EXECUTABLE'] = $php; + putenv("TEST_PHP_EXECUTABLE_ESCAPED=" . escapeshellarg($php)); + $environment['TEST_PHP_EXECUTABLE_ESCAPED'] = escapeshellarg($php); putenv("TEST_PHP_CGI_EXECUTABLE=$php_cgi"); $environment['TEST_PHP_CGI_EXECUTABLE'] = $php_cgi; + putenv("TEST_PHP_CGI_EXECUTABLE_ESCAPED=" . escapeshellarg($php_cgi ?? '')); + $environment['TEST_PHP_CGI_EXECUTABLE_ESCAPED'] = escapeshellarg($php_cgi ?? ''); putenv("TEST_PHPDBG_EXECUTABLE=$phpdbg"); $environment['TEST_PHPDBG_EXECUTABLE'] = $phpdbg; + putenv("TEST_PHPDBG_EXECUTABLE_ESCAPED=" . escapeshellarg($phpdbg ?? '')); + $environment['TEST_PHPDBG_EXECUTABLE_ESCAPED'] = escapeshellarg($phpdbg ?? ''); if ($conf_passed !== null) { if (IS_WINDOWS) { @@ -700,19 +693,20 @@ function main(): void // Run selected tests. $test_cnt = count($test_files); - verify_config(); - write_information(); + verify_config($php); + write_information($user_tests, $phpdbg); if ($test_cnt) { putenv('NO_INTERACTION=1'); usort($test_files, "test_sort"); - $start_time = time(); + $start_timestamp = time(); + $start_time = hrtime(true); echo "Running selected tests.\n"; $test_idx = 0; run_all_tests($test_files, $environment); - $end_time = time(); + $end_time = hrtime(true); if ($failed_tests_file) { fclose($failed_tests_file); @@ -732,33 +726,21 @@ function main(): void echo get_summary(false); if ($output_file != '' && $just_save_results) { - save_or_mail_results(); + save_results($output_file, /* prompt_to_save_results: */ false); } } else { // Compile a list of all test files (*.phpt). $test_files = []; - $exts_tested = count($exts_to_test); - $exts_skipped = 0; - $ignored_by_ext = 0; + $exts_tested = $exts_to_test; + $exts_skipped = []; sort($exts_to_test); - $test_dirs = []; - $optionals = ['Zend', 'tests', 'ext', 'sapi']; - foreach ($optionals as $dir) { + foreach (['Zend', 'tests', 'ext', 'sapi'] as $dir) { if (is_dir($dir)) { - $test_dirs[] = $dir; + find_files(TEST_PHP_SRCDIR . "/{$dir}", $dir == 'ext'); } } - // Convert extension names to lowercase - foreach ($exts_to_test as $key => $val) { - $exts_to_test[$key] = strtolower($val); - } - - foreach ($test_dirs as $dir) { - find_files(TEST_PHP_SRCDIR . "/{$dir}", $dir == 'ext'); - } - foreach ($user_tests as $dir) { find_files($dir, $dir == 'ext'); } @@ -766,13 +748,14 @@ function main(): void $test_files = array_unique($test_files); usort($test_files, "test_sort"); - $start_time = time(); - show_start($start_time); + $start_timestamp = time(); + $start_time = hrtime(true); + show_start($start_timestamp); $test_cnt = count($test_files); $test_idx = 0; run_all_tests($test_files, $environment); - $end_time = time(); + $end_time = hrtime(true); if ($failed_tests_file) { fclose($failed_tests_file); @@ -791,10 +774,10 @@ function main(): void compute_summary(); - show_end($end_time); + show_end($start_timestamp, $start_time, $end_time); show_summary(); - save_or_mail_results(); + save_results($output_file, /* prompt_to_save_results: */ true); } $junit->saveXML(); @@ -824,10 +807,8 @@ function hrtime(bool $as_num = false) } } -function verify_config(): void +function verify_config(string $php): void { - global $php; - if (empty($php) || !file_exists($php)) { error('environment variable TEST_PHP_EXECUTABLE must be set to specify PHP executable!'); } @@ -837,9 +818,13 @@ function verify_config(): void } } -function write_information(): void +/** + * @param string[] $user_tests + */ +function write_information(array $user_tests, $phpdbg): void { - global $php, $php_cgi, $phpdbg, $php_info, $user_tests, $ini_overwrites, $pass_options, $exts_to_test, $valgrind, $no_file_cache; + global $php, $php_cgi, $php_info, $ini_overwrites, $pass_options, $exts_to_test, $valgrind, $no_file_cache; + $php_escaped = escapeshellarg($php); // Get info from php $info_file = __DIR__ . '/run-test-info.php'; @@ -855,11 +840,12 @@ function write_information(): void $info_params = []; settings2array($ini_overwrites, $info_params); $info_params = settings2params($info_params); - $php_info = `$php $pass_options $info_params $no_file_cache "$info_file"`; - define('TESTED_PHP_VERSION', `$php -n -r "echo PHP_VERSION;"`); + $php_info = shell_exec("$php_escaped $pass_options $info_params $no_file_cache \"$info_file\""); + define('TESTED_PHP_VERSION', shell_exec("$php_escaped -n -r \"echo PHP_VERSION;\"")); if ($php_cgi && $php != $php_cgi) { - $php_info_cgi = `$php_cgi $pass_options $info_params $no_file_cache -q "$info_file"`; + $php_cgi_escaped = escapeshellarg($php_cgi); + $php_info_cgi = shell_exec("$php_cgi_escaped $pass_options $info_params $no_file_cache -q \"$info_file\""); $php_info_sep = "\n---------------------------------------------------------------------"; $php_cgi_info = "$php_info_sep\nPHP : $php_cgi $php_info_cgi$php_info_sep"; } else { @@ -867,7 +853,8 @@ function write_information(): void } if ($phpdbg) { - $phpdbg_info = `$phpdbg $pass_options $info_params $no_file_cache -qrr "$info_file"`; + $phpdbg_escaped = escapeshellarg($phpdbg); + $phpdbg_info = shell_exec("$phpdbg_escaped $pass_options $info_params $no_file_cache -qrr \"$info_file\""); $php_info_sep = "\n---------------------------------------------------------------------"; $phpdbg_info = "$php_info_sep\nPHP : $phpdbg $phpdbg_info$php_info_sep"; } else { @@ -882,20 +869,19 @@ function write_information(): void // load list of enabled and loadable extensions save_text($info_file, <<<'PHP' - PHP); - $exts_to_test = explode(',', `$php $pass_options $info_params $no_file_cache "$info_file"`); + echo implode(',', $exts); + PHP); + $extensionsNames = explode(',', shell_exec("$php_escaped $pass_options $info_params $no_file_cache \"$info_file\"")); + $exts_to_test = array_unique(remap_loaded_extensions_names($extensionsNames)); // check for extensions that need special handling and regenerate $info_params_ex = [ 'session' => ['session.auto_start=0'], @@ -930,134 +916,100 @@ function write_information(): void "; } -function save_or_mail_results(): void +function save_results(string $output_file, bool $prompt_to_save_results): void { - global $sum_results, $just_save_results, $failed_test_summary, - $PHP_FAILED_TESTS, $php, $output_file; + global $sum_results, $failed_test_summary, $PHP_FAILED_TESTS, $php; + + if (getenv('NO_INTERACTION')) { + return; + } - /* We got failed Tests, offer the user to send an e-mail to QA team, unless NO_INTERACTION is set */ - if (!getenv('NO_INTERACTION') && !TRAVIS_CI) { + if ($prompt_to_save_results) { + /* We got failed Tests, offer the user to save a QA report */ $fp = fopen("php://stdin", "r+"); if ($sum_results['FAILED'] || $sum_results['BORKED'] || $sum_results['WARNED'] || $sum_results['LEAKED']) { echo "\nYou may have found a problem in PHP."; } - echo "\nThis report can be automatically sent to the PHP QA team at\n"; - echo QA_REPORTS_PAGE . " and http://news.php.net/php.qa.reports\n"; + echo "\nThis report can be saved and used to open an issue on the bug tracker at\n"; + echo "https://github.com/php/php-src/issues\n"; echo "This gives us a better understanding of PHP's behavior.\n"; - echo "If you don't want to send the report immediately you can choose\n"; - echo "option \"s\" to save it. You can then email it to " . PHP_QA_EMAIL . " later.\n"; - echo "Do you want to send this report now? [Yns]: "; + echo "Do you want to save this report in a file? [Yn]: "; flush(); $user_input = fgets($fp, 10); - $just_save_results = (!empty($user_input) && strtolower($user_input[0]) === 's'); - } - - if ($just_save_results || !getenv('NO_INTERACTION') || TRAVIS_CI) { - if ($just_save_results || TRAVIS_CI || strlen(trim($user_input)) == 0 || strtolower($user_input[0]) == 'y') { - /* - * Collect information about the host system for our report - * Fetch phpinfo() output so that we can see the PHP environment - * Make an archive of all the failed tests - * Send an email - */ - if ($just_save_results) { - $user_input = 's'; - } - - /* Ask the user to provide an email address, so that QA team can contact the user */ - if (TRAVIS_CI) { - $user_email = 'travis at php dot net'; - } elseif (!strncasecmp($user_input, 'y', 1) || strlen(trim($user_input)) == 0) { - echo "\nPlease enter your email address.\n(Your address will be mangled so that it will not go out on any\nmailinglist in plain text): "; - flush(); - $user_email = trim(fgets($fp, 1024)); - $user_email = str_replace("@", " at ", str_replace(".", " dot ", $user_email)); - } - - $failed_tests_data = ''; - $sep = "\n" . str_repeat('=', 80) . "\n"; - $failed_tests_data .= $failed_test_summary . "\n"; - $failed_tests_data .= get_summary(true) . "\n"; - - if ($sum_results['FAILED']) { - foreach ($PHP_FAILED_TESTS['FAILED'] as $test_info) { - $failed_tests_data .= $sep . $test_info['name'] . $test_info['info']; - $failed_tests_data .= $sep . file_get_contents(realpath($test_info['output'])); - $failed_tests_data .= $sep . file_get_contents(realpath($test_info['diff'])); - $failed_tests_data .= $sep . "\n\n"; - } - $status = "failed"; - } else { - $status = "success"; - } - - $failed_tests_data .= "\n" . $sep . 'BUILD ENVIRONMENT' . $sep; - $failed_tests_data .= "OS:\n" . PHP_OS . " - " . php_uname() . "\n\n"; - $ldd = $autoconf = $sys_libtool = $libtool = $compiler = 'N/A'; - - if (!IS_WINDOWS) { - /* If PHP_AUTOCONF is set, use it; otherwise, use 'autoconf'. */ - if (getenv('PHP_AUTOCONF')) { - $autoconf = shell_exec(getenv('PHP_AUTOCONF') . ' --version'); - } else { - $autoconf = shell_exec('autoconf --version'); - } + fclose($fp); + if (!(strlen(trim($user_input)) == 0 || strtolower($user_input[0]) == 'y')) { + return; + } + } + /** + * Collect information about the host system for our report + * Fetch phpinfo() output so that we can see the PHP environment + * Make an archive of all the failed tests + */ + $failed_tests_data = ''; + $sep = "\n" . str_repeat('=', 80) . "\n"; + $failed_tests_data .= $failed_test_summary . "\n"; + $failed_tests_data .= get_summary(true) . "\n"; - /* Always use the generated libtool - Mac OSX uses 'glibtool' */ - $libtool = shell_exec(INIT_DIR . '/libtool --version'); + if ($sum_results['FAILED']) { + foreach ($PHP_FAILED_TESTS['FAILED'] as $test_info) { + $failed_tests_data .= $sep . $test_info['name'] . $test_info['info']; + $failed_tests_data .= $sep . file_get_contents(realpath($test_info['output'])); + $failed_tests_data .= $sep . file_get_contents(realpath($test_info['diff'])); + $failed_tests_data .= $sep . "\n\n"; + } + } - /* Use shtool to find out if there is glibtool present (MacOSX) */ - $sys_libtool_path = shell_exec(__DIR__ . '/build/shtool path glibtool libtool'); + $failed_tests_data .= "\n" . $sep . 'BUILD ENVIRONMENT' . $sep; + $failed_tests_data .= "OS:\n" . PHP_OS . " - " . php_uname() . "\n\n"; + $ldd = $autoconf = $sys_libtool = $libtool = $compiler = 'N/A'; - if ($sys_libtool_path) { - $sys_libtool = shell_exec(str_replace("\n", "", $sys_libtool_path) . ' --version'); - } + if (!IS_WINDOWS) { + /* If PHP_AUTOCONF is set, use it; otherwise, use 'autoconf'. */ + if (getenv('PHP_AUTOCONF')) { + $autoconf = shell_exec(getenv('PHP_AUTOCONF') . ' --version'); + } else { + $autoconf = shell_exec('autoconf --version'); + } - /* Try the most common flags for 'version' */ - $flags = ['-v', '-V', '--version']; - $cc_status = 0; + /* Always use the generated libtool - Mac OSX uses 'glibtool' */ + $libtool = shell_exec(INIT_DIR . '/libtool --version'); - foreach ($flags as $flag) { - system(getenv('CC') . " $flag >/dev/null 2>&1", $cc_status); - if ($cc_status == 0) { - $compiler = shell_exec(getenv('CC') . " $flag 2>&1"); - break; - } - } + /* Use shtool to find out if there is glibtool present (MacOSX) */ + $sys_libtool_path = shell_exec(__DIR__ . '/build/shtool path glibtool libtool'); - $ldd = shell_exec("ldd $php 2>/dev/null"); - } + if ($sys_libtool_path) { + $sys_libtool = shell_exec(str_replace("\n", "", $sys_libtool_path) . ' --version'); + } - $failed_tests_data .= "Autoconf:\n$autoconf\n"; - $failed_tests_data .= "Bundled Libtool:\n$libtool\n"; - $failed_tests_data .= "System Libtool:\n$sys_libtool\n"; - $failed_tests_data .= "Compiler:\n$compiler\n"; - $failed_tests_data .= "Bison:\n" . shell_exec('bison --version 2>/dev/null') . "\n"; - $failed_tests_data .= "Libraries:\n$ldd\n"; - $failed_tests_data .= "\n"; + /* Try the most common flags for 'version' */ + $flags = ['-v', '-V', '--version']; + $cc_status = 0; - if (isset($user_email)) { - $failed_tests_data .= "User's E-mail: " . $user_email . "\n\n"; + foreach ($flags as $flag) { + system(getenv('CC') . " $flag >/dev/null 2>&1", $cc_status); + if ($cc_status == 0) { + $compiler = shell_exec(getenv('CC') . " $flag 2>&1"); + break; } + } - $failed_tests_data .= $sep . "PHPINFO" . $sep; - $failed_tests_data .= shell_exec($php . ' -ddisplay_errors=stderr -dhtml_errors=0 -i 2> /dev/null'); - - if (($just_save_results || !mail_qa_team($failed_tests_data, $status)) && !TRAVIS_CI) { - file_put_contents($output_file, $failed_tests_data); + $ldd = shell_exec("ldd $php 2>/dev/null"); + } - if (!$just_save_results) { - echo "\nThe test script was unable to automatically send the report to PHP's QA Team\n"; - } + $failed_tests_data .= "Autoconf:\n$autoconf\n"; + $failed_tests_data .= "Bundled Libtool:\n$libtool\n"; + $failed_tests_data .= "System Libtool:\n$sys_libtool\n"; + $failed_tests_data .= "Compiler:\n$compiler\n"; + $failed_tests_data .= "Bison:\n" . shell_exec('bison --version 2>/dev/null') . "\n"; + $failed_tests_data .= "Libraries:\n$ldd\n"; + $failed_tests_data .= "\n"; + $failed_tests_data .= $sep . "PHPINFO" . $sep; + $failed_tests_data .= shell_exec($php . ' -ddisplay_errors=stderr -dhtml_errors=0 -i 2> /dev/null'); - echo "Please send " . $output_file . " to " . PHP_QA_EMAIL . " manually, thank you.\n"; - } elseif (!getenv('NO_INTERACTION') && !TRAVIS_CI) { - fwrite($fp, "\nThank you for helping to make PHP better.\n"); - fclose($fp); - } - } - } + file_put_contents($output_file, $failed_tests_data); + echo "Report saved to: ", $output_file, "\n"; } function get_binary(string $php, string $sapi, string $sapi_path): ?string @@ -1086,9 +1038,9 @@ function find_files(string $dir, bool $is_ext_dir = false, bool $ignore = false) while (($name = readdir($o)) !== false) { if (is_dir("{$dir}/{$name}") && !in_array($name, ['.', '..', '.svn'])) { - $skip_ext = ($is_ext_dir && !in_array(strtolower($name), $exts_to_test)); + $skip_ext = ($is_ext_dir && !in_array($name, $exts_to_test)); if ($skip_ext) { - $exts_skipped++; + $exts_skipped[] = $name; } find_files("{$dir}/{$name}", false, $ignore || $skip_ext); } @@ -1100,11 +1052,13 @@ function find_files(string $dir, bool $is_ext_dir = false, bool $ignore = false) } // Otherwise we're only interested in *.phpt files. - if (substr($name, -5) == '.phpt') { + // (but not those starting with a dot, which are hidden on + // many platforms) + if (substr($name, -5) == '.phpt' && substr($name, 0, 1) !== '.') { + $testfile = realpath("{$dir}/{$name}"); if ($ignore) { - $ignored_by_ext++; + $ignored_by_ext[] = $testfile; } else { - $testfile = realpath("{$dir}/{$name}"); $test_files[] = $testfile; } } @@ -1120,9 +1074,9 @@ function test_name($name): string { if (is_array($name)) { return $name[0] . ':' . $name[1]; - } else { - return $name; } + + return $name; } /** * @param array|string $a @@ -1140,55 +1094,9 @@ function test_sort($a, $b): int if ($ta == $tb) { return strcmp($a, $b); - } else { - return $tb - $ta; } -} - -// -// Send Email to QA Team -// - -function mail_qa_team(string $data, bool $status = false): bool -{ - $url_bits = parse_url(QA_SUBMISSION_PAGE); - if ($proxy = getenv('http_proxy')) { - $proxy = parse_url($proxy); - $path = $url_bits['host'] . $url_bits['path']; - $host = $proxy['host']; - if (empty($proxy['port'])) { - $proxy['port'] = 80; - } - $port = $proxy['port']; - } else { - $path = $url_bits['path']; - $host = $url_bits['host']; - $port = empty($url_bits['port']) ? 80 : $port = $url_bits['port']; - } - - $data = "php_test_data=" . urlencode(base64_encode(str_replace("\00", '[0x0]', $data))); - $data_length = strlen($data); - - $fs = fsockopen($host, $port, $errno, $errstr, 10); - - if (!$fs) { - return false; - } - - $php_version = urlencode(TESTED_PHP_VERSION); - - echo "\nPosting to " . QA_SUBMISSION_PAGE . "\n"; - fwrite($fs, "POST " . $path . "?status=$status&version=$php_version HTTP/1.1\r\n"); - fwrite($fs, "Host: " . $host . "\r\n"); - fwrite($fs, "User-Agent: QA Browser 0.1\r\n"); - fwrite($fs, "Content-Type: application/x-www-form-urlencoded\r\n"); - fwrite($fs, "Content-Length: " . $data_length . "\r\n\r\n"); - fwrite($fs, $data); - fwrite($fs, "\r\n\r\n"); - fclose($fs); - - return true; + return $tb - $ta; } // @@ -1199,10 +1107,8 @@ function save_text(string $filename, string $text, ?string $filename_copy = null { global $DETAILED; - if ($filename_copy && $filename_copy != $filename) { - if (file_put_contents($filename_copy, $text) === false) { - error("Cannot open file '" . $filename_copy . "' (save_text)"); - } + if ($filename_copy && $filename_copy != $filename && file_put_contents($filename_copy, $text) === false) { + error("Cannot open file '" . $filename_copy . "' (save_text)"); } if (file_put_contents($filename, $text) === false) { @@ -1252,6 +1158,13 @@ function system_with_timeout( ) { global $valgrind; + // when proc_open cmd is passed as a string (without bypass_shell=true option) the cmd goes thru shell + // and on Windows quotes are discarded, this is a fix to honor the quotes and allow values containing + // spaces like '"C:\Program Files\PHP\php.exe"' to be passed as 1 argument correctly + if (IS_WINDOWS) { + $commandline = 'start "" /b /wait ' . $commandline . ' & exit'; + } + $data = ''; $bin_env = []; @@ -1299,12 +1212,16 @@ function system_with_timeout( if ($n === false) { break; - } elseif ($n === 0) { + } + + if ($n === 0) { /* timed out */ $data .= "\n ** ERROR: process timed out **\n"; proc_terminate($proc, 9); return $data; - } elseif ($n > 0) { + } + + if ($n > 0) { if ($captureStdOut) { $line = fread($pipes[1], 8192); } elseif ($captureStdErr) { @@ -1336,10 +1253,7 @@ function system_with_timeout( return $data; } -/** - * @param string|array|null $redir_tested - */ -function run_all_tests(array $test_files, array $env, $redir_tested = null): void +function run_all_tests(array $test_files, array $env, ?string $redir_tested = null): void { global $test_results, $failed_tests_file, $result_tests_file, $php, $test_idx, $file_cache; global $preload; @@ -1417,12 +1331,9 @@ function run_all_tests(array $test_files, array $env, $redir_tested = null): voi } } -/** The heart of parallel testing. - * @param string|array|null $redir_tested - */ -function run_all_tests_parallel(array $test_files, array $env, $redir_tested): void +function run_all_tests_parallel(array $test_files, array $env, ?string $redir_tested): void { - global $workers, $test_idx, $test_cnt, $test_results, $failed_tests_file, $result_tests_file, $PHP_FAILED_TESTS, $shuffle, $SHOW_ONLY_GROUPS, $valgrind; + global $workers, $test_idx, $test_results, $failed_tests_file, $result_tests_file, $PHP_FAILED_TESTS, $shuffle, $valgrind, $show_progress; global $junit; @@ -1533,10 +1444,6 @@ function run_all_tests_parallel(array $test_files, array $env, $redir_tested): v "constants" => [ "INIT_DIR" => INIT_DIR, "TEST_PHP_SRCDIR" => TEST_PHP_SRCDIR, - "PHP_QA_EMAIL" => PHP_QA_EMAIL, - "QA_SUBMISSION_PAGE" => QA_SUBMISSION_PAGE, - "QA_REPORTS_PAGE" => QA_REPORTS_PAGE, - "TRAVIS_CI" => TRAVIS_CI ] ])) . "\n"; @@ -1665,8 +1572,7 @@ function run_all_tests_parallel(array $test_files, array $env, $redir_tested): v ]); } else { proc_terminate($workerProcs[$i]); - unset($workerProcs[$i]); - unset($workerSocks[$i]); + unset($workerProcs[$i], $workerSocks[$i]); goto escape; } break; @@ -1677,13 +1583,13 @@ function run_all_tests_parallel(array $test_files, array $env, $redir_tested): v } $test_idx++; - if (!$SHOW_ONLY_GROUPS) { + if ($show_progress) { clear_show_test(); } echo $resultText; - if (!$SHOW_ONLY_GROUPS) { + if ($show_progress) { show_test($test_idx, count($workerProcs) . "/$workers concurrent test workers running"); } @@ -1716,7 +1622,6 @@ function run_all_tests_parallel(array $test_files, array $env, $redir_tested): v 'E_USER_ERROR', 'E_USER_WARNING', 'E_USER_NOTICE', - 'E_STRICT', // TODO Cleanup when removed from Zend Engine. 'E_RECOVERABLE_ERROR', 'E_DEPRECATED', 'E_USER_DEPRECATED' @@ -1733,7 +1638,7 @@ function run_all_tests_parallel(array $test_files, array $env, $redir_tested): v } } - if (!$SHOW_ONLY_GROUPS) { + if ($show_progress) { clear_show_test(); } @@ -1744,11 +1649,47 @@ function run_all_tests_parallel(array $test_files, array $env, $redir_tested): v } } +/** + * Calls fwrite and retries when network writes fail with errors such as "Resource temporarily unavailable" + * + * @param resource $stream the stream to fwrite to + * @param string $data + * @return int|false + */ +function safe_fwrite($stream, string $data) +{ + // safe_fwrite was tested by adding $message['unused'] = str_repeat('a', 20_000_000); in send_message() + // fwrites on tcp sockets can return false or less than strlen if the recipient is busy. + // (e.g. fwrite(): Send of 577 bytes failed with errno=35 Resource temporarily unavailable) + $bytes_written = 0; + while ($bytes_written < strlen($data)) { + $n = @fwrite($stream, substr($data, $bytes_written)); + if ($n === false) { + $write_streams = [$stream]; + $read_streams = []; + $except_streams = []; + /* Wait for up to 10 seconds for the stream to be ready to write again. */ + $result = stream_select($read_streams, $write_streams, $except_streams, 10); + if (!$result) { + echo "ERROR: send_message() stream_select() failed\n"; + return false; + } + $n = @fwrite($stream, substr($data, $bytes_written)); + if ($n === false) { + echo "ERROR: send_message() Failed to write chunk after stream_select: " . error_get_last()['message'] . "\n"; + return false; + } + } + $bytes_written += $n; + } + return $bytes_written; +} + function send_message($stream, array $message): void { $blocking = stream_get_meta_data($stream)["blocked"]; stream_set_blocking($stream, true); - fwrite($stream, base64_encode(serialize($message)) . "\n"); + safe_fwrite($stream, base64_encode(serialize($message)) . "\n"); stream_set_blocking($stream, $blocking); } @@ -1851,7 +1792,8 @@ function show_file_block(string $file, string $block, ?string $section = null): } } -function skip_test(string $tested, string $tested_file, string $shortname, string $reason) { +function skip_test(string $tested, string $tested_file, string $shortname, string $reason): string +{ global $junit; show_result('SKIP', $tested, $tested_file, "reason: $reason"); @@ -1879,9 +1821,10 @@ function run_test(string $php, $file, array $env): string global $num_repeats; // Parallel testing global $workerID; + global $show_progress; // Temporary - /** @var JUnit */ + /** @var JUnit $junit */ global $junit; static $skipCache; @@ -1890,20 +1833,16 @@ function run_test(string $php, $file, array $env): string $skipCache = new SkipCache($enableSkipCache, $cfg['keep']['skip']); } + $orig_php = $php; + $php = escapeshellarg($php); + $retried = false; retry: - $temp_filenames = null; $org_file = $file; - $orig_php = $php; - if (isset($env['TEST_PHP_CGI_EXECUTABLE'])) { - $php_cgi = $env['TEST_PHP_CGI_EXECUTABLE']; - } - - if (isset($env['TEST_PHPDBG_EXECUTABLE'])) { - $phpdbg = $env['TEST_PHPDBG_EXECUTABLE']; - } + $php_cgi = $env['TEST_PHP_CGI_EXECUTABLE'] ?? null; + $phpdbg = $env['TEST_PHPDBG_EXECUTABLE'] ?? null; if (is_array($file)) { $file = $file[0]; @@ -1960,11 +1899,12 @@ function run_test(string $php, $file, array $env): string } /* For GET/POST/PUT tests, check if cgi sapi is available and if it is, use it. */ + $uses_cgi = false; if ($test->isCGI()) { if (!$php_cgi) { return skip_test($tested, $tested_file, $shortname, 'CGI not available'); } - $php = $php_cgi . ' -C '; + $php = escapeshellarg($php_cgi) . ' -C '; $uses_cgi = true; if ($num_repeats > 1) { return skip_test($tested, $tested_file, $shortname, 'CGI does not support --repeat'); @@ -1975,7 +1915,7 @@ function run_test(string $php, $file, array $env): string $extra_options = ''; if ($test->hasSection('PHPDBG')) { if (isset($phpdbg)) { - $php = $phpdbg . ' -qIb'; + $php = escapeshellarg($phpdbg) . ' -qIb'; // Additional phpdbg command line options for sections that need to // be run straight away. For example, EXTENSIONS, SKIPIF, CLEAN. @@ -1996,7 +1936,7 @@ function run_test(string $php, $file, array $env): string } } - if (!$SHOW_ONLY_GROUPS && !$workerID) { + if ($show_progress && !$workerID) { show_test($test_idx, $shortname); } @@ -2032,7 +1972,7 @@ function run_test(string $php, $file, array $env): string $temp_skipif .= 's'; $temp_file .= 's'; $temp_clean .= 's'; - $copy_file = $temp_dir . DIRECTORY_SEPARATOR . basename(is_array($file) ? $file[1] : $file) . '.phps'; + $copy_file = $temp_dir . DIRECTORY_SEPARATOR . basename($file) . '.phps'; if (!is_dir(dirname($copy_file))) { mkdir(dirname($copy_file), 0777, true) or error("Cannot create output directory - " . dirname($copy_file)); @@ -2041,19 +1981,6 @@ function run_test(string $php, $file, array $env): string if ($test->hasSection('FILE')) { save_text($copy_file, $test->getSection('FILE')); } - - $temp_filenames = [ - 'file' => $copy_file, - 'diff' => $diff_filename, - 'log' => $log_filename, - 'exp' => $exp_filename, - 'out' => $output_filename, - 'mem' => $memcheck_filename, - 'sh' => $sh_filename, - 'php' => $temp_file, - 'skip' => $temp_skipif, - 'clean' => $temp_clean - ]; } if (is_array($IN_REDIRECT)) { @@ -2120,7 +2047,7 @@ function run_test(string $php, $file, array $env): string $ext_prefix = IS_WINDOWS ? "php_" : ""; $missing = []; foreach ($extensions as $req_ext) { - if (!in_array(strtolower($req_ext), $loaded)) { + if (!in_array($req_ext, $loaded, true)) { if ($req_ext == 'opcache' || $req_ext == 'xdebug') { $ext_file = $ext_dir . DIRECTORY_SEPARATOR . $ext_prefix . $req_ext . '.' . PHP_SHLIB_SUFFIX; $ini_settings['zend_extension'][] = $ext_file; @@ -2169,6 +2096,19 @@ function run_test(string $php, $file, array $env): string $ini = str_replace('{TMP}', sys_get_temp_dir(), $ini); $replacement = IS_WINDOWS ? '"' . PHP_BINARY . ' -r \"while ($in = fgets(STDIN)) echo $in;\" > $1"' : 'tee $1 >/dev/null'; $ini = preg_replace('/{MAIL:(\S+)}/', $replacement, $ini); + $skip = false; + $ini = preg_replace_callback('/{ENV:(\S+)}/', function ($m) use (&$skip) { + $name = $m[1]; + $value = getenv($name); + if ($value === false) { + $skip = sprintf('Environment variable %s is not set', $name); + return ''; + } + return $value; + }, $ini); + if ($skip !== false) { + return skip_test($tested, $tested_file, $shortname, $skip); + } settings2array(preg_split("/[\n\r]+/", $ini), $ini_settings); if (isset($ini_settings['opcache.opt_debug_level'])) { @@ -2221,9 +2161,9 @@ function run_test(string $php, $file, array $env): string if (!strncasecmp('skip', $output, 4)) { if (preg_match('/^skip\s*(.+)/i', $output, $m)) { - show_result('SKIP', $tested, $tested_file, "reason: $m[1]", $temp_filenames); + show_result('SKIP', $tested, $tested_file, "reason: $m[1]"); } else { - show_result('SKIP', $tested, $tested_file, '', $temp_filenames); + show_result('SKIP', $tested, $tested_file, ''); } $message = !empty($m[1]) ? $m[1] : ''; @@ -2231,7 +2171,6 @@ function run_test(string $php, $file, array $env): string return 'SKIPPED'; } - if (!strncasecmp('info', $output, 4) && preg_match('/^info\s*(.+)/i', $output, $m)) { $info = " (info: $m[1])"; } elseif (!strncasecmp('warn', $output, 4) && preg_match('/^warn\s+(.+)/i', $output, $m)) { @@ -2240,8 +2179,14 @@ function run_test(string $php, $file, array $env): string } elseif (!strncasecmp('xfail', $output, 5)) { // Pretend we have an XFAIL section $test->setSection('XFAIL', ltrim(substr($output, 5))); + } elseif (!strncasecmp('xleak', $output, 5)) { + // Pretend we have an XLEAK section + $test->setSection('XLEAK', ltrim(substr($output, 5))); + } elseif (!strncasecmp('flaky', $output, 5)) { + // Pretend we have a FLAKY section + $test->setSection('FLAKY', ltrim(substr($output, 5))); } elseif ($output !== '') { - show_result("BORK", $output, $tested_file, 'reason: invalid output from SKIPIF', $temp_filenames); + show_result("BORK", $output, $tested_file, 'reason: invalid output from SKIPIF'); $PHP_FAILED_TESTS['BORKED'][] = [ 'name' => $file, 'test_name' => '', @@ -2257,7 +2202,7 @@ function run_test(string $php, $file, array $env): string if (!extension_loaded("zlib") && $test->hasAnySections("GZIP_POST", "DEFLATE_POST")) { $message = "ext/zlib required"; - show_result('SKIP', $tested, $tested_file, "reason: $message", $temp_filenames); + show_result('SKIP', $tested, $tested_file, "reason: $message"); $junit->markTestAs('SKIP', $shortname, $tested, null, $message); return 'SKIPPED'; } @@ -2301,17 +2246,17 @@ function run_test(string $php, $file, array $env): string $junit->markTestAs('PASS', $shortname, $tested); return 'REDIR'; - } else { - $bork_info = "Redirect info must contain exactly one TEST string to be used as redirect directory."; - show_result("BORK", $bork_info, '', '', $temp_filenames); - $PHP_FAILED_TESTS['BORKED'][] = [ - 'name' => $file, - 'test_name' => '', - 'output' => '', - 'diff' => '', - 'info' => "$bork_info [$file]", - ]; } + + $bork_info = "Redirect info must contain exactly one TEST string to be used as redirect directory."; + show_result("BORK", $bork_info, '', ''); + $PHP_FAILED_TESTS['BORKED'][] = [ + 'name' => $file, + 'test_name' => '', + 'output' => '', + 'diff' => '', + 'info' => "$bork_info [$file]", + ]; } if (is_array($org_file) || $test->hasSection('REDIRECTTEST')) { @@ -2320,7 +2265,7 @@ function run_test(string $php, $file, array $env): string } $bork_info = "Redirected test did not contain redirection info"; - show_result("BORK", $bork_info, '', '', $temp_filenames); + show_result("BORK", $bork_info, '', ''); $PHP_FAILED_TESTS['BORKED'][] = [ 'name' => $file, 'test_name' => '', @@ -2339,7 +2284,7 @@ function run_test(string $php, $file, array $env): string show_file_block('php', $test->getSection('FILE'), 'TEST'); save_text($test_file, $test->getSection('FILE'), $temp_file); } else { - $test_file = $temp_file = ""; + $test_file = ""; } if ($test->hasSection('GET')) { @@ -2397,7 +2342,9 @@ function run_test(string $php, $file, array $env): string } $env['CONTENT_LENGTH'] = strlen($request); - $env['REQUEST_METHOD'] = 'POST'; + if (empty($env['REQUEST_METHOD'])) { + $env['REQUEST_METHOD'] = 'POST'; + } if (empty($request)) { $junit->markTestAs('BORK', $shortname, $tested, null, 'empty $request'); @@ -2494,6 +2441,16 @@ function run_test(string $php, $file, array $env): string $cmd = $valgrind->wrapCommand($cmd, $memcheck_filename, strpos($test_file, "pcre") !== false); } + if ($test->hasSection('XLEAK')) { + $env['ZEND_ALLOC_PRINT_LEAKS'] = '0'; + if (isset($env['SKIP_ASAN'])) { + // $env['LSAN_OPTIONS'] = 'detect_leaks=0'; + /* For unknown reasons, LSAN_OPTIONS=detect_leaks=0 would occasionally not be picked up + * in CI. Skip the test with ASAN, as it's not worth investegating. */ + return skip_test($tested, $tested_file, $shortname, 'xleak does not work with asan'); + } + } + if ($DETAILED) { echo " CONTENT_LENGTH = " . $env['CONTENT_LENGTH'] . " @@ -2528,14 +2485,16 @@ function run_test(string $php, $file, array $env): string ]; } - if ($test->sectionNotEmpty('CLEAN') && (!$no_clean || $cfg['keep']['clean'])) { + // Remember CLEAN output to report borked test if it otherwise passes. + $clean_output = null; + if ((!$no_clean || $cfg['keep']['clean']) && $test->sectionNotEmpty('CLEAN')) { show_file_block('clean', $test->getSection('CLEAN')); save_text($test_clean, trim($test->getSection('CLEAN')), $temp_clean); if (!$no_clean) { $extra = !IS_WINDOWS ? "unset REQUEST_METHOD; unset QUERY_STRING; unset PATH_TRANSLATED; unset SCRIPT_FILENAME; unset REQUEST_METHOD;" : ""; - system_with_timeout("$extra $php $pass_options $extra_options -q $orig_ini_settings $no_file_cache \"$test_clean\"", $env); + $clean_output = system_with_timeout("$extra $orig_php $pass_options -q $orig_ini_settings $no_file_cache \"$test_clean\"", $env); } if (!$cfg['keep']['clean']) { @@ -2579,7 +2538,7 @@ function run_test(string $php, $file, array $env): string /* when using CGI, strip the headers from the output */ $headers = []; - if (!empty($uses_cgi) && preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $out, $match)) { + if ($uses_cgi && preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $out, $match)) { $output = trim($match[2]); $rh = preg_split("/[\n\r]+/", $match[1]); @@ -2591,6 +2550,8 @@ function run_test(string $php, $file, array $env): string } } + $wanted_headers = null; + $output_headers = null; $failed_headers = false; if ($test->hasSection('EXPECTHEADERS')) { @@ -2618,9 +2579,7 @@ function run_test(string $php, $file, array $env): string } } - ksort($wanted_headers); $wanted_headers = implode("\n", $wanted_headers); - ksort($output_headers); $output_headers = implode("\n", $output_headers); } @@ -2641,74 +2600,11 @@ function run_test(string $php, $file, array $env): string $wanted_re = preg_replace('/\r\n/', "\n", $wanted); if ($test->hasSection('EXPECTF')) { - // do preg_quote, but miss out any %r delimited sections - $temp = ""; - $r = "%r"; - $startOffset = 0; - $length = strlen($wanted_re); - while ($startOffset < $length) { - $start = strpos($wanted_re, $r, $startOffset); - if ($start !== false) { - // we have found a start tag - $end = strpos($wanted_re, $r, $start + 2); - if ($end === false) { - // unbalanced tag, ignore it. - $end = $start = $length; - } - } else { - // no more %r sections - $start = $end = $length; - } - // quote a non re portion of the string - $temp .= preg_quote(substr($wanted_re, $startOffset, $start - $startOffset), '/'); - // add the re unquoted. - if ($end > $start) { - $temp .= '(' . substr($wanted_re, $start + 2, $end - $start - 2) . ')'; - } - $startOffset = $end + 2; - } - $wanted_re = $temp; - - // Stick to basics - $wanted_re = str_replace('%e', '\\' . DIRECTORY_SEPARATOR, $wanted_re); - $wanted_re = str_replace('%s', '[^\r\n]+', $wanted_re); - $wanted_re = str_replace('%S', '[^\r\n]*', $wanted_re); - $wanted_re = str_replace('%a', '.+', $wanted_re); - $wanted_re = str_replace('%A', '.*', $wanted_re); - $wanted_re = str_replace('%w', '\s*', $wanted_re); - $wanted_re = str_replace('%i', '[+-]?\d+', $wanted_re); - $wanted_re = str_replace('%d', '\d+', $wanted_re); - $wanted_re = str_replace('%x', '[0-9a-fA-F]+', $wanted_re); - $wanted_re = str_replace('%f', '[+-]?\.?\d+\.?\d*(?:[Ee][+-]?\d+)?', $wanted_re); - $wanted_re = str_replace('%c', '.', $wanted_re); - $wanted_re = str_replace('%0', '\x00', $wanted_re); - // %f allows two points "-.0.0" but that is the best *simple* expression - } - - if (preg_match("/^$wanted_re\$/s", $output)) { + $wanted_re = expectf_to_regex($wanted_re); + } + + if (preg_match('/^' . $wanted_re . '$/s', $output)) { $passed = true; - if (!$cfg['keep']['php'] && !$leaked) { - @unlink($test_file); - @unlink($preload_filename); - } - @unlink($tmp_post); - - if (!$leaked && !$failed_headers) { - if ($test->hasSection('XFAIL')) { - $warn = true; - $info = " (warn: XFAIL section but test passes)"; - } elseif ($test->hasSection('XLEAK')) { - $warn = true; - $info = " (warn: XLEAK section but test passes)"; - } elseif ($retried) { - $warn = true; - $info = " (warn: Test passed on retry attempt)"; - } else { - show_result("PASS", $tested, $tested_file, '', $temp_filenames); - $junit->markTestAs('PASS', $shortname, $tested); - return 'PASSED'; - } - } } } else { $wanted = trim($test->getSection('EXPECT')); @@ -2718,29 +2614,6 @@ function run_test(string $php, $file, array $env): string // compare and leave on success if (!strcmp($output, $wanted)) { $passed = true; - - if (!$cfg['keep']['php'] && !$leaked) { - @unlink($test_file); - @unlink($preload_filename); - } - @unlink($tmp_post); - - if (!$leaked && !$failed_headers) { - if ($test->hasSection('XFAIL')) { - $warn = true; - $info = " (warn: XFAIL section but test passes)"; - } elseif ($test->hasSection('XLEAK')) { - $warn = true; - $info = " (warn: XLEAK section but test passes)"; - } elseif ($retried) { - $warn = true; - $info = " (warn: Test passed on retry attempt)"; - } else { - show_result("PASS", $tested, $tested_file, '', $temp_filenames); - $junit->markTestAs('PASS', $shortname, $tested); - return 'PASSED'; - } - } } $wanted_re = null; @@ -2750,6 +2623,47 @@ function run_test(string $php, $file, array $env): string goto retry; } + if ($passed) { + if (!$cfg['keep']['php'] && !$leaked) { + @unlink($test_file); + @unlink($preload_filename); + } + @unlink($tmp_post); + + if (!$leaked && !$failed_headers) { + // If the test passed and CLEAN produced output, report test as borked. + if ($clean_output) { + show_result("BORK", $output, $tested_file, 'reason: invalid output from CLEAN'); + $PHP_FAILED_TESTS['BORKED'][] = [ + 'name' => $file, + 'test_name' => '', + 'output' => '', + 'diff' => '', + 'info' => "$clean_output [$file]", + ]; + + $junit->markTestAs('BORK', $shortname, $tested, null, $clean_output); + return 'BORKED'; + } + + if ($test->hasSection('XFAIL')) { + $warn = true; + $info = " (warn: XFAIL section but test passes)"; + } elseif ($test->hasSection('XLEAK') && $valgrind) { + // XLEAK with ASAN completely disables LSAN so the test is expected to pass + $warn = true; + $info = " (warn: XLEAK section but test passes)"; + } elseif ($retried) { + $warn = true; + $info = " (warn: Test passed on retry attempt)"; + } else { + show_result("PASS", $tested, $tested_file, ''); + $junit->markTestAs('PASS', $shortname, $tested); + return 'PASSED'; + } + } + } + // Test failed so we need to report details. if ($failed_headers) { $passed = false; @@ -2761,6 +2675,8 @@ function run_test(string $php, $file, array $env): string } } + $restype = []; + if ($leaked) { $restype[] = $test->hasSection('XLEAK') ? 'XLEAK' : 'LEAK'; @@ -2774,7 +2690,8 @@ function run_test(string $php, $file, array $env): string if ($test->hasSection('XFAIL')) { $restype[] = 'XFAIL'; $info = ' XFAIL REASON: ' . rtrim($test->getSection('XFAIL')); - } elseif ($test->hasSection('XLEAK')) { + } elseif ($test->hasSection('XLEAK') && $valgrind) { + // XLEAK with ASAN completely disables LSAN so the test is expected to pass $restype[] = 'XLEAK'; $info = ' XLEAK REASON: ' . rtrim($test->getSection('XLEAK')); } else { @@ -2794,12 +2711,19 @@ function run_test(string $php, $file, array $env): string } // write .diff - $diff = generate_diff($wanted, $wanted_re, $output); + if (!empty($environment['TEST_PHP_DIFF_CMD'])) { + $diff = generate_diff_external($environment['TEST_PHP_DIFF_CMD'], $exp_filename, $output_filename); + } else { + $diff = generate_diff($wanted, $wanted_re, $output); + } + if (is_array($IN_REDIRECT)) { $orig_shortname = str_replace(TEST_PHP_SRCDIR . '/', '', $file); $diff = "# original source file: $orig_shortname\n" . $diff; } - show_file_block('diff', $diff); + if (!$SHOW_ONLY_GROUPS || array_intersect($restype, $SHOW_ONLY_GROUPS)) { + show_file_block('diff', $diff); + } if (strpos($log_format, 'D') !== false && file_put_contents($diff_filename, $diff) === false) { error("Cannot create test diff - $diff_filename"); } @@ -2824,7 +2748,7 @@ function run_test(string $php, $file, array $env): string foreach ($env as $env_var => $env_val) { $env_lines[] = "export $env_var=" . escapeshellarg($env_val ?? ""); } - $exported_environment = $env_lines ? "\n" . implode("\n", $env_lines) . "\n" : ""; + $exported_environment = "\n" . implode("\n", $env_lines) . "\n"; $sh_script = << $start) { + $temp .= '(' . substr($wanted_re, $start + 2, $end - $start - 2) . ')'; + } + $startOffset = $end + 2; + } + $wanted_re = $temp; + + return strtr($wanted_re, [ + '%e' => preg_quote(DIRECTORY_SEPARATOR, '/'), + '%s' => '[^\r\n]+', + '%S' => '[^\r\n]*', + '%a' => '.+', + '%A' => '.*', + '%w' => '\s*', + '%i' => '[+-]?\d+', + '%d' => '\d+', + '%x' => '[0-9a-fA-F]+', + '%f' => '[+-]?(?:\d+|(?=\.\d))(?:\.\d+)?(?:[Ee][+-]?\d+)?', + '%c' => '.', + '%0' => '\x00', + ]); +} + /** * @return bool|int */ @@ -2920,186 +2897,48 @@ function comp_line(string $l1, string $l2, bool $is_reg) { if ($is_reg) { return preg_match('/^' . $l1 . '$/s', $l2); - } else { - return !strcmp($l1, $l2); - } -} - -function count_array_diff( - array $ar1, - array $ar2, - bool $is_reg, - array $w, - int $idx1, - int $idx2, - int $cnt1, - int $cnt2, - int $steps -): int { - $equal = 0; - - while ($idx1 < $cnt1 && $idx2 < $cnt2 && comp_line($ar1[$idx1], $ar2[$idx2], $is_reg)) { - $idx1++; - $idx2++; - $equal++; - $steps--; - } - if (--$steps > 0) { - $eq1 = 0; - $st = $steps / 2; - - for ($ofs1 = $idx1 + 1; $ofs1 < $cnt1 && $st-- > 0; $ofs1++) { - $eq = @count_array_diff($ar1, $ar2, $is_reg, $w, $ofs1, $idx2, $cnt1, $cnt2, $st); - - if ($eq > $eq1) { - $eq1 = $eq; - } - } - - $eq2 = 0; - $st = $steps; - - for ($ofs2 = $idx2 + 1; $ofs2 < $cnt2 && $st-- > 0; $ofs2++) { - $eq = @count_array_diff($ar1, $ar2, $is_reg, $w, $idx1, $ofs2, $cnt1, $cnt2, $st); - if ($eq > $eq2) { - $eq2 = $eq; - } - } - - if ($eq1 > $eq2) { - $equal += $eq1; - } elseif ($eq2 > 0) { - $equal += $eq2; - } } - return $equal; + return !strcmp($l1, $l2); } -function generate_array_diff(array $ar1, array $ar2, bool $is_reg, array $w): array +/** + * Map "Zend OPcache" to "opcache" and convert all ext names to lowercase. + */ +function remap_loaded_extensions_names(array $names): array { - global $context_line_count; - $idx1 = 0; - $cnt1 = @count($ar1); - $idx2 = 0; - $cnt2 = @count($ar2); - $diff = []; - $old1 = []; - $old2 = []; - $number_len = max(3, strlen((string)max($cnt1 + 1, $cnt2 + 1))); - $line_number_spec = '%0' . $number_len . 'd'; - - /** Mapping from $idx2 to $idx1, including indexes of idx2 that are identical to idx1 as well as entries that don't have matches */ - $mapping = []; - - while ($idx1 < $cnt1 && $idx2 < $cnt2) { - $mapping[$idx2] = $idx1; - if (comp_line($ar1[$idx1], $ar2[$idx2], $is_reg)) { - $idx1++; - $idx2++; + $exts = []; + foreach ($names as $name) { + if ($name === 'Core') { continue; - } else { - $c1 = @count_array_diff($ar1, $ar2, $is_reg, $w, $idx1 + 1, $idx2, $cnt1, $cnt2, 10); - $c2 = @count_array_diff($ar1, $ar2, $is_reg, $w, $idx1, $idx2 + 1, $cnt1, $cnt2, 10); - - if ($c1 > $c2) { - $old1[$idx1] = sprintf("{$line_number_spec}- ", $idx1 + 1) . $w[$idx1++]; - } elseif ($c2 > 0) { - $old2[$idx2] = sprintf("{$line_number_spec}+ ", $idx2 + 1) . $ar2[$idx2++]; - } else { - $old1[$idx1] = sprintf("{$line_number_spec}- ", $idx1 + 1) . $w[$idx1++]; - $old2[$idx2] = sprintf("{$line_number_spec}+ ", $idx2 + 1) . $ar2[$idx2++]; - } - $last_printed_context_line = $idx1; - } - } - $mapping[$idx2] = $idx1; - - reset($old1); - $k1 = key($old1); - $l1 = -2; - reset($old2); - $k2 = key($old2); - $l2 = -2; - $old_k1 = -1; - $add_context_lines = function (int $new_k1) use (&$old_k1, &$diff, $w, $context_line_count, $number_len) { - if ($old_k1 >= $new_k1 || !$context_line_count) { - return; - } - $end = $new_k1 - 1; - $range_end = min($end, $old_k1 + $context_line_count); - if ($old_k1 >= 0) { - while ($old_k1 < $range_end) { - $diff[] = str_repeat(' ', $number_len + 2) . $w[$old_k1++]; - } - } - if ($end - $context_line_count > $old_k1) { - $old_k1 = $end - $context_line_count; - if ($old_k1 > 0) { - // Add a '--' to mark sections where the common areas were truncated - $diff[] = '--'; - } - } - $old_k1 = max($old_k1, 0); - while ($old_k1 < $end) { - $diff[] = str_repeat(' ', $number_len + 2) . $w[$old_k1++]; - } - $old_k1 = $new_k1; - }; - - while ($k1 !== null || $k2 !== null) { - if ($k1 == $l1 + 1 || $k2 === null) { - $add_context_lines($k1); - $l1 = $k1; - $diff[] = current($old1); - $old_k1 = $k1; - $k1 = next($old1) ? key($old1) : null; - } elseif ($k2 == $l2 + 1 || $k1 === null) { - $add_context_lines($mapping[$k2]); - $l2 = $k2; - $diff[] = current($old2); - $k2 = next($old2) ? key($old2) : null; - } elseif ($k1 < $mapping[$k2]) { - $add_context_lines($k1); - $l1 = $k1; - $diff[] = current($old1); - $k1 = next($old1) ? key($old1) : null; - } else { - $add_context_lines($mapping[$k2]); - $l2 = $k2; - $diff[] = current($old2); - $k2 = next($old2) ? key($old2) : null; } + $exts[] = ['Zend OPcache' => 'opcache'][$name] ?? strtolower($name); } - while ($idx1 < $cnt1) { - $add_context_lines($idx1 + 1); - $diff[] = sprintf("{$line_number_spec}- ", $idx1 + 1) . $w[$idx1++]; - } + return $exts; +} - while ($idx2 < $cnt2) { - if (isset($mapping[$idx2])) { - $add_context_lines($mapping[$idx2] + 1); - } - $diff[] = sprintf("{$line_number_spec}+ ", $idx2 + 1) . $ar2[$idx2++]; - } - $add_context_lines(min($old_k1 + $context_line_count + 1, $cnt1 + 1)); - if ($context_line_count && $old_k1 < $cnt1 + 1) { - // Add a '--' to mark sections where the common areas were truncated - $diff[] = '--'; - } +function generate_diff_external(string $diff_cmd, string $exp_file, string $output_file): string +{ + $retval = shell_exec("{$diff_cmd} {$exp_file} {$output_file}"); - return $diff; + return is_string($retval) ? $retval : 'Could not run external diff tool set through TEST_PHP_DIFF_CMD environment variable'; } function generate_diff(string $wanted, ?string $wanted_re, string $output): string { $w = explode("\n", $wanted); $o = explode("\n", $output); - $r = is_null($wanted_re) ? $w : explode("\n", $wanted_re); - $diff = generate_array_diff($r, $o, !is_null($wanted_re), $w); + $is_regex = $wanted_re !== null; - return implode(PHP_EOL, $diff); + $differ = new Differ(function ($expected, $new) use ($is_regex) { + if (!$is_regex) { + return $expected === $new; + } + $regex = '/^' . expectf_to_regex($expected). '$/s'; + return preg_match($regex, $new); + }); + return $differ->diff($w, $o); } function error(string $message): void @@ -3108,7 +2947,7 @@ function error(string $message): void exit(1); } -function settings2array(array $settings, &$ini_settings): void +function settings2array(array $settings, array &$ini_settings): void { foreach ($settings as $setting) { if (strpos($setting, '=') !== false) { @@ -3163,7 +3002,7 @@ function compute_summary(): void global $n_total, $test_results, $ignored_by_ext, $sum_results, $percent_results; $n_total = count($test_results); - $n_total += $ignored_by_ext; + $n_total += count($ignored_by_ext); $sum_results = [ 'PASSED' => 0, 'WARNED' => 0, @@ -3179,7 +3018,7 @@ function compute_summary(): void $sum_results[$v]++; } - $sum_results['SKIPPED'] += $ignored_by_ext; + $sum_results['SKIPPED'] += count($ignored_by_ext); $percent_results = []; foreach ($sum_results as $v => $n) { @@ -3211,43 +3050,43 @@ function get_summary(bool $show_ext_summary): string ===================================================================== TEST RESULT SUMMARY --------------------------------------------------------------------- -Exts skipped : ' . sprintf('%4d', $exts_skipped) . ' -Exts tested : ' . sprintf('%4d', $exts_tested) . ' +Exts skipped : ' . sprintf('%5d', count($exts_skipped)) . ($exts_skipped ? ' (' . implode(', ', $exts_skipped) . ')' : '') . ' +Exts tested : ' . sprintf('%5d', count($exts_tested)) . ' --------------------------------------------------------------------- '; } $summary .= ' -Number of tests : ' . sprintf('%4d', $n_total) . ' ' . sprintf('%8d', $x_total); +Number of tests : ' . sprintf('%5d', $n_total) . ' ' . sprintf('%8d', $x_total); if ($sum_results['BORKED']) { $summary .= ' -Tests borked : ' . sprintf('%4d (%5.1f%%)', $sum_results['BORKED'], $percent_results['BORKED']) . ' --------'; +Tests borked : ' . sprintf('%5d (%5.1f%%)', $sum_results['BORKED'], $percent_results['BORKED']) . ' --------'; } $summary .= ' -Tests skipped : ' . sprintf('%4d (%5.1f%%)', $sum_results['SKIPPED'], $percent_results['SKIPPED']) . ' -------- -Tests warned : ' . sprintf('%4d (%5.1f%%)', $sum_results['WARNED'], $percent_results['WARNED']) . ' ' . sprintf('(%5.1f%%)', $x_warned) . ' -Tests failed : ' . sprintf('%4d (%5.1f%%)', $sum_results['FAILED'], $percent_results['FAILED']) . ' ' . sprintf('(%5.1f%%)', $x_failed); +Tests skipped : ' . sprintf('%5d (%5.1f%%)', $sum_results['SKIPPED'], $percent_results['SKIPPED']) . ' -------- +Tests warned : ' . sprintf('%5d (%5.1f%%)', $sum_results['WARNED'], $percent_results['WARNED']) . ' ' . sprintf('(%5.1f%%)', $x_warned) . ' +Tests failed : ' . sprintf('%5d (%5.1f%%)', $sum_results['FAILED'], $percent_results['FAILED']) . ' ' . sprintf('(%5.1f%%)', $x_failed); if ($sum_results['XFAILED']) { $summary .= ' -Expected fail : ' . sprintf('%4d (%5.1f%%)', $sum_results['XFAILED'], $percent_results['XFAILED']) . ' ' . sprintf('(%5.1f%%)', $x_xfailed); +Expected fail : ' . sprintf('%5d (%5.1f%%)', $sum_results['XFAILED'], $percent_results['XFAILED']) . ' ' . sprintf('(%5.1f%%)', $x_xfailed); } if ($valgrind) { $summary .= ' -Tests leaked : ' . sprintf('%4d (%5.1f%%)', $sum_results['LEAKED'], $percent_results['LEAKED']) . ' ' . sprintf('(%5.1f%%)', $x_leaked); +Tests leaked : ' . sprintf('%5d (%5.1f%%)', $sum_results['LEAKED'], $percent_results['LEAKED']) . ' ' . sprintf('(%5.1f%%)', $x_leaked); if ($sum_results['XLEAKED']) { $summary .= ' -Expected leak : ' . sprintf('%4d (%5.1f%%)', $sum_results['XLEAKED'], $percent_results['XLEAKED']) . ' ' . sprintf('(%5.1f%%)', $x_xleaked); +Expected leak : ' . sprintf('%5d (%5.1f%%)', $sum_results['XLEAKED'], $percent_results['XLEAKED']) . ' ' . sprintf('(%5.1f%%)', $x_xleaked); } } $summary .= ' -Tests passed : ' . sprintf('%4d (%5.1f%%)', $sum_results['PASSED'], $percent_results['PASSED']) . ' ' . sprintf('(%5.1f%%)', $x_passed) . ' +Tests passed : ' . sprintf('%5d (%5.1f%%)', $sum_results['PASSED'], $percent_results['PASSED']) . ' ' . sprintf('(%5.1f%%)', $x_passed) . ' --------------------------------------------------------------------- -Time taken : ' . sprintf('%4d seconds', $end_time - $start_time) . ' +Time taken : ' . sprintf('%5.3f seconds', ($end_time - $start_time) / 1e9) . ' ===================================================================== '; $failed_test_summary = ''; @@ -3268,18 +3107,6 @@ function get_summary(bool $show_ext_summary): string $failed_test_summary .= "=====================================================================\n"; } - if (count($PHP_FAILED_TESTS['XFAILED'])) { - $failed_test_summary .= ' -===================================================================== -EXPECTED FAILED TEST SUMMARY ---------------------------------------------------------------------- -'; - foreach ($PHP_FAILED_TESTS['XFAILED'] as $failed_test_data) { - $failed_test_summary .= $failed_test_data['test_name'] . $failed_test_data['info'] . "\n"; - } - $failed_test_summary .= "=====================================================================\n"; - } - if (count($PHP_FAILED_TESTS['BORKED'])) { $failed_test_summary .= ' ===================================================================== @@ -3330,19 +3157,6 @@ function get_summary(bool $show_ext_summary): string $failed_test_summary .= "=====================================================================\n"; } - if (count($PHP_FAILED_TESTS['XLEAKED'])) { - $failed_test_summary .= ' -===================================================================== -EXPECTED LEAK TEST SUMMARY ---------------------------------------------------------------------- -'; - foreach ($PHP_FAILED_TESTS['XLEAKED'] as $failed_test_data) { - $failed_test_summary .= $failed_test_data['test_name'] . $failed_test_data['info'] . "\n"; - } - - $failed_test_summary .= "=====================================================================\n"; - } - if ($failed_test_summary && !getenv('NO_PHPTEST_SUMMARY')) { $summary .= $failed_test_summary; } @@ -3350,14 +3164,14 @@ function get_summary(bool $show_ext_summary): string return $summary; } -function show_start($start_time): void +function show_start(int $start_timestamp): void { - echo "TIME START " . date('Y-m-d H:i:s', $start_time) . "\n=====================================================================\n"; + echo "TIME START " . date('Y-m-d H:i:s', $start_timestamp) . "\n=====================================================================\n"; } -function show_end($end_time): void +function show_end(int $start_timestamp, int|float $start_time, int|float $end_time): void { - echo "=====================================================================\nTIME END " . date('Y-m-d H:i:s', $end_time) . "\n"; + echo "=====================================================================\nTIME END " . date('Y-m-d H:i:s', $start_timestamp + (int)(($end_time - $start_time)/1e9)) . "\n"; } function show_summary(): void @@ -3367,22 +3181,22 @@ function show_summary(): void function show_redirect_start(string $tests, string $tested, string $tested_file): void { - global $SHOW_ONLY_GROUPS; + global $SHOW_ONLY_GROUPS, $show_progress; if (!$SHOW_ONLY_GROUPS || in_array('REDIRECT', $SHOW_ONLY_GROUPS)) { echo "REDIRECT $tests ($tested [$tested_file]) begin\n"; - } else { + } elseif ($show_progress) { clear_show_test(); } } function show_redirect_ends(string $tests, string $tested, string $tested_file): void { - global $SHOW_ONLY_GROUPS; + global $SHOW_ONLY_GROUPS, $show_progress; if (!$SHOW_ONLY_GROUPS || in_array('REDIRECT', $SHOW_ONLY_GROUPS)) { echo "REDIRECT $tests ($tested [$tested_file]) done\n"; - } else { + } elseif ($show_progress) { clear_show_test(); } } @@ -3421,10 +3235,9 @@ function show_result( string $result, string $tested, string $tested_file, - string $extra = '', - ?array $temp_filenames = null + string $extra = '' ): void { - global $SHOW_ONLY_GROUPS, $colorize; + global $SHOW_ONLY_GROUPS, $colorize, $show_progress; if (!$SHOW_ONLY_GROUPS || in_array($result, $SHOW_ONLY_GROUPS)) { if ($colorize) { @@ -3446,10 +3259,9 @@ function show_result( } else { echo "$result $tested [$tested_file] $extra\n"; } - } elseif (!$SHOW_ONLY_GROUPS) { + } elseif ($show_progress) { clear_show_test(); } - } class BorkageException extends Exception @@ -3522,9 +3334,8 @@ public function saveXML(): void fwrite($this->fp, $xml); } - private function getSuitesXML(string $suite_name = '') + private function getSuitesXML(): string { - // FIXME: $suite_name gets overwritten $result = ''; foreach ($this->suites as $suite_name => $suite) { @@ -3803,12 +3614,9 @@ public function getExtensions(string $php): array return $this->extensions[$php]; } - $extDir = `$php -d display_errors=0 -r "echo ini_get('extension_dir');"`; - $extensions = explode(",", `$php -d display_errors=0 -r "echo implode(',', get_loaded_extensions());"`); - $extensions = array_map('strtolower', $extensions); - if (in_array('zend opcache', $extensions)) { - $extensions[] = 'opcache'; - } + $extDir = shell_exec("$php -d display_errors=0 -r \"echo ini_get('extension_dir');\""); + $extensionsNames = explode(",", shell_exec("$php -d display_errors=0 -r \"echo implode(',', get_loaded_extensions());\"")); + $extensions = remap_loaded_extensions_names($extensionsNames); $result = [$extDir, $extensions]; $this->extensions[$php] = $result; @@ -3816,30 +3624,13 @@ public function getExtensions(string $php): array return $result; } - -// public function __destruct() -// { -// echo "Skips: {$this->hits} hits, {$this->misses} misses.\n"; -// echo "Extensions: {$this->extHits} hits, {$this->extMisses} misses.\n"; -// echo "Cache distribution:\n"; -// -// foreach ($this->skips as $php => $cache) { -// echo "$php: " . count($cache) . "\n"; -// } -// } } class RuntestsValgrind { - protected $version = ''; - protected $header = ''; - protected $version_3_8_0 = false; - protected $tool = null; - - public function getVersion(): string - { - return $this->version; - } + protected string $header; + protected bool $version_3_8_0; + protected string $tool; public function getHeader(): string { @@ -3852,16 +3643,14 @@ public function __construct(array $environment, string $tool = 'memcheck') $header = system_with_timeout("valgrind --tool={$this->tool} --version", $environment); if (!$header) { error("Valgrind returned no version info for {$this->tool}, cannot proceed.\n". - "Please check if Valgrind is installed and the tool is named correctly."); + "Please check if Valgrind is installed and the tool is named correctly."); } $count = 0; $version = preg_replace("/valgrind-(\d+)\.(\d+)\.(\d+)([.\w_-]+)?(\s+)/", '$1.$2.$3', $header, 1, $count); if ($count != 1) { error("Valgrind returned invalid version info (\"{$header}\") for {$this->tool}, cannot proceed."); } - $this->version = $version; - $this->header = sprintf( - "%s (%s)", trim($header), $this->tool); + $this->header = sprintf("%s (%s)", trim($header), $this->tool); $this->version_3_8_0 = version_compare($version, '3.8.0', '>='); } @@ -3913,17 +3702,6 @@ public function hasSection(string $name): bool return isset($this->sections[$name]); } - public function hasAllSections(string ...$names): bool - { - foreach ($names as $section) { - if (!isset($this->sections[$section])) { - return false; - } - } - - return true; - } - public function hasAnySections(string ...$names): bool { foreach ($names as $section) { @@ -4011,7 +3789,7 @@ private function readFile(): void // Match the beginning of a section. if (preg_match('/^--([_A-Z]+)--/', $line, $r)) { - $section = (string) $r[1]; + $section = $r[1]; if (isset($this->sections[$section]) && $this->sections[$section]) { throw new BorkageException("duplicated $section section"); @@ -4133,4 +3911,262 @@ function bless_failed_tests(array $failedTests): void proc_open($args, [], $pipes); } +/* + * BSD 3-Clause License + * + * Copyright (c) 2002-2023, Sebastian Bergmann + * All rights reserved. + * + * This file is part of sebastian/diff. + * https://github.com/sebastianbergmann/diff + */ + +final class Differ +{ + public const OLD = 0; + public const ADDED = 1; + public const REMOVED = 2; + private DiffOutputBuilder $outputBuilder; + private $isEqual; + + public function __construct(callable $isEqual) + { + $this->outputBuilder = new DiffOutputBuilder; + $this->isEqual = $isEqual; + } + + public function diff(array $from, array $to): string + { + $diff = $this->diffToArray($from, $to); + + return $this->outputBuilder->getDiff($diff); + } + + public function diffToArray(array $from, array $to): array + { + $fromLine = 1; + $toLine = 1; + + [$from, $to, $start, $end] = $this->getArrayDiffParted($from, $to); + + $common = $this->calculateCommonSubsequence(array_values($from), array_values($to)); + $diff = []; + + foreach ($start as $token) { + $diff[] = [$token, self::OLD]; + $fromLine++; + $toLine++; + } + + reset($from); + reset($to); + + foreach ($common as $token) { + while (!empty($from) && !($this->isEqual)(reset($from), $token)) { + $diff[] = [array_shift($from), self::REMOVED, $fromLine++]; + } + + while (!empty($to) && !($this->isEqual)($token, reset($to))) { + $diff[] = [array_shift($to), self::ADDED, $toLine++]; + } + + $diff[] = [$token, self::OLD]; + $fromLine++; + $toLine++; + + array_shift($from); + array_shift($to); + } + + while (($token = array_shift($from)) !== null) { + $diff[] = [$token, self::REMOVED, $fromLine++]; + } + + while (($token = array_shift($to)) !== null) { + $diff[] = [$token, self::ADDED, $toLine++]; + } + + foreach ($end as $token) { + $diff[] = [$token, self::OLD]; + } + + return $diff; + } + + private function getArrayDiffParted(array &$from, array &$to): array + { + $start = []; + $end = []; + + reset($to); + + foreach ($from as $k => $v) { + $toK = key($to); + + if (($this->isEqual)($toK, $k) && ($this->isEqual)($v, $to[$k])) { + $start[$k] = $v; + + unset($from[$k], $to[$k]); + } else { + break; + } + } + + end($from); + end($to); + + do { + $fromK = key($from); + $toK = key($to); + + if (null === $fromK || null === $toK || !($this->isEqual)(current($from), current($to))) { + break; + } + + prev($from); + prev($to); + + $end = [$fromK => $from[$fromK]] + $end; + unset($from[$fromK], $to[$toK]); + } while (true); + + return [$from, $to, $start, $end]; + } + + public function calculateCommonSubsequence(array $from, array $to): array + { + $cFrom = count($from); + $cTo = count($to); + + if ($cFrom === 0) { + return []; + } + + if ($cFrom === 1) { + foreach ($to as $toV) { + if (($this->isEqual)($from[0], $toV)) { + return [$toV]; + } + } + + return []; + } + + $i = (int) ($cFrom / 2); + $fromStart = array_slice($from, 0, $i); + $fromEnd = array_slice($from, $i); + $llB = $this->commonSubsequenceLength($fromStart, $to); + $llE = $this->commonSubsequenceLength(array_reverse($fromEnd), array_reverse($to)); + $jMax = 0; + $max = 0; + + for ($j = 0; $j <= $cTo; $j++) { + $m = $llB[$j] + $llE[$cTo - $j]; + + if ($m >= $max) { + $max = $m; + $jMax = $j; + } + } + + $toStart = array_slice($to, 0, $jMax); + $toEnd = array_slice($to, $jMax); + + return array_merge( + $this->calculateCommonSubsequence($fromStart, $toStart), + $this->calculateCommonSubsequence($fromEnd, $toEnd) + ); + } + + private function commonSubsequenceLength(array $from, array $to): array + { + $current = array_fill(0, count($to) + 1, 0); + $cFrom = count($from); + $cTo = count($to); + + for ($i = 0; $i < $cFrom; $i++) { + $prev = $current; + + for ($j = 0; $j < $cTo; $j++) { + if (($this->isEqual)($from[$i], $to[$j])) { + $current[$j + 1] = $prev[$j] + 1; + } else { + $current[$j + 1] = max($current[$j], $prev[$j + 1]); + } + } + } + + return $current; + } +} + +class DiffOutputBuilder +{ + public function getDiff(array $diffs): string + { + global $context_line_count; + $i = 0; + $number_len = max(3, strlen((string)count($diffs))); + $line_number_spec = '%0' . $number_len . 'd'; + $buffer = fopen('php://memory', 'r+b'); + while ($i < count($diffs)) { + // Find next difference + $next = $i; + while ($next < count($diffs)) { + if ($diffs[$next][1] !== Differ::OLD) { + break; + } + $next++; + } + // Found no more differentiating rows, we're done + if ($next === count($diffs)) { + if (($i - 1) < count($diffs)) { + fwrite($buffer, "--\n"); + } + break; + } + // Print separator if necessary + if ($i < ($next - $context_line_count)) { + fwrite($buffer, "--\n"); + $i = $next - $context_line_count; + } + // Print leading context + while ($i < $next) { + fwrite($buffer, str_repeat(' ', $number_len + 2)); + fwrite($buffer, $diffs[$i][0]); + fwrite($buffer, "\n"); + $i++; + } + // Print differences + while ($i < count($diffs) && $diffs[$i][1] !== Differ::OLD) { + fwrite($buffer, sprintf($line_number_spec, $diffs[$i][2])); + switch ($diffs[$i][1]) { + case Differ::ADDED: + fwrite($buffer, '+ '); + break; + case Differ::REMOVED: + fwrite($buffer, '- '); + break; + } + fwrite($buffer, $diffs[$i][0]); + fwrite($buffer, "\n"); + $i++; + } + // Print trailing context + $afterContext = min($i + $context_line_count, count($diffs)); + while ($i < $afterContext && $diffs[$i][1] === Differ::OLD) { + fwrite($buffer, str_repeat(' ', $number_len + 2)); + fwrite($buffer, $diffs[$i][0]); + fwrite($buffer, "\n"); + $i++; + } + } + + $diff = stream_get_contents($buffer, -1, 0); + fclose($buffer); + + return $diff; + } +} + main(); diff --git a/sapi/PHP-VERSION.conf b/sapi/PHP-VERSION.conf new file mode 100644 index 0000000000..609ea6449c --- /dev/null +++ b/sapi/PHP-VERSION.conf @@ -0,0 +1 @@ +8.4.14 diff --git a/sapi/SWOOLE-VERSION.conf b/sapi/SWOOLE-VERSION.conf index 1f7391f92b..035fbdc6e7 100644 --- a/sapi/SWOOLE-VERSION.conf +++ b/sapi/SWOOLE-VERSION.conf @@ -1 +1 @@ -master +v6.1.1 diff --git a/sapi/scripts/build-swoole-cli-with-linux-gcc.sh b/sapi/scripts/build-swoole-cli-with-linux-gcc.sh new file mode 100755 index 0000000000..a5155870f6 --- /dev/null +++ b/sapi/scripts/build-swoole-cli-with-linux-gcc.sh @@ -0,0 +1,47 @@ +./configure --prefix=/usr --disable-all \ + --enable-zts \ + --without-pcre-jit \ + --with-openssl --enable-openssl \ + --with-curl \ + --with-iconv \ + --enable-intl \ + --with-bz2 \ + --enable-bcmath \ + --enable-filter \ + --enable-session \ + --enable-tokenizer \ + --enable-mbstring \ + --enable-ctype \ + --with-zlib \ + --with-zip \ + --enable-posix \ + --enable-sockets \ + --enable-pdo \ + --with-sqlite3 \ + --enable-phar \ + --enable-pcntl \ + --enable-mysqlnd \ + --with-mysqli \ + --enable-fileinfo \ + --with-pdo_mysql \ + --enable-soap \ + --with-xsl \ + --with-gmp \ + --enable-exif \ + --with-sodium \ + --enable-xml --enable-simplexml --enable-xmlreader --enable-xmlwriter --enable-dom --with-libxml \ + --enable-gd --with-jpeg --with-freetype \ + --enable-swoole --enable-sockets --enable-mysqlnd --enable-swoole-curl --enable-cares \ + --enable-swoole-pgsql \ + --enable-swoole-sqlite \ + --enable-swoole-thread \ + --enable-brotli \ + --enable-zstd \ + --enable-swoole-stdext \ + --enable-redis \ + --with-imagick \ + --with-yaml \ + --with-readline \ + --enable-opcache + +make -j "$(nproc)" \ No newline at end of file diff --git a/sapi/scripts/cygwin/cygwin-config-ext.sh b/sapi/scripts/cygwin/cygwin-config-ext.sh index d39b7c09b8..7c86fc721d 100644 --- a/sapi/scripts/cygwin/cygwin-config-ext.sh +++ b/sapi/scripts/cygwin/cygwin-config-ext.sh @@ -31,11 +31,10 @@ while [ $# -gt 0 ]; do shift $(($# > 0 ? 1 : 0)) done -REDIS_VERSION=6.1.0 -MONGODB_VERSION=1.17.2 +REDIS_VERSION=6.2.0 +MONGODB_VERSION=1.14.2 YAML_VERSION=2.2.2 -IMAGICK_VERSION=3.7.0 -SWOOLE_VERSION=$(awk 'NR==1{ print $1 }' "${__PROJECT__}/sapi/SWOOLE-VERSION.conf") +IMAGICK_VERSION=3.8.0 mkdir -p pool/ext mkdir -p pool/lib diff --git a/sapi/scripts/download-php-src-archive.php b/sapi/scripts/download-php-src-archive.php index 540d3da9dc..cfec66ce3e 100644 --- a/sapi/scripts/download-php-src-archive.php +++ b/sapi/scripts/download-php-src-archive.php @@ -1,12 +1,15 @@ 0 ? 1 : 0)) done -REDIS_VERSION=6.1.0 +REDIS_VERSION=6.2.0 YAML_VERSION=2.2.2 -IMAGICK_VERSION=3.7.0 +IMAGICK_VERSION=3.8.0 mkdir -p pool/ext mkdir -p pool/lib diff --git a/sapi/scripts/tencent-cloud-object-storage.sh b/sapi/scripts/tencent-cloud-object-storage.sh index ad3522bc03..39519b5f98 100644 --- a/sapi/scripts/tencent-cloud-object-storage.sh +++ b/sapi/scripts/tencent-cloud-object-storage.sh @@ -116,7 +116,7 @@ test -f ${APP_RUNTIME} || curl -fSLo ${APP_RUNTIME} https://github.com/tencentyu chmod a+x ${APP_RUNTIME} BUCKET_NAME=$(grep "\- name: " ${CLOUD_OBJECT_STORAGE_CONFIG} | sed 's/\- name: //g' | sed 's/^ *//;s/ *$//' | tr -d '"') -COSCLI="${__PROJECT__}/var/tencent-cloud-object-storage/${APP_RUNTIME} --config-path ${CLOUD_OBJECT_STORAGE_CONFIG} --log-path ${__PROJECT__}/var/tencent-cloud-object-storage/coscli.log " +COSCLI="${__PROJECT__}/var/tencent-cloud-object-storage/${APP_RUNTIME} --config-path ${CLOUD_OBJECT_STORAGE_CONFIG} --log-path ${__PROJECT__}/var/tencent-cloud-object-storage/" COS_BUCKET_FOLDER="cos://${BUCKET_NAME}/dist/" if [ "${UPLOAD_TYPE}" == 'all' ]; then @@ -135,17 +135,17 @@ set -u if [ "${UPLOAD_TYPE}" == 'all' ]; then SWOOLE_VERSION=$(echo ${SWOOLE_CLI_VERSION} | awk -F '.' '{ printf "%s.%s.%s" ,$1,$2,$3 }') cd ${__PROJECT__}/var/artifact-hash/${SWOOLE_CLI_VERSION} - ${COSCLI} sync swoole-cli-${SWOOLE_VERSION}-cygwin-x64.zip ${COS_BUCKET_FOLDER} - ${COSCLI} sync swoole-cli-${SWOOLE_VERSION}-linux-arm64.tar.xz ${COS_BUCKET_FOLDER} - ${COSCLI} sync swoole-cli-${SWOOLE_VERSION}-linux-x64.tar.xz ${COS_BUCKET_FOLDER} - ${COSCLI} sync swoole-cli-${SWOOLE_VERSION}-macos-arm64.tar.xz ${COS_BUCKET_FOLDER} - ${COSCLI} sync swoole-cli-${SWOOLE_VERSION}-macos-x64.tar.xz ${COS_BUCKET_FOLDER} + ${COSCLI} cp swoole-cli-${SWOOLE_VERSION}-cygwin-x64.zip ${COS_BUCKET_FOLDER} + ${COSCLI} cp swoole-cli-${SWOOLE_VERSION}-linux-arm64.tar.xz ${COS_BUCKET_FOLDER} + ${COSCLI} cp swoole-cli-${SWOOLE_VERSION}-linux-x64.tar.xz ${COS_BUCKET_FOLDER} + ${COSCLI} cp swoole-cli-${SWOOLE_VERSION}-macos-arm64.tar.xz ${COS_BUCKET_FOLDER} + ${COSCLI} cp swoole-cli-${SWOOLE_VERSION}-macos-x64.tar.xz ${COS_BUCKET_FOLDER} cd ${__PROJECT__} exit 0 fi if [ "${UPLOAD_TYPE}" == 'single' ]; then - ${COSCLI} sync ${UPLOAD_FILE} ${COS_BUCKET_FOLDER} + ${COSCLI} cp ${UPLOAD_FILE} ${COS_BUCKET_FOLDER} exit 0 fi diff --git a/sapi/src/Preprocessor.php b/sapi/src/Preprocessor.php index e0cb85f54f..77a6fbd5f4 100644 --- a/sapi/src/Preprocessor.php +++ b/sapi/src/Preprocessor.php @@ -118,6 +118,7 @@ class Preprocessor protected string $buildType = 'release'; protected bool $inVirtualMachine = false; + protected bool $skipHashVerify = false; protected string $proxyConfig = ''; @@ -448,7 +449,7 @@ protected function downloadFile(string $url, string $file, ?object $project = nu throw new Exception("Downloading file[" . basename($file) . "] from url[$url] failed"); } // 下载文件的 hash 不一致 - if ($project->enableHashVerify) { + if (!$this->skipHashVerify and $project->enableHashVerify) { if (!$project->hashVerify($file)) { throw new Exception("The {$project->hashAlgo} of downloaded file[$file] is inconsistent with the configuration"); } @@ -490,6 +491,7 @@ protected function downloadFileWithScript(string $file, string $downloadScript, */ public function addLibrary(Library $lib): void { + if ($lib->enableDownloadScript || !empty($lib->url)) { if (empty($lib->file)) { if ($lib->enableDownloadScript) { @@ -639,7 +641,7 @@ public function addExtension(Extension $ext): void } } - if ($ext->enableHashVerify) { + if (!$this->skipHashVerify and $ext->enableHashVerify) { // 检查文件的 hash,若不一致删除后重新下载 $ext->hashVerify($ext->path); } @@ -1118,7 +1120,10 @@ public function execute(): void $this->scanConfigFiles($dir, $extAvailable); } } + install_libraries($this); + $this->skipHashVerify = boolval($this->getInputOption('skip-hash-verify')); + $this->extEnabled = array_unique($this->extEnabled); foreach ($this->extEnabled as $ext) { if (!isset($extAvailable[$ext])) { diff --git a/sapi/src/builder/enabled_extensions.php b/sapi/src/builder/enabled_extensions.php index 47f3692153..d19c53baa3 100644 --- a/sapi/src/builder/enabled_extensions.php +++ b/sapi/src/builder/enabled_extensions.php @@ -45,5 +45,5 @@ 'pgsql', 'xlswriter', 'gettext', - //'phpy' +# 'phpy' ]; diff --git a/sapi/src/builder/extension/imagick.php b/sapi/src/builder/extension/imagick.php index 8e48589b75..c5c0fcb7fd 100644 --- a/sapi/src/builder/extension/imagick.php +++ b/sapi/src/builder/extension/imagick.php @@ -8,11 +8,10 @@ $p->addExtension( (new Extension('imagick')) ->withOptions('--with-imagick=' . IMAGEMAGICK_PREFIX) - ->withPeclVersion('3.6.0') - ->withFileHash('md5', 'f7b5e9b23fb844e5eb035203d316bc63') + ->withPeclVersion('3.8.0') + ->withFileHash('md5', 'e6185b1412e65a91e598d1c79a00aeb9') ->withHomePage('https://github.com/Imagick/imagick') ->withLicense('https://github.com/Imagick/imagick/blob/master/LICENSE', Extension::LICENSE_PHP) - ->withMd5sum('f7b5e9b23fb844e5eb035203d316bc63') ->withDependentLibraries('imagemagick') ->withDependentExtensions('tokenizer') ->withBuildCached(false) diff --git a/sapi/src/builder/extension/redis.php b/sapi/src/builder/extension/redis.php index 7665653dce..39d1c89bf5 100644 --- a/sapi/src/builder/extension/redis.php +++ b/sapi/src/builder/extension/redis.php @@ -6,9 +6,9 @@ return function (Preprocessor $p) { $p->addExtension( (new Extension('redis')) - ->withOptions('--enable-redis ') - ->withPeclVersion('6.1.0') - ->withFileHash('md5', 'ca6277b27ee35e1f55f9ad7f0b4df29b') + ->withOptions('--enable-redis') + ->withPeclVersion('6.2.0') + ->withFileHash('md5', 'b713b42a7ad2eb6638de739fffd62c3a') ->withHomePage('https://github.com/phpredis/phpredis') ->withLicense('https://github.com/phpredis/phpredis/blob/develop/COPYING', Extension::LICENSE_PHP) ->withDependentExtensions('session') diff --git a/sapi/src/builder/extension/swoole.php b/sapi/src/builder/extension/swoole.php index 7b15e58463..0c4e003c80 100644 --- a/sapi/src/builder/extension/swoole.php +++ b/sapi/src/builder/extension/swoole.php @@ -27,6 +27,11 @@ //call_user_func_array([$ext, 'withDependentLibraries'], $dependentLibraries); //call_user_func_array([$ext, 'withDependentExtensions'], $dependentExtensions); + $libiconv_prefix = ICONV_PREFIX; + + $dependentLibraries = ['curl', 'openssl', 'cares', 'zlib', 'brotli', 'nghttp2', 'sqlite3', 'unix_odbc', 'pgsql', 'libzstd']; + $dependentExtensions = ['curl', 'openssl', 'sockets', 'mysqlnd', 'pdo']; + $options[] = '--enable-swoole'; $options[] = '--enable-sockets'; $options[] = '--enable-mysqlnd'; @@ -77,7 +82,7 @@ # 新版macos getdtablesize 函数缺失 # sed -i '' 's/getdtablesize();/sysconf(_SC_OPEN_MAX);/' ext/standard/php_fopen_wrapper.c - $libc = $p->isMacos() ? '-lc++' : '-lstdc++'; + $libc = $p->isMacos() ? '-lc++ ' : '-lstdc++'; # cd /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/sys/_pthread # 或者 @@ -85,31 +90,21 @@ # grep -r 'pthread_barrier_init' . # grep -r 'pthread_barrier_t' . } - $p->withVariable('LIBS', '$LIBS ' . ($p->isMacos() ? '-lc++' : '-lstdc++')); + $p->withVariable('LIBS', '$LIBS ' . ($p->isMacos() ? '-lc++ ' : '-lstdc++')); $p->withExportVariable('CARES_CFLAGS', '$(pkg-config --cflags --static libcares)'); $p->withExportVariable('CARES_LIBS', '$(pkg-config --libs --static libcares)'); $p->withExportVariable('ZSTD_CFLAGS', '$(pkg-config --cflags --static libzstd)'); $p->withExportVariable('ZSTD_LIBS', '$(pkg-config --libs --static libzstd)'); - $p->withBeforeConfigureScript('swoole', function (Preprocessor $p) { - $cmd = ''; - if ($p->isMacos()) { - $workDir = $p->getPhpSrcDir(); - $cmd = <<withExportVariable('SWOOLE_ODBC_LIBS', '$(pkg-config --libs-only-L --libs-only-l --static odbc odbccr odbcinst readline ncursesw ) ' . " -L{$libiconv_prefix}/lib -liconv "); - }); /* - $p->withBeforeConfigureScript('swoole', function () use ($p) { - $workDir = $p->getWorkDir(); - $shell = "set -x ;cd {$workDir} ; WORKDIR={$workDir} ;" . PHP_EOL; - $shell .= <<<'EOF' + $p->withBeforeConfigureScript('swoole', function () use ($p) { + $workDir = $p->getWorkDir(); + $shell = "set -x ;cd {$workDir} ; WORKDIR={$workDir} ;" . PHP_EOL; + $shell .= <<<'EOF' SWOOLE_VERSION=$(awk 'NR==1{ print $1 }' "sapi/SWOOLE-VERSION.conf") CURRENT_SWOOLE_VERSION='' @@ -124,18 +119,18 @@ fi fi - if [ "${SWOOLE_VERSION}" != "${CURRENT_SWOOLE_VERSION}" ] ;then - test -d ext/swoole && rm -rf ext/swoole - if [ ! -f ${WORKDIR}/pool/ext/swoole-${SWOOLE_VERSION}.tgz ] ;then - test -d /tmp/swoole && rm -rf /tmp/swoole - git clone -b "${SWOOLE_VERSION}" https://github.com/swoole/swoole-src.git /tmp/swoole - cd /tmp/swoole - tar -czvf ${WORKDIR}/pool/ext/swoole-${SWOOLE_VERSION}.tgz . - fi - mkdir -p ${WORKDIR}/ext/swoole/ - tar --strip-components=1 -C ${WORKDIR}/ext/swoole/ -xf ${WORKDIR}/pool/ext/swoole-${SWOOLE_VERSION}.tgz + if [ "${SWOOLE_VERSION}" != "${CURRENT_SWOOLE_VERSION}" ] ;then + test -d ext/swoole && rm -rf ext/swoole + if [ ! -f ${WORKDIR}/pool/ext/swoole-${SWOOLE_VERSION}.tgz ] ;then + test -d /tmp/swoole && rm -rf /tmp/swoole + git clone -b "${SWOOLE_VERSION}" https://github.com/swoole/swoole-src.git /tmp/swoole + cd /tmp/swoole + rm -rf /tmp/swoole/.git/ + tar -czvf ${WORKDIR}/pool/ext/swoole-${SWOOLE_VERSION}.tgz . fi # swoole extension hook + cd {$workDir} + sed -i '' 's/pthread_barrier_init/pthread_barrier_init_x_fake/' ext/swoole/config.m4 EOF; return $shell; diff --git a/sapi/src/builder/extension/swoole_latest.php b/sapi/src/builder/extension/swoole_latest.php index 77a98ebaa9..6691cf0f9f 100644 --- a/sapi/src/builder/extension/swoole_latest.php +++ b/sapi/src/builder/extension/swoole_latest.php @@ -72,4 +72,6 @@ $p->withExportVariable('ZSTD_CFLAGS', '$(pkg-config --cflags --static libzstd)'); $p->withExportVariable('ZSTD_LIBS', '$(pkg-config --libs --static libzstd)'); + $p->withExportVariable('SWOOLE_ODBC_LIBS', '$(pkg-config --libs --static odbc odbccr odbcinst readline ncursesw )' . " -L{$libiconv_prefix}/lib -liconv "); + }; diff --git a/sapi/src/builder/library/curl.php b/sapi/src/builder/library/curl.php index cfad4cc937..889ad9dae9 100644 --- a/sapi/src/builder/library/curl.php +++ b/sapi/src/builder/library/curl.php @@ -7,23 +7,24 @@ $curl_prefix = CURL_PREFIX; $zlib_prefix = ZLIB_PREFIX; $cares_prefix = CARES_PREFIX; - + # HTTP3 (and QUIC) + # https://github.com/curl/curl/blob/master/docs/HTTP3.md#openssl-version $p->addLibrary( (new Library('curl')) ->withHomePage('https://curl.se/') ->withManual('https://curl.se/docs/install.html') ->withLicense('https://github.com/curl/curl/blob/master/COPYING', Library::LICENSE_SPEC) - ->withUrl('https://curl.se/download/curl-8.4.0.tar.gz') - ->withFileHash('md5', '533e8a3b1228d5945a6a512537bea4c7') + ->withUrl('https://github.com/curl/curl/releases/download/curl-8_16_0/curl-8.16.0.tar.gz') + ->withFileHash('md5', '3db9de72cc8f04166fa02d3173ac78bb') ->withPrefix($curl_prefix) ->withConfigure( <<addLibrary( (new Library('freetype')) ->withHomePage('https://freetype.org/') @@ -14,31 +17,30 @@ 'https://gitlab.freedesktop.org/freetype/freetype/-/blob/master/docs/GPLv2.TXT', Library::LICENSE_GPL ) - ->withUrl('https://sourceforge.net/projects/freetype/files/freetype2/2.13.2/freetype-2.13.2.tar.gz') - ->withMd5sum('d99447cf00c5f2679918f66f2b5771f6') - ->withFileHash('md5', 'd99447cf00c5f2679918f66f2b5771f6') + ->withUrl('https://github.com/freetype/freetype/archive/refs/tags/VER-2-13-2.tar.gz') + ->withFile('freetype-2.13.2.tar.gz') + ->withMd5sum('dcd1af080e43fe0c984c34bf3e7d5e16') + ->withFileHash('md5', 'dcd1af080e43fe0c984c34bf3e7d5e16') ->withPrefix($freetype_prefix) - ->withConfigure( + ->withBuildCached(false) + ->withBuildScript( <<withPkgName('freetype2') diff --git a/sapi/src/builder/library/gmp.php b/sapi/src/builder/library/gmp.php index b4367df8f4..ae92d778b9 100644 --- a/sapi/src/builder/library/gmp.php +++ b/sapi/src/builder/library/gmp.php @@ -10,16 +10,21 @@ ->withHomePage('https://gmplib.org/') ->withManual('https://gmplib.org/') ->withLicense('https://www.gnu.org/licenses/old-licenses/gpl-2.0.html', Library::LICENSE_GPL) - ->withUrl('https://ftp.gnu.org/gnu/gmp/gmp-6.3.0.tar.lz') + //->withUrl('https://ftp.gnu.org/gnu/gmp/gmp-6.3.0.tar.lz') + ->withUrl('https://ftpmirror.gnu.org/gnu/gmp/gmp-6.3.0.tar.lz') ->withFileHash('md5', 'db3f4050677df3ff2bd23422c0d3caa1') ->withPrefix($gmp_prefix) ->withConfigure( <<withPkgName('gmp') diff --git a/sapi/src/builder/library/libidn2.php b/sapi/src/builder/library/libidn2.php index 4f8b07da6d..a29229dac0 100644 --- a/sapi/src/builder/library/libidn2.php +++ b/sapi/src/builder/library/libidn2.php @@ -4,19 +4,28 @@ use SwooleCli\Preprocessor; return function (Preprocessor $p) { - if (0) { - $libiconv_prefix = ICONV_PREFIX; - $libidn2_prefix = LIBIDN2_PREFIX; - $libunistring_prefix = LIBUNISTRING_PREFIX; - $p->addLibrary( - (new Library('libidn2')) - ->withHomePage('https://gitlab.com/libidn/libidn2') - ->withManual('https://www.gnu.org/software/libidn/libidn2/manual/') - ->withLicense('https://www.gnu.org/licenses/old-licenses/gpl-2.0.html', Library::LICENSE_GPL) - ->withUrl('https://ftp.gnu.org/gnu/libidn/libidn2-2.3.4.tar.gz') - ->withPrefix($libidn2_prefix) - ->withConfigure( - <<isMacos()) { + $options = '--with-libintl-prefix='.$libintl_prefix; + } else { + $options = '--without-libintl-prefix'; + } + $p->addLibrary( + (new Library('libidn2')) + ->withHomePage('https://gitlab.com/libidn/libidn2') + ->withManual('https://www.gnu.org/software/libidn/libidn2/manual/') + ->withLicense('https://www.gnu.org/licenses/old-licenses/gpl-2.0.html', Library::LICENSE_GPL) + //->withUrl('https://ftp.gnu.org/gnu/libidn/libidn2-2.3.8.tar.gz') + ->withUrl('https://ftpmirror.gnu.org/gnu/libidn/libidn2-2.3.8.tar.gz') + ->withFileHash('md5', 'a8e113e040d57a523684e141970eea7a') + ->withPrefix($libidn2_prefix) + ->withConfigure( + <<withPkgName('libidn2') - ->withDependentLibraries('libiconv', 'libunistring') - ); - } + ) + ->withPkgName('libidn2') + ->withDependentLibraries('libiconv', 'libunistring', 'gettext') + ); + }; diff --git a/sapi/src/builder/library/libpsl.php b/sapi/src/builder/library/libpsl.php new file mode 100644 index 0000000000..0cdfd71266 --- /dev/null +++ b/sapi/src/builder/library/libpsl.php @@ -0,0 +1,48 @@ +isMacos()) { + $options = '--with-libintl-prefix=' . $libintl_prefix; + } else { + $options = '--without-libintl-prefix'; + } + $p->addLibrary( + (new Library('libpsl')) + ->withHomePage('https://rockdaboot.github.io/libpsl') + ->withManual('https://github.com/rockdaboot/libpsl.git') + ->withLicense('https://github.com/rockdaboot/libpsl/blob/master/LICENSE', Library::LICENSE_MIT) + ->withUrl('https://github.com/rockdaboot/libpsl/releases/download/0.21.5/libpsl-0.21.5.tar.gz') + ->withFileHash('md5', '870a798ee9860b6e77896548428dba7b') + ->withPrefix($libpsl_prefix) + ->withConfigure( + <<withPkgName('libpsl') + ->withDependentLibraries('libiconv', 'libunistring', 'gettext', 'libunistring') + ); + +}; diff --git a/sapi/src/builder/library/libssh2.php b/sapi/src/builder/library/libssh2.php index 6e58b10021..425118798d 100644 --- a/sapi/src/builder/library/libssh2.php +++ b/sapi/src/builder/library/libssh2.php @@ -13,8 +13,8 @@ ->withLicense('https://libssh2.org/license.html', Library::LICENSE_SPEC) ->withManual('https://github.com/libssh2/libssh2.git') ->withManual('https://github.com/libssh2/libssh2/blob/master/docs/INSTALL_CMAKE.md') - ->withUrl('https://libssh2.org/download/libssh2-1.11.0.tar.gz') - ->withFileHash('md5', 'a01d543fd891ca48fe47726540d50b17') + ->withUrl('https://libssh2.org/download/libssh2-1.11.1.tar.gz') + ->withFileHash('md5', '38857d10b5c5deb198d6989dacace2e6') ->withPrefix($libssh2_prefix) ->withBuildScript( <<addLibrary( (new Library('nghttp2')) ->withHomePage('https://github.com/nghttp2/nghttp2.git') ->withManual('https://nghttp2.org/') ->withLicense('https://github.com/nghttp2/nghttp2/blob/master/COPYING', Library::LICENSE_MIT) - ->withUrl('https://github.com/nghttp2/nghttp2/releases/download/v1.57.0/nghttp2-1.57.0.tar.gz') - ->withFileHash('md5', 'd4c92d9a85e551bb084964d2be05929c') + ->withUrl('https://github.com/nghttp2/nghttp2/releases/download/v1.68.0/nghttp2-1.68.0.tar.gz') + ->withFileHash('md5', 'e0d023d49a8d07d4d7ff8c5a93725720') ->withPrefix($nghttp2_prefix) - ->withConfigure( + ->withBuildScript( <<withPkgName('libnghttp2') diff --git a/sapi/src/builder/library/nghttp3.php b/sapi/src/builder/library/nghttp3.php index edc7d5e0b2..8036582ac7 100644 --- a/sapi/src/builder/library/nghttp3.php +++ b/sapi/src/builder/library/nghttp3.php @@ -10,9 +10,9 @@ ->withHomePage('https://github.com/ngtcp2/nghttp3') ->withLicense('https://github.com/ngtcp2/nghttp3/blob/main/COPYING', Library::LICENSE_MIT) ->withManual('https://nghttp2.org/nghttp3/') - ->withUrl('https://github.com/ngtcp2/nghttp3/archive/refs/tags/v1.0.0.tar.gz') - ->withFile('nghttp3-v1.0.0.tar.gz') - ->withFileHash('md5', '0446cce05f003bace3ac51277181ae51') + ->withUrl('https://github.com/ngtcp2/nghttp3/releases/download/v1.12.0/nghttp3-1.12.0.tar.gz') + ->withFile('nghttp3-1.12.0.tar.gz') + ->withFileHash('md5', 'ede30acfe793c5e9103eee5d38cd0304') ->withPrefix($nghttp3_prefix) ->withConfigure( <<addLibrary( (new Library('ngtcp2')) ->withHomePage('https://github.com/ngtcp2/ngtcp2') ->withLicense('https://github.com/ngtcp2/ngtcp2/blob/main/COPYING', Library::LICENSE_MIT) ->withManual('https://curl.se/docs/http3.html') - ->withUrl('https://github.com/ngtcp2/ngtcp2/releases/download/v1.1.0/ngtcp2-1.1.0.tar.gz') - ->withFile('ngtcp2-1.1.0.tar.gz') - ->withFileHash('md5', 'e05c501244a2af34b492753763c74e04') + ->withUrl('https://github.com/ngtcp2/ngtcp2/releases/download/v1.17.0/ngtcp2-1.17.0.tar.gz') + ->withFile('ngtcp2-1.17.0.tar.gz') + ->withFileHash('md5', '7b5221830f1f09ea7998aaf7dfcb87ac') ->withPrefix($ngtcp2_prefix) - ->withConfigure( + ->withBuildScript( <<withPkgName('libngtcp2') diff --git a/sapi/src/builder/library/openssl.php b/sapi/src/builder/library/openssl.php index 47b3cb5354..4ccc3b45ba 100644 --- a/sapi/src/builder/library/openssl.php +++ b/sapi/src/builder/library/openssl.php @@ -31,18 +31,18 @@ ->withHomePage('https://www.openssl.org/') ->withLicense('https://github.com/openssl/openssl/blob/master/LICENSE.txt', Library::LICENSE_APACHE2) ->withManual('https://www.openssl.org/docs/') - ->withUrl('https://github.com/quictls/openssl/archive/refs/tags/openssl-3.1.4-quic1.tar.gz') - ->withFileHash('md5', 'ba2d8774a51a38f2481aad43d05aea57') + ->withUrl('https://github.com/openssl/openssl/releases/download/openssl-3.6.0/openssl-3.6.0.tar.gz') + ->withFileHash('md5', '77ab78417082f22a2ce809898bd44da0') ->withPrefix($openssl_prefix) ->withConfigure( <<withMakeOptions('build_sw') @@ -50,6 +50,14 @@ ->withScriptAfterInstall( <<withPkgName('libssl') diff --git a/sapi/src/builder/library/pgsql.php b/sapi/src/builder/library/pgsql.php index 1b4b50eae1..3057e7959c 100644 --- a/sapi/src/builder/library/pgsql.php +++ b/sapi/src/builder/library/pgsql.php @@ -7,6 +7,13 @@ $pgsql_prefix = PGSQL_PREFIX; $ldflags = $p->isMacos() ? '' : ' -static '; $libs = $p->isMacos() ? '-lc++' : ' -lstdc++ '; + + # fix macos error: 'strchrnul' is only available on macOS 15.4 or newer + # https://www.postgresql.org/message-id/385134.1743523038@sss.pgh.pa.us + # fix solution https://github.com/theory/pgenv/issues/93 + $custom_env_start = $p->isMacos() ? 'export MACOSX_DEPLOYMENT_TARGET="$(sw_vers -productVersion)"' : ''; + $custom_env_end = $p->isMacos() ? 'unset MACOSX_DEPLOYMENT_TARGET' : ''; + $p->addLibrary( (new Library('pgsql')) ->withHomePage('https://www.postgresql.org/') @@ -18,6 +25,7 @@ ->withPrefix($pgsql_prefix) ->withBuildScript( <<withScriptAfterInstall( diff --git a/sapi/src/builder/library/unix_odbc.php b/sapi/src/builder/library/unix_odbc.php index b6d83b3b41..6c5b6c9a52 100644 --- a/sapi/src/builder/library/unix_odbc.php +++ b/sapi/src/builder/library/unix_odbc.php @@ -6,6 +6,15 @@ return function (Preprocessor $p) { $unix_odbc_prefix = UNIX_ODBC_PREFIX; $iconv_prefix = ICONV_PREFIX; + $custom_clean_script = ''; + if ($p->isMacos()) { + $custom_clean_script .= <<addLibrary( (new Library('unix_odbc')) ->withHomePage('https://github.com/lurcher/unixODBC') @@ -13,12 +22,6 @@ ->withUrl('https://github.com/lurcher/unixODBC/releases/download/2.3.11/unixODBC-2.3.11.tar.gz') ->withFileHash('md5', '0ff1fdbcb4c3c7dc2357f3fd6ba09169') ->withPrefix($unix_odbc_prefix) - ->withPreInstallCommand( - 'alpine', - <<withconfigure( <<withDependentLibraries('readline', 'libiconv') diff --git a/sapi/src/constants.php b/sapi/src/constants.php index 679a19d58d..281eb8e876 100644 --- a/sapi/src/constants.php +++ b/sapi/src/constants.php @@ -70,13 +70,14 @@ define("LIBB2_PREFIX", $p->getGlobalPrefix() . '/libb2'); define("LIBEXPAT_PREFIX", $p->getGlobalPrefix() . '/libexpat'); - define("LIBEVENT_PREFIX", $p->getGlobalPrefix() . '/libevent'); define("SNAPPY_PREFIX", $p->getGlobalPrefix() . '/snappy'); define("LIBSASL_PREFIX", $p->getGlobalPrefix() . '/sasl'); define("LIBTIFF_PREFIX", $p->getGlobalPrefix() . '/libtiff'); define("SDL2_PREFIX", $p->getGlobalPrefix() . '/sdl2'); define("LIBARGON2_PREFIX", $p->getGlobalPrefix() . '/libargon2'); +define("LIBPSL_PREFIX", $p->getGlobalPrefix() . '/libpsl'); + define("ABSL_PREFIX", $p->getGlobalPrefix() . '/absl'); define("MUSL_CROSS_MAKE_PREFIX", $p->getGlobalPrefix() . '/musl_cross_make'); diff --git a/sync-source-code.php b/sync-source-code.php index e3f9f01f8f..7976d741f6 100644 --- a/sync-source-code.php +++ b/sync-source-code.php @@ -23,11 +23,11 @@ $options = getopt('', $longopts); if (!empty($options['action']) && $options['action'] == 'run') { - //正式同步 + // 正式同步 $action = 'run'; $sync_dest_dir = $project_dir; } else { - //测试同步 + // 测试同步 # 准备工作 测试目录 $directories = array_intersect($scanned_directory_source, $scanned_directory_destination); @@ -68,6 +68,7 @@ echo "sync" # ZendVM + cd ${__WORKDIR__}/ cp -rf $SRC/Zend/. ./Zend # Extension @@ -105,6 +106,7 @@ cp -rf $SRC/ext/mbstring/. ./ext/mbstring cp -rf $SRC/ext/mysqli/. ./ext/mysqli cp -rf $SRC/ext/mysqlnd/. ./ext/mysqlnd + cp -rf $SRC/ext/random/. ./ext/random cp -rf $SRC/ext/opcache/. ./ext/opcache sed -i.backup 's/ext_shared=yes/ext_shared=no/g' ext/opcache/config.m4 @@ -161,6 +163,7 @@ cp -f $SRC/configure.ac ./configure.ac cp -f $SRC/buildconf ./buildconf + cp -f $SRC/.gdbinit ./.gdbinit cp -f $SRC/run-tests.php ./run-tests.php # scripts @@ -205,8 +208,16 @@ # cli # 【执行本命令,影响 swoole-cli 特性,请手动确认功能变更】 # cp -rf $SRC/sapi/cli/. ./sapi/cli + cp -rf $SRC/sapi/cli/ps_title.h ./sapi/cli cp -rf $SRC/sapi/cli/ps_title.c ./sapi/cli cp -rf $SRC/sapi/cli/generate_mime_type_map.php ./sapi/cli + cp -rf $SRC/sapi/cli/mime_type_map.h ./sapi/cli + cp -rf $SRC/sapi/cli/php_http_parser.h ./sapi/cli + cp -rf $SRC/sapi/cli/php_cli_server_arginfo.h ./sapi/cli + cp -rf $SRC/sapi/cli/php_cli_process_title_arginfo.h ./sapi/cli + cp -rf $SRC/sapi/cli/php_cli_process_title.c ./sapi/cli + cp -rf $SRC/sapi/cli/php_cli_server.h ./sapi/cli + cp -rf $SRC/sapi/cli/php_cli_server.c ./sapi/cli cp -rf $SRC/sapi/cli/php.1.in ./sapi/cli # clean file @@ -245,4 +256,25 @@ echo "synchronizing end "; echo PHP_EOL; echo PHP_EOL; + +echo "apply patches .... " . PHP_EOL; +function file_replace_str(string $file, string $search, string $replace): void +{ + $content = file_get_contents($file); + $content = str_replace($search, $replace, $content); + file_put_contents($file, $content); +} + +file_replace_str( + 'sapi/cli/php_cli_server.c', + 'PHP_FUNCTION(apache_request_headers)', + 'static PHP_FUNCTION(apache_request_headers)' +); + +file_replace_str( + 'sapi/cli/php_cli_server.c', + 'PHP_FUNCTION(apache_response_headers)', + 'static PHP_FUNCTION(apache_response_headers)' +); + echo "action: " . $action . ' done !' . PHP_EOL;