From 65505be38054e35845864c262b3a16fb8b6c1a0f Mon Sep 17 00:00:00 2001 From: "Saulo S. de Toledo" Date: Mon, 10 Aug 2026 16:44:04 +0200 Subject: [PATCH] fix(gradle): deduplicate resolved agents across multi-project configurations Previously, target agents were resolved and reported repeatedly for every project module. We now track resolved agents globally across all project closures so each agent is resolved only once. --- jal-build-gradle.el | 53 +++++++++++++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/jal-build-gradle.el b/jal-build-gradle.el index 8a49a07..b0a84b2 100644 --- a/jal-build-gradle.el +++ b/jal-build-gradle.el @@ -27,28 +27,47 @@ "Write a Gradle init script that lists resolved jars for AGENTS-LIST. Returns the path to the created temp file. The script emits one line per resolved artifact in the format: - JAL_ARTIFACT\\tGROUP\\tARTIFACT\\tVERSION\\tFILE_PATH" + JAL_ARTIFACT\\tGROUP\\tARTIFACT\\tVERSION\\tFILE_PATH + +Deduplication uses `gradle.ext.jalFoundArtifacts', an ExtraPropertiesExtension +map that is visible across all project closures in an init script. Resolution +runs inside `afterEvaluate' to comply with Gradle 9's exclusive lock +requirements for configuration resolution. Configurations are queried in +runtime-first order so Gradle's own conflict-resolved runtime version always +wins over compile-only variants." (let* ((quoted-agents (mapconcat (lambda (id) (format "\"%s\"" id)) agents-list ", ")) (init-file (make-temp-file "jal-gradle-init" nil ".gradle"))) (with-temp-file init-file (insert (format - "allprojects { + "// gradle.ext is an ExtraPropertiesExtension visible across all project +// closures in an init script. +gradle.ext.jalFoundArtifacts = [:] + +// Resolution must occur inside afterEvaluate (or during task execution) to +// hold the project's exclusive lock, preventing Gradle 9 thread-safety errors. +allprojects { afterEvaluate { proj -> def targets = [%s] as Set - def foundPaths = [] as Set - ['runtimeClasspath', 'compileClasspath', 'annotationProcessor', 'testCompileClasspath'].each { cfgName -> + // runtimeClasspath is listed first so its conflict-resolved version + // takes precedence over compileClasspath or test variants. + ['runtimeClasspath', 'testRuntimeClasspath', 'compileClasspath', + 'annotationProcessor', 'testCompileClasspath'].each { cfgName -> def cfg = proj.configurations.findByName(cfgName) - if (cfg) { + // canBeResolved was added in Gradle 3.3; older versions lack the + // property, so we default to true and let the catch handle failures. + def resolvable = cfg != null && (cfg.hasProperty('canBeResolved') ? cfg.canBeResolved : true) + if (resolvable) { try { cfg.resolvedConfiguration.resolvedArtifacts - .findAll { targets.contains(it.name) } + .findAll { targets.contains(it.name) && !gradle.ext.jalFoundArtifacts.containsKey(it.name) } .each { art -> - if (foundPaths.add(art.file.absolutePath)) { - println \"JAL_ARTIFACT\\t${art.moduleVersion.id.group}\\t${art.name}\\t${art.moduleVersion.id.version}\\t${art.file.absolutePath}\" - } + gradle.ext.jalFoundArtifacts[art.name] = true + println \"JAL_ARTIFACT\\t${art.moduleVersion.id.group}\\t${art.name}\\t${art.moduleVersion.id.version}\\t${art.file.absolutePath}\" } - } catch (Exception ignored) {} + } catch (Exception e) { + System.err.println(\"JAL: resolution failed for ${cfgName} in ${proj.name}: ${e.message}\") + } } } } @@ -58,12 +77,13 @@ resolved artifact in the format: (defun jal--gradle-parse-init-output (output) "Parse OUTPUT from the JAL Gradle init script. -Returns an alist of (artifact-id . (group version absolute-path)) entries." +Returns a list of (artifact-id group version absolute-path) entries. +Deduplication must have been handled beforehand, with each artifact appearing +only once in OUTPUT." (let ((results '())) (dolist (line (split-string output "\n" t)) (when (string-prefix-p "JAL_ARTIFACT\t" line) - (let* ( - (parts (split-string line "\t" t)) + (let* ((parts (split-string line "\t" t)) (group (nth 1 parts)) (artifact (nth 2 parts)) (version (nth 3 parts)) @@ -119,9 +139,10 @@ or nil on failure." (let ((agent-path (if (and abs-path (file-exists-p abs-path)) abs-path - (jal--resolve-agent-path - (file-name-directory abs-path) - group-id artifact-id version)))) + (when abs-path + (jal--resolve-agent-path + (file-name-directory abs-path) + group-id artifact-id version))))) (when agent-path (push (list artifact-id agent-path version) found-agents))))) (when (null found-agents)