Passed
Pull Request — master (#200)
by Théo
03:01
created

Compile::correctPermissions()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 7
nc 2
nop 3
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the box project.
7
 *
8
 * (c) Kevin Herrera <[email protected]>
9
 *     Théo Fidry <[email protected]>
10
 *
11
 * This source file is subject to the MIT license that is bundled
12
 * with this source code in the file LICENSE.
13
 */
14
15
namespace KevinGH\Box\Console\Command;
16
17
use Amp\MultiReasonException;
18
use Assert\Assertion;
19
use BadMethodCallException;
20
use DateTimeImmutable;
21
use DateTimeZone;
22
use KevinGH\Box\Box;
23
use KevinGH\Box\Compactor;
24
use KevinGH\Box\Configuration;
25
use KevinGH\Box\Console\Logger\BuildLogger;
26
use KevinGH\Box\MapFile;
27
use KevinGH\Box\PhpSettingsHandler;
28
use KevinGH\Box\RequirementChecker\RequirementsDumper;
29
use KevinGH\Box\StubGenerator;
30
use RuntimeException;
31
use stdClass;
32
use Symfony\Component\Console\Helper\QuestionHelper;
33
use Symfony\Component\Console\Input\InputInterface;
34
use Symfony\Component\Console\Input\InputOption;
35
use Symfony\Component\Console\Logger\ConsoleLogger;
36
use Symfony\Component\Console\Output\OutputInterface;
37
use Symfony\Component\Console\Question\Question;
38
use Symfony\Component\Console\Style\SymfonyStyle;
39
use Symfony\Component\VarDumper\Cloner\VarCloner;
40
use Symfony\Component\VarDumper\Dumper\CliDumper;
41
use const DATE_ATOM;
42
use const POSIX_RLIMIT_INFINITY;
43
use const POSIX_RLIMIT_NOFILE;
44
use function array_shift;
45
use function count;
46
use function decoct;
47
use function explode;
48
use function function_exists;
49
use function get_class;
50
use function implode;
51
use function ini_get;
52
use function KevinGH\Box\disable_parallel_processing;
53
use function KevinGH\Box\FileSystem\chmod;
54
use function KevinGH\Box\FileSystem\dump_file;
55
use function KevinGH\Box\FileSystem\make_path_relative;
56
use function KevinGH\Box\FileSystem\remove;
57
use function KevinGH\Box\FileSystem\rename;
58
use function KevinGH\Box\formatted_filesize;
59
use function KevinGH\Box\get_phar_compression_algorithms;
60
use function posix_setrlimit;
61
use function putenv;
62
use function strlen;
63
use function strtolower;
64
use function substr;
65
use function trim;
66
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->removeExistingArtefacts($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
            $memoryInBytes = function (string $value): int {
211
                $unit = strtolower($value[strlen($value) - 1]);
212
213
                $value = (int) $value;
214
                switch ($unit) {
215
                    case 'g':
216
                        $value *= 1024;
217
                    // no break (cumulative multiplier)
218
                    case 'm':
219
                        $value *= 1024;
220
                    // no break (cumulative multiplier)
221
                    case 'k':
222
                        $value *= 1024;
223
                }
224
225
                return $value;
226
            };
227
228
            $memoryLimit = trim(ini_get('memory_limit'));
229
230
            // Increase memory_limit if it is lower than 500MB
231
            if ('-1' !== $memoryLimit && $memoryInBytes($memoryLimit) < 1024 * 1024 * 512 && false === getenv('BOX_MEMORY_LIMIT')) {
232
                @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

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

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