273 lines
12 KiB
Groovy
273 lines
12 KiB
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.http.HttpTransporterFactory
|
|
import org.eclipse.aether.version.Version
|
|
import org.gradle.api.Plugin
|
|
import org.gradle.api.Project
|
|
import org.objectweb.asm.ClassReader
|
|
import org.objectweb.asm.tree.ClassNode
|
|
|
|
import java.util.concurrent.atomic.AtomicReference
|
|
import java.util.jar.JarEntry
|
|
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)
|
|
def scanVersions = project.task('scanVersions') {
|
|
description = "Queries for all versions of configured modules and finds key classes"
|
|
}
|
|
|
|
def hasRelevantTask = project.gradle.startParameter.taskNames.contains('scanVersions')
|
|
hasRelevantTask |= project.gradle.startParameter.taskNames.contains('scanVersionsReport')
|
|
hasRelevantTask |= project.gradle.startParameter.taskNames.contains('verifyVersionScan')
|
|
if (!hasRelevantTask) {
|
|
return
|
|
}
|
|
// println "Adding scan tasks for $project"
|
|
|
|
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: ${allInclude.size()}->$inCommonPresent->$exclusivePresent - ${keyPresent.get()}"
|
|
println "+++++++++++++++++++++"
|
|
println "keyMissing: ${allExclude.size()}->$inCommonMissing->$exclusiveMissing - ${keyMissing.get()}"
|
|
} else {
|
|
println "keyPresent: $inCommonPresent->$exclusivePresent"
|
|
println "keyMissing: $inCommonMissing->$exclusiveMissing"
|
|
}
|
|
}
|
|
}
|
|
if (project.gradle.startParameter.taskNames.contains('scanVersions')) {
|
|
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", "(,)")
|
|
|
|
String legacyGroup = project.versionScan.legacyGroup == null ? group : project.versionScan.legacyGroup
|
|
String legacyModule = project.versionScan.legacyModule == null ? module : project.versionScan.legacyModule
|
|
Artifact allLegacyVersions = new DefaultArtifact(legacyGroup, legacyModule, "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).collect {
|
|
new DefaultArtifact(group, module, "jar", it.toString())
|
|
})
|
|
def excludeVersionSet = Sets.newHashSet(filter(allResult.versions).collect {
|
|
new DefaultArtifact(group, module, "jar", it.toString())
|
|
})
|
|
excludeVersionSet.removeAll(includeVersionSet)
|
|
|
|
if (allVersions != allLegacyVersions) {
|
|
// println "Adding legacy versions $allLegacyVersions"
|
|
rangeRequest.setArtifact(allLegacyVersions)
|
|
VersionRangeResult allLegacyResult = system.resolveVersionRange(session, rangeRequest)
|
|
def legacyVersions = filter(allLegacyResult.versions)
|
|
// println "Found ${legacyVersions.size()} legacy versions for $legacyGroup:$legacyModule"
|
|
excludeVersionSet.addAll(legacyVersions.collect {
|
|
new DefaultArtifact(legacyGroup, legacyModule, "jar", it.toString())
|
|
})
|
|
}
|
|
|
|
if (excludeVersionSet.empty) {
|
|
println "Found ${includeVersionSet.size()} versions, but none to exclude. Skipping..."
|
|
scanVersionsReport.enabled = false
|
|
return
|
|
}
|
|
|
|
Map<String, String> verifyPresent = project.versionScan.verifyPresent
|
|
List<String> verifyMissing = project.versionScan.verifyMissing
|
|
|
|
if (!verifyPresent.isEmpty() || !verifyMissing.isEmpty()) {
|
|
def verifyVersionScan = project.task('verifyVersionScan') {
|
|
description = "Validates that the configured classes and methods are only present where expected."
|
|
doLast {
|
|
// This may already be done by the report task, but repeating for good measure.
|
|
keyPresent.get().removeAll(allExclude)
|
|
keyMissing.get().removeAll(allInclude)
|
|
|
|
assert keyPresent.get() != [] || keyMissing.get() != []
|
|
|
|
def errors = []
|
|
for (String className : verifyPresent.keySet()) {
|
|
String identifier = project.versionScan.scanMethods ? "$className|${verifyPresent.get(className)}" : className
|
|
if (!keyPresent.get().contains(identifier)) {
|
|
errors << "not a 'keyPresent' identifier: $identifier"
|
|
}
|
|
}
|
|
for (String className : verifyMissing) {
|
|
if (!keyMissing.get().contains(className)) {
|
|
errors << "not a 'keyMissing' identifier: $className"
|
|
}
|
|
}
|
|
errors.each {
|
|
System.err.println "Error for $group:$module - $it"
|
|
}
|
|
if (!errors.isEmpty()) {
|
|
throw new AssertionError("Version scan verification failed.\n" +
|
|
"Errors listed above are likely the result of a new module " +
|
|
"being published to Maven, not a code change in this repo.\n" +
|
|
"This does mean a fix should be made though to 'dd-trace-supported-framework.yaml'.")
|
|
}
|
|
}
|
|
}
|
|
|
|
if (project.gradle.startParameter.taskNames.contains('scanVersions')) {
|
|
scanVersions.finalizedBy(verifyVersionScan)
|
|
}
|
|
}
|
|
|
|
// println "Scanning ${includeVersionSet.size()} included and ${excludeVersionSet.size()} excluded versions. Included: ${includeVersionSet.collect { it.version }}}"
|
|
|
|
includeVersionSet.each { version ->
|
|
addScanTask("Include", new DefaultArtifact(version.groupId, version.artifactId, "jar", version.version), keyPresent, allInclude, project)
|
|
}
|
|
|
|
excludeVersionSet.each { version ->
|
|
addScanTask("Exclude", new DefaultArtifact(version.groupId, version.artifactId, "jar", version.version), keyMissing, allExclude, project)
|
|
}
|
|
}
|
|
}
|
|
|
|
def addScanTask(String label, Artifact artifact, AtomicReference<Set<String>> keyIdentifiers, Set<String> allIdentifiers, Project project) {
|
|
def name = "scanVersion$label-$artifact.groupId-$artifact.artifactId-$artifact.version"
|
|
def config = project.configurations.create(name)
|
|
config.dependencies.add(project.dependencies.create("$artifact.groupId:$artifact.artifactId:$artifact.version") {
|
|
transitive = project.versionScan.scanDependencies
|
|
})
|
|
|
|
def task = project.task(name) {
|
|
doLast {
|
|
Set<String> contentSet = Sets.newConcurrentHashSet()
|
|
project.configurations.getByName(name).resolvedConfiguration.files.each { jarFile ->
|
|
def jar = new JarFile(jarFile)
|
|
for (jarEntry in jar.entries()) {
|
|
if (jarEntry.name.endsWith(".class")) {
|
|
def className = jarEntry.name
|
|
className = className.replaceAll("/", ".")
|
|
className = className.replace(".class", "")
|
|
if (project.versionScan.scanMethods) {
|
|
findMethodNames(jar, jarEntry).each {
|
|
contentSet.add("$className|$it".toString())
|
|
}
|
|
} else {
|
|
contentSet.add(className)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
allIdentifiers.addAll(contentSet)
|
|
|
|
if (!keyIdentifiers.compareAndSet(Collections.emptySet(), contentSet)) {
|
|
def intersection = Sets.intersection(keyIdentifiers.get(), contentSet)
|
|
keyIdentifiers.get().retainAll(intersection)
|
|
}
|
|
}
|
|
}
|
|
project.tasks.scanVersions.finalizedBy(task)
|
|
project.tasks.scanVersionsReport.dependsOn(task)
|
|
if (project.tasks.hasProperty("verifyVersionScan")) {
|
|
project.tasks.verifyVersionScan.dependsOn(task)
|
|
}
|
|
}
|
|
|
|
def filter(List<Version> list) {
|
|
list.removeIf {
|
|
def version = it.toString().toLowerCase()
|
|
return version.contains("rc") ||
|
|
version.contains("alpha") ||
|
|
version.contains("beta") ||
|
|
version.contains("-b") ||
|
|
version.contains(".m") ||
|
|
version.contains("-dev")
|
|
}
|
|
return list
|
|
}
|
|
|
|
def findMethodNames(JarFile jar, JarEntry entry) {
|
|
def stream = jar.getInputStream(entry)
|
|
|
|
def classNode = new ClassNode()
|
|
def cr = new ClassReader(stream)
|
|
cr.accept(classNode, 0)
|
|
|
|
return classNode.methods.collect { it.name }
|
|
}
|
|
|
|
RepositorySystem newRepositorySystem() {
|
|
DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator()
|
|
locator.addService(RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.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()
|
|
}
|
|
}
|