com.autonomousapps.internal.JarAnalyzer.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of dependency-analysis-gradle-plugin Show documentation
Show all versions of dependency-analysis-gradle-plugin Show documentation
Analyzes dependency usage in Android and JVM projects
package com.autonomousapps.internal
import com.autonomousapps.advice.Dependency
import com.autonomousapps.internal.asm.ClassReader
import com.autonomousapps.internal.utils.asClassFiles
import com.autonomousapps.internal.utils.mapToOrderedSet
import com.autonomousapps.internal.utils.mapToSet
import com.autonomousapps.services.InMemoryCache
import org.gradle.api.GradleException
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
import org.gradle.api.artifacts.component.ProjectComponentIdentifier
import org.gradle.api.artifacts.result.DependencyResult
import org.gradle.api.artifacts.result.ResolvedDependencyResult
import org.gradle.api.logging.Logger
import java.util.zip.ZipFile
/**
* Used by [DependencyReportTask][com.autonomousapps.tasks.FindClassesTask].
*/
internal class JarAnalyzer(
private val configuration: Configuration,
private val artifacts: List,
private val logger: Logger,
private val inMemoryCache: InMemoryCache
) {
fun components(): List {
computeTransitivity()
return artifacts.asComponents()
}
private fun computeTransitivity() {
val directDependencies = configuration.directDependencies()
// "Artifacts" are everything used to compile the project. If there is a direct artifact with a
// matching identifier, then that artifact is NOT transitive. Otherwise, it IS transitive.
artifacts.forEach { artifact ->
artifact.isTransitive = directDependencies.none { it == artifact.dependency }
}
}
/**
* Maps collection of [Artifact]s to [Component]s, basically by exploding the contents of
* [Artifact.file] into a set of class names ([Component.classes]).
*/
private fun Iterable.asComponents(): List =
map { artifact ->
val analyzedJar = analyzeJar(artifact)
Component(artifact = artifact, analyzedJar = analyzedJar)
}.sorted()
/**
* Analyzes bytecode in order to extract class names and some basic structural information from
* the jar ([Artifact.file]).
*/
private fun analyzeJar(artifact: Artifact): AnalyzedJar {
val zip = ZipFile(artifact.file)
val alreadyAnalyzedJar: AnalyzedJar? = inMemoryCache.analyzedJar(zip.name)
if (alreadyAnalyzedJar != null) {
return alreadyAnalyzedJar
}
inMemoryCache.updateJars(zip.name)
val ktFiles = KtFile.fromZip(zip)
val analyzedClasses = zip.asClassFiles()
.map { classEntry ->
ClassNameAndAnnotationsVisitor(logger).apply {
val reader = zip.getInputStream(classEntry).use { ClassReader(it.readBytes()) }
reader.accept(this, 0)
}
}
.map { it.getAnalyzedClass() }
.filterNot {
// Filter out `java` packages, but not `javax`
it.className.startsWith("java/")
}
.mapToOrderedSet {
// TODO also replace "$"?
it.copy(className = it.className.replace("/", "."))
}
.onEach { inMemoryCache.updateClasses(it.className) }
return AnalyzedJar(analyzedClasses, ktFiles).also {
inMemoryCache.analyzedJars(zip.name, it)
}
}
}
/**
* Traverses the top level of the dependency graph to get all "direct" dependencies.
*/
private fun Configuration.directDependencies(): Set {
// Update all-artifacts list: transitive or not?
// runtime classpath will give me only the direct dependencies
val dependencies: Set =
incoming
.resolutionResult
.root
.dependencies
return traverseDependencies(dependencies)
}
/**
* This was heavily modified from code found in the Gradle 5.6.x documentation. Can't find the link any more.
*/
private fun traverseDependencies(results: Set): Set = results
.filterIsInstance()
.mapToSet { result ->
val componentResult = result.selected
when (val componentIdentifier = componentResult.id) {
is ProjectComponentIdentifier -> Dependency(componentIdentifier)
is ModuleComponentIdentifier -> Dependency(componentIdentifier)
else -> throw GradleException("Unexpected ComponentIdentifier type: ${componentIdentifier.javaClass.simpleName}")
}
}