Skip to content

Commit

Permalink
Maven plugin fixes (#274)
Browse files Browse the repository at this point in the history
  • Loading branch information
lazar-mitrovic committed Aug 4, 2022
2 parents 446897b + 0bdea33 commit 45a516e
Show file tree
Hide file tree
Showing 17 changed files with 201 additions and 42 deletions.
Expand Up @@ -52,8 +52,10 @@
import java.nio.file.StandardCopyOption;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

Expand Down
Expand Up @@ -45,6 +45,7 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Collections;
import java.util.List;
Expand Down Expand Up @@ -72,15 +73,24 @@ public static String nativeImageConfigureFileName() {
return "native-image-configure" + GRAALVM_EXE_EXTENSION;
}

public static List<String> convertToArgsFile(List<String> cliArgs) {
public static List<String> convertToArgsFile(List<String> cliArgs, Path outputDir) {
return convertToArgsFile(cliArgs, outputDir, Paths.get(""));
}

public static List<String> convertToArgsFile(List<String> cliArgs, Path outputDir, Path projectDir) {
try {
File tmpFile = Files.createTempFile("native-image", "args").toFile();
tmpFile.deleteOnExit();
boolean ignored = outputDir.toFile().mkdirs();
File tmpFile = Files.createTempFile(outputDir, "native-image-", ".args").toFile();
// tmpFile.deleteOnExit();
cliArgs = cliArgs.stream().map(NativeImageUtils::escapeArg).collect(Collectors.toList());
Files.write(tmpFile.toPath(), cliArgs, StandardCharsets.UTF_8, StandardOpenOption.CREATE);
return Collections.singletonList("@" + tmpFile.getAbsolutePath());
} catch (IOException e) {

Path resultingPath = tmpFile.toPath().toAbsolutePath();
if (projectDir != null) { // We know where the project dir is, so want to use relative paths
resultingPath = projectDir.toAbsolutePath().relativize(resultingPath);
}
return Collections.singletonList("@" + resultingPath);
} catch (IOException e) {
return Collections.unmodifiableList(cliArgs);
}
}
Expand Down
15 changes: 12 additions & 3 deletions docs/src/docs/asciidoc/index.adoc
Expand Up @@ -16,17 +16,26 @@ If you are using alternative build systems, see <<alternative-build-systems.adoc

[[changelog]]
== Changelog

=== Release 0.9.14

=== Gradle plugin
==== Gradle plugin
* Add ability to set environment variables to the native image builder process
* Argument files are now stored in the `build` directory (workaround for absolute path issue on Windows with older GraalVM versions)

==== Maven plugin
* Added `native:compile` forking goal that can be started from the command line as `mvn native:compile`.
* Argument files are now stored in the `target` directory (workaround for absolute path issue on Windows with older GraalVM versions).
* Default and test outputs are now much less noisy.
* When running tests in JVM mode with the native-image-agent, GraalVM's `java` executable is now always used.
* Maven plugin now shouldn't require that JVM running it must be GraalVM.

=== Release 0.9.13

=== Gradle plugin
==== Gradle plugin
* Reverted a change in the `NativeImagePlugin` that removed publicly accessible constants. This should prevent breakage of external plugins.

=== JUnit testing support
==== JUnit testing support
* Adapted the JUnit automatic metadata registration to changes in annotation handling on newer native image versions.

=== Release 0.9.12
Expand Down
2 changes: 2 additions & 0 deletions docs/src/docs/asciidoc/maven-plugin.adoc
Expand Up @@ -70,6 +70,8 @@ If the heuristics fail with the `no main manifest attribute, in target/<name>.ja
specified in the `<configuration>` node of the plugin.
When `mvn -Pnative package` completes, an executable is ready for use, generated in the `target` directory of the project.

NOTE: Building native image without attaching to the `package` phase can be done by invoking the forking `compile` goal directly, e.g. `mvn native:compile`

[TIP]
.Testing pre-releases
====
Expand Down
Expand Up @@ -53,6 +53,8 @@
import org.gradle.process.CommandLineArgumentProvider;

import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
Expand All @@ -64,16 +66,19 @@ public class NativeImageCommandLineProvider implements CommandLineArgumentProvid
private final Provider<NativeImageOptions> options;
private final Provider<String> executableName;
private final Provider<String> outputDirectory;
private final Provider<String> workingDirectory;
private final Provider<RegularFile> classpathJar;
private final Provider<Boolean> useArgFile;

public NativeImageCommandLineProvider(Provider<NativeImageOptions> options,
Provider<String> executableName,
Provider<String> workingDirectory,
Provider<String> outputDirectory,
Provider<RegularFile> classpathJar,
Provider<Boolean> useArgFile) {
this.options = options;
this.executableName = executableName;
this.workingDirectory = workingDirectory;
this.outputDirectory = outputDirectory;
this.classpathJar = classpathJar;
this.useArgFile = useArgFile;
Expand Down Expand Up @@ -142,7 +147,8 @@ public List<String> asArguments() {
}
cliArgs.addAll(options.getBuildArgs().get());
if (useArgFile.getOrElse(true)) {
return NativeImageUtils.convertToArgsFile(cliArgs);
Path argFileDir = Paths.get(workingDirectory.get());
return NativeImageUtils.convertToArgsFile(cliArgs, argFileDir, argFileDir);
}
return Collections.unmodifiableList(cliArgs);

Expand Down
Expand Up @@ -152,6 +152,7 @@ private List<String> buildActualCommandLineArgs() {
return new NativeImageCommandLineProvider(
getOptions(),
getExecutableShortName(),
getProviders().provider(() -> getWorkingDirectory().get().getAsFile().getAbsolutePath()),
// Can't use getOutputDirectory().map(...) because Gradle would complain that we use
// a mapped value before the task was called, when we are actually calling it...
getProviders().provider(() -> getOutputDirectory().getAsFile().get().getAbsolutePath()),
Expand Down
Expand Up @@ -90,12 +90,12 @@ protected void executeMaven() {
spec.setClasspath(getMavenEmbedderClasspath());
spec.getMainClass().set("org.apache.maven.cli.MavenCli");
spec.systemProperty("maven.multiModuleProjectDirectory", projectdir.getAbsolutePath());
spec.systemProperty("org.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener", "warn");
prepareSpec(spec);
List<String> arguments = new ArrayList<>();
arguments.addAll(Arrays.asList(
"--errors",
"-U",
"-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn",
"--batch-mode",
"--settings", settingsFile.getAbsolutePath(),
"--file", pomFile.getAbsolutePath()
Expand Down
3 changes: 2 additions & 1 deletion native-maven-plugin/build.gradle.kts
Expand Up @@ -63,6 +63,7 @@ dependencies {
implementation(libs.utils)
implementation(libs.jackson.databind)
implementation(libs.jvmReachabilityMetadata)
implementation(libs.graalvm.svm)

compileOnly(libs.maven.pluginApi)
compileOnly(libs.maven.core)
Expand Down Expand Up @@ -173,5 +174,5 @@ tasks {
}

tasks.withType<Checkstyle>().configureEach {
setConfigFile(layout.projectDirectory.dir("../config/checkstyle.xml").asFile)
configFile = layout.projectDirectory.dir("../config/checkstyle.xml").asFile
}
Expand Up @@ -100,7 +100,7 @@ class JavaApplicationFunctionalTest extends AbstractGraalVMMavenFunctionalTest {
withSample("java-application-with-custom-packaging")
when:
mvn 'package', '-Dpackaging=native-image', 'exec:exec@native'
mvnDebug 'package', '-Dpackaging=native-image', 'exec:exec@native'
then:
buildSucceeded
Expand Down
Expand Up @@ -54,7 +54,7 @@ class JavaApplicationWithAgentFunctionalTest extends AbstractGraalVMMavenFunctio
when:
// Run Maven in debug mode (-X) in order to capture the command line arguments
// used to launch Surefire with the agent.
mvn '-X', '-Pnative', 'test', '-DskipNativeTests'
mvnDebug '-Pnative', 'test', '-DskipNativeTests'

then:
// Agent is used with Surefire
Expand Down Expand Up @@ -85,7 +85,7 @@ class JavaApplicationWithAgentFunctionalTest extends AbstractGraalVMMavenFunctio
when:
// Run Maven in debug mode (-X) in order to capture the command line arguments
// used to launch Surefire with the agent.
mvn '-X', '-Pnative', 'test'
mvnDebug '-Pnative', 'test'

then:
outputContains """
Expand Down Expand Up @@ -162,7 +162,7 @@ class JavaApplicationWithAgentFunctionalTest extends AbstractGraalVMMavenFunctio
when:
// Run Maven in debug mode (-X) in order to capture the command line arguments
// used to launch Surefire with the agent.
mvn '-X', '-Pnative', '-DagentOptions=periodic-config', 'test'
mvnDebug '-Pnative', '-DagentOptions=periodic-config', 'test'

then:
outputContains """
Expand Down Expand Up @@ -214,7 +214,7 @@ class JavaApplicationWithAgentFunctionalTest extends AbstractGraalVMMavenFunctio
withSample("java-application-with-reflection")

when:
mvn '-X', '-Pnative', '-DskipTests=true', '-DskipNativeBuild=true', 'package', 'exec:exec@java-agent'
mvnDebug '-Pnative', '-DskipTests=true', '-DskipNativeBuild=true', 'package', 'exec:exec@java-agent'

then:
['jni', 'proxy', 'reflect', 'resource', 'serialization'].each { name ->
Expand Down Expand Up @@ -246,7 +246,7 @@ class JavaApplicationWithAgentFunctionalTest extends AbstractGraalVMMavenFunctio
withSample("java-application-with-reflection")

when:
mvn '-X', '-Pnative', '-Dagent=false', '-DskipTests=true', '-DskipNativeBuild=true', 'package', 'exec:exec@java-agent'
mvnDebug '-Pnative', '-Dagent=false', '-DskipTests=true', '-DskipNativeBuild=true', 'package', 'exec:exec@java-agent'

then:
outputDoesNotContain '-agentlib:native-image-agent'
Expand All @@ -263,7 +263,7 @@ class JavaApplicationWithAgentFunctionalTest extends AbstractGraalVMMavenFunctio
withSample("java-application-with-reflection")

when:
mvn '-X', '-Pnative', '-DagentOptions=periodic-config', '-DskipTests=true', '-DskipNativeBuild=true', 'package', 'exec:exec@java-agent'
mvnDebug '-Pnative', '-DagentOptions=periodic-config', '-DskipTests=true', '-DskipNativeBuild=true', 'package', 'exec:exec@java-agent'

then:
['jni', 'proxy', 'reflect', 'resource', 'serialization'].each { name ->
Expand Down
Expand Up @@ -271,13 +271,20 @@ protected List<String> getBuildArgs() throws MojoExecutionException {
}

if (useArgFile) {
return NativeImageUtils.convertToArgsFile(cliArgs);
Path tmpDir = Paths.get("target", "tmp");
return NativeImageUtils.convertToArgsFile(cliArgs, tmpDir);
}
return Collections.unmodifiableList(cliArgs);
}

protected Path processArtifact(Artifact artifact, String artifactType) throws MojoExecutionException {
File artifactFile = artifact.getFile();

if (artifactFile == null) {
logger.debug("Missing artifact file for artifact " + artifact + " (type: " + artifact.getType() + ")");
return null;
}

if (!artifactType.equals(artifact.getType())) {
logger.warn("Ignoring non-jar type ImageClasspath Entry " + artifact);
return null;
Expand All @@ -288,7 +295,7 @@ protected Path processArtifact(Artifact artifact, String artifactType) throws Mo
}

Path jarFilePath = artifactFile.toPath();
logger.info("ImageClasspath Entry: " + artifact + " (" + jarFilePath.toUri() + ")");
logger.debug("ImageClasspath Entry: " + artifact + " (" + jarFilePath.toUri() + ")");

warnIfWrongMetaInfLayout(jarFilePath, artifact);
return jarFilePath;
Expand All @@ -300,7 +307,7 @@ protected void addArtifactToClasspath(Artifact artifact) throws MojoExecutionExc

protected void warnIfWrongMetaInfLayout(Path jarFilePath, Artifact artifact) throws MojoExecutionException {
if (jarFilePath.toFile().isDirectory()) {
logger.warn("Artifact `" + jarFilePath + "` is a directory.");
logger.debug("Artifact `" + jarFilePath + "` is a directory.");
return;
}
URI jarFileURI = URI.create("jar:" + jarFilePath.toUri());
Expand All @@ -316,8 +323,9 @@ protected void warnIfWrongMetaInfLayout(Path jarFilePath, Artifact artifact) thr
valid = valid && relativeSubDir.getName(0).toString().equals(artifact.getGroupId());
valid = valid && relativeSubDir.getName(1).toString().equals(artifact.getArtifactId());
if (!valid) {
String example = NATIVE_IMAGE_META_INF + "/${groupId}/${artifactId}/" + NATIVE_IMAGE_PROPERTIES_FILENAME;
logger.warn(nativeImageProperty.toUri() + " does not match recommended " + example + " layout.");
String example = NATIVE_IMAGE_META_INF + "/%s/%s/" + NATIVE_IMAGE_PROPERTIES_FILENAME;
example = String.format(example, artifact.getGroupId(), artifact.getArtifactId());
logger.warn("Properties file at '" + nativeImageProperty.toUri() + "' does not match the recommended '" + example + "' layout.");
}
}
}
Expand Down Expand Up @@ -399,7 +407,6 @@ protected void buildImage() throws MojoExecutionException {
if (!outputDirectory.exists() && !outputDirectory.mkdirs()) {
throw new MojoExecutionException("Failed creating output directory");
}
processBuilder.directory(outputDirectory);
processBuilder.inheritIO();

String commandString = String.join(" ", processBuilder.command());
Expand Down
@@ -0,0 +1,60 @@
/*
* 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.plugins.annotations.Execute;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.ResolutionScope;


/**
* This goal builds native images.
* It should be invoked from the command line as a single goal (`mvn native:compile`).
*/
@Mojo(name = "compile", defaultPhase = LifecyclePhase.PACKAGE,
requiresDependencyResolution = ResolutionScope.RUNTIME,
requiresDependencyCollection = ResolutionScope.RUNTIME)
@Execute(phase = LifecyclePhase.PACKAGE)
@SuppressWarnings("unused")
public class NativeCompileMojo extends NativeCompileNoForkMojo {
}
Expand Up @@ -58,10 +58,15 @@
import java.util.List;
import java.util.function.BiFunction;


/**
* This goal runs native builds. It functions the same as the native:compile goal, but it
* does not fork the build, so it is suitable for attaching to the build lifecycle.
*/
@Mojo(name = "build", defaultPhase = LifecyclePhase.PACKAGE,
requiresDependencyResolution = ResolutionScope.RUNTIME,
requiresDependencyCollection = ResolutionScope.RUNTIME)
public class NativeBuildMojo extends AbstractNativeMojo {
public class NativeCompileNoForkMojo extends AbstractNativeMojo {

@Parameter(property = "skipNativeBuild", defaultValue = "false")
private boolean skip;
Expand All @@ -79,7 +84,7 @@ protected List<String> getDependencyScopes() {
@Override
public void execute() throws MojoExecutionException {
if (skip) {
getLog().info("Skipping native-image generation (parameter 'skipNativeBuild' is true).");
logger.info("Skipping native-image generation (parameter 'skipNativeBuild' is true).");
return;
}

Expand Down Expand Up @@ -147,7 +152,7 @@ private void maybeSetMainClassFromPlugin(BiFunction<String, String[], String> ma
mainClass = mainClassProvider.apply(pluginName, nodeNames);

if (mainClass != null) {
getLog().info("Obtained main class from plugin " + pluginName + " with the following path: " + String.join(" -> ", nodeNames));
logger.info("Obtained main class from plugin " + pluginName + " with the following path: " + String.join(" -> ", nodeNames));
}
}
}
Expand Down

0 comments on commit 45a516e

Please sign in to comment.