diff --git a/README.md b/README.md index 2392b859..ea8b2093 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ is not needed to install packages with these frameworks: | Decibel | `decibel-app` | DokuWiki | `dokuwiki-plugin`
`dokuwiki-template` | Dolibarr | `dolibarr-module` -| Drupal | `drupal-core`
`drupal-module`
`drupal-theme`

`drupal-library`
`drupal-profile`
`drupal-drush` +| Drupal | `drupal-core`
`drupal-module`
`drupal-theme`

`drupal-library`
`drupal-profile`
`drupal-drush`
`drupal-custom-theme`
`drupal-custom-module` | Elgg | `elgg-plugin` | Eliasis | `eliasis-component`
`eliasis-module`
`eliasis-plugin`
`eliasis-template` | ExpressionEngine 3 | `ee3-addon`
`ee3-theme` @@ -207,6 +207,51 @@ will allow this: Please note the name entered into `installer-name` will be the final and will not be inflected. +## Disabling installers + +There may be time when you want to disable one or more installers from `composer/installers`. +For example, if you are managing a package or project that uses a framework specific installer that +conflicts with `composer/installers` but also have a dependency on a package that depends on `composer/installers`. + +Installers can be disabled for your project by specifying the extra +`installer-disable` property. If set to `true`, `"all"`, or `"*"` all installers +will be disabled. + +```json +{ + "extra": { + "installer-disable": true + } +} +``` + +Otherwise a single installer or an array of installers may be specified. + +```json +{ + "extra": { + "installer-disable": [ + "cakephp", + "drupal" + ] + } +} +``` + +**Note:** Using a global disable value (`true`, `"all"`, or `"*"`) will take precedence over individual +installer names if used in an array. The example below will disable all installers. + +```json +{ + "extra": { + "installer-disable": [ + "drupal", + "all" + ] + } +} +``` + ## Should we allow dynamic package types or paths? No. What are they? The ability for a package author to determine where a package diff --git a/src/Composer/Installers/DrupalInstaller.php b/src/Composer/Installers/DrupalInstaller.php index a41ee2e1..fef7c525 100644 --- a/src/Composer/Installers/DrupalInstaller.php +++ b/src/Composer/Installers/DrupalInstaller.php @@ -11,6 +11,6 @@ class DrupalInstaller extends BaseInstaller 'profile' => 'profiles/{$name}/', 'drush' => 'drush/{$name}/', 'custom-theme' => 'themes/custom/{$name}/', - 'custom-module' => 'modules/custom/{$name}', + 'custom-module' => 'modules/custom/{$name}/', ); } diff --git a/src/Composer/Installers/Installer.php b/src/Composer/Installers/Installer.php index 9ebf5c68..352cb7fa 100644 --- a/src/Composer/Installers/Installer.php +++ b/src/Composer/Installers/Installer.php @@ -1,13 +1,18 @@ 'PrestashopInstaller' ); + /** + * Installer constructor. + * + * Disables installers specified in main composer extra installer-disable + * list + * + * @param IOInterface $io + * @param Composer $composer + * @param string $type + * @param Filesystem|null $filesystem + * @param BinaryInstaller|null $binaryInstaller + */ + public function __construct( + IOInterface $io, + Composer $composer, + $type = 'library', + Filesystem $filesystem = null, + BinaryInstaller $binaryInstaller = null + ) { + parent::__construct($io, $composer, $type, $filesystem, + $binaryInstaller); + $this->removeDisabledInstallers(); + } + /** * {@inheritDoc} */ @@ -198,4 +227,48 @@ private function getIO() { return $this->io; } + + /** + * Look for installers set to be disabled in composer's extra config and + * remove them from the list of supported installers. + * + * Globals: + * - true, "all", and "*" - disable all installers. + * - false - enable all installers (useful with + * wikimedia/composer-merge-plugin or similar) + * + * @return void + */ + protected function removeDisabledInstallers() + { + $extra = $this->composer->getPackage()->getExtra(); + + if (!isset($extra['installer-disable']) || $extra['installer-disable'] === false) { + // No installers are disabled + return; + } + + // Get installers to disable + $disable = $extra['installer-disable']; + + // Ensure $disabled is an array + if (!is_array($disable)) { + $disable = array($disable); + } + + // Check which installers should be disabled + $all = array(true, "all", "*"); + $intersect = array_intersect($all, $disable); + if (!empty($intersect)) { + // Disable all installers + $this->supportedTypes = array(); + } else { + // Disable specified installers + foreach ($disable as $key => $installer) { + if (is_string($installer) && key_exists($installer, $this->supportedTypes)) { + unset($this->supportedTypes[$installer]); + } + } + } + } } diff --git a/tests/Composer/Installers/Test/InstallerTest.php b/tests/Composer/Installers/Test/InstallerTest.php index 75b402b6..5bc855b0 100644 --- a/tests/Composer/Installers/Test/InstallerTest.php +++ b/tests/Composer/Installers/Test/InstallerTest.php @@ -1,12 +1,12 @@ repository = $this->getMock('Composer\Repository\InstalledRepositoryInterface'); $this->io = $this->getMock('Composer\IO\IOInterface'); + + $consumerPackage = new RootPackage('foo/bar', '1.0.0', '1.0.0'); + $this->composer->setPackage($consumerPackage); + } /** @@ -116,7 +120,14 @@ public function dataForTestSupport() array('decibel-app', true), array('dokuwiki-plugin', true), array('dokuwiki-template', true), + array('drupal-core', true), array('drupal-module', true), + array('drupal-theme', true), + array('drupal-library', true), + array('drupal-profile', true), + array('drupal-drush', true), + array('drupal-custom-theme', true), + array('drupal-custom-module', true), array('dolibarr-module', true), array('ee3-theme', true), array('ee3-addon', true), @@ -279,10 +290,14 @@ public function dataForTestInstallPath() array('dokuwiki-plugin', 'lib/plugins/someplugin/', 'author/someplugin'), array('dokuwiki-template', 'lib/tpl/sometemplate/', 'author/sometemplate'), array('dolibarr-module', 'htdocs/custom/my_module/', 'shama/my_module'), + array('drupal-core', 'core/', 'drupal/core'), array('drupal-module', 'modules/my_module/', 'shama/my_module'), - array('drupal-theme', 'themes/my_module/', 'shama/my_module'), - array('drupal-profile', 'profiles/my_module/', 'shama/my_module'), - array('drupal-drush', 'drush/my_module/', 'shama/my_module'), + array('drupal-theme', 'themes/my_theme/', 'shama/my_theme'), + array('drupal-library', 'libraries/my_library/', 'shama/my_library'), + array('drupal-profile', 'profiles/my_profile/', 'shama/my_profile'), + array('drupal-drush', 'drush/my_command/', 'shama/my_command'), + array('drupal-custom-theme', 'themes/custom/my_theme/', 'shama/my_theme'), + array('drupal-custom-module', 'modules/custom/my_module/', 'shama/my_module'), array('elgg-plugin', 'mod/sample_plugin/', 'test/sample_plugin'), array('eliasis-component', 'components/my_component/', 'shama/my_component'), array('eliasis-module', 'modules/my_module/', 'shama/my_module'), @@ -433,9 +448,7 @@ public function testCustomInstallPath() $installer = new Installer($this->io, $this->composer); $package = new Package('shama/ftp', '1.0.0', '1.0.0'); $package->setType('cakephp-plugin'); - $consumerPackage = new RootPackage('foo/bar', '1.0.0', '1.0.0'); - $this->composer->setPackage($consumerPackage); - $consumerPackage->setExtra(array( + $this->composer->getPackage()->setExtra(array( 'installer-paths' => array( 'my/custom/path/{$name}/' => array( 'shama/ftp', @@ -470,9 +483,7 @@ public function testCustomTypePath() $installer = new Installer($this->io, $this->composer); $package = new Package('slbmeh/my_plugin', '1.0.0', '1.0.0'); $package->setType('wordpress-plugin'); - $consumerPackage = new RootPackage('foo/bar', '1.0.0', '1.0.0'); - $this->composer->setPackage($consumerPackage); - $consumerPackage->setExtra(array( + $this->composer->getPackage()->setExtra(array( 'installer-paths' => array( 'my/custom/path/{$name}/' => array( 'type:wordpress-plugin' @@ -491,9 +502,7 @@ public function testVendorPath() $installer = new Installer($this->io, $this->composer); $package = new Package('penyaskito/my_module', '1.0.0', '1.0.0'); $package->setType('drupal-module'); - $consumerPackage = new RootPackage('drupal/drupal', '1.0.0', '1.0.0'); - $this->composer->setPackage($consumerPackage); - $consumerPackage->setExtra(array( + $this->composer->getPackage()->setExtra(array( 'installer-paths' => array( 'modules/custom/{$name}/' => array( 'vendor:penyaskito' @@ -549,4 +558,44 @@ public function testUninstallAndDeletePackageFromLocalRepo() $installer->uninstall($repo, $package); } + + /** + * testDisabledInstallers + * + * @dataProvider dataForTestDisabledInstallers + */ + public function testDisabledInstallers($disabled, $type, $expected) + { + $this->composer->getPackage()->setExtra(array( + 'installer-disable' => $disabled, + )); + $this->testSupports($type, $expected); + } + + /** + * dataForTestDisabledInstallers + * + * @return array + */ + public function dataForTestDisabledInstallers() + { + return array( + array(false, "drupal-module", true), + array(true, "drupal-module", false), + array("true", "drupal-module", true), + array("all", "drupal-module", false), + array("*", "drupal-module", false), + array("cakephp", "drupal-module", true), + array("drupal", "cakephp-plugin", true), + array("cakephp", "cakephp-plugin", false), + array("drupal", "drupal-module", false), + array(array("drupal", "cakephp"), "cakephp-plugin", false), + array(array("drupal", "cakephp"), "drupal-module", false), + array(array("cakephp", true), "drupal-module", false), + array(array("cakephp", "all"), "drupal-module", false), + array(array("cakephp", "*"), "drupal-module", false), + array(array("cakephp", "true"), "drupal-module", true), + array(array("drupal", "true"), "cakephp-plugin", true), + ); + } }