Passed
Pull Request — master (#390)
by Théo
02:53
created

Compile::configureMetadata()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 8
dl 0
loc 14
rs 10
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 const DATE_ATOM;
24
use DateTimeImmutable;
25
use DateTimeZone;
26
use function decoct;
27
use function explode;
28
use function file_exists;
29
use function filesize;
30
use function function_exists;
31
use function get_class;
32
use function get_loaded_extensions;
33
use function implode;
34
use function is_string;
35
use KevinGH\Box\Box;
36
use const KevinGH\Box\BOX_ALLOW_XDEBUG;
37
use KevinGH\Box\Compactor;
38
use KevinGH\Box\Composer\ComposerConfiguration;
39
use KevinGH\Box\Configuration;
40
use KevinGH\Box\Console\FileDescriptorManipulator;
41
use KevinGH\Box\Console\Logger\CompileLogger;
42
use KevinGH\Box\Console\MessageRenderer;
43
use KevinGH\Box\Console\OutputConfigurator;
44
use function KevinGH\Box\disable_parallel_processing;
45
use function KevinGH\Box\FileSystem\chmod;
46
use function KevinGH\Box\FileSystem\dump_file;
47
use function KevinGH\Box\FileSystem\make_path_relative;
48
use function KevinGH\Box\FileSystem\remove;
49
use function KevinGH\Box\FileSystem\rename;
50
use function KevinGH\Box\format_size;
51
use function KevinGH\Box\get_box_version;
52
use function KevinGH\Box\get_phar_compression_algorithms;
53
use KevinGH\Box\MapFile;
54
use KevinGH\Box\PhpSettingsHandler;
55
use KevinGH\Box\RequirementChecker\RequirementsDumper;
56
use KevinGH\Box\StubGenerator;
57
use function memory_get_peak_usage;
58
use function memory_get_usage;
59
use function microtime;
60
use const PHP_EOL;
61
use const PHP_VERSION;
62
use function posix_getrlimit;
63
use const POSIX_RLIMIT_INFINITY;
64
use const POSIX_RLIMIT_NOFILE;
65
use function posix_setrlimit;
66
use function putenv;
67
use function round;
68
use RuntimeException;
69
use function sprintf;
70
use stdClass;
71
use function strlen;
72
use function substr;
73
use Symfony\Component\Console\Helper\QuestionHelper;
74
use Symfony\Component\Console\Input\InputInterface;
75
use Symfony\Component\Console\Input\InputOption;
76
use Symfony\Component\Console\Input\StringInput;
77
use Symfony\Component\Console\Logger\ConsoleLogger;
78
use Symfony\Component\Console\Output\OutputInterface;
79
use Symfony\Component\Console\Question\Question;
80
use Symfony\Component\Console\Style\SymfonyStyle;
81
use function var_export;
82
83
/**
84
 * @final
85
 * @private
86
 */
87
class Compile extends ConfigurableCommand
88
{
89
    use ChangeableWorkingDirectory;
90
91
    private const HELP = <<<'HELP'
92
The <info>%command.name%</info> command will compile code in a new PHAR based on a variety of settings.
93
<comment>
94
  This command relies on a configuration file for loading
95
  PHAR packaging settings. If a configuration file is not
96
  specified through the <info>--config|-c</info> option, one of
97
  the following files will be used (in order): <info>box.json</info>,
98
  <info>box.json.dist</info>
99
</comment>
100
The configuration file is actually a JSON object saved to a file. For more
101
information check the documentation online:
102
<comment>
103
  https://github.com/humbug/box
104
</comment>
105
HELP;
106
107
    private const DEBUG_OPTION = 'debug';
108
    private const NO_PARALLEL_PROCESSING_OPTION = 'no-parallel';
109
    private const NO_RESTART_OPTION = 'no-restart';
110
    private const DEV_OPTION = 'dev';
111
    private const NO_CONFIG_OPTION = 'no-config';
112
    private const WITH_DOCKER_OPTION = 'with-docker';
113
114
    private const DEBUG_DIR = '.box_dump';
115
116
    /**
117
     * {@inheritdoc}
118
     */
119
    protected function configure(): void
120
    {
121
        parent::configure();
122
123
        $this->setName('compile');
124
        $this->setDescription('🔨  Compiles an application into a PHAR');
125
        $this->setHelp(self::HELP);
126
127
        $this->addOption(
128
            self::DEBUG_OPTION,
129
            null,
130
            InputOption::VALUE_NONE,
131
            'Dump the files added to the PHAR in a `'.self::DEBUG_DIR.'` directory'
132
        );
133
        $this->addOption(
134
            self::NO_PARALLEL_PROCESSING_OPTION,
135
            null,
136
            InputOption::VALUE_NONE,
137
            'Disable the parallel processing'
138
        );
139
        $this->addOption(
140
            self::NO_RESTART_OPTION,
141
            null,
142
            InputOption::VALUE_NONE,
143
            'Do not restart the PHP process. Box restarts the process by default to disable xdebug and set `phar.readonly=0`'
144
        );
145
        $this->addOption(
146
            self::DEV_OPTION,
147
            null,
148
            InputOption::VALUE_NONE,
149
            'Skips the compression step'
150
        );
151
        $this->addOption(
152
            self::NO_CONFIG_OPTION,
153
            null,
154
            InputOption::VALUE_NONE,
155
            'Ignore the config file even when one is specified with the --config option'
156
        );
157
        $this->addOption(
158
            self::WITH_DOCKER_OPTION,
159
            null,
160
            InputOption::VALUE_NONE,
161
            'Generates a Dockerfile'
162
        );
163
164
        $this->configureWorkingDirOption();
165
    }
166
167
    /**
168
     * {@inheritdoc}
169
     */
170
    protected function execute(InputInterface $input, OutputInterface $output): void
171
    {
172
        $io = new SymfonyStyle($input, $output);
173
174
        OutputConfigurator::configure($output);
175
176
        if ($input->getOption(self::NO_RESTART_OPTION)) {
177
            putenv(BOX_ALLOW_XDEBUG.'=1');
178
        }
179
180
        if ($debug = $input->getOption(self::DEBUG_OPTION)) {
181
            $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
182
        }
183
184
        (new PhpSettingsHandler(new ConsoleLogger($output)))->check();
185
186
        if ($input->getOption(self::NO_PARALLEL_PROCESSING_OPTION)) {
187
            disable_parallel_processing();
188
            $io->writeln('<info>[debug] Disabled parallel processing</info>', OutputInterface::VERBOSITY_DEBUG);
189
        }
190
191
        $this->changeWorkingDirectory($input);
192
193
        $io->writeln($this->getApplication()->getHelp());
194
        $io->newLine();
195
196
        $config = $input->getOption(self::NO_CONFIG_OPTION)
197
            ? Configuration::create(null, new stdClass())
198
            : $this->getConfig($input, $output, true)
199
        ;
200
        $path = $config->getOutputPath();
201
202
        $logger = new CompileLogger($io);
203
204
        $startTime = microtime(true);
205
206
        $logger->logStartBuilding($path);
207
208
        $this->removeExistingArtifacts($config, $logger, $debug);
209
210
        $box = $this->createPhar($config, $input, $output, $logger, $io, $debug);
211
212
        $this->correctPermissions($path, $config, $logger);
213
214
        $this->logEndBuilding($config, $logger, $io, $box, $path, $startTime);
215
216
        if ($input->getOption(self::WITH_DOCKER_OPTION)) {
217
            $this->generateDockerFile($output);
218
        }
219
    }
220
221
    private function createPhar(
222
        Configuration $config,
223
        InputInterface $input,
224
        OutputInterface $output,
225
        CompileLogger $logger,
226
        SymfonyStyle $io,
227
        bool $debug
228
    ): Box {
229
        $box = Box::create(
230
            $config->getTmpOutputPath()
231
        );
232
        $box->startBuffering();
233
234
        $this->registerReplacementValues($config, $box, $logger);
235
        $this->registerCompactors($config, $box, $logger);
236
        $this->registerFileMapping($config, $box, $logger);
237
238
        // Registering the main script _before_ adding the rest if of the files is _very_ important. The temporary
239
        // file used for debugging purposes and the Composer dump autoloading will not work correctly otherwise.
240
        $main = $this->registerMainScript($config, $box, $logger);
241
242
        $check = $this->registerRequirementsChecker($config, $box, $logger);
243
244
        $this->addFiles($config, $box, $logger, $io);
245
246
        $this->registerStub($config, $box, $main, $check, $logger);
247
        $this->configureMetadata($config, $box, $logger);
248
249
        $this->commit($box, $config, $logger);
250
251
        $this->checkComposerFiles($box, $config, $logger);
252
253
        if ($debug) {
254
            $box->getPhar()->extractTo(self::DEBUG_DIR, null, true);
255
        }
256
257
        $this->configureCompressionAlgorithm($config, $box, $input->getOption(self::DEV_OPTION), $io, $logger);
258
259
        $this->signPhar($config, $box, $config->getTmpOutputPath(), $input, $output, $logger);
260
261
        if ($config->getTmpOutputPath() !== $config->getOutputPath()) {
262
            rename($config->getTmpOutputPath(), $config->getOutputPath());
263
        }
264
265
        return $box;
266
    }
267
268
    private function removeExistingArtifacts(Configuration $config, CompileLogger $logger, bool $debug): void
269
    {
270
        $path = $config->getOutputPath();
271
272
        if ($debug) {
273
            remove(self::DEBUG_DIR);
274
275
            $date = (new DateTimeImmutable('now', new DateTimeZone('UTC')))->format(DATE_ATOM);
276
            $file = $config->getConfigurationFile() ?? 'No config file';
277
278
            remove(self::DEBUG_DIR);
279
280
            $phpVersion = PHP_VERSION;
281
            $phpExtensions = implode(',', get_loaded_extensions());
282
            $command = implode(' ', $GLOBALS['argv']);
283
            $boxVersion = get_box_version();
284
285
            dump_file(
286
                self::DEBUG_DIR.'/.box_configuration',
287
                <<<EOF
288
//
289
// Processed content of the configuration file "$file" dumped for debugging purposes
290
//
291
// PHP Version: $phpVersion
292
// PHP extensions: $phpExtensions
293
// Command: $command
294
// Box: $boxVersion
295
// Time: $date
296
//
297
298
299
EOF
300
                .$config->export()
301
            );
302
        }
303
304
        if (false === file_exists($path)) {
305
            return;
306
        }
307
308
        $logger->log(
309
            CompileLogger::QUESTION_MARK_PREFIX,
310
            sprintf(
311
                'Removing the existing PHAR "%s"',
312
                $path
313
            )
314
        );
315
316
        remove($path);
317
    }
318
319
    private function registerReplacementValues(Configuration $config, Box $box, CompileLogger $logger): void
320
    {
321
        $values = $config->getReplacements();
322
323
        if ([] === $values) {
324
            return;
325
        }
326
327
        $logger->log(
328
            CompileLogger::QUESTION_MARK_PREFIX,
329
            'Setting replacement values'
330
        );
331
332
        foreach ($values as $key => $value) {
333
            $logger->log(
334
                CompileLogger::PLUS_PREFIX,
335
                sprintf(
336
                    '%s: %s',
337
                    $key,
338
                    $value
339
                )
340
            );
341
        }
342
343
        $box->registerPlaceholders($values);
344
    }
345
346
    private function registerCompactors(Configuration $config, Box $box, CompileLogger $logger): void
347
    {
348
        $compactors = $config->getCompactors();
349
350
        if ([] === $compactors) {
351
            $logger->log(
352
                CompileLogger::QUESTION_MARK_PREFIX,
353
                'No compactor to register'
354
            );
355
356
            return;
357
        }
358
359
        $logger->log(
360
            CompileLogger::QUESTION_MARK_PREFIX,
361
            'Registering compactors'
362
        );
363
364
        $logCompactors = static function (Compactor $compactor) use ($logger): void {
365
            $compactorClassParts = explode('\\', get_class($compactor));
366
367
            if ('_HumbugBox' === substr($compactorClassParts[0], 0, strlen('_HumbugBox'))) {
368
                // Keep the non prefixed class name for the user
369
                array_shift($compactorClassParts);
370
            }
371
372
            $logger->log(
373
                CompileLogger::PLUS_PREFIX,
374
                implode('\\', $compactorClassParts)
375
            );
376
        };
377
378
        array_map($logCompactors, $compactors);
379
380
        $box->registerCompactors($compactors);
381
    }
382
383
    private function registerFileMapping(Configuration $config, Box $box, CompileLogger $logger): void
384
    {
385
        $fileMapper = $config->getFileMapper();
386
387
        $this->logMap($fileMapper, $logger);
388
389
        $box->registerFileMapping($fileMapper);
390
    }
391
392
    private function addFiles(Configuration $config, Box $box, CompileLogger $logger, SymfonyStyle $io): void
393
    {
394
        $logger->log(CompileLogger::QUESTION_MARK_PREFIX, 'Adding binary files');
395
396
        $count = count($config->getBinaryFiles());
397
398
        $box->addFiles($config->getBinaryFiles(), true);
399
400
        $logger->log(
401
            CompileLogger::CHEVRON_PREFIX,
402
            0 === $count
403
                ? 'No file found'
404
                : sprintf('%d file(s)', $count)
405
        );
406
407
        $logger->log(
408
            CompileLogger::QUESTION_MARK_PREFIX,
409
            sprintf(
410
                'Auto-discover files? %s',
411
                $config->hasAutodiscoveredFiles() ? 'Yes' : 'No'
412
            )
413
        );
414
        $logger->log(
415
            CompileLogger::QUESTION_MARK_PREFIX,
416
            sprintf(
417
                'Exclude dev files? %s',
418
                $config->excludeDevFiles() ? 'Yes' : 'No'
419
            )
420
        );
421
        $logger->log(CompileLogger::QUESTION_MARK_PREFIX, 'Adding files');
422
423
        $count = count($config->getFiles());
424
425
        try {
426
            $box->addFiles($config->getFiles(), false);
427
        } catch (MultiReasonException $exception) {
428
            // This exception is handled a different way to give me meaningful feedback to the user
429
            foreach ($exception->getReasons() as $reason) {
430
                $io->error($reason);
431
            }
432
433
            throw $exception;
434
        }
435
436
        $logger->log(
437
            CompileLogger::CHEVRON_PREFIX,
438
            0 === $count
439
                ? 'No file found'
440
                : sprintf('%d file(s)', $count)
441
        );
442
    }
443
444
    private function registerMainScript(Configuration $config, Box $box, CompileLogger $logger): ?string
445
    {
446
        if (false === $config->hasMainScript()) {
447
            $logger->log(
448
                CompileLogger::QUESTION_MARK_PREFIX,
449
                'No main script path configured'
450
            );
451
452
            return null;
453
        }
454
455
        $main = $config->getMainScriptPath();
456
457
        $logger->log(
458
            CompileLogger::QUESTION_MARK_PREFIX,
459
            sprintf(
460
                'Adding main file: %s',
461
                $main
462
            )
463
        );
464
465
        $localMain = $box->addFile(
466
            $main,
467
            $config->getMainScriptContents()
468
        );
469
470
        $relativeMain = make_path_relative($main, $config->getBasePath());
471
472
        if ($localMain !== $relativeMain) {
473
            $logger->log(
474
                CompileLogger::CHEVRON_PREFIX,
475
                $localMain
476
            );
477
        }
478
479
        return $localMain;
480
    }
481
482
    private function registerRequirementsChecker(Configuration $config, Box $box, CompileLogger $logger): bool
483
    {
484
        if (false === $config->checkRequirements()) {
485
            $logger->log(
486
                CompileLogger::QUESTION_MARK_PREFIX,
487
                'Skip requirements checker'
488
            );
489
490
            return false;
491
        }
492
493
        $logger->log(
494
            CompileLogger::QUESTION_MARK_PREFIX,
495
            'Adding requirements checker'
496
        );
497
498
        $checkFiles = RequirementsDumper::dump(
499
            $config->getDecodedComposerJsonContents() ?? [],
500
            $config->getDecodedComposerLockContents() ?? [],
501
            $config->getCompressionAlgorithm()
502
        );
503
504
        foreach ($checkFiles as $fileWithContents) {
505
            [$file, $contents] = $fileWithContents;
506
507
            $box->addFile('.box/'.$file, $contents, true);
508
        }
509
510
        return true;
511
    }
512
513
    private function registerStub(Configuration $config, Box $box, ?string $main, bool $checkRequirements, CompileLogger $logger): void
514
    {
515
        if ($config->isStubGenerated()) {
516
            $logger->log(
517
                CompileLogger::QUESTION_MARK_PREFIX,
518
                'Generating new stub'
519
            );
520
521
            $stub = $this->createStub($config, $main, $checkRequirements, $logger);
522
523
            $box->getPhar()->setStub($stub);
524
525
            return;
526
        }
527
528
        if (null !== ($stub = $config->getStubPath())) {
529
            $logger->log(
530
                CompileLogger::QUESTION_MARK_PREFIX,
531
                sprintf(
532
                    'Using stub file: %s',
533
                    $stub
534
                )
535
            );
536
537
            $box->registerStub($stub);
538
539
            return;
540
        }
541
542
        $aliasWasAdded = $box->getPhar()->setAlias($config->getAlias());
543
544
        Assertion::true(
545
            $aliasWasAdded,
546
            sprintf(
547
                'The alias "%s" is invalid. See Phar::setAlias() documentation for more information.',
548
                $config->getAlias()
549
            )
550
        );
551
552
        $box->getPhar()->setDefaultStub($main);
553
554
        $logger->log(
555
            CompileLogger::QUESTION_MARK_PREFIX,
556
            'Using default stub'
557
        );
558
    }
559
560
    private function configureMetadata(Configuration $config, Box $box, CompileLogger $logger): void
561
    {
562
        if (null !== ($metadata = $config->getMetadata())) {
563
            $logger->log(
564
                CompileLogger::QUESTION_MARK_PREFIX,
565
                'Setting metadata'
566
            );
567
568
            $logger->log(
569
                CompileLogger::MINUS_PREFIX,
570
                is_string($metadata) ? $metadata : var_export($metadata, true)
571
            );
572
573
            $box->getPhar()->setMetadata($metadata);
574
        }
575
    }
576
577
    private function commit(Box $box, Configuration $config, CompileLogger $logger): void
578
    {
579
        $message = $config->dumpAutoload()
580
            ? 'Dumping the Composer autoloader'
581
            : 'Skipping dumping the Composer autoloader'
582
        ;
583
584
        $logger->log(CompileLogger::QUESTION_MARK_PREFIX, $message);
585
586
        $box->endBuffering($config->dumpAutoload());
587
    }
588
589
    private function checkComposerFiles(Box $box, Configuration $config, CompileLogger $logger): void
590
    {
591
        $message = $config->excludeComposerFiles()
592
            ? 'Removing the Composer dump artefacts'
593
            : 'Keep the Composer dump artefacts'
594
        ;
595
596
        $logger->log(CompileLogger::QUESTION_MARK_PREFIX, $message);
597
598
        if ($config->excludeComposerFiles()) {
599
            $box->removeComposerArtefacts(
600
                ComposerConfiguration::retrieveVendorDir(
601
                    $config->getDecodedComposerJsonContents() ?? []
602
                )
603
            );
604
        }
605
    }
606
607
    private function configureCompressionAlgorithm(Configuration $config, Box $box, bool $dev, SymfonyStyle $io, CompileLogger $logger): void
608
    {
609
        if (null === ($algorithm = $config->getCompressionAlgorithm())) {
610
            $logger->log(
611
                CompileLogger::QUESTION_MARK_PREFIX,
612
                'No compression'
613
            );
614
615
            return;
616
        }
617
618
        if ($dev) {
619
            $logger->log(CompileLogger::QUESTION_MARK_PREFIX, 'Dev mode detected: skipping the compression');
620
621
            return;
622
        }
623
624
        $logger->log(
625
            CompileLogger::QUESTION_MARK_PREFIX,
626
            sprintf(
627
                'Compressing with the algorithm "<comment>%s</comment>"',
628
                array_search($algorithm, get_phar_compression_algorithms(), true)
0 ignored issues
show
Bug introduced by
It seems like array_search($algorithm,...ion_algorithms(), true) can also be of type false; however, parameter $args of sprintf() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

628
                /** @scrutinizer ignore-type */ array_search($algorithm, get_phar_compression_algorithms(), true)
Loading history...
629
            )
630
        );
631
632
        $restoreLimit = FileDescriptorManipulator::bumpOpenFileDescriptorLimit(count($box), $io);
633
634
        try {
635
            $extension = $box->compress($algorithm);
636
637
            if (null !== $extension) {
638
                $logger->log(
639
                    CompileLogger::CHEVRON_PREFIX,
640
                    sprintf(
641
                        '<info>Warning: the extension "%s" will now be required to execute the PHAR</info>',
642
                        $extension
643
                    )
644
                );
645
            }
646
        } catch (RuntimeException $exception) {
647
            $io->error($exception->getMessage());
648
649
            // Continue: the compression failure should not result in completely bailing out the compilation process
650
        } finally {
651
            $restoreLimit();
652
        }
653
    }
654
655
    private function signPhar(
656
        Configuration $config,
657
        Box $box,
658
        string $path,
659
        InputInterface $input,
660
        OutputInterface $output,
661
        CompileLogger $logger
662
    ): void {
663
        // Sign using private key when applicable
664
        remove($path.'.pubkey');
665
666
        $key = $config->getPrivateKeyPath();
667
668
        if (null === $key) {
669
            if (null !== ($algorithm = $config->getSigningAlgorithm())) {
0 ignored issues
show
introduced by
The condition null !== $algorithm = $c...->getSigningAlgorithm() is always true.
Loading history...
670
                $box->getPhar()->setSignatureAlgorithm($algorithm);
671
            }
672
673
            return;
674
        }
675
676
        $logger->log(
677
            CompileLogger::QUESTION_MARK_PREFIX,
678
            'Signing using a private key'
679
        );
680
681
        $passphrase = $config->getPrivateKeyPassphrase();
682
683
        if ($config->promptForPrivateKey()) {
684
            if (false === $input->isInteractive()) {
685
                throw new RuntimeException(
686
                    sprintf(
687
                        'Accessing to the private key "%s" requires a passphrase but none provided. Either '
688
                        .'provide one or run this command in interactive mode.',
689
                        $key
690
                    )
691
                );
692
            }
693
694
            /** @var QuestionHelper $dialog */
695
            $dialog = $this->getHelper('question');
696
697
            $question = new Question('Private key passphrase:');
698
            $question->setHidden(false);
699
            $question->setHiddenFallback(false);
700
701
            $passphrase = $dialog->ask($input, $output, $question);
702
703
            $output->writeln('');
704
        }
705
706
        $box->signUsingFile($key, $passphrase);
707
    }
708
709
    private function correctPermissions(string $path, Configuration $config, CompileLogger $logger): void
710
    {
711
        if (null !== ($chmod = $config->getFileMode())) {
712
            $logger->log(
713
                CompileLogger::QUESTION_MARK_PREFIX,
714
                sprintf(
715
                    'Setting file permissions to <comment>%s</comment>',
716
                    '0'.decoct($chmod)
717
                )
718
            );
719
720
            chmod($path, $chmod);
721
        }
722
    }
723
724
    private function createStub(Configuration $config, ?string $main, bool $checkRequirements, CompileLogger $logger): string
725
    {
726
        $stub = StubGenerator::create()
727
            ->alias($config->getAlias())
728
            ->index($main)
729
            ->intercept($config->isInterceptFileFuncs())
730
            ->checkRequirements($checkRequirements)
731
        ;
732
733
        if (null !== ($shebang = $config->getShebang())) {
734
            $logger->log(
735
                CompileLogger::MINUS_PREFIX,
736
                sprintf(
737
                    'Using shebang line: %s',
738
                    $shebang
739
                )
740
            );
741
742
            $stub->shebang($shebang);
743
        } else {
744
            $logger->log(
745
                CompileLogger::MINUS_PREFIX,
746
                'No shebang line'
747
            );
748
        }
749
750
        if (null !== ($bannerPath = $config->getStubBannerPath())) {
751
            $logger->log(
752
                CompileLogger::MINUS_PREFIX,
753
                sprintf(
754
                    'Using custom banner from file: %s',
755
                    $bannerPath
756
                )
757
            );
758
759
            $stub->banner($config->getStubBannerContents());
760
        } elseif (null !== ($banner = $config->getStubBannerContents())) {
761
            $logger->log(
762
                CompileLogger::MINUS_PREFIX,
763
                'Using banner:'
764
            );
765
766
            $bannerLines = explode("\n", $banner);
767
768
            foreach ($bannerLines as $bannerLine) {
769
                $logger->log(
770
                    CompileLogger::CHEVRON_PREFIX,
771
                    $bannerLine
772
                );
773
            }
774
775
            $stub->banner($banner);
776
        }
777
778
        return $stub->generate();
779
    }
780
781
    private function logMap(MapFile $fileMapper, CompileLogger $logger): void
782
    {
783
        $map = $fileMapper->getMap();
784
785
        if ([] === $map) {
786
            return;
787
        }
788
789
        $logger->log(
790
            CompileLogger::QUESTION_MARK_PREFIX,
791
            'Mapping paths'
792
        );
793
794
        foreach ($map as $item) {
795
            foreach ($item as $match => $replace) {
796
                if ('' === $match) {
797
                    $match = '(all)';
798
                    $replace .= '/';
799
                }
800
801
                $logger->log(
802
                    CompileLogger::MINUS_PREFIX,
803
                    sprintf(
804
                        '%s <info>></info> %s',
805
                        $match,
806
                        $replace
807
                    )
808
                );
809
            }
810
        }
811
    }
812
813
    private function logEndBuilding(
814
        Configuration $config,
815
        CompileLogger $logger,
816
        SymfonyStyle $io,
817
        Box $box,
818
        string $path,
819
        float $startTime
820
    ): void {
821
        $logger->log(
822
            CompileLogger::STAR_PREFIX,
823
            'Done.'
824
        );
825
        $io->newLine();
826
827
        MessageRenderer::render($io, $config->getRecommendations(), $config->getWarnings());
828
829
        $io->comment(
830
            sprintf(
831
                'PHAR: %s (%s)',
832
                $box->count() > 1 ? $box->count().' files' : $box->count().' file',
833
                format_size(
834
                    filesize($path)
835
                )
836
            )
837
            .PHP_EOL
838
            .'You can inspect the generated PHAR with the "<comment>info</comment>" command.'
839
        );
840
841
        $io->comment(
842
            sprintf(
843
                '<info>Memory usage: %.2fMB (peak: %.2fMB), time: %.2fs<info>',
844
                round(memory_get_usage() / 1024 / 1024, 2),
845
                round(memory_get_peak_usage() / 1024 / 1024, 2),
846
                round(microtime(true) - $startTime, 2)
847
            )
848
        );
849
    }
850
851
    private function generateDockerFile(OutputInterface $output): void
852
    {
853
        $generateDockerFileCommand = $this->getApplication()->find('docker');
854
855
        $input = new StringInput('');
856
        $input->setInteractive(false);
857
858
        $generateDockerFileCommand->run($input, $output);
859
    }
860
}
861