From 0ca470cd650978610ab14f3e74e62bf7991d8bfe Mon Sep 17 00:00:00 2001 From: cesc delgado Date: Mon, 20 May 2019 11:27:40 +0200 Subject: [PATCH 1/3] fix with composer.lock --- spec/Composer/StudioPluginSpec.php | 171 ++++++++++++++++ .../project-with-path/library/composer.json | 3 + .../project-with-unload/library/composer.json | 3 + src/Composer/StudioPlugin.php | 185 ++++++++++++++++-- 4 files changed, 343 insertions(+), 19 deletions(-) create mode 100644 spec/Composer/StudioPluginSpec.php create mode 100644 spec/Composer/stubs/project-with-path/library/composer.json create mode 100644 spec/Composer/stubs/project-with-unload/library/composer.json diff --git a/spec/Composer/StudioPluginSpec.php b/spec/Composer/StudioPluginSpec.php new file mode 100644 index 0000000..ae66149 --- /dev/null +++ b/spec/Composer/StudioPluginSpec.php @@ -0,0 +1,171 @@ +shouldHaveType('Studio\Composer\StudioPlugin'); + } + + function it_is_activatable(Composer $composer, IOInterface $io) + { + $this->activate($composer, $io); + } + + function it_resolves_subscribed_events() + { + self::getSubscribedEvents()->shouldReturn([ + ScriptEvents::PRE_UPDATE_CMD => 'unlinkStudioPackages', + ScriptEvents::POST_UPDATE_CMD => 'symlinkStudioPackages', + ScriptEvents::POST_INSTALL_CMD => 'symlinkStudioPackages', + ScriptEvents::PRE_AUTOLOAD_DUMP => 'symlinkStudioPackages' + ]); + } + + /** + * Test if studio does not create symlinks when no studio.json is defined + */ + function it_doesnt_create_symlinks_without_file($composer, $io, $rootPackage, $filesystem) + { + // switch working directory + chdir(__DIR__); + + // create stubs + $filesystem->beADoubleOf('Composer\Util\Filesystem'); + $rootPackage->beADoubleOf('Composer\Package\RootPackage'); + $composer->beADoubleOf('Composer\Composer'); + $io->beADoubleOf('Composer\IO\IOInterface'); + + // Construct + $this->beConstructedWith($filesystem); + + // Mock methods + $composer->getInstallationManager()->willReturn(null); + $composer->getDownloadManager()->willReturn(null); + $composer->getPackage()->willReturn($rootPackage); + $rootPackage->getTargetDir()->willReturn(getcwd()); + + // Test + $this->activate($composer, $io); + $this->symlinkStudioPackages(); + } + + /** + * Test if studio does not unlink when no studio.json or .studio/studio.json is defined + */ + function it_doesnt_unlink_without_files($composer, $io, $rootPackage, $filesystem) + { + // switch working directory + chdir(__DIR__); + + // create stubs + $filesystem->beADoubleOf('Composer\Util\Filesystem'); + $rootPackage->beADoubleOf('Composer\Package\RootPackage'); + $composer->beADoubleOf('Composer\Composer'); + $io->beADoubleOf('Composer\IO\IOInterface'); + + // Construct + $this->beConstructedWith($filesystem); + + // Mock methods + $composer->getInstallationManager()->willReturn(null); + $composer->getDownloadManager()->willReturn(null); + $composer->getPackage()->willReturn($rootPackage); + $rootPackage->getTargetDir()->willReturn(getcwd()); + + // Test + $this->activate($composer, $io); + $this->unlinkStudioPackages(); + } + + /** + * Test if studio does create symlinks when studio.json is defined + */ + function it_does_create_symlinks_with_file( + $composer, + $io, + $rootPackage, + $filesystem, + $installationManager, + $downloadManager, + $pathDownloader + ) { + // switch working directory + chdir(__DIR__ . '/stubs/project-with-path'); + + // create stubs + $filesystem->beADoubleOf('Composer\Util\Filesystem'); + $rootPackage->beADoubleOf('Composer\Package\RootPackage'); + $composer->beADoubleOf('Composer\Composer'); + $io->beADoubleOf('Composer\IO\IOInterface'); + $installationManager->beADoubleOf('Composer\Installer\InstallationManager'); + $downloadManager->beADoubleOf('Composer\Downloader\DownloadManager'); + $pathDownloader->beADoubleOf('Composer\Downloader\PathDownloader'); + + // Construct + //$this->beConstructedWith($filesystem); + + // Mock methods + $composer->getInstallationManager()->willReturn($installationManager); + $composer->getDownloadManager()->willReturn($downloadManager); + $composer->getPackage()->willReturn($rootPackage); + $rootPackage->getTargetDir()->willReturn(getcwd()); + $downloadManager->getDownloader('path') + ->willReturn($pathDownloader) + ->shouldBeCalled(); + + $io->write('[Studio] Creating link to library for package acme/library')->shouldBeCalled(); + + // Test + $this->activate($composer, $io); + $this->symlinkStudioPackages(); + } + + + /** + * Test if studio does unlink when studio.json is defined + */ + function it_does_unlink_with_file( + $composer, + $io, + $rootPackage, + $filesystem, + $installationManager, + $pathDownloader + ) { + // switch working directory + chdir(__DIR__ . '/stubs/project-with-unload'); + + // create stubs + $filesystem->beADoubleOf('Composer\Util\Filesystem'); + $rootPackage->beADoubleOf('Composer\Package\RootPackage'); + $composer->beADoubleOf('Composer\Composer'); + $io->beADoubleOf('Composer\IO\IOInterface'); + $installationManager->beADoubleOf('Composer\Installer\InstallationManager'); + $pathDownloader->beADoubleOf('Composer\Downloader\PathDownloader'); + + // Construct + $this->beConstructedWith($filesystem); + + // Mock methods + $composer->getInstallationManager()->willReturn($installationManager); + $composer->getDownloadManager()->willReturn(null); + $composer->getPackage()->willReturn($rootPackage); + $rootPackage->getTargetDir()->willReturn(getcwd()); + $filesystem->isSymlinkedDirectory(null)->willReturn(true)->shouldBeCalled(); + $filesystem->removeDirectory(null)->shouldBeCalled(); + + $io->write('[Studio] Removing linked path library for package acme/library')->shouldBeCalled(); + + // Test + $this->activate($composer, $io); + $this->unlinkStudioPackages(); + } +} diff --git a/spec/Composer/stubs/project-with-path/library/composer.json b/spec/Composer/stubs/project-with-path/library/composer.json new file mode 100644 index 0000000..ae0bdf7 --- /dev/null +++ b/spec/Composer/stubs/project-with-path/library/composer.json @@ -0,0 +1,3 @@ +{ + "name": "acme/library" +} \ No newline at end of file diff --git a/spec/Composer/stubs/project-with-unload/library/composer.json b/spec/Composer/stubs/project-with-unload/library/composer.json new file mode 100644 index 0000000..ae0bdf7 --- /dev/null +++ b/spec/Composer/stubs/project-with-unload/library/composer.json @@ -0,0 +1,3 @@ +{ + "name": "acme/library" +} \ No newline at end of file diff --git a/src/Composer/StudioPlugin.php b/src/Composer/StudioPlugin.php index c1b3b41..225d34a 100644 --- a/src/Composer/StudioPlugin.php +++ b/src/Composer/StudioPlugin.php @@ -3,14 +3,24 @@ namespace Studio\Composer; use Composer\Composer; +use Composer\Downloader\DownloadManager; use Composer\EventDispatcher\EventSubscriberInterface; +use Composer\Installer\InstallationManager; use Composer\IO\IOInterface; +use Composer\Json\JsonFile; +use Composer\Package\Loader\ArrayLoader; +use Composer\Package\Package; +use Composer\Package\RootPackageInterface; use Composer\Plugin\PluginInterface; -use Composer\Repository\PathRepository; use Composer\Script\ScriptEvents; +use Composer\Util\Filesystem; use Studio\Config\Config; -use Studio\Config\FileStorage; +/** + * Class StudioPlugin + * + * @package Studio\Composer + */ class StudioPlugin implements PluginInterface, EventSubscriberInterface { /** @@ -23,42 +33,166 @@ class StudioPlugin implements PluginInterface, EventSubscriberInterface */ protected $io; + /** + * @var Filesystem + */ + protected $filesystem; + + /** + * @var DownloadManager + */ + protected $downloadManager; + + /** + * @var InstallationManager + */ + protected $installationManager; + + /** + * @var RootPackageInterface + */ + protected $rootPackage; + + /** + * StudioPlugin constructor. + * + * @param Filesystem|null $filesystem + */ + public function __construct(Filesystem $filesystem = null) + { + $this->filesystem = $filesystem ?: new Filesystem(); + } + + /** + * @param Composer $composer + * @param IOInterface $io + */ public function activate(Composer $composer, IOInterface $io) { $this->composer = $composer; $this->io = $io; + $this->installationManager = $composer->getInstallationManager(); + $this->downloadManager = $composer->getDownloadManager(); + $this->rootPackage = $composer->getPackage(); } + /** + * @return array + */ public static function getSubscribedEvents() { return [ - ScriptEvents::PRE_INSTALL_CMD => 'registerStudioPackages', - ScriptEvents::PRE_UPDATE_CMD => 'registerStudioPackages', + ScriptEvents::PRE_UPDATE_CMD => 'unlinkStudioPackages', + ScriptEvents::POST_UPDATE_CMD => 'symlinkStudioPackages', + ScriptEvents::POST_INSTALL_CMD => 'symlinkStudioPackages', + ScriptEvents::PRE_AUTOLOAD_DUMP => 'symlinkStudioPackages' ]; } /** - * Register all managed paths with Composer. + * Symlink all managed paths by studio + * + * This happens just before the autoload generator kicks in except with --no-autoloader + * In that case we create the symlinks on the POST_UPDATE, POST_INSTALL events * - * This function configures Composer to treat all Studio-managed paths as local path repositories, so that packages - * therein will be symlinked directly. */ - public function registerStudioPackages() + public function symlinkStudioPackages() { - $repoManager = $this->composer->getRepositoryManager(); - $composerConfig = $this->composer->getConfig(); - + $studioDir = realpath($this->rootPackage->getTargetDir()) . DIRECTORY_SEPARATOR . '.studio'; foreach ($this->getManagedPaths() as $path) { - $this->io->writeError("[Studio] Loading path $path"); + $package = $this->createPackageForPath($path); + $destination = $this->installationManager->getInstallPath($package); + + // Creates the symlink to the package + if (!$this->filesystem->isSymlinkedDirectory($destination) && + !$this->filesystem->isJunction($destination) + ) { + $this->io->write("[Studio] Creating link to $path for package " . $package->getName()); + + // Create copy of original in the `.studio` directory, + // we use the original on the next `composer update` + if (is_dir($destination)) { + $copyPath = $studioDir . DIRECTORY_SEPARATOR . $package->getName(); + $this->filesystem->ensureDirectoryExists($copyPath); + $this->filesystem->copyThenRemove($destination, $copyPath); + } - $repoManager->prependRepository(new PathRepository( - ['url' => $path], - $this->io, - $composerConfig - )); + // Download the managed package from its path with the composer downloader + $pathDownloader = $this->downloadManager->getDownloader('path'); + $pathDownloader->download($package, $destination); + } + } + + // ensure the `.studio` directory only if we manage paths. + // without this check studio will create the `.studio` directory + // in all projects where composer is used + if (count($this->getManagedPaths())) { + $this->filesystem->ensureDirectoryExists('.studio'); + } + + // if we have managed paths or did have we copy the current studio.json + if (count($this->getManagedPaths()) > 0 || + count($this->getPreviouslyManagedPaths()) > 0 + ) { + // If we have the current studio.json copy it to the .studio directory + $studioFile = realpath($this->rootPackage->getTargetDir()) . DIRECTORY_SEPARATOR . 'studio.json'; + if (file_exists($studioFile)) { + copy($studioFile, $studioDir . DIRECTORY_SEPARATOR . 'studio.json'); + } } } + /** + * Removes all symlinks managed by studio + * + */ + public function unlinkStudioPackages() + { + $studioDir = realpath($this->rootPackage->getTargetDir()) . DIRECTORY_SEPARATOR . '.studio'; + $paths = array_merge($this->getPreviouslyManagedPaths(), $this->getManagedPaths()); + + foreach ($paths as $path) { + $package = $this->createPackageForPath($path); + $destination = $this->installationManager->getInstallPath($package); + + if ($this->filesystem->isSymlinkedDirectory($destination) || + $this->filesystem->isJunction($destination) + ) { + $this->io->write("[Studio] Removing linked path $path for package " . $package->getName()); + $this->filesystem->removeDirectory($destination); + + // If we have an original copy move it back + $copyPath = $studioDir . DIRECTORY_SEPARATOR . $package->getName(); + if (is_dir($copyPath)) { + $this->filesystem->copyThenRemove($copyPath, $destination); + } + } + } + } + + /** + * Creates package from given path + * + * @param string $path + * @return Package + */ + private function createPackageForPath($path) + { + $json = (new JsonFile( + realpath($path . DIRECTORY_SEPARATOR . 'composer.json') + ))->read(); + $json['version'] = 'dev-master'; + + // branch alias won't work, otherwise the ArrayLoader::load won't return an instance of CompletePackage + unset($json['extra']['branch-alias']); + + $loader = new ArrayLoader(); + $package = $loader->load($json); + $package->setDistUrl($path); + + return $package; + } + /** * Get the list of paths that are being managed by Studio. * @@ -66,8 +200,21 @@ public function registerStudioPackages() */ private function getManagedPaths() { - $targetDir = realpath($this->composer->getPackage()->getTargetDir()); - $config = Config::make("{$targetDir}/studio.json"); + $targetDir = realpath($this->rootPackage->getTargetDir()); + $config = Config::make($targetDir . DIRECTORY_SEPARATOR . 'studio.json'); + + return $config->getPaths(); + } + + /** + * Get last known managed paths by studio + * + * @return array + */ + private function getPreviouslyManagedPaths() + { + $targetDir = realpath($this->rootPackage->getTargetDir()) . DIRECTORY_SEPARATOR . '.studio'; + $config = Config::make($targetDir . DIRECTORY_SEPARATOR . 'studio.json'); return $config->getPaths(); } From 823237258a1059436aa0a1e6c66fe5c267c4db3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Alvergnat?= Date: Thu, 6 Feb 2020 15:39:55 +0100 Subject: [PATCH 2/3] Add wildcard glob support and link dependencies only --- src/Composer/StudioPlugin.php | 147 ++++++++++++++++++++++++---------- 1 file changed, 106 insertions(+), 41 deletions(-) diff --git a/src/Composer/StudioPlugin.php b/src/Composer/StudioPlugin.php index 225d34a..b6a9e04 100644 --- a/src/Composer/StudioPlugin.php +++ b/src/Composer/StudioPlugin.php @@ -12,6 +12,7 @@ use Composer\Package\Package; use Composer\Package\RootPackageInterface; use Composer\Plugin\PluginInterface; +use Composer\Repository\WritableRepositoryInterface; use Composer\Script\ScriptEvents; use Composer\Util\Filesystem; use Studio\Config\Config; @@ -53,6 +54,11 @@ class StudioPlugin implements PluginInterface, EventSubscriberInterface */ protected $rootPackage; + /** + * @var WritableRepositoryInterface + */ + private $localRepository; + /** * StudioPlugin constructor. * @@ -74,6 +80,7 @@ public function activate(Composer $composer, IOInterface $io) $this->installationManager = $composer->getInstallationManager(); $this->downloadManager = $composer->getDownloadManager(); $this->rootPackage = $composer->getPackage(); + $this->localRepository = $composer->getRepositoryManager()->getLocalRepository(); } /** @@ -100,27 +107,36 @@ public function symlinkStudioPackages() { $studioDir = realpath($this->rootPackage->getTargetDir()) . DIRECTORY_SEPARATOR . '.studio'; foreach ($this->getManagedPaths() as $path) { - $package = $this->createPackageForPath($path); - $destination = $this->installationManager->getInstallPath($package); - - // Creates the symlink to the package - if (!$this->filesystem->isSymlinkedDirectory($destination) && - !$this->filesystem->isJunction($destination) - ) { - $this->io->write("[Studio] Creating link to $path for package " . $package->getName()); - - // Create copy of original in the `.studio` directory, - // we use the original on the next `composer update` - if (is_dir($destination)) { - $copyPath = $studioDir . DIRECTORY_SEPARATOR . $package->getName(); - $this->filesystem->ensureDirectoryExists($copyPath); - $this->filesystem->copyThenRemove($destination, $copyPath); + $resolvedPaths = $this->resolvePath($path); + + foreach ($resolvedPaths as $resolvedPath) { + $package = $this->createPackageForPath($resolvedPath); + if (!$package || !$this->isLocalRepositoryPackage($package)) { + continue; } - // Download the managed package from its path with the composer downloader - $pathDownloader = $this->downloadManager->getDownloader('path'); - $pathDownloader->download($package, $destination); + $destination = $this->installationManager->getInstallPath($package); + + // Creates the symlink to the package + if (!$this->filesystem->isSymlinkedDirectory($destination) && + !$this->filesystem->isJunction($destination) + ) { + $this->io->write("[Studio] Creating link to $resolvedPath for package " . $package->getName()); + + // Create copy of original in the `.studio` directory, + // we use the original on the next `composer update` + if (is_dir($destination)) { + $copyPath = $studioDir . DIRECTORY_SEPARATOR . $package->getName(); + $this->filesystem->ensureDirectoryExists($copyPath); + $this->filesystem->copyThenRemove($destination, $copyPath); + } + + // Download the managed package from its path with the composer downloader + $pathDownloader = $this->downloadManager->getDownloader('path'); + $pathDownloader->download($package, $destination); + } } + } // ensure the `.studio` directory only if we manage paths. @@ -152,19 +168,27 @@ public function unlinkStudioPackages() $paths = array_merge($this->getPreviouslyManagedPaths(), $this->getManagedPaths()); foreach ($paths as $path) { - $package = $this->createPackageForPath($path); - $destination = $this->installationManager->getInstallPath($package); - - if ($this->filesystem->isSymlinkedDirectory($destination) || - $this->filesystem->isJunction($destination) - ) { - $this->io->write("[Studio] Removing linked path $path for package " . $package->getName()); - $this->filesystem->removeDirectory($destination); - - // If we have an original copy move it back - $copyPath = $studioDir . DIRECTORY_SEPARATOR . $package->getName(); - if (is_dir($copyPath)) { - $this->filesystem->copyThenRemove($copyPath, $destination); + $resolvedPaths = $this->resolvePath($path); + + foreach ($resolvedPaths as $resolvedPath) { + $package = $this->createPackageForPath($resolvedPath); + if ($package == null) { + continue; + } + + $destination = $this->installationManager->getInstallPath($package); + + if ($this->filesystem->isSymlinkedDirectory($destination) || + $this->filesystem->isJunction($destination) + ) { + $this->io->write("[Studio] Removing linked path $resolvedPath for package " . $package->getName()); + $this->filesystem->removeDirectory($destination); + + // If we have an original copy move it back + $copyPath = $studioDir . DIRECTORY_SEPARATOR . $package->getName(); + if (is_dir($copyPath)) { + $this->filesystem->copyThenRemove($copyPath, $destination); + } } } } @@ -178,19 +202,60 @@ public function unlinkStudioPackages() */ private function createPackageForPath($path) { - $json = (new JsonFile( - realpath($path . DIRECTORY_SEPARATOR . 'composer.json') - ))->read(); - $json['version'] = 'dev-master'; + $composerJson = $path . DIRECTORY_SEPARATOR . 'composer.json'; + + if (is_readable($composerJson)) { + $json = (new JsonFile($composerJson))->read(); + $json['version'] = 'dev-master'; - // branch alias won't work, otherwise the ArrayLoader::load won't return an instance of CompletePackage - unset($json['extra']['branch-alias']); + // branch alias won't work, otherwise the ArrayLoader::load won't return an instance of CompletePackage + unset($json['extra']['branch-alias']); - $loader = new ArrayLoader(); - $package = $loader->load($json); - $package->setDistUrl($path); + $loader = new ArrayLoader(); + $package = $loader->load($json); + $package->setDistUrl($path); - return $package; + return $package; + } + + return NULL; + } + + + /** + * Resolve path with glob to an array of existing paths. + * + * @param string $path + * @return string[] + */ + private function resolvePath($path) + { + /** @var string[] $paths */ + $paths = []; + + $realPaths = glob($path); + foreach ($realPaths as $realPath) { + if (!in_array($realPath, $paths)) { + $paths[] = $realPath; + } + } + + return $paths; + } + + /** + * Check if this package is a dependency (transitive or not) of the root package. + * + * @param Package $package + * @return bool + */ + private function isLocalRepositoryPackage($package) { + foreach ($this->localRepository->getPackages() as $localRepositoryPackage) { + if ($localRepositoryPackage->getName() == $package->getName()) { + return true; + } + } + return false; } /** From b2c423736f2a2d3ceaf907b0edfe022118d87d79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Alvergnat?= Date: Thu, 6 Feb 2020 16:44:18 +0100 Subject: [PATCH 3/3] Add more tests for wildcard glob support and fix other tests --- .gitignore | 3 +- spec/Composer/StudioPluginSpec.php | 157 +++++++++++++++++- .../stubs/libs/another-library/composer.json | 3 + .../library/composer.json | 0 .../stubs/libs/library2/composer.json | 3 + .../project-with-path-wildcard/.gitignore | 1 + .../project-with-path-wildcard/studio.json | 6 + .../stubs/project-with-path/.gitignore | 1 + .../stubs/project-with-path/studio.json | 6 + .../.studio/studio.json | 6 + .../project-with-unload-wildcard/studio.json | 4 + .../project-with-unload/.studio/studio.json | 6 + .../project-with-unload/library/composer.json | 3 - .../stubs/project-with-unload/studio.json | 4 + .../stubs/project-without-path/.gitignore | 0 15 files changed, 192 insertions(+), 11 deletions(-) create mode 100644 spec/Composer/stubs/libs/another-library/composer.json rename spec/Composer/stubs/{project-with-path => libs}/library/composer.json (100%) create mode 100644 spec/Composer/stubs/libs/library2/composer.json create mode 100644 spec/Composer/stubs/project-with-path-wildcard/.gitignore create mode 100644 spec/Composer/stubs/project-with-path-wildcard/studio.json create mode 100644 spec/Composer/stubs/project-with-path/.gitignore create mode 100644 spec/Composer/stubs/project-with-path/studio.json create mode 100644 spec/Composer/stubs/project-with-unload-wildcard/.studio/studio.json create mode 100644 spec/Composer/stubs/project-with-unload-wildcard/studio.json create mode 100644 spec/Composer/stubs/project-with-unload/.studio/studio.json delete mode 100644 spec/Composer/stubs/project-with-unload/library/composer.json create mode 100644 spec/Composer/stubs/project-with-unload/studio.json create mode 100644 spec/Composer/stubs/project-without-path/.gitignore diff --git a/.gitignore b/.gitignore index aa0f446..4305edb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ -studio.json +/studio.json +/.studio/ composer.phar composer.lock vendor/ diff --git a/spec/Composer/StudioPluginSpec.php b/spec/Composer/StudioPluginSpec.php index ae66149..4d5438f 100644 --- a/spec/Composer/StudioPluginSpec.php +++ b/spec/Composer/StudioPluginSpec.php @@ -4,6 +4,9 @@ use Composer\Composer; use Composer\IO\IOInterface; +use Composer\Repository\ArrayRepository; +use Composer\Repository\RepositoryManager; +use Composer\Repository\WritableArrayRepository; use Composer\Script\ScriptEvents; use PhpSpec\ObjectBehavior; @@ -14,8 +17,14 @@ function it_is_initializable() $this->shouldHaveType('Studio\Composer\StudioPlugin'); } - function it_is_activatable(Composer $composer, IOInterface $io) + function it_is_activatable(Composer $composer, IOInterface $io, RepositoryManager $repositoryManager) { + // Mock methods + $composer->getInstallationManager()->willReturn(null); + $composer->getDownloadManager()->willReturn(null); + $composer->getPackage()->willReturn(null); + $composer->getRepositoryManager()->willReturn($repositoryManager); + $this->activate($composer, $io); } @@ -32,7 +41,7 @@ function it_resolves_subscribed_events() /** * Test if studio does not create symlinks when no studio.json is defined */ - function it_doesnt_create_symlinks_without_file($composer, $io, $rootPackage, $filesystem) + function it_doesnt_create_symlinks_without_file($composer, $io, $rootPackage, $filesystem, $repositoryManager) { // switch working directory chdir(__DIR__); @@ -42,6 +51,7 @@ function it_doesnt_create_symlinks_without_file($composer, $io, $rootPackage, $f $rootPackage->beADoubleOf('Composer\Package\RootPackage'); $composer->beADoubleOf('Composer\Composer'); $io->beADoubleOf('Composer\IO\IOInterface'); + $repositoryManager->beADoubleOf('Composer\Repository\RepositoryManager'); // Construct $this->beConstructedWith($filesystem); @@ -50,6 +60,7 @@ function it_doesnt_create_symlinks_without_file($composer, $io, $rootPackage, $f $composer->getInstallationManager()->willReturn(null); $composer->getDownloadManager()->willReturn(null); $composer->getPackage()->willReturn($rootPackage); + $composer->getRepositoryManager()->willReturn($repositoryManager); $rootPackage->getTargetDir()->willReturn(getcwd()); // Test @@ -60,7 +71,7 @@ function it_doesnt_create_symlinks_without_file($composer, $io, $rootPackage, $f /** * Test if studio does not unlink when no studio.json or .studio/studio.json is defined */ - function it_doesnt_unlink_without_files($composer, $io, $rootPackage, $filesystem) + function it_doesnt_unlink_without_files($composer, $io, $rootPackage, $filesystem, $repositoryManager) { // switch working directory chdir(__DIR__); @@ -70,6 +81,7 @@ function it_doesnt_unlink_without_files($composer, $io, $rootPackage, $filesyste $rootPackage->beADoubleOf('Composer\Package\RootPackage'); $composer->beADoubleOf('Composer\Composer'); $io->beADoubleOf('Composer\IO\IOInterface'); + $repositoryManager->beADoubleOf('Composer\Repository\RepositoryManager'); // Construct $this->beConstructedWith($filesystem); @@ -78,6 +90,7 @@ function it_doesnt_unlink_without_files($composer, $io, $rootPackage, $filesyste $composer->getInstallationManager()->willReturn(null); $composer->getDownloadManager()->willReturn(null); $composer->getPackage()->willReturn($rootPackage); + $composer->getRepositoryManager()->willReturn($repositoryManager); $rootPackage->getTargetDir()->willReturn(getcwd()); // Test @@ -95,7 +108,11 @@ function it_does_create_symlinks_with_file( $filesystem, $installationManager, $downloadManager, - $pathDownloader + $pathDownloader, + $repositoryManager, + $localRepository, + $libraryPackage, + $library2Package ) { // switch working directory chdir(__DIR__ . '/stubs/project-with-path'); @@ -108,6 +125,16 @@ function it_does_create_symlinks_with_file( $installationManager->beADoubleOf('Composer\Installer\InstallationManager'); $downloadManager->beADoubleOf('Composer\Downloader\DownloadManager'); $pathDownloader->beADoubleOf('Composer\Downloader\PathDownloader'); + $repositoryManager->beADoubleOf('Composer\Repository\RepositoryManager'); + $localRepository->beADoubleOf('Composer\Repository\WritableRepositoryInterface'); + $libraryPackage->beADoubleOf('Composer\Package\Package'); + $library2Package->beADoubleOf('Composer\Package\Package'); + + $libraryPackage->getName()->willReturn("acme/library"); + $library2Package->getName()->willReturn("acme/library2"); + + $localRepository->getPackages()->willReturn([$libraryPackage, $library2Package]); + $repositoryManager->getLocalRepository()->willReturn($localRepository); // Construct //$this->beConstructedWith($filesystem); @@ -116,12 +143,15 @@ function it_does_create_symlinks_with_file( $composer->getInstallationManager()->willReturn($installationManager); $composer->getDownloadManager()->willReturn($downloadManager); $composer->getPackage()->willReturn($rootPackage); + $composer->getRepositoryManager()->willReturn($repositoryManager); $rootPackage->getTargetDir()->willReturn(getcwd()); $downloadManager->getDownloader('path') ->willReturn($pathDownloader) ->shouldBeCalled(); - $io->write('[Studio] Creating link to library for package acme/library')->shouldBeCalled(); + $io->write('[Studio] Creating link to ../libs/library for package acme/library')->shouldBeCalled(); + $io->write('[Studio] Creating link to ../libs/library2 for package acme/library2')->shouldNotBeCalled(); + $io->write('[Studio] Creating link to ../libs/another-library for package acme/another-library')->shouldNotBeCalled(); // Test $this->activate($composer, $io); @@ -138,7 +168,8 @@ function it_does_unlink_with_file( $rootPackage, $filesystem, $installationManager, - $pathDownloader + $pathDownloader, + $repositoryManager ) { // switch working directory chdir(__DIR__ . '/stubs/project-with-unload'); @@ -150,6 +181,115 @@ function it_does_unlink_with_file( $io->beADoubleOf('Composer\IO\IOInterface'); $installationManager->beADoubleOf('Composer\Installer\InstallationManager'); $pathDownloader->beADoubleOf('Composer\Downloader\PathDownloader'); + $repositoryManager->beADoubleOf('Composer\Repository\RepositoryManager'); + + // Construct + $this->beConstructedWith($filesystem); + + // Mock methods + $composer->getInstallationManager()->willReturn($installationManager); + $composer->getDownloadManager()->willReturn(null); + $composer->getPackage()->willReturn($rootPackage); + $composer->getRepositoryManager()->willReturn($repositoryManager); + $rootPackage->getTargetDir()->willReturn(getcwd()); + $filesystem->isSymlinkedDirectory(null)->willReturn(true)->shouldBeCalled(); + $filesystem->removeDirectory(null)->shouldBeCalled(); + + $io->write('[Studio] Removing linked path ../libs/library for package acme/library')->shouldBeCalled(); + $io->write('[Studio] Removing linked path ../libs/library2 for package acme/library2')->shouldNotBeCalled(); + $io->write('[Studio] Removing linked path ../libs/another-library for package acme/another-library')->shouldNotBeCalled(); + + // Test + $this->activate($composer, $io); + $this->unlinkStudioPackages(); + } + + /** + * Test if studio does create symlinks when studio.json is defined and contains wildcards path + */ + function it_does_create_symlinks_with_file_and_wildcard_paths( + $composer, + $io, + $rootPackage, + $filesystem, + $installationManager, + $downloadManager, + $pathDownloader, + $repositoryManager, + $localRepository, + $libraryPackage, + $library2Package + ) { + // switch working directory + chdir(__DIR__ . '/stubs/project-with-path-wildcard'); + + // create stubs + $filesystem->beADoubleOf('Composer\Util\Filesystem'); + $rootPackage->beADoubleOf('Composer\Package\RootPackage'); + $composer->beADoubleOf('Composer\Composer'); + $io->beADoubleOf('Composer\IO\IOInterface'); + $installationManager->beADoubleOf('Composer\Installer\InstallationManager'); + $downloadManager->beADoubleOf('Composer\Downloader\DownloadManager'); + $pathDownloader->beADoubleOf('Composer\Downloader\PathDownloader'); + $repositoryManager->beADoubleOf('Composer\Repository\RepositoryManager'); + $localRepository->beADoubleOf('Composer\Repository\WritableRepositoryInterface'); + $libraryPackage->beADoubleOf('Composer\Package\Package'); + $library2Package->beADoubleOf('Composer\Package\Package'); + + $libraryPackage->getName()->willReturn("acme/library"); + $library2Package->getName()->willReturn("acme/library2"); + + $localRepository->getPackages()->willReturn([$libraryPackage, $library2Package]); + $repositoryManager->getLocalRepository()->willReturn($localRepository); + + $repositoryManager->getLocalRepository()->willReturn($localRepository); + + // Construct + //$this->beConstructedWith($filesystem); + + // Mock methods + $composer->getInstallationManager()->willReturn($installationManager); + $composer->getDownloadManager()->willReturn($downloadManager); + $composer->getPackage()->willReturn($rootPackage); + $composer->getRepositoryManager()->willReturn($repositoryManager); + $rootPackage->getTargetDir()->willReturn(getcwd()); + $downloadManager->getDownloader('path') + ->willReturn($pathDownloader) + ->shouldBeCalled(); + + $io->write('[Studio] Creating link to ../libs/library for package acme/library')->shouldBeCalled(); + $io->write('[Studio] Creating link to ../libs/library2 for package acme/library2')->shouldBeCalled(); + $io->write('[Studio] Creating link to ../libs/another-library for package acme/another-library')->shouldNotBeCalled(); + + // Test + $this->activate($composer, $io); + $this->symlinkStudioPackages(); + } + + + /** + * Test if studio does unlink when studio.json is defined and contains wildcard paths. + */ + function it_does_unlink_with_file_and_wildcard_paths( + $composer, + $io, + $rootPackage, + $filesystem, + $installationManager, + $pathDownloader, + $repositoryManager + ) { + // switch working directory + chdir(__DIR__ . '/stubs/project-with-unload-wildcard'); + + // create stubs + $filesystem->beADoubleOf('Composer\Util\Filesystem'); + $rootPackage->beADoubleOf('Composer\Package\RootPackage'); + $composer->beADoubleOf('Composer\Composer'); + $io->beADoubleOf('Composer\IO\IOInterface'); + $installationManager->beADoubleOf('Composer\Installer\InstallationManager'); + $pathDownloader->beADoubleOf('Composer\Downloader\PathDownloader'); + $repositoryManager->beADoubleOf('Composer\Repository\RepositoryManager'); // Construct $this->beConstructedWith($filesystem); @@ -158,11 +298,14 @@ function it_does_unlink_with_file( $composer->getInstallationManager()->willReturn($installationManager); $composer->getDownloadManager()->willReturn(null); $composer->getPackage()->willReturn($rootPackage); + $composer->getRepositoryManager()->willReturn($repositoryManager); $rootPackage->getTargetDir()->willReturn(getcwd()); $filesystem->isSymlinkedDirectory(null)->willReturn(true)->shouldBeCalled(); $filesystem->removeDirectory(null)->shouldBeCalled(); - $io->write('[Studio] Removing linked path library for package acme/library')->shouldBeCalled(); + $io->write('[Studio] Removing linked path ../libs/library for package acme/library')->shouldBeCalled(); + $io->write('[Studio] Removing linked path ../libs/library2 for package acme/library2')->shouldBeCalled(); + $io->write('[Studio] Removing linked path ../libs/another-library for package acme/another-library')->shouldBeCalled(); // Test $this->activate($composer, $io); diff --git a/spec/Composer/stubs/libs/another-library/composer.json b/spec/Composer/stubs/libs/another-library/composer.json new file mode 100644 index 0000000..450a4b7 --- /dev/null +++ b/spec/Composer/stubs/libs/another-library/composer.json @@ -0,0 +1,3 @@ +{ + "name": "acme/another-library" +} \ No newline at end of file diff --git a/spec/Composer/stubs/project-with-path/library/composer.json b/spec/Composer/stubs/libs/library/composer.json similarity index 100% rename from spec/Composer/stubs/project-with-path/library/composer.json rename to spec/Composer/stubs/libs/library/composer.json diff --git a/spec/Composer/stubs/libs/library2/composer.json b/spec/Composer/stubs/libs/library2/composer.json new file mode 100644 index 0000000..4211d8a --- /dev/null +++ b/spec/Composer/stubs/libs/library2/composer.json @@ -0,0 +1,3 @@ +{ + "name": "acme/library2" +} \ No newline at end of file diff --git a/spec/Composer/stubs/project-with-path-wildcard/.gitignore b/spec/Composer/stubs/project-with-path-wildcard/.gitignore new file mode 100644 index 0000000..4788fc4 --- /dev/null +++ b/spec/Composer/stubs/project-with-path-wildcard/.gitignore @@ -0,0 +1 @@ +.studio/ \ No newline at end of file diff --git a/spec/Composer/stubs/project-with-path-wildcard/studio.json b/spec/Composer/stubs/project-with-path-wildcard/studio.json new file mode 100644 index 0000000..c9c595c --- /dev/null +++ b/spec/Composer/stubs/project-with-path-wildcard/studio.json @@ -0,0 +1,6 @@ +{ + "version": 2, + "paths": [ + "../libs/*" + ] +} diff --git a/spec/Composer/stubs/project-with-path/.gitignore b/spec/Composer/stubs/project-with-path/.gitignore new file mode 100644 index 0000000..4788fc4 --- /dev/null +++ b/spec/Composer/stubs/project-with-path/.gitignore @@ -0,0 +1 @@ +.studio/ \ No newline at end of file diff --git a/spec/Composer/stubs/project-with-path/studio.json b/spec/Composer/stubs/project-with-path/studio.json new file mode 100644 index 0000000..91bd90a --- /dev/null +++ b/spec/Composer/stubs/project-with-path/studio.json @@ -0,0 +1,6 @@ +{ + "version": 2, + "paths": [ + "../libs/library" + ] +} diff --git a/spec/Composer/stubs/project-with-unload-wildcard/.studio/studio.json b/spec/Composer/stubs/project-with-unload-wildcard/.studio/studio.json new file mode 100644 index 0000000..c9c595c --- /dev/null +++ b/spec/Composer/stubs/project-with-unload-wildcard/.studio/studio.json @@ -0,0 +1,6 @@ +{ + "version": 2, + "paths": [ + "../libs/*" + ] +} diff --git a/spec/Composer/stubs/project-with-unload-wildcard/studio.json b/spec/Composer/stubs/project-with-unload-wildcard/studio.json new file mode 100644 index 0000000..d5ae520 --- /dev/null +++ b/spec/Composer/stubs/project-with-unload-wildcard/studio.json @@ -0,0 +1,4 @@ +{ + "version": 2, + "paths": [] +} diff --git a/spec/Composer/stubs/project-with-unload/.studio/studio.json b/spec/Composer/stubs/project-with-unload/.studio/studio.json new file mode 100644 index 0000000..91bd90a --- /dev/null +++ b/spec/Composer/stubs/project-with-unload/.studio/studio.json @@ -0,0 +1,6 @@ +{ + "version": 2, + "paths": [ + "../libs/library" + ] +} diff --git a/spec/Composer/stubs/project-with-unload/library/composer.json b/spec/Composer/stubs/project-with-unload/library/composer.json deleted file mode 100644 index ae0bdf7..0000000 --- a/spec/Composer/stubs/project-with-unload/library/composer.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "name": "acme/library" -} \ No newline at end of file diff --git a/spec/Composer/stubs/project-with-unload/studio.json b/spec/Composer/stubs/project-with-unload/studio.json new file mode 100644 index 0000000..d5ae520 --- /dev/null +++ b/spec/Composer/stubs/project-with-unload/studio.json @@ -0,0 +1,4 @@ +{ + "version": 2, + "paths": [] +} diff --git a/spec/Composer/stubs/project-without-path/.gitignore b/spec/Composer/stubs/project-without-path/.gitignore new file mode 100644 index 0000000..e69de29