Add plugin that scans for classes unique to version range
Does not include method signatures yet.  Still a work in progress.
Execute with `./gradlew scanVersions --parallel`.
Using `--parallel` is strongly recommended.
MODIFY
.gitignore
.gitignore
@@ -9,6 +9,7 @@ target
#########
!**/gradle/wrapper/*
/.gradle
+*/.gradle
**/build/
dd-trace-examples/**/build/

ADD
buildSrc/build.gradle
+apply plugin: 'groovy'
+
+repositories {
+ jcenter()
+}
+
+dependencies {
+ compile gradleApi()
+ compile localGroovy()
+
+ compile group: 'org.eclipse.aether', name: 'aether-impl', version: '1.1.0'
+ compile group: 'org.eclipse.aether', name: 'aether-connector-basic', version: '1.1.0'
+ compile group: 'org.eclipse.aether', name: 'aether-transport-http', version: '1.1.0'
+ compile group: 'org.eclipse.aether', name: 'aether-transport-file', version: '1.1.0'
+ compile group: 'org.apache.maven', name: 'maven-aether-provider', version: '3.3.9'
+ compile group: 'com.google.guava', name: 'guava', version: '23.0'
+}

ADD
buildSrc/src/main/groovy/VersionScanExtension.groovy
+class VersionScanExtension {
+ String group
+ String module
+ String versions
+}

ADD
buildSrc/src/main/groovy/VersionScanPlugin.groovy
+import com.google.common.collect.Sets
+import org.apache.maven.repository.internal.MavenRepositorySystemUtils
+import org.eclipse.aether.DefaultRepositorySystemSession
+import org.eclipse.aether.RepositorySystem
+import org.eclipse.aether.RepositorySystemSession
+import org.eclipse.aether.artifact.Artifact
+import org.eclipse.aether.artifact.DefaultArtifact
+import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory
+import org.eclipse.aether.impl.DefaultServiceLocator
+import org.eclipse.aether.repository.LocalRepository
+import org.eclipse.aether.repository.RemoteRepository
+import org.eclipse.aether.resolution.VersionRangeRequest
+import org.eclipse.aether.resolution.VersionRangeResult
+import org.eclipse.aether.spi.connector.RepositoryConnectorFactory
+import org.eclipse.aether.spi.connector.transport.TransporterFactory
+import org.eclipse.aether.transport.file.FileTransporterFactory
+import org.eclipse.aether.transport.http.HttpTransporterFactory
+import org.eclipse.aether.version.Version
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+
+import java.util.concurrent.atomic.AtomicReference
+import java.util.jar.JarFile
+
+/**
+ * Version syntax: https://maven.apache.org/enforcer/enforcer-rules/versionRanges.html
+ */
+class VersionScanPlugin implements Plugin<Project> {
+
+ void apply(Project project) {
+ RepositorySystem system = newRepositorySystem()
+ RepositorySystemSession session = newRepositorySystemSession(system)
+
+ project.extensions.create("versionScan", VersionScanExtension)
+ project.task('scanVersions') {
+ description = "Queries for all versions of configured modules and finds key classes"
+ }
+
+ if (!project.gradle.startParameter.taskNames.contains('scanVersions')) {
+ return
+ }
+
+ Set<String> allInclude = Sets.newConcurrentHashSet()
+ Set<String> allExclude = Sets.newConcurrentHashSet()
+ AtomicReference<Set<String>> keyPresent = new AtomicReference(Collections.emptySet())
+ AtomicReference<Set<String>> keyMissing = new AtomicReference(Collections.emptySet())
+ def scanVersionsReport = project.task('scanVersionsReport') {
+ description = "Prints the result of the scanVersions task"
+ doLast {
+ def inCommonPresent = keyPresent.get().size()
+ def inCommonMissing = keyMissing.get().size()
+ keyPresent.get().removeAll(allExclude)
+ keyMissing.get().removeAll(allInclude)
+ def exclusivePresent = keyPresent.get().size()
+ def exclusiveMissing = keyMissing.get().size()
+
+ if (project.hasProperty("showClasses")) {
+ println "keyPresent: $inCommonPresent->$exclusivePresent - ${keyPresent.get()}"
+ println "+++++++++++++++++++++"
+ println "keyMissing: $inCommonMissing->$exclusiveMissing - ${keyMissing.get()}"
+ } else {
+ println "keyPresent: $inCommonPresent->$exclusivePresent"
+ println "keyMissing: $inCommonMissing->$exclusiveMissing"
+ }
+ }
+ }
+ project.tasks.scanVersions.finalizedBy(scanVersionsReport)
+
+ project.repositories {
+ mavenCentral()
+ jcenter()
+ }
+
+ project.afterEvaluate {
+ String group = project.versionScan.group
+ String module = project.versionScan.module
+ String versions = project.versionScan.versions
+ Artifact artifact = new DefaultArtifact(group, module, "jar", versions)
+ Artifact allVersions = new DefaultArtifact(group, module, "jar", "(,)")
+
+ VersionRangeRequest rangeRequest = new VersionRangeRequest()
+ rangeRequest.setRepositories(newRepositories(system, session))
+
+ rangeRequest.setArtifact(artifact)
+ VersionRangeResult rangeResult = system.resolveVersionRange(session, rangeRequest)
+ rangeRequest.setArtifact(allVersions)
+ VersionRangeResult allResult = system.resolveVersionRange(session, rangeRequest)
+
+ def includeVersionSet = Sets.newHashSet(filter(rangeResult.versions))
+ def excludeVersionSet = Sets.newHashSet(filter(allResult.versions))
+ excludeVersionSet.removeAll(includeVersionSet)
+
+ if (excludeVersionSet.empty) {
+ println "Found ${includeVersionSet.size()} versions, but none to exclude. Skipping..."
+ scanVersionsReport.enabled = false
+ return
+ }
+
+ println "Scanning ${includeVersionSet.size()} included and ${excludeVersionSet.size()} excluded versions. Included: $includeVersionSet"
+
+ includeVersionSet.each { version ->
+ def name = "scanVersionInclude-$group-$module-$version"
+ def config = project.configurations.create(name)
+ config.dependencies.add(project.dependencies.create("$group:$module:$version"))
+
+ def task = project.task(name) {
+ doLast {
+ project.configurations.getByName(name).resolvedConfiguration.files.each { jarFile ->
+ def jar = new JarFile(jarFile)
+ Set<String> contentSet = Sets.newConcurrentHashSet()
+ for (jarEntry in jar.entries()) {
+ contentSet.add(jarEntry.toString())
+ }
+ allInclude.addAll(contentSet)
+
+ if (!keyPresent.compareAndSet(Collections.emptySet(), contentSet)) {
+ def intersection = Sets.intersection(keyPresent.get(), contentSet)
+ keyPresent.get().retainAll(intersection)
+ }
+ }
+ }
+ }
+ project.tasks.scanVersions.finalizedBy(task)
+ project.tasks.scanVersionsReport.mustRunAfter(task)
+ }
+
+ excludeVersionSet.each { version ->
+ def name = "scanVersionExclude-$group-$module-$version"
+ def config = project.configurations.create(name)
+ config.dependencies.add(project.dependencies.create("$group:$module:$version"))
+
+ def task = project.task(name) {
+ doLast {
+ project.configurations.getByName(name).resolvedConfiguration.files.each { jarFile ->
+ def jar = new JarFile(jarFile)
+ Set<String> contentSet = Sets.newConcurrentHashSet()
+ for (jarEntry in jar.entries()) {
+ contentSet.add(jarEntry.toString())
+ }
+ allExclude.addAll(contentSet)
+
+ if (!keyMissing.compareAndSet(Collections.emptySet(), contentSet)) {
+ def intersection = Sets.intersection(keyMissing.get(), contentSet)
+ keyMissing.get().retainAll(intersection)
+ }
+ }
+ }
+ }
+ project.tasks.scanVersions.finalizedBy(task)
+ project.tasks.scanVersionsReport.mustRunAfter(task)
+ }
+ }
+ }
+
+ def filter(List<Version> list) {
+ list.removeIf {
+ def version = it.toString().toLowerCase()
+ return version.contains("rc") || version.contains("alpha") || version.contains("beta")
+ }
+ return list
+ }
+
+ RepositorySystem newRepositorySystem() {
+ DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator()
+ locator.addService(RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class)
+ locator.addService(TransporterFactory.class, FileTransporterFactory.class)
+ locator.addService(TransporterFactory.class, HttpTransporterFactory.class)
+
+ return locator.getService(RepositorySystem.class)
+ }
+
+ DefaultRepositorySystemSession newRepositorySystemSession(RepositorySystem system) {
+ DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession()
+
+ def tempDir = File.createTempDir()
+ tempDir.deleteOnExit()
+ LocalRepository localRepo = new LocalRepository(tempDir)
+ session.setLocalRepositoryManager(system.newLocalRepositoryManager(session, localRepo))
+
+ return session
+ }
+
+ static List<RemoteRepository> newRepositories(RepositorySystem system, RepositorySystemSession session) {
+ return new ArrayList<RemoteRepository>(Arrays.asList(newCentralRepository()))
+ }
+
+ private static RemoteRepository newCentralRepository() {
+ return new RemoteRepository.Builder("central", "default", "http://central.maven.org/maven2/").build()
+ }
+}

ADD
buildSrc/src/main/resources/META-INF/gradle-plugins/version-scan.properties
+implementation-class=VersionScanPlugin

ADD
dd-java-agent/integrations/apache-httpclient/apache-httpclient.gradle
+apply plugin: 'version-scan'
+
+versionScan {
+ group = "org.apache.httpcomponents"
+ module = "httpclient"
+ versions = "[4.3,)"
+}

ADD
dd-java-agent/integrations/aws-sdk/aws-sdk.gradle
+// This results in download a LOT of modules. commenting out for now...
+
+// apply plugin: 'version-scan'
+//
+//versionScan {
+// group = "com.amazonaws"
+// module = "aws-java-sdk"
+// versions = "[1.11.119,)"
+//}

ADD
dd-java-agent/integrations/cassandra/cassandra.gradle
+apply plugin: 'version-scan'
+
+versionScan {
+ group = "com.datastax.cassandra"
+ module = "cassandra-driver-core"
+ versions = "[3.2,)"
+}

ADD
dd-java-agent/integrations/jms/jms.gradle
+apply plugin: 'version-scan'
+
+versionScan {
+ group = "javax.jms"
+ module = "javax.jms-api"
+ versions = "[2,)"
+}

ADD
dd-java-agent/integrations/mongo/mongo.gradle
+apply plugin: 'version-scan'
+
+versionScan {
+ group = "org.mongodb"
+ module = "mongo-java-driver"
+ versions = "[3.0,)"
+}

ADD
dd-java-agent/integrations/okhttp/okhttp.gradle
+apply plugin: 'version-scan'
+
+versionScan {
+ group = "com.squareup.okhttp3"
+ module = "okhttp"
+ versions = "[3.0,)"
+}

ADD
dd-java-agent/integrations/web-servlet-filter/web-servlet-filter.gradle
+apply plugin: 'version-scan'
+
+versionScan {
+ group = "javax.servlet"
+ module = "javax.servlet-api"
+ versions = "(,)"
+}

MODIFY
settings.gradle
settings.gradle
@@ -10,6 +10,14 @@ include ':dd-trace-examples:rest-spark'
include ':dd-trace-examples'
include ':dd-trace-annotations'
+// integrations:
+include ':dd-java-agent:integrations:apache-httpclient'
+include ':dd-java-agent:integrations:aws-sdk'
+include ':dd-java-agent:integrations:cassandra'
+include ':dd-java-agent:integrations:jms'
+include ':dd-java-agent:integrations:mongo'
+include ':dd-java-agent:integrations:okhttp'
+
def setBuildFile(project) {
project.buildFileName = "${project.name}.gradle"
project.children.each {

Add plugin that scans for classes unique to version range

Does not include method signatures yet.  Still a work in progress.

Execute with `./gradlew scanVersions --parallel`.
Using `--parallel` is strongly recommended.