ben-manes/gradle-versions-plugin

Report has differences when nothing changes

dalewking opened this issue · 6 comments

Was trying to upgrade some dependencies and wanted to make sure that I was not getting unexpected transitive dependency updates. What i wanted to do was:

  • run dependencyUpdates
  • save the report
  • upgrade a dependency
  • run dependencyUpdates
  • compare the previous report to see what changed

The problem is there were differences in the report even with no changes to dependencies.

It appears for the most of the changes it it that it is somewhat randomly choosing between the old and new version for the project URL

For example, here is part of the JSON report:

   {
    "group": "androidx.compose.ui",
    "name": "ui",
    "version": "1.4.3",
    "projectUrl": "https://developer.android.com/jetpack/androidx/releases/compose-ui#1.6.2",
    "userReason": null,
    "available": {
     "release": null,
     "milestone": "1.6.2",
     "integration": null
    }
   },

compared to this entry with no changes to dependencies:

   {
    "group": "androidx.compose.ui",
    "name": "ui",
    "version": "1.4.3",
    "projectUrl": "https://developer.android.com/jetpack/androidx/releases/compose-ui#1.4.3",
    "userReason": null,
    "available": {
     "release": null,
     "milestone": "1.6.2",
     "integration": null
    }
   },

I also see differences in reason:

   {
    "group": "androidx.test",
    "name": "rules",
    "version": "1.6.0-alpha01",
    "projectUrl": "1.5.0",
    "userReason": null,
    "reason": "org.gradle.internal.resolve.ModuleVersionResolveException: Could not resolve androidx.test:rules:{strictly 1.6.0-alpha01}.\nRequired by:....

vs.

   {
    "group": "androidx.test",
    "name": "rules",
    "version": "1.6.0-alpha01",
    "projectUrl": "1.5.0",
    "userReason": null,
    "reason": "org.gradle.internal.resolve.ModuleVersionResolveException: Could not resolve androidx.test:rules:+.\nRequired by:

Since we defer to Gradle to resolve the dependency, maybe it is non-deterministic due to caching and repository ordering? I usually add --refresh-dependencies so I wonder if that would make it more consistent for you, as it forces it to bypass the cache?

I don't think it is gradle, because it is not a question of versions (at least for the first part). It is getting the correct versions for current and milestone versions, it is the projectUrl

We actually get that from Gradle as well,

private fun resolveProjectUrl(id: ModuleVersionIdentifier): String? {
return try {
val resolutionResult =
project.dependencies
.createArtifactResolutionQuery()
.forComponents(DefaultModuleComponentIdentifier.newId(id))
.withArtifacts(MavenModule::class.java, MavenPomArtifact::class.java)
.execute()
// size is 0 for gradle plugins, 1 for normal dependencies
for (result in resolutionResult.resolvedComponents) {
// size should always be 1
for (artifact in result.getArtifacts(MavenPomArtifact::class.java)) {
if (artifact is ResolvedArtifactResult) {
val file = artifact.file
project.logger.info("Pom file for $id is $file")
var url = getUrlFromPom(file)
if (!url.isNullOrEmpty()) {
project.logger.info("Found url for $id: $url")
return url.trim()
} else {
val parent = getParentFromPom(file)
if (parent != null &&
"${parent.group.orEmpty()}:${parent.name}" != "org.sonatype.oss:oss-parent"
) {
url = getProjectUrl(parent)
if (!url.isNullOrEmpty()) {
return url.trim()
}
}
}
}
}
}
project.logger.info("Did not find url for $id")

Could there be a race condition ambiguity here whether it uses resolvedCoordinate or originalCoordinate

  private fun getStatus(
    coordinates: Map<Coordinate.Key, Coordinate>,
    resolved: Set<ResolvedDependency>,
    unresolved: Set<UnresolvedDependency>,
  ): Set<DependencyStatus> {
    val result = hashSetOf<DependencyStatus>()
    for (dependency in resolved) {
      val resolvedCoordinate = Coordinate.from(dependency.module.id)
      val originalCoordinate = coordinates[resolvedCoordinate.key]
      val coord = originalCoordinate ?: resolvedCoordinate
      val projectUrl = getProjectUrl(dependency.module.id)
      result.add(DependencyStatus(coord, resolvedCoordinate.version, projectUrl))
    }

I believe the only shared mutable state in this plugin is the projectUrls cache, which uses a precursor idiom to computeIfAbsent as that was written in pre-Java 8 Groovy. The rest is thread local state, so assuming that the configuration is not modified at runtime it should be stable as computations passed down between methods. That's not always true as much of Gradle is mutable and allowed to change, e.g. #98 takes advantage of that. So your idea makes a lot of sense, but I don't think we could do much at the plugin level?

All i am asking is to do a little debugging to see where it is happening. For example, putting some printlns to see if which version number is being used to call getProjectUrl and see if that is changing.