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;
}