diff --git a/src/Composer/InstalledVersions.php b/src/Composer/InstalledVersions.php index e6df5a5a2..5126c5f8b 100644 --- a/src/Composer/InstalledVersions.php +++ b/src/Composer/InstalledVersions.php @@ -24,10 +24,15 @@ use Composer\Semver\VersionParser; */ class InstalledVersions { - private static $installed; + private static $installed = array(); private static $canGetVendors; private static $installedByVendor = array(); + /** + * Initialize $installed array + */ + public static function initializeInstalled() {} + /** * Returns a list of all package names which are present, either by being installed, replaced or provided * @@ -48,6 +53,28 @@ class InstalledVersions return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); } + /** + * Returns a list of all package names with a specific type e.g. 'library' + * + * @param string $type + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackagesByType($type) + { + $packagesByType = array(); + + foreach (self::getInstalled() as $installed) { + foreach ($installed['versions'] as $name => $package) { + if (isset($package['type']) && $package['type'] === $type) { + $packagesByType[] = $name; + } + } + } + + return $packagesByType; + } + /** * Checks whether the given package is installed * @@ -274,3 +301,5 @@ class InstalledVersions return $installed; } } + +InstalledVersions::initializeInstalled(); diff --git a/src/Composer/Repository/FilesystemRepository.php b/src/Composer/Repository/FilesystemRepository.php index fefc52dfe..99af09be7 100644 --- a/src/Composer/Repository/FilesystemRepository.php +++ b/src/Composer/Repository/FilesystemRepository.php @@ -14,6 +14,7 @@ namespace Composer\Repository; use Composer\Json\JsonFile; use Composer\Package\Loader\ArrayLoader; +use Composer\Package\PackageInterface; use Composer\Package\RootPackageInterface; use Composer\Package\AliasPackage; use Composer\Package\Dumper\ArrayDumper; @@ -102,11 +103,15 @@ class FilesystemRepository extends WritableArrayRepository $dumper = new ArrayDumper(); $fs = new Filesystem(); $repoDir = dirname($fs->normalizePath($this->file->getPath())); + $installPaths = array(); foreach ($this->getCanonicalPackages() as $package) { $pkgArray = $dumper->dump($package); $path = $installationManager->getInstallPath($package); - $pkgArray['install-path'] = ('' !== $path && null !== $path) ? $fs->findShortestPath($repoDir, $fs->isAbsolutePath($path) ? $path : getcwd() . '/' . $path, true) : null; + $installPath = ('' !== $path && null !== $path) ? $fs->findShortestPath($repoDir, $fs->isAbsolutePath($path) ? $path : getcwd() . '/' . $path, true) : null; + $installPaths[$package->getName()] = $installPath; + + $pkgArray['install-path'] = $installPath; $data['packages'][] = $pkgArray; // only write to the files the names which are really installed, as we receive the full list @@ -124,24 +129,56 @@ class FilesystemRepository extends WritableArrayRepository $this->file->write($data); if ($this->dumpVersions) { - $versions = $this->generateInstalledVersions($installationManager, $devMode); + $versions = $this->generateInstalledVersions($installationManager, $installPaths, $devMode, $repoDir); - $fs->filePutContentsIfModified($repoDir.'/installed.php', 'filePutContentsIfModified($repoDir.'/installed.php', 'dumpVersion($versions) . ';'."\n"); $installedVersionsClass = file_get_contents(__DIR__.'/../InstalledVersions.php'); // while not strictly needed since https://github.com/composer/composer/pull/9635 - we keep this for BC // and overall broader compatibility with people that may not use Composer's ClassLoader. They can // simply include InstalledVersions.php manually and have it working in a basic way. - $installedVersionsClass = str_replace('private static $installed;', 'private static $installed = '.var_export($versions, true).';', $installedVersionsClass); + $installedVersionsClass = str_replace('public static function initializeInstalled() {}', 'public static function initializeInstalled() {' . PHP_EOL . 'self::$installed = ' . $this->dumpVersion($versions) . ';' . PHP_EOL . '}', $installedVersionsClass); $fs->filePutContentsIfModified($repoDir.'/InstalledVersions.php', $installedVersionsClass); \Composer\InstalledVersions::reload($versions); } } + private function dumpVersion(array $array = array(), $level = 0) + { + $lines = "array(\n"; + $level++; + + foreach ($array as $key => $value) { + $lines .= str_repeat(' ', $level); + $lines .= is_int($key) ? $key . ' => ' : '\'' . $key . '\' => '; + + if (is_array($value)) { + if (!empty($value)) { + $lines .= self::dumpVersion($value, $level); + } else { + $lines .= "array(),\n"; + } + } elseif (is_null($value)) { + $lines .= 'null'; + $lines .= ",\n"; + } elseif (is_bool($value)) { + $lines .= $value ? 'true' : 'false'; + $lines .= ",\n"; + } else { + $stringContent = str_replace(array('\\', '\''), array('\\\\', '\\\''), $value); + $folder = $key === 'install_path' ? '__DIR__ . DIRECTORY_SEPARATOR . ' : ''; + $lines .= $folder . "'" . $stringContent . "',\n"; + } + } + + $lines .= str_repeat(' ', $level - 1) . ')' . ($level - 1 == 0 ? '' : ",\n"); + return $lines; + } + /** * @return ?array */ - private function generateInstalledVersions(InstallationManager $installationManager, $devMode) + private function generateInstalledVersions(InstallationManager $installationManager, $installPaths, $devMode, $repoDir) { if (!$this->dumpVersions) { return null; @@ -170,9 +207,19 @@ class FilesystemRepository extends WritableArrayRepository $reference = ($package->getSourceReference() ?: $package->getDistReference()) ?: null; } + if($package instanceof RootPackageInterface) { + $fs = new Filesystem(); + $to = getcwd(); + $installPath = $fs->findShortestPath($repoDir, $to, true); + } else { + $installPath = $installPaths[$package->getName()]; + } + $versions['versions'][$package->getName()] = array( 'pretty_version' => $package->getPrettyVersion(), 'version' => $package->getVersion(), + 'type' => $package->getType(), + 'install_path' => $installPath, 'aliases' => array(), 'reference' => $reference, 'dev-requirement' => isset($devPackages[$package->getName()]), diff --git a/tests/Composer/Test/InstalledVersionsTest.php b/tests/Composer/Test/InstalledVersionsTest.php index 7896548e2..bfd7feb78 100644 --- a/tests/Composer/Test/InstalledVersionsTest.php +++ b/tests/Composer/Test/InstalledVersionsTest.php @@ -17,6 +17,8 @@ use Composer\Semver\VersionParser; class InstalledVersionsTest extends TestCase { + private $root; + public static function setUpBeforeClass() { // disable multiple-ClassLoader-based checks of InstalledVersions by making it seem like no @@ -33,6 +35,9 @@ class InstalledVersionsTest extends TestCase public function setUp() { + $this->root = $this->getUniqueTmpDirectory(); + + $dir = $this->root; InstalledVersions::reload(require __DIR__.'/Repository/Fixtures/installed.php'); } @@ -187,6 +192,8 @@ class InstalledVersionsTest extends TestCase $this->assertSame(array( 'pretty_version' => 'dev-master', 'version' => 'dev-master', + 'type' => 'library', + 'install_path' => $this->root . DIRECTORY_SEPARATOR . './', 'aliases' => array( '1.10.x-dev', ), @@ -198,6 +205,7 @@ class InstalledVersionsTest extends TestCase public function testGetRawData() { + $dir = $this->root; $this->assertSame(require __DIR__.'/Repository/Fixtures/installed.php', InstalledVersions::getRawData()); } @@ -222,4 +230,17 @@ class InstalledVersionsTest extends TestCase array(null, 'c/c'), ); } + + public function testGetInstalledPackagesByType() + { + $names = array( + '__root__', + 'a/provider', + 'a/provider2', + 'b/replacer', + 'c/c', + ); + + $this->assertSame($names, \Composer\InstalledVersions::getInstalledPackagesByType('library')); + } } diff --git a/tests/Composer/Test/Mock/InstallationManagerMock.php b/tests/Composer/Test/Mock/InstallationManagerMock.php index 4642a4800..7898250cc 100644 --- a/tests/Composer/Test/Mock/InstallationManagerMock.php +++ b/tests/Composer/Test/Mock/InstallationManagerMock.php @@ -119,4 +119,9 @@ class InstallationManagerMock extends InstallationManager { // noop } + + public function getInstalledPackagesByType() + { + return $this->installed; + } } diff --git a/tests/Composer/Test/Repository/FilesystemRepositoryTest.php b/tests/Composer/Test/Repository/FilesystemRepositoryTest.php index 72e7adfa3..41d6ff0c1 100644 --- a/tests/Composer/Test/Repository/FilesystemRepositoryTest.php +++ b/tests/Composer/Test/Repository/FilesystemRepositoryTest.php @@ -18,6 +18,8 @@ use Composer\Json\JsonFile; class FilesystemRepositoryTest extends TestCase { + private $root; + public function testRepositoryRead() { $json = $this->createJsonFileMock(); @@ -121,6 +123,9 @@ class FilesystemRepositoryTest extends TestCase public function testRepositoryWritesInstalledPhp() { $dir = $this->getUniqueTmpDirectory(); + $this->root = $dir; + chdir($dir); + $json = new JsonFile($dir.'/installed.json'); $rootPackage = $this->getPackage('__root__', 'dev-master', 'Composer\Package\RootPackage'); diff --git a/tests/Composer/Test/Repository/Fixtures/installed.php b/tests/Composer/Test/Repository/Fixtures/installed.php index 8c6c148ae..0f6283c7e 100644 --- a/tests/Composer/Test/Repository/Fixtures/installed.php +++ b/tests/Composer/Test/Repository/Fixtures/installed.php @@ -14,6 +14,9 @@ return array( 'root' => array( 'pretty_version' => 'dev-master', 'version' => 'dev-master', + 'type' => 'library', + // @phpstan-ignore-next-line + 'install_path' => $dir . DIRECTORY_SEPARATOR . './', 'aliases' => array( '1.10.x-dev', ), @@ -25,6 +28,9 @@ return array( '__root__' => array( 'pretty_version' => 'dev-master', 'version' => 'dev-master', + 'type' => 'library', + // @phpstan-ignore-next-line + 'install_path' => $dir . DIRECTORY_SEPARATOR . './', 'aliases' => array( '1.10.x-dev', ), @@ -34,6 +40,9 @@ return array( 'a/provider' => array( 'pretty_version' => '1.1', 'version' => '1.1.0.0', + 'type' => 'library', + // @phpstan-ignore-next-line + 'install_path' => $dir . DIRECTORY_SEPARATOR . '/foo/bar/vendor/woop/woop', 'aliases' => array(), 'reference' => 'distref-as-no-source', 'dev-requirement' => false, @@ -41,6 +50,9 @@ return array( 'a/provider2' => array( 'pretty_version' => '1.2', 'version' => '1.2.0.0', + 'type' => 'library', + // @phpstan-ignore-next-line + 'install_path' => $dir . DIRECTORY_SEPARATOR . '/foo/bar/vendor/woop/woop', 'aliases' => array( '1.4', ), @@ -50,6 +62,9 @@ return array( 'b/replacer' => array( 'pretty_version' => '2.2', 'version' => '2.2.0.0', + 'type' => 'library', + // @phpstan-ignore-next-line + 'install_path' => $dir . DIRECTORY_SEPARATOR . '/foo/bar/vendor/woop/woop', 'aliases' => array(), 'reference' => null, 'dev-requirement' => false, @@ -57,6 +72,9 @@ return array( 'c/c' => array( 'pretty_version' => '3.0', 'version' => '3.0.0.0', + 'type' => 'library', + // @phpstan-ignore-next-line + 'install_path' => $dir . DIRECTORY_SEPARATOR . '/foo/bar/vendor/woop/woop', 'aliases' => array(), 'reference' => null, 'dev-requirement' => true,