Skip to content

Commit

Permalink
Add support for merging agent files in Maven
Browse files Browse the repository at this point in the history
Fixes #194
  • Loading branch information
melix committed Feb 3, 2022
1 parent 392e943 commit dc00048
Show file tree
Hide file tree
Showing 6 changed files with 232 additions and 11 deletions.
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}";
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@

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;
Expand All @@ -58,7 +59,7 @@
import java.util.stream.Stream;

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

class MergeAgentFiles implements Action<Task> {
private final Provider<Boolean> agent;
Expand Down Expand Up @@ -100,6 +101,7 @@ public void execute(Task task) {
spec.executable(nativeImage);
spec.args("--macro:native-image-configure-launcher");
});
NativeImageUtils.maybeCreateConfigureUtilSymlink(launcher, nativeImage.toPath());
}
if (launcher.exists()) {
File[] files = outputDir.get().getAsFile().listFiles();
Expand Down Expand Up @@ -129,10 +131,6 @@ public void execute(Task task) {
}
}

private String nativeImageConfigureFileName() {
return "native-image-configure" + GRAALVM_EXE_EXTENSION;
}

private Stream<File> sessionDirectoriesFrom(File[] files) {
return Arrays.stream(files)
.filter(File::isDirectory)
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 All @@ -58,8 +59,6 @@

public abstract class AgentCommandLineProvider implements CommandLineArgumentProvider {

public static final String SESSION_SUBDIR = "session-{pid}-{datetime}";

@Inject
@SuppressWarnings("checkstyle:redundantmodifier")
public AgentCommandLineProvider() {
Expand All @@ -84,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");
}
agentOptions.add("config-output-dir=" + outputDir.getAbsolutePath() + File.separator + SESSION_SUBDIR);
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
@@ -0,0 +1,147 @@
/*
* 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.maven;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.FileUtils;
import org.graalvm.buildtools.Utils;
import org.graalvm.buildtools.utils.NativeImageUtils;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.graalvm.buildtools.maven.NativeExtension.agentOutputDirectoryFor;
import static org.graalvm.buildtools.utils.NativeImageUtils.nativeImageConfigureFileName;

@Mojo(name = "merge-agent-files", defaultPhase = LifecyclePhase.TEST)
public class MergeAgentFilesMojo extends AbstractMojo {
@Parameter(defaultValue = "${project}", readonly = true, required = true)
protected MavenProject project;

@Parameter(defaultValue = "${project.build.directory}", readonly = true, required = true)
protected String target;

@Parameter(property = "native.agent.merge.context", required = true)
protected String context;

@Override
public void execute() throws MojoExecutionException, MojoFailureException {
String agentOutputDirectory = agentOutputDirectoryFor(target, NativeExtension.Context.valueOf(context));
File baseDir = new File(agentOutputDirectory);
Path nativeImageExecutable = Utils.getNativeImage();
File mergerExecutable = tryInstall(nativeImageExecutable);
List<File> sessionDirectories = sessionDirectoriesFrom(baseDir.listFiles()).collect(Collectors.toList());
invokeMerge(mergerExecutable, sessionDirectories, baseDir);
}

private File tryInstall(Path nativeImageExecutablePath) {
File nativeImageExecutable = nativeImageExecutablePath.toAbsolutePath().toFile();
File mergerExecutable = new File(nativeImageExecutable.getParentFile(), nativeImageConfigureFileName());
if (!mergerExecutable.exists()) {
getLog().info("Installing native image merger to " + mergerExecutable);
ProcessBuilder processBuilder = new ProcessBuilder(nativeImageExecutable.toString());
processBuilder.command().add("--macro:native-image-configure-launcher");
processBuilder.directory(mergerExecutable.getParentFile());
processBuilder.inheritIO();

try {
Process installProcess = processBuilder.start();
if (installProcess.waitFor() != 0) {
getLog().warn("Installation of native image merging tool failed");
}
NativeImageUtils.maybeCreateConfigureUtilSymlink(mergerExecutable, nativeImageExecutablePath);
} catch (IOException | InterruptedException e) {
// ignore since we will handle that if the installer doesn't exist later
}

}
return mergerExecutable;
}

private static Stream<File> sessionDirectoriesFrom(File[] files) {
return Arrays.stream(files)
.filter(File::isDirectory)
.filter(f -> f.getName().startsWith("session-"));
}

private void invokeMerge(File mergerExecutable, List<File> inputDirectories, File outputDirectory) throws MojoExecutionException {
if (!mergerExecutable.exists()) {
getLog().warn("Cannot merge agent files because native-image-configure is not installed. Please upgrade to a newer version of GraalVM.");
return;
}
try {
getLog().info("Merging agent " + inputDirectories.size() + " files into " + outputDirectory);
List<String> args = new ArrayList<>(inputDirectories.size() + 2);
args.add("generate");
inputDirectories.stream()
.map(f -> "--input-dir=" + f.getAbsolutePath())
.forEach(args::add);
args.add("--output-dir=" + outputDirectory.getAbsolutePath());
ProcessBuilder processBuilder = new ProcessBuilder(mergerExecutable.toString());
processBuilder.command().addAll(args);
processBuilder.inheritIO();

String commandString = String.join(" ", processBuilder.command());
Process imageBuildProcess = processBuilder.start();
if (imageBuildProcess.waitFor() != 0) {
throw new MojoExecutionException("Execution of " + commandString + " returned non-zero result");
}
for (File inputDirectory : inputDirectories) {
FileUtils.deleteDirectory(inputDirectory);
}
getLog().debug("Agent output: " + Arrays.toString(outputDirectory.listFiles()));
} catch (IOException | InterruptedException e) {
throw new MojoExecutionException("Merging agent files with " + mergerExecutable + " failed", e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.graalvm.buildtools.Utils;
import org.graalvm.buildtools.utils.SharedConstants;

import java.io.File;
import java.util.ArrayList;
Expand All @@ -77,19 +78,19 @@ public class NativeExtension extends AbstractMavenLifecycleParticipant {
* and within the Maven POM as values of the {@code name} attribute in
* {@code <options name="...">}.
*/
private enum Context { main, test };
enum Context { main, test };

static String testIdsDirectory(String baseDir) {
return baseDir + File.separator + "test-ids";
}

static String buildAgentArgument(String baseDir, Context context, List<String> agentOptions) {
List<String> options = new ArrayList<>(agentOptions);
options.add("config-output-dir=" + agentOutputDirectoryFor(baseDir, context));
options.add("config-output-dir=" + agentOutputDirectoryFor(baseDir, context) + File.separator + SharedConstants.AGENT_SESSION_SUBDIR);
return "-agentlib:native-image-agent=" + String.join(",", options);
}

private static String agentOutputDirectoryFor(String baseDir, Context context) {
static String agentOutputDirectoryFor(String baseDir, Context context) {
return (baseDir + "/native/agent-output/" + context).replace('/', File.separatorChar);
}

Expand Down Expand Up @@ -152,6 +153,12 @@ public void afterProjectsRead(MavenSession session) throws MavenExecutionExcepti
Context context = exec.getGoals().stream().anyMatch("test"::equals) ? Context.test : Context.main;
Xpp3Dom agentResourceDirectory = findOrAppend(configuration, "agentResourceDirectory");
agentResourceDirectory.setValue(agentOutputDirectoryFor(target, context));
List<String> goals = new ArrayList<>();
goals.add("merge-agent-files");
goals.addAll(exec.getGoals());
exec.setGoals(goals);
Xpp3Dom agentContext = findOrAppend(configuration, "context");
agentContext.setValue(context.name());
});
}
});
Expand Down

0 comments on commit dc00048

Please sign in to comment.