A new task to build type usage report (ComputeTypeUsageTask).#1639
A new task to build type usage report (ComputeTypeUsageTask).#1639Laimiux wants to merge 2 commits intoautonomousapps:mainfrom
Conversation
api/api.txt
Outdated
| public abstract class AbstractExtension { | ||
| ctor @javax.inject.Inject public AbstractExtension(org.gradle.api.model.ObjectFactory objects, org.gradle.api.invocation.Gradle gradle); | ||
| method public final org.gradle.api.file.RegularFileProperty adviceOutput(); | ||
| method public final org.gradle.api.file.RegularFileProperty typeUsageOutput(); |
There was a problem hiding this comment.
Please provide guidance here - I've added this, but I'm not sure what should actually be included here.
There was a problem hiding this comment.
Whatever the abi generation task requires!
There was a problem hiding this comment.
Is there a Gradle task to generate this?
|
|
||
| import static com.autonomousapps.kit.gradle.dependencies.Dependencies.* | ||
|
|
||
| final class TypeUsageProject extends AbstractProject { |
There was a problem hiding this comment.
Added a functional test for this task (tried to follow project conventions)
|
|
||
| import static com.autonomousapps.kit.gradle.dependencies.Dependencies.* | ||
|
|
||
| final class TypeUsageWithFiltersProject extends AbstractProject { |
There was a problem hiding this comment.
Another functional test for the new task (tried to follow project conventions)
| import static com.autonomousapps.utils.Runner.build | ||
| import static com.google.common.truth.Truth.assertThat | ||
|
|
||
| final class TypeUsageSpec extends AbstractJvmSpec { |
There was a problem hiding this comment.
Following project convention to test computeTypeUsageMain task
| // Excluded specific types | ||
| internal val excludedTypes: SetProperty<String> = objects.setProperty(String::class.java) | ||
| .convention(setOf( | ||
| "kotlin.Unit", |
There was a problem hiding this comment.
Curious if we should provide certain defaults here for exclusion out of the report
| * enabling coupling analysis and complexity metrics. | ||
| */ | ||
| @JsonClass(generateAdapter = false) | ||
| public data class ProjectTypeUsage( |
There was a problem hiding this comment.
Json serialized models for type usage task.
| } | ||
|
|
||
| // Computes type-level usage statistics for complexity analysis. | ||
| val computeTypeUsageTask = tasks.register("computeTypeUsage$taskNameSuffix", ComputeTypeUsageTask::class.java) { t -> |
autonomousapps
left a comment
There was a problem hiding this comment.
Thanks for this! Left some comments.
src/functionalTest/groovy/com/autonomousapps/jvm/projects/TypeUsageMultiModuleProject.groovy
Show resolved
Hide resolved
src/functionalTest/groovy/com/autonomousapps/jvm/projects/TypeUsageProject.groovy
Show resolved
Hide resolved
src/functionalTest/groovy/com/autonomousapps/jvm/projects/TypeUsageWithFiltersProject.groovy
Show resolved
Hide resolved
| then: 'has correct summary' | ||
| def usage = project.actualTypeUsage() | ||
| assertThat(usage.projectPath).isEqualTo(':proj') | ||
| assertThat(usage.summary.totalTypes).isGreaterThan(0) |
There was a problem hiding this comment.
why a fuzzy match here?
Also in general, I think it might be better to just create an instance of a type usage and check equality directly?
There was a problem hiding this comment.
Solid specs! I'm curious why we can't make more straightforward equality checks.
|
|
||
| fun shouldExclude(className: String): Boolean { | ||
| if (excludedTypes.contains(className)) return true | ||
| if (excludedPackages.any { className.startsWith("$it.") }) return true |
There was a problem hiding this comment.
Here we answer one of my questions from earlier about how the packages are treated. I wonder if it might be cleaner if, when accepting the user input, map it and affix a . to what they pass in. Also, we need to handle the case where a user might think they have to pass in a . themselves. I have some functions that do similar stuff:
internal fun String.ensurePrefix(prefix: String = ":"): String {
return if (startsWith(prefix)) this else "$prefix$this"
}That function is in another context. For your use-case, please add a similar function named ensureSuffix that ensures all packages end with exactly one ..
| val allUsedClasses = source.usedNonAnnotationClasses + source.usedAnnotationClasses | ||
|
|
||
| allUsedClasses.forEach { className -> | ||
| if (filter.shouldExclude(className)) return@forEach |
There was a problem hiding this comment.
I generally prefer using filter() to this pattern.
| // Determine coordinates: check project classes first, then external | ||
| val coords = when { | ||
| projectClasses.contains(className) -> project.coordinates.identifier | ||
| else -> classToCoords[className] ?: "UNKNOWN" |
There was a problem hiding this comment.
When would UNKNOWN happen? Would !! or error("shouldn't happen") be more appropriate?
There was a problem hiding this comment.
I've seen cases with R classes. Instead of failure, I suggest exposing this via separate unknownDependencies property.
| private val filter: TypeFilter | ||
| ) { | ||
| fun analyze(): ProjectTypeUsage { | ||
| val usageMap = mutableMapOf<String, MutableMap<String, Int>>() |
There was a problem hiding this comment.
You might be better off using Coordinates instead of String here for the key.
840d8ec to
b3c127e
Compare
| field public static final com.autonomousapps.Flags INSTANCE; | ||
| } | ||
|
|
||
| @org.gradle.work.DisableCachingByDefault(because="Writes to console") public abstract class ListSourceFilesTask extends org.gradle.api.DefaultTask { |
Summary
A new task to build type usage report (#1637).
Introducing a new
ComputeTypeUsageTasktask. It generatestype-usage.jsonreporting which types (classes) your code uses from each dependency. Designed for analyzing module dependency usage and complexity.{ "projectPath": ":app", "summary": { "totalTypes": 245, "totalFiles": 67, "internalTypes": 12, "projectDependencies": 3, "libraryDependencies": 18 }, "internal": { "com.example.MyClass": 5 }, "projectDependencies": { ":core": { "com.example.core.Utils": 2 } }, "libraryDependencies": { "org.apache.commons:commons-collections4": { "org.apache.commons.collections4.bag.HashBag": 3 } } }Usage:
Output location:
Configuration:
dependencyAnalysis { typeUsage { excludePackages("kotlin.jvm.internal") excludeTypes("kotlin.Unit") excludeRegex(".*_Factory$", ".*Companion$") } }Key design decisions:
buildHealthor other critical paths)synthetic-project.json,exploded-jars.json.gz)