Skip to content

Commit d8a3e08

Browse files
authored
chore: modernize native maven profiles to using <agent> configuration and move to root pom. Update CI removing unnecessary metadata generation step. (#2419)
1 parent c9a52fb commit d8a3e08

File tree

22 files changed

+199
-734
lines changed

22 files changed

+199
-734
lines changed

.github/workflows/check-build.yml

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,9 @@ jobs:
124124
125125
echo "Changes detected in powertools modules: $CHANGED_FILES"
126126
127-
# Find modules with graalvm-native profile and run tests
127+
# Find modules with native profile and run tests with the tracing agent
128128
find . -name "pom.xml" -path "./powertools-*" | while read module; do
129-
if grep -q "<id>graalvm-native</id>" "$module"; then
129+
if grep -q "<id>native</id>" "$module"; then
130130
module_dir=$(dirname "$module")
131131
module_name=$(basename "$module_dir")
132132
@@ -135,11 +135,8 @@ jobs:
135135
echo " $CHANGED_FILES " | grep -q " pom.xml " || \
136136
echo "$CHANGED_FILES" | grep -q "powertools-common/"; then
137137
echo "::group::Building $module_name with GraalVM"
138-
echo "Changes detected in $module_name - running GraalVM tests"
139-
echo "Regenerating GraalVM metadata for $module_dir"
140-
mvn -B -q -f "$module" -Pgenerate-graalvm-files clean test
141-
echo "Running GraalVM native tests for $module_dir"
142-
mvn -B -q -f "$module" -Pgraalvm-native test
138+
echo "Changes detected in $module_name - running GraalVM native tests"
139+
mvn -B -q -f "$module" -Pnative -Dagent=true clean test
143140
echo "::endgroup::"
144141
else
145142
echo "No changes detected in $module_name - skipping GraalVM tests"

GraalVM.md

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,44 +19,55 @@ GraalVM native image compilation requires complete knowledge of an application's
1919

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

22-
1. **Add Maven Profiles**
23-
- 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).
24-
- 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).
22+
1. **Add the `native` Maven Profile**
23+
- 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.
24+
- 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).
25+
- If a module needs extra `buildArgs` beyond the common ones, use `<buildArgs combine.children="append">` to add them without overriding the parent configuration.
2526

2627
2. **Generate Reachability Metadata**
2728
- Set the `JAVA_HOME` environment variable to use GraalVM
28-
- Run tests with `-Pgenerate-graalvm-files` profile.
29+
- Run tests with the `-Dagent=true` flag to attach the GraalVM tracing agent to the test execution:
2930
```shell
30-
mvn -Pgenerate-graalvm-files clean test
31+
mvn -Pnative -Dagent=true clean test
3132
```
3233

33-
3. **Validate Native Image Tests**
34+
3. **Copy Metadata to Source**
35+
- Copy the generated metadata from the agent output directory to the module's `src/main/resources` directory:
36+
```shell
37+
mvn -Pnative native:metadata-copy
38+
```
39+
40+
4. **Validate Native Image Tests**
3441
- Set the `JAVA_HOME` environment variable to use GraalVM
35-
- Run tests with `-Pgraalvm-native` profile. This will build a GraalVM native image and run the JUnit tests.
42+
- Run tests with `-Pnative` profile. This will build a GraalVM native image and run the JUnit tests.
3643
```shell
37-
mvn -Pgraalvm-native clean test
44+
mvn -Pnative test
3845
```
3946

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

4754
## Known Issues and Solutions
4855
1. **Mockito Compatibility**
49-
- 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.
56+
- 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.
5057
- 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.
5158

52-
2. **Log4j Compatibility**
59+
2. **Unsafe Allocation Tracing**
60+
- GraalVM 21.0.10+ requires `"unsafeAllocated": true` in `reflect-config.json` for classes instantiated via `Unsafe.allocateInstance()`. Mockito uses Objenesis which relies on this.
61+
- The `enableExperimentalUnsafeAllocationTracing` option is enabled in the root `pluginManagement` agent configuration to address this.
62+
63+
3. **Log4j Compatibility**
5364
- Version 2.22.1 fails with this error
5465
```
5566
java.lang.InternalError: com.oracle.svm.core.jdk.UnsupportedFeatureError: Defining hidden classes at runtime is not supported.
5667
```
5768
- 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
5869

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

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

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

examples/powertools-examples-kafka/src/main/java/org/demo/kafka/protobuf/ProtobufProduct.java

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/powertools-examples-kafka/src/main/java/org/demo/kafka/protobuf/ProtobufProductOrBuilder.java

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/powertools-examples-kafka/src/main/java/org/demo/kafka/protobuf/ProtobufProductOuterClass.java

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pom.xml

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@
117117
<mockito-junit-jupiter.version>5.21.0</mockito-junit-jupiter.version>
118118
<junit-pioneer.version>2.3.0</junit-pioneer.version>
119119
<crac.version>1.5.0</crac.version>
120+
<native-maven-plugin.version>0.11.5</native-maven-plugin.version>
120121

121122
<!-- As we have a .mvn directory at the root of the project, this will evaluate to the root directory
122123
regardless of where maven is run - sub-module, or root. -->
@@ -450,6 +451,45 @@
450451
<artifactId>exec-maven-plugin</artifactId>
451452
<version>3.6.2</version>
452453
</plugin>
454+
<plugin>
455+
<groupId>org.graalvm.buildtools</groupId>
456+
<artifactId>native-maven-plugin</artifactId>
457+
<version>${native-maven-plugin.version}</version>
458+
<extensions>true</extensions>
459+
<executions>
460+
<execution>
461+
<id>test-native</id>
462+
<goals>
463+
<goal>test</goal>
464+
</goals>
465+
<phase>test</phase>
466+
</execution>
467+
</executions>
468+
<configuration>
469+
<agent>
470+
<enabled>true</enabled>
471+
<options>
472+
<enableExperimentalPredefinedClasses>true</enableExperimentalPredefinedClasses>
473+
<enableExperimentalUnsafeAllocationTracing>true</enableExperimentalUnsafeAllocationTracing>
474+
</options>
475+
<metadataCopy>
476+
<disabledStages>
477+
<stage>main</stage>
478+
</disabledStages>
479+
<merge>false</merge>
480+
</metadataCopy>
481+
</agent>
482+
<buildArgs>
483+
<buildArg>--add-opens java.base/java.util=ALL-UNNAMED</buildArg>
484+
<buildArg>--add-opens java.base/java.lang=ALL-UNNAMED</buildArg>
485+
<buildArg>--no-fallback</buildArg>
486+
<buildArg>--verbose</buildArg>
487+
<buildArg>--native-image-info</buildArg>
488+
<buildArg>-H:+UnlockExperimentalVMOptions</buildArg>
489+
<buildArg>-H:+ReportExceptionStackTraces</buildArg>
490+
</buildArgs>
491+
</configuration>
492+
</plugin>
453493
</plugins>
454494
</pluginManagement>
455495
<plugins>
@@ -635,6 +675,29 @@
635675
</plugins>
636676
</build>
637677
</profile>
678+
<!--
679+
The native profile configures surefire to pass add-opens flags via JDK_JAVA_OPTIONS.
680+
This is necessary because the native-maven-plugin's NativeExtension overwrites surefire's
681+
argLine when -Dagent=true is set (it adds its own argLine element for the tracing agent),
682+
which causes the add-opens flags from the jdk16 profile to be lost.
683+
JDK_JAVA_OPTIONS is picked up by the JVM automatically, bypassing the argLine override.
684+
-->
685+
<profile>
686+
<id>native</id>
687+
<build>
688+
<plugins>
689+
<plugin>
690+
<groupId>org.apache.maven.plugins</groupId>
691+
<artifactId>maven-surefire-plugin</artifactId>
692+
<configuration>
693+
<environmentVariables>
694+
<JDK_JAVA_OPTIONS>--add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED</JDK_JAVA_OPTIONS>
695+
</environmentVariables>
696+
</configuration>
697+
</plugin>
698+
</plugins>
699+
</build>
700+
</profile>
638701
</profiles>
639702

640703
</project>

powertools-cloudformation/pom.xml

Lines changed: 7 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -120,33 +120,7 @@
120120

121121
<profiles>
122122
<profile>
123-
<id>generate-graalvm-files</id>
124-
<dependencies>
125-
<dependency>
126-
<groupId>org.mockito</groupId>
127-
<artifactId>mockito-subclass</artifactId>
128-
<scope>test</scope>
129-
</dependency>
130-
</dependencies>
131-
<build>
132-
<plugins>
133-
<plugin>
134-
<groupId>org.apache.maven.plugins</groupId>
135-
<artifactId>maven-surefire-plugin</artifactId>
136-
<configuration>
137-
<argLine>
138-
-Dorg.graalvm.nativeimage.imagecode=agent
139-
-agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-cloudformation,experimental-class-define-support
140-
--add-opens java.base/java.util=ALL-UNNAMED
141-
--add-opens java.base/java.lang=ALL-UNNAMED
142-
</argLine>
143-
</configuration>
144-
</plugin>
145-
</plugins>
146-
</build>
147-
</profile>
148-
<profile>
149-
<id>graalvm-native</id>
123+
<id>native</id>
150124
<dependencies>
151125
<dependency>
152126
<groupId>org.mockito</groupId>
@@ -159,28 +133,15 @@
159133
<plugin>
160134
<groupId>org.graalvm.buildtools</groupId>
161135
<artifactId>native-maven-plugin</artifactId>
162-
<version>0.11.2</version>
163-
<extensions>true</extensions>
164-
<executions>
165-
<execution>
166-
<id>test-native</id>
167-
<goals>
168-
<goal>test</goal>
169-
</goals>
170-
<phase>test</phase>
171-
</execution>
172-
</executions>
173136
<configuration>
174137
<imageName>powertools-cloudformation</imageName>
175-
<buildArgs>
176-
<buildArg>--add-opens java.base/java.util=ALL-UNNAMED</buildArg>
177-
<buildArg>--add-opens java.base/java.lang=ALL-UNNAMED</buildArg>
138+
<agent>
139+
<metadataCopy>
140+
<outputDirectory>src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-cloudformation</outputDirectory>
141+
</metadataCopy>
142+
</agent>
143+
<buildArgs combine.children="append">
178144
<buildArg>--enable-url-protocols=http</buildArg>
179-
<buildArg>--no-fallback</buildArg>
180-
<buildArg>--verbose</buildArg>
181-
<buildArg>--native-image-info</buildArg>
182-
<buildArg>-H:+UnlockExperimentalVMOptions</buildArg>
183-
<buildArg>-H:+ReportExceptionStackTraces</buildArg>
184145
</buildArgs>
185146
</configuration>
186147
</plugin>

powertools-common/pom.xml

Lines changed: 6 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -93,33 +93,7 @@
9393
</dependencies>
9494
<profiles>
9595
<profile>
96-
<id>generate-graalvm-files</id>
97-
<dependencies>
98-
<dependency>
99-
<groupId>org.mockito</groupId>
100-
<artifactId>mockito-subclass</artifactId>
101-
<scope>test</scope>
102-
</dependency>
103-
</dependencies>
104-
<build>
105-
<plugins>
106-
<plugin>
107-
<groupId>org.apache.maven.plugins</groupId>
108-
<artifactId>maven-surefire-plugin</artifactId>
109-
<configuration>
110-
<argLine>
111-
-Dorg.graalvm.nativeimage.imagecode=agent
112-
-agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-common,experimental-class-define-support
113-
--add-opens java.base/java.util=ALL-UNNAMED
114-
--add-opens java.base/java.lang=ALL-UNNAMED
115-
</argLine>
116-
</configuration>
117-
</plugin>
118-
</plugins>
119-
</build>
120-
</profile>
121-
<profile>
122-
<id>graalvm-native</id>
96+
<id>native</id>
12397
<dependencies>
12498
<dependency>
12599
<groupId>org.mockito</groupId>
@@ -132,28 +106,13 @@
132106
<plugin>
133107
<groupId>org.graalvm.buildtools</groupId>
134108
<artifactId>native-maven-plugin</artifactId>
135-
<version>0.11.2</version>
136-
<extensions>true</extensions>
137-
<executions>
138-
<execution>
139-
<id>test-native</id>
140-
<goals>
141-
<goal>test</goal>
142-
</goals>
143-
<phase>test</phase>
144-
</execution>
145-
</executions>
146109
<configuration>
147110
<imageName>powertools-common</imageName>
148-
<buildArgs>
149-
<buildArg>--add-opens java.base/java.util=ALL-UNNAMED</buildArg>
150-
<buildArg>--add-opens java.base/java.lang=ALL-UNNAMED</buildArg>
151-
<buildArg>--no-fallback</buildArg>
152-
<buildArg>--verbose</buildArg>
153-
<buildArg>--native-image-info</buildArg>
154-
<buildArg>-H:+UnlockExperimentalVMOptions</buildArg>
155-
<buildArg>-H:+ReportExceptionStackTraces</buildArg>
156-
</buildArgs>
111+
<agent>
112+
<metadataCopy>
113+
<outputDirectory>src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-common</outputDirectory>
114+
</metadataCopy>
115+
</agent>
157116
</configuration>
158117
</plugin>
159118
</plugins>

0 commit comments

Comments
 (0)