Merge pull request #356 from rtyler/shadow-307

Sync JRubyJarCopyAction with the upstream/originating shadow code
This commit is contained in:
R Tyler Croy 2019-05-19 20:26:23 -07:00 committed by GitHub
commit 411479474e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 186 additions and 86 deletions

View File

@ -17,7 +17,14 @@ generateTestConfig {
dependencies {
compile project(':jruby-gradle-plugin')
compile 'com.github.jengelman.gradle.plugins:shadow:[1.2.2,2.0)' // NEED TO FIX THIS OPEN END
/*
* NOTE: version 5.0.0 of the shadow plugin supports only Gradle 5.x and later
*/
compile 'com.github.jengelman.gradle.plugins:shadow:4.0.4'
compile 'org.codehaus.plexus:plexus-utils:[3.2.0,3.3)'
compile 'org.apache.commons:commons-io:1.3.2'
compile 'org.ow2.asm:asm-commons:[6.1,6.99)'
compile 'org.apache.ant:ant:[1.10.6,2.0)'
testCompile (spockVersion) {
exclude module : 'groovy-all'

View File

@ -1,7 +1,12 @@
package com.github.jrubygradle.jar
/*
* These two internal imports from the Shadow plugin are unavoidable because of
* the expected internals of ShadowCopyAction
*/
import com.github.jengelman.gradle.plugins.shadow.internal.DefaultZipCompressor
import com.github.jengelman.gradle.plugins.shadow.internal.ZipCompressor
import com.github.jrubygradle.JRubyPrepare
import com.github.jrubygradle.jar.internal.JRubyDirInfoTransformer
import com.github.jrubygradle.jar.internal.JRubyJarCopyAction
@ -12,6 +17,7 @@ import org.gradle.api.artifacts.Configuration
import org.gradle.api.file.DuplicatesStrategy
import org.gradle.api.internal.file.copy.CopyAction
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.StopExecutionException
import org.gradle.api.tasks.bundling.Jar
@ -293,11 +299,18 @@ class JRubyJar extends Jar {
return new JRubyJarCopyAction(getArchivePath(),
getInternalCompressor(),
null, /* DocumentationRegistry */
'utf-8', /* encoding */
[new JRubyDirInfoTransformer()], /* transformers */
[], /* relocators */
mainSpec.buildRootResolver().getPatternSet())
mainSpec.buildRootResolver().getPatternSet(), /* patternSet */
false, /* preserveFileTimestamps */
false, /* minimizeJar */
null /* unusedTracker */
)
}
@Internal
protected ZipCompressor getInternalCompressor() {
switch (entryCompression) {
case ZipEntryCompression.DEFLATED:

View File

@ -4,13 +4,13 @@ import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import org.apache.tools.zip.ZipOutputStream
import org.apache.tools.zip.ZipEntry
import shadow.org.apache.tools.zip.ZipEntry
import org.codehaus.plexus.util.IOUtil
import org.gradle.api.file.FileTreeElement
import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator
import shadow.org.apache.tools.zip.ZipOutputStream
import com.github.jengelman.gradle.plugins.shadow.transformers.Transformer
import com.github.jengelman.gradle.plugins.shadow.transformers.TransformerContext
/**
* JRubyDirInfoTransformer implements a {@link Transformer} interface.
@ -44,7 +44,7 @@ class JRubyDirInfoTransformer implements Transformer {
}
/** No-op since we don't transform the actual file */
void transform(String path, InputStream is, List<Relocator> relocators) {
void transform(TransformerContext context) {
return
}
@ -63,7 +63,7 @@ class JRubyDirInfoTransformer implements Transformer {
* This method will also clean up our tempdir to make sure we don't
* clutter the user's machine with junk
*/
void modifyOutputStream(ZipOutputStream os) {
void modifyOutputStream(ZipOutputStream os, boolean preserveFileTimestamps) {
processDirectory(os, tmpDir)
deleteTempDirectory(tmpDir)
}

View File

@ -7,17 +7,18 @@ package com.github.jrubygradle.jar.internal
*/
import com.github.jengelman.gradle.plugins.shadow.impl.RelocatorRemapper
import com.github.jengelman.gradle.plugins.shadow.internal.UnusedTracker
import com.github.jengelman.gradle.plugins.shadow.internal.ZipCompressor
import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator
import com.github.jengelman.gradle.plugins.shadow.transformers.Transformer
import groovy.util.logging.Slf4j
import org.apache.commons.io.FilenameUtils
import org.apache.commons.io.IOUtils
import org.apache.tools.zip.UnixStat
import org.apache.tools.zip.Zip64RequiredException
import org.apache.tools.zip.ZipEntry
import org.apache.tools.zip.ZipFile
import org.apache.tools.zip.ZipOutputStream
import shadow.org.apache.tools.zip.UnixStat
import shadow.org.apache.tools.zip.Zip64RequiredException
import shadow.org.apache.tools.zip.ZipEntry
import shadow.org.apache.tools.zip.ZipFile
import shadow.org.apache.tools.zip.ZipOutputStream
import org.gradle.api.Action
import org.gradle.api.GradleException
import org.gradle.api.UncheckedIOException
@ -26,18 +27,19 @@ import org.gradle.api.file.FileTreeElement
import org.gradle.api.file.RelativePath
import org.gradle.api.internal.DocumentationRegistry
import org.gradle.api.internal.file.CopyActionProcessingStreamAction
import org.gradle.api.internal.file.DefaultFileTreeElement
import org.gradle.api.internal.file.copy.CopyAction
import org.gradle.api.internal.file.copy.CopyActionProcessingStream
import org.gradle.api.internal.file.copy.FileCopyDetailsInternal
import org.gradle.api.tasks.WorkResults
import org.gradle.api.tasks.WorkResult
import org.gradle.api.tasks.WorkResults
import org.gradle.api.tasks.bundling.Zip
import org.gradle.api.tasks.util.PatternSet
import org.gradle.internal.UncheckedException
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.commons.RemappingClassAdapter
import org.objectweb.asm.commons.ClassRemapper
import java.util.zip.ZipException
@ -46,7 +48,7 @@ import java.util.zip.ZipException
*
* This class, in its current form is really just a big copy and paste of the
* shadow plugin's <a
* href="https://github.com/johnrengelman/shadow/blob/51f2b5916edd7690dca5e89e8b3a8f330d435423/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowCopyAction.groovy">ShadowCopyAction</a>.
* href="https://github.com/johnrengelman/shadow/blob/4.0.4/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowCopyAction.groovy">ShadowCopyAction</a>
* with one notable exception, it disables the behavior of unzipping nested
* archives when creating the resulting archive.
*
@ -56,8 +58,9 @@ import java.util.zip.ZipException
*/
@Slf4j
@SuppressWarnings(['ParameterCount', 'CatchException', 'DuplicateStringLiteral',
'CatchThrowable', 'VariableName'])
'CatchThrowable', 'VariableName', 'UnnecessaryGString', 'InvertedIfElse'])
class JRubyJarCopyAction implements CopyAction {
static final long CONSTANT_TIME_FOR_ZIP_ENTRIES = (new GregorianCalendar(1980, 1, 1, 0, 0, 0)).timeInMillis
private final File zipFile
private final ZipCompressor compressor
@ -65,9 +68,15 @@ class JRubyJarCopyAction implements CopyAction {
private final List<Transformer> transformers
private final List<Relocator> relocators
private final PatternSet patternSet
private final String encoding
private final boolean preserveFileTimestamps
private final boolean minimizeJar
private final UnusedTracker unusedTracker
JRubyJarCopyAction(File zipFile, ZipCompressor compressor, DocumentationRegistry documentationRegistry,
List<Transformer> transformers, List<Relocator> relocators, PatternSet patternSet) {
String encoding, List<Transformer> transformers, List<Relocator> relocators,
PatternSet patternSet,
boolean preserveFileTimestamps, boolean minimizeJar, UnusedTracker unusedTracker) {
this.zipFile = zipFile
this.compressor = compressor
@ -75,10 +84,31 @@ class JRubyJarCopyAction implements CopyAction {
this.transformers = transformers
this.relocators = relocators
this.patternSet = patternSet
this.encoding = encoding
this.preserveFileTimestamps = preserveFileTimestamps
this.minimizeJar = minimizeJar
this.unusedTracker = unusedTracker
}
@Override
WorkResult execute(CopyActionProcessingStream stream) {
Set<String> unusedClasses
if (minimizeJar) {
stream.process(new BaseStreamAction() {
@Override
void visitFile(FileCopyDetails fileDetails) {
// All project sources are already present, we just need
// to deal with JAR dependencies.
if (isArchive(fileDetails)) {
unusedTracker.addDependency(fileDetails.file)
}
}
})
unusedClasses = unusedTracker.findUnused()
} else {
unusedClasses = Collections.emptySet()
}
final ZipOutputStream zipOutStr
try {
@ -91,7 +121,8 @@ class JRubyJarCopyAction implements CopyAction {
withResource(zipOutStr, new Action<ZipOutputStream>() {
void execute(ZipOutputStream outputStream) {
try {
stream.process(new StreamAction(outputStream, transformers, relocators, patternSet))
stream.process(new StreamAction(outputStream, encoding, transformers, relocators, patternSet,
unusedClasses))
processTransformers(outputStream)
} catch (Exception e) {
log.error('ex', e)
@ -103,23 +134,33 @@ class JRubyJarCopyAction implements CopyAction {
} catch (UncheckedIOException e) {
if (e.cause instanceof Zip64RequiredException) {
throw new Zip64RequiredException(
String.format('%s\n\nTo build this archive, please enable the zip64 extension.\nSee: %s',
e.cause.message, documentationRegistry.getDslRefForProperty(Zip, 'zip64'))
String.format("%s\n\nTo build this archive, please enable the zip64 extension.\nSee: %s",
e.cause.message, documentationRegistry.getDslRefForProperty(Zip, "zip64"))
)
}
}
return WorkResults.didWork(true)
}
private void processTransformers(ZipOutputStream s) {
private void processTransformers(ZipOutputStream stream) {
transformers.each { Transformer transformer ->
if (transformer.hasTransformedResource()) {
transformer.modifyOutputStream(s)
transformer.modifyOutputStream(stream, preserveFileTimestamps)
}
}
}
@SuppressWarnings('EmptyCatchBlock')
private long getArchiveTimeFor(long timestamp) {
return preserveFileTimestamps ? timestamp : CONSTANT_TIME_FOR_ZIP_ENTRIES
}
private ZipEntry setArchiveTimes(ZipEntry zipEntry) {
if (!preserveFileTimestamps) {
zipEntry.setTime(CONSTANT_TIME_FOR_ZIP_ENTRIES)
}
return zipEntry
}
private static <T extends Closeable> void withResource(T resource, Action<? super T> action) {
try {
action.execute(resource)
@ -127,7 +168,7 @@ class JRubyJarCopyAction implements CopyAction {
try {
resource.close()
} catch (IOException e) {
// Ignored
log.debug("Dropping ignored exception ${e}")
}
throw UncheckedException.throwAsUncheckedException(t)
}
@ -139,25 +180,16 @@ class JRubyJarCopyAction implements CopyAction {
}
}
class StreamAction implements CopyActionProcessingStreamAction {
private final ZipOutputStream zipOutStr
private final List<Transformer> transformers
private final List<Relocator> relocators
private final RelocatorRemapper remapper
private final PatternSet patternSet
private final Set<String> visitedFiles = [] as Set
StreamAction(ZipOutputStream zipOutStr, List<Transformer> transformers, List<Relocator> relocators,
PatternSet patternSet) {
this.zipOutStr = zipOutStr
this.transformers = transformers
this.relocators = relocators
this.remapper = new RelocatorRemapper(relocators)
this.patternSet = patternSet
abstract class BaseStreamAction implements CopyActionProcessingStreamAction {
protected boolean isArchive(FileCopyDetails fileDetails) {
return fileDetails.relativePath.pathString.endsWith('.jar')
}
protected boolean isClass(FileCopyDetails fileDetails) {
return FilenameUtils.getExtension(fileDetails.path) == 'class'
}
@Override
void processFile(FileCopyDetailsInternal details) {
if (details.directory) {
visitDir(details)
@ -166,65 +198,96 @@ class JRubyJarCopyAction implements CopyAction {
}
}
@SuppressWarnings(['UnusedMethodParameter', 'EmptyMethodInAbstractClass'])
protected void visitDir(FileCopyDetails dirDetails) {
}
protected abstract void visitFile(FileCopyDetails fileDetails)
}
private class StreamAction extends BaseStreamAction {
private final ZipOutputStream zipOutStr
private final List<Transformer> transformers
private final List<Relocator> relocators
private final RelocatorRemapper remapper
private final PatternSet patternSet
private final Set<String> unused
private final Set<String> visitedFiles = [] as Set
StreamAction(ZipOutputStream zipOutStr, String encoding, List<Transformer> transformers,
List<Relocator> relocators, PatternSet patternSet, Set<String> unused) {
this.zipOutStr = zipOutStr
this.transformers = transformers
this.relocators = relocators
this.remapper = new RelocatorRemapper(relocators, null)
this.patternSet = patternSet
this.unused = unused
if (encoding != null) {
this.zipOutStr.setEncoding(encoding)
}
}
private boolean recordVisit(RelativePath path) {
return visitedFiles.add(path.pathString)
}
private void visitFile(FileCopyDetails fileDetails) {
@Override
void visitFile(FileCopyDetails fileDetails) {
try {
boolean isClass = (FilenameUtils.getExtension(fileDetails.path) == 'class')
boolean isClass = isClass(fileDetails)
if (!remapper.hasRelocators() || !isClass) {
if (isTransformable(fileDetails)) {
transform(fileDetails)
}
else {
if (!isTransformable(fileDetails)) {
String mappedPath = remapper.map(fileDetails.relativePath.pathString)
ZipEntry archiveEntry = new ZipEntry(mappedPath)
archiveEntry.setTime(fileDetails.lastModified)
archiveEntry.setTime(getArchiveTimeFor(fileDetails.lastModified))
archiveEntry.unixMode = (UnixStat.FILE_FLAG | fileDetails.mode)
zipOutStr.putNextEntry(archiveEntry)
fileDetails.copyTo(zipOutStr)
zipOutStr.closeEntry()
} else {
transform(fileDetails)
}
} else if (isClass) {
} else if (isClass && !isUnused(fileDetails.path)) {
remapClass(fileDetails)
}
recordVisit(fileDetails.relativePath)
} catch (Exception e) {
throw new GradleException(String.format('Could not add %s to ZIP \'%s\'.', fileDetails, zipFile), e)
throw new GradleException(String.format("Could not add %s to ZIP '%s'.", fileDetails, zipFile), e)
}
}
private void visitArchiveDirectory(RelativeArchivePath archiveDir) {
if (recordVisit(archiveDir)) {
zipOutStr.putNextEntry(archiveDir.entry)
zipOutStr.closeEntry()
}
}
private void addParentDirectories(RelativeArchivePath file) {
if (file) {
addParentDirectories(file.parent)
if (!file.file) {
visitArchiveDirectory(file)
}
private boolean isUnused(String classPath) {
final String className = FilenameUtils.removeExtension(classPath)
.replace('/' as char, '.' as char)
final boolean result = unused.contains(className)
if (result) {
log.debug("Dropping unused class: $className")
}
return result
}
private void remapClass(RelativeArchivePath file, ZipFile archive) {
if (file.classFile) {
addParentDirectories(new RelativeArchivePath(new ZipEntry(remapper.mapPath(file) + '.class'), null))
remapClass(archive.getInputStream(file.entry), file.pathString)
ZipEntry zipEntry = setArchiveTimes(new ZipEntry(remapper.mapPath(file) + '.class'))
addParentDirectories(new RelativeArchivePath(zipEntry))
InputStream is = archive.getInputStream(file.entry)
try {
remapClass(is, file.pathString, file.entry.time)
} finally {
is.close()
}
}
}
private void remapClass(FileCopyDetails fileCopyDetails) {
if (FilenameUtils.getExtension(fileCopyDetails.name) == 'class') {
remapClass(fileCopyDetails.file.newInputStream(), fileCopyDetails.path)
remapClass(fileCopyDetails.file.newInputStream(), fileCopyDetails.path, fileCopyDetails.lastModified)
}
}
private void remapClass(InputStream classInputStream, String path) {
private void remapClass(InputStream classInputStream, String path, long lastModified) {
InputStream is = classInputStream
ClassReader cr = new ClassReader(is)
@ -235,12 +298,12 @@ class JRubyJarCopyAction implements CopyAction {
// that use the constant pool to determine the dependencies of a class.
ClassWriter cw = new ClassWriter(0)
ClassVisitor cv = new RemappingClassAdapter(cw, remapper)
ClassVisitor cv = new ClassRemapper(cw, remapper)
try {
cr.accept(cv, ClassReader.EXPAND_FRAMES)
} catch (Throwable ise) {
throw new GradleException('Error in ASM processing class ' + path, ise)
throw new GradleException("Error in ASM processing class " + path, ise)
}
byte[] renamedClass = cw.toByteArray()
@ -248,42 +311,58 @@ class JRubyJarCopyAction implements CopyAction {
// Need to take the .class off for remapping evaluation
String mappedName = remapper.mapPath(path)
InputStream bis = new ByteArrayInputStream(renamedClass)
try {
// Now we put it back on so the class file is written out with the right extension.
zipOutStr.putNextEntry(new ZipEntry(mappedName + '.class'))
IOUtils.copyLarge(new ByteArrayInputStream(renamedClass), zipOutStr)
ZipEntry archiveEntry = new ZipEntry(mappedName + ".class")
archiveEntry.setTime(getArchiveTimeFor(lastModified))
zipOutStr.putNextEntry(archiveEntry)
IOUtils.copyLarge(bis, zipOutStr)
zipOutStr.closeEntry()
} catch (ZipException e) {
log.warn('We have a duplicate ' + mappedName + ' in source project')
log.warn("We have a duplicate " + mappedName + " in source project")
} finally {
bis.close()
}
}
private void visitDir(FileCopyDetails dirDetails) {
@Override
protected void visitDir(FileCopyDetails dirDetails) {
try {
// Trailing slash in name indicates that entry is a directory
String path = dirDetails.relativePath.pathString + '/'
ZipEntry archiveEntry = new ZipEntry(path)
archiveEntry.setTime(dirDetails.lastModified)
archiveEntry.setTime(getArchiveTimeFor(dirDetails.lastModified))
archiveEntry.unixMode = (UnixStat.DIR_FLAG | dirDetails.mode)
zipOutStr.putNextEntry(archiveEntry)
zipOutStr.closeEntry()
recordVisit(dirDetails.relativePath)
} catch (Exception e) {
throw new GradleException(String.format('Could not add %s to ZIP \'%s\'.', dirDetails, zipFile), e)
throw new GradleException(String.format("Could not add %s to ZIP '%s'.", dirDetails, zipFile), e)
}
}
private void transform(ArchiveFileTreeElement element, ZipFile archive) {
transform(element, archive.getInputStream(element.relativePath.entry))
transformAndClose(element, archive.getInputStream(element.relativePath.entry))
}
private void transform(FileCopyDetails details) {
transform(details, details.file.newInputStream())
transformAndClose(details, details.file.newInputStream())
}
private void transform(FileTreeElement element, InputStream is) {
String mappedPath = remapper.map(element.relativePath.pathString)
transformers.find { it.canTransformResource(element) }.transform(mappedPath, is, relocators)
private void transformAndClose(FileTreeElement element, InputStream is) {
try {
String mappedPath = remapper.map(element.relativePath.pathString)
transformers.find { it.canTransformResource(element) }.transform(
TransformerContext.builder()
.path(mappedPath)
.is(is)
.relocators(relocators)
.build()
)
} finally {
is.close()
}
}
private boolean isTransformable(FileTreeElement element) {
@ -295,12 +374,10 @@ class JRubyJarCopyAction implements CopyAction {
class RelativeArchivePath extends RelativePath {
ZipEntry entry
FileCopyDetails details
RelativeArchivePath(ZipEntry entry, FileCopyDetails fileDetails) {
RelativeArchivePath(ZipEntry entry) {
super(!entry.directory, entry.name.split('/'))
this.entry = entry
this.details = fileDetails
}
boolean isClassFile() {
@ -311,14 +388,12 @@ class JRubyJarCopyAction implements CopyAction {
if (!segments || segments.length == 1) {
return null
}
//Parent is always a directory so add / to the end of the path
String path = segments[0..-2].join('/') + '/'
return new RelativeArchivePath(new ZipEntry(path), null)
return new RelativeArchivePath(setArchiveTimes(new ZipEntry(path)))
}
}
@SuppressWarnings('GetterMethodCouldBeProperty')
class ArchiveFileTreeElement implements FileTreeElement {
private final RelativeArchivePath archivePath
@ -331,6 +406,7 @@ class JRubyJarCopyAction implements CopyAction {
return archivePath.classFile
}
@SuppressWarnings(['GetterMethodCouldBeProperty'])
@Override
File getFile() {
return null
@ -385,5 +461,9 @@ class JRubyJarCopyAction implements CopyAction {
int getMode() {
return archivePath.entry.unixMode
}
FileTreeElement asFileTreeElement() {
return new DefaultFileTreeElement(null, new RelativePath(!isDirectory(), archivePath.segments), null, null)
}
}
}