diff --git a/composer.json b/composer.json index ae036f6..3f4931f 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,8 @@ }, "require-dev": { "phpunit/phpunit": "^5.4.7", - "composer/composer": "^1.2.0" + "composer/composer": "^1.3", + "ext-zip": "*" }, "autoload": { "psr-4": { diff --git a/src/PackageVersions/Installer.php b/src/PackageVersions/Installer.php index 7756ce7..81ff5b4 100644 --- a/src/PackageVersions/Installer.php +++ b/src/PackageVersions/Installer.php @@ -80,14 +80,20 @@ public static function getSubscribedEvents() */ public static function dumpVersionsClass(Event $composerEvent) { - $io = $composerEvent->getIO(); + $composer = $composerEvent->getComposer(); + $versions = iterator_to_array(self::getVersions($composer->getLocker(), $composer->getPackage())); - $io->write('ocramius/package-versions: Generating version class...'); + if (!array_key_exists('ocramius/package-versions', $versions)) { + //plugin must be globally installed - we only want to generate versions for projects which specifically + //require ocramius/package-versions + return; + } - $composer = $composerEvent->getComposer(); + $io = $composerEvent->getIO(); + $io->write('ocramius/package-versions: Generating version class...'); self::writeVersionClassToFile( - self::generateVersionsClass($composer), + self::generateVersionsClass($versions), $composer->getConfig(), $composer->getPackage() ); @@ -95,12 +101,12 @@ public static function dumpVersionsClass(Event $composerEvent) $io->write('ocramius/package-versions: ...done generating version class'); } - private static function generateVersionsClass(Composer $composer) : string + private static function generateVersionsClass(array $versions) : string { return sprintf( self::$generatedClassTemplate, 'fin' . 'al ' . 'cla' . 'ss ' . 'Versions', // note: workaround for regex-based code parsers :-( - var_export(iterator_to_array(self::getVersions($composer->getLocker(), $composer->getPackage())), true) + var_export($versions, true) ); } diff --git a/test/PackageVersionsTest/E2EInstallerTest.php b/test/PackageVersionsTest/E2EInstallerTest.php new file mode 100644 index 0000000..8694edd --- /dev/null +++ b/test/PackageVersionsTest/E2EInstallerTest.php @@ -0,0 +1,245 @@ +tempGlobalComposerHome = sys_get_temp_dir() . '/' . uniqid('InstallerTest', true) . '/global'; + $this->tempLocalComposerHome = sys_get_temp_dir() . '/' . uniqid('InstallerTest', true) . '/local'; + $this->tempArtifact = sys_get_temp_dir() . '/' . uniqid('InstallerTest', true) . '/artifacts'; + mkdir($this->tempGlobalComposerHome, 0700, true); + mkdir($this->tempLocalComposerHome, 0700, true); + mkdir($this->tempArtifact, 0700, true); + + putenv('COMPOSER_HOME=' . $this->tempGlobalComposerHome); + } + + public function tearDown() + { + $this->rmDir($this->tempGlobalComposerHome); + $this->rmDir($this->tempLocalComposerHome); + $this->rmDir($this->tempArtifact); + + putenv('COMPOSER_HOME'); + } + + public function testGloballyInstalledPluginDoesNotGenerateVersionsForLocalProject() + { + $this->createPackageVersionsArtifact(); + + $this->writeComposerJsonFile( + [ + 'name' => 'package-versions/e2e-global', + 'require' => [ + 'ocramius/package-versions' => '1.0.0' + ], + 'repositories' => [ + [ + 'packagist' => false, + ], + [ + 'type' => 'artifact', + 'url' => $this->tempArtifact, + ] + ] + ], + $this->tempGlobalComposerHome + ); + + $this->execComposerInDir('global update', $this->tempGlobalComposerHome); + + $this->createArtifact(); + $this->writeComposerJsonFile( + [ + 'name' => 'package-versions/e2e-local', + 'require' => [ + 'test/package' => '2.0.0' + ], + 'repositories' => [ + [ + 'packagist' => false, + ], + [ + 'type' => 'artifact', + 'url' => $this->tempArtifact, + ] + ] + ], + $this->tempLocalComposerHome + ); + + $this->execComposerInDir('update', $this->tempLocalComposerHome); + $this->assertFileNotExists( + $this->tempLocalComposerHome . '/vendor/ocramius/package-versions/src/PackageVersions/Versions.php' + ); + } + + public function testRemovingPluginDoesNotAttemptToGenerateVersions() + { + $this->createPackageVersionsArtifact(); + $this->createArtifact(); + + $this->writeComposerJsonFile( + [ + 'name' => 'package-versions/e2e-local', + 'require' => [ + 'test/package' => '2.0.0', + 'ocramius/package-versions' => '1.0.0' + ], + 'repositories' => [ + [ + 'packagist' => false, + ], + [ + 'type' => 'artifact', + 'url' => $this->tempArtifact, + ] + ] + ], + $this->tempLocalComposerHome + ); + + $this->execComposerInDir('update', $this->tempLocalComposerHome); + $this->assertFileExists( + $this->tempLocalComposerHome . '/vendor/ocramius/package-versions/src/PackageVersions/Versions.php' + ); + + $this->execComposerInDir('remove ocramius/package-versions', $this->tempLocalComposerHome); + + $this->assertFileNotExists( + $this->tempLocalComposerHome . '/vendor/ocramius/package-versions/src/PackageVersions/Versions.php' + ); + } + + private function createPackageVersionsArtifact() + { + $zip = new ZipArchive(); + + $zip->open($this->tempArtifact . '/ocramius-package-versions-1.0.0.zip', ZipArchive::CREATE); + + $files = array_filter( + iterator_to_array(new RecursiveIteratorIterator( + new RecursiveCallbackFilterIterator( + new RecursiveDirectoryIterator(realpath(__DIR__ . '/../../'), RecursiveDirectoryIterator::SKIP_DOTS), + function (SplFileInfo $file, $key, RecursiveDirectoryIterator $iterator) { + return $iterator->getSubPathname()[0] !== '.' && $iterator->getSubPathname() !== 'vendor'; + } + ), + RecursiveIteratorIterator::LEAVES_ONLY + )), + function (SplFileInfo $file) { + return !$file->isDir(); + } + ); + + array_walk( + $files, + function (SplFileInfo $file) use ($zip) { + if ($file->getFilename() === 'composer.json') { + $contents = json_decode(file_get_contents($file->getRealPath()), true); + $contents['version'] = '1.0.0'; + + return $zip->addFromString('composer.json', json_encode($contents)); + } + + $zip->addFile( + $file->getRealPath(), + substr($file->getRealPath(), strlen(realpath(__DIR__ . '/../../')) + 1) + ); + } + ); + + $zip->close(); + } + + private function createArtifact() + { + $zip = new ZipArchive(); + + $zip->open($this->tempArtifact . '/test-package-2.0.0.zip', ZipArchive::CREATE); + $zip->addFromString( + 'composer.json', + json_encode( + [ + 'name' => 'test/package', + 'version' => '2.0.0' + ], + JSON_PRETTY_PRINT + ) + ); + $zip->close(); + } + + private function writeComposerJsonFile(array $config, string $directory) + { + file_put_contents( + $directory . '/composer.json', + json_encode($config, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) + ); + } + + private function execComposerInDir(string $command, string $dir) : array + { + $currentDir = getcwd(); + chdir($dir); + exec(__DIR__ . '/../../vendor/bin/composer ' . $command . ' 2> /dev/null', $output, $exitCode); + $this->assertEquals(0, $exitCode); + chdir($currentDir); + return $output; + } + + /** + * @param string $directory + * + * @return void + */ + private function rmDir(string $directory) + { + if (! is_dir($directory)) { + unlink($directory); + + return; + } + + array_map( + function ($item) use ($directory) { + $this->rmDir($directory . '/' . $item); + }, + array_filter( + scandir($directory), + function (string $dirItem) { + return ! in_array($dirItem, ['.', '..'], true); + } + ) + ); + + rmdir($directory); + } +} diff --git a/test/PackageVersionsTest/InstallerTest.php b/test/PackageVersionsTest/InstallerTest.php index c7cac80..bebd0ab 100644 --- a/test/PackageVersionsTest/InstallerTest.php +++ b/test/PackageVersionsTest/InstallerTest.php @@ -96,6 +96,10 @@ public function testDumpVersionsClass() ->method('getLockData') ->willReturn([ 'packages' => [ + [ + 'name' => 'ocramius/package-versions', + 'version' => '1.0.0', + ], [ 'name' => 'foo/bar', 'version' => '1.2.3', @@ -156,6 +160,7 @@ public function testDumpVersionsClass() final class Versions { const VERSIONS = array ( + 'ocramius/package-versions' => '1.0.0@', 'foo/bar' => '1.2.3@abc123', 'baz/tab' => '4.5.6@def456', 'tar/taz' => '7.8.9@ghi789', @@ -208,6 +213,10 @@ public function testDumpVersionsClassNoDev() ->method('getLockData') ->willReturn([ 'packages' => [ + [ + 'name' => 'ocramius/package-versions', + 'version' => '1.0.0', + ], [ 'name' => 'foo/bar', 'version' => '1.2.3', @@ -259,6 +268,7 @@ public function testDumpVersionsClassNoDev() final class Versions { const VERSIONS = array ( + 'ocramius/package-versions' => '1.0.0@', 'foo/bar' => '1.2.3@abc123', 'baz/tab' => '4.5.6@def456', 'root/package' => '1.3.5@aaabbbcccddd', @@ -315,6 +325,10 @@ public function testDumpVersionsWithoutPackageSourceDetails() ->method('getLockData') ->willReturn([ 'packages' => [ + [ + 'name' => 'ocramius/package-versions', + 'version' => '1.0.0', + ], [ 'name' => 'foo/bar', 'version' => '1.2.3', @@ -363,6 +377,7 @@ public function testDumpVersionsWithoutPackageSourceDetails() final class Versions { const VERSIONS = array ( + 'ocramius/package-versions' => '1.0.0@', 'foo/bar' => '1.2.3@abc123', 'baz/tab' => '4.5.6@', 'root/package' => '1.3.5@aaabbbcccddd', @@ -424,7 +439,12 @@ public function testDumpsVersionsClassToSpecificLocation(RootPackageInterface $r ->expects(self::any()) ->method('getLockData') ->willReturn([ - 'packages' => [], + 'packages' => [ + [ + 'name' => 'ocramius/package-versions', + 'version' => '1.0.0', + ] + ], 'packages-dev' => [], ]); @@ -497,6 +517,65 @@ public function rootPackageProvider() : array ]; } + public function testVersionsAreNotDumpedIfPackageVersionsNotExplicitlyRequired() + { + $config = $this->getMockBuilder(Config::class)->disableOriginalConstructor()->getMock(); + $locker = $this->getMockBuilder(Locker::class)->disableOriginalConstructor()->getMock(); + $repositoryManager = $this->getMockBuilder(RepositoryManager::class)->disableOriginalConstructor()->getMock(); + $installManager = $this->getMockBuilder(InstallationManager::class)->disableOriginalConstructor()->getMock(); + $repository = $this->createMock(InstalledRepositoryInterface::class); + $package = $this->createMock(RootPackageInterface::class); + + $vendorDir = sys_get_temp_dir() . '/' . uniqid('InstallerTest', true); + + $expectedPath = $vendorDir . '/ocramius/package-versions/src/PackageVersions'; + + mkdir($expectedPath, 0777, true); + + $locker + ->expects(self::any()) + ->method('getLockData') + ->willReturn([ + 'packages' => [ + [ + 'name' => 'foo/bar', + 'version' => '1.2.3', + 'dist' => [ + 'reference' => 'abc123', // version defined in the dist, this time + ], + ], + [ + 'name' => 'baz/tab', + 'version' => '4.5.6', // source missing + ], + ], + ]); + + $repositoryManager->expects(self::any())->method('getLocalRepository')->willReturn($repository); + + $this->composer->expects(self::any())->method('getConfig')->willReturn($config); + $this->composer->expects(self::any())->method('getLocker')->willReturn($locker); + $this->composer->expects(self::any())->method('getRepositoryManager')->willReturn($repositoryManager); + $this->composer->expects(self::any())->method('getPackage')->willReturn($package); + $this->composer->expects(self::any())->method('getInstallationManager')->willReturn($installManager); + + $package->expects(self::any())->method('getName')->willReturn('root/package'); + $package->expects(self::any())->method('getVersion')->willReturn('1.3.5'); + $package->expects(self::any())->method('getSourceReference')->willReturn('aaabbbcccddd'); + + $config->expects(self::any())->method('get')->with('vendor-dir')->willReturn($vendorDir); + + Installer::dumpVersionsClass(new Event( + 'post-install-cmd', + $this->composer, + $this->io + )); + + self::assertFileNotExists($expectedPath . '/Versions.php'); + + $this->rmDir($vendorDir); + } + /** * @param string $directory *