// Copyright (C) 2024 Gradle, Inc.
//
// Licensed under the Creative Commons Attribution-Noncommercial-ShareAlike 4.0 International License.;
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      https://creativecommons.org/licenses/by-nc-sa/4.0/
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

= Customizing publishing

[[sec:adding-variants-to-existing-components]]
== 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 <<variant_aware_resolution.adoc#sec:variant-aware-matching, 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 <<declaring_configurations.adoc#sec:resolvable-consumable-configs,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:

====
include::sample[dir="snippets/dependencyManagement/modelingFeatures-crossProjectPublications-advanced-published/kotlin/buildSrc/src/main/kotlin/com/acme",files="InstrumentedJarsPlugin.kt[tags=add_variant_to_existing_component]"]
include::sample[dir="snippets/dependencyManagement/modelingFeatures-crossProjectPublications-advanced-published/groovy/buildSrc/src/main/groovy/com/acme",files="InstrumentedJarsPlugin.groovy[tags=add_variant_to_existing_component]"]
====

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:

====
include::sample[dir="snippets/maven-publish/javaProject/kotlin",files="build.gradle.kts[tags=publish-modify-component]"]
include::sample[dir="snippets/maven-publish/javaProject/groovy",files="build.gradle[tags=publish-modify-component]"]
====

[[sec:publishing-custom-components]]
== Creating and publishing custom components

In the <<sec:adding-variants-to-existing-components, 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 link:{javadocPath}/org/gradle/api/component/AdhocComponentWithVariants.html[AdhocComponentWithVariants] API.
For a custom component, the first step is to create custom outgoing variants, following the instructions in <<variant_aware_resolution.adoc#sec:understanding-variant-selection, 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.

====
include::sample[dir="snippets/dependencyManagement/modelingFeatures-crossProjectPublications-advanced-published/kotlin/buildSrc/src/main/kotlin/com/acme",files="InstrumentedJarsPlugin.kt[tags=create_adhoc_component]"]
include::sample[dir="snippets/dependencyManagement/modelingFeatures-crossProjectPublications-advanced-published/groovy/buildSrc/src/main/groovy/com/acme",files="InstrumentedJarsPlugin.groovy[tags=create_adhoc_component]"]
====

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 <<sec:adding-variants-to-existing-components,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 link:{javadocPath}/org/gradle/api/artifacts/ConfigurationPublications.html[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 link:{javadocPath}/org/gradle/api/component/ConfigurationVariantDetails.html[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 link:{javadocPath}/org/gradle/api/component/ConfigurationVariantDetails.html[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".

[[sec:publishing_custom_artifacts_to_maven]]
== 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 <<how_to_create_feature_variants_of_a_library#feature_variants,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_resolution.adoc#sec:understanding-variant-selection, variant-aware sharing>> section of the documentation to see how to declare such variants and <<#sec:publishing-custom-components, 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 <<sec:adding-variants-to-existing-components,adjust a component>> or use a <<#sec:publishing-custom-components,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 <<glossary.adoc#sub:terminology_configuration,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:

====
include::sample[dir="snippets/maven-publish/publish-artifact/kotlin",files="build.gradle.kts[tags=custom-artifact]"]
include::sample[dir="snippets/maven-publish/publish-artifact/groovy",files="build.gradle[tags=custom-artifact]"]
====

The `artifacts.add()` method — from link:{groovyDslPath}/org.gradle.api.artifacts.dsl.ArtifactHandler.html[ArtifactHandler] — returns an artifact object of type link:{javadocPath}/org/gradle/api/artifacts/PublishArtifact.html[PublishArtifact] that can then be used in defining a publication, as shown in the following sample:

====
include::sample[dir="snippets/maven-publish/publish-artifact/kotlin",files="build.gradle.kts[tags=custom-artifact-publication]"]
include::sample[dir="snippets/maven-publish/publish-artifact/groovy",files="build.gradle[tags=custom-artifact-publication]"]
====

* The `artifact()` method accepts _publish artifacts_ as argument — like `rpmArtifact` in the sample — as well as any type of argument accepted by link:{groovyDslPath}/org.gradle.api.Project.html#org.gradle.api.Project:file(java.lang.Object)[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 <<publishing_maven.adoc#publishing_maven,Maven Publish Plugin>> and the <<publishing_ivy.adoc#publishing_ivy,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 <<#sec:adding-variants-to-existing-components,adjusting the component>> itself, you can combine the `from components.someComponent` and `artifact someArtifact` notations.

[[sec:publishing_maven:conditional_publishing]]
== 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:

====
include::sample[dir="snippets/maven-publish/conditional-publishing/kotlin",files="build.gradle.kts[tags=publishing]"]
include::sample[dir="snippets/maven-publish/conditional-publishing/groovy",files="build.gradle[tags=publishing]"]
====

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 link:{groovyDslPath}/org.gradle.api.Task.html#org.gradle.api.Task:onlyIf(java.lang.String,org.gradle.api.specs.Spec)[Task.onlyIf(String, org.gradle.api.specs.Spec)] method. The following sample demonstrates how to implement the constraints we just mentioned:

====
include::sample[dir="snippets/maven-publish/conditional-publishing/kotlin",files="build.gradle.kts[tags=task-config]"]
include::sample[dir="snippets/maven-publish/conditional-publishing/groovy",files="build.gradle[tags=task-config]"]
====

[source,bash]
----
$ ./gradlew publish
----
[source,text]
----
include::{snippetsPath}/maven-publish/conditional-publishing/tests/publishingMavenConditionally.out[]
----

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:

====
include::sample[dir="snippets/maven-publish/conditional-publishing/kotlin",files="build.gradle.kts[tags=shorthand-tasks]"]
include::sample[dir="snippets/maven-publish/conditional-publishing/groovy",files="build.gradle[tags=shorthand-tasks]"]
====

This particular sample automatically handles the introduction or removal of the relevant publishing tasks by using link:{javadocPath}/org/gradle/api/tasks/TaskCollection.html#withType-java.lang.Class-[TaskCollection.withType(java.lang.Class)] with the link:{groovyDslPath}/org.gradle.api.publish.maven.tasks.PublishToMavenRepository.html[PublishToMavenRepository] task type. You can do the same with link:{groovyDslPath}/org.gradle.api.publish.ivy.tasks.PublishToIvyRepository.html[PublishToIvyRepository] if you're publishing to Ivy-compatible repositories.

[[sec:configuring_publishing_tasks]]
== 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 `generatePomFileFor__PubName__Publication` tasks write their POM files. You can do this by using the link:{javadocPath}/org/gradle/api/tasks/TaskCollection.html#withType-java.lang.Class-[TaskCollection.withType(java.lang.Class)] method, as demonstrated by this sample:

====
include::sample[dir="snippets/publishing/javaLibrary/kotlin",files="build.gradle.kts[tags=configure-generate-task]"]
include::sample[dir="snippets/publishing/javaLibrary/groovy",files="build.gradle[tags=configure-generate-task]"]
====

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.
