Add test coverage for variant capability conflict resolution
Only allow more than one variant of a component if it has different capabilities
This commit significantly reworks how capabilities are handled, in particular
with regards to accepting 2 different variants of the same component to appear
in a graph. It is now allowed to have 2 variants of the same component if they
have distinct capabilities.
It shall be reminded that if a variant doesn't explicitly declare any capability,
it is assumed to carry the _implicit capability_, which corresponds to the GAV
of the component. Said differently, by default, all variants of a component
are supposed to be incompatible with each other (they cannot appear in the
same dependency graph).
If 2 variants are indeed compatible, then they _must_ have different capabilities.
This will be used to support optional dependencies, by making it possible to
express that one has to choose a specific set of compatible variants to enable
There are however a few issues that needed to be addressed in this commit. First
of all, what we've just described is only true if we use variant-aware selection,
and more specifically attribute-based variant selection. If not, then a legacy
mode is used, and it's possible to have 2 "variants" of a component on the same
graph. This is typically the case when there are dependencies on 2 different
_configurations_ of a project:
api project(path: ':foo', configuration: 'conf1')
api project(path: ':foo', configuration: 'conf2')
For backwards compatibility reason, even if the 2 configurations actually
provide the same capability, this is allowed. Another case is when a cycle is
found between a root component and a transitive dependency corresponding to
the same component in a lower version.
Last but not least, in the engine the implicit capability is treated
specially for performance reasons: capability conflict resolution is very
expensive, and adding an explicit capability to all components would trigger
conflict resolution everywhere (to find out if other components provide the
same capability). This leads to some optimizations in the code which are not
necessarily easy to follow, but properly documented.