Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions api/shadow.api
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ public abstract interface class com/github/jengelman/gradle/plugins/shadow/tasks
public abstract fun project (Ljava/lang/Object;)Lorg/gradle/api/specs/Spec;
public abstract fun resolve (Ljava/util/Collection;)Lorg/gradle/api/file/FileCollection;
public abstract fun resolve (Lorg/gradle/api/artifacts/Configuration;)Lorg/gradle/api/file/FileCollection;
public abstract fun resolve (Lorg/gradle/api/file/ConfigurableFileCollection;)Lorg/gradle/api/file/FileCollection;
}

public abstract class com/github/jengelman/gradle/plugins/shadow/tasks/DependencyFilter$AbstractDependencyFilter : com/github/jengelman/gradle/plugins/shadow/tasks/DependencyFilter {
Expand All @@ -182,6 +183,7 @@ public abstract class com/github/jengelman/gradle/plugins/shadow/tasks/Dependenc
public fun resolve (Ljava/util/Collection;)Lorg/gradle/api/file/FileCollection;
protected abstract fun resolve (Ljava/util/Set;Ljava/util/Set;Ljava/util/Set;)V
public fun resolve (Lorg/gradle/api/artifacts/Configuration;)Lorg/gradle/api/file/FileCollection;
public fun resolve (Lorg/gradle/api/file/ConfigurableFileCollection;)Lorg/gradle/api/file/FileCollection;
}

public abstract class com/github/jengelman/gradle/plugins/shadow/tasks/FindResourceInClasspath : org/gradle/api/DefaultTask, org/gradle/api/tasks/util/PatternFilterable {
Expand Down Expand Up @@ -273,6 +275,7 @@ public abstract class com/github/jengelman/gradle/plugins/shadow/tasks/ShadowJar
public fun getMainClass ()Lorg/gradle/api/provider/Property;
public fun getManifest ()Lcom/github/jengelman/gradle/plugins/shadow/tasks/InheritManifest;
public synthetic fun getManifest ()Lorg/gradle/api/java/archives/Manifest;
public fun getMergedDependencies ()Lorg/gradle/api/file/ConfigurableFileCollection;
public fun getMinimizeJar ()Lorg/gradle/api/provider/Property;
public fun getMinimizeSpec ()Lcom/github/jengelman/gradle/plugins/shadow/tasks/MinimizeSpec;
public fun getR8Classpath ()Lorg/gradle/api/file/ConfigurableFileCollection;
Expand Down
2 changes: 1 addition & 1 deletion docs/configuration/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ flowchart TD
D4 -->|"No"| D6
end

click A2 href "../api/shadow/com.github.jengelman.gradle.plugins.shadow.tasks/-shadow-jar/configurations.html" "ShadowJar.configurations"
click A2 href "../api/shadow/com.github.jengelman.gradle.plugins.shadow.tasks/-shadow-jar/merged-dependencies.html" "ShadowJar.mergedDependencies"
click A3 href "../api/shadow/com.github.jengelman.gradle.plugins.shadow/-shadow-base-plugin/-companion/-c-o-n-f-i-g-u-r-a-t-i-o-n_-n-a-m-e.html" "ShadowBasePlugin.CONFIGURATION_NAME"
click A5 href "../api/shadow/com.github.jengelman.gradle.plugins.shadow.tasks/-shadow-jar/included-dependencies.html" "ShadowJar.includedDependencies"
click A4 href "../api/shadow/com.github.jengelman.gradle.plugins.shadow.tasks/-shadow-jar/dependencies.html" "ShadowJar.dependencies"
Expand Down
21 changes: 5 additions & 16 deletions docs/configuration/dependencies/README.md
Original file line number Diff line number Diff line change
@@ -1,41 +1,30 @@
# Configuring Shadowed Dependencies

Shadow configures the default [`ShadowJar`][ShadowJar] task to merge all dependencies from the project's
`runtimeClasspath` configuration into the final JAR. The configurations from which to source dependencies for the
merging can be configured using the [`configurations`][ShadowJar.configurations] property of the
`runtimeClasspath` configuration into the final JAR. The dependencies and files from which to source dependencies for the
merging can be configured using the [`mergedDependencies`][ShadowJar.mergedDependencies] property of the
[`ShadowJar`][ShadowJar] task type.

=== ":material-language-kotlin: build.gradle.kts"

```kotlin
tasks.shadowJar {
configurations = project.configurations.compileClasspath.map { listOf(it) }
mergedDependencies.setFrom(project.configurations.compileClasspath)
}
```

=== ":simple-apachegroovy: build.gradle"

```groovy
tasks.named('shadowJar', com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) {
configurations = project.configurations.named('compileClasspath').map { [it] }
mergedDependencies.setFrom project.configurations.named('compileClasspath')
}
```

The above code sample would configure the [`ShadowJar`][ShadowJar] task to merge dependencies from only the
`compileClasspath` configuration. This means any dependency declared in the `runtimeOnly` configuration would be **not**
be included in the final JAR.

> [!WARNING]
> **Required Configuration**
>
> Note the literal use of [`project.configurations`][Project.configurations] when setting the
> [`configurations`][ShadowJar.configurations] attribute of a [`ShadowJar`][ShadowJar] task.
>
> This is **required**. It may be tempting to specify `configurations = [configurations.compileClasspath]` but
> this will not have the intended effect, as `configurations.compile` will try to delegate to the
> [`configurations`][ShadowJar.configurations] property of the [`ShadowJar`][ShadowJar] task instead of the
> `project`.

## Embedding Local Jar Files into Your Shadowed JAR

The [`ShadowJar`][ShadowJar] task is a subclass of the [`Jar`][Jar] task, which means that the [`Jar.from`][Jar.from]
Expand Down Expand Up @@ -486,7 +475,7 @@ block provides a method that accepts a `Closure` for selecting dependencies.

[Jar.from]: https://docs.gradle.org/current/dsl/org.gradle.jvm.tasks.Jar.html#org.gradle.jvm.tasks.Jar:from(java.lang.Object,%20org.gradle.api.Action)
[Jar]: https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Jar.html
[ShadowJar.configurations]: ../../api/shadow/com.github.jengelman.gradle.plugins.shadow.tasks/-shadow-jar/configurations.html
[ShadowJar.mergedDependencies]: ../../api/shadow/com.github.jengelman.gradle.plugins.shadow.tasks/-shadow-jar/merged-dependencies.html
[ShadowJar.dependencies]: ../../api/shadow/com.github.jengelman.gradle.plugins.shadow.tasks/-shadow-jar/dependencies.html
[ShadowJar]: ../../api/shadow/com.github.jengelman.gradle.plugins.shadow.tasks/-shadow-jar/index.html
[Project.configurations]: https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:configurations
Expand Down
10 changes: 5 additions & 5 deletions docs/configuration/relocation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,8 +231,8 @@ relocating), you can try out the trick like:

```kotlin
tasks.shadowJar {
// Empty configurations list will exclude all dependencies.
configurations = emptyList()
// Empty mergedDependencies will exclude all dependencies.
mergedDependencies.setFrom()
relocate("com.example", "shadow.com.example")
}
```
Expand All @@ -241,14 +241,14 @@ relocating), you can try out the trick like:

```groovy
tasks.named('shadowJar', com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) {
// Empty configurations list will exclude all dependencies.
configurations = []
// Empty mergedDependencies will exclude all dependencies.
mergedDependencies.setFrom()
relocate 'com.example', 'shadow.com.example'
}
```

This is useful in some cases, as mentioned in [#759]. See
[Configuring Shadowed Dependencies][configuring-shadowed-dependencies] for more information about `configurations`.
[Configuring Shadowed Dependencies][configuring-shadowed-dependencies] for more information about `mergedDependencies`.

## Relocating with R8

Expand Down
14 changes: 7 additions & 7 deletions docs/custom-tasks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

The built in [`ShadowJar`][ShadowJar] task only provides an output for the `main` source set of the project. It is
possible to add arbitrary [`ShadowJar`][ShadowJar] tasks to a project. When doing so, ensure that the
[`configurations`][ShadowJar.configurations] property is specified to inform Shadow which dependencies to merge into the
[`mergedDependencies`][ShadowJar.mergedDependencies] property is specified to inform Shadow which dependencies to merge into the
output.

=== ":material-language-kotlin: build.gradle.kts"
Expand All @@ -13,7 +13,7 @@ output.

archiveClassifier = "test"
from(sourceSets.test.map { it.output })
configurations = project.configurations.testRuntimeClasspath.map { listOf(it) }
mergedDependencies.setFrom(project.configurations.testRuntimeClasspath)

manifest {
// Optionally, set the main class for the JAR.
Expand All @@ -36,7 +36,7 @@ output.

archiveClassifier = 'test'
from sourceSets.named('test').map { it.output }
configurations = project.configurations.named('testRuntimeClasspath').map { [it] }
mergedDependencies.setFrom project.configurations.named('testRuntimeClasspath')

manifest {
// Optionally, set the main class for the JAR.
Expand All @@ -59,15 +59,15 @@ The code snippet above will generate a shadowed JAR containing both the `main` a

It is also possible to create a shadow JAR that contains *only* the dependencies and none of the project's own source
code. This is accomplished by creating a custom [`ShadowJar`][ShadowJar] task and configuring the
[`configurations`][ShadowJar.configurations] property, but **not** adding any project sources with `from(...)`.
[`mergedDependencies`][ShadowJar.mergedDependencies] property, but **not** adding any project sources with `from(...)`.

=== ":material-language-kotlin: build.gradle.kts"

```kotlin
val dependencyShadowJar = tasks.register<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("dependencyShadowJar") {
description = "Create a shadow JAR of all dependencies"
archiveClassifier = "dep"
configurations = project.configurations.runtimeClasspath.map { listOf(it) }
mergedDependencies.setFrom(project.configurations.runtimeClasspath)
}
```

Expand All @@ -77,7 +77,7 @@ code. This is accomplished by creating a custom [`ShadowJar`][ShadowJar] task an
tasks.register('dependencyShadowJar', com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) {
description = 'Create a shadow JAR of all dependencies'
archiveClassifier = 'dep'
configurations = project.configurations.named('runtimeClasspath').map { [it] }
mergedDependencies.setFrom project.configurations.named('runtimeClasspath')
}
```

Expand All @@ -86,5 +86,5 @@ configuration. The standard `jar` task will still produce a JAR with only the pr


[Jar]: https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Jar.html
[ShadowJar.configurations]: ../api/shadow/com.github.jengelman.gradle.plugins.shadow.tasks/-shadow-jar/configurations.html
[ShadowJar.mergedDependencies]: ../api/shadow/com.github.jengelman.gradle.plugins.shadow.tasks/-shadow-jar/merged-dependencies.html
[ShadowJar]: ../api/shadow/com.github.jengelman.gradle.plugins.shadow.tasks/-shadow-jar/index.html
6 changes: 3 additions & 3 deletions docs/publishing/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ The Shadow plugin provides a custom configuration (`configurations.shadow`) to s

No other dependencies are automatically configured for inclusion in the POM file. For example, excluded dependencies are
**not** automatically added to the POM file or if the configuration for merging are modified by specifying
`shadowJar.configurations = [configurations.myConfiguration]`, there is no automatic configuration of the POM file.
`shadowJar.mergedDependencies.setFrom(configurations.myConfiguration)`, there is no automatic configuration of the POM file.

This automatic configuration occurs _only_ when using the above methods for configuring publishing. If this behavior is
not desirable, then publishing **must** be manually configured.
Expand Down Expand Up @@ -392,7 +392,7 @@ It is possible to publish a custom [`ShadowJar`][ShadowJar] task's output via th
description = "Create a combined JAR of project and test dependencies"
archiveClassifier = "tests"
from(sourceSets.test.map { it.output })
configurations = project.configurations.testRuntimeClasspath.map { listOf(it) }
mergedDependencies.setFrom(project.configurations.testRuntimeClasspath)
}

dependencies {
Expand Down Expand Up @@ -424,7 +424,7 @@ It is possible to publish a custom [`ShadowJar`][ShadowJar] task's output via th
description = 'Create a combined JAR of project and test dependencies'
archiveClassifier = 'tests'
from sourceSets.named('test').map { it.output }
configurations = project.configurations.named('testRuntimeClasspath').map { [it] }
mergedDependencies.setFrom project.configurations.named('testRuntimeClasspath')
}

dependencies {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -693,7 +693,7 @@ class JavaPluginsTest : BasePluginTest() {
| description = 'Create a combined JAR of project and test dependencies'
| archiveClassifier = 'test'
| from sourceSets.named('test').map { it.output }
| configurations = project.configurations.named('testRuntimeClasspath').map { [it] }
| mergedDependencies.from project.configurations.named('testRuntimeClasspath')
| manifest {
| attributes '$mainClassAttributeKey': 'my.Main'
| }
Expand Down Expand Up @@ -729,7 +729,7 @@ class JavaPluginsTest : BasePluginTest() {
| description = 'Create a combined JAR of project and test dependencies'
| archiveClassifier = 'test'
| from sourceSets.named('test').map { it.output }
| configurations = project.configurations.named('testRuntimeClasspath').map { [it] }
| mergedDependencies.from project.configurations.named('testRuntimeClasspath')
| manifest {
| attributes '$mainClassAttributeKey': 'my.Main'
| }
Expand Down Expand Up @@ -772,7 +772,7 @@ class JavaPluginsTest : BasePluginTest() {
|def $dependencyShadowJar = tasks.register('$dependencyShadowJar', ${ShadowJar::class.java.name}) {
| description = 'Create a shadow JAR of all dependencies'
| archiveClassifier = 'dep'
| configurations = project.configurations.named('runtimeClasspath').map { [it] }
| mergedDependencies.from project.configurations.named('runtimeClasspath')
|}
"""
.trimMargin()
Expand All @@ -790,6 +790,33 @@ class JavaPluginsTest : BasePluginTest() {
}
}

@Test
fun registerCustomShadowJarUsingDeprecatedConfigurations() {
val mainClassEntry = writeClass()
val legacyShadowJar = "legacyShadowJar"

projectScript.appendText(
"""
|dependencies {
| implementation 'junit:junit:3.8.2'
|}
|def $legacyShadowJar = tasks.register('$legacyShadowJar', ${ShadowJar::class.java.name}) {
| description = 'Create a shadow JAR of all dependencies using deprecated configurations'
| archiveClassifier = 'legacy'
| configurations = project.configurations.named('runtimeClasspath').map { [it] }
|}
"""
.trimMargin()
)

runWithSuccess("jar", legacyShadowJar)

assertThat(jarPath("build/libs/my-1.0-legacy.jar")).useAll {
containsOnly(*junitEntries, *manifestEntries)
transform { it.mainAttrSize }.isEqualTo(1)
}
}

@Test
fun registerCustomShadowJarWithoutShadowR8Configuration() {
val customShadowJar = "customShadowJar"
Expand Down Expand Up @@ -1180,7 +1207,7 @@ class JavaPluginsTest : BasePluginTest() {
|}
|
|$shadowJarTask {
| configurations = [project.configurations.runtimeClasspath]
| mergedDependencies.from project.configurations.runtimeClasspath
|}
|
|configurations.runtimeClasspath {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ class PublishingTest : BasePluginTest() {
| description = 'Create a combined JAR of project and test dependencies'
| archiveClassifier = 'tests'
| from sourceSets.named('test').map { it.output }
| configurations = project.configurations.named('testRuntimeClasspath').map { [it] }
| mergedDependencies.from project.configurations.named('testRuntimeClasspath')
|}
"""
.trimMargin(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,7 @@ class RelocationTest : BasePluginTest() {
| implementation 'junit:junit:3.8.2'
|}
|$shadowJarTask {
| configurations = []
| mergedDependencies.setFrom()
| relocate('', 'foo/')
|}
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ constructor(private val softwareComponentFactory: SoftwareComponentFactory) : Pl
val taskProvider =
registerShadowJarCommon(tasks.named("jar", Jar::class.java)) { task ->
task.from(sourceSets.named("main").map { it.output })
@Suppress("DEPRECATION")
task.configurations.convention(provider { listOf(runtimeConfiguration) })
}
artifacts.add(configurations.shadow.name, taskProvider)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public abstract class ShadowKmpPlugin : Plugin<Project> {
val kotlinJvmMain = target.compilations.named("main")
registerShadowJarCommon(tasks.named(target.artifactsTaskName, Jar::class.java)) { task ->
task.from(kotlinJvmMain.map { it.output.allOutputs })
@Suppress("DEPRECATION")
task.configurations.convention(
kotlinJvmMain
.flatMap { configurations.named(it.runtimeDependencyConfigurationName) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import org.gradle.api.artifacts.Dependency
import org.gradle.api.artifacts.ProjectDependency
import org.gradle.api.artifacts.ResolvedArtifact
import org.gradle.api.artifacts.ResolvedDependency
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.FileCollection
import org.gradle.api.provider.Provider
import org.gradle.api.specs.Spec
Expand All @@ -23,6 +24,13 @@ public interface DependencyFilter {
*/
public fun resolve(configurations: Collection<Configuration>): FileCollection

/**
* Resolve a [ConfigurableFileCollection] against the [include]/[exclude] rules in the filter.
*
* Any [Configuration] sources within the collection are resolved individually and combined.
*/
public fun resolve(dependencies: ConfigurableFileCollection): FileCollection

/** Exclude dependencies that match the provided [spec]. */
public fun exclude(spec: Spec<ResolvedDependency>)

Expand Down Expand Up @@ -73,6 +81,39 @@ public interface DependencyFilter {
.reduceOrNull { acc, fileCollection -> acc + fileCollection } ?: project.files()
}

override fun resolve(dependencies: ConfigurableFileCollection): FileCollection {
val extracted = dependencies.from.flatMap { source -> extractConfigurations(source) }
val excludedFiles =
project.files(
project.provider {
val includes = mutableSetOf<ResolvedDependency>()
val excludes = mutableSetOf<ResolvedDependency>()
extracted.forEach { config ->
resolve(
dependencies = config.resolvedConfiguration.firstLevelModuleDependencies,
includedDependencies = includes,
excludedDependencies = excludes,
)
}
excludes.flatMap { it.moduleArtifacts.map(ResolvedArtifact::getFile) }
}
)
return dependencies - excludedFiles
}

private fun extractConfigurations(source: Any): List<Configuration> =
when (source) {
is Configuration -> listOf(source)
is Provider<*> ->
when (val value = source.orNull) {
is Configuration -> listOf(value)
is Iterable<*> -> value.filterIsInstance<Configuration>()
else -> emptyList()
}
is Iterable<*> -> source.filterIsInstance<Configuration>()
else -> emptyList()
}

override fun exclude(spec: Spec<ResolvedDependency>) {
excludeSpecs.add(spec)
}
Expand Down
Loading