feat(compiler): add proxy support for PHP metadata and source downloads

- Implement --proxy <url> CLI option for configuring HTTP(S) or SOCKS proxy
- Add proxy handling in argument parsing with proper validation
- Update documentation in COMPILER_CLI.md, README.md, and README-CN.md
- Enhance Downloader class to support proxy configuration for curl commands
- Add stream context options for HTTP proxy with authentication support
- Include proxy error message sanitization to hide sensitive information
- Add unit tests for proxy functionality covering curl and stream scenarios
- Update bash completion script to include new --proxy option
- Validate proxy URL requirements with appropriate error messages
master
韩天峰 2 weeks ago
parent 15e17230ac
commit e16c787f44
  1. 1
      README-CN.md
  2. 1
      README.md
  3. 2
      completions/tpc.bash
  4. 1
      docs/en/COMPILER_CLI.md
  5. 1
      docs/zh-cn/COMPILER_CLI.md
  6. 68
      phpunit/src/Http/DownloaderTest.php
  7. 18
      src/Http/Downloader.php
  8. 52
      src/compiler.php

@ -693,6 +693,7 @@ bin/tpc.php --wasm=browser app.php
| `--build-dir <dir>` | 生成 C++ 与中间产物的目录 | | `--build-dir <dir>` | 生成 C++ 与中间产物的目录 |
| `--dry` | 只生成 C++,跳过编译与链接 | | `--dry` | 只生成 C++,跳过编译与链接 |
| `--php-version <8.4\|8.5>` | 接受的 PHP 语法版本 | | `--php-version <8.4\|8.5>` | 接受的 PHP 语法版本 |
| `--proxy <url>` | 下载 PHP 元数据和源码归档时使用的代理 |
| `--cxx-std <ver>` | C++ 标准(如 `c++17`、`c++20`) | | `--cxx-std <ver>` | C++ 标准(如 `c++17`、`c++20`) |
| `--march <arch>` | 目标指令集(如 `native`) | | `--march <arch>` | 目标指令集(如 `native`) |
| `--target-platform <triple>` | 交叉编译目标 triple | | `--target-platform <triple>` | 交叉编译目标 triple |

@ -761,6 +761,7 @@ Key options:
| `--build-dir <dir>` | Directory for generated C++ and intermediates | | `--build-dir <dir>` | Directory for generated C++ and intermediates |
| `--dry` | Generate C++ only, skip compile and link | | `--dry` | Generate C++ only, skip compile and link |
| `--php-version <8.4\|8.5>` | PHP syntax version to accept | | `--php-version <8.4\|8.5>` | PHP syntax version to accept |
| `--proxy <url>` | Proxy used for PHP metadata and source archive downloads |
| `--cxx-std <ver>` | C++ standard (e.g. `c++17`, `c++20`) | | `--cxx-std <ver>` | C++ standard (e.g. `c++17`, `c++20`) |
| `--march <arch>` | Target instruction set (e.g. `native`) | | `--march <arch>` | Target instruction set (e.g. `native`) |
| `--target-platform <triple>` | Cross-compilation target triple | | `--target-platform <triple>` | Cross-compilation target triple |

@ -144,7 +144,7 @@ _typephp_tpc()
return return
;; ;;
-* ) -* )
COMPREPLY=( $(compgen -W '--nano -O --optimize -o --output -h --help -v --version --profile --no-literal-strings --php-version -f --force -m --mode --sapi -r --run --debug -j --job --no-console --sanitize --cxx-std --march --compiler --target-platform --no-color --build-dir --dry -I --include-path -D --define --no-progress --lto --format -l --link-lib -L --link-path --full-static --wasm --wasm= --gen-python-helper --convert-python-to-php --output-dir --output-dir= --build-dir= --generate-completion=' -- "$current") ) COMPREPLY=( $(compgen -W '--nano -O --optimize -o --output -h --help -v --version --profile --no-literal-strings --php-version --proxy -f --force -m --mode --sapi -r --run --debug -j --job --no-console --sanitize --cxx-std --march --compiler --target-platform --no-color --build-dir --dry -I --include-path -D --define --no-progress --lto --format -l --link-lib -L --link-path --full-static --wasm --wasm= --gen-python-helper --convert-python-to-php --output-dir --output-dir= --build-dir= --generate-completion=' -- "$current") )
return return
;; ;;
esac esac

@ -75,6 +75,7 @@ bin/tpc.php app.php --dry --build-dir /tmp/typephp-build
| `--format` | Run clang-format on the generated code. | | `--format` | Run clang-format on the generated code. |
| `--no-progress` | Do not show the progress bar; output progress per file. | | `--no-progress` | Do not show the progress bar; output progress per file. |
| `--no-color` | Disable colored output. | | `--no-color` | Disable colored output. |
| `--proxy <url>` | Use an HTTP(S) or SOCKS proxy for PHP metadata and source archive downloads. |
`-v` / `--version` only displays the version; it is not a verbose option. `-v` / `--version` only displays the version; it is not a verbose option.

@ -75,6 +75,7 @@ bin/tpc.php app.php --dry --build-dir /tmp/typephp-build
| `--format` | 对生成代码运行 clang-format。 | | `--format` | 对生成代码运行 clang-format。 |
| `--no-progress` | 不显示进度条,逐文件输出进度。 | | `--no-progress` | 不显示进度条,逐文件输出进度。 |
| `--no-color` | 禁用彩色输出。 | | `--no-color` | 禁用彩色输出。 |
| `--proxy <url>` | 下载 PHP 元数据和源码归档时使用 HTTP(S) 或 SOCKS 代理。 |
`-v` / `--version` 只显示版本,不是 verbose 选项。 `-v` / `--version` 只显示版本,不是 verbose 选项。

@ -0,0 +1,68 @@
<?php
/**
* This file is part of TypePHP(AOT).
*
* @link https://www.swoole.com/aot/
* @contact service@swoole.com
*/
namespace TypePhpTest\Http;
use PHPUnit\Framework\TestCase;
use TypePhp\Http\Downloader;
/**
* @internal
* @coversNothing
*/
final class DownloaderTest extends TestCase
{
public function testProxyIsAddedToCurlDownloads(): void
{
$downloader = new Downloader('socks5h://127.0.0.1:1080');
self::assertSame(
[
'/usr/bin/curl',
'--fail',
'--location',
'--retry',
'3',
'--proxy',
'socks5h://127.0.0.1:1080',
'--output',
'/tmp/archive.tar.xz',
'https://www.php.net/archive.tar.xz',
],
$downloader->curlCommand(
'/usr/bin/curl',
'https://www.php.net/archive.tar.xz',
'/tmp/archive.tar.xz',
),
);
}
public function testHttpProxyIsConfiguredForStreamFallback(): void
{
$downloader = new Downloader('http://user:p%40ss@proxy.example:8080');
self::assertSame(
[
'http' => [
'timeout' => 30,
'user_agent' => 'TypePHP/tpc',
'proxy' => 'tcp://proxy.example:8080',
'request_fulluri' => true,
'header' => 'Proxy-Authorization: Basic ' . base64_encode('user:p@ss'),
],
],
$downloader->streamContextOptions(),
);
}
public function testEmptyProxyIsRejected(): void
{
$this->expectException(\InvalidArgumentException::class);
new Downloader(' ');
}
}

@ -1,4 +1,10 @@
<?php <?php
/**
* This file is part of TypePHP(AOT).
*
* @link https://www.swoole.com/aot/
* @contact service@swoole.com
*/
namespace TypePhp\Http; namespace TypePhp\Http;
@ -94,7 +100,7 @@ final class Downloader
$host = (string) ($parts['host'] ?? ''); $host = (string) ($parts['host'] ?? '');
if (!in_array($scheme, ['http', 'tcp'], true) || $host === '') { if (!in_array($scheme, ['http', 'tcp'], true) || $host === '') {
throw new \RuntimeException( throw new \RuntimeException(
"Proxy {$this->proxy} requires curl; the PHP stream fallback supports only HTTP proxies", 'The configured proxy requires curl; the PHP stream fallback supports only HTTP proxies',
); );
} }
$port = (int) ($parts['port'] ?? 80); $port = (int) ($parts['port'] ?? 80);
@ -123,7 +129,7 @@ final class Downloader
{ {
$process = proc_open($command, [STDIN, STDOUT, STDERR], $pipes); $process = proc_open($command, [STDIN, STDOUT, STDERR], $pipes);
if (!is_resource($process) || proc_close($process) !== 0) { if (!is_resource($process) || proc_close($process) !== 0) {
throw new \RuntimeException('Command failed: ' . implode(' ', $command)); throw new \RuntimeException('Download command failed');
} }
} }
@ -132,14 +138,18 @@ final class Downloader
{ {
$process = proc_open($command, [1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes); $process = proc_open($command, [1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes);
if (!is_resource($process)) { if (!is_resource($process)) {
throw new \RuntimeException('Unable to run command: ' . implode(' ', $command)); throw new \RuntimeException('Unable to start download command');
} }
$stdout = stream_get_contents($pipes[1]); $stdout = stream_get_contents($pipes[1]);
$stderr = stream_get_contents($pipes[2]); $stderr = stream_get_contents($pipes[2]);
fclose($pipes[1]); fclose($pipes[1]);
fclose($pipes[2]); fclose($pipes[2]);
if (proc_close($process) !== 0) { if (proc_close($process) !== 0) {
throw new \RuntimeException(trim($stderr)); $message = trim($stderr);
if ($this->proxy !== null) {
$message = str_replace($this->proxy, '[proxy]', $message);
}
throw new \RuntimeException($message);
} }
return $stdout; return $stdout;
} }

@ -70,7 +70,16 @@ function runCompiler(int $argc, array $argv, CompilerRuntime $runtime): void
function shouldCompileNativeSourceProject(array $argv): bool function shouldCompileNativeSourceProject(array $argv): bool
{ {
foreach (array_slice($argv, 1) as $argument) { $arguments = array_slice($argv, 1);
for ($i = 0, $count = count($arguments); $i < $count; ++$i) {
$argument = $arguments[$i];
if ($argument === '--proxy') {
++$i;
continue;
}
if (str_starts_with($argument, '--proxy=')) {
continue;
}
if ($argument === '' || $argument[0] === '-') { if ($argument === '' || $argument[0] === '-') {
continue; continue;
} }
@ -95,6 +104,21 @@ function compileNativeSourceProject(array $argv, CompilerRuntime $runtime): void
$run = true; $run = true;
continue; continue;
} }
if ($argument === '--proxy') {
if (!isset($arguments[$i + 1]) || $arguments[$i + 1] === '') {
fwrite(STDERR, "Option --proxy requires a URL\n");
exit(1);
}
++$i;
continue;
}
if (str_starts_with($argument, '--proxy=')) {
if (substr($argument, strlen('--proxy=')) === '') {
fwrite(STDERR, "Option --proxy requires a URL\n");
exit(1);
}
continue;
}
if ($argument === '--build-dir') { if ($argument === '--build-dir') {
if (!isset($arguments[$i + 1]) || $arguments[$i + 1] === '') { if (!isset($arguments[$i + 1]) || $arguments[$i + 1] === '') {
fwrite(STDERR, "Option --build-dir requires a directory\n"); fwrite(STDERR, "Option --build-dir requires a directory\n");
@ -167,6 +191,21 @@ function compileWasmProgram(array $argv, CompilerRuntime $runtime): void
$nano = true; $nano = true;
continue; continue;
} }
if ($argument === '--proxy') {
if (!isset($arguments[$i + 1]) || $arguments[$i + 1] === '') {
fwrite(STDERR, "Option --proxy requires a URL\n");
exit(1);
}
++$i;
continue;
}
if (str_starts_with($argument, '--proxy=')) {
if (substr($argument, strlen('--proxy=')) === '') {
fwrite(STDERR, "Option --proxy requires a URL\n");
exit(1);
}
continue;
}
if (str_starts_with($argument, '--wasm=')) { if (str_starts_with($argument, '--wasm=')) {
$value = substr($argument, strlen('--wasm=')); $value = substr($argument, strlen('--wasm='));
if ($value === '') { if ($value === '') {
@ -320,7 +359,16 @@ function shouldCompileWasm(array $argv): bool
} }
$workingDirectory = getcwd(); $workingDirectory = getcwd();
foreach (array_slice($argv, 1) as $argument) { $arguments = array_slice($argv, 1);
for ($i = 0, $count = count($arguments); $i < $count; ++$i) {
$argument = $arguments[$i];
if ($argument === '--proxy') {
++$i;
continue;
}
if (str_starts_with($argument, '--proxy=')) {
continue;
}
if ($argument === '' || $argument[0] === '-') { if ($argument === '' || $argument[0] === '-') {
continue; continue;
} }

Loading…
Cancel
Save