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.
 
 

103 lines
3.5 KiB

<?php
/**
* This file is part of TypePHP(AOT).
*
* @link https://www.swoole.com/aot/
* @contact service@swoole.com
*/
namespace TypePhp\Build;
final class EmbeddedArchiveBuilder
{
/**
* @param list<string> $files
* @param array<string, string> $opcodeBlobs source file => blob file
* @param array<string, string> $opcodeKeys source file => runtime lookup key
*/
public function build(
string $archivePath,
array $files,
array $opcodeBlobs,
array $opcodeKeys = [],
): EmbeddedArchive {
$this->ensureDirectory(dirname($archivePath));
$temporaryPath = $archivePath . '.tmp';
$stream = fopen($temporaryPath, 'wb');
if ($stream === false) {
throw new \RuntimeException("Cannot create embedded file archive: {$temporaryPath}");
}
$fileIndex = [];
$opcodeIndex = [];
try {
foreach ($files as $file) {
$fileIndex[$file] = $this->append($stream, $file, 'embedded file');
}
foreach ($opcodeBlobs as $sourceFile => $blobFile) {
$key = $opcodeKeys[$sourceFile] ?? $sourceFile;
$opcodeIndex[$key] = $this->append($stream, $blobFile, 'opcode blob');
}
} catch (\Throwable $error) {
fclose($stream);
@unlink($temporaryPath);
throw $error;
}
if (!fclose($stream)) {
@unlink($temporaryPath);
throw new \RuntimeException("Cannot close embedded file archive: {$temporaryPath}");
}
$temporaryHash = hash_file('sha256', $temporaryPath);
if (!is_string($temporaryHash)) {
@unlink($temporaryPath);
throw new \RuntimeException("Cannot hash embedded file archive: {$temporaryPath}");
}
$currentHash = is_file($archivePath) ? hash_file('sha256', $archivePath) : false;
if ($currentHash === $temporaryHash) {
if (!unlink($temporaryPath)) {
throw new \RuntimeException("Cannot remove unchanged embedded archive: {$temporaryPath}");
}
} else {
try {
AtomicFile::replace($temporaryPath, $archivePath);
} finally {
if (is_file($temporaryPath)) {
@unlink($temporaryPath);
}
}
}
return new EmbeddedArchive($archivePath, $temporaryHash, $fileIndex, $opcodeIndex);
}
/** @return array{int, int} */
private function append(mixed $output, string $inputPath, string $description): array
{
$offset = ftell($output);
if (!is_int($offset)) {
throw new \RuntimeException('Cannot determine embedded archive offset');
}
$input = fopen($inputPath, 'rb');
if ($input === false) {
throw new \RuntimeException("Cannot read {$description}: {$inputPath}");
}
try {
$length = stream_copy_to_stream($input, $output);
} finally {
fclose($input);
}
if (!is_int($length)) {
throw new \RuntimeException("Cannot copy {$description}: {$inputPath}");
}
return [$offset, $length];
}
private function ensureDirectory(string $directory): void
{
if (!is_dir($directory) && !mkdir($directory, 0777, true) && !is_dir($directory)) {
throw new \RuntimeException("Cannot create embedded archive directory: {$directory}");
}
}
}