Skip to content

Commit 75f6736

Browse files
GooolerCopilot
andauthored
Move injecting Main-Class manifest attr logic from doFirst into copy (#1724)
* Replace `doFirst` with `mainClass` * Update changelog * Fix `errorWhenMainClassNotSet` * Update src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowJar.kt Co-authored-by: Copilot <[email protected]> * Update docs * Add tests --------- Co-authored-by: Copilot <[email protected]>
1 parent 8ff959e commit 75f6736

File tree

9 files changed

+138
-29
lines changed

9 files changed

+138
-29
lines changed

api/shadow.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ public abstract class com/github/jengelman/gradle/plugins/shadow/tasks/ShadowJar
204204
public fun getFailOnDuplicateEntries ()Lorg/gradle/api/provider/Property;
205205
public fun getIncludedDependencies ()Lorg/gradle/api/file/ConfigurableFileCollection;
206206
public fun getIncludes ()Ljava/util/Set;
207+
public fun getMainClass ()Lorg/gradle/api/provider/Property;
207208
public fun getManifest ()Lcom/github/jengelman/gradle/plugins/shadow/tasks/InheritManifest;
208209
public synthetic fun getManifest ()Lorg/gradle/api/java/archives/Manifest;
209210
public fun getMinimizeJar ()Lorg/gradle/api/provider/Property;

docs/changes/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,21 @@
88
- Support relocating Groovy extensions in Module descriptors. ([#1705](https://github.com/GradleUp/shadow/pull/1705))
99
- Add extensions for `Iterable<Relocator>`. ([#1710](https://github.com/GradleUp/shadow/pull/1710))
1010
- Support relocating list of types in `RelocatorRemapper`. ([#1714](https://github.com/GradleUp/shadow/pull/1714))
11+
- Add `mainClass` property into `ShadowJar`. ([#1722](https://github.com/GradleUp/shadow/pull/1722))
12+
```kotlin
13+
tasks.shadowJar {
14+
// This property will be used as a fallback if there is no explicit `Main-Class` attribute set.
15+
mainClass = "my.Main"
16+
}
17+
```
1118

1219
### Changed
1320

1421
- Merge Gradle Module descriptors into the modern `META-INF` path. ([#1706](https://github.com/GradleUp/shadow/pull/1706))
1522
The Gradle Module descriptors (`org.codehaus.groovy.runtime.ExtensionModule` files) defined under `META-INF/services/`
1623
and `META-INF/groovy` will be merged into `META-INF/groovy/org.codehaus.groovy.runtime.ExtensionModule`.
1724
- Move injecting `Class-Path` manifest attr logic from `doFirst` into `copy`. ([#1720](https://github.com/GradleUp/shadow/pull/1720))
25+
- Move injecting `Main-Class` manifest attr logic from `doFirst` into `copy`. ([#1724](https://github.com/GradleUp/shadow/pull/1724))
1826
- Deprecate `InheritManifest`. ([#1722](https://github.com/GradleUp/shadow/pull/1722))
1927
- Use default `JavaExec` error message when main class is not set. ([#1725](https://github.com/GradleUp/shadow/pull/1725))
2028

docs/getting-started/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ Here are the options that can be passed to the `shadowJar`:
154154
--no-enable-auto-relocation Disables option --enable-auto-relocation.
155155
--fail-on-duplicate-entries Fails build if the ZIP entries in the shadowed JAR are duplicate.
156156
--no-fail-on-duplicate-entries Disables option --fail-on-duplicate-entries.
157+
--main-class Main class attribute to add to manifest.
157158
--minimize-jar Minimizes the jar by removing unused classes.
158159
--no-minimize-jar Disables option --minimize-jar.
159160
--relocation-prefix Prefix used for auto relocation of packages in the dependencies.

gradle/lint-baseline.xml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@
8585
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
8686
<location
8787
file="src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowApplicationPlugin.kt"
88-
line="73"
88+
line="72"
8989
column="9"/>
9090
</issue>
9191

@@ -96,7 +96,7 @@
9696
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
9797
<location
9898
file="src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowApplicationPlugin.kt"
99-
line="74"
99+
line="73"
100100
column="9"/>
101101
</issue>
102102

@@ -107,7 +107,7 @@
107107
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
108108
<location
109109
file="src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowApplicationPlugin.kt"
110-
line="75"
110+
line="74"
111111
column="9"/>
112112
</issue>
113113

@@ -118,7 +118,7 @@
118118
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
119119
<location
120120
file="src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowApplicationPlugin.kt"
121-
line="76"
121+
line="75"
122122
column="9"/>
123123
</issue>
124124

@@ -184,7 +184,7 @@
184184
errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
185185
<location
186186
file="src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowJar.kt"
187-
line="43"
187+
line="44"
188188
column="1"/>
189189
</issue>
190190

src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ApplicationPluginTest.kt

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,15 +170,32 @@ class ApplicationPluginTest : BasePluginTest() {
170170
assertions(result.output, "bar")
171171
}
172172

173+
@Test
174+
fun overrideMainClassFromApplicationPlugin() {
175+
prepare()
176+
projectScript.appendText(
177+
"""
178+
$shadowJarTask {
179+
mainClass = 'my.Main2' // Different from application.mainClass.
180+
}
181+
""".trimIndent(),
182+
)
183+
184+
run(runShadowPath) // Run without errors.
185+
186+
assertThat(jarPath("build/libs/myapp-1.0-all.jar")).useAll {
187+
getMainAttr(mainClassAttributeKey).isEqualTo("my.Main2")
188+
}
189+
}
190+
173191
@Test
174192
fun errorWhenMainClassNotSet() {
175193
prepare(mainClassBlock = "")
176194

177195
val result = runWithFailure(runShadowPath)
178196

179197
assertThat(result.output).contains(
180-
"Error: Could not find or load main class",
181-
"Caused by: java.lang.ClassNotFoundException:",
198+
"no main manifest attribute, in",
182199
)
183200
}
184201

src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/JavaPluginsTest.kt

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ import org.gradle.language.base.plugins.LifecycleBasePlugin
4848
import org.gradle.testfixtures.ProjectBuilder
4949
import org.junit.jupiter.api.Test
5050
import org.junit.jupiter.params.ParameterizedTest
51+
import org.junit.jupiter.params.provider.Arguments
5152
import org.junit.jupiter.params.provider.EnumSource
53+
import org.junit.jupiter.params.provider.MethodSource
5254
import org.junit.jupiter.params.provider.ValueSource
5355

5456
class JavaPluginsTest : BasePluginTest() {
@@ -104,6 +106,7 @@ class JavaPluginsTest : BasePluginTest() {
104106
assertThat(enableAutoRelocation.get()).isFalse()
105107
assertThat(failOnDuplicateEntries.get()).isFalse()
106108
assertThat(minimizeJar.get()).isFalse()
109+
assertThat(mainClass.orNull).isNull()
107110

108111
assertThat(relocationPrefix.get()).isEqualTo(ShadowBasePlugin.SHADOW)
109112
assertThat(configurations.get()).all {
@@ -126,6 +129,7 @@ class JavaPluginsTest : BasePluginTest() {
126129
"--no-enable-auto-relocation Disables option --enable-auto-relocation.",
127130
"--fail-on-duplicate-entries Fails build if the ZIP entries in the shadowed JAR are duplicate.",
128131
"--no-fail-on-duplicate-entries Disables option --fail-on-duplicate-entries",
132+
"--main-class Main class attribute to add to manifest.",
129133
"--minimize-jar Minimizes the jar by removing unused classes.",
130134
"--no-minimize-jar Disables option --minimize-jar.",
131135
"--relocation-prefix Prefix used for auto relocation of packages in the dependencies.",
@@ -710,7 +714,7 @@ class JavaPluginsTest : BasePluginTest() {
710714
}
711715

712716
@Test
713-
fun inheritFromOtherManifest() {
717+
fun inheritManifestAttrsFromJars() {
714718
projectScript.appendText(
715719
"""
716720
$jarTask {
@@ -738,6 +742,32 @@ class JavaPluginsTest : BasePluginTest() {
738742
}
739743
}
740744

745+
@Test
746+
fun inheritManifestMainClassFromJar() {
747+
projectScript.appendText(
748+
"""
749+
$jarTask {
750+
manifest {
751+
attributes '$mainClassAttributeKey': 'my.Main'
752+
}
753+
}
754+
$shadowJarTask {
755+
mainClass = 'my.Main2' // This should not override the inherited one.
756+
}
757+
""".trimIndent(),
758+
)
759+
760+
val result = run(shadowJarPath, infoArgument)
761+
762+
assertThat(result.output).contains(
763+
"Skipping adding $mainClassAttributeKey attribute to the manifest as it is already set.",
764+
)
765+
assertThat(outputShadowedJar).useAll {
766+
transform { it.mainAttrSize }.isGreaterThan(1)
767+
getMainAttr("Main-Class").isEqualTo("my.Main")
768+
}
769+
}
770+
741771
@Test
742772
fun addExtraFilesViaFrom() {
743773
val mainClassEntry = writeClass()
@@ -950,7 +980,50 @@ class JavaPluginsTest : BasePluginTest() {
950980
)
951981
}
952982

983+
@ParameterizedTest
984+
@MethodSource("fallbackMainClassProvider")
985+
fun fallbackMainClassByProperty(input: String, expected: String?, message: String) {
986+
projectScript.appendText(
987+
"""
988+
$shadowJarTask {
989+
mainClass = '$input'
990+
}
991+
""".trimIndent(),
992+
)
993+
994+
val result = run(shadowJarPath, infoArgument)
995+
996+
assertThat(result.output).contains(
997+
message,
998+
)
999+
assertThat(outputShadowedJar).useAll {
1000+
getMainAttr(mainClassAttributeKey).isEqualTo(expected)
1001+
}
1002+
}
1003+
1004+
@ParameterizedTest
1005+
@MethodSource("fallbackMainClassProvider")
1006+
fun fallbackMainClassByCliOption(input: String, expected: String?) {
1007+
if (input.isEmpty()) {
1008+
run(shadowJarPath)
1009+
} else {
1010+
run(shadowJarPath, "--main-class", input)
1011+
}
1012+
1013+
assertThat(outputShadowedJar).useAll {
1014+
getMainAttr(mainClassAttributeKey).isEqualTo(expected)
1015+
}
1016+
}
1017+
9531018
private fun dependencies(configuration: String, vararg flags: String): String {
9541019
return run("dependencies", "--configuration", configuration, *flags).output
9551020
}
1021+
1022+
private companion object {
1023+
@JvmStatic
1024+
fun fallbackMainClassProvider() = listOf(
1025+
Arguments.of("my.Main", "my.Main", "Adding $mainClassAttributeKey attribute to the manifest with value"),
1026+
Arguments.of("", null, "Skipping adding $mainClassAttributeKey attribute to the manifest as it is empty."),
1027+
)
1028+
}
9561029
}

src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowApplicationPlugin.kt

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import com.github.jengelman.gradle.plugins.shadow.internal.applicationExtension
66
import com.github.jengelman.gradle.plugins.shadow.internal.distributions
77
import com.github.jengelman.gradle.plugins.shadow.internal.javaPluginExtension
88
import com.github.jengelman.gradle.plugins.shadow.internal.javaToolchainService
9-
import com.github.jengelman.gradle.plugins.shadow.internal.mainClassAttributeKey
109
import com.github.jengelman.gradle.plugins.shadow.internal.requireResourceAsText
1110
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar.Companion.shadowJar
1211
import org.gradle.api.GradleException
@@ -127,17 +126,8 @@ public abstract class ShadowApplicationPlugin : Plugin<Project> {
127126
}
128127

129128
protected open fun Project.configureShadowJarMainClass() {
130-
// Default to empty string to avoid the error of the value not being configured yet.
131-
val mainClassName = applicationExtension.mainClass.convention("")
132129
tasks.shadowJar.configure { task ->
133-
task.inputs.property("mainClassName", mainClassName)
134-
task.doFirst("Set $mainClassAttributeKey attribute in the manifest") {
135-
val realClass = mainClassName.orNull
136-
// Inject the attribute if it is not already present.
137-
if (!task.manifest.attributes.contains(mainClassAttributeKey) && !realClass.isNullOrEmpty()) {
138-
task.manifest.attributes[mainClassAttributeKey] = realClass
139-
}
140-
}
130+
task.mainClass.convention(applicationExtension.mainClass)
141131
}
142132
}
143133

src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowKmpPlugin.kt

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.github.jengelman.gradle.plugins.shadow
22

33
import com.github.jengelman.gradle.plugins.shadow.internal.isAtLeastKgpVersion
4-
import com.github.jengelman.gradle.plugins.shadow.internal.mainClassAttributeKey
54
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar.Companion.SHADOW_JAR_TASK_NAME
65
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar.Companion.registerShadowJarCommon
76
import org.gradle.api.Plugin
@@ -41,15 +40,7 @@ public abstract class ShadowKmpPlugin : Plugin<Project> {
4140

4241
@OptIn(ExperimentalKotlinGradlePluginApi::class)
4342
target.mainRun {
44-
// Fix cannot serialize object of type 'org.jetbrains.kotlin.gradle.targets.jvm.tasks.KotlinJvmRun'.
45-
val mainClassName = provider { mainClass }
46-
task.inputs.property("mainClassName", mainClassName)
47-
task.doFirst("Set $mainClassAttributeKey attribute in the manifest") {
48-
val realClass = mainClassName.get().orNull
49-
if (!task.manifest.attributes.contains(mainClassAttributeKey) && !realClass.isNullOrEmpty()) {
50-
task.manifest.attributes[mainClassAttributeKey] = realClass
51-
}
52-
}
43+
task.mainClass.convention(mainClass)
5344
}
5445
}
5546
}

src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowJar.kt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import com.github.jengelman.gradle.plugins.shadow.internal.MinimizeDependencyFil
88
import com.github.jengelman.gradle.plugins.shadow.internal.UnusedTracker
99
import com.github.jengelman.gradle.plugins.shadow.internal.classPathAttributeKey
1010
import com.github.jengelman.gradle.plugins.shadow.internal.fileCollection
11+
import com.github.jengelman.gradle.plugins.shadow.internal.mainClassAttributeKey
1112
import com.github.jengelman.gradle.plugins.shadow.internal.multiReleaseAttributeKey
1213
import com.github.jengelman.gradle.plugins.shadow.internal.property
1314
import com.github.jengelman.gradle.plugins.shadow.internal.setProperty
@@ -169,6 +170,19 @@ public abstract class ShadowJar : Jar() {
169170
@get:Option(option = "relocation-prefix", description = "Prefix used for auto relocation of packages in the dependencies.")
170171
public open val relocationPrefix: Property<String> = objectFactory.property(ShadowBasePlugin.SHADOW)
171172

173+
/**
174+
* Main class attribute to add to manifest.
175+
*
176+
* This property will be used as a fallback if there is no explicit `Main-Class` attribute set for the [ShadowJar]
177+
* task or the main [Jar] task.
178+
*
179+
* Defaults to `null`.
180+
*/
181+
@get:Optional
182+
@get:Input
183+
@get:Option(option = "main-class", description = "Main class attribute to add to manifest.")
184+
public open val mainClass: Property<String> = objectFactory.property()
185+
172186
/**
173187
* Fails build if the ZIP entries in the shadowed JAR are duplicate.
174188
*
@@ -452,6 +466,20 @@ public abstract class ShadowJar : Jar() {
452466
}
453467

454468
private fun injectManifestAttributes() {
469+
val mainClassValue = mainClass.orNull
470+
when {
471+
manifest.attributes.contains(mainClassAttributeKey) -> {
472+
logger.info("Skipping adding $mainClassAttributeKey attribute to the manifest as it is already set.")
473+
}
474+
mainClassValue.isNullOrEmpty() -> {
475+
logger.info("Skipping adding $mainClassAttributeKey attribute to the manifest as it is empty.")
476+
}
477+
else -> {
478+
manifest.attributes[mainClassAttributeKey] = mainClassValue
479+
logger.info("Adding $mainClassAttributeKey attribute to the manifest with value '$mainClassValue'.")
480+
}
481+
}
482+
455483
val classPathAttr = manifest.attributes[classPathAttributeKey]?.toString().orEmpty()
456484
val shadowFiles = shadowDependencies.get()
457485
if (!shadowFiles.isEmpty) {

0 commit comments

Comments
 (0)