From dc3fa6bbe24085ce513d5c3619238cc95a8d2948 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leonard=20Br=C3=BCnings?= Date: Fri, 29 May 2026 20:58:25 +0200 Subject: [PATCH 1/4] Add Groovy 6 (alpha) executor variant with no-Spock fallback Decouple Spock from the executor core behind a ServiceLoader-based SpecSupport SPI. The Spock implementation (gwc.spock.*) moves to a profile-gated source root (src/spock) compiled only into the Groovy 3/4/5 variants via build-helper. The new groovy_6_0_alpha Maven profile ships without spock-core; when no provider is present, Spock specs and the AST view return a clear 'not supported on this Groovy version yet' message while plain Groovy runs normally. - SpecSupport SPI + SpockSpecSupport provider (runSpec/renderAst/spockVersion) - ExecutionInfo no longer references Spock directly (defaults spockVersion=n/a) - pom: gate spock-core + byte-buddy/objenesis/junit-platform-launcher to 3/4/5; add groovy.6.version and groovy_6_0_alpha profile (JDK 17, no Spock) - tests: G6 fallback tests in src/no-spock-test (plain JUnit 5) - CI: add groovy_6_0_alpha (java 17) to build + deploy matrices - frontend: nicer label for pre-release ids (id contains 'alpha' so the existing view.ts logic never selects it as the default) - README + design spec updated --- .github/workflows/build-groovy-executor.yml | 2 + .github/workflows/deploy-groovy-executor.yml | 2 + README.md | 12 + functions/groovy-executor/pom.xml | 208 ++++++++++++++++-- .../src/main/java/gwc/GFunctionExecutor.java | 37 ++-- .../gwc/representations/ExecutionInfo.java | 5 +- .../src/main/java/gwc/spi/SpecSupport.java | 36 +++ .../gwc/GFunctionExecutorFallbackTest.groovy | 81 +++++++ .../java/gwc/spock/AstRenderer.java | 0 .../java/gwc/spock/ScriptCompiler.java | 0 .../java/gwc/spock/ScriptRunner.java | 0 .../java/gwc/spock/SpockSpecSupport.java | 29 +++ .../java/gwc/spock/output/Color.java | 0 .../java/gwc/spock/output/Theme.java | 0 .../java/gwc/spock/output/TreeNode.java | 0 .../java/gwc/spock/output/TreePrinter.java | 0 .../spock/output/TreePrintingListener.java | 0 .../java/gwc/spock/output/package-info.java | 0 .../META-INF/services/gwc.spi.SpecSupport | 1 + functions/pom.xml | 1 + services/frontend/src/ts/groovy-console.ts | 7 +- 21 files changed, 386 insertions(+), 35 deletions(-) create mode 100644 functions/groovy-executor/src/main/java/gwc/spi/SpecSupport.java create mode 100644 functions/groovy-executor/src/no-spock-test/groovy/gwc/GFunctionExecutorFallbackTest.groovy rename functions/groovy-executor/src/{main => spock}/java/gwc/spock/AstRenderer.java (100%) rename functions/groovy-executor/src/{main => spock}/java/gwc/spock/ScriptCompiler.java (100%) rename functions/groovy-executor/src/{main => spock}/java/gwc/spock/ScriptRunner.java (100%) create mode 100644 functions/groovy-executor/src/spock/java/gwc/spock/SpockSpecSupport.java rename functions/groovy-executor/src/{main => spock}/java/gwc/spock/output/Color.java (100%) rename functions/groovy-executor/src/{main => spock}/java/gwc/spock/output/Theme.java (100%) rename functions/groovy-executor/src/{main => spock}/java/gwc/spock/output/TreeNode.java (100%) rename functions/groovy-executor/src/{main => spock}/java/gwc/spock/output/TreePrinter.java (100%) rename functions/groovy-executor/src/{main => spock}/java/gwc/spock/output/TreePrintingListener.java (100%) rename functions/groovy-executor/src/{main => spock}/java/gwc/spock/output/package-info.java (100%) create mode 100644 functions/groovy-executor/src/spock/resources/META-INF/services/gwc.spi.SpecSupport diff --git a/.github/workflows/build-groovy-executor.yml b/.github/workflows/build-groovy-executor.yml index c1e87f48..3ea4d4b6 100644 --- a/.github/workflows/build-groovy-executor.yml +++ b/.github/workflows/build-groovy-executor.yml @@ -27,6 +27,8 @@ jobs: java: 21 - variant: 'groovy_5_0' java: 21 + - variant: 'groovy_6_0_alpha' + java: 17 steps: - uses: actions/checkout@v6 - name: 'Set up JDK' diff --git a/.github/workflows/deploy-groovy-executor.yml b/.github/workflows/deploy-groovy-executor.yml index d45030ec..2e144e8f 100644 --- a/.github/workflows/deploy-groovy-executor.yml +++ b/.github/workflows/deploy-groovy-executor.yml @@ -15,6 +15,8 @@ jobs: java: 21 - variant: 'groovy_5_0' java: 21 + - variant: 'groovy_6_0_alpha' + java: 17 steps: - uses: actions/checkout@v6 - name: 'Set up JDK' diff --git a/README.md b/README.md index 90eef4e6..e5788f7f 100644 --- a/README.md +++ b/README.md @@ -9,12 +9,24 @@ The output will be in `functions/groovy-executor/target/deployment`. There are different profiles, one for each groovy version: +* `groovy_6_0_alpha` (no Spock — see below) * `groovy_5_0` * `groovy_4_0` (default) * `groovy_3_0` Use `../../mvnw package -P groovy_5_0` +#### Groovy 6 (pre-release, no Spock) + +Spock has no release compatible with Groovy 6 yet, so the `groovy_6_0_alpha` +variant ships **without** Spock. Plain Groovy scripts run normally; submitting a +Spock specification (or using the AST view) returns a "not supported on this +Groovy version yet" message instead. The concrete alpha version is controlled by +the `groovy.6.version` property in `functions/pom.xml`, so bumping to a newer +alpha is a one-line change deployed to the same `groovy_6_0_alpha` function. +Because the runtime id contains `alpha`, the frontend never selects it as the +default version. + ### Deploying the backend Go to https://github.com/groovy-console/groovy-web-console/actions/workflows/deploy.yml and click on `Run Workflow` diff --git a/functions/groovy-executor/pom.xml b/functions/groovy-executor/pom.xml index db8d1ec4..20de3858 100644 --- a/functions/groovy-executor/pom.xml +++ b/functions/groovy-executor/pom.xml @@ -52,7 +52,53 @@ + + + org.spockframework + spock-core + compile + + + + net.bytebuddy + byte-buddy + + + org.objenesis + objenesis + + + org.junit.platform + junit-platform-launcher + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.6.0 + + + add-spock-source + generate-sources + add-source + + src/spock/java + + + + add-spock-resource + generate-resources + add-resource + + src/spock/resources + + + + + + groovy_4_0 @@ -101,7 +147,46 @@ + + net.bytebuddy + byte-buddy + + + org.objenesis + objenesis + + + org.junit.platform + junit-platform-launcher + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.6.0 + + + add-spock-source + generate-sources + add-source + + src/spock/java + + + + add-spock-resource + generate-resources + add-resource + + src/spock/resources + + + + + + groovy_5_0 @@ -147,7 +232,109 @@ + + net.bytebuddy + byte-buddy + + + org.objenesis + objenesis + + + org.junit.platform + junit-platform-launcher + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.6.0 + + + add-spock-source + generate-sources + add-source + + src/spock/java + + + + add-spock-resource + generate-resources + add-resource + + src/spock/resources + + + + + + + + + + groovy_6_0_alpha + + ${groovy.6.version} + 17 + 17 + + + + + org.apache.groovy + groovy-bom + ${groovy.version} + pom + import + + + + + + org.apache.groovy + groovy-all + ${groovy.version} + pom + + + org.codehaus.groovy + groovy-test-testng + + + + + + + + org.junit.jupiter + junit-jupiter + test + + + + + + + org.codehaus.gmavenplus + gmavenplus-plugin + + + + ${project.basedir}/src/no-spock-test/groovy + + **/*.groovy + + + + + + + @@ -156,23 +343,10 @@ com.google.cloud.functions functions-framework-api - - org.spockframework - spock-core - compile - - - net.bytebuddy - byte-buddy - - - org.objenesis - objenesis - - - org.junit.platform - junit-platform-launcher - + org.apache.ivy ivy diff --git a/functions/groovy-executor/src/main/java/gwc/GFunctionExecutor.java b/functions/groovy-executor/src/main/java/gwc/GFunctionExecutor.java index 924aef6d..1f712af2 100644 --- a/functions/groovy-executor/src/main/java/gwc/GFunctionExecutor.java +++ b/functions/groovy-executor/src/main/java/gwc/GFunctionExecutor.java @@ -11,7 +11,7 @@ import groovy.lang.*; import groovy.util.logging.Log; import gwc.representations.*; -import gwc.spock.*; +import gwc.spi.SpecSupport; import gwc.util.*; import org.codehaus.groovy.control.MultipleCompilationErrorsException; import org.codehaus.groovy.control.messages.*; @@ -28,6 +28,11 @@ public class GFunctionExecutor implements HttpFunction { "gwc."); private static final Gson GSON = new Gson(); + // Resolved once: present on Spock-supporting builds (Groovy 3/4/5), absent on + // builds without Spock (e.g. Groovy 6), where spec/AST requests are unsupported. + private static final Optional SPEC_SUPPORT = + ServiceLoader.load(SpecSupport.class).findFirst(); + public GFunctionExecutor() { LOG.info("Groovy function executor initialized"); @@ -71,19 +76,26 @@ private void handleRealInvocation(HttpRequest request, HttpResponse response) th OutputRedirector outputRedirector = new OutputRedirector(); Object result = null; ExecutionInfo stats = new ExecutionInfo(); + SPEC_SUPPORT.map(SpecSupport::spockVersion).ifPresent(stats::setSpockVersion); try (var ignore = new MetaClassRegistryGuard(); var ignore2 = outputRedirector.redirect(); var igonre3 = new SystemPropertiesGuard()) { long executionStart = System.currentTimeMillis(); boolean isSpock = SPOCK_SCRIPT.matcher(inputScriptOrClass).find(); if ("ast".equalsIgnoreCase(scriptRequest.getAction())) { - result = transpileScript(inputScriptOrClass, scriptRequest.getAstPhase(), isSpock); - } else { - if (isSpock) { - result = executeSpock(inputScriptOrClass); + if (SPEC_SUPPORT.isPresent()) { + result = SPEC_SUPPORT.get().renderAst(inputScriptOrClass, scriptRequest.getAstPhase(), isSpock); + } else { + errorOutput.append(featureUnavailable("The AST view is not supported")); + } + } else if (isSpock) { + if (SPEC_SUPPORT.isPresent()) { + result = SPEC_SUPPORT.get().runSpec(inputScriptOrClass); } else { - result = executeGroovyScript(inputScriptOrClass, outputRedirector); + errorOutput.append(featureUnavailable("Spock specifications are not supported")); } + } else { + result = executeGroovyScript(inputScriptOrClass, outputRedirector); } stats.setExecutionTime(System.currentTimeMillis() - executionStart); } catch (MultipleCompilationErrorsException e) { @@ -130,15 +142,10 @@ private Object executeGroovyScript(String inputScriptOrClass, OutputRedirector o return shell.evaluate(inputScriptOrClass); } - private Object executeSpock(String inputScriptOrClass) { - ScriptRunner scriptRunner = new ScriptRunner(); - // TODO revisit colored output - scriptRunner.setDisableColors(true); - return scriptRunner.run(inputScriptOrClass); - } - - private String transpileScript(String script, String astPhase, boolean isSpock) { - return new AstRenderer().render(script, astPhase, isSpock); + private static String featureUnavailable(String lead) { + return lead + " on Groovy " + GroovySystem.getVersion() + + " yet, because Spock has no release compatible with this Groovy version. " + + "Plain Groovy scripts work normally."; } private void handleCompilationErrors(StringBuilder errorOutput, MultipleCompilationErrorsException e) { diff --git a/functions/groovy-executor/src/main/java/gwc/representations/ExecutionInfo.java b/functions/groovy-executor/src/main/java/gwc/representations/ExecutionInfo.java index 98e24988..18087a62 100644 --- a/functions/groovy-executor/src/main/java/gwc/representations/ExecutionInfo.java +++ b/functions/groovy-executor/src/main/java/gwc/representations/ExecutionInfo.java @@ -1,13 +1,14 @@ package gwc.representations; import groovy.lang.GroovySystem; -import org.spockframework.util.SpockReleaseInfo; public class ExecutionInfo { private long executionTime = -1; private String groovyVersion = GroovySystem.getVersion(); - private String spockVersion = SpockReleaseInfo.getVersion().toString(); + // Populated by the executor from the Spock provider; "n/a" when Spock is absent + // (e.g. the Groovy 6 build), since spock-core is not on the classpath here. + private String spockVersion = "n/a"; private String javaVersion = System.getProperty("java.version"); diff --git a/functions/groovy-executor/src/main/java/gwc/spi/SpecSupport.java b/functions/groovy-executor/src/main/java/gwc/spi/SpecSupport.java new file mode 100644 index 00000000..09225f8f --- /dev/null +++ b/functions/groovy-executor/src/main/java/gwc/spi/SpecSupport.java @@ -0,0 +1,36 @@ +package gwc.spi; + +/** + * Optional support for executing Spock specifications and rendering their AST. + * + *

The implementation is Spock-dependent and is only compiled into the + * Spock-supporting build variants (Groovy 3/4/5). On variants without Spock + * (e.g. Groovy 6) no provider is registered and {@link java.util.ServiceLoader} + * resolution yields no result, in which case the executor reports that the + * feature is not supported on the running Groovy version. + */ +public interface SpecSupport { + + /** + * Compiles and runs the given script as one or more Spock specifications. + * + * @param code the script source + * @return the rendered specification result tree + */ + Object runSpec(String code); + + /** + * @return the Spock version available on this build, for reporting in execution info + */ + String spockVersion(); + + /** + * Renders the AST of the given script at the requested compile phase. + * + * @param code the script source + * @param phase the compile phase name (case-insensitive) + * @param isSpock whether the script is a Spock specification + * @return the transpiled source + */ + String renderAst(String code, String phase, boolean isSpock); +} diff --git a/functions/groovy-executor/src/no-spock-test/groovy/gwc/GFunctionExecutorFallbackTest.groovy b/functions/groovy-executor/src/no-spock-test/groovy/gwc/GFunctionExecutorFallbackTest.groovy new file mode 100644 index 00000000..9550b730 --- /dev/null +++ b/functions/groovy-executor/src/no-spock-test/groovy/gwc/GFunctionExecutorFallbackTest.groovy @@ -0,0 +1,81 @@ +package gwc + +import com.google.cloud.functions.HttpRequest +import com.google.cloud.functions.HttpResponse +import groovy.json.JsonOutput +import groovy.json.JsonSlurper +import org.junit.jupiter.api.Test + +import static org.junit.jupiter.api.Assertions.assertEquals +import static org.junit.jupiter.api.Assertions.assertNull +import static org.junit.jupiter.api.Assertions.assertTrue + +/** + * Exercises {@link GFunctionExecutor} on a build WITHOUT Spock (the Groovy 6 variant). + * Plain Groovy must run normally; Spock specs and the AST view must report that they + * are not supported instead of failing obscurely. + */ +class GFunctionExecutorFallbackTest { + + private final GFunctionExecutor executor = new GFunctionExecutor() + + private Map invoke(Map payload) { + def reader = new BufferedReader(new StringReader(JsonOutput.toJson(payload))) + def output = new StringWriter() + def writer = new BufferedWriter(output) + def request = [ + getMethod : { 'POST' }, + getContentType: { Optional.of('application/json') }, + getReader : { reader }, + ] as HttpRequest + def response = [ + appendHeader : { String name, String value -> }, + setContentType: { String contentType -> }, + setStatusCode : { int code -> }, + getWriter : { writer }, + ] as HttpResponse + + // The executor writes the response and closes the writer (try-with-resources), + // which flushes it into `output`. + executor.service(request, response) + new JsonSlurper().parseText(output.toString()) as Map + } + + @Test + void 'plain Groovy script returns its result'() { + def response = invoke(code: '1 + 1') + assertEquals(2, response.result) + assertEquals('', response.err) + } + + @Test + void 'plain Groovy script output is captured'() { + def response = invoke(code: "print 'Hello World'") + assertEquals('Hello World', response.out) + assertEquals('', response.err) + } + + @Test + void 'Spock specification reports not supported'() { + def response = invoke(code: ''' +class ASpec extends Specification { + def "hello world"() { + expect: true + } +} +''') + assertTrue( + response.err.contains('Spock specifications are not supported'), + "unexpected err: ${response.err}") + assertNull(response.result) + } + + @Test + void 'AST action reports not supported'() { + def response = invoke(code: 'def x = 1', action: 'ast', astPhase: 'CONVERSION') + assertTrue( + response.err.contains('The AST view is not supported'), + "unexpected err: ${response.err}") + assertNull(response.result) + } +} diff --git a/functions/groovy-executor/src/main/java/gwc/spock/AstRenderer.java b/functions/groovy-executor/src/spock/java/gwc/spock/AstRenderer.java similarity index 100% rename from functions/groovy-executor/src/main/java/gwc/spock/AstRenderer.java rename to functions/groovy-executor/src/spock/java/gwc/spock/AstRenderer.java diff --git a/functions/groovy-executor/src/main/java/gwc/spock/ScriptCompiler.java b/functions/groovy-executor/src/spock/java/gwc/spock/ScriptCompiler.java similarity index 100% rename from functions/groovy-executor/src/main/java/gwc/spock/ScriptCompiler.java rename to functions/groovy-executor/src/spock/java/gwc/spock/ScriptCompiler.java diff --git a/functions/groovy-executor/src/main/java/gwc/spock/ScriptRunner.java b/functions/groovy-executor/src/spock/java/gwc/spock/ScriptRunner.java similarity index 100% rename from functions/groovy-executor/src/main/java/gwc/spock/ScriptRunner.java rename to functions/groovy-executor/src/spock/java/gwc/spock/ScriptRunner.java diff --git a/functions/groovy-executor/src/spock/java/gwc/spock/SpockSpecSupport.java b/functions/groovy-executor/src/spock/java/gwc/spock/SpockSpecSupport.java new file mode 100644 index 00000000..dcb5ddf1 --- /dev/null +++ b/functions/groovy-executor/src/spock/java/gwc/spock/SpockSpecSupport.java @@ -0,0 +1,29 @@ +package gwc.spock; + +import gwc.spi.SpecSupport; +import org.spockframework.util.SpockReleaseInfo; + +/** + * Spock-backed {@link SpecSupport} provider, registered via {@code ServiceLoader}. + * Only present on the Spock-supporting build variants (Groovy 3/4/5). + */ +public class SpockSpecSupport implements SpecSupport { + + @Override + public Object runSpec(String code) { + ScriptRunner scriptRunner = new ScriptRunner(); + // TODO revisit colored output + scriptRunner.setDisableColors(true); + return scriptRunner.run(code); + } + + @Override + public String renderAst(String code, String phase, boolean isSpock) { + return new AstRenderer().render(code, phase, isSpock); + } + + @Override + public String spockVersion() { + return SpockReleaseInfo.getVersion().toString(); + } +} diff --git a/functions/groovy-executor/src/main/java/gwc/spock/output/Color.java b/functions/groovy-executor/src/spock/java/gwc/spock/output/Color.java similarity index 100% rename from functions/groovy-executor/src/main/java/gwc/spock/output/Color.java rename to functions/groovy-executor/src/spock/java/gwc/spock/output/Color.java diff --git a/functions/groovy-executor/src/main/java/gwc/spock/output/Theme.java b/functions/groovy-executor/src/spock/java/gwc/spock/output/Theme.java similarity index 100% rename from functions/groovy-executor/src/main/java/gwc/spock/output/Theme.java rename to functions/groovy-executor/src/spock/java/gwc/spock/output/Theme.java diff --git a/functions/groovy-executor/src/main/java/gwc/spock/output/TreeNode.java b/functions/groovy-executor/src/spock/java/gwc/spock/output/TreeNode.java similarity index 100% rename from functions/groovy-executor/src/main/java/gwc/spock/output/TreeNode.java rename to functions/groovy-executor/src/spock/java/gwc/spock/output/TreeNode.java diff --git a/functions/groovy-executor/src/main/java/gwc/spock/output/TreePrinter.java b/functions/groovy-executor/src/spock/java/gwc/spock/output/TreePrinter.java similarity index 100% rename from functions/groovy-executor/src/main/java/gwc/spock/output/TreePrinter.java rename to functions/groovy-executor/src/spock/java/gwc/spock/output/TreePrinter.java diff --git a/functions/groovy-executor/src/main/java/gwc/spock/output/TreePrintingListener.java b/functions/groovy-executor/src/spock/java/gwc/spock/output/TreePrintingListener.java similarity index 100% rename from functions/groovy-executor/src/main/java/gwc/spock/output/TreePrintingListener.java rename to functions/groovy-executor/src/spock/java/gwc/spock/output/TreePrintingListener.java diff --git a/functions/groovy-executor/src/main/java/gwc/spock/output/package-info.java b/functions/groovy-executor/src/spock/java/gwc/spock/output/package-info.java similarity index 100% rename from functions/groovy-executor/src/main/java/gwc/spock/output/package-info.java rename to functions/groovy-executor/src/spock/java/gwc/spock/output/package-info.java diff --git a/functions/groovy-executor/src/spock/resources/META-INF/services/gwc.spi.SpecSupport b/functions/groovy-executor/src/spock/resources/META-INF/services/gwc.spi.SpecSupport new file mode 100644 index 00000000..40ef102a --- /dev/null +++ b/functions/groovy-executor/src/spock/resources/META-INF/services/gwc.spi.SpecSupport @@ -0,0 +1 @@ +gwc.spock.SpockSpecSupport diff --git a/functions/pom.xml b/functions/pom.xml index c02b0196..5be8cf8d 100644 --- a/functions/pom.xml +++ b/functions/pom.xml @@ -16,6 +16,7 @@ 3.0.25 4.0.31 5.0.3 + 6.0.0-alpha-1 ${groovy.4.version} 2.4 4.0 diff --git a/services/frontend/src/ts/groovy-console.ts b/services/frontend/src/ts/groovy-console.ts index 1b95c40a..841aa401 100644 --- a/services/frontend/src/ts/groovy-console.ts +++ b/services/frontend/src/ts/groovy-console.ts @@ -7,7 +7,12 @@ export class GroovyVersion { public name: string constructor (public id: string) { - this.name = 'Groovy ' + id.substring('groovy_'.length).replace(/_/g, '.') + const rest = id.substring('groovy_'.length) + // e.g. "6_0_alpha" -> "6.0-alpha", "4_0" -> "4.0"; leave anything unexpected as-is + const match = rest.match(/^(\d+)_(\d+)(?:_(.+))?$/) + this.name = match + ? `Groovy ${match[1]}.${match[2]}${match[3] ? '-' + match[3].replace(/_/g, '-') : ''}` + : 'Groovy ' + rest.replace(/_/g, '.') } } From 7b779f751cb372d4ff1021761da89f7421fa2467 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leonard=20Br=C3=BCnings?= Date: Fri, 29 May 2026 21:18:54 +0200 Subject: [PATCH 2/4] docs: note that switching Maven profiles locally requires clean --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index e5788f7f..44e40e42 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,13 @@ There are different profiles, one for each groovy version: Use `../../mvnw package -P groovy_5_0` +> **Switching profiles locally requires `clean`.** Each profile compiles a +> different set of (test) sources, so run e.g. `../../mvnw clean package -P groovy_6_0_alpha`. +> Without `clean`, stale classes from a previous profile linger in `target/` and +> can cause a confusing `spock/lang/Specification` failure when building the +> Spock-free `groovy_6_0_alpha` variant. (CI is unaffected — it builds from a +> fresh checkout.) + #### Groovy 6 (pre-release, no Spock) Spock has no release compatible with Groovy 6 yet, so the `groovy_6_0_alpha` From 300f133ba50d6207a4ba416fa2483d6892d97a2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leonard=20Br=C3=BCnings?= Date: Fri, 29 May 2026 21:22:27 +0200 Subject: [PATCH 3/4] Clarify fallback message ties the limitation to the selected Groovy version --- .../src/main/java/gwc/GFunctionExecutor.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/functions/groovy-executor/src/main/java/gwc/GFunctionExecutor.java b/functions/groovy-executor/src/main/java/gwc/GFunctionExecutor.java index 1f712af2..ed1ce4a4 100644 --- a/functions/groovy-executor/src/main/java/gwc/GFunctionExecutor.java +++ b/functions/groovy-executor/src/main/java/gwc/GFunctionExecutor.java @@ -143,9 +143,9 @@ private Object executeGroovyScript(String inputScriptOrClass, OutputRedirector o } private static String featureUnavailable(String lead) { - return lead + " on Groovy " + GroovySystem.getVersion() - + " yet, because Spock has no release compatible with this Groovy version. " - + "Plain Groovy scripts work normally."; + return lead + " on the selected Groovy version (" + GroovySystem.getVersion() + + "), because Spock has no release compatible with this Groovy version yet. " + + "Select a different Groovy version to use Spock; plain Groovy scripts work on all versions."; } private void handleCompilationErrors(StringBuilder errorOutput, MultipleCompilationErrorsException e) { From 7ee6ce6a9aeb0eef0c169cdf513dc953d8e48bb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leonard=20Br=C3=BCnings?= Date: Fri, 29 May 2026 21:25:38 +0200 Subject: [PATCH 4/4] Use JDK 21 for Groovy 6 --- .github/workflows/build-groovy-executor.yml | 2 +- .github/workflows/deploy-groovy-executor.yml | 2 +- .../src/main/java/gwc/GFunctionExecutor.java | 16 +++++++++++++--- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-groovy-executor.yml b/.github/workflows/build-groovy-executor.yml index 3ea4d4b6..f53328c9 100644 --- a/.github/workflows/build-groovy-executor.yml +++ b/.github/workflows/build-groovy-executor.yml @@ -28,7 +28,7 @@ jobs: - variant: 'groovy_5_0' java: 21 - variant: 'groovy_6_0_alpha' - java: 17 + java: 21 steps: - uses: actions/checkout@v6 - name: 'Set up JDK' diff --git a/.github/workflows/deploy-groovy-executor.yml b/.github/workflows/deploy-groovy-executor.yml index 2e144e8f..3bbb8860 100644 --- a/.github/workflows/deploy-groovy-executor.yml +++ b/.github/workflows/deploy-groovy-executor.yml @@ -16,7 +16,7 @@ jobs: - variant: 'groovy_5_0' java: 21 - variant: 'groovy_6_0_alpha' - java: 17 + java: 21 steps: - uses: actions/checkout@v6 - name: 'Set up JDK' diff --git a/functions/groovy-executor/src/main/java/gwc/GFunctionExecutor.java b/functions/groovy-executor/src/main/java/gwc/GFunctionExecutor.java index ed1ce4a4..d2a89023 100644 --- a/functions/groovy-executor/src/main/java/gwc/GFunctionExecutor.java +++ b/functions/groovy-executor/src/main/java/gwc/GFunctionExecutor.java @@ -30,8 +30,18 @@ public class GFunctionExecutor implements HttpFunction { // Resolved once: present on Spock-supporting builds (Groovy 3/4/5), absent on // builds without Spock (e.g. Groovy 6), where spec/AST requests are unsupported. - private static final Optional SPEC_SUPPORT = - ServiceLoader.load(SpecSupport.class).findFirst(); + private static final Optional SPEC_SUPPORT = loadSpecSupport(); + + private static Optional loadSpecSupport() { + try { + return ServiceLoader.load(SpecSupport.class).findFirst(); + } catch (ServiceConfigurationError e) { + // A malformed or failing SpecSupport provider must not prevent the function + // from starting; plain Groovy scripts work without it. + LOG.warning("Failed to load SpecSupport provider, spec/AST features disabled: " + e); + return Optional.empty(); + } + } public GFunctionExecutor() { @@ -79,7 +89,7 @@ private void handleRealInvocation(HttpRequest request, HttpResponse response) th SPEC_SUPPORT.map(SpecSupport::spockVersion).ifPresent(stats::setSpockVersion); try (var ignore = new MetaClassRegistryGuard(); var ignore2 = outputRedirector.redirect(); - var igonre3 = new SystemPropertiesGuard()) { + var ignore3 = new SystemPropertiesGuard()) { long executionStart = System.currentTimeMillis(); boolean isSpock = SPOCK_SCRIPT.matcher(inputScriptOrClass).find(); if ("ast".equalsIgnoreCase(scriptRequest.getAction())) {