Problem/Motivation

In Drupal 8, contributed modules may require Composer dependencies, but Drupal core itself will not require site builders and developers to use Composer to assemble a site's code base. This means that contributed modules currently have to ensure their Composer dependencies are installed themselves, which is a task too complex and important to reinvent in every project.

In order to solve this problem we need to determine two things:

  1. Does an extension require composer to be used to install its dependencies?
  2. Has composer been used to install its dependencies?

1. Does the extension require composer

We propose adding a build_dependency[] key to info.yml files that allow extension developers to explicitly declare that composer is a build requirement.

This requires extension maintainers to take an extra step of declaring composer as a dependency.

It has some added benefits where that metadata could be useful beyond just Drupal's runtime/install time. For instance, we could modify project pages to alert users without composer abilities that they cannot use the extension in the first place

2. Has composer been executed for the extension

If we determine that an extension requires composer, we must then determine whether or not that requirement has been fullfilled. If it has not, we need to alert the user that a module cannot be installed, or needs to be uninstalled (Already installed modules may go from not requiring composer to requiring composer).

There are couple of proposed approaches to this as well:

  • A. Validate that the dependencies that are declared are fullfilled.
  • B. Inspect a build artifact that lists the installed extensions to see if an extension is listed, and if it is not listed, assume that it still needs to be executed

Solution A would actually calculate the required dependencies. Since composer supports a variety of dependency declarations (replace, provides, requires, conflict etc), the only sure way to calculate would be to use composer code itself to calculate. Composer's dependency calculation is *extremly slow*, and cannot likely work in an environment where there are web requests involved. Because Composer is a build tool, and should not be utilized at runtime for codebase validation.

Solution B gives us a list of extensions that have been installed with composer. There are two proposed implementations for this solution:

  • a. Inspect vendor/composer/installed.json to determine if an extension has been installed with composer
  • b. Run a composer build script that creates core/composer/installed-extensions.json that gives us a list of what has been installed via composer

Implementation a. uses an artifact from composers build process, but is not part of a published api, and as such might someday change.
Implementation b. shields us from those changes, and gives us complete control over the format of the file we're using to check.

------------------------------

Stating the problem:

These are composer dependency declarations supported by packages.drupal.org (from @Mixologic #309)

  • A. simple, composer.json in the base project dir. (project/composer.json, project/project.info.yml)
  • B. "Module at the root level of the project contains all of its submodule composer package dependencies" (project/composer.json, project/commerce.info.yml, project/commerce_order/commerce_order.info.yml) - where commerce_order's package dependencies are declared at the root of the *project*, not in commerce_order's directory.
  • C. "Parent module at the root level contains composer dependencies, and a submodule contains *different* package dependencies, like the inmail example above.
  • D. "module at the root level of the project has no composer.json, but a submodule does: http://cgit.drupalcode.org/cloud/tree/?h=8.x-1.x && http://cgit.drupalcode.org/cloud/tree/modules/cloud_service_providers/aw...
  • E. "No parent project at the root level, only submodules, each with their own dependencies" (something like http://cgit.drupalcode.org/cod_support/tree/, where the project is really a bunch of related modules, but there is no "parent" module.
  • F. Sometimes there is *more than one module* in the same directory. I dont know if this can still happen in d8 (http://cgit.drupalcode.org/stringoverrides/tree/) and hopefully it doesnt, cause ew.

Situation #1: Non-Composer users will install Drupal 8 from a tarball. They will place contrib modules in modules/ using tarballs.

Desired behavior: If a contrib module has a Composer-based dependency which is not present, the user will be prevented from installing that contrib module. All the restrictions of hook_requirements() returning REQUIREMENT_ERROR will be present: The status page will show an error, the site installer won't work, and update.php will not be able to perform an update.

Situation #2: A user is using a contrib module installed as a tarball. This contrib module has an update, and in the update, it introduces a composer.json file which means there are now Composer-based dependencies.

Desired behavior: The same as #1, except the module is already installed so we can't prevent that. We can show the error in the status page, and we can refuse to update. It might be the case that the module will cause a crash, but that's outside of scope here. We only want to make a good-faith effort to tell the user they should start using Composer.

Situation #3: A user is using a contrib module installed as a tarball. This module has Composer-based dependencies which are always present in Drupal 8 (such as a symfony component). An update to the contrib module changes the dependency version constraints to a higher version than is present in Drupal 8.

Desired behavior: Prevent the user from performing the update to this module, because its dependencies are not met.

Situation #4: A user has contrib modules installed through a mixture of tarball installation and Composer-managed installation. The tarball and Composer-based modules have conflicting requirements and should not be able to both be present.

Desired behavior: Prevent the user from updating the tarball module, which should inform them that they should use Composer to manage that module (once all the conflicts are resolved).

Proposed resolution

General rules of thumb for proposed solutions:

1) Do not make Drupal have a dependency on composer/composer.

2) Introduce as few complexities as possible, in order to leave room for more robust solutions within the Composer layer.

Proposal #1: Look at vendor/composer/installed.json

From #261:

The situation is: A user is trying to install an extention, where:

  • The extension has a composer.json file associated with it
  • The extension's composer.json has evidence of external dependencies (has a require on something other than drupal/*)
  • The extension name does not show up in installed.json

Then we can assume that, regardless of whether or not that particular extension contains any replace or conflict information, that composer has not been run to retrieve it's dependencies, and that it was installed using some other method than composer.

We do not have to concern ourselves if a module has a require on anything drupal/* because they will be one of the following:

  • drupal/core - which will always be satisfied.
  • drupal/core-[component] which will always be satisfied.
  • drupal/[project/module] which should be specified as a dependency in info.yml. I cannot think of a case where an extension requires a project to be downloaded, but does not require that project in order to install that module.

When we have library types on drupal.org, that are not subtree splits of core itself, we may need to have a namespace for those (drupal/lib-libraryname) so we do not count them as a filled dependency. (could also be drupal-lib/libraryname too. )

Proposal #2: Build our own copy of installed.json

Require all modules that are part of a Composer package to be installed/built through Composer prior to installing/enabling it within Drupal.

This is done by compiling a list of Drupal extensions that are Composer packages, and their installation paths during composer install and composer update. Whenever a module is enabled, we check if it's part of a Composer package, and if the extension's path lies anywhere within the paths of installed packages, Drupal considers it installed/built through Composer. This is based on Composer's own restriction that it cannot install one package into another.

An advantage of this approach is that the coupling between Drupal and Composer remains small, and extensions can take full advantage of any requirements specifications and package links Composer allows, as well as class autoloading without having to bootstrap Drupal.

Remaining tasks

  • update the issue summary, listing a (summary) history of the proposed solutions, the pros and cons, and what concerns people had (but may not anymore, or still have) with links to relevant comment numbers. This can help reviewer be up to speed on this issue, and also help ensure that people see their concerns are heard correctly (even if there may not be consensus).
  • (maybe) update the issue summary with a list of criteria for this issue (start with what there is consensus on, then list criteria for a solution that people dont yet agree on, the pros and cons and concerns people have for if an item should be required for this issue. The criteria can help people evaluate the proposed resolutions.
  • (maybe) look at https://www.drupal.org/governance/core and list which sign-offs this issue needs (and keep the list in the summary as "done" (with a link to the comment number) or "todo")
  • (maybe) update the issue with some links to key comments from reviews, and (maybe) include the role of the reviewer [experience with this issue | maintainer with role X of project Y | drupal.org CI maintainer | ... other]
  • (maybe) list some personas for this issue: core contributors, core maintainer of X component/module/system, contrib maintainer, contrib contributor, drupal.org CI maintainer, site builder, site administrator, content creator, site visitor, more? ... and what the impact/risks/benefits/concerns are (and should be?) for each persona.
  • update the issue summary with what follow-up issues might be needed

User interface changes

One additional Composer item will appear in the status report, indicating whether all necessary Composer dependencies have been installed or not. This is the same requirement that can prevent modules from being installed.

From #120 Jan 6 2016

API changes

No code compatibility breaks.
Potentially adds a new api for extension developers to express their dependency on a particular build tool.

This requires any module with a Composer file to be part of the Composer build, before it can be installed within Drupal.

CommentFileSizeAuthor
#349 drupal-2494073-349.interdiff.txt5.73 KBgapple
#349 drupal-2494073-349.patch27.78 KBgapple
#346 drupal-2494073-346.patch27.55 KBgapple
#334 2494073_334.patch27.48 KBmile23
#325 2494073_325.patch27.85 KBmile23
#323 interdiff.txt3.03 KBmile23
#323 2494073_323.patch27.86 KBmile23
#313 2494073_313_build_dependencies.patch27.76 KBmile23
#313 2494073_288_reroll.patch44.28 KBmile23
#288 drupal_2494073_288.patch44.59 KBxano
#288 interdiff.txt901 bytesxano
#281 drupal_2494073_281.patch44.59 KBxano
#281 interdiff.txt607 bytesxano
#278 drupal_2494073_278.patch44.33 KBxano
#278 interdiff.txt1.46 KBxano
#276 drupal_2494073_275.patch44.49 KBxano
#276 interdiff.txt6.22 KBxano
#271 drupal_2494073_271.patch44.14 KBxano
#271 interdiff.txt1.64 KBxano
#269 drupal_2494073_269.patch44.1 KBxano
#269 interdiff.txt15.66 KBxano
#266 drupal_2494073_266.patch47.82 KBxano
#266 interdiff.txt5.27 KBxano
#263 drupal_2494073_263.patch47.51 KBxano
#263 interdiff.txt1.95 KBxano
#262 drupal_2494073_262.patch48.12 KBxano
#262 interdiff.txt38.9 KBxano
#258 drupal_2494073_258.patch54.35 KBxano
#258 interdiff.txt26.84 KBxano
#255 interdiff.txt3.96 KBmile23
#255 2494073_255.patch37.32 KBmile23
#252 interdiff.txt8.51 KBmile23
#252 2494073_252.patch36.6 KBmile23
#250 interdiff_209.txt8.84 KBmile23
#250 2494073_250.patch33.48 KBmile23
#239 drupal_2494073_239.patch59.92 KBxano
#239 interdiff.txt1.42 KBxano
#226 drupal_2494073_226.patch60.24 KBxano
#226 interdiff.txt2.33 KBxano
#213 drupal_2494073_213.patch59.96 KBxano
#213 interdiff.txt1.6 KBxano
#209 interdiff_168.patch1.79 KBmile23
#209 2494073_209.patch31.86 KBmile23
#195 drupal_2494073_195.patch59.95 KBxano
#192 drupal_2494073_191.patch59.95 KBxano
#192 interdiff.txt16.39 KBxano
#191 drupal_2494073_190.patch76.33 KBxano
#191 interdiff.txt57.17 KBxano
#173 drupal_2494073_173.patch31.28 KBxano
#173 interdiff.txt3.26 KBxano
#168 drupal_2494073_168.patch30.59 KBxano
#168 interdiff.txt1.06 KBxano
#166 drupal_2494073_166.patch30.56 KBxano
#164 drupal_2494073_164.patch30.66 KBxano
#164 interdiff.txt721 bytesxano
#161 drupal_2494073_161.patch30.45 KBxano
#161 interdiff.txt677 bytesxano
#158 drupal_2494073_158.patch30.47 KBxano
#154 drupal_2494073_154.patch30.43 KBxano
#154 interdiff.txt6 KBxano
#149 drupal_2494073_149.patch30.84 KBxano
#149 interdiff.txt5.94 KBxano
#145 interdiff.txt1.11 KBmile23
#145 2494073_145.patch29.92 KBmile23
#143 interdiff.txt4.41 KBmile23
#143 2494073_143.patch29.92 KBmile23
#139 unmet_dependencies.jpg43.92 KBmile23
#139 interdiff.txt8.38 KBmile23
#139 2494073_139.patch29.92 KBmile23
#130 interdiff.txt1.82 KBmile23
#130 2494073_130.patch28.26 KBmile23
#127 drupal_2494073_127.patch27.89 KBxano
#127 interdiff.txt5.27 KBxano
#124 drupal_2494073_124.patch27.69 KBxano
#124 interdiff.txt888 bytesxano
#120 unmet_composer.jpg32.73 KBmile23
#119 drupal_2494073_119.patch27.66 KBxano
#119 interdiff.txt6.6 KBxano
#112 drupal_2494073_112.patch26.19 KBxano
#112 interdiff.txt3.91 KBxano
#107 drupal_2494073_107.patch26.18 KBxano
#107 interdiff.txt749 bytesxano
#105 drupal_2494073_105.patch26.18 KBxano
#105 interdiff.txt5.26 KBxano
#103 drupal_2494073_103.patch25.84 KBxano
#103 interdiff.txt2.32 KBxano
#101 drupal_2494073_101.patch25.86 KBxano
#101 interdiff.txt6.63 KBxano
#99 interdiff.txt14.77 KBxano
#98 drupal_2494073_98.patch24.47 KBxano
#98 interdiff.txt26.4 KBxano
#96 drupal_2494073_95.patch19.76 KBxano
#96 interdiff.txt16.05 KBxano
#91 interdiff.txt6.28 KBmile23
#91 2494073_91.patch21.87 KBmile23
#86 interdiff.txt4.46 KBmile23
#86 2494073_86.patch21.47 KBmile23
#79 interdiff.txt9.41 KBmile23
#79 2494073_79.patch21.43 KBmile23
#77 interdiff.txt17.87 KBmile23
#77 2494073_77.patch17.55 KBmile23
#71 mark_modules_with_unmet-2494073-61.patch9.43 KBhussainweb
#66 mark_modules_with_unmet-2494073-66.patch59.01 KBhussainweb
#61 mark_modules_with_unmet-2494073-61.patch9.43 KBborisson_
#61 mark_modules_with_unmet-2494073-61-package-included.patch73.58 KBborisson_
#61 interdiff.txt2 KBborisson_
#56 mark_modules_with_unmet-2494073-56.patch9.46 KBborisson_
#56 mark_modules_with_unmet-2494073-56-include-package.patch73.61 KBborisson_
#56 interdiff.txt1.49 KBborisson_
#53 mark_modules_with_unmet-2494073-53.patch8.83 KBborisson_
#53 mark_modules_with_unmet-2494073-53-include-package.patch72.97 KBborisson_
#53 interdiff.txt9.17 KBborisson_
#47 mark_modules_with_unmet-2494073-47-include-package.patch72.77 KBborisson_
#47 mark_modules_with_unmet-2494073-47.patch8.63 KBborisson_
#47 interdiff.txt2.45 KBborisson_
#38 mark_modules_with_unmet-2494073-38.patch8.31 KBborisson_
#38 mark_modules_with_unmet-2494073-38-include-package.patch72.45 KBborisson_
#38 interdiff.txt704 bytesborisson_
#35 mark_modules_with_unmet-2494073-35-include-package.patch72.45 KBborisson_
#35 mark_modules_with_unmet-2494073-35.patch8.31 KBborisson_
#35 interdiff.txt850 bytesborisson_
#32 mark_modules_with_unmet-2494073-30.patch8.25 KBborisson_
#32 mark_modules_with_unmet-2494073-30-include-package.patch72.4 KBborisson_
#32 interdiff.txt1.36 KBborisson_
#29 mark_modules_with_unmet-2494073-29-code-only.patch8.25 KBborisson_
#29 mark_modules_with_unmet-2494073-29-include-package.patch72.4 KBborisson_
#29 interdiff.txt3.53 KBborisson_
#27 mark_modules_with_unmet-2494073-27.patch71.81 KBborisson_
#27 mark_modules_with_unmet-2494073-27-code-only.patch6.51 KBborisson_
#27 interdiff.txt2.69 KBborisson_
#23 mark_modules_with_unmet-2494073-23.patch7.18 KBborisson_
#23 interdiff.txt1.54 KBborisson_
#20 mark_modules_with_unmet-2494073-20.patch7.21 KBborisson_
#20 interdiff.txt2.84 KBborisson_
#16 mark_modules_with_unmet-2494073-16.patch7.11 KBborisson_
#16 interdiff.txt4.42 KBborisson_
#15 mark_modules_with_unmet-2494073-15.patch7.26 KBborisson_
#15 interdiff.txt6.03 KBborisson_
#13 mark_modules_with_unmet-2494073-13.patch5.86 KBborisson_
#13 interdiff.txt4.57 KBborisson_
#9 mark_modules_with_unmet-2494073-9.patch3.47 KBborisson_
#9 interdiff.txt1.13 KBborisson_
#7 mark_modules_with_unmet-2494073-7.patch3.32 KBborisson_

Issue fork drupal-2494073

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

xano’s picture

Crell’s picture

If we go with a single composer.json and vendor/ directory for core and contrib, it's quite easy. We ship with a hash of the composer.lock file for that specific core tagged version. Before doing certain actions, we rehash it and see if it's changed. If it has, we assume the site owner has added a composer lib, run composer update, or otherwise opted into a Composer World(tm). We therefore disallow composer-unsafe-actions(tm), whatever those happen to be. Instead, we show appropriate instructional text.

If we use a separate vendor/ directory for core and contrib, it's probably much the same except that we have to check for a top-level composer.json/composer.lock independently of core, so the hash logic is not the same.

For hook_requirements, core can implement the following logic when checking installability (pseudo-code):

foreach ($module) {
  if (file_exists($module_dir . '/composer.json')) {
    if (/* composer.json has a requirement that doesn't begin with 'drupal/' */) {
      if (/* root level composer.lock file does not indicate that dependency is installed */) {
        // mark module as uninstallable with instructions to run 'composer require drupal/$module'
      }
    }
  }
}
xano’s picture

The main reason for creating this issue was that several people suggested we provide automatic or boilerplate Composer integration for hook_requirements() implementations, so Drupal won't let you install modules unless their Composer dependencies are satisfied.

Crell’s picture

Title: Explore Composer integration » Mark modules with unmet composer dependencies uninstallable
Issue tags: +Composer

Well then let's focus that title a bit, shall we... :-)

xjm’s picture

...following up on that, we agreed this is actually probably major because the workaround is for each individual module or package to implement its own hook_requirements() logic duplicating the composer dependency check. Ugly, but it would allow developers to work around this until it is fixed.

borisson_’s picture

Status: Active » Needs review
StatusFileSize
new3.32 KB

Attached patch contains a module with a composer.json file that requires scalopus/empty. This is an empty repository so it won't add any actual code to drupal.

I've changed ModulesListForm::buildModuleList to include a check for a composer.json file in the installing module's directory and made sure the module can't be installed for now. Also added a test that checks this behavior.

To do:

- Answer the question posed by @Crell in #2 on wether we should use one /vendor directory for core and contrib or use one for each.
- Adapt the code in ModulesListForm::buildModuleList to check for the actual contents of the composer.lock file.

bojanz’s picture

+ drupal_set_message(t('Module !module can not be installed before composer dependencies are installed. You should run `composer install` from the module directory to install dependencies.', ['!module' => $module]));

Except this is completely false.
And the actual command to run depends on various factors, including whether the root composer.json is used.
I think the best we can do is link to a d.o page.

borisson_’s picture

Issue summary: View changes
StatusFileSize
new1.13 KB
new3.47 KB

Changed the message in the patch.
Added the remaining todo's to the IS.

xano’s picture

Status: Needs review » Needs work
Issue tags: +Needs tests
  1. +++ b/core/modules/system/src/Form/ModulesListForm.php
    @@ -490,6 +490,29 @@ protected function buildModuleList(FormStateInterface $form_state) {
    +    }
    

    We shouldn't manage requirements in a form. This must be done on an API level. What about we introduce hook_requirements_alter() and add this check to any module that comes with a composer.json file?

  2. +++ b/core/modules/system/src/Tests/Module/HookRequirementsTest.php
    @@ -28,4 +28,20 @@ function testHookRequirementsFailure() {
    +  function testComposerDependenciesFailure() {
    

    We must also test that modules can be installed if all their Composer dependencies are met.

borisson_’s picture

@Xano, you're right that we should also test that a module can be installed if it's composer dependencies are installed. I don't think we can write this test before we've solved the question wether we should have one or more vendor directories.

xano’s picture

Good point, but we may introduce false reassurance here.

Where exactly do we check if a module's Composer dependencies are installed? This looks like it just refuses to install any module with Composer dependencies.

borisson_’s picture

Issue summary: View changes
Status: Needs work » Needs review
Issue tags: -Needs tests
StatusFileSize
new4.57 KB
new5.86 KB

Attached patch fixes #12. There now is some code that actually checks for composer requirements.
I'm not sure this is the best way to do this because it doesn't check for version requirements, that should still be improved.

There is now also a test for a module that can be installed, so #10.2 is now also fixed.

#10.1 still needs to be done, I added this to the IS's remaining tasks.

xano’s picture

Status: Needs review » Needs work
  1. +++ b/core/modules/system/src/Form/ModulesListForm.php
    +++ b/core/modules/system/src/Form/ModulesListForm.php
    @@ -489,6 +489,48 @@ protected function buildModuleList(FormStateInterface $form_state) {
    

    Again, this should be done on an API level, and not inside a form. See #10.

  2. +++ b/core/modules/system/src/Form/ModulesListForm.php
    @@ -489,6 +489,48 @@ protected function buildModuleList(FormStateInterface $form_state) {
    +        $composer = json_decode(file_get_contents($module_dir . '/composer.json'));
    ...
    +        $composer_lock = json_decode(file_get_contents(DRUPAL_ROOT . '/core/composer.lock'));
    

    This will break as soon as someone uses an application-level Composer file (as applications should). Maybe we should specify the path in settings.php for now?

  3. +++ b/core/modules/system/src/Form/ModulesListForm.php
    @@ -489,6 +489,48 @@ protected function buildModuleList(FormStateInterface $form_state) {
    +              $dependencies_installed[] = true;
    

    This can never be true without also checking version constraints. Can we use Composer (the PHP library) to easily check this?

borisson_’s picture

StatusFileSize
new6.03 KB
new7.26 KB

#14:

  1. We introduce a new hook_requirements_alter to make sure this is on API level,
  2. We could do this with a path in settings.php but if we're going to suggest that people use an application-specific composer file than we should use that one. Fixed that in the patch.
  3. We could use https://github.com/composer/composer, the \Composer\Package\Version\VersionParser class looks like it should have methods to do this.

The current patch will break the test because the implementation is not correct yet.

borisson_’s picture

Status: Needs work » Needs review
StatusFileSize
new4.42 KB
new7.11 KB

So this makes everything green again, this is good. All we have to do now is do the version checking with composer/composer.

xano’s picture

+++ b/core/modules/system/system.module
@@ -482,6 +482,58 @@ function system_updater_info() {
+      $requirements['composer_dependencies_' . $module] = [
+        'description' => t('Module !module can not be installed before composer dependencies are installed. Please find more information on <a href="!handbook">this handbook page</a>.',
+          [
+            '!module' => $module,
+            '!handbook' => 'https://www.drupal.org/node/2494073'
+          ]),
+        'severity' => REQUIREMENT_ERROR,
+        'value' => t('Never run'),
+        'title' => $module,
+      ];

Even though multiple modules can have missing Composer dependencies, all these can be fixed with a single command (or non-command equivalent, such as using Composer Manager). I think we declare just one single error for all modules (and maybe list their names), instead of an error per module.

Perhaps we should also display an OK message if no missing dependencies have been found.

borisson_’s picture

Status: Needs work » Needs review
StatusFileSize
new2.84 KB
new7.21 KB

Fixes #18

xano’s picture

This is getting better and better :)

  1. +++ b/core/modules/system/system.module
    @@ -491,6 +491,15 @@ function system_updater_info() {
    +      'value' => t('Never run'),
    

    Copy/paste left-over?

  2. +++ b/core/modules/system/system.module
    @@ -518,10 +527,9 @@ function system_requirements_alter(array &$requirements = [], $module) {
    +        'description' => t('Not all the modules have their composer dependencies installed yet. Please find more information on <a href="@handbook">this handbook page</a>.',
    

    Composer with a capital C.

  3. +++ b/core/modules/system/system.module
    @@ -518,10 +527,9 @@ function system_requirements_alter(array &$requirements = [], $module) {
                 '!handbook' => 'https://www.drupal.org/node/2494073'
    

    !handbook must be @handbook to match the string it's replace in.

borisson_’s picture

Status: Needs work » Needs review
StatusFileSize
new1.54 KB
new7.18 KB
borisson_’s picture

Related issues: +#2575469: Require the composer/semver library to do version checking.
StatusFileSize
new2.69 KB
new6.51 KB
new71.81 KB

Attached patch includes the semver package, that should only be in #2575469: Require the composer/semver library to do version checking.. We currently still have failures in the tests because of the alter hook.

Anonymous’s picture

Re #27:

  1. +++ b/core/modules/system/src/Tests/Module/HookRequirementsTest.php
    @@ -28,4 +28,36 @@ function testHookRequirementsFailure() {
    +   * Assert that a module cannot be installed if composer dependencies are not
    +   * installed.
    

    This should be on a single line.

  2. +++ b/core/modules/system/src/Tests/Module/HookRequirementsTest.php
    @@ -28,4 +28,36 @@ function testHookRequirementsFailure() {
    +   * Assert that a module cannot be installed if composer dependencies that are
    +   * already installed.
    

    Same.

  3. +++ b/core/modules/system/system.module
    @@ -482,6 +483,74 @@ function system_updater_info() {
    + *   requirements array already provided by hook_requirements
    ...
    + *   module name
    

    Capitals, trailing dot.

  4. +++ b/core/modules/system/system.module
    @@ -482,6 +483,74 @@ function system_updater_info() {
    +    // and is more reliable than checking composer.lock, vendors actually have to be installed to
    

    80 chars. And probably some words.

  5. +++ b/core/modules/system/system.module
    @@ -482,6 +483,74 @@ function system_updater_info() {
    +    } else {
    

    This should go on a new line.

Re #23:

+++ b/core/includes/update.inc
@@ -97,6 +97,7 @@ function update_system_schema_requirements() {
+  \Drupal::moduleHandler()->alter('requirements', $requirements);

$module is missing here, that might be causing the failures?

borisson_’s picture

Status: Needs work » Needs review
StatusFileSize
new3.53 KB
new72.4 KB
new8.25 KB

Thanks @pjonckiere, attached patch resolves your remarks.

borisson_’s picture

Status: Needs work » Needs review
StatusFileSize
new1.36 KB
new72.4 KB
new8.25 KB

Locally the HookRequirementsTest works again. Hoping the rest is green as well.

borisson_’s picture

Status: Needs work » Needs review
StatusFileSize
new850 bytes
new8.31 KB
new72.45 KB

can't merge a NULL value and an array, should fix at least Drupal\minimal\Tests\MinimalTest

borisson_’s picture

Status: Needs work » Needs review
StatusFileSize
new704 bytes
new72.45 KB
new8.31 KB

In #35 \Drupal\system\Tests\Update\UpdateScriptTest failed, this is now green locally. This shouldn't break anything else either so hopefully all is green now.

webflo’s picture

  1. +++ b/core/modules/system/system.module
    @@ -482,6 +483,84 @@ function system_updater_info() {
    +    if (file_exists('core/vendor/composer/installed.json')) {
    

    This hardcodes our vendor directory. This will cause problems if user want to move their vendor dir somewhere else.

  2. +++ b/core/modules/system/system.module
    @@ -482,6 +483,84 @@ function system_updater_info() {
    +        'description' => t('Not all the modules have their Composer dependencies installed yet. Please find more information on <a href="@handbook">this handbook page</a>.',
    

    Maybe scope creep, but whats about themes and install profiles? Maybe we can implement this in a generic way with ExtensionDiscovery?

  3. +++ b/core/modules/system/tests/modules/composer_installable/composer.json
    @@ -0,0 +1,6 @@
    +{
    

    Nitpick, we use 2 spaces in composer.json

  4. +++ b/core/modules/system/tests/modules/composer_uninstallable/composer.json
    @@ -0,0 +1,6 @@
    +{
    

    Codestyle, 2 spaces

borisson_’s picture

StatusFileSize
new2.45 KB
new8.63 KB
new72.77 KB

Fixes #41.1, .3 and .4.

I feel like .2 is scope creep.

borisson_’s picture

StatusFileSize
new9.17 KB
new72.97 KB
new8.83 KB

Removed the hook_requirements_alter.

borisson_’s picture

Status: Needs work » Needs review
StatusFileSize
new1.49 KB
new73.61 KB
new9.46 KB

Introduced a new Exception: ExtensionComposerRequirementsException.

borisson_’s picture

Status: Needs work » Needs review

mark_modules_with_unmet-2494073-56.patch is supposed to fail because the required composer/semver isn't in yet. That's why the mark_modules_with_unmet-2494073-56-include-package.patch does work.

borisson_’s picture

Fixed some small nitpicks, same argument as in #60 still applies: only the -include-package patch will pass.

hussainweb’s picture

Status: Needs work » Needs review
StatusFileSize
new59.01 KB

I applied the smaller patch in #61 and updated the package to the correct location. The failures in #61 were expected because it did not actually include the package. Let's see how this one fares.

hussainweb’s picture

@borisson_, a suggestion: You can use the extension .txt if you don't want the patch without the package to be tested. Alternatively, you can suffix "-do-not-test" in the filename as well, e.g. mark_modules_with_unmet-2494073-61-do-not-test.patch.

borisson_’s picture

@hussainweb, the library should be added in #2575469: Require the composer/semver library to do version checking., that's why it was added as 2 seperate patches every time.

hussainweb’s picture

Status: Needs review » Postponed

The other issue is RTBC and accepted by framework manager. We can get back to this once #2575469: Require the composer/semver library to do version checking. gets in.

hussainweb’s picture

The other issue is RTBC and accepted by framework manager. We can get back to this once #2575469: Require the composer/semver library to do version checking. gets in.

hussainweb’s picture

Status: Postponed » Needs review
StatusFileSize
new9.43 KB

#2575469: Require the composer/semver library to do version checking. is in. I am uploading the small patch in #61 again, which still applies.

borisson_’s picture

Issue summary: View changes

If we don't get this in now, I think we should remove core's dependency on composer/semver as well; because that code was only intended to be used by this patch and is otherwise dead. (Reverting #2575469: Require the composer/semver library to do version checking.).

mile23’s picture

mile23’s picture

Assigned: mile23 » Unassigned
StatusFileSize
new17.55 KB
new17.87 KB

Added a testable class called Drupal\Core\Composer\ExtensionComposerDependencies, with tests, which you can use to query whether the Composer dependencies are met. It's a best-faith effort to verify Composer dependencies without just including composer/composer in the repo.

This simplifies drupal_check_composer_requirements() by making it non-existent.

This patch doesn't pass tests because it (accurately) throws ExtensionComposerRequirementsException which isn't handled anywhere during form building for the module list.

Anyone else is welcome to figure out how to build the form with this in mind.

mile23’s picture

Status: Needs work » Needs review
StatusFileSize
new21.43 KB
new9.41 KB

Fixed errors in #77, some improvements.

fabianx’s picture

Status: Needs review » Reviewed & tested by the community

RTBC - looks great to me!

mile23’s picture

Status: Needs work » Reviewed & tested by the community
xano’s picture

  1. +++ b/core/lib/Drupal/Core/Composer/ExtensionComposerDependencies.php
    @@ -0,0 +1,156 @@
    +      // @todo: Determine how to tell if the root package is a dev installation
    

    If we introduce a new to-do (technical debt), we should create a follow-up issue for it and reference it in this comment.

  2. +++ b/core/lib/Drupal/Core/Composer/ExtensionComposerDependencies.php
    @@ -0,0 +1,156 @@
    +   * @param array $installed_packages
    

    array is never an acceptable type in documentation. It looks like the values are strings, which mean the type must be string[].

  3. +++ b/core/lib/Drupal/Core/Composer/ExtensionComposerDependencies.php
    @@ -0,0 +1,156 @@
    +   * @return array
    

    Same here.

  4. +++ b/core/lib/Drupal/Core/Extension/ExtensionComposerRequirementsException.php
    @@ -0,0 +1,50 @@
    +class ExtensionComposerRequirementsException extends \Exception {
    

    +1 for using a specific exception class!

  5. +++ b/core/lib/Drupal/Core/Extension/ExtensionComposerRequirementsException.php
    @@ -0,0 +1,50 @@
    +   * Constructor.
    

    Constructs a new instance., as our coding standards specify the one-line summaries must start with a third-person verb.

  6. +++ b/core/lib/Drupal/Core/Extension/ExtensionComposerRequirementsException.php
    @@ -0,0 +1,50 @@
    +  public function getTranslatedMessage(TranslationInterface $string_translation) {
    

    Exceptions are developers' tools. Why do we translate the message?

  7. +++ b/core/modules/system/src/Form/ModulesListForm.php
    @@ -471,6 +472,13 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
    +          $e->getTranslatedMessage($this->getStringTranslation()),
    

    I see why we translate the exception message. I would instead construct the translated message here using data stored on the exception class. This provides a cleaner separation between API and UI.

  8. +++ b/core/modules/system/src/Tests/Module/HookRequirementsTest.php
    @@ -28,4 +30,53 @@ function testHookRequirementsFailure() {
    +    $message = 'Attempting to install composer_uninstallable threw ExtensionComposerRequirementsException.';
    

    No bug, but a tip (for the future): we can use ExtensionComposerRequirementsException::class to dynamically generate a string with the FQN. It usually takes less characters, and it's automatically changed when refactoring the referenced class or its namespace.

mile23’s picture

Status: Reviewed & tested by the community » Needs review
StatusFileSize
new21.47 KB
new4.46 KB

@Xano #85 :Thanks. :-)

#85.1: Research tells me that Composer doesn't store the dev status of a root package, so we can't determine it. So now that part of the code only checks for requires and not requires-dev.

#85.4: Thank @borrison. :-)

#85.6, 7: It's the same pattern as UnmetDependenciesException, except with even simpler logic. Check out ModulesListForm::submitForm() to see the similarities. Also, having the translatable string in the exception allows other code to use the message.

This patch addresses all these things, except ExtensionComposerRequirementsException still generates a translated string.

jhodgdon’s picture

There are numerous documentation problems with the latest patch...

  1. +++ b/core/lib/Drupal/Core/Composer/ExtensionComposerDependencies.php
    @@ -0,0 +1,157 @@
    +   * Determine whether Composer-based dependencies are still required.
    

    Determine -> Determines

  2. +++ b/core/lib/Drupal/Core/Composer/ExtensionComposerDependencies.php
    @@ -0,0 +1,157 @@
    +        'description' => t('Not all the modules have their Composer dependencies installed yet. Please find more information on <a href="@handbook">this handbook page</a>.', [
    

    Please make the link text in this string accessible. Link text like "this handbook page" is not correct.

    And don't use the word "please" in UI text, and we should NEVER call the online documentation the "handbook".

    Also aren't we using :url not @url in t() for URLs?

    Anyway I suggest the wording of the second sentence of this t() should be:

    For more information, see the (link start)online documentation on Composer(link end)

    One more thing: we usually avoid linking to node IDs. Do we need an alias for that page? If so, what should it be?

  3. +++ b/core/lib/Drupal/Core/Composer/ExtensionComposerDependencies.php
    @@ -0,0 +1,157 @@
    +   * Determine whether the Composer-based dependencies of an extension are met.
    

    Determine => Determines

  4. +++ b/core/lib/Drupal/Core/Composer/ExtensionComposerDependencies.php
    @@ -0,0 +1,157 @@
    +   * Parse an installed.json file into the useful parts.
    

    Parse => Parses

  5. +++ b/core/lib/Drupal/Core/Composer/ExtensionComposerDependencies.php
    @@ -0,0 +1,157 @@
    +   *   JSON contents of installed.json file.
    

    of => of an

  6. +++ b/core/lib/Drupal/Core/Composer/ExtensionComposerDependencies.php
    @@ -0,0 +1,157 @@
    +   * @return array
    

    seems to be string[] rather than generic array?

  7. +++ b/core/lib/Drupal/Core/Composer/ExtensionComposerDependencies.php
    @@ -0,0 +1,157 @@
    +   *   Array with package names as the keys and normalized version as the
    

    version -> versions

  8. +++ b/core/lib/Drupal/Core/Extension/ExtensionComposerRequirementsException.php
    @@ -0,0 +1,50 @@
    +   * The machine name of the module with unmet dependencies.
    ...
    +  protected $moduleName;
    

    Is this just for modules? The class header docs say "extension".

    Let's get the member variable name here all docs headers on the same page. Either always use extension or always use module. Not a mix.

  9. +++ b/core/lib/Drupal/Core/Extension/ExtensionComposerRequirementsException.php
    @@ -0,0 +1,50 @@
    +    return $string_translation->translate("Module @module has unmet Composer-based dependencies.", ['@module' => $this->moduleName]);
    

    Question: does calling $string_translation->translate() get a string into the POT database? I don't think it does?

  10. +++ b/core/lib/Drupal/Core/Extension/ModuleInstaller.php
    @@ -98,9 +101,16 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
    +      $composer_dependencies = new ExtensionComposerDependencies();
    

    Is there a reason this isn't a service?

  11. +++ b/core/modules/system/src/Tests/Module/HookRequirementsTest.php
    @@ -28,4 +30,49 @@ function testHookRequirementsFailure() {
    +  /**
    +   * A module with uninstalled Composer dependencies is not installable.
    +   */
    +  function testComposerDependenciesFailure() {
    

    This docs header doesn't conform to our normal docs standards for a function/method.

  12. +++ b/core/modules/system/src/Tests/Module/HookRequirementsTest.php
    @@ -28,4 +30,49 @@ function testHookRequirementsFailure() {
    +  /**
    +   * A module with installed Composer dependencies is installable.
    +   */
    +  function testComposerDependenciesSuccess() {
    

    Same here.

    Both of these should start with something like:

    Tests that...

  13. +++ b/core/tests/Drupal/Tests/Core/Composer/ExtensionComposerDependenciesTest.php
    @@ -0,0 +1,124 @@
    +/**
    + * @coversDefaultClass \Drupal\Core\Composer\ExtensionComposerDependencies
    + *
    + * @group Composer
    + */
    +class ExtensionComposerDependenciesTest extends UnitTestCase {
    

    Don't we require a summary line for all class docs headers, or is there an (unwritten in standards) exception for test classes?

  14. +++ b/core/tests/Drupal/Tests/Core/Composer/ExtensionComposerDependenciesTest.php
    @@ -0,0 +1,124 @@
    +  /**
    +   * @return array
    +   *   - Expected parsed installed.json data.
    +   *   - Contents of installed.json file.
    +   */
    +  public function provideParseInstalled() {
    

    this docs header has no summary line.

  15. +++ b/core/tests/Drupal/Tests/Core/Composer/ExtensionComposerDependenciesTest.php
    @@ -0,0 +1,124 @@
    +  /**
    +   * @covers ::parseInstalledPackages
    +   *
    +   * @dataProvider provideParseInstalled
    +   */
    +  public function testParseInstalledPackages($expected, $installed) {
    

    no summary line

  16. +++ b/core/tests/Drupal/Tests/Core/Composer/ExtensionComposerDependenciesTest.php
    @@ -0,0 +1,124 @@
    +  /**
    +   * @return array
    +   *   - Boolean showing whether the dependencies are met by the installed
    +   *     packages.
    +   *   - Object of dependencies parsed from JSON.
    +   *   - Array of installed dependencies, with package name as key and version
    +   *     as value.
    +   */
    +  public function provideExtensionDependencies() {
    

    no summary line

    I've skipped pointing out the rest of this class lacks good docs for the methods.

mile23’s picture

Assigned: Unassigned » mile23
Issue tags: -rc target triage

Nice, thanks for the review. :-)

Is there a reason this isn't a service?

Good point. I think we only ever need it when there aren't a lot of dependencies available, such as install time. Also it's not a good candidate for replacement with an alternate service. Someone with more service-fu can address this.

mile23’s picture

Assigned: mile23 » Unassigned
Issue tags: +Needs documentation
StatusFileSize
new21.87 KB
new6.28 KB

One more thing: we usually avoid linking to node IDs. Do we need an alias for that page? If so, what should it be?

Heh. The node with the ID is this issue. :-)

Clearly we need a better doc page. We currently don't have a defined behavior for contrib-with-composer, so it's hard to tell people what to do.

I've left that part (#89.2) as-is, and addressed the rest of the concerns. So it still needs work.

As far as the PHPUnit summary lines... You can see that it's mostly redundant. The annotation tells all the stories. There are a lot of tests with no summary lines for this reason.

xano’s picture

dawehner’s picture

+++ b/core/lib/Drupal/Core/Composer/ExtensionComposerDependencies.php
@@ -0,0 +1,157 @@
+   */
+  public function extensionComposerRequirements($drupal_root, Extension $extension) {
+    $requirements = [];
+    if (!$this->dependenciesAreMet($drupal_root, $extension)) {
...
+   */
+  public function dependenciesAreMet($drupal_root, Extension $extension) {
+    // Does the extension have a composer.json file?
+    $extension_composer_json = $drupal_root . '/' . $extension->getPath() . '/composer.json';
+    if (is_readable($extension_composer_json)) {

I'm wondering whether the drupal root should be passed in as constructor parameter?

xano’s picture

Status: Needs work » Needs review

This patch cleans up some protected methods, so we don't have to check the value of a property before getting the list of installed packages. This makes ::dependenciesAreMet() so much simpler, we can easily merge ::extensionDependenciesAreMet() into it. This removes the need for ::testExtensionDependenciesAreMet(). The remaining protected method is tested through ::extensionDependenciesAreMet(), so we no longer directly test internal code, while maintaining coverage, which makes it easier to refactor in the future.

The patch also implements @dawehner's suggestion from #94.

One final thing I'd like to do is move ::extensionComposerRequirements() to a separate class which can then depend on ExtensionComposerDependencies and TranslationInterface, so we have full DI and testability. Either this, or we require TranslationInterface in ExtensionComposerDependencies::__construct().

xano’s picture

StatusFileSize
new16.05 KB
new19.76 KB

Surprise!

xano’s picture

Status: Needs work » Needs review
Issue tags: -Needs documentation
StatusFileSize
new26.4 KB
new24.47 KB

I split off the requirements building to a separate class, so we don't have one class that requires too many dependencies, or has confusing optional ones. This new class also has a kernel test.

I updated the docs URL to the dedicated handbook page about installing Composer dependencies, and accordingly removed the needs documentation tag from this issue.

This patch also fixes that one test failure we've been experiencing for a while.

xano’s picture

StatusFileSize
new14.77 KB

This interdiff includes file moves.

xano’s picture

StatusFileSize
new6.63 KB
new25.86 KB

I improved the requirements message a bit, because we shouldn't refer to our documentation as handbook anymore.

Using Currency I confirmed this patch works well.

I updated the issue summary earlier saying that the status report page will notify site admins of the status of Composer dependencies. That was untrue, because no hook_requirements() implementation performed this check. This patch adds this, as it is required for updates to work properly, for instance.

xano’s picture

Status: Needs work » Needs review
StatusFileSize
new2.32 KB
new25.84 KB

This message makes slightly more sense.

borisson_’s picture

Status: Needs review » Needs work

This patch is great and a whole lot better than how I left it. Great work @Xano & @Mile23.

I did however find a couple of small issues that I think can be resolved. But I only feel strongly about the documentation remarks I made, if you disagree with the other remarks they can be left as-is.

  1. +++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyChecker.php
    @@ -0,0 +1,110 @@
    +   * @var string[]
    +   *   Keys are package names and values are package versions. Example:
    +   *   ['crell/api-problem' => '1.7.0'.\].
    

    There's a syntax error here. The .\ should be removed.

  2. +++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyChecker.php
    @@ -0,0 +1,110 @@
    +    if (is_readable($composer_file_path)) {
    

    Can we change this? By reverting this check, we can make sure the rest of the code isn't as deeply nested.

    if (!is_readable($composer_file_path)) {
      // Modules that don't have a composer file don't have external dependencies, which means they are met.
      return TRUE; 
    }
    
    $composer_dependencies = ...
    
  3. +++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyChecker.php
    @@ -0,0 +1,110 @@
    +      // Gather all the requirements.
    +      $require = [];
    

    Can we change this variable name to $requirements? That makes more sense I think.

  4. +++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyChecker.php
    @@ -0,0 +1,110 @@
    +   *   Keys are package names and values are package versions. Example:
    +   *   ['crell/api-problem' => '1.7.0'.\].
    

    See earlier comment.

  5. +++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyChecker.php
    @@ -0,0 +1,110 @@
    +    // Find the installed packages once, and cache the data.
    +    if (empty($this->composerInstalled)) {
    

    We can also change this check to reduce the nesting.

    if (!empty($this->composerInstalled) {
      return $this->composerInstalled;
    }
    ...
    
xano’s picture

Status: Needs work » Needs review
StatusFileSize
new5.26 KB
new26.18 KB

Thanks for the review! I addressed all your concerns.

In addition to addressing the feedback from in #104, I also optimized ::getInstalledPackages() so all operations are postponed as long as possible, and we check for installed.json absolutely only once; the previous patches would check repeatedly if the file did not exist.

Several variables were renamed for clarity, and comments were added.

borisson_’s picture

Status: Needs review » Needs work

I noticed one small typo, otherwise I feel this is RTBC.

+++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyChecker.php
@@ -56,30 +54,35 @@ public function __construct($drupal_root) {
+    // If the extension has on Composer file, it has no unmet dependencies.

s/on/no/

xano’s picture

Status: Needs work » Needs review
StatusFileSize
new749 bytes
new26.18 KB
borisson_’s picture

Status: Needs review » Reviewed & tested by the community

This patch changed significantly since the RTBC in #80 and all the work I did on the patch was before that. Because of that, I feel confident setting this to RTBC.

alexpott’s picture

Status: Reviewed & tested by the community » Needs review

What happens to a site which has a module installed where the composer dependencies are not satisfied? Do we need to think about an upgrade path for this?

xano’s picture

This patch does not change runtime behavior for existing sites, Sites will only experience functional changes when users try to enable additional modules, but those are only meant to ensure site integrity. With or without this patch it's still possible to end up with a site with unmet Composer dependencies.

Does this answer your question? If not, could you elaborate on what kind of upgrade you were thinking about?

jhodgdon’s picture

Status: Needs review » Needs work

I think the docs and UI text strings need a few small fixes in this patch as well:

  1. +++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyChecker.php
    @@ -0,0 +1,122 @@
    + * Performs a naive check for Composer-based module dependencies.
    

    Just a comment: a class that is used to deny installation of a module, which says it is "naive" right in the doc block, is a bit scary! :)

  2. +++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyChecker.php
    @@ -0,0 +1,122 @@
    +   * @see self::getInstalledPackages()
    

    @see should be at the end of doc blocks, and @see should always have the namespace/class in the line not self::

  3. +++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyChecker.php
    @@ -0,0 +1,122 @@
    +   *   Either NULL when the installed packages have not been computed yet, or an
    +   *   array of which keys are package names and values are package versions.
    

    This is a bit garbled. I started ready "... or an array of which keys are package names", and then realized it meant "an array whose keys are package names and whose values are" when I got to "values".

    Also the example... how is crell/api-problem a package name? maybe this needs more explanation?

  4. +++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyChecker.php
    @@ -0,0 +1,122 @@
    +   * @param \Drupal\Core\Extension\Extension $extension
    

    Should this be an interface?

  5. +++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyChecker.php
    @@ -0,0 +1,122 @@
    +   *   Keys are package names and values are package versions. Example:
    +   *   ['crell/api-problem' => '1.7.0'].
    

    Again I am not sure what "package names" are?

  6. +++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyRequirements.php
    @@ -0,0 +1,82 @@
    +        'description' => $this->t('Some modules have unmet Composer dependencies. Read the <a href="@documentation">documentation</a> on how to install them.', [
    

    This is not great link text for accessibility. We generally want the text in a link to tell you where the link goes. This one "documentation" doesn't really do that.

  7. +++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyRequirements.php
    @@ -0,0 +1,82 @@
    +        'description' => $this->t('All Composer dependencies have been installed.'),
    

    Hm. Are the composer dependencies just other modules that need to be installed, or can they be other things? maybe just say "have been met", as elsewhere in this patch?

  8. +++ b/core/lib/Drupal/Core/Extension/ExtensionComposerRequirementsException.php
    @@ -0,0 +1,50 @@
    +    return $string_translation->translate("Extension @extension has unmet Composer dependencies.", ['@extension' => $this->extensionName]);
    

    Um... does the POTX extractor pick up ->translate() calls? I thought you had to use t()?

  9. +++ b/core/lib/Drupal/Core/Extension/ModuleInstallerInterface.php
    @@ -42,6 +42,9 @@
    +   *   Thrown when Composer-based dependencies are not present.
    

    present -> met

  10. +++ b/core/modules/system/src/Tests/Module/HookRequirementsTest.php
    @@ -28,4 +30,49 @@ function testHookRequirementsFailure() {
    +   * Test that a module with uninstalled dependencies is not installable.
    

    Test -> Tests

  11. +++ b/core/modules/system/src/Tests/Module/HookRequirementsTest.php
    @@ -28,4 +30,49 @@ function testHookRequirementsFailure() {
    +   * Test that a module with installed dependencies is installable.
    

    Test -> Tests

xano’s picture

Status: Needs work » Needs review
StatusFileSize
new3.91 KB
new26.19 KB

@see should be at the end of doc blocks, and @see should always have the namespace/class in the line not self::

Could you link to the documentation? This seems highly unproductive, as using an FQN decreases specificity and makes refactoring harder, because those FQNs are not picked up by static analysis tools.

This is a bit garbled. I started ready "... or an array of which keys are package names", and then realized it meant "an array whose keys are package names and whose values are" when I got to "values".

whose and of which are both valid in this case (I verified with a native speaker of US/UK/AU English who is also an English major, and the Merriam Webster and Oxford Dictionaries).

Hm. Are the composer dependencies just other modules that need to be installed, or can they be other things? maybe just say "have been met", as elsewhere in this patch?

Also the example... how is crell/api-problem a package name? maybe this needs more explanation?

Valid questions, which are answered by http://getcomposer.org and https://packagist.org. I don't think we should explain the underlying workings of Composer in our own documentation. The Composer people are much better at that.
TLDR; Composer packages are generic PHP packages. They are not Drupal modules.

Should this be an interface?

There is none for \Drupal\Core\Extension\Extension, because it is a simple value object.

Um... does the POTX extractor pick up ->translate() calls? I thought you had to use t()?

That's a good one. I am not sure. I pinged Gabor on IRC, and will report back when I know more.

All your other feedback has been addressed in this patch.

xano’s picture

Um... does the POTX extractor pick up ->translate() calls? I thought you had to use t()?

It's not officially supported. See the test that should verify this.

jhodgdon’s picture

Regarding @see and references to classes/methods in docs, see:
https://www.drupal.org/node/1354#classes

"Immediately after an @tag (@param, @return, @ver, etc.), class and interface names must always include the fully-qualified namespace."

So. I guess we have not explictly said you cannot use self:: but we normally in @see do put in the FQNS/class::method().

Regarding my other comments, I agree we shouldn't document Composer but just saying "composer package name" would have at least alerted me that it was Composer jargon and not something specific to this function? And do we have a link somewhere to the composer docs you mentioned that explain what the package name is, maybe in the docs header for the class (not saying it should be on each method but a reference link would be helpful if we have a class in Drupal namespace that is dealing with this stuff)?

xano’s picture

"Immediately after an @tag (@param, @return, @ver, etc.), class and interface names must always include the fully-qualified namespace."

Something is missing there. I recall an issue to loosen these strict rules, as they prohibit things like @return static and @return $this, which are valid and separate cases which we use all over core. The additional problem with @see is that it's a tag for arbitrary text and static analysis tools don't parse the contents. This means that using FQNs here is not only redundant (because self is a valid PHP class reference), but that when auto-renaming or moving the class in an IDE, this comment does not get updated, so we run the risk of it becoming outdated.

jhodgdon’s picture

Yes, we can use @return static and @return $this. That is not the same as @see self::methodName(). It is a special case for @return, as described at
https://www.drupal.org/node/1354#types

Anyway. Whatever. api.drupal.org can actually handle @see self::methodName(). If IDEs can also handle it, that is OK. I'm not going to insist.

mile23’s picture

Status: Needs review » Needs work

Just a comment: a class that is used to deny installation of a module, which says it is "naive" right in the doc block, is a bit scary! :)

Well it *is* naive. If it weren't, we'd require composer/composer and then we'd use composer's code to do the check. Totally OK with changing that docblock though. :-)

+++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyChecker.php
@@ -0,0 +1,122 @@
+   * @see self::getInstalledPackages()

Return type can be static or $this without the FQNS for @return, but @see needs FQNS: https://www.drupal.org/coding-standards/docs#types and https://www.drupal.org/coding-standards/docs#see

I think #109 is valid, too: If we have contrib_module v.1.0 installed, and the update to v.2.0 now adds a composer.json file with unmet dependencies, then we should be blocked from doing an update. Maybe in update_check_requirements()?

xano’s picture

I think #109 is valid, too: If we have contrib_module v.1.0 installed, and the update to v.2.0 now adds a composer.json file with unmet dependencies, then we should be blocked from doing an update. Maybe in update_check_requirements()?

That was already fixed in #101. Also see #110.

xano’s picture

Status: Needs work » Needs review
StatusFileSize
new6.6 KB
new27.66 KB

This patch changes two things:

  1. On IRC @bojanz suggested making the new classes services. That's done and all usages were converted. This improves testability of calling code and makes future development of these new classes easier.
  2. @alexpott in #109 informed about how we make sure that admins of existing broken sites are made aware of missing Composer packages. A small change in system.install ensures that these requirements are checked during updates, even before admins can select the updates to perform. This means that even if 8.0.2/8.1.0 does not introduce database updates, site admins will not find out until they have updated their Compose dependencies.
mile23’s picture

Issue summary: View changes
Status: Needs review » Reviewed & tested by the community
StatusFileSize
new32.73 KB

VERY glad @alexpott clarified that. Sounds great.

I added a non-existent requirement to a contrib module and went to update.php on my dev site. It showed me this:

update.php shows me that I have unmet composer dependencies

The documentation link sent me here: https://www.drupal.org/node/2627292

As @jhogdon pointed out, we should have a readable path for that instead of a node id, but that can be a follow-up.

RTBC from me, even though I wrote a little bit of it.

jhodgdon’s picture

I aliased the page https://www.drupal.org/documentation/install/composer-dependencies if someone wants to update the patch.

xano’s picture

Issue summary: View changes

Is that alias valid? The page is purposefully nested under Drupal 8 documentation, because we need Drupal-specific workarounds to integrate with Composer and those are different across Drupal versions.

jhodgdon’s picture

I'm happy to change it. What would you suggest? I just named it similar to other pages in the section.

xano’s picture

StatusFileSize
new888 bytes
new27.69 KB

We can always add subpages later, and make this a landing page. This is good for now.

mile23’s picture

+1

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
FILE: ...ore/lib/Drupal/Core/Composer/ExtensionDependencyRequirements.php
----------------------------------------------------------------------
FOUND 0 ERRORS AND 3 WARNINGS AFFECTING 3 LINES
----------------------------------------------------------------------
 10 | WARNING | [x] Unused use statement
 11 | WARNING | [x] Unused use statement
 67 | WARNING | [x] A comma should follow the last multiline array
    |         |     item. Found:
    |         |     'https://www.drupal.org/documentation/install/composer-dependencies'
----------------------------------------------------------------------
PHPCBF CAN FIX THE 3 MARKED SNIFF VIOLATIONS AUTOMATICALLY
----------------------------------------------------------------------


FILE: ...al/core/modules/system/src/Tests/Module/HookRequirementsTest.php
----------------------------------------------------------------------
FOUND 4 ERRORS AFFECTING 4 LINES
----------------------------------------------------------------------
 37 | ERROR | [ ] Visibility must be declared on method
    |       |     "testComposerDependenciesFailure"
 67 | ERROR | [ ] Visibility must be declared on method
    |       |     "testComposerDependenciesSuccess"
 78 | ERROR | [x] The closing brace for the class must have an empty
    |       |     line before it
----------------------------------------------------------------------
PHPCBF CAN FIX THE 1 MARKED SNIFF VIOLATIONS AUTOMATICALLY
----------------------------------------------------------------------


FILE: ...ts/Drupal/Tests/Core/Composer/ExtensionDependencyCheckerTest.php
----------------------------------------------------------------------
FOUND 8 ERRORS AFFECTING 8 LINES
----------------------------------------------------------------------
 40 | ERROR | If the line declaring an array spans longer than 80
    |       | characters, each element should be broken into its own
    |       | line
 41 | ERROR | If the line declaring an array spans longer than 80
    |       | characters, each element should be broken into its own
    |       | line
 45 | ERROR | If the line declaring an array spans longer than 80
    |       | characters, each element should be broken into its own
    |       | line
 47 | ERROR | If the line declaring an array spans longer than 80
    |       | characters, each element should be broken into its own
    |       | line
 48 | ERROR | If the line declaring an array spans longer than 80
    |       | characters, each element should be broken into its own
    |       | line
 52 | ERROR | If the line declaring an array spans longer than 80
    |       | characters, each element should be broken into its own
    |       | line
 53 | ERROR | If the line declaring an array spans longer than 80
    |       | characters, each element should be broken into its own
    |       | line
 57 | ERROR | Missing short description in doc comment
----------------------------------------------------------------------


FILE: ...upal/Tests/Core/Composer/ExtensionDependencyRequirementsTest.php
----------------------------------------------------------------------
FOUND 1 ERROR AFFECTING 1 LINE
----------------------------------------------------------------------
 42 | ERROR | Missing short description in doc comment
----------------------------------------------------------------------

Some code style issues that can be discovered using coder...

xano’s picture

Status: Needs work » Needs review
StatusFileSize
new5.27 KB
new27.89 KB

IIRC correctly (and @dawehner seemed to recall so too) we do not require descriptions for docblocks that use @covers.

xano’s picture

Status: Needs review » Needs work

This patch does not take platform packages into account. Maybe we need to include Composer and use its API to check whether dependencies are met instead of our current 'naive' check.

bojanz’s picture

We shouldn't care about platform packages at all.

Quoting composer_manager:

/**
   * Removes platform packages from the requirements.
   *
   * Platform packages include 'php' and its various extensions ('ext-curl',
   * 'ext-intl', etc). Drupal modules have their own methods for raising the PHP
   * requirement ('php' key in $extension.info.yml) or requiring additional
   * PHP extensions (hook_requirements()).
   *
   * @param array $requirements
   *   The requirements.
   *
   * @return array
   *   The filtered requirements array.
   */
  protected function filterPlatformPackages($requirements) {
    foreach ($requirements as $package => $constraint) {
      if (strpos($package, '/') === FALSE) {
        unset($requirements[$package]);
      }
    }

    return $requirements;
  }
mile23’s picture

Status: Needs work » Needs review
StatusFileSize
new28.26 KB
new1.82 KB

We don't want to include composer/composer unless we're willing to take on full responsibility.

For this issue, we only want to tell the user to do something outside of Drupal.

It's right to ignore platform requirements here, because if the platform requirements are unmet then the user couldn't install them through Composer anyway, so we should have uninstalled packages at the point we're checking for them.

(I think composer_manager *should* care about platform requirements, because it's taking on the responsibility of managing dependencies, but that's out of scope here.)

So here's a patch.

borisson_’s picture

Status: Needs review » Reviewed & tested by the community

I agree with the logic in #129 and #130, back to RTBC it is.

borisson_’s picture

This has been changed to 8.1.x in #88, but I think we should get it into 8.0.x, because it's a bug.

We've been discussing external code support improvements for search api solr and this issue is currently a blocker for us.

xano’s picture

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

If I understand @Mile23's intentions correctly, he changed the version because that's what we used to do to prevent regressions (commit to the newest branch, then 'backport' to older branches). However, a while ago @webchick explained when it comes to bug fixes for 8.0.x and 8.1.x, we set them to 8.0.x if the patch applies to both branches. I'm setting back the version in accordance with this explanation.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
  1. +++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyRequirements.php
    @@ -0,0 +1,80 @@
    +        'description' => $this->t('Some modules have unmet Composer dependencies. Read the <a href="@documentation">documentation on drupal.org</a> on how to install them.', [
    +          '@documentation' => 'https://www.drupal.org/documentation/install/composer-dependencies',
    +        ]),
    

    I think this could be way more helpful. How we're not saying what module and what dependency? Also I think it probably should be one requirement fail per module.

    Also this would help what happens when you install a module missing a dependency as this fires before the module install code. At the moment when you install a module missing a dependency this is the message you see.

  2. +++ b/core/modules/system/src/Form/ModulesListForm.php
    @@ -471,6 +472,13 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
    +      catch (ExtensionComposerRequirementsException $e) {
    +        drupal_set_message(
    +          $e->getTranslatedMessage($this->getStringTranslation()),
    +          'error'
    +        );
    +        return;
    +      }
    

    How can this exception catching ever be hit at runtime (and tested)? Requirements are tested before we get here.

xano’s picture

I think this could be way more helpful. How we're not saying what module and what dependency? Also I think it probably should be one requirement fail per module.

We considered this irrelevant, as it is not how Composer works. While in Drupal you can easily see which dependencies are missing and choose which ones to add to your codebase, Composer uses an all-or-nothing approach. This patch was built with the following philosophy: if you want to install a certain module, you'll want its dependencies too. Doing what you suggest would not only require much more complex code, but would also make the requirements more complex, while all we need to tell site builders is to perform one single action that will fix any missing dependencies.

alexpott’s picture

@Xano well we still have the problem of installing several modules at once and not telling the user which one failed.

xano’s picture

You're right. We do the same thing with module dependencies (see ModulesListConfirmForm::buildForm()).

mile23’s picture

Assigned: Unassigned » mile23

OK. 8.0.x it is, attempting to at least tell which module is failing the requirements test.

mile23’s picture

Assigned: mile23 » Unassigned
Status: Needs work » Needs review
StatusFileSize
new29.92 KB
new8.38 KB
new43.92 KB

OK, we have helpful information about module names and their dependencies.

Unmet Dependencies Message

xano’s picture

  1. +++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyChecker.php
    @@ -56,11 +56,26 @@ public function __construct($drupal_root) {
    +   * Get a list of all the unmet Composer-based dependencies for an extension.
    

    Must start with a third person singular verb.

  2. +++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyChecker.php
    @@ -56,11 +56,26 @@ public function __construct($drupal_root) {
    +   * @return array
    

    @return array must provide a more specific type. The description indicates this must be @return string[].

  3. +++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyChecker.php
    @@ -56,11 +56,26 @@ public function __construct($drupal_root) {
    +  public function unmetDependencies(Extension $extension) {
    

    Can we add a verb to the method name? Something like calculateUnmetDependencies()?

  4. +++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyRequirements.php
    @@ -52,16 +52,17 @@ public function buildRequirements(array $extensions) {
    +      $extension_unmet = $this->dependencyChecker->unmetDependencies($extension);
    

    This variable does not contain an extension, but is a list of extensions and constraints.

  5. +++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyRequirements.php
    @@ -52,16 +52,17 @@ public function buildRequirements(array $extensions) {
    +        $unmet_dependencies[] = $extension->getName() . ' (' . $this->formatComposerDependencies($extension_unmet) . ')';
    

    We use this in a translatable string later on, so we should tun this through t() as well.

    Can we use ModuleHandlerInterface::getName() to display a human-readable name?

  6. +++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyRequirements.php
    @@ -73,8 +74,25 @@ public function buildRequirements(array $extensions) {
    +   * @param array $dependencies
    

    @param string[]

  7. +++ b/core/tests/Drupal/Tests/Core/Composer/ExtensionDependencyRequirementsTest.php
    @@ -46,16 +47,17 @@ public function providerBuildRequirements() {
    +   * @param array $unmet_dependencies
    

    @param string[]

xano’s picture

I am wondering whether it's useful to specify the missing dependencies in such detail. As an experienced developer, I don't mind, but it may not be very useful for site builders, because all dependencies are installed using a single command anyway, and it may even be confusing.

rszrama’s picture

I may be being too much of a pedant here, but these aren't really Composer dependencies, are they? They're just PHP library dependencies that happen to be managed via Composer. (e.g. we wouldn't call other Drupal modules "Drush dependencies" or "Info file dependencies")

mile23’s picture

StatusFileSize
new29.92 KB
new4.41 KB

Thanks for the reviews.

I addressed everything in #140, #142 except a few things:

#140.3: Changed to getUnmetDependencies() because no one cares if it's calculated. :-)

#140.5: Re: t(). The string is entirely calculated. I'm not sure any translator is going to be translating composer package names.

Re: Human-readable names: We're working with extensions, so we'd have to check if it's a module before gleaning the human-readable name that way. Also, we have to be able to do this at install time, and it looks like system_get_info() needs a container (and thus a kernel). Maybe after #2208429: Extension System, Part III: ExtensionList, ModuleExtensionList and ProfileExtensionList

We really should come up with a way to encapsulate all the info file info into a model class like Extension. Like just read the info file if someone needs the human-readable name and there are no services. This patch should also do the same thing for composer.json files, adding it as a method on Extension rather than computing it during the check. Someone say yes and I'll add it.

mile23’s picture

Status: Needs work » Needs review
StatusFileSize
new29.92 KB
new1.11 KB

Ewps. Forgot that test.

dawehner’s picture

  1. +++ b/core/tests/Drupal/Tests/Core/Composer/ExtensionDependencyCheckerTest.php
    @@ -0,0 +1,116 @@
    +      // No Composer dependencies.
    +      [TRUE, NULL, NULL],
    +      // No Composer dependencies, without any installed packages.
    +      [TRUE, NULL, '[]'],
    +      // No Composer dependencies, with an empty extension Composer file.
    +      [TRUE, '{}', '[]'],
    +      // One met dependency using different version constraints.
    +      [
    +        TRUE,
    +        '{"require": {"vendor/package":"1.0.0"}}',
    +        '[{"name":"vendor/package","version_normalized":"1.0.0"}]'
    +      ],
    +      [
    +        TRUE,
    +        '{"require": {"vendor/package":"~1.0"}}',
    +        '[{"name":"vendor/package","version_normalized":"1.0.0"}]'
    +      ],
    +      // One unmet dependency, without any installed packages.
    +      [FALSE, '{"require": {"vendor/not-installed":"1.0.0"}}', '[]'],
    +      // One unmet dependency, with another package installed.
    +      [
    +        FALSE,
    +        '{"require": {"vendor/not-installed":"1.0.0"}}',
    +        '[{"name":"vendor/package","version_normalized":"1.0.0"}]'
    +      ],
    +      // One unmet dependency using different version constraints.
    +      [
    +        FALSE,
    +        '{"require": {"vendor/package":"1.1.1"}}',
    +        '[{"name":"vendor/package","version_normalized":"1.0.0"}]'
    +      ],
    +      [
    +        FALSE,
    +        '{"require": {"vendor/package":"~1.1"}}',
    +        '[{"name":"vendor/package","version_normalized":"1.0.0"}]'
    +      ],
    +      // One unmet require-dev dependency, which is ignored, because it is not
    +      // required for normal site operation.
    +      [TRUE, '{"require-dev": {"vendor/package":"~1.1"}}', NULL],
    +      [
    +        TRUE,
    +        '{"require-dev": {"vendor/package":"1.1.1"}}',
    +        '[{"name":"vendor/package","version_normalized":"1.0.0"}]'
    +      ],
    +      [
    +        TRUE,
    +        '{"require-dev": {"vendor/package":"~1.1"}}',
    +        '[{"name":"vendor/package","version_normalized":"1.0.0"}]'
    +      ],
    +      // Platform dependencies are ignored.
    +      [
    +        TRUE,
    +        '{"require": {"php":">10.0"}}',
    +        '[{"name":"php","version_normalized":"1.0.0"}]'
    +      ],
    +    ];
    

    What about using keys for the data provider for better debugability?

  2. +++ b/core/tests/Drupal/Tests/Core/Composer/ExtensionDependencyRequirementsTest.php
    @@ -0,0 +1,77 @@
    +    $dependency_checker = $this->getMockBuilder(ExtensionDependencyChecker::class)
    +      ->disableOriginalConstructor()
    +      ->getMock();
    +    $dependency_checker->expects($this->once())
    +      ->method('getUnmetDependencies')
    +      ->willReturn($unmet_dependencies);
    +
    +    $string_translation = $this->getMock(TranslationInterface::class);
    +
    +    $extension = $this->getMockBuilder(Extension::class)
    +      ->disableOriginalConstructor()
    +      ->getMock();
    

    Is there a reason we don't use prophecy? Prophecy is IMHO way less verbose and makes it easier to understand the test

xano’s picture

What about using keys for the data provider for better debugability?

I've heard about that, but whenever my tests fail locally, the output never includes the key of the provided data. Do I have to do something to make that work?

Is there a reason we don't use prophecy? Prophecy is IMHO way less verbose and makes it easier to understand the test

Because it took @klausi until Drupalcon Asia to finally convince me to use it ;-)

xano’s picture

StatusFileSize
new5.94 KB
new30.84 KB

This patch addresses all feedback from #147.

Mixologic’s picture

There will come a time (er, well, now) when maintainers of modules will express their module dependencies in composer as well (http://cgit.drupalcode.org/pathauto/tree/composer.json), and this will soon be fully supported via the composer façade. Not sure if this is in or out of scope for this issue, but we're essentially providing two mechanisms for expressing dependencies - .info.yml and the composer.json. If there is a discrepancy between the two (in this case the versions are specific in the composer.json, but ambiguous in the info.yml file (http://cgit.drupalcode.org/pathauto/tree/pathauto.info.yml), then that should probably be handled as well.

mile23’s picture

composer.json only tells you what files should be in /vendor (or in /modules or whatever). We can query installed.json and figure out if that dependency is met, and prevent installing modules this way.

.info.yml only tells you which extensions should be enabled before a given extension can be enabled. We already have this check, and we check if the composer requirements are met before we check whether dependencies are enabled.

If a contrib module declares its dependency on a module in composer.json then that dependency will be unmet if it's not in installed.json, even if the module has been downloaded and placed in /modules. Using the pathauto link above as an example, the worst case scenario here is that someone already installed ctools without Composer, and then tried to install pathauto. They'd be prevented from installing pathauto because installed.json wouldn't reflect that ctools exists. Then they'd do composer install and potentially end up with two ctools in the file system, maybe even different versions.

There are a few ways to solve this for realsies:

1) Have Drupal poke around in installed.json when it installs modules. This is an unsustainable solution for a lot of reasons.

2) Have a way to install extensions under /vendor and then always do that. This would be a big change.

3) Require a Drupal-specific dependency management Composer plugin. This plugin could take on the responsibility of bridging the info.yml/json gap, but it would be a much less sustainable solution than #2. It could determine whether a given extension is in the filesystem and then add it to installed.json. This is similar to the embedded composer solution so many batted around before.

That's all out of scope here, of course, where we just want to tell the user to run composer if they need to. Implementing any of those other solutions would fix the problem outlined here.

borisson_’s picture

Status: Needs review » Needs work

#150 and #151 are out of scope and should be discussed somewhere else, this issue long enough as is. Let's keep it on topic.

The patch in #149 addressed all feedback, so that's great.

I have a few minor docs things that should be changed, but otherwise this has my vote of approval.

  1. +++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyChecker.php
    @@ -0,0 +1,144 @@
    +/**
    + * @file
    + * Contains \Drupal\Core\Composer\ExtensionComposerDependencies.
    + */
    

    Should be removed.

  2. +++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyRequirements.php
    @@ -0,0 +1,98 @@
    +   * @return array[]
    +   *   Requirements array, suitable for hook_requirements().
    

    Maybe we should add an @see to hook_requirements() so we link to the allowed keys for this array?

dawehner’s picture

Just a couple of small bits ...

  1. +++ b/core/includes/install.inc
    @@ -982,9 +983,22 @@ function drupal_requirements_severity(&$requirements) {
       $requirements = \Drupal::moduleHandler()->invoke($module, 'requirements', array('install'));
    -  if (is_array($requirements) && drupal_requirements_severity($requirements) == REQUIREMENT_ERROR) {
    +  // Make sure the requirements are an array, even if the hook invocation
    +  // returns NULL.
    +  if (!is_array($requirements)) {
    +    $requirements = [];
    +  }
    

    You know I would have just used $foo ?: []; and call it a day.

  2. +++ b/core/includes/install.inc
    @@ -982,9 +983,22 @@ function drupal_requirements_severity(&$requirements) {
    +  $dependency_requirements = \Drupal::service('composer.extension_dependency_requirements');
    

    Can we put a inline /** @var */ just to be sure, and make it easier for people?

  3. +++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyChecker.php
    @@ -0,0 +1,144 @@
    +/**
    + * @file
    + * Contains \Drupal\Core\Composer\ExtensionComposerDependencies.
    + */
    

    @file is now dropped

  4. +++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyChecker.php
    @@ -0,0 +1,144 @@
    +   * @param string $drupal_root
    +   *   The path to the Drupal root directory.
    ...
    +  public function __construct($drupal_root) {
    +    $this->drupalRoot = $drupal_root;
    +  }
    

    In other places we called that app root. Feel free to change if you want

  5. +++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyRequirements.php
    @@ -0,0 +1,98 @@
    +        'description' => $this->t('The following extensions have unmet Composer-based dependencies: @extensions. Read the <a href="@documentation">documentation on drupal.org</a> on how to install them.', [
    ...
    +          '@documentation' => 'https://www.drupal.org/documentation/install/composer-dependencies',
    

    Note: we have the ":" placeholder for URLs these days

xano’s picture

Status: Needs work » Needs review
StatusFileSize
new6 KB
new30.43 KB
xano’s picture

StatusFileSize
new30.47 KB

Re-roll.

xano’s picture

StatusFileSize
new677 bytes
new30.45 KB
xano’s picture

Status: Needs work » Needs review
StatusFileSize
new721 bytes
new30.66 KB

Add back the check we removed last weekend, and improve its documentation.

xano’s picture

Status: Needs work » Needs review
StatusFileSize
new30.56 KB

Re-roll.

jhedstrom’s picture

I'm tagging this as DX, since otherwise, many contrib modules will have to start implementing this on their own (for instance #2687197: Verify the composer managed dependencies using hook_requirements()).

+++ b/core/includes/install.inc
@@ -984,9 +985,23 @@ function drupal_requirements_severity(&$requirements) {
+  // Make sure the requirements are an array, even if the hook invocation
+  // returns NULL, because the module does not implement hook_requirements().
+  if (!is_array($requirements)) {
+    $requirements = [];
+  }

I think @dawehner was suggesting in #153 that this can be removed and simplified to

$requirements = \Drupal::moduleHandler()->invoke(...) ?: [];
xano’s picture

StatusFileSize
new1.06 KB
new30.59 KB
borisson_’s picture

Status: Needs review » Reviewed & tested by the community
fabianx’s picture

RTBC + 1

xano’s picture

StatusFileSize
new3.26 KB
new31.28 KB

This introduces a global constant in the autoloader that tells Drupal where to find the vendor directory. When using a different build tool such as drupal-composer/drupal-scaffold, the tool can write its own autoload.php with the new path to the vendor directory.

borisson_’s picture

Status: Needs review » Reviewed & tested by the community
fabianx’s picture

+++ b/autoload.php
@@ -11,4 +11,11 @@
+if (!defined('DRUPAL_COMPOSER_VENDOR_DIRECTORY')) {
+  define('DRUPAL_COMPOSER_VENDOR_DIRECTORY', 'vendor');
+}

Maybe we want to define this as absolute path as well?

Using __DIR__ . '/vendor'?

mile23’s picture

Status: Reviewed & tested by the community » Needs work

+1 on #175.

+++ b/autoload.php
@@ -11,4 +11,11 @@
+/**
+ * Defines the path to the Composer vendor directory.
+ */
+if (!defined('DRUPAL_COMPOSER_VENDOR_DIRECTORY')) {
+  define('DRUPAL_COMPOSER_VENDOR_DIRECTORY', 'vendor');
+}
+
 return require __DIR__ . '/vendor/autoload.php';

Use the constant here instead of rebuilding the path without it. That way other systems can just set the constant.

xano’s picture

Working on that already. It's in no way important at all, since build tools will have to override the entire file anyway, but it will provide us with early and clear failures if the constant value is incorrect.

xano’s picture

We'll write a Composer script that stores a list of installed packages during composer install. Ideally we'll make this a Composer plugin in the future, but those must be composer-plugin type packages of their own. We'll need #2474007-21: Add a “General” project type to Drupal.org to host those ourselves.

mile23’s picture

We'll write a Composer script that stores a list of installed packages during composer install.

Why? (Fergawdsakes...)

Composer does this already. It's in a file called ./vendor/composer/installed.json.

See ExtensionDependencyChecker->getInstalledPackages() in the patch.

It's the basis of the solution to this issue.

borisson_’s picture

Composer does this already. It's in a file called ./vendor/composer/installed.json.

See ExtensionDependencyChecker->getInstalledPackages() in the patch.

It's the basis of the solution to this issue.

Composer does this, but installed.json doesn't have a schema so we're not sure if we can depend on that file always having the same structure.

mile23’s picture

@borrison_ OK, that makes a little bit of sense, but here we have a fork in the road.

On one side, we have the current patch, which trusts installed.json, which means that if it stops working we have to patch it and make it better. One premise of this approach is that we then don't have to require Composer itself as a dependency, because no one really wants to require Composer.

On the other side, we have a proposal to write a Composer plugin which duplicates the behavior of Composer. This is based on the idea that at some point in the future, the most widely-used package manager in the PHP universe will change the way installed.json files work. It also means we get to maintain a set of code which doesn't do anything we couldn't accomplish by simply requiring Composer as a dependency and then asking it what it has installed at runtime. This is a use-case that Composer wasn't designed for. You'll recall that the problem we're trying to solve here is whether Composer has been used to install something, which might not even be a requirement for a given site.

I think the former is better, because it's smaller, less of a hassle, doesn't re-implement an external library, and accomplishes one goal effectively.

Mixologic’s picture

I can understand the concern with there not being a schema, and installed.json isn't static, and has changed over time (as a look at the Blame for ArrayDumper, the part of composer that translates the Package interface into json: https://github.com/composer/composer/blame/346133d2a112dbc52163eceeee678...). If something were to change with the schema that was incompatible with our expectations, that would break *existing* drupal installs, and yes, we could patch, but every site out there would be unable to leverage this feature until they do.

On the other hand, our expectations are modest - we're not dependent upon the entire comprehensive schema of installed.json. It looks like here:

+++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyChecker.php
@@ -0,0 +1,140 @@
+    // Parse the list once and then cache it.
+    $json = file_get_contents($installed_json_path, FALSE);
+    $this->installedPackages = [];
+    foreach (json_decode($json) as $package) {
+      if (isset($package->name) && isset($package->version_normalized)) {
+        $this->installedPackages[$package->name] = $package->version_normalized;
+      }
+    }
+
+    return $this->installedPackages;

we're only reliant on two things: That installed.json is an array at the top level of packages, and that packages have a version_normalized key.

I think that the likelihood that those two things would change are very low, and thus its a tolerable risk to assume we can rely on installed.json not changing out from underneath us.

If we do decide that the risk is too great, we do not actually need #2474007: Add a “General” project type to Drupal.org to take place (especially since thats not actually going to happen anytime soonish). Since it would be a package, it could be placed on github, and included in composer.json like all the other packages we currently require. If it needs to be more official than that, it can live in the drupal/ namespace on github.

xano’s picture

As mentioned in #178, we don't need #2474007: Add a “General” project type to Drupal.org now at all. It would be preferable over using a Composer script, but it is not an absolute requirement. The fact that that issue will not be fixed any time soon indicates a problem with our processes rather than with the solution we are working on in this issue.

With regards to publishing non-extension packages elsewhere: what would be the preferred way of publishing such packages?

xano’s picture

One of the problems is that the root package (drupal/drupal) and all packages merged into it (drupal/core) do not end up in installed.json, so our dump does not work in case anyone depends on core modules through their composer.json.

// Edit: problem solved. Patch coming later today.

Mixologic’s picture

@Xano, Can you clarify what you are saying in #183? My understanding is that instead of moving forward with the patch to core in #173, that you'd like to alter the plan to be a composer script/plugin that is hosted on drupal.org?

Why do we need the root package and all extensions merged into it end up in installed.json? How would it be possible to have a module that depends on a core exetension that can be an unmet dependency?

xano’s picture

@Xano, Can you clarify what you are saying in #183? My understanding is that instead of moving forward with the patch to core in #173, that you'd like to alter the plan to be a composer script/plugin that is hosted on drupal.org?

Composer scripts are static PHP callbacks. Composer plugins are stand-alone packages, and as such they need to be hosted on Packagist, or the application's root composer.json must include a custom repository that provides the package. At the Drupal Dev Days we decided we do not want to parse installed.json runtime, and the alternative is to do this through Composer's own API during build time using a Composer script or plugin.

Why do we need the root package and all extensions merged into it end up in installed.json? How would it be possible to have a module that depends on a core exetension that can be an unmet dependency?

Because I can write a perfectly valid composer.json that depends on any package we ship with Drupal core. This is about Composer integration, not about *.info.yml files or how we currently publish Drupal core. Besides, we may well publish core differently in the future (#2352091: Create (and maintain) a subtree split of Drupal core), and we don't want to have to rewrite any badly written Composer support code then. TLDR; we should avoid introducing Drupalisms in our Composer integration.

mile23’s picture

OK, we've got two things going on here:

1) @xano wants to move forward on having 'library' repo types, which is fine and good, but only related here in that we'd need to store the composer plugin in one. In the mean time, such a repo could be on github or even as a d.o sandbox project.

2)

One of the problems is that the root package (drupal/drupal) and all packages merged into it (drupal/core) do not end up in installed.json

That's incorrect, which is why I'm objecting to a Composer plugin in the first place. All packages merged will be reflected in the installed.json file. If it's in the vendor directory, it's reflected in installed.json.

To prove it:

$ cd your/drupal/root
$ rm -rf vendor
$ composer install
$ grep symfony/http-foundation composer.json 
// No results, since http-foundation is not declared in composer.json.
$ grep symfony/http-foundation vendor/composer/installed.json
// Lots of results, since installed.json reflects the packages that were merged from core/composer.json.
xano’s picture

1) @xano wants to move forward on having 'library' repo types, which is fine and good, but only related here in that we'd need to store the composer plugin in one. In the mean time, such a repo could be on github or even as a d.o sandbox project.

No. Please read #178 and #183 again. I said that for this issue a Composer plugin is preferable over a Composer script, but that the other issue does not necessarily block this one as using a script instead of a plugin is a workable solution.

That's incorrect, which is why I'm objecting to a Composer plugin in the first place. All packages merged will be reflected in the installed.json file. If it's in the vendor directory, it's reflected in installed.json.

I am in fact correct. Please run the following command in a sandbox folder and share with us which packages from the drupal vendor you see in the grep result: git clone --branch 8.2.x https://git.drupal.org/project/drupal.git && cd drupal && composer install && cat ./vendor/composer/installed.json | grep drupal. Also, what version of Composer are you running? My results are from 1.1.3 (yesterday's release).

mile23’s picture

Ahh, you want to discover which dependencies exist *from core.* But those won't be in installed.json because they were never installed by Composer. We have all those 'replace' directives in core/composer.json, and we use the merge plugin rather than declaring dependencies.

So follow your instructions and then do this:

$ composer require drupal/coder
$ cat ./vendor/composer/installed.json | grep drupal
            "drupal",
        "name": "drupal/coder",
        "homepage": "https://www.drupal.org/project/coder",

Now you see drupal/coder in the results.

The real sore thumb here is drupal/core which should have an entry but doesn't, because we use the merge plugin rather than declare it as a dependency of drupal/drupal.

So if you want to address what's in the installed.json file, don't re-implement Composer. Fix the 'replace' section in composer.json and figure out how to move us away from composer-merge-plugin.

In this issue we're using hook_requirements() to determine whether to allow someone to install stuff based on installed.json. If the user downloaded a module tarball that declares composer-based dependencies, we'll catch that by looking at installed.json. If they used Composer to install the module then Composer will have installed the dependency and we'll catch that case. If their vendor directory was deleted for whatever reason, we'll catch that. hook_requirements() already checks for the presence and status of core and contrib module dependencies by asking the module system, in existing code we're not modifying here.

Basically: Show me a composer-based dependency that a contrib extension can declare that won't be covered by the changes here and then we can talk about making a plugin.

I'd RTBC for #173 except we still need to change the constant as per #175 and use it as per #176.

Mixologic’s picture

Im unsure that we share the same understanding of the problem we are trying to solve.

From the issue summary:

Drupal core itself will not require site builders and developers to use Composer to assemble a site's code base

Therefore we must solve the problem of unmet dependencies under the assumption that no part of the solution involves the end user using composer. Therefore neither adding a composer script to the composer json to be executed, nor adding a composer plugin can solve this problem.

The issue we're facing is that a site maintainer may use a different method *other than composer* (e.g. drush/git/ftp) to download and assemble their codebase, which is the only way they could encounter the problem of unmet dependencies in the first place. How else can we determine that those dependencies are unmet, other than to:

A. Include composer as a dependency of core, and use its api to tell us if the deps are unmet
B. Parse the installed.json ourselves.

?

I can write a perfectly valid composer.json that depends on any package we ship with Drupal core.

If your module relies on core's plugin component, and you add a require in your composer json for drupal/core-plugin, there does not exist a scenario where an end user, who is *not* using composer, will not have that dependency unsatisfied, and simultaneously have a drupal install that can tell them that their dependency is unsatisfied.

The fact that drupal/core, and all of the various drupal/components do not exist in the installed.json, is not relevant to this issue at all. As soon as we (I - thats on my relatively soon roadmap) build out the subtree splits, one will be able to assemble a codebase *using composer* to pick and choose pieces of drupal core, specifically components, and or drupal/core itself.

The end user will not be able to pick and choose pieces of drupal core *without* using composer. We are not going to publish a download of drupal/core or any components like drupal/core-pluginas a gzip/tarball on any user facing page, (the tarballs will exist, but it will only be available to users using composer). So regardless of which pieces of core we allow to be composered together, I don't believe there is a problem to solve where an end user's missing dependency is a piece of core itself, as they can never get into that state.

At the Drupal Dev Days we decided we do not want to parse installed.json runtime

Other than the fact that there isn't a published schema, and that installed.json doesnt have drupal parts in it, were there other considerations?

xano’s picture

Status: Needs work » Needs review
StatusFileSize
new57.17 KB
new76.33 KB

Basically: Show me a composer-based dependency that a contrib extension can declare that won't be covered by the changes here and then we can talk about making a plugin.

@Mile23: Read my previous comments again. We decided to go with build-time aggregation of the installed package information at Drupal Dev Days. This can either be done using a Composer script, or a Composer plugin. Those are two completely different things. We're going with a script because #2474007: Add a “General” project type to Drupal.org has not been worked on yet. Upgrading this script to a plugin in a follow-up once that issue is fixed, would be preferable.
A Composer-based dependency that wouldn't work with previous patches would be one on drupal/views:~8.1, for instance. The patch attached to this comment fixes that. For reasons why we must support this, I refer back to #186.

xano’s picture

StatusFileSize
new16.39 KB
new59.95 KB

Seems one file slipped past my Thorough Review Skills™.

xano’s picture

Status: Needs work » Needs review
StatusFileSize
new59.95 KB

Re-roll.

@Mixologic in #190: Drupal requires Composer anyway, especially since 8.1, in which the vendor directory was removed from our VCS. Either our packaging script or developers' own build tools run composer install and in doing so trigger the script that dumps the list of installed packages. This means the list is always available, unless something went wrong.

borisson_’s picture

Status: Needs review » Needs work

I went through the entire patch again and I noticed one thing that I think can be improved.

+++ b/core/lib/Drupal/Core/Composer/Composer.php
@@ -246,4 +250,46 @@ protected static function deleteRecursive($path) {
+    $installed_dump_file_path = __DIR__ . '/../../../../composer/installed-packages.json';

Can we use DRUPAL_ROOT instead of __DIR__ . '/../'...?

Mixologic’s picture

Either our packaging script or developers' own build tools run composer install and in doing so trigger the script that dumps the list of installed packages

Our packaging script does *not* have access to the end users installed packages. And the target audience of this patch does *not* have build tools that run composer install.

This only solves a problem for people that do not have this problem.

xano’s picture

Can you provide step-by-step instructions to reproduce the problems you are talking about? That will not only help us understand your concerns, but it will also enable us to quickly analyze and hopefully fix the problems.

Can we use DRUPAL_ROOT instead of __DIR__ . '/../'...?

Unfortunately we can't, because this line is in a Composer script and because Drupal is not bootstrapped, bootstrap.inc (which defines DRUPAL_ROOT) has not been included.

mile23’s picture

@Xano: Read my previous comments again. We decided to go with build-time aggregation of the installed package information at Drupal Dev Days.

Yes, I realize that. It's the wrong decision for the reasons @Mixologic and I are pointing out, but which you have not addressed. We have working code that meets the scope of this issue without adding a script, and without making a copy of installed.json in the same directory during install time.

As I've pointed out, and @Mixologic confirms: The problems you have with installed.json are a consequence of not yet building using subtree split, and using a merge. These problems don't interfere with the goals of this issue.

Drupal requires Composer anyway, especially since 8.1, in which the vendor directory was removed from our VCS

No Drupal package requires Composer as a dependency. Composer is a requirement in order to do these installations, but it's not an explicit requirement of either drupal/drupal or drupal/core. Homonyms are a problem, yes? :-)

xano’s picture

As I've pointed out, and @Mixologic confirms: The problems you have with installed.json are a consequence of not yet building using subtree split, and using a merge. These problems don't interfere with the goals of this issue.

We experienced that Composer simply does not include replaced packages in its list of installed packages. While that is fine for normal Composer usage, we need this information to correctly find missing dependencies when a user attempts to install a module with Composer-based dependencies. We'd get false negatives without this approach.

No Drupal package requires Composer as a dependency. Composer is a requirement in order to do these installations, but it's not an explicit requirement of either drupal/drupal or drupal/core. Homonyms are a problem, yes? :-)

It most definitely is a requirement of both drupal/drupal and drupal/core. You cannot bootstrap Drupal without installing those packages' Composer-based dependencies, or you will encounter Class not found errors. The packaging script takes care of this when one wants to download a compressed archive of Drupal core, but that does not mean that Drupal core itself does not require Composer.

Please provide step-by-step instructions to reproduce the problems you are talking about? The discussion we have been having does not seem to have led anywhere, so let's try a different approach.

Mixologic’s picture

How about we rephrase this as a user story
As a user, who does not have Command line access, should be warned when I try to install a module with unmet dependencies.

  1. Download drupal : https://www.drupal.org/project/drupal/releases/8.1.3
  2. Apply the patch in #195
  3. Download address module https://www.drupal.org/project/address/releases/8.x-1.0-beta3
  4. Attempt to enable the address module.

I should see "Extension address has unmet Composer dependencies"

This patch is relying on the end user, at some point, running composer install or composer update in order to trigger the dump-packages. That will never happen for the users we're trying to warn.

Mixologic’s picture

By explicit requirement, mile23 is saying that 'require composer/composer' does not exist in either root composer.json, or in core/composer.json. An explicit requirement means we need composer's php codebase to run drupal, which, currently, we do not. We need it, *externally* to build drupal, but we do not need it internally, to *execute* drupal.

mile23’s picture

We experienced that Composer simply does not include replaced packages in its list of installed packages.

We don't need it to do that for this issue.

xano’s picture

This patch is relying on the end user, at some point, running composer install or composer update in order to trigger the dump-packages. That will never happen for the users we're trying to warn.

When does it rely on that? Your instructions are good, but forget that once this patch is applied, the packaging script will have run composer install already, so when, in your scenario, the user tries to install the Address module, our dumped list of installed packages will exist, but without the dependencies Address needs, so its installation will fail correctly.
To reproduce the future use case correctly, one must download a compressed Drupal core archive, apply the patch, execute composer update, and then try to install the Address module.

We don't need it to do that for this issue.

Please include argumentation we can use to reproduce the problem or confirm your statement with.

Mixologic’s picture

Ah I see, so you're saying that installed-packages.json will exist in that scenario, as core would essentially ship with it, and for everybody except users running composer, it will always contain only the external packages that ship with core, and therefore the end user will always get a message that they have unmet external dependencies.

I now understand now how this approach is designed to work, and this approach is essentially A. from #190

So it seems to me that both approaches will accomplish what we want to accomplish, and that the only thing the decision hinges on whether or not the schema of installed.json is going to change.

Earlier I said

If something were to change with the schema that was incompatible with our expectations, that would break *existing* drupal installs, and yes, we could patch, but every site out there would be unable to leverage this feature until they do

- Now I realize I was wrong about that.

Existing drupal installs, with users that *do not use composer*, would continue to have an installed.json that matches whatever schema was expected with whatever version of drupal they downloaded. This issue only exists to alert those users that do not use composer, so if we add some defensive code to the ExtensionDependencyChecker that expects a possibly differnent installed.json, we're safe to proceed with using installed.json, whether or not the schema is defined.

So, I dont see why its necessary to re-do what composer is already doing with installed.json, and I don't see any real threat with installed.json changing schemas and it seems to me that the patch in 173 (with a couple of tweaks) accomplishes what we need without adding a lot of additional dependencies.

mile23’s picture

Status: Needs work » Needs review
StatusFileSize
new31.86 KB
new1.79 KB

A from #190 says: A. Include composer as a dependency of core, and use its api to tell us if the deps are unmet

We want to avoid an explicit dependency on composer, because that's the way of madness. We also want to avoid re-implementing composer for similar reasons.

Making assumptions about the schema of installed.json is a reasonable way to avoid both of these, and we can explicitly add a test to tell us whether changes to the schema have broken our hook_requirements().

So here's a patch which goes back to #168, because I'm not sure we need DRUPAL_COMPOSER_VENDOR_DIRECTORY. I think we should come up with a better way to inject the vendor directory in a follow-up, because it fits a lot more use cases than just this one.

This patch also adds a test which reads in the existing live installed.json and tries to make sense of it. installed.json should be present if we're able to run phpunit-based tests.

Thus, when/if Composer changes its schema, we'll have a fail of at least this explicit test, and probably others.

borisson_’s picture

A from #190 says: A. Include composer as a dependency of core, and use its api to tell us if the deps are unmet
We want to avoid an explicit dependency on composer, because that's the way of madness. We also want to avoid re-implementing composer for similar reasons.

Yeah, let's avoid creating an explicit dependency on composer, that makes sense. The patch in #195 doesn't introduce that though.
It introduced an additional script in the current composer.json, but we already have scripts there, so no additional dependencies are introduced there. Well, there's one script, but I don't think that's a dependency.

Making assumptions about the schema of installed.json is a reasonable way to avoid both of these, and we can explicitly add a test to tell us whether changes to the schema have broken our hook_requirements().

Let's not introduce this behavior; "It'll work untill it doesn't, when it breaks in the future we'll probably fix it as soon as possible". If that eventually breaks and that issue takes as long as this one to resolve that'll take an extra 10 months.

So here's a patch which goes back to #168, because I'm not sure we need DRUPAL_COMPOSER_VENDOR_DIRECTORY. I think we should come up with a better way to inject the vendor directory in a follow-up, because it fits a lot more use cases than just this one.

We need DRUPAL_COMPOSER_VENDOR_DIRECTORY for people who don't use composer in a standard way, for example people using drupal-scaffold or drupal-composer. Or even another implementation of their own composer files.

Let's not think that the way core currently uses is the best practice forever and support those users as well. Xano's patch is in a pretty good state that also supports those things.

This patch also adds a test which reads in the existing live installed.json and tries to make sense of it. installed.json should be present if we're able to run phpunit-based tests.

Thus, when/if Composer changes its schema, we'll have a fail of at least this explicit test, and probably others.

I think #195 is pretty close - so let's just try to finish that approach, it's the most complete and covers all scenario's.

xano’s picture

StatusFileSize
new1.6 KB
new59.96 KB

This patch is based on #195 and fixes the test failures caused by that patch (PHP 5.5 incompatibility).

I purposefully decided not to build on #209 based on the feedback in #212, and because #209 fails to address the concerns that led to the development of the patch in #195, namely the dependency on our build tool's internal storage format which is subject to change at any time, and the lack of inclusion of merged and replaced packages, which we need to track in order to properly compute extensions' unmet dependencies.

Mixologic’s picture

We need DRUPAL_COMPOSER_VENDOR_DIRECTORY for people who don't use composer in a standard way, for example people using drupal-scaffold or drupal-composer. Or even another implementation of their own composer files.

Again, this patch is to warn people who do not use composer, if you are using drupal scaffold, or some other implementation of your own composer files, you are using composer, and you are *not* going to run into an issue of unmet dependencies.

Yeah, let's avoid creating an explicit dependency on composer, that makes sense. The patch in #195 doesn't introduce that though.
It introduced an additional script in the current composer.json, but we already have scripts there, so no additional dependencies are introduced there. Well, there's one script, but I don't think that's a dependency.

This patch most certainly adds an explicit dependency on composer, and everything else that comes with it:

+++ b/composer.lock
@@ -4,10 +4,142 @@
+            "name": "composer/ca-bundle",
+            "version": "1.0.2",
...
+            "name": "composer/composer",
+            "version": "1.1.3",

@@ -164,6 +296,67 @@
+            "name": "composer/spdx-licenses",
+            "version": "1.1.4",

@@ -890,6 +1083,72 @@
+            "name": "justinrainbow/json-schema",
+            "version": "1.6.1",

@@ -1090,6 +1349,144 @@
+            "name": "seld/cli-prompt",
+            "version": "1.0.2",
...
+            "name": "seld/jsonlint",
+            "version": "1.4.0",
...
+            "name": "seld/phar-utils",
+            "version": "1.0.1",

@@ -1487,6 +1884,104 @@
+            "name": "symfony/filesystem",
+            "version": "v3.1.1",
...
+            "name": "symfony/finder",
+            "version": "v3.1.1",
Let's not introduce this behavior; "It'll work untill it doesn't, when it breaks in the future we'll probably fix it as soon as possible". If that eventually breaks and that issue takes as long as this one to resolve that'll take an extra 10 months.

What would we need as reassurance that installed.json is not going to go from an array of packages to a different schema? I just read a bunch of backscroll in #composer and see this:

[06/25/2016 -:- 05:12:29 AM] <MichaelGooden> maybe a PR with a installed.json schema would be appreciated

Is that really all we need to do?

As an aside, even if the schema changes, it will *not* break for our target audience, it would be a critical bugfix we'd need to make before the next release, and it wouldn't take 10 months, because we're not establishing a new behavior, we'd be making a bugfix to handle whatever the new schema is.

xano’s picture

This patch most certainly adds an explicit dependency on composer, and everything else that comes with it:

Yes, it does. We made sure to only use this code on build time, though, because it's bad practice to rely on build tools during runtime, which means read-only file systems are supported. Composer does not support something like require-build, so we're stuck adding this to require. Since we use \Composer\Package, we need composer/composer in its entirety, as that did not seem to be available in its own package.

[06/25/2016 -:- 05:12:29 AM] maybe a PR with a installed.json schema would be appreciated

Is that really all we need to do?

No. We'd also need confirmation the schema is part of the public API and as such will not change.

As an aside, even if the schema changes, it will *not* break for our target audience, it would be a critical bugfix we'd need to make before the next release, and it wouldn't take 10 months, because we're not establishing a new behavior, we'd be making a bugfix to handle whatever the new schema is.

That would still mean we would willingly introduce a dependency on another application's internal storage here which is a bad practice.

xano’s picture

As much as I liked how 'lean' the previous generation of patches was, I do think we need Composer to make this work properly. There is no guarantee we will be able to used installed.json indefinitely, and several people pointed out to us, both at the sprints and in #composer on irc.freenode.net, that relying on build tools during runtime is a bad idea, both conceptually (they are build tools after all) and practically (read-only FSs).

On top of that, installed.json does not even contain all the package information we need. That means we cannot solve this issue without adding some information from other sources.

xano’s picture

We may also need to prevent modules with Composer dependencies from being installed on multi-site applications, as we cannot correctly prevent problems on those. @bojanz might be able to explain this in more detail as he is the one who made me aware of this in the first place.

Mixologic’s picture

On top of that, installed.json does not even contain all the package information we need. That means we cannot solve this issue without adding some information from other sources.

Can you please present a user story/use case where we need the information that is missing in the installed.json? In every scenario I can think of, if an end user, not using composer, gets a copy of drupal, and they download a module who's composer.json says it depends upon a component of drupal, they cannot have an unmet dependency.

All other alternative future build scenarios involve the end user using composer to assemble their build, and as such, will not have this problem. It's not clear to me how the 'missing info' in installed.json matters to the users we're trying to help.

relying on build tools during runtime is a bad idea

I agree, but in we're not relying on build tools during runtime in either patch, we're relying on build tool metadata artifacts. And as far as I can tell, all this does is re-create a different build tool metadata artifact will all of the same information that we already have, adds some information we dont actually need, all so that we can be shield ourselves from the perceived threat of schema changes.

And I would also agree that relying on somebody elses build tool metadata is a bad idea in the general sense, in this specific case the consequences of the very unlikely event that the schema would change are not significant enough to warrant this much additional dependencies and code. The worst case scenario would be that the schema changes, we somehow do not know that it changed (unless we have tests to alert us), we roll a full release of core thats incompatible with the schema of installed.json, an end user gets a copy of that, then they get a module with unmet composer deps, and their site breaks when they enable that module. A schema change would not affect existing 'non composer' users, nor would it affect users using composer to build their sites. It would only be a problem if we didnt fix core before a release went out.

No. We'd also need confirmation the schema is part of the public API and as such will not change.

Wouldn't that be implicit if they accepted this pull request? Cant we just ask them if "hey, you have a schema that has always been an array of packages since the beginning, what are the odds that its going to change? Do you currently have any plans or ideas that would affect this?"

xano’s picture

Status: Needs work » Needs review
StatusFileSize
new2.33 KB
new60.24 KB

Can you please present a user story/use case where we need the information that is missing in the installed.json? In every scenario I can think of, if an end user, not using composer, gets a copy of drupal, and they download a module who's composer.json says it depends upon a component of drupal, they cannot have an unmet dependency.

We want to encourage developers to be as specific with their dependencies as possible, especially since we are planning to provide subtree splits and allow smaller components to be retrieved. Modules may choose to depend on core modules. Modules may choose to depend on a package that merely requires drupal/core-plugin. In neither case is the dependency listed in Composer's installed.json after one has run composer install inside the drupal/drupal root.
In short: installed.json does not feature all packages available in the application, which means we will cause false negatives when relying solely on its contents.

I agree, but in we're not relying on build tools during runtime in either patch, we're relying on build tool metadata artifacts. And as far as I can tell, all this does is re-create a different build tool metadata artifact will all of the same information that we already have, adds some information we dont actually need, all so that we can be shield ourselves from the perceived threat of schema changes.

I stand corrected. We would indeed not require Composer, but merely one of its artifacts runtime. I discussed this with people and they were still wary of this.

And I would also agree that relying on somebody elses build tool metadata is a bad idea in the general sense, in this specific case the consequences of the very unlikely event that the schema would change are not significant enough to warrant this much additional dependencies and code. The worst case scenario would be that the schema changes, we somehow do not know that it changed (unless we have tests to alert us), we roll a full release of core thats incompatible with the schema of installed.json, an end user gets a copy of that, then they get a module with unmet composer deps, and their site breaks when they enable that module. A schema change would not affect existing 'non composer' users, nor would it affect users using composer to build their sites. It would only be a problem if we didnt fix core before a release went out.

I disagree and think that we should require ourselves to access other software's data through the APIs designed for that purpose. Also, I know I have time to work on this issue now. I do not know if I will have time to fix our mistakes in the future. Using Composer's API ensures that we won't have to risk this.

Wouldn't that be implicit if they accepted this pull request? Cant we just ask them if "hey, you have a schema that has always been an array of packages since the beginning, what are the odds that its going to change? Do you currently have any plans or ideas that would affect this?"

That would not be implicit, no. Software should always validate its own input, even if that input is not part of a public API but an internal artifact.

fabianx’s picture

Chiming in:

You both want the same, there is just a slight misunderstanding in terminology:

- Xano wants to write a drupal_installed.json
- Mixologic wants to use already existing installed.json

Both are in the end equivalent, however the drupal_installed.json will never break.

Every user that downloads a generated root package of Drupal will have the drupal_installed.json as well. The composer script will take care of that while the package is packaged by the drupal.org infrastructure. We know that packages work as e.g. the autoloder is generated in the same way and that works, too.

There is no dependency on composer here, just a replacement of installed.json with drupal_installed.json, but both are written at package install time.

In theory we should be able to take any of Mile23's scripts, rename installed.json with drupal-installed.json, adjust the schema (if not compatible), plug in Xano's script and everything will stiil work in the exact same way.

So similar are both solutions.

I am voting for the composer script, a drupal-installed.json will be handy for other things as well.

Mixologic’s picture

In short: installed.json does not feature all packages available in the application, which means we will cause false negatives when relying solely on its contents

All of the packages that it leaves out will always be part of the tarball that an end user, who is not using composer to build their site, will be using. They will never be able to get themselves into a state where they are trying to enable a module that has a dependency on core modules or components, and that dependency is not fulfilled. installed.json does lack information, but we honestly dont need any of it.

There is no dependency on composer here

If there wasn't, I wouldn't have an issue with this approach. But there is a dependency on composer. Look at the changes to composer.lock in the patch. We're embedding the entire composer application inside of drupal in order to fix a problem that has been broken for seven months, because we're concerned that it might someday break again.

If we do add composer and all of its dependencies, we're defacto adding a bunch of api's to drupal core, and some contrib developer will start using the symfony filesystem component instead of \Drupal\Core\File\FileSystem because its there. Im unsure whether or not that really matters, as a contrib dev could just add a require for any of those components regardless.

In any case, composer isnt all that huge (adds about 8Mb of extra vendor) so core goes from 221mb to 229mb after composer install.

mile23’s picture

@mixologic: If you look at the patch, it does add composer/stuff to composer.lock file, but not composer.json file. I'm willing to wager that's because @Xano is not being careful about his patches. :-)

@xano: That would still mean we would willingly introduce a dependency on another application's internal storage here which is a bad practice.

Hmmmmm...... Well in that light, let's review the patch:

+++ b/core/lib/Drupal/Core/Composer/Composer.php
@@ -246,4 +250,46 @@ protected static function deleteRecursive($path) {
+  public static function dumpInstalledPackages(Event $event) {
+    $composer = $event->getComposer();
+
+    // Get the packages Composer itself says have been installed.
+    $vendor_directory = $composer->getConfig()->get('vendor-dir');
+    $composer_installed_json_path = $vendor_directory . '/composer/installed.json';
+    $composer_installed_repository = new InstalledFilesystemRepository(new JsonFile($composer_installed_json_path));
+    $installed_packages = $composer_installed_repository->getPackages();

What if they move the file or change its name? We really must not introduce a dependency on another application's internal storage. :-)

Look: We have two solutions that make pretty much the same set of assumptions. One involves an extra layer of complexity that is just as fragile as the one without the extra layer of complexity. Therefore, don't do the complexity.

Mixologic’s picture

+++ b/core/composer.json
@@ -30,7 +30,9 @@
         "masterminds/html5": "~2.1",
         "symfony/psr-http-message-bridge": "v0.2",
         "zendframework/zend-diactoros": "~1.1",
+        "composer/composer": "~1.0",
         "composer/semver": "~1.0",
+        "justinrainbow/json-schema": "~1.0",
         "paragonie/random_compat": "~1.0"
     },

Im pretty sure that adds composer/composer to core/composer.json

What if they change the api in installed.json? wont we have to upgrade our version of composer.json? (make sure we add these deps to this list too: https://www.drupal.org/node/2400407)

xano’s picture

You both want the same, there is just a slight misunderstanding in terminology:

- Xano wants to write a drupal_installed.json
- Mixologic wants to use already existing installed.json

Both are in the end equivalent, however the drupal_installed.json will never break.

They are mostly equivalent, with the major difference that the latest versions of the patch extend the list of packages from installed.json with information about merged and replaced packages as well. I wouldn't go as far as to say we unconditionally support merged Composer files (merged through wikimedia/merge-plugin), but at least this works when people work with drupal/drupal, for instance after having downloaded a compressed Drupal core archive, and want to extend that with additional Composer dependencies for contrib. We cannot get this information runtime without knowing the location of the application's composer.json. The last versions of the patch avoid all this by simply aggregating all information build-time through Composer's public PHP APIs, with the notable exception of the hardcoded path to installed.json. Composer itself handles API access to this file through \Compser\Factory::addLocalRepository(), which is internal and the public methods calling this require CLI usage.

@mixologic: If you look at the patch, it does add composer/stuff to composer.lock file, but not composer.json file. I'm willing to wager that's because @Xano is not being careful about his patches. :-)

I'm willing to wager it's in ./core/composer.json because of the package merging we've been talking about for some time now.

diff --git a/core/composer.json b/core/composer.json
index 550c781..8d0e805 100644
--- a/core/composer.json
+++ b/core/composer.json
@@ -30,7 +30,9 @@
         "masterminds/html5": "~2.1",
         "symfony/psr-http-message-bridge": "v0.2",
         "zendframework/zend-diactoros": "~1.1",
+        "composer/composer": "~1.0",
         "composer/semver": "~1.0",
+        "justinrainbow/json-schema": "~1.0",
         "paragonie/random_compat": "~1.0"
     },
     "require-dev": {
mile23’s picture

I'm willing to wager it's in ./core/composer.json because of the package merging we've been talking about for some time now.

We don't need to require composer/composer to make your code work. When the script runs, it's in the context of the Composer runtime, so you'll be able to load the installed packages object.

So you can take that out of the patch.

That still leaves us with the question: Should we re-implement composer's behavior and create our own version of the installed.json file?

In one version of this scenario, we just rely on the installed.json file to be there and be a reasonably stable schema. We add a test to make sure we can read it in the patch in #209.

In the other version of this scenario, we add an install/update script to Composer which relies on installed.json being present, reads the installed packages from it, and then writes them out again to a second, Drupal-specific similar file called installed-packages.json. This is the patch in #226.

I'd say the latter counts as Drupalism, and we shouldn't do that. Other opinions differ. Who will solve this conundrum? :-)

Mixologic’s picture

Cool, well if we're not adding 8 more external libs to drupal itself, Im a lot less concerned about it. I dont think we need our own version of installed.json, but as Fabian mentioned in #227 It may be handy for other uses. This conversation between Jordi and Xano seems to leave it open to either as well: https://twitter.com/seldaek/status/748129921046814720 It sort validates both our positions "it hasn't changed in years really and there is little reason to change it" and "or read installed.json if needed but dumping specialized info will be more reliable and less overhead."

So Im +1.1 on #209 and +0.9 on #226

xano’s picture

We can't just read installed.json runtime as I explained in #231. The script approach solves that problem, with the exception of 'multi-step' replace links (installed package A replaces B replaces C; we currently do not list C).

We don't need to require composer/composer to make your code work. When the script runs, it's in the context of the Composer runtime, so you'll be able to load the installed packages object.

So you can take that out of the patch.

That makes total sense. Thanks!

mile23’s picture

I'm not sure what Fabian means by "Useful for other things as well" because those uses are unspecified and out of scope here. If we find that other systems could use this file then let's do it in a follow-up. I think it'd be better to make a simple solution now and iterate over real needs.

xano’s picture

We need the custom dump, as described in #231, #234, and earlier comments. Please read and comment on those before continuing to discuss reading installed.json runtime.

Mixologic’s picture

We need the custom dump

No, we dont, at least as far as I understand it.

The only thing the custom dump adds, is dependencies that *already ship with drupal*, therefore it is not ever possible for a contrib module to depend on a core module/component without having had that dependency fulfilled.

Am I missing something and an unmet dependency can somehow exist? Under what circumstances do we need that missing information?

xano’s picture

No, we dont, at least as far as I understand it.

The only thing the custom dump adds, is dependencies that *already ship with drupal*, therefore it is not ever possible for a contrib module to depend on a core module/component without having had that dependency fulfilled.

Am I missing something and an unmet dependency can somehow exist? Under what circumstances do we need that missing information?

Neither composer.lock nor installed.json store a list of all available packages on the system, because it takes some information from Packagist servers and replace links to resolve dependencies. While this allows Composer itself to conclude that in a drupal/drupal project a dependency on drupal/node is met, we do not have this information runtime. Try this with the older patches, it won't work. You'll get an error that says whatever module you are testing with has drupal/node as an unmet Composer-based dependency.

Steps to reproduce:

  1. Set up drupal/drupal:dev-8.2.x somewhere, patch it with https://www.drupal.org/files/issues/drupal_2494073_226.patch, run composer install and install the site..
  2. Put *any* module in ./modules. Go to its module root and run composer require drupal/node:*.
  3. Try to install the module. This should work.
  4. Uninstall the module.
  5. In \Drupal\Core\Composer\Composer::dumpInstalledPackages(), comment out the following line: $installed_packages_constraints[$link->getTarget()] = $link->getConstraint()->getPrettyString();.
  6. Run composer install in your site root to trigger the script again.
  7. Try installing the module. This should result in an error saying drupal/node:* is not available.

All this results from replaced packages. We're not the only framework using replace links, although we are the only one I have seen merging another composer.json containing replace links into its application-level composer.json. The last few patches solve this problem by collecting information about the root package, and by adding all packages that are replaced by installed packages.

I've asked for steps to reproduce several times now. If anyone disagrees with this post and still disagrees with the approach taken in the last few patches, posting steps to reproduce the problems you experience or ancitipate will help move this issue forward much more quickly.

xano’s picture

StatusFileSize
new1.42 KB
new59.92 KB

This patch addresses two concerns from #232: the hard dependency on installed.json and composer/composer. I do have to note that this code will likely break when Composer 2 comes out and people start using that for their CLI, or when Composer 1 changes its dependencies, because we now use composer/semver and justinrainbow/json-schema as composer/composer dependencies and not as our own. I suggest re-adding them, but since @Mile23 brought this up in #232 and nobody else commented on it, I assume most people are in favor of removing these requirements. I personally disagree and vote for adding them back in again so we can ensure stability. We currently have a dependency on composer/composer:~1.0, but we chose not to enforce it explicitly. It does not remove the dependency, just our safeguard.

cweagans’s picture

Embedding all of Composer is way overkill. What's wrong with just looking at `composer.lock` to see what packages are installed, and then determining if a module can be installed based on that?

mile23’s picture

@Xano: Neither composer.lock nor installed.json store a list of all available packages on the system, because it takes some information from Packagist servers and replace links to resolve dependencies. While this allows Composer itself to conclude that in a drupal/drupal project a dependency on drupal/node is met, we do not have this information runtime. Try this with the older patches, it won't work. You'll get an error that says whatever module you are testing with has drupal/node as an unmet Composer-based dependency.

It's out of scope for this issue. We'll probably find that we need to do this pretty soon, but let's have that conversation in-scope somewhere else.

@Xano: We need the custom dump, as described in #231, #234, and earlier comments. Please read and comment on those before continuing to discuss reading installed.json runtime.

We might need the custom dump in other contexts. When we do, we'll change it.

The problem outlined in #231 (which I addressed already, along with others) is that merge and replace aren't reflected in installed.json. That suggests to me that we should use composer instead of hacking around it, by working to get rid of the merge and replaces. We'll be getting rid of the merge when we finally do the subtree split. We'll be getting rid of at least some of the replaces when we subtree split for Component. That leaves core modules which we might also split out, or perhaps not, dunno.

As @mixologic pointed out earlier and again just a few comments ago, this issue is for people who didn't use Composer to install their Drupal or their contrib, to inform them that they need to. installed.json is already there, populated with all we need to know in order to tell them.

If a module depends on drupal/node in composer.json but not in .info.yml, then that's a bug in the module, because there is currently no specified behavior for contrib extensions with composer.json files. Also, this is not the issue where we should define it. This issue only makes a best-faith effort to tell the user that they need to learn some Composer.

Now: Will we go through this again? It's true that we *might* need to store our own database of installed packages somewhere. We don't need it here, and we shouldn't implement it here.

The patch in #209 still reflects this.

mile23’s picture

@cweagans: What's wrong with just looking at `composer.lock` to see what packages are installed, and then determining if a module can be installed based on that?

composer.lock doesn't tell you what's installed, only what *will be* installed. So you look at /vendor/composer/installed.json instead.

100+ comments on this thread are a conversation about whether to just read the file or to use a script during Composer install time to replicate a special Drupal-only version of the same information, because the schema might change at some point.

xano’s picture

Now: Will we go through this again? It's true that we *might* need to store our own database of installed packages somewhere. We don't need it here, and we shouldn't implement it here.

@Mile23: If you haven't followed my steps to reproduce the problem with your solution as described in #238, then I kindly request you do not post further comments in this issue that criticize the current approach; you keep repeating more or less the same statements without proof. If you have any reasons to believe the last patch does not sufficiently solve the problem, I am looking forward to your next comment including steps to reproduce that behavior, or even a test case.

xano’s picture

Let's turn this around a bit:

  1. Every extension in a Drupal code base MAY be a Composer package.
  2. Any extension MAY contain zero or more other extensions, which MAY be Composer packages too. If they are, the project's Composer file should contain replace links for these packages.
  3. If an extension has Composer-based dependencies, it MUST have been built by Composer prior to its installation in Drupal.
  4. When installing a module in Drupal, we do not have to check its requirements, but can simply check whether the package itself exists in the code base Composer built.

Does this make sense?

@Mile23 does have a point in #244 where he suggests we may need Composer after all. I'm trying to get some feedback from the people behind Composer to see if our approach is in any way reliable. If it's not, using Composer runtime might be our only option.

Mixologic’s picture

I finally was able to understand what the problem would be with the 'just look at installed.json' approach, namely that if a contrib project has a require on anything that doesnt show up in installed.json (say, a drupal/core-plugin) then the initial approach will produce a false negative, telling that user they do not have all their requirements installed, when, in fact, they do, even if they are a composer user.

Additionally, since composer has the 'replace' key, The package name that a contrib module may require, may be satisfied by a packagename that is listed in installed.json, but does not match what is in composer.json. So if I run into a bug with commerceguys/tax, and fork it to mixologic/tax, add a replace to mixologic/tax composer.json, add require mixologic/tax in my root composer.json, and then try and install commerce module, it should know that the commerceguys/tax requirement is fullfilled.

So Im now seeing that if we just parse installed.json and compare it to what our modules may have in them, we may end up throwing false negatives, especially for the composer using folks, so it seems like we'd have to handle all the replaces logic too, which starts to sound like we're rebuilding composers resolver, i.e. Im thinking the latter patch, while more complicated, would be a better direction in that case.

as an aside:

If a module depends on drupal/node in composer.json but not in .info.yml, then that's a bug in the module, because there is currently no specified behavior for contrib extensions with composer.json files

Thats actually desired behavior for some modules. If you look at the composer.json for commerce (http://cgit.drupalcode.org/commerce/tree/composer.json?h=8.x-2.x) it has a variety of requires for drupal modules that are dependencies of its submodules. However, those submodule dependencies are not actually in commerce's info.yml: http://cgit.drupalcode.org/commerce/tree/commerce.info.yml?h=8.x-2.x

That way, if a user runs "composer require drupal/commerce" they end up downloading all of the dependencies of the submodules, whether they get used/enabled or not, so that the end user gets the whole suite, and its ready to use without having to do additional composer require drupal/commerce_order.

mile23’s picture

Status: Needs work » Needs review
StatusFileSize
new33.48 KB
new8.84 KB

@Mixologic: I finally was able to understand what the problem would be with the 'just look at installed.json' approach, namely that if a contrib project has a require on anything that doesnt show up in installed.json (say, a drupal/core-plugin) then the initial approach will produce a false negative, telling that user they do not have all their requirements installed, when, in fact, they do, even if they are a composer user.

The case of drupal/core-plugin gets magically fixed when we do a subtree split of Component.

The case of core modules is an easy special case to filter against when we determine whether the dependency is met.

@Mixologic: That way, if a user runs "composer require drupal/commerce" they end up downloading all of the dependencies of the submodules, whether they get used/enabled or not, so that the end user gets the whole suite, and its ready to use without having to do additional composer require drupal/commerce_order.

Well, neither of the files you link to require commerce_order, but yah.

If somevendor/lib is a requirement of a number of submodules then declaring it as a requirement in the top-level composer.json isn't such a big deal. If the top-level module doesn't have the kind of dependency on the submodule that would put it in the info.yml file, then it's a lie if it's in composer.json. The user already has it because they're all in the same project, right?

But here's a solution. The tests will fail because it's late. :-) It's based off #209.

When we read in the module's composer.json file, we also read in the .info.yml file.

We ignore drupal/whatever requirements in composer.json, if the whatever part is in the .info.yml file under 'dependencies.'

This yields a dependency missing error if the drupal/* dependency is declared in composer.json but not in info.yml (and it's not installed)

We workaround the fact that core modules are replaced by ignoring them if they're in the info.yml file (where they should be, and where hook_requirements() will see them). The same for drupal/core-components, which are currently replaced. The downside is version constraints for the components will be ignored, but until we have the subtree split this won't be a problem.

Whether this is more or less fragile than keeping our own copy of installed.json is debatable.

mile23’s picture

StatusFileSize
new36.6 KB
new8.51 KB

Added some more tests.

To recap:

This patch says that if a composer-based dependency in the namespace drupal/ also exists in the info.yml file, then we won't check whether it's in installed.json.

This is useful because of a few basic assumptions:

  1. drupal/core-components will always be available in core, even when they are subtree splits.
  2. drupal/coreextensions will also always be available as replace, but not present in installed.json.
  3. Core extensions which are present in composer.json should also be declared in info.yml, because we don't currently have a defined behavior for Composer-based core extensions.

What will happen if this patch is committed:

  1. Contrib modules which declare core extensions in their composer.json file but not info.yml will become uninstallable. I view this as showing them that their info.yml file needs an update, but obviously others might disagree.
  2. After we do the subtree split for Component, contrib can start declaring dependency on drupal/core-whatever in composer.json (since we can't depend on a non-extension library in info.yml). If they do this before the subtree split, their extension will be uninstallable.

Note also that eventually we will have #2597778: Install composer dependencies for contrib projects before running tests so extensions which use composer-based dependencies can be tested with drupalci. Once that's in place, we can then trust hook_requirements() to fail tests if dependencies don't resolve, or are unavailable, or if contrib declares a core extension in composer.json but not info.yml.

fabianx’s picture

I like #252 a lot and consider 1. and 2. features and a good thing rather than limitations.

xano’s picture

Status: Needs review » Needs work

This is wrong, as it unifies code base building and extension installation, which are two completely different concepts. The build must be complete and successful before installation can take place, but other than that there is no relationship between the two phases.

After we do the subtree split for Component, contrib can start declaring dependency on drupal/core-whatever in composer.json (since we can't depend on a non-extension library in info.yml). If they do this before the subtree split, their extension will be uninstallable.

This is a bug: it purposefully ignores that Composer itself is perfectly capable to successfully build code bases this way and adds a limitation on top of that, pending the introduction of a possible future core split. Also, as mentioned before in #238 and #248 (and plenty of earlier comments), core is not the only project that provides replace links in its Composer file, so we'll run into the same problem with other packages (depending on symfony/http-foundation while symfony/symfony is installed will cause false negatives with the patch in #252).

mile23’s picture

Status: Needs work » Needs review
StatusFileSize
new37.32 KB
new3.96 KB

This is a bug: it purposefully ignores that Composer itself is perfectly capable to successfully build code bases this way

Well no, it's the premise of why we're having a conversation about which ugly hack we need because Composer won't tell us what's been replaced. :-)

Once the subtree split happens for the components, then Composer will be able to tell us that drupal/core-whatever has been installed.

We know that for core, we already have drupal/core-whatever because otherwise we're not checking for requirements in the first place, so we can ignore it, until we have the subtree split, at which point we'll change it.

#2513388: Create (and maintain) a subtree split of each Drupal Component

So there we go. Added in the patch here.

core is not the only project that provides replace links in its Composer file, so we'll run into the same problem with other packages (depending on symfony/http-foundation while symfony/symfony is installed will cause false negatives with the patch in #252).

a) Core doesn't require symfony/symfony, and if we're depending on drupal/core, we have an explicit dependency on symfony components instead of symfony/symfony, and if you're making an extension that depends on symfony/symfony you're doing it wrong anyway.

But none of that matters because:

b) The real culprit in this case is the lack of a subtree split for core, our reliance on composer-merge-plugin, and our replace directives in core/composer.json. See #189. That will be fixed when we finally do the subtree split, and we shouldn't try to solve it here.

So: Rather than add another install script and a separate database, let's just read the installer.json file, make a best faith effort, and light a fire under the subtree split process which is where these problems will really be solved and we can, as you say, let Composer do it's Composer-y thing.

#2352091: Create (and maintain) a subtree split of Drupal core

This patch:

Ignores drupal/core-component packages under the premise that they're already installed in core.

Updates tests.

xano’s picture

Subtree splits have nothing to do with this issue. Yes, we want one. Yes, they make things easier. No, publishing a subtree split of core will not remove this requirement from this issue, because any project can do these things. This issue is not solely about supporting core's oddities. This issue is about supporting Composer-based package management in its entirety, and all its oddities.

@Mile23: Would you be able to take the time to address all feedback on your patches and not just a few items? Repeatedly it's been pointed out that we cannot unify modules' composer.json and *.info.yml files, and that replace links matter, yet you have not addressed these issues in your patches a single time. #238 requested contributors to follow those steps to reproduce problems with the patches that were posted, to which you also did not reply.

mile23’s picture

Subtree splits have nothing to do with this issue.

Subtree splits make the replace issues go away for core, and COMPOSER WILL INSTALL ALL ITEMS EITHER MERGED OR REPLACED FROM CORE. Which is why half the problems you want to solve with a database will go away.

Repeatedly it's been pointed out that we cannot unify modules' composer.json and *.info.yml files

In an ideal world, we wouldn't have to care. However, Drupal 8 does not have a defined behavior for extension dependencies defined in composer.json files. I tried to get one defined before the 8.0.0 release and nearly left the project over it, but no one else seemed to care. Now I have to work with what the community decided, so here you are, folks.

and that replace links matter

Q: Which replace links? A: The ones that contrib has already defined as a workaround to the problem we seem unable to fix here.

#238 requested contributors to follow those steps to reproduce problems with the patches that were posted, to which you also did not reply.

The repro steps in #238 do the following:

  • Patch core to create a database of installs and replaces.
  • Add a contrib module.
  • Make it so that the patch can't create the database by commenting out a line.
  • Try to install a core module.
  • Fail.
  • Because you broke your own patch.

I'm not sure exactly what it's supposed to prove.

#238 also says this:

All this results from replaced packages.

It uses the example of drupal/node, which is marked as replace in core/composer.json. The reason it's marked as 'replace' is because we currently have no method of specifying a core module in composer.json. We have to use info.yml for that, which is why we have to care about info.yml. And, in fact, is why subsequent patches I've offered have addressed the issue of including info.yml, despite your protestations that they haven't.

But rather than cycling through that conversation again, let's try this:

  • Apply the patch in #255.
  • Try to do things you normally do.
  • Tell me what breaks.

Then we'll have data.

xano’s picture

StatusFileSize
new26.84 KB
new54.35 KB

This patch builds on #239.

After discussing our situation with several active contributors in #composer on IRC, we have come to the conclusion that checking extensions' dependencies is not the best idea. It's hard to do, and all our attempts so far neglected to use package links other than replace, such as conflict, let alone anything else in extensions' Composer files such as plugins.

Instead, we decided to try and enforce modules to be installed through Composer if they ship with a Composer file. We do not actually do anything with the contents of these files, so suites of modules are responsible for handling their submodules themselves, which is done by adding replace links and the submodules' dependencies to the package's primary Composer file and have that installed.

mile23’s picture

After discussing our situation with several active contributors in #composer on IRC, we have come to the conclusion that checking extensions' dependencies is not the best idea. It's hard to do, and all our attempts so far neglected to use package links other than replace, such as conflict, let alone anything else in extensions' Composer files such as plugins.

The scope of this issue is that we want to inform someone who *didn't* use Composer to install their Drupal that they need to in order for an extension to function. That means that if you take a tarball from d.o and put it on a server, you haven't even begun to deal with replace or conflict or anything else. The goal is to tell a user that they need to run Composer. The goal is not to make a dependency manager.

Did the #composer IRC people understand this?

Instead, we decided to try and enforce modules to be installed through Composer if they ship with a Composer file.

What if the extension has a composer.json file that only declares a package name, and not any dependencies? This is the opposite problem of too many replaces. :-)

suites of modules are responsible for handling their submodules themselves, which is done by adding replace links and the submodules' dependencies to the package's primary Composer file and have that installed.

I talked with @Mixologic about this a few days ago... The composer facade of d.o allows you to specify a sub-module which is inside a d.o project. The whole project is added in this circumstance, though only the specific dependency is added to the composer.json file.

So: #2758737: Add a packages.drupal.org to root composer.json is relevant to us here.

As far as reviewing the patch: It still generates a parallel installed.json file even though the comment says it changes strategy to not even compare what's installed.

Mixologic’s picture

I do believe that Xano is onto something regarding what we really need to check, which is that we dont need to know all of an extensions dependencies, we only need to know if an extension that has external dependencies was installed via composer. If it was not, we warn the user.

The situation is: A user is trying to install an extention, where:

  • The extension has a composer.json file associated with it
  • The extension's composer.json has evidence of external dependencies (has a require on something other than drupal/*)
  • The extension name does not show up in installed.json

Then we can assume that, regardless of whether or not that particular extension contains any replace or conflict information, that composer has not been run to retrieve it's dependencies, and that it was installed using some other method than composer.

We do not have to concern ourselves if a module has a require on anything drupal/* because they will be one of the following:

  1. drupal/core - which will always be satisfied.
  2. drupal/core-[component] which will always be satisfied.
  3. drupal/[project/module] which should be specified as a dependency in info.yml. I cannot think of a case where an extension requires a project to be downloaded, but does not require that project in order to install that module.

When we have library types on drupal.org, that are not subtree splits of core itself, we may need to have a namespace for those (drupal/lib-libraryname) so we do not count them as a filled dependency. (could also be drupal-lib/libraryname too. )

Given that the chances of a false positive are reduced to pretty much zero in this scenario, Im back to feeling that utilizing installed.json is totally fine, and despite there being a miniscule risk that the composer folks would change the format/structure/api of it, even if that happens, theres still no risk to the "non composering" end user as they would never end up with a release of drupal that has mismatched expectations as to the format of the installed.json file. And if a non-composering user decided to become a composering user and use a new version of composer with an outdated version of core and ends up in a mismatched state, we could detect that situation as well and code defensively for a situation that will probably never happen.

Lest anybody think that "We're using other peoples unpublished API's" is such a bad thing, the entire composer façade is essentially built using "unpublished, yet probably not going to change API's that Satis, Toran Proxy, and Packagist all currently assume". Because its a defacto standard that we feel safe enough implementing.

xano’s picture

Status: Needs work » Needs review
StatusFileSize
new38.9 KB
new48.12 KB

This patch builds upon the latest approach as suggested in #258, but adds test coverage for submodules, and fixes some bugs related to them.

I tried to structure and document ExtensionDependencyChecker::requiresInstallation() well enough to be easy to understand. In essence, we consider an extension's directory and all its parent directories, up until and including the app root's subdirectory it is in as directories of packages the extension could belong to. We then find all actual package directories by checking for Composer files. In the last part we check if any of the packages in these directories have been installed. All this works based on the idea that if an extension is part of a Composer package higher up the directory tree, whether the extension is a Composer package itself or not, the package higher up the tree owns the extension-to-be-installed and is therefore responsible for requiring the necessary dependencies. This in turn is based on the restriction that Composer cannot install one package into another. This supports submodules, whether they have their own Composer files or not (a potential consequence of subtree splits for larger contributed projects; imagine Drupal Commerce).

This approach avoids dealing with version constraints, package links, or platform requirements, effectively delegating this to Composer (so we don't reinvent the wheel) by simply checking whether a module must be added to the code base through Composer or not. This leaves maintainers with the most flexibility in developing their Composer files, and keeps our dependency on Composer's internals as limited as possible. This results in less and cleaner code, and higher maintainability.

If anyone feels this does not adequately address our needs, I urge you to provide a follow-up patch in which you only update the tests to include missing use cases. That way we can see the code fail, and prevent long discussions about the approach.

xano’s picture

StatusFileSize
new1.95 KB
new47.51 KB

Removed some debugging code, and redundant production code (although I do expect HookRequirementsTest to fail as I didn't have time to look at it anymore).

xano’s picture

Status: Needs work » Needs review
StatusFileSize
new5.27 KB
new47.82 KB

This should leave a single test failure in HookRequirementsTest, because we're adding coverage for installing modules that are part of Composer packages, but we cannot add that package to the code base during a test, since we cannot run composer install. Any ideas on how to include coverage in a way that works? The only way I can think of right now is to fake installed-modules.json.

Status: Needs review » Needs work

The last submitted patch, 266: drupal_2494073_266.patch, failed testing.

mile23’s picture

#261:

I do believe that Xano is onto something regarding what we really need to check, which is that we dont need to know all of an extensions dependencies, we only need to know if an extension that has external dependencies was installed via composer. If it was not, we warn the user.

The situation is: A user is trying to install an extention, where:

  • The extension has a composer.json file associated with it
  • The extension's composer.json has evidence of external dependencies (has a require on something other than drupal/*)
  • The extension name does not show up in installed.json

Then we can assume that, regardless of whether or not that particular extension contains any replace or conflict information, that composer has not been run to retrieve it's dependencies, and that it was installed using some other method than composer.

Totally OK with this idea.

Still think we don't need to roll our own list of installed extensions to accomplish this. I think it'd be great to have such a thing for other purposes, but it's out of scope here. Find a use case for it, make an issue, we'll review it there, and even postpone this issue for it if we can.

#266

This should leave a single test failure in HookRequirementsTest, because we're adding coverage for installing modules that are part of Composer packages, but we cannot add that package to the code base during a test, since we cannot run composer install.

drupalci does in fact run composer install. I'm pretty sure it's after patching, as well.

  1. +++ b/core/composer.json
    @@ -30,8 +30,8 @@
    -        "composer/semver": "~1.0",
    

    We still need SemVer for preAutoloadDump. We have it in both core/composer.json and DRUPAL_ROOT/composer.json, for reasons. I guess. ::shrug::

  2. +++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyChecker.php
    @@ -0,0 +1,164 @@
    +use JsonSchema\RefResolver;
    +use JsonSchema\Uri\UriRetriever;
    +use JsonSchema\Validator;
    

    We can use these to validate installed.json against a best-guess schema definition so we'll know if it changes. Then file an upstream issue to add this validation to composer.

xano’s picture

Status: Needs work » Needs review
StatusFileSize
new15.66 KB
new44.1 KB

This should fix the remaining test failures. Note that I moved the coverage for ModuleInstaller to ModuleInstallerTest, and that I removed the failing coverage for installing a module that's a Composer package and that was already added to the code base using Composer, because I do not know how to mock this during a test.

drupalci does in fact run composer install. I'm pretty sure it's after patching, as well.

Before tests, but not during, which is what I said in #266.

Still think we don't need to roll our own list of installed extensions to accomplish this. I think it'd be great to have such a thing for other purposes, but it's out of scope here. Find a use case for it, make an issue, we'll review it there, and even postpone this issue for it if we can.

We have a use case: this very patch. It will not work without it, as we need the installation paths of all Composer packages that are Drupal modules.

We still need SemVer for preAutoloadDump. We have it in both core/composer.json and DRUPAL_ROOT/composer.json, for reasons. I guess. ::shrug::

Good find! I put this requirement back in.

We can use these to validate installed.json against a best-guess schema definition so we'll know if it changes. Then file an upstream issue to add this validation to composer.

Composer has nothing to do with this file. Its our own, and we validate it to prevent confusing PHP errors, just like Composer validates its own JSON files. It's input to PHP code, after all.

xano’s picture

Status: Needs work » Needs review
StatusFileSize
new1.64 KB
new44.14 KB
mile23’s picture

Before tests, but not during, which is what I said in #266.

That's what unit tests are for. :-)

You can access the Composer class and unit test it. It might need some refactoring so you can inject a mock filesystem and compare the output of the file writer.

Review of #269

  1. +++ b/core/composer/installed-modules.json
    --- /dev/null
    +++ b/core/composer/installed-modules.schema.json
    

    If we're embarking into a new world of jsonschema strictness, let's not limit it to composer. We should maybe have a core/jsonschema/ directory or similar, since this shouldn't really be limited to composer.

    Also, call it installed-extensions.json, pleeze. :-)

  2. +++ b/core/composer/installed-modules.schema.json
    @@ -0,0 +1,11 @@
    +  "description": "Drupal modules installed through Composer. Properties are package names and values are their installation paths, relative to Drupal's app root.",
    +  "type": "object",
    +  "items": [
    +    {
    +      "type": "string",
    +      "required": true,
    +      "pattern": "^[^/]{0}.+?\/$"
    +    }
    +  ]
    

    I admit I'm a newb on jsonschema, but this looks pretty not-specific. If we're not trusting installed.json because it's ill-defined, this needs quite a bit more work to actually specify what the description says.

  3. +++ b/core/lib/Drupal/Core/Composer/Composer.php
    @@ -246,4 +247,34 @@ protected static function deleteRecursive($path) {
    +      return $package->getType() === 'drupal-module';
    

    Also need drupal-profile and drupal-theme, right?

  4. +++ b/core/lib/Drupal/Core/Composer/Composer.php
    @@ -246,4 +247,34 @@ protected static function deleteRecursive($path) {
    +      // Drupal modules must be placed in one of several subdirectories of the
    +      // app root.
    +      if (strpos($absolute_installation_path, $app_root) !== 0) {
    +        throw new ComposerIntegrationException(sprintf('Composer package "%s" is a Drupal module and must be installed in a subdirectory of %s, but was installed in %s. You may need to use the "composer/installers" package (https://packagist.org/packages/composer/installers).', $package->getName(), $app_root, $absolute_installation_path));
    +      }
    

    Nice to check, but maybe out of scope here. drupal/drupal requires composer/installers already, but yah.

  5. +++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyChecker.php
    @@ -0,0 +1,164 @@
    +    // Find all directories of potential Composer packages this extension can
    +    // belong to.
    ...
    +    // Filter out any directories that are no subdirectories of the app root and
    +    // therefore cannot contain Composer packages.
    ...
    +    // Filter out any directories without Composer files.
    ...
    +    // Filter out drupal/core, because drupal/drupal uses
    +    // wikimedia/composer-merge-plugin.
    

    So we get all subdirectories for this extension, all the way to /. Then we compare all those directories to app_root in order to filter out the ones which are above app_root. Then we filter out all the ones which don't have composer.json files in them (because not all composer.json files have a corresponding .info.yml file). Then we exclude all directories under core/. Which, btw, leaves us with vendor/.

    At times like this, I wish we had symfony/finder, and really don't understand why we don't.

    Anyway, this doesn't follow the heuristic of #261, and it also wastes the effort of keeping the paths of installed extensions in a file. If the premise is that we already know whether we installed this module in installed-modules.json, then we can just check whether the module has composer.json and then ask installed-modules.json whether it's been installed.

    If installed-modules.json doesn't store the information we need, then why are we generating it in the first place?

  6. +++ b/core/tests/Drupal/KernelTests/Core/Extension/ModuleInstallerTest.php
    @@ -44,4 +44,16 @@ public function testRouteRebuild() {
    +   * @expectedException \Drupal\Core\Extension\ExtensionComposerRequirementsException
    

    Convert this to setExpectedException(), so we can use ::class.

  7. +++ b/core/tests/Drupal/KernelTests/Core/Extension/ModuleInstallerTest.php
    @@ -44,4 +44,16 @@ public function testRouteRebuild() {
    +  public function testInstallWithModuleWithoutRequiredComposerInstallation() {
    +    $module_name = 'composer_uninstallable';
    

    Still need a test case for composer_installable, unless I missed it elsewhere.

xano’s picture

Nice to check, but maybe out of scope here. drupal/drupal requires composer/installers already, but yah.

None of my Drupal 8 projects use drupal/drupal, and there are plenty of others that don't use it either. It's a discardable front controller only, and most projects seem to use drupal-composer/drupal-scaffold instead. This issue is about drupal/core, but the latest patches contain a small workaround to support drupal/drupal's (or any other front controller package's) somewhat awkward use of merging drupal/core into itself.

So we get all subdirectories for this extension, all the way to /. Then we compare all those directories to app_root in order to filter out the ones which are above app_root. Then we filter out all the ones which don't have composer.json files in them (because not all composer.json files have a corresponding .info.yml file). Then we exclude all directories under core/. Which, btw, leaves us with vendor/.

*.info.yml files have nothing to do with this issue, as mentioned dozens of times before. We're checking build-level requirements, and not the integration-level requirements for installing/enabling modules within Drupal. As we've discussed before, these are two separate steps in getting a module to run within Drupal.
Edit: the vendor directory is not relevant, because it should never contain Drupal extensions, and even if it does, Drupal cannot handle them (enable them, use them as other modules' dependencies in their *.info.yml files, or make their assets web accessible).

Still need a test case for composer_installable, unless I missed it elsewhere.

As mentioned in #266 I couldn't find a way to do this at the time. If you do know the solution, please provide a patch.

Anyway, this doesn't follow the heuristic of #261, and it also wastes the effort of keeping the paths of installed extensions in a file. If the premise is that we already know whether we installed this module in installed-modules.json, then we can just check whether the module has composer.json and then ask installed-modules.json whether it's been installed.

#261 is unfortunately not entirely correct about the suggested approach. We do not analyze the contents of modules' Composer files. We merely take the existence of these files as evidence modules are Composer packages and must be added to the build through Composer. The reasons for this were outlined in #262.

If installed-modules.json doesn't store the information we need, then why are we generating it in the first place?

It contains exactly what we need. What part of the patch or explanation accidentally gave you the impression this was not the case?

@Mile23: We appreciate your feedback, and have incorporated a lot of it in the patches. Most of what you say has already been argued against in the past, or is a repetition of what you said earlier, to which we replied with requests to provide patches that demonstrate the problems you see. If you think code is redundant, file a patch that removes it to prove it works without the redundant code. If you think a use case is unsupported, file a patch that adds test coverage for that use case so we can watch it fail. That ensures we can incorporate your feedback much more quickly.

fabianx’s picture

Assigned: Unassigned » alexpott
Issue tags: +Needs framework manager review

As there are good argument pro and contra various solutions, I suggest we push this for core committer review and in this special case framework manager review to ensure the direction chosen is approved.

Tagging appropriately.

If the participants in this issue want, it might be a good idea to update the IS in advance with pro and con of each of the discussed approaches.

mile23’s picture

*.info.yml files have nothing to do with this issue, as mentioned dozens of times before. We're checking build-level requirements, and not the integration-level requirements for installing/enabling modules within Drupal. As we've discussed before, these are two separate steps in getting a module to run within Drupal.

Yes. That is why I mentioned it: To demonstrate that I understand what this code is doing.

Edit: the vendor directory is not relevant, because it should never contain Drupal extensions, and even if it does, Drupal cannot handle them (enable them, use them as other modules' dependencies in their *.info.yml files, or make their assets web accessible).

...which is why we should not search it.

Still need a test case for composer_installable, unless I missed it elsewhere.

As mentioned in #266 I couldn't find a way to do this at the time. If you do know the solution, please provide a patch.

+++ b/core/tests/Drupal/KernelTests/Core/Extension/ModuleInstallerTest.php
@@ -44,4 +44,16 @@ public function testRouteRebuild() {
+  /**
+   * @covers ::install
+   *
+   * @expectedException \Drupal\Core\Extension\ExtensionComposerRequirementsException
+   */
+  public function testInstallWithModuleWithoutRequiredComposerInstallation() {
+    $module_name = 'composer_uninstallable';
+    /** @var \Drupal\Core\Extension\ModuleInstallerInterface $installer */
+    $installer = $this->container->get('module_installer');
+    $installer->install([$module_name], TRUE);
+  }

Add another test method very similar to this one which enables composer_installable and then uses the module service to check that it was installed.

If you think code is redundant, file a patch that removes it to prove it works without the redundant code.

I have been submitting the patch you're asking for here, starting with #77 (November 2015), most recent being #255. We've been working on this since before 8.x-beta, btw.

The situation here is that we solved this problem, and it should have been committed months ago, and then subsequent issues should have been iterations and follow-ups. Then we would have a process based on demonstrated needs.

Adding the complexity of keeping a spare database around for the one use-case of people who downloaded the tarball is a bit of overkill. You ask for tests to demonstrate the non-need for this database, which is, as I point out, the fact that it all worked before you added it.

I'm totally willing to be convinced that we need to create our own little json database and associated complexity if it's actually needed. I still don't think it is. But even with that in mind, I reviewed the implementation of the strategy in #272.1 and .2 which still need to be addressed.

Please don't act as if this is a technical question I have yet to answer, or as if I am not participating properly in this process.

In the near future, we will perform the subtree split, many of the replace problems will go away, Drupal will get more and more like a real Composer app, and we'll iterate into things we actually need.

We should use the heuristic @Mixologic outlines in #261.

If I write a patch to do that, you'll ignore it, so I am reviewing your patch.

Go for it.

xano’s picture

StatusFileSize
new6.22 KB
new44.49 KB

If we're embarking into a new world of jsonschema strictness, let's not limit it to composer. We should maybe have a core/jsonschema/ directory or similar, since this shouldn't really be limited to composer.

That's not a bad idea. Where should we store the generated JSON file?

Also, call it installed-extensions.json, pleeze. :-)

Done.

Convert this to setExpectedException(), so we can use ::class.

Done. Maintainability++.

Also need drupal-profile and drupal-theme, right?

Done, but because this issue is about modules and supporting profiles and themes as well would require us to work on other subsystems, I suggest we delay the implementation of this for those other extensions to follow-up issues.

As mentioned in #266 I couldn't find a way to do this at the time. If you do know the solution, please provide a patch.
+++ b/core/tests/Drupal/KernelTests/Core/Extension/ModuleInstallerTest.php
@@ -44,4 +44,16 @@ public function testRouteRebuild() {
+  /**
+   * @covers ::install
+   *
+   * @expectedException \Drupal\Core\Extension\ExtensionComposerRequirementsException
+   */
+  public function testInstallWithModuleWithoutRequiredComposerInstallation() {
+    $module_name = 'composer_uninstallable';
+    /** @var \Drupal\Core\Extension\ModuleInstallerInterface $installer */
+    $installer = $this->container->get('module_installer');
+    $installer->install([$module_name], TRUE);
+  }

Add another test method very similar to this one which enables composer_installable and then uses the module service to check that it was installed.

As far as I see that will not work, because the module will not be installed through Composer, and therefore will not appear in our dump of installation paths. If I am wrong, please file a patch, because I do not understand your suggested solution for testing this.

xano’s picture

Status: Needs work » Needs review
StatusFileSize
new1.46 KB
new44.33 KB
mile23’s picture

Re: composer_installable test: As far as I see that will not work, because the module will not be installed through Composer, and therefore will not appear in our dump of installation paths.

That is kind of the point: The dependency is met (it's psr/log, and you can see it in vendor/composer/installed.json), but the module won't be installed through Composer.

So in that circumstance, the user should be able to enable the module. The patch in #255 accomplishes this.

This issue is to warn people who don't use Composer that their module won't install. composer_installable *will* install, because its dependencies are met.

To make the test pass in your scenario, we have to add the installed-extensions.json file in the patch. We can't do that, however, because we don't know which profile the user will pick on installation, and thus which extensions are actually installed.

xano’s picture

Issue summary: View changes
Status: Needs work » Needs review
StatusFileSize
new607 bytes
new44.59 KB

There is another reason why we cannot use any other approach than the one outlined in #258: it does not allow dependency collisions, while our earlier approaches did. By not requiring extensions that are Composer packages to be built/installed through Composer, Composer cannot make sure these packages and their dependencies do not conflict with packages that are part of the Composer build.

Mixologic’s picture

Can you clarify what a dependency collision is with an example? Its not clear to me what you mean by that.

Additionally, what is

An extention that is a composer package

? We either have modules/themes/install profiles(extensions), or we have composer packages (libraries).

xano’s picture

Can you clarify what a dependency collision is with an example? Its not clear to me what you mean by that.

A module that is not built/installed through Composer may require symfony/event-dispatcher:~2.8, and our previous approaches resolved this, but the requirement was never added to Composer's dependency tree, which means anyone could install 3.0 afterwards without so much as a warning from Composer.

? We either have modules/themes/install profiles(extensions), or we have composer packages (libraries).

Composer packages are packages with a Composer file. Drupal extensions are first and foremost Drupal extensions, but can also optionally expose themselves as Composer packages too. If they do, those build instructions should be taken into account.

Mixologic’s picture

our previous approaches resolved this, but the requirement was never added to Composer's dependency tree

I was under the impression that our previous requirement resolved this by warning the user that they cannot install the module because there are unmet composer dependencies, and the only way to meet those dependencies is to use composer. Therefore the requirement would have to be added to the dependency tree, ergo I dont see how it would be possible to install 3.0 without a composer warning.

Drupal extensions cannot optionally expose themselves as Composer packages. They are all already exposed as composer packages at the façade. The only thing they can do is provide their own composer.json, which *may* express additional external dependencies.

xano’s picture

I was under the impression that our previous requirement resolved this by warning the user that they cannot install the module because there are unmet composer dependencies, and the only way to meet those dependencies is to use composer. Therefore the requirement would have to be added to the dependency tree, ergo I dont see how it would be possible to install 3.0 without a composer warning.

You are describing exactly why that is wrong: previous approaches checked whether modules' dependencies were met. They did that only on module installation, and never on Composer events, or runtime. This, and the fact Composer was always unaware of these modules' requirements, allowed code bases to be invalidated after modules were enabled in Drupal. The final approach solves this by making sure modules are part of Composer code base builds entirely, giving Composer access to the entire dependency tree, allowing it to take extensions' requirements and other package links into account whenever it needs to make changes to the code base.

Drupal extensions cannot optionally expose themselves as Composer packages. They are all already exposed as composer packages at the façade. The only thing they can do is provide their own composer.json, which *may* express additional external dependencies.

Yes, they can. A Composer package is a package that ships with a Composer file. The drupal.org façade happens to work around this, but that does not mean developers do not want to add Composer files explicitly, even if just to install those packages through local repositories, for instance.
Also, Composer files may do many more things than just require package dependencies: they allow platform requirements, package links such as conflicts and replacements, and class autoloading. The final approach enables extensions to leverage all these Composer features without explicitly supporting them in Drupal.

mile23’s picture

A module that is not built/installed through Composer may require symfony/event-dispatcher:~2.8, and our previous approaches resolved this, but the requirement was never added to Composer's dependency tree, which means anyone could install 3.0 afterwards without so much as a warning from Composer.

Yes, that's a problem and it's out of scope here. It was solved somewhat by composer_manager and also this Composer plugin: https://github.com/paul-m/drupal-merge-plugin To solve this problem in the right way, we'd come up with something similar to drupal-merge-plugin, which does discovery of extensions and their composer packages and then merges them into Composer for dependency resolution during all Composer phases. This would be a much better solution than making our own database of installed extensions.

If you want to postpone here and work on integrating that, I'll gladly review. :-)

The desired behavior here, however, is that if the dependencies are met, a user can skate by without using composer. If the user tries to update their contrib extension as a tarball, and it has new dependencies that aren't met, we won't allow it. If we somehow sneak in symfony/event-dispatcher 3.0 and the contrib module has a hard dependency on 2.8, then it will show up in Status Report and also will prevent updates. Then the user can learn Composer and also how to file issues against the contrib module.

This behavior is demonstrated by the composer_installable test case, and the composer_mixed_uninstallable and composer_mixed_installable test cases present in #255.

Crell’s picture

+++ b/core/tests/Drupal/KernelTests/Core/Extension/ModuleInstallerTest.php
@@ -44,4 +45,15 @@ public function testRouteRebuild() {
+    $this->setExpectedException(ExtensionComposerRequirementsException::class);

I believe PHPUnit has deprecated this method in favor of $this->expectException($class). I'm not sure in what PHPUnit version, though...

Also, it's good practice to put it just before the line that is expected to throw.

xano’s picture

StatusFileSize
new901 bytes
new44.59 KB

I believe PHPUnit has deprecated this method in favor of $this->expectException($class). I'm not sure in what PHPUnit version, though...

Also, it's good practice to put it just before the line that is expected to throw.

I moved the line. The method itself wasn't deprecated and replaced until 5.2.0, which we do not require in Drupal core.

@Mile23 in #286: It is very much in scope. You are advocating an approach that we realized is inferior to the latest proposed one. It is very similar to a proposal I once supported in this issue, but we've realized it has significant limitations, which is why we no longer support it. You are now essentially saying that it is fine to advocate a broken Composer build (without packages that are present elsewhere in the code base) to our users. As explained in #281 and later, we cannot use any solution that does not require modules-to-be-installed to be built using Composer, for reasons identical to why we threw any plans for multiple Composer directories in the garbage: Composer cannot prevent conflicts between packages in the system, and it cannot prevent multiple versions of the same package from being installed, because it is unaware of all of them.

Edit: the reason the latest approach (checking whether modules have been installed through Composer rather than checking their dependencies) is the best one yet, is that previous iterations aimed to verify the requirements were met when enabling modules, but would never be able to guarantee this in the future. The latest approach delegates this responsibility to Composer, which is very much capable of building code bases without incompatibilities. On top of that, it allows developers to leverage the full feature set composer.json provides.

yesct’s picture

Issue summary: View changes
Issue tags: +Needs issue summary update

I agree with @Fabianx in #274 (https://www.drupal.org/governance/core), and for

it might be a good idea to update the IS in advance with pro and con of each of the discussed approaches.

adding the needs issue summary update tag. (I suspect that doing the update before @alexpott looks at this, may *help* alex look at it.) (I would set this issue to needs work for that, but this both needs work and needs review and the select field can only be one.)

alexpott’s picture

An issue summary update describing the various approaches with their pros and cons would indeed help - I've read most of the comments on the issue and see positions changing over time. There's a lot to take in.

Looking at the current patch, one thought that sticks out is

+++ b/core/lib/Drupal/Core/Composer/Composer.php
@@ -246,4 +247,34 @@ protected static function deleteRecursive($path) {
+    $installed_dump_file_path = $app_root . '/core/composer/installed-extensions.json';

Not entirely sure about this path and the current update instructions

Mixologic’s picture

I get the impression that we're kicking at totally different goalposts here.

Perhaps if we re-iterate which problem we're trying to solve in the issue summary as well.

Core does not require composer to build a site, however various contrib modules *do* require composer to build a site, and we want to alert users who have not or cannot run composer that this extension cannot be installed.

or

Ensure that we have a valid build, ensure that all requirements and dependencies are satisfied, and ensure that we have a valid code base.

I feel like the latter steps into the realm of what composer already does. Either you are using composer, and therefore you should have a valid build, or you are not, and therefore you need to see a warning message, and be prevented from installing a module that will be broken.

For the first problem, We're looking for two indicators

  1. composer becomes a requirement for building your site by using this particular extension.
  2. evidence that composer has not been run.

We do not need to concern ourselves with any of the build functionality that composer provides (replaces, conflicts, provides, autoload etc), and we do not need to concern ourselves with whether or not dependencies have actually been satisfied, and we do not need to concern ourselves with whether or not the build is valid. Thats composer's job.

mile23’s picture

Issue summary: View changes

Updating the IS with some use-case scenarios. Please amend as desired so that we can talk about the behavior instead of the code.

Also attempted to summarize the two competing ideas.

xano’s picture

Can we update the requirements/Definition of Done so that it requires the solution to keep a compatible code base rather than just perform this check on module installation? It's not much use checking one module's requirements if installing another can break them again.

borisson_’s picture

I realise that there should be discussion about which approach we should take, but @Xano asked me to take a look at the latest patch here, so I did.

  1. +++ b/core/composer/installed-modules.json
    @@ -0,0 +1 @@
    +{}
    \ No newline at end of file
    

    Needs newline.

  2. +++ b/core/includes/install.inc
    @@ -985,9 +986,20 @@ function drupal_requirements_severity(&$requirements) {
    +  if (drupal_requirements_severity($requirements) == REQUIREMENT_ERROR) {
    

    Can we use strict checking (===) here?

  3. +++ b/core/lib/Drupal/Core/Composer/Composer.php
    @@ -246,4 +247,34 @@ protected static function deleteRecursive($path) {
    +    $installed_module_paths = array_reduce($installed_module_packages, function ($installed_module_paths, PackageInterface $package) use ($app_root, $composer) {
    

    I don't really understand this very well, can we document what's happening here?

  4. +++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyChecker.php
    @@ -0,0 +1,164 @@
    +   * Determines whether a Drupal extension must be installed through Composer.
    

    I don't think this is right, the drupal extension doesn't need to be installed trough composer, it's dependencies should be installed trough composer. I think something like: "Checks if all required composer packages are installed" makes more sense?

Mixologic’s picture

Can we update the requirements/Definition of Done so that it requires the solution to keep a compatible code base

Do we need to keep a compatible code base, or do we need to detect circumstances that may indicate that we don't have one and warn the user?

xano’s picture

My opinion is that we MUST strive to keep a consistent code base. Without it, this issue becomes a mere novelty that won't help users much. Why would we warn someone just on module install time while the chances of things going wrong later are so high? By requiring modules that expose themselves as Composer packages (those with Composer files) to be installed through Composer, we let Composer ensure the integrity of the code base, making our check within Drupal more reliable and useful.
Currently we have no way of detecting any of this within Drupal, without building a dependency tree during the requirements check.

mile23’s picture

My opinion is that we MUST strive to keep a consistent code base. Without it, this issue becomes a mere novelty that won't help users much. Why would we warn someone just on module install time while the chances of things going wrong later are so high?

Not just module install time, but: Site install, install of any extension, update of any extension, status page. Anywhere hook_requirements() could step in and say no.

By requiring modules that expose themselves as Composer packages (those with Composer files) to be installed through Composer, we let Composer ensure the integrity of the code base, making our check within Drupal more reliable and useful.

I appreciate the value of this idea, but it also means that if you install a contrib module that depends on something like psr/log, you'll have to learn Composer and change your workflow, even though psr/log is already present. From a shared-hosting non-sophisticated user's perspective, this means we made them learn a whole bunch of complicated stuff in order to get ahold of a dependency that is already present.

Currently we have no way of detecting any of this within Drupal, without building a dependency tree during the requirements check.

True. And we *shouldn't* have it within Drupal, for all the reasons you point out, which is why we shouldn't build the dependency tree. Instead, we do a best-faith effort which steers people in the direction of Composer once they really need it.

Again: If you have a behavior in mind that you think isn't represented in the issue summary, please add it to the list of scenarios and their desired behavior.

xano’s picture

I appreciate the value of this idea, but it also means that if you install a contrib module that depends on something like psr/log, you'll have to learn Composer and change your workflow, even though psr/log is already present. From a shared-hosting non-sophisticated user's perspective, this means we made them learn a whole bunch of complicated stuff in order to get ahold of a dependency that is already present.

As complex as the problem is (and it is), this requirement is in fact relatively simple: there is no way to guarantee that extensions' Composer dependencies (and all their package links including conflicts) are met without using Composer, as it's a complex task that requires lots of code, and for good reasons, several people here opposed the idea of using composer/composer runtime, which leaves us with the option of delegating everything to the Composer CLI. It is nonsense to say we want to support a tool that requires CLI usage without wanting to use the CLI. This approach only requires extensions to be installed/built through Composer if those extensions say so by including a Composer file. If they can also be installed in another way, a different version without Composer integration should be published

TLDR; It is very difficult to satisfy extensions' dependency files by simply analyzing their Composer files, as we have experienced over the course of this issue, and it's impossible to keep these dependencies satisfied without using Composer.

Mixologic’s picture

it's impossible to keep these dependencies satisfied without using Composer

Thats inherently the problem. We have to warn the non-composer using folks that it is impossible for them to use the extension without using composer.

In order to create a consistent code base:

  • We support using composer.
  • We support using tarballs, git clones, ftp, drush, etc, as long none of the extensions require composer.

We do not support, and cannot currently support using using tarballs, git clones, ftp, drush, if the extension we're installing requires composer.

We do not have any ability whatsoever to satisfy extensions dependency files at runtime or install time, we can only determine whether or not an extension requires composer to satisfy those dependencies.

The algorithm for determining whether or not dependencies are satisfied is NP-Complete, and is accomplished in composer using an SAT solver. The real impact of that is that its *SLOW*, because it cant really be any faster until the hardest problem in computer science is solved. So unless we want to add 30-240 seconds to enabling a module and fighting with php timeouts, there is no way to use composer/composer at runtime.

We do not need to know why that extension requires composer, We do not need to know what those dependencies are, what they replace, or what kind of package links they have. None of that is possible to determine at runtime/install time. We *have* to assume that we have a valid codebase, *unless* we detect that an extension requires composer to function.

So the two things this needs to do is:

  • A. Detect whether an extension to be installed requires composer to function
  • B. Detect whether composer has been run after the extension has been downloaded.

If A is true and B is false, warn the user.

Perhaps determining A does not require implicit deduction and guessing at what is inside of the composer.json structure, and instead is an explicitly declared dependency. If a contrib maintainer declares a dependency on composer in info.yml file, we warn unless composer has been run.

xano’s picture

We do not have any ability whatsoever to satisfy extensions dependency files at runtime or install time, we can only determine whether or not an extension requires composer to satisfy those dependencies.

That's an excellent way of phrasing it!

Detect whether composer has been run after the extension has been downloaded.

Downloading the extension without Composer may require manual removal of said extension before Composer can be run, but I'd have to check that to verify. What we can do is provide the user with the Composer commands to run in order to allow Drupal to enable their extensions. Especially if we detect the modules' versions, we can add version constraints to those commands as well. It's easy enough to copy and paste them into a terminal.

Perhaps determining A does not require implicit deduction and guessing at what is inside of the composer.json structure, and instead is an explicitly declared dependency. If a contrib maintainer declares a dependency on composer in info.yml file, we warn unless composer has been run.

That would again be coupling the code base building and application integration phases, which I think it bad practice. Even if we support another build tool in the future, I think we should keep these phases separated as much as we can. I cannot think of any reason to ship a Composer file with an extension that does not need Composer, given that we have a façade that exposes package information whether packages provide Composer files or not. If any such reasons exist, I'd be happy to hear them, though.

mile23’s picture

@Xano: Downloading the extension without Composer may require manual removal of said extension before Composer can be run, but I'd have to check that to verify.

Yes, that's a problem. We should be sure and add that to the instructions on how to Composer-ify the installation.

Edit: Hah. I didn't want to be the guy who sent us to page 2. :-)

xano’s picture

We need input on the proposal in #293: do we want to ensure that the requirements we check during module enabling continue to be met throughout the application's lifetime, or are we fine with just checking on enabling and risking code base integrity errors afterwards?

fago’s picture

Guiding users to use the composer CLI once a module with a composer.json is detected seems to be a very good solution to me. We could even ship with a composer.phar file to avoid possible composer installation process issues.

In order to detect when composer is required, checking for an existing composer.json file seems reasonable, BUT might lead to issues for modules that just add an optional composer.json for discoverability reasons, e.g. when hosted externally on some github repos or so. However, we could easily support an OPT-OUT approach of requiring composer if a composer.json is there. I think opt-out would be better than opt-in here as it ensures that the "composer requirement" cannot be accidentally forgotten.

As a first step, denying activating composer-dependent modules that have not been installed via composer with some simple message pointing to a good drupal.org handbook page seems enough to me as it is a fast improvement compared to the status quo. Then, we can still improve this to provide a better composer-onboarding usuablity in follow-ups.

However, I wonder: Can we reliably detect whether a module was downloaded via composer or not? Maybe via checking composer's installed.json?

fago’s picture

oh: What if a module adds a composer.json including a dependency during an update? Seems like we have to check enabled modules for a suddenly added composer-dependency as well.

Mixologic’s picture

Re #302 I do not believe it is possible for us verify the integrity of the codebase either on module enabling, or during runtime. The only tool that can verify *php* codebase integrity is composer, and as I said before, its a build only tool because that verification process is long, slow, and cannot run during any sort of web request. There is also the issue of javascript codebase integrity, which is probably another tool entirely.

I think the only thing we are capable of doing is "detecting a situation that likely indicates that we need to utilize an external tool to verify the integrity of the codebase".

So, in order to detect that situation for php, one way we can do that is to parse composer.json files in module directories if they exist, and guess whether or not composer has or has not been run based on those contents, and have a lot of exceptions to make sure we're not falsely asking a user to run composer if they dont need to.

Another alternative is that we add a new type of dependency to the info.yml file metadata that drupal uses, and module maintainers explicitly tell drupal "you cannot install this module unless you use composer"

So to more formally suggest a solution that I hinted at at the end of #299:

We add a build_dependencies key to the info.yml file that could contain "composer" and could potentially contain other build tools that are required (eg. bower for javascript)

If a module is trying to be installed, and it has a build_dependencies key with composer as a value, we check installed.json to see if that module was added with composer. If it was not, we ask the user to remove the module, and 'composer require drupal/' in order to use it. (removal because it may or may not be in the same directory composer would install it to, and could potentially duplicate)

For private, custom modules that do not exist on either drupal.org's facade or on packagist.org, but also have external package dependencies, the end user would have to know to add a custom repository to their composer.json and composer require the module that way.

Mixologic’s picture

An added benefit to having that explicit in the info.yml file is that when project_dependency runs and extracts project metadata from the info.yml files, it can look for that key, and we can use that information on drupal.org's project pages to indicate * Composer required or something to that effect - which might also allow us to feed the update status module with information that the Update Manager can consume to understand that it cannot update a module that requires composer. (even if the current version does not)

We should also have a runtime check as well (to handle an installed module going from no composer requirement to having a composer requirement)

mile23’s picture

@Mixologic: So, in order to detect [platform dependencies] situation for php...

This is basically the big assumption to begin with. If we build our Drupal tarball's vendor directory on a PHP platform higher than the stated minimum version, we might introduce a compatibility issue for anyone running that minimum version.

Basically, any dependency check we do for this issue, which doesn't involve running Composer, is assuming that the platform PHP version is compatible with whatever the tarball was built with. We commit this act of faith because to do otherwise would require re-implementing Composer. :-)

@Fago: However, I wonder: Can we reliably detect whether a module was downloaded via composer or not? Maybe via checking composer's installed.json?

It would be in installed.json, yes. There are some weirdnesses, like the fact that the module's composer.json file won't be discovered again on subsequent updates... But that's out of scope here.

@Fago: As a first step, denying activating composer-dependent modules that have not been installed via composer with some simple message pointing to a good drupal.org handbook page seems enough to me as it is a fast improvement compared to the status quo. Then, we can still improve this to provide a better composer-onboarding usuablity in follow-ups.

+1 (really more than one...) for iterating.

That brings us to: build_dependencies from #305.

I think it's a decent idea, because a bunch of tools could use the meta info. There are a bunch of interesting questions like figuring out whether bower has been run in an extension.

But it's out of scope here because we can derive everything we need from what we already have.

xano’s picture

oh: What if a module adds a composer.json including a dependency during an update? Seems like we have to check enabled modules for a suddenly added composer-dependency as well.

That is solved by requiring modules that are Composer packages (filtered by a potential opt-out as you suggested) to be added to the code base (installed) through Composer. Updating the module would then also have to be done using Composer, although at this moment we did not build any checks to make sure that once a module was built through Composer, it was not subsequently replaced with a newer version of itself that was extracted from a tarball.

Re #302 I do not believe it is possible for us verify the integrity of the codebase either on module enabling, or during runtime. The only tool that can verify *php* codebase integrity is composer, and as I said before, its a build only tool because that verification process is long, slow, and cannot run during any sort of web request. There is also the issue of javascript codebase integrity, which is probably another tool entirely.

Which is why I suggested delegating all of this to Composer instead of doing this ourselves. The patch in #258 (and later revisions) uses this concept not only to preserve code base integrity, but also because so far none of us has come up with a working solution to check whether modules can be enabled ourselves (no solution analyzed conflict package links, for instance).

An added benefit to having that explicit in the info.yml file is that when project_dependency runs and extracts project metadata from the info.yml files, it can look for that key, and we can use that information on drupal.org's project pages to indicate * Composer required or something to that effect - which might also allow us to feed the update status module with information that the Update Manager can consume to understand that it cannot update a module that requires composer. (even if the current version does not)

We can do this without such a flag as well, although I'd be okay with introducing the opt-out flag as described by @fago in #303 on top of our current scanning for Composer files.

Basically, any dependency check we do for this issue, which doesn't involve running Composer, is assuming that the platform PHP version is compatible with whatever the tarball was built with. We commit this act of faith because to do otherwise would require re-implementing Composer. :-)

Composer itself allows anyone to ignore platform dependencies using composer install --ignore-platform-reqs. https://hannesvdvreken.com/2015/01/18/composer-ignore-platform-reqs-flag/ does a good job at explaining why this exists.

@Fago: However, I wonder: Can we reliably detect whether a module was downloaded via composer or not? Maybe via checking composer's installed.json?

Yes. See \Drupal\Core\Composer\Composer::dumpInstalledPackages() in the patch in #288.

It would be in installed.json, yes. There are some weirdnesses, like the fact that the module's composer.json file won't be discovered again on subsequent updates... But that's out of scope here.

This is the first time I hear about this problem. Can you describe it in more detail?

Anyway, let's get back to why this issue stalled in the first place: what are this issue's requirements? The last few patches have proven that we can tell whether or not extensions can be enabled with almost 100% accuracy by simply delegating this to Composer, circumventing our earlier incapabilities of analyzing extensions' package requirements and compatibilities ourselves. In #303 @fago outlined an approach for modules to opt-out in case our delegating to Composer is a little too much of a good thing for them. The latest patches have also been smaller and faster (Drupal runtime) than most other approaches we've tried so far.

Mixologic’s picture

Which is why I suggested delegating all of this to Composer instead of doing this ourselves

The only thing the patch in #288 delegates to composer is reconstructing a file we already have in order to be sure that we do not someday conflict with a future schema change in installed.json.

Lets assume for a moment that that is an implementation detail, and that at the end of the day, not that important.

none of us has come up with a working solution to check whether modules can be enabled ourselves (no solution analyzed conflict package links, for instance).

Nor can we, not without re-inventing composer, or running composer's solver at runtime. build time is the only valid time for codebase integrity checks.

#288 *does not* analyze package requirements *or* compatibilities. It only checks whether not composer knows whether or not an extention was installed - so I think we're all in agreement that that is the best we can do.

So lets talk about *how* it determines that composer needs to be run:

From what I see, it walks up the directory tree, looking for composer.json files and compares that to what we have in our list of what it knows to be installed from the last time it ran.

So, some problems with that:

1. presence or absence of a composer.json is not enough to *require* composer to be run, there are hundreds of projects that followed the advice of adding a simple blank composer.json. Making all of those projects unusable for the audience we're trying to help here is overly hostile, and even adding some sort of "opt out" means that until that opt out patch gets accepted, those users will not be able to use projects like http://cgit.drupalcode.org/redirect/tree/composer.json or http://cgit.drupalcode.org/pathauto/tree/composer.json for example. When those users who built their site on 8.1.x upgrade to whatever, and we tell them that they have to run composer now, when they didn't need to before, well, no. thats awful.

This assertion is incorrect:

+++ b/core/tests/Drupal/Tests/Core/Composer/ExtensionDependencyCheckerTest.php
@@ -0,0 +1,157 @@
+      'The module is a submodule and a Composer package. Its parent module is a Composer package and has been installed through Composer.' => [
+        FALSE,
+        '{"drupal/some_parent_module":"modules/some_parent_module"}',
+        'modules/some_parent_module/modules/some_module',
+        TRUE,
+        'modules/some_parent_module',
+        TRUE,
+      ],

2. Presence or absence of a composer.json as a parent module is not enough to determine whether or not a modules composer dependencies are met/unmet and composer needs to run: example if I composer require drupal/inmail I will get http://cgit.drupalcode.org/inmail/tree/composer.json, however if I try and enable inmail_phpmailerbmh, which is a submodule of inmail, but has its own unique package dependencies, then It will think that composer has already been run, because it will climb up to the inmail level, see that inmail was installed with composer and will not have its dependencies installed. False negative.

3. Because we support *many* different project structures, like the following:

  • A. simple, composer.json in the base project dir. (project/composer.json, project/project.info.yml)
  • B. "Module at the root level of the project contains all of its submodule composer package dependencies" (project/composer.json, project/commerce.info.yml, project/commerce_order/commerce_order.info.yml) - where commerce_order's package dependencies are declared at the root of the *project*, not in commerce_order's directory.
  • C. "Parent module at the root level contains composer dependencies, and a submodule contains *different* package dependencies, like the inmail example above.
  • D. "module at the root level of the project has no composer.json, but a submodule does: http://cgit.drupalcode.org/cloud/tree/?h=8.x-1.x && http://cgit.drupalcode.org/cloud/tree/modules/cloud_service_providers/aw...
  • E. "No parent project at the root level, only submodules, each with their own dependencies" (something like http://cgit.drupalcode.org/cod_support/tree/, where the project is really a bunch of related modules, but there is no "parent" module.
  • F. Sometimes there is *more than one module* in the same directory. I dont know if this can still happen in d8 (http://cgit.drupalcode.org/stringoverrides/tree/) and hopefully it doesnt, cause ew.

Then we add in that a module upgrade may actually change one of the above categories as well.

Anyhow, thats just determining which composer.json file counts as belonging to that extension. It doesnt count the fact that we still should probably *look in* that composer.json to make sure that we can tell whether or not we should *care* if composer is run or not. Blank composer.jsons, composer.jsons with dependencies on drupal modules that are installed, composer.json's with dependencies on core-components (someday), and composer.jsons with dependencies on things that core comes with anyhow (psr/log). None of those should false positive when a user tries to enable that extension.

I guess Im not so sure that we *can* derive everything we need from what we already have.

The safest way for a developer of a module to ensure that composer is run for an extension they develop is to explicitly declare its needed. Parsing that declaration is simple.

xano’s picture

The only thing the patch in #288 delegates to composer is reconstructing a file we already have in order to be sure that we do not someday conflict with a future schema change in installed.json.

That is untrue. The latest patches delegates all dependency resolving to Composer. Dumping the list of installed extensions is something we do ourselves, because we need packages' installation paths.

Nor can we, not without re-inventing composer, or running composer's solver at runtime. build time is the only valid time for codebase integrity checks.

#288 *does not* analyze package requirements *or* compatibilities. It only checks whether not composer knows whether or not an extention was installed - so I think we're all in agreement that that is the best we can do.

Nor can we, which is why we delegate all this to Composer by requiring modules to be built/installed there (with the optional exception of a flag as mentioned in #303). This way the analysis takes place without custom code on our side.

Presence or absence of a composer.json as a parent module is not enough to determine whether or not a modules composer dependencies are met/unmet and composer needs to run: example if I composer require drupal/inmail I will get http://cgit.drupalcode.org/inmail/tree/composer.json, however if I try and enable inmail_phpmailerbmh, which is a submodule of inmail, but has its own unique package dependencies, then It will think that composer has already been run, because it will climb up to the inmail level, see that inmail was installed with composer and will not have its dependencies installed. False negative.

Positive negative, because the maintainers messed up. Composer does not support nested Composer files. If people want to ship submodules, their root Composer files are responsible as per Composer specifications.

If people decide to add Composer files to submodules, it's not our responaibility to handle those simple because Composer doesn't either.

mile23’s picture

Issue summary: View changes

Positive negative, because the maintainers messed up. Composer does not support nested Composer files. If people want to ship submodules, their root Composer files are responsible as per Composer specifications.

..Well except not. :-)

We do, in fact, support submodules with different dependencies through the facade. So we have to deal with it here.

Mixologic’s picture

The latest patches delegates all dependency resolving to Composer.

My bad. Im was thinking about imaginary patches that were never submitted. In #261, I agreed with this approach, and had assumed that another patch that utilized that methodology, but relied on install.json existed.

Positive negative, because the maintainers messed up. Composer does not support nested Composer files. If people want to ship submodules, their root Composer files are responsible as per Composer specifications.

Modules are not packages. Projects are not packages. Adding a composer.json does not make it a package. It is perfectly acceptable for a drupal project to have a composer.json at the root level *as well as* at the submodule level, and in many cases it is *strongly encouraged*.

We do not want to force module maintainers, and end users to download every potential dependency that a project might need for all of its submodules, because lest we forget, drupal modules are composer dependencies too, and if I have a package like http://cgit.drupalcode.org/cod_support/tree/, that would mean that over 100 modules get downloaded but the end user might only need 10 of them.

This is the price we pay for taking a tool that was never designed with a sitebuilder in mind (composer), and shoehorning it to fit it into a system that has *better* tools for sitebuilders (groups of optional modules aggregated together into projects).

We have a fundamental collision between the design of composer and the design of drupal modules and projects. We have worked around many of those limitations, but many of them remain. Multisites, sitebuilders with no CLI, developers can no longer have an arbitrary module organization, etc.

Anyhow, I feel like we should not add a bunch of code to do codebase forensics to determine whether or not composer is a requirement. Instead we add the explicit requirement, tell devs "if you need composer, this is how you ensure it is run".

mile23’s picture

StatusFileSize
new44.28 KB
new27.76 KB

Here are two patches:

One is a reroll of #288.

The other is an attempt at adding a build_dependencies key to .info.yml files. It's based off #255 just because the refactoring was easier since it mostly deletes stuff. It doesn't add an installed-extensions.json file, but it that would be relatively easy to graft back on.

Mixologic’s picture

Issue summary: View changes

I have updated the issue summary with what I think the problems are that we are trying to solve, and the current state of where we are at in the solution. Please review it, I have left the old summary as Im not sure what we dont need in there.

One additional caveat with the build dependencies key. In order to support projects like drupal/commerce that have all of their dependencies declared at the root level, and projects like inmail that might have dependencies in submodules and dependencies in the root module, the build_dependencies[] = composer *should not* be set in an info.yml file if there is not a corresponding composer.json (i.e. even though commerce_order requires composer, since its deps are specified at the commerce level, that is where the build dep key should be set.

This is so that we do not have to figure out how to handle submodules that will not be listed in either installed.json/installed-packages.json because it was installed via a composer require drupal/parentmodule

Mixologic’s picture

Issue summary: View changes
eric_a’s picture

I just read the issue summary changes by @Mixologic.

1. Does the extension require composer

Hmm, module maintainers are used to implementing hook_requirements(). The main issue here is that composer helper code is missing. Wether we add an info key (per phase?) seems like a separate issue. Slightly off-topic: think runtime checks for suggested composer packages. Are we planning to invent another info key for that? Or do we consider that less of a use case compared to install time?

With helper code present in core simpletest module could just check for 'phpunit/phpunit' (EDIT: or 'all my composer dependencies, please') instead of doing class_exists() like #2780093-5: Have simpletest, run-tests.sh enforce their dependency on PHPUnit.

xjm’s picture

Component: base system » extension system
Category: Bug report » Task
Issue tags: +Triaged core major

The committers discussed this issue awhile back and agreed that it should be a major task based on its impact (but it does not need to be a bug because of available workarounds and existing limitations). Thanks!

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.

Saphyel’s picture

From composer point of view, a package it's a self-defined isolated library.
A sub-module if you see like is part of the package should be in the main composer.json if not, maybe you should exclude it from the module. Maybe from some contrib maintainer point of view may differ because they are use to the old way but I think is better keep things simple

borisson_’s picture

Status: Needs review » Needs work

I think we can remove the Needs issue summary update tag from this issue after #314?

I reviewed #313-build-dependencies. I like the approach of adding "composer" as a build dependency to a module, it's not too complicated.

I have some nits to pick though.

  1. +++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyChecker.php
    @@ -0,0 +1,111 @@
    +    if (isset($info['build_dependencies'])) {
    +      if ($info['build_dependencies'] === 'composer') {
    +        return TRUE;
    +      }
    +      if (is_array($info['build_dependencies'])) {
    +        return in_array('composer', $info['build_dependencies']);
    +      }
    +    }
    +    return FALSE;
    

    I think we can simplify this code by switching the ifs.

    if (!isset($info['build_dependencies']) {
      return FALSE;
    }
    
    if (is_array($info['build_dependencies']) {
      return in_array('composer', $info['build_dependencies'], TRUE);
    }
    
    return ($info['build_dependencies'] === 'composer');
    
  2. +++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyChecker.php
    @@ -0,0 +1,111 @@
    +   * @param Extension $extension
    

    Needs FQN

  3. +++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyChecker.php
    @@ -0,0 +1,111 @@
    +    // Quickly return the cached list, if it exists.
    

    Can we change this comment? I think it'd be better if it was: If there's a cached version of the installed packages, return it.

  4. +++ b/core/modules/system/src/Tests/Module/HookRequirementsTest.php
    @@ -24,4 +32,53 @@ function testHookRequirementsFailure() {
    +    $this->drupalPostForm('admin/modules', $edit, t('Install'));
    

    We don't need the t() here.

  5. +++ b/core/tests/Drupal/Tests/Core/Composer/ExtensionDependencyCheckerTest.php
    @@ -0,0 +1,124 @@
    +   * @var numeric
    

    /s/numeric/int/?

mile23’s picture

Status: Needs work » Needs review
StatusFileSize
new27.86 KB
new3.03 KB

Thanks, @borrison.

Rerolled, added all the changes from #322.

I also moved the kernel test to the Drupal\KernelTests namespace, and changed awkward package name examples. I accidentally did this during the reroll so there's no interdiff for those changes.

I think it's safe to say we should go forward with adding a build_dependencies key to info.yml files.

Status: Needs review » Needs work

The last submitted patch, 323: 2494073_323.patch, failed testing.

mile23’s picture

Status: Needs work » Needs review
StatusFileSize
new27.85 KB

Fixed failing test.

mile23’s picture

mile23’s picture

Issue summary: View changes

Update issue summary to reflect adding build_dependencies to info.yml.

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

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

wim leers’s picture

Status: Needs review » Needs work

Mark modules with unmet composer dependencies uninstallable YES PLEASE!

Here's a best-effort review, because I'm neither a Composer expert nor a module system expert, and this issue will definitely need reviews from experts in those areas.

  1. Overall: this patch looks super clear and clean. Great work! 👏
  2. +++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyChecker.php
    @@ -0,0 +1,109 @@
    +class ExtensionDependencyChecker {
    
    +++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyRequirements.php
    @@ -0,0 +1,81 @@
    +class ExtensionDependencyRequirements {
    
    +++ b/core/lib/Drupal/Core/Extension/ExtensionComposerRequirementsException.php
    @@ -0,0 +1,45 @@
    +class ExtensionComposerRequirementsException extends \Exception {
    

    Nit: I think these should be marked @internal?

  3. +++ b/core/lib/Drupal/Core/Extension/ModuleInstaller.php
    @@ -93,9 +104,15 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
    +        // Throw an exception if there are unmet Composer-based dependencies.
    +        if ($this->extensionComposerDependencyChecker->requiresComposerInstallation($module_extension)) {
    

    The comment is accurate ("unmet Composer-based dependencies"), but the method name is not ("requires composer installation").

    Once the method is renamed, we can even drop the comment.

  4. +++ b/core/modules/system/src/Tests/Module/HookRequirementsTest.php
    @@ -2,10 +2,18 @@
    + * @todo Figure out how to demonstrate installation success for
    + *   Composer-installed modules.
    

    Woah this sounds scary complex — but also an interesting challenge 😀

  5. +++ b/core/modules/system/src/Tests/Module/HookRequirementsTest.php
    @@ -17,11 +25,60 @@ public function testHookRequirementsFailure() {
    +   * Tests that modules which require Composer installation are un-installable.
    

    s/require Composer installation/require Composer dependencies/

    I think that'd be more accurate?

  6. +++ b/core/modules/system/src/Tests/Module/HookRequirementsTest.php
    @@ -17,11 +25,60 @@ public function testHookRequirementsFailure() {
    +    // Attempt to install the module using internals. This should throw a
    

    "using internals" — what does that mean?

  7. +++ b/core/modules/system/tests/modules/composer_required_array/composer_required_array.info.yml
    @@ -0,0 +1,8 @@
    +build_dependencies:
    +  - composer
    

    Oh this is new. Interesting. Why exactly do we need this? Can't this be inferred from a module's composer.json - i.e. if it has dependencies on packages outside the drupal/ namespace?

I think the @todo I quoted in point 4 is the most important remaining task?

mile23’s picture

Thanks for the review, Wim.

Let's soft-postpone this on #2830880: Warn site admins when composer dev dependencies are installed inside of docroot, which has a much more limited scope, but helps us by adding services to discover Composer stuff. It's an easier decision to make and gives us tools to use here.

wim leers’s picture

Ok, great, I'll try to review that next week.

kyvour’s picture

+  protected function getInstalledPackages() {
+    // Return the cached list of packages if it exists.
+    if (is_array($this->installedPackages)) {
+      return $this->installedPackages;
+    }
+
+    $installed_json_path = $this->appRoot . '/vendor/composer/installed.json';

Could you check my comment for #2830880: Warn site admins when composer dev dependencies are installed inside of docroot please? There is a same problem:

  • composer root and app root may not be the same
  • vendor dir has hardcoded name 'vendor', but it can be specified in composer.json by developer.

Version: 8.5.x-dev » 8.6.x-dev

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

mile23’s picture

Status: Needs work » Needs review
StatusFileSize
new27.48 KB

Reroll of #325 after mention in #1398772: Replace .info.yml with composer.json for extensions

Still needs work from review in #329.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

tedbow’s picture

Sorry I didn't read through the whole issue but seems this may be handled now by #3005229: Provide optional support for using composer.json for dependency metadata

the current plan there now(I am about to update the IS) is if modules don't provide any of core, type, dependencies keys in there info.yml file then the composer.json file will be used instead

aaronmchale’s picture

Title: Mark modules with unmet composer dependencies uninstallable » Prevent modules which have unmet Composer dependencies from being installed

Re #337: I'm in favour of that, also renaming this issue as the title has always threw me, I think the word "uninstallable" is too ambiguous.

Mixologic’s picture

#3005229: Provide optional support for using composer.json for dependency metadata wont necessarily help this issue, because this is in regards to third party libraries that are not drupal modules. I think we'll still benefit from keeping this and the other one as separate fixable issues.

aaronmchale’s picture

Re #339: Well both issues are covering Composer, but given the current scope of #3005229: Provide optional support for using composer.json for dependency metadata I can see where you're coming from, maybe then we could expand the scope of that issue or re-purpose this issue to be a follow-up?

Mixologic’s picture

Status: Needs review » Postponed

The intention is to repurpose this issue to be a follow up. There is a significant number of issues happening in the composer initiative, and as such, the underlying assumptions that this issue is based on are no longer accurate. Many of those issues are not quite into core yet, but once they do, this one will be much easier to tackle.

Im going to go ahead and postpone this as a lot of other issues that need to get resolved first. Probably best to follow this one:

#2982674: [meta] Composer Initiative Phase 1: Add composer build support to core
And this one:
#3069795: [meta] Improve dependency management for extensions

Mixologic’s picture

https://github.com/Ocramius/PackageVersions looks like something we might be keen on adapting. They've dropped php7.2 support, but Im sure we could do something similar. saving here for when we reopen this.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

alexpott’s picture

Assigned: alexpott » Unassigned

I don't think this issue should be assigned to me.

Mixologic’s picture

Status: Postponed » Active

We should start talking about this again.

gapple’s picture

Status: Active » Needs review
StatusFileSize
new27.55 KB

Reroll of #334 to 8.9

Made one change to replace an instance of drupal_set_message() to use the messenger service.

Status: Needs review » Needs work

The last submitted patch, 346: drupal-2494073-346.patch, failed testing. View results

andypost’s picture

instead of constants from install.inc it could use the same from \Drupal\system\SystemManager

  1. +++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyRequirements.php
    @@ -0,0 +1,81 @@
    +        'severity' => REQUIREMENT_OK,
    

    \Drupal\system\SystemManager::REQUIREMENT_OK

  2. +++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyRequirements.php
    @@ -0,0 +1,81 @@
    +        'severity' => REQUIREMENT_ERROR,
    

    \Drupal\system\SystemManager::REQUIREMENT_ERROR

  3. +++ b/core/tests/Drupal/KernelTests/Core/Composer/ExtensionDependencyRequirementsTest.php
    @@ -0,0 +1,80 @@
    +   *   - One of the REQUIREMENT_* constants.
    ...
    +      // REQUIREMENT_ERROR is 2.
    +      'REQUIREMENT_ERROR' => [
    ...
    +      // REQUIREMENT_OK is 0.
    +      'REQUIREMENT_OK' => [
    ...
    +      'REQUIREMENT_OK' => [
    

    it looks weird in unittest because defined in install.inc

gapple’s picture

Status: Needs work » Needs review
StatusFileSize
new27.78 KB
new5.73 KB

- Use SystemManager constants
- pass $this->appRoot to InfoParser in test so that it doesn't fallback to using DRUPAL_ROOT
- Fix test provider using duplicate key, resulting in skipped test case.
- Added TODO regarding change in installed.json format (https://github.com/composer/composer/pull/7999)

Status: Needs review » Needs work

The last submitted patch, 349: drupal-2494073-349.patch, failed testing. View results

dww’s picture

#3102724 adds a 'Compatible' vs. 'Not compatible' <details> element to the Available updates (Update status) report to deal with the fact that a new release of a contrib might or might not be compatible with your currently installed version of core. Over there, I argued we should keep the <summary> of that <details> simple (just "Compatible" vs. "Not compatible") and suggested that we might want to expand the reasons something is marked as 'Not compatible' to include more than just the core version constraints. This could be another thing that could make a new release incompatible with your site. I wonder if there's a way to write this such that it could be re-used / called by Update module. I guess the problem is that we won't know about unmet composer dependencies until the new composer.json is on the filesystem and can be inspected. ;) So probably this is all silly. But I thought I'd mention it here just in case.

Cheers,
-Derek

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

nicxvan made their first commit to this issue’s fork.

nicxvan’s picture

Since composer is the only recommended way to install can we close this?

borisson_’s picture

Since composer is the only recommended way to install can we close this?

As long as it's not the only possible way to install modules, there's still value here I think.

dww’s picture

berdir’s picture

Even if we'd only support composer, there are still things like submodules that have their own dependencies.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.