Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package com.github.jengelman.gradle.plugins.shadow

import com.github.jengelman.gradle.plugins.shadow.internal.JavaJarExec
import com.github.jengelman.gradle.plugins.shadow.internal.requireResourceAsText
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.gradle.api.GradleException
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.distribution.DistributionContainer
import org.gradle.api.plugins.ApplicationPlugin
import org.gradle.api.plugins.JavaApplication
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.tasks.Sync
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.application.CreateStartScripts
import org.gradle.jvm.application.scripts.TemplateBasedScriptGenerator
import org.gradle.jvm.toolchain.JavaToolchainService

abstract class ShadowApplicationPlugin : Plugin<Project> {
private lateinit var project: Project
private lateinit var javaApplication: JavaApplication

override fun apply(project: Project) {
this.project = project
this.javaApplication = project.extensions.getByType(JavaApplication::class.java)

addRunTask()
addCreateScriptsTask()
configureDistSpec()
configureJarMainClass()
configureInstallTask()
}

protected open fun configureJarMainClass() {
val classNameProvider = javaApplication.mainClass
shadowJar.configure { jar ->
jar.inputs.property("mainClassName", classNameProvider)
jar.doFirst {
jar.manifest.attributes["Main-Class"] = classNameProvider.get()
}
}
}

protected open fun addRunTask() {
project.tasks.register(SHADOW_RUN_TASK_NAME, JavaJarExec::class.java) {
val install = project.tasks.named(SHADOW_INSTALL_TASK_NAME, Sync::class.java)
it.dependsOn(install)
it.mainClass.set("-jar")
it.description = "Runs this project as a JVM application using the shadow jar"
it.group = ApplicationPlugin.APPLICATION_GROUP
it.conventionMapping.map("jvmArgs") { javaApplication.applicationDefaultJvmArgs }
it.jarFile.fileProvider(
project.providers.provider {
project.file("${install.get().destinationDir.path}/lib/${shadowJar.get().archiveFile.get().asFile.name}")
},
)
val toolchain = project.extensions.getByType(JavaPluginExtension::class.java).toolchain
val defaultLauncher = project.extensions.getByType(JavaToolchainService::class.java)
.launcherFor(toolchain)
it.javaLauncher.set(defaultLauncher)
}
}

protected open fun addCreateScriptsTask() {
project.tasks.register(SHADOW_SCRIPTS_TASK_NAME, CreateStartScripts::class.java) {
(it.unixStartScriptGenerator as TemplateBasedScriptGenerator).template =
project.resources.text.fromString(this::class.java.requireResourceAsText("internal/unixStartScript.txt"))
(it.windowsStartScriptGenerator as TemplateBasedScriptGenerator).template =
project.resources.text.fromString(this::class.java.requireResourceAsText("internal/windowsStartScript.txt"))
it.description = "Creates OS specific scripts to run the project as a JVM application using the shadow jar"
it.group = ApplicationPlugin.APPLICATION_GROUP
it.classpath = project.files(shadowJar)
it.conventionMapping.map("mainClassName") { javaApplication.mainClass.get() }
it.conventionMapping.map("applicationName") { javaApplication.applicationName }
it.conventionMapping.map("outputDir") { project.layout.buildDirectory.dir("scriptsShadow").get().asFile }
it.conventionMapping.map("defaultJvmOpts") { javaApplication.applicationDefaultJvmArgs }
it.inputs.files(project.files(shadowJar))
}
}

protected open fun configureInstallTask() {
project.tasks.named(SHADOW_INSTALL_TASK_NAME, Sync::class.java).configure { task ->
val applicationName = project.providers.provider { javaApplication.applicationName }

task.doFirst {
if (
!task.destinationDir.listFiles().isNullOrEmpty() &&
(
!task.destinationDir.resolve("lib").isDirectory ||
!task.destinationDir.resolve("bin").isDirectory
)
) {
throw GradleException(
"The specified installation directory '${task.destinationDir}' is neither empty nor does it contain an installation for '${applicationName.get()}'.\n" +
"If you really want to install to this directory, delete it and run the install task again.\n" +
"Alternatively, choose a different installation directory.",
)
}
}
task.doLast {
task.eachFile {
if (it.path == "bin/${applicationName.get()}") {
it.mode = 0x755
}
}
}
}
}

protected open fun configureDistSpec() {
project.extensions.getByType(DistributionContainer::class.java)
.register(ShadowBasePlugin.DISTRIBUTION_NAME) { distributions ->
distributions.contents { contents ->
contents.from(project.file("src/dist"))
contents.into("lib") { lib ->
lib.from(shadowJar)
lib.from(project.configurations.named(ShadowBasePlugin.CONFIGURATION_NAME))
}
contents.into("bin") { bin ->
bin.from(project.tasks.named(SHADOW_SCRIPTS_TASK_NAME))
bin.filePermissions { it.unix(493) }
}
}
}
}

protected val shadowJar: TaskProvider<ShadowJar>
get() = project.tasks.named(ShadowJavaPlugin.SHADOW_JAR_TASK_NAME, ShadowJar::class.java)

companion object {
const val SHADOW_RUN_TASK_NAME: String = "runShadow"
const val SHADOW_SCRIPTS_TASK_NAME: String = "startShadowScripts"
const val SHADOW_INSTALL_TASK_NAME: String = "installShadowDist"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package com.github.jengelman.gradle.plugins.shadow

import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import javax.inject.Inject
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.attributes.Bundling
import org.gradle.api.attributes.Category
import org.gradle.api.attributes.LibraryElements
import org.gradle.api.attributes.Usage
import org.gradle.api.component.AdhocComponentWithVariants
import org.gradle.api.component.SoftwareComponentFactory
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.tasks.SourceSetContainer
import org.gradle.api.tasks.TaskProvider
import org.gradle.jvm.tasks.Jar
import org.gradle.plugin.devel.plugins.JavaGradlePluginPlugin

abstract class ShadowJavaPlugin @Inject constructor(
private val softwareComponentFactory: SoftwareComponentFactory,
) : Plugin<Project> {

override fun apply(project: Project) {
val shadowConfiguration = project.configurations.getByName(ShadowBasePlugin.CONFIGURATION_NAME)
val shadowTaskProvider = configureShadowTask(project, shadowConfiguration)

project.configurations.named(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME) {
it.extendsFrom(shadowConfiguration)
}

val shadowRuntimeElements = project.configurations.create(SHADOW_RUNTIME_ELEMENTS_CONFIGURATION_NAME) {
it.extendsFrom(shadowConfiguration)
it.isCanBeConsumed = true
it.isCanBeResolved = false
it.attributes { attr ->
attr.attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(Usage::class.java, Usage.JAVA_RUNTIME))
attr.attribute(Category.CATEGORY_ATTRIBUTE, project.objects.named(Category::class.java, Category.LIBRARY))
attr.attribute(
LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE,
project.objects.named(LibraryElements::class.java, LibraryElements.JAR),
)
attr.attribute(Bundling.BUNDLING_ATTRIBUTE, project.objects.named(Bundling::class.java, Bundling.SHADOWED))
}
it.outgoing.artifact(shadowTaskProvider)
}

project.components.named("java", AdhocComponentWithVariants::class.java) {
it.addVariantsFromConfiguration(shadowRuntimeElements) { variant ->
variant.mapToOptional()
}
}

val shadowComponent = softwareComponentFactory.adhoc(ShadowBasePlugin.COMPONENT_NAME)
project.components.add(shadowComponent)
shadowComponent.addVariantsFromConfiguration(shadowRuntimeElements) { variant ->
variant.mapToMavenScope("runtime")
}

project.plugins.withType(JavaGradlePluginPlugin::class.java).configureEach {
// Remove the gradleApi so it isn't merged into the jar file.
// This is required because 'java-gradle-plugin' adds gradleApi() to the 'api' configuration.
// See https://github.com/gradle/gradle/blob/972c3e5c6ef990dd2190769c1ce31998a9402a79/subprojects/plugin-development/src/main/java/org/gradle/plugin/de
project.configurations.named(JavaPlugin.API_CONFIGURATION_NAME) {
it.dependencies.remove(project.dependencies.gradleApi())
}
// Compile only gradleApi() to make sure the plugin can compile against Gradle API.
project.configurations.named(JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME) {
it.dependencies.add(project.dependencies.gradleApi())
}
}
}

private fun configureShadowTask(project: Project, shadowConfiguration: Configuration): TaskProvider<ShadowJar> {
val sourceSets = project.extensions.getByType(SourceSetContainer::class.java)
val jarTask = project.tasks.named(JavaPlugin.JAR_TASK_NAME, Jar::class.java)
val taskProvider = project.tasks.register(SHADOW_JAR_TASK_NAME, ShadowJar::class.java) { shadow ->
shadow.group = ShadowBasePlugin.GROUP_NAME
shadow.description = "Create a combined JAR of project and runtime dependencies"
shadow.archiveClassifier.set("all")
shadow.manifest.inheritFrom(jarTask.get().manifest)
val attrProvider = jarTask.map { it.manifest.attributes["Class-Path"]?.toString().orEmpty() }
val files = project.objects.fileCollection().from(shadowConfiguration)
shadow.doFirst {
if (!files.isEmpty) {
val attrs = listOf(attrProvider.getOrElse("")) + files.map { it.name }
shadow.manifest.attributes["Class-Path"] = attrs.joinToString(" ").trim()
}
}
shadow.from(sourceSets.getByName("main").output)
shadow.configurations = listOf(
project.configurations.findByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME)
?: project.configurations.getByName("runtime"),
)
shadow.exclude(
"META-INF/INDEX.LIST",
"META-INF/*.SF",
"META-INF/*.DSA",
"META-INF/*.RSA",
"module-info.class",
)
}
project.artifacts.add(shadowConfiguration.name, taskProvider)
return taskProvider
}

companion object {
const val SHADOW_JAR_TASK_NAME: String = "shadowJar"
const val SHADOW_RUNTIME_ELEMENTS_CONFIGURATION_NAME: String = "shadowRuntimeElements"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.github.jengelman.gradle.plugins.shadow.internal

import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.JavaExec
import org.gradle.api.tasks.TaskAction

internal abstract class JavaJarExec : JavaExec() {
@get:InputFile
abstract val jarFile: RegularFileProperty

@TaskAction
override fun exec() {
val allArgs = buildList {
add(jarFile.get().asFile.path)
// Must cast args to List<String> here to avoid type mismatch.
addAll(args as List<String>)
}
setArgs(allArgs)
super.exec()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.github.jengelman.gradle.plugins.shadow.internal

import java.io.InputStream

internal fun Class<*>.requireResourceAsText(name: String): String {
return requireResourceAsStream(name).bufferedReader().readText()
}

private fun Class<*>.requireResourceAsStream(name: String): InputStream {
return getResourceAsStream(name) ?: error("Resource $name not found.")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.github.jengelman.gradle.plugins.shadow.tasks

import org.gradle.api.Action
import org.gradle.api.internal.file.FileResolver
import org.gradle.api.java.archives.Manifest
import org.gradle.api.java.archives.internal.DefaultManifest
import org.gradle.api.java.archives.internal.DefaultManifestMergeSpec

open class DefaultInheritManifest @JvmOverloads constructor(
private val fileResolver: FileResolver,
private val internalManifest: DefaultManifest = DefaultManifest(fileResolver),
) : InheritManifest,
Manifest by internalManifest {
private val inheritMergeSpecs = mutableListOf<DefaultManifestMergeSpec>()

override fun inheritFrom(
vararg inheritPaths: Any,
): InheritManifest {
return inheritFrom(inheritPaths = inheritPaths, action = null)
}

override fun inheritFrom(
vararg inheritPaths: Any,
action: Action<*>?,
): InheritManifest = apply {
val mergeSpec = DefaultManifestMergeSpec()
mergeSpec.from(*inheritPaths)
inheritMergeSpecs.add(mergeSpec)
@Suppress("UNCHECKED_CAST")
(action as? Action<DefaultManifestMergeSpec>)?.execute(mergeSpec)
}

override fun getEffectiveManifest(): DefaultManifest {
var base = DefaultManifest(fileResolver)
inheritMergeSpecs.forEach {
base = it.merge(base, fileResolver)
}
base.from(internalManifest)
return base.effectiveManifest
}

override fun writeTo(path: Any): Manifest = apply {
effectiveManifest.writeTo(path)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.github.jengelman.gradle.plugins.shadow.tasks

import com.github.jengelman.gradle.plugins.shadow.internal.requireResourceAsText
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction

abstract class KnowsTask : DefaultTask() {

@TaskAction
fun knows() {
logger.info(
"""
No, The Shadow Knows....

${this::class.java.requireResourceAsText("/shadowBanner.txt")}
""".trimIndent(),
)
}

companion object {
const val NAME: String = "knows"
const val DESC: String = "Do you know who knows?"
}
}
Loading