Skip to content

Commit

Permalink
close #55 apply class remappings to source project files
Browse files Browse the repository at this point in the history
  • Loading branch information
johnrengelman committed Jun 27, 2014
1 parent 6928a53 commit 279572b
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 32 deletions.
1 change: 1 addition & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ v1.0.0
+ All changes from 0.9.0-M1 to 0.9.0-M5
+ Properly configure the ShadowJar task inputs to observe the include/excludes from the `dependencies` block. This
allows UP-TO-DATE checking to work properly when changing the `dependencies` rules ([Issue #54](https://github.com/johnrengelman/shadow/issues/54))
+ Apply relocation remappings to classes and imports in source project ([Issue #55](https://github.com/johnrengelman/shadow/issues/55))

v0.9.0-M5
=========
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.github.jengelman.gradle.plugins.shadow.impl.RelocatorRemapper
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
Expand Down Expand Up @@ -148,13 +149,17 @@ public class ShadowCopyAction implements CopyAction {
private void visitFile(FileCopyDetails fileDetails) {
if (!isArchive(fileDetails)) {
try {
String path = fileDetails.relativePath.pathString
ZipEntry archiveEntry = new ZipEntry(path)
archiveEntry.setTime(fileDetails.lastModified)
archiveEntry.unixMode = (UnixStat.FILE_FLAG | fileDetails.mode)
zipOutStr.putNextEntry(archiveEntry)
fileDetails.copyTo(zipOutStr)
zipOutStr.closeEntry()
if (!remapper.hasRelocators()) {
String path = fileDetails.relativePath.pathString
ZipEntry archiveEntry = new ZipEntry(path)
archiveEntry.setTime(fileDetails.lastModified)
archiveEntry.unixMode = (UnixStat.FILE_FLAG | fileDetails.mode)
zipOutStr.putNextEntry(archiveEntry)
fileDetails.copyTo(zipOutStr)
zipOutStr.closeEntry()
} else {
remapClass(fileDetails)
}
recordVisit(fileDetails.relativePath)
} catch (Exception e) {
throw new GradleException(String.format("Could not add %s to ZIP '%s'.", fileDetails, zipFile), e)
Expand Down Expand Up @@ -216,37 +221,47 @@ public class ShadowCopyAction implements CopyAction {

private void remapClass(RelativeArchivePath file, ZipFile archive) {
if (file.classFile) {
InputStream is = archive.getInputStream(file.entry)
ClassReader cr = new ClassReader(is)
remapClass(archive.getInputStream(file.entry), file.pathString)
}
}

// We don't pass the ClassReader here. This forces the ClassWriter to rebuild the constant pool.
// Copying the original constant pool should be avoided because it would keep references
// to the original class names. This is not a problem at runtime (because these entries in the
// constant pool are never used), but confuses some tools such as Felix' maven-bundle-plugin
// that use the constant pool to determine the dependencies of a class.
ClassWriter cw = new ClassWriter(0)
private void remapClass(FileCopyDetails fileCopyDetails) {
if (FilenameUtils.getExtension(fileCopyDetails.name) == 'class') {
remapClass(fileCopyDetails.file.newInputStream(), fileCopyDetails.path)
}
}

ClassVisitor cv = new RemappingClassAdapter(cw, remapper)
private void remapClass(InputStream classInputStream, String path) {
InputStream is = classInputStream
ClassReader cr = new ClassReader(is)

try {
cr.accept(cv, ClassReader.EXPAND_FRAMES)
} catch (Throwable ise) {
throw new GradleException("Error in ASM processing class " + file.pathString, ise)
}
// We don't pass the ClassReader here. This forces the ClassWriter to rebuild the constant pool.
// Copying the original constant pool should be avoided because it would keep references
// to the original class names. This is not a problem at runtime (because these entries in the
// constant pool are never used), but confuses some tools such as Felix' maven-bundle-plugin
// that use the constant pool to determine the dependencies of a class.
ClassWriter cw = new ClassWriter(0)

byte[] renamedClass = cw.toByteArray()
ClassVisitor cv = new RemappingClassAdapter(cw, remapper)

// Need to take the .class off for remapping evaluation
String mappedName = remapper.map(file.pathString.substring(0, file.pathString.indexOf('.')))
try {
cr.accept(cv, ClassReader.EXPAND_FRAMES)
} catch (Throwable ise) {
throw new GradleException("Error in ASM processing class " + path, ise)
}

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)
zipOutStr.closeEntry()
} catch (ZipException e) {
log.warn("We have a duplicate " + mappedName + " in " + archive)
}
byte[] renamedClass = cw.toByteArray()

// Need to take the .class off for remapping evaluation
String mappedName = remapper.map(path.substring(0, path.indexOf('.')))

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)
zipOutStr.closeEntry()
} catch (ZipException e) {
log.warn("We have a duplicate " + mappedName + " in source project")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.github.jengelman.gradle.plugins.shadow

import com.github.jengelman.gradle.plugins.shadow.util.PluginSpecification
import org.gradle.testkit.functional.ExecutionResult
import spock.lang.Issue

class RelocationSpec extends PluginSpecification {

Expand Down Expand Up @@ -135,4 +136,60 @@ class RelocationSpec extends PluginSpecification {
'junit/framework/Protectable.class'
])
}

@Issue('SHADOW-55')
def "remap class names for relocated files in project source"() {
given:
buildFile << """
|apply plugin: 'java'
|apply plugin: ${ShadowPlugin.name}
|
|repositories { jcenter() }
|
|dependencies {
| compile 'junit:junit:3.8.2'
|}
|
|shadowJar {
| baseName = 'shadow'
| classifier = null
| relocate 'junit.framework', 'shadow.junit'
|}
""".stripMargin()

file('src/main/java/shadow/ShadowTest.java') << '''
|package shadow;
|
|import junit.framework.Test;
|import junit.framework.TestResult;
|public class ShadowTest implements Test {
| public int countTestCases() { return 0; }
| public void run(TestResult result) { }
|}
'''.stripMargin()

when:
runner.arguments << 'shadowJar'
ExecutionResult result = runner.run()

then:
success(result)

and:
contains(output, [
'shadow/ShadowTest.class',
'shadow/junit/Test.class',
])

and:
doesNotContain(output, [
'junit/framework/Test.class'
])

and: 'check that the class can be loaded. If the file was not relocated properly, we should get a NoDefClassFound'
// Isolated class loader with only the JVM system jars and the output jar from the test project
URLClassLoader classLoader = new URLClassLoader([output.toURI().toURL()] as URL[],
ClassLoader.systemClassLoader.parent)
classLoader.loadClass('shadow.ShadowTest')
}
}

0 comments on commit 279572b

Please sign in to comment.