autonomousapps / dependency-analysis-gradle-plugin

Gradle plugin for JVM projects written in Java, Kotlin, Groovy, or Scala; and Android projects written in Java or Kotlin. Provides advice for managing dependencies and other applied plugins
Apache License 2.0
1.66k stars 116 forks source link

Incomplete reason explanation on multi capabilities #1170

Closed seregamorph closed 1 month ago

seregamorph commented 2 months ago

If there is more than one dependency per sourceSet to the same module, but with different capabilities (like default one vs testFixtures), the reason --id :module explanation is incomplete. It shows only a single explanation of possible routes to dependency instead of all.

Root cause analysis: problem with ComputeAdviceTask toStringCoordinates

The problem is in ComputeAdviceTask:206. The dependencyUsages object is valid before serialization, but the stored object is first wrapped via toStringCoordinates call and there is a problem:

internal fun Map<Coordinates, Set<Usage>>.toStringCoordinates(buildPath: String): Map<String, Set<Usage>> =
  map { (key, value) ->
    toCoordinatesKey(key, buildPath) to value
  }.toMap()

Each calculated via toCoordinatesKey repeated key is simply lost in favor of the last one which just rewrites the map.

Root cause analysis: problem with ReasonTask getUsageFor

The fix of toStringCoordinates is not enough as there is the second place where the reason explanation blocks are squashed as well. There is a sorting method

private fun getUsageFor(id: String): Set<Usage> {
  return dependencyUsages.entries.find(id::equalsKey)?.value?.toSortedSet(Usage.BY_VARIANT)
    ?: annotationProcessorUsages.entries.find(id::equalsKey)?.value?.toSortedSet(Usage.BY_VARIANT)

which calls toSortedSet and it uses TreeSet under the hood. The problem is that even if there are Usage elements which are non-equal via equals, they are squashed if compareTo()==0 from sorting comparator. So it should be addressed too.

Before and after

The sample difference of reason task explanations before the fix:

...
Source: main
------------
(no usages)
...

vs after

...
Source: main
------------
* Uses 17 classes, 5 of which are shown: com.acme.Class1, com.acme.Class2, com.acme.Class3, com.acme.Class4, com.acme.Class5 (implies implementation).
* Provides 1 service loader: com.acme.MetaService (implies runtimeOnly).

Source: main
------------
(no usages)

Further plan

Once we agree on how to approach the issue I'd like to implement a functional test covering it. Existing functional tests seem to be fine. Also it makes sense to enhance the output reason task explanation in case if Source: x is repeated, e.g. Source: x (via module-dependency-test-fixtures) specifying the according capabilities.