-
Notifications
You must be signed in to change notification settings - Fork 240
GraalVM support #255
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
GraalVM support #255
Changes from all commits
50ff815
ce27b2e
9e11dab
71279ee
567d59c
c85b0a3
693bbc8
ae2af66
8a34d28
602180e
837b38f
1e42782
80ea23c
a67cabf
187cebf
c4c8661
a81c1fc
33060df
9430ae3
6c7ce8d
c5b928f
c9479ad
e68cbb1
a266a67
2df8a6e
47bbe1e
612c5a9
ff795e6
c27cb9a
200d5b8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,4 +29,3 @@ jobs: | |
| - name: Run 'pr' target | ||
| working-directory: ./aws-lambda-java-runtime-interface-client | ||
| run: make pr | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,4 +21,7 @@ dependency-reduced-pom.xml | |
| .gradle | ||
| .settings | ||
| .classpath | ||
| .project | ||
| .project | ||
|
|
||
| #MacOS | ||
| .DS_Store | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| package com.amazonaws.services.lambda.runtime.graalvm; | ||
|
|
||
| import com.amazonaws.services.lambda.runtime.LambdaRuntime; | ||
| import com.amazonaws.services.lambda.runtime.LambdaRuntimeInternal; | ||
| import org.graalvm.nativeimage.hosted.Feature; | ||
| import org.graalvm.nativeimage.hosted.RuntimeReflection; | ||
|
|
||
| import java.lang.reflect.Constructor; | ||
| import java.lang.reflect.Field; | ||
| import java.lang.reflect.Method; | ||
| import java.util.HashSet; | ||
| import java.util.Set; | ||
|
|
||
| /** | ||
| * This class programmatically registers classes, methods and fields | ||
| * to be added to an application when used with GraalVM native-image. | ||
| * | ||
| * These specific classes are registered because GraalVM can't detect | ||
| * them during static analysis. | ||
| * | ||
| * @see <a href="https://www.graalvm.org/reference-manual/native-image/">GraalVM native-image</a> | ||
| */ | ||
| public class LambdaCoreGraalVMFeature implements Feature { | ||
|
|
||
| private static final Set<Class> classesForReflectConfig = new HashSet<>(); | ||
|
|
||
| static { | ||
| classesForReflectConfig.add(LambdaRuntime.class); | ||
| classesForReflectConfig.add(LambdaRuntimeInternal.class); | ||
| } | ||
|
|
||
| @Override | ||
| public void beforeAnalysis(BeforeAnalysisAccess access) { | ||
| for (Class aClass : classesForReflectConfig) { | ||
| registerClass(aClass); | ||
| registerMethods(aClass); | ||
| registerFields(aClass); | ||
| } | ||
| } | ||
|
|
||
| private void registerMethods(Class<?> cl) { | ||
| for (Method method : cl.getDeclaredMethods()) { | ||
| RuntimeReflection.register(method); | ||
| } | ||
| } | ||
|
|
||
| private void registerFields(Class<?> cl) { | ||
| for (Field field : cl.getDeclaredFields()) { | ||
| RuntimeReflection.register(field); | ||
| } | ||
| } | ||
|
|
||
| private void registerClass(Class<?> cl) { | ||
| RuntimeReflection.register(cl); | ||
| for (Constructor<?> constructor : cl.getDeclaredConstructors()) { | ||
| RuntimeReflection.register(constructor); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Args = --features=com.amazonaws.services.lambda.runtime.graalvm.LambdaCoreGraalVMFeature |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| [ | ||
| { | ||
| "name":"com.amazonaws.services.lambda.runtime.LambdaRuntime", | ||
| "methods":[{"name":"<init>","parameterTypes":[] }], | ||
| "fields":[{"name":"logger"}], | ||
| "allPublicMethods":true | ||
| }, | ||
| { | ||
| "name":"com.amazonaws.services.lambda.runtime.LambdaRuntimeInternal", | ||
| "methods":[{"name":"<init>","parameterTypes":[] }], | ||
| "allPublicMethods":true | ||
| } | ||
| ] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| package com.amazonaws.services.lambda.runtime.graalvm; | ||
|
|
||
| import org.graalvm.nativeimage.hosted.Feature; | ||
| import org.graalvm.nativeimage.hosted.RuntimeReflection; | ||
|
|
||
| import java.io.File; | ||
| import java.io.IOException; | ||
| import java.lang.reflect.Constructor; | ||
| import java.lang.reflect.Field; | ||
| import java.lang.reflect.Method; | ||
| import java.net.URL; | ||
| import java.util.ArrayList; | ||
| import java.util.Enumeration; | ||
| import java.util.List; | ||
|
|
||
| /** | ||
| * The aws-lambda-java-events library supports GraalVM by containing a reflect-config.json file. This is located | ||
| * src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json | ||
| * | ||
| * This config is used by the GraalVM native-image tool in order to load the required classes and methods into the | ||
| * native binary it creates. | ||
| * | ||
| * Any event or response class added to this library needs to be added to this config file. | ||
| * | ||
| * This implementation of Feature does that by iterating through the events package and registering | ||
| * them for runtime reflection. | ||
| */ | ||
| public class LambdaEventsGraalVMFeature implements Feature { | ||
|
|
||
| public static final String EVENTS_PACKAGE_NAME = "com.amazonaws.services.lambda.runtime.events"; | ||
|
Comment on lines
+28
to
+30
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should make this generic enough for it to be reused across all our libraries in this project, and move it out of the runtime addressable scope. It should be a build-only class that is not exposed in the library. This is essentially boilerplate code that will traverse our libraries and determine what the appropriate |
||
|
|
||
| public static List<Class<?>> getClasses(String packageName) | ||
| throws ClassNotFoundException, IOException { | ||
| ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); | ||
| assert classLoader != null; | ||
| String path = packageName.replace('.', '/'); | ||
| Enumeration<URL> resources = classLoader.getResources(path); | ||
| List<File> dirs = new ArrayList<>(); | ||
| while (resources.hasMoreElements()) { | ||
| URL resource = resources.nextElement(); | ||
| dirs.add(new File(resource.getFile())); | ||
| } | ||
| ArrayList<Class<?>> classes = new ArrayList<>(); | ||
| for (File directory : dirs) { | ||
| classes.addAll(findClasses(directory, packageName)); | ||
| } | ||
| return classes; | ||
| } | ||
|
|
||
| private static List<Class<?>> findClasses(File directory, String packageName) throws ClassNotFoundException { | ||
| List<Class<?>> classes = new ArrayList<>(); | ||
| if (!directory.exists()) { | ||
| return classes; | ||
| } | ||
| File[] files = directory.listFiles(); | ||
| if(files == null){ | ||
| return classes; | ||
| } | ||
| for (File file : files) { | ||
| String fileName = file.getName(); | ||
| if (file.isDirectory()) { | ||
| assert !fileName.contains("."); | ||
| classes.addAll(findClasses(file, packageName + "." + fileName)); | ||
| } else if (fileName.endsWith(".class")) { | ||
| classes.add(Class.forName(packageName + '.' + fileName.substring(0, fileName.length() - ".class".length()))); | ||
| } | ||
| } | ||
| return classes; | ||
| } | ||
|
|
||
| @Override | ||
| public void beforeAnalysis(BeforeAnalysisAccess access) { | ||
| try { | ||
| List<Class<?>> classes = getClasses(EVENTS_PACKAGE_NAME); | ||
| for (Class<?> cl : classes) { | ||
| System.out.println("Registering class:"+cl.getName()); | ||
| registerClass(cl); | ||
| registerMethods(cl); | ||
| registerFields(cl); | ||
| } | ||
| } catch (ClassNotFoundException | IOException e) { | ||
| e.printStackTrace(); | ||
| System.err.println("Failed to automatically load classes from "+ LambdaEventsGraalVMFeature.class.getName()); | ||
| System.exit(1); | ||
| } | ||
| } | ||
|
|
||
| private void registerMethods(Class<?> cl) { | ||
| for (Method method : cl.getDeclaredMethods()) { | ||
| RuntimeReflection.register(method); | ||
| } | ||
| } | ||
|
|
||
| private void registerFields(Class<?> cl) { | ||
| for (Field field : cl.getDeclaredFields()) { | ||
| RuntimeReflection.register(field); | ||
| } | ||
| } | ||
|
|
||
| private void registerClass(Class<?> cl) { | ||
| RuntimeReflection.register(cl); | ||
| for (Constructor<?> constructor : cl.getDeclaredConstructors()) { | ||
| RuntimeReflection.register(constructor); | ||
| } | ||
| System.out.printf("\tAdding constructors for %s%n", cl.getName()); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| Args = --enable-all-security-services \ | ||
| --enable-url-protocols=http,https \ | ||
| --features=com.amazonaws.services.lambda.runtime.graalvm.LambdaEventsGraalVMFeature |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| package com.amazonaws.services.lambda.runtime.events.graalvm; | ||
|
|
||
| import com.amazonaws.services.lambda.runtime.graalvm.LambdaEventsGraalVMFeature; | ||
| import io.github.classgraph.ClassGraph; | ||
| import org.junit.jupiter.api.Assertions; | ||
| import org.junit.jupiter.api.DynamicTest; | ||
| import org.junit.jupiter.api.TestFactory; | ||
|
|
||
| import java.io.IOException; | ||
| import java.util.HashSet; | ||
| import java.util.Set; | ||
| import java.util.stream.Stream; | ||
|
|
||
| import static com.amazonaws.services.lambda.runtime.graalvm.LambdaEventsGraalVMFeature.EVENTS_PACKAGE_NAME; | ||
|
|
||
| /** | ||
| * The aws-lambda-java-events library supports GraalVM by containing a reflect-config.json file. This is located | ||
| * src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json | ||
| * | ||
| * This config is used by the GraalVM native-image tool in order to load the required classes and methods into the | ||
| * native binary it creates. | ||
| * | ||
| * Any event or response class added to this library needs to be added to this config file. | ||
| * | ||
| * This test asserts that all the classes are included. | ||
| */ | ||
| public class LambdaEventsGraalVMFeatureTest { | ||
|
|
||
| @TestFactory | ||
| public Stream<DynamicTest> testEventClassesAreFound() throws IOException, ClassNotFoundException { | ||
|
|
||
| Set<Class<?>> eventClassNames = new HashSet<>(LambdaEventsGraalVMFeature.getClasses(EVENTS_PACKAGE_NAME)); | ||
|
|
||
| return new ClassGraph() | ||
| .enableClassInfo() | ||
| .acceptPackages(EVENTS_PACKAGE_NAME) | ||
| .scan().getAllClasses().stream() | ||
| .map(classInfo -> DynamicTest.dynamicTest("Test " + classInfo.getSimpleName(), () -> { | ||
| Class<?> eventClass = classInfo.loadClass(); | ||
| Assertions.assertTrue(eventClassNames.contains(eventClass)); | ||
| })); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,2 @@ | ||
| compile-flags.txt | ||
| codebuild.* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this file needed if we have the dynamic
Feature?