diff --git a/api/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ComponentsXmlResourceTransformer.kt b/api/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ComponentsXmlResourceTransformer.kt new file mode 100644 index 000000000..258623830 --- /dev/null +++ b/api/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ComponentsXmlResourceTransformer.kt @@ -0,0 +1,134 @@ +package com.github.jengelman.gradle.plugins.shadow.transformers + +import com.github.jengelman.gradle.plugins.shadow.relocation.RelocateClassContext +import com.github.jengelman.gradle.plugins.shadow.transformers.TransformerContext.Companion.getEntryTimestamp +import java.io.BufferedInputStream +import java.io.ByteArrayOutputStream +import java.io.IOException +import org.apache.tools.zip.ZipEntry +import org.apache.tools.zip.ZipOutputStream +import org.codehaus.plexus.util.xml.XmlStreamReader +import org.codehaus.plexus.util.xml.XmlStreamWriter +import org.codehaus.plexus.util.xml.Xpp3Dom +import org.codehaus.plexus.util.xml.Xpp3DomBuilder +import org.codehaus.plexus.util.xml.Xpp3DomWriter +import org.gradle.api.file.FileTreeElement + +/** + * A resource processor that aggregates plexus `components.xml` files. + * + * Modified from `org.apache.maven.plugins.shade.resource.ComponentsXmlResourceTransformer.java` + * + * @author John Engelman + */ +open class ComponentsXmlResourceTransformer : Transformer { + private val components = mutableMapOf() + + override fun canTransformResource(element: FileTreeElement): Boolean { + return COMPONENTS_XML_PATH == element.relativePath.pathString + } + + override fun transform(context: TransformerContext) { + val newDom = try { + val bis = object : BufferedInputStream(context.inputStream) { + @Throws(IOException::class) + override fun close() { + // leave ZIP open + } + } + Xpp3DomBuilder.build(XmlStreamReader(bis)) + } catch (e: Exception) { + throw IOException("Error parsing components.xml in ${context.inputStream}", e) + } + + // Only try to merge in components if there are some elements in the component-set + if (newDom.getChild("components") == null) return + + val children = newDom.getChild("components").getChildren("component") + for (component in children) { + var role: String? = getValue(component, "role") + role = getRelocatedClass(role, context) + setValue(component, "role", role) + + val roleHint = getValue(component, "role-hint") + + var impl: String? = getValue(component, "implementation") + impl = getRelocatedClass(impl, context) + setValue(component, "implementation", impl) + + val key = "$role:$roleHint" + // TODO: use the tools in Plexus to merge these properly. For now, I just need an all-or-nothing + // configuration carry over + components[key]?.getChild("configuration")?.let { + component.addChild(it) + } + + val requirements = component.getChild("requirements") + if (requirements != null && requirements.childCount > 0) { + for (r in requirements.childCount - 1 downTo 0) { + val requirement = requirements.getChild(r) + var requiredRole: String? = getValue(requirement, "role") + requiredRole = getRelocatedClass(requiredRole, context) + setValue(requirement, "role", requiredRole) + } + } + components[key] = component + } + } + + override fun modifyOutputStream(os: ZipOutputStream, preserveFileTimestamps: Boolean) { + val entry = ZipEntry(COMPONENTS_XML_PATH) + entry.time = getEntryTimestamp(preserveFileTimestamps, entry.time) + os.putNextEntry(entry) + + transformedResource.inputStream().use { + it.copyTo(os) + } + components.clear() + } + + override fun hasTransformedResource(): Boolean = components.isNotEmpty() + + @get:Throws(IOException::class) + private val transformedResource: ByteArray + get() { + val os = ByteArrayOutputStream(1024 * 4) + XmlStreamWriter(os).use { writer -> + val dom = Xpp3Dom("component-set") + val componentDom = Xpp3Dom("components") + dom.addChild(componentDom) + for (component in components.values) { + componentDom.addChild(component) + } + Xpp3DomWriter.write(writer, dom) + } + return os.toByteArray() + } + + companion object { + const val COMPONENTS_XML_PATH: String = "META-INF/plexus/components.xml" + + private fun getRelocatedClass(className: String?, context: TransformerContext): String? { + val stats = context.stats + if (!className.isNullOrEmpty()) { + for (relocator in context.relocators) { + if (relocator.canRelocateClass(className)) { + val relocateClassContext = RelocateClassContext(className, stats) + return relocator.relocateClass(relocateClassContext) + } + } + } + return className + } + + private fun getValue(dom: Xpp3Dom, element: String): String { + return dom.getChild(element).value.orEmpty() + } + + private fun setValue(dom: Xpp3Dom, element: String, value: String?) { + val child = dom.getChild(element) + if (child == null || value.isNullOrEmpty()) return + child.value = value + } + } +} diff --git a/api/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ServiceFileTransformer.kt b/api/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ServiceFileTransformer.kt new file mode 100644 index 000000000..824ac063a --- /dev/null +++ b/api/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ServiceFileTransformer.kt @@ -0,0 +1,116 @@ +package com.github.jengelman.gradle.plugins.shadow.transformers + +import com.github.jengelman.gradle.plugins.shadow.relocation.RelocateClassContext +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowCopyAction +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.File +import java.io.IOException +import java.io.InputStream +import org.apache.tools.zip.ZipEntry +import org.apache.tools.zip.ZipOutputStream +import org.gradle.api.file.FileTreeElement +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.util.PatternFilterable +import org.gradle.api.tasks.util.PatternSet + +/** + * Modified from `org.apache.maven.plugins.shade.resource.ServiceResourceTransformer.java` + * + * Resources transformer that appends entries in `META-INF/services` resources into + * a single resource. For example, if there are several `META-INF/services/org.apache.maven.project.ProjectBuilder` + * resources spread across many JARs the individual entries will all be concatenated into a single + * `META-INF/services/org.apache.maven.project.ProjectBuilder` resource packaged into the resultant JAR produced + * by the shading process. + * + * @author jvanzyl + * @author Charlie Knudsen + * @author John Engelman + */ +@CacheableTransformer +open class ServiceFileTransformer( + private val patternSet: PatternSet = PatternSet() + .include(SERVICES_PATTERN) + .exclude(GROOVY_EXTENSION_MODULE_DESCRIPTOR_PATTERN), +) : Transformer, + PatternFilterable by patternSet { + private val serviceEntries = mutableMapOf() + + override fun canTransformResource(element: FileTreeElement): Boolean { + val target = if (element is ShadowCopyAction.ArchiveFileTreeElement) element.asFileTreeElement() else element + return patternSet.asSpec.isSatisfiedBy(target) + } + + override fun transform(context: TransformerContext) { + val lines = context.inputStream.bufferedReader().readLines().toMutableList() + var targetPath = context.path + context.relocators.forEach { rel -> + if (rel.canRelocateClass(File(targetPath).name)) { + val classContext = RelocateClassContext.builder().className(targetPath).stats(context.stats).build() + targetPath = rel.relocateClass(classContext) + } + lines.forEachIndexed { i, line -> + if (rel.canRelocateClass(line)) { + val lineContext = RelocateClassContext.builder().className(line).stats(context.stats).build() + lines[i] = rel.relocateClass(lineContext) + } + } + } + lines.forEach { line -> + serviceEntries[targetPath] = serviceEntries.getOrDefault(targetPath, ServiceStream()).apply { + append(ByteArrayInputStream(line.toByteArray())) + } + } + } + + override fun hasTransformedResource(): Boolean = serviceEntries.isNotEmpty() + + override fun modifyOutputStream(os: ZipOutputStream, preserveFileTimestamps: Boolean) { + serviceEntries.forEach { (path, stream) -> + val entry = ZipEntry(path) + entry.time = TransformerContext.getEntryTimestamp(preserveFileTimestamps, entry.time) + os.putNextEntry(entry) + stream.toInputStream().use { + it.copyTo(os) + } + os.closeEntry() + } + } + + /** + * {@inheritDoc} + */ + @Input + override fun getIncludes(): Set = patternSet.includes + + /** + * {@inheritDoc} + */ + @Input + override fun getExcludes(): Set = patternSet.excludes + + open fun setPath(path: String): PatternFilterable = apply { + patternSet.setIncludes(listOf("$path/**")) + } + + open class ServiceStream : ByteArrayOutputStream(1024) { + @Throws(IOException::class) + open fun append(inputStream: InputStream) { + if (count > 0 && buf[count - 1] != '\n'.code.toByte() && buf[count - 1] != '\r'.code.toByte()) { + val newline = "\n".toByteArray() + write(newline, 0, newline.size) + } + inputStream.use { + it.copyTo(this) + } + } + + open fun toInputStream(): InputStream = ByteArrayInputStream(buf, 0, count) + } + + private companion object { + private const val SERVICES_PATTERN = "META-INF/services/**" + private const val GROOVY_EXTENSION_MODULE_DESCRIPTOR_PATTERN = + "META-INF/services/org.codehaus.groovy.runtime.ExtensionModule" + } +} diff --git a/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/ComponentsXmlResourceTransformer.groovy b/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/ComponentsXmlResourceTransformer.groovy deleted file mode 100644 index 8321bc1c7..000000000 --- a/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/ComponentsXmlResourceTransformer.groovy +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License") you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package com.github.jengelman.gradle.plugins.shadow.transformers - -import com.github.jengelman.gradle.plugins.shadow.ShadowStats -import com.github.jengelman.gradle.plugins.shadow.relocation.RelocateClassContext -import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator -import org.apache.tools.zip.ZipEntry -import org.apache.tools.zip.ZipOutputStream -import org.codehaus.plexus.util.IOUtil -import org.codehaus.plexus.util.xml.XmlStreamReader -import org.codehaus.plexus.util.xml.XmlStreamWriter -import org.codehaus.plexus.util.xml.Xpp3Dom -import org.codehaus.plexus.util.xml.Xpp3DomBuilder -import org.codehaus.plexus.util.xml.Xpp3DomWriter -import org.gradle.api.file.FileTreeElement - -/** - * A resource processor that aggregates plexus components.xml files. - *

- * Modified from org.apache.maven.plugins.shade.resource.ComponentsXmlResourceTransformer.java - * - * @author John Engelman - */ -class ComponentsXmlResourceTransformer implements Transformer { - private Map components = new LinkedHashMap() - - public static final String COMPONENTS_XML_PATH = "META-INF/plexus/components.xml" - - @Override - boolean canTransformResource(FileTreeElement element) { - def path = element.relativePath.pathString - return COMPONENTS_XML_PATH == path - } - - @Override - void transform(TransformerContext context) { - Xpp3Dom newDom - - try { - BufferedInputStream bis = new BufferedInputStream(context.inputStream) { - void close() - throws IOException { - // leave ZIP open - } - } - - Reader reader = new XmlStreamReader(bis) - - newDom = Xpp3DomBuilder.build(reader) - } - catch (Exception e) { - throw (IOException) new IOException("Error parsing components.xml in " + context.inputStream).initCause(e) - } - - // Only try to merge in components if there are some elements in the component-set - if (newDom.getChild("components") == null) { - return - } - - Xpp3Dom[] children = newDom.getChild("components").getChildren("component") - - for (int i = 0; i < children.length; i++) { - Xpp3Dom component = children[i] - - String role = getValue(component, "role") - role = getRelocatedClass(role, context) - setValue(component, "role", role) - - String roleHint = getValue(component, "role-hint") - - String impl = getValue(component, "implementation") - impl = getRelocatedClass(impl, context) - setValue(component, "implementation", impl) - - String key = role + ':' + roleHint - if (components.containsKey(key)) { - // TODO: use the tools in Plexus to merge these properly. For now, I just need an all-or-nothing - // configuration carry over - - Xpp3Dom dom = components.get(key) - if (dom.getChild("configuration") != null) { - component.addChild(dom.getChild("configuration")) - } - } - - Xpp3Dom requirements = component.getChild("requirements") - if (requirements != null && requirements.getChildCount() > 0) { - for (int r = requirements.getChildCount() - 1; r >= 0; r--) { - Xpp3Dom requirement = requirements.getChild(r) - - String requiredRole = getValue(requirement, "role") - requiredRole = getRelocatedClass(requiredRole, context) - setValue(requirement, "role", requiredRole) - } - } - - components.put(key, component) - } - } - - @Override - void modifyOutputStream(ZipOutputStream os, boolean preserveFileTimestamps) { - byte[] data = getTransformedResource() - - ZipEntry entry = new ZipEntry(COMPONENTS_XML_PATH) - entry.time = TransformerContext.getEntryTimestamp(preserveFileTimestamps, entry.time) - - os.putNextEntry(entry) - - IOUtil.copy(data, os) - - components.clear() - } - - @Override - boolean hasTransformedResource() { - return !components.isEmpty() - } - - private byte[] getTransformedResource() - throws IOException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(1024 * 4) - - try (Writer writer = new XmlStreamWriter(baos)) { - Xpp3Dom dom = new Xpp3Dom("component-set") - - Xpp3Dom componentDom = new Xpp3Dom("components") - - dom.addChild(componentDom) - - for (Xpp3Dom component : components.values()) { - componentDom.addChild(component) - } - - Xpp3DomWriter.write(writer, dom) - } - - return baos.toByteArray() - } - - private static String getRelocatedClass(String className, TransformerContext context) { - List relocators = context.relocators - ShadowStats stats = context.stats - if (className != null && className.length() > 0 && relocators != null) { - for (Relocator relocator : relocators) { - if (relocator.canRelocateClass(className)) { - RelocateClassContext relocateClassContext = new RelocateClassContext(className, stats) - return relocator.relocateClass(relocateClassContext) - } - } - } - - return className - } - - private static String getValue(Xpp3Dom dom, String element) { - Xpp3Dom child = dom.getChild(element) - - return (child != null && child.getValue() != null) ? child.getValue() : "" - } - - private static void setValue(Xpp3Dom dom, String element, String value) { - Xpp3Dom child = dom.getChild(element) - - if (child == null || value == null || value.length() <= 0) { - return - } - - child.setValue(value) - } - -} diff --git a/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/ServiceFileTransformer.groovy b/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/ServiceFileTransformer.groovy deleted file mode 100644 index 78f3184d9..000000000 --- a/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/ServiceFileTransformer.groovy +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package com.github.jengelman.gradle.plugins.shadow.transformers - -import com.github.jengelman.gradle.plugins.shadow.relocation.RelocateClassContext -import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowCopyAction -import org.apache.tools.zip.ZipEntry -import org.apache.tools.zip.ZipOutputStream -import org.gradle.api.file.FileTreeElement -import org.gradle.api.specs.Spec -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.util.PatternFilterable -import org.gradle.api.tasks.util.PatternSet -import org.codehaus.plexus.util.IOUtil - -/** - * Modified from org.apache.maven.plugins.shade.resource.ServiceResourceTransformer.java - *

- * Resources transformer that appends entries in META-INF/services resources into - * a single resource. For example, if there are several META-INF/services/org.apache.maven.project.ProjectBuilder - * resources spread across many JARs the individual entries will all be concatenated into a single - * META-INF/services/org.apache.maven.project.ProjectBuilder resource packaged into the resultant JAR produced - * by the shading process. - * - * @author jvanzyl - * @author Charlie Knudsen - * @author John Engelman - */ -@CacheableTransformer -class ServiceFileTransformer implements Transformer, PatternFilterable { - - private static final String SERVICES_PATTERN = "META-INF/services/**" - - private static final String GROOVY_EXTENSION_MODULE_DESCRIPTOR_PATTERN = - "META-INF/services/org.codehaus.groovy.runtime.ExtensionModule" - - private Map serviceEntries = [:].withDefault { new ServiceStream() } - - private final PatternSet patternSet = - new PatternSet().include(SERVICES_PATTERN).exclude(GROOVY_EXTENSION_MODULE_DESCRIPTOR_PATTERN) - - void setPath(String path) { - patternSet.setIncludes(["${path}/**"]) - } - - @Override - boolean canTransformResource(FileTreeElement element) { - FileTreeElement target = element instanceof ShadowCopyAction.ArchiveFileTreeElement ? element.asFileTreeElement() : element - return patternSet.asSpec.isSatisfiedBy(target) - } - - @Override - void transform(TransformerContext context) { - def lines = context.inputStream.readLines() - def targetPath = context.path - context.relocators.each {rel -> - if (rel.canRelocateClass(new File(targetPath).name)) { - targetPath = rel.relocateClass(RelocateClassContext.builder().className(targetPath).stats(context.stats).build()) - } - lines.eachWithIndex { String line, int i -> - if (rel.canRelocateClass(line)) { - def lineContext = RelocateClassContext.builder().className(line).stats(context.stats).build() - lines[i] = rel.relocateClass(lineContext) - } - } - } - lines.each {line -> serviceEntries[targetPath].append(new ByteArrayInputStream(line.getBytes()))} - } - - @Override - boolean hasTransformedResource() { - return serviceEntries.size() > 0 - } - - @Override - void modifyOutputStream(ZipOutputStream os, boolean preserveFileTimestamps) { - serviceEntries.each { String path, ServiceStream stream -> - ZipEntry entry = new ZipEntry(path) - entry.time = TransformerContext.getEntryTimestamp(preserveFileTimestamps, entry.time) - os.putNextEntry(entry) - IOUtil.copy(stream.toInputStream(), os) - os.closeEntry() - } - } - - static class ServiceStream extends ByteArrayOutputStream { - - ServiceStream(){ - super( 1024 ) - } - - void append(InputStream is ) throws IOException { - if ( super.count > 0 && super.buf[super.count - 1] != '\n' && super.buf[super.count - 1] != '\r' ) { - byte[] newline = '\n'.bytes - write(newline, 0, newline.length) - } - IOUtil.copy(is, this) - } - - InputStream toInputStream() { - return new ByteArrayInputStream( super.buf, 0, super.count ) - } - } - - /** - * {@inheritDoc} - */ - @Override - ServiceFileTransformer include(String... includes) { - patternSet.include(includes) - return this - } - - /** - * {@inheritDoc} - */ - @Override - ServiceFileTransformer include(Iterable includes) { - patternSet.include(includes) - return this - } - - /** - * {@inheritDoc} - */ - @Override - ServiceFileTransformer include(Spec includeSpec) { - patternSet.include(includeSpec) - return this - } - - /** - * {@inheritDoc} - */ - @Override - ServiceFileTransformer include(Closure includeSpec) { - patternSet.include(includeSpec) - return this - } - - /** - * {@inheritDoc} - */ - @Override - ServiceFileTransformer exclude(String... excludes) { - patternSet.exclude(excludes) - return this - } - - /** - * {@inheritDoc} - */ - @Override - ServiceFileTransformer exclude(Iterable excludes) { - patternSet.exclude(excludes) - return this - } - - /** - * {@inheritDoc} - */ - @Override - ServiceFileTransformer exclude(Spec excludeSpec) { - patternSet.exclude(excludeSpec) - return this - } - - /** - * {@inheritDoc} - */ - @Override - ServiceFileTransformer exclude(Closure excludeSpec) { - patternSet.exclude(excludeSpec) - return this - } - - /** - * {@inheritDoc} - */ - @Override - @Input - Set getIncludes() { - return patternSet.includes - } - - /** - * {@inheritDoc} - */ - @Override - ServiceFileTransformer setIncludes(Iterable includes) { - patternSet.includes = includes - return this - } - - /** - * {@inheritDoc} - */ - @Override - @Input - Set getExcludes() { - return patternSet.excludes - } - - /** - * {@inheritDoc} - */ - @Override - ServiceFileTransformer setExcludes(Iterable excludes) { - patternSet.excludes = excludes - return this - } -}