com.autonomousapps.internal.models.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
@file:Suppress("UnstableApiUsage")
package com.autonomousapps.internal
import com.autonomousapps.advice.Advice
import com.autonomousapps.advice.Dependency
import com.autonomousapps.advice.HasDependency
import com.autonomousapps.internal.AndroidPublicRes.Line
import com.autonomousapps.internal.advice.ComputedAdvice
import com.autonomousapps.internal.asm.Opcodes
import com.autonomousapps.internal.utils.asString
import com.autonomousapps.internal.utils.efficient
import com.autonomousapps.internal.utils.mapToSet
import com.autonomousapps.internal.utils.resolvedVersion
import org.gradle.api.artifacts.component.ComponentIdentifier
import java.io.File
import java.io.Serializable
import java.lang.annotation.RetentionPolicy
import java.util.regex.Pattern
/**
* A tuple of an identifier (project or external module) and the name of the configuration on which it is declared.
*/
data class DependencyConfiguration(
val identifier: String,
val configurationName: String
) : Serializable
/**
* Primarily used as a pointer to a [file] on disk; a physical artifact.
*/
data class Artifact(
/**
* A tuple of an `identifier` and a resolved version. See [Dependency].
*/
val dependency: Dependency,
/**
* If false, a direct dependency (declared in the `dependencies {}` block). If true, a transitive
* dependency.
*/
var isTransitive: Boolean? = null,
/**
* Physical artifact on disk; a jar file.
*/
var file: File
) {
constructor(
componentIdentifier: ComponentIdentifier,
file: File,
candidates: Set
) : this(
dependency = Dependency(
identifier = componentIdentifier.asString(),
resolvedVersion = componentIdentifier.resolvedVersion(),
configurationName = candidates.find { it.identifier == componentIdentifier.asString() }?.configurationName
),
file = file
)
}
/**
* A dependency that has bundled native libs (.so files).
*/
data class NativeLibDependency(
val dependency: Dependency,
val fileNames: Set
) {
constructor(
componentIdentifier: ComponentIdentifier,
candidates: Set,
fileNames: Set
) : this(
dependency = Dependency(
identifier = componentIdentifier.asString(),
resolvedVersion = componentIdentifier.resolvedVersion(),
configurationName = candidates.find { it.identifier == componentIdentifier.asString() }?.configurationName
),
fileNames = fileNames
)
}
class VariantClass(
/**
* A class (fully-qualified) _used-by_ a given project.
*/
val theClass: String,
/**
* The set of variants (e.g., "main", "debug", "release"...) that supply [theClass].
*/
val variants: Set
) : Comparable {
override fun compareTo(other: VariantClass): Int = theClass.compareTo(other.theClass)
override fun toString(): String = "VariantClass(theClass='$theClass', variants=$variants)"
}
data class VariantDependency(
override val dependency: Dependency,
/**
* The set of variants (e.g., "main", "debug", "release"...) that supply [dependency]. May be
* empty.
*/
val variants: Set = emptySet()
) : HasDependency, Comparable {
override fun compareTo(other: VariantDependency): Int = dependency.compareTo(other.dependency)
}
/**
* A library or project, along with the set of classes declared by, and other information contained
* within, this component.
*/
data class Component(
/**
* A tuple of an `identifier` and a resolved version. See [Dependency].
*/
val dependency: Dependency,
/**
* If false, a direct dependency (declared in the `dependencies {}` block). If true, a transitive dependency.
*/
val isTransitive: Boolean,
/**
* True if this dependency contains only annotation that are only needed at compile-time (`CLASS` and `SOURCE` level
* retention policies). False otherwise.
*/
val isCompileOnlyAnnotations: Boolean = false,
/**
* True if this dependency contains a class that extends [java.security.Provider].
*/
val isSecurityProvider: Boolean = false,
/**
* The classes declared by this library.
*/
val classes: Set,
/**
* A map of each class declared by this library to the set of constants it defines. The latter may
* be empty for any given declared class.
*/
val constantFields: Map>,
/**
* All of the "Kt" files within this component.
*/
val ktFiles: List
) : Comparable {
internal constructor(artifact: Artifact, analyzedJar: AnalyzedJar) : this(
dependency = artifact.dependency,
isTransitive = artifact.isTransitive!!,
isCompileOnlyAnnotations = analyzedJar.isCompileOnlyCandidate,
isSecurityProvider = analyzedJar.isSecurityProvider,
classes = analyzedJar.classNames,
constantFields = analyzedJar.constants,
ktFiles = analyzedJar.ktFiles
)
override fun compareTo(other: Component): Int = dependency.compareTo(other.dependency)
}
/**
* Represents a "mis-used" transitive dependency. The [dependency] represents the unique name
* (see [Dependency.identifier]), and the [usedTransitiveClasses] are the class members of the dependency that are used
* directly (which "shouldn't" be).
*/
data class TransitiveComponent(
/**
* A tuple of an `identifier` and a resolved version. See [Dependency].
*/
val dependency: Dependency,
/**
* These are class members of this dependency that are used directly by the project in question. They have leaked
* onto the classpath (either unintentionally or by design). Unintentional leakage is usually the result of use of
* the `compile` configuration (or Maven scope); cf the `api` configuration, which "leaks" by design.
*/
val usedTransitiveClasses: Set,
/**
* The variants in which this component is used (e.g., "main", "debug", "release", ...). May be
* empty if we are unable to determine this.
*/
val variants: Set = emptySet()
)
data class ComponentWithInlineMembers(
/**
* A tuple of an `identifier` and a resolved version. See [Dependency].
*/
val dependency: Dependency,
/**
* A set of imports that indicates a possible use of an inline member from this component ([dependency]).
*/
val imports: Set
) : Comparable {
override fun compareTo(other: ComponentWithInlineMembers): Int = dependency.compareTo(other.dependency)
}
data class ComponentWithConstantMembers(
/**
* A tuple of an `identifier` and a resolved version. See [Dependency].
*/
val dependency: Dependency,
/**
* A set of imports that indicates a possible use of a constant member from this component ([dependency]).
*/
val imports: Set
) : Comparable {
override fun compareTo(other: ComponentWithConstantMembers): Int = dependency.compareTo(other.dependency)
}
/**
* Represents a dependency publicly exposed as part of a project's ABI. Includes the classes so
* exposed.
*/
data class PublicComponent(
/**
* A tuple of an `identifier` and a resolved version. See [Dependency].
*/
val dependency: Dependency,
/**
* The set of classes publicly exposed.
*/
val classes: Set
) : Comparable {
override fun compareTo(other: PublicComponent): Int = dependency.compareTo(other.dependency)
}
data class Imports(
val sourceType: SourceType,
val imports: Set
)
enum class SourceType {
JAVA, KOTLIN
}
data class Res(
/**
* A tuple of an `identifier` and a resolved version. See [Dependency].
*/
val dependency: Dependency,
/**
* An import that indicates a possible use of an Android resource from this component ([dependency]).
*/
val import: String
) {
constructor(componentIdentifier: ComponentIdentifier, import: String) : this(
dependency = Dependency(
identifier = componentIdentifier.asString(),
resolvedVersion = componentIdentifier.resolvedVersion()
),
import = import
)
}
data class AndroidPublicRes(
/**
* A tuple of an `identifier` and a resolved version. See [Dependency].
*/
val dependency: Dependency,
/**
* A list of "lines" ([Line]) that are in a `public.txt` file.
*/
val lines: List
) : Comparable {
constructor(componentIdentifier: ComponentIdentifier, lines: List) : this(
dependency = Dependency(
identifier = componentIdentifier.asString(),
resolvedVersion = componentIdentifier.resolvedVersion()
),
lines = lines
)
data class Line(val type: String, val value: String)
override fun compareTo(other: AndroidPublicRes): Int = dependency.compareTo(other.dependency)
}
/**
* Metadata from an Android manifest.
*/
data class Manifest(
/**
* The package name per ``.
*/
val packageName: String,
/**
* True if the manifest contains Android components (Activity, Service, BroadcastReceiver, ContentProvider).
*/
val hasComponents: Boolean,
/**
* A tuple of an `identifier` and a resolved version. See [Dependency].
*/
val dependency: Dependency
) : Comparable {
constructor(packageName: String, hasComponents: Boolean, componentIdentifier: ComponentIdentifier) : this(
packageName = packageName,
hasComponents = hasComponents,
dependency = Dependency(
identifier = componentIdentifier.asString(),
resolvedVersion = componentIdentifier.resolvedVersion()
)
)
override fun compareTo(other: Manifest): Int {
return dependency.compareTo(other.dependency)
}
}
data class AnalyzedClass(
val className: String,
val superClassName: String?,
val retentionPolicy: RetentionPolicy?,
/**
* Ignoring constructors and static initializers. Suc a class will not prejudice the compileOnly algorithm against
* declaring the containing jar "annotations-only". See for example `org.jetbrains.annotations.ApiStatus`. This
* outer class only exists as a sort of "namespace" for the annotations it contains.
*/
val hasNoMembers: Boolean,
val access: Access,
val methods: Set,
val innerClasses: Set,
val constantFields: Set
) : Comparable {
constructor(
className: String,
superClassName: String?,
retentionPolicy: String?,
isAnnotation: Boolean,
hasNoMembers: Boolean,
access: Access,
methods: Set,
innerClasses: Set,
constantClasses: Set
) : this(
className = className,
superClassName = superClassName,
retentionPolicy = fromString(retentionPolicy, isAnnotation),
hasNoMembers = hasNoMembers,
access = access,
methods = methods,
innerClasses = innerClasses,
constantFields = constantClasses
)
companion object {
fun fromString(name: String?, isAnnotation: Boolean): RetentionPolicy? = when {
RetentionPolicy.CLASS.name == name -> RetentionPolicy.CLASS
RetentionPolicy.SOURCE.name == name -> RetentionPolicy.SOURCE
RetentionPolicy.RUNTIME.name == name -> RetentionPolicy.RUNTIME
// Default if RetentionPolicy is not specified.
isAnnotation -> RetentionPolicy.CLASS
else -> null
}
}
override fun compareTo(other: AnalyzedClass): Int = className.compareTo(other.className)
}
enum class Access {
PUBLIC, PROTECTED, PRIVATE, PACKAGE_PRIVATE;
companion object {
fun fromInt(access: Int): Access {
return when {
isPublic(access) -> PUBLIC
isProtected(access) -> PROTECTED
isPrivate(access) -> PRIVATE
isPackagePrivate(access) -> PACKAGE_PRIVATE
else -> throw IllegalArgumentException("Access <$access> is an unknown value")
}
}
private fun isPackagePrivate(access: Int): Boolean =
!isPublic(access) && !isPrivate(access) && !isProtected(access)
private fun isPublic(access: Int): Boolean = access and Opcodes.ACC_PUBLIC != 0
private fun isPrivate(access: Int): Boolean = access and Opcodes.ACC_PRIVATE != 0
private fun isProtected(access: Int): Boolean = access and Opcodes.ACC_PROTECTED != 0
}
}
data class Method internal constructor(val types: Set) {
constructor(descriptor: String) : this(findTypes(descriptor))
companion object {
private val DESCRIPTOR = Pattern.compile("L(.+?);")
private fun findTypes(descriptor: String): Set {
val types = sortedSetOf()
val m = DESCRIPTOR.matcher(descriptor)
while (m.find()) {
types.add(m.group(1))
}
return types.efficient()
}
}
}
data class AnnotationProcessor(
override val dependency: Dependency,
val processor: String,
val supportedAnnotationTypes: Set
) : Comparable, HasDependency {
constructor(
processor: String,
supportedAnnotationTypes: Set,
componentIdentifier: ComponentIdentifier,
candidates: Set
) : this(
processor = processor, supportedAnnotationTypes = supportedAnnotationTypes,
dependency = Dependency(
identifier = componentIdentifier.asString(),
resolvedVersion = componentIdentifier.resolvedVersion(),
configurationName = candidates.find { it.identifier == componentIdentifier.asString() }?.configurationName
)
)
override fun compareTo(other: AnnotationProcessor): Int {
return dependency.compareTo(other.dependency)
}
}
internal data class ServiceLoader(
override val dependency: Dependency,
val providerFile: String,
val providerClasses: Set
) : HasDependency, Comparable {
constructor(
providerFile: String,
providerClasses: Set,
componentIdentifier: ComponentIdentifier,
candidates: Set
) : this(
providerFile = providerFile,
providerClasses = providerClasses,
dependency = Dependency(
identifier = componentIdentifier.asString(),
resolvedVersion = componentIdentifier.resolvedVersion(),
configurationName = candidates.find { it.identifier == componentIdentifier.asString() }?.configurationName
)
)
override fun compareTo(other: ServiceLoader): Int {
return dependency.compareTo(other.dependency)
}
}
internal data class ConsoleReport(
val addToApiAdvice: Set,
val addToImplAdvice: Set,
val removeAdvice: Set,
val changeToApiAdvice: Set,
val changeToImplAdvice: Set,
val compileOnlyDependencies: Set,
val unusedProcsAdvice: Set
) {
fun isEmpty() = addToApiAdvice.isEmpty() &&
addToImplAdvice.isEmpty() &&
removeAdvice.isEmpty() &&
changeToApiAdvice.isEmpty() &&
changeToImplAdvice.isEmpty() &&
compileOnlyDependencies.isEmpty() &&
unusedProcsAdvice.isEmpty()
companion object {
fun from(computedAdvice: ComputedAdvice): ConsoleReport {
var addToApiAdvice = emptySet()
var addToImplAdvice = emptySet()
var removeAdvice = emptySet()
var changeToApiAdvice = emptySet()
var changeToImplAdvice = emptySet()
var compileOnlyDependencies = emptySet()
var unusedProcsAdvice = emptySet()
if (!computedAdvice.filterRemove && computedAdvice.removeAdvice.isNotEmpty()) {
removeAdvice = computedAdvice.removeAdvice
}
val addAdvices = computedAdvice.addToApiAdvice + computedAdvice.addToImplAdvice
if (!computedAdvice.filterAdd && addAdvices.isNotEmpty()) {
addToApiAdvice = computedAdvice.addToApiAdvice
addToImplAdvice = computedAdvice.addToImplAdvice
}
val changeAdvices = computedAdvice.changeToApiAdvice + computedAdvice.changeToImplAdvice
if (!computedAdvice.filterChange && changeAdvices.isNotEmpty()) {
changeToApiAdvice = computedAdvice.changeToApiAdvice
changeToImplAdvice = computedAdvice.changeToImplAdvice
}
if (!computedAdvice.filterCompileOnly && computedAdvice.compileOnlyAdvice.isNotEmpty()) {
compileOnlyDependencies = computedAdvice.compileOnlyAdvice
}
if (!computedAdvice.filterUnusedProcsAdvice && computedAdvice.unusedProcsAdvice.isNotEmpty()) {
unusedProcsAdvice = computedAdvice.unusedProcsAdvice
}
return ConsoleReport(
addToApiAdvice,
addToImplAdvice,
removeAdvice,
changeToApiAdvice,
changeToImplAdvice,
compileOnlyDependencies,
unusedProcsAdvice
)
}
}
}
internal data class AbiExclusions(
val annotationExclusions: Set = emptySet(),
val classExclusions: Set = emptySet(),
val pathExclusions: Set = emptySet()
) {
@Transient
private val annotationRegexes = annotationExclusions.mapToSet(String::toRegex)
@Transient
private val classRegexes = classExclusions.mapToSet(String::toRegex)
@Transient
private val pathRegexes = pathExclusions.mapToSet(String::toRegex)
fun excludesAnnotation(fqcn: String): Boolean = annotationRegexes.any { it.containsMatchIn(fqcn) }
fun excludesClass(fqcn: String) = classRegexes.any { it.containsMatchIn(fqcn) }
fun excludesPath(path: String) = pathRegexes.any { it.containsMatchIn(path) }
companion object {
val NONE = AbiExclusions()
}
}