- Replace direct php::concat calls with flattened concatenation using parseFlattenedConcat - Add prepareConcatOperand method to handle proper string conversion for different types - Implement unified foreach iterator using php::ForeachIterator for both arrays and objects - Remove separate parseForeachObject method and consolidate iteration logic - Add support for precompiled headers in GCC-like backends - Implement PrecompiledHeaderManager for PCH preparation and caching - Optimize build queue with SourceCompileQueue using largest-first sorting strategy - Improve progress percentage calculation during compilation - Add new test case for unified foreach iteration over different iterable typespull/18/head
parent
cf8bfdef06
commit
aae4f2a07d
13 changed files with 369 additions and 123 deletions
@ -0,0 +1,104 @@ |
||||
<?php |
||||
|
||||
namespace TypePhp\Build; |
||||
|
||||
use TypePhp\Backend\CompilerBackend; |
||||
|
||||
final readonly class PrecompiledHeaderManager |
||||
{ |
||||
public function __construct( |
||||
private CompilerBackend $backend, |
||||
private NativeBuilder $builder, |
||||
) { |
||||
} |
||||
|
||||
/** |
||||
* @param list<string> $headers |
||||
* @param list<string> $dependencyDirectories |
||||
* @return array{header: string, artifact: string, cached: bool, command: string} |
||||
*/ |
||||
public function prepare( |
||||
array $headers, |
||||
array $dependencyDirectories, |
||||
string $cacheDirectory, |
||||
CompileOptions $options, |
||||
): array { |
||||
if (!$this->backend->supportsPrecompiledHeaders()) { |
||||
throw new \LogicException($this->backend->getName() . ' does not support precompiled headers'); |
||||
} |
||||
|
||||
$fingerprint = $this->buildFingerprint($headers, $dependencyDirectories, $options); |
||||
$directory = rtrim($cacheDirectory, '/\\') . DIRECTORY_SEPARATOR . $fingerprint; |
||||
if (!is_dir($directory) && !mkdir($directory, 0777, true) && !is_dir($directory)) { |
||||
throw new \RuntimeException('Cannot create precompiled header cache directory: ' . $directory); |
||||
} |
||||
|
||||
$headerFile = $directory . DIRECTORY_SEPARATOR . 'typephp_pch.hpp'; |
||||
$artifact = $this->backend->getPrecompiledHeaderArtifact($headerFile); |
||||
$source = "#pragma once\n"; |
||||
foreach ($headers as $header) { |
||||
$source .= '#include <' . $header . ">\n"; |
||||
} |
||||
if (!is_file($headerFile) || file_get_contents($headerFile) !== $source) { |
||||
if (file_put_contents($headerFile, $source) === false) { |
||||
throw new \RuntimeException('Cannot write precompiled header: ' . $headerFile); |
||||
} |
||||
} |
||||
|
||||
if (is_file($artifact)) { |
||||
return ['header' => $headerFile, 'artifact' => $artifact, 'cached' => true, 'command' => '']; |
||||
} |
||||
|
||||
$result = $this->builder->compile($headerFile, $artifact, $options, 'c++-header', true); |
||||
if ($result['status'] !== 0 || !is_file($artifact)) { |
||||
$message = implode(PHP_EOL, $result['output']); |
||||
throw new \RuntimeException('Failed to build PHPX precompiled header' . ($message === '' ? '' : ': ' . $message)); |
||||
} |
||||
|
||||
return ['header' => $headerFile, 'artifact' => $artifact, 'cached' => false, 'command' => $result['command']]; |
||||
} |
||||
|
||||
/** @param list<string> $headers @param list<string> $dependencyDirectories */ |
||||
private function buildFingerprint(array $headers, array $dependencyDirectories, CompileOptions $options): string |
||||
{ |
||||
$compilerVersion = []; |
||||
exec(escapeshellcmd($this->backend->getCompilerCommand()) . ' --version 2>&1', $compilerVersion); |
||||
$context = hash_init('sha256'); |
||||
hash_update($context, $this->backend::class . "\0" . implode("\n", $compilerVersion) . "\0"); |
||||
$optionValues = $options->toArray(); |
||||
// prof_output only affects code when profiling is enabled. Keeping a |
||||
// target-specific inactive filename here would defeat PCH reuse across |
||||
// projects with otherwise identical native build configurations. |
||||
if (empty($optionValues['enable_profiler'])) { |
||||
unset($optionValues['prof_output']); |
||||
} |
||||
unset($optionValues['precompiled_header']); |
||||
hash_update($context, serialize($optionValues) . "\0" . implode("\0", $headers)); |
||||
|
||||
$files = []; |
||||
foreach ($dependencyDirectories as $directory) { |
||||
if (!is_dir($directory)) { |
||||
continue; |
||||
} |
||||
$iterator = new \RecursiveIteratorIterator( |
||||
new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS) |
||||
); |
||||
foreach ($iterator as $file) { |
||||
if (!$file->isFile() || preg_match('/\.(?:h|hh|hpp|hxx|inc)$/i', $file->getFilename()) !== 1) { |
||||
continue; |
||||
} |
||||
$files[] = $file->getPathname(); |
||||
} |
||||
} |
||||
sort($files, SORT_STRING); |
||||
foreach ($files as $file) { |
||||
hash_update($context, $file . "\0"); |
||||
if (!hash_update_file($context, $file)) { |
||||
throw new \RuntimeException('Cannot fingerprint precompiled-header dependency: ' . $file); |
||||
} |
||||
hash_update($context, "\0"); |
||||
} |
||||
|
||||
return substr(hash_final($context), 0, 24); |
||||
} |
||||
} |
||||
@ -0,0 +1,22 @@ |
||||
<?php |
||||
|
||||
namespace TypePhp\Build; |
||||
|
||||
final class SourceCompileQueue |
||||
{ |
||||
/** @param list<string> $sources @return list<string> */ |
||||
public static function largestFirst(array $sources): array |
||||
{ |
||||
$entries = []; |
||||
foreach (array_values($sources) as $index => $source) { |
||||
$size = @filesize($source); |
||||
$entries[] = ['source' => $source, 'size' => $size === false ? -1 : $size, 'index' => $index]; |
||||
} |
||||
|
||||
usort($entries, static function (array $left, array $right): int { |
||||
return ($right['size'] <=> $left['size']) ?: ($left['index'] <=> $right['index']); |
||||
}); |
||||
|
||||
return array_column($entries, 'source'); |
||||
} |
||||
} |
||||
@ -0,0 +1,46 @@ |
||||
--TEST-- |
||||
foreach uses one loop body for dynamically typed arrays and objects |
||||
--FILE-- |
||||
<?php |
||||
|
||||
function flatten($groups): array |
||||
{ |
||||
$result = []; |
||||
foreach ($groups as $groupName => $group) { |
||||
foreach ($group as $itemName => $value) { |
||||
$result[] = "$groupName:$itemName=$value"; |
||||
} |
||||
} |
||||
return $result; |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
var_dump(flatten([ |
||||
'array' => ['first' => 1, 'second' => 2], |
||||
])); |
||||
|
||||
var_dump(flatten(new ArrayIterator([ |
||||
'iterator' => new ArrayIterator(['third' => 3]), |
||||
]))); |
||||
|
||||
$plain = new stdClass(); |
||||
$plain->object = (object) ['fourth' => 4]; |
||||
var_dump(flatten($plain)); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
array(2) { |
||||
[0]=> |
||||
string(13) "array:first=1" |
||||
[1]=> |
||||
string(14) "array:second=2" |
||||
} |
||||
array(1) { |
||||
[0]=> |
||||
string(16) "iterator:third=3" |
||||
} |
||||
array(1) { |
||||
[0]=> |
||||
string(15) "object:fourth=4" |
||||
} |
||||
Loading…
Reference in new issue