Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
d593bd2
Register the GSP codecs settings and servlet without live instances
codeconsole Aug 4, 2026
ff515d1
Do not add a ConfigurationClassPostProcessor to an AOT context
codeconsole Aug 4, 2026
38e941f
Keep abstract bean definitions out of AOT processing
codeconsole Aug 4, 2026
b3811ad
Let the container supply the SiteMesh view resolver's servlet context
codeconsole Aug 4, 2026
bc53780
Document ahead-of-time processing and cover it with tests
codeconsole Aug 5, 2026
227d746
Register the GORM event publisher as a bean definition
codeconsole Aug 6, 2026
3206f60
Read the Spring Security version without nested reflection
codeconsole Aug 6, 2026
075c648
Add the license header to the AOT example configuration
codeconsole Aug 6, 2026
df347f1
Expand scaffolded views at build time
codeconsole Aug 6, 2026
55b7dcc
Register the request path's reflective API for ahead-of-time images
codeconsole Aug 6, 2026
3b3537d
Register the framework's closures for ahead-of-time images
codeconsole Aug 6, 2026
842ce6f
Record an application's own classes and pages for ahead-of-time images
codeconsole Aug 6, 2026
fa8e4e4
Register the rendering, scaffolding and persistence runtimes
codeconsole Aug 6, 2026
81ce344
Register the classes a tag library declares inside itself
codeconsole Aug 6, 2026
f92e2e0
Use the pages compiled at build time in an ahead-of-time image
codeconsole Aug 6, 2026
d366841
Ask one question about which scanned types may be registered
codeconsole Aug 6, 2026
e1e3aae
Cover what an application records for an ahead-of-time image
codeconsole Aug 6, 2026
29053d5
Follow the configured import order and indentation
codeconsole Aug 6, 2026
22c100b
Expand scaffolded views only where a controller is scaffolded
codeconsole Aug 6, 2026
b1611e3
Carry a bean's injection into the code generated for it
codeconsole Aug 6, 2026
3e0a6fe
Contribute tag library definitions without replacing what is registered
codeconsole Aug 6, 2026
2885d88
Gather a variable-argument constructor argument into the array it feeds
codeconsole Aug 6, 2026
36d20d1
Leave a datastore's configuration to be read where the application runs
codeconsole Aug 6, 2026
b708736
Keep the bean definitions that were generated ahead of time
codeconsole Aug 6, 2026
d2ad00c
Do not add a second processor for configuration classes
codeconsole Aug 6, 2026
ee481ed
Cover the two ahead-of-time processors contributing to one bean
codeconsole Aug 6, 2026
c250a95
Let a plugin declare its beans from inside an image
codeconsole Aug 7, 2026
07273ed
Answer where the application runs, not where it was built, for GSP
codeconsole Aug 7, 2026
32e3467
Leave the Hibernate datastore's configuration to be read where it run…
codeconsole Aug 7, 2026
a650c42
Inherit no search location while code is being generated
codeconsole Aug 7, 2026
d218534
Let an image call the classes that extend types Groovy did not declare
codeconsole Aug 7, 2026
33c54d8
Read the flag directly rather than looking it up
codeconsole Aug 7, 2026
0d0dc23
Do not read the application's own bean declarations a second time
codeconsole Aug 7, 2026
2b0824f
Write down the artefacts an application is made of
codeconsole Aug 7, 2026
26f464b
Leave the plugins' own artefacts to the plugins
codeconsole Aug 7, 2026
f304120
Define the url mappings holder as reloadable only where it can reload
codeconsole Aug 7, 2026
9ba4d14
Carry what a page compiled at build time is read from
codeconsole Aug 7, 2026
feb740f
Ship what an image needs with the framework rather than asking for it
codeconsole Aug 7, 2026
7d2bad0
Colour the output of an image that is being run at a shell
codeconsole Aug 7, 2026
79f1dcc
Show the banner art in the framework's colour
codeconsole Aug 7, 2026
af29982
Let an application choose the colour the banner art is shown in
codeconsole Aug 7, 2026
181ac75
Address review: metadata only for a native build, and four smaller th…
codeconsole Aug 7, 2026
7687f84
Leave a datastore's proxy handler alone
codeconsole Aug 7, 2026
813e191
Let an image call the framework's own interfaces
codeconsole Aug 7, 2026
7a063e3
Skip a closure whose surroundings cannot be loaded
codeconsole Aug 7, 2026
5846c43
Let an image call the Spring interfaces a plugin is handed
codeconsole Aug 7, 2026
ac091ad
Use plain map-entry spacing in the native metadata task
codeconsole Aug 7, 2026
da9dcd7
Train a JDK AOT cache from a run of the application
codeconsole Aug 7, 2026
b2c7f43
Document the AOT cache
codeconsole Aug 7, 2026
59e644b
Package compiled assets where an executable jar can read them
codeconsole Aug 7, 2026
3fec0cd
Correct the AOT cache figures
codeconsole Aug 7, 2026
aeb9004
Address review of the AOT cache task
codeconsole Aug 7, 2026
b2009c4
Assert what the registrar now says these beans produce
codeconsole Aug 7, 2026
ac9075a
Follow every type a class file names, not some of them
codeconsole Aug 7, 2026
790b5c1
Train on the toolchain's JDK, and leave the cache beside the application
codeconsole Aug 7, 2026
03b9909
Say under the banner how the application was started
codeconsole Aug 7, 2026
2187890
Keep the banner's option enums usable in an image
codeconsole Aug 7, 2026
8ba500d
Read an optional version without setting the library going
codeconsole Aug 7, 2026
8a28c10
Show Spring Security by default, and leave out what cannot be determined
codeconsole Aug 7, 2026
199eb8e
Show the container being served on, and stop at the first one found
codeconsole Aug 8, 2026
223ac95
Build on JDK 21.0.12
codeconsole Aug 8, 2026
bd86459
Give an application building ahead of time what it needs
codeconsole Aug 8, 2026
f18532d
Space a mark as it was written rather than shouting it
codeconsole Aug 8, 2026
61d9031
Register an image's resources as hints rather than as build arguments
codeconsole Aug 8, 2026
fe4c9cf
Stop caching what a forked compiler produced
codeconsole Aug 8, 2026
baf6402
Say how the extracted application is compared, so the task validates
codeconsole Aug 8, 2026
1848212
Say why the training run ended, not only that it did
codeconsole Aug 8, 2026
c465952
Do not hand the training run a variable the daemon kept but emptied
codeconsole Aug 9, 2026
eef515d
Ask about the return that rewinds a line, not the one that ends it
codeconsole Aug 10, 2026
fa710fd
Merge branch '8.0.x' into fix/aot-live-instance-beans-8.0.x
codeconsole Aug 10, 2026
2876503
Fold the task's two javadoc blocks into one
codeconsole Aug 10, 2026
1240ce7
Keep the setters a domain class's constraints are applied through
codeconsole Aug 9, 2026
49ec1f0
Record the reflection a running application does, from a declared lis…
codeconsole Aug 9, 2026
2649ca4
Let a traced form be told what to put in it
codeconsole Aug 9, 2026
33af958
Sort the AOT imports the cherry-pick left out of order
codeconsole Aug 10, 2026
4b9352d
Ask for nullability with the annotation Spring did not deprecate
codeconsole Aug 12, 2026
49a5944
Give two version lookups back the documentation left above a third
codeconsole Aug 12, 2026
cbc1ffa
Let every datastore name its event publisher rather than hold one
codeconsole Aug 12, 2026
4016bae
Keep the image to the properties files something actually reads
codeconsole Aug 12, 2026
48d9d2b
Tell Gradle what decides whether the views are staged
codeconsole Aug 12, 2026
9bbd566
Say which JDK a cache needs, rather than let the run fail for it
codeconsole Aug 12, 2026
e517a9d
Say which banner version option was not recognised
codeconsole Aug 12, 2026
6fa81e7
Write down what the banner now says without being asked
codeconsole Aug 12, 2026
1d95c8e
Leave collectMappedClasses saying what it has always said
codeconsole Aug 12, 2026
082b6b4
Ask the post-processor about colour through the hook that decides it
codeconsole Aug 12, 2026
0a8b8f5
Make the ahead-of-time application serve a page, not only start
codeconsole Aug 12, 2026
4788ceb
Say what makes standing down over the configuration processor safe
codeconsole Aug 12, 2026
35657c9
Leave Neo4j holding the configuration it can still reach
codeconsole Aug 12, 2026
5a924d1
Put back the cookie handler the build was using
codeconsole Aug 12, 2026
cb374b1
Trace what was asked for, or say that it was not
codeconsole Aug 12, 2026
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: 1 addition & 1 deletion .github/workflows/release-publish-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ permissions: { }
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
JAVA_DISTRIBUTION: liberica
JAVA_VERSION: 21.0.7
JAVA_VERSION: 21.0.12
TARGET_BRANCH: ${{ github.ref_name }}
VERSION: ${{ inputs.version }}
jobs:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GRAILS_PUBLISH_RELEASE: 'true'
JAVA_DISTRIBUTION: liberica
JAVA_VERSION: 21.0.7 # this must be a specific version for reproducible builds, keep it synced with .sdkmanrc and verification container
JAVA_VERSION: 21.0.12 # this must be a specific version for reproducible builds, keep it synced with .sdkmanrc and verification container
JAVA_VERSION_MICRONAUT: 25.0.3 # the Grails-Micronaut "island" (grails-micronaut, grails-micronaut-bom) is built against Micronaut 5 which targets JVM 25 bytecode. Keep this synced with the secondary JDK installed in etc/bin/Dockerfile and the JDK_25_HOME branch in etc/bin/verify-reproducible.sh.
PROJECT_DESC: >
Grails is a powerful Groovy-based web application framework for the JVM,
Expand Down
2 changes: 1 addition & 1 deletion .sdkmanrc
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# $JAVA_VERSION_MICRONAUT in release.yml; for local verification, install that JDK 25
# alongside this one (sdk install java <version>-librca) and follow the dual-JDK
# instructions in RELEASE.md "Manual Verification: Reproducible Jar Files".
java=21.0.7-librca
java=21.0.12-librca
# Keep gradle version synced with gradle.properties (gradleToolingApiVersion).
# Update the gradle-bootstrap project to propagate the version to all gradle-wrapper.properties files.
gradle=9.6.0
4 changes: 2 additions & 2 deletions dependencies.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,8 @@ ext {
'sitemesh.version' : '2.6.0',
'scribejava.version' : '8.3.3',
'spock.version' : '2.4-groovy-5.0',
'starter-sitemesh.version' : '3.3.0-M3',
'spring-webmvc-sitemesh.version': '3.3.0-M3',
'starter-sitemesh.version' : '3.3.0-SNAPSHOT',
'spring-webmvc-sitemesh.version': '3.3.0-SNAPSHOT',
// Spring Boot 4 no longer manages spring-retry; pin it here so the
// grails-shell-cli SpringRetryCompilerAutoConfiguration's unversioned
// reference resolves and consumer apps using @Retryable get a known version.
Expand Down
2 changes: 1 addition & 1 deletion etc/bin/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
# run this from the root of the project
# `docker build -t grails:testing -f etc/bin/Dockerfile . && docker run -it --rm -v $(pwd):/home/groovy/project grails:testing bash`
# Keep java version synced with .sdkmanrc and .github/workflows/release.yml ($JAVA_VERSION)
FROM bellsoft/liberica-openjdk-debian:21.0.7
FROM bellsoft/liberica-openjdk-debian:21.0.12

USER root
RUN apt-get update && apt-get install -y ca-certificates curl unzip coreutils libdigest-sha-perl gpg vim sudo psmisc locales groovy rsync nano
Expand Down
251 changes: 251 additions & 0 deletions grails-common/src/main/groovy/org/grails/aot/RegistrableTypes.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.grails.aot;

import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedHashSet;
import java.util.Set;

import org.jspecify.annotations.Nullable;

import org.springframework.asm.ClassReader;
import org.springframework.asm.ClassVisitor;
import org.springframework.asm.ConstantDynamic;
import org.springframework.asm.FieldVisitor;
import org.springframework.asm.Handle;
import org.springframework.asm.Label;
import org.springframework.asm.MethodVisitor;
import org.springframework.asm.SpringAsmInfo;
import org.springframework.asm.Type;
import org.springframework.util.ClassUtils;

/**
* Decides whether a type found by scanning can be registered for reflection.
*
* <p>Registering one that cannot be loaded here does not degrade at run time -- it fails the build,
* because the image analysis parses what it is asked to keep. The framework compiles against
* optional integrations, so a scan of its own packages finds classes an application need not be able
* to load: the JSP support without the JSP API, the GSP compiler's task without Ant, a datastore
* without its driver.</p>
*
* <p>Two questions are asked, because either alone lets one of those through. Whether the type and
* the class declaring it load covers a closure whose enclosing class extends something absent, which
* the closure's own bytecode need not name -- it reaches it through invokedynamic. Whether the types
* its bytecode names load covers the opposite case, a class that loads cleanly while a method body
* names something absent, because loading resolves signatures and not bodies.</p>
*
* @since 8.0
*/
public final class RegistrableTypes {

private RegistrableTypes() {
}

/**
* Whether the type, and the class declaring it, load here.
*
* @param className the binary name of the type
* @param classLoader the loader to resolve against
* @return whether the type can be registered
*/
public static boolean loads(String className, @Nullable ClassLoader classLoader) {
int declaring = className.indexOf('$');
if (declaring > 0 && !ClassUtils.isPresent(className.substring(0, declaring), classLoader)) {
return false;
}
return ClassUtils.isPresent(className, classLoader);
}

/**
* Whether every type named in the given bytecode loads here, following the declaration and the
* bodies of the methods.
*
* @param bytecode the class file to read; closed by this method
* @param classLoader the loader to resolve against
* @return whether the type can be registered
*/
public static boolean referencesLoad(InputStream bytecode, @Nullable ClassLoader classLoader) {
Set<String> referenced = new LinkedHashSet<>();
try (InputStream input = bytecode) {
new ClassReader(input).accept(new ReferenceCollector(referenced),
ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
}
catch (IOException | RuntimeException ex) {
return false;
}
for (String name : referenced) {
if (!ClassUtils.isPresent(name, classLoader)) {
return false;
}
}
return true;
}

/** Collects the types a class names, in its own declaration and in the bodies of its methods. */
private static final class ReferenceCollector extends ClassVisitor {

private final Set<String> referenced;

private ReferenceCollector(Set<String> referenced) {
super(SpringAsmInfo.ASM_VERSION);
this.referenced = referenced;
}

private void add(@Nullable String internalName) {
if (internalName == null) {
return;
}
if (internalName.startsWith("[")) {
// an array names its element type, which is the class that has to be there
addType(Type.getType(internalName));
return;
}
String className = ClassUtils.convertResourcePathToClassName(internalName);
// the JDK is always present, and skipping it keeps this to the classes that can be absent
if (!className.startsWith("java.") && !className.startsWith("jdk.")) {
referenced.add(className);
}
}

/** Adds a type, reducing an array to the element type it is an array of. */
private void addType(@Nullable Type type) {
if (type == null) {
return;
}
Type element = type;
while (element.getSort() == Type.ARRAY) {
element = element.getElementType();
}
if (element.getSort() == Type.OBJECT) {
add(element.getInternalName());
}
}

/** Adds every type a method descriptor names: what it takes and what it gives back. */
private void addMethodDescriptor(@Nullable String descriptor) {
if (descriptor == null) {
return;
}
for (Type argument : Type.getArgumentTypes(descriptor)) {
addType(argument);
}
addType(Type.getReturnType(descriptor));
}

/** Adds whichever constant carries a type: a class literal, a handle, or a method type. */
private void addConstant(@Nullable Object constant) {
if (constant instanceof Type type) {
if (type.getSort() == Type.METHOD) {
addMethodDescriptor(type.getDescriptor());
}
else {
addType(type);
}
}
else if (constant instanceof Handle handle) {
add(handle.getOwner());
addMethodDescriptor(handle.getDesc());
}
else if (constant instanceof ConstantDynamic dynamic) {
addType(Type.getType(dynamic.getDescriptor()));
add(dynamic.getBootstrapMethod().getOwner());
for (int i = 0; i < dynamic.getBootstrapMethodArgumentCount(); i++) {
addConstant(dynamic.getBootstrapMethodArgument(i));
}
}
}

@Override
public void visit(int version, int access, String name, String signature, String superName,
String[] interfaces) {
add(superName);
if (interfaces != null) {
for (String each : interfaces) {
add(each);
}
}
}

@Override
public FieldVisitor visitField(int access, String name, String descriptor, String signature,
Object value) {
addType(Type.getType(descriptor));
return null;
}

@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
String[] exceptions) {
addMethodDescriptor(descriptor);
if (exceptions != null) {
for (String each : exceptions) {
add(each);
}
}
return new MethodVisitor(SpringAsmInfo.ASM_VERSION) {
@Override
public void visitTypeInsn(int opcode, String type) {
add(type);
}

@Override
public void visitMethodInsn(int opcode, String owner, String methodName,
String methodDescriptor, boolean isInterface) {
add(owner);
addMethodDescriptor(methodDescriptor);
}

@Override
public void visitFieldInsn(int opcode, String owner, String fieldName,
String fieldDescriptor) {
add(owner);
addType(Type.getType(fieldDescriptor));
}

@Override
public void visitLdcInsn(Object value) {
// a class literal, which names a type without ever calling anything on it
addConstant(value);
}

@Override
public void visitInvokeDynamicInsn(String methodName, String methodDescriptor,
Handle bootstrap, Object... arguments) {
// how a Groovy call site names what it dispatches on
addMethodDescriptor(methodDescriptor);
addConstant(bootstrap);
for (Object argument : arguments) {
addConstant(argument);
}
}

@Override
public void visitMultiANewArrayInsn(String descriptor, int dimensions) {
addType(Type.getType(descriptor));
}

@Override
public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
add(type);
}
};
}
}
}
Loading
Loading