aboutsummaryrefslogtreecommitdiff
path: root/build.gradle
diff options
context:
space:
mode:
authorAaron Hill <aa1ronham@gmail.com>2019-04-25 16:44:20 -0400
committerAaron Hill <aa1ronham@gmail.com>2019-04-25 21:47:24 -0400
commit747c582804bdcd0409417fdd92f7a3042d80b6b5 (patch)
tree945e46af6d7359a987d31a30c162bc167e741407 /build.gradle
parent37d713bdb7459cdd44707b350fd12fd59517b415 (diff)
downloadArtifactural-747c582804bdcd0409417fdd92f7a3042d80b6b5.tar.gz
Artifactural-747c582804bdcd0409417fdd92f7a3042d80b6b5.tar.bz2
Artifactural-747c582804bdcd0409417fdd92f7a3042d80b6b5.zip
Add compatibility with Gradle 4.10 and above
This commits adds compatibility for Gradle 4.10 onward (tested on 4.10 and 5,4), while retainting compatibiliy with 4.9 Due to the ABI-incompatible changes in some internal gradle classes, we need to use bytecode manipulation to ensure that our built class files are compatible with both Gradle 4.9 and Gradle >= 4.10 Bytecode manipulation is performed at built time. A custom Gradle task is used to read in the compiled class files from disk, and write out a modified version to the final jar artifact. In order to make as few bytecode modifications as possible, this commit bumps the Gradle wrapper version to 4.10. This means that we're compiling against Gradle 4.10, and using bytecode manipulation to retain compatibility with 4.9. Doing the reverse (compiling against 4.9) would be significantly more difficult, as we would need to statically reference classes that exist in Gradle 4.10 but not 4.9 (specifically, RepositoryDescriptor) We perform two different bytecode transformations: 1. We modify the call to 'super()' in GradleRepositoryAdapter. In Gradle 4.10, the suepr constructor takes one argument, but in 4.9, it takes zero arguments. In order to allow GradleRepositoryAdapter to compile normally, we write a 'fake' call to 'super(null)', and replace with a call to 'super()'. 2. We delete the method 'getDescriptor' from GradleRepositoryAdapter. In Gradle 4.9, its return type does not exist, and will cause a NoClassDefFoundError when Gradle attempts to classload it via Class#getDeclaredMethods. However, it's necessary to include 'getDescriptor' so that GradleRepositoryAdapter (we need to override the abstract method in a parent class).
Diffstat (limited to 'build.gradle')
-rw-r--r--build.gradle158
1 files changed, 156 insertions, 2 deletions
diff --git a/build.gradle b/build.gradle
index 635950d..d6cbd04 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,9 +1,30 @@
+buildscript {
+ repositories {
+ mavenCentral()
+ }
+ dependencies {
+ // Needed for bytecode manipulation
+ classpath 'org.ow2.asm:asm-all:5.2'
+ }
+}
+
plugins {
id 'net.minecrell.licenser' version '0.3'
id 'org.ajoberstar.grgit' version '2.3.0'
//id 'com.github.johnrengelman.shadow' version '2.0.4'
}
+import java.nio.file.Paths
+import org.objectweb.asm.ClassReader
+import org.objectweb.asm.ClassWriter
+import org.objectweb.asm.Opcodes
+import org.objectweb.asm.tree.ClassNode
+import org.objectweb.asm.tree.MethodInsnNode
+import org.objectweb.asm.tree.VarInsnNode
+import org.objectweb.asm.tree.InsnNode
+
+import java.util.jar.JarFile
+
apply plugin: 'java'
apply plugin: 'maven-publish'
@@ -15,7 +36,7 @@ def gitVersion() {
def hash = desc.remove(desc.size() - 1)
def offset = desc.remove(desc.size() - 1)
def tag = desc.join('-')
- def branch = grgit.branch.current().name
+ def branch = grgit.branch.current().name
return "${tag}.${offset}" //${t -> if (branch != 'master') t << '-' + branch}"
}
@@ -52,10 +73,143 @@ dependencies {
compile sourceSets.gradlecomp.output
}
+
+
+def gradleRepositoryAdapterPath = Paths.get("com", "amadornes", "artifactural", "gradle", "GradleRepositoryAdapter.class")
+def classesDirs = sourceSets.gradlecomp.output.classesDirs.getFiles().first().toPath()
+
+
+def _constructorName = "<init>"
+def _constructorDesc = "(Lcom/amadornes/artifactural/api/repository/Repository;Lorg/gradle/api/internal/artifacts/repositories/DefaultMavenLocalArtifactRepository;)V"
+def _modifiedSuperDesc = "()V"
+
+// This task patches 'GradleRepositoryAdapter' to make it compatibile with both Gradle 4.9 and 4.10
+// In Gradle 4.9, the constructor of AbstractArtifactRepository changes has zero arguments
+// (instead of the one-argument constructor in 4.10, which we compile against)
+// It's not possible to write a normal Java class that calls a constructor which doesn't exist at compile time.
+// Therefore, we need to patch the bytecode of the class to properly call the non-arg super() constrcutor
+class PatchGradleRepositoryAdapter extends DefaultTask {
+
+ @Input String constructorName
+ @Input String constructorDesc
+ @Input String modifiedSuperDesc
+ @Input File gradleRepositoryAdapter
+ @Input File classesDir
+ @OutputDirectory java.nio.file.Path outputDir
+
+
+ @TaskAction
+ void patchClass() {
+ def originalGradleRepositoryAdapter = Paths.get(classesDir.toString(), gradleRepositoryAdapter.toString())
+
+ ClassNode node = new ClassNode()
+ ClassReader reader = new ClassReader(new FileInputStream(originalGradleRepositoryAdapter.toFile()))
+ reader.accept(node, 0)
+
+
+ def constructor = node.methods.find {
+ it.name == constructorName && it.desc == constructorDesc
+ }
+
+ def getDescriptor = node.methods.find {
+ it.name == "getDescriptor"
+ }
+
+ if (constructor == null) {
+ throw new RuntimeException("Failed to find target constructor!")
+ }
+
+ if (getDescriptor == null) {
+ throw new RuntimeException("Failed to find getDescriptor()")
+ }
+
+ // Strip out this method - it only exists
+ // so that GradleRepositoryAdapter will compile
+ node.methods.remove(getDescriptor)
+
+ def superInvoc = constructor.instructions.find {
+ it instanceof MethodInsnNode && it.owner == "org/gradle/api/internal/artifacts/repositories/AbstractArtifactRepository"
+ } as MethodInsnNode
+
+ if (superInvoc == null) {
+ throw new RuntimeException("Failed to find target super() invocation!")
+ }
+
+ superInvoc.desc = modifiedSuperDesc
+
+ def aconstNull = superInvoc.previous
+ if (!(aconstNull instanceof InsnNode) || aconstNull.type != 0) {
+ throw new RuntimeException("Unexpected instruction: " + aconstNull)
+ }
+
+ constructor.instructions.remove(aconstNull)
+
+ // Push first parameter of constructor (the ObjectFactory) onto the stack.
+ // This has the effect of calling super(objectFactory)
+ //constructor.instructions.insertBefore(superInvoc, new VarInsnNode(Opcodes.ALOAD, 1))
+
+ ClassWriter writer = new ClassWriter(0)
+ node.accept(writer)
+
+ def outputFile = outputDir.resolve(gradleRepositoryAdapter.toPath()).toFile()
+
+ outputFile.parentFile.mkdirs()
+ FileOutputStream fs = new FileOutputStream(outputFile)
+ fs.write(writer.toByteArray())
+ }
+}
+
+
+
+task patchConstructor(type: PatchGradleRepositoryAdapter) {
+ constructorName = _constructorName
+ constructorDesc = _constructorDesc
+ modifiedSuperDesc = _modifiedSuperDesc
+ classesDir = classesDirs.toFile()
+ outputDir = Paths.get(buildDir.toString(), "modifiedclasses")
+ gradleRepositoryAdapter = gradleRepositoryAdapterPath.toFile()
+}
+
jar {
from sourceSets.api.output
from sourceSets.shared.output
- from sourceSets.gradlecomp.output
+ from(sourceSets.gradlecomp.output) {
+ exclude gradleRepositoryAdapterPath.toString()
+ }
+
+ from patchConstructor.outputs
+}
+
+jar.doLast {
+ def jarPath = it.outputs.files.getFiles().first()
+ def jarFile = new JarFile(jarPath)
+ def entry = jarFile.getEntry(gradleRepositoryAdapterPath.toString())
+ def stream = jarFile.getInputStream(entry)
+
+ ClassReader reader = new ClassReader(stream)
+ ClassNode node = new ClassNode()
+ reader.accept(node, 0)
+
+ def constructor = node.methods.find {
+ it.name == _constructorName && it.desc == _constructorDesc
+ }
+
+ def superInvoc = constructor.instructions.find {
+ it instanceof MethodInsnNode && it.owner == "org/gradle/api/internal/artifacts/repositories/AbstractArtifactRepository"
+ } as MethodInsnNode
+
+ if (superInvoc.desc == _modifiedSuperDesc) {
+ println("Successfully modified super() call!")
+ } else {
+ throw new RuntimeException("Failed to modify super() invocation - got desc of " + superInvoc.desc)
+ }
+
+ def getDescriptor = node.methods.find { it.name == "getDescriptor"}
+ if (getDescriptor != null) {
+ throw new RuntimeException("Failed to remove getDescriptor with signature: " + getDescriptor.signature)
+ } else {
+ println("Successfully stripped getDescriptor method!")
+ }
}
task sourcesJar(type: Jar, dependsOn: classes) {