Skip to content

Commit 1f25269

Browse files
authored
Kotlin migration part 2 (#1019)
* Convert KnowsTask * Convert DefaultInheritManifest * Convert ShadowJavaPlugin * Convert JavaJarExec * Convert ShadowApplicationPlugin
1 parent 4521887 commit 1f25269

File tree

12 files changed

+349
-390
lines changed

12 files changed

+349
-390
lines changed
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package com.github.jengelman.gradle.plugins.shadow
2+
3+
import com.github.jengelman.gradle.plugins.shadow.internal.JavaJarExec
4+
import com.github.jengelman.gradle.plugins.shadow.internal.requireResourceAsText
5+
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
6+
import org.gradle.api.GradleException
7+
import org.gradle.api.Plugin
8+
import org.gradle.api.Project
9+
import org.gradle.api.distribution.DistributionContainer
10+
import org.gradle.api.plugins.ApplicationPlugin
11+
import org.gradle.api.plugins.JavaApplication
12+
import org.gradle.api.plugins.JavaPluginExtension
13+
import org.gradle.api.tasks.Sync
14+
import org.gradle.api.tasks.TaskProvider
15+
import org.gradle.api.tasks.application.CreateStartScripts
16+
import org.gradle.jvm.application.scripts.TemplateBasedScriptGenerator
17+
import org.gradle.jvm.toolchain.JavaToolchainService
18+
19+
abstract class ShadowApplicationPlugin : Plugin<Project> {
20+
private lateinit var project: Project
21+
private lateinit var javaApplication: JavaApplication
22+
23+
override fun apply(project: Project) {
24+
this.project = project
25+
this.javaApplication = project.extensions.getByType(JavaApplication::class.java)
26+
27+
addRunTask()
28+
addCreateScriptsTask()
29+
configureDistSpec()
30+
configureJarMainClass()
31+
configureInstallTask()
32+
}
33+
34+
protected open fun configureJarMainClass() {
35+
val classNameProvider = javaApplication.mainClass
36+
shadowJar.configure { jar ->
37+
jar.inputs.property("mainClassName", classNameProvider)
38+
jar.doFirst {
39+
jar.manifest.attributes["Main-Class"] = classNameProvider.get()
40+
}
41+
}
42+
}
43+
44+
protected open fun addRunTask() {
45+
project.tasks.register(SHADOW_RUN_TASK_NAME, JavaJarExec::class.java) {
46+
val install = project.tasks.named(SHADOW_INSTALL_TASK_NAME, Sync::class.java)
47+
it.dependsOn(install)
48+
it.mainClass.set("-jar")
49+
it.description = "Runs this project as a JVM application using the shadow jar"
50+
it.group = ApplicationPlugin.APPLICATION_GROUP
51+
it.conventionMapping.map("jvmArgs") { javaApplication.applicationDefaultJvmArgs }
52+
it.jarFile.fileProvider(
53+
project.providers.provider {
54+
project.file("${install.get().destinationDir.path}/lib/${shadowJar.get().archiveFile.get().asFile.name}")
55+
},
56+
)
57+
val toolchain = project.extensions.getByType(JavaPluginExtension::class.java).toolchain
58+
val defaultLauncher = project.extensions.getByType(JavaToolchainService::class.java)
59+
.launcherFor(toolchain)
60+
it.javaLauncher.set(defaultLauncher)
61+
}
62+
}
63+
64+
protected open fun addCreateScriptsTask() {
65+
project.tasks.register(SHADOW_SCRIPTS_TASK_NAME, CreateStartScripts::class.java) {
66+
(it.unixStartScriptGenerator as TemplateBasedScriptGenerator).template =
67+
project.resources.text.fromString(this::class.java.requireResourceAsText("internal/unixStartScript.txt"))
68+
(it.windowsStartScriptGenerator as TemplateBasedScriptGenerator).template =
69+
project.resources.text.fromString(this::class.java.requireResourceAsText("internal/windowsStartScript.txt"))
70+
it.description = "Creates OS specific scripts to run the project as a JVM application using the shadow jar"
71+
it.group = ApplicationPlugin.APPLICATION_GROUP
72+
it.classpath = project.files(shadowJar)
73+
it.conventionMapping.map("mainClassName") { javaApplication.mainClass.get() }
74+
it.conventionMapping.map("applicationName") { javaApplication.applicationName }
75+
it.conventionMapping.map("outputDir") { project.layout.buildDirectory.dir("scriptsShadow").get().asFile }
76+
it.conventionMapping.map("defaultJvmOpts") { javaApplication.applicationDefaultJvmArgs }
77+
it.inputs.files(project.files(shadowJar))
78+
}
79+
}
80+
81+
protected open fun configureInstallTask() {
82+
project.tasks.named(SHADOW_INSTALL_TASK_NAME, Sync::class.java).configure { task ->
83+
val applicationName = project.providers.provider { javaApplication.applicationName }
84+
85+
task.doFirst {
86+
if (
87+
!task.destinationDir.listFiles().isNullOrEmpty() &&
88+
(
89+
!task.destinationDir.resolve("lib").isDirectory ||
90+
!task.destinationDir.resolve("bin").isDirectory
91+
)
92+
) {
93+
throw GradleException(
94+
"The specified installation directory '${task.destinationDir}' is neither empty nor does it contain an installation for '${applicationName.get()}'.\n" +
95+
"If you really want to install to this directory, delete it and run the install task again.\n" +
96+
"Alternatively, choose a different installation directory.",
97+
)
98+
}
99+
}
100+
task.doLast {
101+
task.eachFile {
102+
if (it.path == "bin/${applicationName.get()}") {
103+
it.mode = 0x755
104+
}
105+
}
106+
}
107+
}
108+
}
109+
110+
protected open fun configureDistSpec() {
111+
project.extensions.getByType(DistributionContainer::class.java)
112+
.register(ShadowBasePlugin.DISTRIBUTION_NAME) { distributions ->
113+
distributions.contents { contents ->
114+
contents.from(project.file("src/dist"))
115+
contents.into("lib") { lib ->
116+
lib.from(shadowJar)
117+
lib.from(project.configurations.named(ShadowBasePlugin.CONFIGURATION_NAME))
118+
}
119+
contents.into("bin") { bin ->
120+
bin.from(project.tasks.named(SHADOW_SCRIPTS_TASK_NAME))
121+
bin.filePermissions { it.unix(493) }
122+
}
123+
}
124+
}
125+
}
126+
127+
protected val shadowJar: TaskProvider<ShadowJar>
128+
get() = project.tasks.named(ShadowJavaPlugin.SHADOW_JAR_TASK_NAME, ShadowJar::class.java)
129+
130+
companion object {
131+
const val SHADOW_RUN_TASK_NAME: String = "runShadow"
132+
const val SHADOW_SCRIPTS_TASK_NAME: String = "startShadowScripts"
133+
const val SHADOW_INSTALL_TASK_NAME: String = "installShadowDist"
134+
}
135+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package com.github.jengelman.gradle.plugins.shadow
2+
3+
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
4+
import javax.inject.Inject
5+
import org.gradle.api.Plugin
6+
import org.gradle.api.Project
7+
import org.gradle.api.artifacts.Configuration
8+
import org.gradle.api.attributes.Bundling
9+
import org.gradle.api.attributes.Category
10+
import org.gradle.api.attributes.LibraryElements
11+
import org.gradle.api.attributes.Usage
12+
import org.gradle.api.component.AdhocComponentWithVariants
13+
import org.gradle.api.component.SoftwareComponentFactory
14+
import org.gradle.api.plugins.JavaPlugin
15+
import org.gradle.api.tasks.SourceSetContainer
16+
import org.gradle.api.tasks.TaskProvider
17+
import org.gradle.jvm.tasks.Jar
18+
import org.gradle.plugin.devel.plugins.JavaGradlePluginPlugin
19+
20+
abstract class ShadowJavaPlugin @Inject constructor(
21+
private val softwareComponentFactory: SoftwareComponentFactory,
22+
) : Plugin<Project> {
23+
24+
override fun apply(project: Project) {
25+
val shadowConfiguration = project.configurations.getByName(ShadowBasePlugin.CONFIGURATION_NAME)
26+
val shadowTaskProvider = configureShadowTask(project, shadowConfiguration)
27+
28+
project.configurations.named(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME) {
29+
it.extendsFrom(shadowConfiguration)
30+
}
31+
32+
val shadowRuntimeElements = project.configurations.create(SHADOW_RUNTIME_ELEMENTS_CONFIGURATION_NAME) {
33+
it.extendsFrom(shadowConfiguration)
34+
it.isCanBeConsumed = true
35+
it.isCanBeResolved = false
36+
it.attributes { attr ->
37+
attr.attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(Usage::class.java, Usage.JAVA_RUNTIME))
38+
attr.attribute(Category.CATEGORY_ATTRIBUTE, project.objects.named(Category::class.java, Category.LIBRARY))
39+
attr.attribute(
40+
LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE,
41+
project.objects.named(LibraryElements::class.java, LibraryElements.JAR),
42+
)
43+
attr.attribute(Bundling.BUNDLING_ATTRIBUTE, project.objects.named(Bundling::class.java, Bundling.SHADOWED))
44+
}
45+
it.outgoing.artifact(shadowTaskProvider)
46+
}
47+
48+
project.components.named("java", AdhocComponentWithVariants::class.java) {
49+
it.addVariantsFromConfiguration(shadowRuntimeElements) { variant ->
50+
variant.mapToOptional()
51+
}
52+
}
53+
54+
val shadowComponent = softwareComponentFactory.adhoc(ShadowBasePlugin.COMPONENT_NAME)
55+
project.components.add(shadowComponent)
56+
shadowComponent.addVariantsFromConfiguration(shadowRuntimeElements) { variant ->
57+
variant.mapToMavenScope("runtime")
58+
}
59+
60+
project.plugins.withType(JavaGradlePluginPlugin::class.java).configureEach {
61+
// Remove the gradleApi so it isn't merged into the jar file.
62+
// This is required because 'java-gradle-plugin' adds gradleApi() to the 'api' configuration.
63+
// See https://github.com/gradle/gradle/blob/972c3e5c6ef990dd2190769c1ce31998a9402a79/subprojects/plugin-development/src/main/java/org/gradle/plugin/de
64+
project.configurations.named(JavaPlugin.API_CONFIGURATION_NAME) {
65+
it.dependencies.remove(project.dependencies.gradleApi())
66+
}
67+
// Compile only gradleApi() to make sure the plugin can compile against Gradle API.
68+
project.configurations.named(JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME) {
69+
it.dependencies.add(project.dependencies.gradleApi())
70+
}
71+
}
72+
}
73+
74+
private fun configureShadowTask(project: Project, shadowConfiguration: Configuration): TaskProvider<ShadowJar> {
75+
val sourceSets = project.extensions.getByType(SourceSetContainer::class.java)
76+
val jarTask = project.tasks.named(JavaPlugin.JAR_TASK_NAME, Jar::class.java)
77+
val taskProvider = project.tasks.register(SHADOW_JAR_TASK_NAME, ShadowJar::class.java) { shadow ->
78+
shadow.group = ShadowBasePlugin.GROUP_NAME
79+
shadow.description = "Create a combined JAR of project and runtime dependencies"
80+
shadow.archiveClassifier.set("all")
81+
shadow.manifest.inheritFrom(jarTask.get().manifest)
82+
val attrProvider = jarTask.map { it.manifest.attributes["Class-Path"]?.toString().orEmpty() }
83+
val files = project.objects.fileCollection().from(shadowConfiguration)
84+
shadow.doFirst {
85+
if (!files.isEmpty) {
86+
val attrs = listOf(attrProvider.getOrElse("")) + files.map { it.name }
87+
shadow.manifest.attributes["Class-Path"] = attrs.joinToString(" ").trim()
88+
}
89+
}
90+
shadow.from(sourceSets.getByName("main").output)
91+
shadow.configurations = listOf(
92+
project.configurations.findByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME)
93+
?: project.configurations.getByName("runtime"),
94+
)
95+
shadow.exclude(
96+
"META-INF/INDEX.LIST",
97+
"META-INF/*.SF",
98+
"META-INF/*.DSA",
99+
"META-INF/*.RSA",
100+
"module-info.class",
101+
)
102+
}
103+
project.artifacts.add(shadowConfiguration.name, taskProvider)
104+
return taskProvider
105+
}
106+
107+
companion object {
108+
const val SHADOW_JAR_TASK_NAME: String = "shadowJar"
109+
const val SHADOW_RUNTIME_ELEMENTS_CONFIGURATION_NAME: String = "shadowRuntimeElements"
110+
}
111+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.github.jengelman.gradle.plugins.shadow.internal
2+
3+
import org.gradle.api.file.RegularFileProperty
4+
import org.gradle.api.tasks.InputFile
5+
import org.gradle.api.tasks.JavaExec
6+
import org.gradle.api.tasks.TaskAction
7+
8+
internal abstract class JavaJarExec : JavaExec() {
9+
@get:InputFile
10+
abstract val jarFile: RegularFileProperty
11+
12+
@TaskAction
13+
override fun exec() {
14+
val allArgs = buildList {
15+
add(jarFile.get().asFile.path)
16+
// Must cast args to List<String> here to avoid type mismatch.
17+
addAll(args as List<String>)
18+
}
19+
setArgs(allArgs)
20+
super.exec()
21+
}
22+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.github.jengelman.gradle.plugins.shadow.internal
2+
3+
import java.io.InputStream
4+
5+
internal fun Class<*>.requireResourceAsText(name: String): String {
6+
return requireResourceAsStream(name).bufferedReader().readText()
7+
}
8+
9+
private fun Class<*>.requireResourceAsStream(name: String): InputStream {
10+
return getResourceAsStream(name) ?: error("Resource $name not found.")
11+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.github.jengelman.gradle.plugins.shadow.tasks
2+
3+
import org.gradle.api.Action
4+
import org.gradle.api.internal.file.FileResolver
5+
import org.gradle.api.java.archives.Manifest
6+
import org.gradle.api.java.archives.internal.DefaultManifest
7+
import org.gradle.api.java.archives.internal.DefaultManifestMergeSpec
8+
9+
open class DefaultInheritManifest @JvmOverloads constructor(
10+
private val fileResolver: FileResolver,
11+
private val internalManifest: DefaultManifest = DefaultManifest(fileResolver),
12+
) : InheritManifest,
13+
Manifest by internalManifest {
14+
private val inheritMergeSpecs = mutableListOf<DefaultManifestMergeSpec>()
15+
16+
override fun inheritFrom(
17+
vararg inheritPaths: Any,
18+
): InheritManifest {
19+
return inheritFrom(inheritPaths = inheritPaths, action = null)
20+
}
21+
22+
override fun inheritFrom(
23+
vararg inheritPaths: Any,
24+
action: Action<*>?,
25+
): InheritManifest = apply {
26+
val mergeSpec = DefaultManifestMergeSpec()
27+
mergeSpec.from(*inheritPaths)
28+
inheritMergeSpecs.add(mergeSpec)
29+
@Suppress("UNCHECKED_CAST")
30+
(action as? Action<DefaultManifestMergeSpec>)?.execute(mergeSpec)
31+
}
32+
33+
override fun getEffectiveManifest(): DefaultManifest {
34+
var base = DefaultManifest(fileResolver)
35+
inheritMergeSpecs.forEach {
36+
base = it.merge(base, fileResolver)
37+
}
38+
base.from(internalManifest)
39+
return base.effectiveManifest
40+
}
41+
42+
override fun writeTo(path: Any): Manifest = apply {
43+
effectiveManifest.writeTo(path)
44+
}
45+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.github.jengelman.gradle.plugins.shadow.tasks
2+
3+
import com.github.jengelman.gradle.plugins.shadow.internal.requireResourceAsText
4+
import org.gradle.api.DefaultTask
5+
import org.gradle.api.tasks.TaskAction
6+
7+
abstract class KnowsTask : DefaultTask() {
8+
9+
@TaskAction
10+
fun knows() {
11+
logger.info(
12+
"""
13+
No, The Shadow Knows....
14+
15+
${this::class.java.requireResourceAsText("/shadowBanner.txt")}
16+
""".trimIndent(),
17+
)
18+
}
19+
20+
companion object {
21+
const val NAME: String = "knows"
22+
const val DESC: String = "Do you know who knows?"
23+
}
24+
}

0 commit comments

Comments
 (0)