Passed
Push — master ( 4c3962...96d1ab )
by Théo
03:03
created

Compile::executeCommand()   B

Complexity

Conditions 6
Paths 32

Size

Total Lines 49
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

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

623
                /** @scrutinizer ignore-type */ array_search($algorithm, get_phar_compression_algorithms(), true)
Loading history...
624
            )
625
        );
626
627
        $restoreLimit = self::bumpOpenFileDescriptorLimit($box, $io);
628
629
        try {
630
            $extension = $box->compress($algorithm);
631
632
            if (null !== $extension) {
633
                $logger->log(
634
                    CompilerLogger::CHEVRON_PREFIX,
635
                    sprintf(
636
                        '<info>Warning: the extension "%s" will now be required to execute the PHAR</info>',
637
                        $extension
638
                    )
639
                );
640
            }
641
        } catch (RuntimeException $exception) {
642
            $io->error($exception->getMessage());
643
644
            // Continue: the compression failure should not result in completely bailing out the compilation process
645
        } finally {
646
            $restoreLimit();
647
        }
648
    }
649
650
    /**
651
     * Bumps the maximum number of open file descriptor if necessary.
652
     *
653
     * @return Closure callable to call to restore the original maximum number of open files descriptors
654
     */
655
    private static function bumpOpenFileDescriptorLimit(Box $box, IO $io): Closure
656
    {
657
        $filesCount = count($box) + 128;  // Add a little extra for good measure
658
659
        if (false === function_exists('posix_getrlimit') || false === function_exists('posix_setrlimit')) {
660
            $io->writeln(
661
                '<info>[debug] Could not check the maximum number of open file descriptors: the functions "posix_getrlimit()" and '
662
                .'"posix_setrlimit" could not be found.</info>',
663
                OutputInterface::VERBOSITY_DEBUG
664
            );
665
666
            return static function (): void {};
667
        }
668
669
        $softLimit = posix_getrlimit()['soft openfiles'];
670
        $hardLimit = posix_getrlimit()['hard openfiles'];
671
672
        if ($softLimit >= $filesCount) {
673
            return static function (): void {};
674
        }
675
676
        $io->writeln(
677
            sprintf(
678
                '<info>[debug] Increased the maximum number of open file descriptors from ("%s", "%s") to ("%s", "%s")'
679
                .'</info>',
680
                $softLimit,
681
                $hardLimit,
682
                $filesCount,
683
                'unlimited'
684
            ),
685
            OutputInterface::VERBOSITY_DEBUG
686
        );
687
688
        posix_setrlimit(
689
            POSIX_RLIMIT_NOFILE,
690
            $filesCount,
691
            'unlimited' === $hardLimit ? POSIX_RLIMIT_INFINITY : $hardLimit
692
        );
693
694
        return static function () use ($io, $softLimit, $hardLimit): void {
695
            if (function_exists('posix_setrlimit') && isset($softLimit, $hardLimit)) {
696
                posix_setrlimit(
697
                    POSIX_RLIMIT_NOFILE,
698
                    $softLimit,
699
                    'unlimited' === $hardLimit ? POSIX_RLIMIT_INFINITY : $hardLimit
700
                );
701
702
                $io->writeln(
703
                    '<info>[debug] Restored the maximum number of open file descriptors</info>',
704
                    OutputInterface::VERBOSITY_DEBUG
705
                );
706
            }
707
        };
708
    }
709
710
    private function signPhar(
711
        Configuration $config,
712
        Box $box,
713
        string $path,
714
        IO $io,
715
        CompilerLogger $logger
716
    ): void {
717
        // Sign using private key when applicable
718
        remove($path.'.pubkey');
719
720
        $key = $config->getPrivateKeyPath();
721
722
        if (null === $key) {
723
            if (null !== ($algorithm = $config->getSigningAlgorithm())) {
0 ignored issues
show
introduced by
The condition null !== $algorithm = $c...->getSigningAlgorithm() is always true.
Loading history...
724
                $box->getPhar()->setSignatureAlgorithm($algorithm);
725
            }
726
727
            return;
728
        }
729
730
        $logger->log(
731
            CompilerLogger::QUESTION_MARK_PREFIX,
732
            'Signing using a private key'
733
        );
734
735
        $passphrase = $config->getPrivateKeyPassphrase();
736
737
        if ($config->promptForPrivateKey()) {
738
            if (false === $io->isInteractive()) {
739
                throw new RuntimeException(
740
                    sprintf(
741
                        'Accessing to the private key "%s" requires a passphrase but none provided. Either '
742
                        .'provide one or run this command in interactive mode.',
743
                        $key
744
                    )
745
                );
746
            }
747
748
            $question = new Question('Private key passphrase');
749
            $question->setHidden(false);
750
            $question->setHiddenFallback(false);
751
752
            $passphrase = $io->askQuestion($question);
753
754
            $io->writeln('');
755
        }
756
757
        $box->signUsingFile($key, $passphrase);
758
    }
759
760
    private function correctPermissions(string $path, Configuration $config, CompilerLogger $logger): void
761
    {
762
        if (null !== ($chmod = $config->getFileMode())) {
763
            $logger->log(
764
                CompilerLogger::QUESTION_MARK_PREFIX,
765
                sprintf(
766
                    'Setting file permissions to <comment>%s</comment>',
767
                    '0'.decoct($chmod)
768
                )
769
            );
770
771
            chmod($path, $chmod);
772
        }
773
    }
774
775
    private function createStub(
776
        Configuration $config,
777
        ?string $main,
778
        bool $checkRequirements,
779
        CompilerLogger $logger
780
    ): string {
781
        $stub = StubGenerator::create()
782
            ->alias($config->getAlias())
783
            ->index($main)
784
            ->intercept($config->isInterceptFileFuncs())
785
            ->checkRequirements($checkRequirements)
786
        ;
787
788
        if (null !== ($shebang = $config->getShebang())) {
789
            $logger->log(
790
                CompilerLogger::MINUS_PREFIX,
791
                sprintf(
792
                    'Using shebang line: %s',
793
                    $shebang
794
                )
795
            );
796
797
            $stub->shebang($shebang);
798
        } else {
799
            $logger->log(
800
                CompilerLogger::MINUS_PREFIX,
801
                'No shebang line'
802
            );
803
        }
804
805
        if (null !== ($bannerPath = $config->getStubBannerPath())) {
806
            $logger->log(
807
                CompilerLogger::MINUS_PREFIX,
808
                sprintf(
809
                    'Using custom banner from file: %s',
810
                    $bannerPath
811
                )
812
            );
813
814
            $stub->banner($config->getStubBannerContents());
815
        } elseif (null !== ($banner = $config->getStubBannerContents())) {
816
            $logger->log(
817
                CompilerLogger::MINUS_PREFIX,
818
                'Using banner:'
819
            );
820
821
            $bannerLines = explode("\n", $banner);
822
823
            foreach ($bannerLines as $bannerLine) {
824
                $logger->log(
825
                    CompilerLogger::CHEVRON_PREFIX,
826
                    $bannerLine
827
                );
828
            }
829
830
            $stub->banner($banner);
831
        }
832
833
        return $stub->generate();
834
    }
835
836
    private function logMap(MapFile $fileMapper, CompilerLogger $logger): void
837
    {
838
        $map = $fileMapper->getMap();
839
840
        if ([] === $map) {
841
            return;
842
        }
843
844
        $logger->log(
845
            CompilerLogger::QUESTION_MARK_PREFIX,
846
            'Mapping paths'
847
        );
848
849
        foreach ($map as $item) {
850
            foreach ($item as $match => $replace) {
851
                if ('' === $match) {
852
                    $match = '(all)';
853
                    $replace .= '/';
854
                }
855
856
                $logger->log(
857
                    CompilerLogger::MINUS_PREFIX,
858
                    sprintf(
859
                        '%s <info>></info> %s',
860
                        $match,
861
                        $replace
862
                    )
863
                );
864
            }
865
        }
866
    }
867
868
    private function logEndBuilding(
869
        Configuration $config,
870
        CompilerLogger $logger,
871
        IO $io,
872
        Box $box,
873
        string $path,
874
        float $startTime
875
    ): void {
876
        $logger->log(
877
            CompilerLogger::STAR_PREFIX,
878
            'Done.'
879
        );
880
        $io->newLine();
881
882
        MessageRenderer::render($io, $config->getRecommendations(), $config->getWarnings());
883
884
        $io->comment(
885
            sprintf(
886
                'PHAR: %s (%s)',
887
                $box->count() > 1 ? $box->count().' files' : $box->count().' file',
888
                format_size(
889
                    filesize($path)
890
                )
891
            )
892
            .PHP_EOL
893
            .'You can inspect the generated PHAR with the "<comment>info</comment>" command.'
894
        );
895
896
        $io->comment(
897
            sprintf(
898
                '<info>Memory usage: %s (peak: %s), time: %s<info>',
899
                format_size(memory_get_usage()),
900
                format_size(memory_get_peak_usage()),
901
                format_time(microtime(true) - $startTime)
902
            )
903
        );
904
    }
905
906
    private function generateDockerFile(OutputInterface $output): void
907
    {
908
        $generateDockerFileCommand = $this->getApplication()->find('docker');
909
910
        Assertion::isInstanceOf($generateDockerFileCommand, GenerateDockerFile::class);
911
912
        $input = new StringInput('');
913
        $input->setInteractive(false);
914
915
        $generateDockerFileCommand->run($input, $output);
916
    }
917
}
918