Follow-up to #2745355: Use "composer install --no-dev" to create tagged core packages
Problem/Motivation
Drupal.org no longer packages composer dev dependencies (--no-dev) in the tarball release, because dev dependencies are not intended to be present on production sites. If a production site contains dev dependencies, that is probably unintentional and could be bad.
In fact, Drupal core 8.2.7 is a security release which was necessary because dev dependencies were present in the production site: https://www.drupal.org/project/drupal/releases/8.2.7
We should warn site admins if dev dependencies are installed.
Proposed resolution
Some upstream help: https://github.com/composer/composer/issues/3008
Add composer.project_root and composer.dev_dependency_finder services.
composer.project_root gathers information about the current Composer install and detects if it is a dev installed based on the installed.json file provided by Composer 2.
composer.dev_dependency_finder builds the requirements based on this information and can be called by SystemRequirementsHooks.
Remaining tasks
User interface changes
Added a new ProjectRootInterface that is implemented by the new composer.project_root service.
API changes
Data model changes
| Comment | File | Size | Author |
|---|---|---|---|
| #71 | 2830880-nr-bot.txt | 146 bytes | needs-review-queue-bot |
Issue fork drupal-2830880
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
cilefen commentedComment #3
mile23There's at least one other composer-related status page issue.
Comment #4
mile23Here's a patch which makes a best-guess effort at determining whether core's dev dependencies are installed.
This works now, but won't when
drupal/coreis a subtree split, unless we move all of core's dev requirements to the root project dependencies.Still to do: Tests.
Comment #7
mile23lazy: badComment #8
mile23Comment #9
jibranWhat if vendor is outside doc root? As described in Moving all PHP files out of the docroot
Comment #10
mile23If vendor is outside doc root, then the status page won't find any dev requirements in installed.json, and won't warn the user that they have dev requirements even if they do.
We can't handle all the special cases here. Unless we can which I'd love to see a patch for. :-) But if you have a special requirement and need for this status to report the right thing, it's all in services which you can replace. So a composer-based distro which stores vendor somewhere else could include a module to swap out services.
Comment #12
mile23Hello, https://www.drupal.org/SA-2017-001
One of the vulnerabilities documented there is exploiting the existence of phpunit on the production site.
Comment #13
mile23Reroll.
Still need to address the concerns of #9.
Comment #14
MixologicWhat if we just, uh, massage the scope a little? Do we need to warn if they are installed outside of docroot?
Comment #15
mile23Sure, that's why you put vendor/ outside of docroot. :-) If vendor/ isn't where ProjectRoot expects it, it stops trying to figure out the dev requirements.
There are other reasons why you'd want to avoid putting dev stuff on production, such as the giant classmap for phpunit that eats up memory, so we should figure out how to tell people even if the vendor is somewhere else. I'm not sure what the solution for that is.
Comment #16
mile23Added some tests which led to some tighter code in ProjectRoot. Other misc tidying.
Comment #17
mile23Patch still applies, re-running test.
Comment #18
mile23Updated summary.
Comment #19
mradcliffe1. Testbot found:
core/tests/Drupal/KernelTests/Core/Composer/Dependencies/DevDependencyFinderTest.php
line 52 Expected 1 space after FOREACH keyword; 0 found
2. Should the latter also check contents FALSE? Or the former not do so at all?
3. Tests should be marked @internal. And I think DevDependencyFinder should be as well since it doesn't implement an interface like ProjectRoot class.
---
I think that there shouldn't be any issue with adding three file reads in system requirements. The approach looks okay.
Comment #21
groovedork commented.
Comment #22
mile23Re: #19: Fixed CS, added @internal to tests, though I'm pretty sure they don't need it. Added @internal to DevDependencyFinder. There's no interface for DevDependencyFinder because the only variation we want is for people to re-implement the project root finder. Or look for common places in a follow-up.
Rerolled for 8.5.x. Rock on.
Comment #23
wim leersComment #24
kyvour commentedI've made a quick look for the last patch (#22) and I think that patch need work.
1. Drupal root and composer root are same only in the default case but this might be changed in future (https://www.drupal.org/node/2385387). Also drupal-project is widely used and this project has a different composer and drupal roots.
So dev-dependencies should be searched only if composer root is the same as drupal root.
2. Vendor dir is 'COMPOSER_ROOT/vendor' in the default case only. Vendor dir can be specified in composer.json file and might be changed by developer in any moment. In this case composer.project_root service won't work as designed.
I suggest to use drupal-finder package for this service. You can check a patch for composer unit tests - https://www.drupal.org/node/2545344#comment-12245148 . I used DrupalFinder class there so it may be helpful for you.
Comment #25
mile23A few different commenters here have talked about arbitrary vendor directories.
We can't cover all use-cases, so for now we can talk about the default location.
That issue #2545344-30: Drupal\Tests\ComposerIntegrationTest assumes there is a composer.json in DRUPAL_ROOT has some work to do, since there are a lot of failing tests. But if the drupal-finder package makes it in within that issue, then we can have a follow-up to modify the Composer services to use it.
Comment #26
kyvour commentedDrupal finder allow easily use composer root, drupal root and vendor directory. And it handles the case with custom vendor dir in composer.json. Here is an example:
Comment #27
kyvour commentedBut we can...
Comment #28
kyvour commentedAs for issue #2545344-30 there was a problem, that aren't related to FrupalFinder, but in any case, thanks for your note
Comment #29
kyvour commentedI've updated patch #22 to use DrupalFinder.
Needs review.
Comment #30
kyvour commentedSeems like I missed update the tests in the previous patch.
There is new one.
Comment #31
mile23We don't need this since we already have the app.root service.
And really, that's the problem I find with drupal-finder: We don't need to find the app root. In fact, we want to NOT find the app root since we already know it. We want to find composer/vendor root relative to it. We're not allowed to supply app root to DrupalFinder, so it goes about discovering a directory we could have supplied. If its discovery method develops a bug, or core changes such that DrupalFinder can't find it, then we could end up in a situation where we never find a valid installed.json.
So the problem of infinitely configurable composer directories still remains, albeit now with an external library instead of one we can change in core. In fact, even if we were to require composer itself, we couldn't use it to discover all this stuff.
But.... I thought about it over dinner and realized that we have one trick up our sleeve due to the way Drupal does autoloading. There is (supposed to) always be a DRUPAL_ROOT/autoload.php file, even in drupal-project. This file returns an object that is autogenerated by Composer in a consistent way, and we can use reflection to find out where it's located. From that, we will glean the vendor directory.
So here's that version, based off #22.
This version solves the problem of where to find vendor/, but still hard-codes the merge of core's two composer.json files. That's because even if someone supplies a composer.json file somewhere way off in outer space, we still have core's two files, and they still give us enough to determine whether dev dependencies are installed or not, for most cases. Edge cases can be follow-ups.
Note that there really isn't any way to find an arbitrary composer.json file, because a) Composer does not store this information, and b) You can use a composer.json file like this:
$ COMPOSER=../../../../../../../someplace/somewhere/composer.json composer installComment #33
mile23That feeling when you thought you ran the tests locally again after the last-minute change.
Also, fixing CS errors.
Comment #34
borisson_Awesome! Introducing this as @internal is a great idea, however I think we can do the same for the ProjectRoot class.
Do we fix this @todo in this issue or in a followup? If we do this in a followup we should create one and link to it from the comment.
The same number as what?
Oh, I see it's the same number of installed dev dependencies as required dev dependencies. Why do we do this?
Shouldn't the empty check be sufficient? I mean, it doesn't matter how many of those deps are installed, even 1 is too many.
If we decided to keep this check, can we at least use strict equals?
Personal preference: I try to remove as much of the
else {}-blocks I can find. This one can be removed by setting it in the initial $requirements array and only overwriting if we find installed dev dependencies.I understand that this is why we created this class, however that's not the actual thing that this class does, it provides information about the project root and where the vendor directory is located.
Can we update this docblock?
Needs an additional newline in between those.
Needs a docblock
Very micro optimisation.
Same remark as the other remark about description.
Let's do this todo. Should be as simple as changing the baseclass and moving the file.
Comment #35
mile23Thanks, @borrison_
I think this gets pretty much covers everything except #10. We can't do that since the system under test still has a hard requirement on install.inc.
Here's an updated inline docblock, which should give some indication as to why we should be extremely limited in the types of promises we make about Composer in core:
Comment #36
borisson_This looks good to me, I think this is ready. Not sure if I can RTBC this or if we should wait on more reviews. You can set it back to NR if needed.
Comment #37
mile23Other folks will come along and disagree as needed. :-)
Thanks.
Comment #38
larowlanI love this issue, great work @Mile23. Couple of observations
Do you think we should cache this with a tag that includes the deployment indicator. hook requirements runs a lot when admins are using the admin areas. I think this is unlikely to change unless a deployment occurs? See \Drupal\system\Controller\SystemController::overview
Any reason not to use
nit, no need for the !empty here, just
!$installed_devwill doShouldn't this use strict comparison
0 would evaluate to FALSE
I'd make this a requirements error, on security grounds. But that's my personal preference.
I think our future selves would appreciate a comment here.
It's clear what we're doing here now, but may not be later.
Suggest something similar to what you have in the comment above
The comment here doesn't meet our standards. Can we specify what we intend to change in the future?
this sentence doesn't make sense, I think the word if isn't needed?
Can we get an issue created for this and linked here.
Great work here.
Comment #39
mile23Thanks.
Good calls. :-) Just a few things:
#38.1: The status of this warning can change on a filesystem change. The only way to reliably invalidate the cache would be to do the discovery again. Similar problem to #1667822: Remove caching of test classes in TestDiscovery
#38.5: If you don't want people to be able to install or update a site with dev dependencies present, then yah, we can make it an error. That means whenever you do dev work you won't be able to install from your codebase unless you say
composer install --no-dev. This would prevent running tests until you installed again. Also:BrowserTestBasemight need to be able to install Drupal. :-)#38.7: I'll just remove the comment. We can change things as needed later.
#38.9: @see for new issue: #2909480: Move REQUIREMENT_* constants out of install.inc file
Comment #40
larowlan#38.5 - yep you're right, ignore me
Comment #41
borisson_Let's get this back to RTBC. #39 resolves everything asked for in #38.
Comment #42
xjmIt's recently (okay, six months ago) come to my attention that we overuse
REQUIREMENTS_WARNING: see #2880439: Status report requirements should be more defined and #2877128: [META] Evaluate status report warnings and determine which should be reduced to info, removed, etc.. So far the consensus there comes down to it a distinction between whether something might be wrong, versus something being actually wrong that needs to be fixed.In the case of this issue, 8.2.7 resolved a known/disclosed security issue with a dev dependency. Something was definitely wrong before 8.2.7, but for 8.4.x it might be in the "might be wrong" category. That said, the dev dependencies thing was serious enough to require a PSA, an SA, and d.o infrastructure changes previously, so I'm still leaning toward warning for this one in particular.
Leaving at RTBC while I get a second opinion on it. Thanks!
Comment #43
xjmThis is tagged as a blocker, but @Wim Leers provided no explanation when so tagging it. What is it blocking?
Comment #44
cilefen commented@xjm This seems on par with not setting trusted host or needing to upgrade for security reasons, both of which are errors.
Comment #45
cilefen commentedRe: #38.5, switching to REQUIREMENTS_ERROR does not inhibit installing in any way.
Comment #46
xjmI missed where @larowlan said "this is almost an error because it's so serious"; I think that (plus the fact that @cilefen posted it in the first place) is motivation/committer signoff for it to be at least a warning. So +1 for the current approach. As @cilefen said, while we don't know for sure that anything is wrong, "you have been warned."
Comment #47
xjmHum, the patch is adding a lot more code and API surface than I was expecting for a simple (maybe not totally simple) requirements warning. E.g., do we really need a ProjectRootInterface?
Comment #48
mile23The idea was that this is a soft-ish blocker for #2494073-330: Prevent modules which have unmet Composer dependencies from being installed (300+ comments and in need of issue summary love) because whatever we decide on this issue makes it easier to decide things on that one.
So adding a project root and other services would be a step in the direction of helping resolve whether modules need composer dependencies and so forth. An interface for project root would enable different discovery services as needed.
I'm not sure how all this fits with #2912406: [META] Replace update_manager with a more powerful solution which seems to be the latest plan around composer, AFAICT. We could turn the composer services into a separate issue and link them in to the process on #2912406: [META] Replace update_manager with a more powerful solution
This issue still addresses a security concern, and it's easy enough to adapt to any new discovery method, and we can have it send an error instead of warning.
Comment #49
xjmSo, if this includes something we want added as API, let's do so in a dedicated issue that can discuss the short-term and long-term goals, and then just scope this issue to just be adding the message and implementing whatever API was reviewed and added elsewhere. DevDependencyFinder is also a pattern we haven't seen before -- basically building out a hook implementation with a value object. There's something there.
Being able to do this:
It's pretty cool, hey! But overall this feels over-architected for the purposes of this issue at least. Based on my reservations I'm tagging for framework managers to look at it too. Leaving RTBC though because I would also like to get the scope described in the summary in ASAP.
Comment #50
alexpottThis doesn't reflect that we are only caring about core.
Why do we only care about Core? I've read the issue summary and that's not really explained as far as I can see. I would have thought that contrib dev requirements being installed is also problematic.
Variables named
$dev_installedand$installed_devdon't make for easy code reading.I'm not sure why a single dev dependency that satisfies the conditions should prevent the warning.
It's weird that so much requirement stuff is bleeding into the service. Well I guess that's the entire point of the service. In some ways, I think the ProjectRoot service should have a method like hasDevRequirementsInstalled() that just returns either a Boolean as in or a list of the dev requirements installed. And this stuff could be in system_requirements().
Also for me what's odd is that this method is doing two things. One is working out if dev requirements are installed and the second is constructing the requirement array. Why isn't the first thing part of the ProjectRoot class?
If we're going to have a requirement builder service, I think the requirement building would simpler if we were just returning the single requirement. Ie. in system_requirements() just doing:
Not sure about the word
appearhere. It makes it seem very cautious which makes it very hard for a user to know what to do. Even in the warning message we use the word appear too. This could leave the user thinking how do I ensure that I've not got dev dependencies installed. Also as above we're only checking core's dev dependencies - but the message doesn't say this so you might have a module's dev dependencies installed but the requirements are telling you you've got none installed.I think ProjectRoot is an odd name for what this class does. I think Project is kinda overloaded already - we have \Drupal\Core\Utility\ProjectInfo - which
Performs operations on drupal.org project data.. How about ComposerInfo or something like that.Why not
getRequireDevso it is inline with the key you're getting. Less to learn.Comment #51
mile23It's not that we only care about core. It's that the only require-dev dependencies we can determine are from drupal/drupal (and through merge plugin, drupal/core) because that's how Composer works.
It could be that contrib has a non-dev requirement that is also a core dev requirement. That's why we say 'appears.'
The rest of the review is good stuff.
ProjectRootis a thing because it was going to help out with [#12244933] so it's structured as a separate service. Now we have #2912406: [META] Replace update_manager with a more powerful solution so it's unclear what's actually needed in this issue or that one.Comment #52
borisson_The new update manager seems to have slowed down a bit. But even if it was still active, I think that initiative can make use of the
ProjectRoot. So I would love to get this patch in. This improvement can be really helpful for those that are not going to use the new update manager.Comment #54
mile23Patch in #39 still applies. Re-testing. Giving a poke because of interest in #2494073: Prevent modules which have unmet Composer dependencies from being installed from #1398772: Replace .info.yml with composer.json for extensions
The biggest outstanding issue here is @xjm's scope issue of #49, so if there's some kind of more specific guidance as to what to do to move forward that'd be great.
We have a composer initiative happening, in order to support a lot of different initiative changes: #2958021: Proposal: Composer Support in Core initiative It'd be great to be able to provide this service so core can do some Composer introspection and support all those other things.
Comment #57
xjmComment #58
mile23Reroll.
We still have an outstanding review from #50 which need to be addressed.
We still have issues of scope from #49. Some commenters here seem to want the services, but those needs were expressed in 2017. :-)
The composer initiative doesn't seem to require that we have these things at the moment.
Comment #59
mile23And here I thought I had added the patch already.Never mind.
Weird. I need to go eat or something.
Comment #60
alonaoneill commentedPatch applied!
Comment #61
cilefen commented@alonaoneill The testbots validate patches automatically.
Comment #62
mile23Composer has an upstream issue about storing some of this info in installed.json: https://github.com/composer/composer/issues/3008
That's great for new sites after Composer 2.0 is released, but old sites would still need the complex logic here.
Comment #63
mile23Comment #71
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It either no longer applies to Drupal core, or fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".
Apart from a re-roll or rebase, this issue may need more work to address feedback in the issue or MR comments. To progress an issue, incorporate this feedback as part of the process of updating the issue. This helps other contributors to know what is outstanding.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #75
prudloff commentedI think this is still relevant.
I opened a MR based on the latest patch.
Comment #76
prudloff commentedComment #77
borisson_Composer 2 is now a required part of installing drupal. So I think we can use that information in installed.json instead of doing all the logic in this patch?
Comment #78
smustgrave commentedCan #77 be explored
Comment #79
alexpottYes we can read that json file and usage the 'dev' key. Another possibility would be to add this to \Drupal\Composer\Plugin\Scaffold\DrupalInstalledTemplate::getCode() as that is generated when you run composer install. That way we wouldn't be dependent on Composer's JSON. However, that has an iinteresting issue that you need to run composer install --no-dev (or composer install) twice to switch from one to the other.... not sure why and that's a bug.
Comment #81
prudloff commentedI updated the MR to get the info from the dev key in installed.json.
Comment #82
smustgrave commentedLeft some comments on the MR. Tagging for a CR for the new interface. Summary probably needs to be updated to mention that too.
Thanks.
Comment #83
prudloff commentedI made the suggested changes and added a CR.
Comment #84
alexpottI think we don't need all the stuff in Drupal\Core\Composer we can just do
\Composer\InstalledVersions::getRootPackage()['dev'];to find out if the project has dependencies installed and put all the requirements stuff in the hook class. Less complexity. We can test this with a build test - see \Drupal\BuildTests\Framework\Tests\HtRouterTest::testHtRouter for an example.Comment #85
smustgrave commentedThanks! Based on the feedback moving to NW