Problem/Motivation

TL;DR: composer-merge-plugin can load composer.json for contrib using only configuration.

I was all set to fork wikimedia/composer-merge-plugin to bend it to our needs.

Then I RT'd the FM. :-)

The wikimedia/composer-merge-plugin Composer plugin allows you to add a merge-plugin section to the extra section of your composer.json file.

We are currently using this to merge the core/composer.json file.

merge-plugin is an array. Each array entry is a glob pattern which will be used to find composer.json files.

These will be merged into the dependencies of the main composer.json file.

What we currently need: A way to extend this behavior to merge in the Composer-based dependencies for contrib modules.

Proposed resolution

Configure our root-level composer.json file so that wikimedia/composer-merge-plugin searches for contrib modules.

This would allow any Drupal user to say composer update and get the dependencies they need for their installed contrib modules.

There are some limitations, in that we can only use glob()-friendly patterns. This disallows searching arbitrary subdirectories, but it's reasonable to require that dependencies be declared at the top level of a module directory. Also, this limitation could be iterated upon later, given public feedback.

It also requires that the user run composer update every time they change dependencies, or add a module which needs a dependency, possibly without knowing much about the hows and whys.

This could be ameliorated by #2536576: Add composer_dependency module, have it help users figure out how to install Composer dependencies or other similar measures.

Remaining tasks

User interface changes

API changes

Data model changes

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Mile23 created an issue. See original summary.

Mile23’s picture

Here are two patches.

One is of the composer.json file only. It demonstrates how this works. I have it set to search within modules/* and sites/*/modules/* for a composer.json file.

The second patch has an extra module with its own composer.json dependency.

Since #2315537: Install composer dependencies before running tests is in, we should see this patch causing the testbot to install the crell/api-problem package. This should be visible on the testbot console output, with a line like this:

  - Installing crell/api-problem (1.7)

Edit: My mistake. Testbot will run composer install, which will see composer.lock, which means nothing will get installed.

Also included is a change to the pre-autoload-dump script setup in the root composer.json file. This is due to the fact that core/composer.json should be responsible for setting up the classmap, not the root composer.json. Also because if it's present in the root composer.json file, it breaks. :-)

Mile23’s picture

Status: Active » Needs review
Mile23’s picture

Issue summary: View changes
Mile23’s picture

andypost’s picture

This change slightly depends on how modules are stored

+++ b/composer.json
@@ -23,7 +23,9 @@
-        "core/composer.json"
+        "core/composer.json",
+        "modules/*/composer.json",
+        "sites/*/modules/*/composer.json"

I'm using modules/contrib/*/composer.json and sites/all/modules/contrib/*/composer.json

Mile23’s picture

Right, that's the limitation I mention. This is a quick and easy way to add support for contrib dependencies.

If adding /contrib/ in there is a common pattern for another tool, then we can add it here as a pattern.

Dave Reid’s picture

Would this be autoloading code for modules that are not even installed?

We would have to consider not just sites/*/modules/contrib, but, sites/*/modules/custom, and any arbitrary depth. I guess that could be up to the devs to put in the right paths that need autoloading, but it's another manual step.

Mile23’s picture

Would this be autoloading code for modules that are not even installed?

This will merge composer.json files it finds, regardless of enabled status. If the composer.json file has an autoload section, it will merge that.

I think that at this moment in time, it's a bad idea to have autoload in composer.json files of type drupal-module, since it's not up to composer to autoload them. Module manager does this.

As far as dependencies are concerned: They're managed by Composer, so Composer manages their autoloading.

As I mention: This is a quick, easy, relatively non-disruptive way to have composer do the dependencies. Having some well-known paths to search, such as those made by drush make etc would be reasonable. Adding arbitrary directory depths leads to re-writing the plugin. :-)

I guess that could be up to the devs to put in the right paths that need autoloading, but it's another manual step.

I'm really not sure what this means. A module is autoloaded by the module manager. Dependencies are autoloaded by Composer. What other paths are there?

Status: Needs review » Needs work

The last submitted patch, 2: 2609568_2_extra_module.patch, failed testing.

Mile23’s picture

Issue tags: +rc eligible

Adding RC Eligble tag because this only deals with composer config to minimally solve a problem that's been long-standing.

Mile23’s picture

Issue summary: View changes
jhedstrom’s picture

If adding /contrib/ in there is a common pattern for another tool, then we can add it here as a pattern.

This has been a best-practice from at least the D6 days. See Commerce Kickstart, Panopoly, and Open Atrium distributions' drush make files for instance.

So +1 for supporting contrib.

yched’s picture

I'm not sure we need to specifically take care of '/contrib/'. If we leave it out and scan the whole of modules/ and sites/*/modules, then it means custome modules are handled as well, which seems like the desired behavior ?

This means that custom modules specify their deps in a composer.json of their own, just like contrib modules, and that's IMO the correct practice :
- a module can start as custom and be later contributed
- a contrib module can (even temporarily) get committed in the local site git repo as a "custom fork", for example if too heavily patched, waiting for the patches to get officially committed

Having contrib and custom modules handled the same way is conceptually simpler and ensures you can seamlessly move from one to the other.

Mile23’s picture

If we leave it out and scan the whole of modules/ and sites/*/modules, then it means custome modules are handled as well, which seems like the desired behavior ?

The plugin can't be configured to 'scan.'

It's any path you can glob(). It has to end in '/composer.json'.

yched’s picture

@Mile23 : hm, right, the wikimedia plugin doesn't scan subfolders.

- Having to point the exact folder that contains modules ('modules/*/composer.json' or 'modules/contrib/*/composer.json' / 'modules/custom/*/composer.json') is an annoying limitation.
- Along with the fact that it will never catch composer.jsons of submodules.

Our modules discovery system is based on a recursive scan of subfolders. I'd think an auto-discovery of modules composer.jsons should follow the same logic, or we face inconsistencies and weird behaviors.

That might mean extending/forking our own version of the wikimedia plugin, or maybe just adding a preliminary step in a separate plugin of our own that does the scan discovery and dynamically adds the corresponding glob paths at runtime before the wikimedia plugin does its thing ?

yched’s picture

Also, re: @Dave Reid #8 :

Would this be autoloading code for modules that are not even installed?

Yes, that's the normal behavior. Regardless of "drupal modules", any time you 'composer require' a package, all its deps are downloaded and added to the autoloader, even if no code in your app actually uses the package yet.
For e.g a Symfony bundle in a Symfony app, the bundle and all its dependencies are added to the autoloader even if the actual code of your app doesnt 'enable' the bundle.

That's kind of tied to an implicit assumption that if you require a package, you intend to use it at some point, so we make it ready for you to use. That is a reasonable assumption most of the times, but our practice of submodules (optional subpackages shipped in a package...) is a bit at odds with that...

andypost’s picture

Another problem with wildcard approach is a confusion when same module lives in /modules and sites/{somesite}/module - I mean that composer could process inclusion differently from core

yched’s picture

when same module lives
in /modules and sites/{somesite}/module

Ouch, that seems difficult to support if those are different versions with different deps. Multisites share one single set of dependencies in one single vendor dir... One site cannot jump in and say "nope I'll use a different version of that package".

I guess we should merge all the same, across all sites, and let composer try to resolve to a satisfying version of each package, and fail as usual if the requirements are conflicting ?

andypost’s picture

hm, makes sense... looks no other way
so only remaining question about abusing autoloader with disabled modules, I think that performance related

yched’s picture

so only remaining question about abusing autoloader with disabled modules, I think that performance related

As explained in #17, I don't think this can be avoided. If a module is present in the codebase, enabled or not, its deps are present in the autoloader... (not the classes in the module's own src/ though, that is still controlled by the module being enabled in the drupal sense)

I don't think we want to track which module requires which package and only dynamically add those to the autoloader only if the module is enabled, that sounds like an awful can of worms...

bojanz’s picture

This issue is truly a terrible idea :)

It basically adds composer_manager to core (between RC3 and RC4?) while also breaking the sites of people who use Composer to install Drupal modules.

composer_manager has to do the following flow:
1) Use ExtensionDiscovery to find modules
2) Checks the discovered modules against Composer's installed.json to exclude any installed via Composer directly
3) Add the discovered modules to the merge list, as well as the replace list, so that they aren't redownloaded because another module specifies them in its requirements.

We'd need to do all three for this issue to actually work. There's no point, since we actually want to tell people to use Composer directly, leaving the composer_manager flow for edge cases (such as testing, local dev, etc).

bojanz’s picture

Version: 8.0.x-dev » 8.1.x-dev
Issue tags: -rc eligible

There's nothing RC eligible about this, the breakage introduced just wasn't forseen.

webflo’s picture

Issue tags: +Composer
Mile23’s picture

Version: 8.1.x-dev » 8.0.x-dev
Issue tags: +rc eligible
FileSize
960 bytes
563 bytes

@bojanz: It basically adds composer_manager to core (between RC3 and RC4?) while also breaking the sites of people who use Composer to install Drupal modules.

The wikimedia team will be surprised to hear they've written a Drupal module.

Pretty sure you're wrong on both points.

The *whole point* of this patch is that you can use composer to manage module dependencies in addition to modules. That's why I went to the trouble in the first place.

I suggest you try the following and if you see an error please make a comment about it. Until that happens, you're wrong. :-)

  1. Apply the patch.
  2. Use Composer to add whatever module you think will break.
  3. Verify that composer-based dependencies were added during that install phase.

@bojanz: composer_manager has to do the following flow:
1) Use ExtensionDiscovery to find modules [...]

Thankfully, you're wrong about this being composer_manager, and are therefore wrong about this dependency and the others you mention.

As noted: wikimedia's plugin only searches within the paths you tell it to search within. It doesn't need any of core to work. It does not do proper discovery, only loading paths you tell it to.

It is not composer_manager. I don't want composer_manager. I think composer_manager is a flawed idea. I am anti-composer_manager. I went to the trouble here so we can avoid composer_manager. I wish we had solved this problem before composer_manager was ported. composer_manager has too many dependencies. I wish I could think of more negative things to say about composer_manager so this would be a bigger paragraph.

Am I clear? :-)

@yched: Ouch, that seems difficult to support if those are different versions with different deps. Multisites share one single set of dependencies in one single vendor dir... One site cannot jump in and say "nope I'll use a different version of that package".

Yup. As I've mentioned frequently throughout this issue: The point of this issue is that it is a reasonably easy patch which defines some reasonable behavior for a lot of different use-cases, that creates ZERO code to maintain.

Because it's better to have a reasonably-limited implementation of something than to not have one at all.

Multi-site with different composer dependencies per-site is a use-case that this does not address, and which Composer really can't address without a plugin, which apparently no one feels motivated to write. There are lots of reasons why it's a bad idea, and only very few reasons which seem like good ideas but really aren't.

I don't think we want to track which module requires which package and only dynamically add those to the autoloader only if the module is enabled, that sounds like an awful can of worms...

Exactly. Here's a patch which does all the things you want but also not that.

This patch adds the contrib and custom directories so you can use your drushmake stuff. It still assumes that the top-level directory of the project directory has the composer.json file because a) in the composer universe that's such a reasonable assumption as to be unthinkably obvious and b) there's no reasonable way to configure it otherwise.

Moving back to 8.0.x, and RC eligible because it only changes configuration and doesn't break anything.

Mile23’s picture

Status: Needs work » Needs review

The last submitted patch, 2: 2609568_2_extra_module.patch, failed testing.

bojanz’s picture

Status: Needs review » Needs work

As I've documented before, there are currently two ways to approach getting a module's dependencies:
1) A module is already present on disk, and now its dependencies need to be brought in with Composer.
This is what composer_manager assumes, and what this patch assumes.

2) The module is downloaded via Composer, and its dependencies are fetched along the way. This includes libraries and other Drupal modules.
This is the "Use Composer as Drush Make" pure approach that requires no additional modules. This is what we've been working towards for the past year.

Core has no business mandating #1. Furthermore, it was never the desired outcome.

Your comments on composer_manager make me believe you last read its source code a year ago. Especially since you are keen to run into bugs we've already fixed there. The logic consists of around 200 lines of code, centered around the PackageManager class. Give it a look.

The technical problems with the patch:
- This module discovery does not match the core discovery.
A module can be found by Drupal, but not by merge plugin based on the specified paths.
Install profiles are excluded, and specific module subdirectories (contrib, custom) are mandated, which is a big change in itself.

- The merged extensions are not added to the "replace" list (the merge plugin doesn't do it).
So if you've downloaded Address, it got merged, then you decided to add Commerce, which has a "drupal/address" requirement, then Address will be redownloaded and replaced.

However, once you start maintaining the replace list, you get the main issue:

If drupal/commerce depends on drupal/entity then Composer will download the Entity module and place it in the appropriate place.
Let's say that on the next Composer run the merge plugin discovers drupal/entity, merges it in, and adds it to the replace list.
This confuses Composer (having the same module in installed.json and in the replace list), and the module gets removed.

The only workaround is to tell modules not to require other modules in the require list.
Or do what composer_manager does and skip adding extensions present in installed.json to the merge list.

So, here are the pros and cons of the current patch:
Pros:
- No need to download composer_manager.

Cons:
- Can't use Composer as a Drush Make alternative anymore. In fact, the whole Drupal Packagist becomes pointless.

tim.plunkett’s picture

Issue tags: -rc eligible

rc eligible: This tag is for non-invasive patches which solely make coding standards, testing, and docs improvements.

Regardless of if this is 8.1.x or 8.0.x, it certainly is not 'rc eligible'.
You can try for 'rc target triage' if you think breaking BC is worth it.

Mile23’s picture

You can try for 'rc target triage' if you think breaking BC is worth it.

It doesn't break BC.

Kind of wish someone would apply the patch and try it and then tell me it broke their thing.

andypost’s picture

@Mile23 thanx for working on that but... this change is pointless.
The main reason for me is abusing autoloader, then this is no-go for shared hostings (limit for opcacher is 100k files and inodes limit) and last
you will need to add own paths anyway if your folder structure of project different.

Also +100500 to what #28 said after "The technical problems with the patch"

+++ b/composer.json
@@ -23,7 +23,13 @@
+        "modules/*/composer.json",
+        "modules/*/contrib/*/composer.json",
+        "modules/*/custom/*/composer.json",
+        "sites/*/modules/*/composer.json",
+        "sites/*/modules/*/contrib/*/composer.json",
+        "sites/*/modules/*/custom/*/composer.json"

@@ -36,7 +42,6 @@
-    "pre-autoload-dump": "Drupal\\Core\\Composer\\Composer::preAutoloadDump",

That's all what the patch does

Mile23’s picture

@bojanz #28: If drupal/commerce depends on drupal/entity then Composer will download the Entity module and place it in the appropriate place.

... OK....

So with this patch what will happen is that if drupal/commerce is discovered in one of the paths the plugin is configured to look in, drupal/entity will be fetched and placed in modules/ (since it's type: drupal-module). Also, it's composer.json file will be merged such that its dependencies are resolved because that's the normal behavior of Composer.

Let's say that on the next Composer run the merge plugin discovers drupal/entity, merges it in, and adds it to the replace list.

... On the subsequent run, since drupal/entity is in modules/, this patch finds it and looks at its composer.json file again. Then it reconciles the requirements against existing dependencies WHICH ARE MET because we used Composer to install it last time. So nothing happens.

This confuses Composer (having the same module in installed.json and in the replace list), and the module gets removed.

Except that it doesn't do that, because we used Composer and didn't manage the replaced list. Because it's generally a bad idea to manage the replaced list.

Let's try with your example of Address:

We have Address in sites/all/modules, unzipped from a tarball.

We use Composer to require drupal/commerce, which requires Address.

Composer has no record of the existing Address module, so it fetches Address and puts it in modules/ (since it's type: drupal-module).

Composer also gathers composer.json dependencies for Address, since that's what Composer does.

And that leaves us with Address in two places, one of which Composer knows about.

The confused bit of code here will not be Composer, but Drupal, because it has two copies of a module to discover, perhaps even different versions. And of course the user will likely be confused as well.

This problem is not unique to Composer. It's the same in Drush: Unless you know your site is a distribution built from a profile, you will end up with two copies of views or whatever if you say drush dl module

With the patch presented here, you have a one-dependency way to install Composer-related dependencies. Without it, you have a dependency-heavy way to install them, Both solutions are still problematic for any of a number of other reasons, but this solution is easy: Install composer and type composer install, and then upload/deploy your codebase.

@andypost #31: The main reason for me is abusing autoloader, then this is no-go for shared hostings (limit for opcacher is 100k files and inodes limit)

I'm not sure what you mean. This only merges autoload needs for whatever dependencies are declared in the modules. Regardless of how you get Composer to build the dependencies, you'll still need a way to autoload them. It's not really a concern here.

and last
you will need to add own paths anyway if your folder structure of project different.

Well, yah, that's the limitation of this solution: Since it's configuration-only, and the configuration options are limited, so are the paths we can search. The up-side is that we can have core manage this for us in a way that's easy.

Mile23’s picture

Also, earlier today I made a Composer plugin to do all this, leveraging the Wikimedia merge plugin and Drupal\Core\Extension\ExtensionDiscovery.

https://github.com/paul-m/drupal-merge-plugin

POC, almost.

Mile23’s picture

Version: 8.0.x-dev » 8.2.x-dev

Work resumes on drupal-merge-plugin: https://github.com/paul-m/drupal-merge-plugin

Add some more tests and it will be ready for an issue on D.O.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.0-beta1 was released on August 3, 2016, which means new developments and disruptive changes should now be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

pcambra’s picture

Not sure if this on topic, or a new issue would be needed but I think another use case for something similar to this (non composer manager related) is to have a composer.local.json file, for example to avoid modifying core's composer.json file and use the local json file instead for the "composer as drush make" approach.

This way, when the core main composer.json file changes, you would not have the file out of sync.

        "merge-plugin": {
            "include": [
                "core/composer.json",
                "composer.local.json"
            ],
            "recurse": false,
            "replace": false,
            "merge-extra": false
        }
bojanz’s picture

@pcambra
The root composer.json is not core's, it's yours, and it's expected to be modified/
Core's is the one in core/composer.json.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

cilefen’s picture

Now that #2758737: Add a packages.drupal.org to root composer.json is in, is there any point to this issue?

Mixologic’s picture

I really dont think that using wikimedia merge is a good idea for contrib modules. It might have been in an era when people would use tarballs to place modules into folders, but the best practice (probably the only practice) with composer is to composer require a module to get its dependencies.

My suggestion is we close wontfix.

cilefen’s picture

Status: Needs work » Closed (won't fix)

Agreed.