// Copyright (C) 2023 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.

[[cross_project_publications]]
= Sharing outputs between projects

A common pattern, in multi-project builds, is that one project consumes the artifacts of another project.
In general, the simplest consumption form in the Java ecosystem is that when `A` depends on `B`, then `A` would depend on the `jar` produced by project `B`.
As previously described in this chapter, this is modeled by `A` depending on a _variant of_ `B`, where the variant is selected based on the needs of `A`.
For compilation, we need the API dependencies of `B`, provided by the `apiElements` variant.
For runtime, we need the runtime dependencies of `B`, provided by the `runtimeElements` variant.

However, what if you need a _different_ artifact than the main one?
Gradle provides, for example, built-in support for depending on the <<java_testing.adoc#sec:java_test_fixtures,test fixtures>> of another project, but sometimes the artifact you need to depend on simply isn't exposed as a variant.

In order to be _safe to share_ between projects and allow maximum performance (parallelism), such artifacts must be exposed via _outgoing configurations_.

.Don't reference other project tasks directly
--
A frequent anti-pattern to declare cross-project dependencies is:

[source,groovy]
----
dependencies {
   // this is unsafe!
   implementation project(":other").tasks.someOtherJar
}
----

This publication model is _unsafe_ and can lead to non-reproducible and hard to parallelize builds.
This section explains how to _properly create cross-project boundaries_ by defining "exchanges" between projects by using _variants_.
--

There are two, complementary, options to share artifacts between projects.
The <<#sec:simple-sharing-artifacts-between-projects,simplified version>> is only suitable if what you need to share is a simple artifact that doesn't depend on the consumer.
The simple solution is also limited to cases where this artifact is not published to a repository.
This also implies that the consumer does not publish a dependency to this artifact.
In cases where the consumer resolves to different artifacts in different contexts (e.g., different target platforms) or that publication is required, you need to use the <<#sec:variant-aware-sharing,advanced version>>.

[[sec:simple-sharing-artifacts-between-projects]]
== Simple sharing of artifacts between projects

First, a producer needs to declare a configuration which is going to be _exposed_ to consumers.
As explained in the <<declaring_dependencies.adoc#sec:resolvable-consumable-configs, configurations chapter>>, this corresponds to a _consumable configuration_.

Let's imagine that the consumer requires _instrumented classes_ from the producer, but that this artifact is _not_ the main one.
The producer can expose its instrumented classes by creating a configuration that will "carry" this artifact:

.Declaring an outgoing variant
====
include::sample[dir="snippets/dependencyManagement/modelingFeatures-crossProjectPublications-simple/kotlin",files="producer/build.gradle.kts[tags=declare-outgoing-configuration]"]
include::sample[dir="snippets/dependencyManagement/modelingFeatures-crossProjectPublications-simple/groovy",files="producer/build.gradle[tags=declare-outgoing-configuration]"]
====

This configuration is _consumable_, which means it's an "exchange" meant for consumers.
We're now going to add artifacts to this configuration, that consumers would get when they consume it:

.Attaching an artifact to an outgoing configuration
====
include::sample[dir="snippets/dependencyManagement/modelingFeatures-crossProjectPublications-simple/kotlin",files="producer/build.gradle.kts[tags=attach-outgoing-artifact]"]
include::sample[dir="snippets/dependencyManagement/modelingFeatures-crossProjectPublications-simple/groovy",files="producer/build.gradle[tags=attach-outgoing-artifact]"]
====

Here the "artifact" we're attaching is a _task_ that actually generates a Jar.
Doing so, Gradle can automatically track dependencies of this task and build them as needed.
This is possible because the `Jar` task extends `AbstractArchiveTask`.
If it's not the case, you will need to explicitly declare how the artifact is generated.

.Explicitly declaring the task dependency of an artifact
====
include::sample[dir="snippets/dependencyManagement/modelingFeatures-crossProjectPublications-simple/kotlin",files="producer/build.gradle.kts[tags=attach-outgoing-artifact-explicit]"]
include::sample[dir="snippets/dependencyManagement/modelingFeatures-crossProjectPublications-simple/groovy",files="producer/build.gradle[tags=attach-outgoing-artifact-explicit]"]
====

Now the _consumer_ needs to depend on this configuration in order to get the right artifact:

.An explicit configuration dependency
====
include::sample[dir="snippets/dependencyManagement/modelingFeatures-crossProjectPublications-simple/kotlin",files="consumer/build.gradle.kts[tags=explicit-configuration-dependency]"]
include::sample[dir="snippets/dependencyManagement/modelingFeatures-crossProjectPublications-simple/groovy",files="consumer/build.gradle[tags=explicit-configuration-dependency]"]
====

WARNING: Declaring a dependency on an explicit target configuration is _not recommended_.
If you plan to publish the component which has this dependency, this will likely lead to broken metadata.
If you need to publish the component on a remote repository, follow the instructions of the <<#sec:variant-aware-sharing,variant-aware cross publication documentation>>.

In this case, we're adding the dependency to the _instrumentedClasspath_ configuration, which is a _consumer specific configuration_.
In Gradle terminology, this is called a <<declaring_dependencies.adoc#sec:resolvable-consumable-configs,resolvable configuration>>, which is defined this way:

.Declaring a resolvable configuration on the consumer
====
include::sample[dir="snippets/dependencyManagement/modelingFeatures-crossProjectPublications-simple/kotlin",files="consumer/build.gradle.kts[tags=resolvable-configuration]"]
include::sample[dir="snippets/dependencyManagement/modelingFeatures-crossProjectPublications-simple/groovy",files="consumer/build.gradle[tags=resolvable-configuration]"]
====

[[sec:variant-aware-sharing]]
== Variant-aware sharing of artifacts between projects

In the <<#sec:simple-sharing-artifacts-between-projects, simple sharing solution>>, we defined a configuration on the producer side which serves as an exchange of artifacts between the producer and the consumer.
However, the consumer has to explicitly tell which configuration it depends on, which is something we want to avoid in _variant aware resolution_.
In fact, we also <<variant_model.adoc#understanding-variant-selection, have explained>> that it is possible for a consumer to express requirements using _attributes_ and that the producer should provide the appropriate outgoing variants using attributes too.
This allows for smarter selection, because using a single dependency declaration, without any explicit target configuration, the consumer may resolve different things.
The typical example is that using a single dependency declaration `project(":myLib")`, we would either choose the `arm64` or `i386` version of `myLib` depending on the architecture.

To do this, we will add attributes to both the consumer and the producer.

--
It is important to understand that once configurations have attributes, they participate in _variant aware resolution_, which means that they are candidates considered whenever _any_ notation like `project(":myLib")` is used.
In other words, the attributes set on the producer _must be consistent with the other variants produced on the same project_.
They must not, in particular, introduce ambiguity for the existing selection.

In practice, it means that the attribute set used on the configuration you create are likely to be dependent on the _ecosystem_ in use (Java, C++, ...) because the relevant plugins for those ecosystems often use different attributes.
--

Let's enhance our previous example which happens to be a Java Library project.
Java libraries expose a couple of variants to their consumers, `apiElements` and `runtimeElements`. Now, we're adding a 3rd one, `instrumentedJars`.

Therefore, we need to understand what our new variant is used for in order to set the proper attributes on it.
Let's look at the attributes we find on the `runtimeElements` configuration on the producer:

.gradle outgoingVariants --variant runtimeElements
----
Attributes
    - org.gradle.category            = library
    - org.gradle.dependency.bundling = external
    - org.gradle.jvm.version         = 11
    - org.gradle.libraryelements     = jar
    - org.gradle.usage               = java-runtime
----

What it tells us is that the Java Library plugin produces variants with 5 attributes:

- `org.gradle.category` tells us that this variant represents a _library_
- `org.gradle.dependency.bundling` tells us that the dependencies of this variant are found as jars (they are not, for example, repackaged inside the jar)
- `org.gradle.jvm.version` tells us that the minimum Java version this library supports is Java 11
- `org.gradle.libraryelements` tells us this variant contains all elements found in a jar (classes and resources)
- `org.gradle.usage` says that this variant is a Java runtime, therefore suitable for a Java compiler but also at runtime

As a consequence, if we want our instrumented classes to be used in place of this variant when executing tests, we need to attach similar attributes to our variant.
In fact, the attribute we care about is `org.gradle.libraryelements` which explains _what the variant contains_, so we can setup the variant this way:

.Declaring the variant attributes
====
include::sample[dir="snippets/dependencyManagement/modelingFeatures-crossProjectPublications-advanced/kotlin",files="producer/build.gradle.kts[tags=declare-outgoing-configuration]"]
include::sample[dir="snippets/dependencyManagement/modelingFeatures-crossProjectPublications-advanced/groovy",files="producer/build.gradle[tags=declare-outgoing-configuration]"]
====

[NOTE]
====
Choosing the right attributes to set is the hardest thing in this process, because they carry the semantics of the variant.
Therefore, before adding _new attributes_, you should always ask yourself if there isn't an attribute which carries the semantics you need.
If there isn't, then you may add a new attribute.
When adding new attributes, you must also be careful because it's possible that it creates ambiguity during selection.
Often adding an attribute means adding it to _all_ existing variants.
====

What we have done here is that we have added a _new_ variant, which can be used _at runtime_, but contains instrumented classes instead of the normal classes.
However, it now means that for runtime, the consumer has to choose between two variants:

- `runtimeElements`, the regular variant offered by the `java-library` plugin
- `instrumentedJars`, the variant we have created

In particular, say we want the instrumented classes on the test runtime classpath.
We can now, on the consumer, declare our dependency as a regular project dependency:

.Declaring the project dependency
====
include::sample[dir="snippets/dependencyManagement/modelingFeatures-crossProjectPublications-advanced/kotlin",files="consumer/build.gradle.kts[tags=test_dependency]"]
include::sample[dir="snippets/dependencyManagement/modelingFeatures-crossProjectPublications-advanced/groovy",files="consumer/build.gradle[tags=test_dependency]"]
====

If we stop here, Gradle will still select the `runtimeElements` variant in place of our `instrumentedJars` variant.
This is because the `testRuntimeClasspath` configuration asks for a configuration which `libraryelements` attribute is `jar`, and our new `instrumented-jars` value is _not compatible_.

So we need to change the requested attributes so that we now look for instrumented jars:

.Changing the consumer attributes
====
include::sample[dir="snippets/dependencyManagement/modelingFeatures-crossProjectPublications-advanced/kotlin",files="consumer/build.gradle.kts[tags=ask-for-instrumented-classes]"]
include::sample[dir="snippets/dependencyManagement/modelingFeatures-crossProjectPublications-advanced/groovy",files="consumer/build.gradle[tags=ask-for-instrumented-classes]"]
====

We can look at another report _on the consumer side_ to view exactly what attributes of each dependency will be requested:

.gradle resolvableConfigurations --configuration testRuntimeClasspath
----
Attributes
    - org.gradle.category            = library
    - org.gradle.dependency.bundling = external
    - org.gradle.jvm.version         = 11
    - org.gradle.libraryelements     = instrumented-jar
    - org.gradle.usage               = java-runtime
----

The `resolvableConfigurations` report is the complement of the `outgoingVariants` report.
By running both of these reports on the consumer and producer sides of a relationship, respectively, you can see exactly what attributes are involved in matching during dependency resolution and better predict the outcome when configurations are resolved.

Now, we're saying that whenever we're going to resolve the test runtime classpath, what we are looking for is _instrumented classes_.
There is a problem though: in our dependencies list, we have JUnit, which, obviously, is _not_ instrumented.
So if we stop here, Gradle is going to fail, explaining that there's no variant of JUnit which provide instrumented classes.
This is because we didn't explain that it's fine to use the regular jar, if no instrumented version is available.
To do this, we need to write a _compatibility rule_:

.A compatibility rule
====
include::sample[dir="snippets/dependencyManagement/modelingFeatures-crossProjectPublications-advanced/kotlin",files="consumer/build.gradle.kts[tags=compatibility-rule]"]
include::sample[dir="snippets/dependencyManagement/modelingFeatures-crossProjectPublications-advanced/groovy",files="consumer/build.gradle[tags=compatibility-rule]"]
====

which we need to declare on the attributes schema:

.Making use of the compatibility rule
====
include::sample[dir="snippets/dependencyManagement/modelingFeatures-crossProjectPublications-advanced/kotlin",files="consumer/build.gradle.kts[tags=compatibility-rule-use]"]
include::sample[dir="snippets/dependencyManagement/modelingFeatures-crossProjectPublications-advanced/groovy",files="consumer/build.gradle[tags=compatibility-rule-use]"]
====

And that's it! Now we have:

- added a variant which provides instrumented jars
- explained that this variant is a substitute for the runtime
- explained that the consumer needs this variant _only for test runtime_

Gradle therefore offers a powerful mechanism to select the right variants based on preferences and compatibility.
More details can be found in the <<variant_attributes.adoc#variant_attributes, variant aware plugins section of the documentation>>.

[WARNING]
====
By adding a value to an existing attribute like we have done, or by defining new attributes, we are extending the model.
This means that _all consumers_ have to know about this extended model.

For local consumers, this is usually not a problem because all projects understand and share the same schema, but if you had to publish this new variant to an external repository, it means that external consumers would have to add the same rules to their builds for them to pass.
This is in general not a problem for _ecosystem plugins_ (e.g: the Kotlin plugin) where consumption is in any case not possible without applying the plugin, but it is a problem if you add custom values or attributes.

So, **avoid publishing custom variants** if they are for internal use only.
====

[[targeting-different-platforms]]
== Targeting different platforms

It is common for a library to target different platforms.
In the Java ecosystem, we often see different artifacts for the same library, distinguished by a different _classifier_.
A typical example is Guava, which is published as this:

- `guava-jre` for JDK 8 and above
- `guava-android` for JDK 7

The problem with this approach is that there's no semantics associated with the classifier.
The dependency resolution engine, in particular, cannot determine automatically which version to use based on the consumer requirements.
For example, it would be better to express that you have a dependency on Guava, and let the engine choose between `jre` and `android` based on what is compatible.

Gradle provides an improved model for this, which doesn't have the weakness of classifiers: attributes.

In particular, in the Java ecosystem, Gradle provides a built-in attribute that library authors can use to express compatibility with the Java ecosystem: `org.gradle.jvm.version`.
This attribute expresses the _minimal version that a consumer must have in order to work properly_.

When you apply the `java` or `java-library` plugins, Gradle will automatically associate this attribute to the outgoing variants.
This means that all libraries published with Gradle automatically tell which target platform they use.

By default, the `org.gradle.jvm.version` is set to the value of the <<building_java_projects#sec:java_cross_compilation,`release` property>> (or as fallback to the `targetCompatibility` value) of the main compilation task of the source set.

While this attribute is automatically set, Gradle _will not_, by default, let you build a project for different JVMs.
If you need to do this, then you will need to create additional variants following the <<#sec:variant-aware-sharing,instructions on variant-aware matching>>.

NOTE: Future versions of Gradle will provide ways to automatically build for different Java platforms.
