You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by jd...@apache.org on 2026/10/01 08:08:31 UTC
(grails-core) 04/04: Apply the dependency-analysis plugin only to its own tasks and keep it inside the 3G daemon
This is an automated email from the ASF dual-hosted git repository.
jdaugherty pushed a commit to branch 8.0.x
in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit 626c81dca39b713f8270bc101acaf92d11632b34
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Thu Oct 1 04:07:55 2026 -0400
Apply the dependency-analysis plugin only to its own tasks and keep it inside the 3G daemon
The build-health settings plugin applied itself to every project on every
invocation. Measured on 8.0.x, that alone cost a plain build about nine seconds
of configuration and 560 MB of daemon heap (help: 803 MB live without the
plugin, 1364 MB with it applied and nothing executed), and running buildHealth
pushed the 3G daemon into GC thrash at 16 workers.
Three changes, each with its measured basis recorded next to it:
- Each build declares the plugin with 'apply false' and the shared settings
script applies it only when one of the plugin's tasks is requested by full
name, checking the root build's request so ':build-logic-root:buildHealth'
from the root still covers the included build. A plain build's task graph no
longer contains any analysis task.
- The plugin's exploded-jar cache is bounded to 200 jars. Unbounded, the live
set grew from 1.5 GB to 2.8 GB at four workers and the daemon survived only
through 48 full collections; bounded it stops at 2.0 GB with six.
- A build service with maxParallelUsages 4 limits how many analyses run in the
daemon at once. The plugin's workers are class-loader or no-isolation, so each
concurrent analysis holds its exploded classpath in the daemon heap. At 16
workers and 3G this brings a buildHealth run from 30 full collections to 8,
with no thrash.
---
AGENTS.md | 2 +-
build-logic/gradle.properties | 2 ++
build-logic/settings.gradle | 2 +-
gradle.properties | 9 ++++++
gradle/dependency-analysis.settings.gradle | 49 ++++++++++++++++++++++++++++++
grails-forge/gradle.properties | 2 ++
grails-forge/settings.gradle | 2 +-
grails-gradle/gradle.properties | 2 ++
grails-gradle/settings.gradle | 2 +-
settings.gradle | 2 +-
10 files changed, 69 insertions(+), 5 deletions(-)
diff --git a/AGENTS.md b/AGENTS.md
index 503d00f549..b6667e17ae 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -147,7 +147,7 @@ All managed dependency versions live in `dependencies.gradle` (the single source
- **Prefer inheriting from the Spring Boot BOM.** Do not re-pin a coordinate that `spring-boot-dependencies` (4.1.x) already manages unless you are intentionally overriding it to a newer version (e.g. a security fix); note the reason inline.
- **An override of a Spring Boot managed version must use Spring Boot's property name, and must change the version.** `validateBomProperties` (plugin `org.apache.grails.buildsrc.bom-property-validator`, run in its own CI job) checks the POM each Grails BOM publishes against `spring-boot-dependencies` (`parentBoms` in `dependencies.gradle`). It fails when a pin is declared under a different property than the one Boot controls it with - for a module Boot manages through an imported BOM, th [...]
- **Every version key must be used.** The same task (opt out with `-PskipBomPropertyValidation`, not `skipDependencyValidation`) fails when a version property a BOM owns (`bomVersionProperties` in each BOM's build script) is used by no entry of its published POM - a key no dependency references, or one whose dependency keys do not reduce to it. Delete the key or add the dependency that should use it. The few deliberate exceptions to these rules live, each with its reason, in `bomUnusedVe [...]
-- **No two dependencies may provide the same class.** The `buildHealth` task (the `com.autonomousapps.build-health` settings plugin, configured in `gradle/dependency-analysis.settings.gradle` and applied to every build in the repository) fails when two dependencies on a classpath provide the same fully qualified class name that the module's own code references, because compile and runtime behavior then depend on classpath order. The Build Health workflow runs it on every push and pull re [...]
+- **No two dependencies may provide the same class.** The `buildHealth` task (the `com.autonomousapps.build-health` settings plugin, configured in `gradle/dependency-analysis.settings.gradle`, which applies it to a build only when one of its tasks is requested by full name, so a plain build never pays for it) fails when two dependencies on a classpath provide the same fully qualified class name that the module's own code references, because compile and runtime behavior then depend on cla [...]
## Key Modules
diff --git a/build-logic/gradle.properties b/build-logic/gradle.properties
index d58addb41b..39e2ab62ab 100644
--- a/build-logic/gradle.properties
+++ b/build-logic/gradle.properties
@@ -16,3 +16,5 @@
org.gradle.caching=true
org.gradle.daemon=true
org.gradle.jvmargs=-Dfile.encoding=UTF-8 -Xmx1536M
+# Bound the dependency-analysis jar cache; see the root gradle.properties for the measured basis.
+systemProp.dependency.analysis.cache.max=200
diff --git a/build-logic/settings.gradle b/build-logic/settings.gradle
index cbcb9fcb01..1cab9b3567 100644
--- a/build-logic/settings.gradle
+++ b/build-logic/settings.gradle
@@ -28,7 +28,7 @@ pluginManagement {
plugins {
id 'com.gradle.develocity' version '4.3.2'
id 'com.gradle.common-custom-user-data-gradle-plugin' version '2.4.0'
- id 'com.autonomousapps.build-health' version '3.19.2'
+ id 'com.autonomousapps.build-health' version '3.19.2' apply false
}
apply from: file('../gradle/dependency-analysis.settings.gradle')
diff --git a/gradle.properties b/gradle.properties
index 532e530f23..66b7add5ae 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -117,3 +117,12 @@ org.gradle.jvmargs=-Dfile.encoding=UTF-8 -Xmx3G
# own 1500m default. To get a documentation run that will not fit moving without editing a
# build script, pass -PgroovydocMaxHeapSize=<size> or -PguideMaxHeapSize=<size>; either beats
# the build script.
+
+# The dependency-analysis plugin (buildHealth) keeps every jar it explodes in a daemon-wide cache
+# for the rest of the build, unbounded by default. Measured on 8.0.x with the analysis tasks
+# executing at the four workers of a CI runner: unbounded, the live set grew from 1.5 GB to
+# 2.8 GB and the 3G daemon survived only through 48 full collections; bounded to 200 jars it
+# stopped at 2.0 GB with 6, for about 30 s more wall time. The plugin reads this as a JVM system
+# property, hence the systemProp prefix. gradle/dependency-analysis.settings.gradle holds the
+# matching concurrency limit.
+systemProp.dependency.analysis.cache.max=200
diff --git a/gradle/dependency-analysis.settings.gradle b/gradle/dependency-analysis.settings.gradle
index 9e76aaffca..fc0cc95097 100644
--- a/gradle/dependency-analysis.settings.gradle
+++ b/gradle/dependency-analysis.settings.gradle
@@ -16,6 +16,33 @@
* specific language governing permissions and limitations
* under the License.
*/
+import org.gradle.api.services.BuildService
+import org.gradle.api.services.BuildServiceParameters
+
+// The dependency-analysis plugin registers about eighty tasks per project and resolves every source
+// set's compile and runtime classpath, which costs configuration time and daemon heap on every
+// invocation. Only its own tasks need any of that, so each build declares the plugin with
+// `apply false` and this script applies it when one of those tasks is requested, by full name
+// (an abbreviation such as `bH` is not recognised). The root build's request is checked so that
+// `:build-logic-root:buildHealth` run from the root also applies it to the included build.
+Set<String> dependencyAnalysisTasks = [
+ 'buildHealth', 'projectHealth', 'reason',
+ 'computeDuplicateDependencies', 'printDuplicateDependencies',
+ 'computeDominatorTree', 'printDominatorTree',
+] as Set
+Gradle rootGradle = gradle
+while (rootGradle.parent != null) {
+ rootGradle = rootGradle.parent
+}
+boolean dependencyAnalysisRequested = rootGradle.startParameter.taskNames.any { String requested ->
+ dependencyAnalysisTasks.contains(requested.substring(requested.lastIndexOf(':') + 1))
+}
+if (!dependencyAnalysisRequested) {
+ return
+}
+
+apply plugin: 'com.autonomousapps.build-health'
+
dependencyAnalysis {
issues {
all {
@@ -48,3 +75,25 @@ dependencyAnalysis {
}
}
}
+
+// The analysis runs inside the Gradle daemon: ExplodeJarTask and AbiAnalysisTask use class-loader
+// isolated workers and the rest no-isolation workers, so every analysis in flight holds its exploded
+// classpath in the daemon heap at the same time, and Gradle starts as many as it has workers. On a
+// 16-core machine that is enough to push the 3G daemon into GC thrash; measured on 8.0.x, each
+// extra concurrent analysis costs roughly 40 MB. The service below is only a lock: tasks that use it
+// run at most maxParallelUsages at a time, matching the four workers of a CI runner, while the rest
+// of the build keeps its full parallelism.
+abstract class DependencyAnalysisThrottle implements BuildService<BuildServiceParameters.None> {}
+
+Provider<DependencyAnalysisThrottle> dependencyAnalysisThrottle = gradle.sharedServices.registerIfAbsent(
+ 'dependencyAnalysisThrottle', DependencyAnalysisThrottle) { spec ->
+ spec.maxParallelUsages.set(4)
+}
+
+gradle.allprojects { Project project ->
+ project.tasks.configureEach { Task task ->
+ if (task.class.name.startsWith('com.autonomousapps.tasks.')) {
+ task.usesService(dependencyAnalysisThrottle)
+ }
+ }
+}
diff --git a/grails-forge/gradle.properties b/grails-forge/gradle.properties
index 3b948f5ab6..d362ec48e9 100644
--- a/grails-forge/gradle.properties
+++ b/grails-forge/gradle.properties
@@ -57,3 +57,5 @@ grails.codestyle.dir.checkstyle=config/checkstyle
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.jvmargs=-Dfile.encoding=UTF-8 -Xmx5G
+# Bound the dependency-analysis jar cache; see the root gradle.properties for the measured basis.
+systemProp.dependency.analysis.cache.max=200
diff --git a/grails-forge/settings.gradle b/grails-forge/settings.gradle
index b02b2e36ff..d02c42770f 100644
--- a/grails-forge/settings.gradle
+++ b/grails-forge/settings.gradle
@@ -36,7 +36,7 @@ plugins {
id 'com.gradle.develocity' version '4.5.0'
id 'com.gradle.common-custom-user-data-gradle-plugin' version '2.4.0'
id 'org.apache.grails.buildsrc.repo'
- id 'com.autonomousapps.build-health' version '3.19.2'
+ id 'com.autonomousapps.build-health' version '3.19.2' apply false
}
apply from: file('../gradle/dependency-analysis.settings.gradle')
diff --git a/grails-gradle/gradle.properties b/grails-gradle/gradle.properties
index d58addb41b..39e2ab62ab 100644
--- a/grails-gradle/gradle.properties
+++ b/grails-gradle/gradle.properties
@@ -16,3 +16,5 @@
org.gradle.caching=true
org.gradle.daemon=true
org.gradle.jvmargs=-Dfile.encoding=UTF-8 -Xmx1536M
+# Bound the dependency-analysis jar cache; see the root gradle.properties for the measured basis.
+systemProp.dependency.analysis.cache.max=200
diff --git a/grails-gradle/settings.gradle b/grails-gradle/settings.gradle
index b9cdde69fa..e1207ca84e 100644
--- a/grails-gradle/settings.gradle
+++ b/grails-gradle/settings.gradle
@@ -33,7 +33,7 @@ plugins {
id 'com.gradle.develocity' version '4.3.2'
id 'com.gradle.common-custom-user-data-gradle-plugin' version '2.4.0'
id 'org.apache.grails.buildsrc.repo'
- id 'com.autonomousapps.build-health' version '3.19.2'
+ id 'com.autonomousapps.build-health' version '3.19.2' apply false
}
apply from: file('../gradle/dependency-analysis.settings.gradle')
diff --git a/settings.gradle b/settings.gradle
index 56d8fc08d2..135297910f 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -36,7 +36,7 @@ plugins {
id 'com.gradle.develocity' version '4.5.0'
id 'com.gradle.common-custom-user-data-gradle-plugin' version '2.4.0'
id 'org.apache.grails.buildsrc.repo'
- id 'com.autonomousapps.build-health' version '3.19.2'
+ id 'com.autonomousapps.build-health' version '3.19.2' apply false
}
apply from: file('gradle/dependency-analysis.settings.gradle')