Passed
Push — master ( 813742...26b3f4 )
by Théo
02:16
created

Compile::bumpOpenFileDescriptorLimit()   C

Complexity

Conditions 8
Paths 3

Size

Total Lines 46
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 46
rs 5.5555
c 0
b 0
f 0
cc 8
eloc 32
nc 3
nop 2
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 POSIX_RLIMIT_INFINITY;
42
use const POSIX_RLIMIT_NOFILE;
43
use function array_shift;
44
use function count;
45
use function decoct;
46
use function explode;
47
use function function_exists;
48
use function get_class;
49
use function implode;
50
use function ini_get;
51
use function KevinGH\Box\disable_parallel_processing;
52
use function KevinGH\Box\FileSystem\chmod;
53
use function KevinGH\Box\FileSystem\dump_file;
54
use function KevinGH\Box\FileSystem\make_path_relative;
55
use function KevinGH\Box\FileSystem\remove;
56
use function KevinGH\Box\FileSystem\rename;
57
use function KevinGH\Box\formatted_filesize;
58
use function KevinGH\Box\get_phar_compression_algorithms;
59
use function KevinGH\Box\memory_to_bytes;
60
use function posix_setrlimit;
61
use function putenv;
62
use function sprintf;
63
use function strlen;
64
use function substr;
65
use function trim;
66
67
/**
68
 * @final
69
 * @private
70
 * TODO: make final when Build is removed
71
 */
72
class Compile extends Configurable
73
{
74
    use ChangeableWorkingDirectory;
75
76
    private const HELP = <<<'HELP'
77
The <info>%command.name%</info> command will compile code in a new PHAR based on a variety of settings.
78
<comment>
79
  This command relies on a configuration file for loading
80
  PHAR packaging settings. If a configuration file is not
81
  specified through the <info>--config|-c</info> option, one of
82
  the following files will be used (in order): <info>box.json</info>,
83
  <info>box.json.dist</info>
84
</comment>
85
The configuration file is actually a JSON object saved to a file. For more
86
information check the documentation online:
87
<comment>
88
  https://github.com/humbug/box
89
</comment>
90
HELP;
91
92
    private const DEBUG_OPTION = 'debug';
93
    private const NO_PARALLEL_PROCESSING_OPTION = 'no-parallel';
94
    private const NO_RESTART_OPTION = 'no-restart';
95
    private const DEV_OPTION = 'dev';
96
    private const NO_CONFIG_OPTION = 'no-config';
97
98
    private const DEBUG_DIR = '.box_dump';
99
100
    /**
101
     * {@inheritdoc}
102
     */
103
    protected function configure(): void
104
    {
105
        parent::configure();
106
107
        $this->setName('compile');
108
        $this->setDescription('Compile an application into a PHAR');
109
        $this->setHelp(self::HELP);
110
111
        $this->addOption(
112
            self::DEBUG_OPTION,
113
            null,
114
            InputOption::VALUE_NONE,
115
            'Dump the files added to the PHAR in a `'.self::DEBUG_DIR.'` directory'
116
        );
117
        $this->addOption(
118
            self::NO_PARALLEL_PROCESSING_OPTION,
119
            null,
120
            InputOption::VALUE_NONE,
121
            'Disable the parallel processing'
122
        );
123
        $this->addOption(
124
            self::NO_RESTART_OPTION,
125
            null,
126
            InputOption::VALUE_NONE,
127
            'Do not restart the PHP process. Box restarts the process by default to disable xdebug and set `phar.readonly=0`'
128
        );
129
        $this->addOption(
130
            self::DEV_OPTION,
131
            null,
132
            InputOption::VALUE_NONE,
133
            'Skips the compression step'
134
        );
135
        $this->addOption(
136
            self::NO_CONFIG_OPTION,
137
            null,
138
            InputOption::VALUE_NONE,
139
            'Ignore the config file even when one is specified with the --config option'
140
        );
141
142
        $this->configureWorkingDirOption();
143
    }
144
145
    /**
146
     * {@inheritdoc}
147
     */
148
    protected function execute(InputInterface $input, OutputInterface $output): void
149
    {
150
        $io = new SymfonyStyle($input, $output);
151
152
        if ($input->getOption(self::NO_RESTART_OPTION)) {
153
            putenv('BOX_ALLOW_XDEBUG=1');
154
        }
155
156
        if ($debug = $input->getOption(self::DEBUG_OPTION)) {
157
            $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
158
        }
159
160
        self::checkPhpSettings($io, $output);
161
162
        if ($input->getOption(self::NO_PARALLEL_PROCESSING_OPTION)) {
163
            disable_parallel_processing();
164
            $io->writeln('<info>[debug] Disabled parallel processing</info>', OutputInterface::VERBOSITY_DEBUG);
165
        }
166
167
        $this->changeWorkingDirectory($input);
168
169
        $io->writeln($this->getApplication()->getHelp());
170
        $io->writeln('');
171
172
        $config = $input->getOption(self::NO_CONFIG_OPTION)
173
            ? Configuration::create(null, new stdClass())
174
            : $this->getConfig($input, $output, true)
175
        ;
176
        $path = $config->getOutputPath();
177
178
        $logger = new BuildLogger($io);
179
180
        $startTime = microtime(true);
181
182
        $this->removeExistingArtifacts($config, $logger, $debug);
183
184
        $logger->logStartBuilding($path);
185
186
        $this->createPhar($config, $input, $output, $logger, $io, $debug);
187
188
        $this->correctPermissions($path, $config, $logger);
189
190
        $logger->log(
191
            BuildLogger::STAR_PREFIX,
192
            'Done.'
193
        );
194
195
        $io->comment('You can inspect the generated PHAR with the "<comment>info</comment>" command.');
196
197
        $this->logCommandResourcesUsage($io, $path, $startTime);
198
    }
199
200
    /**
201
     * Taken from Composer.
202
     *
203
     * @see https://github.com/composer/composer/blob/34c371f5f23e25eb9aa54ccc65136cf50930612e/bin/composer#L20-L50
204
     */
205
    private static function checkPhpSettings(SymfonyStyle $io, OutputInterface $output): void
206
    {
207
        (new PhpSettingsHandler(new ConsoleLogger($output)))->check();
208
209
        if (function_exists('ini_set')) {
210
            $memoryLimit = trim(ini_get('memory_limit'));
211
212
            // Increase memory_limit if it is lower than 500MB
213
            if ('-1' !== $memoryLimit && memory_to_bytes($memoryLimit) < 1024 * 1024 * 512 && false === getenv('BOX_MEMORY_LIMIT')) {
214
                @ini_set('memory_limit', '512M');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for ini_set(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

214
                /** @scrutinizer ignore-unhandled */ @ini_set('memory_limit', '512M');

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
215
216
                $io->writeln(
217
                    sprintf(
218
                        '<info>[debug] Bumped the memory limit from "%s" to "%s".</info>',
219
                        $memoryLimit,
220
                        '512M'
221
                    ),
222
                    OutputInterface::VERBOSITY_DEBUG
223
                );
224
            }
225
226
            // Set user defined memory limit
227
            if ($newMemoryLimit = getenv('BOX_MEMORY_LIMIT')) {
228
                @ini_set('memory_limit', $newMemoryLimit);
229
230
                $io->writeln(
231
                    sprintf(
232
                        '<info>[debug] Bumped the memory limit from "%s" to BOX_MEMORY_LIMIT="%s".</info>',
233
                        $memoryLimit,
234
                        $newMemoryLimit
235
                    ),
236
                    OutputInterface::VERBOSITY_DEBUG
237
                );
238
            }
239
240
            // Note that there is no need to restore since those changes will be applied only to the current process which will die once
241
            // the compilation is done.
242
        }
243
    }
244
245
    private function createPhar(
246
        Configuration $config,
247
        InputInterface $input,
248
        OutputInterface $output,
249
        BuildLogger $logger,
250
        SymfonyStyle $io,
251
        bool $debug
252
    ): void {
253
        $box = Box::create(
254
            $config->getTmpOutputPath()
255
        );
256
        $box->startBuffering();
257
258
        $this->registerReplacementValues($config, $box, $logger);
259
        $this->registerCompactors($config, $box, $logger);
260
        $this->registerFileMapping($config, $box, $logger);
261
262
        // Registering the main script _before_ adding the rest if of the files is _very_ important. The temporary
263
        // file used for debugging purposes and the Composer dump autoloading will not work correctly otherwise.
264
        $main = $this->registerMainScript($config, $box, $logger);
265
266
        $check = $this->registerRequirementsChecker($config, $box, $logger);
267
268
        $this->addFiles($config, $box, $logger, $io);
269
270
        $this->registerStub($config, $box, $main, $check, $logger);
271
        $this->configureMetadata($config, $box, $logger);
272
273
        $box->endBuffering($config->dumpAutoload());
274
275
        $this->configureCompressionAlgorithm($config, $box, $input->getOption(self::DEV_OPTION), $io, $logger);
276
277
        if ($debug) {
278
            $box->getPhar()->extractTo(self::DEBUG_DIR, null, true);
279
        }
280
281
        $this->signPhar($config, $box, $config->getTmpOutputPath(), $input, $output, $logger);
282
283
        if ($config->getTmpOutputPath() !== $config->getOutputPath()) {
284
            rename($config->getTmpOutputPath(), $config->getOutputPath());
285
        }
286
    }
287
288
    private function removeExistingArtifacts(Configuration $config, BuildLogger $logger, bool $debug): void
289
    {
290
        $path = $config->getOutputPath();
291
292
        if ($debug) {
293
            remove(self::DEBUG_DIR);
294
295
            $date = (new DateTimeImmutable('now', new DateTimeZone('UTC')))->format(DATE_ATOM);
296
            $file = null !== $config->getFile() ? $config->getFile() : 'No config file';
297
298
            remove(self::DEBUG_DIR);
299
300
            dump_file(
301
                self::DEBUG_DIR.'/.box_configuration',
302
                <<<EOF
303
//
304
// Processed content of the configuration file "$file" dumped for debugging purposes
305
// Time: $date
306
//
307
308
309
EOF
310
                .(new CliDumper())->dump(
311
                    (new VarCloner())->cloneVar($config),
312
                    true
313
                )
314
            );
315
        }
316
317
        if (false === file_exists($path)) {
318
            return;
319
        }
320
321
        $logger->log(
322
            BuildLogger::QUESTION_MARK_PREFIX,
323
            sprintf(
324
                'Removing the existing PHAR "%s"',
325
                $path
326
            )
327
        );
328
329
        remove($path);
330
    }
331
332
    private function registerReplacementValues(Configuration $config, Box $box, BuildLogger $logger): void
333
    {
334
        $values = $config->getProcessedReplacements();
335
336
        if ([] === $values) {
337
            return;
338
        }
339
340
        $logger->log(
341
            BuildLogger::QUESTION_MARK_PREFIX,
342
            'Setting replacement values'
343
        );
344
345
        foreach ($values as $key => $value) {
346
            $logger->log(
347
                BuildLogger::PLUS_PREFIX,
348
                sprintf(
349
                    '%s: %s',
350
                    $key,
351
                    $value
352
                )
353
            );
354
        }
355
356
        $box->registerPlaceholders($values);
357
    }
358
359
    private function registerCompactors(Configuration $config, Box $box, BuildLogger $logger): void
360
    {
361
        $compactors = $config->getCompactors();
362
363
        if ([] === $compactors) {
364
            $logger->log(
365
                BuildLogger::QUESTION_MARK_PREFIX,
366
                'No compactor to register'
367
            );
368
369
            return;
370
        }
371
372
        $logger->log(
373
            BuildLogger::QUESTION_MARK_PREFIX,
374
            'Registering compactors'
375
        );
376
377
        $logCompactors = function (Compactor $compactor) use ($logger): void {
378
            $compactorClassParts = explode('\\', get_class($compactor));
379
380
            if ('_HumbugBox' === substr($compactorClassParts[0], 0, strlen('_HumbugBox'))) {
381
                // Keep the non prefixed class name for the user
382
                array_shift($compactorClassParts);
383
            }
384
385
            $logger->log(
386
                BuildLogger::PLUS_PREFIX,
387
                implode('\\', $compactorClassParts)
388
            );
389
        };
390
391
        array_map($logCompactors, $compactors);
392
393
        $box->registerCompactors($compactors);
394
    }
395
396
    private function registerFileMapping(Configuration $config, Box $box, BuildLogger $logger): void
397
    {
398
        $fileMapper = $config->getFileMapper();
399
400
        $this->logMap($fileMapper, $logger);
401
402
        $box->registerFileMapping(
403
            $config->getBasePath(),
404
            $fileMapper
405
        );
406
    }
407
408
    private function addFiles(Configuration $config, Box $box, BuildLogger $logger, SymfonyStyle $io): void
409
    {
410
        $logger->log(BuildLogger::QUESTION_MARK_PREFIX, 'Adding binary files');
411
412
        $count = count($config->getBinaryFiles());
413
414
        $box->addFiles($config->getBinaryFiles(), true);
415
416
        $logger->log(
417
            BuildLogger::CHEVRON_PREFIX,
418
            0 === $count
419
                ? 'No file found'
420
                : sprintf('%d file(s)', $count)
421
        );
422
423
        $logger->log(BuildLogger::QUESTION_MARK_PREFIX, 'Adding files');
424
425
        $count = count($config->getFiles());
426
427
        try {
428
            $box->addFiles($config->getFiles(), false);
429
        } catch (MultiReasonException $exception) {
430
            // This exception is handled a different way to give me meaningful feedback to the user
431
            foreach ($exception->getReasons() as $reason) {
432
                $io->error($reason);
433
            }
434
435
            throw $exception;
436
        }
437
438
        $logger->log(
439
            BuildLogger::CHEVRON_PREFIX,
440
            0 === $count
441
                ? 'No file found'
442
                : sprintf('%d file(s)', $count)
443
        );
444
    }
445
446
    private function registerMainScript(Configuration $config, Box $box, BuildLogger $logger): string
447
    {
448
        $main = $config->getMainScriptPath();
449
450
        $logger->log(
451
            BuildLogger::QUESTION_MARK_PREFIX,
452
            sprintf(
453
                'Adding main file: %s',
454
                $main
455
            )
456
        );
457
458
        $localMain = $box->addFile(
459
            $main,
460
            $config->getMainScriptContents()
461
        );
462
463
        $relativeMain = make_path_relative($main, $config->getBasePath());
464
465
        if ($localMain !== $relativeMain) {
466
            $logger->log(
467
                BuildLogger::CHEVRON_PREFIX,
468
                $localMain
469
            );
470
        }
471
472
        return $localMain;
473
    }
474
475
    private function registerRequirementsChecker(Configuration $config, Box $box, BuildLogger $logger): bool
476
    {
477
        if (false === $config->checkRequirements()) {
478
            return false;
479
        }
480
481
        $logger->log(
482
            BuildLogger::QUESTION_MARK_PREFIX,
483
            'Adding requirements checker'
484
        );
485
486
        $checkFiles = RequirementsDumper::dump(
487
            $config->getComposerJsonDecodedContents() ?? [],
488
            $config->getComposerLockDecodedContents() ?? [],
489
            $config->getCompressionAlgorithm()
490
        );
491
492
        foreach ($checkFiles as $fileWithContents) {
493
            [$file, $contents] = $fileWithContents;
494
495
            $box->addFile('.box/'.$file, $contents, true);
496
        }
497
498
        return true;
499
    }
500
501
    private function registerStub(Configuration $config, Box $box, string $main, bool $checkRequirements, BuildLogger $logger): void
502
    {
503
        if ($config->isStubGenerated()) {
504
            $logger->log(
505
                BuildLogger::QUESTION_MARK_PREFIX,
506
                'Generating new stub'
507
            );
508
509
            $stub = $this->createStub($config, $main, $checkRequirements, $logger);
510
511
            $box->getPhar()->setStub($stub->generate());
512
513
            return;
514
        }
515
        if (null !== ($stub = $config->getStubPath())) {
516
            $logger->log(
517
                BuildLogger::QUESTION_MARK_PREFIX,
518
                sprintf(
519
                    'Using stub file: %s',
520
                    $stub
521
                )
522
            );
523
524
            $box->registerStub($stub);
525
526
            return;
527
        }
528
529
        // TODO: add warning that the check requirements could not be added
530
        $aliasWasAdded = $box->getPhar()->setAlias($config->getAlias());
531
532
        Assertion::true(
533
            $aliasWasAdded,
534
            sprintf(
535
                'The alias "%s" is invalid. See Phar::setAlias() documentation for more information.',
536
                $config->getAlias()
537
            )
538
        );
539
540
        $box->getPhar()->setDefaultStub($main);
541
542
        $logger->log(
543
            BuildLogger::QUESTION_MARK_PREFIX,
544
            'Using default stub'
545
        );
546
    }
547
548
    private function configureMetadata(Configuration $config, Box $box, BuildLogger $logger): void
549
    {
550
        if (null !== ($metadata = $config->getMetadata())) {
551
            $logger->log(
552
                BuildLogger::QUESTION_MARK_PREFIX,
553
                'Setting metadata'
554
            );
555
556
            $logger->log(
557
                BuildLogger::MINUS_PREFIX,
558
                is_string($metadata) ? $metadata : var_export($metadata, true)
559
            );
560
561
            $box->getPhar()->setMetadata($metadata);
562
        }
563
    }
564
565
    private function configureCompressionAlgorithm(Configuration $config, Box $box, bool $dev, SymfonyStyle $io, BuildLogger $logger): void
566
    {
567
        if (null === ($algorithm = $config->getCompressionAlgorithm())) {
568
            $logger->log(
569
                BuildLogger::QUESTION_MARK_PREFIX,
570
                $dev
571
                    ? 'No compression'
572
                    : '<error>No compression</error>'
573
            );
574
575
            return;
576
        }
577
578
        $logger->log(
579
            BuildLogger::QUESTION_MARK_PREFIX,
580
            sprintf(
581
                'Compressing with the algorithm "<comment>%s</comment>"',
582
                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

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

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
827
            sprintf(
828
                "<info>PHAR size: %s\nMemory usage: %.2fMB (peak: %.2fMB), time: %.2fs<info>",
829
                formatted_filesize($path),
830
                round(memory_get_usage() / 1024 / 1024, 2),
831
                round(memory_get_peak_usage() / 1024 / 1024, 2),
832
                round(microtime(true) - $startTime, 2)
833
            )
834
        );
835
    }
836
}
837