diff --git a/docs/src/docs/asciidoc/gradle-plugin.adoc b/docs/src/docs/asciidoc/gradle-plugin.adoc index 126cb7c6a..354d795bd 100644 --- a/docs/src/docs/asciidoc/gradle-plugin.adoc +++ b/docs/src/docs/asciidoc/gradle-plugin.adoc @@ -348,6 +348,30 @@ graalvmNative { } ``` +[[plugin-configurations]] +=== Configurations defined by the plugin + +For each binary (`main` and `test`), the plugin declares 2 configurations that users or plugin authors can use to tweak the native image compilation classpath: + +- `nativeImageCompileOnly` (for the `main` binary) and `nativeImageTestCompileOnly` (for the `test` binary) can be used to declare dependencies which are only needed at image compilation. +- `nativeImageClasspath` (for the `main` binary) and `nativeImageTestClasspath` (for the `test` binary) are the configurations which are resolved to determine the image classpaths. + +The native image "compile only" configurations can typically be used to declare dependencies which are only required when building a native binary, and therefore shouldn't leak to the classic "JVM" runtime. + +For example, you could declare a source set which uses the GraalVM SDK to implement native features. +This source set would contain code which is only relevant to native images building: + +.Declaring a custom source set +[role="multi-language-sample"] +```groovy +include::../../../../samples/java-application-with-extra-sourceset/build.gradle[tag=extra-sourceset] +``` + +[role="multi-language-sample"] +```kotlin +include::../../../../samples/java-application-with-extra-sourceset/build.gradle.kts[tag=extra-sourceset] +``` + == Javadocs In addition, you can consult the link:javadocs/native-gradle-plugin/index.html[Javadocs of the plugin]. 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 24eb6d92c..1cbe5b90a 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 @@ -42,6 +42,7 @@ package org.graalvm.buildtools.gradle import org.graalvm.buildtools.gradle.fixtures.AbstractFunctionalTest +import spock.lang.Issue class JavaApplicationFunctionalTest extends AbstractFunctionalTest { def "can build a native image for a simple application"() { @@ -79,4 +80,36 @@ class JavaApplicationFunctionalTest extends AbstractFunctionalTest { where: version << TESTED_GRADLE_VERSIONS } + + @Issue("https://github.com/graalvm/native-build-tools/issues/129") + def "can build a native image with dependencies only needed by native image"() { + gradleVersion = version + def nativeApp = file("build/native/nativeCompile/java-application") + debug = true + + given: + withSample("java-application-with-extra-sourceset") + + when: + run 'nativeCompile' + + then: + tasks { + succeeded ':jar', ':nativeCompile', ':compileGraalJava' + doesNotContain ':build', ':run' + } + + and: + outputContains 'Application Feature' + nativeApp.exists() + + when: + def process = execute(nativeApp) + + then: + process.output.contains "Hello, native!" + + where: + version << TESTED_GRADLE_VERSIONS + } } 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 8f5b77109..909ac66ab 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 @@ -50,6 +50,7 @@ import org.graalvm.buildtools.gradle.internal.DeprecatedNativeImageOptions; import org.graalvm.buildtools.gradle.internal.GraalVMLogger; import org.graalvm.buildtools.gradle.internal.GradleUtils; +import org.graalvm.buildtools.gradle.internal.NativeConfigurations; import org.graalvm.buildtools.gradle.internal.ProcessGeneratedGraalResourceFiles; import org.graalvm.buildtools.gradle.tasks.BuildNativeImageTask; import org.graalvm.buildtools.gradle.tasks.GenerateResourcesConfigFile; @@ -60,6 +61,10 @@ 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.artifacts.ConfigurationContainer; +import org.gradle.api.attributes.Attribute; +import org.gradle.api.attributes.AttributeContainer; import org.gradle.api.file.ArchiveOperations; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.Directory; @@ -94,8 +99,6 @@ import java.util.function.Consumer; import java.util.stream.Collectors; -import static org.graalvm.buildtools.gradle.internal.GradleUtils.findConfiguration; -import static org.graalvm.buildtools.gradle.internal.GradleUtils.findMainArtifacts; import static org.graalvm.buildtools.gradle.internal.GradleUtils.transitiveProjectArtifacts; import static org.graalvm.buildtools.utils.SharedConstants.AGENT_OUTPUT_FOLDER; import static org.graalvm.buildtools.utils.SharedConstants.AGENT_PROPERTY; @@ -368,15 +371,58 @@ private static void registerServiceProvider(Project project, Provider 0) { + return name.substring(0, 1).toLowerCase(Locale.US) + name.substring(1); + } + return name; + } + + @SuppressWarnings("unchecked") + private static NativeConfigurations createNativeConfigurations(Project project, String binaryName, String baseClasspathConfigurationName) { + ConfigurationContainer configurations = project.getConfigurations(); + String prefix = "main".equals(binaryName) ? "" : capitalize(binaryName); + Configuration baseClasspath = configurations.getByName(baseClasspathConfigurationName); + Configuration compileOnly = configurations.create("nativeImage" + prefix + "CompileOnly", c -> { + c.setCanBeResolved(false); + c.setCanBeConsumed(false); + }); + Configuration classpath = configurations.create("nativeImage" + prefix + "Classpath", c -> { + c.setCanBeConsumed(false); + c.setCanBeResolved(true); + c.extendsFrom(compileOnly); + baseClasspath.getExtendsFrom().forEach(c::extendsFrom); + c.attributes(attrs -> { + AttributeContainer baseAttributes = baseClasspath.getAttributes(); + for (Attribute attribute : baseAttributes.keySet()) { + Attribute attr = (Attribute) attribute; + Object value = baseAttributes.getAttribute(attr); + attrs.attribute(attr, value); + } + }); + }); + compileOnly.getDependencies().add(project.getDependencies().create(project)); + return new NativeConfigurations(compileOnly, classpath); + } + private static NativeImageOptions createMainOptions(GraalVMExtension graalExtension, Project project) { NativeImageOptions buildExtension = graalExtension.getBinaries().create(NATIVE_MAIN_EXTENSION); - buildExtension.getClasspath().from(GradleUtils.findMainArtifacts(project)); - buildExtension.getClasspath().from(GradleUtils.findConfiguration(project, JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME)); + NativeConfigurations configs = createNativeConfigurations( + project, + "main", + JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME + ); + buildExtension.getClasspath().from(configs.getImageClasspathConfiguration()); return buildExtension; } private static NativeImageOptions createTestOptions(GraalVMExtension graalExtension, Project project, NativeImageOptions mainExtension, DirectoryProperty testListDirectory) { NativeImageOptions testExtension = graalExtension.getBinaries().create(NATIVE_TEST_EXTENSION); + NativeConfigurations configs = createNativeConfigurations( + project, + "test", + JavaPlugin.TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME + ); testExtension.getMainClass().set("org.graalvm.junit.platform.NativeImageJUnitLauncher"); testExtension.getMainClass().finalizeValue(); testExtension.getImageName().convention(mainExtension.getImageName().map(name -> name + SharedConstants.NATIVE_TESTS_SUFFIX)); @@ -387,8 +433,7 @@ private static NativeImageOptions createTestOptions(GraalVMExtension graalExtens // Set system property read indirectly by the JUnitPlatformFeature. testExtension.getBuildArgs().add(project.provider(() -> "-Djunit.platform.listeners.uid.tracking.output.dir=" + testListDirectory.getAsFile().get().getAbsolutePath())); ConfigurableFileCollection classpath = testExtension.getClasspath(); - classpath.from(findMainArtifacts(project)); - classpath.from(findConfiguration(project, JavaPlugin.TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME)); + classpath.from(configs.getImageClasspathConfiguration()); 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; diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/NativeConfigurations.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/NativeConfigurations.java new file mode 100644 index 000000000..924ea0b75 --- /dev/null +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/NativeConfigurations.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2020, 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.artifacts.Configuration; + +public class NativeConfigurations { + private final Configuration imageCompileOnly; + private final Configuration imageClasspath; + + public NativeConfigurations(Configuration imageCompileOnly, Configuration imageClasspath) { + this.imageCompileOnly = imageCompileOnly; + this.imageClasspath = imageClasspath; + } + + public Configuration getImageCompileOnlyConfiguration() { + return imageCompileOnly; + } + + public Configuration getImageClasspathConfiguration() { + return imageClasspath; + } +} diff --git a/samples/java-application-with-extra-sourceset/build.gradle b/samples/java-application-with-extra-sourceset/build.gradle new file mode 100644 index 000000000..348880ce0 --- /dev/null +++ b/samples/java-application-with-extra-sourceset/build.gradle @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2020, 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. + */ + +plugins { + id 'application' + id 'org.graalvm.buildtools.native' +} + +application { + mainClass.set('org.graalvm.demo.Application') +} + +repositories { + mavenCentral() +} + +// tag::extra-sourceset[] +sourceSets { + graal +} + +dependencies { + graalImplementation 'org.graalvm.nativeimage:svm:21.2.0' + graalImplementation 'org.graalvm.sdk:graal-sdk:21.2.0' + nativeImageCompileOnly sourceSets.graal.output.classesDirs +} + +configurations { + nativeImageClasspath.extendsFrom(graalImplementation) +} +// end::extra-sourceset[] diff --git a/samples/java-application-with-extra-sourceset/build.gradle.kts b/samples/java-application-with-extra-sourceset/build.gradle.kts new file mode 100644 index 000000000..cbf3db1b8 --- /dev/null +++ b/samples/java-application-with-extra-sourceset/build.gradle.kts @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2020, 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. + */ + +plugins { + application + id("org.graalvm.buildtools.native") +} + +application { + mainClass.set("org.graalvm.demo.Application") +} + +repositories { + mavenCentral() +} + +// tag::extra-sourceset[] +val graal by sourceSets.creating + +dependencies { + "graalImplementation"("org.graalvm.nativeimage:svm:21.2.0") + "graalImplementation"("org.graalvm.sdk:graal-sdk:21.2.0") + nativeImageCompileOnly(graal.output.classesDirs) +} + +configurations { + nativeImageClasspath.extendsFrom(getByName("graalImplementation")) +} +// end::extra-sourceset[] diff --git a/samples/java-application-with-extra-sourceset/gradle.properties b/samples/java-application-with-extra-sourceset/gradle.properties new file mode 100644 index 000000000..a4d90e14f --- /dev/null +++ b/samples/java-application-with-extra-sourceset/gradle.properties @@ -0,0 +1,3 @@ +native.gradle.plugin.version = 0.9.6-SNAPSHOT +junit.jupiter.version = 5.7.2 +junit.platform.version = 1.7.2 diff --git a/samples/java-application-with-extra-sourceset/settings.gradle b/samples/java-application-with-extra-sourceset/settings.gradle new file mode 100644 index 000000000..53fde2a34 --- /dev/null +++ b/samples/java-application-with-extra-sourceset/settings.gradle @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2020, 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. + */ + +pluginManagement { + plugins { + id 'org.graalvm.buildtools.native' version getProperty('native.gradle.plugin.version') + } + repositories { + mavenCentral() + gradlePluginPortal() + } +} + +rootProject.name = 'java-application' diff --git a/samples/java-application-with-extra-sourceset/src/graal/java/org/graalvm/demo/ApplicationFeature.java b/samples/java-application-with-extra-sourceset/src/graal/java/org/graalvm/demo/ApplicationFeature.java new file mode 100644 index 000000000..70c5d317a --- /dev/null +++ b/samples/java-application-with-extra-sourceset/src/graal/java/org/graalvm/demo/ApplicationFeature.java @@ -0,0 +1,12 @@ +package org.graalvm.demo; + +import org.graalvm.nativeimage.hosted.Feature; +import com.oracle.svm.core.annotate.AutomaticFeature; + +@AutomaticFeature +public class ApplicationFeature implements Feature { + @Override + public void duringSetup(DuringSetupAccess access) { + System.out.println("Application Feature"); + } +} diff --git a/samples/java-application-with-extra-sourceset/src/main/java/org/graalvm/demo/Application.java b/samples/java-application-with-extra-sourceset/src/main/java/org/graalvm/demo/Application.java new file mode 100644 index 000000000..a8a992b18 --- /dev/null +++ b/samples/java-application-with-extra-sourceset/src/main/java/org/graalvm/demo/Application.java @@ -0,0 +1,7 @@ +package org.graalvm.demo; + +public class Application { + public static void main(String[] args) { + System.out.println("Hello, native!"); + } +}