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),
+ );
+ }
}