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:
- Does an extension require composer to be used to install its dependencies?
- 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.
| Comment | File | Size | Author |
|---|---|---|---|
| #349 | drupal-2494073-349.interdiff.txt | 5.73 KB | gapple |
| #349 | drupal-2494073-349.patch | 27.78 KB | gapple |
| #323 | interdiff.txt | 3.03 KB | mile23 |
Issue fork drupal-2494073
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 #1
xanoComment #2
Crell commentedIf 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):
Comment #3
xanoThe 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.Comment #4
Crell commentedWell then let's focus that title a bit, shall we... :-)
Comment #6
xjm...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.Comment #7
borisson_Attached patch contains a module with a
composer.jsonfile that requiresscalopus/empty. This is an empty repository so it won't add any actual code to drupal.I've changed
ModulesListForm::buildModuleListto include a check for acomposer.jsonfile 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::buildModuleListto check for the actual contents of the composer.lock file.Comment #8
bojanz commentedExcept 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.
Comment #9
borisson_Changed the message in the patch.
Added the remaining todo's to the IS.
Comment #10
xanoWe 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 acomposer.jsonfile?We must also test that modules can be installed if all their Composer dependencies are met.
Comment #11
borisson_@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
vendordirectories.Comment #12
xanoGood 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.
Comment #13
borisson_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.
Comment #14
xanoAgain, this should be done on an API level, and not inside a form. See #10.
This will break as soon as someone uses an application-level Composer file (as applications should). Maybe we should specify the path in
settings.phpfor now?This can never be true without also checking version constraints. Can we use Composer (the PHP library) to easily check this?
Comment #15
borisson_#14:
hook_requirements_alterto make sure this is on API level,settings.phpbut 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.\Composer\Package\Version\VersionParserclass looks like it should have methods to do this.The current patch will break the test because the implementation is not correct yet.
Comment #16
borisson_So this makes everything green again, this is good. All we have to do now is do the version checking with composer/composer.
Comment #18
xanoEven 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.
Comment #20
borisson_Fixes #18
Comment #21
xanoThis is getting better and better :)
Copy/paste left-over?
Composer with a capital C.
!handbookmust be@handbookto match the string it's replace in.Comment #23
borisson_Comment #27
borisson_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.
Comment #28
Anonymous (not verified) commentedRe #27:
This should be on a single line.
Same.
Capitals, trailing dot.
80 chars. And probably some words.
This should go on a new line.
Re #23:
$module is missing here, that might be causing the failures?
Comment #29
borisson_Thanks @pjonckiere, attached patch resolves your remarks.
Comment #32
borisson_Locally the
HookRequirementsTestworks again. Hoping the rest is green as well.Comment #35
borisson_can't merge a NULL value and an array, should fix at least
Drupal\minimal\Tests\MinimalTestComment #38
borisson_In #35
\Drupal\system\Tests\Update\UpdateScriptTestfailed, this is now green locally. This shouldn't break anything else either so hopefully all is green now.Comment #41
webflo commentedThis hardcodes our vendor directory. This will cause problems if user want to move their vendor dir somewhere else.
Maybe scope creep, but whats about themes and install profiles? Maybe we can implement this in a generic way with ExtensionDiscovery?
Nitpick, we use 2 spaces in composer.json
Codestyle, 2 spaces
Comment #47
borisson_Fixes #41.1, .3 and .4.
I feel like .2 is scope creep.
Comment #53
borisson_Removed the
hook_requirements_alter.Comment #56
borisson_Introduced a new Exception:
ExtensionComposerRequirementsException.Comment #60
borisson_mark_modules_with_unmet-2494073-56.patchis supposed to fail because the requiredcomposer/semverisn't in yet. That's why themark_modules_with_unmet-2494073-56-include-package.patchdoes work.Comment #61
borisson_Fixed some small nitpicks, same argument as in #60 still applies: only the
-include-packagepatch will pass.Comment #66
hussainwebI 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.
Comment #67
hussainweb@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.Comment #68
borisson_@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.
Comment #69
hussainwebThe 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.
Comment #70
hussainwebThe 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.
Comment #71
hussainweb#2575469: Require the composer/semver library to do version checking. is in. I am uploading the small patch in #61 again, which still applies.
Comment #72
mile23See also: #2536576: Add composer_dependency module, have it help users figure out how to install Composer dependencies
Comment #74
borisson_If we don't get this in now, I think we should remove core's dependency on
composer/semveras 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.).Comment #75
mile23Using Semver here to define constraints: #2579663: Can't use 'composer install' with missing composer.lock and vendor folder
Comment #77
mile23Added 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 includingcomposer/composerin the repo.This simplifies
drupal_check_composer_requirements()by making it non-existent.This patch doesn't pass tests because it (accurately) throws
ExtensionComposerRequirementsExceptionwhich 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.
Comment #79
mile23Fixed errors in #77, some improvements.
Comment #80
fabianx commentedRTBC - looks great to me!
Comment #83
mile23CI error: https://dispatcher.drupalci.org/job/default/39506/console
Setting back to RTBC.
Comment #85
xanoIf we introduce a new to-do (technical debt), we should create a follow-up issue for it and reference it in this comment.
arrayis never an acceptable type in documentation. It looks like the values are strings, which mean the type must bestring[].Same here.
+1 for using a specific exception class!
Constructs a new instance., as our coding standards specify the one-line summaries must start with a third-person verb.
Exceptions are developers' tools. Why do we translate the message?
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.
No bug, but a tip (for the future): we can use
ExtensionComposerRequirementsException::classto 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.Comment #86
mile23@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
requiresand notrequires-dev.#85.4: Thank @borrison. :-)
#85.6, 7: It's the same pattern as
UnmetDependenciesException, except with even simpler logic. Check outModulesListForm::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
ExtensionComposerRequirementsExceptionstill generates a translated string.Comment #89
jhodgdonThere are numerous documentation problems with the latest patch...
Determine -> Determines
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?
Determine => Determines
Parse => Parses
of => of an
seems to be string[] rather than generic array?
version -> versions
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.
Question: does calling $string_translation->translate() get a string into the POT database? I don't think it does?
Is there a reason this isn't a service?
This docs header doesn't conform to our normal docs standards for a function/method.
Same here.
Both of these should start with something like:
Tests that...
Don't we require a summary line for all class docs headers, or is there an (unwritten in standards) exception for test classes?
this docs header has no summary line.
no summary line
no summary line
I've skipped pointing out the rest of this class lacks good docs for the methods.
Comment #90
mile23Nice, thanks for the review. :-)
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.
Comment #91
mile23Heh. 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.
Comment #93
xanoThere is https://www.drupal.org/node/2627292.
Comment #94
dawehnerI'm wondering whether the drupal root should be passed in as constructor parameter?
Comment #95
xanoThis 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 onExtensionComposerDependenciesandTranslationInterface, so we have full DI and testability. Either this, or we requireTranslationInterfaceinExtensionComposerDependencies::__construct().Comment #96
xanoSurprise!
Comment #98
xanoI 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.
Comment #99
xanoThis interdiff includes file moves.
Comment #101
xanoI 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.Comment #103
xanoThis message makes slightly more sense.
Comment #104
borisson_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.
There's a syntax error here. The .\ should be removed.
Can we change this? By reverting this check, we can make sure the rest of the code isn't as deeply nested.
Can we change this variable name to
$requirements? That makes more sense I think.See earlier comment.
We can also change this check to reduce the nesting.
Comment #105
xanoThanks 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 forinstalled.jsonabsolutely only once; the previous patches would check repeatedly if the file did not exist.Several variables were renamed for clarity, and comments were added.
Comment #106
borisson_I noticed one small typo, otherwise I feel this is RTBC.
s/on/no/
Comment #107
xanoComment #108
borisson_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.
Comment #109
alexpottWhat 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?
Comment #110
xanoThis 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?
Comment #111
jhodgdonI think the docs and UI text strings need a few small fixes in this patch as well:
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! :)
@see should be at the end of doc blocks, and @see should always have the namespace/class in the line not self::
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?
Should this be an interface?
Again I am not sure what "package names" are?
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.
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?
Um... does the POTX extractor pick up ->translate() calls? I thought you had to use t()?
present -> met
Test -> Tests
Test -> Tests
Comment #112
xanoCould 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.
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).
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.
There is none for
\Drupal\Core\Extension\Extension, because it is a simple value object.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.
Comment #113
xanoIt's not officially supported. See the test that should verify this.
Comment #114
jhodgdonRegarding @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)?
Comment #115
xanoSomething is missing there. I recall an issue to loosen these strict rules, as they prohibit things like
@return staticand@return $this, which are valid and separate cases which we use all over core. The additional problem with@seeis 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 (becauseselfis 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.Comment #116
jhodgdonYes, 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.
Comment #117
mile23Well it *is* naive. If it weren't, we'd require
composer/composerand then we'd use composer's code to do the check. Totally OK with changing that docblock though. :-)Return type can be
staticor$thiswithout the FQNS for@return, but@seeneeds FQNS: https://www.drupal.org/coding-standards/docs#types and https://www.drupal.org/coding-standards/docs#seeI 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.jsonfile with unmet dependencies, then we should be blocked from doing an update. Maybe inupdate_check_requirements()?Comment #118
xanoThat was already fixed in #101. Also see #110.
Comment #119
xanoThis patch changes two things:
system.installensures 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.Comment #120
mile23VERY glad @alexpott clarified that. Sounds great.
I added a non-existent requirement to a contrib module and went to
update.phpon my dev site. It showed me this: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.
Comment #121
jhodgdonI aliased the page https://www.drupal.org/documentation/install/composer-dependencies if someone wants to update the patch.
Comment #122
xanoIs 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.
Comment #123
jhodgdonI'm happy to change it. What would you suggest? I just named it similar to other pages in the section.
Comment #124
xanoWe can always add subpages later, and make this a landing page. This is good for now.
Comment #125
mile23+1
Comment #126
alexpottSome code style issues that can be discovered using coder...
Comment #127
xanoIIRC correctly (and @dawehner seemed to recall so too) we do not require descriptions for docblocks that use
@covers.Comment #128
xanoThis 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.
Comment #129
bojanz commentedWe shouldn't care about platform packages at all.
Quoting composer_manager:
Comment #130
mile23We 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.
Comment #131
borisson_I agree with the logic in #129 and #130, back to RTBC it is.
Comment #132
borisson_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.
Comment #133
xanoIf 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.
Comment #134
alexpottI 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.
How can this exception catching ever be hit at runtime (and tested)? Requirements are tested before we get here.
Comment #135
xanoWe 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.
Comment #136
alexpott@Xano well we still have the problem of installing several modules at once and not telling the user which one failed.
Comment #137
xanoYou're right. We do the same thing with module dependencies (see
ModulesListConfirmForm::buildForm()).Comment #138
mile23OK. 8.0.x it is, attempting to at least tell which module is failing the requirements test.
Comment #139
mile23OK, we have helpful information about module names and their dependencies.
Comment #140
xanoMust start with a third person singular verb.
@return arraymust provide a more specific type. The description indicates this must be@return string[].Can we add a verb to the method name? Something like
calculateUnmetDependencies()?This variable does not contain an extension, but is a list of extensions and constraints.
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?@param string[]@param string[]Comment #141
xanoI 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.
Comment #142
rszrama commentedI 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")
Comment #143
mile23Thanks 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 ProfileExtensionListWe 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.
Comment #145
mile23Ewps. Forgot that test.
Comment #147
dawehnerWhat about using keys for the data provider for better debugability?
Is there a reason we don't use prophecy? Prophecy is IMHO way less verbose and makes it easier to understand the test
Comment #148
xanoI'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?
Because it took @klausi until Drupalcon Asia to finally convince me to use it ;-)
Comment #149
xanoThis patch addresses all feedback from #147.
Comment #150
MixologicThere 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.
Comment #151
mile23composer.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.
Comment #152
borisson_#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.
Should be removed.
Maybe we should add an
@seeto hook_requirements() so we link to the allowed keys for this array?Comment #153
dawehnerJust a couple of small bits ...
You know I would have just used $foo ?: []; and call it a day.
Can we put a inline
/** @var */just to be sure, and make it easier for people?@file is now dropped
In other places we called that app root. Feel free to change if you want
Note: we have the ":" placeholder for URLs these days
Comment #154
xanoComment #158
xanoRe-roll.
Comment #161
xanoComment #164
xanoAdd back the check we removed last weekend, and improve its documentation.
Comment #166
xanoRe-roll.
Comment #167
jhedstromI'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()).
I think @dawehner was suggesting in #153 that this can be removed and simplified to
Comment #168
xanoComment #169
borisson_Comment #170
fabianx commentedRTBC + 1
Comment #173
xanoThis 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 ownautoload.phpwith the new path to the vendor directory.Comment #174
borisson_Comment #175
fabianx commentedMaybe we want to define this as absolute path as well?
Using __DIR__ . '/vendor'?
Comment #176
mile23+1 on #175.
Use the constant here instead of rebuilding the path without it. That way other systems can just set the constant.
Comment #177
xanoWorking 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.
Comment #178
xanoWe'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 becomposer-plugintype packages of their own. We'll need #2474007-21: Add a “General” project type to Drupal.org to host those ourselves.Comment #179
mile23Why? (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.
Comment #180
borisson_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.
Comment #181
mile23@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.
Comment #182
MixologicI 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:
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.
Comment #183
xanoAs 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?
Comment #184
xanoOne of the problems is that the root package (
drupal/drupal) and all packages merged into it (drupal/core) do not end up ininstalled.json, so our dump does not work in case anyone depends on core modules through theircomposer.json.// Edit: problem solved. Patch coming later today.
Comment #185
Mixologic@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?
Comment #186
xanoComposer 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.jsonmust include a custom repository that provides the package. At the Drupal Dev Days we decided we do not want to parseinstalled.jsonruntime, and the alternative is to do this through Composer's own API during build time using a Composer script or plugin.Because I can write a perfectly valid
composer.jsonthat depends on any package we ship with Drupal core. This is about Composer integration, not about*.info.ymlfiles 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.Comment #187
mile23OK, 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)
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:
Comment #188
xanoNo. 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.
I am in fact correct. Please run the following command in a sandbox folder and share with us which packages from the
drupalvendor you see in thegrepresult: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).Comment #189
mile23Ahh, you want to discover which dependencies exist *from core.* But those won't be in
installed.jsonbecause they were never installed by Composer. We have all those 'replace' directives incore/composer.json, and we use the merge plugin rather than declaring dependencies.So follow your instructions and then do this:
Now you see
drupal/coderin the results.The real sore thumb here is
drupal/corewhich should have an entry but doesn't, because we use the merge plugin rather than declare it as a dependency ofdrupal/drupal.So if you want to address what's in the
installed.jsonfile, don't re-implement Composer. Fix the 'replace' section incomposer.jsonand figure out how to move us away fromcomposer-merge-plugin.In this issue we're using
hook_requirements()to determine whether to allow someone to install stuff based oninstalled.json. If the user downloaded a module tarball that declares composer-based dependencies, we'll catch that by looking atinstalled.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.
Comment #190
MixologicIm unsure that we share the same understanding of the problem we are trying to solve.
From the issue summary:
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.
?
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/coreor any components likedrupal/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.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?
Comment #191
xano@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.Comment #192
xanoSeems one file slipped past my Thorough Review Skills™.
Comment #195
xanoRe-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 installand in doing so trigger the script that dumps the list of installed packages. This means the list is always available, unless something went wrong.Comment #197
borisson_I went through the entire patch again and I noticed one thing that I think can be improved.
Can we use DRUPAL_ROOT instead of __DIR__ . '/../'...?
Comment #199
MixologicOur 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.
Comment #201
xanoCan 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.
Unfortunately we can't, because this line is in a Composer script and because Drupal is not bootstrapped,
bootstrap.inc(which definesDRUPAL_ROOT) has not been included.Comment #202
mile23Yes, 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.jsonin 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.
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? :-)Comment #203
xanoWe 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.
It most definitely is a requirement of both
drupal/drupalanddrupal/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.
Comment #204
MixologicHow 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.
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.
Comment #205
MixologicBy 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.
Comment #206
mile23We don't need it to do that for this issue.
Comment #207
xanoWhen does it rely on that? Your instructions are good, but forget that once this patch is applied, the packaging script will have run
composer installalready, 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.Please include argumentation we can use to reproduce the problem or confirm your statement with.
Comment #208
MixologicAh 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
- 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.
Comment #209
mile23A 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.
Comment #212
borisson_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.
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.
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.
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.
Comment #213
xanoThis 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.
Comment #221
MixologicAgain, 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.
This patch most certainly adds an explicit dependency on composer, and everything else that comes with it:
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:
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.
Comment #222
xanoYes, 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 torequire. Since we use\Composer\Package, we needcomposer/composerin its entirety, as that did not seem to be available in its own package.No. We'd also need confirmation the schema is part of the public API and as such will not change.
That would still mean we would willingly introduce a dependency on another application's internal storage here which is a bad practice.
Comment #223
xanoAs 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.jsonindefinitely, and several people pointed out to us, both at the sprints and in#composeron 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.jsondoes not even contain all the package information we need. That means we cannot solve this issue without adding some information from other sources.Comment #224
xanoWe 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.
Comment #225
MixologicCan 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.
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.
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?"
Comment #226
xanoWe 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'sinstalled.jsonafter one has runcomposer installinside thedrupal/drupalroot.In short:
installed.jsondoes not feature all packages available in the application, which means we will cause false negatives when relying solely on its contents.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.
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.
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.
Comment #227
fabianx commentedChiming 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.
Comment #228
MixologicAll 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.
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.
Comment #229
mile23@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. :-)
Hmmmmm...... Well in that light, let's review the patch:
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.
Comment #230
MixologicIm 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)
Comment #231
xanoThey are mostly equivalent, with the major difference that the latest versions of the patch extend the list of packages from
installed.jsonwith information about merged and replaced packages as well. I wouldn't go as far as to say we unconditionally support merged Composer files (merged throughwikimedia/merge-plugin), but at least this works when people work withdrupal/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'scomposer.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 toinstalled.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.I'm willing to wager it's in
./core/composer.jsonbecause of the package merging we've been talking about for some time now.Comment #232
mile23We 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? :-)
Comment #233
MixologicCool, 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
Comment #234
xanoWe can't just read
installed.jsonruntime as I explained in #231. The script approach solves that problem, with the exception of 'multi-step'replacelinks (installed package A replaces B replaces C; we currently do not list C).That makes total sense. Thanks!
Comment #235
mile23I'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.
Comment #236
xanoWe need the custom dump, as described in #231, #234, and earlier comments. Please read and comment on those before continuing to discuss reading
installed.jsonruntime.Comment #237
MixologicNo, 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?
Comment #238
xanoNeither
composer.locknorinstalled.jsonstore a list of all available packages on the system, because it takes some information from Packagist servers andreplacelinks to resolve dependencies. While this allows Composer itself to conclude that in adrupal/drupalproject a dependency ondrupal/nodeis 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 hasdrupal/nodeas an unmet Composer-based dependency.Steps to reproduce:
drupal/drupal:dev-8.2.xsomewhere, patch it with https://www.drupal.org/files/issues/drupal_2494073_226.patch, runcomposer installand install the site.../modules. Go to its module root and runcomposer require drupal/node:*.\Drupal\Core\Composer\Composer::dumpInstalledPackages(), comment out the following line:$installed_packages_constraints[$link->getTarget()] = $link->getConstraint()->getPrettyString();.composer installin your site root to trigger the script again.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.jsoncontaining replace links into its application-levelcomposer.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.
Comment #239
xanoThis patch addresses two concerns from #232: the hard dependency on
installed.jsonandcomposer/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 usecomposer/semverandjustinrainbow/json-schemaascomposer/composerdependencies 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 oncomposer/composer:~1.0, but we chose not to enforce it explicitly. It does not remove the dependency, just our safeguard.Comment #243
cweagansEmbedding 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?
Comment #244
mile23It'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.
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
replacearen'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 andreplaces. 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 thereplaces 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.jsonis already there, populated with all we need to know in order to tell them.If a module depends on
drupal/nodeincomposer.jsonbut not in.info.yml, then that's a bug in the module, because there is currently no specified behavior for contrib extensions withcomposer.jsonfiles. 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.
Comment #245
mile23composer.lockdoesn't tell you what's installed, only what *will be* installed. So you look at/vendor/composer/installed.jsoninstead.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.
Comment #246
xano@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.
Comment #247
xanoLet's turn this around a bit:
replacelinks for these packages.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.
Comment #248
MixologicI 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
replaceto mixologic/tax composer.json, addrequire mixologic/taxin 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:
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.
Comment #250
mile23The 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.
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/whateverrequirements in composer.json, if thewhateverpart 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 currentlyreplaced. 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.
Comment #252
mile23Added some more tests.
To recap:
This patch says that if a composer-based dependency in the namespace
drupal/also exists in theinfo.ymlfile, then we won't check whether it's ininstalled.json.This is useful because of a few basic assumptions:
drupal/core-componentswill always be available in core, even when they are subtree splits.drupal/coreextensionswill also always be available asreplace, but not present ininstalled.json.composer.jsonshould also be declared ininfo.yml, because we don't currently have a defined behavior for Composer-based core extensions.What will happen if this patch is committed:
composer.jsonfile but notinfo.ymlwill become uninstallable. I view this as showing them that theirinfo.ymlfile needs an update, but obviously others might disagree.drupal/core-whateverincomposer.json(since we can't depend on a non-extension library ininfo.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 incomposer.jsonbut notinfo.yml.Comment #253
fabianx commentedI like #252 a lot and consider 1. and 2. features and a good thing rather than limitations.
Comment #254
xanoThis 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.
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
replacelinks in its Composer file, so we'll run into the same problem with other packages (depending onsymfony/http-foundationwhilesymfony/symfonyis installed will cause false negatives with the patch in #252).Comment #255
mile23Well 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-whateverhas been installed.We know that for core, we already have
drupal/core-whateverbecause 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.
a) Core doesn't require
symfony/symfony, and if we're depending ondrupal/core, we have an explicit dependency on symfony components instead ofsymfony/symfony, and if you're making an extension that depends onsymfony/symfonyyou'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 ourreplacedirectives incore/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.jsonfile, 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-componentpackages under the premise that they're already installed in core.Updates tests.
Comment #256
xanoSubtree 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.jsonand*.info.ymlfiles, 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.Comment #257
mile23Subtree 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.
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.
Q: Which replace links? A: The ones that contrib has already defined as a workaround to the problem we seem unable to fix here.
The repro steps in #238 do the following:
I'm not sure exactly what it's supposed to prove.
#238 also says this:
It uses the example of
drupal/node, which is marked as replace incore/composer.json. The reason it's marked as 'replace' is because we currently have no method of specifying a core module incomposer.json. We have to use info.yml for that, which is why we have to care aboutinfo.yml. And, in fact, is why subsequent patches I've offered have addressed the issue of includinginfo.yml, despite your protestations that they haven't.But rather than cycling through that conversation again, let's try this:
Then we'll have data.
Comment #258
xanoThis patch builds on #239.
After discussing our situation with several active contributors in
#composeron 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 thanreplace, such asconflict, 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
replacelinks and the submodules' dependencies to the package's primary Composer file and have that installed.Comment #260
mile23The 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?
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. :-)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.
Comment #261
MixologicI 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:
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:
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.
Comment #262
xanoThis 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.
Comment #263
xanoRemoved some debugging code, and redundant production code (although I do expect
HookRequirementsTestto fail as I didn't have time to look at it anymore).Comment #266
xanoThis 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 runcomposer install. Any ideas on how to include coverage in a way that works? The only way I can think of right now is to fakeinstalled-modules.json.Comment #268
mile23Totally 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.
drupalci does in fact run
composer install. I'm pretty sure it's after patching, as well.We still need SemVer for preAutoloadDump. We have it in both core/composer.json and DRUPAL_ROOT/composer.json, for reasons. I guess. ::shrug::
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.
Comment #269
xanoThis should fix the remaining test failures. Note that I moved the coverage for
ModuleInstallertoModuleInstallerTest, 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.Before tests, but not during, which is what I said in #266.
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.
Good find! I put this requirement back in.
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.
Comment #271
xanoComment #272
mile23That'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
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. :-)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.
Also need drupal-profile and drupal-theme, right?
Nice to check, but maybe out of scope here. drupal/drupal requires composer/installers already, but yah.
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 havecomposer.jsonfiles in them (because not allcomposer.jsonfiles have a corresponding.info.ymlfile). Then we exclude all directories undercore/. Which, btw, leaves us withvendor/.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?
Convert this to
setExpectedException(), so we can use::class.Still need a test case for composer_installable, unless I missed it elsewhere.
Comment #273
xanoNone 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 usedrupal-composer/drupal-scaffoldinstead. This issue is aboutdrupal/core, but the latest patches contain a small workaround to supportdrupal/drupal's (or any other front controller package's) somewhat awkward use of mergingdrupal/coreinto itself.*.info.ymlfiles 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.ymlfiles, or make their assets web accessible).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.
#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.
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.
Comment #274
fabianx commentedAs 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.
Comment #275
mile23Yes. That is why I mentioned it: To demonstrate that I understand what this code is doing.
...which is why we should not search it.
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.
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
replaceproblems 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.
Comment #276
xanoThat's not a bad idea. Where should we store the generated JSON file?
Done.
Done. Maintainability++.
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 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.
Comment #278
xanoComment #280
mile23That 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.
Comment #281
xanoThere 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.
Comment #282
MixologicCan you clarify what a dependency collision is with an example? Its not clear to me what you mean by that.
Additionally, what is
? We either have modules/themes/install profiles(extensions), or we have composer packages (libraries).
Comment #283
xanoA 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.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.
Comment #284
MixologicI 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.
Comment #285
xanoYou 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.
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.
Comment #286
mile23Yes, that's a problem and it's out of scope here. It was solved somewhat by
composer_managerand 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 todrupal-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-dispatcher3.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_installabletest case, and thecomposer_mixed_uninstallableandcomposer_mixed_installabletest cases present in #255.Comment #287
Crell commentedI 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.
Comment #288
xanoI 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.jsonprovides.Comment #289
yesct commentedI agree with @Fabianx in #274 (https://www.drupal.org/governance/core), and for
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.)
Comment #290
alexpottAn 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
Not entirely sure about this path and the current update instructions
Comment #291
MixologicI 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.
or
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
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.
Comment #292
mile23Updating 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.
Comment #293
xanoCan 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.
Comment #294
borisson_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.
Needs newline.
Can we use strict checking (===) here?
I don't really understand this very well, can we document what's happening here?
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?
Comment #295
MixologicDo 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?
Comment #296
xanoMy 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.
Comment #297
mile23Not 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.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.
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.
Comment #298
xanoAs 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/composerruntime, 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 publishedTLDR; 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.
Comment #299
MixologicThats 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 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:
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.
Comment #300
xanoThat's an excellent way of phrasing it!
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.
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.
Comment #301
mile23Yes, 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. :-)
Comment #302
xanoWe 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?
Comment #303
fagoGuiding 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?
Comment #304
fagooh: 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.
Comment #305
MixologicRe #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_dependencieskey 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 requirethe module that way.Comment #306
MixologicAn 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)
Comment #307
mile23This 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. :-)
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.
+1 (really more than one...) for iterating.
That brings us to:
build_dependenciesfrom #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.
Comment #308
xanoThat 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.
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
conflictpackage links, for instance).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.
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.Yes. See
\Drupal\Core\Composer\Composer::dumpInstalledPackages()in the patch in #288.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.
Comment #309
MixologicThe 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.
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:
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/inmailI 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:
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.
Comment #310
xanoThat 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, 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.
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.
Comment #311
mile23..Well except not. :-)
We do, in fact, support submodules with different dependencies through the facade. So we have to deal with it here.
Comment #312
MixologicMy 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.
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".
Comment #313
mile23Here are two patches:
One is a reroll of #288.
The other is an attempt at adding a
build_dependencieskey 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.Comment #314
MixologicI 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/parentmoduleComment #315
MixologicComment #317
eric_a commentedI just read the issue summary changes by @Mixologic.
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 doingclass_exists()like #2780093-5: Have simpletest, run-tests.sh enforce their dependency on PHPUnit.Comment #318
mile23Similar issue here: #2830880: Warn site admins when composer dev dependencies are installed inside of docroot
Comment #319
xjmThe 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!
Comment #321
Saphyel commentedFrom 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
Comment #322
borisson_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.
I think we can simplify this code by switching the ifs.
Needs FQN
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.We don't need the
t()here./s/numeric/int/?
Comment #323
mile23Thanks, @borrison.
Rerolled, added all the changes from #322.
I also moved the kernel test to the
Drupal\KernelTestsnamespace, 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.
Comment #325
mile23Fixed failing test.
Comment #326
mile23Added a change record: https://www.drupal.org/node/2885154
Comment #327
mile23Update issue summary to reflect adding build_dependencies to info.yml.
Comment #329
wim leersYES 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.
Nit: I think these should be marked
@internal?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.
Woah this sounds scary complex — but also an interesting challenge 😀
s/require Composer installation/require Composer dependencies/
I think that'd be more accurate?
"using internals" — what does that mean?
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 thedrupal/namespace?I think the
@todoI quoted in point 4 is the most important remaining task?Comment #330
mile23Thanks 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.
Comment #331
wim leersOk, great, I'll try to review that next week.
Comment #332
kyvour commentedCould you check my comment for #2830880: Warn site admins when composer dev dependencies are installed inside of docroot please? There is a same problem:
Comment #334
mile23Reroll of #325 after mention in #1398772: Replace .info.yml with composer.json for extensions
Still needs work from review in #329.
Comment #337
tedbowSorry 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
Comment #338
aaronmchaleRe #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.
Comment #339
Mixologic#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.
Comment #340
aaronmchaleRe #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?
Comment #341
MixologicThe 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
Comment #342
Mixologichttps://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.
Comment #344
alexpottI don't think this issue should be assigned to me.
Comment #345
MixologicWe should start talking about this again.
Comment #346
gappleReroll of #334 to 8.9
Made one change to replace an instance of
drupal_set_message()to use the messenger service.Comment #348
andypostinstead of constants from install.inc it could use the same from
\Drupal\system\SystemManager\Drupal\system\SystemManager::REQUIREMENT_OK\Drupal\system\SystemManager::REQUIREMENT_ERRORit looks weird in unittest because defined in install.inc
Comment #349
gapple- Use SystemManager constants
- pass
$this->appRootto InfoParser in test so that it doesn't fallback to usingDRUPAL_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)
Comment #351
dww#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
Comment #360
nicxvan commentedSince composer is the only recommended way to install can we close this?
Comment #361
borisson_As long as it's not the only possible way to install modules, there's still value here I think.
Comment #362
dwwIndeed. While #3491731: [META] Remove the ability to update modules and themes via authorize.php is done, #3285191: [meta] Only support Drupal core installs managed by composer is not. So I agree there’s still value in locking this down.
Comment #363
berdirEven if we'd only support composer, there are still things like submodules that have their own dependencies.