Skip to content

Commit e1afe74

Browse files
committed
fix(build): disable PEAR and refine symlink scanning
1 parent 60f2361 commit e1afe74

9 files changed

Lines changed: 107 additions & 47 deletions

File tree

docs/en/LIBPHP_INSTALLER.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ When `libphp.so` is missing, the installer asks in sequence:
2121
3. the install directory, defaulting to `~/.typephp`;
2222
4. whether to install missing development packages via the detected `apt-get`, `dnf`, or `yum`.
2323

24-
The installer reads the current `php-config --configure-options`, keeps the current PHP's extension configuration, replaces the install path, and adds `--enable-embed=shared`. PHP source is downloaded only from PHP.net, and verified using the SHA-256 from the official release information.
24+
The installer reads the current `php-config --configure-options`, keeps the current PHP's supported extension configuration, replaces the install path, and adds `--enable-embed=shared`. It always uses `--without-pear` because PEAR is deprecated. PHP source is downloaded only from PHP.net, and verified using the SHA-256 from the official release information.
2525

2626
After compilation, the main files are as follows:
2727

docs/zh-cn/LIBPHP_INSTALLER.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ vendor/bin/tpc.php project.yml
2121
3. 安装目录,默认为 `~/.typephp`
2222
4. 是否通过检测到的 `apt-get``dnf``yum` 安装缺失的开发包。
2323

24-
安装器读取当前 `php-config --configure-options`,保留当前 PHP 的扩展配置,替换安装路径并加入 `--enable-embed=shared`。PHP 源码只从 PHP.net 下载,并使用官方发布信息中的 SHA-256 校验。
24+
安装器读取当前 `php-config --configure-options`,保留当前 PHP 支持的扩展配置,替换安装路径并加入 `--enable-embed=shared`。由于 PEAR 已废弃,构建始终使用 `--without-pear`。PHP 源码只从 PHP.net 下载,并使用官方发布信息中的 SHA-256 校验。
2525

2626
编译完成后主要文件如下:
2727

phpunit/src/Build/FileScannerTest.php

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,16 @@ public function testSymlinkCycleIsScannedOnce(): void
6868
* Two links to one directory are one source file seen twice, and compiling
6969
* it twice would define its symbols twice.
7070
*/
71-
public function testAliasesOfOneFileAreScannedOnce(): void
71+
public function testAliasesOfOneFileAreCompiledOnce(): void
7272
{
7373
symlink($this->root . '/outside', $this->root . '/project/link-a');
7474
symlink($this->root . '/outside', $this->root . '/project/link-b');
75+
file_put_contents(
76+
$this->root . '/project/project.yml',
77+
"name: demo\nsources:\n - .\n",
78+
);
7579

76-
$files = (new FileScanner($this->root . '/project'))->scan();
80+
$files = $this->scanProject($this->root . '/project/project.yml');
7781

7882
$this->assertSame([
7983
$this->root . '/project/link-a/src/Linked.php',
@@ -102,6 +106,45 @@ public function testExcludingOneAliasKeepsTheOther(): void
102106
}
103107
}
104108

109+
/**
110+
* YAML ignore rules run outside FileScanner, so real-path deduplication must
111+
* wait until the project-level filter has selected the surviving alias.
112+
*/
113+
public function testYamlIgnoreOfOneAliasKeepsTheOther(): void
114+
{
115+
symlink($this->root . '/outside', $this->root . '/project/link-a');
116+
symlink($this->root . '/outside', $this->root . '/project/link-b');
117+
118+
foreach (['link-a' => 'link-b', 'link-b' => 'link-a'] as $excluded => $kept) {
119+
file_put_contents(
120+
$this->root . '/project/project.yml',
121+
"name: demo\nsources:\n - .\nignore:\n - {$excluded}\n",
122+
);
123+
124+
$this->assertSame([
125+
$this->root . '/project/' . $kept . '/src/Linked.php',
126+
$this->root . '/project/src/Own.php',
127+
], $this->scanProject($this->root . '/project/project.yml'));
128+
}
129+
}
130+
131+
public function testLexicalPathNormalizationPreservesNetworkRoot(): void
132+
{
133+
$compiler = CompilerTest::create($this->root . '/project');
134+
$normalize = (new \ReflectionClass($compiler))->getMethod('normalizeLexicalPath');
135+
$separator = DIRECTORY_SEPARATOR;
136+
$path = $separator . $separator . implode(
137+
$separator,
138+
['server', 'share', 'project', '.', 'vendor', '..', '..', '..', 'package'],
139+
);
140+
141+
$this->assertSame(
142+
$separator . $separator . 'server' . $separator . 'share'
143+
. $separator . 'package',
144+
$normalize->invoke($compiler, $path),
145+
);
146+
}
147+
105148
/**
106149
* `ignore` names the path that reaches a directory, not the path it points
107150
* at, so the scanned path is what an entry has to be compared against.
@@ -144,10 +187,9 @@ private function scanProject(string $projectFile): array
144187
$compiler = CompilerTest::create(dirname($projectFile));
145188
$reflection = new \ReflectionClass($compiler);
146189

147-
$parse = $reflection->getMethod('parseProjectYaml');
148-
$filter = $reflection->getMethod('filterIgnoredFiles');
190+
$parse = $reflection->getMethod('parseProjectYaml');
149191

150-
return array_values($filter->invoke($compiler, $parse->invoke($compiler, $projectFile)));
192+
return array_values($parse->invoke($compiler, $projectFile));
151193
}
152194

153195
private function removeDirectory(string $directory): void

phpunit/src/Installer/PhpBuildConfigurationTest.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,23 @@ public function testDeriveDropsProgramNameTransformsAndStaleConfigureCaches(): v
121121
self::assertContains('--enable-pcntl', $options);
122122
}
123123

124+
public function testDeriveAlwaysDisablesDeprecatedPearInstallation(): void
125+
{
126+
$options = PhpBuildConfiguration::derive(
127+
"'--with-pear=/usr/share/php' '--enable-mbstring'",
128+
'/home/test/.typephp'
129+
);
130+
131+
self::assertSame(
132+
['--without-pear'],
133+
array_values(array_filter(
134+
$options,
135+
static fn(string $option): bool => str_contains($option, 'pear')
136+
))
137+
);
138+
self::assertContains('--enable-mbstring', $options);
139+
}
140+
124141
public function testParseShellWordsRejectsIncompleteInput(): void
125142
{
126143
$this->expectException(\InvalidArgumentException::class);

src/Build/FileScanner.php

Lines changed: 5 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,11 @@ public function scan(): array
104104
// keep both generated code and cache classification deterministic.
105105
sort($files, SORT_STRING);
106106

107-
return $this->deduplicateByRealPath($files);
107+
// Keep aliases until the project-level ignore rules have run. If two
108+
// links reach the same file and only one is ignored, removing aliases
109+
// here would also remove the allowed path. The source pipeline performs
110+
// real-path deduplication after filtering.
111+
return $files;
108112
}
109113

110114
/**
@@ -166,34 +170,6 @@ private function closesLoop(\SplFileInfo $entry): bool
166170
}
167171
}
168172

169-
/**
170-
* One file reachable through several links is still one source file, and
171-
* compiling it twice would define its symbols twice.
172-
*
173-
* Exclusions have already run, so a path the project excluded can never be
174-
* the alias that survives here. The list is sorted, so which alias survives
175-
* does not depend on directory-entry order.
176-
*
177-
* @param list<string> $files
178-
* @return list<string>
179-
*/
180-
private function deduplicateByRealPath(array $files): array
181-
{
182-
$unique = [];
183-
$seen = [];
184-
185-
foreach ($files as $file) {
186-
$identity = realpath($file) ?: $file;
187-
if (isset($seen[$identity])) {
188-
continue;
189-
}
190-
$seen[$identity] = true;
191-
$unique[] = $file;
192-
}
193-
194-
return $unique;
195-
}
196-
197173
private function isExcluded(string $filePath): bool
198174
{
199175
$excluded = false;

src/Build/SourcePipelineTrait.php

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -947,15 +947,22 @@ protected function shouldIgnoreFile(string $file): bool
947947

948948
protected function filterIgnoredFiles(array $files): array
949949
{
950-
if (empty($this->ignorePaths)) {
951-
return $files;
952-
}
953-
954950
$filteredFiles = [];
951+
$seenRealPaths = [];
955952
foreach ($files as $file) {
956-
if (!$this->shouldIgnoreFile($file)) {
957-
$filteredFiles[] = $file;
953+
if ($this->shouldIgnoreFile($file)) {
954+
continue;
955+
}
956+
957+
// Ignore rules describe the path used to reach a source. Resolve
958+
// identity only after those rules have selected the surviving
959+
// aliases, then compile each physical file once.
960+
$identity = realpath($file) ?: $file;
961+
if (isset($seenRealPaths[$identity])) {
962+
continue;
958963
}
964+
$seenRealPaths[$identity] = true;
965+
$filteredFiles[] = $file;
959966
}
960967

961968
return $filteredFiles;

src/Installer/LibPhpInstaller.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,10 @@ private function install(array $release, string $prefix): void
182182
$this->run(['tar', '-xJf', $archive, '-C', $workDir]);
183183
}
184184

185-
$this->console->write('Configuring PHP with the current installation options plus --enable-embed=shared');
185+
$this->console->write(
186+
'Configuring PHP with compatible current installation options, '
187+
. '--enable-embed=shared, and --without-pear'
188+
);
186189
$this->run([$sourceDir . '/configure', ...$options], $sourceDir);
187190
// PHP is a large build; capping parallelism avoids exhausting memory on
188191
// hosts that expose many CPUs (especially containers and CI runners).

src/Installer/PhpBuildConfiguration.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@ public static function derive(string|array $configureOptions, string $prefix): a
118118
$replace = [
119119
'--prefix', '--with-config-file-path', '--with-config-file-scan-dir',
120120
'--enable-embed', '--enable-cli', '--disable-cli', '--with-libdir',
121+
// PEAR is deprecated by PHP and PECL installation is being replaced
122+
// by PIE. Do not inherit either form from the host PHP build.
123+
'--with-pear', '--without-pear',
121124
];
122125
$drop = [
123126
'--with-apxs', '--with-apxs2', '--enable-fpm', '--with-fpm-systemd',
@@ -143,6 +146,7 @@ public static function derive(string|array $configureOptions, string $prefix): a
143146
'--with-config-file-scan-dir=' . $prefix . '/lib/conf.d',
144147
'--enable-embed=shared',
145148
'--enable-cli',
149+
'--without-pear',
146150
...$result,
147151
];
148152
}

src/Translator.php

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ class Translator extends Preprocessor
8888
use ResourceCompilationTrait;
8989
use ClassConstantValueTrait;
9090

91-
public const string VERSION = '0.9.0';
91+
public const string VERSION = '0.9.1';
9292
public const string APP_NAME = 'TypePHP Compiler (AOT)';
9393

9494
protected bool $hasExplicitOutput = false;
@@ -3916,20 +3916,31 @@ protected function normalizeLexicalPath(string $path): string
39163916
$path = str_replace('\\', '/', $path);
39173917
}
39183918

3919+
// Preserve the double leading separator of Windows UNC paths. Keeping
3920+
// it on POSIX as well makes the normalization rule deterministic and
3921+
// retains the implementation-defined // network-root form.
3922+
$networkRoot = str_starts_with($path, '//');
39193923
$absolute = str_starts_with($path, '/');
3924+
// The server and share names form the root of a UNC path. `..` cannot
3925+
// cross that boundary into another share on the same server.
3926+
$rootDepth = $networkRoot ? 2 : 0;
39203927
$segments = [];
39213928
foreach (explode('/', $path) as $segment) {
39223929
if ($segment === '' || $segment === '.') {
39233930
continue;
39243931
}
3925-
if ($segment === '..' && $segments !== [] && end($segments) !== '..') {
3926-
array_pop($segments);
3932+
if ($segment === '..') {
3933+
if (count($segments) > $rootDepth && end($segments) !== '..') {
3934+
array_pop($segments);
3935+
} elseif (!$absolute) {
3936+
$segments[] = $segment;
3937+
}
39273938
continue;
39283939
}
39293940
$segments[] = $segment;
39303941
}
39313942

3932-
$normalized = ($absolute ? '/' : '') . implode('/', $segments);
3943+
$normalized = ($networkRoot ? '//' : ($absolute ? '/' : '')) . implode('/', $segments);
39333944
if ($prefix !== '') {
39343945
$normalized = $prefix . $normalized;
39353946
}

0 commit comments

Comments
 (0)