TypePHP 编译器
https://swoole.com/aot/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
212 lines
8.2 KiB
212 lines
8.2 KiB
<?php
|
|
|
|
namespace TypePhp\Build;
|
|
|
|
use RuntimeException;
|
|
use TypePhp\Analysis\CompilationStatistics;
|
|
|
|
/** Resolves the Composer-provided runtime sources used by a Nano build. */
|
|
final class NanoSourceComposer
|
|
{
|
|
/**
|
|
* @return array{
|
|
* packages: list<ComposerNativePackage>,
|
|
* packageSources: list<string>,
|
|
* includeDirs: list<string>,
|
|
* defines: list<string>,
|
|
* registry: string,
|
|
* selection: NanoExtensionSelection|null
|
|
* }
|
|
*/
|
|
public function compose(
|
|
string $buildDir,
|
|
string $targetName,
|
|
bool $registerProject,
|
|
?CompilationStatistics $statistics = null,
|
|
): array
|
|
{
|
|
$runtime = ComposerNativePackage::load('swoole/php-nano');
|
|
$phpx = ComposerNativePackage::load('swoole/phpx');
|
|
if ($runtime->abi !== $phpx->abi) {
|
|
throw new RuntimeException(
|
|
"Native ABI mismatch: swoole/php-nano={$runtime->abi}, swoole/phpx={$phpx->abi}"
|
|
);
|
|
}
|
|
|
|
$packagesByName = [
|
|
$runtime->name => $runtime,
|
|
$phpx->name => $phpx,
|
|
];
|
|
foreach (ComposerNativePackage::discover() as $package) {
|
|
$packagesByName[$package->name] = $package;
|
|
}
|
|
$packages = array_values($packagesByName);
|
|
|
|
$selection = $statistics === null
|
|
? null
|
|
: (new NanoExtensionSelector())->select($statistics);
|
|
$activeComponents = $selection === null
|
|
? null
|
|
: $this->selectComponents($packages, $selection);
|
|
|
|
$packageSources = [];
|
|
$includeDirs = [];
|
|
$defines = [];
|
|
foreach ($packages as $package) {
|
|
if ($package->abi !== $runtime->abi) {
|
|
throw new RuntimeException(
|
|
"Native ABI mismatch: {$package->name}={$package->abi}, "
|
|
. "swoole/php-nano={$runtime->abi}"
|
|
);
|
|
}
|
|
if ($activeComponents === null || $package->components === []) {
|
|
array_push($packageSources, ...$package->sources);
|
|
} else {
|
|
$ownedSources = [];
|
|
foreach ($package->components as $component) {
|
|
foreach ($component->sources as $source) {
|
|
$ownedSources[$source] = true;
|
|
}
|
|
if (isset($activeComponents[$package->name][$component->name])) {
|
|
array_push($packageSources, ...$component->sources);
|
|
array_push($defines, ...$component->defines);
|
|
}
|
|
}
|
|
foreach ($package->sources as $source) {
|
|
if (!isset($ownedSources[$source])) {
|
|
$packageSources[] = $source;
|
|
}
|
|
}
|
|
}
|
|
array_push($includeDirs, ...$package->includeDirs);
|
|
}
|
|
if ($activeComponents !== null) {
|
|
$defines[] = 'PHP_NANO_SELECTIVE=1';
|
|
}
|
|
|
|
return [
|
|
'packages' => $packages,
|
|
'packageSources' => array_values(array_unique($packageSources)),
|
|
'includeDirs' => array_values(array_unique($includeDirs)),
|
|
'defines' => array_values(array_unique($defines)),
|
|
'selection' => $selection,
|
|
'registry' => $this->writeExtensionRegistry(
|
|
$buildDir,
|
|
$targetName,
|
|
$packages,
|
|
$registerProject,
|
|
$activeComponents,
|
|
),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param list<ComposerNativePackage> $packages
|
|
* @return array<string, array<string, true>>
|
|
*/
|
|
private function selectComponents(
|
|
array $packages,
|
|
NanoExtensionSelection $selection,
|
|
): array {
|
|
$extensionSet = array_fill_keys($selection->extensions, true);
|
|
$featureSet = array_fill_keys($selection->features, true);
|
|
$componentsByName = [];
|
|
foreach ($packages as $package) {
|
|
foreach ($package->components as $component) {
|
|
$componentsByName[$component->name][] = [$package, $component];
|
|
}
|
|
}
|
|
|
|
$active = [];
|
|
$pending = [];
|
|
foreach ($packages as $package) {
|
|
foreach ($package->components as $component) {
|
|
if (isset($extensionSet[$component->extension])
|
|
|| isset($featureSet[$component->name])) {
|
|
$active[$package->name][$component->name] = true;
|
|
$pending[] = $component;
|
|
}
|
|
}
|
|
}
|
|
while (($component = array_pop($pending)) !== null) {
|
|
foreach ($component->requires as $requirement) {
|
|
if (!isset($componentsByName[$requirement])) {
|
|
throw new RuntimeException(
|
|
"Nano component `{$component->name}` requires unknown component `{$requirement}`"
|
|
);
|
|
}
|
|
foreach ($componentsByName[$requirement] ?? [] as [$package, $required]) {
|
|
if (!isset($active[$package->name][$required->name])) {
|
|
$active[$package->name][$required->name] = true;
|
|
$pending[] = $required;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return $active;
|
|
}
|
|
|
|
/** @param list<ComposerNativePackage> $packages */
|
|
private function writeExtensionRegistry(
|
|
string $buildDir,
|
|
string $targetName,
|
|
array $packages,
|
|
bool $registerProject,
|
|
?array $activeComponents,
|
|
): string {
|
|
if (!is_dir($buildDir) && !mkdir($buildDir, 0777, true) && !is_dir($buildDir)) {
|
|
throw new RuntimeException("Unable to create Nano build directory: {$buildDir}");
|
|
}
|
|
|
|
$extensions = array_values(array_filter(
|
|
$packages,
|
|
static fn(ComposerNativePackage $package): bool => $package->kind === 'extension',
|
|
));
|
|
$path = $buildDir . DIRECTORY_SEPARATOR . 'composer_extensions.cpp';
|
|
$declarations = [];
|
|
$entries = [];
|
|
foreach ($packages as $package) {
|
|
foreach ($package->components as $component) {
|
|
if (($activeComponents !== null
|
|
&& !isset($activeComponents[$package->name][$component->name]))
|
|
|| $component->moduleEntry === null) {
|
|
continue;
|
|
}
|
|
$moduleEntry = $component->moduleEntry;
|
|
$declarations[$moduleEntry] = "extern \"C\" zend_module_entry {$moduleEntry};";
|
|
$entries[$moduleEntry] = " &{$moduleEntry},";
|
|
}
|
|
}
|
|
foreach ($extensions as $extension) {
|
|
$moduleEntry = $extension->extensionModuleEntry;
|
|
$declarations[$moduleEntry] = "extern \"C\" zend_module_entry {$moduleEntry};";
|
|
$entries[$moduleEntry] = " &{$moduleEntry},";
|
|
}
|
|
if ($registerProject) {
|
|
$namespace = 'typephp_project_' . $targetName;
|
|
$moduleEntry = 'typephp_' . $targetName . '_module_entry';
|
|
$declarations[$moduleEntry] = "namespace {$namespace} { extern zend_module_entry {$moduleEntry}; }";
|
|
$entries[$moduleEntry] = " &{$namespace}::{$moduleEntry},";
|
|
}
|
|
|
|
$count = count($entries);
|
|
$storageSize = max(1, $count);
|
|
$contents = "#include <php_nano_extension.h>\n\n"
|
|
. implode("\n", array_values($declarations)) . "\n\n"
|
|
. "extern \"C\" zend_result php_nano_startup_composer_extensions() {\n"
|
|
. " static zend_module_entry *extensions[{$storageSize}] = {\n"
|
|
. implode("\n", array_values($entries)) . "\n"
|
|
. " };\n"
|
|
. " return php_nano_startup_extensions(extensions, {$count});\n"
|
|
. "}\n\n"
|
|
. "extern \"C\" void php_nano_shutdown_composer_extensions() {\n"
|
|
. " php_nano_shutdown_extensions();\n"
|
|
. "}\n";
|
|
if (!is_file($path) || file_get_contents($path) !== $contents) {
|
|
if (file_put_contents($path, $contents) === false) {
|
|
throw new RuntimeException("Unable to write Nano extension registry: {$path}");
|
|
}
|
|
}
|
|
return $path;
|
|
}
|
|
}
|
|
|