composer require fails with Composer 2

Steps to reproduce

comp require --update-no-dev --update-with-dependencies --no-plugins drupal/entity drupal/smtp drupal/spambot drupal/webform:^6.0 wikimedia/composer-merge-plugin

> Drupal\Core\Composer\Composer::vendorTestCodeCleanup

Fatal error: Uncaught Error: Call to undefined method Composer\DependencyResolver\Operation\UpdateOperation::getJobType() in /home/stelnews/public_html/core/lib/Drupal/Core/Composer/Composer.php:170
Stack trace:
#0 phar:///home/stelnews/public_html/composer/src/Composer/EventDispatcher/EventDispatcher.php(319): Drupal\Core\Composer\Composer::vendorTestCodeCleanup(Object(Composer\Installer\PackageEvent))
#1 phar:///home/stelnews/public_html/composer/src/Composer/EventDispatcher/EventDispatcher.php(216): Composer\EventDispatcher\EventDispatcher->executeEventPhpScript('Drupal\\Core\\Com...', 'vendorTestCodeC...', Object(Composer\Installer\PackageEvent))
#2 phar:///home/stelnews/public_html/composer/src/Composer/EventDispatcher/EventDispatcher.php(123): Composer\EventDispatcher\EventDispatcher->doDispatch(Object(Composer\Installer\PackageEvent))
#3 phar:///home/stelnews/public_html/composer/src/Composer/Installer/InstallationManager.php(386): Composer\EventDispatcher\EventDispatcher->dispatchPackageEvent('post-package-up...', in /home/stelnews/public_html/core/lib/Drupal/Core/Composer/Composer.php on line 170

Proposed resolution

Downgrade to composer 1. But that fails with same error!

Remaining tasks

Fix composer2

User interface changes

API changes

Data model changes

Release notes snippet

An incompatibility with Composer 2, which affects a minority of sites when trying to update certain packages, has been fixed in Drupal 9.2.N, 9.3.N, and later. This incompatibility may cause fatal errors similar to:

> Drupal\Core\Composer\Composer::vendorTestCodeCleanup

Fatal error: Uncaught Error: Call to undefined method Composer\DependencyResolver\Operation\UpdateOperation::getJobType()

This problem can be fixed permanently by running these commands on any version of Drupal 9:

composer config --unset scripts.post-package-install
composer config --unset scripts.post-package-update
composer require drupal/core-vendor-hardening:^9

Other workarounds require updating Drupal to 9.2.N, 9.3.N, or later, and are documented at https://www.drupal.org/node/3267857.

Comments

tjtj created an issue. See original summary.

jackson.cooper’s picture

StatusFileSize
new828 bytes

This patch fixes the issue for me.

jasonhoward7’s picture

This patch worked for me, too. Be sure to run composer clearcache after patching.

jasonhoward7’s picture

This patch worked for me, too.

nanak’s picture

According to #3183290: Drupal\Core\Composer\Composer::vendorTestCodeCleanup NOT compatible with Composer 2 you should not rely on vendorTestCodeCleanup anymore but require drupal/core-vendor-hardening instead

drupgirl’s picture

+1 RTBC, as patch in #2 solved this issue.

bendev’s picture

patch #2 also working in my setup. Thank you

alezu’s picture

Removing "zaporylie/composer-drupal-optimizations" fixed such error for me.

jigarius’s picture

Status: Active » Reviewed & tested by the community
  • I can confirm that the patch #2 works.
  • I already have drupal/core-vendor-hardening and it doesn't have any effect on this problem.
  • I am not using ScriptHandler::vendorTestCodeCleanup() but I still have this problem.
  • I do not have zaporylie/composer-drupal-optimizations installed, but I still have the problem.

Seems to be a API change and I think patch #2 is doing a good job at fixing the problem. Since many participants on this issue confirm that patch #2 solves the problem for them, I'm marking this as RTBC.

freelock’s picture

I'm getting this on every single site I try to update with Composer 2. The patch works. Can we get it committed, please?

pslcbs’s picture

I also get that error on all my sites when using Composer 2.
Patch work ok.
Please commit.
Thanks

freelock’s picture

StatusFileSize
new830 bytes

Updated patch for 9.1.8.

freelock’s picture

Project: Composer » Drupal core
Version: 8.x-1.x-dev » 10.0.x-dev
Component: Code » composer
Priority: Critical » Major

Moving this issue to Core -- what even is the "Composer" project???

amanire’s picture

I can't believe this isn't a higher priority. This patch doesn't even seem to get applied before the error occurs. I'm finding that I have to manually apply the change to get composer to complete far enough that composer-merge-plugin can start applying patches.

longwave’s picture

Version: 10.0.x-dev » 9.3.x-dev
Related issues: +#3076684: Remove deprecated vendor cleanup scripts

Thanks to everyone for reporting and testing this patch.

If you are running Composer 2 and you have something like the following in composer.json:

"scripts": {
    "post-package-install": "Drupal\\Core\\Composer\\Composer::vendorTestCodeCleanup"
}

then you composer require a new package, Composer crashes with

PHP Fatal error:  Uncaught Error: Call to undefined method Composer\DependencyResolver\Operation\InstallOperation::getJobType() in core/lib/Drupal/Core/Composer/Composer.php:189

This script predates Drupal 8.8 but there are obviously cases where it is still in use, and we never formally deprecated it. I think this should be committed to all actively supported branches including 8.9.x in order to it easier for people to upgrade to Composer 2.

It should be feasible to add a build test for this script, we have no test coverage for this in core it seems.

longwave’s picture

Issue tags: +Bug Smash Initiative
longwave’s picture

Status: Reviewed & tested by the community » Needs review
StatusFileSize
new5.66 KB
new6.47 KB

Heavily influenced by ComposerProjectTemplatesTest, this probably needs some refactoring into a trait but this works locally and proves the problem.

longwave’s picture

StatusFileSize
new5.63 KB
new6.44 KB

The last submitted patch, 18: 3162228-18-test-only.patch, failed testing. View results

amanire’s picture

This script predates Drupal 8.8 but there are obviously cases where it is still in use, and we never formally deprecated it.

@longwave just to confirm, you are saying that this call can be safely removed from composer.json in Drupal 8.8+?

        "post-package-install": "Drupal\\Core\\Composer\\Composer::vendorTestCodeCleanup",
        "post-package-update": "Drupal\\Core\\Composer\\Composer::vendorTestCodeCleanup"
longwave’s picture

@amanire: yes, this script has been migrated to a Composer plugin; if you composer require drupal/core-vendor-hardening then you can remove those two lines from your composer.json. See https://www.drupal.org/node/3059717 for more information.

amanire’s picture

Got it, many thanks @longwave!

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.

xjm’s picture

Does this still need to be addressed?

Tentatively adding it to the D10 requirements scope as a should-have, in case this is still preventing folks from using Composer 2.

xjm’s picture

Issue tags: +Composer 2
phenaproxima’s picture

I've reviewed this issue, and the code, and I think I understand what's happening here and what the next steps should be.

Who's affected by this bug

Anyone who started a site using the drupal/drupal Composer project before Drupal 8.8 was released. Before Drupal 8.8, drupal/drupal had this vendorTestCodeCleanup script defined in the root composer.json. It was removed in Drupal 8.8, which introduced drupal/recommended-project and drupal/legacy-project, which don't have this script. (recommended-project doesn't need it in the first place, and legacy-project includes the drupal/core-vendor-hardening plugin, which does the same thing as the script did.)

How to test this

I think that @longwave's build test is sufficient for automated test coverage. However, this should have a manual test to ensure it works when upgrading. The following commands should produce the error, which roughly map to "start a Drupal 8.7 site from drupal/drupal with Composer 1, then update to Composer 2 and try to update to Drupal 9":

composer self-update --1
composer create-project drupal/drupal:~8.7.0 test
cd test

At this point, edit composer.json to remove the replace section. Then continue:

composer self-update --rollback
composer require --no-update wikimedia/composer-merge-plugin:^2 drupal/core:^9
composer update

Without the patch, this should produce the fatal error from the issue summary.

Is there a workaround?

Yes, there are three.

#1: Replace the broken script with the drupal/core-vendor-hardening plugin, as @longwave said in #21. Assuming you're running Drupal 9, the following commands oughta do the trick:

composer config --unset scripts.post-package-install
composer config --unset scripts.post-package-update
composer require drupal/core-vendor-hardening:^9

This is the more permanent fix.

#2: When this patch is committed -- let's say it's released in 9.3.8 -- disable scripts while upgrading core to that version:

composer require drupal/core:^9.3.8 --no-scripts --update-with-all-dependencies

#3: When this patch is committed, use Composer 1 to update to the fixed version. As far as I can tell, Composer 1 is unaffected by this bug. So you could do something like:

composer self-update --1
composer require drupal/core:^9.3.8 --update-with-all-dependencies
composer self-update --rollback

Next steps

  • Try the manual test steps I just put down, and refine them until they accurately reproduce the problem. Then change them such that we simulate a "real" update path, starting from the last tagged release of 8.7, to 8.8.0, then to the latest tagged release of 9.3. You don't actually have to install Drupal; we just care about Composer being able to update the code base here, but that's the sequence of updates that one would have to do, at minimum, to get from 8.7.x to 9.3.x in a real-world scenario.
  • Test that the workaround succeeds. Then test that, if core gets patched with the fix in this issue before updating to 9.x, it succeeds.
  • Update the issue summary with a release note explaining the workaround, because otherwise people may not be able to update to a core version that fixes this bug. I don't think there's any need for a change record, as this doesn't affect our API.
  • RTBC and commit the patch.
  • Partayyyyyyy
xjm’s picture

Awesome, thanks @phenaproxima. So I think the next steps are:

  1. Write a change record documenting all three workarounds.
  2. Write a release note linking to the CR.
  3. Code review of the patch.
  4. Backport this to 9.3.x and possibly 9.2.x as an upgrade path blocker.
  5. Include the release note in the affected releases as well as whatever handbook pagew talk about our Composer 2 requirement.
phenaproxima’s picture

Draft change record written: https://www.drupal.org/node/3267857

We'll need to update it with the real core versions that will contain the fix.

phenaproxima’s picture

Issue summary: View changes
Issue tags: -Needs release note

Added release note to the issue summary.

phenaproxima’s picture

Status: Needs review » Needs work

Kicking this back to "needs work", since the patch is not passing Drupal CI.

phenaproxima’s picture

Issue tags: +Needs followup
+++ b/core/tests/Drupal/BuildTests/Composer/LegacyScriptsTest.php
@@ -0,0 +1,134 @@
+  /**
+   * Get Composer items that we want to be path repos, from within a directory.
+   *
+   * @param string $workspace_directory
+   *   The full path to the workspace directory.
+   * @param string $subdir
+   *   The subdirectory to search under composer/.
+   *
+   * @return string[]
+   *   Array of paths, indexed by package name.
+   */
+  public function getPathReposForType($workspace_directory, $subdir) {
+    // Find the Composer items that we want to be path repos.
+    /** @var \SplFileInfo[] $path_repos */
+    $path_repos = Composer::composerSubprojectPaths($workspace_directory, $subdir);
+
+    $data = [];
+    foreach ($path_repos as $path_repo) {
+      $json_file = new JsonFile($path_repo->getPathname());
+      $json = $json_file->read();
+      $data[$json['name']] = $path_repo->getPath();
+    }
+    return $data;
+  }

I agree with @longwave that this (and a bunch of other stuff in the test) should all be refactored into a trait, since it came from another build test, but it's out of scope here. We should probably open a follow-up issue for that.

We could also modify the existing build tests to add that script, which would allow us to not duplicate code. But then we'd be testing legacy junk in our non-legacy tests, which isn't great. But then again, Composer scripts are not like other "legacy" code -- if developers don't explicitly remove them from composer.json, they stay there forever. So they occupy a weird middle ground between legacy and non-legacy. So maybe what we have is fine, except for the copypasta we should clean up later.

Otherwise, code looks great to me. No objections at all to RTBC, once the patch is passing tests.

phenaproxima’s picture

Added #3268426: Abstract common build test logic into a trait.

All this needs now is for the spelling error to be fixed ("SUT" can be replaced with "system under test" or "site under test") and, when tests pass, IMHO this is ready to be committed.

spokje’s picture

StatusFileSize
new6.46 KB
new1.42 KB

All this needs now is for the spelling error to be fixed ("SUT" can be replaced with "system under test" or "site under test")

Ran with site under test in attached patch.

spokje’s picture

Status: Needs work » Needs review
phenaproxima’s picture

Status: Needs review » Reviewed & tested by the community

Nice, let's get this in.

Committers, this is tagged "needs change record updates" so that we will say which tagged versions of core introduced this fix.

longwave’s picture

Once this is done we should work on deprecating these scripts and migrating users to the plugin equivalents over in #3076684: Remove deprecated vendor cleanup scripts

alexpott’s picture

Version: 9.4.x-dev » 9.3.x-dev
Status: Reviewed & tested by the community » Fixed

Committed and pushed 05ff954188 to 10.0.x and 55bb63f889 to 9.4.x and 6dd760025e to 9.3.x. Thanks!

  • alexpott committed 05ff954 on 10.0.x
    Issue #3162228 by longwave, Spokje, freelock, jackson.cooper,...

  • alexpott committed 55bb63f on 9.4.x
    Issue #3162228 by longwave, Spokje, freelock, jackson.cooper,...

  • alexpott committed 6dd7600 on 9.3.x
    Issue #3162228 by longwave, Spokje, freelock, jackson.cooper,...
alexpott’s picture

I went to update the change record - but I'm really not sure that the CR serves any purpose. The CR is announcing a bug is fixed. From Drupal 9.3.x onwards no one is going to need to do anything as the bug has been fixed - right? I think the CR will be more applicable to #3076684: Remove deprecated vendor cleanup scripts - where we'll need to issue a deprecation and tell people to remove the old script from their root composer.json. For similar reasons I'm not sure what the release note is telling people. Like now this has been fixed what does someone actually need to do?

phenaproxima’s picture

As per discussion with @xjm, the CR is there to tell people about the workarounds for the bug, so that they can actually stay up to date in the interim until this is released. Or, in trickier scenarios, so that they can update at all.

alexpott’s picture

@phenaproxima I guess the problem is is that CR's are for telling people to make changes in response to changes we've made. I'm not sure why someone would look for a CR to work-around a bug that is fixed by the issue that is linked to the CR. If you have this problem you are way more likely to go to the issue queue and, fingers-crossed, find this issue. Which hopefully documents the work-arounds in the issue summary.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

quietone’s picture

the change record has been updated, removing tag.