Failed Conditions
Pull Request — master (#49)
by Helmut
19:53
created

src/PuliPluginImpl.php (1 issue)

Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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\Config;
16
use Composer\IO\IOInterface;
17
use Composer\Json\JsonFile;
18
use Composer\Package\AliasPackage;
19
use Composer\Package\PackageInterface;
20
use Composer\Script\Event;
21
use Composer\Util\RemoteFilesystem;
22
use Exception;
23
use RuntimeException;
24
use Symfony\Component\Filesystem\Filesystem;
25
use Webmozart\PathUtil\Path;
26
27
/**
28
 * Implementation of the Puli plugin.
29
 *
30
 * This class is separate from the main {@link PuliPlugin} class so that it can
31
 * be loaded lazily after updating the sources of this package in the project
32
 * that requires the package.
33
 *
34
 * @author Bernhard Schussek <[email protected]>
35
 */
36
class PuliPluginImpl
37
{
38
    /**
39
     * The version of the Puli plugin.
40
     */
41
    const VERSION = '@package_version@';
42
43
    /**
44
     * The minimum version of the Puli CLI.
45
     */
46
    const MIN_CLI_VERSION = '1.0.0-beta10';
47
48
    /**
49
     * The maximum version of the Puli CLI.
50
     */
51
    const MAX_CLI_VERSION = '1.999.99999';
52
53
    /**
54
     * The name of the installer.
55
     */
56
    const INSTALLER_NAME = 'composer';
57
58
    /**
59
     * @var Composer
60
     */
61
    private $composer;
62
63
    /**
64
     * @var IOInterface
65
     */
66
    private $io;
67
68
    /**
69
     * @var Config
70
     */
71
    private $config;
72
73
    /**
74
     * @var bool
75
     */
76
    private $isDev;
77
78
    /**
79
     * @var PuliRunner
80
     */
81
    private $puliRunner;
82
83
    /**
84
     * @var string
85
     */
86
    private $rootDir;
87
88
    /**
89
     * @var bool
90
     */
91
    private $runPreAutoloadDump = true;
92
93
    /**
94
     * @var bool
95
     */
96
    private $runPostAutoloadDump = true;
97
98
    /**
99
     * @var bool
100
     */
101
    private $runPostInstall = true;
102
103
    /**
104
     * @var bool
105
     */
106
    private $initialized = false;
107
108
    public function __construct(Event $event, PuliRunner $puliRunner = null)
109
    {
110
        $this->composer = $event->getComposer();
111 42
        $this->io = $event->getIO();
112
        $this->config = $this->composer->getConfig();
113 42
        $this->isDev = $event->isDevMode();
114 42
        $this->puliRunner = $puliRunner;
115 42
        $this->rootDir = Path::normalize(getcwd());
116 42
    }
117 42
118 42
    public function preAutoloadDump()
119
    {
120 42
        // This method is called multiple times. Run it only once.
121
        if (!$this->runPreAutoloadDump) {
122
            return;
123
        }
124 42
125
        $this->runPreAutoloadDump = false;
126 42
127 42
        $factoryClass = $this->getConfigKeyFromJsonFile('factory.in.class');
128
        $factoryFile = $this->getConfigKeyFromJsonFile('factory.in.file');
129 3
        $factoryFile = Path::makeAbsolute($factoryFile, $this->rootDir);
130
131 3
        $autoload = $this->composer->getPackage()->getAutoload();
132 3
        $autoload['classmap'][] = $factoryFile;
133
134
        $this->composer->getPackage()->setAutoload($autoload);
135
136 3
        if (!file_exists($factoryFile)) {
137
            $filesystem = new Filesystem();
138
            // Let Composer find the factory class with a temporary stub
139
140 3
            $namespace = explode('\\', ltrim($factoryClass, '\\'));
141
            $className = array_pop($namespace);
142
143 3
            if (count($namespace)) {
144 3
                $stub = '<?php namespace '.implode('\\', $namespace).'; class '.$className.' {}';
145
            } else {
146
                $stub = '<?php class '.$className.' {}';
147
            }
148
149
            $filesystem->dumpFile($factoryFile, $stub);
150
        }
151 3
    }
152
153 3
    public function postAutoloadDump()
154 3
    {
155
        if (!$this->initialized) {
156 3
            $this->initialize();
157
        }
158 3
159 2
        // This method is called multiple times. Run it only once.
160
        if (!$this->runPostAutoloadDump) {
161
            return;
162 2
        }
163 2
164
        $this->runPostAutoloadDump = false;
165 2
166 1
        try {
167
            $factoryClass = $this->getConfigKey('factory.in.class');
168 1
        } catch (PuliRunnerException $e) {
169
            $this->printWarning('Could not load Puli configuration', $e);
170
171 2
            return;
172
        }
173 3
174
        $vendorDir = $this->config->get('vendor-dir');
175 5
176
        // On TravisCI, $vendorDir is a relative path. Probably an old Composer
177 5
        // build or something. Usually, $vendorDir should be absolute already.
178 5
        $vendorDir = Path::makeAbsolute($vendorDir, $this->rootDir);
179
180
        $autoloadFile = $vendorDir.'/autoload.php';
181
        $this->insertFactoryClassConstant($autoloadFile, $factoryClass);
182 5
        $this->setBootstrapFile($autoloadFile);
183 1
    }
184
185
    /**
186 5
     * Updates the Puli repository after Composer installations/updates.
187
     */
188
    public function postInstall()
189 5
    {
190 1
        if (!$this->initialized) {
191 1
            $this->initialize();
192
        }
193 1
194
        // This method is called multiple times. Run it only once.
195
        if (!$this->runPostInstall) {
196 4
            return;
197 4
        }
198 4
199
        $this->runPostInstall = false;
200
201
        $this->io->write('<info>Synchronizing Puli with Composer</info>');
202
203 34
        $rootPackage = $this->composer->getPackage();
204
        $composerPackages = $this->loadComposerPackages();
205 34
        $prodPackageNames = $this->filterProdPackageNames($composerPackages, $rootPackage);
206 34
        $env = $this->isDev ? PuliPackage::ENV_DEV : PuliPackage::ENV_PROD;
207
208
        try {
209
            $puliPackages = $this->loadPuliPackages();
210 34
        } catch (PuliRunnerException $e) {
211 3
            $this->printWarning('Could not load Puli packages', $e);
212
213
            return;
214 32
        }
215
216 32
        // Don't remove non-existing packages in production environment
217
        // Removed packages could be dev dependencies (i.e. "require-dev"
218 32
        // of the root package or "require" of another dev dependency), and
219 32
        // we can't find out whether they are since Composer doesn't load them
220 32
        if (PuliPackage::ENV_PROD !== $env) {
221 32
            $this->removeRemovedPackages($composerPackages, $puliPackages);
222
        }
223
224 32
        $this->installNewPackages($composerPackages, $prodPackageNames, $puliPackages);
225 1
226 1
        // Don't print warnings for non-existing packages in production
227
        if (PuliPackage::ENV_PROD !== $env) {
228 1
            $this->checkForNotFoundErrors($puliPackages);
229
        }
230
231
        $this->checkForNotLoadableErrors($puliPackages);
232
        $this->adoptComposerName($puliPackages);
233
        $this->removePuliDir();
234
        $this->buildPuli();
235 31
    }
236 3
237
    private function initialize()
238
    {
239 31
        $this->initialized = true;
240
241
        // Keep the manually set runner
242 31
        if (null === $this->puliRunner) {
243 3
            try {
244
                // Add Composer's bin directory in case the "puli" executable is
245
                // installed with Composer
246 31
                $this->puliRunner = new PuliRunner($this->config->get('bin-dir'));
247 31
            } catch (RuntimeException $e) {
248 31
                $this->printWarning('Plugin initialization failed', $e);
249 31
                $this->runPreAutoloadDump = false;
250 31
                $this->runPostAutoloadDump = false;
251
                $this->runPostInstall = false;
252 42
            }
253
        }
254 42
255
        // Use the runner to verify if Puli has the right version
256
        try {
257
            $this->verifyPuliVersion();
258
        } catch (RuntimeException $e) {
259
            $this->printWarning('Version check failed', $e);
260
            $this->runPreAutoloadDump = false;
261 42
            $this->runPostAutoloadDump = false;
262
            $this->runPostInstall = false;
263
        }
264 42
    }
265
266
    /**
267
     * @param PackageInterface[] $composerPackages
268
     * @param bool[]             $prodPackageNames
269
     * @param PuliPackage[]      $puliPackages
270
     */
271
    private function installNewPackages(array $composerPackages, array $prodPackageNames, array &$puliPackages)
272
    {
273
        $installationManager = $this->composer->getInstallationManager();
274
275
        foreach ($composerPackages as $packageName => $package) {
276
            if ($package instanceof AliasPackage) {
277
                $package = $package->getAliasOf();
278
            }
279 42
280 2
            // We need to normalize the system-dependent paths returned by Composer
281 2
            $installPath = Path::normalize($installationManager->getInstallPath($package));
282 2
            $env = isset($prodPackageNames[$packageName]) ? PuliPackage::ENV_PROD : PuliPackage::ENV_DEV;
283 2
284 2
            // Skip meta packages
285
            if ('' === $installPath) {
286 42
                continue;
287
            }
288
289
            if (isset($puliPackages[$packageName])) {
290
                $puliPackage = $puliPackages[$packageName];
291
292
                // Only proceed if the install path or environment has changed
293 31
                if ($installPath === $puliPackage->getInstallPath() && $env === $puliPackage->getEnvironment()) {
294
                    continue;
295 31
                }
296
297 31
                // Only remove packages installed by Composer
298 21
                if (self::INSTALLER_NAME === $puliPackage->getInstallerName()) {
299 2
                    $this->io->write(sprintf(
300
                        'Reinstalling <info>%s</info> (<comment>%s</comment>) in <comment>%s</comment>',
301
                        $packageName,
302
                        Path::makeRelative($installPath, $this->rootDir),
303 21
                        $env
304 21
                    ));
305
306
                    try {
307 21
                        $this->removePackage($packageName);
308 1
                    } catch (PuliRunnerException $e) {
309
                        $this->printPackageWarning('Could not remove package "%s" (at "%s")', $packageName, $installPath, $e);
310
311 20
                        continue;
312 15
                    }
313
                }
314
            } else {
315 15
                $this->io->write(sprintf(
316 14
                    'Installing <info>%s</info> (<comment>%s</comment>) in <comment>%s</comment>',
317
                    $packageName,
318
                    Path::makeRelative($installPath, $this->rootDir),
319
                    $env
320 6
                ));
321 5
            }
322 5
323
            try {
324 5
                $this->installPackage($installPath, $packageName, $env);
325
            } catch (PuliRunnerException $e) {
326
                $this->printPackageWarning('Could not install package "%s" (at "%s")', $packageName, $installPath, $e);
327
328
                continue;
329 5
            }
330 1
331 1
            $puliPackages[$packageName] = new PuliPackage(
332
                $packageName,
333 6
                self::INSTALLER_NAME,
334
                $installPath,
335
                PuliPackage::STATE_ENABLED,
336
                $env
337 6
            );
338 6
        }
339
    }
340 6
341
    /**
342
     * @param PackageInterface[] $composerPackages
343
     * @param PuliPackage[]      $puliPackages
344
     */
345
    private function removeRemovedPackages(array $composerPackages, array &$puliPackages)
346 11
    {
347 3
        /** @var PuliPackage[] $notFoundPackages */
348 3
        $notFoundPackages = array_filter($puliPackages, function (PuliPackage $package) {
349
            return PuliPackage::STATE_NOT_FOUND === $package->getState()
350 3
                && PuliPluginImpl::INSTALLER_NAME === $package->getInstallerName();
351
        });
352
353 8
        foreach ($notFoundPackages as $packageName => $package) {
354
            // Check whether package was only moved
355 8
            if (isset($composerPackages[$packageName])) {
356
                continue;
357 8
            }
358
359
            $this->io->write(sprintf(
360
                'Removing <info>%s</info> (<comment>%s</comment>)',
361 31
                $packageName,
362
                Path::makeRelative($package->getInstallPath(), $this->rootDir)
363
            ));
364
365
            try {
366
                $this->removePackage($packageName);
367 3
            } catch (PuliRunnerException $e) {
368
                $this->printPackageWarning('Could not remove package "%s" (at "%s")', $packageName, $package->getInstallPath(), $e);
369
370
                continue;
371 3
            }
372 3
373 3
            unset($puliPackages[$packageName]);
374
        }
375 3
    }
376
377 3 View Code Duplication
    private function checkForNotFoundErrors(array $puliPackages)
378 1
    {
379
        /** @var PuliPackage[] $notFoundPackages */
380
        $notFoundPackages = array_filter($puliPackages,
381 2
            function (PuliPackage $package) {
382 2
                return PuliPackage::STATE_NOT_FOUND === $package->getState()
383
                && PuliPluginImpl::INSTALLER_NAME === $package->getInstallerName();
384 2
            });
385
386
        foreach ($notFoundPackages as $package) {
387
            $this->printPackageWarning(
388 2
                'The package "%s" (at "%s") could not be found',
389 1
                $package->getName(),
390 1
                $package->getInstallPath()
391
            );
392 1
        }
393
    }
394
395 1 View Code Duplication
    private function checkForNotLoadableErrors(array $puliPackages)
396
    {
397 3
        /** @var PuliPackage[] $notLoadablePackages */
398
        $notLoadablePackages = array_filter($puliPackages, function (PuliPackage $package) {
399 3
            return PuliPackage::STATE_NOT_LOADABLE === $package->getState()
400
                && PuliPluginImpl::INSTALLER_NAME === $package->getInstallerName();
401
        });
402 3
403
        foreach ($notLoadablePackages as $package) {
404 3
            $this->printPackageWarning(
405 3
                'The package "%s" (at "%s") could not be loaded',
406 3
                $package->getName(),
407
                $package->getInstallPath()
408 3
            );
409 2
        }
410 2
    }
411 2
412 2
    private function adoptComposerName(array $puliPackages)
413
    {
414
        $rootDir = $this->rootDir;
415 3
416
        /** @var PuliPackage[] $rootPackages */
417 31
        $rootPackages = array_filter($puliPackages, function (PuliPackage $package) use ($rootDir) {
418
            return !$package->getInstallerName() && $rootDir === $package->getInstallPath();
419
        });
420
421 31
        if (0 === count($rootPackages)) {
422 31
            // This should never happen
423 31
            $this->printWarning('No root package could be found');
424
425 31
            return;
426 1
        }
427 1
428 1
        if (count($rootPackages) > 1) {
429 1
            // This should never happen
430
            $this->printWarning('More than one root package was found');
431
432 31
            return;
433
        }
434 31
435
        /** @var PuliPackage $rootPackage */
436 31
        $rootPackage = reset($rootPackages);
437
        $name = $rootPackage->getName();
438
        $newName = $this->composer->getPackage()->getName();
439 31
440 31
        // Rename the root package after changing the name in composer.json
441 31
        if ($name !== $newName) {
442
            try {
443 31
                $this->renamePackage($name, $newName);
444
            } catch (PuliRunnerException $e) {
445
                $this->printWarning(sprintf(
446
                    'Could not rename root package to "%s"',
447
                    $newName
448
                ), $e);
449
            }
450 31
        }
451
    }
452
453
    private function insertFactoryClassConstant($autoloadFile, $factoryClass)
454
    {
455
        if (!file_exists($autoloadFile)) {
456
            throw new PuliPluginException(sprintf(
457
                'Could not adjust autoloader: The file %s was not found.',
458 31
                $autoloadFile
459 31
            ));
460 31
        }
461
462
        $this->io->write('<info>Generating the "PULI_FACTORY_CLASS" constant</info>');
463 31
464
        $contents = file_get_contents($autoloadFile);
465 2
        $escFactoryClass = var_export($factoryClass, true);
466 1
        $constant = "if (!defined('PULI_FACTORY_CLASS')) {\n";
467 1
        $constant .= sprintf("    define('PULI_FACTORY_CLASS', %s);\n", $escFactoryClass);
468 1
        $constant .= "}\n\n";
469
470
        // Regex modifiers:
471
        // "m": \s matches newlines
472
        // "D": $ matches at EOF only
473 31
        // Translation: insert before the last "return" in the file
474
        $contents = preg_replace('/\n(?=return [^;]+;\s*$)/mD', "\n".$constant,
475 4
            $contents);
476
477 4
        file_put_contents($autoloadFile, $contents);
478
    }
479
480
    private function setBootstrapFile($autoloadFile)
481
    {
482
        $bootstrapFile = $this->getConfigKey('bootstrap-file');
483
484 4
        // Don't change user-defined bootstrap files
485
        if (!empty($bootstrapFile)) {
486 4
            return;
487 4
        }
488 4
489 4
        $relAutoloadFile = Path::makeRelative($autoloadFile, $this->rootDir);
490 4
491
        $this->io->write(sprintf('<info>Setting "bootstrap-file" to "%s"</info>', $relAutoloadFile));
492
493
        $this->setConfigKey('bootstrap-file', $relAutoloadFile);
494
    }
495
496 4
    /**
497
     * Loads Composer's currently installed packages.
498
     *
499 4
     * @return PackageInterface[] The installed packages indexed by their names
500 4
     */
501
    private function loadComposerPackages()
502 4
    {
503
        $repository = $this->composer->getRepositoryManager()->getLocalRepository();
504 4
        $packages = array();
505
506
        foreach ($repository->getPackages() as $package) {
507 4
            /* @var PackageInterface $package */
508 1
            $packages[$package->getName()] = $package;
509
        }
510
511 3
        return $packages;
512
    }
513 3
514
    private function getConfigKeyFromJsonFile($key)
515 3
    {
516 3
        $value = null;
0 ignored issues
show
$value is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
517
        $jsonPath = realpath(substr($this->config->get('vendor-dir'), 0, -strlen($this->config->get('vendor-dir', Config::RELATIVE_PATHS)))).DIRECTORY_SEPARATOR.'puli.json';
518
        if (file_exists($jsonPath)) {
519
            $jsonFile = new JsonFile($jsonPath, new RemoteFilesystem($this->io));
520
            $config = $jsonFile->read();
521
            if (empty($config['version']) || '1.0' !== $config['version']) {
522
                throw new \RuntimeException('Invalid configuration version schema of puli.json file!');
523 32
            }
524
        }
525 32
        switch ($key) {
526 32 View Code Duplication
            case 'factory.in.file':
527
                if (empty($config) || empty($config['config']['factory']['in']['file'])) {
528 32
                    $value = '.puli/GeneratedPuliFactory.php';
529
                } else {
530 21
                    $value = $config['config']['factory']['in']['file'];
531
                }
532
                break;
533 32 View Code Duplication
            case 'factory.in.class':
534
                if (empty($config) || empty($config['config']['factory']['in']['class'])) {
535
                    $value = 'Puli\\GeneratedPuliFactory';
536 39
                } else {
537
                    $value = $config['config']['factory']['in']['class'];
538 39
                }
539 39
                break;
540
            default:
541
                throw new \RuntimeException(sprintf('Cannot extract key "%s" from config!', $key));
542
        }
543 38
544 1
        return $value;
545 38
    }
546
547 38
    private function getConfigKey($key)
548
    {
549
        $value = trim($this->puliRunner->run('config %key% --parsed', array(
550 38
            'key' => $key,
551
        )));
552
553
        switch ($value) {
554 3
            case 'null':
555
                return null;
556 3
            case 'true':
557 3
                return true;
558 3
            case 'false':
559
                return false;
560 3
            default:
561
                return $value;
562
        }
563
    }
564
565 32
    private function setConfigKey($key, $value)
566
    {
567 32
        $this->puliRunner->run('config %key% %value%', array(
568
            'key' => $key,
569 32
            'value' => $value,
570 32
        ));
571
    }
572
573
    /**
574 31
     * @return PuliPackage[]
575 31
     */
576 31
    private function loadPuliPackages()
577
    {
578
        $packages = array();
579 31
580
        $output = $this->puliRunner->run('package --list --format %format%', array(
581 31
            'format' => '%name%;%installer%;%install_path%;%state%;%env%',
582 31
        ));
583 31
584 31
        // PuliRunner replaces \r\n by \n for those Windows boxes
585 31
        foreach (explode("\n", $output) as $packageLine) {
586 31
            if (!$packageLine) {
587
                continue;
588
            }
589
590 31
            $packageParts = explode(';', $packageLine);
591
592
            $packages[$packageParts[0]] = new PuliPackage(
593 11
                $packageParts[0],
594
                $packageParts[1],
595 11
                $packageParts[2],
596
                $packageParts[3],
597 11
                $packageParts[4]
598 11
            );
599 11
        }
600 11
601
        return $packages;
602 8
    }
603
604 7
    private function installPackage($installPath, $packageName, $env)
605
    {
606 7
        $env = PuliPackage::ENV_DEV === $env ? ' --dev' : '';
607 7
608
        $this->puliRunner->run('package --install %path% %package_name% --installer %installer%'.$env, array(
609 5
            'path' => $installPath,
610
            'package_name' => $packageName,
611 31
            'installer' => self::INSTALLER_NAME,
612
        ));
613 31
    }
614
615 31
    private function removePackage($packageName)
616
    {
617
        $this->puliRunner->run('package --delete %package_name%', array(
618 31
            'package_name' => $packageName,
619 30
        ));
620
    }
621
622 1
    private function removePuliDir()
623
    {
624
        $relativePuliDir = rtrim($this->getConfigKey('puli-dir'), '/');
625 1
626 1
        $puliDir = Path::makeAbsolute($relativePuliDir, $this->rootDir);
627 1
628
        // Only remove existing sub-directories of the root directory
629 31
        if (!file_exists($puliDir) || 0 !== strpos($puliDir, $this->rootDir.'/')) {
630
            return;
631 31
        }
632
633 31
        $this->io->write(sprintf('<info>Deleting the "%s" directory</info>', $relativePuliDir));
634 31
635
        // Remove the .puli directory to prevent upgrade problems
636 2
        $filesystem = new Filesystem();
637
        $filesystem->remove($puliDir);
638 2
    }
639 2
640 2
    private function buildPuli()
641
    {
642 1
        $this->io->write('<info>Running "puli build"</info>');
643
644
        $this->puliRunner->run('build');
645
    }
646
647
    private function renamePackage($name, $newName)
648 12
    {
649
        $this->puliRunner->run('package --rename %old_name% %new_name%', array(
650 12
            'old_name' => $name,
651 3
            'new_name' => $newName,
652 10
        ));
653
    }
654
655
    /**
656
     * @param                $message
657 10
     * @param Exception|null $exception
658 8
     */
659 10
    private function printWarning($message, Exception $exception = null)
660
    {
661
        if (!$exception) {
662 12
            $reasonPhrase = '';
663 12
        } elseif ($this->io->isVerbose()) {
664
            $reasonPhrase = $exception instanceof PuliRunnerException
665 12
                ? $exception->getFullError()
666
                : $exception->getMessage()."\n\n".$exception->getTraceAsString();
667 12
        } else {
668
            $reasonPhrase = $exception instanceof PuliRunnerException
669 7
                ? $exception->getShortError()
670
                : $exception->getMessage();
671 7
        }
672
673
        $this->io->writeError(sprintf(
674 7
            '<warning>Warning: %s%s</warning>',
675
            $message,
676 7
            $reasonPhrase ? ': '.$reasonPhrase : '.'
677
        ));
678 32
    }
679
680
    private function printPackageWarning($message, $packageName, $installPath, PuliRunnerException $exception = null)
681 32
    {
682 2
        $this->printWarning(sprintf(
683
            $message,
684
            $packageName,
685
            Path::makeRelative($installPath, $this->rootDir)
686 32
        ), $exception);
687
    }
688
689
    private function filterProdPackageNames(array $composerPackages, PackageInterface $package, array &$result = array())
690 32
    {
691
        // Resolve aliases
692
        if ($package instanceof AliasPackage) {
693 32
            $package = $package->getAliasOf();
694 21
        }
695 21
696
        // Package was processed already
697
        if (isset($result[$package->getName()])) {
698
            return $result;
699 32
        }
700
701
        $result[$package->getName()] = true;
702 42
703
        // Recursively filter package names
704 42
        foreach ($package->getRequires() as $packageName => $link) {
705
            if (isset($composerPackages[$packageName])) {
706 42
                $this->filterProdPackageNames($composerPackages, $composerPackages[$packageName], $result);
707
            }
708
        }
709
710
        return $result;
711
    }
712
713
    private function verifyPuliVersion()
714
    {
715
        $versionString = $this->puliRunner->run('-V');
716 42
717 1
        if (!preg_match('~^Puli version (\S+)$~', $versionString, $matches)) {
718
            throw new RuntimeException(sprintf(
719
                'Could not determine Puli version. "puli -V" returned: %s',
720 41
                $versionString
721 1
            ));
722
        }
723
724 1
        // the development build of the plugin is always considered compatible
725 1
        // with the development build of the CLI
726 1
        // Split strings to prevent replacement during release
727 1
        if ('@package_'.'version@' === self::VERSION && '@package_'.'version@' === $matches[1]) {
728
            return;
729
        }
730
731 40 View Code Duplication
        if (version_compare($matches[1], self::MIN_CLI_VERSION, '<')) {
732 1
            throw new RuntimeException(sprintf(
733
                'Found an unsupported version of the Puli CLI: %s. Please '.
734
                'upgrade to version %s or higher. You can also install the '.
735 1
                'puli/cli dependency at version %s in your project.',
736 1
                $matches[1],
737 1
                self::MIN_CLI_VERSION,
738
                self::MIN_CLI_VERSION
739
            ));
740 39
        }
741
742 View Code Duplication
        if (version_compare($matches[1], self::MAX_CLI_VERSION, '>')) {
743
            throw new RuntimeException(sprintf(
744
                'Found an unsupported version of the Puli CLI: %s. Please '.
745
                'downgrade to a lower version than %s. You can also install '.
746
                'the puli/cli dependency in your project.',
747
                $matches[1],
748
                self::MAX_CLI_VERSION
749
            ));
750
        }
751
    }
752
}
753