Passed
Push — master ( d13433...bd569e )
by Théo
02:47
created

Compile::commit()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 23
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 15
dl 0
loc 23
rs 9.7666
c 0
b 0
f 0
cc 3
nc 2
nop 3
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the box project.
7
 *
8
 * (c) Kevin Herrera <[email protected]>
9
 *     Théo Fidry <[email protected]>
10
 *
11
 * This source file is subject to the MIT license that is bundled
12
 * with this source code in the file LICENSE.
13
 */
14
15
namespace KevinGH\Box\Console\Command;
16
17
use Amp\MultiReasonException;
18
use function array_map;
19
use function array_search;
20
use function array_shift;
21
use Assert\Assertion;
22
use function count;
23
use function decoct;
24
use function explode;
25
use function file_exists;
26
use function filesize;
27
use function get_class;
28
use Humbug\PhpScoper\Whitelist;
29
use function implode;
30
use function is_string;
31
use KevinGH\Box\Box;
32
use const KevinGH\Box\BOX_ALLOW_XDEBUG;
33
use function KevinGH\Box\bump_open_file_descriptor_limit;
34
use function KevinGH\Box\check_php_settings;
35
use KevinGH\Box\Compactor\Compactor;
36
use KevinGH\Box\Composer\ComposerConfiguration;
37
use KevinGH\Box\Composer\ComposerOrchestrator;
38
use KevinGH\Box\Configuration\Configuration;
39
use KevinGH\Box\Console\IO\IO;
40
use KevinGH\Box\Console\Logger\CompilerLogger;
41
use KevinGH\Box\Console\MessageRenderer;
42
use function KevinGH\Box\disable_parallel_processing;
43
use function KevinGH\Box\FileSystem\chmod;
44
use function KevinGH\Box\FileSystem\dump_file;
45
use function KevinGH\Box\FileSystem\make_path_relative;
46
use function KevinGH\Box\FileSystem\remove;
47
use function KevinGH\Box\FileSystem\rename;
48
use function KevinGH\Box\format_size;
49
use function KevinGH\Box\format_time;
50
use function KevinGH\Box\get_phar_compression_algorithms;
51
use KevinGH\Box\MapFile;
52
use KevinGH\Box\RequirementChecker\RequirementsDumper;
53
use KevinGH\Box\StubGenerator;
54
use function memory_get_peak_usage;
55
use function memory_get_usage;
56
use function microtime;
57
use const PHP_EOL;
58
use function putenv;
59
use RuntimeException;
60
use function sprintf;
61
use stdClass;
62
use Symfony\Component\Console\Input\InputOption;
63
use Symfony\Component\Console\Input\StringInput;
64
use Symfony\Component\Console\Output\OutputInterface;
65
use Symfony\Component\Console\Question\Question;
66
use function var_export;
67
68
/**
69
 * @final
70
 * @private
71
 */
72
class Compile extends ConfigurableBaseCommand
73
{
74
    use ChangeableWorkingDirectory;
75
76
    private const HELP = <<<'HELP'
77
The <info>%command.name%</info> command will compile code in a new PHAR based on a variety of settings.
78
<comment>
79
  This command relies on a configuration file for loading
80
  PHAR packaging settings. If a configuration file is not
81
  specified through the <info>--config|-c</info> option, one of
82
  the following files will be used (in order): <info>box.json</info>,
83
  <info>box.json.dist</info>
84
</comment>
85
The configuration file is actually a JSON object saved to a file. For more
86
information check the documentation online:
87
<comment>
88
  https://github.com/humbug/box
89
</comment>
90
HELP;
91
92
    private const DEBUG_OPTION = 'debug';
93
    private const NO_PARALLEL_PROCESSING_OPTION = 'no-parallel';
94
    private const NO_RESTART_OPTION = 'no-restart';
95
    private const DEV_OPTION = 'dev';
96
    private const NO_CONFIG_OPTION = 'no-config';
97
    private const WITH_DOCKER_OPTION = 'with-docker';
98
99
    private const DEBUG_DIR = '.box_dump';
100
101
    /**
102
     * {@inheritdoc}
103
     */
104
    protected function configure(): void
105
    {
106
        parent::configure();
107
108
        $this->setName('compile');
109
        $this->setDescription('🔨  Compiles an application into a PHAR');
110
        $this->setHelp(self::HELP);
111
112
        $this->addOption(
113
            self::DEBUG_OPTION,
114
            null,
115
            InputOption::VALUE_NONE,
116
            'Dump the files added to the PHAR in a `'.self::DEBUG_DIR.'` directory'
117
        );
118
        $this->addOption(
119
            self::NO_PARALLEL_PROCESSING_OPTION,
120
            null,
121
            InputOption::VALUE_NONE,
122
            'Disable the parallel processing'
123
        );
124
        $this->addOption(
125
            self::NO_RESTART_OPTION,
126
            null,
127
            InputOption::VALUE_NONE,
128
            'Do not restart the PHP process. Box restarts the process by default to disable xdebug and set `phar.readonly=0`'
129
        );
130
        $this->addOption(
131
            self::DEV_OPTION,
132
            null,
133
            InputOption::VALUE_NONE,
134
            'Skips the compression step'
135
        );
136
        $this->addOption(
137
            self::NO_CONFIG_OPTION,
138
            null,
139
            InputOption::VALUE_NONE,
140
            'Ignore the config file even when one is specified with the --config option'
141
        );
142
        $this->addOption(
143
            self::WITH_DOCKER_OPTION,
144
            null,
145
            InputOption::VALUE_NONE,
146
            'Generates a Dockerfile'
147
        );
148
149
        $this->configureWorkingDirOption();
150
    }
151
152
    /**
153
     * {@inheritdoc}
154
     */
155
    protected function executeCommand(IO $io): int
156
    {
157
        $input = $io->getInput();
158
159
        if ($input->getOption(self::NO_RESTART_OPTION)) {
160
            putenv(BOX_ALLOW_XDEBUG.'=1');
161
        }
162
163
        if ($debug = $input->getOption(self::DEBUG_OPTION)) {
164
            $io->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
165
        }
166
167
        check_php_settings($io);
168
169
        if ($input->getOption(self::NO_PARALLEL_PROCESSING_OPTION)) {
170
            disable_parallel_processing();
171
            $io->writeln('<info>[debug] Disabled parallel processing</info>', OutputInterface::VERBOSITY_DEBUG);
172
        }
173
174
        $this->changeWorkingDirectory($input);
175
176
        $io->writeln($this->getApplication()->getHelp());
177
        $io->newLine();
178
179
        $config = $input->getOption(self::NO_CONFIG_OPTION)
180
            ? Configuration::create(null, new stdClass())
181
            : $this->getConfig($io, true)
182
        ;
183
        $path = $config->getOutputPath();
184
185
        $logger = new CompilerLogger($io);
186
187
        $startTime = microtime(true);
188
189
        $logger->logStartBuilding($path);
190
191
        $this->removeExistingArtifacts($config, $logger, $debug);
192
193
        $box = $this->createPhar($config, $logger, $io, $debug);
194
195
        $this->correctPermissions($path, $config, $logger);
196
197
        $this->logEndBuilding($config, $logger, $io, $box, $path, $startTime);
198
199
        if ($input->getOption(self::WITH_DOCKER_OPTION)) {
200
            $this->generateDockerFile($io);
201
        }
202
203
        return 0;
204
    }
205
206
    private function createPhar(
207
        Configuration $config,
208
        CompilerLogger $logger,
209
        IO $io,
210
        bool $debug
211
    ): Box {
212
        $box = Box::create($config->getTmpOutputPath());
213
214
        $box->startBuffering();
215
216
        $this->registerReplacementValues($config, $box, $logger);
217
        $this->registerCompactors($config, $box, $logger);
218
        $this->registerFileMapping($config, $box, $logger);
219
220
        // Registering the main script _before_ adding the rest if of the files is _very_ important. The temporary
221
        // file used for debugging purposes and the Composer dump autoloading will not work correctly otherwise.
222
        $main = $this->registerMainScript($config, $box, $logger);
223
224
        $check = $this->registerRequirementsChecker($config, $box, $logger);
225
226
        $this->addFiles($config, $box, $logger, $io);
227
228
        $this->registerStub($config, $box, $main, $check, $logger);
229
        $this->configureMetadata($config, $box, $logger);
230
231
        $this->commit($box, $config, $logger);
232
233
        $this->checkComposerFiles($box, $config, $logger);
234
235
        if ($debug) {
236
            $box->getPhar()->extractTo(self::DEBUG_DIR, null, true);
237
        }
238
239
        $this->configureCompressionAlgorithm(
240
            $config,
241
            $box,
242
            $io->getInput()->getOption(self::DEV_OPTION),
243
            $io,
244
            $logger
245
        );
246
247
        $this->signPhar($config, $box, $config->getTmpOutputPath(), $io, $logger);
248
249
        if ($config->getTmpOutputPath() !== $config->getOutputPath()) {
250
            rename($config->getTmpOutputPath(), $config->getOutputPath());
251
        }
252
253
        return $box;
254
    }
255
256
    private function removeExistingArtifacts(Configuration $config, CompilerLogger $logger, bool $debug): void
257
    {
258
        $path = $config->getOutputPath();
259
260
        if ($debug) {
261
            remove(self::DEBUG_DIR);
262
263
            dump_file(
264
                self::DEBUG_DIR.'/.box_configuration',
265
                ConfigurationExporter::export($config)
266
            );
267
        }
268
269
        if (false === file_exists($path)) {
270
            return;
271
        }
272
273
        $logger->log(
274
            CompilerLogger::QUESTION_MARK_PREFIX,
275
            sprintf(
276
                'Removing the existing PHAR "%s"',
277
                $path
278
            )
279
        );
280
281
        remove($path);
282
    }
283
284
    private function registerReplacementValues(Configuration $config, Box $box, CompilerLogger $logger): void
285
    {
286
        $values = $config->getReplacements();
287
288
        if ([] === $values) {
289
            return;
290
        }
291
292
        $logger->log(
293
            CompilerLogger::QUESTION_MARK_PREFIX,
294
            'Setting replacement values'
295
        );
296
297
        foreach ($values as $key => $value) {
298
            $logger->log(
299
                CompilerLogger::PLUS_PREFIX,
300
                sprintf(
301
                    '%s: %s',
302
                    $key,
303
                    $value
304
                )
305
            );
306
        }
307
308
        $box->registerPlaceholders($values);
309
    }
310
311
    private function registerCompactors(Configuration $config, Box $box, CompilerLogger $logger): void
312
    {
313
        $compactors = $config->getCompactors();
314
315
        if (0 === count($compactors)) {
316
            $logger->log(
317
                CompilerLogger::QUESTION_MARK_PREFIX,
318
                'No compactor to register'
319
            );
320
321
            return;
322
        }
323
324
        $logger->log(
325
            CompilerLogger::QUESTION_MARK_PREFIX,
326
            'Registering compactors'
327
        );
328
329
        $logCompactors = static function (Compactor $compactor) use ($logger): void {
330
            $compactorClassParts = explode('\\', get_class($compactor));
331
332
            if (0 === strpos($compactorClassParts[0], '_HumbugBox')) {
333
                // Keep the non prefixed class name for the user
334
                array_shift($compactorClassParts);
335
            }
336
337
            $logger->log(
338
                CompilerLogger::PLUS_PREFIX,
339
                implode('\\', $compactorClassParts)
340
            );
341
        };
342
343
        array_map($logCompactors, $compactors->toArray());
344
345
        $box->registerCompactors($compactors);
346
    }
347
348
    private function registerFileMapping(Configuration $config, Box $box, CompilerLogger $logger): void
349
    {
350
        $fileMapper = $config->getFileMapper();
351
352
        $this->logMap($fileMapper, $logger);
353
354
        $box->registerFileMapping($fileMapper);
355
    }
356
357
    private function addFiles(Configuration $config, Box $box, CompilerLogger $logger, IO $io): void
358
    {
359
        $logger->log(CompilerLogger::QUESTION_MARK_PREFIX, 'Adding binary files');
360
361
        $count = count($config->getBinaryFiles());
362
363
        $box->addFiles($config->getBinaryFiles(), true);
364
365
        $logger->log(
366
            CompilerLogger::CHEVRON_PREFIX,
367
            0 === $count
368
                ? 'No file found'
369
                : sprintf('%d file(s)', $count)
370
        );
371
372
        $logger->log(
373
            CompilerLogger::QUESTION_MARK_PREFIX,
374
            sprintf(
375
                'Auto-discover files? %s',
376
                $config->hasAutodiscoveredFiles() ? 'Yes' : 'No'
377
            )
378
        );
379
        $logger->log(
380
            CompilerLogger::QUESTION_MARK_PREFIX,
381
            sprintf(
382
                'Exclude dev files? %s',
383
                $config->excludeDevFiles() ? 'Yes' : 'No'
384
            )
385
        );
386
        $logger->log(CompilerLogger::QUESTION_MARK_PREFIX, 'Adding files');
387
388
        $count = count($config->getFiles());
389
390
        try {
391
            $box->addFiles($config->getFiles(), false);
392
        } catch (MultiReasonException $exception) {
393
            // This exception is handled a different way to give me meaningful feedback to the user
394
            foreach ($exception->getReasons() as $reason) {
395
                $io->error($reason);
396
            }
397
398
            throw $exception;
399
        }
400
401
        $logger->log(
402
            CompilerLogger::CHEVRON_PREFIX,
403
            0 === $count
404
                ? 'No file found'
405
                : sprintf('%d file(s)', $count)
406
        );
407
    }
408
409
    private function registerMainScript(Configuration $config, Box $box, CompilerLogger $logger): ?string
410
    {
411
        if (false === $config->hasMainScript()) {
412
            $logger->log(
413
                CompilerLogger::QUESTION_MARK_PREFIX,
414
                'No main script path configured'
415
            );
416
417
            return null;
418
        }
419
420
        $main = $config->getMainScriptPath();
421
422
        $logger->log(
423
            CompilerLogger::QUESTION_MARK_PREFIX,
424
            sprintf(
425
                'Adding main file: %s',
426
                $main
427
            )
428
        );
429
430
        $localMain = $box->addFile(
431
            $main,
432
            $config->getMainScriptContents()
433
        );
434
435
        $relativeMain = make_path_relative($main, $config->getBasePath());
436
437
        if ($localMain !== $relativeMain) {
438
            $logger->log(
439
                CompilerLogger::CHEVRON_PREFIX,
440
                $localMain
441
            );
442
        }
443
444
        return $localMain;
445
    }
446
447
    private function registerRequirementsChecker(Configuration $config, Box $box, CompilerLogger $logger): bool
448
    {
449
        if (false === $config->checkRequirements()) {
450
            $logger->log(
451
                CompilerLogger::QUESTION_MARK_PREFIX,
452
                'Skip requirements checker'
453
            );
454
455
            return false;
456
        }
457
458
        $logger->log(
459
            CompilerLogger::QUESTION_MARK_PREFIX,
460
            'Adding requirements checker'
461
        );
462
463
        $checkFiles = RequirementsDumper::dump(
464
            $config->getDecodedComposerJsonContents() ?? [],
465
            $config->getDecodedComposerLockContents() ?? [],
466
            $config->getCompressionAlgorithm()
467
        );
468
469
        foreach ($checkFiles as $fileWithContents) {
470
            [$file, $contents] = $fileWithContents;
471
472
            $box->addFile('.box/'.$file, $contents, true);
473
        }
474
475
        return true;
476
    }
477
478
    private function registerStub(
479
        Configuration $config,
480
        Box $box,
481
        ?string $main,
482
        bool $checkRequirements,
483
        CompilerLogger $logger
484
    ): void {
485
        if ($config->isStubGenerated()) {
486
            $logger->log(
487
                CompilerLogger::QUESTION_MARK_PREFIX,
488
                'Generating new stub'
489
            );
490
491
            $stub = $this->createStub($config, $main, $checkRequirements, $logger);
492
493
            $box->getPhar()->setStub($stub);
494
495
            return;
496
        }
497
498
        if (null !== ($stub = $config->getStubPath())) {
499
            $logger->log(
500
                CompilerLogger::QUESTION_MARK_PREFIX,
501
                sprintf(
502
                    'Using stub file: %s',
503
                    $stub
504
                )
505
            );
506
507
            $box->registerStub($stub);
508
509
            return;
510
        }
511
512
        $aliasWasAdded = $box->getPhar()->setAlias($config->getAlias());
513
514
        Assertion::true(
515
            $aliasWasAdded,
516
            sprintf(
517
                'The alias "%s" is invalid. See Phar::setAlias() documentation for more information.',
518
                $config->getAlias()
519
            )
520
        );
521
522
        $box->getPhar()->setDefaultStub($main);
523
524
        $logger->log(
525
            CompilerLogger::QUESTION_MARK_PREFIX,
526
            'Using default stub'
527
        );
528
    }
529
530
    private function configureMetadata(Configuration $config, Box $box, CompilerLogger $logger): void
531
    {
532
        if (null !== ($metadata = $config->getMetadata())) {
533
            $logger->log(
534
                CompilerLogger::QUESTION_MARK_PREFIX,
535
                'Setting metadata'
536
            );
537
538
            $logger->log(
539
                CompilerLogger::MINUS_PREFIX,
540
                is_string($metadata) ? $metadata : var_export($metadata, true)
541
            );
542
543
            $box->getPhar()->setMetadata($metadata);
544
        }
545
    }
546
547
    private function commit(Box $box, Configuration $config, CompilerLogger $logger): void
548
    {
549
        $message = $config->dumpAutoload()
550
            ? 'Dumping the Composer autoloader'
551
            : 'Skipping dumping the Composer autoloader'
552
        ;
553
554
        $logger->log(CompilerLogger::QUESTION_MARK_PREFIX, $message);
555
556
        $excludeDevFiles = $config->excludeDevFiles();
557
        $io = $logger->getIO();
558
559
        $box->endBuffering(
560
            $config->dumpAutoload()
561
                ? static function (Whitelist $whitelist, string $prefix) use ($excludeDevFiles, $io): void {
562
                    ComposerOrchestrator::dumpAutoload(
563
                        $whitelist,
564
                        $prefix,
565
                        $excludeDevFiles,
566
                        $io
567
                    );
568
                }
569
                : null
570
        );
571
    }
572
573
    private function checkComposerFiles(Box $box, Configuration $config, CompilerLogger $logger): void
574
    {
575
        $message = $config->excludeComposerFiles()
576
            ? 'Removing the Composer dump artefacts'
577
            : 'Keep the Composer dump artefacts'
578
        ;
579
580
        $logger->log(CompilerLogger::QUESTION_MARK_PREFIX, $message);
581
582
        if ($config->excludeComposerFiles()) {
583
            $box->removeComposerArtefacts(
584
                ComposerConfiguration::retrieveVendorDir(
585
                    $config->getDecodedComposerJsonContents() ?? []
586
                )
587
            );
588
        }
589
    }
590
591
    private function configureCompressionAlgorithm(
592
        Configuration $config,
593
        Box $box,
594
        bool $dev,
595
        IO $io,
596
        CompilerLogger $logger
597
    ): void {
598
        if (null === ($algorithm = $config->getCompressionAlgorithm())) {
599
            $logger->log(
600
                CompilerLogger::QUESTION_MARK_PREFIX,
601
                'No compression'
602
            );
603
604
            return;
605
        }
606
607
        if ($dev) {
608
            $logger->log(CompilerLogger::QUESTION_MARK_PREFIX, 'Dev mode detected: skipping the compression');
609
610
            return;
611
        }
612
613
        $logger->log(
614
            CompilerLogger::QUESTION_MARK_PREFIX,
615
            sprintf(
616
                'Compressing with the algorithm "<comment>%s</comment>"',
617
                (string) array_search($algorithm, get_phar_compression_algorithms(), true)
618
            )
619
        );
620
621
        $restoreLimit = bump_open_file_descriptor_limit($box, $io);
622
623
        try {
624
            $extension = $box->compress($algorithm);
625
626
            if (null !== $extension) {
627
                $logger->log(
628
                    CompilerLogger::CHEVRON_PREFIX,
629
                    sprintf(
630
                        '<info>Warning: the extension "%s" will now be required to execute the PHAR</info>',
631
                        $extension
632
                    )
633
                );
634
            }
635
        } catch (RuntimeException $exception) {
636
            $io->error($exception->getMessage());
637
638
            // Continue: the compression failure should not result in completely bailing out the compilation process
639
        } finally {
640
            $restoreLimit();
641
        }
642
    }
643
644
    private function signPhar(
645
        Configuration $config,
646
        Box $box,
647
        string $path,
648
        IO $io,
649
        CompilerLogger $logger
650
    ): void {
651
        // Sign using private key when applicable
652
        remove($path.'.pubkey');
653
654
        $key = $config->getPrivateKeyPath();
655
656
        if (null === $key) {
657
            $box->getPhar()->setSignatureAlgorithm(
658
                $config->getSigningAlgorithm()
659
            );
660
661
            return;
662
        }
663
664
        $logger->log(
665
            CompilerLogger::QUESTION_MARK_PREFIX,
666
            'Signing using a private key'
667
        );
668
669
        $passphrase = $config->getPrivateKeyPassphrase();
670
671
        if ($config->promptForPrivateKey()) {
672
            if (false === $io->isInteractive()) {
673
                throw new RuntimeException(
674
                    sprintf(
675
                        'Accessing to the private key "%s" requires a passphrase but none provided. Either '
676
                        .'provide one or run this command in interactive mode.',
677
                        $key
678
                    )
679
                );
680
            }
681
682
            $question = new Question('Private key passphrase');
683
            $question->setHidden(false);
684
            $question->setHiddenFallback(false);
685
686
            $passphrase = $io->askQuestion($question);
687
688
            $io->writeln('');
689
        }
690
691
        $box->signUsingFile($key, $passphrase);
692
    }
693
694
    private function correctPermissions(string $path, Configuration $config, CompilerLogger $logger): void
695
    {
696
        if (null !== ($chmod = $config->getFileMode())) {
697
            $logger->log(
698
                CompilerLogger::QUESTION_MARK_PREFIX,
699
                sprintf(
700
                    'Setting file permissions to <comment>%s</comment>',
701
                    '0'.decoct($chmod)
702
                )
703
            );
704
705
            chmod($path, $chmod);
706
        }
707
    }
708
709
    private function createStub(
710
        Configuration $config,
711
        ?string $main,
712
        bool $checkRequirements,
713
        CompilerLogger $logger
714
    ): string {
715
        $stub = StubGenerator::create()
716
            ->alias($config->getAlias())
717
            ->index($main)
718
            ->intercept($config->isInterceptFileFuncs())
719
            ->checkRequirements($checkRequirements)
720
        ;
721
722
        if (null !== ($shebang = $config->getShebang())) {
723
            $logger->log(
724
                CompilerLogger::MINUS_PREFIX,
725
                sprintf(
726
                    'Using shebang line: %s',
727
                    $shebang
728
                )
729
            );
730
731
            $stub->shebang($shebang);
732
        } else {
733
            $logger->log(
734
                CompilerLogger::MINUS_PREFIX,
735
                'No shebang line'
736
            );
737
        }
738
739
        if (null !== ($bannerPath = $config->getStubBannerPath())) {
740
            $logger->log(
741
                CompilerLogger::MINUS_PREFIX,
742
                sprintf(
743
                    'Using custom banner from file: %s',
744
                    $bannerPath
745
                )
746
            );
747
748
            $stub->banner($config->getStubBannerContents());
749
        } elseif (null !== ($banner = $config->getStubBannerContents())) {
750
            $logger->log(
751
                CompilerLogger::MINUS_PREFIX,
752
                'Using banner:'
753
            );
754
755
            $bannerLines = explode("\n", $banner);
756
757
            foreach ($bannerLines as $bannerLine) {
758
                $logger->log(
759
                    CompilerLogger::CHEVRON_PREFIX,
760
                    $bannerLine
761
                );
762
            }
763
764
            $stub->banner($banner);
765
        }
766
767
        return $stub->generate();
768
    }
769
770
    private function logMap(MapFile $fileMapper, CompilerLogger $logger): void
771
    {
772
        $map = $fileMapper->getMap();
773
774
        if ([] === $map) {
775
            return;
776
        }
777
778
        $logger->log(
779
            CompilerLogger::QUESTION_MARK_PREFIX,
780
            'Mapping paths'
781
        );
782
783
        foreach ($map as $item) {
784
            foreach ($item as $match => $replace) {
785
                if ('' === $match) {
786
                    $match = '(all)';
787
                    $replace .= '/';
788
                }
789
790
                $logger->log(
791
                    CompilerLogger::MINUS_PREFIX,
792
                    sprintf(
793
                        '%s <info>></info> %s',
794
                        $match,
795
                        $replace
796
                    )
797
                );
798
            }
799
        }
800
    }
801
802
    private function logEndBuilding(
803
        Configuration $config,
804
        CompilerLogger $logger,
805
        IO $io,
806
        Box $box,
807
        string $path,
808
        float $startTime
809
    ): void {
810
        $logger->log(
811
            CompilerLogger::STAR_PREFIX,
812
            'Done.'
813
        );
814
        $io->newLine();
815
816
        MessageRenderer::render($io, $config->getRecommendations(), $config->getWarnings());
817
818
        $io->comment(
819
            sprintf(
820
                'PHAR: %s (%s)',
821
                $box->count() > 1 ? $box->count().' files' : $box->count().' file',
822
                format_size(
823
                    filesize($path)
824
                )
825
            )
826
            .PHP_EOL
827
            .'You can inspect the generated PHAR with the "<comment>info</comment>" command.'
828
        );
829
830
        $io->comment(
831
            sprintf(
832
                '<info>Memory usage: %s (peak: %s), time: %s<info>',
833
                format_size(memory_get_usage()),
834
                format_size(memory_get_peak_usage()),
835
                format_time(microtime(true) - $startTime)
836
            )
837
        );
838
    }
839
840
    private function generateDockerFile(OutputInterface $output): void
841
    {
842
        $generateDockerFileCommand = $this->getApplication()->find('docker');
843
844
        Assertion::isInstanceOf($generateDockerFileCommand, GenerateDockerFile::class);
845
846
        $input = new StringInput('');
847
        $input->setInteractive(false);
848
849
        $generateDockerFileCommand->run($input, $output);
850
    }
851
}
852