Passed
Pull Request — master (#233)
by Théo
02:33
created

Compile   F

Complexity

Total Complexity 71

Size/Duplication

Total Lines 751
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 71
dl 0
loc 751
rs 2.1052
c 0
b 0
f 0

20 Methods

Rating   Name   Duplication   Size   Complexity  
A registerFileMapping() 0 9 1
B addFiles() 0 35 5
B execute() 0 43 5
B registerReplacementValues() 0 25 3
B registerCompactors() 0 35 3
B removeExistingArtifacts() 0 42 4
B createPhar() 0 43 3
B configure() 0 40 1
B registerMainScript() 0 36 3
B createStub() 0 55 5
C bumpOpenFileDescriptorLimit() 0 46 8
A commit() 0 10 2
B signPhar() 0 53 5
A configureMetadata() 0 14 3
B registerRequirementsChecker() 0 29 3
B logMap() 0 26 5
A correctPermissions() 0 12 2
B registerStub() 0 44 3
B configureCompressionAlgorithm() 0 41 5
A logEndBuilding() 0 23 2

How to fix   Complexity   

Complex Class

Complex classes like Compile often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Compile, and based on these observations, apply Extract Interface, too.

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

557
                /** @scrutinizer ignore-type */ array_search($algorithm, get_phar_compression_algorithms(), true)
Loading history...
558
            )
559
        );
560
561
        $restoreLimit = $this->bumpOpenFileDescriptorLimit($box, $io);
562
563
        try {
564
            $extension = $box->compress($algorithm);
565
566
            if (null !== $extension) {
567
                $logger->log(
568
                    BuildLogger::CHEVRON_PREFIX,
569
                    sprintf(
570
                        '<info>Warning: the extension "%s" will now be required to execute the PHAR</info>',
571
                        $extension
572
                    )
573
                );
574
            }
575
        } catch (RuntimeException $exception) {
576
            $io->error($exception->getMessage());
577
578
            // Continue: the compression failure should not result in completely bailing out the compilation process
579
        } finally {
580
            $restoreLimit();
581
        }
582
    }
583
584
    /**
585
     * Bumps the maximum number of open file descriptor if necessary.
586
     *
587
     * @return callable callable to call to restore the original maximum number of open files descriptors
588
     */
589
    private function bumpOpenFileDescriptorLimit(Box $box, SymfonyStyle $io): callable
590
    {
591
        $filesCount = count($box) + 128;  // Add a little extra for good measure
592
593
        if (function_exists('posix_getrlimit') && function_exists('posix_setrlimit')) {
594
            $softLimit = posix_getrlimit()['soft openfiles'];
595
            $hardLimit = posix_getrlimit()['hard openfiles'];
596
597
            if ($softLimit < $filesCount) {
598
                $io->writeln(
599
                    sprintf(
600
                        '<info>[debug] Increased the maximum number of open file descriptors from ("%s", "%s") to ("%s", "%s")'
601
                        .'</info>',
602
                        $softLimit,
603
                        $hardLimit,
604
                        $filesCount,
605
                        'unlimited'
606
                    ),
607
                    OutputInterface::VERBOSITY_DEBUG
608
                );
609
610
                posix_setrlimit(
611
                    POSIX_RLIMIT_NOFILE,
612
                    $filesCount,
613
                    'unlimited' === $hardLimit ? POSIX_RLIMIT_INFINITY : $hardLimit
614
                );
615
            }
616
        } else {
617
            $io->writeln(
618
                '<info>[debug] Could not check the maximum number of open file descriptors: the functions "posix_getrlimit()" and '
619
                .'"posix_setrlimit" could not be found.</info>',
620
                OutputInterface::VERBOSITY_DEBUG
621
            );
622
        }
623
624
        return function () use ($io, $softLimit, $hardLimit): void {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $softLimit does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $hardLimit does not seem to be defined for all execution paths leading up to this point.
Loading history...
625
            if (function_exists('posix_setrlimit') && isset($softLimit, $hardLimit)) {
626
                posix_setrlimit(
627
                    POSIX_RLIMIT_NOFILE,
628
                    $softLimit,
629
                    'unlimited' === $hardLimit ? POSIX_RLIMIT_INFINITY : $hardLimit
630
                );
631
632
                $io->writeln(
633
                    '<info>[debug] Restored the maximum number of open file descriptors</info>',
634
                    OutputInterface::VERBOSITY_DEBUG
635
                );
636
            }
637
        };
638
    }
639
640
    private function signPhar(
641
        Configuration $config,
642
        Box $box,
643
        string $path,
644
        InputInterface $input,
645
        OutputInterface $output,
646
        BuildLogger $logger
647
    ): void {
648
        // sign using private key, if applicable
649
        //TODO: check that out
650
        remove($path.'.pubkey');
651
652
        $key = $config->getPrivateKeyPath();
653
654
        if (null === $key) {
655
            if (null !== ($algorithm = $config->getSigningAlgorithm())) {
0 ignored issues
show
introduced by
The condition null !== $algorithm = $c...->getSigningAlgorithm() is always true.
Loading history...
656
                $box->getPhar()->setSignatureAlgorithm($algorithm);
657
            }
658
659
            return;
660
        }
661
662
        $logger->log(
663
            BuildLogger::QUESTION_MARK_PREFIX,
664
            'Signing using a private key'
665
        );
666
667
        $passphrase = $config->getPrivateKeyPassphrase();
668
669
        if ($config->isPrivateKeyPrompt()) {
670
            if (false === $input->isInteractive()) {
671
                throw new RuntimeException(
672
                    sprintf(
673
                        'Accessing to the private key "%s" requires a passphrase but none provided. Either '
674
                        .'provide one or run this command in interactive mode.',
675
                        $key
676
                    )
677
                );
678
            }
679
680
            /** @var $dialog QuestionHelper */
681
            $dialog = $this->getHelper('question');
682
683
            $question = new Question('Private key passphrase:');
684
            $question->setHidden(false);
685
            $question->setHiddenFallback(false);
686
687
            $passphrase = $dialog->ask($input, $output, $question);
688
689
            $output->writeln('');
690
        }
691
692
        $box->signUsingFile($key, $passphrase);
693
    }
694
695
    private function correctPermissions(string $path, Configuration $config, BuildLogger $logger): void
696
    {
697
        if (null !== ($chmod = $config->getFileMode())) {
698
            $logger->log(
699
                BuildLogger::QUESTION_MARK_PREFIX,
700
                sprintf(
701
                    'Setting file permissions to <comment>%s</comment>',
702
                    '0'.decoct($chmod)
703
                )
704
            );
705
706
            chmod($path, $chmod);
707
        }
708
    }
709
710
    private function createStub(Configuration $config, ?string $main, bool $checkRequirements, BuildLogger $logger): StubGenerator
711
    {
712
        $stub = StubGenerator::create()
713
            ->alias($config->getAlias())
714
            ->index($main)
715
            ->intercept($config->isInterceptFileFuncs())
716
            ->checkRequirements($checkRequirements)
717
        ;
718
719
        if (null !== ($shebang = $config->getShebang())) {
720
            $logger->log(
721
                BuildLogger::MINUS_PREFIX,
722
                sprintf(
723
                    'Using shebang line: %s',
724
                    $shebang
725
                )
726
            );
727
728
            $stub->shebang($shebang);
729
        } else {
730
            $logger->log(
731
                BuildLogger::MINUS_PREFIX,
732
                'No shebang line'
733
            );
734
        }
735
736
        if (null !== ($bannerPath = $config->getStubBannerPath())) {
737
            $logger->log(
738
                BuildLogger::MINUS_PREFIX,
739
                sprintf(
740
                    'Using custom banner from file: %s',
741
                    $bannerPath
742
                )
743
            );
744
745
            $stub->banner($config->getStubBannerContents());
746
        } elseif (null !== ($banner = $config->getStubBannerContents())) {
747
            $logger->log(
748
                BuildLogger::MINUS_PREFIX,
749
                'Using banner:'
750
            );
751
752
            $bannerLines = explode("\n", $banner);
753
754
            foreach ($bannerLines as $bannerLine) {
755
                $logger->log(
756
                    BuildLogger::CHEVRON_PREFIX,
757
                    $bannerLine
758
                );
759
            }
760
761
            $stub->banner($banner);
762
        }
763
764
        return $stub;
765
    }
766
767
    private function logMap(MapFile $fileMapper, BuildLogger $logger): void
768
    {
769
        $map = $fileMapper->getMap();
770
771
        if ([] === $map) {
772
            return;
773
        }
774
775
        $logger->log(
776
            BuildLogger::QUESTION_MARK_PREFIX,
777
            'Mapping paths'
778
        );
779
780
        foreach ($map as $item) {
781
            foreach ($item as $match => $replace) {
782
                if ('' === $match) {
783
                    $match = '(all)';
784
                    $replace .= '/';
785
                }
786
787
                $logger->log(
788
                    BuildLogger::MINUS_PREFIX,
789
                    sprintf(
790
                        '%s <info>></info> %s',
791
                        $match,
792
                        $replace
793
                    )
794
                );
795
            }
796
        }
797
    }
798
799
    private function logEndBuilding(BuildLogger $logger, SymfonyStyle $io, Box $box, string $path, float $startTime): void
800
    {
801
        $logger->log(
802
            BuildLogger::STAR_PREFIX,
803
            'Done.'
804
        );
805
806
        $io->comment(
807
            sprintf(
808
                'PHAR: %s (%s)',
809
                $box->count() > 1 ? $box->count().' files' : $box->count().' file',
810
                formatted_filesize($path)
811
            )
812
            .PHP_EOL
813
            .'You can inspect the generated PHAR with the "<comment>info</comment>" command.'
814
        );
815
816
        $io->comment(
817
            sprintf(
818
                '<info>Memory usage: %.2fMB (peak: %.2fMB), time: %.2fs<info>',
819
                round(memory_get_usage() / 1024 / 1024, 2),
820
                round(memory_get_peak_usage() / 1024 / 1024, 2),
821
                round(microtime(true) - $startTime, 2)
822
            )
823
        );
824
    }
825
}
826