Completed
Pull Request — master (#200)
by Théo
02:44
created

Compile::signPhar()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 53
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 53
rs 8.7155
c 0
b 0
f 0
cc 5
eloc 24
nc 5
nop 6

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 Assert\Assertion;
19
use BadMethodCallException;
20
use DateTimeImmutable;
21
use DateTimeZone;
22
use KevinGH\Box\Box;
23
use KevinGH\Box\Compactor;
24
use KevinGH\Box\Configuration;
25
use KevinGH\Box\Console\Logger\BuildLogger;
26
use KevinGH\Box\MapFile;
27
use KevinGH\Box\PhpSettingsHandler;
28
use KevinGH\Box\RequirementChecker\RequirementsDumper;
29
use KevinGH\Box\StubGenerator;
30
use RuntimeException;
31
use stdClass;
32
use Symfony\Component\Console\Helper\QuestionHelper;
33
use Symfony\Component\Console\Input\InputInterface;
34
use Symfony\Component\Console\Input\InputOption;
35
use Symfony\Component\Console\Logger\ConsoleLogger;
36
use Symfony\Component\Console\Output\OutputInterface;
37
use Symfony\Component\Console\Question\Question;
38
use Symfony\Component\Console\Style\SymfonyStyle;
39
use Symfony\Component\VarDumper\Cloner\VarCloner;
40
use Symfony\Component\VarDumper\Dumper\CliDumper;
41
use const DATE_ATOM;
42
use const POSIX_RLIMIT_INFINITY;
43
use const POSIX_RLIMIT_NOFILE;
44
use function array_shift;
45
use function count;
46
use function decoct;
47
use function explode;
48
use function function_exists;
49
use function get_class;
50
use function implode;
51
use function ini_get;
52
use function KevinGH\Box\disable_parallel_processing;
53
use function KevinGH\Box\FileSystem\chmod;
54
use function KevinGH\Box\FileSystem\dump_file;
55
use function KevinGH\Box\FileSystem\make_path_relative;
56
use function KevinGH\Box\FileSystem\remove;
57
use function KevinGH\Box\FileSystem\rename;
58
use function KevinGH\Box\formatted_filesize;
59
use function KevinGH\Box\get_phar_compression_algorithms;
60
use function posix_setrlimit;
61
use function putenv;
62
use function strlen;
63
use function strtolower;
64
use function substr;
65
use function trim;
66
use const DATE_ATOM;
0 ignored issues
show
Bug introduced by
A parse error occurred: Cannot use DATE_ATOM as DATE_ATOM because the name is already in use
Loading history...
67
68
/**
69
 * @final
70
 * @private
71
 * TODO: make final when Build is removed
72
 */
73
class Compile extends Configurable
74
{
75
    use ChangeableWorkingDirectory;
76
77
    private const HELP = <<<'HELP'
78
The <info>%command.name%</info> command will compile code in a new PHAR based on a variety of settings.
79
<comment>
80
  This command relies on a configuration file for loading
81
  PHAR packaging settings. If a configuration file is not
82
  specified through the <info>--config|-c</info> option, one of
83
  the following files will be used (in order): <info>box.json</info>,
84
  <info>box.json.dist</info>
85
</comment>
86
The configuration file is actually a JSON object saved to a file. For more
87
information check the documentation online:
88
<comment>
89
  https://github.com/humbug/box
90
</comment>
91
HELP;
92
93
    private const DEBUG_OPTION = 'debug';
94
    private const NO_PARALLEL_PROCESSING_OPTION = 'no-parallel';
95
    private const NO_RESTART_OPTION = 'no-restart';
96
    private const DEV_OPTION = 'dev';
97
    private const NO_CONFIG_OPTION = 'no-config';
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('Compile 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
143
        $this->configureWorkingDirOption();
144
    }
145
146
    /**
147
     * {@inheritdoc}
148
     */
149
    protected function execute(InputInterface $input, OutputInterface $output): void
150
    {
151
        $io = new SymfonyStyle($input, $output);
152
153
        if ($input->getOption(self::NO_RESTART_OPTION)) {
154
            putenv('BOX_ALLOW_XDEBUG=1');
155
        }
156
157
        if ($debug = $input->getOption(self::DEBUG_OPTION)) {
158
            $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
159
        }
160
161
        self::checkPhpSettings($io, $output);
162
163
        if ($input->getOption(self::NO_PARALLEL_PROCESSING_OPTION)) {
164
            disable_parallel_processing();
165
            $io->writeln('<info>[debug] Disabled parallel processing</info>', OutputInterface::VERBOSITY_DEBUG);
166
        }
167
168
        $this->changeWorkingDirectory($input);
169
170
        $io->writeln($this->getApplication()->getHelp());
171
        $io->writeln('');
172
173
        $config = $input->getOption(self::NO_CONFIG_OPTION)
174
            ? Configuration::create(null, new stdClass())
175
            : $this->getConfig($input, $output, true)
176
        ;
177
        $path = $config->getOutputPath();
178
179
        $logger = new BuildLogger($io);
180
181
        $startTime = microtime(true);
182
183
        $this->removeExistingArtefacts($config, $logger, $debug);
184
185
        $logger->logStartBuilding($path);
186
187
        $this->createPhar($config, $input, $output, $logger, $io, $debug);
188
189
        $this->correctPermissions($path, $config, $logger);
190
191
        $logger->log(
192
            BuildLogger::STAR_PREFIX,
193
            'Done.'
194
        );
195
196
        $io->comment('You can inspect the generated PHAR with the "<comment>info</comment>" command.');
197
198
        $this->logCommandResourcesUsage($io, $path, $startTime);
199
    }
200
201
    /**
202
     * Taken from Composer.
203
     *
204
     * @see https://github.com/composer/composer/blob/34c371f5f23e25eb9aa54ccc65136cf50930612e/bin/composer#L20-L50
205
     */
206
    private static function checkPhpSettings(SymfonyStyle $io, OutputInterface $output): void
207
    {
208
        (new PhpSettingsHandler(new ConsoleLogger($output)))->check();
209
210
        if (function_exists('ini_set')) {
211
            $memoryInBytes = function (string $value): int {
212
                $unit = strtolower($value[strlen($value) - 1]);
213
214
                $value = (int) $value;
215
                switch ($unit) {
216
                    case 'g':
217
                        $value *= 1024;
218
                    // no break (cumulative multiplier)
219
                    case 'm':
220
                        $value *= 1024;
221
                    // no break (cumulative multiplier)
222
                    case 'k':
223
                        $value *= 1024;
224
                }
225
226
                return $value;
227
            };
228
229
            $memoryLimit = trim(ini_get('memory_limit'));
230
231
            // Increase memory_limit if it is lower than 500MB
232
            if ('-1' !== $memoryLimit && $memoryInBytes($memoryLimit) < 1024 * 1024 * 512 && false === getenv('BOX_MEMORY_LIMIT')) {
233
                @ini_set('memory_limit', '512M');
234
235
                $io->writeln(
236
                    sprintf(
237
                        '<info>[debug] Bumped the memory limit from "%s" to "%s".</info>',
238
                        $memoryLimit,
239
                        '512M'
240
                    ),
241
                    OutputInterface::VERBOSITY_DEBUG
242
                );
243
            }
244
245
            // Set user defined memory limit
246
            if ($newMemoryLimit = getenv('BOX_MEMORY_LIMIT')) {
247
                @ini_set('memory_limit', $newMemoryLimit);
248
249
                $io->writeln(
250
                    sprintf(
251
                        '<info>[debug] Bumped the memory limit from "%s" to BOX_MEMORY_LIMIT="%s".</info>',
252
                        $memoryLimit,
253
                        $newMemoryLimit
254
                    ),
255
                    OutputInterface::VERBOSITY_DEBUG
256
                );
257
            }
258
        }
259
    }
260
261
    private function createPhar(
262
        Configuration $config,
263
        InputInterface $input,
264
        OutputInterface $output,
265
        BuildLogger $logger,
266
        SymfonyStyle $io,
267
        bool $debug
268
    ): void {
269
        $box = Box::create(
270
            $config->getTmpOutputPath()
271
        );
272
        $box->startBuffering();
273
274
        $this->setReplacementValues($config, $box, $logger);
275
        $this->registerCompactors($config, $box, $logger);
276
        $this->registerFileMapping($config, $box, $logger);
277
278
        // Registering the main script _before_ adding the rest if of the files is _very_ important. The temporary
279
        // file used for debugging purposes and the Composer dump autoloading will not work correctly otherwise.
280
        $main = $this->registerMainScript($config, $box, $logger);
281
282
        $check = $this->registerRequirementsChecker($config, $box, $logger);
283
284
        $this->addFiles($config, $box, $logger, $io);
285
286
        $this->registerStub($config, $box, $main, $check, $logger);
287
        $this->configureMetadata($config, $box, $logger);
288
289
        $box->endBuffering(null !== $config->getComposerJson());
290
291
        $this->configureCompressionAlgorithm($config, $box, $input->getOption(self::DEV_OPTION), $io, $logger);
292
293
        if ($debug) {
294
            $box->getPhar()->extractTo(self::DEBUG_DIR, null, true);
295
        }
296
297
        $this->signPhar($config, $box, $config->getTmpOutputPath(), $input, $output, $logger);
298
299
        if ($config->getTmpOutputPath() !== $config->getOutputPath()) {
300
            rename($config->getTmpOutputPath(), $config->getOutputPath());
301
        }
302
    }
303
304
    private function removeExistingArtefacts(Configuration $config, BuildLogger $logger, bool $debug): void
305
    {
306
        $path = $config->getOutputPath();
307
308
        if ($debug) {
309
            remove(self::DEBUG_DIR);
310
311
            $date = (new DateTimeImmutable('now', new DateTimeZone('UTC')))->format(DATE_ATOM);
312
            $file = null !== $config->getFile() ? $config->getFile() : 'No config file';
313
314
            remove(self::DEBUG_DIR);
315
316
            dump_file(
317
                self::DEBUG_DIR.'/.box_configuration',
318
                <<<EOF
319
//
320
// Processed content of the configuration file "$file" dumped for debugging purposes
321
// Time: $date
322
//
323
324
325
EOF
326
                .(new CliDumper())->dump(
327
                    (new VarCloner())->cloneVar($config),
328
                    true
329
                )
330
            );
331
        }
332
333
        if (false === file_exists($path)) {
334
            return;
335
        }
336
337
        $logger->log(
338
            BuildLogger::QUESTION_MARK_PREFIX,
339
            sprintf(
340
                'Removing the existing PHAR "%s"',
341
                $path
342
            )
343
        );
344
345
        remove($path);
346
    }
347
348
    private function setReplacementValues(Configuration $config, Box $box, BuildLogger $logger): void
349
    {
350
        $values = $config->getProcessedReplacements();
351
352
        if ([] === $values) {
353
            return;
354
        }
355
356
        $logger->log(
357
            BuildLogger::QUESTION_MARK_PREFIX,
358
            'Setting replacement values'
359
        );
360
361
        foreach ($values as $key => $value) {
362
            $logger->log(
363
                BuildLogger::PLUS_PREFIX,
364
                sprintf(
365
                    '%s: %s',
366
                    $key,
367
                    $value
368
                )
369
            );
370
        }
371
372
        $box->registerPlaceholders($values);
373
    }
374
375
    private function registerCompactors(Configuration $config, Box $box, BuildLogger $logger): void
376
    {
377
        $compactors = $config->getCompactors();
378
379
        if ([] === $compactors) {
380
            $logger->log(
381
                BuildLogger::QUESTION_MARK_PREFIX,
382
                'No compactor to register'
383
            );
384
385
            return;
386
        }
387
388
        $logger->log(
389
            BuildLogger::QUESTION_MARK_PREFIX,
390
            'Registering compactors'
391
        );
392
393
        $logCompactors = function (Compactor $compactor) use ($logger): void {
394
            $compactorClassParts = explode('\\', get_class($compactor));
395
396
            if ('_HumbugBox' === substr($compactorClassParts[0], 0, strlen('_HumbugBox'))) {
397
                array_shift($compactorClassParts);
398
            }
399
400
            $logger->log(
401
                BuildLogger::PLUS_PREFIX,
402
                implode('\\', $compactorClassParts)
403
            );
404
        };
405
406
        array_map($logCompactors, $compactors);
407
408
        $box->registerCompactors($compactors);
409
    }
410
411
    private function registerFileMapping(Configuration $config, Box $box, BuildLogger $logger): void
412
    {
413
        $fileMapper = $config->getFileMapper();
414
415
        $this->logMap($fileMapper, $logger);
416
417
        $box->registerFileMapping(
418
            $config->getBasePath(),
419
            $fileMapper
420
        );
421
    }
422
423
    private function addFiles(Configuration $config, Box $box, BuildLogger $logger, SymfonyStyle $io): void
424
    {
425
        $logger->log(BuildLogger::QUESTION_MARK_PREFIX, 'Adding binary files');
426
427
        $count = count($config->getBinaryFiles());
428
429
        $box->addFiles($config->getBinaryFiles(), true);
430
431
        $logger->log(
432
            BuildLogger::CHEVRON_PREFIX,
433
            0 === $count
434
                ? 'No file found'
435
                : sprintf('%d file(s)', $count)
436
        );
437
438
        $logger->log(BuildLogger::QUESTION_MARK_PREFIX, 'Adding files');
439
440
        $count = count($config->getFiles());
441
442
        try {
443
            $box->addFiles($config->getFiles(), false);
444
        } catch (MultiReasonException $exception) {
445
            // This exception is handled a different way to give me meaningful feedback to the user
446
            foreach ($exception->getReasons() as $reason) {
447
                $io->error($reason);
448
            }
449
450
            throw $exception;
451
        }
452
453
        $logger->log(
454
            BuildLogger::CHEVRON_PREFIX,
455
            0 === $count
456
                ? 'No file found'
457
                : sprintf('%d file(s)', $count)
458
        );
459
    }
460
461
    private function registerMainScript(Configuration $config, Box $box, BuildLogger $logger): string
462
    {
463
        $main = $config->getMainScriptPath();
464
465
        $logger->log(
466
            BuildLogger::QUESTION_MARK_PREFIX,
467
            sprintf(
468
                'Adding main file: %s',
469
                $main
470
            )
471
        );
472
473
        $localMain = $box->addFile(
474
            $main,
475
            $config->getMainScriptContents()
476
        );
477
478
        $relativeMain = make_path_relative($main, $config->getBasePath());
479
480
        if ($localMain !== $relativeMain) {
481
            $logger->log(
482
                BuildLogger::CHEVRON_PREFIX,
483
                $localMain
484
            );
485
        }
486
487
        return $localMain;
488
    }
489
490
    private function registerRequirementsChecker(Configuration $config, Box $box, BuildLogger $logger): bool
491
    {
492
        if (false === $config->checkRequirements()) {
493
            return false;
494
        }
495
496
        $logger->log(
497
            BuildLogger::QUESTION_MARK_PREFIX,
498
            'Adding requirements checker'
499
        );
500
501
        $checkFiles = RequirementsDumper::dump(
502
            $config->getComposerLockDecodedContents(),
503
            null !== $config->getCompressionAlgorithm()
504
        );
505
506
        foreach ($checkFiles as $fileWithContents) {
507
            [$file, $contents] = $fileWithContents;
508
509
            $box->addFile('.box/'.$file, $contents, true);
510
        }
511
512
        return true;
513
    }
514
515
    private function registerStub(Configuration $config, Box $box, string $main, bool $checkRequirements, BuildLogger $logger): void
516
    {
517
        if ($config->isStubGenerated()) {
518
            $logger->log(
519
                BuildLogger::QUESTION_MARK_PREFIX,
520
                'Generating new stub'
521
            );
522
523
            $stub = $this->createStub($config, $main, $checkRequirements, $logger);
524
525
            $box->getPhar()->setStub($stub->generate());
526
        } elseif (null !== ($stub = $config->getStubPath())) {
527
            $logger->log(
528
                BuildLogger::QUESTION_MARK_PREFIX,
529
                sprintf(
530
                    'Using stub file: %s',
531
                    $stub
532
                )
533
            );
534
535
            $box->registerStub($stub);
536
        } else {
537
            // TODO: add warning that the check requirements could not be added
538
            $aliasWasAdded = $box->getPhar()->setAlias($config->getAlias());
539
540
            Assertion::true(
541
                $aliasWasAdded,
542
                sprintf(
543
                    'The alias "%s" is invalid. See Phar::setAlias() documentation for more information.',
544
                    $config->getAlias()
545
                )
546
            );
547
548
            $box->getPhar()->setDefaultStub($main);
549
550
            $logger->log(
551
                BuildLogger::QUESTION_MARK_PREFIX,
552
                'Using default stub'
553
            );
554
        }
555
    }
556
557
    private function configureMetadata(Configuration $config, Box $box, BuildLogger $logger): void
558
    {
559
        if (null !== ($metadata = $config->getMetadata())) {
560
            $logger->log(
561
                BuildLogger::QUESTION_MARK_PREFIX,
562
                'Setting metadata'
563
            );
564
565
            $logger->log(
566
                BuildLogger::MINUS_PREFIX,
567
                is_string($metadata) ? $metadata : var_export($metadata, true)
568
            );
569
570
            $box->getPhar()->setMetadata($metadata);
571
        }
572
    }
573
574
    private function configureCompressionAlgorithm(Configuration $config, Box $box, bool $dev, SymfonyStyle $io, BuildLogger $logger): void
575
    {
576
        if (null === ($algorithm = $config->getCompressionAlgorithm())) {
577
            $logger->log(
578
                BuildLogger::QUESTION_MARK_PREFIX,
579
                $dev
580
                    ? 'No compression'
581
                    : '<error>No compression</error>'
582
            );
583
584
            return;
585
        }
586
587
        $logger->log(
588
            BuildLogger::QUESTION_MARK_PREFIX,
589
            sprintf(
590
                'Compressing with the algorithm "<comment>%s</comment>"',
591
                array_search($algorithm, get_phar_compression_algorithms(), true)
592
            )
593
        );
594
595
        $filesCount = count($config->getBinaryFiles()) + count($config->getFiles()) + 128;  // Add a little extra for good measure
596
597
        if (function_exists('posix_getrlimit') && function_exists('posix_setrlimit')) {
598
            $softLimit = posix_getrlimit()['soft openfiles'];
599
            $hardLimit = posix_getrlimit()['hard openfiles'];
600
601
            if ($softLimit < $filesCount) {
602
                $io->writeln(
603
                    sprintf(
604
                        '<info>[debug] Increased the maximum number of open file descriptors from ("%s", "%s") to ("%s", "%s")'
605
                        .'</info>',
606
                        $softLimit,
607
                        $hardLimit,
608
                        $filesCount,
609
                        'unlimited'
610
                    ),
611
                    OutputInterface::VERBOSITY_DEBUG
612
                );
613
614
                posix_setrlimit(
615
                    POSIX_RLIMIT_NOFILE,
616
                    $filesCount,
617
                    'unlimited' === $hardLimit ? POSIX_RLIMIT_INFINITY : $hardLimit
618
                );
619
            }
620
        } else {
621
            $io->writeln(
622
                '<info>[debug] Could not check the maximum number of open file descriptors: the functions "posix_getrlimit()" and '
623
                .'"posix_setrlimit" could not be found.</info>',
624
                OutputInterface::VERBOSITY_DEBUG
625
            );
626
        }
627
628
        try {
629
            $box->getPhar()->compressFiles($algorithm);
630
        } catch (BadMethodCallException $exception) {
631
            if ('unable to create temporary file' !== $exception->getMessage()) {
632
                throw $exception;
633
            }
634
635
            $io->error(
636
                sprintf(
637
                    'Could not compress the PHAR: the compression requires too many file descriptors to be opened (%s). Check '
638
                    .'your system limits or install the posix extension to allow Box to automatically configure it during the compression',
639
                    $filesCount
640
                )
641
            );
642
        } finally {
643
            if (function_exists('posix_setrlimit') && isset($softLimit, $hardLimit)) {
644
                posix_setrlimit(
645
                    POSIX_RLIMIT_NOFILE,
646
                    $softLimit,
647
                    'unlimited' === $hardLimit ? POSIX_RLIMIT_INFINITY : $hardLimit
648
                );
649
650
                $io->writeln(
651
                    '<info>[debug] Restored the maximum number of open file descriptors</info>',
652
                    OutputInterface::VERBOSITY_DEBUG
653
                );
654
            }
655
        }
656
    }
657
658
    private function signPhar(
659
        Configuration $config,
660
        Box $box,
661
        string $path,
662
        InputInterface $input,
663
        OutputInterface $output,
664
        BuildLogger $logger
665
    ): void {
666
        // sign using private key, if applicable
667
        //TODO: check that out
668
        remove($path.'.pubkey');
669
670
        $key = $config->getPrivateKeyPath();
671
672
        if (null === $key) {
673
            if (null !== ($algorithm = $config->getSigningAlgorithm())) {
674
                $box->getPhar()->setSignatureAlgorithm($algorithm);
675
            }
676
677
            return;
678
        }
679
680
        $logger->log(
681
            BuildLogger::QUESTION_MARK_PREFIX,
682
            'Signing using a private key'
683
        );
684
685
        $passphrase = $config->getPrivateKeyPassphrase();
686
687
        if ($config->isPrivateKeyPrompt()) {
688
            if (false === $input->isInteractive()) {
689
                throw new RuntimeException(
690
                    sprintf(
691
                        'Accessing to the private key "%s" requires a passphrase but none provided. Either '
692
                        .'provide one or run this command in interactive mode.',
693
                        $key
694
                    )
695
                );
696
            }
697
698
            /** @var $dialog QuestionHelper */
699
            $dialog = $this->getHelper('question');
700
701
            $question = new Question('Private key passphrase:');
702
            $question->setHidden(false);
703
            $question->setHiddenFallback(false);
704
705
            $passphrase = $dialog->ask($input, $output, $question);
706
707
            $output->writeln('');
708
        }
709
710
        $box->signUsingFile($key, $passphrase);
711
    }
712
713
    private function correctPermissions(string $path, Configuration $config, BuildLogger $logger): void
714
    {
715
        if (null !== ($chmod = $config->getFileMode())) {
716
            $logger->log(
717
                BuildLogger::QUESTION_MARK_PREFIX,
718
                sprintf(
719
                    'Setting file permissions to <comment>%s</comment>',
720
                    '0'.decoct($chmod)
721
                )
722
            );
723
724
            chmod($path, $chmod);
725
        }
726
    }
727
728
    private function createStub(Configuration $config, ?string $main, bool $checkRequirements, BuildLogger $logger): StubGenerator
729
    {
730
        $stub = StubGenerator::create()
731
            ->alias($config->getAlias())
732
            ->index($main)
733
            ->intercept($config->isInterceptFileFuncs())
734
            ->checkRequirements($checkRequirements)
735
        ;
736
737
        if (null !== ($shebang = $config->getShebang())) {
738
            $logger->log(
739
                BuildLogger::MINUS_PREFIX,
740
                sprintf(
741
                    'Using shebang line: %s',
742
                    $shebang
743
                )
744
            );
745
746
            $stub->shebang($shebang);
747
        } else {
748
            $logger->log(
749
                BuildLogger::MINUS_PREFIX,
750
                'No shebang line'
751
            );
752
        }
753
754
        if (null !== ($bannerPath = $config->getStubBannerPath())) {
755
            $logger->log(
756
                BuildLogger::MINUS_PREFIX,
757
                sprintf(
758
                    'Using custom banner from file: %s',
759
                    $bannerPath
760
                )
761
            );
762
763
            $stub->banner($config->getStubBannerContents());
764
        } elseif (null !== ($banner = $config->getStubBannerContents())) {
765
            $logger->log(
766
                BuildLogger::MINUS_PREFIX,
767
                'Using banner:'
768
            );
769
770
            $bannerLines = explode("\n", $banner);
771
772
            foreach ($bannerLines as $bannerLine) {
773
                $logger->log(
774
                    BuildLogger::CHEVRON_PREFIX,
775
                    $bannerLine
776
                );
777
            }
778
779
            $stub->banner($banner);
780
        }
781
782
        return $stub;
783
    }
784
785
    private function logMap(MapFile $fileMapper, BuildLogger $logger): void
786
    {
787
        $map = $fileMapper->getMap();
788
789
        if ([] === $map) {
790
            return;
791
        }
792
793
        $logger->log(
794
            BuildLogger::QUESTION_MARK_PREFIX,
795
            'Mapping paths'
796
        );
797
798
        foreach ($map as $item) {
799
            foreach ($item as $match => $replace) {
800
                if ('' === $match) {
801
                    $match = '(all)';
802
                    $replace .= '/';
803
                }
804
805
                $logger->log(
806
                    BuildLogger::MINUS_PREFIX,
807
                    sprintf(
808
                        '%s <info>></info> %s',
809
                        $match,
810
                        $replace
811
                    )
812
                );
813
            }
814
        }
815
    }
816
817
    private function logCommandResourcesUsage(SymfonyStyle $io, string $path, float $startTime)
818
    {
819
        return $io->comment(
820
            sprintf(
821
                "<info>PHAR size: %s\nMemory usage: %.2fMB (peak: %.2fMB), time: %.2fs<info>",
822
                formatted_filesize($path),
823
                round(memory_get_usage() / 1024 / 1024, 2),
824
                round(memory_get_peak_usage() / 1024 / 1024, 2),
825
                round(microtime(true) - $startTime, 2)
826
            )
827
        );
828
    }
829
}
830