Skip to content

Commit 48a3482

Browse files
authored
Kotlin migration part 6 (#1024)
* Convert GroovyExtensionModuleTransformer * Convert Log4j2PluginsCacheFileTransformer * Simplify relocatePlugins * Convert SimpleRelocator * Simplify SimpleRelocator
1 parent aed9e11 commit 48a3482

File tree

6 files changed

+352
-505
lines changed

6 files changed

+352
-505
lines changed
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
package com.github.jengelman.gradle.plugins.shadow.relocation
2+
3+
import java.util.regex.Pattern
4+
import org.codehaus.plexus.util.SelectorUtils
5+
import org.gradle.api.tasks.Input
6+
import org.gradle.api.tasks.Optional
7+
8+
/**
9+
* Modified from `org.apache.maven.plugins.shade.relocation.SimpleRelocator.java`
10+
*
11+
* @author Jason van Zyl
12+
* @author Mauro Talevi
13+
* @author John Engelman
14+
*/
15+
@CacheableRelocator
16+
open class SimpleRelocator @JvmOverloads constructor(
17+
pattern: String?,
18+
shadedPattern: String?,
19+
includes: List<String>?,
20+
excludes: List<String>?,
21+
private val _isRawString: Boolean = false,
22+
) : Relocator {
23+
private val _pattern: String?
24+
private val _pathPattern: String
25+
private val _shadedPattern: String?
26+
private val _shadedPathPattern: String
27+
private val _includes = mutableSetOf<String>()
28+
private val _excludes = mutableSetOf<String>()
29+
30+
init {
31+
if (_isRawString) {
32+
_pathPattern = pattern.orEmpty()
33+
_shadedPathPattern = shadedPattern.orEmpty()
34+
_pattern = null // not used for raw string relocator
35+
_shadedPattern = null // not used for raw string relocator
36+
} else {
37+
if (pattern == null) {
38+
_pattern = ""
39+
_pathPattern = ""
40+
} else {
41+
_pattern = pattern.replace('/', '.')
42+
_pathPattern = pattern.replace('.', '/')
43+
}
44+
if (shadedPattern == null) {
45+
_shadedPattern = "hidden.${_pattern}"
46+
_shadedPathPattern = "hidden/$_pathPattern"
47+
} else {
48+
_shadedPattern = shadedPattern.replace('/', '.')
49+
_shadedPathPattern = shadedPattern.replace('.', '/')
50+
}
51+
}
52+
_includes += normalizePatterns(includes)
53+
_excludes += normalizePatterns(excludes)
54+
}
55+
56+
@get:Input
57+
@get:Optional
58+
open val pattern: String? get() = _pattern
59+
60+
@get:Input
61+
open val pathPattern: String get() = _pathPattern
62+
63+
@get:Input
64+
@get:Optional
65+
open val shadedPattern: String? get() = _shadedPattern
66+
67+
@get:Input
68+
open val shadedPathPattern: String get() = _shadedPathPattern
69+
70+
@get:Input
71+
open val isRawString: Boolean get() = _isRawString
72+
73+
@get:Input
74+
open val includes: Set<String> get() = _includes
75+
76+
@get:Input
77+
open val excludes: Set<String> get() = _excludes
78+
79+
open fun include(pattern: String): SimpleRelocator = apply {
80+
_includes += normalizePatterns(listOf(pattern))
81+
}
82+
83+
open fun exclude(pattern: String): SimpleRelocator = apply {
84+
_excludes += normalizePatterns(listOf(pattern))
85+
}
86+
87+
override fun canRelocatePath(path: String): Boolean {
88+
if (_isRawString) return Pattern.compile(_pathPattern).matcher(path).find()
89+
// If string is too short - no need to perform expensive string operations
90+
if (path.length < _pathPattern.length) return false
91+
val adjustedPath = if (path.endsWith(".class")) {
92+
// Safeguard against strings containing only ".class"
93+
if (path.length == 6) return false
94+
path.dropLast(6)
95+
} else {
96+
path
97+
}
98+
// Allow for annoying option of an extra / on the front of a path. See MSHADE-119 comes from getClass().getResource("/a/b/c.properties").
99+
val startIndex = if (adjustedPath.startsWith("/")) 1 else 0
100+
val pathStartsWithPattern = adjustedPath.startsWith(_pathPattern, startIndex)
101+
return pathStartsWithPattern && isIncluded(adjustedPath) && !isExcluded(adjustedPath)
102+
}
103+
104+
override fun canRelocateClass(className: String): Boolean {
105+
return !_isRawString && !className.contains('/') && canRelocatePath(className.replace('.', '/'))
106+
}
107+
108+
override fun relocatePath(context: RelocatePathContext): String {
109+
val path = context.path
110+
context.stats.relocate(_pathPattern, _shadedPathPattern)
111+
return if (_isRawString) {
112+
path.replace(_pathPattern.toRegex(), _shadedPathPattern)
113+
} else {
114+
path.replaceFirst(_pathPattern, _shadedPathPattern)
115+
}
116+
}
117+
118+
override fun relocateClass(context: RelocateClassContext): String {
119+
context.stats.relocate(_pathPattern, _shadedPathPattern)
120+
return context.className.replaceFirst(_pattern.orEmpty(), _shadedPattern.orEmpty())
121+
}
122+
123+
override fun applyToSourceContent(sourceContent: String): String {
124+
return if (_isRawString) {
125+
sourceContent
126+
} else {
127+
sourceContent.replace("\\b$_pattern".toRegex(), _shadedPattern.orEmpty())
128+
}
129+
}
130+
131+
private fun normalizePatterns(patterns: Collection<String>?) = buildSet {
132+
for (pattern in patterns.orEmpty()) {
133+
// Regex patterns don't need to be normalized and stay as is
134+
if (pattern.startsWith(SelectorUtils.REGEX_HANDLER_PREFIX)) {
135+
add(pattern)
136+
continue
137+
}
138+
139+
val classPattern = pattern.replace('.', '/')
140+
add(classPattern)
141+
142+
if (classPattern.endsWith("/*")) {
143+
val packagePattern = classPattern.substring(0, classPattern.lastIndexOf('/'))
144+
add(packagePattern)
145+
}
146+
}
147+
}
148+
149+
private fun isIncluded(path: String): Boolean {
150+
return _includes.isEmpty() || _includes.any { SelectorUtils.matchPath(it, path, "/", true) }
151+
}
152+
153+
private fun isExcluded(path: String): Boolean {
154+
return _excludes.any { SelectorUtils.matchPath(it, path, "/", true) }
155+
}
156+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package com.github.jengelman.gradle.plugins.shadow.transformers
2+
3+
import com.github.jengelman.gradle.plugins.shadow.transformers.TransformerContext.Companion.getEntryTimestamp
4+
import java.io.ByteArrayInputStream
5+
import java.io.ByteArrayOutputStream
6+
import java.io.InputStream
7+
import java.util.Properties
8+
import org.apache.tools.zip.ZipEntry
9+
import org.apache.tools.zip.ZipOutputStream
10+
import org.gradle.api.file.FileTreeElement
11+
12+
/**
13+
* Modified from `eu.appsatori.gradle.fatjar.tasks.PrepareFiles.groovy`
14+
*
15+
* Resource transformer that merges Groovy extension module descriptor files into a single file.
16+
* Groovy extension module descriptor files have the name org.codehaus.groovy.runtime.ExtensionModule
17+
* and live in the META-INF/services (Groovy up to 2.4) or META-INF/groovy (Groovy 2.5+) directory.
18+
* See [GROOVY-8480](https://issues.apache.org/jira/browse/GROOVY-8480) for more details of the change.
19+
*
20+
* If there are several descriptor files spread across many JARs the individual
21+
* entries will be merged into a single descriptor file which will be
22+
* packaged into the resultant JAR produced by the shadowing process.
23+
* It will live in the legacy directory (META-INF/services) if all the processed descriptor
24+
* files came from the legacy location, otherwise it will be written into the now standard location (META-INF/groovy).
25+
* Note that certain JDK9+ tooling will break when using the legacy location.
26+
*/
27+
@CacheableTransformer
28+
open class GroovyExtensionModuleTransformer : Transformer {
29+
private val module = Properties()
30+
31+
/**
32+
* default to Groovy 2.4 or earlier
33+
*/
34+
private var legacy = true
35+
36+
override fun canTransformResource(element: FileTreeElement): Boolean {
37+
val path = element.relativePath.pathString
38+
if (path == GROOVY_EXTENSION_MODULE_DESCRIPTOR_PATH) {
39+
// Groovy 2.5+
40+
legacy = false
41+
return true
42+
}
43+
return path == GROOVY_LEGACY_EXTENSION_MODULE_DESCRIPTOR_PATH
44+
}
45+
46+
override fun transform(context: TransformerContext) {
47+
val props = Properties()
48+
props.load(context.inputStream)
49+
props.forEach { key, value ->
50+
when (key as String) {
51+
MODULE_NAME_KEY -> handle(key, value as String) {
52+
module.setProperty(key, MERGED_MODULE_NAME)
53+
}
54+
MODULE_VERSION_KEY -> handle(key, value as String) {
55+
module.setProperty(key, MERGED_MODULE_VERSION)
56+
}
57+
EXTENSION_CLASSES_KEY,
58+
STATIC_EXTENSION_CLASSES_KEY,
59+
-> handle(key, value as String) { existingValue ->
60+
module.setProperty(key, "$existingValue,$value")
61+
}
62+
}
63+
}
64+
}
65+
66+
private fun handle(key: String, value: String, mergeValue: (String) -> Unit) {
67+
val existingValue = module.getProperty(key)
68+
if (existingValue != null) {
69+
mergeValue(existingValue)
70+
} else {
71+
module.setProperty(key, value)
72+
}
73+
}
74+
75+
override fun hasTransformedResource(): Boolean = module.isNotEmpty()
76+
77+
override fun modifyOutputStream(os: ZipOutputStream, preserveFileTimestamps: Boolean) {
78+
val name = if (legacy) GROOVY_LEGACY_EXTENSION_MODULE_DESCRIPTOR_PATH else GROOVY_EXTENSION_MODULE_DESCRIPTOR_PATH
79+
val entry = ZipEntry(name)
80+
entry.time = getEntryTimestamp(preserveFileTimestamps, entry.time)
81+
os.putNextEntry(entry)
82+
module.inputStream().use {
83+
it.copyTo(os)
84+
}
85+
os.closeEntry()
86+
}
87+
88+
private companion object {
89+
private fun Properties.inputStream(): InputStream {
90+
val os = ByteArrayOutputStream()
91+
store(os, null)
92+
return ByteArrayInputStream(os.toByteArray())
93+
}
94+
95+
private const val GROOVY_LEGACY_EXTENSION_MODULE_DESCRIPTOR_PATH =
96+
"META-INF/services/org.codehaus.groovy.runtime.ExtensionModule"
97+
private const val GROOVY_EXTENSION_MODULE_DESCRIPTOR_PATH =
98+
"META-INF/groovy/org.codehaus.groovy.runtime.ExtensionModule"
99+
private const val MODULE_NAME_KEY = "moduleName"
100+
private const val MODULE_VERSION_KEY = "moduleVersion"
101+
private const val EXTENSION_CLASSES_KEY = "extensionClasses"
102+
private const val STATIC_EXTENSION_CLASSES_KEY = "staticExtensionClasses"
103+
private const val MERGED_MODULE_NAME = "MergedByShadowJar"
104+
private const val MERGED_MODULE_VERSION = "1.0.0"
105+
}
106+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package com.github.jengelman.gradle.plugins.shadow.transformers
2+
3+
import com.github.jengelman.gradle.plugins.shadow.ShadowStats
4+
import com.github.jengelman.gradle.plugins.shadow.relocation.RelocateClassContext
5+
import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator
6+
import com.github.jengelman.gradle.plugins.shadow.transformers.TransformerContext.Companion.getEntryTimestamp
7+
import java.io.File
8+
import java.net.URL
9+
import java.util.Collections
10+
import java.util.Enumeration
11+
import org.apache.commons.io.output.CloseShieldOutputStream
12+
import org.apache.logging.log4j.core.config.plugins.processor.PluginCache
13+
import org.apache.logging.log4j.core.config.plugins.processor.PluginProcessor
14+
import org.apache.tools.zip.ZipEntry
15+
import org.apache.tools.zip.ZipOutputStream
16+
import org.gradle.api.file.FileTreeElement
17+
18+
/**
19+
* Modified from the maven equivalent to work with gradle
20+
*
21+
* @author Paul Nelson Baker
22+
* @see [LinkedIn](https://www.linkedin.com/in/paul-n-baker/)
23+
* @see [GitHub](https://github.com/paul-nelson-baker/)
24+
* @see [PluginsCacheFileTransformer.java](https://github.com/edwgiz/maven-shaded-log4j-transformer/blob/master/src/main/java/com/github/edwgiz/mavenShadePlugin/log4j2CacheTransformer/PluginsCacheFileTransformer.java)
25+
*/
26+
@CacheableTransformer
27+
open class Log4j2PluginsCacheFileTransformer : Transformer {
28+
private val temporaryFiles = mutableListOf<File>()
29+
private val relocators = mutableListOf<Relocator>()
30+
private var stats: ShadowStats? = null
31+
32+
override fun canTransformResource(element: FileTreeElement): Boolean {
33+
return PluginProcessor.PLUGIN_CACHE_FILE == element.name
34+
}
35+
36+
override fun transform(context: TransformerContext) {
37+
val temporaryFile = File.createTempFile("Log4j2Plugins", ".dat")
38+
temporaryFile.deleteOnExit()
39+
temporaryFiles.add(temporaryFile)
40+
val fos = temporaryFile.outputStream()
41+
context.inputStream.use {
42+
it.copyTo(fos)
43+
}
44+
45+
relocators.addAll(context.relocators)
46+
47+
if (stats == null) {
48+
stats = context.stats
49+
}
50+
}
51+
52+
override fun hasTransformedResource(): Boolean {
53+
// This functionality matches the original plugin, however, I'm not clear what
54+
// the exact logic is. From what I can tell temporaryFiles should be never be empty
55+
// if anything has been performed.
56+
val hasTransformedMultipleFiles = temporaryFiles.size > 1
57+
val hasAtLeastOneFileAndRelocator = temporaryFiles.isNotEmpty() && relocators.isNotEmpty()
58+
return hasTransformedMultipleFiles || hasAtLeastOneFileAndRelocator
59+
}
60+
61+
override fun modifyOutputStream(os: ZipOutputStream, preserveFileTimestamps: Boolean) {
62+
val pluginCache = PluginCache()
63+
pluginCache.loadCacheFiles(urlEnumeration)
64+
relocatePlugins(pluginCache)
65+
val entry = ZipEntry(PluginProcessor.PLUGIN_CACHE_FILE)
66+
entry.time = getEntryTimestamp(preserveFileTimestamps, entry.time)
67+
os.putNextEntry(entry)
68+
pluginCache.writeCache(CloseShieldOutputStream.wrap(os))
69+
temporaryFiles.clear()
70+
}
71+
72+
private fun relocatePlugins(pluginCache: PluginCache) {
73+
pluginCache.allCategories.values.forEach { currentMap ->
74+
currentMap.values.forEach { currentPluginEntry ->
75+
val className = currentPluginEntry.className
76+
val relocateClassContext = RelocateClassContext(className, requireNotNull(stats))
77+
relocators.firstOrNull { it.canRelocateClass(className) }?.let { relocator ->
78+
// Then we perform that relocation and update the plugin entry to reflect the new value.
79+
currentPluginEntry.className = relocator.relocateClass(relocateClassContext)
80+
}
81+
}
82+
}
83+
}
84+
85+
private val urlEnumeration: Enumeration<URL>
86+
get() {
87+
val urls = temporaryFiles.map { it.toURI().toURL() }
88+
return Collections.enumeration(urls)
89+
}
90+
}

0 commit comments

Comments
 (0)