Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright (c) 2020, 2022 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.utils;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

import static org.graalvm.buildtools.utils.SharedConstants.GRAALVM_EXE_EXTENSION;

public class NativeImageUtils {
public static void maybeCreateConfigureUtilSymlink(File configureUtilFile, Path nativeImageExecutablePath) {
if (!configureUtilFile.exists()) {
// possibly the symlink is missing
Path target = configureUtilFile.toPath();
Path source = nativeImageExecutablePath.getParent().getParent().resolve("lib/svm/bin/" + nativeImageConfigureFileName());
if (Files.exists(source)) {
try {
Files.createLink(target, source);
} catch (IOException e) {
// ignore as this is handled by consumers
}
}
}
}

public static String nativeImageConfigureFileName() {
return "native-image-configure" + GRAALVM_EXE_EXTENSION;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,5 @@ public interface SharedConstants {
"META-INF/INDEX.LIST",
".*/package.html"
));
String AGENT_SESSION_SUBDIR = "session-{pid}-{datetime}";
}
2 changes: 2 additions & 0 deletions docs/src/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@ If you are interested in contributing, please refer to our https://github.com/gr

* Native testing support can now be explicitly disabled via `skipNativeTests`.
- See <<maven-plugin.adoc#testing-support-disabling, Disabling testing support>> for details.
* Fixed race condition which prevented the agent files to be generated properly if tests were executed concurrently
* Documented version compatibility for the JUnit Platform and Maven Surefire plugin.
- See <<maven-plugin.adoc#testing-support-version-compatibility, Version compatibility>> for details.

==== Gradle plugin

* Fixed `nativeRun` not working properly under Windows
* Fixed race condition which prevented the agent files to be generated properly if tests were executed concurrently

=== Release 0.9.9

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*
* Copyright (c) 2020, 2022 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.dsl.NativeImageOptions;
import org.graalvm.buildtools.gradle.internal.GraalVMLogger;
import org.graalvm.buildtools.utils.NativeImageUtils;
import org.gradle.api.Action;
import org.gradle.api.Task;
import org.gradle.api.file.Directory;
import org.gradle.api.file.FileSystemOperations;
import org.gradle.api.logging.Logger;
import org.gradle.api.provider.Provider;
import org.gradle.process.ExecOperations;
import org.gradle.process.ExecResult;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

import static org.graalvm.buildtools.gradle.internal.NativeImageExecutableLocator.findNativeImageExecutable;
import static org.graalvm.buildtools.utils.NativeImageUtils.nativeImageConfigureFileName;

class MergeAgentFiles implements Action<Task> {
private final Provider<Boolean> agent;
private final Provider<String> graalvmHomeProvider;
private final Provider<Directory> outputDir;
private final Provider<Boolean> disableToolchainDetection;
private final ExecOperations execOperations;
private final NativeImageOptions options;
private final FileSystemOperations fileOperations;
private final Logger logger;

MergeAgentFiles(Provider<Boolean> agent,
Provider<String> graalvmHomeProvider,
Provider<Directory> outputDir,
Provider<Boolean> disableToolchainDetection,
NativeImageOptions options,
ExecOperations execOperations,
FileSystemOperations fileOperations,
Logger logger) {
this.agent = agent;
this.graalvmHomeProvider = graalvmHomeProvider;
this.outputDir = outputDir;
this.disableToolchainDetection = disableToolchainDetection;
this.options = options;
this.execOperations = execOperations;
this.fileOperations = fileOperations;
this.logger = logger;
}

@Override
public void execute(Task task) {
if (agent.get()) {
File nativeImage = findNativeImageExecutable(options, disableToolchainDetection, graalvmHomeProvider, execOperations, GraalVMLogger.of(logger));
File workingDir = nativeImage.getParentFile();
File launcher = new File(workingDir, nativeImageConfigureFileName());
if (!launcher.exists()) {
logger.info("Installing native-image-configure");
execOperations.exec(spec -> {
spec.executable(nativeImage);
spec.args("--macro:native-image-configure-launcher");
});
NativeImageUtils.maybeCreateConfigureUtilSymlink(launcher, nativeImage.toPath());
}
if (launcher.exists()) {
File[] files = outputDir.get().getAsFile().listFiles();
List<String> args = new ArrayList<>(files.length + 2);
args.add("generate");
sessionDirectoriesFrom(files)
.map(f -> "--input-dir=" + f.getAbsolutePath())
.forEach(args::add);
if (args.size() > 1) {
logger.info("Merging agent files");
args.add("--output-dir=" + outputDir.get().getAsFile().getAbsolutePath());
ExecResult exec = execOperations.exec(spec -> {
spec.executable(launcher);
spec.args(args);
spec.setStandardOutput(System.out);
spec.setErrorOutput(System.err);
});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO we should migrate logic that is currently located in ProcessGeneratedGraalResourceFiles to this native-image-configure invocation.
So something like:

    args.add("--exclude-classes=java.**");
    args.add("--exclude-classes=org.gradle.**");

(we should also probably make that configurable somehow).

That would be useful as a workaround for oracle/graal#3968 that is one of blockers for Mockito support (cc @raphw):

    args.add("--exclude-classes=net.bytebuddy.utility.Invoker$Dispatcher");

We should then probably change MergeAgentFiles to something like ProcessAgentFiles.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not convinced we should merge them. The "merge" logic can be seen as an internal implementation detail of the agent implementation: instead of outputting n files, it should output a single file. Therefore, this logic needs to be invoked as part of the task execution itself, so that, for example, we don't mix files from different invocations.

The "process agent files" task, on the other hand, is, as its name states, a post processing task which, yes, could be made more configurable, but I don't think we should collate both aspects.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds reasonable. I have no strong opinions about this, so exact steps in my proposal aren't set in stone. My logic was that we might want to do it in a single native-image-configure invocation so it sounded like a job for a single task.

I however believe that we should (somehow) utilize native-image-configure to filter out Gradle entries, and add a way for end user to append values into getFilterableEntries.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You seem to imply that native-image-configure can be used to filter entries. Is this documented somewhere?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$ native-image-configure --help

contains the following argument:

    --exclude-classes=<class-pattern>
                          adds a single rule to exclude classes matching the
                          specified pattern, potentially overriding rules from
                          preceding parameters. Can be specified several times.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Let's start with what we have now and we can always change the implementation if needed, I'm not too worried.

if (exec.getExitValue() == 0) {
fileOperations.delete(spec -> sessionDirectoriesFrom(files).forEach(spec::delete));
} else {
exec.rethrowFailure();
}
}
} else {
logger.warn("Cannot merge agent files because native-image-configure is not installed. Please upgrade to a newer version of GraalVM.");
}
}
}

private Stream<File> sessionDirectoriesFrom(File[] files) {
return Arrays.stream(files)
.filter(File::isDirectory)
.filter(f -> f.getName().startsWith("session-"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
import org.gradle.api.file.DuplicatesStrategy;
import org.gradle.api.file.FileCollection;
import org.gradle.api.file.FileSystemLocation;
import org.gradle.api.file.FileSystemOperations;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.plugins.ApplicationPlugin;
import org.gradle.api.plugins.JavaApplication;
Expand All @@ -95,6 +96,7 @@
import org.gradle.api.tasks.testing.Test;
import org.gradle.jvm.toolchain.JavaToolchainService;
import org.gradle.language.base.plugins.LifecycleBasePlugin;
import org.gradle.process.ExecOperations;
import org.gradle.process.CommandLineArgumentProvider;
import org.gradle.process.JavaForkOptions;
import org.gradle.util.GFileUtils;
Expand All @@ -110,6 +112,7 @@
import java.util.stream.Collectors;

import static org.graalvm.buildtools.gradle.internal.GradleUtils.transitiveProjectArtifacts;
import static org.graalvm.buildtools.gradle.internal.NativeImageExecutableLocator.graalvmHomeProvider;
import static org.graalvm.buildtools.utils.SharedConstants.AGENT_OUTPUT_FOLDER;
import static org.graalvm.buildtools.utils.SharedConstants.AGENT_PROPERTY;

Expand Down Expand Up @@ -151,6 +154,17 @@ public ArchiveOperations getArchiveOperations() {
throw new UnsupportedOperationException();
}

@Inject
public ExecOperations getExecOperations() {
throw new UnsupportedOperationException();
}

@Inject
public FileSystemOperations getFileOperations() {
throw new UnsupportedOperationException();
}


@Override
public void apply(Project project) {
Provider<NativeImageService> nativeImageServiceProvider = NativeImageService.registerOn(project);
Expand All @@ -164,7 +178,7 @@ public void apply(Project project) {
graalExtension.getBinaries().all(options -> {
AgentConfiguration agentConfiguration = options.getAgent();
if (agentConfiguration.getInstrumentedTask().isPresent()) {
configureAgent(p, agents, options);
configureAgent(p, agents, graalExtension.getToolchainDetection().map(b -> !b), options, getExecOperations(), getFileOperations());
}
});
});
Expand Down Expand Up @@ -218,7 +232,7 @@ private void configureJavaProject(Project project, Provider<NativeImageService>
config.forTestTask(tasks.named("test", Test.class));
config.usingSourceSet(GradleUtils.findSourceSet(project, SourceSet.TEST_SOURCE_SET_NAME));
});
}
}

private void configureAutomaticTaskCreation(Project project,
GraalVMExtension graalExtension,
Expand Down Expand Up @@ -342,8 +356,8 @@ private TaskProvider<GenerateResourcesConfigFile> registerResourcesConfigTask(Pr
}

public void registerTestBinary(Project project,
DefaultGraalVmExtension graalExtension,
DefaultTestBinaryConfig config) {
DefaultGraalVmExtension graalExtension,
DefaultTestBinaryConfig config) {
NativeImageOptions mainOptions = graalExtension.getBinaries().getByName("main");
String name = config.getName();
boolean isPrimaryTest = "test".equals(name);
Expand Down Expand Up @@ -506,7 +520,10 @@ private static NativeImageOptions createTestOptions(GraalVMExtension graalExtens

private static void configureAgent(Project project,
Map<String, Provider<Boolean>> agents,
NativeImageOptions nativeImageOptions) {
Provider<Boolean> disableToolchainDetection,
NativeImageOptions nativeImageOptions,
ExecOperations execOperations,
FileSystemOperations fileOperations) {
String postProcessTaskName = PROCESS_AGENT_RESOURCES_TASK_NAME_PREFIX + capitalize(nativeImageOptions.getName()) + PROCESS_AGENT_RESOURCES_TASK_NAME_SUFFIX;
TaskProvider<ProcessGeneratedGraalResourceFiles> postProcessingTask = registerProcessAgentFilesTask(project, postProcessTaskName);
TaskProvider<? extends JavaForkOptions> instrumentedTask = nativeImageOptions.getAgent().getInstrumentedTask().get();
Expand All @@ -517,6 +534,15 @@ private static void configureAgent(Project project,
cliProvider.getOutputDirectory().set(outputDir);
cliProvider.getAgentOptions().set(nativeImageOptions.getAgent().getOptions());
instrumentedTask.get().getJvmArgumentProviders().add(cliProvider);
instrumentedTask.configure(task -> task.doLast(new MergeAgentFiles(
agent,
graalvmHomeProvider(project.getProviders()),
outputDir,
disableToolchainDetection,
nativeImageOptions,
execOperations,
fileOperations,
project.getLogger())));
// 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())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@

package org.graalvm.buildtools.gradle.internal;

import org.graalvm.buildtools.utils.SharedConstants;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.Property;
Expand Down Expand Up @@ -82,7 +83,7 @@ public Iterable<String> asArguments() {
if (agentOptions.stream().map(s -> s.split("=")[0]).anyMatch(s -> s.contains("config-output-dir"))) {
throw new IllegalStateException("config-output-dir cannot be supplied as an agent option");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at #189, in context of this PR, it seems that native-image-configure approach will break #190 (since config-merge-dir now has absolutely no meaning).

Not a blocker for merging this PR, but something to think about. We might want to add wrapper Gradle/Maven agent properties for following agentlib parameters:

  • trace-output
  • config-output-dir
  • config-merge-dir

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea with #190 is now to have an enum which tells what we want to generate: trace output or config output. I'm not sure what config merge dir is used for. In any case, this should probably not be mixed into this PR.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From docs:

It can be necessary to run the target application more than once with different inputs to trigger separate execution paths for a better coverage of dynamic accesses. The agent supports this with the config-merge-dir option which adds the intercepted accesses to an existing set of configuration files:

$JAVA_HOME/bin/java -agentlib:native-image-agent=config-merge-dir=/path/to/config-dir/ ...
                                                              ^^^^^

If the specified target directory or configuration files in it are missing when using config-merge-dir, the agent creates them and prints a warning.

Basically we would need to reproduce this behavior using native-image-configure invocation.

I agree that this should not be mixed with this PR, but it is something to keep in mind when designing tasks related to native-image-configure invocation.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, in combination with this race condition this starts to become bind blowing.

}
agentOptions.add("config-output-dir=" + outputDir.getAbsolutePath());
agentOptions.add("config-output-dir=" + outputDir.getAbsolutePath() + File.separator + SharedConstants.AGENT_SESSION_SUBDIR);
return Arrays.asList(
"-agentlib:native-image-agent=" + String.join(",", agentOptions),
"-Dorg.graalvm.nativeimage.imagecode=agent"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ public void lifecycle(String s) {
delegate.lifecycle("[native-image-plugin] {}", s);
}

public void lifecycle(String pattern, Object... args) {
delegate.lifecycle("[native-image-plugin] " + pattern, args);
}

public void error(String s) {
delegate.error("[native-image-plugin] {}", s);
}
Expand Down
Loading