members() {
+ return value.entrySet().stream()
+ .map(e -> new JsonMember(e.getKey(), e.getValue()))
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public void forEach(JsonTransform transform) {
+ members().forEach(member -> transform.accept(null, member));
+ }
+}
diff --git a/core/builder/src/main/java/io/quarkus/builder/json/JsonString.java b/core/builder/src/main/java/io/quarkus/builder/json/JsonString.java
new file mode 100644
index 0000000000000..5f13517539e05
--- /dev/null
+++ b/core/builder/src/main/java/io/quarkus/builder/json/JsonString.java
@@ -0,0 +1,30 @@
+package io.quarkus.builder.json;
+
+import java.util.Objects;
+
+public final class JsonString implements JsonValue {
+ private final String value;
+
+ public JsonString(String value) {
+ this.value = value;
+ }
+
+ public String value() {
+ return value;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ JsonString that = (JsonString) o;
+ return Objects.equals(value, that.value);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(value);
+ }
+}
diff --git a/core/builder/src/main/java/io/quarkus/builder/json/JsonValue.java b/core/builder/src/main/java/io/quarkus/builder/json/JsonValue.java
new file mode 100644
index 0000000000000..a990951d5679b
--- /dev/null
+++ b/core/builder/src/main/java/io/quarkus/builder/json/JsonValue.java
@@ -0,0 +1,4 @@
+package io.quarkus.builder.json;
+
+public interface JsonValue {
+}
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/NativeImageAgentConfigDirectoryBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/NativeImageAgentConfigDirectoryBuildItem.java
new file mode 100644
index 0000000000000..c85bd992bccb8
--- /dev/null
+++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/NativeImageAgentConfigDirectoryBuildItem.java
@@ -0,0 +1,25 @@
+package io.quarkus.deployment.builditem.nativeimage;
+
+import io.quarkus.builder.item.SimpleBuildItem;
+import io.quarkus.deployment.pkg.NativeConfig;
+
+/**
+ * Native configuration generated by native image agent can be integrated
+ * directly into subsequence native build steps,
+ * if the user enables {@link NativeConfig#agentConfigurationApply()}.
+ * This build item is used to transfer the native configuration folder path
+ * onto the {@link io.quarkus.deployment.pkg.steps.NativeImageBuildStep}.
+ * If the build item is passed,
+ * the directory is added to the native image build execution.
+ */
+public final class NativeImageAgentConfigDirectoryBuildItem extends SimpleBuildItem {
+ private final String directory;
+
+ public NativeImageAgentConfigDirectoryBuildItem(String directory) {
+ this.directory = directory;
+ }
+
+ public String getDirectory() {
+ return directory;
+ }
+}
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java
index 310090fd7c1ee..5f25613e770c1 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java
@@ -486,6 +486,19 @@ interface Debug {
*/
Compression compression();
+ /**
+ * Configuration files generated by the Quarkus build, using native image agent, are informative by default.
+ * In other words, the generated configuration files are presented in the build log but are not applied.
+ * When this option is set to true, generated configuration files are applied to the native executable building process.
+ *
+ * Enabling this option should be done with care, because it can make native image configuration and/or behaviour
+ * dependant on other non-obvious factors. For example, if the native image agent generated configuration was generated
+ * from running JVM unit tests, disabling test(s) can result in a different native image configuration being generated,
+ * which in turn can misconfigure the native executable or affect its behaviour in unintended ways.
+ */
+ @WithDefault("false")
+ boolean agentConfigurationApply();
+
@ConfigGroup
interface Compression {
/**
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildLocalContainerRunner.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildLocalContainerRunner.java
index 1afb99e41f7fd..c2ec3493c1694 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildLocalContainerRunner.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildLocalContainerRunner.java
@@ -11,40 +11,54 @@
import org.apache.commons.lang3.SystemUtils;
import io.quarkus.deployment.pkg.NativeConfig;
+import io.quarkus.deployment.util.ContainerRuntimeUtil.ContainerRuntime;
import io.quarkus.deployment.util.FileUtil;
public class NativeImageBuildLocalContainerRunner extends NativeImageBuildContainerRunner {
public NativeImageBuildLocalContainerRunner(NativeConfig nativeConfig) {
super(nativeConfig);
+ List containerRuntimeArgs = new ArrayList<>(Arrays.asList(baseContainerRuntimeArgs));
+ if (SystemUtils.IS_OS_LINUX && containerRuntime.isInWindowsWSL()) {
+ containerRuntimeArgs.add("--interactive");
+ }
+ containerRuntimeArgs.addAll(getVolumeAccessArguments(containerRuntime));
+ baseContainerRuntimeArgs = containerRuntimeArgs.toArray(baseContainerRuntimeArgs);
+ }
+
+ public static List getVolumeAccessArguments(ContainerRuntime containerRuntime) {
+ final List result = new ArrayList<>();
if (SystemUtils.IS_OS_LINUX || SystemUtils.IS_OS_MAC) {
- final ArrayList containerRuntimeArgs = new ArrayList<>(Arrays.asList(baseContainerRuntimeArgs));
- if (containerRuntime.isInWindowsWSL()) {
- containerRuntimeArgs.add("--interactive");
- }
if (containerRuntime.isDocker() && containerRuntime.isRootless()) {
- Collections.addAll(containerRuntimeArgs, "--user", String.valueOf(0));
+ Collections.addAll(result, "--user", String.valueOf(0));
} else {
String uid = getLinuxID("-ur");
String gid = getLinuxID("-gr");
if (uid != null && gid != null && !uid.isEmpty() && !gid.isEmpty()) {
- Collections.addAll(containerRuntimeArgs, "--user", uid + ":" + gid);
+ Collections.addAll(result, "--user", uid + ":" + gid);
if (containerRuntime.isPodman() && containerRuntime.isRootless()) {
// Needed to avoid AccessDeniedExceptions
- containerRuntimeArgs.add("--userns=keep-id");
+ result.add("--userns=keep-id");
}
}
}
- baseContainerRuntimeArgs = containerRuntimeArgs.toArray(baseContainerRuntimeArgs);
}
+ return result;
}
@Override
protected List getContainerRuntimeBuildArgs(Path outputDir) {
final List containerRuntimeArgs = super.getContainerRuntimeBuildArgs(outputDir);
String volumeOutputPath = outputDir.toAbsolutePath().toString();
+ addVolumeParameter(volumeOutputPath, NativeImageBuildStep.CONTAINER_BUILD_VOLUME_PATH, containerRuntimeArgs,
+ containerRuntime);
+ return containerRuntimeArgs;
+ }
+
+ public static void addVolumeParameter(String localPath, String remotePath, List args,
+ ContainerRuntime containerRuntime) {
if (SystemUtils.IS_OS_WINDOWS) {
- volumeOutputPath = FileUtil.translateToVolumePath(volumeOutputPath);
+ localPath = FileUtil.translateToVolumePath(localPath);
}
final String selinuxBindOption;
@@ -54,9 +68,7 @@ protected List getContainerRuntimeBuildArgs(Path outputDir) {
selinuxBindOption = ":z";
}
- Collections.addAll(containerRuntimeArgs, "-v",
- volumeOutputPath + ":" + NativeImageBuildStep.CONTAINER_BUILD_VOLUME_PATH + selinuxBindOption);
- return containerRuntimeArgs;
+ args.add("-v");
+ args.add(localPath + ":" + remotePath + selinuxBindOption);
}
-
}
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java
index bf483fae4621f..1c1d8f54b73e1 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java
@@ -28,6 +28,7 @@
import io.quarkus.deployment.builditem.SuppressNonRuntimeConfigChangedWarningBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ExcludeConfigBuildItem;
import io.quarkus.deployment.builditem.nativeimage.JPMSExportBuildItem;
+import io.quarkus.deployment.builditem.nativeimage.NativeImageAgentConfigDirectoryBuildItem;
import io.quarkus.deployment.builditem.nativeimage.NativeImageAllowIncompleteClasspathAggregateBuildItem;
import io.quarkus.deployment.builditem.nativeimage.NativeImageEnableModule;
import io.quarkus.deployment.builditem.nativeimage.NativeImageSecurityProviderBuildItem;
@@ -178,6 +179,7 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, LocalesBuildTimeCon
Optional processInheritIODisabled,
Optional processInheritIODisabledBuildItem,
List nativeImageFeatures,
+ Optional nativeImageAgentConfigDirectoryBuildItem,
NativeImageRunnerBuildItem nativeImageRunner) {
if (nativeConfig.debug().enabled()) {
copyJarSourcesToLib(outputTargetBuildItem, curateOutcomeBuildItem);
@@ -245,6 +247,7 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, LocalesBuildTimeCon
.setGraalVMVersion(graalVMVersion)
.setNativeImageFeatures(nativeImageFeatures)
.setContainerBuild(isContainerBuild)
+ .setNativeImageAgentConfigDirectory(nativeImageAgentConfigDirectoryBuildItem)
.build();
List nativeImageArgs = commandAndExecutable.args;
@@ -593,12 +596,19 @@ static class Builder {
private String nativeImageName;
private boolean classpathIsBroken;
private boolean containerBuild;
+ private Optional nativeImageAgentConfigDirectory = Optional.empty();
public Builder setNativeConfig(NativeConfig nativeConfig) {
this.nativeConfig = nativeConfig;
return this;
}
+ public Builder setNativeImageAgentConfigDirectory(
+ Optional nativeImageAgentConfigDirectory) {
+ this.nativeImageAgentConfigDirectory = nativeImageAgentConfigDirectory;
+ return this;
+ }
+
public Builder setLocalesBuildTimeConfig(LocalesBuildTimeConfig localesBuildTimeConfig) {
this.localesBuildTimeConfig = localesBuildTimeConfig;
return this;
@@ -983,6 +993,9 @@ public NativeImageInvokerInfo build() {
}
}
+ nativeImageAgentConfigDirectory
+ .ifPresent(dir -> nativeImageArgs.add("-H:ConfigurationFileDirectories=" + dir.getDirectory()));
+
for (ExcludeConfigBuildItem excludeConfig : excludeConfigs) {
nativeImageArgs.add("--exclude-config");
nativeImageArgs.add(excludeConfig.getJarFile());
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/ApplyNativeImageAgentConfigStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/ApplyNativeImageAgentConfigStep.java
new file mode 100644
index 0000000000000..ca68bac029d2f
--- /dev/null
+++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/ApplyNativeImageAgentConfigStep.java
@@ -0,0 +1,58 @@
+package io.quarkus.deployment.steps;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+
+import org.jboss.logging.Logger;
+
+import io.quarkus.deployment.annotations.BuildProducer;
+import io.quarkus.deployment.annotations.BuildStep;
+import io.quarkus.deployment.builditem.nativeimage.NativeImageAgentConfigDirectoryBuildItem;
+import io.quarkus.deployment.pkg.NativeConfig;
+import io.quarkus.deployment.pkg.builditem.BuildSystemTargetBuildItem;
+import io.quarkus.deployment.pkg.builditem.NativeImageSourceJarBuildItem;
+import io.quarkus.deployment.pkg.steps.NativeOrNativeSourcesBuild;
+
+/**
+ * This configuration step looks for native configuration folder generated
+ * with the native image agent running inside Quarkus integration tests.
+ * If the folder is detected and {@link NativeConfig#agentConfigurationApply()} is enabled,
+ * the folder's path is passed onto the {@link io.quarkus.deployment.pkg.steps.NativeImageBuildStep},
+ * wrapped inside a {@link NativeImageAgentConfigDirectoryBuildItem},
+ * so that the folder is added as a configuration folder for the native image process execution.
+ */
+public class ApplyNativeImageAgentConfigStep {
+ private static final Logger log = Logger.getLogger(ApplyNativeImageAgentConfigStep.class);
+
+ @BuildStep(onlyIf = NativeOrNativeSourcesBuild.class)
+ void transformConfig(NativeConfig nativeConfig,
+ BuildProducer nativeImageAgentConfigDirectoryProducer,
+ NativeImageSourceJarBuildItem nativeImageSourceJarBuildItem,
+ BuildSystemTargetBuildItem buildSystemTargetBuildItem) throws IOException {
+ final Path basePath = buildSystemTargetBuildItem.getOutputDirectory()
+ .resolve(Path.of("native-image-agent-final-config"));
+ if (basePath.toFile().exists() && nativeConfig.agentConfigurationApply()) {
+ final Path outputDir = nativeImageSourceJarBuildItem.getPath().getParent();
+ final String targetDirName = "native-image-agent-config";
+ final Path targetPath = outputDir.resolve(Path.of(targetDirName));
+ if (!targetPath.toFile().exists()) {
+ targetPath.toFile().mkdirs();
+ }
+ Files.copy(basePath.resolve("reflect-config.json"), targetPath.resolve("reflect-config.json"),
+ StandardCopyOption.REPLACE_EXISTING);
+ Files.copy(basePath.resolve("serialization-config.json"), targetPath.resolve("serialization-config.json"),
+ StandardCopyOption.REPLACE_EXISTING);
+ Files.copy(basePath.resolve("jni-config.json"), targetPath.resolve("jni-config.json"),
+ StandardCopyOption.REPLACE_EXISTING);
+ Files.copy(basePath.resolve("proxy-config.json"), targetPath.resolve("proxy-config.json"),
+ StandardCopyOption.REPLACE_EXISTING);
+ Files.copy(basePath.resolve("resource-config.json"), targetPath.resolve("resource-config.json"),
+ StandardCopyOption.REPLACE_EXISTING);
+
+ log.info("Applying native image agent generated files to current native executable build");
+ nativeImageAgentConfigDirectoryProducer.produce(new NativeImageAgentConfigDirectoryBuildItem(targetDirName));
+ }
+ }
+}
diff --git a/core/deployment/src/test/java/io/quarkus/deployment/pkg/TestNativeConfig.java b/core/deployment/src/test/java/io/quarkus/deployment/pkg/TestNativeConfig.java
index 0a0f28227bb26..4c276528b2494 100644
--- a/core/deployment/src/test/java/io/quarkus/deployment/pkg/TestNativeConfig.java
+++ b/core/deployment/src/test/java/io/quarkus/deployment/pkg/TestNativeConfig.java
@@ -230,6 +230,11 @@ public Compression compression() {
return null;
}
+ @Override
+ public boolean agentConfigurationApply() {
+ return false;
+ }
+
private class TestBuildImageConfig implements BuilderImageConfig {
private final String image;
private final ImagePullStrategy pull;
diff --git a/devtools/maven/src/main/java/io/quarkus/maven/GenerateCodeTestsMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/GenerateCodeTestsMojo.java
index a5a2644e8f16c..9cc14ccc24627 100644
--- a/devtools/maven/src/main/java/io/quarkus/maven/GenerateCodeTestsMojo.java
+++ b/devtools/maven/src/main/java/io/quarkus/maven/GenerateCodeTestsMojo.java
@@ -1,16 +1,100 @@
package io.quarkus.maven;
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.ResolutionScope;
+import io.quarkus.bootstrap.app.CuratedApplication;
+import io.quarkus.builder.Json;
+import io.quarkus.maven.dependency.ResolvedDependency;
+import io.quarkus.runtime.LaunchMode;
+
@Mojo(name = "generate-code-tests", defaultPhase = LifecyclePhase.GENERATE_TEST_SOURCES, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME, threadSafe = true)
public class GenerateCodeTestsMojo extends GenerateCodeMojo {
@Override
protected void doExecute() throws MojoExecutionException, MojoFailureException {
generateCode(getParentDirs(mavenProject().getTestCompileSourceRoots()),
path -> mavenProject().addTestCompileSourceRoot(path.toString()), true);
+
+ final String testProfile = System.getProperty("quarkus.test.integration-test-profile");
+ if ("test-with-native-agent".equals(testProfile)) {
+ generateNativeAgentFilters();
+ }
+ }
+
+ private void generateNativeAgentFilters() throws MojoExecutionException {
+ getLog().debug("Generate native image agent filters");
+
+ // Get packages to exclude
+ Collection commonExcludePackageNames = getCommonExcludePackageNames();
+
+ // Generate json using the packages
+ generateNativeAgentFilter(commonExcludePackageNames,
+ Path.of(mavenProject().getModel().getBuild().getDirectory(),
+ "quarkus-caller-filter.json"));
+ generateNativeAgentFilter(commonExcludePackageNames,
+ Path.of(mavenProject().getModel().getBuild().getDirectory(),
+ "quarkus-access-filter.json"));
+ }
+
+ private Collection getAccessExcludePackageNames(Collection commonExcludePackageNames) {
+ final Set result = new HashSet<>(commonExcludePackageNames);
+ // Quarkus bootstrap depends on CRaC on startup and its APIs do reflection lookups.
+ // These should be excluded from generated configuration because Quarkus takes care of it.
+ result.add("javax.crac");
+ result.add("jdk.crac");
+ return result;
+ }
+
+ private void generateNativeAgentFilter(Collection packageNames, Path path) throws MojoExecutionException {
+ final Json.JsonObjectBuilder result = Json.object();
+
+ final Json.JsonArrayBuilder rules = Json.array();
+ packageNames.stream()
+ .map(packageName -> Json.object().put("excludeClasses", packageName + ".**"))
+ .forEach(rules::add);
+ result.put("rules", rules);
+
+ final Json.JsonArrayBuilder regexRules = Json.array();
+ regexRules.add(Json.object().put("excludeClasses", ".*_Bean"));
+ result.put("regexRules", regexRules);
+
+ try (BufferedWriter writer = new BufferedWriter(new FileWriter(path.toFile(), StandardCharsets.UTF_8))) {
+ result.appendTo(writer);
+ } catch (IOException e) {
+ throw new MojoExecutionException("Unable to write quarkus native image agent caller filter to " + path, e);
+ }
+ }
+
+ private Collection getDependencies() throws MojoExecutionException {
+ try (CuratedApplication curatedApplication = bootstrapApplication(LaunchMode.TEST)) {
+ return curatedApplication.getApplicationModel().getDependencies();
+ } catch (Exception any) {
+ throw new MojoExecutionException("Quarkus native image agent filter generation phase has failed", any);
+ }
+ }
+
+ private Set getCommonExcludePackageNames() {
+ Set packageNames = new HashSet<>();
+ // Any calls that access or originate in these packages
+ // that require native configuration should be handled by Quarkus.
+ packageNames.add("io.netty");
+ packageNames.add("io.quarkus");
+ packageNames.add("io.smallrye");
+ packageNames.add("io.vertx");
+ packageNames.add("jakarta");
+ packageNames.add("org.jboss");
+ return packageNames;
}
}
diff --git a/devtools/maven/src/main/java/io/quarkus/maven/NativeImageAgentMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/NativeImageAgentMojo.java
new file mode 100644
index 0000000000000..69dcb639c28ab
--- /dev/null
+++ b/devtools/maven/src/main/java/io/quarkus/maven/NativeImageAgentMojo.java
@@ -0,0 +1,123 @@
+package io.quarkus.maven;
+
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.Arrays;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.ResolutionScope;
+
+import io.quarkus.builder.Json;
+import io.quarkus.builder.JsonReader;
+import io.quarkus.builder.JsonTransform;
+import io.quarkus.builder.json.JsonMember;
+import io.quarkus.builder.json.JsonObject;
+import io.quarkus.builder.json.JsonString;
+import io.quarkus.builder.json.JsonValue;
+
+@Mojo(name = "native-image-agent", defaultPhase = LifecyclePhase.POST_INTEGRATION_TEST, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME, threadSafe = true)
+public class NativeImageAgentMojo extends QuarkusBootstrapMojo {
+
+ private final Pattern resourceSkipPattern;
+
+ public NativeImageAgentMojo() {
+ // Exclude resource configuration for resources that Quarkus takes care of registering.
+ resourceSkipPattern = discardPattern("application.properties", "jakarta", "jboss",
+ "logging.properties", "microprofile",
+ "quarkus", "slf4j", "smallrye", "vertx");
+ }
+
+ @Override
+ protected boolean beforeExecute() throws MojoExecutionException, MojoFailureException {
+ // Only execute transformation if integration tests were run in JVM mode
+ return !QuarkusBootstrapMojo.isNativeProfileEnabled(mavenProject());
+ }
+
+ @Override
+ protected void doExecute() throws MojoExecutionException, MojoFailureException {
+ final String dirName = "native-image-agent-base-config";
+ final Path basePath = buildDir().toPath().resolve(Path.of(dirName));
+ getLog().debug("Checking if native image agent config folder exits at " + basePath);
+ if (basePath.toFile().exists()) {
+ try {
+ final Path targetPath = buildDir().toPath().resolve(Path.of("native-image-agent-final-config"));
+ if (!targetPath.toFile().exists()) {
+ targetPath.toFile().mkdirs();
+ }
+ getLog().debug("Native image agent config folder exits, copy and transform to " + targetPath);
+ final Path reflectConfigJsonPath = basePath.resolve("reflect-config.json");
+ if (reflectConfigJsonPath.toFile().exists()) {
+ Files.copy(reflectConfigJsonPath, targetPath.resolve("reflect-config.json"),
+ StandardCopyOption.REPLACE_EXISTING);
+ Files.copy(basePath.resolve("serialization-config.json"), targetPath.resolve("serialization-config.json"),
+ StandardCopyOption.REPLACE_EXISTING);
+ Files.copy(basePath.resolve("jni-config.json"), targetPath.resolve("jni-config.json"),
+ StandardCopyOption.REPLACE_EXISTING);
+ Files.copy(basePath.resolve("proxy-config.json"), targetPath.resolve("proxy-config.json"),
+ StandardCopyOption.REPLACE_EXISTING);
+ transformJsonObject(basePath, "resource-config.json", targetPath,
+ JsonTransform.dropping(this::discardResource));
+
+ if (getLog().isInfoEnabled()) {
+ getLog().info("Discovered native image agent generated files in " + targetPath);
+ }
+ } else {
+ final Path reflectOriginsTxtPath = basePath.resolve("reflect-origins.txt");
+ if (reflectOriginsTxtPath.toFile().exists()) {
+ getLog().info("Native image agent configuration origin files exist, inspect them manually inside "
+ + basePath);
+ }
+ }
+ } catch (IOException e) {
+ throw new MojoExecutionException("Failed to transform native image agent configuration", e);
+ }
+ } else {
+ getLog().info("Missing " + dirName + " directory with native image agent configuration to transform");
+ }
+ }
+
+ private void transformJsonObject(Path base, String name, Path target, JsonTransform transform) throws IOException {
+ getLog().debug("Discarding resources from native image configuration that match the following regular expression: "
+ + resourceSkipPattern);
+ final String original = Files.readString(base.resolve(name));
+ final JsonObject jsonRead = JsonReader.of(original).read();
+ final Json.JsonObjectBuilder jsonBuilder = Json.object(false, true);
+ jsonBuilder.transform(jsonRead, transform);
+
+ try (BufferedWriter writer = new BufferedWriter(
+ new FileWriter(target.resolve(name).toFile(), StandardCharsets.UTF_8))) {
+ jsonBuilder.appendTo(writer);
+ }
+ }
+
+ private boolean discardResource(JsonValue value) {
+ if (value instanceof JsonMember) {
+ final JsonMember member = (JsonMember) value;
+ if ("pattern".equals(member.attribute().value())) {
+ final JsonString memberValue = (JsonString) member.value();
+ final boolean discarded = resourceSkipPattern.matcher(memberValue.value()).find();
+ if (discarded) {
+ getLog().debug("Discarded included resource with pattern: " + memberValue.value());
+ }
+ return discarded;
+ }
+ }
+
+ return false;
+ }
+
+ private static Pattern discardPattern(String... ignoredElements) {
+ final String pattern = Arrays.stream(ignoredElements).collect(Collectors.joining("|", ".*(", ").*"));
+ return Pattern.compile(pattern);
+ }
+}
diff --git a/devtools/maven/src/main/java/io/quarkus/maven/QuarkusBootstrapMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/QuarkusBootstrapMojo.java
index 0a70ff33f9bfb..1ead48cabc663 100644
--- a/devtools/maven/src/main/java/io/quarkus/maven/QuarkusBootstrapMojo.java
+++ b/devtools/maven/src/main/java/io/quarkus/maven/QuarkusBootstrapMojo.java
@@ -326,7 +326,7 @@ protected boolean setNativeEnabledIfNativeProfileEnabled() {
}
}
- private boolean isNativeProfileEnabled(MavenProject mavenProject) {
+ static boolean isNativeProfileEnabled(MavenProject mavenProject) {
// gotcha: mavenProject.getActiveProfiles() does not always contain all active profiles (sic!),
// but getInjectedProfileIds() does (which has to be "flattened" first)
Stream activeProfileIds = mavenProject.getInjectedProfileIds().values().stream().flatMap(List::stream);
@@ -334,6 +334,6 @@ private boolean isNativeProfileEnabled(MavenProject mavenProject) {
return true;
}
// recurse into parent (if available)
- return Optional.ofNullable(mavenProject.getParent()).map(this::isNativeProfileEnabled).orElse(false);
+ return Optional.ofNullable(mavenProject.getParent()).map(QuarkusBootstrapMojo::isNativeProfileEnabled).orElse(false);
}
}
diff --git a/docs/src/main/asciidoc/native-reference.adoc b/docs/src/main/asciidoc/native-reference.adoc
index 8c7d741a5a8b1..f6b202af45199 100644
--- a/docs/src/main/asciidoc/native-reference.adoc
+++ b/docs/src/main/asciidoc/native-reference.adoc
@@ -528,6 +528,283 @@ ${FG_HOME}/stackcollapse.pl < out.stacks | ${FG_HOME}/flamegraph.pl \
--color=mem --title="mmap munmap Flame Graph" --countname="calls" > out.svg
----
+[[native-image-agent-integration]]
+== Native Image Tracing Agent Integration
+
+Quarkus users that want to integrate new libraries/components into native image process
+(e.g. link:https://github.com/hierynomus/smbj[smbj]),
+or want to use JDK APIs that require extensive native image configuration to work (e.g. graphical user interfaces),
+face a considerable challenge coming up with the native image configuration to make their use cases work.
+These users can tweak their applications to run in JVM mode with the native image agent in order to
+auto-generate native image configuration that will help them get a head start getting applications to work as native executables.
+
+The native image tracing agent is a JVM tool interface (JVMTI) agent available within both GraalVM and Mandrel that
+tracks all usages of dynamic features such as reflection, JNI, dynamic proxies, access classpath resources...etc,
+during an application's regular JVM execution.
+When the JVM stops, it dumps the information on the dynamic features used during the run
+onto a collection of native image configuration files that can be used in subsequent native image builds.
+
+Using the agent and applying the generated data can be difficult for Quarkus users.
+First, the agent can be cumbersome because it requires the JVM arguments to be modified,
+and the generated configuration needs to be placed in a specific location such that the subsequent native image builds picks them up.
+Secondly, the native image configuration produced contains a lot of superfluous configuration that the Quarkus integration takes care of.
+
+Native image tracing agent integration is included in Quarkus to make the agent easier to consume.
+In this section you will learn about the integration and how to apply it to your Quarkus application.
+
+[NOTE]
+====
+The integration is currently only available for Maven applications.
+link:https://github.com/quarkusio/quarkus/issues/40361[Gradle integration] will follow up.
+====
+
+=== Integration Testing with the Tracing Agent
+
+Quarkus users can now run JVM mode integration tests on Quarkus Maven applications transparently with the native image tracing agent.
+To do this make sure a container runtime is available,
+because JVM mode integration tests will run using the JVM within the default Mandrel builder container image.
+This image contains the agent libraries required to produce native image configuration,
+hence avoiding the need for a local Mandrel or GraalVM installation.
+
+[TIP]
+====
+It is highly recommended to align the Mandrel version used in integration testing
+with the Mandrel version used to build native executables.
+Doing in-container native builds with the default Mandrel builder image,
+is the safest way to keep both versions aligned.
+====
+
+Additionally make sure that the `native-image-agent` goal is present in the `quarkus-maven-plugin` configuration:
+
+[source,bash]
+----
+
+ ${quarkus.platform.group-id}
+ quarkus-maven-plugin
+ ...
+
+
+
+ ...
+ native-image-agent
+
+
+
+
+----
+
+With a container runtime running,
+invoke Maven's `verify` goal with `-DskipITs=false -Dquarkus.test.integration-test-profile=test-with-native-agent` to run the JVM mode integration tests and
+generate the native image configuration.
+For example:
+
+[source,bash]
+----
+$ ./mvnw verify -DskipITs=false -Dquarkus.test.integration-test-profile=test-with-native-agent
+...
+[INFO] --- failsafe:3.2.5:integration-test (default) @ new-project ---
+...
+[INFO] -------------------------------------------------------
+[INFO] T E S T S
+[INFO] -------------------------------------------------------
+[INFO] Running org.acme.GreetingResourceIT
+2024-05-14 16:29:53,941 INFO [io.qua.tes.com.DefaultDockerContainerLauncher] (main) Executing "podman run --name quarkus-integration-test-PodgW -i --rm --user 501:20 -p 8081:8081 -p 8444:8444 --entrypoint java -v /tmp/new-project/target:/project --env QUARKUS_LOG_CATEGORY__IO_QUARKUS__LEVEL=INFO --env QUARKUS_HTTP_PORT=8081 --env QUARKUS_HTTP_SSL_PORT=8444 --env TEST_URL=http://localhost:8081 --env QUARKUS_PROFILE=test-with-native-agent --env QUARKUS_TEST_INTEGRATION_TEST_PROFILE=test-with-native-agent quay.io/quarkus/ubi-quarkus-mandrel-builder-image:jdk-21 -agentlib:native-image-agent=access-filter-file=quarkus-access-filter.json,caller-filter-file=quarkus-caller-filter.json,config-output-dir=native-image-agent-base-config, -jar quarkus-app/quarkus-run.jar"
+...
+[INFO]
+[INFO] --- quarkus:999-SNAPSHOT:native-image-agent (default) @ new-project ---
+[INFO] Discovered native image agent generated files in /tmp/new-project/target/native-image-agent-final-config
+[INFO]
+...
+----
+
+When the Maven invocation completes,
+you can inspect the generated configuration in the `target/native-image-agent-final-config` folder:
+
+[source,bash]
+----
+$ cat ./target/native-image-agent-final-config/reflect-config.json
+[
+...
+{
+ "name":"org.acme.Alice",
+ "methods":[{"name":"","parameterTypes":[] }, {"name":"sayMyName","parameterTypes":[] }]
+},
+{
+ "name":"org.acme.Bob"
+},
+...
+]
+----
+
+=== Informative By Default
+
+By default the generated native image configuration files are not used by subsequent native image building processes.
+This precaution is taken to avoid situations where seemingly unrelated actions have unintended consequences on the native executable produced,
+e.g. disabling randomly failing tests.
+
+Quarkus users are free to copy the files from the folder reported in the build,
+store them under source control and evolve as needed.
+Ideally these files should be stored under the `src/main/resources/META-INF/native-image//`` folder,
+in which case the native image process will automatically pick them up.
+
+[WARNING]
+====
+If managing native image agent configuration files manually,
+it is highly recommended to regenerate them each time a Mandrel version update occurs,
+because the configuration necessary to make the application work might have varied due to internal Mandrel changes.
+====
+
+It is possible to instruct Quarkus to optionally apply the generated native image configuration files into subsequent native image processes,
+by setting the -Dquarkus.native.agent-configuration-apply` property.
+This can be useful to verify that the native integration tests work as expected,
+assuming that the JVM unit tests have generated the correct native image configuration.
+The typical workflow here would be to first run the integration tests with the native image agent as shown in the previous section:
+
+[source,bash]
+----
+$ ./mvnw verify -DskipITs=false -Dquarkus.test.integration-test-profile=test-with-native-agent
+...
+[INFO] --- quarkus:999-SNAPSHOT:native-image-agent (default) @ new-project ---
+[INFO] Discovered native image agent generated files in /tmp/new-project/target/native-image-agent-final-config
+----
+
+And then request a native build passing in the configuration apply flag.
+A message during the native build process will indicate that the native image agent generated configuration files are being applied:
+
+[source,bash]
+----
+$ ./mvnw verify -Dnative -Dquarkus.native.agent-configuration-apply
+...
+[INFO] --- quarkus:999-SNAPSHOT:build (default) @ new-project ---
+[INFO] [io.quarkus.deployment.pkg.steps.JarResultBuildStep] Building native image source jar: /tmp/new-project/target/new-project-1.0.0-SNAPSHOT-native-image-source-jar/new-project-1.0.0-SNAPSHOT-runner.jar
+[INFO] [io.quarkus.deployment.steps.ApplyNativeImageAgentConfigStep] Applying native image agent generated files to current native executable build
+----
+
+=== Debugging the Tracing Agent Integration
+
+If the generated native image agent configuration is not satisfactory,
+more information can be obtained using any of the following techniques:
+
+==== Debugging Filters
+
+Quarkus generates native image tracing agent configuration filters.
+These filters exclude commonly used packages for which Quarkus already applies the necessary configuration.
+
+If native image agent is generating a configuration that it’s not working as expected,
+you should check that the configuration files include the expected information.
+For example, if some method happens to be accessed via reflection at runtime and you get an error,
+you want to verify that the configuration file contains a reflection entry for that method.
+
+If the entry is missing, it could be that some call path is being filtered that maybe shouldn’t have been.
+To verify that, inspect the contents of `target/quarkus-caller-filter.json` and `target/quarkus-access-filter.json` files,
+and confirm that the class and/or package making the call or being accessed is not being filtered out.
+
+If the missing entry is related to some resource,
+you should inspect the Quarkus build debug output and verify which resource patterns are being discarded, e.g.
+
+[source,bash]
+----
+$ ./mvnw -X verify -DskipITs=false -Dquarkus.test.integration-test-profile=test-with-native-agent
+...
+[INFO] --- quarkus:999-SNAPSHOT:native-image-agent (default) @ new-project ---
+...
+[DEBUG] Discarding resources from native image configuration that match the following regular expression: .*(application.properties|jakarta|jboss|logging.properties|microprofile|quarkus|slf4j|smallrye|vertx).*
+[DEBUG] Discarded included resource with pattern: \\QMETA-INF/microprofile-config.properties\\E
+[DEBUG] Discarded included resource with pattern: \\QMETA-INF/services/io.quarkus.arc.ComponentsProvider\\E
+...
+----
+
+==== Tracing Agent Logging
+
+The native image tracing agent can log the method invocations that result in the generated configuration to a JSON file.
+This can help understand why a configuration entry is generated.
+To enable this logging,
+`-Dquarkus.test.native.agent.output.property.name=trace-output` and
+`-Dquarkus.test.native.agent.output.property.value=native-image-agent-trace-file.json`
+system properties need to be added.
+For example:
+
+[source,bash]
+----
+$ ./mvnw verify -DskipITs=false \
+ -Dquarkus.test.integration-test-profile=test-with-native-agent \
+ -Dquarkus.test.native.agent.output.property.name=trace-output \
+ -Dquarkus.test.native.agent.output.property.value=native-image-agent-trace-file.json
+----
+
+When trace output is configured, no native image configuration is generated,
+and instead a `target/native-image-agent-trace-file.json` file is generated that contains trace information.
+For example:
+
+[source,json]
+----
+[
+{"tracer":"meta", "event":"initialization", "version":"1"},
+{"tracer":"meta", "event":"phase_change", "phase":"start"},
+{"tracer":"jni", "function":"FindClass", "caller_class":"java.io.ObjectStreamClass", "result":true, "args":["java/lang/NoSuchMethodError"]},
+...
+{"tracer":"reflect", "function":"findConstructorHandle", "class":"io.vertx.core.impl.VertxImpl$1$1$$Lambda/0x000000f80125f4e8", "caller_class":"java.lang.invoke.InnerClassLambdaMetafactory", "result":true, "args":[["io.vertx.core.Handler"]]},
+{"tracer":"meta", "event":"phase_change", "phase":"dead"},
+{"tracer":"meta", "event":"phase_change", "phase":"unload"}
+]
+----
+
+Unfortunately the trace output does not take into account the applied configuration filters,
+so the output contains all configuration decisions made by the agent.
+This is unlikely to change in the near future
+(see link:https://github.com/oracle/graal/issues/7635[oracle/graal#7635]).
+
+==== Configuration With Origins (Experimental)
+
+Alternative to the trace output,
+it is possible to configure the native image agent with an experimental flag that shows the origins of the configuration entries.
+You can enable that with the following additional system property:
+
+[source,bash]
+----
+$ ./mvnw verify -DskipITs=false \
+ -Dquarkus.test.integration-test-profile=test-with-native-agent \
+ -Dquarkus.test.native.agent.additional.args=experimental-configuration-with-origins
+----
+
+The origins of the configuration entries can be found in text files inside the `target/native-image-agent-base-config` folder.
+For example:
+
+[source,bash]
+----
+$ cat target/native-image-agent-base-config/reflect-origins.txt
+root
+├── java.lang.Thread#run()
+│ └── java.lang.Thread#runWith(java.lang.Object,java.lang.Runnable)
+│ └── io.netty.util.concurrent.FastThreadLocalRunnable#run()
+│ └── org.jboss.threads.ThreadLocalResettingRunnable#run()
+│ └── org.jboss.threads.DelegatingRunnable#run()
+│ └── org.jboss.threads.EnhancedQueueExecutor$ThreadBody#run()
+│ └── org.jboss.threads.EnhancedQueueExecutor$Task#run()
+│ └── org.jboss.threads.EnhancedQueueExecutor$Task#doRunWith(java.lang.Runnable,java.lang.Object)
+│ └── io.quarkus.vertx.core.runtime.VertxCoreRecorder$14#runWith(java.lang.Runnable,java.lang.Object)
+│ └── org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext#run()
+│ └── io.quarkus.resteasy.reactive.server.runtime.QuarkusResteasyReactiveRequestContext#invokeHandler(int)
+│ └── org.jboss.resteasy.reactive.server.handlers.InvocationHandler#handle(org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext)
+│ └── org.acme.GreetingResource$quarkusrestinvoker$greeting_709ef95cd764548a2bbac83843a7f4cdd8077016#invoke(java.lang.Object,java.lang.Object[])
+│ └── org.acme.GreetingResource#greeting(java.lang.String)
+│ └── org.acme.GreetingService_ClientProxy#greeting(java.lang.String)
+│ └── org.acme.GreetingService#greeting(java.lang.String)
+│ ├── java.lang.Class#forName(java.lang.String) - [ { "name":"org.acme.Alice" }, { "name":"org.acme.Bob" } ]
+│ ├── java.lang.Class#getDeclaredConstructor(java.lang.Class[]) - [ { "name":"org.acme.Alice", "methods":[{"name":"","parameterTypes":[] }] } ]
+│ ├── java.lang.reflect.Constructor#newInstance(java.lang.Object[]) - [ { "name":"org.acme.Alice", "methods":[{"name":"","parameterTypes":[] }] } ]
+│ ├── java.lang.reflect.Method#invoke(java.lang.Object,java.lang.Object[]) - [ { "name":"org.acme.Alice", "methods":[{"name":"sayMyName","parameterTypes":[] }] } ]
+│ └── java.lang.Class#getMethod(java.lang.String,java.lang.Class[]) - [ { "name":"org.acme.Alice", "methods":[{"name":"sayMyName","parameterTypes":[] }] } ]
+...
+----
+
+==== Debugging With GDB
+
+The native image agent itself is a native executable produced with GraalVM that uses JVMTI to intercept the calls that require native image configuration.
+As a last resort, it is possible to debug the native image agent with GDB,
+see link:https://github.com/oracle/graal/blob/master/substratevm/src/com.oracle.svm.agent/README.md[here]
+for instructions on how to do that.
+
[[inspecting-and-debugging]]
== Inspecting and Debugging Native Executables
This debugging guide provides further details on debugging issues in Quarkus native executables that might arise during development or production.
diff --git a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/buildtool/maven/base/pom.tpl.qute.xml b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/buildtool/maven/base/pom.tpl.qute.xml
index ac046389bab46..511b172afc41b 100644
--- a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/buildtool/maven/base/pom.tpl.qute.xml
+++ b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/buildtool/maven/base/pom.tpl.qute.xml
@@ -140,6 +140,9 @@
build
generate-code
generate-code-tests
+ {#if generate-native}
+ native-image-agent
+ {/if}
diff --git a/independent-projects/tools/devtools-testing/src/test/resources/__snapshots__/QuarkusCodestartGenerationTest/generateDefault/pom.xml b/independent-projects/tools/devtools-testing/src/test/resources/__snapshots__/QuarkusCodestartGenerationTest/generateDefault/pom.xml
index bf5dd9b3afc09..5eb35bfd30a83 100644
--- a/independent-projects/tools/devtools-testing/src/test/resources/__snapshots__/QuarkusCodestartGenerationTest/generateDefault/pom.xml
+++ b/independent-projects/tools/devtools-testing/src/test/resources/__snapshots__/QuarkusCodestartGenerationTest/generateDefault/pom.xml
@@ -54,6 +54,7 @@
build
generate-code
generate-code-tests
+ native-image-agent
diff --git a/independent-projects/tools/devtools-testing/src/test/resources/__snapshots__/QuarkusCodestartGenerationTest/generateMavenWithCustomDep/pom.xml b/independent-projects/tools/devtools-testing/src/test/resources/__snapshots__/QuarkusCodestartGenerationTest/generateMavenWithCustomDep/pom.xml
index 78a1232f844a2..4479635a0f786 100644
--- a/independent-projects/tools/devtools-testing/src/test/resources/__snapshots__/QuarkusCodestartGenerationTest/generateMavenWithCustomDep/pom.xml
+++ b/independent-projects/tools/devtools-testing/src/test/resources/__snapshots__/QuarkusCodestartGenerationTest/generateMavenWithCustomDep/pom.xml
@@ -69,6 +69,7 @@
build
generate-code
generate-code-tests
+ native-image-agent
diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/NativeAgentIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/NativeAgentIT.java
new file mode 100644
index 0000000000000..1e39c866fd9f1
--- /dev/null
+++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/NativeAgentIT.java
@@ -0,0 +1,33 @@
+package io.quarkus.maven.it;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.maven.shared.invoker.MavenInvocationException;
+import org.junit.jupiter.api.Test;
+
+import io.quarkus.maven.it.verifier.MavenProcessInvocationResult;
+import io.quarkus.maven.it.verifier.RunningInvoker;
+
+@EnableForNative
+public class NativeAgentIT extends MojoTestBase {
+
+ @Test
+ public void testRunIntegrationTests() throws MavenInvocationException, IOException, InterruptedException {
+ final File testDir = initProject("projects/native-agent-integration");
+ final RunningInvoker running = new RunningInvoker(testDir, false);
+
+ MavenProcessInvocationResult runJvmITsWithAgent = running.execute(
+ List.of("clean", "verify", "-DskipITs=false", "-Dquarkus.test.integration-test-profile=test-with-native-agent"),
+ Map.of());
+ assertThat(runJvmITsWithAgent.getProcess().waitFor()).isZero();
+
+ MavenProcessInvocationResult runNativeITs = running
+ .execute(List.of("verify", "-Dnative", "-Dquarkus.native.agent-configuration-apply"), Map.of());
+ assertThat(runNativeITs.getProcess().waitFor()).isZero();
+ }
+}
\ No newline at end of file
diff --git a/integration-tests/maven/src/test/resources-filtered/projects/native-agent-integration/pom.xml b/integration-tests/maven/src/test/resources-filtered/projects/native-agent-integration/pom.xml
new file mode 100644
index 0000000000000..d89154e0c4c31
--- /dev/null
+++ b/integration-tests/maven/src/test/resources-filtered/projects/native-agent-integration/pom.xml
@@ -0,0 +1,117 @@
+
+
+ 4.0.0
+ org.acme
+ acme
+ 1.0-SNAPSHOT
+
+ io.quarkus
+ quarkus-bom
+ @project.version@
+ @project.version@
+ ${compiler-plugin.version}
+ ${version.surefire.plugin}
+ ${maven.compiler.source}
+ ${maven.compiler.target}
+ UTF-8
+
+
+
+
+ \${quarkus.platform.group-id}
+ \${quarkus.platform.artifact-id}
+ \${quarkus.platform.version}
+ pom
+ import
+
+
+
+
+
+
+ io.quarkus
+ quarkus-resteasy
+
+
+ io.quarkus
+ quarkus-junit5
+ test
+
+
+ io.rest-assured
+ rest-assured
+ test
+
+
+
+
+
+ maven-compiler-plugin
+ \${compiler-plugin.version}
+
+
+ -parameters
+
+
+
+
+ maven-surefire-plugin
+ \${surefire-plugin.version}
+
+
+ org.jboss.logmanager.LogManager
+ \${maven.home}
+
+
+
+
+ maven-failsafe-plugin
+ \${surefire-plugin.version}
+
+
+
+ integration-test
+ verify
+
+
+
+ \${project.build.directory}/\${project.build.finalName}-runner
+ org.jboss.logmanager.LogManager
+ \${maven.home}
+
+
+
+
+
+
+ io.quarkus
+ quarkus-maven-plugin
+ \${quarkus-plugin.version}
+
+
+
+ build
+ generate-code
+ generate-code-tests
+ native-image-agent
+
+
+
+
+
+
+
+
+ native
+
+
+ native
+
+
+
+ native
+
+
+
+
diff --git a/integration-tests/maven/src/test/resources-filtered/projects/native-agent-integration/src/main/java/org/acme/Alice.java b/integration-tests/maven/src/test/resources-filtered/projects/native-agent-integration/src/main/java/org/acme/Alice.java
new file mode 100644
index 0000000000000..af279cc89a22c
--- /dev/null
+++ b/integration-tests/maven/src/test/resources-filtered/projects/native-agent-integration/src/main/java/org/acme/Alice.java
@@ -0,0 +1,9 @@
+package org.acme;
+
+public class Alice
+{
+ public String sayMyName()
+ {
+ return "Alice";
+ }
+}
diff --git a/integration-tests/maven/src/test/resources-filtered/projects/native-agent-integration/src/main/java/org/acme/Carol.java b/integration-tests/maven/src/test/resources-filtered/projects/native-agent-integration/src/main/java/org/acme/Carol.java
new file mode 100644
index 0000000000000..fa3672ee85b25
--- /dev/null
+++ b/integration-tests/maven/src/test/resources-filtered/projects/native-agent-integration/src/main/java/org/acme/Carol.java
@@ -0,0 +1,12 @@
+package org.acme;
+
+import io.quarkus.runtime.annotations.RegisterForReflection;
+
+@RegisterForReflection
+public class Carol
+{
+ public String sayMyName()
+ {
+ return "Carol";
+ }
+}
diff --git a/integration-tests/maven/src/test/resources-filtered/projects/native-agent-integration/src/main/java/org/acme/GreetingResource.java b/integration-tests/maven/src/test/resources-filtered/projects/native-agent-integration/src/main/java/org/acme/GreetingResource.java
new file mode 100644
index 0000000000000..87516b9396749
--- /dev/null
+++ b/integration-tests/maven/src/test/resources-filtered/projects/native-agent-integration/src/main/java/org/acme/GreetingResource.java
@@ -0,0 +1,26 @@
+package org.acme;
+
+import io.quarkus.logging.Log;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+
+@Path("/hello")
+public class GreetingResource
+{
+ @Inject
+ GreetingService service;
+
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ @Path("/{name}")
+ // Add @PathParam to avoid getting an empty name
+ public String greeting(@PathParam("name") String name)
+ {
+ Log.infof("Call greeting service with %s", name);
+ return service.greeting(name);
+ }
+}
diff --git a/integration-tests/maven/src/test/resources-filtered/projects/native-agent-integration/src/main/java/org/acme/GreetingService.java b/integration-tests/maven/src/test/resources-filtered/projects/native-agent-integration/src/main/java/org/acme/GreetingService.java
new file mode 100644
index 0000000000000..9c75f9df1eef5
--- /dev/null
+++ b/integration-tests/maven/src/test/resources-filtered/projects/native-agent-integration/src/main/java/org/acme/GreetingService.java
@@ -0,0 +1,28 @@
+package org.acme;
+
+import io.quarkus.logging.Log;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.ws.rs.NotFoundException;
+
+import java.lang.reflect.Method;
+
+@ApplicationScoped
+public class GreetingService
+{
+ public String greeting(String name)
+ {
+ try
+ {
+ final Class> clazz = Class.forName("org.acme." + name);
+ final Method method = clazz.getMethod("sayMyName");
+ final Object obj = clazz.getDeclaredConstructor().newInstance();
+ final Object result = method.invoke(obj);
+ return "Hello " + result;
+ }
+ catch (Exception e)
+ {
+ Log.debugf(e, "Unable to create a greeting");
+ throw new NotFoundException("Unknown name: " + name);
+ }
+ }
+}
diff --git a/integration-tests/maven/src/test/resources-filtered/projects/native-agent-integration/src/test/java/org/acme/GreetingResourceIT.java b/integration-tests/maven/src/test/resources-filtered/projects/native-agent-integration/src/test/java/org/acme/GreetingResourceIT.java
new file mode 100644
index 0000000000000..26bc65acea02b
--- /dev/null
+++ b/integration-tests/maven/src/test/resources-filtered/projects/native-agent-integration/src/test/java/org/acme/GreetingResourceIT.java
@@ -0,0 +1,8 @@
+package org.acme;
+
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+
+@QuarkusIntegrationTest
+public class GreetingResourceIT extends GreetingResourceTest {
+ // Execute the same tests but in packaged mode.
+}
diff --git a/integration-tests/maven/src/test/resources-filtered/projects/native-agent-integration/src/test/java/org/acme/GreetingResourceTest.java b/integration-tests/maven/src/test/resources-filtered/projects/native-agent-integration/src/test/java/org/acme/GreetingResourceTest.java
new file mode 100644
index 0000000000000..2f0e3284d3d71
--- /dev/null
+++ b/integration-tests/maven/src/test/resources-filtered/projects/native-agent-integration/src/test/java/org/acme/GreetingResourceTest.java
@@ -0,0 +1,31 @@
+package org.acme;
+
+import io.quarkus.test.junit.QuarkusTest;
+import org.junit.jupiter.api.Test;
+
+import static io.restassured.RestAssured.given;
+import static org.hamcrest.CoreMatchers.is;
+
+@QuarkusTest
+public class GreetingResourceTest
+{
+ @Test
+ public void testKnownName()
+ {
+ given()
+ .when().get("/hello/Alice")
+ .then()
+ .statusCode(200)
+ .body(is("Hello Alice"));
+ }
+
+ @Test
+ public void testUnknownName()
+ {
+ given()
+ .when().get("/hello/Bob")
+ .then()
+ .statusCode(404)
+ .body(is(""));
+ }
+}
\ No newline at end of file
diff --git a/test-framework/common/src/main/java/io/quarkus/test/common/DefaultDockerContainerLauncher.java b/test-framework/common/src/main/java/io/quarkus/test/common/DefaultDockerContainerLauncher.java
index 20fc7ed3a6c9d..2d01ef727fd4a 100644
--- a/test-framework/common/src/main/java/io/quarkus/test/common/DefaultDockerContainerLauncher.java
+++ b/test-framework/common/src/main/java/io/quarkus/test/common/DefaultDockerContainerLauncher.java
@@ -16,6 +16,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@@ -24,7 +25,9 @@
import org.apache.commons.lang3.RandomStringUtils;
import org.jboss.logging.Logger;
+import io.quarkus.deployment.pkg.steps.NativeImageBuildLocalContainerRunner;
import io.quarkus.deployment.util.ContainerRuntimeUtil;
+import io.quarkus.deployment.util.ContainerRuntimeUtil.ContainerRuntime;
import io.quarkus.test.common.http.TestHTTPResourceManager;
import io.smallrye.config.common.utils.StringUtil;
@@ -50,6 +53,8 @@ public class DefaultDockerContainerLauncher implements DockerContainerArtifactLa
private final String containerName = "quarkus-integration-test-" + RandomStringUtils.random(5, true, false);
private String containerRuntimeBinaryName;
private final ExecutorService executorService = Executors.newSingleThreadExecutor();
+ private Optional entryPoint;
+ private List programArgs;
@Override
public void init(DockerContainerArtifactLauncher.DockerInitContext initContext) {
@@ -65,6 +70,8 @@ public void init(DockerContainerArtifactLauncher.DockerInitContext initContext)
this.additionalExposedPorts = initContext.additionalExposedPorts();
this.volumeMounts = initContext.volumeMounts();
this.labels = initContext.labels();
+ this.entryPoint = initContext.entryPoint();
+ this.programArgs = initContext.programArgs();
}
@Override
@@ -75,7 +82,8 @@ public LaunchResult runToCompletion(String[] args) {
@Override
public void start() throws IOException {
- containerRuntimeBinaryName = determineBinary();
+ final ContainerRuntime containerRuntime = ContainerRuntimeUtil.detectContainerRuntime();
+ containerRuntimeBinaryName = containerRuntime.getExecutableName();
if (pullRequired) {
log.infof("Pulling container image '%s'", containerImage);
@@ -109,17 +117,23 @@ public void start() throws IOException {
args.add(containerName);
args.add("-i"); // Interactive, write logs to stdout
args.add("--rm");
+
+ args.addAll(NativeImageBuildLocalContainerRunner.getVolumeAccessArguments(containerRuntime));
+
args.add("-p");
args.add(httpPort + ":" + httpPort);
args.add("-p");
args.add(httpsPort + ":" + httpsPort);
+ if (entryPoint.isPresent()) {
+ args.add("--entrypoint");
+ args.add(entryPoint.get());
+ }
for (Map.Entry entry : additionalExposedPorts.entrySet()) {
args.add("-p");
args.add(entry.getKey() + ":" + entry.getValue());
}
for (Map.Entry entry : volumeMounts.entrySet()) {
- args.add("-v");
- args.add(entry.getKey() + ":" + entry.getValue());
+ NativeImageBuildLocalContainerRunner.addVolumeParameter(entry.getKey(), entry.getValue(), args, containerRuntime);
}
// if the dev services resulted in creating a dedicated network, then use it
if (devServicesLaunchResult.networkId() != null) {
@@ -151,6 +165,7 @@ public void start() throws IOException {
args.add(e.getKey() + "=" + e.getValue());
}
args.add(containerImage);
+ args.addAll(programArgs);
final Path logFile = PropertyTestUtil.getLogFilePath();
try {
@@ -176,16 +191,14 @@ public void start() throws IOException {
waitTimeSeconds, logFile);
isSsl = result.isSsl();
} else {
+ log.info("Wait for server to start by capturing listening data...");
final ListeningAddress result = waitForCapturedListeningData(containerProcess, logFile, waitTimeSeconds);
+ log.infof("Server started on port %s", result.getPort());
updateConfigForPort(result.getPort());
isSsl = result.isSsl();
}
}
- private String determineBinary() {
- return ContainerRuntimeUtil.detectContainerRuntime().getExecutableName();
- }
-
private int getRandomPort() throws IOException {
try (ServerSocket socket = new ServerSocket(0)) {
return socket.getLocalPort();
@@ -217,14 +230,17 @@ private String convertPropertyToEnvVar(String property) {
@Override
public void close() {
+ log.info("Close the container");
try {
final Process dockerStopProcess = new ProcessBuilder(containerRuntimeBinaryName, "stop", containerName)
.redirectError(DISCARD)
.redirectOutput(DISCARD).start();
+ log.debug("Wait for container to stop");
dockerStopProcess.waitFor(10, TimeUnit.SECONDS);
} catch (IOException | InterruptedException e) {
log.errorf("Unable to stop container '%s'", containerName);
}
+ log.debug("Container stopped");
executorService.shutdown();
}
}
diff --git a/test-framework/common/src/main/java/io/quarkus/test/common/DockerContainerArtifactLauncher.java b/test-framework/common/src/main/java/io/quarkus/test/common/DockerContainerArtifactLauncher.java
index 525fd1cf44b7b..520bc963c84b8 100644
--- a/test-framework/common/src/main/java/io/quarkus/test/common/DockerContainerArtifactLauncher.java
+++ b/test-framework/common/src/main/java/io/quarkus/test/common/DockerContainerArtifactLauncher.java
@@ -1,6 +1,8 @@
package io.quarkus.test.common;
+import java.util.List;
import java.util.Map;
+import java.util.Optional;
public interface DockerContainerArtifactLauncher extends ArtifactLauncher {
@@ -15,5 +17,9 @@ interface DockerInitContext extends InitContext {
Map labels();
Map volumeMounts();
+
+ Optional entryPoint();
+
+ List programArgs();
}
}
diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusIntegrationTestExtension.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusIntegrationTestExtension.java
index ae83728df626c..3f7680375d4a6 100644
--- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusIntegrationTestExtension.java
+++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusIntegrationTestExtension.java
@@ -275,13 +275,14 @@ public void close() throws Throwable {
Config config = LauncherUtil.installAndGetSomeConfig();
Duration waitDuration = TestConfigUtil.waitTimeValue(config);
String target = TestConfigUtil.runTarget(config);
+ String testProfile = TestConfigUtil.integrationTestProfile(config);
// try to execute a run command published by an extension if it exists. We do this so that extensions that have a custom run don't have to create any special artifact type
launcher = RunCommandLauncher.tryLauncher(devServicesLaunchResult.getCuratedApplication().getQuarkusBootstrap(),
target, waitDuration);
if (launcher == null) {
ServiceLoader loader = ServiceLoader.load(ArtifactLauncherProvider.class);
for (ArtifactLauncherProvider launcherProvider : loader) {
- if (launcherProvider.supportsArtifactType(artifactType)) {
+ if (launcherProvider.supportsArtifactType(artifactType, testProfile)) {
launcher = launcherProvider.create(
new DefaultArtifactLauncherCreateContext(quarkusArtifactProperties, context,
requiredTestClass,
diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusMainIntegrationTestExtension.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusMainIntegrationTestExtension.java
index 023eb5680c140..deb98903ab1ad 100644
--- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusMainIntegrationTestExtension.java
+++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusMainIntegrationTestExtension.java
@@ -17,6 +17,7 @@
import java.util.Properties;
import java.util.ServiceLoader;
+import org.eclipse.microprofile.config.Config;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
@@ -27,6 +28,8 @@
import io.quarkus.runtime.logging.JBossVersion;
import io.quarkus.test.common.ArtifactLauncher;
+import io.quarkus.test.common.LauncherUtil;
+import io.quarkus.test.common.TestConfigUtil;
import io.quarkus.test.common.TestResourceManager;
import io.quarkus.test.junit.launcher.ArtifactLauncherProvider;
import io.quarkus.test.junit.main.Launch;
@@ -154,10 +157,13 @@ private ArtifactLauncher.LaunchResult doProcessStart(ExtensionContext context, S
testResourceManager.inject(context.getRequiredTestInstance());
+ Config config = LauncherUtil.installAndGetSomeConfig();
+ String testProfile = TestConfigUtil.integrationTestProfile(config);
+
ArtifactLauncher> launcher = null;
ServiceLoader loader = ServiceLoader.load(ArtifactLauncherProvider.class);
for (ArtifactLauncherProvider launcherProvider : loader) {
- if (launcherProvider.supportsArtifactType(artifactType)) {
+ if (launcherProvider.supportsArtifactType(artifactType, testProfile)) {
launcher = launcherProvider.create(
new DefaultArtifactLauncherCreateContext(quarkusArtifactProperties, context, requiredTestClass,
devServicesLaunchResult));
diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/launcher/ArtifactLauncherProvider.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/launcher/ArtifactLauncherProvider.java
index 7a8cca781b46a..c63bd1b713b5f 100644
--- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/launcher/ArtifactLauncherProvider.java
+++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/launcher/ArtifactLauncherProvider.java
@@ -11,7 +11,7 @@ public interface ArtifactLauncherProvider {
* Determines whether this provider support the artifact type
*
*/
- boolean supportsArtifactType(String type);
+ boolean supportsArtifactType(String type, String testProfile);
/**
* Returns an instance of {@link ArtifactLauncher} on which the {@code init} method has been called
diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/launcher/DockerContainerLauncherProvider.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/launcher/DockerContainerLauncherProvider.java
index 68e5d02992776..0ee3a8e35628b 100644
--- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/launcher/DockerContainerLauncherProvider.java
+++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/launcher/DockerContainerLauncherProvider.java
@@ -1,18 +1,24 @@
package io.quarkus.test.junit.launcher;
+import static io.quarkus.deployment.pkg.NativeConfig.DEFAULT_MANDREL_BUILDER_IMAGE;
import static io.quarkus.test.junit.ArtifactTypeUtil.isContainer;
+import static io.quarkus.test.junit.ArtifactTypeUtil.isJar;
import static io.quarkus.test.junit.IntegrationTestUtil.DEFAULT_HTTPS_PORT;
import static io.quarkus.test.junit.IntegrationTestUtil.DEFAULT_PORT;
import java.time.Duration;
+import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
+import java.util.Optional;
import java.util.OptionalInt;
import java.util.ServiceLoader;
+import io.quarkus.deployment.util.FileUtil;
import io.quarkus.test.common.ArtifactLauncher;
import io.quarkus.test.common.DefaultDockerContainerLauncher;
import io.quarkus.test.common.DockerContainerArtifactLauncher;
@@ -23,8 +29,8 @@
public class DockerContainerLauncherProvider implements ArtifactLauncherProvider {
@Override
- public boolean supportsArtifactType(String type) {
- return isContainer(type);
+ public boolean supportsArtifactType(String type, String testProfile) {
+ return isContainer(type) || (isJar(type) && "test-with-native-agent".equals(testProfile));
}
@Override
@@ -42,25 +48,81 @@ public DockerContainerArtifactLauncher create(CreateContext context) {
launcher = new DefaultDockerContainerLauncher();
}
SmallRyeConfig config = (SmallRyeConfig) LauncherUtil.installAndGetSomeConfig();
- launcher.init(new DefaultDockerInitContext(
- config.getValue("quarkus.http.test-port", OptionalInt.class).orElse(DEFAULT_PORT),
- config.getValue("quarkus.http.test-ssl-port", OptionalInt.class).orElse(DEFAULT_HTTPS_PORT),
- TestConfigUtil.waitTimeValue(config),
- TestConfigUtil.integrationTestProfile(config),
- TestConfigUtil.argLineValue(config),
- TestConfigUtil.env(config),
- context.devServicesLaunchResult(),
- containerImage,
- pullRequired,
- additionalExposedPorts(config),
- labels(config),
- volumeMounts(config)));
+ launcherInit(context, launcher, config, containerImage, pullRequired, Optional.empty(), volumeMounts(config),
+ Collections.emptyList());
return launcher;
} else {
- throw new IllegalStateException("The container image to be launched could not be determined");
+ // Running quarkus integration tests with a native image agent,
+ // which can be achieved with a specific test profile name,
+ // involves having Quarkus run with the java process inside the default Mandrel builder container image.
+ // This block achieves this by swapping the entry point to be the java executable,
+ // adding a volume mapping pointing to the build output directory,
+ // and then instructing the java process to run the run jar,
+ // along with the native image agent arguments and any other additional parameters.
+ SmallRyeConfig config = (SmallRyeConfig) LauncherUtil.installAndGetSomeConfig();
+ String testProfile = TestConfigUtil.integrationTestProfile(config);
+
+ if ("test-with-native-agent".equals(testProfile)) {
+ DockerContainerArtifactLauncher launcher = new DefaultDockerContainerLauncher();
+ Optional entryPoint = Optional.of("java");
+ Map volumeMounts = new HashMap<>(volumeMounts(config));
+ volumeMounts.put(context.buildOutputDirectory().toString(), "/project");
+ containerImage = DEFAULT_MANDREL_BUILDER_IMAGE;
+
+ List programArgs = new ArrayList<>();
+ addNativeAgentProgramArgs(programArgs, context);
+
+ launcherInit(context, launcher, config, containerImage, pullRequired, entryPoint, volumeMounts, programArgs);
+ return launcher;
+ } else {
+ throw new IllegalStateException("The container image to be launched could not be determined");
+ }
}
}
+ private void launcherInit(CreateContext context, DockerContainerArtifactLauncher launcher, SmallRyeConfig config,
+ String containerImage, boolean pullRequired, Optional entryPoint, Map volumeMounts,
+ List programArgs) {
+ launcher.init(new DefaultDockerInitContext(
+ config.getValue("quarkus.http.test-port", OptionalInt.class).orElse(DEFAULT_PORT),
+ config.getValue("quarkus.http.test-ssl-port", OptionalInt.class).orElse(DEFAULT_HTTPS_PORT),
+ TestConfigUtil.waitTimeValue(config),
+ TestConfigUtil.integrationTestProfile(config),
+ TestConfigUtil.argLineValue(config),
+ TestConfigUtil.env(config),
+ context.devServicesLaunchResult(),
+ containerImage,
+ pullRequired,
+ additionalExposedPorts(config),
+ labels(config),
+ volumeMounts,
+ entryPoint,
+ programArgs));
+ }
+
+ private void addNativeAgentProgramArgs(List programArgs, CreateContext context) {
+ final String outputPropertyName = System.getProperty("quarkus.test.native.agent.output.property.name",
+ "config-output-dir");
+ final String outputPropertyValue = System.getProperty("quarkus.test.native.agent.output.property.value",
+ "native-image-agent-base-config");
+ final String agentAdditionalArgs = System.getProperty("quarkus.test.native.agent.additional.args", "");
+
+ final String accessFilter = "access-filter-file=quarkus-access-filter.json";
+ final String callerFilter = "caller-filter-file=quarkus-caller-filter.json";
+
+ final String output = String.format(
+ "%s=%s", outputPropertyName, outputPropertyValue);
+
+ String agentLibArg = String.format(
+ "-agentlib:native-image-agent=%s,%s,%s,%s", accessFilter, callerFilter, output, agentAdditionalArgs);
+
+ programArgs.add(agentLibArg);
+
+ programArgs.add("-jar");
+ final String jarPath = FileUtil.translateToVolumePath(context.quarkusArtifactProperties().getProperty("path"));
+ programArgs.add(jarPath);
+ }
+
private Map additionalExposedPorts(SmallRyeConfig config) {
try {
return config.getValues("quarkus.test.container.additional-exposed-ports", Integer.class, Integer.class);
@@ -90,6 +152,8 @@ static class DefaultDockerInitContext extends DefaultInitContextBase
private final String containerImage;
private final boolean pullRequired;
private final Map additionalExposedPorts;
+ private final Optional entryPoint;
+ private final List programArgs;
private Map labels;
private Map volumeMounts;
@@ -99,13 +163,15 @@ public DefaultDockerInitContext(int httpPort, int httpsPort, Duration waitTime,
String containerImage, boolean pullRequired,
Map additionalExposedPorts,
Map labels,
- Map volumeMounts) {
+ Map volumeMounts, Optional entryPoint, List programArgs) {
super(httpPort, httpsPort, waitTime, testProfile, argLine, env, devServicesLaunchResult);
this.containerImage = containerImage;
this.pullRequired = pullRequired;
this.additionalExposedPorts = additionalExposedPorts;
this.labels = labels;
this.volumeMounts = volumeMounts;
+ this.entryPoint = entryPoint;
+ this.programArgs = programArgs;
}
@Override
@@ -133,5 +199,14 @@ public Map volumeMounts() {
return volumeMounts;
}
+ @Override
+ public Optional entryPoint() {
+ return entryPoint;
+ }
+
+ @Override
+ public List programArgs() {
+ return programArgs;
+ }
}
}
diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/launcher/JarLauncherProvider.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/launcher/JarLauncherProvider.java
index ce2efc03d62a9..ef360c7500053 100644
--- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/launcher/JarLauncherProvider.java
+++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/launcher/JarLauncherProvider.java
@@ -23,7 +23,7 @@
public class JarLauncherProvider implements ArtifactLauncherProvider {
@Override
- public boolean supportsArtifactType(String type) {
+ public boolean supportsArtifactType(String type, String testProfile) {
return isJar(type);
}
diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/launcher/NativeImageLauncherProvider.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/launcher/NativeImageLauncherProvider.java
index 1ade26e674a59..e33465517c0c2 100644
--- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/launcher/NativeImageLauncherProvider.java
+++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/launcher/NativeImageLauncherProvider.java
@@ -21,7 +21,7 @@
public class NativeImageLauncherProvider implements ArtifactLauncherProvider {
@Override
- public boolean supportsArtifactType(String type) {
+ public boolean supportsArtifactType(String type, String testProfile) {
return isNativeBinary(type);
}
diff --git a/test-framework/maven/src/main/java/io/quarkus/maven/it/verifier/MavenProcessInvoker.java b/test-framework/maven/src/main/java/io/quarkus/maven/it/verifier/MavenProcessInvoker.java
index 4c90095151ddd..4a5051076cc57 100644
--- a/test-framework/maven/src/main/java/io/quarkus/maven/it/verifier/MavenProcessInvoker.java
+++ b/test-framework/maven/src/main/java/io/quarkus/maven/it/verifier/MavenProcessInvoker.java
@@ -56,6 +56,9 @@ public InvocationResult execute(InvocationRequest request) throws MavenInvocatio
Commandline cli;
try {
cli = cliBuilder.build(request);
+ if (logger != null) {
+ logger.debug("Running Maven CLI command: " + cli);
+ }
} catch (CommandLineConfigurationException e) {
throw new MavenInvocationException("Error configuring command-line. Reason: " + e.getMessage(), e);
}