Understanding SoftwareComponentFactory

I am working on a plugin for projects which produce multiple publications. To be clear, this is not a multi-project build; just the single project creates the multiple publications. Each publication maps to a unique SourceSet. Roughly, we have:

projectDir/
    src/
        deployment/
            java/
            resources/
        main/
            java/
            resources/
        spi/
            java/
            resources/
        test/
            java/
            resources/

The plugin applies the java-library plugin, which is where main/test come from. It creates the other 2 itself.

At first, I simply tried to create the 2 publications and just add the corresponding jar, javadoc-jar and sources-jar to it. But that leads to problems with dependencies and descriptors.

If I understand correctly, the intended approach to this would be using SoftwareComponent(Factory). But I am having difficulty understanding how to use it even after reading through the relevant docs that I could find. So I went to the source-code, but still not finding all of the answers…

First, is there a simple way to apply additional SoftwareComponents that reflect the java component? These deployment and spi components basically “mirror” the java one (same structure), though applying different artifacts. I dug into JavaPlugin, but most of the component-related handling is defined in internal contracts.

Assuming Gradle does not offer such a simplified SoftwareComponent creation, any pointers on creating SoftwareComponents that mirror the java SoftwareComponent?

I realize this is largely open-ended; sorry about that. But honestly, I am pretty lost when it comes to components, variants, etc.

Ok, I think I got it. Or parts of it anyway. At the least I have the publications building module.json files that mirror the main one.

But the variant names are messed up. I have this (consider the deployment source-set):

private void preparePublication(SourceSet deploymentSourceSet, Project project) {
    // `deployment` e.g.
    final String publicationName = sourceSet.getName();
    final MavenPublication publication = publishingExtension.getPublications().create( 
            publicationName,
            MavenPublication.class
    );

    final AdhocComponentWithVariants publicationComponent = componentFactory.adhoc( publicationName );
    project.getComponents().add( publicationComponent );
    publication.from( publicationComponent );

    // NOTE : this `apiElements` Configuration would be named `deploymentsApiElements`
    final Configuration apiElements = project.getConfigurations().maybeCreate( sourceSet.getApiElementsConfigurationName() );
    apiElements.setCanBeResolved(false);
    apiElements.setCanBeConsumed(true);

    ...

    final ConfigurationPublications apiElementsOutgoing = apiElements.getOutgoing();
    apiElementsOutgoing.artifact( jarTask );
    apiElementsOutgoing.getAttributes().attribute( ArtifactAttributes.ARTIFACT_FORMAT, "jar");
    publicationComponent.addVariantsFromConfiguration( apiElements, (details) -> {
        details.mapToMavenScope( "compile" );
    } );
}

The problem is that the variants seemed to be named after the Configuration name, and as far as I can tell there is no way to override this. So I end up with:

  ...
  "variants": [
    {
      "name": "deploymentApiElements",
      ...
  ]
  ...

rather than:

  ...
  "variants": [
    {
      "name": "apiElements",
      ...
  ]
  ...

Is there a way to adjust these variant names?

I just did some post-processing of the pom.xml and module.json files as a quick hack to keep moving. But I’d love to hear more about components/modules/variants if anyone has pointers.

What do you want to change the variant name for?
Usually variants are not selected by name, but by capabilities and attributes.
Iirc the name should be pretty irrelevant and is mainly used for output in error messages if the resolution mechanism for example cannot decide between two variants.

So the convention names such as apiElements, etc are not important at all?

I might be wrong, as I still fall into traps with variant-aware stuff myself.
But yes, I think they are just mere labels used for the error reporting.
The important things for resolution are the capabilities and the attributes.

You can for example have two variants of a library with identical capabilities and attributes, except for the attribute for Java version compatibility, so a Java 11 build uses the Java 11 variant while a Java 8 build uses the Java 8 variant.

Or you can have two variants of a Gradle plugin with identical capabilities and attributes, except for the attribute for Gradle version compatibility, so a Gradle 7+ build uses the Gradle 7+ variant of the plugin while a Gradle -6 build uses the Gradle -6 variant.

Or you can have two artifacts of a library with different capabilities in their variants where the consuming build can explicitly request the capabilities, so that you can for example have a base code of the library with some database support and then have a variant for mysql, a variant for postgresql, and a variant for db2 which you can then select using the corresponding capability.

This whole variant-aware resolution with capabilities and attributes is a bit hard to grasp at first, but once you got your head wrapped around it to a certain degree it starts to be a really nice feature.