diff --git a/README-CN.md b/README-CN.md index 9c526a3d..80689c66 100644 --- a/README-CN.md +++ b/README-CN.md @@ -693,6 +693,7 @@ bin/tpc.php --wasm=browser app.php | `--build-dir ` | 生成 C++ 与中间产物的目录 | | `--dry` | 只生成 C++,跳过编译与链接 | | `--php-version <8.4\|8.5>` | 接受的 PHP 语法版本 | +| `--proxy ` | 下载 PHP 元数据和源码归档时使用的代理 | | `--cxx-std ` | C++ 标准(如 `c++17`、`c++20`) | | `--march ` | 目标指令集(如 `native`) | | `--target-platform ` | 交叉编译目标 triple | diff --git a/README.md b/README.md index 5e8e53b8..3f8cd2e0 100644 --- a/README.md +++ b/README.md @@ -761,6 +761,7 @@ Key options: | `--build-dir ` | Directory for generated C++ and intermediates | | `--dry` | Generate C++ only, skip compile and link | | `--php-version <8.4\|8.5>` | PHP syntax version to accept | +| `--proxy ` | Proxy used for PHP metadata and source archive downloads | | `--cxx-std ` | C++ standard (e.g. `c++17`, `c++20`) | | `--march ` | Target instruction set (e.g. `native`) | | `--target-platform ` | Cross-compilation target triple | diff --git a/completions/tpc.bash b/completions/tpc.bash index f2758804..0ce657e2 100644 --- a/completions/tpc.bash +++ b/completions/tpc.bash @@ -144,7 +144,7 @@ _typephp_tpc() 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 ;; esac diff --git a/docs/en/COMPILER_CLI.md b/docs/en/COMPILER_CLI.md index c9e377bc..d10560fd 100644 --- a/docs/en/COMPILER_CLI.md +++ b/docs/en/COMPILER_CLI.md @@ -75,6 +75,7 @@ bin/tpc.php app.php --dry --build-dir /tmp/typephp-build | `--format` | Run clang-format on the generated code. | | `--no-progress` | Do not show the progress bar; output progress per file. | | `--no-color` | Disable colored output. | +| `--proxy ` | 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. diff --git a/docs/zh-cn/COMPILER_CLI.md b/docs/zh-cn/COMPILER_CLI.md index 36e6e797..48a007e6 100644 --- a/docs/zh-cn/COMPILER_CLI.md +++ b/docs/zh-cn/COMPILER_CLI.md @@ -75,6 +75,7 @@ bin/tpc.php app.php --dry --build-dir /tmp/typephp-build | `--format` | 对生成代码运行 clang-format。 | | `--no-progress` | 不显示进度条,逐文件输出进度。 | | `--no-color` | 禁用彩色输出。 | +| `--proxy ` | 下载 PHP 元数据和源码归档时使用 HTTP(S) 或 SOCKS 代理。 | `-v` / `--version` 只显示版本,不是 verbose 选项。 diff --git a/phpunit/src/Http/DownloaderTest.php b/phpunit/src/Http/DownloaderTest.php new file mode 100644 index 00000000..9d748ba0 --- /dev/null +++ b/phpunit/src/Http/DownloaderTest.php @@ -0,0 +1,68 @@ +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(' '); + } +} diff --git a/src/Http/Downloader.php b/src/Http/Downloader.php index 0cd270d8..aaed6761 100644 --- a/src/Http/Downloader.php +++ b/src/Http/Downloader.php @@ -1,4 +1,10 @@ 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); @@ -123,7 +129,7 @@ final class Downloader { $process = proc_open($command, [STDIN, STDOUT, STDERR], $pipes); 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); 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]); $stderr = stream_get_contents($pipes[2]); fclose($pipes[1]); fclose($pipes[2]); 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; } diff --git a/src/compiler.php b/src/compiler.php index 26d569f9..0f5d70f0 100644 --- a/src/compiler.php +++ b/src/compiler.php @@ -70,7 +70,16 @@ function runCompiler(int $argc, array $argv, CompilerRuntime $runtime): void 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] === '-') { continue; } @@ -95,6 +104,21 @@ function compileNativeSourceProject(array $argv, CompilerRuntime $runtime): void $run = true; 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 (!isset($arguments[$i + 1]) || $arguments[$i + 1] === '') { fwrite(STDERR, "Option --build-dir requires a directory\n"); @@ -167,6 +191,21 @@ function compileWasmProgram(array $argv, CompilerRuntime $runtime): void $nano = true; 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=')) { $value = substr($argument, strlen('--wasm=')); if ($value === '') { @@ -320,7 +359,16 @@ function shouldCompileWasm(array $argv): bool } $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] === '-') { continue; }