Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/build-groovy-executor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ jobs:
java: 21
- variant: 'groovy_5_0'
java: 21
- variant: 'groovy_6_0_alpha'
java: 21
steps:
- uses: actions/checkout@v6
- name: 'Set up JDK'
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/deploy-groovy-executor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ jobs:
java: 21
- variant: 'groovy_5_0'
java: 21
- variant: 'groovy_6_0_alpha'
java: 21
steps:
- uses: actions/checkout@v6
- name: 'Set up JDK'
Expand Down
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,31 @@ 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`

> **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`
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`
Expand Down
208 changes: 191 additions & 17 deletions functions/groovy-executor/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,53 @@
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<scope>compile</scope>
<!-- For Groovy 3 the groupId is still org.codehaus.groovy, so no exclusion is needed. -->
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
</dependency>
<dependency>
<groupId>org.objenesis</groupId>
<artifactId>objenesis</artifactId>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<id>add-spock-source</id>
<phase>generate-sources</phase>
<goals><goal>add-source</goal></goals>
<configuration>
<sources><source>src/spock/java</source></sources>
</configuration>
</execution>
<execution>
<id>add-spock-resource</id>
<phase>generate-resources</phase>
<goals><goal>add-resource</goal></goals>
<configuration>
<resources><resource><directory>src/spock/resources</directory></resource></resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>groovy_4_0</id>
Expand Down Expand Up @@ -101,7 +147,46 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
</dependency>
<dependency>
<groupId>org.objenesis</groupId>
<artifactId>objenesis</artifactId>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<id>add-spock-source</id>
<phase>generate-sources</phase>
<goals><goal>add-source</goal></goals>
<configuration>
<sources><source>src/spock/java</source></sources>
</configuration>
</execution>
<execution>
<id>add-spock-resource</id>
<phase>generate-resources</phase>
<goals><goal>add-resource</goal></goals>
<configuration>
<resources><resource><directory>src/spock/resources</directory></resource></resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>groovy_5_0</id>
Expand Down Expand Up @@ -147,7 +232,109 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
</dependency>
<dependency>
<groupId>org.objenesis</groupId>
<artifactId>objenesis</artifactId>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<id>add-spock-source</id>
<phase>generate-sources</phase>
<goals><goal>add-source</goal></goals>
<configuration>
<sources><source>src/spock/java</source></sources>
</configuration>
</execution>
<execution>
<id>add-spock-resource</id>
<phase>generate-resources</phase>
<goals><goal>add-resource</goal></goals>
<configuration>
<resources><resource><directory>src/spock/resources</directory></resource></resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<!-- Groovy 6 is pre-release and Spock has no compatible release yet, so this
variant ships WITHOUT spock-core. Spec/AST requests return a "not supported"
message via the SpecSupport SPI (no provider is on the classpath). -->
<id>groovy_6_0_alpha</id>
<properties>
<groovy.version>${groovy.6.version}</groovy.version>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.source>17</maven.compiler.source>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.groovy</groupId>
<artifactId>groovy-bom</artifactId>
<version>${groovy.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>${groovy.version}</version>
<type>pom</type>
<exclusions>
<exclusion>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-test-testng</artifactId>
<!-- Exclude testng for now, as this causes surefire to ignore junit5 platform tests -->
</exclusion>
</exclusions>
</dependency>

<!-- Spock is absent on this variant, so the fallback tests use plain JUnit 5. -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Compile the no-Spock fallback tests instead of the Spock-based default tests. -->
<plugin>
<groupId>org.codehaus.gmavenplus</groupId>
<artifactId>gmavenplus-plugin</artifactId>
<configuration>
<testSources>
<testSource>
<directory>${project.basedir}/src/no-spock-test/groovy</directory>
<includes>
<include>**/*.groovy</include>
</includes>
</testSource>
</testSources>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>

Expand All @@ -156,23 +343,10 @@
<groupId>com.google.cloud.functions</groupId>
<artifactId>functions-framework-api</artifactId>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
</dependency>
<dependency>
<groupId>org.objenesis</groupId>
<artifactId>objenesis</artifactId>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
</dependency>
<!-- spock-core and its Spock-only runtime deps (byte-buddy, objenesis,
junit-platform-launcher) are intentionally NOT declared here: they are added
per-profile for the Spock-supporting variants (Groovy 3/4/5) only, so the
Groovy 6 build ships without Spock or its runtime helpers. -->
<dependency>
<groupId>org.apache.ivy</groupId>
<artifactId>ivy</artifactId>
Expand Down
49 changes: 33 additions & 16 deletions functions/groovy-executor/src/main/java/gwc/GFunctionExecutor.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.*;
Expand All @@ -28,6 +28,21 @@ 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<SpecSupport> SPEC_SUPPORT = loadSpecSupport();

private static Optional<SpecSupport> 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();
}
}


Comment thread
leonard84 marked this conversation as resolved.
public GFunctionExecutor() {
LOG.info("Groovy function executor initialized");
Expand Down Expand Up @@ -71,19 +86,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()) {
var ignore3 = new SystemPropertiesGuard()) {
long executionStart = System.currentTimeMillis();
Comment thread
leonard84 marked this conversation as resolved.
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 {
result = executeGroovyScript(inputScriptOrClass, outputRedirector);
errorOutput.append(featureUnavailable("The AST view is not supported"));
}
} else if (isSpock) {
if (SPEC_SUPPORT.isPresent()) {
result = SPEC_SUPPORT.get().runSpec(inputScriptOrClass);
} else {
errorOutput.append(featureUnavailable("Spock specifications are not supported"));
}
} else {
result = executeGroovyScript(inputScriptOrClass, outputRedirector);
}
stats.setExecutionTime(System.currentTimeMillis() - executionStart);
} catch (MultipleCompilationErrorsException e) {
Expand Down Expand Up @@ -130,15 +152,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 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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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");

Expand Down
Loading
Loading