wikimedia / composer-merge-plugin

Merge one or more additional composer.json files at Composer runtime
MIT License
934 stars 159 forks source link

Incompatible with composer/installers #139

Open grasmash opened 7 years ago

grasmash commented 7 years ago

Steps to reproduce

Create composer.json:

{
    "require": {
        "wikimedia/composer-merge-plugin": "^1.4"
    },
    "extra": {
        "merge-plugin": {
            "require": [
                "composer.include.json"
            ],
            "merge-extra": true,
            "merge-extra-deep": true
        }
    }
}

composer.include.json:

{
  "require": {
    "drupal/core": "^8.0",
    "composer/installers": "^1.2"
  },
  "extra": {
    "installer-paths": {
      "docroot/core": [
        "type:drupal-core"
      ]
    }
  }
}

Execute composer install.

Note that at this point, docroot/core is created as expected.

  1. mkdir subdir
  2. cp composer.* subdir/
  3. cd subdir
  4. composer install

At this point subdir/docroot/core is not created. subdir/core is instead.

It seems that composer/installers cannot correctly create the expected paths. I suspect that this is in part because the path of composer.json has changed. This only occurs when composer-merge-plugin is used.

grasmash commented 7 years ago

This has something to with autoloading. If I subsequently run composer dump-autoload && composer install after the initial failed composer install, then subdir/docroot/core will be created. However, I then have both subdir/core and subdir/docroot/core.

grasmash commented 7 years ago

So I'm starting to think that the installer-paths config is simply merged too late for composer/installers to recognize it.

The following output is written to screen after drupal/core is installed:

Generating autoload files
  [merge-plugin] Loading composer.include.json...
  [merge-plugin] Merging drupal/core

  [RuntimeException]
  Could not scan for classes inside "docroot/core/lib/Drupal.php" which does not appear to be a file nor a folder

Exception trace:
 () at phar:///usr/local/Cellar/composer/1.2.2_1/libexec/composer.phar/src/Composer/Autoload/ClassMapGenerator.php:69
 Composer\Autoload\ClassMapGenerator::createMap() at phar:///usr/local/Cellar/composer/1.2.2_1/libexec/composer.phar/src/Composer/Autoload/AutoloadGenerator.php:336
 Composer\Autoload\AutoloadGenerator->generateClassMap() at phar:///usr/local/Cellar/composer/1.2.2_1/libexec/composer.phar/src/Composer/Autoload/AutoloadGenerator.php:319
 Composer\Autoload\AutoloadGenerator->addClassMapCode() at phar:///usr/local/Cellar/composer/1.2.2_1/libexec/composer.phar/src/Composer/Autoload/AutoloadGenerator.php:266
 Composer\Autoload\AutoloadGenerator->dump() at phar:///usr/local/Cellar/composer/1.2.2_1/libexec/composer.phar/src/Composer/Installer.php:298
 Composer\Installer->run() at phar:///usr/local/Cellar/composer/1.2.2_1/libexec/composer.phar/src/Composer/Command/InstallCommand.php:119
 Composer\Command\InstallCommand->execute() at phar:///usr/local/Cellar/composer/1.2.2_1/libexec/composer.phar/vendor/symfony/console/Command/Command.php:267
 Symfony\Component\Console\Command\Command->run() at phar:///usr/local/Cellar/composer/1.2.2_1/libexec/composer.phar/vendor/symfony/console/Application.php:846
 Symfony\Component\Console\Application->doRunCommand() at phar:///usr/local/Cellar/composer/1.2.2_1/libexec/composer.phar/vendor/symfony/console/Application.php:191
 Symfony\Component\Console\Application->doRun() at phar:///usr/local/Cellar/composer/1.2.2_1/libexec/composer.phar/src/Composer/Console/Application.php:227
 Composer\Console\Application->doRun() at phar:///usr/local/Cellar/composer/1.2.2_1/libexec/composer.phar/vendor/symfony/console/Application.php:122
 Symfony\Component\Console\Application->run() at phar:///usr/local/Cellar/composer/1.2.2_1/libexec/composer.phar/src/Composer/Console/Application.php:100
 Composer\Console\Application->run() at phar:///usr/local/Cellar/composer/1.2.2_1/libexec/composer.phar/bin/composer:54
 require() at /usr/local/Cellar/composer/1.2.2_1/libexec/composer.phar:24

So, composer/installers has already written drupal/core to core before composer-merge-plugin merges in the installer-paths config... I think. A subsequent composer install works but you end up with the package in two places.

grasmash commented 7 years ago

@bd808 Is there a way to manually call composer-merge-plugin using a static method in order to force the merge before drupal/core is installed? Or else a way to call it earlier?

bd808 commented 7 years ago

When running under Composer 1.1+, composer-merge-plugin hooks the INIT hook which is the very first plugin signal sent by the platform. There is not any way to initialize sooner and still have access to the full plugin system.

paul-m commented 7 years ago

Using the files from the original issue summary, (with no lock file), we do this:

$ composer install
# Composer does its thing...
$ ls docroot/
core
# Let's do a reset...
$ rm -rf docroot/
$ rm -rf vendor/
# This leaves us with only the two json files and a lock file.
$ composer install
# Eventually see this error:
  [RuntimeException]
  Could not scan for classes inside "docroot/core/lib/Drupal.php" which does
  not appear to be a file nor a folder
$ ls 
composer.include.json   composer.lock       vendor
composer.json       core
# Note no docroot/, only core/.

Remove the lock file and it works again.

The fact that drupal/core ends up in core/ tells us that composer/installers plugin is working, since that's the default location for a drupal-core package according to composer-installers.

It also tells us that it hasn't gotten the special config from composer.include.json, or it would be docroot/core/ instead.

However, the autoloader is being told that drupal/core lives in docroot/core/, which is why we get the error about scanning for classes.

The fact that this changes when you add/remove composer.lock tells us that this is a race condition between the two plugins: When there's no lock file, composer/installer is always loaded after wikimedia/composer-merge-plugin because it's merged by it. If there is a lock file, then C comes before W (or some other arbitrary ordering problem).

I'd call this a limitation of the plugin system.

adubovskoy commented 6 years ago

Does anyone has solutions for that case?

rutiolma commented 5 years ago

This is easy to reproduce. As a workaround, you can remove "installer-paths" from composer.include.json and add it to composer.json, but this is just a quickfix for the issue.

jurgenhaas commented 4 years ago

I'm seeing this too and the workaround from @rutiolma is working ok. It's a bit unfortunate because I'd love to outsource those installer paths into a separate file (even into a separate package) in order to make re-use of complex setup possible. So I hope this can be fiexed eventually.