milamberspace commented on code in PR #6846:
URL: https://github.com/apache/jmeter/pull/6846#discussion_r4415785885


##########
gradle.md:
##########
@@ -53,7 +53,8 @@ However, it might be useful to perform a "clean" build:
       # Expected checksums are stored in /checksum.properties file
       # Actual checksums are stored in 
/build/checksum/computed.checksum.properties
 
-      # Update expected dependencies after updating a dependency version
+      # Update src/dist/src/dist/expected_release_artifacts.txt after adding 
or removing a dependency,
+      # or when the release outgrows total-size-limit. A version update that 
keeps the same modules needs no change

Review Comment:
   Since only the dist task reads the property, `gw 
:src:dist:verifyReleaseDependencies -PupdateExpectedJars` is enough and much 
faster than the full `check`. (nit)



##########
src/dist/build.gradle.kts:
##########
@@ -179,97 +178,132 @@ val populateLibs by tasks.registering {
     }
 }
 
-val updateExpectedJars by props(default = 
gradle.startParameter.writeDependencyVerifications.isNotEmpty())
+val updateExpectedJars by props()
+
+// -PupdateExpectedJars sets total-size-limit to the current total size plus 
this headroom,
+// so a dependency update can grow the release a little without touching the 
expected file.
+val releaseJarsSizeHeadroom = 5L * 1024 * 1024
 
 val verifyReleaseDependencies by tasks.registering {
-    description = "Verifies if binary release archive contains the expected 
set of external jars"
+    description = "Verifies the set of external modules and the total size of 
external jars in the binary release"
     group = LifecycleBasePlugin.VERIFICATION_GROUP
 
-    dependsOn(configurations.runtimeClasspath)
-    val expectedLibs = file("src/dist/expected_release_jars.csv")
-    inputs.file(expectedLibs)
+    val expectedFile = file("src/dist/expected_release_artifacts.txt")
+    val projectPath = project.path
+    inputs.file(expectedFile)
+    inputs.files(configurations.runtimeClasspath)
     inputs.property("updateExpectedJars", updateExpectedJars)
-    val actualLibs = 
layout.buildDirectory.file("dist/expected_release_jars.csv")
-    outputs.file(actualLibs)
-    val ignoreJarsMismatch = version.toString().endsWith("-SNAPSHOT")
-    if (ignoreJarsMismatch || updateExpectedJars) {
-        // The task does not fail in case of -SNAPSHOT version, so we make the 
task never UP-TO-DATE
-        // in that case. Otherwise, the task never executes on the second 
request, even if the user runs with
-        // -PupdateExpectedJars
+    val actualJars = layout.buildDirectory.file("dist/release_jars.csv")
+    outputs.file(actualJars)
+    if (updateExpectedJars) {
+        // Otherwise the update does nothing when the previous run passed with 
the same inputs
         outputs.upToDateWhen { false }
     }
     doLast {
-        val caseInsensitive: Comparator<String> = 
compareBy(String.CASE_INSENSITIVE_ORDER, { it })
-
-        val deps = 
configurations.runtimeClasspath.get().resolvedConfiguration.resolvedArtifacts
-        val libs = deps.asSequence()
-            .filter {
-                val compId = it.id.componentIdentifier
-                compId !is ProjectComponentIdentifier
-            }
-            .map { it.file.name to it.file.length() }
-            .sortedWith(compareBy(caseInsensitive) { it.first })
-            .associate { it }
-
-        val expected = expectedLibs.readLines().asSequence()
-            .filter { "," in it }
-            .map {
-                val (length, name) = it.split(",", limit = 2)
-                name to length.toLong()
+        val caseInsensitive = String.CASE_INSENSITIVE_ORDER.thenBy { it }
+        val expectedPath = expectedFile.relativeTo(rootDir)
+        val actualFile = actualJars.get().asFile
+        val actualPath = actualFile.relativeTo(rootDir)
+
+        val artifacts = 
configurations.runtimeClasspath.get().resolvedConfiguration.resolvedArtifacts
+            .filter { it.id.componentIdentifier !is ProjectComponentIdentifier 
}
+        val jarsByModule = artifacts
+            .groupBy {
+                // Coordinates without the version, so a version update keeps 
the expected file intact
+                val id = it.id.componentIdentifier as? 
ModuleComponentIdentifier
+                    ?: throw GradleException("${it.file.name} in 
runtimeClasspath comes from ${it.id.componentIdentifier.displayName}, but only 
modules from a repository and JMeter projects are expected")
+                buildString {
+                    append(id.moduleIdentifier)
+                    it.classifier?.let { classifier -> 
append(':').append(classifier) }
+                    if (it.extension != "jar") {
+                        append('@').append(it.extension)
+                    }
+                }
             }
-            .associate { it }
+            .toSortedMap(caseInsensitive)
+        val totalSize = artifacts.sumOf { it.file.length() }
 
-        if (libs == expected) {
-            return@doLast
+        actualFile.writeText(
+            artifacts.sortedWith(compareBy(caseInsensitive) { it.file.name })
+                .joinToString("\n", postfix = "\n") { 
"${it.file.length()},${it.file.name}" }
+        )
+
+        // The expected file lists modules, not jars, so a second jar of a 
listed module would go unnoticed
+        val ambiguous = jarsByModule.filterValues { it.size > 1 }
+        if (ambiguous.isNotEmpty()) {
+            throw GradleException(
+                "Each module in $expectedPath must resolve to a single jar, 
but these resolve to several: " +
+                    ambiguous.entries.joinToString("; ") { (module, files) -> 
"$module (${files.joinToString { it.file.name }})" }
+            )
         }
 
-        val sb = StringBuilder()
-        sb.append("External dependencies differ (you could update 
${expectedLibs.relativeTo(rootDir)} if you run $path -PupdateExpectedJars):")
+        val sizeLimitPrefix = "total-size-limit="
+        val expectedLines = expectedFile.readLines()
+            .map { it.trim() }
+            .filter { it.isNotEmpty() && !it.startsWith("#") }
+        val existingSizeLimit = expectedLines.singleOrNull { 
it.startsWith(sizeLimitPrefix) }

Review Comment:
   `existingSizeLimit` is computed before the update branch but only used after 
it; moving the lookup next to `val sizeLimit = existingSizeLimit ?: throw ...` 
keeps the update path independent of the old file's contents. (nit)



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to