Skip to content

Commit 4b8250b

Browse files
committed
Improve compilation process
1 parent 697c50a commit 4b8250b

32 files changed

+769
-331
lines changed

src/Action/ActionInterface.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@
88

99
/**
1010
* @template TStatus of \UnitEnum
11+
* @template TKey of mixed = mixed
1112
*/
1213
interface ActionInterface
1314
{
1415
/**
15-
* @return iterable<mixed, TStatus>
16+
* @return iterable<TKey, TStatus>
1617
*/
1718
public function process(Configuration $config): iterable;
1819
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Boson\Component\Compiler\Action;
6+
7+
use Boson\Component\Compiler\Configuration;
8+
use Boson\Component\Compiler\Target\TargetInterface;
9+
10+
/**
11+
* @template-extends TargetAction<ApplyExecutePermissionsStatus>
12+
*/
13+
final readonly class ApplyExecutePermissionsAction extends TargetAction
14+
{
15+
public function __construct(
16+
/**
17+
* @var non-empty-string
18+
*/
19+
private string $targetFilename,
20+
TargetInterface $target,
21+
) {
22+
parent::__construct($target);
23+
}
24+
25+
public function process(Configuration $config): iterable
26+
{
27+
yield $this->target => ApplyExecutePermissionsStatus::ReadyToApply;
28+
29+
$targetPathname = $this->getBuildDirectory($config)
30+
. \DIRECTORY_SEPARATOR
31+
. $this->targetFilename;
32+
33+
$status = @\chmod($targetPathname, 0o755);
34+
35+
if ($status === false) {
36+
throw new \RuntimeException(\sprintf(
37+
'Unable to make %s executable',
38+
$targetPathname,
39+
));
40+
}
41+
42+
yield $this->target => ApplyExecutePermissionsStatus::Applied;
43+
}
44+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Boson\Component\Compiler\Action;
6+
7+
enum ApplyExecutePermissionsStatus
8+
{
9+
case ReadyToApply;
10+
case Applied;
11+
}

src/Action/CompileAction.php

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Boson\Component\Compiler\Action;
6+
7+
use Boson\Component\Compiler\Configuration;
8+
use Boson\Component\Compiler\Target\TargetInterface;
9+
10+
/**
11+
* @template-extends TargetAction<CompileStatus>
12+
*/
13+
final readonly class CompileAction extends TargetAction
14+
{
15+
/**
16+
* @var non-empty-string
17+
*/
18+
private const string DEFAULT_INI_CONFIG = <<<'INI'
19+
ffi.enable=1
20+
opcache.enable=1
21+
opcache.enable_cli=1
22+
INI;
23+
24+
public function __construct(
25+
/**
26+
* @var non-empty-string
27+
*/
28+
private string $sfx,
29+
/**
30+
* @var non-empty-string
31+
*/
32+
private string $targetFilename,
33+
TargetInterface $target,
34+
) {
35+
parent::__construct($target);
36+
}
37+
38+
public function process(Configuration $config): iterable
39+
{
40+
yield $this->target => CompileStatus::ReadyToCompile;
41+
42+
$targetPathname = $this->getBuildDirectory($config)
43+
. \DIRECTORY_SEPARATOR
44+
. $this->targetFilename;
45+
46+
$targetStream = @\fopen($targetPathname, 'wb+');
47+
48+
if ($targetStream === false) {
49+
throw new \RuntimeException(\sprintf(
50+
'Unable to create target binary "%s"',
51+
$targetPathname,
52+
));
53+
}
54+
55+
\flock($targetStream, \LOCK_EX);
56+
57+
yield $this->target => CompileStatus::Progress;
58+
$this->appendSfxArchive($targetStream);
59+
60+
yield $this->target => CompileStatus::Progress;
61+
$this->appendPhpConfig($targetStream, $config);
62+
63+
yield $this->target => CompileStatus::Progress;
64+
$this->appendSource($targetStream, $config);
65+
66+
\flock($targetStream, \LOCK_UN);
67+
\fclose($targetStream);
68+
69+
yield $this->target => CompileStatus::Compiled;
70+
}
71+
72+
/**
73+
* @return non-empty-string
74+
*/
75+
private function getPhpConfigString(Configuration $config): string
76+
{
77+
$ini = self::DEFAULT_INI_CONFIG;
78+
79+
foreach ($config->ini as $key => $value) {
80+
$ini .= "\n$key=" . match ($value) {
81+
false => '0',
82+
true => '1',
83+
default => (string) $value,
84+
};
85+
}
86+
87+
return $ini . "\n";
88+
}
89+
90+
/**
91+
* @param resource $stream
92+
*/
93+
private function appendSource(mixed $stream, Configuration $config): void
94+
{
95+
$sourceStream = @\fopen($config->pharPathname, 'rb');
96+
97+
if ($sourceStream === false) {
98+
throw new \RuntimeException(\sprintf(
99+
'Unable to open application phar file "%s"',
100+
$config->pharPathname,
101+
));
102+
}
103+
104+
\flock($sourceStream, \LOCK_SH);
105+
\stream_copy_to_stream($sourceStream, $stream);
106+
\fclose($sourceStream);
107+
}
108+
109+
/**
110+
* @param resource $stream
111+
*/
112+
private function appendPhpConfig(mixed $stream, Configuration $config): void
113+
{
114+
$ini = $this->getPhpConfigString($config);
115+
116+
\fwrite($stream, "\xfd\xf6\x69\xe6");
117+
\fwrite($stream, \pack('N', \strlen($ini)));
118+
\fwrite($stream, $ini);
119+
}
120+
121+
/**
122+
* @param resource $stream
123+
*/
124+
private function appendSfxArchive(mixed $stream): void
125+
{
126+
$archiveStream = @\fopen($this->sfx, 'rb');
127+
128+
if ($archiveStream === false) {
129+
throw new \RuntimeException(\sprintf(
130+
'Unable to open application SFX file "%s"',
131+
$this->sfx,
132+
));
133+
}
134+
135+
\flock($archiveStream, \LOCK_SH);
136+
\stream_copy_to_stream($archiveStream, $stream);
137+
\fclose($archiveStream);
138+
}
139+
}

src/Action/CopyAction.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Boson\Component\Compiler\Action;
6+
7+
/**
8+
* @template TStatus of \UnitEnum = CopyStatus
9+
*
10+
* @template-extends TargetAction<TStatus>
11+
*/
12+
abstract readonly class CopyAction extends TargetAction
13+
{
14+
/**
15+
* @param non-empty-string $from
16+
* @param non-empty-string $to
17+
*/
18+
protected function copyOrFail(string $from, string $to): void
19+
{
20+
$status = @\copy($from, $to);
21+
22+
if ($status === false) {
23+
throw new \RuntimeException(\sprintf(
24+
'Unable to copy %s to %s',
25+
$from,
26+
$to,
27+
));
28+
}
29+
}
30+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Boson\Component\Compiler\Action;
6+
7+
use Boson\Component\Compiler\Configuration;
8+
9+
final readonly class CopyAllRuntimeBinariesAction extends CopyRuntimeAction
10+
{
11+
public function process(Configuration $config): iterable
12+
{
13+
yield $this->target => CopyStatus::ReadyToCopy;
14+
15+
$output = $this->getBuildDirectory($config);
16+
17+
foreach ($this->getSourceRuntimeBinaries() as $sourceBinary) {
18+
$targetBinary = $output
19+
. \DIRECTORY_SEPARATOR
20+
. \basename($sourceBinary);
21+
22+
$this->copyOrFail($sourceBinary, $targetBinary);
23+
24+
yield $this->target => CopyStatus::Progress;
25+
}
26+
27+
yield $this->target => CopyStatus::Completed;
28+
}
29+
30+
/**
31+
* @return iterable<array-key, non-empty-string>
32+
*/
33+
private function getSourceRuntimeBinaries(): iterable
34+
{
35+
$binaries = new \DirectoryIterator($this->getSourceRuntimeBinDirectory());
36+
37+
/** @var \SplFileInfo $binary */
38+
foreach ($binaries as $binary) {
39+
if ($binary->isDot() || $binary->isDir()) {
40+
continue;
41+
}
42+
43+
/** @var non-empty-string */
44+
yield $binary->getPathname();
45+
}
46+
}
47+
}

src/Action/CopyPharAction.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Boson\Component\Compiler\Action;
6+
7+
use Boson\Component\Compiler\Configuration;
8+
9+
/**
10+
* @template-extends CopyAction<CopyStatus>
11+
*/
12+
final readonly class CopyPharAction extends CopyAction
13+
{
14+
public function process(Configuration $config): iterable
15+
{
16+
yield $this->target => CopyStatus::ReadyToCopy;
17+
18+
$targetPharPathname = $this->getBuildDirectory($config)
19+
. \DIRECTORY_SEPARATOR
20+
. \basename($config->pharPathname);
21+
22+
$this->copyOrFail($config->pharPathname, $targetPharPathname);
23+
24+
yield $this->target => CopyStatus::Completed;
25+
}
26+
}

src/Action/CopyRuntimeAction.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Boson\Component\Compiler\Action;
6+
7+
use Composer\InstalledVersions;
8+
9+
/**
10+
* @template TStatus of \UnitEnum = CopyStatus
11+
*
12+
* @template-extends CopyAction<TStatus>
13+
*/
14+
abstract readonly class CopyRuntimeAction extends CopyAction
15+
{
16+
/**
17+
* @var non-empty-string
18+
*/
19+
private const string RUNTIME_PACKAGE_NAME = 'boson-php/saucer';
20+
21+
/**
22+
* @var non-empty-string
23+
*/
24+
private const string RUNTIME_BIN_DIRECTORY = 'bin';
25+
26+
/**
27+
* @return non-empty-string
28+
*/
29+
protected function getSourceRuntimeBinDirectory(): string
30+
{
31+
return InstalledVersions::getInstallPath(self::RUNTIME_PACKAGE_NAME)
32+
. \DIRECTORY_SEPARATOR
33+
. self::RUNTIME_BIN_DIRECTORY;
34+
}
35+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Boson\Component\Compiler\Action;
6+
7+
use Boson\Component\Compiler\Configuration;
8+
use Boson\Component\Compiler\Target\TargetInterface;
9+
10+
final readonly class CopyRuntimeBinaryAction extends CopyRuntimeAction
11+
{
12+
public function __construct(
13+
/**
14+
* @var non-empty-string
15+
*/
16+
private string $binary,
17+
TargetInterface $target,
18+
) {
19+
parent::__construct($target);
20+
}
21+
22+
public function process(Configuration $config): iterable
23+
{
24+
yield $this->target => CopyStatus::ReadyToCopy;
25+
26+
$runtimeSourcePathname = $this->getSourceRuntimeBinDirectory()
27+
. \DIRECTORY_SEPARATOR
28+
. $this->binary;
29+
30+
$runtimeTargetPathname = $this->getBuildDirectory($config)
31+
. \DIRECTORY_SEPARATOR
32+
. $this->binary;
33+
34+
$this->copyOrFail($runtimeSourcePathname, $runtimeTargetPathname);
35+
36+
yield $this->target => CopyStatus::Completed;
37+
}
38+
}

0 commit comments

Comments
 (0)