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
11 changes: 4 additions & 7 deletions .github/workflows/check-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,9 @@ jobs:

echo "Changes detected in powertools modules: $CHANGED_FILES"

# Find modules with graalvm-native profile and run tests
# Find modules with native profile and run tests with the tracing agent
find . -name "pom.xml" -path "./powertools-*" | while read module; do
if grep -q "<id>graalvm-native</id>" "$module"; then
if grep -q "<id>native</id>" "$module"; then
module_dir=$(dirname "$module")
module_name=$(basename "$module_dir")

Expand All @@ -135,11 +135,8 @@ jobs:
echo " $CHANGED_FILES " | grep -q " pom.xml " || \
echo "$CHANGED_FILES" | grep -q "powertools-common/"; then
echo "::group::Building $module_name with GraalVM"
echo "Changes detected in $module_name - running GraalVM tests"
echo "Regenerating GraalVM metadata for $module_dir"
mvn -B -q -f "$module" -Pgenerate-graalvm-files clean test
echo "Running GraalVM native tests for $module_dir"
mvn -B -q -f "$module" -Pgraalvm-native test
echo "Changes detected in $module_name - running GraalVM native tests"
mvn -B -q -f "$module" -Pnative -Dagent=true clean test
echo "::endgroup::"
else
echo "No changes detected in $module_name - skipping GraalVM tests"
Expand Down
41 changes: 26 additions & 15 deletions GraalVM.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,44 +19,55 @@ GraalVM native image compilation requires complete knowledge of an application's

In order to generate the metadata reachability files for Powertools for Lambda, follow these general steps.

1. **Add Maven Profiles**
- Add profile for generating GraalVM reachability metadata files. You can find an example of this in profile `generate-graalvm-files` of this [pom.xml](powertools-common/pom.xml).
- Add another profile for running the tests in the native image. You can find and example of this in profile `graalvm-native` of this [pom.xml](powertools-common/pom.xml).
1. **Add the `native` Maven Profile**
- The root `pom.xml` provides a shared `pluginManagement` configuration for the `native-maven-plugin` with the `<agent>` configuration, common `buildArgs`, and the `test-native` execution.
- Each module only needs to define a `native` profile that specifies module-specific configuration: `imageName` and `metadataCopy.outputDirectory`. You can find an example in the `native` profile of this [pom.xml](powertools-common/pom.xml).
- If a module needs extra `buildArgs` beyond the common ones, use `<buildArgs combine.children="append">` to add them without overriding the parent configuration.

2. **Generate Reachability Metadata**
- Set the `JAVA_HOME` environment variable to use GraalVM
- Run tests with `-Pgenerate-graalvm-files` profile.
- Run tests with the `-Dagent=true` flag to attach the GraalVM tracing agent to the test execution:
```shell
mvn -Pgenerate-graalvm-files clean test
mvn -Pnative -Dagent=true clean test
```

3. **Validate Native Image Tests**
3. **Copy Metadata to Source**
- Copy the generated metadata from the agent output directory to the module's `src/main/resources` directory:
```shell
mvn -Pnative native:metadata-copy
```

4. **Validate Native Image Tests**
- Set the `JAVA_HOME` environment variable to use GraalVM
- Run tests with `-Pgraalvm-native` profile. This will build a GraalVM native image and run the JUnit tests.
- Run tests with `-Pnative` profile. This will build a GraalVM native image and run the JUnit tests.
```shell
mvn -Pgraalvm-native clean test
mvn -Pnative test
```

4. **Clean Up Metadata**
- GraalVM metadata reachability files generated in Step 2 contains references to the test scoped dependencies as well.
5. **Clean Up Metadata**
- GraalVM metadata reachability files generated in Step 2 contain references to the test scoped dependencies as well.
- Remove the references in generated metadata files for the following (and any other references to test scoped resources and classes):
- JUnit
- Mockito
- ByteBuddy

## Known Issues and Solutions
1. **Mockito Compatibility**
- Powertools uses Mockito 5.x which uses inline mock maker as the default. This mock maker does not play well with GraalVM. Mockito [recommends](https://github.com/mockito/mockito/releases/tag/v5.0.0) using subclass mock maker with GraalVM. Therefore `generate-graalvm-files` profile uses subclass mock maker instead of inline mock maker.
- Powertools uses Mockito 5.x which uses "inline mock maker" as the default. This mock maker does not play well with GraalVM. Mockito [recommends](https://github.com/mockito/mockito/releases/tag/v5.0.0) using subclass mock maker with GraalVM. Therefore the `native` profile adds the `mockito-subclass` dependency where needed.
- Subclass mock maker does not support testing static methods. Tests have therefore been modified to use [JUnit Pioneer](https://junit-pioneer.org/docs/environment-variables/) to inject the environment variables in the scope of the test's execution.

2. **Log4j Compatibility**
2. **Unsafe Allocation Tracing**
- GraalVM 21.0.10+ requires `"unsafeAllocated": true` in `reflect-config.json` for classes instantiated via `Unsafe.allocateInstance()`. Mockito uses Objenesis which relies on this.
- The `enableExperimentalUnsafeAllocationTracing` option is enabled in the root `pluginManagement` agent configuration to address this.

3. **Log4j Compatibility**
- Version 2.22.1 fails with this error
```
java.lang.InternalError: com.oracle.svm.core.jdk.UnsupportedFeatureError: Defining hidden classes at runtime is not supported.
```
- This has been [fixed](https://github.com/apache/logging-log4j2/discussions/2364#discussioncomment-8950077) in Log4j 2.24.x. PT has been updated to use this version of Log4j

3. **Test Class Organization**
4. **Test Class Organization**
- **Issue**: Anonymous inner classes and lambda expressions in Mockito matchers cause `NoSuchMethodError` in GraalVM native tests
- **Solution**:
- Extract static inner test classes to separate concrete classes in the same package as the class under test
Expand All @@ -72,12 +83,12 @@ java.lang.InternalError: com.oracle.svm.core.jdk.UnsupportedFeatureError: Defini
})
```

4. **Package Visibility Issues**
5. **Package Visibility Issues**
- **Issue**: Test handler classes cannot access package-private methods when placed in subpackages
- **Solution**: Place test handler classes in the same package as the class under test, not in subpackages like `handlers/`
- **Example**: Use `software.amazon.lambda.powertools.cloudformation` instead of `software.amazon.lambda.powertools.cloudformation.handlers`

5. **Test Stubs Best Practice**
6. **Test Stubs Best Practice**
- **Best Practice**: Avoid mocking where possible and use concrete test stubs provided by `powertools-common` package
- **Solution**: Use `TestLambdaContext` and other test stubs from `powertools-common` test-jar instead of Mockito mocks
- **Implementation**: Add `powertools-common` test-jar dependency and replace `mock(Context.class)` with `new TestLambdaContext()`
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

63 changes: 63 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@
<mockito-junit-jupiter.version>5.21.0</mockito-junit-jupiter.version>
<junit-pioneer.version>2.3.0</junit-pioneer.version>
<crac.version>1.5.0</crac.version>
<native-maven-plugin.version>0.11.5</native-maven-plugin.version>

<!-- As we have a .mvn directory at the root of the project, this will evaluate to the root directory
regardless of where maven is run - sub-module, or root. -->
Expand Down Expand Up @@ -450,6 +451,45 @@
<artifactId>exec-maven-plugin</artifactId>
<version>3.6.2</version>
</plugin>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>${native-maven-plugin.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<id>test-native</id>
<goals>
<goal>test</goal>
</goals>
<phase>test</phase>
</execution>
</executions>
<configuration>
<agent>
<enabled>true</enabled>
<options>
<enableExperimentalPredefinedClasses>true</enableExperimentalPredefinedClasses>
<enableExperimentalUnsafeAllocationTracing>true</enableExperimentalUnsafeAllocationTracing>
</options>
<metadataCopy>
<disabledStages>
<stage>main</stage>
</disabledStages>
<merge>false</merge>
</metadataCopy>
</agent>
<buildArgs>
<buildArg>--add-opens java.base/java.util=ALL-UNNAMED</buildArg>
<buildArg>--add-opens java.base/java.lang=ALL-UNNAMED</buildArg>
<buildArg>--no-fallback</buildArg>
<buildArg>--verbose</buildArg>
<buildArg>--native-image-info</buildArg>
<buildArg>-H:+UnlockExperimentalVMOptions</buildArg>
<buildArg>-H:+ReportExceptionStackTraces</buildArg>
</buildArgs>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<plugins>
Expand Down Expand Up @@ -635,6 +675,29 @@
</plugins>
</build>
</profile>
<!--
The native profile configures surefire to pass add-opens flags via JDK_JAVA_OPTIONS.
This is necessary because the native-maven-plugin's NativeExtension overwrites surefire's
argLine when -Dagent=true is set (it adds its own argLine element for the tracing agent),
which causes the add-opens flags from the jdk16 profile to be lost.
JDK_JAVA_OPTIONS is picked up by the JVM automatically, bypassing the argLine override.
-->
<profile>
<id>native</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<environmentVariables>
<JDK_JAVA_OPTIONS>--add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED</JDK_JAVA_OPTIONS>
</environmentVariables>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>

</project>
53 changes: 7 additions & 46 deletions powertools-cloudformation/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -120,33 +120,7 @@

<profiles>
<profile>
<id>generate-graalvm-files</id>
<dependencies>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-subclass</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>
-Dorg.graalvm.nativeimage.imagecode=agent
-agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-cloudformation,experimental-class-define-support
--add-opens java.base/java.util=ALL-UNNAMED
--add-opens java.base/java.lang=ALL-UNNAMED
</argLine>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>graalvm-native</id>
<id>native</id>
<dependencies>
<dependency>
<groupId>org.mockito</groupId>
Expand All @@ -159,28 +133,15 @@
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>0.11.2</version>
<extensions>true</extensions>
<executions>
<execution>
<id>test-native</id>
<goals>
<goal>test</goal>
</goals>
<phase>test</phase>
</execution>
</executions>
<configuration>
<imageName>powertools-cloudformation</imageName>
<buildArgs>
<buildArg>--add-opens java.base/java.util=ALL-UNNAMED</buildArg>
<buildArg>--add-opens java.base/java.lang=ALL-UNNAMED</buildArg>
<agent>
<metadataCopy>
<outputDirectory>src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-cloudformation</outputDirectory>
</metadataCopy>
</agent>
<buildArgs combine.children="append">
<buildArg>--enable-url-protocols=http</buildArg>
<buildArg>--no-fallback</buildArg>
<buildArg>--verbose</buildArg>
<buildArg>--native-image-info</buildArg>
<buildArg>-H:+UnlockExperimentalVMOptions</buildArg>
<buildArg>-H:+ReportExceptionStackTraces</buildArg>
</buildArgs>
</configuration>
</plugin>
Expand Down
53 changes: 6 additions & 47 deletions powertools-common/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -93,33 +93,7 @@
</dependencies>
<profiles>
<profile>
<id>generate-graalvm-files</id>
<dependencies>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-subclass</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>
-Dorg.graalvm.nativeimage.imagecode=agent
-agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-common,experimental-class-define-support
--add-opens java.base/java.util=ALL-UNNAMED
--add-opens java.base/java.lang=ALL-UNNAMED
</argLine>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>graalvm-native</id>
<id>native</id>
<dependencies>
<dependency>
<groupId>org.mockito</groupId>
Expand All @@ -132,28 +106,13 @@
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>0.11.2</version>
<extensions>true</extensions>
<executions>
<execution>
<id>test-native</id>
<goals>
<goal>test</goal>
</goals>
<phase>test</phase>
</execution>
</executions>
<configuration>
<imageName>powertools-common</imageName>
<buildArgs>
<buildArg>--add-opens java.base/java.util=ALL-UNNAMED</buildArg>
<buildArg>--add-opens java.base/java.lang=ALL-UNNAMED</buildArg>
<buildArg>--no-fallback</buildArg>
<buildArg>--verbose</buildArg>
<buildArg>--native-image-info</buildArg>
<buildArg>-H:+UnlockExperimentalVMOptions</buildArg>
<buildArg>-H:+ReportExceptionStackTraces</buildArg>
</buildArgs>
<agent>
<metadataCopy>
<outputDirectory>src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-common</outputDirectory>
</metadataCopy>
</agent>
</configuration>
</plugin>
</plugins>
Expand Down
Loading
Loading