Add imap and BuildRootTracker

This commit is contained in:
crazywhalecc 2026-02-06 09:48:51 +08:00
parent 7ae16e5be8
commit 8f798c9006
No known key found for this signature in database
GPG Key ID: 1F4BDD59391F2680
4 changed files with 328 additions and 0 deletions

14
config/pkg/lib/imap.yml Normal file
View File

@ -0,0 +1,14 @@
imap:
type: library
artifact:
source: {
"type": "git",
"url": "https://github.com/static-php/imap.git",
"rev": "master"
}
metadata:
license-files: [LICENSE]
static-libs@unix:
- libc-client.a
suggests@unix:
- openssl

View File

@ -6,10 +6,15 @@ namespace Package\Library;
use Package\Target\php;
use StaticPHP\Attribute\Package\AfterStage;
use StaticPHP\Attribute\Package\BuildFor;
use StaticPHP\Attribute\Package\Library;
use StaticPHP\Attribute\Package\PatchBeforeBuild;
use StaticPHP\Attribute\PatchDescription;
use StaticPHP\Package\LibraryPackage;
use StaticPHP\Package\PackageInstaller;
use StaticPHP\Runtime\SystemTarget;
use StaticPHP\Util\FileSystem;
use StaticPHP\Util\SourcePatcher;
#[Library('imap')]
class imap
@ -22,4 +27,91 @@ class imap
FileSystem::replaceFileRegex(BUILD_BIN_PATH . '/php-config', '/^libs="(.*)"$/m', 'libs="$1 -lcrypt"');
}
}
#[PatchBeforeBuild]
#[PatchDescription('Patch imap build system for Linux and macOS compatibility')]
public function patchBeforeBuild(LibraryPackage $lib): void
{
if (SystemTarget::getTargetOS() === 'Linux') {
$cc = getenv('CC') ?: 'gcc';
// FileSystem::replaceFileStr($lib->getSourceDir() . '/Makefile', '-DMAC_OSX_KLUDGE=1', '');
FileSystem::replaceFileStr("{$lib->getSourceDir()}/src/osdep/unix/Makefile", 'CC=cc', "CC={$cc}");
/* FileSystem::replaceFileStr($lib->getSourceDir() . '/src/osdep/unix/Makefile', '-lcrypto -lz', '-lcrypto');
FileSystem::replaceFileStr($lib->getSourceDir() . '/src/osdep/unix/Makefile', '-lcrypto', '-lcrypto -lz');
FileSystem::replaceFileStr(
$lib->getSourceDir() . '/src/osdep/unix/ssl_unix.c',
"#include <x509v3.h>\n#include <ssl.h>",
"#include <ssl.h>\n#include <x509v3.h>"
);
// SourcePatcher::patchFile('1006_openssl1.1_autoverify.patch', $lib->getSourceDir());
SourcePatcher::patchFile('2014_openssl1.1.1_sni.patch', $lib->getSourceDir()); */
FileSystem::replaceFileStr("{$lib->getSourceDir()}/Makefile", 'SSLINCLUDE=/usr/include/openssl', "SSLINCLUDE={$lib->getIncludeDir()}");
FileSystem::replaceFileStr("{$lib->getSourceDir()}/Makefile", 'SSLLIB=/usr/lib', "SSLLIB={$lib->getLibDir()}");
} elseif (SystemTarget::getTargetOS() === 'Darwin') {
$cc = getenv('CC') ?: 'clang';
SourcePatcher::patchFile('0001_imap_macos.patch', $lib->getSourceDir());
FileSystem::replaceFileStr($lib->getSourceDir() . '/src/osdep/unix/Makefile', 'CC=cc', "CC={$cc}");
FileSystem::replaceFileStr($lib->getSourceDir() . '/Makefile', 'SSLINCLUDE=/usr/include/openssl', 'SSLINCLUDE=' . $lib->getIncludeDir());
FileSystem::replaceFileStr($lib->getSourceDir() . '/Makefile', 'SSLLIB=/usr/lib', 'SSLLIB=' . $lib->getLibDir());
}
}
#[BuildFor('Linux')]
public function buildLinux(LibraryPackage $lib, PackageInstaller $installer): void
{
if ($installer->isPackageResolved('openssl')) {
$ssl_options = "SPECIALAUTHENTICATORS=ssl SSLTYPE=unix.nopwd SSLINCLUDE={$lib->getIncludeDir()} SSLLIB={$lib->getLibDir()}";
} else {
$ssl_options = 'SSLTYPE=none';
}
$libcVer = SystemTarget::getLibcVersion();
$extraLibs = $libcVer && version_compare($libcVer, '2.17', '<=') ? 'EXTRALDFLAGS="-ldl -lrt -lpthread"' : '';
shell()->cd($lib->getSourceDir())
->exec('make clean')
->exec('touch ip6')
->exec('chmod +x tools/an')
->exec('chmod +x tools/ua')
->exec('chmod +x src/osdep/unix/drivers')
->exec('chmod +x src/osdep/unix/mkauths')
->exec("yes | make slx {$ssl_options} EXTRACFLAGS='-fPIC -Wno-implicit-function-declaration -Wno-incompatible-function-pointer-types' {$extraLibs}");
try {
shell()
->exec("cp -rf {$lib->getSourceDir()}/c-client/c-client.a {$lib->getLibDir()}/libc-client.a")
->exec("cp -rf {$lib->getSourceDir()}/c-client/*.c {$lib->getLibDir()}/")
->exec("cp -rf {$lib->getSourceDir()}/c-client/*.h {$lib->getIncludeDir()}/")
->exec("cp -rf {$lib->getSourceDir()}/src/osdep/unix/*.h {$lib->getIncludeDir()}/");
} catch (\Throwable) {
// last command throws an exception, no idea why since it works
}
}
#[BuildFor('Darwin')]
public function buildDarwin(LibraryPackage $lib, PackageInstaller $installer): void
{
if ($installer->isPackageResolved('openssl')) {
$ssl_options = "SPECIALAUTHENTICATORS=ssl SSLTYPE=unix.nopwd SSLINCLUDE={$lib->getIncludeDir()} SSLLIB={$lib->getLibDir()}";
} else {
$ssl_options = 'SSLTYPE=none';
}
$out = shell()->execWithResult('echo "-include $(xcrun --show-sdk-path)/usr/include/poll.h -include $(xcrun --show-sdk-path)/usr/include/time.h -include $(xcrun --show-sdk-path)/usr/include/utime.h"')[1][0];
shell()->cd($lib->getSourceDir())
->exec('make clean')
->exec('touch ip6')
->exec('chmod +x tools/an')
->exec('chmod +x tools/ua')
->exec('chmod +x src/osdep/unix/drivers')
->exec('chmod +x src/osdep/unix/mkauths')
->exec(
"echo y | make osx {$ssl_options} EXTRACFLAGS='-Wno-implicit-function-declaration -Wno-incompatible-function-pointer-types {$out}'"
);
try {
shell()
->exec("cp -rf {$lib->getSourceDir()}/c-client/c-client.a {$lib->getLibDir()}/libc-client.a")
->exec("cp -rf {$lib->getSourceDir()}/c-client/*.c {$lib->getLibDir()}/")
->exec("cp -rf {$lib->getSourceDir()}/c-client/*.h {$lib->getIncludeDir()}/")
->exec("cp -rf {$lib->getSourceDir()}/src/osdep/unix/*.h {$lib->getIncludeDir()}/");
} catch (\Throwable) {
// last command throws an exception, no idea why since it works
}
}
}

View File

@ -13,6 +13,7 @@ use StaticPHP\DI\ApplicationContext;
use StaticPHP\Exception\WrongUsageException;
use StaticPHP\Registry\PackageLoader;
use StaticPHP\Runtime\SystemTarget;
use StaticPHP\Util\BuildRootTracker;
use StaticPHP\Util\DependencyResolver;
use StaticPHP\Util\DirDiff;
use StaticPHP\Util\FileSystem;
@ -42,6 +43,9 @@ class PackageInstaller
/** @var bool Whether to download missing sources automatically */
protected bool $download = true;
/** @var null|BuildRootTracker buildroot file tracker for debugging purpose */
protected ?BuildRootTracker $tracker = null;
public function __construct(protected array $options = [])
{
ApplicationContext::set(PackageInstaller::class, $this);
@ -53,6 +57,11 @@ class PackageInstaller
if (!empty($options['no-download'])) {
$this->download = false;
}
// Initialize BuildRootTracker if tracking is enabled (default: enabled unless --no-tracker)
if (empty($options['no-tracker'])) {
$this->tracker = new BuildRootTracker();
}
}
/**
@ -111,6 +120,16 @@ class PackageInstaller
return $this;
}
/**
* Get the BuildRootTracker instance.
*
* @return null|BuildRootTracker The tracker instance or null if tracking is disabled
*/
public function getTracker(): ?BuildRootTracker
{
return $this->tracker;
}
public function printBuildPackageOutputs(): void
{
foreach ($this->build_packages as $package) {
@ -183,8 +202,14 @@ class PackageInstaller
InteractiveTerm::indicateProgress('Installing package: ' . ConsoleColor::yellow($package->getName()));
}
try {
// Start tracking for binary installation
$this->tracker?->startTracking($package, 'install');
$status = $this->installBinary($package);
// Stop tracking and record changes
$this->tracker?->stopTracking();
} catch (\Throwable $e) {
// Stop tracking on error
$this->tracker?->stopTracking();
if ($interactive) {
InteractiveTerm::finish('Installing binary package failed: ' . ConsoleColor::red($package->getName()), false);
echo PHP_EOL;
@ -199,6 +224,9 @@ class PackageInstaller
InteractiveTerm::indicateProgress('Building package: ' . ConsoleColor::yellow($package->getName()));
}
try {
// Start tracking for build
$this->tracker?->startTracking($package, 'build');
if ($is_to_build && ($this->options['pack-mode'] ?? false) === true) {
$dirdiff = new DirDiff(BUILD_ROOT_PATH, false);
ApplicationContext::set(DirDiff::class, $dirdiff);
@ -209,7 +237,12 @@ class PackageInstaller
if ($is_to_build && ($this->options['pack-mode'] ?? false) === true) {
$package->runStage('packPrebuilt');
}
// Stop tracking and record changes
$this->tracker?->stopTracking();
} catch (\Throwable $e) {
// Stop tracking on error
$this->tracker?->stopTracking();
if ($interactive) {
InteractiveTerm::finish('Building package failed: ' . ConsoleColor::red($package->getName()), false);
echo PHP_EOL;

View File

@ -0,0 +1,189 @@
<?php
declare(strict_types=1);
namespace StaticPHP\Util;
use StaticPHP\Package\Package;
/**
* BuildRootTracker tracks which files in buildroot directory are created/modified by which package.
* This helps to understand the file provenance and manage build artifacts.
*/
class BuildRootTracker
{
/** @var array<string, array{package: string, type: string, files: array<string>}> Tracking data */
protected array $tracking_data = [];
protected static string $tracker_file = BUILD_ROOT_PATH . '/.spc-tracker.json';
protected ?DirDiff $current_diff = null;
protected ?string $current_package = null;
protected ?string $current_type = null;
public function __construct()
{
$this->loadTrackingData();
}
/**
* Start tracking for a package.
*
* @param Package $package The package to track
* @param string $type The operation type: 'build' or 'install'
*/
public function startTracking(Package $package, string $type = 'build'): void
{
$this->current_package = $package->getName();
$this->current_type = $type;
$this->current_diff = new DirDiff(BUILD_ROOT_PATH, false);
}
/**
* Stop tracking and record the changes.
*/
public function stopTracking(): void
{
if ($this->current_diff === null || $this->current_package === null) {
return;
}
$increment_files = $this->current_diff->getIncrementFiles(true);
if ($increment_files !== []) {
// Remove buildroot prefix if exists and normalize paths
$normalized_files = array_map(function ($file) {
// Remove leading slashes
return ltrim($file, '/\\');
}, $increment_files);
$this->tracking_data[$this->current_package] = [
'package' => $this->current_package,
'type' => $this->current_type,
'files' => array_values($normalized_files),
'time' => date('Y-m-d H:i:s'),
];
$this->saveTrackingData();
}
$this->current_diff = null;
$this->current_package = null;
$this->current_type = null;
}
/**
* Get tracking data for a specific package.
*
* @param string $package_name Package name
* @return null|array Tracking data or null if not found
*/
public function getPackageTracking(string $package_name): ?array
{
return $this->tracking_data[$package_name] ?? null;
}
/**
* Get all tracking data.
*
* @return array<string, array> All tracking data
*/
public function getAllTracking(): array
{
return $this->tracking_data;
}
/**
* Find which package introduced a specific file.
*
* @param string $file File path (relative to buildroot)
* @return null|string Package name or null if not found
*/
public function findFileSource(string $file): ?string
{
$file = ltrim($file, '/\\');
foreach ($this->tracking_data as $package_name => $data) {
if (in_array($file, $data['files'], true)) {
return $package_name;
}
}
return null;
}
/**
* Clear tracking data for a specific package.
*
* @param string $package_name Package name
*/
public function clearPackageTracking(string $package_name): void
{
unset($this->tracking_data[$package_name]);
$this->saveTrackingData();
}
/**
* Clear all tracking data.
*/
public function clearAllTracking(): void
{
$this->tracking_data = [];
$this->saveTrackingData();
}
/**
* Get tracking statistics.
*
* @return array{total_packages: int, total_files: int, by_type: array<string, int>}
*/
public function getStatistics(): array
{
$total_files = 0;
$by_type = [];
foreach ($this->tracking_data as $data) {
$total_files += count($data['files']);
$type = $data['type'];
$by_type[$type] = ($by_type[$type] ?? 0) + 1;
}
return [
'total_packages' => count($this->tracking_data),
'total_files' => $total_files,
'by_type' => $by_type,
];
}
/**
* Get the tracker file path.
*/
public static function getTrackerFilePath(): string
{
return self::$tracker_file;
}
/**
* Load tracking data from file.
*/
protected function loadTrackingData(): void
{
if (is_file(self::$tracker_file)) {
$content = file_get_contents(self::$tracker_file);
$data = json_decode($content, true);
if (is_array($data)) {
$this->tracking_data = $data;
}
}
}
/**
* Save tracking data to file.
*/
protected function saveTrackingData(): void
{
FileSystem::createDir(dirname(self::$tracker_file));
$content = json_encode($this->tracking_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
FileSystem::writeFile(self::$tracker_file, $content);
}
}