From f6a768d1638aa34effcdbf21097c3b488dadcdfa Mon Sep 17 00:00:00 2001 From: Sander Visser Date: Wed, 22 Mar 2017 22:19:54 +0100 Subject: [PATCH 1/7] Initial composer.lock fix --- src/Composer/StudioPlugin.php | 73 +++++++++++++++++++++++++++-------- 1 file changed, 57 insertions(+), 16 deletions(-) diff --git a/src/Composer/StudioPlugin.php b/src/Composer/StudioPlugin.php index c1b3b41..fd65e44 100644 --- a/src/Composer/StudioPlugin.php +++ b/src/Composer/StudioPlugin.php @@ -5,11 +5,12 @@ use Composer\Composer; use Composer\EventDispatcher\EventSubscriberInterface; use Composer\IO\IOInterface; +use Composer\Json\JsonFile; +use Composer\Package\Package; 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 implements PluginInterface, EventSubscriberInterface { @@ -32,33 +33,73 @@ public function activate(Composer $composer, IOInterface $io) public static function getSubscribedEvents() { return [ - ScriptEvents::PRE_INSTALL_CMD => 'registerStudioPackages', - ScriptEvents::PRE_UPDATE_CMD => 'registerStudioPackages', + ScriptEvents::PRE_UPDATE_CMD => 'unlinkStudioPackages', + ScriptEvents::PRE_INSTALL_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(); + foreach ($this->getManagedPaths() as $path) { + $package = $this->createPackageForPath($path); + $destination = $this->composer->getInstallationManager()->getInstallPath($package); + + // Creates the symlink to the package + $filesystem = new Filesystem(); + if (!$filesystem->isSymlinkedDirectory($destination)) { + $this->io->writeError("[Studio] Creating symlink to $path for package " . $package->getName()); + // Download the package from the path with the composer downloader + $pathDownloader = $this->composer->getDownloadManager()->getDownloader('path'); + $pathDownloader->download($package, $destination); + } + + } + } + + /** + * Removes all symlinks managed by studio + * + */ + public function unlinkStudioPackages() + { foreach ($this->getManagedPaths() as $path) { - $this->io->writeError("[Studio] Loading path $path"); + $package = $this->createPackageForPath($path); + $destination = $this->composer->getInstallationManager()->getInstallPath($package); - $repoManager->prependRepository(new PathRepository( - ['url' => $path], - $this->io, - $composerConfig - )); + $filesystem = new Filesystem(); + if ($filesystem->isSymlinkedDirectory($destination)) { + $this->io->writeError("[Studio] Removing symlink $path for package " . $package->getName()); + $filesystem->removeDirectory($destination); + } } } + /** + * Creates package from given path + * + * @param string $path + * @return Package + */ + private function createPackageForPath($path) + { + $packageName = (new JsonFile(realpath($path) . DIRECTORY_SEPARATOR . 'composer.json'))->read()['name']; + $package = new Package($packageName, 'dev-master', 'dev-master'); + $package->setDistUrl($path); + + return $package; + } + /** * Get the list of paths that are being managed by Studio. * From 42527e486a45247627328969e7453d8b30516fca Mon Sep 17 00:00:00 2001 From: Sander Visser Date: Thu, 23 Mar 2017 00:48:54 +0100 Subject: [PATCH 2/7] Introducing the .studio directory --- src/Composer/StudioPlugin.php | 42 +++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/src/Composer/StudioPlugin.php b/src/Composer/StudioPlugin.php index fd65e44..58abcb5 100644 --- a/src/Composer/StudioPlugin.php +++ b/src/Composer/StudioPlugin.php @@ -34,7 +34,7 @@ public static function getSubscribedEvents() { return [ ScriptEvents::PRE_UPDATE_CMD => 'unlinkStudioPackages', - ScriptEvents::PRE_INSTALL_CMD => 'unlinkStudioPackages', +// ScriptEvents::PRE_INSTALL_CMD => 'unlinkStudioPackages', ScriptEvents::POST_UPDATE_CMD => 'symlinkStudioPackages', ScriptEvents::POST_INSTALL_CMD => 'symlinkStudioPackages', ScriptEvents::PRE_AUTOLOAD_DUMP => 'symlinkStudioPackages' @@ -50,6 +50,8 @@ public static function getSubscribedEvents() */ public function symlinkStudioPackages() { + $targetDir = realpath($this->composer->getPackage()->getTargetDir()) . '/.studio'; + foreach ($this->getManagedPaths() as $path) { $package = $this->createPackageForPath($path); $destination = $this->composer->getInstallationManager()->getInstallPath($package); @@ -59,12 +61,25 @@ public function symlinkStudioPackages() if (!$filesystem->isSymlinkedDirectory($destination)) { $this->io->writeError("[Studio] Creating symlink to $path for package " . $package->getName()); + // Create copy of original + if (is_dir($destination)) { + $copyPath = $targetDir . DIRECTORY_SEPARATOR . $package->getName(); + $filesystem->ensureDirectoryExists($copyPath); + $filesystem->copyThenRemove($destination, $copyPath); + $this->io->writeError("[Studio] Store original " . $package->getName()); + } + // Download the package from the path with the composer downloader $pathDownloader = $this->composer->getDownloadManager()->getDownloader('path'); $pathDownloader->download($package, $destination); } } + + copy( + realpath($this->composer->getPackage()->getTargetDir()) . DIRECTORY_SEPARATOR . 'studio.json', + $targetDir . DIRECTORY_SEPARATOR . 'studio.json' + ); } /** @@ -73,7 +88,10 @@ public function symlinkStudioPackages() */ public function unlinkStudioPackages() { - foreach ($this->getManagedPaths() as $path) { + $targetDir = realpath($this->composer->getPackage()->getTargetDir()) . '/.studio'; + $paths = array_merge($this->getManagedPaths(), $this->getPreviouslyManagedPaths()); + + foreach ($paths as $path) { $package = $this->createPackageForPath($path); $destination = $this->composer->getInstallationManager()->getInstallPath($package); @@ -81,6 +99,13 @@ public function unlinkStudioPackages() if ($filesystem->isSymlinkedDirectory($destination)) { $this->io->writeError("[Studio] Removing symlink $path for package " . $package->getName()); $filesystem->removeDirectory($destination); + + // If we have an original copy move it back + $copyPath = $targetDir . DIRECTORY_SEPARATOR . $package->getName(); + if (is_dir($copyPath)) { + $filesystem->copyThenRemove($copyPath, $destination); + $this->io->writeError("[Studio] Restoring original " . $package->getName()); + } } } } @@ -112,4 +137,17 @@ private function getManagedPaths() return $config->getPaths(); } + + /** + * Get last known managed paths by studio + * + * @return array + */ + private function getPreviouslyManagedPaths() + { + $targetDir = realpath($this->composer->getPackage()->getTargetDir()) . DIRECTORY_SEPARATOR . '.studio'; + $config = Config::make("{$targetDir}/studio.json"); + + return $config->getPaths(); + } } From 4e615e431b35fbbc3ad58a830adccdff4dec26c8 Mon Sep 17 00:00:00 2001 From: Sander Visser Date: Fri, 24 Mar 2017 14:22:31 +0100 Subject: [PATCH 3/7] Win fixes Additional check for studio.json --- src/Composer/StudioPlugin.php | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Composer/StudioPlugin.php b/src/Composer/StudioPlugin.php index 58abcb5..bef6699 100644 --- a/src/Composer/StudioPlugin.php +++ b/src/Composer/StudioPlugin.php @@ -34,7 +34,6 @@ public static function getSubscribedEvents() { return [ ScriptEvents::PRE_UPDATE_CMD => 'unlinkStudioPackages', -// ScriptEvents::PRE_INSTALL_CMD => 'unlinkStudioPackages', ScriptEvents::POST_UPDATE_CMD => 'symlinkStudioPackages', ScriptEvents::POST_INSTALL_CMD => 'symlinkStudioPackages', ScriptEvents::PRE_AUTOLOAD_DUMP => 'symlinkStudioPackages' @@ -50,16 +49,16 @@ public static function getSubscribedEvents() */ public function symlinkStudioPackages() { - $targetDir = realpath($this->composer->getPackage()->getTargetDir()) . '/.studio'; + $filesystem = new Filesystem(); + $targetDir = realpath($this->composer->getPackage()->getTargetDir()) . DIRECTORY_SEPARATOR . '.studio'; foreach ($this->getManagedPaths() as $path) { $package = $this->createPackageForPath($path); $destination = $this->composer->getInstallationManager()->getInstallPath($package); // Creates the symlink to the package - $filesystem = new Filesystem(); - if (!$filesystem->isSymlinkedDirectory($destination)) { - $this->io->writeError("[Studio] Creating symlink to $path for package " . $package->getName()); + if (!$filesystem->isSymlinkedDirectory($destination) && !$filesystem->isJunction($destination)) { + $this->io->writeError("[Studio] Creating link to $path for package " . $package->getName()); // Create copy of original if (is_dir($destination)) { @@ -76,10 +75,11 @@ public function symlinkStudioPackages() } - copy( - realpath($this->composer->getPackage()->getTargetDir()) . DIRECTORY_SEPARATOR . 'studio.json', - $targetDir . DIRECTORY_SEPARATOR . 'studio.json' - ); + $filesystem->ensureDirectoryExists('.studio'); + $studioFile = realpath($this->composer->getPackage()->getTargetDir()) . DIRECTORY_SEPARATOR . 'studio.json'; + if (file_exists($studioFile)) { + copy($studioFile, $targetDir . DIRECTORY_SEPARATOR . 'studio.json'); + } } /** @@ -88,16 +88,16 @@ public function symlinkStudioPackages() */ public function unlinkStudioPackages() { - $targetDir = realpath($this->composer->getPackage()->getTargetDir()) . '/.studio'; + $filesystem = new Filesystem(); + $targetDir = realpath($this->composer->getPackage()->getTargetDir()) . DIRECTORY_SEPARATOR . '.studio'; $paths = array_merge($this->getManagedPaths(), $this->getPreviouslyManagedPaths()); foreach ($paths as $path) { $package = $this->createPackageForPath($path); $destination = $this->composer->getInstallationManager()->getInstallPath($package); - $filesystem = new Filesystem(); - if ($filesystem->isSymlinkedDirectory($destination)) { - $this->io->writeError("[Studio] Removing symlink $path for package " . $package->getName()); + if ($filesystem->isSymlinkedDirectory($destination) || $filesystem->isJunction($destination)) { + $this->io->writeError("[Studio] Removing linked path $path for package " . $package->getName()); $filesystem->removeDirectory($destination); // If we have an original copy move it back @@ -133,7 +133,7 @@ private function createPackageForPath($path) private function getManagedPaths() { $targetDir = realpath($this->composer->getPackage()->getTargetDir()); - $config = Config::make("{$targetDir}/studio.json"); + $config = Config::make($targetDir . DIRECTORY_SEPARATOR . 'studio.json'); return $config->getPaths(); } @@ -146,8 +146,8 @@ private function getManagedPaths() private function getPreviouslyManagedPaths() { $targetDir = realpath($this->composer->getPackage()->getTargetDir()) . DIRECTORY_SEPARATOR . '.studio'; - $config = Config::make("{$targetDir}/studio.json"); + $config = Config::make($targetDir . DIRECTORY_SEPARATOR . 'studio.json'); return $config->getPaths(); } -} +} \ No newline at end of file From 9c60f3e8c157f8c0b2fb1d7a12653c7239e2ea6f Mon Sep 17 00:00:00 2001 From: Sander Visser Date: Sun, 26 Mar 2017 22:29:12 +0200 Subject: [PATCH 4/7] - update readme.md - Fix custom installer paths --- README.md | 17 +++++++++++------ src/Composer/StudioPlugin.php | 11 +++++++++-- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 3bdbc73..877c40f 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ Instead of installing the packages you're working on from the Packagist reposito Under the hood, it uses Composer's [path repositories](https://getcomposer.org/doc/05-repositories.md#path) to do so. As a result, you won't have to develop in the `vendor` directory. +To maintain the integrity of the `composer.lock` file, studio creates a `.studio` directory to keep the original packages from Packagist. + Studio also knows how to configure development tools that might be part of your workflow. This includes the following: @@ -65,16 +67,19 @@ And finally, tell Studio to set up the symlinks: If all goes well, you should now see a brief message along the following as part of Composer's output: -> [Studio] Loading path installer +> [Studio] Creating link to path/to/world-domination for package my/world-domination This is what will happen under the hood: -1. Composer begins checking dependencies for updates. -2. Studio jumps in and informs Composer to prefer packages from the directories listed in the `studio.json` file over downloading them from Packagist. -3. Composer symlinks these packages into the `vendor` directory or any other appropriate place (e.g. for [custom installers](https://getcomposer.org/doc/articles/custom-installers.md)). +1. Studio will restore all original packages from the `.studio` directory that were managed by studio. +2. Composer begins checking dependencies for updates. +3. When Composer installed all packages into the `vendor` directory studio jumps in and re installs all packages + listed in the `studio.json` with the [path repository](https://getcomposer.org/doc/05-repositories.md#path) installer. + The original packages are stored in the `.studio` directory. +4. Composer symlinks these packages into the `vendor` directory or any other appropriate place (e.g. for [custom installers](https://getcomposer.org/doc/articles/custom-installers.md)). Thus, to your application, these packages will behave just like "normal" Composer packages. -4. Composer generates proper autoloading rules for the Studio packages. -5. For non-Studio packages, Composer works as always. +5. Composer generates proper autoloading rules for the Studio packages. +6. For non-Studio packages, Composer works as always. **Pro tip:** If you keep all your libraries in one directory, you can let Studio find all of them by using a wildcard: diff --git a/src/Composer/StudioPlugin.php b/src/Composer/StudioPlugin.php index bef6699..8183eb3 100644 --- a/src/Composer/StudioPlugin.php +++ b/src/Composer/StudioPlugin.php @@ -6,6 +6,7 @@ use Composer\EventDispatcher\EventSubscriberInterface; use Composer\IO\IOInterface; use Composer\Json\JsonFile; +use Composer\Package\Loader\ArrayLoader; use Composer\Package\Package; use Composer\Plugin\PluginInterface; use Composer\Script\ScriptEvents; @@ -118,8 +119,14 @@ public function unlinkStudioPackages() */ private function createPackageForPath($path) { - $packageName = (new JsonFile(realpath($path) . DIRECTORY_SEPARATOR . 'composer.json'))->read()['name']; - $package = new Package($packageName, 'dev-master', 'dev-master'); + $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; From 191f41af675d77836eec4f09acf08d6107db3f58 Mon Sep 17 00:00:00 2001 From: Sander Visser Date: Tue, 2 May 2017 03:37:32 +0200 Subject: [PATCH 5/7] Add some test (initial) --- spec/Composer/StudioPluginSpec.php | 176 ++++++++++++++++++ .../project-with-path/library/composer.json | 3 + .../stubs/project-with-path/studio.json | 6 + .../project-with-unload/.studio/studio.json | 6 + .../project-with-unload/library/composer.json | 3 + .../stubs/project-with-unload/studio.json | 4 + src/Composer/StudioPlugin.php | 123 +++++++++--- 7 files changed, 290 insertions(+), 31 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-path/studio.json create mode 100644 spec/Composer/stubs/project-with-unload/.studio/studio.json create mode 100644 spec/Composer/stubs/project-with-unload/library/composer.json create mode 100644 spec/Composer/stubs/project-with-unload/studio.json diff --git a/spec/Composer/StudioPluginSpec.php b/spec/Composer/StudioPluginSpec.php new file mode 100644 index 0000000..0d59597 --- /dev/null +++ b/spec/Composer/StudioPluginSpec.php @@ -0,0 +1,176 @@ +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(Filesystem::class); + $rootPackage->beADoubleOf(RootPackage::class); + $composer->beADoubleOf(Composer::class); + $io->beADoubleOf(IOInterface::class); + + // 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(Filesystem::class); + $rootPackage->beADoubleOf(RootPackage::class); + $composer->beADoubleOf(Composer::class); + $io->beADoubleOf(IOInterface::class); + + // 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(Filesystem::class); + $rootPackage->beADoubleOf(RootPackage::class); + $composer->beADoubleOf(Composer::class); + $io->beADoubleOf(IOInterface::class); + $installationManager->beADoubleOf(InstallationManager::class); + $downloadManager->beADoubleOf(DownloadManager::class); + $pathDownloader->beADoubleOf(PathDownloader::class); + + // 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(Filesystem::class); + $rootPackage->beADoubleOf(RootPackage::class); + $composer->beADoubleOf(Composer::class); + $io->beADoubleOf(IOInterface::class); + $installationManager->beADoubleOf(InstallationManager::class); + $pathDownloader->beADoubleOf(PathDownloader::class); + + // 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-path/studio.json b/spec/Composer/stubs/project-with-path/studio.json new file mode 100644 index 0000000..5ce1339 --- /dev/null +++ b/spec/Composer/stubs/project-with-path/studio.json @@ -0,0 +1,6 @@ +{ + "version": 2, + "paths": [ + "library" + ] +} 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..5ce1339 --- /dev/null +++ b/spec/Composer/stubs/project-with-unload/.studio/studio.json @@ -0,0 +1,6 @@ +{ + "version": 2, + "paths": [ + "library" + ] +} 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/spec/Composer/stubs/project-with-unload/studio.json b/spec/Composer/stubs/project-with-unload/studio.json new file mode 100644 index 0000000..5d07940 --- /dev/null +++ b/spec/Composer/stubs/project-with-unload/studio.json @@ -0,0 +1,4 @@ +{ + "version": 2, + "paths": [] +} diff --git a/src/Composer/StudioPlugin.php b/src/Composer/StudioPlugin.php index 8183eb3..225d34a 100644 --- a/src/Composer/StudioPlugin.php +++ b/src/Composer/StudioPlugin.php @@ -3,16 +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\Script\ScriptEvents; use Composer\Util\Filesystem; use Studio\Config\Config; +/** + * Class StudioPlugin + * + * @package Studio\Composer + */ class StudioPlugin implements PluginInterface, EventSubscriberInterface { /** @@ -25,12 +33,52 @@ 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 [ @@ -50,36 +98,47 @@ public static function getSubscribedEvents() */ public function symlinkStudioPackages() { - $filesystem = new Filesystem(); - $targetDir = realpath($this->composer->getPackage()->getTargetDir()) . DIRECTORY_SEPARATOR . '.studio'; - + $studioDir = realpath($this->rootPackage->getTargetDir()) . DIRECTORY_SEPARATOR . '.studio'; foreach ($this->getManagedPaths() as $path) { $package = $this->createPackageForPath($path); - $destination = $this->composer->getInstallationManager()->getInstallPath($package); + $destination = $this->installationManager->getInstallPath($package); // Creates the symlink to the package - if (!$filesystem->isSymlinkedDirectory($destination) && !$filesystem->isJunction($destination)) { - $this->io->writeError("[Studio] Creating link to $path for package " . $package->getName()); + 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 + // Create copy of original in the `.studio` directory, + // we use the original on the next `composer update` if (is_dir($destination)) { - $copyPath = $targetDir . DIRECTORY_SEPARATOR . $package->getName(); - $filesystem->ensureDirectoryExists($copyPath); - $filesystem->copyThenRemove($destination, $copyPath); - $this->io->writeError("[Studio] Store original " . $package->getName()); + $copyPath = $studioDir . DIRECTORY_SEPARATOR . $package->getName(); + $this->filesystem->ensureDirectoryExists($copyPath); + $this->filesystem->copyThenRemove($destination, $copyPath); } - // Download the package from the path with the composer downloader - $pathDownloader = $this->composer->getDownloadManager()->getDownloader('path'); + // 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'); } - $filesystem->ensureDirectoryExists('.studio'); - $studioFile = realpath($this->composer->getPackage()->getTargetDir()) . DIRECTORY_SEPARATOR . 'studio.json'; - if (file_exists($studioFile)) { - copy($studioFile, $targetDir . DIRECTORY_SEPARATOR . 'studio.json'); + // 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'); + } } } @@ -89,23 +148,23 @@ public function symlinkStudioPackages() */ public function unlinkStudioPackages() { - $filesystem = new Filesystem(); - $targetDir = realpath($this->composer->getPackage()->getTargetDir()) . DIRECTORY_SEPARATOR . '.studio'; - $paths = array_merge($this->getManagedPaths(), $this->getPreviouslyManagedPaths()); + $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->composer->getInstallationManager()->getInstallPath($package); + $destination = $this->installationManager->getInstallPath($package); - if ($filesystem->isSymlinkedDirectory($destination) || $filesystem->isJunction($destination)) { - $this->io->writeError("[Studio] Removing linked path $path for package " . $package->getName()); - $filesystem->removeDirectory($destination); + 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 = $targetDir . DIRECTORY_SEPARATOR . $package->getName(); + $copyPath = $studioDir . DIRECTORY_SEPARATOR . $package->getName(); if (is_dir($copyPath)) { - $filesystem->copyThenRemove($copyPath, $destination); - $this->io->writeError("[Studio] Restoring original " . $package->getName()); + $this->filesystem->copyThenRemove($copyPath, $destination); } } } @@ -119,7 +178,9 @@ public function unlinkStudioPackages() */ private function createPackageForPath($path) { - $json = (new JsonFile(realpath($path) . DIRECTORY_SEPARATOR . 'composer.json'))->read(); + $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 @@ -139,7 +200,7 @@ private function createPackageForPath($path) */ private function getManagedPaths() { - $targetDir = realpath($this->composer->getPackage()->getTargetDir()); + $targetDir = realpath($this->rootPackage->getTargetDir()); $config = Config::make($targetDir . DIRECTORY_SEPARATOR . 'studio.json'); return $config->getPaths(); @@ -152,9 +213,9 @@ private function getManagedPaths() */ private function getPreviouslyManagedPaths() { - $targetDir = realpath($this->composer->getPackage()->getTargetDir()) . DIRECTORY_SEPARATOR . '.studio'; + $targetDir = realpath($this->rootPackage->getTargetDir()) . DIRECTORY_SEPARATOR . '.studio'; $config = Config::make($targetDir . DIRECTORY_SEPARATOR . 'studio.json'); return $config->getPaths(); } -} \ No newline at end of file +} From c6e39220c1cd2c80e91c68e1a231da75ddf4a0b5 Mon Sep 17 00:00:00 2001 From: Sander Visser Date: Tue, 2 May 2017 04:20:54 +0200 Subject: [PATCH 6/7] PHP 5.4 fixes --- spec/Composer/StudioPluginSpec.php | 47 +++++++++++++----------------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/spec/Composer/StudioPluginSpec.php b/spec/Composer/StudioPluginSpec.php index 0d59597..dbab7f4 100644 --- a/spec/Composer/StudioPluginSpec.php +++ b/spec/Composer/StudioPluginSpec.php @@ -3,13 +3,8 @@ namespace spec\Studio\Composer; use Composer\Composer; -use Composer\Downloader\DownloadManager; -use Composer\Downloader\PathDownloader; -use Composer\Installer\InstallationManager; use Composer\IO\IOInterface; -use Composer\Package\RootPackage; use Composer\Script\ScriptEvents; -use Composer\Util\Filesystem; use PhpSpec\ObjectBehavior; class StudioPluginSpec extends ObjectBehavior @@ -43,10 +38,10 @@ function it_doesnt_create_symlinks_without_file($composer, $io, $rootPackage, $f chdir(__DIR__); // create stubs - $filesystem->beADoubleOf(Filesystem::class); - $rootPackage->beADoubleOf(RootPackage::class); - $composer->beADoubleOf(Composer::class); - $io->beADoubleOf(IOInterface::class); + $filesystem->beADoubleOf('Composer\Util\Filesystem'); + $rootPackage->beADoubleOf('Composer\Downloader\PathDownloader'); + $composer->beADoubleOf('Composer\Composer'); + $io->beADoubleOf('Composer\IO\IOInterface'); // Construct $this->beConstructedWith($filesystem); @@ -71,10 +66,10 @@ function it_doesnt_unlink_without_files($composer, $io, $rootPackage, $filesyste chdir(__DIR__); // create stubs - $filesystem->beADoubleOf(Filesystem::class); - $rootPackage->beADoubleOf(RootPackage::class); - $composer->beADoubleOf(Composer::class); - $io->beADoubleOf(IOInterface::class); + $filesystem->beADoubleOf('Composer\Util\Filesystem'); + $rootPackage->beADoubleOf('Composer\Downloader\PathDownloader'); + $composer->beADoubleOf('Composer\Composer'); + $io->beADoubleOf('Composer\IO\IOInterface'); // Construct $this->beConstructedWith($filesystem); @@ -106,13 +101,13 @@ function it_does_create_symlinks_with_file( chdir(__DIR__ . '/stubs/project-with-path'); // create stubs - $filesystem->beADoubleOf(Filesystem::class); - $rootPackage->beADoubleOf(RootPackage::class); - $composer->beADoubleOf(Composer::class); - $io->beADoubleOf(IOInterface::class); - $installationManager->beADoubleOf(InstallationManager::class); - $downloadManager->beADoubleOf(DownloadManager::class); - $pathDownloader->beADoubleOf(PathDownloader::class); + $filesystem->beADoubleOf('Composer\Util\Filesystem'); + $rootPackage->beADoubleOf('Composer\Downloader\PathDownloader'); + $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); @@ -149,12 +144,12 @@ function it_does_unlink_with_file( chdir(__DIR__ . '/stubs/project-with-unload'); // create stubs - $filesystem->beADoubleOf(Filesystem::class); - $rootPackage->beADoubleOf(RootPackage::class); - $composer->beADoubleOf(Composer::class); - $io->beADoubleOf(IOInterface::class); - $installationManager->beADoubleOf(InstallationManager::class); - $pathDownloader->beADoubleOf(PathDownloader::class); + $filesystem->beADoubleOf('Composer\Util\Filesystem'); + $rootPackage->beADoubleOf('Composer\Downloader\PathDownloader'); + $composer->beADoubleOf('Composer\Composer'); + $io->beADoubleOf('Composer\IO\IOInterface'); + $installationManager->beADoubleOf('Composer\Installer\InstallationManager'); + $pathDownloader->beADoubleOf('Composer\Downloader\PathDownloader'); // Construct $this->beConstructedWith($filesystem); From 049fa9dcdd8e543ff68ee9e2455f2492a525a24c Mon Sep 17 00:00:00 2001 From: Sander Visser Date: Tue, 2 May 2017 04:27:29 +0200 Subject: [PATCH 7/7] Rootpackage fix in test --- spec/Composer/StudioPluginSpec.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/Composer/StudioPluginSpec.php b/spec/Composer/StudioPluginSpec.php index dbab7f4..ae66149 100644 --- a/spec/Composer/StudioPluginSpec.php +++ b/spec/Composer/StudioPluginSpec.php @@ -39,7 +39,7 @@ function it_doesnt_create_symlinks_without_file($composer, $io, $rootPackage, $f // create stubs $filesystem->beADoubleOf('Composer\Util\Filesystem'); - $rootPackage->beADoubleOf('Composer\Downloader\PathDownloader'); + $rootPackage->beADoubleOf('Composer\Package\RootPackage'); $composer->beADoubleOf('Composer\Composer'); $io->beADoubleOf('Composer\IO\IOInterface'); @@ -67,7 +67,7 @@ function it_doesnt_unlink_without_files($composer, $io, $rootPackage, $filesyste // create stubs $filesystem->beADoubleOf('Composer\Util\Filesystem'); - $rootPackage->beADoubleOf('Composer\Downloader\PathDownloader'); + $rootPackage->beADoubleOf('Composer\Package\RootPackage'); $composer->beADoubleOf('Composer\Composer'); $io->beADoubleOf('Composer\IO\IOInterface'); @@ -102,7 +102,7 @@ function it_does_create_symlinks_with_file( // create stubs $filesystem->beADoubleOf('Composer\Util\Filesystem'); - $rootPackage->beADoubleOf('Composer\Downloader\PathDownloader'); + $rootPackage->beADoubleOf('Composer\Package\RootPackage'); $composer->beADoubleOf('Composer\Composer'); $io->beADoubleOf('Composer\IO\IOInterface'); $installationManager->beADoubleOf('Composer\Installer\InstallationManager'); @@ -145,7 +145,7 @@ function it_does_unlink_with_file( // create stubs $filesystem->beADoubleOf('Composer\Util\Filesystem'); - $rootPackage->beADoubleOf('Composer\Downloader\PathDownloader'); + $rootPackage->beADoubleOf('Composer\Package\RootPackage'); $composer->beADoubleOf('Composer\Composer'); $io->beADoubleOf('Composer\IO\IOInterface'); $installationManager->beADoubleOf('Composer\Installer\InstallationManager');