diff --git a/.gitignore b/.gitignore index 59c81a7669..3dec068221 100644 --- a/.gitignore +++ b/.gitignore @@ -21,8 +21,6 @@ derby.log /build buildSrc/build /spring-*/build -/spring-core/graalvm/build -/spring-core/kotlin-coroutines/build /framework-bom/build /integration-tests/build /src/asciidoc/build diff --git a/framework-platform/framework-platform.gradle b/framework-platform/framework-platform.gradle index 3055c8a4cf..c42e2d251b 100644 --- a/framework-platform/framework-platform.gradle +++ b/framework-platform/framework-platform.gradle @@ -120,7 +120,7 @@ dependencies { api("org.glassfish.external:opendmk_jmxremote_optional_jar:1.0-b01-ea") api("org.glassfish.tyrus:tyrus-container-servlet:2.0.1") api("org.glassfish:jakarta.el:4.0.2") - api("org.graalvm.nativeimage:svm:22.1.0.1") + api("org.graalvm.sdk:graal-sdk:22.3.0") api("org.hamcrest:hamcrest:2.2") api("org.hibernate:hibernate-core-jakarta:5.6.12.Final") api("org.hibernate:hibernate-validator:7.0.5.Final") diff --git a/settings.gradle b/settings.gradle index d28b7d0625..7ba0847bb4 100644 --- a/settings.gradle +++ b/settings.gradle @@ -18,8 +18,6 @@ include "spring-context" include "spring-context-indexer" include "spring-context-support" include "spring-core" -include "graalvm-feature" -project(':graalvm-feature').projectDir = file('spring-core/graalvm') include "spring-core-test" include "spring-expression" include "spring-instrument" diff --git a/spring-core/graalvm/graalvm-feature.gradle b/spring-core/graalvm/graalvm-feature.gradle deleted file mode 100644 index d6578778a7..0000000000 --- a/spring-core/graalvm/graalvm-feature.gradle +++ /dev/null @@ -1,40 +0,0 @@ -description = "Spring Core GraalVM feature" - -configurations { - classesOnlyElements { - canBeConsumed = true - canBeResolved = false - } -} - -artifacts { - classesOnlyElements(compileJava.destinationDirectory) - classesOnlyElements(sourceSets.main.resources.srcDirs) -} - -tasks.withType(JavaCompile) { - options.compilerArgs += [ - "--add-modules", - "jdk.internal.vm.ci", - "--add-exports", - "jdk.internal.vm.ci/jdk.vm.ci.meta=ALL-UNNAMED" - ] -} - -tasks.withType(Javadoc) { - enabled = false -} - -eclipse.classpath.file { - whenMerged { - entries.find{ it.path ==~ '.*JRE_CONTAINER.*' }.each { - it.entryAttributes['module'] = true - it.entryAttributes['add-exports'] = 'jdk.internal.vm.ci/jdk.vm.ci.meta=ALL-UNNAMED' - it.entryAttributes['limit-modules'] = 'java.se,jdk.accessibility,jdk.attach,jdk.compiler,jdk.httpserver,jdk.jartool,jdk.jconsole,jdk.jdi,jdk.management,jdk.sctp,jdk.security.auth,jdk.security.jgss,jdk.unsupported,jdk.dynalink,jdk.incubator.foreign,jdk.incubator.vector,jdk.javadoc,jdk.jfr,jdk.jshell,jdk.management.jfr,jdk.net,jdk.nio.mapmode,jdk.unsupported.desktop,jdk.jsobject,jdk.xml.dom,jdk.internal.vm.ci' - } - } -} - -dependencies { - compileOnly("org.graalvm.nativeimage:svm") -} diff --git a/spring-core/graalvm/src/main/java/org/springframework/aot/graalvm/ConstantFieldFeature.java b/spring-core/graalvm/src/main/java/org/springframework/aot/graalvm/ConstantFieldFeature.java deleted file mode 100644 index 2201503f4a..0000000000 --- a/spring-core/graalvm/src/main/java/org/springframework/aot/graalvm/ConstantFieldFeature.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2002-2022 the original author or authors. - * - * Licensed 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.springframework.aot.graalvm; - -import com.oracle.svm.core.annotate.AutomaticFeature; -import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl; -import org.graalvm.compiler.debug.DebugContext; -import org.graalvm.nativeimage.hosted.Feature; - -/** - * GraalVM {@link Feature} that substitutes field values that match a certain pattern - * with constants without causing build-time initialization. - * - * @author Phillip Webb - * @author Sebastien Deleuze - * @since 6.0 - */ -@AutomaticFeature -class ConstantFieldFeature implements Feature { - - @Override - public void duringSetup(DuringSetupAccess access) { - duringSetup((DuringSetupAccessImpl) access); - } - - private void duringSetup(DuringSetupAccessImpl access) { - DebugContext debug = access.getDebugContext(); - try (DebugContext.Scope scope = debug.scope("ConstantFieldFeature.duringSetup")) { - debug.log("Installing constant field substitution processor : " + scope); - ClassLoader classLoader = ConstantFieldFeature.class.getClassLoader(); - ConstantFieldSubstitutionProcessor substitutionProcessor = - new ConstantFieldSubstitutionProcessor(debug, classLoader); - access.registerSubstitutionProcessor(substitutionProcessor); - } - } - -} diff --git a/spring-core/graalvm/src/main/java/org/springframework/aot/graalvm/ConstantFieldSubstitutionProcessor.java b/spring-core/graalvm/src/main/java/org/springframework/aot/graalvm/ConstantFieldSubstitutionProcessor.java deleted file mode 100644 index 6cbaed9e4a..0000000000 --- a/spring-core/graalvm/src/main/java/org/springframework/aot/graalvm/ConstantFieldSubstitutionProcessor.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2002-2022 the original author or authors. - * - * Licensed 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.springframework.aot.graalvm; - -import java.lang.reflect.Field; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.regex.Pattern; - -import com.oracle.graal.pointsto.infrastructure.SubstitutionProcessor; -import com.oracle.svm.core.meta.SubstrateObjectConstant; -import com.oracle.svm.core.util.UserError; -import jdk.vm.ci.meta.JavaConstant; -import jdk.vm.ci.meta.JavaKind; -import jdk.vm.ci.meta.ResolvedJavaField; -import jdk.vm.ci.meta.ResolvedJavaType; -import org.graalvm.compiler.debug.DebugContext; - -/** - * {@link SubstitutionProcessor} to compute at build time the value of the - * boolean static fields identified by {@link #patterns} in order to allow - * efficient code shrinking without using class build time initialization. - * - * @author Phillip Webb - * @author Sebastien Deleuze - * @since 6.0 - */ -class ConstantFieldSubstitutionProcessor extends SubstitutionProcessor { - - // Later should be an explicit signal, like an annotation or even a Java keyword - private static Pattern[] patterns = { - Pattern.compile(Pattern.quote("org.springframework.core.NativeDetector#imageCode")), - Pattern.compile(Pattern.quote("org.springframework.") + ".*#.*Present"), - Pattern.compile(Pattern.quote("org.springframework.") + ".*#.*PRESENT"), - Pattern.compile(Pattern.quote("reactor.") + ".*#.*Available") - }; - - private final ThrowawayClassLoader throwawayClassLoader; - - private ConcurrentMap cache = new ConcurrentHashMap<>(); - - private Set ignoredFields = ConcurrentHashMap.newKeySet(); - - - ConstantFieldSubstitutionProcessor(DebugContext debug, ClassLoader applicationClassLoader) { - this.throwawayClassLoader = new ThrowawayClassLoader(applicationClassLoader); - } - - @Override - public ResolvedJavaField lookup(ResolvedJavaField field) { - ResolvedJavaType declaringClass = field.getDeclaringClass(); - if (field.getType().getJavaKind() == JavaKind.Boolean && field.isStatic()) { - String fieldIdentifier = declaringClass.toJavaName() + "#" + field.getName(); - for (Pattern pattern : patterns) { - if (pattern.matcher(fieldIdentifier).matches() && !this.ignoredFields.contains(fieldIdentifier)) { - try { - return this.cache.computeIfAbsent(fieldIdentifier, key -> { - JavaConstant constant = lookupConstant(declaringClass.toJavaName(), field.getName()); - // TODO Use proper logging only when --verbose is specified when https://github.com/oracle/graal/issues/4669 will be fixed - ConstantReadableJavaField readableJavaField = new ConstantReadableJavaField(field, constant); - System.out.println("Field " + fieldIdentifier + " set to " + constant.toValueString() + " at build time"); - return readableJavaField; - }); - } - catch (Throwable ex) { - this.ignoredFields.add(fieldIdentifier); - System.out.println("Processing of field " + fieldIdentifier + " skipped due the following error : " + ex.getMessage()); - } - } - } - } - return super.lookup(field); - } - - private JavaConstant lookupConstant(String className, String fieldName) { - try { - Class throwawayClass = this.throwawayClassLoader.loadClass(className); - Field field = throwawayClass.getDeclaredField(fieldName); - field.setAccessible(true); - Object value = field.get(null); - if (!(value instanceof Boolean)) { - throw UserError.abort("Unable to get the value of " + className + "." + fieldName); - } - return SubstrateObjectConstant.forBoxedValue(JavaKind.Boolean, value); - } - catch (Exception ex) { - throw new IllegalStateException("Unable to read value from " + className + "." + fieldName, ex); - } - } - -} diff --git a/spring-core/graalvm/src/main/java/org/springframework/aot/graalvm/ConstantReadableJavaField.java b/spring-core/graalvm/src/main/java/org/springframework/aot/graalvm/ConstantReadableJavaField.java deleted file mode 100644 index b2a20f53dd..0000000000 --- a/spring-core/graalvm/src/main/java/org/springframework/aot/graalvm/ConstantReadableJavaField.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2002-2022 the original author or authors. - * - * Licensed 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.springframework.aot.graalvm; - -import java.lang.annotation.Annotation; - -import com.oracle.graal.pointsto.infrastructure.WrappedElement; -import com.oracle.svm.core.meta.ReadableJavaField; -import jdk.vm.ci.meta.JavaConstant; -import jdk.vm.ci.meta.JavaType; -import jdk.vm.ci.meta.MetaAccessProvider; -import jdk.vm.ci.meta.ResolvedJavaField; -import jdk.vm.ci.meta.ResolvedJavaType; - -/** - * {@link ReadableJavaField} for a constant value. - * - * @author Phillip Webb - * @since 6.0 - */ -class ConstantReadableJavaField implements ReadableJavaField, WrappedElement { - - private final ResolvedJavaField original; - - private final JavaConstant constant; - - - public ConstantReadableJavaField(ResolvedJavaField original, JavaConstant constant) { - this.original = original; - this.constant = constant; - } - - - @Override - public T getAnnotation(Class annotationClass) { - return this.original.getAnnotation(annotationClass); - } - - @Override - public Annotation[] getAnnotations() { - return this.original.getAnnotations(); - } - - @Override - public Annotation[] getDeclaredAnnotations() { - return this.original.getDeclaredAnnotations(); - } - - @Override - public ResolvedJavaType getDeclaringClass() { - return this.original.getDeclaringClass(); - } - - @Override - public int getModifiers() { - return this.original.getModifiers(); - } - - @Override - public String getName() { - return this.original.getName(); - } - - @Override - public int getOffset() { - return this.original.getOffset(); - } - - @Override - public JavaType getType() { - return this.original.getType(); - } - - @Override - public boolean isInternal() { - return this.original.isInternal(); - } - - @Override - public boolean isSynthetic() { - return this.original.isSynthetic(); - } - - @Override - public JavaConstant readValue(MetaAccessProvider metaAccess, JavaConstant receiver) { - return this.constant; - } - - @Override - public boolean allowConstantFolding() { - return true; - } - - @Override - public boolean injectFinalForRuntimeCompilation() { - return true; - } - - @Override - public Object getWrapped() { - return this.original; - } -} diff --git a/spring-core/graalvm/src/main/java/org/springframework/aot/graalvm/package-info.java b/spring-core/graalvm/src/main/java/org/springframework/aot/graalvm/package-info.java deleted file mode 100644 index f5128d92f9..0000000000 --- a/spring-core/graalvm/src/main/java/org/springframework/aot/graalvm/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * GraalVM implementation specific support which might change at any time, so not considered part of Spring Framework public API. - */ -package org.springframework.aot.graalvm; - diff --git a/spring-core/spring-core.gradle b/spring-core/spring-core.gradle index 25c6f7ce78..9c9f4a04a8 100644 --- a/spring-core/spring-core.gradle +++ b/spring-core/spring-core.gradle @@ -58,11 +58,11 @@ task objenesisSourceJar(type: Jar) { dependencies { javapoet("com.squareup:javapoet:${javapoetVersion}@jar") objenesis("org.objenesis:objenesis:${objenesisVersion}@jar") - graalvm(project(path: ":graalvm-feature", configuration: 'classesOnlyElements')) api(files(javapoetRepackJar)) api(files(objenesisRepackJar)) api(project(":spring-jcl")) compileOnly("io.projectreactor.tools:blockhound") + compileOnly("org.graalvm.sdk:graal-sdk") optional("net.sf.jopt-simple:jopt-simple") optional("org.aspectj:aspectjweaver") optional("org.jetbrains.kotlin:kotlin-reflect") @@ -108,8 +108,6 @@ jar { from(zipTree(objenesisRepackJar.archivePath)) { include "org/springframework/objenesis/**" } - - from configurations.graalvm } test { diff --git a/spring-core/src/main/java/org/springframework/aot/nativex/feature/PreComputeFieldFeature.java b/spring-core/src/main/java/org/springframework/aot/nativex/feature/PreComputeFieldFeature.java new file mode 100644 index 0000000000..2c421c00af --- /dev/null +++ b/spring-core/src/main/java/org/springframework/aot/nativex/feature/PreComputeFieldFeature.java @@ -0,0 +1,86 @@ +/* + * Copyright 2002-2022 the original author or authors. + * + * Licensed 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.springframework.aot.nativex.feature; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.regex.Pattern; + +import org.graalvm.nativeimage.hosted.Feature; + +/** + * GraalVM {@link Feature} that substitutes boolean field values that match a certain pattern + * with values pre-computed AOT without causing class build-time initialization. + * + * @author Sebastien Deleuze + * @author Phillip Webb + * @since 6.0 + */ +class PreComputeFieldFeature implements Feature { + + private static Pattern[] patterns = { + Pattern.compile(Pattern.quote("org.springframework.core.NativeDetector#imageCode")), + Pattern.compile(Pattern.quote("org.springframework.") + ".*#.*Present"), + Pattern.compile(Pattern.quote("org.springframework.") + ".*#.*PRESENT"), + Pattern.compile(Pattern.quote("reactor.") + ".*#.*Available") + }; + + private final ThrowawayClassLoader throwawayClassLoader = new ThrowawayClassLoader(PreComputeFieldFeature.class.getClassLoader()); + + @Override + public void beforeAnalysis(BeforeAnalysisAccess access) { + access.registerSubtypeReachabilityHandler(this::iterateFields, Object.class); + } + + /* This method is invoked for every type that is reachable. */ + private void iterateFields(DuringAnalysisAccess access, Class subtype) { + try { + for (Field field : subtype.getDeclaredFields()) { + int modifiers = field.getModifiers(); + if (!Modifier.isStatic(modifiers) || !Modifier.isFinal(modifiers) || field.isEnumConstant() || + (field.getType() != boolean.class && field.getType() != Boolean.class)) { + continue; + } + String fieldIdentifier = field.getDeclaringClass().getName() + "#" + field.getName(); + for (Pattern pattern : patterns) { + if (pattern.matcher(fieldIdentifier).matches()) { + try { + Object fieldValue = provideFieldValue(field); + access.registerFieldValueTransformer(field, (receiver, originalValue) -> fieldValue); + System.out.println("Field " + fieldIdentifier + " set to " + fieldValue + " at build time"); + } + catch (Throwable ex) { + System.out.println("Processing of field " + fieldIdentifier + " skipped due the following error : " + ex.getMessage()); + } + } + } + } + } + catch (NoClassDefFoundError ex) { + // Skip classes that have not all their field types in the classpath + } + } + + /* This method is invoked when the field value is written to the image heap or the field is constant folded. */ + private Object provideFieldValue(Field field) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { + Class throwawayClass = this.throwawayClassLoader.loadClass(field.getDeclaringClass().getName()); + Field throwawayField = throwawayClass.getDeclaredField(field.getName()); + throwawayField.setAccessible(true); + return throwawayField.get(null); + } + +} diff --git a/spring-core/graalvm/src/main/java/org/springframework/aot/graalvm/ThrowawayClassLoader.java b/spring-core/src/main/java/org/springframework/aot/nativex/feature/ThrowawayClassLoader.java similarity index 97% rename from spring-core/graalvm/src/main/java/org/springframework/aot/graalvm/ThrowawayClassLoader.java rename to spring-core/src/main/java/org/springframework/aot/nativex/feature/ThrowawayClassLoader.java index 01cf682be4..88b3784607 100644 --- a/spring-core/graalvm/src/main/java/org/springframework/aot/graalvm/ThrowawayClassLoader.java +++ b/spring-core/src/main/java/org/springframework/aot/nativex/feature/ThrowawayClassLoader.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.aot.graalvm; +package org.springframework.aot.nativex.feature; import java.io.ByteArrayOutputStream; import java.io.IOException; diff --git a/spring-core/src/main/java/org/springframework/aot/nativex/feature/package-info.java b/spring-core/src/main/java/org/springframework/aot/nativex/feature/package-info.java new file mode 100644 index 0000000000..db71c8c1a6 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/aot/nativex/feature/package-info.java @@ -0,0 +1,9 @@ +/** + * GraalVM native image features, not part of Spring Framework public API. + */ +@NonNullApi +@NonNullFields +package org.springframework.aot.nativex.feature; + +import org.springframework.lang.NonNullApi; +import org.springframework.lang.NonNullFields; diff --git a/spring-core/graalvm/src/main/java/org/springframework/aot/graalvm/Target_ClassFinder.java b/spring-core/src/main/java/org/springframework/aot/nativex/substitution/Target_ClassFinder.java similarity index 95% rename from spring-core/graalvm/src/main/java/org/springframework/aot/graalvm/Target_ClassFinder.java rename to spring-core/src/main/java/org/springframework/aot/nativex/substitution/Target_ClassFinder.java index e1c3c3fa14..d3cc059e3a 100644 --- a/spring-core/graalvm/src/main/java/org/springframework/aot/graalvm/Target_ClassFinder.java +++ b/spring-core/src/main/java/org/springframework/aot/nativex/substitution/Target_ClassFinder.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.aot.graalvm; +package org.springframework.aot.nativex.substitution; import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.TargetClass; diff --git a/spring-core/graalvm/src/main/java/org/springframework/aot/graalvm/Target_Introspector.java b/spring-core/src/main/java/org/springframework/aot/nativex/substitution/Target_Introspector.java similarity index 96% rename from spring-core/graalvm/src/main/java/org/springframework/aot/graalvm/Target_Introspector.java rename to spring-core/src/main/java/org/springframework/aot/nativex/substitution/Target_Introspector.java index c1dfae9ee0..ed98d969d0 100644 --- a/spring-core/graalvm/src/main/java/org/springframework/aot/graalvm/Target_Introspector.java +++ b/spring-core/src/main/java/org/springframework/aot/nativex/substitution/Target_Introspector.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.aot.graalvm; +package org.springframework.aot.nativex.substitution; import java.beans.Customizer; diff --git a/spring-core/src/main/java/org/springframework/aot/nativex/substitution/package-info.java b/spring-core/src/main/java/org/springframework/aot/nativex/substitution/package-info.java new file mode 100644 index 0000000000..dca0e7c560 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/aot/nativex/substitution/package-info.java @@ -0,0 +1,9 @@ +/** + * GraalVM native image substitutions, not part of Spring Framework public API. + */ +@NonNullApi +@NonNullFields +package org.springframework.aot.nativex.substitution; + +import org.springframework.lang.NonNullApi; +import org.springframework.lang.NonNullFields; diff --git a/spring-core/src/main/resources/META-INF/native-image/org.springframework/spring-core/native-image.properties b/spring-core/src/main/resources/META-INF/native-image/org.springframework/spring-core/native-image.properties index f75232b9b3..b3dff76d44 100644 --- a/spring-core/src/main/resources/META-INF/native-image/org.springframework/spring-core/native-image.properties +++ b/spring-core/src/main/resources/META-INF/native-image/org.springframework/spring-core/native-image.properties @@ -1,5 +1,2 @@ -Args = --initialize-at-build-time=org.springframework.aot.graalvm.ThrowawayClassLoader \ ---add-exports org.graalvm.nativeimage.builder/com.oracle.svm.hosted=ALL-UNNAMED \ ---add-exports jdk.internal.vm.compiler/org.graalvm.compiler.debug=ALL-UNNAMED \ ---add-exports jdk.internal.vm.ci/jdk.vm.ci.meta=ALL-UNNAMED \ ---add-exports org.graalvm.nativeimage.builder/com.oracle.svm.core.meta=ALL-UNNAMED +Args = --initialize-at-build-time=org.springframework.aot.nativex.feature.ThrowawayClassLoader \ +--features=org.springframework.aot.nativex.feature.PreComputeFieldFeature