diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index 386c2717a..f52dc965f 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -277,6 +277,7 @@ EOF; ); } + $classMap['Composer\\InstalledVersions'] = "\$vendorDir . '/composer/InstalledVersions.php',\n"; ksort($classMap); foreach ($classMap as $class => $code) { $classmapFile .= ' '.var_export($class, true).' => '.$code; @@ -296,33 +297,33 @@ EOF; } } - $this->filePutContentsIfModified($targetDir.'/autoload_namespaces.php', $namespacesFile); - $this->filePutContentsIfModified($targetDir.'/autoload_psr4.php', $psr4File); - $this->filePutContentsIfModified($targetDir.'/autoload_classmap.php', $classmapFile); + $filesystem->filePutContentsIfModified($targetDir.'/autoload_namespaces.php', $namespacesFile); + $filesystem->filePutContentsIfModified($targetDir.'/autoload_psr4.php', $psr4File); + $filesystem->filePutContentsIfModified($targetDir.'/autoload_classmap.php', $classmapFile); $includePathFilePath = $targetDir.'/include_paths.php'; if ($includePathFileContents = $this->getIncludePathsFile($packageMap, $filesystem, $basePath, $vendorPath, $vendorPathCode52, $appBaseDirCode)) { - $this->filePutContentsIfModified($includePathFilePath, $includePathFileContents); + $filesystem->filePutContentsIfModified($includePathFilePath, $includePathFileContents); } elseif (file_exists($includePathFilePath)) { unlink($includePathFilePath); } $includeFilesFilePath = $targetDir.'/autoload_files.php'; if ($includeFilesFileContents = $this->getIncludeFilesFile($autoloads['files'], $filesystem, $basePath, $vendorPath, $vendorPathCode52, $appBaseDirCode)) { - $this->filePutContentsIfModified($includeFilesFilePath, $includeFilesFileContents); + $filesystem->filePutContentsIfModified($includeFilesFilePath, $includeFilesFileContents); } elseif (file_exists($includeFilesFilePath)) { unlink($includeFilesFilePath); } - $this->filePutContentsIfModified($targetDir.'/autoload_static.php', $this->getStaticFile($suffix, $targetDir, $vendorPath, $basePath, $staticPhpVersion)); + $filesystem->filePutContentsIfModified($targetDir.'/autoload_static.php', $this->getStaticFile($suffix, $targetDir, $vendorPath, $basePath, $staticPhpVersion)); $checkPlatform = $config->get('platform-check'); if ($checkPlatform) { - $this->filePutContentsIfModified($targetDir.'/platform_check.php', $this->getPlatformCheck($packageMap)); + $filesystem->filePutContentsIfModified($targetDir.'/platform_check.php', $this->getPlatformCheck($packageMap)); } elseif (file_exists($targetDir.'/platform_check.php')) { unlink($targetDir.'/platform_check.php'); } - $this->filePutContentsIfModified($vendorPath.'/autoload.php', $this->getAutoloadFile($vendorPathToTargetDirCode, $suffix)); - $this->filePutContentsIfModified($targetDir.'/autoload_real.php', $this->getAutoloadRealFile(true, (bool) $includePathFileContents, $targetDirLoader, (bool) $includeFilesFileContents, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader, $staticPhpVersion, $checkPlatform)); + $filesystem->filePutContentsIfModified($vendorPath.'/autoload.php', $this->getAutoloadFile($vendorPathToTargetDirCode, $suffix)); + $filesystem->filePutContentsIfModified($targetDir.'/autoload_real.php', $this->getAutoloadRealFile(true, (bool) $includePathFileContents, $targetDirLoader, (bool) $includeFilesFileContents, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader, $staticPhpVersion, $checkPlatform)); - $this->safeCopy(__DIR__.'/ClassLoader.php', $targetDir.'/ClassLoader.php'); - $this->safeCopy(__DIR__.'/../../../LICENSE', $targetDir.'/LICENSE'); + $filesystem->safeCopy(__DIR__.'/ClassLoader.php', $targetDir.'/ClassLoader.php'); + $filesystem->safeCopy(__DIR__.'/../../../LICENSE', $targetDir.'/LICENSE'); if ($this->runScripts) { $this->eventDispatcher->dispatchScript(ScriptEvents::POST_AUTOLOAD_DUMP, $this->devMode, array(), array( @@ -333,16 +334,6 @@ EOF; return count($classMap); } - private function filePutContentsIfModified($path, $content) - { - $currentContent = @file_get_contents($path); - if (!$currentContent || ($currentContent != $content)) { - return file_put_contents($path, $content); - } - - return 0; - } - private function addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, $namespaceFilter, $autoloadType, array $classMap, array &$ambiguousClasses, array &$scannedFiles) { foreach ($this->generateClassMap($dir, $blacklist, $namespaceFilter, $autoloadType, true, $scannedFiles) as $class => $path) { @@ -1133,51 +1124,4 @@ INITIALIZER; return $sortedPackageMap; } - - /** - * Copy file using stream_copy_to_stream to work around https://bugs.php.net/bug.php?id=6463 - * - * @param string $source - * @param string $target - */ - protected function safeCopy($source, $target) - { - if (!file_exists($target) || !file_exists($source) || !$this->filesAreEqual($source, $target)) { - $source = fopen($source, 'r'); - $target = fopen($target, 'w+'); - - stream_copy_to_stream($source, $target); - fclose($source); - fclose($target); - } - } - - /** - * compare 2 files - * https://stackoverflow.com/questions/3060125/can-i-use-file-get-contents-to-compare-two-files - */ - private function filesAreEqual($a, $b) - { - // Check if filesize is different - if (filesize($a) !== filesize($b)) { - return false; - } - - // Check if content is different - $ah = fopen($a, 'rb'); - $bh = fopen($b, 'rb'); - - $result = true; - while (!feof($ah)) { - if (fread($ah, 8192) != fread($bh, 8192)) { - $result = false; - break; - } - } - - fclose($ah); - fclose($bh); - - return $result; - } } diff --git a/src/Composer/Compiler.php b/src/Composer/Compiler.php index fdae04720..240870a1c 100644 --- a/src/Composer/Compiler.php +++ b/src/Composer/Compiler.php @@ -139,6 +139,8 @@ class Compiler $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/autoload_files.php')); $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/autoload_real.php')); $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/autoload_static.php')); + $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/installed.php')); + $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/InstalledVersions.php')); if (file_exists(__DIR__.'/../../vendor/composer/platform_check.php')) { $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/platform_check.php')); } diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php index b5bbb0a0b..c7ab40039 100644 --- a/src/Composer/Composer.php +++ b/src/Composer/Composer.php @@ -55,6 +55,17 @@ class Composer const RELEASE_DATE = '@release_date@'; const SOURCE_VERSION = '2.0-dev+source'; + /** + * Version number of the internal composer-runtime-api package + * + * This is used to version features available to projects at runtime + * like the platform-check file, the Composer\InstalledVersions class + * and possibly others in the future. + * + * @var string + */ + const RUNTIME_API_VERSION = '2.0.0'; + public static function getVersion() { // no replacement done, this must be a source checkout diff --git a/src/Composer/DependencyResolver/Transaction.php b/src/Composer/DependencyResolver/Transaction.php index 1e681a04f..a919238f6 100644 --- a/src/Composer/DependencyResolver/Transaction.php +++ b/src/Composer/DependencyResolver/Transaction.php @@ -261,9 +261,9 @@ class Transaction // is this a plugin or a dependency of a plugin? if ($isPlugin || count(array_intersect($package->getNames(), $pluginRequires))) { - // get the package's requires, but filter out any platform requirements or 'composer-plugin-api' + // get the package's requires, but filter out any platform requirements $requires = array_filter(array_keys($package->getRequires()), function ($req) { - return $req !== 'composer-plugin-api' && !preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $req); + return !preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $req); }); // is this a plugin with no meaningful dependencies? diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 85b958370..dd1a7b308 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -17,6 +17,7 @@ use Composer\Json\JsonFile; use Composer\IO\IOInterface; use Composer\Package\Archiver; use Composer\Package\Version\VersionGuesser; +use Composer\Package\RootPackageInterface; use Composer\Repository\RepositoryManager; use Composer\Repository\RepositoryFactory; use Composer\Repository\WritableRepositoryInterface; @@ -344,9 +345,6 @@ class Factory $rm = RepositoryFactory::manager($io, $config, $httpDownloader, $dispatcher); $composer->setRepositoryManager($rm); - // load local repository - $this->addLocalRepository($io, $rm, $vendorDir); - // force-set the version of the global package if not defined as // guessing it adds no value and only takes time if (!$fullLoad && !isset($localConfig['version'])) { @@ -360,6 +358,9 @@ class Factory $package = $loader->load($localConfig, 'Composer\Package\RootPackage', $cwd); $composer->setPackage($package); + // load local repository + $this->addLocalRepository($io, $rm, $vendorDir, $package); + // initialize installation manager $im = $this->createInstallationManager($loop, $io, $dispatcher); $composer->setInstallationManager($im); @@ -431,9 +432,9 @@ class Factory * @param Repository\RepositoryManager $rm * @param string $vendorDir */ - protected function addLocalRepository(IOInterface $io, RepositoryManager $rm, $vendorDir) + protected function addLocalRepository(IOInterface $io, RepositoryManager $rm, $vendorDir, RootPackageInterface $rootPackage) { - $rm->setLocalRepository(new Repository\InstalledFilesystemRepository(new JsonFile($vendorDir.'/composer/installed.json', null, $io))); + $rm->setLocalRepository(new Repository\InstalledFilesystemRepository(new JsonFile($vendorDir.'/composer/installed.json', null, $io), true, $rootPackage)); } /** diff --git a/src/Composer/InstalledVersions.php b/src/Composer/InstalledVersions.php new file mode 100644 index 000000000..c2c6dbd3e --- /dev/null +++ b/src/Composer/InstalledVersions.php @@ -0,0 +1,186 @@ + + */ + public static function getInstalledPackages() + { + return array_keys(self::$installed['versions']); + } + + /** + * Checks whether the given package is installed + * + * This also returns true if the package name is provided or replaced by another package + * + * @param string $packageName + * @return bool + */ + public static function isInstalled($packageName) + { + return isset(self::$installed['versions'][$packageName]); + } + + /** + * Checks whether the given package satisfies a version constraint + * + * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: + * + * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') + * + * @param VersionParser $parser Install composer/semver to have access to this class and functionality + * @param string $packageName + * @param ?string $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package + * + * @return bool + */ + public static function satisfies(VersionParser $parser, $packageName, $constraint) + { + $constraint = $parser->parseConstraints($constraint); + $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + + return $provided->matches($constraint); + } + + /** + * Returns a version constraint representing all the range(s) which are installed for a given package + * + * It is easier to use this via isInstalled() with the $constraint argument if you need to check + * whether a given version of a package is installed, and not just whether it exists + * + * @param string $packageName + * @return string Version constraint usable with composer/semver + */ + public static function getVersionRanges($packageName) + { + if (!isset(self::$installed['versions'][$packageName])) { + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + $ranges = array(); + if (isset(self::$installed['versions'][$packageName]['pretty_version'])) { + $ranges[] = self::$installed['versions'][$packageName]['pretty_version']; + } + if (array_key_exists('aliases', self::$installed['versions'][$packageName])) { + $ranges = array_merge($ranges, self::$installed['versions'][$packageName]['aliases']); + } + if (array_key_exists('replaced', self::$installed['versions'][$packageName])) { + $ranges = array_merge($ranges, self::$installed['versions'][$packageName]['replaced']); + } + if (array_key_exists('provided', self::$installed['versions'][$packageName])) { + $ranges = array_merge($ranges, self::$installed['versions'][$packageName]['provided']); + } + + return implode(' || ', $ranges); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getVersion($packageName) + { + if (!isset(self::$installed['versions'][$packageName])) { + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + if (!isset(self::$installed['versions'][$packageName]['version'])) { + return null; + } + + return self::$installed['versions'][$packageName]['version']; + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getPrettyVersion($packageName) + { + if (!isset(self::$installed['versions'][$packageName])) { + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + if (!isset(self::$installed['versions'][$packageName]['pretty_version'])) { + return null; + } + + return self::$installed['versions'][$packageName]['pretty_version']; + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference + */ + public static function getReference($packageName) + { + if (!isset(self::$installed['versions'][$packageName])) { + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + if (!isset(self::$installed['versions'][$packageName]['reference'])) { + return null; + } + + return self::$installed['versions'][$packageName]['reference']; + } + + /** + * @return array + * @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[]} + */ + public static function getRootPackage() + { + return self::$installed['root']; + } + + /** + * Returns the raw installed.php data for custom implementations + * + * @return array[] + * @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[]}, versions: list} + */ + public static function getRawData() + { + return self::$installed; + } + + /** + * Lets you reload the static array from another file + * + * This is only useful for complex integrations in which a project needs to use + * this class but then also needs to execute another project's autoloader in process, + * and wants to ensure both projects have access to their version of installed.php. + * + * A typical case would be PHPUnit, where it would need to make sure it reads all + * the data it needs from this class, then call reload() with + * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure + * the project in which it runs can then also use this class safely, without + * interference between PHPUnit's dependencies and the project's dependencies. + * + * @param array[] $data A vendor/composer/installed.php data set + * @return void + * + * @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[]}, versions: list} $data + */ + public static function reload($data) + { + self::$installed = $data; + } +} diff --git a/src/Composer/Installer/InstallationManager.php b/src/Composer/Installer/InstallationManager.php index 6d752ae58..4b8db7877 100644 --- a/src/Composer/Installer/InstallationManager.php +++ b/src/Composer/Installer/InstallationManager.php @@ -284,6 +284,11 @@ class InstallationManager throw $e; } + + // do a last write so that we write the repository even if nothing changed + // as that can trigger an update of some files like InstalledVersions.php if + // running a new composer version + $repo->write($devMode, $this); } /** diff --git a/src/Composer/Plugin/PluginInterface.php b/src/Composer/Plugin/PluginInterface.php index 4390764ff..f18799e7b 100644 --- a/src/Composer/Plugin/PluginInterface.php +++ b/src/Composer/Plugin/PluginInterface.php @@ -25,6 +25,11 @@ interface PluginInterface /** * Version number of the internal composer-plugin-api package * + * This is used to denote the API version of Plugin specific + * features, but is also bumped to a new major if Composer + * includes a major break in internal APIs which are susceptible + * to be used by plugins. + * * @var string */ const PLUGIN_API_VERSION = '2.0.0'; diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php index 97757f4fa..1dbbd2960 100644 --- a/src/Composer/Plugin/PluginManager.php +++ b/src/Composer/Plugin/PluginManager.php @@ -134,8 +134,8 @@ class PluginManager $currentPluginApiVersion = $this->getPluginApiVersion(); $currentPluginApiConstraint = new Constraint('==', $this->versionParser->normalize($currentPluginApiVersion)); - if ($requiresComposer->getPrettyString() === '1.0.0' && $this->getPluginApiVersion() === '1.0.0') { - $this->io->writeError('The "' . $package->getName() . '" plugin requires composer-plugin-api 1.0.0, this *WILL* break in the future and it should be fixed ASAP (require ^1.0 for example).'); + if ($requiresComposer->getPrettyString() === $this->getPluginApiVersion()) { + $this->io->writeError('The "' . $package->getName() . '" plugin requires composer-plugin-api '.$this->getPluginApiVersion().', this *WILL* break in the future and it should be fixed ASAP (require ^'.$this->getPluginApiVersion().' instead for example).'); } elseif (!$requiresComposer->matches($currentPluginApiConstraint)) { $this->io->writeError('The "' . $package->getName() . '" plugin was skipped because it requires a Plugin API version ("' . $requiresComposer->getPrettyString() . '") that does not match your Composer installation ("' . $currentPluginApiVersion . '"). You may need to run composer update with the "--no-plugins" option.'); diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 5e9e0c5b3..7082d29c2 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -502,7 +502,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito { if (!$this->hasPartialPackages() || !isset($this->partialPackagesByName[$name])) { // skip platform packages, root package and composer-plugin-api - if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name) || '__root__' === $name || 'composer-plugin-api' === $name) { + if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name) || '__root__' === $name) { return array(); } @@ -672,7 +672,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $realName = preg_replace('{~dev$}', '', $name); // skip platform packages, root package and composer-plugin-api - if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $realName) || '__root__' === $realName || 'composer-plugin-api' === $realName) { + if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $realName) || '__root__' === $realName) { continue; } diff --git a/src/Composer/Repository/FilesystemRepository.php b/src/Composer/Repository/FilesystemRepository.php index 3bebfbdb3..dcd94f7b3 100644 --- a/src/Composer/Repository/FilesystemRepository.php +++ b/src/Composer/Repository/FilesystemRepository.php @@ -14,6 +14,8 @@ namespace Composer\Repository; use Composer\Json\JsonFile; use Composer\Package\Loader\ArrayLoader; +use Composer\Package\RootPackageInterface; +use Composer\Package\AliasPackage; use Composer\Package\Dumper\ArrayDumper; use Composer\Installer\InstallationManager; use Composer\Util\Filesystem; @@ -27,16 +29,25 @@ use Composer\Util\Filesystem; class FilesystemRepository extends WritableArrayRepository { private $file; + private $dumpVersions; + private $rootPackage; /** * Initializes filesystem repository. * * @param JsonFile $repositoryFile repository json file + * @param bool $dumpVersions + * @param ?RootPackageInterface $rootPackage Must be provided if $dumpVersions is true */ - public function __construct(JsonFile $repositoryFile) + public function __construct(JsonFile $repositoryFile, $dumpVersions = false, RootPackageInterface $rootPackage = null) { parent::__construct(); $this->file = $repositoryFile; + $this->dumpVersions = $dumpVersions; + $this->rootPackage = $rootPackage; + if ($dumpVersions && !$rootPackage) { + throw new \InvalidArgumentException('Expected a root package instance if $dumpVersions is true'); + } } /** @@ -105,5 +116,82 @@ class FilesystemRepository extends WritableArrayRepository }); $this->file->write($data); + + if ($this->dumpVersions) { + $versions = array('versions' => array()); + $packages = $this->getPackages(); + $packages[] = $rootPackage = $this->rootPackage; + while ($rootPackage instanceof AliasPackage) { + $rootPackage = $rootPackage->getAliasOf(); + $packages[] = $rootPackage; + } + + // add real installed packages + foreach ($packages as $package) { + if ($package instanceof AliasPackage) { + continue; + } + + $reference = null; + if ($package->getInstallationSource()) { + $reference = $package->getInstallationSource() === 'source' ? $package->getSourceReference() : $package->getDistReference(); + } + if (null === $reference) { + $reference = ($package->getSourceReference() ?: $package->getDistReference()) ?: null; + } + + $versions['versions'][$package->getName()] = array( + 'pretty_version' => $package->getPrettyVersion(), + 'version' => $package->getVersion(), + 'aliases' => array(), + 'reference' => $reference, + ); + if ($package instanceof RootPackageInterface) { + $versions['root'] = $versions['versions'][$package->getName()]; + $versions['root']['name'] = $package->getName(); + } + } + + // add provided/replaced packages + foreach ($packages as $package) { + foreach ($package->getReplaces() as $replace) { + $replaced = $replace->getPrettyConstraint(); + if ($replaced === 'self.version') { + $replaced = $package->getPrettyVersion(); + } + if (!isset($versions['versions'][$replace->getTarget()]['replaced']) || !in_array($replaced, $versions['versions'][$replace->getTarget()]['replaced'], true)) { + $versions['versions'][$replace->getTarget()]['replaced'][] = $replaced; + } + } + foreach ($package->getProvides() as $provide) { + $provided = $provide->getPrettyConstraint(); + if ($provided === 'self.version') { + $provided = $package->getPrettyVersion(); + } + if (!isset($versions['versions'][$provide->getTarget()]['provided']) || !in_array($provided, $versions['versions'][$provide->getTarget()]['provided'], true)) { + $versions['versions'][$provide->getTarget()]['provided'][] = $provided; + } + } + } + + // add aliases + foreach ($packages as $package) { + if (!$package instanceof AliasPackage) { + continue; + } + $versions['versions'][$package->getName()]['aliases'][] = $package->getPrettyVersion(); + if ($package instanceof RootPackageInterface) { + $versions['root']['aliases'][] = $package->getPrettyVersion(); + } + } + + ksort($versions['versions']); + ksort($versions); + + $fs->filePutContentsIfModified($repoDir.'/installed.php', 'filePutContentsIfModified($repoDir.'/InstalledVersions.php', $installedVersionsClass); + } } } diff --git a/src/Composer/Repository/PlatformRepository.php b/src/Composer/Repository/PlatformRepository.php index 84f3d4b66..6ee05ca60 100644 --- a/src/Composer/Repository/PlatformRepository.php +++ b/src/Composer/Repository/PlatformRepository.php @@ -20,6 +20,7 @@ use Composer\Util\ProcessExecutor; use Composer\Util\Silencer; use Composer\Util\Platform; use Composer\XdebugHandler\XdebugHandler; +use Composer\Composer; use Symfony\Component\Process\ExecutableFinder; /** @@ -27,7 +28,7 @@ use Symfony\Component\Process\ExecutableFinder; */ class PlatformRepository extends ArrayRepository { - const PLATFORM_PACKAGE_REGEX = '{^(?:php(?:-64bit|-ipv6|-zts|-debug)?|hhvm|(?:ext|lib)-[a-z0-9](?:[_.-]?[a-z0-9]+)*|composer-plugin-api)$}iD'; + const PLATFORM_PACKAGE_REGEX = '{^(?:php(?:-64bit|-ipv6|-zts|-debug)?|hhvm|(?:ext|lib)-[a-z0-9](?:[_.-]?[a-z0-9]+)*|composer-(?:plugin|runtime)-api)$}iD'; private $versionParser; @@ -79,6 +80,12 @@ class PlatformRepository extends ArrayRepository $composerPluginApi->setDescription('The Composer Plugin API'); $this->addPackage($composerPluginApi); + $prettyVersion = Composer::RUNTIME_API_VERSION; + $version = $this->versionParser->normalize($prettyVersion); + $composerRuntimeApi = new CompletePackage('composer-runtime-api', $version, $prettyVersion); + $composerRuntimeApi->setDescription('The Composer Runtime API'); + $this->addPackage($composerRuntimeApi); + try { $prettyVersion = PHP_VERSION; $version = $this->versionParser->normalize($prettyVersion); diff --git a/src/Composer/Util/Filesystem.php b/src/Composer/Util/Filesystem.php index 8df7de8b5..0e516ef29 100644 --- a/src/Composer/Util/Filesystem.php +++ b/src/Composer/Util/Filesystem.php @@ -313,7 +313,7 @@ class Filesystem if (!function_exists('proc_open')) { $this->copyThenRemove($source, $target); - + return; } @@ -721,4 +721,61 @@ class Filesystem return $this->rmdir($junction); } + + public function filePutContentsIfModified($path, $content) + { + $currentContent = @file_get_contents($path); + if (!$currentContent || ($currentContent != $content)) { + return file_put_contents($path, $content); + } + + return 0; + } + + /** + * Copy file using stream_copy_to_stream to work around https://bugs.php.net/bug.php?id=6463 + * + * @param string $source + * @param string $target + */ + public function safeCopy($source, $target) + { + if (!file_exists($target) || !file_exists($source) || !$this->filesAreEqual($source, $target)) { + $source = fopen($source, 'r'); + $target = fopen($target, 'w+'); + + stream_copy_to_stream($source, $target); + fclose($source); + fclose($target); + } + } + + /** + * compare 2 files + * https://stackoverflow.com/questions/3060125/can-i-use-file-get-contents-to-compare-two-files + */ + private function filesAreEqual($a, $b) + { + // Check if filesize is different + if (filesize($a) !== filesize($b)) { + return false; + } + + // Check if content is different + $ah = fopen($a, 'rb'); + $bh = fopen($b, 'rb'); + + $result = true; + while (!feof($ah)) { + if (fread($ah, 8192) != fread($bh, 8192)) { + $result = false; + break; + } + } + + fclose($ah); + fclose($bh); + + return $result; + } } diff --git a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php index f9de00199..4159cf003 100644 --- a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php +++ b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php @@ -452,6 +452,7 @@ class AutoloadGeneratorTest extends TestCase $this->assertEquals( array( 'B\\C\\C' => $this->vendorDir.'/b/b/src/C/C.php', + 'Composer\\InstalledVersions' => $this->vendorDir . '/composer/InstalledVersions.php', ), include $this->vendorDir.'/composer/autoload_classmap.php' ); @@ -599,7 +600,9 @@ class AutoloadGeneratorTest extends TestCase $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_8'); $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated."); $this->assertEquals( - array(), + array( + 'Composer\\InstalledVersions' => $this->vendorDir.'/composer/InstalledVersions.php', + ), include $this->vendorDir.'/composer/autoload_classmap.php' ); } @@ -636,6 +639,7 @@ class AutoloadGeneratorTest extends TestCase \$baseDir = dirname(\$vendorDir); return array( + 'Composer\\\\InstalledVersions' => \$vendorDir . '/composer/InstalledVersions.php', 'psr0_match' => \$baseDir . '/psr0/psr0/match.php', 'psr4\\\\match' => \$baseDir . '/psr4/match.php', ); @@ -677,6 +681,7 @@ EOF; 'ClassMapBar' => $this->vendorDir.'/b/b/src/b.php', 'ClassMapBaz' => $this->vendorDir.'/b/b/lib/c.php', 'ClassMapFoo' => $this->vendorDir.'/a/a/src/a.php', + 'Composer\\InstalledVersions' => $this->vendorDir.'/composer/InstalledVersions.php', ), include $this->vendorDir.'/composer/autoload_classmap.php' ); @@ -717,6 +722,7 @@ EOF; 'ClassMapBar' => $this->vendorDir.'/a/a/target/lib/b.php', 'ClassMapBaz' => $this->vendorDir.'/b/b/src/c.php', 'ClassMapFoo' => $this->vendorDir.'/a/a/target/src/a.php', + 'Composer\\InstalledVersions' => $this->vendorDir.'/composer/InstalledVersions.php', ), include $this->vendorDir.'/composer/autoload_classmap.php' ); @@ -758,6 +764,7 @@ EOF; 'ClassMapBar' => $this->vendorDir.'/b/b/test.php', 'ClassMapBaz' => $this->vendorDir.'/c/c/foo/test.php', 'ClassMapFoo' => $this->vendorDir.'/a/a/src/a.php', + 'Composer\\InstalledVersions' => $this->vendorDir.'/composer/InstalledVersions.php', ), include $this->vendorDir.'/composer/autoload_classmap.php' ); @@ -805,6 +812,7 @@ EOF; 'ClassMapBar' => $this->vendorDir.'/b/b/ClassMapBar.php', 'ClassMapBaz' => $this->vendorDir.'/c/c/foo/ClassMapBaz.php', 'ClassMapFoo' => $this->vendorDir.'/a/a/src/ClassMapFoo.php', + 'Composer\\InstalledVersions' => $this->vendorDir.'/composer/InstalledVersions.php', ), include $this->vendorDir.'/composer/autoload_classmap.php' ); @@ -852,7 +860,8 @@ EOF; $this->assertFileContentEquals(__DIR__.'/Fixtures/autoload_static_functions.php', $this->vendorDir.'/composer/autoload_static.php'); $this->assertFileContentEquals(__DIR__.'/Fixtures/autoload_files_functions.php', $this->vendorDir.'/composer/autoload_files.php'); - include $this->vendorDir . '/autoload.php'; + $loader = require $this->vendorDir . '/autoload.php'; + $loader->unregister(); $this->assertTrue(function_exists('testFilesAutoloadGeneration1')); $this->assertTrue(function_exists('testFilesAutoloadGeneration2')); $this->assertTrue(function_exists('testFilesAutoloadGeneration3')); @@ -988,7 +997,8 @@ EOF; $this->assertFileContentEquals(__DIR__ . '/Fixtures/autoload_real_files_by_dependency.php', $this->vendorDir . '/composer/autoload_real.php'); $this->assertFileContentEquals(__DIR__ . '/Fixtures/autoload_static_files_by_dependency.php', $this->vendorDir . '/composer/autoload_static.php'); - require $this->vendorDir . '/autoload.php'; + $loader = require $this->vendorDir . '/autoload.php'; + $loader->unregister(); $this->assertTrue(function_exists('testFilesAutoloadOrderByDependency1')); $this->assertTrue(function_exists('testFilesAutoloadOrderByDependency2')); @@ -1087,6 +1097,7 @@ EOF; return array( 'A\\\\B\\\\C' => \$baseDir . '/lib/A/B/C.php', + 'Composer\\\\InstalledVersions' => \$vendorDir . '/composer/InstalledVersions.php', 'Foo\\\\Bar' => \$baseDir . '/src/classes.php', ); @@ -1155,7 +1166,8 @@ EOF; $oldIncludePath = get_include_path(); - require $this->vendorDir."/autoload.php"; + $loader = require $this->vendorDir."/autoload.php"; + $loader->unregister(); $this->assertEquals( $this->vendorDir."/a/a/lib".PATH_SEPARATOR.$oldIncludePath, @@ -1183,7 +1195,8 @@ EOF; $oldIncludePath = get_include_path(); - require $this->vendorDir."/autoload.php"; + $loader = require $this->vendorDir."/autoload.php"; + $loader->unregister(); $this->assertEquals( $this->workingDir."/lib".PATH_SEPARATOR.$this->workingDir."/src".PATH_SEPARATOR.$this->vendorDir."/a/a/lib".PATH_SEPARATOR.$oldIncludePath, @@ -1356,6 +1369,7 @@ $baseDir = dirname($vendorDir).'/working-dir'; return array( 'Bar\\Bar' => $vendorDir . '/b/b/classmaps/classes.php', 'Bar\\Foo' => $vendorDir . '/b/b/lib/Bar/Foo.php', + 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', 'Foo\\Bar' => $baseDir . '/src/Foo/Bar.php', 'Foo\\Foo' => $baseDir . '/classmap/classes.php', ); @@ -1434,6 +1448,7 @@ $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir).'/working-dir'; return array( + 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', 'Foo\\Bar' => $baseDir . '/../src/Foo/Bar.php', 'Foo\\Foo' => $baseDir . '/../classmap/classes.php', ); @@ -1503,6 +1518,7 @@ $baseDir = dirname($vendorDir); return array( 'Classmap\\Foo' => $baseDir . '/class.php', + 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', 'Foo\\Bar' => $baseDir . '/Foo/Bar.php', ); diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_classmap.php b/tests/Composer/Test/Autoload/Fixtures/autoload_classmap.php index a33c6674a..e49f8b7d8 100644 --- a/tests/Composer/Test/Autoload/Fixtures/autoload_classmap.php +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_classmap.php @@ -8,6 +8,7 @@ $baseDir = dirname($vendorDir); return array( 'Acme\\Cake\\ClassMapBar' => $baseDir . '/src-cake/ClassMapBar.php', 'ClassMapFoo' => $baseDir . '/composersrc/foo.php', + 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', 'Lala\\ClassMapMain' => $baseDir . '/src/Lala/ClassMapMain.php', 'Lala\\Test\\ClassMapMainTest' => $baseDir . '/src/Lala/Test/ClassMapMainTest.php', ); diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_classmap2.php b/tests/Composer/Test/Autoload/Fixtures/autoload_classmap2.php index 9cc0484f1..60cec93b3 100644 --- a/tests/Composer/Test/Autoload/Fixtures/autoload_classmap2.php +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_classmap2.php @@ -7,4 +7,5 @@ $baseDir = dirname(dirname($vendorDir)); return array( 'ClassMapFoo' => $baseDir . '/composersrc/foo.php', + 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', ); diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_classmap3.php b/tests/Composer/Test/Autoload/Fixtures/autoload_classmap3.php index dacb8ff90..3bfb2bb80 100644 --- a/tests/Composer/Test/Autoload/Fixtures/autoload_classmap3.php +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_classmap3.php @@ -7,5 +7,6 @@ $baseDir = $vendorDir; return array( 'ClassMapFoo' => $vendorDir . '/composersrc/foo.php', + 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', 'Main\\Foo' => $vendorDir . '/src/Main/Foo.php', ); diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_classmap4.php b/tests/Composer/Test/Autoload/Fixtures/autoload_classmap4.php index ae8025544..767ec5e5d 100644 --- a/tests/Composer/Test/Autoload/Fixtures/autoload_classmap4.php +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_classmap4.php @@ -9,4 +9,5 @@ return array( 'ClassMapBar' => $vendorDir . '/b/b/src/b.php', 'ClassMapBaz' => $vendorDir . '/b/b/lib/c.php', 'ClassMapFoo' => $vendorDir . '/a/a/src/a.php', + 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', ); diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_classmap5.php b/tests/Composer/Test/Autoload/Fixtures/autoload_classmap5.php index 71bbc004d..e88a14606 100644 --- a/tests/Composer/Test/Autoload/Fixtures/autoload_classmap5.php +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_classmap5.php @@ -9,4 +9,5 @@ return array( 'ClassMapBar' => $vendorDir . '/b/b/test.php', 'ClassMapBaz' => $vendorDir . '/c/c/foo/test.php', 'ClassMapFoo' => $vendorDir . '/a/a/src/a.php', + 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', ); diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_classmap6.php b/tests/Composer/Test/Autoload/Fixtures/autoload_classmap6.php index ef97fb501..d7a357ace 100644 --- a/tests/Composer/Test/Autoload/Fixtures/autoload_classmap6.php +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_classmap6.php @@ -8,4 +8,5 @@ $baseDir = dirname($vendorDir); return array( 'ClassMapBar' => $baseDir . '/lib/rootbar.php', 'ClassMapFoo' => $baseDir . '/src/rootfoo.php', + 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', ); diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_classmap7.php b/tests/Composer/Test/Autoload/Fixtures/autoload_classmap7.php index 5768726d1..6818e6b82 100644 --- a/tests/Composer/Test/Autoload/Fixtures/autoload_classmap7.php +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_classmap7.php @@ -6,5 +6,6 @@ $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( + 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', 'Main\\ClassMain' => $baseDir . '/src/Main/ClassMain.php', ); diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_classmap8.php b/tests/Composer/Test/Autoload/Fixtures/autoload_classmap8.php index 0a40d114c..a002faf5a 100644 --- a/tests/Composer/Test/Autoload/Fixtures/autoload_classmap8.php +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_classmap8.php @@ -9,4 +9,5 @@ return array( 'ClassMapBar' => $vendorDir . '/b/b/ClassMapBar.php', 'ClassMapBaz' => $vendorDir . '/c/c/foo/ClassMapBaz.php', 'ClassMapFoo' => $vendorDir . '/a/a/src/ClassMapFoo.php', + 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', ); diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_classmap9.php b/tests/Composer/Test/Autoload/Fixtures/autoload_classmap9.php index f9ad3ca30..75bc86230 100644 --- a/tests/Composer/Test/Autoload/Fixtures/autoload_classmap9.php +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_classmap9.php @@ -8,5 +8,6 @@ $baseDir = dirname($vendorDir); return array( 'A' => $vendorDir . '/a/a/src/A.php', 'C' => $vendorDir . '/c/c/src/C.php', + 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', 'D' => $vendorDir . '/d/d/src/D.php', ); diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_phar_static.php b/tests/Composer/Test/Autoload/Fixtures/autoload_phar_static.php index 486a5c0dc..ceaf04a0c 100644 --- a/tests/Composer/Test/Autoload/Fixtures/autoload_phar_static.php +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_phar_static.php @@ -75,12 +75,17 @@ class ComposerStaticInitPhar ), ); + public static $classMap = array ( + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + ); + public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { $loader->prefixLengthsPsr4 = ComposerStaticInitPhar::$prefixLengthsPsr4; $loader->prefixDirsPsr4 = ComposerStaticInitPhar::$prefixDirsPsr4; $loader->prefixesPsr0 = ComposerStaticInitPhar::$prefixesPsr0; + $loader->classMap = ComposerStaticInitPhar::$classMap; }, null, ClassLoader::class); } diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_static_files_by_dependency.php b/tests/Composer/Test/Autoload/Fixtures/autoload_static_files_by_dependency.php index ba1f9238a..0c5ce896d 100644 --- a/tests/Composer/Test/Autoload/Fixtures/autoload_static_files_by_dependency.php +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_static_files_by_dependency.php @@ -15,9 +15,14 @@ class ComposerStaticInitFilesAutoloadOrder '334307692417e52db5a08c3271700a7e' => __DIR__ . '/../..' . '/root2.php', ); + public static $classMap = array ( + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + ); + public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { + $loader->classMap = ComposerStaticInitFilesAutoloadOrder::$classMap; }, null, ClassLoader::class); } diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_static_functions.php b/tests/Composer/Test/Autoload/Fixtures/autoload_static_functions.php index dfe3c5bcc..e67b7803e 100644 --- a/tests/Composer/Test/Autoload/Fixtures/autoload_static_functions.php +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_static_functions.php @@ -14,9 +14,14 @@ class ComposerStaticInitFilesAutoload '61b776fd0ee84fb7d7d958ae46118ded' => __DIR__ . '/../..' . '/root.php', ); + public static $classMap = array ( + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + ); + public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { + $loader->classMap = ComposerStaticInitFilesAutoload::$classMap; }, null, ClassLoader::class); } diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_static_functions_with_include_paths.php b/tests/Composer/Test/Autoload/Fixtures/autoload_static_functions_with_include_paths.php index dfe3c5bcc..e67b7803e 100644 --- a/tests/Composer/Test/Autoload/Fixtures/autoload_static_functions_with_include_paths.php +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_static_functions_with_include_paths.php @@ -14,9 +14,14 @@ class ComposerStaticInitFilesAutoload '61b776fd0ee84fb7d7d958ae46118ded' => __DIR__ . '/../..' . '/root.php', ); + public static $classMap = array ( + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + ); + public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { + $loader->classMap = ComposerStaticInitFilesAutoload::$classMap; }, null, ClassLoader::class); } diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_static_functions_with_removed_include_paths_and_autolad_files.php b/tests/Composer/Test/Autoload/Fixtures/autoload_static_functions_with_removed_include_paths_and_autolad_files.php index 11897e11a..44fd701d6 100644 --- a/tests/Composer/Test/Autoload/Fixtures/autoload_static_functions_with_removed_include_paths_and_autolad_files.php +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_static_functions_with_removed_include_paths_and_autolad_files.php @@ -6,9 +6,14 @@ namespace Composer\Autoload; class ComposerStaticInitFilesAutoload { + public static $classMap = array ( + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + ); + public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { + $loader->classMap = ComposerStaticInitFilesAutoload::$classMap; }, null, ClassLoader::class); } diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_static_include_path.php b/tests/Composer/Test/Autoload/Fixtures/autoload_static_include_path.php index 3db5da28a..82bbd2ff7 100644 --- a/tests/Composer/Test/Autoload/Fixtures/autoload_static_include_path.php +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_static_include_path.php @@ -20,10 +20,15 @@ class ComposerStaticInitIncludePath ), ); + public static $classMap = array ( + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + ); + public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { $loader->prefixesPsr0 = ComposerStaticInitIncludePath::$prefixesPsr0; + $loader->classMap = ComposerStaticInitIncludePath::$classMap; }, null, ClassLoader::class); } diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_static_target_dir.php b/tests/Composer/Test/Autoload/Fixtures/autoload_static_target_dir.php index 114b5a98a..60fd33faa 100644 --- a/tests/Composer/Test/Autoload/Fixtures/autoload_static_target_dir.php +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_static_target_dir.php @@ -28,6 +28,7 @@ class ComposerStaticInitTargetDir public static $classMap = array ( 'ClassMapBar' => __DIR__ . '/../..' . '/lib/rootbar.php', 'ClassMapFoo' => __DIR__ . '/../..' . '/src/rootfoo.php', + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', ); public static function getInitializer(ClassLoader $loader) diff --git a/tests/Composer/Test/InstalledVersionsTest.php b/tests/Composer/Test/InstalledVersionsTest.php new file mode 100644 index 000000000..c136e1584 --- /dev/null +++ b/tests/Composer/Test/InstalledVersionsTest.php @@ -0,0 +1,211 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test; + +use Composer\Test\TestCase; +use Composer\InstalledVersions; +use Composer\Semver\VersionParser; + +class InstalledVersionsTest extends TestCase +{ + public function setUp() + { + InstalledVersions::reload(require __DIR__.'/Repository/Fixtures/installed.php'); + } + + public function testGetInstalledPackages() + { + $names = array( + '__root__', + 'a/provider', + 'a/provider2', + 'b/replacer', + 'c/c', + 'foo/impl', + 'foo/impl2', + 'foo/replaced', + ); + $this->assertSame($names, InstalledVersions::getInstalledPackages()); + } + + /** + * @dataProvider isInstalledProvider + */ + public function testIsInstalled($expected, $name, $constraint = null) + { + $this->assertSame($expected, InstalledVersions::isInstalled($name)); + } + + public static function isInstalledProvider() + { + return array( + array(true, 'foo/impl'), + array(true, 'foo/replaced'), + array(true, 'c/c'), + array(true, '__root__'), + array(true, 'b/replacer'), + array(false, 'not/there'), + array(false, 'not/there', '^1.0'), + ); + } + + /** + * @dataProvider satisfiesProvider + */ + public function testSatisfies($expected, $name, $constraint) + { + $this->assertSame($expected, InstalledVersions::satisfies(new VersionParser, $name, $constraint)); + } + + public static function satisfiesProvider() + { + return array( + array(true, 'foo/impl', '1.5'), + array(true, 'foo/impl', '1.2'), + array(true, 'foo/impl', '^1.0'), + array(true, 'foo/impl', '^3 || ^2'), + array(false, 'foo/impl', '^3'), + + array(true, 'foo/replaced', '3.5'), + array(true, 'foo/replaced', '^3.2'), + array(false, 'foo/replaced', '4.0'), + + array(true, 'c/c', '3.0.0'), + array(true, 'c/c', '^3'), + array(false, 'c/c', '^3.1'), + + array(true, '__root__', 'dev-master'), + array(true, '__root__', '^1.10'), + array(false, '__root__', '^2'), + + array(true, 'b/replacer', '^2.1'), + array(false, 'b/replacer', '^2.3'), + + array(true, 'a/provider2', '^1.2'), + array(true, 'a/provider2', '^1.4'), + array(false, 'a/provider2', '^1.5'), + ); + } + + /** + * @dataProvider getVersionRangesProvider + */ + public function testGetVersionRanges($expected, $name) + { + $this->assertSame($expected, InstalledVersions::getVersionRanges($name)); + } + + public static function getVersionRangesProvider() + { + return array( + array('dev-master || 1.10.x-dev', '__root__'), + array('^1.1 || 1.2 || 1.4 || 2.0', 'foo/impl'), + array('2.2 || 2.0', 'foo/impl2'), + array('^3.0', 'foo/replaced'), + array('1.1', 'a/provider'), + array('1.2 || 1.4', 'a/provider2'), + array('2.2', 'b/replacer'), + array('3.0', 'c/c'), + ); + } + + /** + * @dataProvider getVersionProvider + */ + public function testGetVersion($expected, $name) + { + $this->assertSame($expected, InstalledVersions::getVersion($name)); + } + + public static function getVersionProvider() + { + return array( + array('dev-master', '__root__'), + array(null, 'foo/impl'), + array(null, 'foo/impl2'), + array(null, 'foo/replaced'), + array('1.1.0.0', 'a/provider'), + array('1.2.0.0', 'a/provider2'), + array('2.2.0.0', 'b/replacer'), + array('3.0.0.0', 'c/c'), + ); + } + + /** + * @dataProvider getPrettyVersionProvider + */ + public function testGetPrettyVersion($expected, $name) + { + $this->assertSame($expected, InstalledVersions::getPrettyVersion($name)); + } + + public static function getPrettyVersionProvider() + { + return array( + array('dev-master', '__root__'), + array(null, 'foo/impl'), + array(null, 'foo/impl2'), + array(null, 'foo/replaced'), + array('1.1', 'a/provider'), + array('1.2', 'a/provider2'), + array('2.2', 'b/replacer'), + array('3.0', 'c/c'), + ); + } + + public function testGetVersionOutOfBounds() + { + $this->setExpectedException('OutOfBoundsException'); + InstalledVersions::getVersion('not/installed'); + } + + public function testGetRootPackage() + { + $this->assertSame(array( + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'aliases' => array( + '1.10.x-dev', + ), + 'reference' => 'sourceref-by-default', + 'name' => '__root__', + ), InstalledVersions::getRootPackage()); + } + + public function testGetRawData() + { + $this->assertSame(require __DIR__.'/Repository/Fixtures/installed.php', InstalledVersions::getRawData()); + } + + /** + * @dataProvider getReferenceProvider + */ + public function testGetReference($expected, $name) + { + $this->assertSame($expected, InstalledVersions::getReference($name)); + } + + public static function getReferenceProvider() + { + return array( + array('sourceref-by-default', '__root__'), + array(null, 'foo/impl'), + array(null, 'foo/impl2'), + array(null, 'foo/replaced'), + array('distref-as-no-source', 'a/provider'), + array('distref-as-installed-from-dist', 'a/provider2'), + array(null, 'b/replacer'), + array(null, 'c/c'), + ); + } +} diff --git a/tests/Composer/Test/Mock/FactoryMock.php b/tests/Composer/Test/Mock/FactoryMock.php index d4dc444a0..608d572dd 100644 --- a/tests/Composer/Test/Mock/FactoryMock.php +++ b/tests/Composer/Test/Mock/FactoryMock.php @@ -17,6 +17,7 @@ use Composer\Config; use Composer\Factory; use Composer\Repository\RepositoryManager; use Composer\Repository\WritableRepositoryInterface; +use Composer\Package\RootPackageInterface; use Composer\Installer; use Composer\EventDispatcher\EventDispatcher; use Composer\IO\IOInterface; @@ -37,7 +38,7 @@ class FactoryMock extends Factory return $config; } - protected function addLocalRepository(IOInterface $io, RepositoryManager $rm, $vendorDir) + protected function addLocalRepository(IOInterface $io, RepositoryManager $rm, $vendorDir, RootPackageInterface $rootPackage) { } diff --git a/tests/Composer/Test/Repository/FilesystemRepositoryTest.php b/tests/Composer/Test/Repository/FilesystemRepositoryTest.php index 97747ebc5..e2959fde8 100644 --- a/tests/Composer/Test/Repository/FilesystemRepositoryTest.php +++ b/tests/Composer/Test/Repository/FilesystemRepositoryTest.php @@ -14,6 +14,7 @@ namespace Composer\Test\Repository; use Composer\Repository\FilesystemRepository; use Composer\Test\TestCase; +use Composer\Json\JsonFile; class FilesystemRepositoryTest extends TestCase { @@ -113,6 +114,50 @@ class FilesystemRepositoryTest extends TestCase $repository->write(true, $im); } + public function testRepositoryWritesInstalledPhp() + { + $dir = $this->getUniqueTmpDirectory(); + $json = new JsonFile($dir.'/installed.json'); + + $rootPackage = $this->getPackage('__root__', 'dev-master', 'Composer\Package\RootPackage'); + $rootPackage->setSourceReference('sourceref-by-default'); + $rootPackage->setDistReference('distref'); + $this->configureLinks($rootPackage, array('provide' => array('foo/impl' => '2.0'))); + $rootPackage = $this->getAliasPackage($rootPackage, '1.10.x-dev'); + + $repository = new FilesystemRepository($json, true, $rootPackage); + $pkg = $this->getPackage('a/provider', '1.1'); + $this->configureLinks($pkg, array('provide' => array('foo/impl' => '^1.1', 'foo/impl2' => '2.0'))); + $pkg->setDistReference('distref-as-no-source'); + $repository->addPackage($pkg); + + $pkg = $this->getPackage('a/provider2', '1.2'); + $this->configureLinks($pkg, array('provide' => array('foo/impl' => 'self.version', 'foo/impl2' => '2.0'))); + $pkg->setSourceReference('sourceref'); + $pkg->setDistReference('distref-as-installed-from-dist'); + $pkg->setInstallationSource('dist'); + $repository->addPackage($pkg); + + $repository->addPackage($this->getAliasPackage($pkg, '1.4')); + + $pkg = $this->getPackage('b/replacer', '2.2'); + $this->configureLinks($pkg, array('replace' => array('foo/impl2' => 'self.version', 'foo/replaced' => '^3.0'))); + $repository->addPackage($pkg); + + $pkg = $this->getPackage('c/c', '3.0'); + $repository->addPackage($pkg); + + $im = $this->getMockBuilder('Composer\Installer\InstallationManager') + ->disableOriginalConstructor() + ->getMock(); + $im->expects($this->any()) + ->method('getInstallPath') + ->will($this->returnValue('/foo/bar/vendor/woop/woop')); + + $repository->write(true, $im); + $this->assertSame(require __DIR__.'/Fixtures/installed.php', require $dir.'/installed.php'); + } + private function createJsonFileMock() { return $this->getMockBuilder('Composer\Json\JsonFile') diff --git a/tests/Composer/Test/Repository/Fixtures/installed.php b/tests/Composer/Test/Repository/Fixtures/installed.php new file mode 100644 index 000000000..567414a66 --- /dev/null +++ b/tests/Composer/Test/Repository/Fixtures/installed.php @@ -0,0 +1,68 @@ + array( + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'aliases' => array( + '1.10.x-dev', + ), + 'reference' => 'sourceref-by-default', + 'name' => '__root__', + ), + 'versions' => array( + '__root__' => array( + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'aliases' => array( + '1.10.x-dev', + ), + 'reference' => 'sourceref-by-default', + ), + 'a/provider' => array( + 'pretty_version' => '1.1', + 'version' => '1.1.0.0', + 'aliases' => array(), + 'reference' => 'distref-as-no-source', + ), + 'a/provider2' => array( + 'pretty_version' => '1.2', + 'version' => '1.2.0.0', + 'aliases' => array( + '1.4', + ), + 'reference' => 'distref-as-installed-from-dist', + ), + 'b/replacer' => array( + 'pretty_version' => '2.2', + 'version' => '2.2.0.0', + 'aliases' => array(), + 'reference' => NULL, + ), + 'c/c' => array( + 'pretty_version' => '3.0', + 'version' => '3.0.0.0', + 'aliases' => array(), + 'reference' => NULL, + ), + 'foo/impl' => array( + 'provided' => array( + '^1.1', + '1.2', + '1.4', + '2.0', + ) + ), + 'foo/impl2' => array( + 'provided' => array( + '2.0', + ), + 'replaced' => array( + '2.2', + ), + ), + 'foo/replaced' => array( + 'replaced' => array( + '^3.0', + ), + ), + ), +); diff --git a/tests/Composer/Test/TestCase.php b/tests/Composer/Test/TestCase.php index d43d3d911..331d41199 100644 --- a/tests/Composer/Test/TestCase.php +++ b/tests/Composer/Test/TestCase.php @@ -14,11 +14,15 @@ namespace Composer\Test; use Composer\Semver\VersionParser; use Composer\Package\AliasPackage; +use Composer\Package\RootPackageInterface; +use Composer\Package\PackageInterface; use Composer\Semver\Constraint\Constraint; use Composer\Util\Filesystem; use Composer\Util\Silencer; use PHPUnit\Framework\TestCase as BaseTestCase; use Symfony\Component\Process\ExecutableFinder; +use Composer\Package\Loader\ArrayLoader; +use Composer\Package\BasePackage; abstract class TestCase extends BaseTestCase { @@ -73,7 +77,31 @@ abstract class TestCase extends BaseTestCase { $normVersion = self::getVersionParser()->normalize($version); - return new AliasPackage($package, $normVersion, $version); + $class = 'Composer\Package\AliasPackage'; + if ($package instanceof RootPackageInterface) { + $class = 'Composer\Package\RootAliasPackage'; + } + + return new $class($package, $normVersion, $version); + } + + protected function configureLinks(PackageInterface $package, array $config) + { + $arrayLoader = new ArrayLoader(); + + foreach (BasePackage::$supportedLinkTypes as $type => $opts) { + if (isset($config[$type])) { + $method = 'set'.ucfirst($opts['method']); + $package->{$method}( + $arrayLoader->parseLinks( + $package->getName(), + $package->getPrettyVersion(), + $opts['description'], + $config[$type] + ) + ); + } + } } protected static function ensureDirectoryExistsAndClear($directory)