# Customizing publishing

version 9.8.0-20260726010428+0000

## Modifying and adding variants to existing components for publishing

Gradle’s publication model is based on the notion of components, which are defined by plugins. For example, the Java Library plugin defines a `java` component which corresponds to a library, but the Java Platform plugin defines another kind of component, named `javaPlatform`, which is effectively a different kind of software component (a platform).

Sometimes we want to add more variants to or modify existing variants of an existing component. For example, if you added a variant of a Java library for a different platform, you may just want to declare this additional variant on the `java` component itself. In general, declaring additional variants is often the best solution to publish additional artifacts.

To perform such additions or modifications, the `AdhocComponentWithVariants` interface declares two methods called `addVariantsFromConfiguration` and `withVariantsFromConfiguration` which accept two parameters:

- the outgoing configuration that is used as a variant source

- a customization action which allows you to filter which variants are going to be published

To utilise these methods, you must make sure that the `SoftwareComponent` you work with is itself an `AdhocComponentWithVariants`, which is the case for the components created by the Java plugins (Java, Java Library, Java Platform). Adding a variant is then very simple:

InstrumentedJarsPlugin.kt

    val javaComponent = components.findByName("java") as AdhocComponentWithVariants
    javaComponent.addVariantsFromConfiguration(outgoing) {
        // dependencies for this variant are considered runtime dependencies
        mapToMavenScope("runtime")
        // and also optional dependencies, because we don't want them to leak
        mapToOptional()
    }

InstrumentedJarsPlugin.groovy

    AdhocComponentWithVariants javaComponent = (AdhocComponentWithVariants) project.components.findByName("java")
    javaComponent.addVariantsFromConfiguration(outgoing) {
        // dependencies for this variant are considered runtime dependencies
        it.mapToMavenScope("runtime")
        // and also optional dependencies, because we don't want them to leak
        it.mapToOptional()
    }

In other cases, you might want to modify a variant that was added by one of the Java plugins already. For example, if you activate publishing of Javadoc and sources, these become additional variants of the `java` component. If you only want to publish one of them, e.g. only Javadoc but no sources, you can modify the `sources` variant to not being published:

build.gradle.kts

    java {
        withJavadocJar()
        withSourcesJar()
    }

    val javaComponent = components["java"] as AdhocComponentWithVariants
    javaComponent.withVariantsFromConfiguration(configurations["sourcesElements"]) {
        skip()
    }

    publishing {
        publications {
            create<MavenPublication>("mavenJava") {
                from(components["java"])
            }
        }
    }

build.gradle

    java {
        withJavadocJar()
        withSourcesJar()
    }

    components.java.withVariantsFromConfiguration(configurations.sourcesElements) {
        skip()
    }

    publishing {
        publications {
            mavenJava(MavenPublication) {
                from components.java
            }
        }
    }

## Creating and publishing custom components

In the previous example, we have demonstrated how to extend or modify an existing component, like the components provided by the Java plugins. But Gradle also allows you to build a custom component (not a Java Library, not a Java Platform, not something supported natively by Gradle).

Declaring what a custom component publishes is also done via the AdhocComponentWithVariants API. For a custom component, the first step is to create custom outgoing variants, following the instructions in this chapter. At this stage, what you should have is variants which can be used in cross-project dependencies, but that we are now going to publish to external repositories.

InstrumentedJarsPlugin.kt

    // create an adhoc component
    val softwareComponentFactory = extensions.getByType(PublishingExtension::class.java).softwareComponentFactory
    val adhocComponent = softwareComponentFactory.adhoc("myAdhocComponent")
    // add it to the list of components that this project declares
    components.add(adhocComponent)
    // and register a variant for publication
    adhocComponent.addVariantsFromConfiguration(outgoing) {
        mapToMavenScope("runtime")
    }

InstrumentedJarsPlugin.groovy

    // create an adhoc component
    def softwareComponentFactory = project.extensions.getByType(PublishingExtension).softwareComponentFactory
    def adhocComponent = softwareComponentFactory.adhoc("myAdhocComponent")
    // add it to the list of components that this project declares
    project.components.add(adhocComponent)
    // and register a variant for publication
    adhocComponent.addVariantsFromConfiguration(outgoing) {
        it.mapToMavenScope("runtime")
    }

First we use the factory to create a new adhoc component. Then we add a variant through the `addVariantsFromConfiguration` method, which is described in more detail in the previous section.

In simple cases, there’s a one-to-one mapping between a `Configuration` and a variant, in which case you can publish all variants issued from a single `Configuration` because they are effectively the same thing. However, there are cases where a `Configuration` is associated with additional configuration publications that we also call secondary variants. Such configurations make sense in a multi-project build, but not when publishing externally. This is for example the case when between projects you share a directory of files, but there’s no way you can publish a directory directly on a Maven repository (only packaged things like jars or zips). Look at the ConfigurationVariantDetails class for details about how to skip publication of a particular variant. If `addVariantsFromConfiguration` has already been called for a configuration, further modification of the resulting variants can be performed using `withVariantsFromConfiguration`.

When publishing an adhoc component like this:

- Gradle Module Metadata will exactly represent the published variants. In particular, all outgoing variants will inherit dependencies, artifacts and attributes of the published configuration.

- Maven and Ivy metadata files will be generated, but you need to declare how the dependencies are mapped to Maven scopes via the ConfigurationVariantDetails class.

In practice, it means that components created this way can be consumed by Gradle the same way as if they were "local components".

## Adding custom artifacts to a publication

Instead of thinking in terms of artifacts, you should embrace the variant aware model of Gradle. It is expected that a single module may need multiple artifacts. However this rarely stops there, if the additional artifacts represent an optional feature, they might also have different dependencies and more.

Gradle, via Gradle Module Metadata, supports the publication of additional variants which make those artifacts known to the dependency resolution engine. Please refer to the variant-aware sharing section of the documentation to see how to declare such variants and check out how to publish custom components.

If you attach extra artifacts to a publication directly, they are published "out of context". That means, they are not referenced in the metadata at all and can then only be addressed directly through a classifier on a dependency. In contrast to Gradle Module Metadata, Maven pom metadata will not contain information on additional artifacts regardless of whether they are added through a variant or directly, as variants cannot be represented in the pom format.

The following section describes how you publish artifacts directly if you are sure that metadata, for example Gradle or POM metadata, is irrelevant for your use case. For example, if your project doesn’t need to be consumed by other projects and the only thing required as result of the publishing are the artifacts themselves.

In general, there are two options:

- Create a publication only with artifacts

- Add artifacts to a publication based on a component with metadata (not recommended, instead adjust a component or use a adhoc component publication which will both also produce metadata fitting your artifacts)

To create a publication based on artifacts, start by defining a custom artifact and attaching it to a Gradle configuration of your choice. The following sample defines an RPM artifact that is produced by an `rpm` task (not shown) and attaches that artifact to the `conf` configuration:

build.gradle.kts

    configurations {
        create("conf")
    }
    val rpmFile = layout.buildDirectory.file("rpms/my-package.rpm")
    val rpmArtifact = artifacts.add("conf", rpmFile.get().asFile) {
        type = "rpm"
        builtBy("rpm")
    }

build.gradle

    configurations {
        conf
    }
    def rpmFile = layout.buildDirectory.file('rpms/my-package.rpm')
    def rpmArtifact = artifacts.add('conf', rpmFile.get().asFile) {
        type = 'rpm'
        builtBy 'rpm'
    }

The `artifacts.add()` method — from ArtifactHandler — returns an artifact object of type PublishArtifact that can then be used in defining a publication, as shown in the following sample:

build.gradle.kts

    publishing {
        publications {
            create<MavenPublication>("maven") {
                artifact(rpmArtifact)
            }
        }
    }

build.gradle

    publishing {
        publications {
            maven(MavenPublication) {
                artifact rpmArtifact
            }
        }
    }

- The `artifact()` method accepts publish artifacts as argument — like `rpmArtifact` in the sample — as well as any type of argument accepted by Project.file(java.lang.Object), such as a `File` instance, a string file path or a archive task.

- Publishing plugins support different artifact configuration properties, so always check the plugin documentation for more details. The `classifier` and `extension` properties are supported by both the Maven Publish Plugin and the Ivy Publish Plugin.

- Custom artifacts need to be distinct within a publication, typically via a unique combination of `classifier` and `extension`. See the documentation for the plugin you’re using for the precise requirements.

- If you use `artifact()` with an archive task, Gradle automatically populates the artifact’s metadata with the `classifier` and `extension` properties from that task.

Now you can publish the RPM.

If you really want to add an artifact to a publication based on a component, instead of adjusting the component itself, you can combine the `from components.someComponent` and `artifact someArtifact` notations.

## Restricting publications to specific repositories

When you have defined multiple publications or repositories, you often want to control which publications are published to which repositories. For instance, consider the following sample that defines two publications — one that consists of just a binary and another that contains the binary and associated sources — and two repositories — one for internal use and one for external consumers:

build.gradle.kts

    publishing {
        publications {
            create<MavenPublication>("binary") {
                from(components["java"])
            }
            create<MavenPublication>("binaryAndSources") {
                from(components["java"])
                artifact(tasks["sourcesJar"])
            }
        }
        repositories {
            // change URLs to point to your repos, e.g. http://my.org/repo
            maven {
                name = "external"
                url = uri(layout.buildDirectory.dir("repos/external"))
            }
            maven {
                name = "internal"
                url = uri(layout.buildDirectory.dir("repos/internal"))
            }
        }
    }

build.gradle

    publishing {
        publications {
            binary(MavenPublication) {
                from components.java
            }
            binaryAndSources(MavenPublication) {
                from components.java
                artifact sourcesJar
            }
        }
        repositories {
            // change URLs to point to your repos, e.g. http://my.org/repo
            maven {
                name = 'external'
                url = layout.buildDirectory.dir('repos/external')
            }
            maven {
                name = 'internal'
                url = layout.buildDirectory.dir('repos/internal')
            }
        }
    }

The publishing plugins will create tasks that allow you to publish either of the publications to either repository. They also attach those tasks to the `publish` aggregate task. But let’s say you want to restrict the binary-only publication to the external repository and the binary-with-sources publication to the internal one. To do that, you need to make the publishing conditional.

Gradle allows you to skip any task you want based on a condition via the Task.onlyIf(String, org.gradle.api.specs.Spec) method. The following sample demonstrates how to implement the constraints we just mentioned:

build.gradle.kts

    tasks.withType<PublishToMavenRepository>().configureEach {
        val predicate = provider {
            (repository == publishing.repositories["external"] &&
                publication == publishing.publications["binary"]) ||
            (repository == publishing.repositories["internal"] &&
                publication == publishing.publications["binaryAndSources"])
        }
        onlyIf("publishing binary to the external repository, or binary and sources to the internal one") {
            predicate.get()
        }
    }
    tasks.withType<PublishToMavenLocal>().configureEach {
        val predicate = provider {
            publication == publishing.publications["binaryAndSources"]
        }
        onlyIf("publishing binary and sources") {
            predicate.get()
        }
    }

build.gradle

    tasks.withType(PublishToMavenRepository) {
        def predicate = provider {
            (repository == publishing.repositories.external &&
                publication == publishing.publications.binary) ||
            (repository == publishing.repositories.internal &&
                publication == publishing.publications.binaryAndSources)
        }
        onlyIf("publishing binary to the external repository, or binary and sources to the internal one") {
            predicate.get()
        }
    }
    tasks.withType(PublishToMavenLocal) {
        def predicate = provider {
            publication == publishing.publications.binaryAndSources
        }
        onlyIf("publishing binary and sources") {
            predicate.get()
        }
    }

    $ ./gradlew publish

    > Task :compileJava
    > Task :processResources
    > Task :classes
    > Task :jar
    > Task :generateMetadataFileForBinaryAndSourcesPublication
    > Task :generatePomFileForBinaryAndSourcesPublication
    > Task :sourcesJar
    > Task :publishBinaryAndSourcesPublicationToExternalRepository SKIPPED
    > Task :publishBinaryAndSourcesPublicationToInternalRepository
    > Task :generateMetadataFileForBinaryPublication
    > Task :generatePomFileForBinaryPublication
    > Task :publishBinaryPublicationToExternalRepository
    > Task :publishBinaryPublicationToInternalRepository SKIPPED
    > Task :publish

    BUILD SUCCESSFUL in 0s
    10 actionable tasks: 10 executed

You may also want to define your own aggregate tasks to help with your workflow. For example, imagine that you have several publications that should be published to the external repository. It could be very useful to publish all of them in one go without publishing the internal ones.

The following sample demonstrates how you can do this by defining an aggregate task — `publishToExternalRepository` — that depends on all the relevant publish tasks:

build.gradle.kts

    tasks.register("publishToExternalRepository") {
        group = "publishing"
        description = "Publishes all Maven publications to the external Maven repository."
        dependsOn(tasks.withType<PublishToMavenRepository>().matching {
            it.repository == publishing.repositories["external"]
        })
    }

build.gradle

    tasks.register('publishToExternalRepository') {
        group = 'publishing'
        description = 'Publishes all Maven publications to the external Maven repository.'
        dependsOn tasks.withType(PublishToMavenRepository).matching {
            it.repository == publishing.repositories.external
        }
    }

This particular sample automatically handles the introduction or removal of the relevant publishing tasks by using TaskCollection.withType(java.lang.Class) with the PublishToMavenRepository task type. You can do the same with PublishToIvyRepository if you’re publishing to Ivy-compatible repositories.

## Configuring publishing tasks

The publishing plugins create their non-aggregate tasks after the project has been evaluated, which means you cannot directly reference them from your build script. If you would like to configure any of these tasks, you should use deferred task configuration. This can be done in a number of ways via the project’s `tasks` collection.

For example, imagine you want to change where the `generatePomFileForPubNamePublication` tasks write their POM files. You can do this by using the TaskCollection.withType(java.lang.Class) method, as demonstrated by this sample:

build.gradle.kts

    tasks.withType<GenerateMavenPom>().configureEach {
        val matcher = Regex("""generatePomFileFor(\w+)Publication""").matchEntire(name)
        val publicationName = matcher?.let { it.groupValues[1] }
        destination = layout.buildDirectory.file("poms/${publicationName}-pom.xml").get().asFile
    }

build.gradle

    tasks.withType(GenerateMavenPom).all {
        def matcher = name =~ /generatePomFileFor(\w+)Publication/
        def publicationName = matcher[0][1]
        destination = layout.buildDirectory.file("poms/${publicationName}-pom.xml").get().asFile
    }

The above sample uses a regular expression to extract the name of the publication from the name of the task. This is so that there is no conflict between the file paths of all the POM files that might be generated. If you only have one publication, then you don’t have to worry about such conflicts since there will only be one POM file.