From aa9963d10e888ab11246d6c72727d6171cfcfccf Mon Sep 17 00:00:00 2001 From: Cedric Champeau Date: Tue, 22 Jun 2021 11:06:11 +0200 Subject: [PATCH 01/23] Use logger warn as intended The previous call was displaying a warning but using error level. --- .../java/org/graalvm/buildtools/gradle/NativeImagePlugin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java index 397446b04..d53bb7409 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java @@ -107,7 +107,7 @@ public void apply(Project project) { @SuppressWarnings("NullableProblems") @Override public void execute(Task task) { - logger.error("[WARNING] Task 'nativeImage' is deprecated. " + logger.warn("Task 'nativeImage' is deprecated. " + String.format("Use '%s' instead.", NativeBuildTask.TASK_NAME)); } }); From 43e181ef5883041f2140727bac4dceee4dc4101e Mon Sep 17 00:00:00 2001 From: Cedric Champeau Date: Tue, 22 Jun 2021 17:15:25 +0200 Subject: [PATCH 02/23] Avoid extending Exec task Instead, the task should use the `ExecOperations`. It basically uses delegation instead of inheritance. This has several advantages: - it makes sure that we can explicitly list all inputs to the task - it reduces the API surface of the task itself - it will make sure the task is compatible with the Gradle configuration cache The task is not configuration cache ready yet, since it uses the project in the task action: this will be fixed later. Signed-off-by: Cedric Champeau --- .../gradle/tasks/TestNativeBuildTask.java | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/TestNativeBuildTask.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/TestNativeBuildTask.java index be29b51b8..ea32dee5d 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/TestNativeBuildTask.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/TestNativeBuildTask.java @@ -44,32 +44,41 @@ import org.graalvm.buildtools.gradle.GradleUtils; import org.graalvm.buildtools.gradle.dsl.JUnitPlatformOptions; import org.graalvm.buildtools.gradle.internal.GraalVMLogger; +import org.gradle.api.DefaultTask; import org.gradle.api.Project; import org.gradle.api.artifacts.ProjectDependency; +import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.FileCollection; import org.gradle.api.plugins.JavaBasePlugin; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.provider.Property; -import org.gradle.api.tasks.AbstractExecTask; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.Internal; import org.gradle.api.tasks.OutputFile; import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.TaskAction; import org.gradle.jvm.tasks.Jar; +import org.gradle.process.ExecOperations; +import javax.inject.Inject; import java.io.File; import java.util.List; import static org.graalvm.buildtools.gradle.GradleUtils.DEPENDENT_CONFIGURATIONS; -public abstract class TestNativeBuildTask extends AbstractExecTask { +public abstract class TestNativeBuildTask extends DefaultTask { public static final String TASK_NAME = "nativeTestBuild"; protected JUnitPlatformOptions options; + @Inject + protected abstract ExecOperations getExecOperations(); + + @Internal + protected abstract DirectoryProperty getWorkingDirectory(); + public TestNativeBuildTask() { - super(TestNativeBuildTask.class); dependsOn("testClasses"); getProject().getConfigurations().configureEach( configuration -> { @@ -87,7 +96,7 @@ public TestNativeBuildTask() { } } ); - setWorkingDir(getProject().getBuildDir()); + getWorkingDirectory().set(getProject().getBuildDir()); setDescription("Builds native image with tests."); setGroup(JavaBasePlugin.VERIFICATION_GROUP); @@ -119,7 +128,7 @@ public File getOutputFile() { @Internal public abstract Property getServer(); - @Override + @TaskAction @SuppressWarnings("ConstantConditions") public void exec() { Project project = getProject(); @@ -134,11 +143,12 @@ public void exec() { System.out.println("Args are:"); System.out.println(args); } - - this.args(args); - getServer().get(); - setExecutable(Utils.getNativeImage()); - super.exec(); + getExecOperations().exec(spec -> { + spec.setWorkingDir(getWorkingDirectory()); + spec.args(args); + getServer().get(); + spec.setExecutable(Utils.getNativeImage()); + }); System.out.println("Native Image written to: " + getOutputFile()); } } From 872cc528bcefd45d7ebbff4e744c3e8540b5b538 Mon Sep 17 00:00:00 2001 From: Cedric Champeau Date: Tue, 22 Jun 2021 18:52:40 +0200 Subject: [PATCH 03/23] Remove legacy nativeImage task This removes the deprecated native image task. Bridging "old" tasks like that is in any case not right because if some tasks in the build were using the old image outputs, then they wouldn't be wired properly anymore. --- .../graalvm/buildtools/gradle/NativeImagePlugin.java | 12 ------------ .../buildtools/gradle/dsl/NativeImageOptions.java | 4 +--- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java index d53bb7409..57d8bd21b 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java @@ -49,7 +49,6 @@ import org.graalvm.buildtools.gradle.tasks.NativeRunTask; import org.graalvm.buildtools.gradle.tasks.TestNativeBuildTask; import org.graalvm.buildtools.gradle.tasks.TestNativeRunTask; -import org.gradle.api.Action; import org.gradle.api.GradleException; import org.gradle.api.Plugin; import org.gradle.api.Project; @@ -101,17 +100,6 @@ public void apply(Project project) { task.getServer().set(nativeImageServiceProvider); }); - Task oldTask = project.getTasks().create("nativeImage"); - oldTask.dependsOn(NativeBuildTask.TASK_NAME); - oldTask.doLast(new Action() { - @SuppressWarnings("NullableProblems") - @Override - public void execute(Task task) { - logger.warn("Task 'nativeImage' is deprecated. " - + String.format("Use '%s' instead.", NativeBuildTask.TASK_NAME)); - } - }); - project.getTasks().register(NativeRunTask.TASK_NAME, NativeRunTask.class); if (project.hasProperty(Utils.AGENT_PROPERTY) || buildExtension.getAgent().get()) { diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java index 8665d8960..8bb733770 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java @@ -74,8 +74,6 @@ */ @SuppressWarnings({"unused", "UnusedReturnValue"}) public class NativeImageOptions { - public static final String EXTENSION_NAME = "nativeBuild"; - private final Property imageName; private final Property mainClass; private final ListProperty buildArgs; @@ -121,7 +119,7 @@ public NativeImageOptions(ObjectFactory objectFactory) { } public static NativeImageOptions register(Project project) { - return project.getExtensions().create(EXTENSION_NAME, NativeImageOptions.class, project.getObjects()); + return project.getExtensions().create("nativeBuild", NativeImageOptions.class, project.getObjects()); } public void configure(Project project) { From 4b40cb42d4d8fc2efeed303c202e47e8e0b478cd Mon Sep 17 00:00:00 2001 From: Cedric Champeau Date: Wed, 23 Jun 2021 08:28:50 +0200 Subject: [PATCH 04/23] Remove deprecated configuration options This is in preparation for cleanup of the extension configuration. --- .../gradle/dsl/NativeImageOptions.java | 145 +----------------- 1 file changed, 1 insertion(+), 144 deletions(-) diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java index 8bb733770..1586a3ec1 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java @@ -230,30 +230,6 @@ public NativeImageOptions setImageName(@Nullable String name) { return this; } - /** - * Gets the name of the native executable to be generated. - * - * @return The image name property. - * @deprecated This method was used for compatibility with the com.palantir.graal plugin. - *

Use {@link #getImageName()} instead.

- */ - public Property getOutputName() { - return imageName; - } - - /** - * Sets the name of the native executable to be generated. - * - * @param name The image name property. - * @return this - * @deprecated This method was used for compatibility with the com.palantir.graal plugin. - *

Use {@link #setImageName(String)} instead.

- */ - public NativeImageOptions setOutputName(@Nullable String name) { - imageName.set(name); - return this; - } - /** * Returns the fully qualified name of the Main class to be executed. *

@@ -362,126 +338,7 @@ public NativeImageOptions setBuildArgs(@Nullable Iterable buildArgs) { } return this; } - - /** - * Adds arguments for the native-image invocation. - * - * @param buildArgs Arguments for the native-image invocation. - * @return this - * @deprecated This method was used for compatibility with the io.micronaut.application plugin. - *

Use {@link #buildArgs(Object...)} instead.

- */ - public NativeImageOptions args(Object... buildArgs) { - return buildArgs(buildArgs); - } - - /** - * Adds arguments for the native-image invocation. - * - * @param buildArgs Arguments for the native-image invocation. - * @return this - * @deprecated This method was used for compatibility with the io.micronaut.application plugin. - *

Use {@link #buildArgs(Iterable)} instead.

- */ - public NativeImageOptions args(Iterable buildArgs) { - return setBuildArgs(buildArgs); - } - - /** - * Returns the arguments for the native-image invocation. - * - * @return Arguments for the native-image invocation. - * @deprecated This method was used for compatibility with the io.micronaut.application plugin. - *

Use {@link #getBuildArgs()} instead.

- */ - public ListProperty getArgs() { - return getBuildArgs(); - } - - /** - * Sets the arguments for the native-image invocation. - * - * @param buildArgs Arguments for the native-image invocation. - * @return this - * @deprecated This method was used for compatibility with the io.micronaut.application plugin. - *

Use {@link #setBuildArgs(List)} instead.

- */ - public NativeImageOptions setArgs(@Nullable List buildArgs) { - return setBuildArgs(buildArgs); - } - - /** - * Sets the arguments for the native-image invocation. - * - * @param buildArgs Arguments for the native-image invocation. - * @return this - * @deprecated This method was used for compatibility with the io.micronaut.application plugin. - *

Use {@link #setBuildArgs(Iterable)} instead.

- */ - public NativeImageOptions setArgs(@Nullable Iterable buildArgs) { - return setBuildArgs(buildArgs); - } - - /** - * Adds arguments for the native-image invocation. - * - * @param buildArgs Arguments for the native-image invocation. - * @return this - * @deprecated This method was used for compatibility with the com.palantir.graal plugin. - *

Use {@link #buildArgs(Object...)} instead.

- */ - public NativeImageOptions option(Object... buildArgs) { - return buildArgs(buildArgs); - } - - /** - * Adds arguments for the native-image invocation. - * - * @param buildArgs Arguments for the native-image invocation. - * @return this - * @deprecated This method was used for compatibility with the com.palantir.graal plugin. - *

Use {@link #buildArgs(Iterable)} instead.

- */ - public NativeImageOptions option(Iterable buildArgs) { - return buildArgs(buildArgs); - } - - /** - * Returns the arguments for the native-image invocation. - * - * @return arguments for the native-image invocation. - * @deprecated This method was used for compatibility with the com.palantir.graal plugin. - *

Use {@link #getBuildArgs()} instead.

- */ - public ListProperty getOption() { - return getBuildArgs(); - } - - - /** - * Sets the args for the native-image invocation. - * - * @param buildArgs Arguments for the native-image invocation. - * @return this - * @deprecated This method was used for compatibility with the com.palantir.graal plugin. - *

Use {@link #setBuildArgs(List)} instead.

- */ - public NativeImageOptions setOption(@Nullable List buildArgs) { - return setBuildArgs(buildArgs); - } - - /** - * Sets the args for the native-image invocation. - * - * @param buildArgs Arguments for the native-image invocation. - * @return this - * @deprecated This method was used for compatibility with the com.palantir.graal plugin. - *

Use {@link #setBuildArgs(Iterable)} instead.

- */ - public NativeImageOptions setOption(@Nullable Iterable buildArgs) { - return setBuildArgs(buildArgs); - } - + /** * Returns the system properties which will be used by the native-image builder process. * From 633c34cad8e169b832c8ccfb729ccefaed1a5535 Mon Sep 17 00:00:00 2001 From: Cedric Champeau Date: Wed, 23 Jun 2021 09:04:19 +0200 Subject: [PATCH 05/23] Use idiomatic extension configuration This commit refactors the native image configuration extension to be more Gradle idiomatic. In particular: - creation of the properties is now delegated to Gradle, instead of directly instantiating - setter methods were removed, as Gradle is responsible for providing a consistent DSL from property definitions. In particular, it is responsible for creating setter-like methods in the Groovy DSL (Kotlin DSL will come at some point) - setter methods which were actually appending were confusing, and removed - only some "appending" methods were kept, for the DSL to be a bit nicer, although they should probably be removed at some point too, once the DSL generated by Gradle gets nicer The wiring of the internal state hasn't been changed yet. --- .../gradle/dsl/JUnitPlatformOptions.java | 21 +- .../gradle/dsl/NativeImageOptions.java | 509 +++++------------- 2 files changed, 149 insertions(+), 381 deletions(-) diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/JUnitPlatformOptions.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/JUnitPlatformOptions.java index d0aaa4d2d..fa8c3378b 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/JUnitPlatformOptions.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/JUnitPlatformOptions.java @@ -45,35 +45,26 @@ import org.gradle.api.model.ObjectFactory; import org.gradle.api.tasks.SourceSet; -import javax.annotation.Nullable; import java.nio.file.Paths; -public class JUnitPlatformOptions extends NativeImageOptions { +public abstract class JUnitPlatformOptions extends NativeImageOptions { public static final String EXTENSION_NAME = "nativeTest"; @SuppressWarnings("UnstableApiUsage") public JUnitPlatformOptions(ObjectFactory objectFactory) { super(objectFactory); - super.setMainClass("org.graalvm.junit.platform.NativeImageJUnitLauncher"); - super.setImageName(Utils.NATIVE_TESTS_EXE); - super.runtimeArgs("--xml-output-dir", Paths.get("test-results").resolve("test-native")); + getMainClass().set("org.graalvm.junit.platform.NativeImageJUnitLauncher"); + getMainClass().finalizeValue(); + getImageName().set(Utils.NATIVE_TESTS_EXE); + getImageName().finalizeValue(); + runtimeArgs("--xml-output-dir", Paths.get("test-results").resolve("test-native")); } public static JUnitPlatformOptions register(Project project) { return project.getExtensions().create(EXTENSION_NAME, JUnitPlatformOptions.class, project.getObjects()); } - @Override - public NativeImageOptions setMainClass(@Nullable String main) { - throw new IllegalStateException("Main class for test task cannot be changed"); - } - - @Override - public NativeImageOptions setImageName(@Nullable String image) { - throw new IllegalStateException("Image name for test task cannot be changed"); - } - @Override public void configure(Project project) { buildArgs("--features=org.graalvm.junit.platform.JUnitPlatformFeature"); diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java index 1586a3ec1..bfbe75757 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java @@ -51,20 +51,18 @@ import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.MapProperty; import org.gradle.api.provider.Property; -import org.gradle.api.tasks.Classpath; import org.gradle.api.tasks.SourceSet; -import javax.annotation.Nullable; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.function.BooleanSupplier; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; /** @@ -73,47 +71,118 @@ * @author gkrocher */ @SuppressWarnings({"unused", "UnusedReturnValue"}) -public class NativeImageOptions { - private final Property imageName; - private final Property mainClass; - private final ListProperty buildArgs; - private final MapProperty systemProperties; - private @Nullable FileCollection classpath; - private final ListProperty jvmArgs; - private final ListProperty runtimeArgs; - private final Property debug; - private final Property server; - private final Property fallback; - private final Property verbose; - private final Map booleanCmds; - private final Property agent; - private final Property persistConfig; +public abstract class NativeImageOptions { + /** + * Gets the name of the native executable to be generated. + * + * @return The image name property. + */ + public abstract Property getImageName(); + + /** + * Returns the fully qualified name of the Main class to be executed. + *

+ * This does not need to be set if using an Executable Jar with a {@code Main-Class} attribute. + *

+ * + * @return mainClass The main class. + */ + public abstract Property getMainClass(); + + /** + * Returns the arguments for the native-image invocation. + * + * @return Arguments for the native-image invocation. + */ + public abstract ListProperty getBuildArgs(); + + /** + * Returns the system properties which will be used by the native-image builder process. + * + * @return The system properties. Returns an empty map when there are no system properties. + */ + public abstract MapProperty getSystemProperties(); + + /** + * Returns the classpath for the native-image building. + * + * @return classpath The classpath for the native-image building. + */ + public abstract ConfigurableFileCollection getClasspath(); + + /** + * Returns the extra arguments to use when launching the JVM for the native-image building process. + * Does not include system properties and the minimum/maximum heap size. + * + * @return The arguments. Returns an empty list if there are no arguments. + */ + public abstract ListProperty getJvmArgs(); + + /** + * Returns the arguments to use when launching the built image. + * + * @return The arguments. Returns an empty list if there are no arguments. + */ + public abstract ListProperty getRuntimeArgs(); + + /** + * Gets the value which toggles native-image debug symbol output. + * + * @return Is debug enabled + */ + public abstract Property getDebug(); + + /** + * Returns the server property, used to determine if the native image + * build server should be used. + * + * @return the server property + */ + public abstract Property getServer(); + + /** + * @return Whether to enable fallbacks (defaults to false). + */ + public abstract Property getFallback(); + + /** + * Gets the value which toggles native-image verbose output. + * + * @return Is verbose output + */ + public abstract Property getVerbose(); + + /** + * Gets the value which toggles the native-image-agent usage. + * + * @return The value which toggles the native-image-agent usage. + */ + public abstract Property getAgent(); + + /** + * Gets the value which toggles persisting of agent config to META-INF. + * + * @return The value which toggles persisting of agent config to META-INF. + */ + public abstract Property getPersistConfig(); + + // internal state private boolean configured; + private final Map booleanCmds; public NativeImageOptions(ObjectFactory objectFactory) { - this.imageName = objectFactory.property(String.class); - this.mainClass = objectFactory.property(String.class); - this.buildArgs = objectFactory.listProperty(String.class) - .convention(new ArrayList<>(5)); - this.systemProperties = objectFactory.mapProperty(String.class, Object.class) - .convention(new LinkedHashMap<>(5)); - this.classpath = objectFactory.fileCollection(); - this.jvmArgs = objectFactory.listProperty(String.class) - .convention(new ArrayList<>(5)); - this.runtimeArgs = objectFactory.listProperty(String.class) - .convention(new ArrayList<>(5)); - this.debug = objectFactory.property(Boolean.class).convention(false); - this.server = objectFactory.property(Boolean.class).convention(false); - this.fallback = objectFactory.property(Boolean.class).convention(false); - this.verbose = objectFactory.property(Boolean.class).convention(false); + getDebug().convention(false); + getServer().convention(false); + getFallback().convention(false); + getVerbose().convention(false); + getAgent().convention(false); + getPersistConfig().convention(false); this.booleanCmds = new LinkedHashMap<>(3); - this.booleanCmds.put(debug::get, "-H:GenerateDebugInfo=1"); - this.booleanCmds.put(() -> !fallback.get(), "--no-fallback"); - this.booleanCmds.put(verbose::get, "--verbose"); - this.booleanCmds.put(server::get, "-Dcom.oracle.graalvm.isaot=true"); - this.agent = objectFactory.property(Boolean.class).convention(false); - this.persistConfig = objectFactory.property(Boolean.class).convention(false); + this.booleanCmds.put(getDebug()::get, "-H:GenerateDebugInfo=1"); + this.booleanCmds.put(() -> !getFallback().get(), "--no-fallback"); + this.booleanCmds.put(getVerbose()::get, "--verbose"); + this.booleanCmds.put(getServer()::get, "-Dcom.oracle.graalvm.isaot=true"); this.configured = false; } @@ -129,7 +198,7 @@ public void configure(Project project) { /** * Configures the arguments for the native-image invocation based on user supplied options. * - * @param project The current project. + * @param project The current project. * @param sourceSetName Used source set name. */ protected void configure(Project project, String sourceSetName) { @@ -141,8 +210,8 @@ protected void configure(Project project, String sourceSetName) { // User arguments should be at the end of the native-image invocation // but at this moment they are already set. So we remove them here // and add them back later. - List userArgs = buildArgs.get(); - buildArgs.set(Collections.emptyList()); + List userArgs = getBuildArgs().get(); + getBuildArgs().set(Collections.emptyList()); FileCollection classpath = GradleUtils.getClassPath(project); FileCollection userClasspath = getClasspath(); @@ -164,7 +233,7 @@ protected void configure(Project project, String sourceSetName) { buildArgs("-H:Path=" + Utils.NATIVE_IMAGE_OUTPUT_FOLDER); if (!getImageName().isPresent()) { - setImageName(project.getName().toLowerCase()); + getImageName().set(project.getName().toLowerCase()); } buildArgs("-H:Name=" + getImageName().get()); @@ -199,7 +268,7 @@ protected void configure(Project project, String sourceSetName) { if (!getMainClass().isPresent()) { JavaApplication app = project.getExtensions().findByType(JavaApplication.class); if (app != null && app.getMainClass().isPresent()) { - setMainClass(app.getMainClass().get()); + getMainClass().set(app.getMainClass().get()); } } @@ -207,72 +276,7 @@ protected void configure(Project project, String sourceSetName) { buildArgs("-H:Class=" + getMainClass().get()); } - buildArgs.addAll(userArgs); - } - - /** - * Gets the name of the native executable to be generated. - * - * @return The image name property. - */ - public Property getImageName() { - return imageName; - } - - /** - * Sets the name of the native executable to be generated. - * - * @param name The image name property. - * @return this - */ - public NativeImageOptions setImageName(@Nullable String name) { - imageName.set(name); - return this; - } - - /** - * Returns the fully qualified name of the Main class to be executed. - *

- * This does not need to be set if using an Executable Jar with a {@code Main-Class} attribute. - *

- * - * @return mainClass The main class. - */ - public Property getMain() { - return mainClass; - } - - /** - * Returns the fully qualified name of the Main class to be executed. - *

- * This does not need to be set if using an Executable Jar with a {@code Main-Class} attribute. - *

- * - * @return mainClass The main class. - */ - public Property getMainClass() { - return mainClass; - } - - /** - * Sets the fully qualified name of the main class to be executed. - * - * @param mainClass The fully qualified name of the main class to be executed. - * @return this - */ - public NativeImageOptions setMain(@Nullable String mainClass) { - return setMainClass(mainClass); - } - - /** - * Sets the fully qualified name of the main class to be executed. - * - * @param mainClass The fully qualified name of the main class to be executed. - * @return this - */ - public NativeImageOptions setMainClass(@Nullable String mainClass) { - this.mainClass.set(mainClass); - return this; + getBuildArgs().addAll(userArgs); } /** @@ -282,7 +286,11 @@ public NativeImageOptions setMainClass(@Nullable String mainClass) { * @return this */ public NativeImageOptions buildArgs(Object... buildArgs) { - setBuildArgs(Arrays.asList(buildArgs)); + getBuildArgs().addAll( + Arrays.stream(buildArgs) + .map(String::valueOf) + .collect(Collectors.toList()) + ); return this; } @@ -293,73 +301,13 @@ public NativeImageOptions buildArgs(Object... buildArgs) { * @return this */ public NativeImageOptions buildArgs(Iterable buildArgs) { - return setBuildArgs(buildArgs); - } - - /** - * Returns the arguments for the native-image invocation. - * - * @return Arguments for the native-image invocation. - */ - public ListProperty getBuildArgs() { - return buildArgs; - } - - /** - * Sets the arguments for the native-image invocation. - * - * @param buildArgs Arguments for the native-image invocation. - * @return this - */ - public NativeImageOptions setBuildArgs(@Nullable List buildArgs) { - if (buildArgs == null) { - this.buildArgs.set(new ArrayList<>(5)); - } else { - this.buildArgs.addAll(buildArgs); - } - return this; - } - - /** - * Sets the arguments for the native-image invocation. - * - * @param buildArgs Arguments for the native-image invocation. - * @return this - */ - public NativeImageOptions setBuildArgs(@Nullable Iterable buildArgs) { - if (buildArgs == null) { - this.buildArgs.set(Collections.emptyList()); - } else { - for (Object argument : buildArgs) { - if (argument != null) { - this.buildArgs.add(argument.toString()); - } - } - } + getBuildArgs().addAll( + StreamSupport.stream(buildArgs.spliterator(), false) + .map(String::valueOf) + .collect(Collectors.toList()) + ); return this; } - - /** - * Returns the system properties which will be used by the native-image builder process. - * - * @return The system properties. Returns an empty map when there are no system properties. - */ - public MapProperty getSystemProperties() { - return systemProperties; - } - - /** - * Sets the system properties to be used by the native-image builder process. - * - * @param properties The system properties. Must not be null. - */ - public void setSystemProperties(Map properties) { - if (properties == null) { - this.systemProperties.set(new LinkedHashMap<>()); - } else { - this.systemProperties.set(properties); - } - } /** * Adds some system properties to be used by the native-image builder process. @@ -369,21 +317,20 @@ public void setSystemProperties(Map properties) { */ @SuppressWarnings("unused") public NativeImageOptions systemProperties(Map properties) { - setSystemProperties(properties); + MapProperty map = getSystemProperties(); + properties.forEach((key, value) -> map.put(key, value == null ? null : String.valueOf(value))); return this; } /** * Adds a system property to be used by the native-image builder process. * - * @param name The name of the property + * @param name The name of the property * @param value The value for the property. May be null. * @return this */ public NativeImageOptions systemProperty(String name, Object value) { - if (name != null && value != null) { - this.systemProperties.put(name, value.toString()); - } + getSystemProperties().put(name, value == null ? null : String.valueOf(value)); return this; } @@ -394,29 +341,7 @@ public NativeImageOptions systemProperty(String name, Object value) { * @return this */ public NativeImageOptions classpath(Object... paths) { - classpath = ((ConfigurableFileCollection) Objects.requireNonNull(classpath)).from(paths); - return this; - } - - /** - * Returns the classpath for the native-image building. - * - * @return classpath The classpath for the native-image building. - */ - @Classpath - @Nullable - public FileCollection getClasspath() { - return this.classpath; - } - - /** - * Sets the classpath for the native-image building. - * - * @param classpath The classpath. - * @return this - */ - public NativeImageOptions setClasspath(FileCollection classpath) { - this.classpath = classpath; + getClasspath().from(paths); return this; } @@ -427,7 +352,7 @@ public NativeImageOptions setClasspath(FileCollection classpath) { * @return this */ public NativeImageOptions jvmArgs(Object... arguments) { - setJvmArgs(Arrays.asList(arguments)); + getJvmArgs().addAll(Arrays.stream(arguments).map(String::valueOf).collect(Collectors.toList())); return this; } @@ -438,52 +363,14 @@ public NativeImageOptions jvmArgs(Object... arguments) { * @return this */ public NativeImageOptions jvmArgs(Iterable arguments) { - setJvmArgs(arguments); + getJvmArgs().addAll( + StreamSupport.stream(arguments.spliterator(), false) + .map(String::valueOf) + .collect(Collectors.toList()) + ); return this; } - /** - * Returns the extra arguments to use when launching the JVM for the native-image building process. - * Does not include system properties and the minimum/maximum heap size. - * - * @return The arguments. Returns an empty list if there are no arguments. - */ - public ListProperty getJvmArgs() { - return this.jvmArgs; - } - - /** - * Sets the extra arguments to use when launching the JVM for the native-image building process. - * System properties and minimum/maximum heap size are updated. - * - * @param arguments The arguments. Must not be null. - */ - public void setJvmArgs(@Nullable List arguments) { - if (arguments == null) { - this.jvmArgs.set(new ArrayList<>(5)); - } else { - this.jvmArgs.addAll(arguments); - } - } - - /** - * Sets the extra arguments to use when launching the JVM for the native-image building process. - * System properties and minimum/maximum heap size are updated. - * - * @param arguments The arguments. Must not be null. - */ - public void setJvmArgs(@Nullable Iterable arguments) { - if (arguments == null) { - this.jvmArgs.set(Collections.emptyList()); - } else { - for (Object argument : arguments) { - if (argument != null) { - this.jvmArgs.add(argument.toString()); - } - } - } - } - /** * Adds some arguments to use when launching the built image. * @@ -491,7 +378,7 @@ public void setJvmArgs(@Nullable Iterable arguments) { * @return this */ public NativeImageOptions runtimeArgs(Object... arguments) { - setRuntimeArgs(Arrays.asList(arguments)); + getRuntimeArgs().addAll(Arrays.stream(arguments).map(String::valueOf).collect(Collectors.toList())); return this; } @@ -502,69 +389,14 @@ public NativeImageOptions runtimeArgs(Object... arguments) { * @return this */ public NativeImageOptions runtimeArgs(Iterable arguments) { - setRuntimeArgs(arguments); - return this; - } - - /** - * Returns the arguments to use when launching the built image. - * - * @return The arguments. Returns an empty list if there are no arguments. - */ - public ListProperty getRuntimeArgs() { - return this.runtimeArgs; - } - - /** - * Sets the extra arguments to use when launching the built image. - * - * @param arguments The arguments. Must not be null. - */ - public void setRuntimeArgs(@Nullable List arguments) { - if (arguments == null) { - this.runtimeArgs.set(new ArrayList<>(5)); - } else { - this.runtimeArgs.addAll(arguments); - } - } - - /** - * Sets the arguments to use when launching the built image. - * - * @param arguments The arguments. Must not be null. - */ - public void setRuntimeArgs(@Nullable Iterable arguments) { - if (arguments == null) { - this.runtimeArgs.set(Collections.emptyList()); - } else { - for (Object argument : arguments) { - if (argument != null) { - this.runtimeArgs.add(argument.toString()); - } - } - } - } - - /** - * Sets the native image build to be verbose. - * - * @param verbose Value which controls whether the native-image is in the verbose mode. - * @return this - */ - public NativeImageOptions verbose(boolean verbose) { - this.verbose.set(verbose); + getRuntimeArgs().addAll( + StreamSupport.stream(arguments.spliterator(), false) + .map(String::valueOf) + .collect(Collectors.toList()) + ); return this; } - /** - * Gets the value which toggles native-image verbose output. - * - * @return Is verbose output - */ - public Property getVerbose() { - return verbose; - } - /** * Enables server build. Server build is disabled by default. * @@ -572,63 +404,8 @@ public Property getVerbose() { * @return this */ public NativeImageOptions enableServerBuild(boolean enabled) { - server.set(enabled); - return this; - } - - /** - * Toggles native-image debug symbol output. - * - * @param debug Value which controls whether the debug symbols should be generated. - * @return this - */ - public NativeImageOptions debug(boolean debug) { - this.debug.set(debug); - return this; - } - - /** - * Gets the value which toggles native-image debug symbol output. - * - * @return Is debug enabled - */ - public Property getDebug() { - return debug; - } - - /** - * Sets whether to enable a fallback native image build or not. - * - * @param fallback The value which toggles a fallback native image build. - * @return this - */ - public NativeImageOptions fallback(boolean fallback) { - this.fallback.set(fallback); + getServer().set(enabled); return this; } - /** - * @return Whether to enable fallbacks (defaults to false). - */ - public Property getFallback() { - return fallback; - } - - /** - * Gets the value which toggles the native-image-agent usage. - * - * @return The value which toggles the native-image-agent usage. - */ - public Property getAgent() { - return this.agent; - } - - /** - * Gets the value which toggles persisting of agent config to META-INF. - * - * @return The value which toggles persisting of agent config to META-INF. - */ - public Property getPersistConfig() { - return this.persistConfig; - } } From c0c7e15d60794a0bad6cf2660ae6ac6803ee4a39 Mon Sep 17 00:00:00 2001 From: Cedric Champeau Date: Wed, 23 Jun 2021 14:02:24 +0200 Subject: [PATCH 06/23] Simplify task configuration Previously to this commit, there were 2 extensions of 2 different types to configure the native extension, depending on whether it was for tests or not. It wasn't necessary, as the configuration of the native image task is the same in both cases, it's simply the values of the extensions which differ slightly. Therefore, this commit simplifies everything by using a _single_ extension type. Similarly, there were 2 "build image" task types, when in practice the building mechanism is the same in both cases. Therefore, the 2 tasks were replaced with a single one, `BuildNativeImageTask`, which is setup appropriately in both cases. Last, for the same reason, the 2 "run" task types have been replaced with a single version. Last but not least, configuration of the tasks is now properly done by the plugin, instead of being mixed between the plugin configuration and the task creation. There were ordering issues due by the fact that the configuration of the task was mutating its own state. Instead, tasks are now properly isolated, without any knowledge of their configuration, and the plugin is responsible for injecting the configuration of the tasks. This makes it possible, for example, to derive the name of the test image from the name of the main image. It's worth noting that this commit didn't fully refactor the configuration phase, because there's still fishy configuration of the 'agent' side of things. This will be done in a subsequent commit. --- .../JavaApplicationFunctionalTest.groovy | 4 +- ...aApplicationWithTestsFunctionalTest.groovy | 2 +- .../java/org/graalvm/buildtools/Utils.java | 107 --------- .../buildtools/gradle/GradleUtils.java | 92 -------- .../buildtools/gradle/NativeImagePlugin.java | 103 +++++++-- .../gradle/dsl/NativeImageOptions.java | 180 ++++++--------- .../gradle/internal/GraalVMLogger.java | 4 + .../GradleUtils.java} | 39 ++-- .../Utils.java} | 53 ++--- .../gradle/tasks/BuildNativeImageTask.java | 209 ++++++++++++++++++ .../gradle/tasks/NativeBuildTask.java | 132 ----------- .../gradle/tasks/NativeRunTask.java | 38 ++-- .../gradle/tasks/TestNativeBuildTask.java | 154 ------------- 13 files changed, 420 insertions(+), 697 deletions(-) delete mode 100644 native-gradle-plugin/src/main/java/org/graalvm/buildtools/Utils.java delete mode 100644 native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/GradleUtils.java rename native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/{dsl/JUnitPlatformOptions.java => internal/GradleUtils.java} (65%) rename native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/{tasks/TestNativeRunTask.java => internal/Utils.java} (58%) create mode 100644 native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/BuildNativeImageTask.java delete mode 100644 native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/NativeBuildTask.java delete mode 100644 native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/TestNativeBuildTask.java diff --git a/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JavaApplicationFunctionalTest.groovy b/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JavaApplicationFunctionalTest.groovy index e14722cee..8680cc246 100644 --- a/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JavaApplicationFunctionalTest.groovy +++ b/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JavaApplicationFunctionalTest.groovy @@ -45,7 +45,7 @@ import org.graalvm.buildtools.gradle.fixtures.AbstractFunctionalTest class JavaApplicationFunctionalTest extends AbstractFunctionalTest { def "can build a native image for a simple application"() { gradleVersion = version - def nativeApp = file("build/native/java-application") + def nativeApp = file("build/native/nativeBuild/java-application") given: withSample("java-application") @@ -56,7 +56,7 @@ class JavaApplicationFunctionalTest extends AbstractFunctionalTest { then: tasks { succeeded ':jar', ':nativeBuild' - // doesNotContain ':build' + doesNotContain ':build' } and: diff --git a/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JavaApplicationWithTestsFunctionalTest.groovy b/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JavaApplicationWithTestsFunctionalTest.groovy index 3116d94de..dcfe38414 100644 --- a/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JavaApplicationWithTestsFunctionalTest.groovy +++ b/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JavaApplicationWithTestsFunctionalTest.groovy @@ -48,7 +48,7 @@ class JavaApplicationWithTestsFunctionalTest extends AbstractFunctionalTest { @Unroll("can execute tests in a native image on Gradle #version with JUnit Platform #junitVersion") def "can build a native image and run it"() { gradleVersion = version - def nativeTestsApp = file("build/native/native-tests") + def nativeTestsApp = file("build/native/nativeTestBuild/java-application-tests") given: withSample("java-application-with-tests") diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/Utils.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/Utils.java deleted file mode 100644 index 91f4cec9c..000000000 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/Utils.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (c) 2021, 2021 Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * The Universal Permissive License (UPL), Version 1.0 - * - * Subject to the condition set forth below, permission is hereby granted to any - * person obtaining a copy of this software, associated documentation and/or - * data (collectively the "Software"), free of charge and under any and all - * copyright rights in the Software, and any and all patent rights owned or - * freely licensable by each licensor hereunder covering either (i) the - * unmodified Software as contributed to or provided by such licensor, or (ii) - * the Larger Works (as defined below), to deal in both - * - * (a) the Software, and - * - * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if - * one is included with the Software each a "Larger Work" to which the Software - * is contributed by such licensors), - * - * without restriction, including without limitation the rights to copy, create - * derivative works of, display, perform, and distribute the Software and make, - * use, sell, offer for sale, import, export, have made, and have sold the - * Software and the Larger Work(s), and to sublicense the foregoing rights on - * either these or other terms. - * - * This license is subject to the following condition: - * - * The above copyright notice and either this complete permission notice or at a - * minimum a reference to the UPL must be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package org.graalvm.buildtools; - -import java.io.File; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Optional; -import java.util.regex.Pattern; -import java.util.stream.Stream; - -/** - * Utility class containing various native-image and JVM related methods. - * Keep this file in sync across all build tool plugins. - */ -public class Utils { - public static final boolean IS_WINDOWS = System.getProperty("os.name", "unknown").contains("Windows"); - public static final String EXECUTABLE_EXTENSION = (IS_WINDOWS ? ".exe" : ""); - public static final String NATIVE_IMAGE_EXE = "native-image" + (IS_WINDOWS ? ".cmd" : ""); - public static final String NATIVE_IMAGE_OUTPUT_FOLDER = "native"; - public static final String AGENT_PROPERTY = "agent"; - public static final String AGENT_OUTPUT_FOLDER = Paths.get(NATIVE_IMAGE_OUTPUT_FOLDER, "agent-output").toString(); - public static final String NATIVE_TESTS_EXE = "native-tests" + EXECUTABLE_EXTENSION; - public static final String AGENT_FILTER = "agent-filter.json"; - public static final String PERSIST_CONFIG_PROPERTY = "persistConfig"; - public static final String MAVEN_GROUP_ID = "org.graalvm.buildtools"; - - public static Path getJavaHomeNativeImage(String javaHomeVariable, Boolean failFast) { - String graalHome = System.getenv(javaHomeVariable); - if (graalHome == null) { - return null; - } - - Path graalExe = Paths.get(graalHome).resolve("bin").resolve(NATIVE_IMAGE_EXE); - if (!Files.exists(graalExe) && failFast) { - throw new RuntimeException("native-image is not installed in your " + javaHomeVariable + "." + - "You should install it using `gu install native-image`"); - } - return graalExe; - } - - public static Path getNativeImageFromPath() { - Optional exePath = Stream.of(System.getenv("PATH").split(Pattern.quote(File.pathSeparator))) - .map(Paths::get) - .filter(path -> Files.exists(path.resolve(NATIVE_IMAGE_EXE))) - .findFirst(); - return exePath.map(path -> path.resolve(NATIVE_IMAGE_EXE)).orElse(null); - } - - public static Path getNativeImage() { - Path nativeImage = getJavaHomeNativeImage("JAVA_HOME", false); - - if (nativeImage == null) { - nativeImage = getNativeImageFromPath(); - } - - if (nativeImage == null) { - nativeImage = getJavaHomeNativeImage("GRAALVM_HOME", true); - } - - if (nativeImage == null) { - throw new RuntimeException("GraalVM native-image is missing from your system.\n " + - "Make sure that GRAALVM_HOME environment variable is present."); - } - - return nativeImage; - } -} diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/GradleUtils.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/GradleUtils.java deleted file mode 100644 index 6147e46a2..000000000 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/GradleUtils.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (c) 2021, 2021 Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * The Universal Permissive License (UPL), Version 1.0 - * - * Subject to the condition set forth below, permission is hereby granted to any - * person obtaining a copy of this software, associated documentation and/or - * data (collectively the "Software"), free of charge and under any and all - * copyright rights in the Software, and any and all patent rights owned or - * freely licensable by each licensor hereunder covering either (i) the - * unmodified Software as contributed to or provided by such licensor, or (ii) - * the Larger Works (as defined below), to deal in both - * - * (a) the Software, and - * - * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if - * one is included with the Software each a "Larger Work" to which the Software - * is contributed by such licensors), - * - * without restriction, including without limitation the rights to copy, create - * derivative works of, display, perform, and distribute the Software and make, - * use, sell, offer for sale, import, export, have made, and have sold the - * Software and the Larger Work(s), and to sublicense the foregoing rights on - * either these or other terms. - * - * This license is subject to the following condition: - * - * The above copyright notice and either this complete permission notice or at a - * minimum a reference to the UPL must be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package org.graalvm.buildtools.gradle; - -import org.graalvm.buildtools.Utils; -import org.gradle.api.Project; -import org.gradle.api.file.FileCollection; -import org.gradle.api.plugins.JavaPlugin; -import org.gradle.api.plugins.JavaPluginConvention; -import org.gradle.api.tasks.SourceSet; -import org.gradle.api.tasks.SourceSetContainer; - -import java.io.File; -import java.nio.file.Path; -import java.util.Arrays; -import java.util.List; - -/** - * Utility class containing various gradle related methods. - */ -@SuppressWarnings("unused") -public class GradleUtils { - - public static final List DEPENDENT_CONFIGURATIONS = Arrays.asList( - JavaPlugin.API_CONFIGURATION_NAME, - JavaPlugin.RUNTIME_ONLY_CONFIGURATION_NAME, - JavaPlugin.IMPLEMENTATION_CONFIGURATION_NAME); - - @SuppressWarnings("BooleanMethodIsAlwaysInverted") - public static boolean hasTestClasses(Project project) { - FileCollection testClasspath = getSourceSet(project, SourceSet.TEST_SOURCE_SET_NAME).getOutput().getClassesDirs(); - return testClasspath.getFiles().stream().anyMatch(File::exists); - } - - public static SourceSet getSourceSet(Project project, String sourceSetName) { - SourceSetContainer sourceSetContainer = project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets(); - return sourceSetContainer.findByName(sourceSetName); - } - - public static FileCollection getClassPath(Project project) { - FileCollection main = getClassPath(project, SourceSet.MAIN_SOURCE_SET_NAME); - FileCollection test = getClassPath(project, SourceSet.TEST_SOURCE_SET_NAME); - return main.plus(test); - } - - @SuppressWarnings("SameParameterValue") - public static FileCollection getClassPath(Project project, String sourceSetName) { - return getSourceSet(project, sourceSetName).getRuntimeClasspath(); - } - - public static Path getTargetDir(Project project) { - return project.getBuildDir().toPath().resolve(Utils.NATIVE_IMAGE_OUTPUT_FOLDER); - } -} diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java index 57d8bd21b..2e51d74e1 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java @@ -40,23 +40,27 @@ */ package org.graalvm.buildtools.gradle; -import org.graalvm.buildtools.Utils; +import org.graalvm.buildtools.gradle.internal.Utils; import org.graalvm.buildtools.VersionInfo; -import org.graalvm.buildtools.gradle.dsl.JUnitPlatformOptions; import org.graalvm.buildtools.gradle.dsl.NativeImageOptions; import org.graalvm.buildtools.gradle.internal.GraalVMLogger; -import org.graalvm.buildtools.gradle.tasks.NativeBuildTask; +import org.graalvm.buildtools.gradle.internal.GradleUtils; +import org.graalvm.buildtools.gradle.tasks.BuildNativeImageTask; import org.graalvm.buildtools.gradle.tasks.NativeRunTask; -import org.graalvm.buildtools.gradle.tasks.TestNativeBuildTask; -import org.graalvm.buildtools.gradle.tasks.TestNativeRunTask; import org.gradle.api.GradleException; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.Task; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.file.FileCollection; import org.gradle.api.plugins.ApplicationPlugin; +import org.gradle.api.plugins.JavaApplication; import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.TaskProvider; import org.gradle.process.JavaForkOptions; import java.io.IOException; @@ -67,14 +71,19 @@ import java.util.Arrays; import java.util.Objects; -import static org.graalvm.buildtools.Utils.AGENT_FILTER; -import static org.graalvm.buildtools.Utils.AGENT_OUTPUT_FOLDER; +import static org.graalvm.buildtools.gradle.internal.Utils.AGENT_FILTER; +import static org.graalvm.buildtools.gradle.internal.Utils.AGENT_OUTPUT_FOLDER; /** * Gradle plugin for GraalVM Native Image. */ @SuppressWarnings("unused") public class NativeImagePlugin implements Plugin { + public static final String NATIVE_BUILD_TASK_NAME = "nativeBuild"; + public static final String NATIVE_TEST_TASK_NAME = "nativeTest"; + public static final String NATIVE_TEST_BUILD_TASK_NAME = "nativeTestBuild"; + public static final String NATIVE_TEST_EXTENSION = "nativeTest"; + public static final String NATIVE_BUILD_EXTENSION = "nativeBuild"; private GraalVMLogger logger; @@ -90,17 +99,22 @@ public void apply(Project project) { logger.log("===================="); // Add DSL extensions for building and testing - NativeImageOptions buildExtension = NativeImageOptions.register(project); + NativeImageOptions buildExtension = createMainExtension(project); + NativeImageOptions testExtension = createTestExtension(project, buildExtension); - JUnitPlatformOptions testExtension = JUnitPlatformOptions.register(project); + project.getPlugins().withId("application", p -> buildExtension.getMainClass().convention( + project.getExtensions().findByType(JavaApplication.class).getMainClass() + )); + + registerServiceProvider(project, nativeImageServiceProvider); // Register Native Image tasks - project.getTasks().register(NativeBuildTask.TASK_NAME, NativeBuildTask.class, task -> { - task.usesService(nativeImageServiceProvider); - task.getServer().set(nativeImageServiceProvider); - }); + TaskProvider imageBuilder = project.getTasks().register(NATIVE_BUILD_TASK_NAME, BuildNativeImageTask.class); - project.getTasks().register(NativeRunTask.TASK_NAME, NativeRunTask.class); + project.getTasks().register(NativeRunTask.TASK_NAME, NativeRunTask.class, task -> { + task.getImage().convention(imageBuilder.map(t -> t.getOutputFile().get())); + task.getRuntimeArgs().convention(buildExtension.getRuntimeArgs()); + }); if (project.hasProperty(Utils.AGENT_PROPERTY) || buildExtension.getAgent().get()) { // We want to add agent invocation to "run" task, but it is only available when @@ -119,19 +133,22 @@ public void apply(Project project) { // Testing part begins here. Task test = project.getTasksByName(JavaPlugin.TEST_TASK_NAME, false).stream().findFirst().orElse(null); if (test != null) { + // Following ensures that required feature jar is on classpath for every project injectTestPluginDependencies(project); // If `test` task was found we should add `nativeTestBuild` and `nativeTest` // tasks to this project as well. - project.getTasks().register(TestNativeBuildTask.TASK_NAME, TestNativeBuildTask.class, task -> { - task.usesService(nativeImageServiceProvider); - task.getServer().set(nativeImageServiceProvider); + TaskProvider testImageBuilder = project.getTasks().register(NATIVE_TEST_BUILD_TASK_NAME, BuildNativeImageTask.class, task -> { + task.setDescription("Builds native image with tests."); + task.getOptions().set(testExtension); + task.dependsOn(test); // FIXME: this is wrong, there should be an explicit input + }); + project.getTasks().register(NATIVE_TEST_TASK_NAME, NativeRunTask.class, task -> { + task.setDescription("Runs native-image compiled tests."); + task.getImage().convention(testImageBuilder.map(t -> t.getOutputFile().get())); + task.getRuntimeArgs().convention(testExtension.getRuntimeArgs()); }); - Task nativeTest = project.getTasksByName(TestNativeBuildTask.TASK_NAME, false).stream().findFirst().orElse(null); - nativeTest.dependsOn(test); - - project.getTasks().register(TestNativeRunTask.TASK_NAME, TestNativeRunTask.class); if (project.hasProperty(Utils.AGENT_PROPERTY) || testExtension.getAgent().get()) { // Add agent invocation to test task. @@ -143,6 +160,50 @@ public void apply(Project project) { }); } + private static void registerServiceProvider(Project project, Provider nativeImageServiceProvider) { + project.getTasks() + .withType(BuildNativeImageTask.class) + .configureEach(task -> { + task.usesService(nativeImageServiceProvider); + task.getService().set(nativeImageServiceProvider); + }); + } + + private NativeImageOptions createMainExtension(Project project) { + NativeImageOptions buildExtension = NativeImageOptions.register(project, NATIVE_BUILD_EXTENSION); + buildExtension.getClasspath().from(findMainArtifacts(project)); + buildExtension.getClasspath().from(findConfiguration(project, JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME)); + return buildExtension; + } + + private static Configuration findConfiguration(Project project, String name) { + return project.getConfigurations().getByName(name); + } + + private static FileCollection findMainArtifacts(Project project) { + return findConfiguration(project, JavaPlugin.RUNTIME_ELEMENTS_CONFIGURATION_NAME) + .getOutgoing() + .getArtifacts() + .getFiles(); + } + + private NativeImageOptions createTestExtension(Project project, NativeImageOptions mainExtension) { + NativeImageOptions testExtension = NativeImageOptions.register(project, NATIVE_TEST_EXTENSION); + testExtension.getMainClass().set("org.graalvm.junit.platform.NativeImageJUnitLauncher"); + testExtension.getMainClass().finalizeValue(); + testExtension.getImageName().convention(mainExtension.getImageName().map(name -> name + Utils.NATIVE_TESTS_SUFFIX)); + ListProperty runtimeArgs = testExtension.getRuntimeArgs(); + runtimeArgs.add("--xml-output-dir"); + runtimeArgs.add(project.getLayout().getBuildDirectory().dir("test-results/test-native").map(d -> d.getAsFile().getAbsolutePath())); + testExtension.buildArgs("--features=org.graalvm.junit.platform.JUnitPlatformFeature"); + ConfigurableFileCollection classpath = testExtension.getClasspath(); + classpath.from(findMainArtifacts(project)); + classpath.from(findConfiguration(project, JavaPlugin.TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME)); + classpath.from(GradleUtils.findSourceSet(project, SourceSet.TEST_SOURCE_SET_NAME).getOutput().getClassesDirs()); + classpath.from(GradleUtils.findSourceSet(project, SourceSet.TEST_SOURCE_SET_NAME).getOutput().getResourcesDir()); + return testExtension; + } + private Provider registerNativeImageService(Project project) { return project.getGradle() .getSharedServices() diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java index bfbe75757..120ceaf90 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java @@ -40,27 +40,26 @@ */ package org.graalvm.buildtools.gradle.dsl; -import org.graalvm.buildtools.Utils; -import org.graalvm.buildtools.gradle.GradleUtils; -import org.gradle.api.GradleException; +import org.graalvm.buildtools.gradle.internal.Utils; +import org.graalvm.buildtools.gradle.internal.GradleUtils; import org.gradle.api.Project; import org.gradle.api.file.ConfigurableFileCollection; -import org.gradle.api.file.FileCollection; import org.gradle.api.model.ObjectFactory; -import org.gradle.api.plugins.JavaApplication; import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.MapProperty; import org.gradle.api.provider.Property; -import org.gradle.api.tasks.SourceSet; +import org.gradle.api.provider.ProviderFactory; +import org.gradle.api.tasks.Classpath; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputFiles; +import org.gradle.api.tasks.Nested; +import org.gradle.jvm.toolchain.JavaLanguageVersion; +import org.gradle.jvm.toolchain.JavaLauncher; +import org.gradle.jvm.toolchain.JavaToolchainService; +import org.gradle.jvm.toolchain.JvmVendorSpec; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; -import java.util.function.BooleanSupplier; import java.util.stream.Collectors; import java.util.stream.StreamSupport; @@ -77,6 +76,7 @@ public abstract class NativeImageOptions { * * @return The image name property. */ + @Input public abstract Property getImageName(); /** @@ -87,6 +87,7 @@ public abstract class NativeImageOptions { * * @return mainClass The main class. */ + @Input public abstract Property getMainClass(); /** @@ -94,6 +95,7 @@ public abstract class NativeImageOptions { * * @return Arguments for the native-image invocation. */ + @Input public abstract ListProperty getBuildArgs(); /** @@ -101,6 +103,7 @@ public abstract class NativeImageOptions { * * @return The system properties. Returns an empty map when there are no system properties. */ + @Input public abstract MapProperty getSystemProperties(); /** @@ -108,6 +111,8 @@ public abstract class NativeImageOptions { * * @return classpath The classpath for the native-image building. */ + @InputFiles + @Classpath public abstract ConfigurableFileCollection getClasspath(); /** @@ -116,6 +121,7 @@ public abstract class NativeImageOptions { * * @return The arguments. Returns an empty list if there are no arguments. */ + @Input public abstract ListProperty getJvmArgs(); /** @@ -123,6 +129,7 @@ public abstract class NativeImageOptions { * * @return The arguments. Returns an empty list if there are no arguments. */ + @Input public abstract ListProperty getRuntimeArgs(); /** @@ -130,6 +137,7 @@ public abstract class NativeImageOptions { * * @return Is debug enabled */ + @Input public abstract Property getDebug(); /** @@ -138,11 +146,13 @@ public abstract class NativeImageOptions { * * @return the server property */ + @Input public abstract Property getServer(); /** * @return Whether to enable fallbacks (defaults to false). */ + @Input public abstract Property getFallback(); /** @@ -150,6 +160,7 @@ public abstract class NativeImageOptions { * * @return Is verbose output */ + @Input public abstract Property getVerbose(); /** @@ -157,6 +168,7 @@ public abstract class NativeImageOptions { * * @return The value which toggles the native-image-agent usage. */ + @Input public abstract Property getAgent(); /** @@ -164,119 +176,57 @@ public abstract class NativeImageOptions { * * @return The value which toggles persisting of agent config to META-INF. */ + @Input public abstract Property getPersistConfig(); - // internal state - private boolean configured; - private final Map booleanCmds; + /** + * Returns the toolchain used to invoke native-image. Currently pointing + * to a Java launcher due to Gradle limitations. + */ + @Nested + public abstract Property getJavaLauncher(); + + /** + * Returns the list of configuration file directories (e.g resource-config.json, ...) which need + * to be passed to native-image. + * + * @return a collection of directories + */ + @InputFiles + public abstract ConfigurableFileCollection getConfigurationFileDirectories(); - public NativeImageOptions(ObjectFactory objectFactory) { + public NativeImageOptions(ObjectFactory objectFactory, + ProviderFactory providers, + JavaToolchainService toolchains, + String defaultImageName) { getDebug().convention(false); getServer().convention(false); getFallback().convention(false); getVerbose().convention(false); - getAgent().convention(false); + getAgent().convention(providers.gradleProperty(Utils.AGENT_PROPERTY) + .forUseAtConfigurationTime() + .map(Boolean::valueOf) + .orElse(false)); getPersistConfig().convention(false); - - this.booleanCmds = new LinkedHashMap<>(3); - this.booleanCmds.put(getDebug()::get, "-H:GenerateDebugInfo=1"); - this.booleanCmds.put(() -> !getFallback().get(), "--no-fallback"); - this.booleanCmds.put(getVerbose()::get, "--verbose"); - this.booleanCmds.put(getServer()::get, "-Dcom.oracle.graalvm.isaot=true"); - - this.configured = false; - } - - public static NativeImageOptions register(Project project) { - return project.getExtensions().create("nativeBuild", NativeImageOptions.class, project.getObjects()); - } - - public void configure(Project project) { - configure(project, SourceSet.MAIN_SOURCE_SET_NAME); + getImageName().convention(defaultImageName); + getJavaLauncher().convention( + toolchains.launcherFor(spec -> { + spec.getLanguageVersion().set(JavaLanguageVersion.of(11)); + if (GradleUtils.isAtLeastGradle7()) { + spec.getVendor().set(JvmVendorSpec.matching("GraalVM Community")); + } + }) + ); } - /** - * Configures the arguments for the native-image invocation based on user supplied options. - * - * @param project The current project. - * @param sourceSetName Used source set name. - */ - protected void configure(Project project, String sourceSetName) { - if (configured) { - return; - } - this.configured = true; - - // User arguments should be at the end of the native-image invocation - // but at this moment they are already set. So we remove them here - // and add them back later. - List userArgs = getBuildArgs().get(); - getBuildArgs().set(Collections.emptyList()); - - FileCollection classpath = GradleUtils.getClassPath(project); - FileCollection userClasspath = getClasspath(); - if (userClasspath != null) { - classpath = classpath.plus(userClasspath); - } - - String cp = classpath.getAsPath(); - if (cp.length() > 0) { - buildArgs("-cp", cp); - } - - booleanCmds.forEach((property, cmd) -> { - if (property.getAsBoolean()) { - buildArgs(cmd); - } - }); - - buildArgs("-H:Path=" + Utils.NATIVE_IMAGE_OUTPUT_FOLDER); - - if (!getImageName().isPresent()) { - getImageName().set(project.getName().toLowerCase()); - } - buildArgs("-H:Name=" + getImageName().get()); - - Map sysProps = getSystemProperties().get(); - sysProps.forEach((n, v) -> { - if (v != null) { - buildArgs("-D" + n + "=\"" + v + "\""); - } - }); - - List jvmArgs = getJvmArgs().get(); - for (String jvmArg : jvmArgs) { - buildArgs("-J" + jvmArg); - } - - if (project.hasProperty(Utils.AGENT_PROPERTY) || getAgent().get()) { - Path agentOutput = project.getBuildDir().toPath() - .resolve(Utils.AGENT_OUTPUT_FOLDER).resolve(sourceSetName).toAbsolutePath(); - - if (!agentOutput.toFile().exists()) { - // Maybe user chose to persist configuration into the codebase, so lets also check if that folder exists. - agentOutput = Paths.get(project.getProjectDir().getAbsolutePath(), "src", sourceSetName, "resources", "META-INF", "native-image"); - if (!agentOutput.toFile().exists()) { - throw new GradleException("Agent output missing while `agent` option is set.\n" + - "Did you run the corresponding task in JVM mode before with the `-Pagent` option enabled?"); - } - } - buildArgs("-H:ConfigurationFileDirectories=" + agentOutput); - buildArgs("--allow-incomplete-classpath"); - } - - if (!getMainClass().isPresent()) { - JavaApplication app = project.getExtensions().findByType(JavaApplication.class); - if (app != null && app.getMainClass().isPresent()) { - getMainClass().set(app.getMainClass().get()); - } - } - - if (getMainClass().isPresent()) { - buildArgs("-H:Class=" + getMainClass().get()); - } - - getBuildArgs().addAll(userArgs); + public static NativeImageOptions register(Project project, String extensionName) { + return project.getExtensions().create(extensionName, + NativeImageOptions.class, + project.getObjects(), + project.getProviders(), + project.getExtensions().findByType(JavaToolchainService.class), + project.getName() + ); } /** diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/GraalVMLogger.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/GraalVMLogger.java index 117f40961..6a957cf80 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/GraalVMLogger.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/GraalVMLogger.java @@ -56,6 +56,10 @@ public void log(String s) { delegate.info("[native-image-plugin] {}", s); } + public void lifecycle(String s) { + delegate.lifecycle("[native-image-plugin] {}", s); + } + public void error(String s) { delegate.error("[native-image-plugin] {}", s); } diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/JUnitPlatformOptions.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/GradleUtils.java similarity index 65% rename from native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/JUnitPlatformOptions.java rename to native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/GradleUtils.java index fa8c3378b..ec27193d4 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/JUnitPlatformOptions.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/GradleUtils.java @@ -38,36 +38,27 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -package org.graalvm.buildtools.gradle.dsl; +package org.graalvm.buildtools.gradle.internal; -import org.graalvm.buildtools.Utils; import org.gradle.api.Project; -import org.gradle.api.model.ObjectFactory; +import org.gradle.api.plugins.JavaPluginConvention; import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.SourceSetContainer; +import org.gradle.util.GradleVersion; -import java.nio.file.Paths; - -public abstract class JUnitPlatformOptions extends NativeImageOptions { - public static final String EXTENSION_NAME = "nativeTest"; - - - @SuppressWarnings("UnstableApiUsage") - public JUnitPlatformOptions(ObjectFactory objectFactory) { - super(objectFactory); - getMainClass().set("org.graalvm.junit.platform.NativeImageJUnitLauncher"); - getMainClass().finalizeValue(); - getImageName().set(Utils.NATIVE_TESTS_EXE); - getImageName().finalizeValue(); - runtimeArgs("--xml-output-dir", Paths.get("test-results").resolve("test-native")); - } +/** + * Utility class containing various gradle related methods. + */ +@SuppressWarnings("unused") +public class GradleUtils { + private static final GradleVersion GRADLE_7 = GradleVersion.version("7.0"); - public static JUnitPlatformOptions register(Project project) { - return project.getExtensions().create(EXTENSION_NAME, JUnitPlatformOptions.class, project.getObjects()); + public static SourceSet findSourceSet(Project project, String sourceSetName) { + SourceSetContainer sourceSetContainer = project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets(); + return sourceSetContainer.findByName(sourceSetName); } - @Override - public void configure(Project project) { - buildArgs("--features=org.graalvm.junit.platform.JUnitPlatformFeature"); - super.configure(project, SourceSet.TEST_SOURCE_SET_NAME); + public static boolean isAtLeastGradle7() { + return GradleVersion.current().compareTo(GRADLE_7) >= 0; } } diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/TestNativeRunTask.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/Utils.java similarity index 58% rename from native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/TestNativeRunTask.java rename to native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/Utils.java index cc3961b5c..1712b5112 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/TestNativeRunTask.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/Utils.java @@ -38,43 +38,28 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -package org.graalvm.buildtools.gradle.tasks; +package org.graalvm.buildtools.gradle.internal; -import org.graalvm.buildtools.gradle.GradleUtils; -import org.graalvm.buildtools.gradle.dsl.JUnitPlatformOptions; -import org.graalvm.buildtools.gradle.internal.GraalVMLogger; -import org.gradle.api.Project; -import org.gradle.api.plugins.JavaBasePlugin; -import org.gradle.api.tasks.AbstractExecTask; +import org.gradle.api.provider.Provider; -import java.io.File; +import java.nio.file.Paths; -@SuppressWarnings("unused") -public class TestNativeRunTask extends AbstractExecTask { - public static final String TASK_NAME = "nativeTest"; - - protected JUnitPlatformOptions options; - - public TestNativeRunTask() { - super(TestNativeRunTask.class); - dependsOn(TestNativeBuildTask.TASK_NAME); - setWorkingDir(getProject().getBuildDir()); - setDescription("Runs native-image compiled tests."); - setGroup(JavaBasePlugin.VERIFICATION_GROUP); - - options = getProject().getExtensions().findByType(JUnitPlatformOptions.class); - } +/** + * Utility class containing various native-image and JVM related methods. + * Keep this file in sync across all build tool plugins. + */ +public class Utils { + private static final boolean IS_WINDOWS = System.getProperty("os.name", "unknown").contains("Windows"); + private static final String EXECUTABLE_EXTENSION = (IS_WINDOWS ? ".cmd" : ""); + public static final String NATIVE_IMAGE_EXE = "native-image" + EXECUTABLE_EXTENSION; + public static final String NATIVE_IMAGE_OUTPUT_FOLDER = "native"; + public static final String AGENT_PROPERTY = "agent"; + public static final String AGENT_OUTPUT_FOLDER = Paths.get(NATIVE_IMAGE_OUTPUT_FOLDER, "agent-output").toString(); + public static final String NATIVE_TESTS_SUFFIX = "-tests"; + public static final String AGENT_FILTER = "agent-filter.json"; + public static final String PERSIST_CONFIG_PROPERTY = "persistConfig"; - @Override - public void exec() { - Project project = getProject(); - GraalVMLogger logger = new GraalVMLogger(getLogger()); - if (!GradleUtils.hasTestClasses(project)) { - logger.log("There were no test classes in project " + project.getName() + ", so it was skipped."); - return; - } - setExecutable(new File(GradleUtils.getTargetDir(project).toFile(), options.getImageName().get())); - args(options.getRuntimeArgs().get()); - super.exec(); + public static Provider executableNameOf(Provider provider) { + return provider.map(name -> name + EXECUTABLE_EXTENSION); } } diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/BuildNativeImageTask.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/BuildNativeImageTask.java new file mode 100644 index 000000000..db4bae9c7 --- /dev/null +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/BuildNativeImageTask.java @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2021, 2021 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.graalvm.buildtools.gradle.tasks; + +import org.graalvm.buildtools.gradle.internal.Utils; +import org.graalvm.buildtools.gradle.NativeImageService; +import org.graalvm.buildtools.gradle.dsl.NativeImageOptions; +import org.graalvm.buildtools.gradle.internal.GraalVMLogger; +import org.gradle.api.DefaultTask; +import org.gradle.api.GradleException; +import org.gradle.api.Transformer; +import org.gradle.api.file.Directory; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.FileSystemLocation; +import org.gradle.api.file.RegularFile; +import org.gradle.api.plugins.JavaBasePlugin; +import org.gradle.api.provider.Property; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.Internal; +import org.gradle.api.tasks.Nested; +import org.gradle.api.tasks.Optional; +import org.gradle.api.tasks.OutputFile; +import org.gradle.api.tasks.TaskAction; +import org.gradle.jvm.toolchain.JavaInstallationMetadata; +import org.gradle.process.ExecOperations; + +import javax.inject.Inject; +import java.io.File; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import static org.graalvm.buildtools.gradle.internal.Utils.NATIVE_IMAGE_EXE; + +/** + * This task is responsible for generating a native image by + * calling the corresponding tool in the GraalVM toolchain. + */ +public abstract class BuildNativeImageTask extends DefaultTask { + private static final Transformer NEGATE = b -> !b; + + @Nested + public abstract Property getOptions(); + + @Inject + protected abstract ExecOperations getExecOperations(); + + @Internal + protected abstract DirectoryProperty getWorkingDirectory(); + + @OutputFile + public abstract DirectoryProperty getOutputDirectory(); + + @Optional + @Input + protected Provider getGraalVMHome() { + return getProject().getProviders().environmentVariable("GRAALVM_HOME"); + } + + @Internal + public Provider getExecutableName() { + return Utils.executableNameOf(getOptions().flatMap(NativeImageOptions::getImageName)); + } + + @Internal + public Provider getOutputFile() { + return getOutputDirectory().map(dir -> dir.file(getExecutableName()).get()); + } + + public BuildNativeImageTask() { + DirectoryProperty buildDir = getProject().getLayout().getBuildDirectory(); + Provider outputDir = buildDir.dir("native/" + getName()); + getWorkingDirectory().set(outputDir); + setDescription("Builds a native image."); + setGroup(JavaBasePlugin.VERIFICATION_GROUP); + + getOptions().convention(getProject().getExtensions().findByType(NativeImageOptions.class)); + getOutputDirectory().convention(outputDir); + } + + private List buildActualCommandLineArgs() { + getOptions().finalizeValue(); + NativeImageOptions options = getOptions().get(); + List cliArgs = new ArrayList<>(20); + + cliArgs.add("-cp"); + cliArgs.add(options.getClasspath().getAsPath()); + + appendBooleanOption(cliArgs, options.getDebug(), "-H:GenerateDebugInfo=1"); + appendBooleanOption(cliArgs, options.getFallback().map(NEGATE), "--no-fallback"); + appendBooleanOption(cliArgs, options.getVerbose(), "--verbose"); + appendBooleanOption(cliArgs, options.getServer(), "-Dcom.oracle.graalvm.isaot=true"); + + cliArgs.add("-H:Path=" + getOutputDirectory().getAsFile().get().getAbsolutePath()); + cliArgs.add("-H:Name=" + getExecutableName().get()); + + options.getSystemProperties().get().forEach((n, v) -> { + if (v != null) { + cliArgs.add("-D" + n + "=\"" + v + "\""); + } + }); + + options.getJvmArgs().get().forEach(jvmArg -> cliArgs.add("-J" + jvmArg)); + + String configFiles = options.getConfigurationFileDirectories() + .getElements() + .get() + .stream() + .map(FileSystemLocation::getAsFile) + .map(File::getAbsolutePath) + .collect(Collectors.joining(",")); + if (!configFiles.isEmpty()) { + cliArgs.add("-H:ConfigurationFileDirectories=" + configFiles); + } + if (options.getAgent().get()) { + cliArgs.add("--allow-incomplete-classpath"); + } + if (options.getMainClass().isPresent()) { + cliArgs.add("-H:Class=" + options.getMainClass().get()); + } + cliArgs.addAll(options.getBuildArgs().get()); + return Collections.unmodifiableList(cliArgs); + } + + private static void appendBooleanOption(List cliArgs, Provider provider, String whenTrue) { + if (provider.get()) { + cliArgs.add(whenTrue); + } + } + + + // This property provides access to the service instance + @Internal + public abstract Property getService(); + + @TaskAction + @SuppressWarnings("ConstantConditions") + public void exec() { + List args = buildActualCommandLineArgs(); + NativeImageOptions options = getOptions().get(); + GraalVMLogger logger = new GraalVMLogger(getLogger()); + if (options.getVerbose().get()) { + logger.lifecycle("Args are: " + args); + } + JavaInstallationMetadata metadata = options.getJavaLauncher().get().getMetadata(); + File executablePath = metadata.getInstallationPath().file(NATIVE_IMAGE_EXE).getAsFile(); + if (!executablePath.exists() && getGraalVMHome().isPresent()) { + executablePath = Paths.get(getGraalVMHome().get()).resolve("bin").resolve(NATIVE_IMAGE_EXE).toFile(); + } + if (executablePath.exists()) { + logger.log("Using executable path: " + executablePath); + String executable = executablePath.getAbsolutePath(); + File outputDir = getOutputDirectory().getAsFile().get(); + if (outputDir.isDirectory() || outputDir.mkdirs()) { + getExecOperations().exec(spec -> { + spec.setWorkingDir(getWorkingDirectory()); + spec.args(args); + getService().get(); + spec.setExecutable(executable); + }); + logger.lifecycle("Native Image written to: " + outputDir); + } + return; + } + throw new GradleException("Expected to find " + NATIVE_IMAGE_EXE + " executable but it wasn't found. " + + "Make sure to declare the GRAALVM_HOME environment variable or install GraalVM with native-image in a standard location recognized by Gradle Java toolchain support"); + } +} diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/NativeBuildTask.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/NativeBuildTask.java deleted file mode 100644 index 2f4d856b9..000000000 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/NativeBuildTask.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (c) 2021, 2021 Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * The Universal Permissive License (UPL), Version 1.0 - * - * Subject to the condition set forth below, permission is hereby granted to any - * person obtaining a copy of this software, associated documentation and/or - * data (collectively the "Software"), free of charge and under any and all - * copyright rights in the Software, and any and all patent rights owned or - * freely licensable by each licensor hereunder covering either (i) the - * unmodified Software as contributed to or provided by such licensor, or (ii) - * the Larger Works (as defined below), to deal in both - * - * (a) the Software, and - * - * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if - * one is included with the Software each a "Larger Work" to which the Software - * is contributed by such licensors), - * - * without restriction, including without limitation the rights to copy, create - * derivative works of, display, perform, and distribute the Software and make, - * use, sell, offer for sale, import, export, have made, and have sold the - * Software and the Larger Work(s), and to sublicense the foregoing rights on - * either these or other terms. - * - * This license is subject to the following condition: - * - * The above copyright notice and either this complete permission notice or at a - * minimum a reference to the UPL must be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package org.graalvm.buildtools.gradle.tasks; - -import org.graalvm.buildtools.gradle.GradleUtils; -import org.graalvm.buildtools.gradle.NativeImageService; -import org.graalvm.buildtools.Utils; -import org.graalvm.buildtools.gradle.dsl.NativeImageOptions; -import org.gradle.api.Project; -import org.gradle.api.artifacts.ProjectDependency; -import org.gradle.api.file.FileCollection; -import org.gradle.api.plugins.JavaPlugin; -import org.gradle.api.provider.Property; -import org.gradle.api.tasks.AbstractExecTask; -import org.gradle.api.tasks.Input; -import org.gradle.api.tasks.InputFiles; -import org.gradle.api.tasks.Internal; -import org.gradle.api.tasks.OutputFile; -import org.gradle.api.tasks.SourceSet; -import org.gradle.jvm.tasks.Jar; -import org.gradle.language.base.plugins.LifecycleBasePlugin; - -import java.io.File; -import java.util.List; - -import static org.graalvm.buildtools.gradle.GradleUtils.DEPENDENT_CONFIGURATIONS; - -public abstract class NativeBuildTask extends AbstractExecTask { - public static final String TASK_NAME = "nativeBuild"; - - protected NativeImageOptions options; - - public NativeBuildTask() { - super(NativeBuildTask.class); - dependsOn("build"); - getProject().getConfigurations().configureEach( - configuration -> { - if (DEPENDENT_CONFIGURATIONS.contains(configuration.getName())) { - configuration.getDependencies().stream() - .filter(ProjectDependency.class::isInstance) - .forEach(dependency -> { - final Project otherProject = ((ProjectDependency) dependency).getDependencyProject(); - otherProject.getTasks().withType(Jar.class, jar -> { - if (jar.getName().equals(JavaPlugin.JAR_TASK_NAME)) { - dependsOn(jar); - } - }); - }); - } - } - ); - setWorkingDir(getProject().getBuildDir()); - setDescription("Builds native-image from this project."); - setGroup(LifecycleBasePlugin.BUILD_GROUP); - - options = getProject().getExtensions().findByType(NativeImageOptions.class); - options.configure(getProject()); - } - - @InputFiles - public FileCollection getInputFiles() { - return GradleUtils.getClassPath(getProject(), SourceSet.MAIN_SOURCE_SET_NAME); - } - - @Input - public List getArgs() { - return options.getBuildArgs().get(); - } - - @OutputFile - public File getOutputFile() { - return GradleUtils.getTargetDir(getProject()).resolve(options.getImageName().get()).toFile(); - } - - // This property provides access to the service instance - @Internal - public abstract Property getServer(); - - @Override - @SuppressWarnings("ConstantConditions") - public void exec() { - List args = getArgs(); - if (options.getVerbose().get()) { - System.out.println("Args are:"); - System.out.println(args); - } - this.args(args); - - getServer().get(); - setExecutable(Utils.getNativeImage()); - super.exec(); - System.out.println("Native Image written to: " + getOutputFile()); - } -} diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/NativeRunTask.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/NativeRunTask.java index 45935cb82..031056518 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/NativeRunTask.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/NativeRunTask.java @@ -40,33 +40,41 @@ */ package org.graalvm.buildtools.gradle.tasks; -import org.graalvm.buildtools.gradle.GradleUtils; -import org.graalvm.buildtools.gradle.dsl.NativeImageOptions; +import org.gradle.api.DefaultTask; +import org.gradle.api.file.RegularFileProperty; import org.gradle.api.plugins.ApplicationPlugin; -import org.gradle.api.tasks.AbstractExecTask; +import org.gradle.api.provider.ListProperty; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.TaskAction; +import org.gradle.process.ExecOperations; -import java.io.File; +import javax.inject.Inject; @SuppressWarnings("unused") -public class NativeRunTask extends AbstractExecTask { +public abstract class NativeRunTask extends DefaultTask { public static final String TASK_NAME = "nativeRun"; - protected NativeImageOptions options; + @InputFile + public abstract RegularFileProperty getImage(); + + @Input + public abstract ListProperty getRuntimeArgs(); + + @Inject + protected abstract ExecOperations getExecOperations(); public NativeRunTask() { - super(NativeRunTask.class); - dependsOn(NativeBuildTask.TASK_NAME); - setWorkingDir(getProject().getBuildDir()); + setDescription("Runs this project as a native-image application"); setGroup(ApplicationPlugin.APPLICATION_GROUP); - - options = getProject().getExtensions().findByType(NativeImageOptions.class); } - @Override + @TaskAction public void exec() { - setExecutable(new File(GradleUtils.getTargetDir(getProject()).toFile(), options.getImageName().get())); - args(options.getRuntimeArgs()); - super.exec(); + getExecOperations().exec(spec -> { + spec.setExecutable(getImage().get().getAsFile().getAbsolutePath()); + spec.args(getRuntimeArgs().get()); + }); } } diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/TestNativeBuildTask.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/TestNativeBuildTask.java deleted file mode 100644 index ea32dee5d..000000000 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/TestNativeBuildTask.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (c) 2021, 2021 Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * The Universal Permissive License (UPL), Version 1.0 - * - * Subject to the condition set forth below, permission is hereby granted to any - * person obtaining a copy of this software, associated documentation and/or - * data (collectively the "Software"), free of charge and under any and all - * copyright rights in the Software, and any and all patent rights owned or - * freely licensable by each licensor hereunder covering either (i) the - * unmodified Software as contributed to or provided by such licensor, or (ii) - * the Larger Works (as defined below), to deal in both - * - * (a) the Software, and - * - * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if - * one is included with the Software each a "Larger Work" to which the Software - * is contributed by such licensors), - * - * without restriction, including without limitation the rights to copy, create - * derivative works of, display, perform, and distribute the Software and make, - * use, sell, offer for sale, import, export, have made, and have sold the - * Software and the Larger Work(s), and to sublicense the foregoing rights on - * either these or other terms. - * - * This license is subject to the following condition: - * - * The above copyright notice and either this complete permission notice or at a - * minimum a reference to the UPL must be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package org.graalvm.buildtools.gradle.tasks; - -import org.graalvm.buildtools.Utils; -import org.graalvm.buildtools.gradle.GradleUtils; -import org.graalvm.buildtools.gradle.dsl.JUnitPlatformOptions; -import org.graalvm.buildtools.gradle.internal.GraalVMLogger; -import org.gradle.api.DefaultTask; -import org.gradle.api.Project; -import org.gradle.api.artifacts.ProjectDependency; -import org.gradle.api.file.DirectoryProperty; -import org.gradle.api.file.FileCollection; -import org.gradle.api.plugins.JavaBasePlugin; -import org.gradle.api.plugins.JavaPlugin; -import org.gradle.api.provider.Property; -import org.gradle.api.tasks.Input; -import org.gradle.api.tasks.InputFiles; -import org.gradle.api.tasks.Internal; -import org.gradle.api.tasks.OutputFile; -import org.gradle.api.tasks.SourceSet; -import org.gradle.api.tasks.TaskAction; -import org.gradle.jvm.tasks.Jar; -import org.gradle.process.ExecOperations; - -import javax.inject.Inject; -import java.io.File; -import java.util.List; - -import static org.graalvm.buildtools.gradle.GradleUtils.DEPENDENT_CONFIGURATIONS; - -public abstract class TestNativeBuildTask extends DefaultTask { - public static final String TASK_NAME = "nativeTestBuild"; - - protected JUnitPlatformOptions options; - - @Inject - protected abstract ExecOperations getExecOperations(); - - @Internal - protected abstract DirectoryProperty getWorkingDirectory(); - - public TestNativeBuildTask() { - dependsOn("testClasses"); - getProject().getConfigurations().configureEach( - configuration -> { - if (DEPENDENT_CONFIGURATIONS.contains(configuration.getName())) { - configuration.getDependencies().stream() - .filter(ProjectDependency.class::isInstance) - .forEach(dependency -> { - final Project otherProject = ((ProjectDependency) dependency).getDependencyProject(); - otherProject.getTasks().withType(Jar.class, jar -> { - if (jar.getName().equals(JavaPlugin.JAR_TASK_NAME)) { - dependsOn(jar); - } - }); - }); - } - } - ); - getWorkingDirectory().set(getProject().getBuildDir()); - setDescription("Builds native image with tests."); - setGroup(JavaBasePlugin.VERIFICATION_GROUP); - - options = getProject().getExtensions().findByType(JUnitPlatformOptions.class); - } - - @InputFiles - public FileCollection getInputFiles() { - Project project = this.getProject(); - FileCollection main = GradleUtils.getSourceSet(project, SourceSet.MAIN_SOURCE_SET_NAME).getOutput().getClassesDirs(); - FileCollection test = GradleUtils.getSourceSet(project, SourceSet.TEST_SOURCE_SET_NAME).getOutput().getClassesDirs(); - return main.plus(test); - } - - @Input - public List getArgs() { - options.configure(getProject()); - return options.getBuildArgs().get(); - } - - @OutputFile - public File getOutputFile() { - return GradleUtils.getTargetDir(getProject()).resolve(Utils.NATIVE_TESTS_EXE).toFile(); - } - - // This property provides access to the service instance - // It should be Property but because of a bug in Gradle - // we have to use a more generic type, see https://github.com/gradle/gradle/issues/17559 - @Internal - public abstract Property getServer(); - - @TaskAction - @SuppressWarnings("ConstantConditions") - public void exec() { - Project project = getProject(); - GraalVMLogger logger = new GraalVMLogger(getLogger()); - if (!GradleUtils.hasTestClasses(project)) { - logger.log("There were no test classes in project " + project.getName() + ", so it was skipped."); - return; - } - - List args = getArgs(); - if (options.getVerbose().get()) { - System.out.println("Args are:"); - System.out.println(args); - } - getExecOperations().exec(spec -> { - spec.setWorkingDir(getWorkingDirectory()); - spec.args(args); - getServer().get(); - spec.setExecutable(Utils.getNativeImage()); - }); - System.out.println("Native Image written to: " + getOutputFile()); - } -} From e5318d4ac4d0c58a8b72e69b2e8c5f0b4a1ce50c Mon Sep 17 00:00:00 2001 From: Cedric Champeau Date: Wed, 23 Jun 2021 16:15:56 +0200 Subject: [PATCH 07/23] Fix test discovery file not being used when building test image The test discovery file was generated, but never used in the native image, which caused the use of the discovery mode instead. --- ...UniqueIdTrackingTestExecutionListener.java | 14 +++- ...aApplicationWithTestsFunctionalTest.groovy | 5 ++ .../buildtools/gradle/NativeImagePlugin.java | 70 ++++++++++++------- .../gradle/internal/GradleUtils.java | 6 +- .../fixtures/AbstractFunctionalTest.groovy | 4 ++ 5 files changed, 70 insertions(+), 29 deletions(-) diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/UniqueIdTrackingTestExecutionListener.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/UniqueIdTrackingTestExecutionListener.java index 49156ed84..a5819d611 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/UniqueIdTrackingTestExecutionListener.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/UniqueIdTrackingTestExecutionListener.java @@ -67,6 +67,8 @@ @SuppressWarnings("unused") public class UniqueIdTrackingTestExecutionListener implements TestExecutionListener { + private static final String OUTPUT_DIR_SYSTEM_PROPERTY = "graalvm.testids.outputdir"; + static final String FILE_NAME = "test_ids.txt"; private final List uniqueIds = new ArrayList<>(); @@ -105,11 +107,17 @@ public void testPlanExecutionFinished(TestPlan testPlan) { @SuppressWarnings("ResultOfMethodCallIgnored") private static File getFile() { - File buildDir; + File buildDir = null; + + String buildDirProp = System.getProperty(OUTPUT_DIR_SYSTEM_PROPERTY, null); + if (buildDirProp != null) { + buildDir = new File(buildDirProp); + } - if (new File("pom.xml").exists()) { + if (buildDir == null && new File("pom.xml").exists()) { buildDir = new File("target"); - } else { + } + if (buildDir == null) { buildDir = new File("build"); } diff --git a/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JavaApplicationWithTestsFunctionalTest.groovy b/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JavaApplicationWithTestsFunctionalTest.groovy index dcfe38414..70959e507 100644 --- a/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JavaApplicationWithTestsFunctionalTest.groovy +++ b/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JavaApplicationWithTestsFunctionalTest.groovy @@ -61,6 +61,8 @@ class JavaApplicationWithTestsFunctionalTest extends AbstractFunctionalTest { succeeded ':testClasses', ':nativeTestBuild' // doesNotContain ':build' } + outputDoesNotContain "Running in 'test discovery' mode. Note that this is a fallback mode." + outputContains "Running in 'test listener' mode." and: nativeTestsApp.exists() @@ -109,6 +111,9 @@ class JavaApplicationWithTestsFunctionalTest extends AbstractFunctionalTest { } then: + outputDoesNotContain "Running in 'test discovery' mode. Note that this is a fallback mode." + outputContains "Running in 'test listener' mode." + outputContains """ [ 3 containers found ] [ 0 containers skipped ] diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java index 2e51d74e1..a40790f04 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java @@ -40,11 +40,11 @@ */ package org.graalvm.buildtools.gradle; -import org.graalvm.buildtools.gradle.internal.Utils; import org.graalvm.buildtools.VersionInfo; import org.graalvm.buildtools.gradle.dsl.NativeImageOptions; import org.graalvm.buildtools.gradle.internal.GraalVMLogger; import org.graalvm.buildtools.gradle.internal.GradleUtils; +import org.graalvm.buildtools.gradle.internal.Utils; import org.graalvm.buildtools.gradle.tasks.BuildNativeImageTask; import org.graalvm.buildtools.gradle.tasks.NativeRunTask; import org.gradle.api.GradleException; @@ -53,6 +53,7 @@ import org.gradle.api.Task; import org.gradle.api.artifacts.Configuration; import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.FileCollection; import org.gradle.api.plugins.ApplicationPlugin; import org.gradle.api.plugins.JavaApplication; @@ -60,9 +61,12 @@ import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.TaskCollection; import org.gradle.api.tasks.TaskProvider; +import org.gradle.api.tasks.testing.Test; import org.gradle.process.JavaForkOptions; +import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -130,36 +134,52 @@ public void apply(Project project) { }); } + // In future Gradle releases this becomes a proper DirectoryProperty + File testResultsDir = GradleUtils.getJavaPluginConvention(project).getTestResultsDir(); + DirectoryProperty testListDirectory = project.getObjects().directoryProperty(); + // Testing part begins here. - Task test = project.getTasksByName(JavaPlugin.TEST_TASK_NAME, false).stream().findFirst().orElse(null); - if (test != null) { - - // Following ensures that required feature jar is on classpath for every project - injectTestPluginDependencies(project); - - // If `test` task was found we should add `nativeTestBuild` and `nativeTest` - // tasks to this project as well. - TaskProvider testImageBuilder = project.getTasks().register(NATIVE_TEST_BUILD_TASK_NAME, BuildNativeImageTask.class, task -> { - task.setDescription("Builds native image with tests."); - task.getOptions().set(testExtension); - task.dependsOn(test); // FIXME: this is wrong, there should be an explicit input - }); - project.getTasks().register(NATIVE_TEST_TASK_NAME, NativeRunTask.class, task -> { - task.setDescription("Runs native-image compiled tests."); - task.getImage().convention(testImageBuilder.map(t -> t.getOutputFile().get())); - task.getRuntimeArgs().convention(testExtension.getRuntimeArgs()); - }); + TaskCollection testTask = findTestTask(project); + testTask.configureEach(test -> { + testListDirectory.set(new File(testResultsDir, test.getName() + "/testlist")); + test.getOutputs().dir(testResultsDir); + test.systemProperty("graalvm.testids.outputdir", testListDirectory.getAsFile().get()); + }); + + // Following ensures that required feature jar is on classpath for every project + injectTestPluginDependencies(project); - if (project.hasProperty(Utils.AGENT_PROPERTY) || testExtension.getAgent().get()) { - // Add agent invocation to test task. - Boolean persistConfig = System.getProperty(Utils.PERSIST_CONFIG_PROPERTY) != null - || testExtension.getPersistConfig().get(); - setAgentArgs(project, SourceSet.TEST_SOURCE_SET_NAME, test, persistConfig); - } + // If `test` task was found we should add `nativeTestBuild` and `nativeTest` + // tasks to this project as well. + TaskProvider testImageBuilder = project.getTasks().register(NATIVE_TEST_BUILD_TASK_NAME, BuildNativeImageTask.class, task -> { + task.setDescription("Builds native image with tests."); + task.getOptions().set(testExtension); + ConfigurableFileCollection testList = project.getObjects().fileCollection(); + // Later this will be replaced by a dedicated task not requiring execution of tests + testList.from(testListDirectory).builtBy(testTask); + testExtension.getClasspath().from(testList); + }); + + project.getTasks().register(NATIVE_TEST_TASK_NAME, NativeRunTask.class, task -> { + task.setDescription("Runs native-image compiled tests."); + task.getImage().convention(testImageBuilder.map(t -> t.getOutputFile().get())); + task.getRuntimeArgs().convention(testExtension.getRuntimeArgs()); + }); + + if (project.hasProperty(Utils.AGENT_PROPERTY) || testExtension.getAgent().get()) { + // Add agent invocation to test task. + Boolean persistConfig = System.getProperty(Utils.PERSIST_CONFIG_PROPERTY) != null + || testExtension.getPersistConfig().get(); + // setAgentArgs(project, SourceSet.TEST_SOURCE_SET_NAME, test, persistConfig); } + }); } + private TaskCollection findTestTask(Project project) { + return project.getTasks().withType(Test.class).matching(task -> JavaPlugin.TEST_TASK_NAME.equals(task.getName())); + } + private static void registerServiceProvider(Project project, Provider nativeImageServiceProvider) { project.getTasks() .withType(BuildNativeImageTask.class) diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/GradleUtils.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/GradleUtils.java index ec27193d4..335afa5d7 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/GradleUtils.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/GradleUtils.java @@ -54,10 +54,14 @@ public class GradleUtils { private static final GradleVersion GRADLE_7 = GradleVersion.version("7.0"); public static SourceSet findSourceSet(Project project, String sourceSetName) { - SourceSetContainer sourceSetContainer = project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets(); + SourceSetContainer sourceSetContainer = getJavaPluginConvention(project).getSourceSets(); return sourceSetContainer.findByName(sourceSetName); } + public static JavaPluginConvention getJavaPluginConvention(Project project) { + return project.getConvention().getPlugin(JavaPluginConvention.class); + } + public static boolean isAtLeastGradle7() { return GradleVersion.current().compareTo(GRADLE_7) >= 0; } diff --git a/native-gradle-plugin/src/testFixtures/groovy/org/graalvm/buildtools/gradle/fixtures/AbstractFunctionalTest.groovy b/native-gradle-plugin/src/testFixtures/groovy/org/graalvm/buildtools/gradle/fixtures/AbstractFunctionalTest.groovy index 26d7e9ada..3f8b37ce5 100644 --- a/native-gradle-plugin/src/testFixtures/groovy/org/graalvm/buildtools/gradle/fixtures/AbstractFunctionalTest.groovy +++ b/native-gradle-plugin/src/testFixtures/groovy/org/graalvm/buildtools/gradle/fixtures/AbstractFunctionalTest.groovy @@ -131,6 +131,10 @@ abstract class AbstractFunctionalTest extends Specification { assert output.contains(text) } + void outputDoesNotContain(String text) { + assert !output.contains(text) + } + void errorOutputContains(String text) { assert errorOutput.contains(text) } From 686a5fd1032aa583f29f157210f7b57837c97b7c Mon Sep 17 00:00:00 2001 From: Cedric Champeau Date: Wed, 23 Jun 2021 22:56:03 +0200 Subject: [PATCH 08/23] Rework how the agent is injected This commit reworks how the agent is injected on the `run` and `test` tasks. There is now a task which role is to copy the filter json file, but more importantly the value of the `agent` property is not read eagerly. A new test has been added to make sure that the agent is properly injected and that the configuration files are generated. This doesn't work properly for GraalVM versions <21.1. The "persistConfig" property is temporarily _unused_, we have to decide what to do with it. --- ...rg.graalvm.build.functional-testing.gradle | 11 +- .../config/checkstyle/checkstyle.xml | 3 +- ...aApplicationWithAgentFunctionalTest.groovy | 116 ++++++++++++ .../buildtools/gradle/NativeImagePlugin.java | 169 +++++++++++------- .../gradle/dsl/NativeImageOptions.java | 15 +- .../internal/CopyClasspathResourceTask.java | 83 +++++++++ .../gradle/tasks/BuildNativeImageTask.java | 7 +- .../build.gradle | 30 ++++ .../settings.gradle | 1 + .../java/org/graalvm/demo/Application.java | 19 ++ .../java/org/graalvm/demo/Calculator.java | 9 + .../main/java/org/graalvm/demo/Message.java | 5 + .../org/graalvm/demo/ApplicationTest.java | 38 ++++ .../java/org/graalvm/demo/CalculatorTest.java | 17 ++ .../gradle/fixtures/GraalVMSupport.groovy | 68 +++++++ 15 files changed, 515 insertions(+), 76 deletions(-) create mode 100644 native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JavaApplicationWithAgentFunctionalTest.groovy create mode 100644 native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/CopyClasspathResourceTask.java create mode 100644 native-gradle-plugin/src/samples/java-application-with-reflection/build.gradle create mode 100644 native-gradle-plugin/src/samples/java-application-with-reflection/settings.gradle create mode 100644 native-gradle-plugin/src/samples/java-application-with-reflection/src/main/java/org/graalvm/demo/Application.java create mode 100644 native-gradle-plugin/src/samples/java-application-with-reflection/src/main/java/org/graalvm/demo/Calculator.java create mode 100644 native-gradle-plugin/src/samples/java-application-with-reflection/src/main/java/org/graalvm/demo/Message.java create mode 100644 native-gradle-plugin/src/samples/java-application-with-reflection/src/test/java/org/graalvm/demo/ApplicationTest.java create mode 100644 native-gradle-plugin/src/samples/java-application-with-reflection/src/test/java/org/graalvm/demo/CalculatorTest.java create mode 100644 native-gradle-plugin/src/testFixtures/groovy/org/graalvm/buildtools/gradle/fixtures/GraalVMSupport.groovy diff --git a/native-gradle-plugin/buildSrc/src/main/groovy/org.graalvm.build.functional-testing.gradle b/native-gradle-plugin/buildSrc/src/main/groovy/org.graalvm.build.functional-testing.gradle index 823018d04..842cbfcb2 100644 --- a/native-gradle-plugin/buildSrc/src/main/groovy/org.graalvm.build.functional-testing.gradle +++ b/native-gradle-plugin/buildSrc/src/main/groovy/org.graalvm.build.functional-testing.gradle @@ -72,6 +72,11 @@ configurations { gradlePlugin.testSourceSets(sourceSets.functionalTest) configurations.functionalTestImplementation.extendsFrom(configurations.testImplementation) +def graalVm = javaToolchains.launcherFor { + languageVersion.set(JavaLanguageVersion.of(11)) + vendor.set(JvmVendorSpec.matching("GraalVM Community")) +} + ['functionalTest', 'fullFunctionalTest'].each { // Add a task to run the functional tests tasks.register(it, Test) { @@ -80,12 +85,10 @@ configurations.functionalTestImplementation.extendsFrom(configurations.testImple inputs.files(configurations.functionalTestCommonRepository) systemProperty('common.repo.url', configurations.functionalTestCommonRepository.incoming.files.files[0]) systemProperty('versions.junit', project.properties.junit_jupiter_version) - environment('GRAALVM_HOME', javaToolchains.launcherFor { - languageVersion.set(JavaLanguageVersion.of(11)) - vendor.set(JvmVendorSpec.matching("GraalVM Community")) - }.forUseAtConfigurationTime().get().metadata.installationPath.asFile.absolutePath) + environment('GRAALVM_HOME', graalVm.forUseAtConfigurationTime().get().metadata.installationPath.asFile.absolutePath) testClassesDirs = sourceSets.functionalTest.output.classesDirs classpath = sourceSets.functionalTest.runtimeClasspath + javaLauncher.set(graalVm) } } diff --git a/native-gradle-plugin/config/checkstyle/checkstyle.xml b/native-gradle-plugin/config/checkstyle/checkstyle.xml index 0bbed772e..a0cd0829a 100644 --- a/native-gradle-plugin/config/checkstyle/checkstyle.xml +++ b/native-gradle-plugin/config/checkstyle/checkstyle.xml @@ -15,8 +15,9 @@ + - + diff --git a/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JavaApplicationWithAgentFunctionalTest.groovy b/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JavaApplicationWithAgentFunctionalTest.groovy new file mode 100644 index 000000000..352739997 --- /dev/null +++ b/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JavaApplicationWithAgentFunctionalTest.groovy @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2021, 2021 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.graalvm.buildtools.gradle + +import org.graalvm.buildtools.gradle.fixtures.AbstractFunctionalTest +import org.graalvm.buildtools.gradle.fixtures.GraalVMSupport +import spock.lang.Requires +import spock.lang.Unroll + +@Requires({ + GraalVMSupport.isGraal() + && GraalVMSupport.majorVersion >= 21 + && GraalVMSupport.minorVersion > 0 +}) +class JavaApplicationWithAgentFunctionalTest extends AbstractFunctionalTest { + + @Unroll("agent is passed and generates resources files on Gradle #version with JUnit Platform #junitVersion") + def "agent is passed"() { + gradleVersion = version + debug = true + given: + withSample("java-application-with-reflection") + + when: + run 'nativeTest' + + then: + tasks { + succeeded ':jar', + ':copyAgentFilter', + ':nativeTest' + doesNotContain ':build' + } + + then: + outputContains """ +[ 4 containers found ] +[ 0 containers skipped ] +[ 4 containers started ] +[ 0 containers aborted ] +[ 4 containers successful ] +[ 0 containers failed ] +[ 7 tests found ] +[ 0 tests skipped ] +[ 7 tests started ] +[ 0 tests aborted ] +[ 7 tests successful ] +[ 0 tests failed ] +""".trim() + + and: + ['jni', 'proxy', 'reflect', 'resource', 'serialization'].each { name -> + assert file("build/native/agent-output/test/${name}-config.json").exists() + } + + where: + version << TESTED_GRADLE_VERSIONS + junitVersion = System.getProperty('versions.junit') + } + + @Unroll("agent property takes precedence on Gradle #version with JUnit Platform #junitVersion") + def "agent property takes precedence"() { + gradleVersion = version + debug = true + given: + withSample("java-application-with-reflection") + + when: + fails 'nativeTest', '-Pagent=false' + + then: + outputContains """org.graalvm.demo.ApplicationTest > message is hello native FAILED""" + + where: + version << TESTED_GRADLE_VERSIONS + junitVersion = System.getProperty('versions.junit') + } +} diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java index a40790f04..4647fd990 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java @@ -42,41 +42,47 @@ import org.graalvm.buildtools.VersionInfo; import org.graalvm.buildtools.gradle.dsl.NativeImageOptions; +import org.graalvm.buildtools.gradle.internal.CopyClasspathResourceTask; import org.graalvm.buildtools.gradle.internal.GraalVMLogger; import org.graalvm.buildtools.gradle.internal.GradleUtils; import org.graalvm.buildtools.gradle.internal.Utils; import org.graalvm.buildtools.gradle.tasks.BuildNativeImageTask; import org.graalvm.buildtools.gradle.tasks.NativeRunTask; -import org.gradle.api.GradleException; import org.gradle.api.Plugin; import org.gradle.api.Project; -import org.gradle.api.Task; import org.gradle.api.artifacts.Configuration; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.FileCollection; +import org.gradle.api.file.RegularFileProperty; import org.gradle.api.plugins.ApplicationPlugin; import org.gradle.api.plugins.JavaApplication; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.provider.ListProperty; +import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.JavaExec; +import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.PathSensitive; +import org.gradle.api.tasks.PathSensitivity; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.TaskCollection; +import org.gradle.api.tasks.TaskContainer; import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.testing.Test; +import org.gradle.process.CommandLineArgumentProvider; import org.gradle.process.JavaForkOptions; +import javax.inject.Inject; import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; import java.util.Arrays; -import java.util.Objects; +import java.util.Collections; import static org.graalvm.buildtools.gradle.internal.Utils.AGENT_FILTER; import static org.graalvm.buildtools.gradle.internal.Utils.AGENT_OUTPUT_FOLDER; +import static org.graalvm.buildtools.gradle.internal.Utils.AGENT_PROPERTY; /** * Gradle plugin for GraalVM Native Image. @@ -88,6 +94,7 @@ public class NativeImagePlugin implements Plugin { public static final String NATIVE_TEST_BUILD_TASK_NAME = "nativeTestBuild"; public static final String NATIVE_TEST_EXTENSION = "nativeTest"; public static final String NATIVE_BUILD_EXTENSION = "nativeBuild"; + public static final String COPY_AGENT_FILTER_TASK_NAME = "copyAgentFilter"; private GraalVMLogger logger; @@ -113,26 +120,25 @@ public void apply(Project project) { registerServiceProvider(project, nativeImageServiceProvider); // Register Native Image tasks - TaskProvider imageBuilder = project.getTasks().register(NATIVE_BUILD_TASK_NAME, BuildNativeImageTask.class); + TaskContainer tasks = project.getTasks(); - project.getTasks().register(NativeRunTask.TASK_NAME, NativeRunTask.class, task -> { + Provider agent = agentPropertyOverride(project, buildExtension); + TaskProvider imageBuilder = tasks.register(NATIVE_BUILD_TASK_NAME, + BuildNativeImageTask.class, builder -> builder.getAgentEnabled().set(agent)); + tasks.register(NativeRunTask.TASK_NAME, NativeRunTask.class, task -> { task.getImage().convention(imageBuilder.map(t -> t.getOutputFile().get())); task.getRuntimeArgs().convention(buildExtension.getRuntimeArgs()); }); - if (project.hasProperty(Utils.AGENT_PROPERTY) || buildExtension.getAgent().get()) { - // We want to add agent invocation to "run" task, but it is only available when - // Application Plugin is initialized. - project.getPlugins().withType(ApplicationPlugin.class, applicationPlugin -> { - Task run = project.getTasksByName(ApplicationPlugin.TASK_RUN_NAME, false).stream().findFirst() - .orElse(null); - assert run != null : "Application plugin didn't register 'run' task"; - - boolean persistConfig = System.getProperty(Utils.PERSIST_CONFIG_PROPERTY) != null - || buildExtension.getPersistConfig().get(); - setAgentArgs(project, SourceSet.MAIN_SOURCE_SET_NAME, run, persistConfig); - }); - } + TaskProvider copyAgentFilterTask = registerCopyAgentFilterTask(project); + + // We want to add agent invocation to "run" task, but it is only available when + // Application Plugin is initialized. + project.getPlugins().withType(ApplicationPlugin.class, applicationPlugin -> + tasks.withType(JavaExec.class).named(ApplicationPlugin.TASK_RUN_NAME, run -> { + Provider cliProvider = configureAgent(project, run, copyAgentFilterTask, agent, buildExtension, run.getName()); + buildExtension.getConfigurationFileDirectories().from(cliProvider); + })); // In future Gradle releases this becomes a proper DirectoryProperty File testResultsDir = GradleUtils.getJavaPluginConvention(project).getTestResultsDir(); @@ -140,39 +146,59 @@ public void apply(Project project) { // Testing part begins here. TaskCollection testTask = findTestTask(project); + Provider testAgent = agentPropertyOverride(project, testExtension); + testTask.configureEach(test -> { testListDirectory.set(new File(testResultsDir, test.getName() + "/testlist")); test.getOutputs().dir(testResultsDir); test.systemProperty("graalvm.testids.outputdir", testListDirectory.getAsFile().get()); + Provider cliProviderFile = configureAgent(project, test, copyAgentFilterTask, testAgent, testExtension, test.getName()); + testExtension.getConfigurationFileDirectories().from(cliProviderFile); }); // Following ensures that required feature jar is on classpath for every project injectTestPluginDependencies(project); - // If `test` task was found we should add `nativeTestBuild` and `nativeTest` - // tasks to this project as well. - TaskProvider testImageBuilder = project.getTasks().register(NATIVE_TEST_BUILD_TASK_NAME, BuildNativeImageTask.class, task -> { + TaskProvider testImageBuilder = tasks.register(NATIVE_TEST_BUILD_TASK_NAME, BuildNativeImageTask.class, task -> { task.setDescription("Builds native image with tests."); task.getOptions().set(testExtension); ConfigurableFileCollection testList = project.getObjects().fileCollection(); // Later this will be replaced by a dedicated task not requiring execution of tests testList.from(testListDirectory).builtBy(testTask); testExtension.getClasspath().from(testList); + task.getAgentEnabled().set(testAgent); }); - project.getTasks().register(NATIVE_TEST_TASK_NAME, NativeRunTask.class, task -> { + tasks.register(NATIVE_TEST_TASK_NAME, NativeRunTask.class, task -> { task.setDescription("Runs native-image compiled tests."); task.getImage().convention(testImageBuilder.map(t -> t.getOutputFile().get())); task.getRuntimeArgs().convention(testExtension.getRuntimeArgs()); }); - if (project.hasProperty(Utils.AGENT_PROPERTY) || testExtension.getAgent().get()) { - // Add agent invocation to test task. - Boolean persistConfig = System.getProperty(Utils.PERSIST_CONFIG_PROPERTY) != null - || testExtension.getPersistConfig().get(); - // setAgentArgs(project, SourceSet.TEST_SOURCE_SET_NAME, test, persistConfig); - } + }); + } + + /** + * Returns a provider which prefers the CLI arguments over the configured + * extension value. + */ + private Provider agentPropertyOverride(Project project, NativeImageOptions extension) { + return project.getProviders() + .gradleProperty(AGENT_PROPERTY) + .forUseAtConfigurationTime() + .map(v -> { + if (!v.isEmpty()) { + return Boolean.valueOf(v); + } + return true; + }) + .orElse(extension.getAgent()); + } + private TaskProvider registerCopyAgentFilterTask(Project project) { + return project.getTasks().register(COPY_AGENT_FILTER_TASK_NAME, CopyClasspathResourceTask.class, task -> { + task.getClasspathResource().set("/" + AGENT_FILTER); + task.getOutputFile().set(project.getLayout().getBuildDirectory().file("native/agent-filter/" + AGENT_FILTER)); }); } @@ -231,41 +257,56 @@ private Provider registerNativeImageService(Project project) spec -> spec.getMaxParallelUsages().set(1 + Runtime.getRuntime().availableProcessors() / 16)); } - private void setAgentArgs(Project project, String sourceSetName, Task task, Boolean persistConfig) { - Path buildFolder = project.getBuildDir().toPath(); - Path accessFilter = buildFolder.resolve("tmp").resolve(AGENT_FILTER); - - task.doFirst((__) -> { - try { - // Before JVM execution (during `run` or `test` task), we want to copy - // access-filter file so that native-image-agent run doesn't catch internal - // gradle classes. - Files.copy(Objects.requireNonNull(this.getClass().getResourceAsStream("/" + AGENT_FILTER)), - accessFilter, StandardCopyOption.REPLACE_EXISTING); - } catch (IOException | NullPointerException e) { - throw new GradleException("Error while copying access-filter file.", e); - } - }); - - Path agentOutput; - if (persistConfig) { // If user chooses, we can persist native-image-agent generated configuration - // into the codebase. - agentOutput = Paths.get(project.getProjectDir().getAbsolutePath(), "src", sourceSetName, "resources", - "META-INF", "native-image"); - logger.log("Persist config option was set."); - } else { - agentOutput = buildFolder.resolve(AGENT_OUTPUT_FOLDER).resolve(sourceSetName).toAbsolutePath(); - } - - ((JavaForkOptions) task) - .setJvmArgs(Arrays.asList( - "-agentlib:native-image-agent=experimental-class-loader-support," + "config-output-dir=" - + agentOutput + "," + "access-filter-file=" + accessFilter, - "-Dorg.graalvm.nativeimage.imagecode=agent")); + private Provider configureAgent(Project project, + JavaForkOptions javaForkOptions, + TaskProvider filterProvider, + Provider agent, + NativeImageOptions nativeImageOptions, + String context) { + AgentCommandLineProvider cliProvider = project.getObjects().newInstance(AgentCommandLineProvider.class); + cliProvider.getEnabled().set(agent); + cliProvider.getAccessFilter().set(filterProvider.flatMap(CopyClasspathResourceTask::getOutputFile)); + cliProvider.getOutputDirectory().set(project.getLayout().getBuildDirectory().dir(AGENT_OUTPUT_FOLDER + "/" + context)); + javaForkOptions.getJvmArgumentProviders().add(cliProvider); + // We're "desugaring" the output intentionally to workaround a Gradle bug which absolutely + // wants the file to track the input but since it's not from a task it would fail + return project.getProviders().provider(() -> cliProvider.getOutputDirectory().get().getAsFile()); } private void injectTestPluginDependencies(Project project) { project.getDependencies().add("implementation", Utils.MAVEN_GROUP_ID + ":junit-platform-native:" + VersionInfo.JUNIT_PLATFORM_NATIVE_VERSION); } + + abstract static class AgentCommandLineProvider implements CommandLineArgumentProvider { + + @Inject + @SuppressWarnings("checkstyle:redundantmodifier") + public AgentCommandLineProvider() { + + } + + @Input + public abstract Property getEnabled(); + + @InputFile + @PathSensitive(PathSensitivity.NONE) + public abstract RegularFileProperty getAccessFilter(); + + @OutputDirectory + public abstract DirectoryProperty getOutputDirectory(); + + @Override + public Iterable asArguments() { + if (getEnabled().get()) { + return Arrays.asList( + "-agentlib:native-image-agent=experimental-class-loader-support," + + "config-output-dir=" + getOutputDirectory().getAsFile().get().getAbsolutePath() + "," + + "access-filter-file=" + getAccessFilter().getAsFile().get().getAbsolutePath(), + "-Dorg.graalvm.nativeimage.imagecode=agent" + ); + } + return Collections.emptyList(); + } + } } diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java index 120ceaf90..de164c771 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java @@ -48,6 +48,7 @@ import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.MapProperty; import org.gradle.api.provider.Property; +import org.gradle.api.provider.Provider; import org.gradle.api.provider.ProviderFactory; import org.gradle.api.tasks.Classpath; import org.gradle.api.tasks.Input; @@ -203,11 +204,8 @@ public NativeImageOptions(ObjectFactory objectFactory, getServer().convention(false); getFallback().convention(false); getVerbose().convention(false); - getAgent().convention(providers.gradleProperty(Utils.AGENT_PROPERTY) - .forUseAtConfigurationTime() - .map(Boolean::valueOf) - .orElse(false)); - getPersistConfig().convention(false); + getAgent().convention(false); + getPersistConfig().convention(property(providers, Utils.PERSIST_CONFIG_PROPERTY)); getImageName().convention(defaultImageName); getJavaLauncher().convention( toolchains.launcherFor(spec -> { @@ -219,6 +217,13 @@ public NativeImageOptions(ObjectFactory objectFactory, ); } + private static Provider property(ProviderFactory providers, String name) { + return providers.gradleProperty(name) + .forUseAtConfigurationTime() + .map(Boolean::valueOf) + .orElse(false); + } + public static NativeImageOptions register(Project project, String extensionName) { return project.getExtensions().create(extensionName, NativeImageOptions.class, diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/CopyClasspathResourceTask.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/CopyClasspathResourceTask.java new file mode 100644 index 000000000..5f2b17236 --- /dev/null +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/CopyClasspathResourceTask.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2021, 2021 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.graalvm.buildtools.gradle.internal; + +import org.gradle.api.DefaultTask; +import org.gradle.api.GradleException; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.provider.Property; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.OutputFile; +import org.gradle.api.tasks.TaskAction; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.Objects; + +/** + * This task is for internal use only, its responsibility is to + * generate a filter config file which is used by native image. + */ +public abstract class CopyClasspathResourceTask extends DefaultTask { + @Input + public abstract Property getClasspathResource(); + + @OutputFile + public abstract RegularFileProperty getOutputFile(); + + @TaskAction + public void copy() { + File outputFile = getOutputFile().getAsFile().get(); + File directory = outputFile.getParentFile(); + String resource = getClasspathResource().get(); + if (directory.isDirectory() || directory.mkdirs()) { + try { + Files.copy(Objects.requireNonNull(this.getClass().getResourceAsStream(resource)), + outputFile.toPath(), + StandardCopyOption.REPLACE_EXISTING); + } catch (IOException | NullPointerException e) { + throw new GradleException("Error while copying access-filter file.", e); + } + } + } +} diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/BuildNativeImageTask.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/BuildNativeImageTask.java index db4bae9c7..5082c7b84 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/BuildNativeImageTask.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/BuildNativeImageTask.java @@ -40,10 +40,10 @@ */ package org.graalvm.buildtools.gradle.tasks; -import org.graalvm.buildtools.gradle.internal.Utils; import org.graalvm.buildtools.gradle.NativeImageService; import org.graalvm.buildtools.gradle.dsl.NativeImageOptions; import org.graalvm.buildtools.gradle.internal.GraalVMLogger; +import org.graalvm.buildtools.gradle.internal.Utils; import org.gradle.api.DefaultTask; import org.gradle.api.GradleException; import org.gradle.api.Transformer; @@ -108,6 +108,9 @@ public Provider getOutputFile() { return getOutputDirectory().map(dir -> dir.file(getExecutableName()).get()); } + @Input + public abstract Property getAgentEnabled(); + public BuildNativeImageTask() { DirectoryProperty buildDir = getProject().getLayout().getBuildDirectory(); Provider outputDir = buildDir.dir("native/" + getName()); @@ -153,7 +156,7 @@ private List buildActualCommandLineArgs() { if (!configFiles.isEmpty()) { cliArgs.add("-H:ConfigurationFileDirectories=" + configFiles); } - if (options.getAgent().get()) { + if (getAgentEnabled().get()) { cliArgs.add("--allow-incomplete-classpath"); } if (options.getMainClass().isPresent()) { diff --git a/native-gradle-plugin/src/samples/java-application-with-reflection/build.gradle b/native-gradle-plugin/src/samples/java-application-with-reflection/build.gradle new file mode 100644 index 000000000..fbdd3469d --- /dev/null +++ b/native-gradle-plugin/src/samples/java-application-with-reflection/build.gradle @@ -0,0 +1,30 @@ +plugins { + id 'application' + id 'org.graalvm.buildtools.native' +} + +repositories { + mavenCentral() +} + +application { + mainClass.set('org.graalvm.demo.Application') +} + +def junitVersion = providers.systemProperty('versions.junit') + .forUseAtConfigurationTime() + .orElse('5.7.2') // so that the sample runs if someone checks it out without the testing infra + .get() + +dependencies { + testImplementation(platform("org.junit:junit-bom:${junitVersion}")) + testImplementation('org.junit.jupiter:junit-jupiter') +} + +test { + useJUnitPlatform() +} + +nativeTest { + agent = true +} \ No newline at end of file diff --git a/native-gradle-plugin/src/samples/java-application-with-reflection/settings.gradle b/native-gradle-plugin/src/samples/java-application-with-reflection/settings.gradle new file mode 100644 index 000000000..6695db306 --- /dev/null +++ b/native-gradle-plugin/src/samples/java-application-with-reflection/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'java-application' diff --git a/native-gradle-plugin/src/samples/java-application-with-reflection/src/main/java/org/graalvm/demo/Application.java b/native-gradle-plugin/src/samples/java-application-with-reflection/src/main/java/org/graalvm/demo/Application.java new file mode 100644 index 000000000..7617f8e75 --- /dev/null +++ b/native-gradle-plugin/src/samples/java-application-with-reflection/src/main/java/org/graalvm/demo/Application.java @@ -0,0 +1,19 @@ +package org.graalvm.demo; + +import java.util.Arrays; +import java.util.stream.Collectors; + +public class Application { + static String getMessage() { + try { + String className = Arrays.asList("org", "graalvm", "demo", "Message").stream().collect(Collectors.joining(".")); + return (String) Class.forName(className).getDeclaredField("MESSAGE").get(null); + } catch (Exception e) { + return null; + } + } + + public static void main(String[] args) { + System.out.println(getMessage()); + } +} diff --git a/native-gradle-plugin/src/samples/java-application-with-reflection/src/main/java/org/graalvm/demo/Calculator.java b/native-gradle-plugin/src/samples/java-application-with-reflection/src/main/java/org/graalvm/demo/Calculator.java new file mode 100644 index 000000000..41e072c9b --- /dev/null +++ b/native-gradle-plugin/src/samples/java-application-with-reflection/src/main/java/org/graalvm/demo/Calculator.java @@ -0,0 +1,9 @@ +package org.graalvm.demo; + +public class Calculator { + + public int add(int a, int b) { + return a + b; + } + +} diff --git a/native-gradle-plugin/src/samples/java-application-with-reflection/src/main/java/org/graalvm/demo/Message.java b/native-gradle-plugin/src/samples/java-application-with-reflection/src/main/java/org/graalvm/demo/Message.java new file mode 100644 index 000000000..3a73c1a18 --- /dev/null +++ b/native-gradle-plugin/src/samples/java-application-with-reflection/src/main/java/org/graalvm/demo/Message.java @@ -0,0 +1,5 @@ +package org.graalvm.demo; + +public class Message { + public static final String MESSAGE = "Hello, native!"; +} \ No newline at end of file diff --git a/native-gradle-plugin/src/samples/java-application-with-reflection/src/test/java/org/graalvm/demo/ApplicationTest.java b/native-gradle-plugin/src/samples/java-application-with-reflection/src/test/java/org/graalvm/demo/ApplicationTest.java new file mode 100644 index 000000000..842385862 --- /dev/null +++ b/native-gradle-plugin/src/samples/java-application-with-reflection/src/test/java/org/graalvm/demo/ApplicationTest.java @@ -0,0 +1,38 @@ +package org.graalvm.demo; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class CalculatorTest { + + @Test + @DisplayName("1 + 1 = 2") + void addsTwoNumbers() { + Calculator calculator = new Calculator(); + assertEquals(2, calculator.add(1, 1), "1 + 1 should equal 2"); + } + + @Test + @DisplayName("1 + 2 = 3") + void addsTwoNumbers2() { + Calculator calculator = new Calculator(); + assertEquals(3, calculator.add(1, 2), "1 + 2 should equal 3"); + } + + @ParameterizedTest(name = "{0} + {1} = {2}") + @CsvSource({ + "0, 1, 1", + "1, 2, 3", + "49, 51, 100", + "1, 100, 101" + }) + void add(int first, int second, int expectedResult) { + Calculator calculator = new Calculator(); + assertEquals(expectedResult, calculator.add(first, second), + () -> first + " + " + second + " should equal " + expectedResult); + } +} diff --git a/native-gradle-plugin/src/samples/java-application-with-reflection/src/test/java/org/graalvm/demo/CalculatorTest.java b/native-gradle-plugin/src/samples/java-application-with-reflection/src/test/java/org/graalvm/demo/CalculatorTest.java new file mode 100644 index 000000000..3728c3665 --- /dev/null +++ b/native-gradle-plugin/src/samples/java-application-with-reflection/src/test/java/org/graalvm/demo/CalculatorTest.java @@ -0,0 +1,17 @@ +package org.graalvm.demo; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class ApplicationTest { + + @Test + @DisplayName("message is hello native") + void usesReflection() { + assertEquals("Hello, native!", Application.getMessage()); + } +} diff --git a/native-gradle-plugin/src/testFixtures/groovy/org/graalvm/buildtools/gradle/fixtures/GraalVMSupport.groovy b/native-gradle-plugin/src/testFixtures/groovy/org/graalvm/buildtools/gradle/fixtures/GraalVMSupport.groovy new file mode 100644 index 000000000..fa3b658f8 --- /dev/null +++ b/native-gradle-plugin/src/testFixtures/groovy/org/graalvm/buildtools/gradle/fixtures/GraalVMSupport.groovy @@ -0,0 +1,68 @@ +/* + * + * * Copyright (c) 2021, 2021 Oracle and/or its affiliates. All rights reserved. + * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * * + * * The Universal Permissive License (UPL), Version 1.0 + * * + * * Subject to the condition set forth below, permission is hereby granted to any + * * person obtaining a copy of this software, associated documentation and/or + * * data (collectively the "Software"), free of charge and under any and all + * * copyright rights in the Software, and any and all patent rights owned or + * * freely licensable by each licensor hereunder covering either (i) the + * * unmodified Software as contributed to or provided by such licensor, or (ii) + * * the Larger Works (as defined below), to deal in both + * * + * * (a) the Software, and + * * + * * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * * one is included with the Software each a "Larger Work" to which the Software + * * is contributed by such licensors), + * * + * * without restriction, including without limitation the rights to copy, create + * * derivative works of, display, perform, and distribute the Software and make, + * * use, sell, offer for sale, import, export, have made, and have sold the + * * Software and the Larger Work(s), and to sublicense the foregoing rights on + * * either these or other terms. + * * + * * This license is subject to the following condition: + * * + * * The above copyright notice and either this complete permission notice or at a + * * minimum a reference to the UPL must be included in all copies or substantial + * * portions of the Software. + * * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * * SOFTWARE. + * + * + */ + +package org.graalvm.buildtools.gradle.fixtures + +import groovy.transform.CompileStatic + +@CompileStatic +class GraalVMSupport { + static boolean isGraal() { + System.getProperty("java.vendor.version", "").contains("GraalVM") + } + + static String getVersion() { + (System.getProperty("java.vendor.version", "") - 'GraalVM' - 'CE').trim() + } + + static int getMajorVersion() { + String v = version + v.substring(0, v.indexOf('.')).toInteger() + } + + static int getMinorVersion() { + String v = version - "${majorVersion}." + v.substring(0, v.indexOf('.')).toInteger() + } +} From fc8770ee591ae877b61e2bb8fd90dd250ad36cec Mon Sep 17 00:00:00 2001 From: Cedric Champeau Date: Thu, 24 Jun 2021 08:33:11 +0200 Subject: [PATCH 09/23] Do not use `Path` in constant --- .../java/org/graalvm/buildtools/gradle/internal/Utils.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/Utils.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/Utils.java index 1712b5112..e12cfe3f0 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/Utils.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/Utils.java @@ -42,8 +42,6 @@ import org.gradle.api.provider.Provider; -import java.nio.file.Paths; - /** * Utility class containing various native-image and JVM related methods. * Keep this file in sync across all build tool plugins. @@ -54,7 +52,7 @@ public class Utils { public static final String NATIVE_IMAGE_EXE = "native-image" + EXECUTABLE_EXTENSION; public static final String NATIVE_IMAGE_OUTPUT_FOLDER = "native"; public static final String AGENT_PROPERTY = "agent"; - public static final String AGENT_OUTPUT_FOLDER = Paths.get(NATIVE_IMAGE_OUTPUT_FOLDER, "agent-output").toString(); + public static final String AGENT_OUTPUT_FOLDER = NATIVE_IMAGE_OUTPUT_FOLDER + "/agent-output"; public static final String NATIVE_TESTS_SUFFIX = "-tests"; public static final String AGENT_FILTER = "agent-filter.json"; public static final String PERSIST_CONFIG_PROPERTY = "persistConfig"; From 0bbe784afe19110a57cab8e17fa2ee2e3be7b19b Mon Sep 17 00:00:00 2001 From: Cedric Champeau Date: Thu, 24 Jun 2021 08:51:09 +0200 Subject: [PATCH 10/23] Refactor code for readability --- .../buildtools/gradle/NativeImagePlugin.java | 227 +++++++----------- .../buildtools/gradle/NativeImageService.java | 9 + .../internal/AgentCommandLineProvider.java | 87 +++++++ .../gradle/internal/GradleUtils.java | 14 ++ 4 files changed, 194 insertions(+), 143 deletions(-) create mode 100644 native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/AgentCommandLineProvider.java diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java index 4647fd990..57e4d6c6b 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java @@ -42,6 +42,7 @@ import org.graalvm.buildtools.VersionInfo; import org.graalvm.buildtools.gradle.dsl.NativeImageOptions; +import org.graalvm.buildtools.gradle.internal.AgentCommandLineProvider; import org.graalvm.buildtools.gradle.internal.CopyClasspathResourceTask; import org.graalvm.buildtools.gradle.internal.GraalVMLogger; import org.graalvm.buildtools.gradle.internal.GradleUtils; @@ -50,35 +51,22 @@ import org.graalvm.buildtools.gradle.tasks.NativeRunTask; import org.gradle.api.Plugin; import org.gradle.api.Project; -import org.gradle.api.artifacts.Configuration; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.DirectoryProperty; -import org.gradle.api.file.FileCollection; -import org.gradle.api.file.RegularFileProperty; import org.gradle.api.plugins.ApplicationPlugin; import org.gradle.api.plugins.JavaApplication; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.provider.ListProperty; -import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; -import org.gradle.api.tasks.Input; -import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.JavaExec; -import org.gradle.api.tasks.OutputDirectory; -import org.gradle.api.tasks.PathSensitive; -import org.gradle.api.tasks.PathSensitivity; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.TaskCollection; import org.gradle.api.tasks.TaskContainer; import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.testing.Test; -import org.gradle.process.CommandLineArgumentProvider; import org.gradle.process.JavaForkOptions; -import javax.inject.Inject; import java.io.File; -import java.util.Arrays; -import java.util.Collections; import static org.graalvm.buildtools.gradle.internal.Utils.AGENT_FILTER; import static org.graalvm.buildtools.gradle.internal.Utils.AGENT_OUTPUT_FOLDER; @@ -98,83 +86,84 @@ public class NativeImagePlugin implements Plugin { private GraalVMLogger logger; - @SuppressWarnings("UnstableApiUsage") public void apply(Project project) { - Provider nativeImageServiceProvider = registerNativeImageService(project); + Provider nativeImageServiceProvider = NativeImageService.registerOn(project); logger = new GraalVMLogger(project.getLogger()); - project.getPlugins().withType(JavaPlugin.class, javaPlugin -> { - logger.log("===================="); - logger.log("Initializing project: " + project.getName()); - logger.log("===================="); - - // Add DSL extensions for building and testing - NativeImageOptions buildExtension = createMainExtension(project); - NativeImageOptions testExtension = createTestExtension(project, buildExtension); - - project.getPlugins().withId("application", p -> buildExtension.getMainClass().convention( - project.getExtensions().findByType(JavaApplication.class).getMainClass() - )); - - registerServiceProvider(project, nativeImageServiceProvider); - - // Register Native Image tasks - TaskContainer tasks = project.getTasks(); - - Provider agent = agentPropertyOverride(project, buildExtension); - TaskProvider imageBuilder = tasks.register(NATIVE_BUILD_TASK_NAME, - BuildNativeImageTask.class, builder -> builder.getAgentEnabled().set(agent)); - tasks.register(NativeRunTask.TASK_NAME, NativeRunTask.class, task -> { - task.getImage().convention(imageBuilder.map(t -> t.getOutputFile().get())); - task.getRuntimeArgs().convention(buildExtension.getRuntimeArgs()); - }); + project.getPlugins() + .withType(JavaPlugin.class, javaPlugin -> configureJavaProject(project, nativeImageServiceProvider)); + } - TaskProvider copyAgentFilterTask = registerCopyAgentFilterTask(project); + private void configureJavaProject(Project project, Provider nativeImageServiceProvider) { + logger.log("===================="); + logger.log("Initializing project: " + project.getName()); + logger.log("===================="); - // We want to add agent invocation to "run" task, but it is only available when - // Application Plugin is initialized. - project.getPlugins().withType(ApplicationPlugin.class, applicationPlugin -> - tasks.withType(JavaExec.class).named(ApplicationPlugin.TASK_RUN_NAME, run -> { - Provider cliProvider = configureAgent(project, run, copyAgentFilterTask, agent, buildExtension, run.getName()); - buildExtension.getConfigurationFileDirectories().from(cliProvider); - })); + // Add DSL extensions for building and testing + NativeImageOptions buildExtension = createMainExtension(project); + NativeImageOptions testExtension = createTestExtension(project, buildExtension); - // In future Gradle releases this becomes a proper DirectoryProperty - File testResultsDir = GradleUtils.getJavaPluginConvention(project).getTestResultsDir(); - DirectoryProperty testListDirectory = project.getObjects().directoryProperty(); + project.getPlugins().withId("application", p -> buildExtension.getMainClass().convention( + project.getExtensions().findByType(JavaApplication.class).getMainClass() + )); - // Testing part begins here. - TaskCollection testTask = findTestTask(project); - Provider testAgent = agentPropertyOverride(project, testExtension); + registerServiceProvider(project, nativeImageServiceProvider); - testTask.configureEach(test -> { - testListDirectory.set(new File(testResultsDir, test.getName() + "/testlist")); - test.getOutputs().dir(testResultsDir); - test.systemProperty("graalvm.testids.outputdir", testListDirectory.getAsFile().get()); - Provider cliProviderFile = configureAgent(project, test, copyAgentFilterTask, testAgent, testExtension, test.getName()); - testExtension.getConfigurationFileDirectories().from(cliProviderFile); - }); + // Register Native Image tasks + TaskContainer tasks = project.getTasks(); - // Following ensures that required feature jar is on classpath for every project - injectTestPluginDependencies(project); + Provider agent = agentPropertyOverride(project, buildExtension); + TaskProvider imageBuilder = tasks.register(NATIVE_BUILD_TASK_NAME, + BuildNativeImageTask.class, builder -> builder.getAgentEnabled().set(agent)); + tasks.register(NativeRunTask.TASK_NAME, NativeRunTask.class, task -> { + task.getImage().convention(imageBuilder.map(t -> t.getOutputFile().get())); + task.getRuntimeArgs().convention(buildExtension.getRuntimeArgs()); + }); - TaskProvider testImageBuilder = tasks.register(NATIVE_TEST_BUILD_TASK_NAME, BuildNativeImageTask.class, task -> { - task.setDescription("Builds native image with tests."); - task.getOptions().set(testExtension); - ConfigurableFileCollection testList = project.getObjects().fileCollection(); - // Later this will be replaced by a dedicated task not requiring execution of tests - testList.from(testListDirectory).builtBy(testTask); - testExtension.getClasspath().from(testList); - task.getAgentEnabled().set(testAgent); - }); + TaskProvider copyAgentFilterTask = registerCopyAgentFilterTask(project); + + // We want to add agent invocation to "run" task, but it is only available when + // Application Plugin is initialized. + project.getPlugins().withType(ApplicationPlugin.class, applicationPlugin -> + tasks.withType(JavaExec.class).named(ApplicationPlugin.TASK_RUN_NAME, run -> { + Provider cliProvider = configureAgent(project, run, copyAgentFilterTask, agent, buildExtension, run.getName()); + buildExtension.getConfigurationFileDirectories().from(cliProvider); + })); + + // In future Gradle releases this becomes a proper DirectoryProperty + File testResultsDir = GradleUtils.getJavaPluginConvention(project).getTestResultsDir(); + DirectoryProperty testListDirectory = project.getObjects().directoryProperty(); + + // Testing part begins here. + TaskCollection testTask = findTestTask(project); + Provider testAgent = agentPropertyOverride(project, testExtension); + + testTask.configureEach(test -> { + testListDirectory.set(new File(testResultsDir, test.getName() + "/testlist")); + test.getOutputs().dir(testResultsDir); + test.systemProperty("graalvm.testids.outputdir", testListDirectory.getAsFile().get()); + Provider cliProviderFile = configureAgent(project, test, copyAgentFilterTask, testAgent, testExtension, test.getName()); + testExtension.getConfigurationFileDirectories().from(cliProviderFile); + }); - tasks.register(NATIVE_TEST_TASK_NAME, NativeRunTask.class, task -> { - task.setDescription("Runs native-image compiled tests."); - task.getImage().convention(testImageBuilder.map(t -> t.getOutputFile().get())); - task.getRuntimeArgs().convention(testExtension.getRuntimeArgs()); - }); + // Following ensures that required feature jar is on classpath for every project + injectTestPluginDependencies(project); + + TaskProvider testImageBuilder = tasks.register(NATIVE_TEST_BUILD_TASK_NAME, BuildNativeImageTask.class, task -> { + task.setDescription("Builds native image with tests."); + task.getOptions().set(testExtension); + ConfigurableFileCollection testList = project.getObjects().fileCollection(); + // Later this will be replaced by a dedicated task not requiring execution of tests + testList.from(testListDirectory).builtBy(testTask); + testExtension.getClasspath().from(testList); + task.getAgentEnabled().set(testAgent); + }); + tasks.register(NATIVE_TEST_TASK_NAME, NativeRunTask.class, task -> { + task.setDescription("Runs native-image compiled tests."); + task.getImage().convention(testImageBuilder.map(t -> t.getOutputFile().get())); + task.getRuntimeArgs().convention(testExtension.getRuntimeArgs()); }); } @@ -182,7 +171,7 @@ public void apply(Project project) { * Returns a provider which prefers the CLI arguments over the configured * extension value. */ - private Provider agentPropertyOverride(Project project, NativeImageOptions extension) { + private static Provider agentPropertyOverride(Project project, NativeImageOptions extension) { return project.getProviders() .gradleProperty(AGENT_PROPERTY) .forUseAtConfigurationTime() @@ -195,17 +184,18 @@ private Provider agentPropertyOverride(Project project, NativeImageOpti .orElse(extension.getAgent()); } - private TaskProvider registerCopyAgentFilterTask(Project project) { + private static TaskProvider registerCopyAgentFilterTask(Project project) { return project.getTasks().register(COPY_AGENT_FILTER_TASK_NAME, CopyClasspathResourceTask.class, task -> { task.getClasspathResource().set("/" + AGENT_FILTER); task.getOutputFile().set(project.getLayout().getBuildDirectory().file("native/agent-filter/" + AGENT_FILTER)); }); } - private TaskCollection findTestTask(Project project) { + private static TaskCollection findTestTask(Project project) { return project.getTasks().withType(Test.class).matching(task -> JavaPlugin.TEST_TASK_NAME.equals(task.getName())); } + @SuppressWarnings("UnstableApiUsage") private static void registerServiceProvider(Project project, Provider nativeImageServiceProvider) { project.getTasks() .withType(BuildNativeImageTask.class) @@ -215,25 +205,14 @@ private static void registerServiceProvider(Project project, Provider d.getAsFile().getAbsolutePath())); testExtension.buildArgs("--features=org.graalvm.junit.platform.JUnitPlatformFeature"); ConfigurableFileCollection classpath = testExtension.getClasspath(); - classpath.from(findMainArtifacts(project)); - classpath.from(findConfiguration(project, JavaPlugin.TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME)); + classpath.from(GradleUtils.findMainArtifacts(project)); + classpath.from(GradleUtils.findConfiguration(project, JavaPlugin.TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME)); classpath.from(GradleUtils.findSourceSet(project, SourceSet.TEST_SOURCE_SET_NAME).getOutput().getClassesDirs()); classpath.from(GradleUtils.findSourceSet(project, SourceSet.TEST_SOURCE_SET_NAME).getOutput().getResourcesDir()); return testExtension; } - private Provider registerNativeImageService(Project project) { - return project.getGradle() - .getSharedServices() - .registerIfAbsent("nativeImage", NativeImageService.class, - spec -> spec.getMaxParallelUsages().set(1 + Runtime.getRuntime().availableProcessors() / 16)); - } - - private Provider configureAgent(Project project, - JavaForkOptions javaForkOptions, - TaskProvider filterProvider, - Provider agent, - NativeImageOptions nativeImageOptions, - String context) { + private static Provider configureAgent(Project project, + JavaForkOptions javaForkOptions, + TaskProvider filterProvider, + Provider agent, + NativeImageOptions nativeImageOptions, + String context) { AgentCommandLineProvider cliProvider = project.getObjects().newInstance(AgentCommandLineProvider.class); cliProvider.getEnabled().set(agent); cliProvider.getAccessFilter().set(filterProvider.flatMap(CopyClasspathResourceTask::getOutputFile)); @@ -273,40 +245,9 @@ private Provider configureAgent(Project project, return project.getProviders().provider(() -> cliProvider.getOutputDirectory().get().getAsFile()); } - private void injectTestPluginDependencies(Project project) { - project.getDependencies().add("implementation", Utils.MAVEN_GROUP_ID + ":junit-platform-native:" + private static void injectTestPluginDependencies(Project project) { + project.getDependencies().add("implementation", "org.graalvm.buildtools:junit-platform-native:" + VersionInfo.JUNIT_PLATFORM_NATIVE_VERSION); } - abstract static class AgentCommandLineProvider implements CommandLineArgumentProvider { - - @Inject - @SuppressWarnings("checkstyle:redundantmodifier") - public AgentCommandLineProvider() { - - } - - @Input - public abstract Property getEnabled(); - - @InputFile - @PathSensitive(PathSensitivity.NONE) - public abstract RegularFileProperty getAccessFilter(); - - @OutputDirectory - public abstract DirectoryProperty getOutputDirectory(); - - @Override - public Iterable asArguments() { - if (getEnabled().get()) { - return Arrays.asList( - "-agentlib:native-image-agent=experimental-class-loader-support," + - "config-output-dir=" + getOutputDirectory().getAsFile().get().getAbsolutePath() + "," + - "access-filter-file=" + getAccessFilter().getAsFile().get().getAbsolutePath(), - "-Dorg.graalvm.nativeimage.imagecode=agent" - ); - } - return Collections.emptyList(); - } - } } diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImageService.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImageService.java index 5a6d40f99..ef6318b56 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImageService.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImageService.java @@ -40,9 +40,18 @@ */ package org.graalvm.buildtools.gradle; +import org.gradle.api.Project; +import org.gradle.api.provider.Provider; import org.gradle.api.services.BuildService; import org.gradle.api.services.BuildServiceParameters; @SuppressWarnings({"UnstableApiUsage", "unused"}) public abstract class NativeImageService implements BuildService { + public static Provider registerOn(Project project) { + return project.getGradle() + .getSharedServices() + .registerIfAbsent("nativeImage", NativeImageService.class, + spec -> spec.getMaxParallelUsages().set(1 + Runtime.getRuntime().availableProcessors() / 16)); + + } } diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/AgentCommandLineProvider.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/AgentCommandLineProvider.java new file mode 100644 index 000000000..9e95238a1 --- /dev/null +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/AgentCommandLineProvider.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2021, 2021 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.graalvm.buildtools.gradle.internal; + +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.provider.Property; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.PathSensitive; +import org.gradle.api.tasks.PathSensitivity; +import org.gradle.process.CommandLineArgumentProvider; + +import javax.inject.Inject; +import java.util.Arrays; +import java.util.Collections; + +public abstract class AgentCommandLineProvider implements CommandLineArgumentProvider { + + @Inject + @SuppressWarnings("checkstyle:redundantmodifier") + public AgentCommandLineProvider() { + + } + + @Input + public abstract Property getEnabled(); + + @InputFile + @PathSensitive(PathSensitivity.NONE) + public abstract RegularFileProperty getAccessFilter(); + + @OutputDirectory + public abstract DirectoryProperty getOutputDirectory(); + + @Override + public Iterable asArguments() { + if (getEnabled().get()) { + return Arrays.asList( + "-agentlib:native-image-agent=experimental-class-loader-support," + + "config-output-dir=" + getOutputDirectory().getAsFile().get().getAbsolutePath() + "," + + "access-filter-file=" + getAccessFilter().getAsFile().get().getAbsolutePath(), + "-Dorg.graalvm.nativeimage.imagecode=agent" + ); + } + return Collections.emptyList(); + } +} diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/GradleUtils.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/GradleUtils.java index 335afa5d7..531835a27 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/GradleUtils.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/GradleUtils.java @@ -41,6 +41,9 @@ package org.graalvm.buildtools.gradle.internal; import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.file.FileCollection; +import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginConvention; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSetContainer; @@ -65,4 +68,15 @@ public static JavaPluginConvention getJavaPluginConvention(Project project) { public static boolean isAtLeastGradle7() { return GradleVersion.current().compareTo(GRADLE_7) >= 0; } + + public static Configuration findConfiguration(Project project, String name) { + return project.getConfigurations().getByName(name); + } + + public static FileCollection findMainArtifacts(Project project) { + return findConfiguration(project, JavaPlugin.RUNTIME_ELEMENTS_CONFIGURATION_NAME) + .getOutgoing() + .getArtifacts() + .getFiles(); + } } From 28f814de0092899cd4992a30bf60418db61804c0 Mon Sep 17 00:00:00 2001 From: Cedric Champeau Date: Thu, 24 Jun 2021 10:05:07 +0200 Subject: [PATCH 11/23] Fix compatibility with the Gradle configuration cache A new test suite, `configCacheFunctionalTest` enables the configuration cache. But currently the test suite fails, not because of the compatibility itself, but because for some reason Gradle fails to write its report. --- .../org.graalvm.build.functional-testing.gradle | 6 +++++- .../gradle/tasks/BuildNativeImageTask.java | 5 ++++- .../fixtures/AbstractFunctionalTest.groovy | 17 +++++++++++++++-- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/native-gradle-plugin/buildSrc/src/main/groovy/org.graalvm.build.functional-testing.gradle b/native-gradle-plugin/buildSrc/src/main/groovy/org.graalvm.build.functional-testing.gradle index 842cbfcb2..437e0ceab 100644 --- a/native-gradle-plugin/buildSrc/src/main/groovy/org.graalvm.build.functional-testing.gradle +++ b/native-gradle-plugin/buildSrc/src/main/groovy/org.graalvm.build.functional-testing.gradle @@ -77,7 +77,7 @@ def graalVm = javaToolchains.launcherFor { vendor.set(JvmVendorSpec.matching("GraalVM Community")) } -['functionalTest', 'fullFunctionalTest'].each { +['functionalTest', 'fullFunctionalTest', 'configCacheFunctionalTest'].each { // Add a task to run the functional tests tasks.register(it, Test) { // Any change to samples invalidates functional tests @@ -96,6 +96,10 @@ tasks.named('fullFunctionalTest') { systemProperty('full.coverage', 'true') } +tasks.named('configCacheFunctionalTest') { + systemProperty('config.cache', 'true') +} + tasks.named('check') { // Run the functional tests as part of `check` dependsOn(tasks.fullFunctionalTest) diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/BuildNativeImageTask.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/BuildNativeImageTask.java index 5082c7b84..20aef8ceb 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/BuildNativeImageTask.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/BuildNativeImageTask.java @@ -80,6 +80,8 @@ public abstract class BuildNativeImageTask extends DefaultTask { private static final Transformer NEGATE = b -> !b; + private final Provider graalvmHomeProvider; + @Nested public abstract Property getOptions(); @@ -95,7 +97,7 @@ public abstract class BuildNativeImageTask extends DefaultTask { @Optional @Input protected Provider getGraalVMHome() { - return getProject().getProviders().environmentVariable("GRAALVM_HOME"); + return graalvmHomeProvider; } @Internal @@ -120,6 +122,7 @@ public BuildNativeImageTask() { getOptions().convention(getProject().getExtensions().findByType(NativeImageOptions.class)); getOutputDirectory().convention(outputDir); + this.graalvmHomeProvider = getProject().getProviders().environmentVariable("GRAALVM_HOME"); } private List buildActualCommandLineArgs() { diff --git a/native-gradle-plugin/src/testFixtures/groovy/org/graalvm/buildtools/gradle/fixtures/AbstractFunctionalTest.groovy b/native-gradle-plugin/src/testFixtures/groovy/org/graalvm/buildtools/gradle/fixtures/AbstractFunctionalTest.groovy index 3f8b37ce5..d10aa2d1d 100644 --- a/native-gradle-plugin/src/testFixtures/groovy/org/graalvm/buildtools/gradle/fixtures/AbstractFunctionalTest.groovy +++ b/native-gradle-plugin/src/testFixtures/groovy/org/graalvm/buildtools/gradle/fixtures/AbstractFunctionalTest.groovy @@ -155,12 +155,13 @@ abstract class AbstractFunctionalTest extends Specification { assertInitScript() outputWriter = new StringWriter() errorOutputWriter = new StringWriter() + ArrayList autoArgs = computeAutoArgs() def runner = GradleRunner.create() .forwardStdOutput(tee(new OutputStreamWriter(System.out), outputWriter)) .forwardStdError(tee(new OutputStreamWriter(System.err), errorOutputWriter)) .withPluginClasspath() .withProjectDir(testDirectory.toFile()) - .withArguments(["-S", "-I", initScript.getAbsolutePath(), *args]) + .withArguments([*autoArgs, *args]) if (gradleVersion) { runner.withGradleVersion(gradleVersion) } @@ -170,6 +171,18 @@ abstract class AbstractFunctionalTest extends Specification { runner } + private ArrayList computeAutoArgs() { + List autoArgs = [ + "-S", + ] + if (Boolean.getBoolean("config.cache")) { + autoArgs << '--configuration-cache' + } + autoArgs << "-I" + autoArgs << initScript.getAbsolutePath() + autoArgs + } + private static Writer tee(Writer one, Writer two) { return TeeWriter.of(one, two) } @@ -193,7 +206,7 @@ abstract class AbstractFunctionalTest extends Specification { allprojects { repositories { maven { - url = "\${System.getProperty('common.repo.url')}" + url = "\${providers.systemProperty('common.repo.url').forUseAtConfigurationTime().get()}" } mavenCentral() } From 2373f824f77f8ec32dc8d74f2318ad59723a7045 Mon Sep 17 00:00:00 2001 From: Cedric Champeau Date: Thu, 24 Jun 2021 11:57:44 +0200 Subject: [PATCH 12/23] Fix support for Kotlin tests Because of a weird ordering issue, it was possible that the native test image task configuration was executed _before_ the test task configuration, which caused an error when tests were executed. --- ...nApplicationWithTestsFunctionalTest.groovy | 104 ++++++++++++++++++ .../buildtools/gradle/NativeImagePlugin.java | 10 ++ .../build.gradle | 24 ++++ .../settings.gradle | 1 + .../src/main/kotlin/ktest/App.kt | 15 +++ .../src/test/kotlin/ktest/AppTest.kt | 11 ++ 6 files changed, 165 insertions(+) create mode 100644 native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/KotlinApplicationWithTestsFunctionalTest.groovy create mode 100644 native-gradle-plugin/src/samples/kotlin-application-with-tests/build.gradle create mode 100644 native-gradle-plugin/src/samples/kotlin-application-with-tests/settings.gradle create mode 100644 native-gradle-plugin/src/samples/kotlin-application-with-tests/src/main/kotlin/ktest/App.kt create mode 100644 native-gradle-plugin/src/samples/kotlin-application-with-tests/src/test/kotlin/ktest/AppTest.kt diff --git a/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/KotlinApplicationWithTestsFunctionalTest.groovy b/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/KotlinApplicationWithTestsFunctionalTest.groovy new file mode 100644 index 000000000..aaeaeb640 --- /dev/null +++ b/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/KotlinApplicationWithTestsFunctionalTest.groovy @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2021, 2021 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.graalvm.buildtools.gradle + +import org.graalvm.buildtools.gradle.fixtures.AbstractFunctionalTest +import org.graalvm.buildtools.gradle.fixtures.TestResults +import spock.lang.Unroll + +class KotlinApplicationWithTestsFunctionalTest extends AbstractFunctionalTest { + + @Unroll("can execute Kotlin tests in a native image directly on Gradle #version with JUnit Platform #junitVersion") + def "can execute Kotlin tests in a native image directly"() { + gradleVersion = version + debug = true + given: + withSample("kotlin-application-with-tests") + + when: + run 'nativeTest' + + then: + tasks { + succeeded ':compileTestKotlin', + ':nativeTestBuild', + ':test', + ':nativeTest' + doesNotContain ':build' + } + + then: + outputDoesNotContain "Running in 'test discovery' mode. Note that this is a fallback mode." + outputContains "Running in 'test listener' mode." + + outputContains 'ktest.AppTest > testAppHasAGreeting() SUCCESSFUL' + outputContains """ +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] +""".trim() + + and: + def results = TestResults.from(file("build/test-results/test/TEST-ktest.AppTest.xml")) + def nativeResults = TestResults.from(file("build/test-results/test-native/TEST-junit-jupiter.xml")) + + results == nativeResults + results.with { + tests == 1 + failures == 0 + skipped == 0 + errors == 0 + } + + where: + version << TESTED_GRADLE_VERSIONS + junitVersion = System.getProperty('versions.junit') + } +} diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java index 57e4d6c6b..8e9f04f80 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java @@ -67,6 +67,7 @@ import org.gradle.process.JavaForkOptions; import java.io.File; +import java.util.function.Consumer; import static org.graalvm.buildtools.gradle.internal.Utils.AGENT_FILTER; import static org.graalvm.buildtools.gradle.internal.Utils.AGENT_OUTPUT_FOLDER; @@ -84,6 +85,14 @@ public class NativeImagePlugin implements Plugin { public static final String NATIVE_BUILD_EXTENSION = "nativeBuild"; public static final String COPY_AGENT_FILTER_TASK_NAME = "copyAgentFilter"; + /** + * This looks strange, but it is used to force the configuration of a dependent + * task during the configuration of another one. This is a workaround for a bug + * when applying the Kotlin plugin, where the test task is configured too late + * for some reason. + */ + private static final Consumer FORCE_CONFIG = t -> { }; + private GraalVMLogger logger; public void apply(Project project) { @@ -153,6 +162,7 @@ private void configureJavaProject(Project project, Provider TaskProvider testImageBuilder = tasks.register(NATIVE_TEST_BUILD_TASK_NAME, BuildNativeImageTask.class, task -> { task.setDescription("Builds native image with tests."); task.getOptions().set(testExtension); + testTask.forEach(FORCE_CONFIG); ConfigurableFileCollection testList = project.getObjects().fileCollection(); // Later this will be replaced by a dedicated task not requiring execution of tests testList.from(testListDirectory).builtBy(testTask); diff --git a/native-gradle-plugin/src/samples/kotlin-application-with-tests/build.gradle b/native-gradle-plugin/src/samples/kotlin-application-with-tests/build.gradle new file mode 100644 index 000000000..cc7cbd1fa --- /dev/null +++ b/native-gradle-plugin/src/samples/kotlin-application-with-tests/build.gradle @@ -0,0 +1,24 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' version '1.5.10' + id 'application' + id 'org.graalvm.buildtools.native' +} + +repositories { + mavenCentral() +} + +dependencies { + implementation platform('org.jetbrains.kotlin:kotlin-bom') + implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' + + testImplementation 'org.jetbrains.kotlin:kotlin-test' +} + +application { + mainClass = 'ktest.AppKt' +} + +tasks.withType(Test).configureEach { + useJUnitPlatform() +} \ No newline at end of file diff --git a/native-gradle-plugin/src/samples/kotlin-application-with-tests/settings.gradle b/native-gradle-plugin/src/samples/kotlin-application-with-tests/settings.gradle new file mode 100644 index 000000000..1eb6c088f --- /dev/null +++ b/native-gradle-plugin/src/samples/kotlin-application-with-tests/settings.gradle @@ -0,0 +1 @@ +rootProject.name = "kotlin-application" \ No newline at end of file diff --git a/native-gradle-plugin/src/samples/kotlin-application-with-tests/src/main/kotlin/ktest/App.kt b/native-gradle-plugin/src/samples/kotlin-application-with-tests/src/main/kotlin/ktest/App.kt new file mode 100644 index 000000000..cb4b8b653 --- /dev/null +++ b/native-gradle-plugin/src/samples/kotlin-application-with-tests/src/main/kotlin/ktest/App.kt @@ -0,0 +1,15 @@ +/* + * This Kotlin source file was generated by the Gradle 'init' task. + */ +package ktest + +class App { + val greeting: String + get() { + return "Hello World!" + } +} + +fun main() { + println(App().greeting) +} diff --git a/native-gradle-plugin/src/samples/kotlin-application-with-tests/src/test/kotlin/ktest/AppTest.kt b/native-gradle-plugin/src/samples/kotlin-application-with-tests/src/test/kotlin/ktest/AppTest.kt new file mode 100644 index 000000000..1dfc08945 --- /dev/null +++ b/native-gradle-plugin/src/samples/kotlin-application-with-tests/src/test/kotlin/ktest/AppTest.kt @@ -0,0 +1,11 @@ +package ktest + +import kotlin.test.Test +import kotlin.test.assertNotNull + +class AppTest { + @Test fun testAppHasAGreeting() { + val classUnderTest = App() + assertNotNull(classUnderTest.greeting, "app should have a greeting") + } +} From 2f047f001a85074d7b5a302e195c62e51793bbbe Mon Sep 17 00:00:00 2001 From: Cedric Champeau Date: Thu, 24 Jun 2021 15:48:06 +0200 Subject: [PATCH 13/23] Remove persistConfig There were several issues with this flag: 1. it persists outputs in the (re)sources directories, which means that potentially, during a build, a task can change the contents of the (re)sources. Therefore, there's a potential ordering and non reproducibility issue based on when a task sees the resources. 2. it arbitrarily chooses a source set to persist data, but in practice this shouldn't live in a source set, since the result of the analysis, and actually the resources needed, may be split accross different source sets (think Java vs Kotlin sources) Therefore we agreed on removing it. A user can still access the result of the agent analysis via the build directory, and copy them manually if needed, or they can implement their own task, with their own risks taking, if they really want to do this. --- .../buildtools/gradle/dsl/NativeImageOptions.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java index de164c771..ab5c3f4c2 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java @@ -40,7 +40,6 @@ */ package org.graalvm.buildtools.gradle.dsl; -import org.graalvm.buildtools.gradle.internal.Utils; import org.graalvm.buildtools.gradle.internal.GradleUtils; import org.gradle.api.Project; import org.gradle.api.file.ConfigurableFileCollection; @@ -172,14 +171,6 @@ public abstract class NativeImageOptions { @Input public abstract Property getAgent(); - /** - * Gets the value which toggles persisting of agent config to META-INF. - * - * @return The value which toggles persisting of agent config to META-INF. - */ - @Input - public abstract Property getPersistConfig(); - /** * Returns the toolchain used to invoke native-image. Currently pointing * to a Java launcher due to Gradle limitations. @@ -205,7 +196,6 @@ public NativeImageOptions(ObjectFactory objectFactory, getFallback().convention(false); getVerbose().convention(false); getAgent().convention(false); - getPersistConfig().convention(property(providers, Utils.PERSIST_CONFIG_PROPERTY)); getImageName().convention(defaultImageName); getJavaLauncher().convention( toolchains.launcherFor(spec -> { From e8bd268308497e1fd0ce7b9a7e04f317da4cc1f0 Mon Sep 17 00:00:00 2001 From: Cedric Champeau Date: Thu, 24 Jun 2021 16:51:59 +0200 Subject: [PATCH 14/23] Adjust README with the removal of deprecated methods --- native-gradle-plugin/README.md | 49 ++++++++++++++-------------------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/native-gradle-plugin/README.md b/native-gradle-plugin/README.md index be84dacc6..a4356a431 100644 --- a/native-gradle-plugin/README.md +++ b/native-gradle-plugin/README.md @@ -77,12 +77,10 @@ nativeBuild { runtimeArgs("--help") // Passes '--help' to built image, during "nativeRun" task systemProperties = [name1: 'value1', name2: 'value2'] // Sets system properties for the native image builder agent = false // Can be also set on command line using '-Pagent' - persistConfig = false // Used in conjunction with 'agent' to save its output to META-INF } nativeTest { agent = false // Can be also set on command line using '-Pagent' - persistConfig = false // Used in conjunction with 'agent' to save its output to META-INF //... // all of the options from 'nativeBuild' block are supported here except for changing main class name. // Note that 'nativeBuild' configuration is separate to 'nativeTest' one and that they don't inherit settings from each other. @@ -97,29 +95,25 @@ Kotlin ```kotlin -tasks { - nativeBuild { - imageName.set("application") - mainClass.set("org.test.Main") // Main class - buildArgs("--no-server") // Arguments to be passed to native-image invocation - debug.set(false) // Determines if debug info should be generated - verbose.set(false) - fallback.set(false) - classpath("dir1", "dir2") // Adds "dir1" and "dir2" to the classpath - jvmArgs("flag") // Passes 'flag' directly to the JVM running the native image builder - runtimeArgs("--help") // Passes '--help' to built image, during "nativeRun" task - systemProperties.put("key1", "value1") // Sets a system property for the native-image builder - agent.set(false) // Can be also set on command line using '-Pagent' - persistConfig.set(false) // Used in conjunction with 'agent' to save its output to META-INF - } - - nativeTest { - agent.set(false) // Can be also set on command line using '-Pagent' - persistConfig.set(false) // Used in conjunction with 'agent' to save its output to META-INF - //... - // all of the options from 'nativeBuild' block are supported here except for changing main class name - // Note that 'nativeBuild' configuration is separate to 'nativeTest' one and that they don't inherit settings from each other - } +nativeBuild { + imageName.set("application") + mainClass.set("org.test.Main") // Main class + buildArgs("--no-server") // Arguments to be passed to native-image invocation + debug.set(false) // Determines if debug info should be generated + verbose.set(false) + fallback.set(false) + classpath("dir1", "dir2") // Adds "dir1" and "dir2" to the classpath + jvmArgs("flag") // Passes 'flag' directly to the JVM running the native image builder + runtimeArgs("--help") // Passes '--help' to built image, during "nativeRun" task + systemProperties.put("key1", "value1") // Sets a system property for the native-image builder + agent.set(false) // Can be also set on command line using '-Pagent' +} + +nativeTest { + agent.set(false) // Can be also set on command line using '-Pagent' + //... + // all of the options from 'nativeBuild' block are supported here except for changing main class name + // Note that 'nativeBuild' configuration is separate to 'nativeTest' one and that they don't inherit settings from each other } ``` @@ -128,10 +122,7 @@ tasks {
-> :information_source: Most of the plugin configuration syntax and tasks from `io.micronaut.application` and `com.palantir.graal` plugins is out of the box supported at the moment via aliasing. -> However, this behaviour should be considered transitional and therefore deprecated. - -> :information_source: Also note that for options that can be set using command-line, if both DSL and command-line options are present, command-line options take precedence. +> :information_source: For options that can be set using command-line, if both DSL and command-line options are present, command-line options take precedence. ### Available tasks: ``` From 07fe7f61791558aa5819d01fee74d1b8b6c5e0dd Mon Sep 17 00:00:00 2001 From: Cedric Champeau Date: Thu, 24 Jun 2021 18:19:02 +0200 Subject: [PATCH 15/23] Don't specify "Community" in the GraalVM edition This would allow use of any GraalVM version (Community or Enterprise). --- .../src/main/groovy/org.graalvm.build.functional-testing.gradle | 2 +- .../org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/native-gradle-plugin/buildSrc/src/main/groovy/org.graalvm.build.functional-testing.gradle b/native-gradle-plugin/buildSrc/src/main/groovy/org.graalvm.build.functional-testing.gradle index 437e0ceab..046970f79 100644 --- a/native-gradle-plugin/buildSrc/src/main/groovy/org.graalvm.build.functional-testing.gradle +++ b/native-gradle-plugin/buildSrc/src/main/groovy/org.graalvm.build.functional-testing.gradle @@ -74,7 +74,7 @@ configurations.functionalTestImplementation.extendsFrom(configurations.testImple def graalVm = javaToolchains.launcherFor { languageVersion.set(JavaLanguageVersion.of(11)) - vendor.set(JvmVendorSpec.matching("GraalVM Community")) + vendor.set(JvmVendorSpec.matching("GraalVM")) } ['functionalTest', 'fullFunctionalTest', 'configCacheFunctionalTest'].each { diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java index ab5c3f4c2..02270b723 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java @@ -201,7 +201,7 @@ public NativeImageOptions(ObjectFactory objectFactory, toolchains.launcherFor(spec -> { spec.getLanguageVersion().set(JavaLanguageVersion.of(11)); if (GradleUtils.isAtLeastGradle7()) { - spec.getVendor().set(JvmVendorSpec.matching("GraalVM Community")); + spec.getVendor().set(JvmVendorSpec.matching("GraalVM")); } }) ); From a4c2ae16e83fea80057b5bb977b30fdd5ce6019b Mon Sep 17 00:00:00 2001 From: Cedric Champeau Date: Mon, 28 Jun 2021 09:01:16 +0200 Subject: [PATCH 16/23] Write test results in the `build` directory In case the tests were executed directly, the working directory was set to the project directory, leading to test results written in the root dir instead of the build directory. --- .../buildtools/gradle/fixtures/AbstractFunctionalTest.groovy | 3 ++- .../buildtools/gradle/fixtures/ProcessController.groovy | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/native-gradle-plugin/src/testFixtures/groovy/org/graalvm/buildtools/gradle/fixtures/AbstractFunctionalTest.groovy b/native-gradle-plugin/src/testFixtures/groovy/org/graalvm/buildtools/gradle/fixtures/AbstractFunctionalTest.groovy index d10aa2d1d..ff893bfe8 100644 --- a/native-gradle-plugin/src/testFixtures/groovy/org/graalvm/buildtools/gradle/fixtures/AbstractFunctionalTest.groovy +++ b/native-gradle-plugin/src/testFixtures/groovy/org/graalvm/buildtools/gradle/fixtures/AbstractFunctionalTest.groovy @@ -197,7 +197,8 @@ abstract class AbstractFunctionalTest extends Specification { } ProcessController execute(File executablePath) { - new ProcessController(executablePath).execute() + new ProcessController(executablePath, file("build")) + .execute() } private void assertInitScript() { diff --git a/native-gradle-plugin/src/testFixtures/groovy/org/graalvm/buildtools/gradle/fixtures/ProcessController.groovy b/native-gradle-plugin/src/testFixtures/groovy/org/graalvm/buildtools/gradle/fixtures/ProcessController.groovy index 80faff5c0..54bb8406f 100644 --- a/native-gradle-plugin/src/testFixtures/groovy/org/graalvm/buildtools/gradle/fixtures/ProcessController.groovy +++ b/native-gradle-plugin/src/testFixtures/groovy/org/graalvm/buildtools/gradle/fixtures/ProcessController.groovy @@ -45,16 +45,19 @@ import groovy.transform.CompileStatic @CompileStatic class ProcessController { private final File executablePath + private final File workingDir private final StringWriter out = new StringWriter() private final StringWriter err = new StringWriter() - ProcessController(File executablePath) { + ProcessController(File executablePath, File workingDir) { this.executablePath = executablePath + this.workingDir = workingDir } ProcessController execute() { def process = new ProcessBuilder(executablePath.absolutePath) + .directory(workingDir) .start() TeeWriter.of( new OutputStreamWriter(System.out), From aba160a335bd255e5ba93c33dd1aa9ce6c4c86ef Mon Sep 17 00:00:00 2001 From: Cedric Champeau Date: Mon, 28 Jun 2021 09:05:14 +0200 Subject: [PATCH 17/23] Fix review feedback - remove unused persistConfig constant - don't use the `.cmd` extension under Windows for `-H:Name` - fix detection of GraalVM EE in test fixtures - fix formatting --- .../java/org/graalvm/buildtools/gradle/internal/Utils.java | 7 ------- .../buildtools/gradle/tasks/BuildNativeImageTask.java | 3 +-- .../src/main/java/org/graalvm/demo/Message.java | 2 +- .../buildtools/gradle/fixtures/GraalVMSupport.groovy | 2 +- 4 files changed, 3 insertions(+), 11 deletions(-) diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/Utils.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/Utils.java index e12cfe3f0..0a6e4c4b5 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/Utils.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/Utils.java @@ -40,8 +40,6 @@ */ package org.graalvm.buildtools.gradle.internal; -import org.gradle.api.provider.Provider; - /** * Utility class containing various native-image and JVM related methods. * Keep this file in sync across all build tool plugins. @@ -55,9 +53,4 @@ public class Utils { public static final String AGENT_OUTPUT_FOLDER = NATIVE_IMAGE_OUTPUT_FOLDER + "/agent-output"; public static final String NATIVE_TESTS_SUFFIX = "-tests"; public static final String AGENT_FILTER = "agent-filter.json"; - public static final String PERSIST_CONFIG_PROPERTY = "persistConfig"; - - public static Provider executableNameOf(Provider provider) { - return provider.map(name -> name + EXECUTABLE_EXTENSION); - } } diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/BuildNativeImageTask.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/BuildNativeImageTask.java index 20aef8ceb..5b7a54453 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/BuildNativeImageTask.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/BuildNativeImageTask.java @@ -43,7 +43,6 @@ import org.graalvm.buildtools.gradle.NativeImageService; import org.graalvm.buildtools.gradle.dsl.NativeImageOptions; import org.graalvm.buildtools.gradle.internal.GraalVMLogger; -import org.graalvm.buildtools.gradle.internal.Utils; import org.gradle.api.DefaultTask; import org.gradle.api.GradleException; import org.gradle.api.Transformer; @@ -102,7 +101,7 @@ protected Provider getGraalVMHome() { @Internal public Provider getExecutableName() { - return Utils.executableNameOf(getOptions().flatMap(NativeImageOptions::getImageName)); + return getOptions().flatMap(NativeImageOptions::getImageName); } @Internal diff --git a/native-gradle-plugin/src/samples/java-application-with-reflection/src/main/java/org/graalvm/demo/Message.java b/native-gradle-plugin/src/samples/java-application-with-reflection/src/main/java/org/graalvm/demo/Message.java index 3a73c1a18..6e9260044 100644 --- a/native-gradle-plugin/src/samples/java-application-with-reflection/src/main/java/org/graalvm/demo/Message.java +++ b/native-gradle-plugin/src/samples/java-application-with-reflection/src/main/java/org/graalvm/demo/Message.java @@ -2,4 +2,4 @@ public class Message { public static final String MESSAGE = "Hello, native!"; -} \ No newline at end of file +} diff --git a/native-gradle-plugin/src/testFixtures/groovy/org/graalvm/buildtools/gradle/fixtures/GraalVMSupport.groovy b/native-gradle-plugin/src/testFixtures/groovy/org/graalvm/buildtools/gradle/fixtures/GraalVMSupport.groovy index fa3b658f8..afebde946 100644 --- a/native-gradle-plugin/src/testFixtures/groovy/org/graalvm/buildtools/gradle/fixtures/GraalVMSupport.groovy +++ b/native-gradle-plugin/src/testFixtures/groovy/org/graalvm/buildtools/gradle/fixtures/GraalVMSupport.groovy @@ -53,7 +53,7 @@ class GraalVMSupport { } static String getVersion() { - (System.getProperty("java.vendor.version", "") - 'GraalVM' - 'CE').trim() + (System.getProperty("java.vendor.version", "") - 'GraalVM' - 'CE' - 'EE').trim() } static int getMajorVersion() { From 249ff0fd1eecebbb993bdecc18f4216f473b86fe Mon Sep 17 00:00:00 2001 From: Cedric Champeau Date: Mon, 28 Jun 2021 15:06:04 +0200 Subject: [PATCH 18/23] Fix missing newlines --- .../src/samples/java-application-with-reflection/build.gradle | 2 +- .../src/samples/kotlin-application-with-tests/build.gradle | 2 +- .../src/samples/kotlin-application-with-tests/settings.gradle | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/native-gradle-plugin/src/samples/java-application-with-reflection/build.gradle b/native-gradle-plugin/src/samples/java-application-with-reflection/build.gradle index fbdd3469d..9acc81da6 100644 --- a/native-gradle-plugin/src/samples/java-application-with-reflection/build.gradle +++ b/native-gradle-plugin/src/samples/java-application-with-reflection/build.gradle @@ -27,4 +27,4 @@ test { nativeTest { agent = true -} \ No newline at end of file +} diff --git a/native-gradle-plugin/src/samples/kotlin-application-with-tests/build.gradle b/native-gradle-plugin/src/samples/kotlin-application-with-tests/build.gradle index cc7cbd1fa..ddebe6369 100644 --- a/native-gradle-plugin/src/samples/kotlin-application-with-tests/build.gradle +++ b/native-gradle-plugin/src/samples/kotlin-application-with-tests/build.gradle @@ -21,4 +21,4 @@ application { tasks.withType(Test).configureEach { useJUnitPlatform() -} \ No newline at end of file +} diff --git a/native-gradle-plugin/src/samples/kotlin-application-with-tests/settings.gradle b/native-gradle-plugin/src/samples/kotlin-application-with-tests/settings.gradle index 1eb6c088f..b7e08ed48 100644 --- a/native-gradle-plugin/src/samples/kotlin-application-with-tests/settings.gradle +++ b/native-gradle-plugin/src/samples/kotlin-application-with-tests/settings.gradle @@ -1 +1 @@ -rootProject.name = "kotlin-application" \ No newline at end of file +rootProject.name = "kotlin-application" From e465782a638bd7f0cf06fc17978e8d61c15a3efe Mon Sep 17 00:00:00 2001 From: Cedric Champeau Date: Mon, 28 Jun 2021 15:08:39 +0200 Subject: [PATCH 19/23] Backport fix from master This commit backports the fix from #80 --- .../buildtools/gradle/tasks/BuildNativeImageTask.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/BuildNativeImageTask.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/BuildNativeImageTask.java index 5b7a54453..5ae580e58 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/BuildNativeImageTask.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/BuildNativeImageTask.java @@ -40,7 +40,6 @@ */ package org.graalvm.buildtools.gradle.tasks; -import org.graalvm.buildtools.gradle.NativeImageService; import org.graalvm.buildtools.gradle.dsl.NativeImageOptions; import org.graalvm.buildtools.gradle.internal.GraalVMLogger; import org.gradle.api.DefaultTask; @@ -176,8 +175,10 @@ private static void appendBooleanOption(List cliArgs, Provider // This property provides access to the service instance + // It should be Property but because of a bug in Gradle + // we have to use a more generic type, see https://github.com/gradle/gradle/issues/17559 @Internal - public abstract Property getService(); + public abstract Property getService(); @TaskAction @SuppressWarnings("ConstantConditions") From 42dd11a88bf5533ac641416fc62b631ca5455211 Mon Sep 17 00:00:00 2001 From: Cedric Champeau Date: Mon, 28 Jun 2021 20:59:03 +0200 Subject: [PATCH 20/23] Fix incorrect output annotation --- .../graalvm/buildtools/gradle/tasks/BuildNativeImageTask.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/BuildNativeImageTask.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/BuildNativeImageTask.java index 5ae580e58..48fd5cbe5 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/BuildNativeImageTask.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/BuildNativeImageTask.java @@ -56,7 +56,7 @@ import org.gradle.api.tasks.Internal; import org.gradle.api.tasks.Nested; import org.gradle.api.tasks.Optional; -import org.gradle.api.tasks.OutputFile; +import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.TaskAction; import org.gradle.jvm.toolchain.JavaInstallationMetadata; import org.gradle.process.ExecOperations; @@ -89,7 +89,7 @@ public abstract class BuildNativeImageTask extends DefaultTask { @Internal protected abstract DirectoryProperty getWorkingDirectory(); - @OutputFile + @OutputDirectory public abstract DirectoryProperty getOutputDirectory(); @Optional From 7e5a9269cf4ef874e2de44f904c834be6b453fc5 Mon Sep 17 00:00:00 2001 From: Cedric Champeau Date: Tue, 29 Jun 2021 09:05:32 +0200 Subject: [PATCH 21/23] Make sure the `run` task isn't accidentally executed This commit fixes a problem with the `run` task which could be accidentally executed if `nativeBuild` was called *and* that the `run` task was eagerly configured, even if the agent property wasn't set. --- .../gradle/JavaApplicationFunctionalTest.groovy | 9 ++++++++- .../buildtools/gradle/NativeImagePlugin.java | 13 +++++++------ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JavaApplicationFunctionalTest.groovy b/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JavaApplicationFunctionalTest.groovy index 8680cc246..5f09c2c78 100644 --- a/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JavaApplicationFunctionalTest.groovy +++ b/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JavaApplicationFunctionalTest.groovy @@ -46,17 +46,24 @@ class JavaApplicationFunctionalTest extends AbstractFunctionalTest { def "can build a native image for a simple application"() { gradleVersion = version def nativeApp = file("build/native/nativeBuild/java-application") + debug = true given: withSample("java-application") + buildFile << """ + // force realization of the run task to verify that it + // doesn't accidentally introduce a dependency + tasks.getByName('run') + """.stripIndent() + when: run 'nativeBuild' then: tasks { succeeded ':jar', ':nativeBuild' - doesNotContain ':build' + doesNotContain ':build', ':run' } and: diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java index 8e9f04f80..c0792978b 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java @@ -136,8 +136,7 @@ private void configureJavaProject(Project project, Provider // Application Plugin is initialized. project.getPlugins().withType(ApplicationPlugin.class, applicationPlugin -> tasks.withType(JavaExec.class).named(ApplicationPlugin.TASK_RUN_NAME, run -> { - Provider cliProvider = configureAgent(project, run, copyAgentFilterTask, agent, buildExtension, run.getName()); - buildExtension.getConfigurationFileDirectories().from(cliProvider); + configureAgent(project, run, copyAgentFilterTask, agent, buildExtension, run.getName()); })); // In future Gradle releases this becomes a proper DirectoryProperty @@ -152,8 +151,7 @@ private void configureJavaProject(Project project, Provider testListDirectory.set(new File(testResultsDir, test.getName() + "/testlist")); test.getOutputs().dir(testResultsDir); test.systemProperty("graalvm.testids.outputdir", testListDirectory.getAsFile().get()); - Provider cliProviderFile = configureAgent(project, test, copyAgentFilterTask, testAgent, testExtension, test.getName()); - testExtension.getConfigurationFileDirectories().from(cliProviderFile); + configureAgent(project, test, copyAgentFilterTask, testAgent, testExtension, test.getName()); }); // Following ensures that required feature jar is on classpath for every project @@ -239,7 +237,7 @@ private static NativeImageOptions createTestExtension(Project project, NativeIma return testExtension; } - private static Provider configureAgent(Project project, + private static void configureAgent(Project project, JavaForkOptions javaForkOptions, TaskProvider filterProvider, Provider agent, @@ -252,7 +250,10 @@ private static Provider configureAgent(Project project, javaForkOptions.getJvmArgumentProviders().add(cliProvider); // We're "desugaring" the output intentionally to workaround a Gradle bug which absolutely // wants the file to track the input but since it's not from a task it would fail - return project.getProviders().provider(() -> cliProvider.getOutputDirectory().get().getAsFile()); + Provider producer = project.getProviders().provider(() -> cliProvider.getOutputDirectory().get().getAsFile()); + nativeImageOptions.getConfigurationFileDirectories().from( + agent.map(enabled -> enabled ? producer.get() : project.files()) + ); } private static void injectTestPluginDependencies(Project project) { From 0c790bb66d9ddbd63afb11acc2b24c2824eb0117 Mon Sep 17 00:00:00 2001 From: Cedric Champeau Date: Thu, 1 Jul 2021 09:04:49 +0200 Subject: [PATCH 22/23] Remove redundant comment --- .../src/main/java/org/graalvm/buildtools/Utils.java | 1 - 1 file changed, 1 deletion(-) diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/Utils.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/Utils.java index 91f4cec9c..4e8c260e8 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/Utils.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/Utils.java @@ -50,7 +50,6 @@ /** * Utility class containing various native-image and JVM related methods. - * Keep this file in sync across all build tool plugins. */ public class Utils { public static final boolean IS_WINDOWS = System.getProperty("os.name", "unknown").contains("Windows"); From eb81a70dda7e60b63bad1dcd57dba7da4979ff10 Mon Sep 17 00:00:00 2001 From: Cedric Champeau Date: Fri, 25 Jun 2021 17:58:42 +0200 Subject: [PATCH 23/23] Replace agent-filter.json with post-processing This commit replaces the adhoc `agent-filter.json` file with a post-processing step. Basically, instead of passing an access filter file to the agent, we now post-process the generated JSON files to remove entries that we don't need. There are several advantages doing this: 1. user can rewire the image build task to use different files 2. filering can be made more configurable in the future 3. the plugin is now compatible with older GraalVM releases The current way the files are filtered, though, is very rough. Because there's no official JSON schemas for the generated files, it's an adhoc, deep inspection algorithm which filters known patterns. --- ...aApplicationWithAgentFunctionalTest.groovy | 11 +- ...nApplicationWithTestsFunctionalTest.groovy | 2 +- .../buildtools/gradle/NativeImagePlugin.java | 68 +++--- .../internal/AgentCommandLineProvider.java | 13 +- .../internal/CopyClasspathResourceTask.java | 83 -------- .../ProcessGeneratedGraalResourceFiles.java | 199 ++++++++++++++++++ .../buildtools/gradle/internal/Utils.java | 1 - .../src/main/resources/agent-filter.json | 7 - 8 files changed, 246 insertions(+), 138 deletions(-) delete mode 100644 native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/CopyClasspathResourceTask.java create mode 100644 native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/ProcessGeneratedGraalResourceFiles.java delete mode 100644 native-gradle-plugin/src/main/resources/agent-filter.json diff --git a/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JavaApplicationWithAgentFunctionalTest.groovy b/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JavaApplicationWithAgentFunctionalTest.groovy index 352739997..8e045e214 100644 --- a/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JavaApplicationWithAgentFunctionalTest.groovy +++ b/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JavaApplicationWithAgentFunctionalTest.groovy @@ -41,15 +41,8 @@ package org.graalvm.buildtools.gradle import org.graalvm.buildtools.gradle.fixtures.AbstractFunctionalTest -import org.graalvm.buildtools.gradle.fixtures.GraalVMSupport -import spock.lang.Requires import spock.lang.Unroll -@Requires({ - GraalVMSupport.isGraal() - && GraalVMSupport.majorVersion >= 21 - && GraalVMSupport.minorVersion > 0 -}) class JavaApplicationWithAgentFunctionalTest extends AbstractFunctionalTest { @Unroll("agent is passed and generates resources files on Gradle #version with JUnit Platform #junitVersion") @@ -65,7 +58,7 @@ class JavaApplicationWithAgentFunctionalTest extends AbstractFunctionalTest { then: tasks { succeeded ':jar', - ':copyAgentFilter', + ':filterAgentTestResources', ':nativeTest' doesNotContain ':build' } @@ -99,7 +92,7 @@ class JavaApplicationWithAgentFunctionalTest extends AbstractFunctionalTest { @Unroll("agent property takes precedence on Gradle #version with JUnit Platform #junitVersion") def "agent property takes precedence"() { gradleVersion = version - debug = true + given: withSample("java-application-with-reflection") diff --git a/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/KotlinApplicationWithTestsFunctionalTest.groovy b/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/KotlinApplicationWithTestsFunctionalTest.groovy index aaeaeb640..9546b571f 100644 --- a/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/KotlinApplicationWithTestsFunctionalTest.groovy +++ b/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/KotlinApplicationWithTestsFunctionalTest.groovy @@ -49,7 +49,7 @@ class KotlinApplicationWithTestsFunctionalTest extends AbstractFunctionalTest { @Unroll("can execute Kotlin tests in a native image directly on Gradle #version with JUnit Platform #junitVersion") def "can execute Kotlin tests in a native image directly"() { gradleVersion = version - debug = true + given: withSample("kotlin-application-with-tests") diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java index c0792978b..d2722035f 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java @@ -43,15 +43,17 @@ import org.graalvm.buildtools.VersionInfo; import org.graalvm.buildtools.gradle.dsl.NativeImageOptions; import org.graalvm.buildtools.gradle.internal.AgentCommandLineProvider; -import org.graalvm.buildtools.gradle.internal.CopyClasspathResourceTask; import org.graalvm.buildtools.gradle.internal.GraalVMLogger; import org.graalvm.buildtools.gradle.internal.GradleUtils; +import org.graalvm.buildtools.gradle.internal.ProcessGeneratedGraalResourceFiles; import org.graalvm.buildtools.gradle.internal.Utils; import org.graalvm.buildtools.gradle.tasks.BuildNativeImageTask; import org.graalvm.buildtools.gradle.tasks.NativeRunTask; import org.gradle.api.Plugin; import org.gradle.api.Project; +import org.gradle.api.Task; import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.file.Directory; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.plugins.ApplicationPlugin; import org.gradle.api.plugins.JavaApplication; @@ -67,9 +69,10 @@ import org.gradle.process.JavaForkOptions; import java.io.File; +import java.util.Arrays; +import java.util.concurrent.Callable; import java.util.function.Consumer; -import static org.graalvm.buildtools.gradle.internal.Utils.AGENT_FILTER; import static org.graalvm.buildtools.gradle.internal.Utils.AGENT_OUTPUT_FOLDER; import static org.graalvm.buildtools.gradle.internal.Utils.AGENT_PROPERTY; @@ -83,7 +86,8 @@ public class NativeImagePlugin implements Plugin { public static final String NATIVE_TEST_BUILD_TASK_NAME = "nativeTestBuild"; public static final String NATIVE_TEST_EXTENSION = "nativeTest"; public static final String NATIVE_BUILD_EXTENSION = "nativeBuild"; - public static final String COPY_AGENT_FILTER_TASK_NAME = "copyAgentFilter"; + public static final String PROCESS_AGENT_RESOURCES_TASK_NAME = "filterAgentResources"; + public static final String PROCESS_AGENT_TEST_RESOURCES_TASK_NAME = "filterAgentTestResources"; /** * This looks strange, but it is used to force the configuration of a dependent @@ -91,7 +95,8 @@ public class NativeImagePlugin implements Plugin { * when applying the Kotlin plugin, where the test task is configured too late * for some reason. */ - private static final Consumer FORCE_CONFIG = t -> { }; + private static final Consumer FORCE_CONFIG = t -> { + }; private GraalVMLogger logger; @@ -130,14 +135,14 @@ private void configureJavaProject(Project project, Provider task.getRuntimeArgs().convention(buildExtension.getRuntimeArgs()); }); - TaskProvider copyAgentFilterTask = registerCopyAgentFilterTask(project); + TaskProvider processAgentFiles = registerProcessAgentFilesTask(project, PROCESS_AGENT_RESOURCES_TASK_NAME); // We want to add agent invocation to "run" task, but it is only available when // Application Plugin is initialized. - project.getPlugins().withType(ApplicationPlugin.class, applicationPlugin -> - tasks.withType(JavaExec.class).named(ApplicationPlugin.TASK_RUN_NAME, run -> { - configureAgent(project, run, copyAgentFilterTask, agent, buildExtension, run.getName()); - })); + project.getPlugins().withType(ApplicationPlugin.class, applicationPlugin -> { + TaskProvider runTask = tasks.withType(JavaExec.class).named(ApplicationPlugin.TASK_RUN_NAME); + runTask.configure(run -> configureAgent(project, agent, buildExtension, runTask, processAgentFiles)); + }); // In future Gradle releases this becomes a proper DirectoryProperty File testResultsDir = GradleUtils.getJavaPluginConvention(project).getTestResultsDir(); @@ -146,12 +151,13 @@ private void configureJavaProject(Project project, Provider // Testing part begins here. TaskCollection testTask = findTestTask(project); Provider testAgent = agentPropertyOverride(project, testExtension); + TaskProvider processAgentTestFiles = registerProcessAgentFilesTask(project, PROCESS_AGENT_TEST_RESOURCES_TASK_NAME); testTask.configureEach(test -> { testListDirectory.set(new File(testResultsDir, test.getName() + "/testlist")); test.getOutputs().dir(testResultsDir); test.systemProperty("graalvm.testids.outputdir", testListDirectory.getAsFile().get()); - configureAgent(project, test, copyAgentFilterTask, testAgent, testExtension, test.getName()); + configureAgent(project, testAgent, testExtension, testTask.named(test.getName()), processAgentTestFiles); }); // Following ensures that required feature jar is on classpath for every project @@ -192,10 +198,10 @@ private static Provider agentPropertyOverride(Project project, NativeIm .orElse(extension.getAgent()); } - private static TaskProvider registerCopyAgentFilterTask(Project project) { - return project.getTasks().register(COPY_AGENT_FILTER_TASK_NAME, CopyClasspathResourceTask.class, task -> { - task.getClasspathResource().set("/" + AGENT_FILTER); - task.getOutputFile().set(project.getLayout().getBuildDirectory().file("native/agent-filter/" + AGENT_FILTER)); + private static TaskProvider registerProcessAgentFilesTask(Project project, String name) { + return project.getTasks().register(name, ProcessGeneratedGraalResourceFiles.class, task -> { + task.getFilterableEntries().convention(Arrays.asList("org.gradle.", "java.")); + task.getOutputDirectory().convention(project.getLayout().getBuildDirectory().dir("native/processed/agent/" + name)); }); } @@ -238,22 +244,30 @@ private static NativeImageOptions createTestExtension(Project project, NativeIma } private static void configureAgent(Project project, - JavaForkOptions javaForkOptions, - TaskProvider filterProvider, - Provider agent, - NativeImageOptions nativeImageOptions, - String context) { + Provider agent, + NativeImageOptions nativeImageOptions, + TaskProvider instrumentedTask, + TaskProvider postProcessingTask) { AgentCommandLineProvider cliProvider = project.getObjects().newInstance(AgentCommandLineProvider.class); cliProvider.getEnabled().set(agent); - cliProvider.getAccessFilter().set(filterProvider.flatMap(CopyClasspathResourceTask::getOutputFile)); - cliProvider.getOutputDirectory().set(project.getLayout().getBuildDirectory().dir(AGENT_OUTPUT_FOLDER + "/" + context)); - javaForkOptions.getJvmArgumentProviders().add(cliProvider); - // We're "desugaring" the output intentionally to workaround a Gradle bug which absolutely - // wants the file to track the input but since it's not from a task it would fail - Provider producer = project.getProviders().provider(() -> cliProvider.getOutputDirectory().get().getAsFile()); - nativeImageOptions.getConfigurationFileDirectories().from( - agent.map(enabled -> enabled ? producer.get() : project.files()) + Provider outputDir = project.getLayout().getBuildDirectory().dir(AGENT_OUTPUT_FOLDER + "/" + instrumentedTask.getName()); + cliProvider.getOutputDirectory().set(outputDir); + instrumentedTask.get().getJvmArgumentProviders().add(cliProvider); + // Gradle won't let us configure from configure so we have to eagerly create the post-processing task :( + postProcessingTask.get().getGeneratedFilesDir().set( + instrumentedTask.map(t -> outputDir.get()) ); + // We can't set from(postProcessingTask) directly, otherwise a task + // dependency would be introduced even if the agent is not enabled. + // We should be able to write this: + // nativeImageOptions.getConfigurationFileDirectories().from( + // agent.map(enabled -> enabled ? postProcessingTask : project.files()) + // ) + // but Gradle won't track the postProcessingTask dependency so we have to write this: + ConfigurableFileCollection files = project.getObjects().fileCollection(); + files.from(agent.map(enabled -> enabled ? postProcessingTask : project.files())); + files.builtBy((Callable) () -> agent.get() ? postProcessingTask.get() : null); + nativeImageOptions.getConfigurationFileDirectories().from(files); } private static void injectTestPluginDependencies(Project project) { diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/AgentCommandLineProvider.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/AgentCommandLineProvider.java index 9e95238a1..6de052f60 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/AgentCommandLineProvider.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/AgentCommandLineProvider.java @@ -41,16 +41,13 @@ package org.graalvm.buildtools.gradle.internal; import org.gradle.api.file.DirectoryProperty; -import org.gradle.api.file.RegularFileProperty; import org.gradle.api.provider.Property; import org.gradle.api.tasks.Input; -import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.OutputDirectory; -import org.gradle.api.tasks.PathSensitive; -import org.gradle.api.tasks.PathSensitivity; import org.gradle.process.CommandLineArgumentProvider; import javax.inject.Inject; +import java.io.File; import java.util.Arrays; import java.util.Collections; @@ -65,20 +62,16 @@ public AgentCommandLineProvider() { @Input public abstract Property getEnabled(); - @InputFile - @PathSensitive(PathSensitivity.NONE) - public abstract RegularFileProperty getAccessFilter(); - @OutputDirectory public abstract DirectoryProperty getOutputDirectory(); @Override public Iterable asArguments() { if (getEnabled().get()) { + File outputDir = getOutputDirectory().getAsFile().get(); return Arrays.asList( "-agentlib:native-image-agent=experimental-class-loader-support," + - "config-output-dir=" + getOutputDirectory().getAsFile().get().getAbsolutePath() + "," + - "access-filter-file=" + getAccessFilter().getAsFile().get().getAbsolutePath(), + "config-output-dir=" + outputDir.getAbsolutePath(), "-Dorg.graalvm.nativeimage.imagecode=agent" ); } diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/CopyClasspathResourceTask.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/CopyClasspathResourceTask.java deleted file mode 100644 index 5f2b17236..000000000 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/CopyClasspathResourceTask.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2021, 2021 Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * The Universal Permissive License (UPL), Version 1.0 - * - * Subject to the condition set forth below, permission is hereby granted to any - * person obtaining a copy of this software, associated documentation and/or - * data (collectively the "Software"), free of charge and under any and all - * copyright rights in the Software, and any and all patent rights owned or - * freely licensable by each licensor hereunder covering either (i) the - * unmodified Software as contributed to or provided by such licensor, or (ii) - * the Larger Works (as defined below), to deal in both - * - * (a) the Software, and - * - * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if - * one is included with the Software each a "Larger Work" to which the Software - * is contributed by such licensors), - * - * without restriction, including without limitation the rights to copy, create - * derivative works of, display, perform, and distribute the Software and make, - * use, sell, offer for sale, import, export, have made, and have sold the - * Software and the Larger Work(s), and to sublicense the foregoing rights on - * either these or other terms. - * - * This license is subject to the following condition: - * - * The above copyright notice and either this complete permission notice or at a - * minimum a reference to the UPL must be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package org.graalvm.buildtools.gradle.internal; - -import org.gradle.api.DefaultTask; -import org.gradle.api.GradleException; -import org.gradle.api.file.RegularFileProperty; -import org.gradle.api.provider.Property; -import org.gradle.api.tasks.Input; -import org.gradle.api.tasks.OutputFile; -import org.gradle.api.tasks.TaskAction; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.StandardCopyOption; -import java.util.Objects; - -/** - * This task is for internal use only, its responsibility is to - * generate a filter config file which is used by native image. - */ -public abstract class CopyClasspathResourceTask extends DefaultTask { - @Input - public abstract Property getClasspathResource(); - - @OutputFile - public abstract RegularFileProperty getOutputFile(); - - @TaskAction - public void copy() { - File outputFile = getOutputFile().getAsFile().get(); - File directory = outputFile.getParentFile(); - String resource = getClasspathResource().get(); - if (directory.isDirectory() || directory.mkdirs()) { - try { - Files.copy(Objects.requireNonNull(this.getClass().getResourceAsStream(resource)), - outputFile.toPath(), - StandardCopyOption.REPLACE_EXISTING); - } catch (IOException | NullPointerException e) { - throw new GradleException("Error while copying access-filter file.", e); - } - } - } -} diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/ProcessGeneratedGraalResourceFiles.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/ProcessGeneratedGraalResourceFiles.java new file mode 100644 index 000000000..3d160e639 --- /dev/null +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/ProcessGeneratedGraalResourceFiles.java @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2021, 2021 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.graalvm.buildtools.gradle.internal; + +import groovy.json.JsonGenerator; +import groovy.json.JsonOutput; +import groovy.json.JsonSlurper; +import org.gradle.api.DefaultTask; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.provider.ListProperty; +import org.gradle.api.tasks.CacheableTask; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputDirectory; +import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.PathSensitive; +import org.gradle.api.tasks.PathSensitivity; +import org.gradle.api.tasks.TaskAction; +import org.gradle.work.FileChange; +import org.gradle.work.InputChanges; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This task is responsible for processing the JSON files generated by + * the GraalVM agent, in particular to filter out entries which are + * inherited from the Gradle environment itself. + */ +@CacheableTask +public abstract class ProcessGeneratedGraalResourceFiles extends DefaultTask { + /** + * The directory which contains the files generated by the GraalVM agent. + */ + @InputDirectory + @PathSensitive(PathSensitivity.NAME_ONLY) + public abstract DirectoryProperty getGeneratedFilesDir(); + + @OutputDirectory + public abstract DirectoryProperty getOutputDirectory(); + + @Input + public abstract ListProperty getFilterableEntries(); + + @TaskAction + public void filterResources(InputChanges inputChanges) throws IOException { + File outputDir = getOutputDirectory().get().getAsFile(); + if (outputDir.isDirectory() || outputDir.mkdirs()) { + if (inputChanges.isIncremental()) { + Iterable fileChanges = inputChanges.getFileChanges(getGeneratedFilesDir()); + for (FileChange change : fileChanges) { + switch (change.getChangeType()) { + case ADDED: + case MODIFIED: + processFile(change.getFile(), outputDir); + break; + case REMOVED: + new File(outputDir, change.getFile().getName()).delete(); + break; + } + } + } else { + for (File resourceFile : getGeneratedFilesDir().getAsFileTree().getFiles()) { + processFile(resourceFile, outputDir); + } + } + } + } + + protected void processFile(File file, File outputDir) throws IOException { + if (file.getName().endsWith(".json")) { + processJsonFile(file, outputDir); + } else { + copyFile(file, outputDir); + } + } + + private static void copyFile(File file, File outputDir) throws IOException { + Files.copy(file.toPath(), outputDir.toPath().resolve(file.getName()), StandardCopyOption.REPLACE_EXISTING); + } + + protected void processJsonFile(File jsonFile, File outputDir) throws IOException { + JsonSlurper json = new JsonSlurper(); + Object result = json.parse(jsonFile); + Object filtered = filter(result); + JsonGenerator generator = new JsonGenerator.Options() + .build(); + String processed = JsonOutput.prettyPrint(generator.toJson(filtered)); + try (Writer writer = new OutputStreamWriter(new FileOutputStream(new File(outputDir, jsonFile.getName())), StandardCharsets.UTF_8)) { + writer.write(processed); + } + } + + /** + * Filters the parsed JSON file to remove entries which are configured + * by the filterable entries parameter. This is a very rough algorithm + * which would deserve specific implementation for each JSON format. + * Instead it takes a "brute force" approach which may result in some + * weird errors. + */ + @SuppressWarnings("unchecked") + private Object filter(Object in) { + Class clazz = in.getClass(); + if (shouldFilterString(in)) { + return null; + } + if (List.class.isAssignableFrom(clazz)) { + return filterList((List) in); + } + if (Map.class.isAssignableFrom(clazz)) { + return filterMap((Map) in); + } + return in; + } + + private Map filterMap(Map map) { + if (shouldFilterString(map.get("name"))) { + return null; + } + Map out = new HashMap<>(map.size()); + for (Map.Entry entry : map.entrySet()) { + Object key = entry.getKey(); + Object value = entry.getValue(); + if (shouldFilterString(value)) { + continue; + } + out.put(key, filter(value)); + } + return out; + } + + private boolean shouldFilterString(Object value) { + if (value instanceof CharSequence) { + String string = value.toString(); + return getFilterableEntries().get().stream().anyMatch(string::startsWith); + } + return false; + } + + private List filterList(List in) { + List out = new ArrayList<>(in.size()); + for (Object element : in) { + Object filtered = filter(element); + if (filtered == null || (filtered instanceof Collection && ((Collection) filtered).isEmpty())) { + continue; + } + out.add(filtered); + } + return out; + } +} diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/Utils.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/Utils.java index 0a6e4c4b5..85b02ac50 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/Utils.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/Utils.java @@ -52,5 +52,4 @@ public class Utils { public static final String AGENT_PROPERTY = "agent"; public static final String AGENT_OUTPUT_FOLDER = NATIVE_IMAGE_OUTPUT_FOLDER + "/agent-output"; public static final String NATIVE_TESTS_SUFFIX = "-tests"; - public static final String AGENT_FILTER = "agent-filter.json"; } diff --git a/native-gradle-plugin/src/main/resources/agent-filter.json b/native-gradle-plugin/src/main/resources/agent-filter.json deleted file mode 100644 index 790b44c54..000000000 --- a/native-gradle-plugin/src/main/resources/agent-filter.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "rules":[ - { - "excludeClasses":"org.gradle.**" - } - ] -}