You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by ma...@apache.org on 2026/09/08 18:59:01 UTC

(grails-core) branch test/compiler-config-runtime-classpath-producer created (now 9cde802ef7)

This is an automated email from the ASF dual-hosted git repository.

matrei pushed a change to branch test/compiler-config-runtime-classpath-producer
in repository https://gitbox.apache.org/repos/asf/grails-core.git


      at 9cde802ef7 test: pin that the compiler config generator stays off the runtime classpath

This branch includes the following new commits:

     new 9cde802ef7 test: pin that the compiler config generator stays off the runtime classpath

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(grails-core) 01/01: test: pin that the compiler config generator stays off the runtime classpath

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

matrei pushed a commit to branch test/compiler-config-runtime-classpath-producer
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit 9cde802ef7f9aff0bc4d65fc2872e294f38e9911
Author: Mattias Reichel <ma...@gmail.com>
AuthorDate: Tue Sep 8 20:53:37 2026 +0200

    test: pin that the compiler config generator stays off the runtime classpath
    
    The task-based wiring that 9a529d1996 replaced with a doFirst declared
    runtimeClasspath as an input of the generator task, so any task whose
    output reached the runtime classpath became a transitive dependency of
    compileGroovy. A post-compile step that feeds the runtime classpath, such
    as asset-pipeline output or the exploded plugin directory, then closed a
    cycle. That is the concern raised on #16114.
    
    The generator introduced there takes its script from grails { } state
    and declares no classpath input, so that edge no longer exists. This
    test recreates the scenario: a task that runs after classes and puts its
    output on the runtime classpath via runtimeOnly files(task). It asserts
    the generator does not depend on that task, and that compileGroovy and
    the producer schedule together without a cycle.
---
 .../core/GrailsGroovyCompilerConfigSpec.groovy     | 23 +++++++++++++
 .../build.gradle                                   | 40 ++++++++++++++++++++++
 .../gradle.properties                              |  1 +
 .../settings.gradle                                |  1 +
 4 files changed, 65 insertions(+)

diff --git a/grails-gradle/plugins/src/test/groovy/org/grails/gradle/plugin/core/GrailsGroovyCompilerConfigSpec.groovy b/grails-gradle/plugins/src/test/groovy/org/grails/gradle/plugin/core/GrailsGroovyCompilerConfigSpec.groovy
index 184e11bb35..14a11383f7 100644
--- a/grails-gradle/plugins/src/test/groovy/org/grails/gradle/plugin/core/GrailsGroovyCompilerConfigSpec.groovy
+++ b/grails-gradle/plugins/src/test/groovy/org/grails/gradle/plugin/core/GrailsGroovyCompilerConfigSpec.groovy
@@ -59,6 +59,29 @@ class GrailsGroovyCompilerConfigSpec extends GradleSpecification {
         result.output.contains('GENERATOR_HAS_CONTENT_INPUT=true')
     }
 
+    def "a task that adds to the runtime classpath after compiling does not cycle through the generator"() {
+        given: 'an application whose runtime classpath carries the output of a task that runs after classes'
+        setupTestResourceProject('compiler-config-runtime-classpath-producer')
+
+        when: 'the wiring is inspected'
+        def result = executeTask('inspectRuntimeProducer')
+
+        then: 'the producer is on the runtime classpath, and the generator does not depend on it'
+        result.output.contains('RUNTIME_CLASSPATH_BUILT_BY_PRODUCER=true')
+        result.output.contains('GENERATOR_DEPENDS_ON_RUNTIME_PRODUCER=false')
+        result.output.contains('COMBINED_CONTAINS_GRAILS_IMPORT=true')
+
+        when: 'the compile task and that producer are scheduled together'
+        def graph = executeTask('compileGroovy', ['packageRuntimeExtra', '--dry-run'])
+
+        then: 'the graph builds without a cycle, the generator ahead of the compile and the producer after'
+        def order = graph.output.readLines().findAll { it.startsWith(':') }
+        order.findIndexOf { it.startsWith(':generateCompileGroovyGrailsCompilerConfig') } <
+                order.findIndexOf { it.startsWith(':compileGroovy') }
+        order.findIndexOf { it.startsWith(':compileGroovy') } <
+                order.findIndexOf { it.startsWith(':packageRuntimeExtra') }
+    }
+
     def "a configurationScript assigned from a later callback is folded in, not clobbered"() {
         given: 'a user assigning configurationScript from projectsEvaluated'
         setupTestResourceProject('compiler-config-late-assignment')
diff --git a/grails-gradle/plugins/src/test/resources/test-projects/compiler-config-runtime-classpath-producer/build.gradle b/grails-gradle/plugins/src/test/resources/test-projects/compiler-config-runtime-classpath-producer/build.gradle
new file mode 100644
index 0000000000..9e51e9df1b
--- /dev/null
+++ b/grails-gradle/plugins/src/test/resources/test-projects/compiler-config-runtime-classpath-producer/build.gradle
@@ -0,0 +1,40 @@
+plugins {
+    id 'org.apache.grails.gradle.grails-app'
+}
+
+grails {
+    starImports = ['com.example.custom']
+}
+
+// A task that runs after the classes are compiled and puts its output on the runtime
+// classpath: the shape of asset-pipeline, the exploded plugin directory, or any other
+// post-compile packaging step. Its output reaches compileGroovy only through the
+// runtime classpath, which the compile classpath does not include.
+def packageRuntimeExtra = tasks.register('packageRuntimeExtra') {
+    dependsOn 'classes'
+    def out = layout.buildDirectory.dir('runtime-extra')
+    outputs.dir(out)
+    doLast {
+        def dir = out.get().asFile
+        dir.mkdirs()
+        new File(dir, 'extra.properties').text = 'generated=true'
+    }
+}
+
+dependencies {
+    runtimeOnly files(packageRuntimeExtra)
+}
+
+tasks.register('inspectRuntimeProducer') {
+    dependsOn 'generateCompileGroovyGrailsCompilerConfig'
+    doLast {
+        def gen = tasks.named('generateCompileGroovyGrailsCompilerConfig').get()
+        def deps = gen.taskDependencies.getDependencies(gen)*.path
+        println "GENERATOR_DEPENDENCIES=${deps}"
+        println "GENERATOR_DEPENDS_ON_RUNTIME_PRODUCER=${deps.any { it == ':packageRuntimeExtra' }}"
+        def runtimeProducers = configurations.runtimeClasspath.buildDependencies.getDependencies(null)*.path
+        println "RUNTIME_CLASSPATH_BUILT_BY_PRODUCER=${runtimeProducers.any { it == ':packageRuntimeExtra' }}"
+        def combined = file('build/grailsGroovyCompilerConfig-compileGroovy.groovy')
+        println "COMBINED_CONTAINS_GRAILS_IMPORT=${combined.exists() && combined.text.contains(/star 'com.example.custom'/)}"
+    }
+}
diff --git a/grails-gradle/plugins/src/test/resources/test-projects/compiler-config-runtime-classpath-producer/gradle.properties b/grails-gradle/plugins/src/test/resources/test-projects/compiler-config-runtime-classpath-producer/gradle.properties
new file mode 100644
index 0000000000..35c332fb87
--- /dev/null
+++ b/grails-gradle/plugins/src/test/resources/test-projects/compiler-config-runtime-classpath-producer/gradle.properties
@@ -0,0 +1 @@
+grailsVersion=__PROJECT_VERSION__
diff --git a/grails-gradle/plugins/src/test/resources/test-projects/compiler-config-runtime-classpath-producer/settings.gradle b/grails-gradle/plugins/src/test/resources/test-projects/compiler-config-runtime-classpath-producer/settings.gradle
new file mode 100644
index 0000000000..ce227a48f9
--- /dev/null
+++ b/grails-gradle/plugins/src/test/resources/test-projects/compiler-config-runtime-classpath-producer/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'compiler-config-runtime-classpath-producer'