Completed
Push — master ( 59ee96...3c9696 )
by Bernhard
15:47
created

PuliPlugin::activate()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 47
Code Lines 29

Duplication

Lines 23
Ratio 48.94 %

Importance

Changes 5
Bugs 2 Features 1
Metric Value
c 5
b 2
f 1
dl 23
loc 47
rs 8.5126
cc 5
eloc 29
nc 5
nop 2
1
<?php
2
3
/*
4
 * This file is part of the puli/composer-plugin package.
5
 *
6
 * (c) Bernhard Schussek <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Puli\ComposerPlugin;
13
14
use Composer\Composer;
15
use Composer\EventDispatcher\EventSubscriberInterface;
16
use Composer\IO\IOInterface;
17
use Composer\Package\AliasPackage;
18
use Composer\Package\PackageInterface;
19
use Composer\Plugin\PluginInterface;
20
use Composer\Script\CommandEvent;
21
use Composer\Script\Event;
22
use Composer\Script\ScriptEvents;
23
use RuntimeException;
24
use Webmozart\PathUtil\Path;
25
26
/**
27
 * A Puli plugin for Composer.
28
 *
29
 * The plugin updates the Puli package repository based on the Composer
30
 * packages whenever `composer install` or `composer update` is executed.
31
 *
32
 * @since  1.0
33
 *
34
 * @author Bernhard Schussek <[email protected]>
35
 */
36
class PuliPlugin implements PluginInterface, EventSubscriberInterface
37
{
38
    /**
39
     * The minimum version of the Puli CLI.
40
     */
41
    const MIN_CLI_VERSION = '1.0.0-beta9';
42
43
    /**
44
     * The maximum version of the Puli CLI.
45
     */
46
    const MAX_CLI_VERSION = '1.999.99999';
47
48
    /**
49
     * The name of the installer.
50
     */
51
    const INSTALLER_NAME = 'composer';
52
53
    /**
54
     * @var bool
55
     */
56
    private $initialized = false;
57
58
    /**
59
     * @var PuliRunner
60
     */
61
    private $puliRunner;
62
63
    /**
64
     * @var string
65
     */
66
    private $rootDir;
67
68
    /**
69
     * @var bool
70
     */
71
    private $runPostInstall = true;
72
73
    /**
74
     * @var bool
75
     */
76
    private $runPostAutoloadDump = true;
77
78
    /**
79
     * {@inheritdoc}
80
     */
81
    public static function getSubscribedEvents()
82
    {
83
        return array(
84
            ScriptEvents::POST_INSTALL_CMD => 'postInstall',
85
            ScriptEvents::POST_UPDATE_CMD => 'postInstall',
86
            ScriptEvents::POST_AUTOLOAD_DUMP => 'postAutoloadDump',
87
        );
88
    }
89
90
    public function __construct(PuliRunner $puliRunner = null)
91
    {
92
        $this->puliRunner = $puliRunner;
93
        $this->rootDir = Path::normalize(getcwd());
94
    }
95
96
    /**
97
     * {@inheritdoc}
98
     */
99
    public function activate(Composer $composer, IOInterface $io)
100
    {
101
        // Verify if Puli has the right version
102
        try {
103
            $versionString = $this->puliRunner->run('-V');
104
        } catch (PuliRunnerException $e) {
105
            $this->printWarning($io, 'Could not determine Puli version', $e);
106
107
            return;
108
        }
109
110
        if (!preg_match('~\d+\.\d+\.\d+(-\w+)?~', $versionString, $matches)) {
111
            $this->printWarning($io, sprintf(
112
                'Could not determine Puli version. "puli -V" returned: %s',
113
                $versionString
114
            ));
115
116
            return;
117
        }
118
119 View Code Duplication
        if (version_compare($matches[0], self::MIN_CLI_VERSION, '<')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
120
            $this->printWarning($io, sprintf(
121
                'Found an unsupported version of the Puli CLI: %s. Please '.
122
                'upgrade to version %s or higher. You can also install the '.
123
                'puli/cli dependency at version %s in your project.',
124
                $matches[0],
125
                self::MIN_CLI_VERSION,
126
                self::MIN_CLI_VERSION
127
            ));
128
129
            return;
130
        }
131
132 View Code Duplication
        if (version_compare($matches[0], self::MAX_CLI_VERSION, '>')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
133
            $this->printWarning($io, sprintf(
134
                'Found an unsupported version of the Puli CLI: %s. Please '.
135
                'downgrade to a lower version. You can also install the '.
136
                'puli/cli dependency at a lower version than %s in your project.',
137
                $matches[0],
138
                self::MAX_CLI_VERSION
139
            ));
140
141
            return;
142
        }
143
144
        $composer->getEventDispatcher()->addSubscriber($this);
145
    }
146
147
    public function postAutoloadDump(Event $event)
148
    {
149
        // Plugin has been uninstalled
150
        if (!file_exists(__FILE__)) {
151
            return;
152
        }
153
154
        if (!$this->initialized) {
155
            $this->initialize($event->getComposer(), $event->getIO());
156
        }
157
158
        // This method is called twice. Run it only once.
159
        if (!$this->runPostAutoloadDump) {
160
            return;
161
        }
162
163
        $this->runPostAutoloadDump = false;
164
165
        $io = $event->getIO();
166
167
        $compConfig = $event->getComposer()->getConfig();
168
        $vendorDir = $compConfig->get('vendor-dir');
169
170
        // On TravisCI, $vendorDir is a relative path. Probably an old Composer
171
        // build or something. Usually, $vendorDir should be absolute already.
172
        $vendorDir = Path::makeAbsolute($vendorDir, $this->rootDir);
173
174
        $autoloadFile = $vendorDir.'/autoload.php';
175
        $classMapFile = $vendorDir.'/composer/autoload_classmap.php';
176
177
        try {
178
            $factoryClass = $this->getConfigKey('factory.in.class');
179
            $factoryFile = $this->getConfigKey('factory.in.file');
180
        } catch (PuliRunnerException $e) {
181
            $this->printWarning($io, 'Could not load Puli configuration', $e);
182
183
            return;
184
        }
185
186
        $factoryFile = Path::makeAbsolute($factoryFile, $this->rootDir);
0 ignored issues
show
Bug introduced by
It seems like $factoryFile can also be of type boolean or null; however, Webmozart\PathUtil\Path::makeAbsolute() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
187
188
        $this->insertFactoryClassConstant($io, $autoloadFile, $factoryClass);
189
        $this->insertFactoryClassMap($io, $classMapFile, $vendorDir, $factoryClass, $factoryFile);
190
        $this->setBootstrapFile($io, $autoloadFile);
191
    }
192
193
    /**
194
     * Updates the Puli repository after Composer installations/updates.
195
     *
196
     * @param CommandEvent $event The Composer event.
197
     */
198
    public function postInstall(CommandEvent $event)
199
    {
200
        // Plugin has been uninstalled
201
        if (!file_exists(__FILE__)) {
202
            return;
203
        }
204
205
        if (!$this->initialized) {
206
            $this->initialize($event->getComposer(), $event->getIO());
207
        }
208
209
        // This method is called twice. Run it only once.
210
        if (!$this->runPostInstall) {
211
            return;
212
        }
213
214
        $this->runPostInstall = false;
215
216
        $io = $event->getIO();
217
218
        $io->write('<info>Looking for updated Puli packages</info>');
219
220
        $rootPackage = $event->getComposer()->getPackage();
221
        $composerPackages = $this->loadComposerPackages($event->getComposer());
222
        $prodPackageNames = $this->filterProdPackageNames($composerPackages, $rootPackage);
223
        $env = $event->isDevMode() ? PuliPackage::ENV_DEV : PuliPackage::ENV_PROD;
224
225
        try {
226
            $puliPackages = $this->loadPuliPackages();
227
        } catch (PuliRunnerException $e) {
228
            $this->printWarning($io, 'Could not load Puli packages', $e);
229
230
            return;
231
        }
232
233
        // Don't remove non-existing packages in production environment
234
        // Removed packages could be dev dependencies (i.e. "require-dev"
235
        // of the root package or "require" of another dev dependency), and
236
        // we can't find out whether they are since Composer doesn't load them
237
        if (PuliPackage::ENV_PROD !== $env) {
238
            $this->removeRemovedPackages($composerPackages, $puliPackages, $io);
239
        }
240
241
        $this->installNewPackages($composerPackages, $prodPackageNames, $puliPackages, $io, $event->getComposer());
242
243
        // Don't print warnings for non-existing packages in production
244
        if (PuliPackage::ENV_PROD !== $env) {
245
            $this->checkForNotFoundErrors($puliPackages, $io);
246
        }
247
248
        $this->checkForNotLoadableErrors($puliPackages, $io);
249
        $this->adoptComposerName($puliPackages, $io, $event->getComposer());
250
        $this->buildPuli($io);
251
    }
252
253
    /**
254
     * @param Composer    $composer
255
     * @param IOInterface $io
256
     */
257
    private function initialize(Composer $composer, IOInterface $io)
258
    {
259
        // This method must be run after all packages are installed, otherwise
260
        // it could be that the puli/cli is not yet installed and hence the
261
        // CLI is not available
262
263
        // Previously, this was called in activate(), which is called
264
        // immediately after installing the plugin, but potentially before
265
        // installing the CLI
266
267
        $this->initialized = true;
268
269
        // Keep the manually set runner
270
        if ($this->puliRunner) {
271
            return;
272
        }
273
274
        try {
275
            // Add Composer's bin directory in case the "puli" executable is
276
            // installed with Composer
277
            $this->puliRunner = new PuliRunner($composer->getConfig()->get('bin-dir'));
278
        } catch (RuntimeException $e) {
279
            $io->writeError('<warning>'.$e->getMessage().'</warning>');
280
            $this->runPostAutoloadDump = false;
281
            $this->runPostInstall = false;
282
        }
283
    }
284
285
    /**
286
     * @param PackageInterface[] $composerPackages
287
     * @param bool[]             $prodPackageNames
288
     * @param PuliPackage[]      $puliPackages
289
     * @param IOInterface        $io
290
     * @param Composer           $composer
291
     */
292
    private function installNewPackages(array $composerPackages, array $prodPackageNames, array &$puliPackages, IOInterface $io, Composer $composer)
293
    {
294
        $installationManager = $composer->getInstallationManager();
295
296
        foreach ($composerPackages as $packageName => $package) {
297
            if ($package instanceof AliasPackage) {
298
                $package = $package->getAliasOf();
299
            }
300
301
            // We need to normalize the system-dependent paths returned by Composer
302
            $installPath = Path::normalize($installationManager->getInstallPath($package));
303
            $env = isset($prodPackageNames[$packageName]) ? PuliPackage::ENV_PROD : PuliPackage::ENV_DEV;
304
305
            // Skip meta packages
306
            if ('' === $installPath) {
307
                continue;
308
            }
309
310
            if (isset($puliPackages[$packageName])) {
311
                $puliPackage = $puliPackages[$packageName];
312
313
                // Only proceed if the install path or environment has changed
314
                if ($installPath === $puliPackage->getInstallPath() && $env === $puliPackage->getEnvironment()) {
315
                    continue;
316
                }
317
318
                // Only remove packages installed by Composer
319
                if (self::INSTALLER_NAME === $puliPackage->getInstallerName()) {
320
                    $io->write(sprintf(
321
                        'Reinstalling <info>%s</info> (<comment>%s</comment>) in <comment>%s</comment>',
322
                        $packageName,
323
                        Path::makeRelative($installPath, $this->rootDir),
324
                        $env
325
                    ));
326
327
                    try {
328
                        $this->removePackage($packageName);
329
                    } catch (PuliRunnerException $e) {
330
                        $this->printPackageWarning($io, 'Could not remove package "%s" (at ./%s)', $packageName, $installPath, $e);
331
332
                        continue;
333
                    }
334
                }
335
            } else {
336
                $io->write(sprintf(
337
                    'Installing <info>%s</info> (<comment>%s</comment>) in <comment>%s</comment>',
338
                    $packageName,
339
                    Path::makeRelative($installPath, $this->rootDir),
340
                    $env
341
                ));
342
            }
343
344
            try {
345
                $this->installPackage($installPath, $packageName, $env);
346
            } catch (PuliRunnerException $e) {
347
                $this->printPackageWarning($io, 'Could not install package "%s" (at ./%s)', $packageName, $installPath, $e);
348
349
                continue;
350
            }
351
352
            $puliPackages[$packageName] = new PuliPackage(
353
                $packageName,
354
                self::INSTALLER_NAME,
355
                $installPath,
356
                PuliPackage::STATE_ENABLED,
357
                $env
358
            );
359
        }
360
    }
361
362
    /**
363
     * @param PackageInterface[] $composerPackages
364
     * @param PuliPackage[]      $puliPackages
365
     * @param IOInterface        $io
366
     */
367
    private function removeRemovedPackages(array $composerPackages, array &$puliPackages, IOInterface $io)
368
    {
369
        /** @var PuliPackage[] $notFoundPackages */
370
        $notFoundPackages = array_filter($puliPackages, function (PuliPackage $package) {
371
            return PuliPackage::STATE_NOT_FOUND === $package->getState()
372
                && PuliPlugin::INSTALLER_NAME === $package->getInstallerName();
373
        });
374
375
        foreach ($notFoundPackages as $packageName => $package) {
376
            // Check whether package was only moved
377
            if (isset($composerPackages[$packageName])) {
378
                continue;
379
            }
380
381
            $io->write(sprintf(
382
                'Removing <info>%s</info> (<comment>%s</comment>)',
383
                $packageName,
384
                Path::makeRelative($package->getInstallPath(), $this->rootDir)
385
            ));
386
387
            try {
388
                $this->removePackage($packageName);
389
            } catch (PuliRunnerException $e) {
390
                $this->printPackageWarning($io, 'Could not remove package "%s" (at ./%s)', $packageName, $package->getInstallPath(), $e);
391
392
                continue;
393
            }
394
395
            unset($puliPackages[$packageName]);
396
        }
397
    }
398
399 View Code Duplication
    private function checkForNotFoundErrors(array $puliPackages, IOInterface $io)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
400
    {
401
        /** @var PuliPackage[] $notFoundPackages */
402
        $notFoundPackages = array_filter($puliPackages,
403
            function (PuliPackage $package) {
404
                return PuliPackage::STATE_NOT_FOUND === $package->getState()
405
                && PuliPlugin::INSTALLER_NAME === $package->getInstallerName();
406
            });
407
408
        foreach ($notFoundPackages as $package) {
409
            $this->printPackageWarning(
410
                $io,
411
                'The package "%s" (at ./%s) could not be found',
412
                $package->getName(),
413
                $package->getInstallPath()
414
            );
415
        }
416
    }
417
418 View Code Duplication
    private function checkForNotLoadableErrors(array $puliPackages, IOInterface $io)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
419
    {
420
        /** @var PuliPackage[] $notLoadablePackages */
421
        $notLoadablePackages = array_filter($puliPackages, function (PuliPackage $package) {
422
            return PuliPackage::STATE_NOT_LOADABLE === $package->getState()
423
                && PuliPlugin::INSTALLER_NAME === $package->getInstallerName();
424
        });
425
426
        foreach ($notLoadablePackages as $package) {
427
            $this->printPackageWarning(
428
                $io,
429
                'The package "%s" (at ./%s) could not be loaded',
430
                $package->getName(),
431
                $package->getInstallPath()
432
            );
433
        }
434
    }
435
436
    private function adoptComposerName(array $puliPackages, IOInterface $io, Composer $composer)
437
    {
438
        $rootDir = $this->rootDir;
439
440
        /** @var PuliPackage[] $rootPackages */
441
        $rootPackages = array_filter($puliPackages, function (PuliPackage $package) use ($rootDir) {
442
            return !$package->getInstallerName() && $rootDir === $package->getInstallPath();
443
        });
444
445
        if (0 === count($rootPackages)) {
446
            // This should never happen
447
            $this->printWarning($io, 'No root package could be found');
448
449
            return;
450
        }
451
452
        if (count($rootPackages) > 1) {
453
            // This should never happen
454
            $this->printWarning($io, 'More than one root package was found');
455
456
            return;
457
        }
458
459
        /** @var PuliPackage $rootPackage */
460
        $rootPackage = reset($rootPackages);
461
        $name = $rootPackage->getName();
462
        $newName = $composer->getPackage()->getName();
463
464
        // Rename the root package after changing the name in composer.json
465
        if ($name !== $newName) {
466
            try {
467
                $this->renamePackage($name, $newName);
468
            } catch (PuliRunnerException $e) {
469
                $this->printWarning($io, sprintf(
470
                    'Could not rename root package to "%s"',
471
                    $newName
472
                ), $e);
473
            }
474
        }
475
    }
476
477
    private function insertFactoryClassConstant(IOInterface $io, $autoloadFile, $factoryClass)
478
    {
479
        if (!file_exists($autoloadFile)) {
480
            throw new PuliPluginException(sprintf(
481
                'Could not adjust autoloader: The file %s was not found.',
482
                $autoloadFile
483
            ));
484
        }
485
486
        $io->write('<info>Generating PULI_FACTORY_CLASS constant</info>');
487
488
        $contents = file_get_contents($autoloadFile);
489
        $escFactoryClass = var_export($factoryClass, true);
490
        $constant = "if (!defined('PULI_FACTORY_CLASS')) {\n";
491
        $constant .= sprintf("    define('PULI_FACTORY_CLASS', %s);\n", $escFactoryClass);
492
        $constant .= "}\n\n";
493
494
        // Regex modifiers:
495
        // "m": \s matches newlines
496
        // "D": $ matches at EOF only
497
        // Translation: insert before the last "return" in the file
498
        $contents = preg_replace('/\n(?=return [^;]+;\s*$)/mD', "\n".$constant,
499
            $contents);
500
501
        file_put_contents($autoloadFile, $contents);
502
    }
503
504
    private function insertFactoryClassMap(IOInterface $io, $classMapFile, $vendorDir, $factoryClass, $factoryFile)
505
    {
506
        if (!file_exists($classMapFile)) {
507
            throw new PuliPluginException(sprintf(
508
                'Could not adjust autoloader: The file %s was not found.',
509
                $classMapFile
510
            ));
511
        }
512
513
        $io->write(sprintf('<info>Registering %s with the class-map autoloader</info>', $factoryClass));
514
515
        $relFactoryFile = Path::makeRelative($factoryFile, $vendorDir);
516
        $escFactoryClass = var_export($factoryClass, true);
517
        $escFactoryFile = var_export('/'.$relFactoryFile, true);
518
        $classMap = sprintf("\n    %s => \$vendorDir . %s,", $escFactoryClass, $escFactoryFile);
519
520
        $contents = file_get_contents($classMapFile);
521
522
        // Regex modifiers:
523
        // "m": \s matches newlines
524
        // "D": $ matches at EOF only
525
        // Translation: insert before the last ");" in the file
526
        $contents = preg_replace('/\n(?=\);\s*$)/mD', "\n".$classMap, $contents);
527
528
        file_put_contents($classMapFile, $contents);
529
    }
530
531
    private function setBootstrapFile(IOInterface $io, $autoloadFile)
532
    {
533
        $bootstrapFile = $this->getConfigKey('bootstrap-file');
534
535
        // Don't change user-defined bootstrap files
536
        if (!empty($bootstrapFile)) {
537
            return;
538
        }
539
540
        $relAutoloadFile = Path::makeRelative($autoloadFile, $this->rootDir);
541
542
        $io->write(sprintf('<info>Setting "bootstrap-file" to "%s"</info>', $relAutoloadFile));
543
544
        $this->setConfigKey('bootstrap-file', $relAutoloadFile);
545
    }
546
547
    /**
548
     * Loads Composer's currently installed packages.
549
     *
550
     * @param Composer $composer The Composer instance.
551
     *
552
     * @return PackageInterface[] The installed packages indexed by their names.
553
     */
554
    private function loadComposerPackages(Composer $composer)
555
    {
556
        $repository = $composer->getRepositoryManager()->getLocalRepository();
557
        $packages = array();
558
559
        foreach ($repository->getPackages() as $package) {
560
            /* @var PackageInterface $package */
561
            $packages[$package->getName()] = $package;
562
        }
563
564
        return $packages;
565
    }
566
567
    private function getConfigKey($key)
568
    {
569
        $value = trim($this->puliRunner->run('config %key% --parsed', array(
570
            'key' => $key,
571
        )));
572
573
        switch ($value) {
574
            case 'null':
575
                return null;
576
            case 'true':
577
                return true;
578
            case 'false':
579
                return false;
580
            default:
581
                return $value;
582
        }
583
    }
584
585
    private function setConfigKey($key, $value)
586
    {
587
        $this->puliRunner->run('config %key% %value%', array(
588
            'key' => $key,
589
            'value' => $value,
590
        ));
591
    }
592
593
    /**
594
     * @return PuliPackage[]
595
     */
596
    private function loadPuliPackages()
597
    {
598
        $packages = array();
599
600
        $output = $this->puliRunner->run('package --list --format %format%', array(
601
            'format' => '%name%;%installer%;%install_path%;%state%;%env%',
602
        ));
603
604
        // PuliRunner replaces \r\n by \n for those Windows boxes
605
        foreach (explode("\n", $output) as $packageLine) {
606
            if (!$packageLine) {
607
                continue;
608
            }
609
610
            $packageParts = explode(';', $packageLine);
611
612
            $packages[$packageParts[0]] = new PuliPackage(
613
                $packageParts[0],
614
                $packageParts[1],
615
                $packageParts[2],
616
                $packageParts[3],
617
                $packageParts[4]
618
            );
619
        }
620
621
        return $packages;
622
    }
623
624
    private function installPackage($installPath, $packageName, $env)
625
    {
626
        $env = PuliPackage::ENV_DEV === $env ? ' --dev' : '';
627
628
        $this->puliRunner->run('package --install %path% %package_name% --installer %installer%'.$env, array(
629
            'path' => $installPath,
630
            'package_name' => $packageName,
631
            'installer' => self::INSTALLER_NAME,
632
        ));
633
    }
634
635
    private function removePackage($packageName)
636
    {
637
        $this->puliRunner->run('package --delete %package_name%', array(
638
            'package_name' => $packageName,
639
        ));
640
    }
641
642
    private function buildPuli(IOInterface $io)
643
    {
644
        $io->write('<info>Running "puli build"</info>');
645
646
        $this->puliRunner->run('build');
647
    }
648
649
    private function renamePackage($name, $newName)
650
    {
651
        $this->puliRunner->run('package --rename %old_name% %new_name%', array(
652
            'old_name' => $name,
653
            'new_name' => $newName,
654
        ));
655
    }
656
657
    private function printWarning(IOInterface $io, $message, PuliRunnerException $exception = null)
658
    {
659
        if (!$exception) {
660
            $reasonPhrase = '';
661
        } elseif ($io->isVerbose()) {
662
            $reasonPhrase = $exception->getFullError();
663
        } else {
664
            $reasonPhrase = $exception->getShortError();
665
        }
666
667
        $io->writeError(sprintf(
668
            '<warning>Warning: %s%s</warning>',
669
            $message,
670
            $reasonPhrase ? ': '.$reasonPhrase : '.'
671
        ));
672
    }
673
674
    private function printPackageWarning(IOInterface $io, $message, $packageName, $installPath, PuliRunnerException $exception = null)
675
    {
676
        $this->printWarning($io, sprintf(
677
            $message,
678
            $packageName,
679
            Path::makeRelative($installPath, $this->rootDir)
680
        ), $exception);
681
    }
682
683
    private function filterProdPackageNames(array $composerPackages, PackageInterface $package, array &$result = array())
684
    {
685
        // Resolve aliases
686
        if ($package instanceof AliasPackage) {
687
            $package = $package->getAliasOf();
688
        }
689
690
        // Package was processed already
691
        if (isset($result[$package->getName()])) {
692
            return $result;
693
        }
694
695
        $result[$package->getName()] = true;
696
697
        // Recursively filter package names
698
        foreach ($package->getRequires() as $packageName => $link) {
699
            if (isset($composerPackages[$packageName])) {
700
                $this->filterProdPackageNames($composerPackages, $composerPackages[$packageName], $result);
701
            }
702
        }
703
704
        return $result;
705
    }
706
}
707