diff options
author | Christian Williams <christianw@google.com> | 2016-12-21 17:38:44 -0800 |
---|---|---|
committer | Christian Williams <christianw@google.com> | 2016-12-21 17:38:44 -0800 |
commit | 5c292730b8ae2ea89bf60e682e299fbe4a99ef6c (patch) | |
tree | 60abb84e4284cf4371f52d1bd60a8ae9ca94f2f4 /buildSrc | |
parent | b724e4323ccbc4bd96a501b80faf24295e9e7e1e (diff) | |
download | robolectric-shadows-5c292730b8ae2ea89bf60e682e299fbe4a99ef6c.tar.gz |
WIP
Diffstat (limited to 'buildSrc')
-rw-r--r-- | buildSrc/build.gradle | 2 | ||||
-rw-r--r-- | buildSrc/src/main/groovy/CheckApiChangesPlugin.groovy | 218 | ||||
-rw-r--r-- | buildSrc/src/main/groovy/ProvideBuildClasspathTask.groovy | 2 |
3 files changed, 221 insertions, 1 deletions
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 0da01d240..c634e0431 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -8,4 +8,6 @@ repositories { dependencies { compile gradleApi() compile localGroovy() + + compile "org.ow2.asm:asm-tree:5.0.1" } diff --git a/buildSrc/src/main/groovy/CheckApiChangesPlugin.groovy b/buildSrc/src/main/groovy/CheckApiChangesPlugin.groovy new file mode 100644 index 000000000..8a392bb96 --- /dev/null +++ b/buildSrc/src/main/groovy/CheckApiChangesPlugin.groovy @@ -0,0 +1,218 @@ +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.objectweb.asm.ClassReader +import org.objectweb.asm.tree.AnnotationNode +import org.objectweb.asm.tree.ClassNode +import org.objectweb.asm.tree.MethodNode + +import java.util.jar.JarEntry +import java.util.jar.JarInputStream +import java.util.regex.Pattern + +import static java.util.Arrays.asList +import static org.objectweb.asm.Opcodes.ACC_PRIVATE + +class CheckApiChangesPlugin implements Plugin<Project> { + @Override + void apply(Project project) { +// project.extensions.create("checkApiChanges", CheckApiChangesExtension) + + project.configurations { + checkApiChanges + } + +// project.afterEvaluate { +// project.dependencies.checkApiChanges project.checkApiChanges.baseArtifact +// } + + project.task('checkForApiChanges', dependsOn: 'jar') { + doLast { + + def baseUrls = project.configurations.checkApiChanges*.toURI()*.toURL() + println "${project.name}: checkForApiChanges: ${baseUrls}" + Map<String, ClassMethod> prevClassMethods = findClassMethods(baseUrls) + Map<String, ClassMethod> curClassMethods = findClassMethods(asList(new URL("file://${project.jar.archivePath}"))) + Set<String> allMethods = new TreeSet<>(prevClassMethods.keySet()) + allMethods.addAll(curClassMethods.keySet()) + String prevClassName = null + for (String classMethodName : allMethods) { + ClassMethod prevClassMethod = prevClassMethods.get(classMethodName) + ClassMethod curClassMethod = curClassMethods.get(classMethodName) + + def introClass = { classMethod -> + if (classMethod.className != prevClassName) { + prevClassName = classMethod.className + println "\n$prevClassName:" + } + } + + if (prevClassMethod == null) { + // added + if (curClassMethod.visible) { + introClass(curClassMethod) + println "+ $curClassMethod.methodDesc" + } + } else if (curClassMethod == null) { + // removed + if (prevClassMethod.visible && !prevClassMethod.deprecated) { + introClass(prevClassMethod) + println "- $prevClassMethod.methodDesc (not previously @Deprecated)" } + + } else { +// println "changed: $classMethodName" + } + } + + println "it = ${}" +// println prevClassMethods + } + } + } + + private Map<String, ClassMethod> findClassMethods(List<URL> baseUrls) { + Map<String, ClassMethod> classMethods = new HashMap<>() + for (URL url : baseUrls) { + if (url.protocol == 'file') { + def file = new File(url.path) + println "file = ${file}" + def stream = new FileInputStream(file) + def jarStream = new JarInputStream(stream) + while (true) { + JarEntry entry = jarStream.nextJarEntry + if (entry == null) break + + if (!entry.directory && entry.name.endsWith(".class")) { + def reader = new ClassReader(jarStream) + def node = new ClassNode() + reader.accept(node, ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES) +// println "node = $node.name" + for (MethodNode method : node.methods) { + def classMethod = new ClassMethod(node, method) + classMethods.put(classMethod.desc, classMethod) + if (method.access != ACC_PRIVATE) { +// println "method.name = $method.name ${method.signature}" + for (AnnotationNode annotationNode : method.visibleAnnotations) { + println "annotationNode = $annotationNode.desc" + } + } + } + } + } + stream.close() + } + } + classMethods + } + + static class ClassMethod { + ClassNode classNode + MethodNode methodNode + + ClassMethod(ClassNode classNode, MethodNode methodNode) { + this.classNode = classNode + this.methodNode = methodNode + } + + boolean equals(o) { + if (this.is(o)) return true + if (getClass() != o.class) return false + + ClassMethod that = (ClassMethod) o + + if (classNode.name != that.classNode.name) return false + if (methodNode.name != that.methodNode.name) return false + if (methodNode.signature != that.methodNode.signature) return false + + return true + } + + int hashCode() { + int result + result = (classNode.name != null ? classNode.name.hashCode() : 0) + result = 31 * result + (methodNode.name != null ? methodNode.name.hashCode() : 0) + result = 31 * result + (methodNode.signature != null ? methodNode.signature.hashCode() : 0) + return result + } + + public String getDesc() { + return "$className#$methodDesc" + } + + private String getMethodDesc() { + def args = new StringBuilder() + def returnType = new StringBuilder() + def buf = args + + int arrayDepth = 0 + def write = { typeName -> + if (buf.size() > 0) buf.append(", ") + buf.append(typeName) + for (; arrayDepth > 0; arrayDepth--) { + buf.append("[]") + } + } + + def chars = methodNode.desc.toCharArray() + def i = 0 + + def readObj = { + if (buf.size() > 0) buf.append(", ") + for (; i < chars.length; i++) { + char c = chars[i] + if (c == ';' as char) break + buf.append((c == '/' as char) ? '.' : c) + } + } + + for (; i < chars.length;) { + def c = chars[i++] + switch (c) { + case '(': break; + case ')': buf = returnType; break; + case '[': arrayDepth++; break; + case 'Z': write('boolean'); break; + case 'B': write('byte'); break; + case 'S': write('short'); break; + case 'I': write('int'); break; + case 'J': write('long'); break; + case 'F': write('float'); break; + case 'D': write('double'); break; + case 'C': write('char'); break; + case 'L': readObj(); break; + case 'V': write('void'); break; + } + } + "${returnType.toString()} $methodNode.name(${args.toString()})" + } + + @Override + public String toString() { + return className + "#$methodNode.desc"; + } + + private String getSignature() { + methodNode.signature == null ? "()V" : methodNode.signature + } + + private String getClassName() { + classNode.name.replace('/', '.') + } + + boolean isDeprecated() { + for (AnnotationNode annotationNode : methodNode.visibleAnnotations) { + if (annotationNode.desc == "Ljava/lang/Deprecated;") { + return true + } + } + false + } + + boolean isVisible() { + classNode.access != ACC_PRIVATE && !(classNode.name =~ /\$[0-9]/) && !(methodNode.name =~ /^access\$/) + } + } +} + +class CheckApiChangesExtension { + String baseArtifact +}
\ No newline at end of file diff --git a/buildSrc/src/main/groovy/ProvideBuildClasspathTask.groovy b/buildSrc/src/main/groovy/ProvideBuildClasspathTask.groovy index c329f0d64..81e21d4b5 100644 --- a/buildSrc/src/main/groovy/ProvideBuildClasspathTask.groovy +++ b/buildSrc/src/main/groovy/ProvideBuildClasspathTask.groovy @@ -12,7 +12,7 @@ class ProvideBuildClasspathTask extends DefaultTask { project.rootProject.allprojects.each { Project otherProject -> def match = otherProject.name =~ /robolectric-shadows\/(.*)/ if (match.matches()) { - def artifactName = "${otherProject.group}:${otherProject.mavenArtifactName()}:${otherProject.version}" + def artifactName = "${otherProject.group}:${otherProject.mavenArtifactName}:${otherProject.version}" File classesDir = otherProject.sourceSets['main'].output.classesDir File resourcesDir = otherProject.sourceSets['main'].output.resourcesDir paths << "${artifactName.replaceAll(/:/, '\\\\:')}: ${classesDir}:${resourcesDir}" |