Module dependencies expansion algorithm
This visibility inference is not configurable by a user and always happens whenever both the consumer and the producer of a dependency are MPP projects.
We determine visibility for fragments in a conservative way, by ensuring that a fragment can’t see any other fragment whose declarations won’t be actually available during platform code generation. Having said that, it’s important to note that the algorithm is based on the variants structure of the project.
Here is a formal description of the algorithm. Consider skipping to the [Example] below before getting to complete understanding of the algorithm.
Consider a fragment s of a multiplatform project p that has a module dependency d that references q, which is another MPP module.
The resulting set R of q ’s fragments that is visible to s is determined as follows:
Let C be the set of the variants in the project p which include the fragment s in their refines-closure
As the dependency d gets expanded for each of the variant in C, a compatible variant of the project q is chosen. Let V be the set of the variants chosen for all of the variants in C.
For each variant v i in V, let R i be the set of fragments in the project q that participated in building the variant v i.
The intersection of all R i gives the resulting set R of the visible fragments.
Note that this requires that a dependency is declared as a dependency on the whole library, not separate dependencies on foo-metadata , foo-jvm, foo-js etc.
For an illustration, consider the following project structure, with the producer above and the consumer below. This picture only shows the project structure and does not show fragments visibility nor explains how the visibility is determined:
Here’s an example of how visibility is determined for the consumer’s fragment
Note that the visibility inference algorithm shows that the consumer’s fragment
allJsMain should see the producer’s
jsMain as well, but this doesn’t yet happen because currently, we can’t produce Kotlin metadata from sources that can use platform-specific dependencies and language features. This is a purely technical limitation.
And another example for the consumer’s source set
Note that the consumer’s fragment
commonMain would see exactly the same set of the producer’s fragments as it participates in the same set of variants.
However, if you add a Linux target to the consumer project,
commonMain will automatically participate in its variants, and the new visibility results will show that the consumer’s
commonMain only sees the producer’s
commonMain and no other source sets.
You can see which variant a dependency resolved to in a particular configuration by running:
It will usually print something like this (the variant name is
fooCompileClasspath is a configuration name, like
bar is a substring of the dependency’s group and version (for
com.example:bar:lib-bar, it is enough to specify
bar ). The particular configuration name for a certain compilation can be taken from a
project("...") dependencies, the variant names that Gradle prints are actually the names of the configurations like
jvmApiElements, and these configurations are internally matched with the published variants like
jvm-api by the visibility inference code.
The full list of variants is available in the Gradle module metadata (
*.module) of a published library and in the Kotlin HMPP structure metadata generated by the task
generateProjectStructureMetadata and put under
build/kotlinProjectStructureMetadata/kotlin-project-structure-metadata.xml (note that the latter only lists platform variants and does not list the Kotlin metadata variants).