feat(manifest): implement BuildManifestDumper for generating build manifest

This commit is contained in:
crazywhalecc
2026-07-13 16:59:19 +08:00
parent 422323a41d
commit 4d00e338af
7 changed files with 333 additions and 4 deletions

View File

@@ -328,6 +328,49 @@ class php extends TargetPackage
return $info;
}
public function getBuildManifestData(PackageInstaller $installer): array
{
if ($this->getName() !== 'php') {
return [];
}
$all_extensions = [];
$static_extensions = [];
$shared_extensions = [];
foreach ($installer->getResolvedPackages(PhpExtensionPackage::class) as $extension) {
$extension_name = $extension->getExtensionName();
if ($extension->isBuildStatic() || $extension->isBuildShared()) {
$all_extensions[] = $extension_name;
}
if ($extension->isBuildStatic()) {
$static_extensions[] = $extension_name;
}
if ($extension->isBuildShared()) {
$shared_extensions[] = $extension_name;
}
}
$sapis = array_values(array_filter([
$installer->isPackageResolved('php-cli') ? 'cli' : null,
$installer->isPackageResolved('php-fpm') ? 'fpm' : null,
$installer->isPackageResolved('php-micro') ? 'micro' : null,
$installer->isPackageResolved('php-cgi') ? 'cgi' : null,
$installer->isPackageResolved('php-embed') ? 'embed' : null,
$installer->isPackageResolved('frankenphp') ? 'frankenphp' : null,
]));
return [
'version' => static::getPHPVersion(return_null_if_failed: true),
'thread-safety' => $this->getBuildOption('enable-zts', false) ? 'zts' : 'nts',
'sapis' => $sapis,
'extensions' => [
'all' => $all_extensions,
'static' => $static_extensions,
'shared' => $shared_extensions,
],
];
}
#[BeforeStage('php', 'build')]
public function beforeBuild(PackageBuilder $builder, Package $package): void
{

View File

@@ -15,6 +15,7 @@ use StaticPHP\Exception\EnvironmentException;
use StaticPHP\Exception\WrongUsageException;
use StaticPHP\Registry\PackageLoader;
use StaticPHP\Runtime\SystemTarget;
use StaticPHP\Util\BuildManifestDumper;
use StaticPHP\Util\BuildRootTracker;
use StaticPHP\Util\DependencyResolver;
use StaticPHP\Util\DirDiff;
@@ -282,6 +283,11 @@ class PackageInstaller
// pipeline above (they were merged into $this->packages in resolvePackages()). This only
// throws if a tool genuinely failed to become available (e.g. no artifact for this platform).
$this->ensureRequiredTools();
// Pure install operations must not overwrite the manifest of an existing build.
if ($this->build_packages !== []) {
new BuildManifestDumper()->dump($this->packages, $this->build_packages, $this->install_packages, $this);
}
}
public function isBuildPackage(Package|string $package): bool

View File

@@ -117,6 +117,19 @@ class TargetPackage extends LibraryPackage
return null;
}
/**
* Get target-specific structured data for the build manifest.
*
* Target implementations may override this method to expose build facts that
* cannot be represented by the generic resolved package list.
*
* @return array<string, mixed>
*/
public function getBuildManifestData(PackageInstaller $installer): array
{
return [];
}
/**
* Gets all build options for the target package.
*

View File

@@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
namespace StaticPHP\Util;
use StaticPHP\ConsoleApplication;
use StaticPHP\Exception\SPCInternalException;
use StaticPHP\Package\Package;
use StaticPHP\Package\PackageInstaller;
use StaticPHP\Package\TargetPackage;
use StaticPHP\Runtime\SystemTarget;
final class BuildManifestDumper
{
public const int SCHEMA_VERSION = 1;
/**
* @param array<string, Package> $packages Resolved packages in dependency order
* @param array<string, Package> $buildPackages Packages explicitly scheduled for building
* @param array<string, Package> $installPackages Packages explicitly scheduled for installation
*/
public function dump(
array $packages,
array $buildPackages,
array $installPackages,
PackageInstaller $installer,
string $outputPath = BUILD_ROOT_PATH . '/build-manifest.json',
): void {
$targets = [];
foreach ($packages as $package) {
if (!$package instanceof TargetPackage) {
continue;
}
$data = $package->getBuildManifestData($installer);
if ($data !== []) {
$targets[$package->getName()] = $data;
}
}
$manifest = [
'schema-version' => self::SCHEMA_VERSION,
'spc-version' => ConsoleApplication::VERSION,
'target' => [
'platform' => SystemTarget::getCurrentPlatformString(),
'os' => SystemTarget::getTargetOS(),
'architecture' => SystemTarget::getTargetArch(),
'libc' => SystemTarget::getLibc(),
'libc-version' => SystemTarget::getLibcVersion(),
'spc-target' => getenv('SPC_TARGET') ?: null,
'toolchain' => getenv('SPC_TOOLCHAIN') ?: null,
],
'requested-packages' => [
'build' => array_keys($buildPackages),
'install' => array_keys($installPackages),
],
'packages' => array_values(array_map(
static function (Package $package): array {
$artifact = $package->getArtifact();
return [
'name' => $package->getName(),
'type' => $package->getType(),
'artifact' => $artifact?->getName(),
];
},
$packages,
)),
'targets' => $targets,
];
$json = json_encode(
$manifest,
JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR,
);
if (FileSystem::writeFile($outputPath, $json . PHP_EOL) === false) {
throw new SPCInternalException("Failed to write build manifest: {$outputPath}");
}
logger()->info('Generated build manifest with ' . count($packages) . " package(s): {$outputPath}");
}
}