You want to share a file made by a task in one project with a task in another project. For example, one task makes a file, and the other task reads the file and uses some information inside it. This is one way you can share information across project boundaries. (Another way is to use extension objects.)

This demonstrates the simple version of sharing information across project boundaries, by explicitly specifying which producer project’s consumable configuration to use for a locally available artifact.

When the producer publishes an artifact to a repository, to retrieve that artifact you will need to use the advanced version of variant aware dependency resolution. This method will also work locally.

Example

Example 1. Settings
settings.gradle
rootProject.name = "sharing-outputs"
include("producer")
include("consumer")
settings.gradle.kts
rootProject.name = "sharing-outputs"
include("producer")
include("consumer")
Example 2. Producer’s build
producer/build.gradle
def makeFile = tasks.register("makeFile") {
    def sharedFile = layout.buildDirectory.file("some-subdir/shared-file.txt")
    outputs.file(sharedFile)
    doFirst {
        sharedFile.get().asFile << "This file is shared across Gradle subprojects."
    }
}

configurations {
    sharedConfiguration {
        canBeConsumed = true
        canBeResolved = false
    }
}

artifacts {
    sharedConfiguration(makeFile)
}
producer/build.gradle.kts
val makeFile = tasks.register("makeFile") {
    val sharedFile = layout.buildDirectory.file("some-subdir/shared-file.txt")
    outputs.file(sharedFile)
    doFirst {
        sharedFile.get().asFile.writeText("This file is shared across Gradle subprojects.")
    }
}

val sharedConfiguration by configurations.creating {
    isCanBeConsumed = true
    isCanBeResolved = false
}

artifacts {
    add(sharedConfiguration.name, makeFile)
}
Example 3. Consumer’s build
consumer/build.gradle
configurations {
    sharedConfiguration {
        canBeConsumed = false
        canBeResolved = true
    }
}

dependencies {
    sharedConfiguration(project("path": ":producer", "configuration": "sharedConfiguration"))
}

tasks.register("showFile") {
    FileCollection sharedFiles = configurations.getByName("sharedConfiguration")
    inputs.files(sharedFiles)
    doFirst {
        logger.lifecycle("Shared file contains the text: '{}'", sharedFiles.singleFile.text)
    }
}
consumer/build.gradle.kts
val sharedConfiguration: Configuration by configurations.creating {
    isCanBeConsumed = false
    isCanBeResolved = true
}

dependencies {
    sharedConfiguration(project(path = ":producer", configuration = "sharedConfiguration"))
}

tasks.register("showFile") {
    val sharedFiles: FileCollection = sharedConfiguration
    inputs.files(sharedFiles)
    doFirst {
        logger.lifecycle("Shared file contains the text: '{}'", sharedFiles.singleFile.readText())
    }
}

See also:

Anti-patterns:

Don’t reference other project tasks directly

A frequent anti-pattern to declare cross-project dependencies is below. This publication model is unsafe and can lead to non-reproducible and hard to parallelize builds. By declaring a dependency in this way, the task ordering between consumers and producers is not known to Gradle at the time when it is deciding the order of tasks for a given build. This means that potentially, consumers of the file "someOtherJar" can execute before the producer task that creates the jar! This would lead to builds that are either totally broken, or worse, broken is a way that is subtle, flaky, and difficult to debug.

dependencies {
  // This publication model can make your build flaky and broken!
  implementation project(":other").tasks.someOtherJar
}