fix(build): disable PEAR and refine symlink scanning

master
韩天峰 3 weeks ago
parent 60f2361b96
commit e1afe748b8
  1. 2
      docs/en/LIBPHP_INSTALLER.md
  2. 2
      docs/zh-cn/LIBPHP_INSTALLER.md
  3. 50
      phpunit/src/Build/FileScannerTest.php
  4. 17
      phpunit/src/Installer/PhpBuildConfigurationTest.php
  5. 34
      src/Build/FileScanner.php
  6. 19
      src/Build/SourcePipelineTrait.php
  7. 5
      src/Installer/LibPhpInstaller.php
  8. 4
      src/Installer/PhpBuildConfiguration.php
  9. 17
      src/Translator.php

@ -21,7 +21,7 @@ When `libphp.so` is missing, the installer asks in sequence:
3. the install directory, defaulting to `~/.typephp`;
4. whether to install missing development packages via the detected `apt-get`, `dnf`, or `yum`.
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.
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.
After compilation, the main files are as follows:

@ -21,7 +21,7 @@ vendor/bin/tpc.php project.yml
3. 安装目录,默认为 `~/.typephp`;
4. 是否通过检测到的 `apt-get`、`dnf` 或 `yum` 安装缺失的开发包。
安装器读取当前 `php-config --configure-options`,保留当前 PHP 的扩展配置,替换安装路径并加入 `--enable-embed=shared`。PHP 源码只从 PHP.net 下载,并使用官方发布信息中的 SHA-256 校验。
安装器读取当前 `php-config --configure-options`,保留当前 PHP 支持的扩展配置,替换安装路径并加入 `--enable-embed=shared`。由于 PEAR 已废弃,构建始终使用 `--without-pear`。PHP 源码只从 PHP.net 下载,并使用官方发布信息中的 SHA-256 校验。
编译完成后主要文件如下:

@ -68,12 +68,16 @@ final class FileScannerTest extends TestCase
* Two links to one directory are one source file seen twice, and compiling
* it twice would define its symbols twice.
*/
public function testAliasesOfOneFileAreScannedOnce(): void
public function testAliasesOfOneFileAreCompiledOnce(): void
{
symlink($this->root . '/outside', $this->root . '/project/link-a');
symlink($this->root . '/outside', $this->root . '/project/link-b');
file_put_contents(
$this->root . '/project/project.yml',
"name: demo\nsources:\n - .\n",
);
$files = (new FileScanner($this->root . '/project'))->scan();
$files = $this->scanProject($this->root . '/project/project.yml');
$this->assertSame([
$this->root . '/project/link-a/src/Linked.php',
@ -102,6 +106,45 @@ final class FileScannerTest extends TestCase
}
}
/**
* YAML ignore rules run outside FileScanner, so real-path deduplication must
* wait until the project-level filter has selected the surviving alias.
*/
public function testYamlIgnoreOfOneAliasKeepsTheOther(): void
{
symlink($this->root . '/outside', $this->root . '/project/link-a');
symlink($this->root . '/outside', $this->root . '/project/link-b');
foreach (['link-a' => 'link-b', 'link-b' => 'link-a'] as $excluded => $kept) {
file_put_contents(
$this->root . '/project/project.yml',
"name: demo\nsources:\n - .\nignore:\n - {$excluded}\n",
);
$this->assertSame([
$this->root . '/project/' . $kept . '/src/Linked.php',
$this->root . '/project/src/Own.php',
], $this->scanProject($this->root . '/project/project.yml'));
}
}
public function testLexicalPathNormalizationPreservesNetworkRoot(): void
{
$compiler = CompilerTest::create($this->root . '/project');
$normalize = (new \ReflectionClass($compiler))->getMethod('normalizeLexicalPath');
$separator = DIRECTORY_SEPARATOR;
$path = $separator . $separator . implode(
$separator,
['server', 'share', 'project', '.', 'vendor', '..', '..', '..', 'package'],
);
$this->assertSame(
$separator . $separator . 'server' . $separator . 'share'
. $separator . 'package',
$normalize->invoke($compiler, $path),
);
}
/**
* `ignore` names the path that reaches a directory, not the path it points
* at, so the scanned path is what an entry has to be compared against.
@ -145,9 +188,8 @@ final class FileScannerTest extends TestCase
$reflection = new \ReflectionClass($compiler);
$parse = $reflection->getMethod('parseProjectYaml');
$filter = $reflection->getMethod('filterIgnoredFiles');
return array_values($filter->invoke($compiler, $parse->invoke($compiler, $projectFile)));
return array_values($parse->invoke($compiler, $projectFile));
}
private function removeDirectory(string $directory): void

@ -121,6 +121,23 @@ final class PhpBuildConfigurationTest extends TestCase
self::assertContains('--enable-pcntl', $options);
}
public function testDeriveAlwaysDisablesDeprecatedPearInstallation(): void
{
$options = PhpBuildConfiguration::derive(
"'--with-pear=/usr/share/php' '--enable-mbstring'",
'/home/test/.typephp'
);
self::assertSame(
['--without-pear'],
array_values(array_filter(
$options,
static fn(string $option): bool => str_contains($option, 'pear')
))
);
self::assertContains('--enable-mbstring', $options);
}
public function testParseShellWordsRejectsIncompleteInput(): void
{
$this->expectException(\InvalidArgumentException::class);

@ -104,7 +104,11 @@ class FileScanner
// keep both generated code and cache classification deterministic.
sort($files, SORT_STRING);
return $this->deduplicateByRealPath($files);
// Keep aliases until the project-level ignore rules have run. If two
// links reach the same file and only one is ignored, removing aliases
// here would also remove the allowed path. The source pipeline performs
// real-path deduplication after filtering.
return $files;
}
/**
@ -166,34 +170,6 @@ class FileScanner
}
}
/**
* One file reachable through several links is still one source file, and
* compiling it twice would define its symbols twice.
*
* Exclusions have already run, so a path the project excluded can never be
* the alias that survives here. The list is sorted, so which alias survives
* does not depend on directory-entry order.
*
* @param list<string> $files
* @return list<string>
*/
private function deduplicateByRealPath(array $files): array
{
$unique = [];
$seen = [];
foreach ($files as $file) {
$identity = realpath($file) ?: $file;
if (isset($seen[$identity])) {
continue;
}
$seen[$identity] = true;
$unique[] = $file;
}
return $unique;
}
private function isExcluded(string $filePath): bool
{
$excluded = false;

@ -947,15 +947,22 @@ PHP
protected function filterIgnoredFiles(array $files): array
{
if (empty($this->ignorePaths)) {
return $files;
}
$filteredFiles = [];
$seenRealPaths = [];
foreach ($files as $file) {
if (!$this->shouldIgnoreFile($file)) {
$filteredFiles[] = $file;
if ($this->shouldIgnoreFile($file)) {
continue;
}
// Ignore rules describe the path used to reach a source. Resolve
// identity only after those rules have selected the surviving
// aliases, then compile each physical file once.
$identity = realpath($file) ?: $file;
if (isset($seenRealPaths[$identity])) {
continue;
}
$seenRealPaths[$identity] = true;
$filteredFiles[] = $file;
}
return $filteredFiles;

@ -182,7 +182,10 @@ final class LibPhpInstaller
$this->run(['tar', '-xJf', $archive, '-C', $workDir]);
}
$this->console->write('Configuring PHP with the current installation options plus --enable-embed=shared');
$this->console->write(
'Configuring PHP with compatible current installation options, '
. '--enable-embed=shared, and --without-pear'
);
$this->run([$sourceDir . '/configure', ...$options], $sourceDir);
// PHP is a large build; capping parallelism avoids exhausting memory on
// hosts that expose many CPUs (especially containers and CI runners).

@ -118,6 +118,9 @@ final class PhpBuildConfiguration
$replace = [
'--prefix', '--with-config-file-path', '--with-config-file-scan-dir',
'--enable-embed', '--enable-cli', '--disable-cli', '--with-libdir',
// PEAR is deprecated by PHP and PECL installation is being replaced
// by PIE. Do not inherit either form from the host PHP build.
'--with-pear', '--without-pear',
];
$drop = [
'--with-apxs', '--with-apxs2', '--enable-fpm', '--with-fpm-systemd',
@ -143,6 +146,7 @@ final class PhpBuildConfiguration
'--with-config-file-scan-dir=' . $prefix . '/lib/conf.d',
'--enable-embed=shared',
'--enable-cli',
'--without-pear',
...$result,
];
}

@ -88,7 +88,7 @@ class Translator extends Preprocessor
use ResourceCompilationTrait;
use ClassConstantValueTrait;
public const string VERSION = '0.9.0';
public const string VERSION = '0.9.1';
public const string APP_NAME = 'TypePHP Compiler (AOT)';
protected bool $hasExplicitOutput = false;
@ -3916,20 +3916,31 @@ CODE;
$path = str_replace('\\', '/', $path);
}
// Preserve the double leading separator of Windows UNC paths. Keeping
// it on POSIX as well makes the normalization rule deterministic and
// retains the implementation-defined // network-root form.
$networkRoot = str_starts_with($path, '//');
$absolute = str_starts_with($path, '/');
// The server and share names form the root of a UNC path. `..` cannot
// cross that boundary into another share on the same server.
$rootDepth = $networkRoot ? 2 : 0;
$segments = [];
foreach (explode('/', $path) as $segment) {
if ($segment === '' || $segment === '.') {
continue;
}
if ($segment === '..' && $segments !== [] && end($segments) !== '..') {
if ($segment === '..') {
if (count($segments) > $rootDepth && end($segments) !== '..') {
array_pop($segments);
} elseif (!$absolute) {
$segments[] = $segment;
}
continue;
}
$segments[] = $segment;
}
$normalized = ($absolute ? '/' : '') . implode('/', $segments);
$normalized = ($networkRoot ? '//' : ($absolute ? '/' : '')) . implode('/', $segments);
if ($prefix !== '') {
$normalized = $prefix . $normalized;
}

Loading…
Cancel
Save