How does virtual-platform work

doc

Align versions of modules without a published platform

says:

The identifier, in this case `com.fasterxml.jackson:jackson-virtual-platform`, is  something you as the build author define yourself. The "content" of the platform is then created by Gradle on the fly by collecting all `belongsTo` statements pointing at the same virtual platform.

a example:

dependencies {
    api 'com.fasterxml.jackson.core:jackson-databind:2.9.5' // dep core
    api 'com.fasterxml.jackson.core:jackson-core:2.9.7'
    components.all(JacksonBomAlignmentRule)
}

abstract class JacksonBomAlignmentRule implements ComponentMetadataRule {
    //@Override
    void execute(ComponentMetadataContext ctx) {
        // println("execute:"+ctx.getDetails())
        ctx.details.with {
            println("details:"+it.id+"->platform:"+id.version)
            if (id.group.startsWith("com.fasterxml.jackson")) {
                // declare that Jackson modules all belong to the Jackson virtual platform
                belongsTo("com.fasterxml.jackson:jackson-virtual-platform:${id.version}")
            }
        }
    }
}

by log:

details:com.fasterxml.jackson.core:jackson-databind:2.9.5->platform:2.9.5
details:com.fasterxml.jackson.core:jackson-core:2.9.7->platform:2.9.7
details:com.fasterxml.jackson.core:jackson-annotations:2.9.0->platform:2.9.0
details:com.fasterxml.jackson.core:jackson-databind:2.9.7->platform:2.9.7
details:com.fasterxml.jackson.core:jackson-annotations:2.9.7->platform:2.9.7 // where dose this jackson-annotations:2.9.7 comes from? 

form 3 version virtual-platform:

platform:2.9.5
  jackson-databind:2.9.5

platform:2.9.7
  jackson-core:2.9.7
  jackson-databind:2.9.7
  jackson-annotations:2.9.7

platform:2.9.0
  jackson-annotations:2.9.0

compileClasspath, runtimeClasspath:

+--- com.fasterxml.jackson.core:jackson-databind:2.9.5 -> 2.9.7
|    +--- com.fasterxml.jackson.core:jackson-annotations:2.9.0 -> 2.9.7
|    \--- com.fasterxml.jackson.core:jackson-core:2.9.7
\--- com.fasterxml.jackson.core:jackson-core:2.9.7

quetion:

The "content" of the platform is then created by Gradle on the fly by collecting all `belongsTo` statements pointing at the same virtual platform.

if the virtual-platform’s include libs by belongsTo, then the log should be:

details:com.fasterxml.jackson.core:jackson-databind:2.9.5->platform:2.9.5
details:com.fasterxml.jackson.core:jackson-core:2.9.7->platform:2.9.7
details:com.fasterxml.jackson.core:jackson-annotations:2.9.0->platform:2.9.0
details:com.fasterxml.jackson.core:jackson-databind:2.9.7->platform:2.9.7

form 3 version virtual-platform:

platform:2.9.5
  jackson-databind:2.9.5

platform:2.9.7
  jackson-core:2.9.7
  jackson-databind:2.9.7

platform:2.9.0
  jackson-annotations:2.9.0

but infact, there is this line in log:

details:com.fasterxml.jackson.core:jackson-annotations:2.9.7->platform:2.9.7 // where dose this jackson-annotations:2.9.7 comes from? 

So there is chicken egg question:
is platform:2.9.7 comes first or jackson-annotations:2.9.7 comes first?

  1. if platform:2.9.7 comes first, how the relation between platform:2.9.7 and jackson-annotations:2.9.7 generated?
  2. if jackson-annotations:2.9.7 comes first, where is jackson-annotations:2.9.7 comes from?

and when add enforcedPlatform to the example, there is the same question, where is the relation belongsTo of jackson-core:2.8.9 to platform:2.8.9 comes from?

implementation enforcedPlatform('com.fasterxml.jackson:jackson-virtual-platform:2.8.9')

If you read Automatically align Dependencies with Platforms and Gradle Module Metadata it might get a bit clearer.
The belongsTo actually does, what the blog post is recommending lib authors should do to automatically align versions.
Indeed in the meantime - as of 2.12.0 - Jackson adapted those recommendations, so for recent Jackson versions you do not need a virtual platform.

What the belongsTo roughly does is, it creates a platform that depends on all those libs that have the same belongsTo and it adds a platform dependency to this virtual platform from all those libs.

So you start with

jackson-databind:2.9.5
jackson-core:2.9.7
jackson-annotations:2.9.0

this creates a virtual platform in three versions

jackson-virtual-platform:2.9.5
jackson-virtual-platform:2.9.7
jackson-virtual-platform:2.9.0

The virtual platform depends on the three libs and the three libs depend on the platform.
The highest platform 2.9.7 is selected, so all three libs are used in 2.9.7 and thus of course also have belongsTo the 2.9.7 platform in the end.

belongsTo("com.fasterxml.jackson:jackson-virtual-platform:${id.version}")

I think this code only doing:
s1: add $lib_n:version_m$ (direct and indirect dep) 's belongsTo $platfrom1:version_m$ , to $lib_n:version_m$'s metaInfo

But there’s some none depdency lib, like jackson-annotations:2.9.7.

So as you say, there must is also some work doing implicitly:
s2: for $platform1:version_m$, add $version_m$ to verArr
s3: colect all $lib_n$ to libArr
s4: gen and run rule, for all lib of highest version.

top version $version_m$ in verArr:
    for $lib_n$ in libArr:
        generate $lib_n:version_m$  if not exists previously(not direct and indirect  dep);  run JacksonBomAlignmentRule add belongsTo over $lib_n:version_m$ 

s5: collect all belongsTo to platform1.

Is those steps right?

  1. I didn’t really understand your setps
  2. I have no idea what the exact implementation algorithm is. If you are interested in that, I’d suggest you look at the Gradle source code available on GitHub.

I’v read the src, and find the algrithm.

Here the platform will compose all the module and version in the platform, thus add the jackson-annotations:2.9.7 to jackson-virtual-platform:2.9.7

DependencyGraphBuilder.traverseGraph->
LenientPlatformDependencyMetadata.getDependencies:

        public List<? extends ModuleDependencyMetadata> getDependencies() {
            List<ModuleDependencyMetadata> result = null;
            List<String> candidateVersions = platformState.getCandidateVersions();
            Set<ModuleResolveState> modules = platformState.getParticipatingModules();
            for (ModuleResolveState module : modules) {
                ComponentState selected = module.getSelected();
                if (selected != null) {
                    String componentVersion = selected.getId().getVersion();
                    for (String target : candidateVersions) {
                        // here composed  the new version: jackson-annotations:2.9.7
                        ModuleComponentIdentifier leafId = DefaultModuleComponentIdentifier.newId(module.getId(), target);
                        ModuleComponentSelector leafSelector = DefaultModuleComponentSelector.newSelector(module.getId(), target);
                        ComponentIdentifier platformId = platformState.getSelectedPlatformId();
                        if (platformId == null) {
                            // Not sure this can happen, unless in error state
                            platformId = this.platformId;
                        }
                        if (!componentVersion.equals(target)) {
                            // We will only add dependencies to the leaves if there is such a published module
                            // call JacksonBomAlignmentRule
                            PotentialEdge potentialEdge = PotentialEdge.of(resolveState, from, leafId, leafSelector, 
                                                                           platformId, platformState.isForced(), false);
                            if (potentialEdge.metadata != null) {
                                result = registerPlatformEdge(result, modules, leafId, leafSelector, 
                                                              platformId, platformState.isForced());
                                break;
                            }
                        } else {
                            // at this point we know the component exists
                            result = registerPlatformEdge(result, modules, leafId, leafSelector, platformId, platformState.isForced());
                            break;
                        }
                    }
                    platformState.attachOrphanEdges();
                }
            }
            return result == null ? Collections.emptyList() : result;
        }