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:
- #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.
- 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.
- 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. - #3316668: ComposerSettingsValidator should run `composer config` to determine if HTTPS is enabled
- #3337760: ComposerPatchesValidator should use ComposerInspector instead of ComposerUtility
- #3342227: ScaffoldFilePermissionsValidator should use ComposerInspector instead of ComposerUtility
- #3345754: Updater should use ComposerInspector instead of ComposerUtility
- #3345755: ExtensionUpdater should use ComposerInspector instead of ComposerUtility
- #3345757: \Drupal\automatic_updates_extensions\Form\UpdateReady should use ComposerInspector instead of ComposerUtility
- #3345760: \Drupal\automatic_updates_extensions\Form\UpdaterForm should use ComposerInspector instead of ComposerUtility
- #3345761: UpdateReleaseValidator should use ComposerInspector instead of ComposerUtility
- #3345762: GitExcluder should use ComposerInspector instead of ComposerUtility
- #3345763: UnknownPathExcluder should use ComposerInspector instead of ComposerUtility
- #3345764: OverwriteExistingPackagesValidator should use ComposerInspector instead of ComposerUtility
- #3345765: SupportedReleaseValidator should use ComposerInspector instead of ComposerUtility
- #3345766: CollectIgnoredPathsFailValidator should use ComposerInspector instead of ComposerUtility
- #3345767: FakeSiteFixtureTest should use ComposerInspector instead of ComposerUtility
- #3345768: StatusCheckTraitTest should use ComposerInspector instead of ComposerUtility
- #3345769: RequestedUpdateValidator should use ComposerInspector instead of ComposerUtility
- #3345771: VersionPolicyValidator should use ComposerInspector instead of ComposerUtility
- #3345772: \Drupal\automatic_updates\Form\UpdateReady should use ComposerInspector instead of ComposerUtility
| Comment | File | Size | Author |
|---|---|---|---|
| #22 | Screen Shot 2023-03-03 at 12.45.29 PM.png | 207.83 KB | wim leers |
| #22 | Screen Shot 2023-03-03 at 12.45.22 PM.png | 463.28 KB | wim leers |
| #22 | Screen Shot 2023-03-03 at 12.42.00 PM.png | 90.7 KB | wim leers |
Issue fork automatic_updates-3316368
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
Comment #2
effulgentsia commented+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.
Comment #3
effulgentsia commentedIn fact, Composer Stager has such a
ComposerRunnerInterfaceservice, so might even make sense to use that one.Comment #4
phenaproximaYeah, 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.Comment #5
phenaproximaComment #6
wim leersBased 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 .
Assigning to @tedbow to confirm that @phenaproxima's proposed plan is still the plan to execute. Also: should we convert this from a to a ?
Comment #7
tedbowYes 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.
Should it still be plan because the work will be done in child issues?
Comment #8
wim leersBecause 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 and remove the title prefix.Comment #9
tedbowAdding this to the sprint since it is core-mvp we should work on it
Comment #10
wim leers@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 officialcomposerAPI 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: → this actually suggest to double down on the thing I called out as problematic 😱
Next, the issue summary says → 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-mvpissues shrinking, this is quickly becoming very important. Let's get this issue to an actionable state?Comment #11
wim leersThe MR #10 links to is #3315834: GitExcluder should not ignore .git directories that belong to packages installed by Composer.
Comment #12
tedbow#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
Comment #13
tedbowComment #14
tedbowComment #15
tedbowComment #16
wim leers#3334994: Add new InstalledPackagesList which does not rely on Composer API to get package info landed.
#3337760: ComposerPatchesValidator should use ComposerInspector instead of ComposerUtility needs many more sibling issues. #3342227: ScaffoldFilePermissionsValidator should use ComposerInspector instead of ComposerUtility is the only one that exists so far — we need those for all validators that currently use
ComposerUtility— as mentioned in#autoupdatesDrupal Slack yesterday.Comment #17
effulgentsia commentedThe issues in #16 look like they're relevant to removing a
composer/composerdependency entirely. However, is there anything left unresolved before we can movecomposer/composerfromrequiretorequire-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).Comment #18
phenaproximaI'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.Comment #19
effulgentsia commentedOh, sorry. I see now that #3337760: ComposerPatchesValidator should use ComposerInspector instead of ComposerUtility is runtime code, not just test code.
Comment #20
wim leersPer #3343827-29: Update FixtureManipulator to work with InstalledPackagesList, real composer show command, #3337760: ComposerPatchesValidator should use ComposerInspector instead of ComposerUtility and #3342227: ScaffoldFilePermissionsValidator should use ComposerInspector instead of ComposerUtility are now unblocked.
While working on #3343827: Update FixtureManipulator to work with InstalledPackagesList, real composer show command, I got pretty confident that we should be able to remove all things
ComposerInspectorafter those land. Let's see how well-founded that confidence was…Comment #22
wim leersThat 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()andStage::getStageComposer(), as well as 2 remaining direct uses ofComposerUtility:ComposerUtilityStage::getActiveComposer()Stage::getStageComposer()The good news is that that should now be easy 😊🤞
Comment #23
wim leersThis MR is currently
+37, -1113, but won't be able to land until the following are done, to address #22:ScaffoldFilePermissionsValidator→ #3342227: ScaffoldFilePermissionsValidator should use ComposerInspector instead of ComposerUtilityComposerPatchesValidator→ #3337760: ComposerPatchesValidator should use ComposerInspector instead of ComposerUtilityUpdater→ #3345754: Updater should use ComposerInspector instead of ComposerUtilityExtensionUpdater→ #3345755: ExtensionUpdater should use ComposerInspector instead of ComposerUtility\Drupal\automatic_updates_extensions\Form\UpdateReady→ #3345757: \Drupal\automatic_updates_extensions\Form\UpdateReady should use ComposerInspector instead of ComposerUtility\Drupal\automatic_updates_extensions\Form\UpdaterForm→ #3345760: \Drupal\automatic_updates_extensions\Form\UpdaterForm should use ComposerInspector instead of ComposerUtilityUpdateReleaseValidator→ #3345761: UpdateReleaseValidator should use ComposerInspector instead of ComposerUtilityGitExcluder→ #3345762: GitExcluder should use ComposerInspector instead of ComposerUtilityUnknownPathExcluder→ #3345763: UnknownPathExcluder should use ComposerInspector instead of ComposerUtilityOverwriteExistingPackagesValidator→ #3345764: OverwriteExistingPackagesValidator should use ComposerInspector instead of ComposerUtilitySupportedReleaseValidator→ #3345765: SupportedReleaseValidator should use ComposerInspector instead of ComposerUtilityCollectIgnoredPathsFailValidator→ #3345766: CollectIgnoredPathsFailValidator should use ComposerInspector instead of ComposerUtilityFakeSiteFixtureTest→ #3345767: FakeSiteFixtureTest should use ComposerInspector instead of ComposerUtilityStatusCheckTraitTest→ #3345768: StatusCheckTraitTest should use ComposerInspector instead of ComposerUtilityRequestedUpdateValidator→ #3345769: RequestedUpdateValidator should use ComposerInspector instead of ComposerUtilityVersionPolicyValidator→ #3345771: VersionPolicyValidator should use ComposerInspector instead of ComposerUtility\Drupal\automatic_updates\Form\UpdateReady→ #3345772: \Drupal\automatic_updates\Form\UpdateReady should use ComposerInspector instead of ComposerUtilityComment #24
wim leersComment #25
tedbowI 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
Comment #26
wim leersSure, 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.Comment #27
wim leersLanded:
That leaves 14. Reflecting that in the title.
Comment #28
tedbowComment #29
phenaproxima#3345772: \Drupal\automatic_updates\Form\UpdateReady should use ComposerInspector instead of ComposerUtility, #3345766: CollectIgnoredPathsFailValidator should use ComposerInspector instead of ComposerUtility and #3345754: Updater should use ComposerInspector instead of ComposerUtility are in. Lowering the count...
Comment #30
phenaproxima#3345767: FakeSiteFixtureTest should use ComposerInspector instead of ComposerUtility landed.
Comment #31
joachim commented> 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.
Comment #32
tedbow@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
Comment #33
wim leersLanded:
Comment #34
phenaproximaJoined by:
#3345769: RequestedUpdateValidator should use ComposerInspector instead of ComposerUtility
#3345755: ExtensionUpdater should use ComposerInspector instead of ComposerUtility
Comment #35
wim leers#3345761: UpdateReleaseValidator should use ComposerInspector instead of ComposerUtility landed.
Comment #36
wim leers#3345760: \Drupal\automatic_updates_extensions\Form\UpdaterForm should use ComposerInspector instead of ComposerUtility landed.
Comment #37
phenaproximaAs did #3337760: ComposerPatchesValidator should use ComposerInspector instead of ComposerUtility.
Comment #38
wim leers#3345762: GitExcluder should use ComposerInspector instead of ComposerUtility landed.
Comment #39
wim leers#3345763: UnknownPathExcluder should use ComposerInspector instead of ComposerUtility landed.
Comment #40
phenaproximaAdded #3347031: Stage::validatePackageNames() should not use the Composer API.
Comment #41
phenaproximaThe 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.
Comment #42
phenaproximaHmmm...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
validateon it -- might be the best approach here.Comment #43
phenaproximaYeah, 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.
Comment #44
wim leersThere are a bunch of mentions to
installed.jsonandinstalled.phpleft after this MR:FixtureManipulatorTest(for example to verify that the original fixture'sinstalled.jsonandinstalled.phpare unchanged)TemplateProjectTestBase(to help generate a composer project from scratch forBuildtests)I think these can all be factored away, but … that doesn't block removing the
composer/composerdependency, so that's out of scope for this issue. So created follow-up issues for both:Comment #45
wim leers#3347031: Stage::validatePackageNames() should not use the Composer API is in, updating this MR… 🥳
Comment #46
wim leersTurns 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-updateworks even forrequire-devpackages, but only if there's a separate subsequentcomposer update!@phenaproxima changed it to do
composer require some/package(without the subsequentcomposer update), and that now fails. This is simple enough to fix though: make sure we passTRUEtoFixtureManipulator::removePackage()calls if they're forrequire-devpackages.Fixed in the commit I just pushed!
Comment #47
wim leersThere is not a single contentious thing in here. It's deleting code that's effectively unused in HEAD.
The diffstat speaks for itself:
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! 🚀
Comment #49
wim leers🚢