Add a basic markdown doc explaining dependency resolution.
This commit is contained in:
committed by
TeamCityServer
parent
6aee396e4b
commit
66b8a444c2
@@ -0,0 +1,84 @@
|
||||
# Dependency Resolution in the Project Model
|
||||
|
||||
Dependency resolution is not a single task, it is a complex pipeline where the requested dependencies, considered an input, are transformed
|
||||
to mutliple possible kinds of results. To produce one kind of the results, another sort of dependency resolution result may be needed.
|
||||
Therefore, one may think of the process of resolving dependencies as of a pipeline that may produce results of different shapes when
|
||||
requested.
|
||||
|
||||
Some use cases only require a dependency resolution result of one kind (for example, 'get all other fragments that a given
|
||||
fragment can see'), and for them, it might be convenient to have a single facade returning just that kind of result.
|
||||
|
||||
This document describes all the granular tasks that one might put before dependency resolution implementations that work with the Kotlin
|
||||
Project Model, without attaching them to specific use cases.
|
||||
|
||||
## Internal dependency expansion
|
||||
|
||||
> We may remove this section if it turns out that we are going to use modules for test-on-production dependencies and the like
|
||||
|
||||
We allow declaring only some of the fragment dependencies between fragments inside a single module, and the fragment dependencies for the
|
||||
depending fragment's refines-children are inferred automatically. Also, declaring such a dependency implies that all refines-parents of the
|
||||
dependency fragment should be visible, too, as well as everything it sees via its own expanded module fragment dependencies (as well as
|
||||
transitive expansion results of the first-level expansion results, and so on).
|
||||
|
||||
The interface for this is `InternalDependencyExpansion` with a function that takes a `fragment` and returns all other fragments of the same
|
||||
module that the fragment should see. For ease of explanation and diagnostics, the results are grouped by the actual declared fragment
|
||||
dependency that led to each particular fragment having been added to the result.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
commonMain commonTestFixtures < - - - - -commonTest
|
||||
| | |
|
||||
jvmAndJsMain < - - - - jvmAndJsTestFixtures jvmAndJsTest
|
||||
| | |
|
||||
jvmMain jvmTestFixtures jvmTest
|
||||
```
|
||||
|
||||
Here, dashed arrows denote declared fragment dependencies. When we ask for fragments that `jvmAndJsTest` sees, we get:
|
||||
`commonTestFixtures` and `jvmAndJsTestFixtures` because `commonTest` depends on `commonTestFixtures, and then also `commonMain` and
|
||||
`jvmAndJsMain` because `jvmAndJsTestFixtures` depends on `jvmAndJsMain`.
|
||||
|
||||
Currently, the `DefaultInternalDependencyExpansion` implementation takes a function that matches variants of the module as if they take
|
||||
part in variant-aware dependency resolution. This function may actually perform matching of the variants or may rely on additional
|
||||
information (until attributes matching is implemented, it may search for explicit dependencies added between the variants).
|
||||
|
||||
## Dependency discovery
|
||||
|
||||
Given that dependency resolution may bring transtive dependencies, it is not enough to know what *declared dependencies* a fragment has to
|
||||
be able to properly inspect the resolution results. You With just those, you won't be able to ask
|
||||
'what fragments does this fragment sees from module `foo`?' if the module `foo` is only a transitive dependency.
|
||||
|
||||
This defines a task of *discovering module dependencies*, that is, finding which modules a fragment actually depends on, including the
|
||||
modules brought in as transitive dependencies.
|
||||
|
||||
With the granular modules of the Kotlin Project Model, this not a trivial task. A module may reference another module as a dependency onl in
|
||||
some of its fragments. Therefore, to decide whether a particular module `m` should be transitively included in the resolution results for
|
||||
one of our fragments `f` we have to first find out whether `f` sees any other fragment that declares a dependency on `m`. This is another
|
||||
dependency resolution task that is covered below.
|
||||
|
||||
## Module resolution
|
||||
|
||||
Given a module dependency, we should be able to build a Kotlin module for that dependency, as we will have to decompose the dependency on
|
||||
the whole module to dependencies on its granular parts. These granular parts are the variants and fragments. So it is reasonable to build
|
||||
a Kotlin module for a resolved dependency and reason about it in the same terms as we use for locally built modules.
|
||||
|
||||
This task in dependency resolution takes a module dependency and returns a Kotlin module. This may in fact be a Kotlin module that is built
|
||||
locally (for dependencies that point to it directly).
|
||||
|
||||
## Variant resolution
|
||||
|
||||
This is a simple task that is very much like Gradle's variant aware dependency resolution: given a variant (not just a fragment) of a
|
||||
consuming module and another dependency module, we should tell which of the dependency module's variants is the best match.
|
||||
|
||||
When we look at a consumer's variant, it is important to be able to pick a dependency's variant (and not as set of fragments), because
|
||||
variants produce final binaries, and for the consumer to produce a final binary from its variant, it needs a guarantee that the producer
|
||||
also could generate a complete binary (with all `expect`s covered by `actual`s). The confirmation of this is exactly the producer's variant.
|
||||
|
||||
## Fragment resolution
|
||||
|
||||
This is the task of dependency resolution that we ultimately need to find out which declarations a module fragment sees. Namely, given a
|
||||
module dependency, we have to determine which of the dependency's fragments the consumer's fragment may see.
|
||||
|
||||
To do that, we have to perform variant resolution for each of the variants that the depending variant participates in, and then intersect
|
||||
the refines-closures of the resolved variants, this will be the safe set of fragments whose declarations we may use because they are
|
||||
available in any variant that we will compile the fragment for.
|
||||
@@ -119,6 +119,11 @@ class DefaultInternalDependencyExpansion(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation matches the variants of the module by checking
|
||||
* their [KotlinModuleFragment.declaredContainingModuleFragmentDependencies], similar to how associate compilations work in MPP.
|
||||
* If more than one such dependency is declared, the result is deemed ambiguous.
|
||||
*/
|
||||
class AssociateVariants : DefaultInternalDependencyExpansion.ContainingModuleVariantResolver {
|
||||
override fun getChosenVariant(
|
||||
dependingVariant: KotlinModuleVariant,
|
||||
|
||||
Reference in New Issue
Block a user