Problem/Motivation

We currently have a runtime dependency on composer/composer. This is mainly so we can invoke Composer's API to get information about what packages are currently installed (in either the active directory or staging area). The bulk of that takes place in Package Manager's ComposerUtility class.

This architecture, however, means that core will need to make composer/composer a runtime dependency in order to support Automatic Updates. @effulgentsia opened a policy issue for that, which has honestly not seen a ton of action (#3243899: [policy] Move composer/composer from a dev dependency to a production dependency). Although one knowledgeable person did weigh in and emphatically asked us NOT to do it: #3243899-10: [policy] Move composer/composer from a dev dependency to a production dependency .

That got me thinking: do we really need to have this runtime dependency? Is there no other way to get the information we need from Composer? There is a very clear benefit to being able to remove the dependency, namely that we don't have an additional dependency (and we sidestep any runtime issues that folks might encounter).

Another problem is that ComposerUtility is not API-compatible with both Composer 2.2 and 2.3+. This problem was only revealed while trying to make Automatic Updates Drupal 10-compatible, since Drupal 9's constraints will implicitly force the installation of Composer 2.2. This plan would effectively eliminate this problem, and advance Drupal 10 compatibility.

After some thought, I propose that we absolutely have a path to removing the runtime dependency. Rather than relying on Composer's API, I think we can instead rely on two things to get the information we need:

  • The installed.php that Composer generates when packages are installed or updated. We already rely on this quite a bit in ComposerUtility.
  • The output of various Composer commands. We already rely on this in ComposerExecutableValidator, for a start, but Composer has a very robust command-line interface.

Eventually, I think we might even be able to drop the first item, since installed.php is technically internal to Composer, and rely solely on the output of Composer commands. But that could be done later.

Here's the path I think we should take:

  1. #3334994: Add new InstalledPackagesList which does not rely on Composer API to get package info Right now, we have a method in ComposerUtility called getInstalledPackages(), which uses Composer's API to return a keyed array of PackageInterface objects. I propose we replace that with two classes: InstalledPackage, which is a simple read-only value object representing a single installed package, including the information that can be gleaned from installed.php. There would also be an InstalledPackagesList object, which would include most of the functionality currently in ComposerUtility, but its getInstalledPackages() method (or some other name) would return InstalledPackage objects, not PackageInterface. This class would rely only on the information in installed.php, and never try to load Composer's API.
  2. We would add new methods to Stage: getInstalledPackages() and getStagedPackages(). Each would return an InstalledPackagesList object. Stage's getActiveComposer() and getStageComposer() methods, along with ComposerUtility, would be deprecated.
  3. ComposerUtility::validatePackageNames() would be replaced by a new validator that subscribes to PreRequireEvent. This subscriber would write a fake composer.json to a temporary directory, and put all the required packages into its require section. Then it would run composer validate /path/to/fake/file, which would quickly validate that all the requirements are legitimate. This is proceeding in #3347031: Stage::validatePackageNames() should not use the Composer API.
  4. #3316668: ComposerSettingsValidator should run `composer config` to determine if HTTPS is enabled
  5. #3337760: ComposerPatchesValidator should use ComposerInspector instead of ComposerUtility
  6. #3342227: ScaffoldFilePermissionsValidator should use ComposerInspector instead of ComposerUtility
  7. #3345754: Updater should use ComposerInspector instead of ComposerUtility
  8. #3345755: ExtensionUpdater should use ComposerInspector instead of ComposerUtility
  9. #3345757: \Drupal\automatic_updates_extensions\Form\UpdateReady should use ComposerInspector instead of ComposerUtility
  10. #3345760: \Drupal\automatic_updates_extensions\Form\UpdaterForm should use ComposerInspector instead of ComposerUtility
  11. #3345761: UpdateReleaseValidator should use ComposerInspector instead of ComposerUtility
  12. #3345762: GitExcluder should use ComposerInspector instead of ComposerUtility
  13. #3345763: UnknownPathExcluder should use ComposerInspector instead of ComposerUtility
  14. #3345764: OverwriteExistingPackagesValidator should use ComposerInspector instead of ComposerUtility
  15. #3345765: SupportedReleaseValidator should use ComposerInspector instead of ComposerUtility
  16. #3345766: CollectIgnoredPathsFailValidator should use ComposerInspector instead of ComposerUtility
  17. #3345767: FakeSiteFixtureTest should use ComposerInspector instead of ComposerUtility
  18. #3345768: StatusCheckTraitTest should use ComposerInspector instead of ComposerUtility
  19. #3345769: RequestedUpdateValidator should use ComposerInspector instead of ComposerUtility
  20. #3345771: VersionPolicyValidator should use ComposerInspector instead of ComposerUtility
  21. #3345772: \Drupal\automatic_updates\Form\UpdateReady should use ComposerInspector instead of ComposerUtility
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

phenaproxima created an issue. See original summary.

effulgentsia’s picture

+1 to this plan. I'd recommend creating a service for executing a composer command that can be used by #3, #4, #5, and other places. That way, if a contrib module wants to make things work for sites without proc_open, it would only need to override that one service, rather than a separate one for each of those. Such a contrib module could then use composer/composer to perform the command execution in process. I don't think there's going to be overlap between sites that disable proc_open and sites where a composer/composer dependency is problematic, so I think an approach where core doesn't depend on composer/composer but uses proc_open, and a separate contrib module is available for sites without proc_open but that can depend on composer/composer instead, is a good one.

effulgentsia’s picture

I'd recommend creating a service for executing a composer command

In fact, Composer Stager has such a ComposerRunnerInterface service, so might even make sense to use that one.

phenaproxima’s picture

Yeah, ComposerExecutableValidator already uses Composer Stager's ComposerRunner, so I think we'd just continue using that. To assist contrib (and, for that matter, make our own testing easier), we could wrap those calls in protected methods that could be overridden if some module wants to get the same information without running Composer.

phenaproxima’s picture

Issue summary: View changes
wim leers’s picture

Title: [meta] Remove our runtime dependency on composer/composer » [meta] Attempt to remove our runtime dependency on composer/composer
Assigned: Unassigned » tedbow
Priority: Normal » Critical
Issue tags: +core-mvp
Parent issue: » #3319030: Drupal Core Roadmap for Package Manager and Update Manager
Related issues: +#3243899: [policy] Move composer/composer from a dev dependency to a production dependency

Based on Drupal core release manager @catch confirming that this should indeed happen (if at all possible) on November 3 over at #3243899-14: [policy] Move composer/composer from a dev dependency to a production dependency, bumping this to Critical.

Assigning to @tedbow to confirm that @phenaproxima's proposed plan is still the plan to execute. Also: should we convert this from a Plan to a Task?

tedbow’s picture

Assigned: tedbow » Unassigned

Assigning to @tedbow to confirm that @phenaproxima's proposed plan is still the plan to execute.

Yes I think it is the way to go though obviously we will have see if it actually works out as planned as we try it.

Also: should we convert this from a Plan to a Task?

Should it still be plan because the work will be done in child issues?

wim leers’s picture

Should it still be plan because the work will be done in child issues?

Because this was titled [meta] …, which indeed makes it sound like this needs child issues. If it can/should all be done in a single issue, we should change it to Task and remove the title prefix.

tedbow’s picture

Issue tags: +sprint

Adding this to the sprint since it is core-mvp we should work on it

wim leers’s picture

Assigned: Unassigned » tedbow
Issue tags: +Needs issue summary update, +Needs title update

@tedbow: this is a meta/plan issue. Can you please make this more actionable?

Especially now that you pointed me here over at https://git.drupalcode.org/project/automatic_updates/-/merge_requests/54..., where I flagged \ComposerUtility::getInstalledPackagesData() not using the official composer API as problematic and you pointed to this issue as having a scope that includes getting rid of that.

But I don't see that mentioned here explicitly, only implicitly: I think we can instead rely on two things to get the information we need: […] The installed.php that Composer generates when packages are installed or updated. We already rely on this quite a bit in ComposerUtility. → this actually suggest to double down on the thing I called out as problematic 😱
Next, the issue summary says Eventually, I think we might even be able to drop the first item, since installed.php is technically internal to Composer, and rely solely on the output of Composer commands. But that could be done later.this is the thing I flagged as risky.

This tells me that this plan needs refining for it to become executable. With the list of remaining core-mvp issues shrinking, this is quickly becoming very important. Let's get this issue to an actionable state?

tedbow’s picture

#10 @Wim Leers sorry I just noticed the issue was not what I thought it was. Not sure if this from discussions with @phenaproxima that didn't get updated here. I will update the summary and get feedback from @phenaproxima

tedbow’s picture

Issue summary: View changes
tedbow’s picture

Issue summary: View changes
tedbow’s picture

Version: 8.x-2.x-dev » 3.0.x-dev
wim leers’s picture

effulgentsia’s picture

The issues in #16 look like they're relevant to removing a composer/composer dependency entirely. However, is there anything left unresolved before we can move composer/composer from require to require-dev? If not, it would be great to do that, close this issue, and open a separate meta for tracking all of the improvements to tests (if our other meta issues aren't already sufficient for that).

phenaproxima’s picture

However, is there anything left unresolved before we can move composer/composer from require to require-dev

I'd say so. Any use of ComposerUtility in our runtime code (and there's still a lot of that) necessitates a runtime dependency on composer/composer.

effulgentsia’s picture

Oh, sorry. I see now that #3337760: ComposerPatchesValidator should use ComposerInspector instead of ComposerUtility is runtime code, not just test code.

wim leers’s picture

Title: [meta] Attempt to remove our runtime dependency on composer/composer » Remove our runtime dependency on composer/composer: remove ComposerUtility
Assigned: tedbow » wim leers
Category: Plan » Task

wim leers’s picture

That confidence seems to have been pretty well-founded! But as I indicated in #16, this needs many more blocking issues, because we need to refactor away the many remaining uses of Stage::getActiveComposer() and Stage::getStageComposer(), as well as 2 remaining direct uses of ComposerUtility:

ComposerUtility

Stage::getActiveComposer()
Stage::getStageComposer()

The good news is that that should now be easy 😊🤞

wim leers’s picture

Assigned: wim leers » Unassigned
Status: Active » Postponed

This MR is currently +37, -1113, but won't be able to land until the following are done, to address #22:

  1. ScaffoldFilePermissionsValidator#3342227: ScaffoldFilePermissionsValidator should use ComposerInspector instead of ComposerUtility
  2. ComposerPatchesValidator#3337760: ComposerPatchesValidator should use ComposerInspector instead of ComposerUtility
  3. Updater#3345754: Updater should use ComposerInspector instead of ComposerUtility
  4. ExtensionUpdater#3345755: ExtensionUpdater should use ComposerInspector instead of ComposerUtility
  5. \Drupal\automatic_updates_extensions\Form\UpdateReady#3345757: \Drupal\automatic_updates_extensions\Form\UpdateReady should use ComposerInspector instead of ComposerUtility
  6. \Drupal\automatic_updates_extensions\Form\UpdaterForm#3345760: \Drupal\automatic_updates_extensions\Form\UpdaterForm should use ComposerInspector instead of ComposerUtility
  7. UpdateReleaseValidator#3345761: UpdateReleaseValidator should use ComposerInspector instead of ComposerUtility
  8. GitExcluder#3345762: GitExcluder should use ComposerInspector instead of ComposerUtility
  9. UnknownPathExcluder#3345763: UnknownPathExcluder should use ComposerInspector instead of ComposerUtility
  10. OverwriteExistingPackagesValidator#3345764: OverwriteExistingPackagesValidator should use ComposerInspector instead of ComposerUtility
  11. SupportedReleaseValidator#3345765: SupportedReleaseValidator should use ComposerInspector instead of ComposerUtility
  12. CollectIgnoredPathsFailValidator#3345766: CollectIgnoredPathsFailValidator should use ComposerInspector instead of ComposerUtility
  13. FakeSiteFixtureTest#3345767: FakeSiteFixtureTest should use ComposerInspector instead of ComposerUtility
  14. StatusCheckTraitTest#3345768: StatusCheckTraitTest should use ComposerInspector instead of ComposerUtility
  15. RequestedUpdateValidator#3345769: RequestedUpdateValidator should use ComposerInspector instead of ComposerUtility
  16. VersionPolicyValidator#3345771: VersionPolicyValidator should use ComposerInspector instead of ComposerUtility
  17. \Drupal\automatic_updates\Form\UpdateReady#3345772: \Drupal\automatic_updates\Form\UpdateReady should use ComposerInspector instead of ComposerUtility
wim leers’s picture

tedbow’s picture

I think any issue not in package_manager but in automatic_updates can be marked as core-post-mvp
Worst case scenario we could move ComposerUtility into automatic_updates as this will not be going into core first
so please work on package_manager issues first

wim leers’s picture

Sure, but … until the ones in AU/AUE are removed … we cannot actually remove the composer/composer dependency 🙈 So not impossible, but definitely would leave things in a confusing state, although #3341974: Finalize \Drupal\automatic_updates\Development\Converter script to update core MR could compensate for it for the package_manager-only MR.

wim leers’s picture

Title: Remove our runtime dependency on composer/composer: remove ComposerUtility » [PP-14] Remove our runtime dependency on composer/composer: remove ComposerUtility
tedbow’s picture

Issue summary: View changes
phenaproxima’s picture

Title: [PP-14] Remove our runtime dependency on composer/composer: remove ComposerUtility » [PP-11] Remove our runtime dependency on composer/composer: remove ComposerUtility
phenaproxima’s picture

Title: [PP-11] Remove our runtime dependency on composer/composer: remove ComposerUtility » [PP-10] Remove our runtime dependency on composer/composer: remove ComposerUtility
joachim’s picture

> We currently have a runtime dependency on composer/composer. This is mainly so we can invoke Composer's API to get information about what packages are currently installed (in either the active directory or staging area). The bulk of that takes place in Package Manager's ComposerUtility class.

We could depend on https://github.com/joachim-n/composer-manifest to know which packages are currently installed.

Would maybe want to add an API to that so that the list of Composer packages gets stored somewhere under Drupal's control.

tedbow’s picture

@joachim very interesting!

What if someone runs a command from the terminal with `--no-plugins`? I don't think we could stop that. Won't this list then be out of date because the yml file not be updated? I guess maybe to get around that you could store a hash of the `composer.lock` in the yml file from that last time the .yml was updated and then anyone reading the file could check it is up-to-date. But still it would be out of date whether you detect I am not sure helps

wim leers’s picture

Title: [PP-10] Remove our runtime dependency on composer/composer: remove ComposerUtility » [PP-8] Remove our runtime dependency on composer/composer: remove ComposerUtility
phenaproxima’s picture

Title: [PP-8] Remove our runtime dependency on composer/composer: remove ComposerUtility » [PP-6] Remove our runtime dependency on composer/composer: remove ComposerUtility
wim leers’s picture

Title: [PP-6] Remove our runtime dependency on composer/composer: remove ComposerUtility » [PP-5] Remove our runtime dependency on composer/composer: remove ComposerUtility
wim leers’s picture

Title: [PP-5] Remove our runtime dependency on composer/composer: remove ComposerUtility » [PP-4] Remove our runtime dependency on composer/composer: remove ComposerUtility
phenaproxima’s picture

Title: [PP-4] Remove our runtime dependency on composer/composer: remove ComposerUtility » [PP-3] Remove our runtime dependency on composer/composer: remove ComposerUtility
wim leers’s picture

Title: [PP-3] Remove our runtime dependency on composer/composer: remove ComposerUtility » [PP-2] Remove our runtime dependency on composer/composer: remove ComposerUtility
wim leers’s picture

Title: [PP-2] Remove our runtime dependency on composer/composer: remove ComposerUtility » [PP-1] Remove our runtime dependency on composer/composer: remove ComposerUtility
phenaproxima’s picture

Title: [PP-1] Remove our runtime dependency on composer/composer: remove ComposerUtility » [PP-2] Remove our runtime dependency on composer/composer: remove ComposerUtility
Issue summary: View changes
phenaproxima’s picture

Title: [PP-2] Remove our runtime dependency on composer/composer: remove ComposerUtility » Remove our runtime dependency on composer/composer: remove ComposerUtility
Assigned: Unassigned » phenaproxima
Status: Postponed » Needs work

The final blockers are in: #3342227: ScaffoldFilePermissionsValidator should use ComposerInspector instead of ComposerUtility and #3345633: Remove FixtureManipulator::modifyPackage() last usage.

I haven't got #3347031: Stage::validatePackageNames() should not use the Composer API done yet, but I don't think we need to wait for that. Here's why: Composer won't accept a bad constraint, as far as I know. Even if you do composer require --no-update. So although it's unsophisticated, I think the very, very basic validation we have in this merge request is sufficient. In #3347031: Stage::validatePackageNames() should not use the Composer API, I'll restore it by checking against a regex that Composer itself supplies, and using the VersionParser class, which is a safe runtime dependency provided by core.

But I don't see any reason we should keep ComposerUtility around a day longer. Self-assiging to finish this now and rip it out.

phenaproxima’s picture

Hmmm...we might still be blocked by validatePackageNames(). The basic str_contains($package_name, '/') validation is not good enough, because it doesn't allow for platform packages, or other legitimate requirements that Composer supports which don't follow that format.

The original plan outlined in the issue summary -- writing the requirements to an alternate composer.json and calling validate on it -- might be the best approach here.

phenaproxima’s picture

Title: Remove our runtime dependency on composer/composer: remove ComposerUtility » [PP-1] Remove our runtime dependency on composer/composer: remove ComposerUtility
Assigned: phenaproxima » Unassigned
Status: Needs work » Postponed

Yeah, sorry, folks -- this is blocked by #3347031: Stage::validatePackageNames() should not use the Composer API.

However, I'm leaving that one in a reviewable state. Once it's committed, the MR here should pass without any problem.

wim leers’s picture

There are a bunch of mentions to installed.json and installed.php left after this MR:

  • 99% of them occur in FixtureManipulatorTest (for example to verify that the original fixture's installed.json and installed.php are unchanged)
  • 1% in TemplateProjectTestBase (to help generate a composer project from scratch for Build tests)

I think these can all be factored away, but … that doesn't block removing the composer/composer dependency, so that's out of scope for this issue. So created follow-up issues for both:

wim leers’s picture

Title: [PP-1] Remove our runtime dependency on composer/composer: remove ComposerUtility » Remove our runtime dependency on composer/composer: remove ComposerUtility
Assigned: Unassigned » wim leers
Status: Postponed » Needs work
wim leers’s picture

Status: Needs work » Needs review

Turns out that those failures are being triggered by @phenaproxima's nice simplification in \Drupal\fixture_manipulator\FixtureManipulator::removePackage() … but that simplification was right!

Apparently somehow composer require some/package --no-update works even for require-dev packages, but only if there's a separate subsequent composer update!

@phenaproxima changed it to do composer require some/package (without the subsequent composer update), and that now fails. This is simple enough to fix though: make sure we pass TRUE to FixtureManipulator::removePackage() calls if they're for require-dev packages.

Fixed in the commit I just pushed!

wim leers’s picture

Assigned: wim leers » Unassigned
Status: Needs review » Reviewed & tested by the community

There is not a single contentious thing in here. It's deleting code that's effectively unused in HEAD.

The diffstat speaks for itself:

+25 -967

The only "change" that is introduced here is described in #46. And adding docs. Those are the last 2 commits. They're tiny in the grand scheme of things.

@phenaproxima did not spot problems in his reviews last night (see #40#43).

Therefore, going ahead and merging this, so we can get a green core merge request going at #3346707: Add Alpha level Experimental Package Manager module! 🚀

  • Wim Leers committed ec8949c9 on 3.0.x
    Issue #3316368 by Wim Leers, phenaproxima, tedbow: Remove our runtime...
wim leers’s picture

Status: Reviewed & tested by the community » Fixed

🚢

Status: Fixed » Closed (fixed)

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