Skip to content

Commit

Permalink
Add a check for the minimal GraalVM version (#353)
Browse files Browse the repository at this point in the history
This commit adds a new Gradle and Maven requiredVersion configuration
option that specifies the minimal GraalVM version and can be set to
MAJOR, MAJOR.MINOR or MAJOR.MINOR.PATCH.

Closes gh-346
  • Loading branch information
sdeleuze committed Oct 31, 2022
1 parent d2ff7e2 commit c4762a7
Show file tree
Hide file tree
Showing 12 changed files with 349 additions and 4 deletions.
Expand Up @@ -49,11 +49,18 @@
import java.nio.file.StandardOpenOption;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

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

public class NativeImageUtils {

private static final Pattern requiredVersionPattern = Pattern.compile("^([0-9]+)(?:\\.([0-9]+)?)?(?:\\.([0-9]+)?)?$");

private static final Pattern graalvmVersionPattern = Pattern.compile("^GraalVM ([0-9]+)\\.([0-9]+)\\.([0-9]+).*");

public static void maybeCreateConfigureUtilSymlink(File configureUtilFile, Path nativeImageExecutablePath) {
if (!configureUtilFile.exists()) {
// possibly the symlink is missing
Expand Down Expand Up @@ -102,4 +109,44 @@ public static String escapeArg(String arg) {
}
return arg;
}

/**
*
* @param requiredVersion Required version can be {@code MAJOR}, {@code MAJOR.MINOR} or {@code MAJOR.MINOR.PATCH}
* @param versionToCheck The version to check, as returned by {@code native-image --version}
* @throws IllegalStateException when the version is not correct
*/
public static void checkVersion(String requiredVersion, String versionToCheck) {
Matcher requiredMatcher = requiredVersionPattern.matcher(requiredVersion);
if (!requiredMatcher.matches()) {
throw new IllegalArgumentException("Invalid version " + requiredVersion + ", should be for example \"22\", \"22.3\" or \"22.3.0\".");
}
Matcher checkedMatcher = graalvmVersionPattern.matcher(versionToCheck);
if (!checkedMatcher.matches()) {
throw new IllegalArgumentException("Version to check '" + versionToCheck + "' can't be parsed.");
}
int requiredMajor = Integer.parseInt(requiredMatcher.group(1));
int checkedMajor = Integer.parseInt(checkedMatcher.group(1));
if (checkedMajor < requiredMajor) {
throw new IllegalStateException("GraalVM version " + requiredMajor + " is required but " + checkedMajor +
" has been detected, please upgrade.");
}
if (requiredMatcher.group(2) != null) {
int requiredMinor = Integer.parseInt(requiredMatcher.group(2));
int checkedMinor = Integer.parseInt(checkedMatcher.group(2));
if (checkedMinor < requiredMinor) {
throw new IllegalStateException("GraalVM version " + requiredMajor + "." + requiredMinor +
" is required but " + checkedMajor + "." + checkedMinor + " has been detected, please upgrade.");
}
if (requiredMatcher.group(3) != null) {
int requiredPatch = Integer.parseInt(requiredMatcher.group(3));
int checkedPatch = Integer.parseInt(checkedMatcher.group(3));
if (checkedPatch < requiredPatch) {
throw new IllegalStateException("GraalVM version " + requiredMajor + "." + requiredMinor + "." +
requiredPatch + " is required but " + checkedMajor + "." + checkedMinor + "." + checkedPatch +
" has been detected, please upgrade.");
}
}
}
}
}
@@ -0,0 +1,105 @@
/*
* Copyright (c) 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 org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

class NativeImageUtilsTest {

@Test
void invalidVersionToCheck() {
Assertions.assertThrows(IllegalArgumentException.class, () ->
NativeImageUtils.checkVersion("22.3", "invalid"));
Assertions.assertThrows(IllegalArgumentException.class, () ->
NativeImageUtils.checkVersion("22.3", "GraalVM"));
}

@Test
void invalidRequiredVersion() {
Assertions.assertThrows(IllegalArgumentException.class, () ->
NativeImageUtils.checkVersion("invalid", "GraalVM 22.3.0"));
Assertions.assertThrows(IllegalArgumentException.class, () ->
NativeImageUtils.checkVersion("22.3.0-dev", "GraalVM 22.3.0"));
}

@Test
void checkGraalVMCEVersion() {
NativeImageUtils.checkVersion("22", "GraalVM 22.3.0 Java 17 CE (Java Version 17.0.5+8-jvmci-22.3-b08)");
NativeImageUtils.checkVersion("22.3", "GraalVM 22.3.0 Java 17 CE (Java Version 17.0.5+8-jvmci-22.3-b08)");
NativeImageUtils.checkVersion("22.3.0", "GraalVM 22.3.0 Java 17 CE (Java Version 17.0.5+8-jvmci-22.3-b08)");
}

@Test
void checkGraalVMCEDevVersion() {
NativeImageUtils.checkVersion("22", "GraalVM 22.3.0-dev Java 17 CE (Java Version 17.0.5+8-LTS)");
NativeImageUtils.checkVersion("22.3", "GraalVM 22.3.0-dev Java 17 CE (Java Version 17.0.5+8-LTS)");
NativeImageUtils.checkVersion("22.3.0", "GraalVM 22.3.0-dev Java 17 CE (Java Version 17.0.5+8-LTS)");
}

@Test
void checkGraalVMEEVersion() {
NativeImageUtils.checkVersion("22", "GraalVM 22.3.0 Java 17 EE (Java Version 17.0.5+9-LTS-jvmci-22.3-b07)");
NativeImageUtils.checkVersion("22.3", "GraalVM 22.3.0 Java 17 EE (Java Version 17.0.5+9-LTS-jvmci-22.3-b07)");
NativeImageUtils.checkVersion("22.3.0", "GraalVM 22.3.0 Java 17 EE (Java Version 17.0.5+9-LTS-jvmci-22.3-b07)");
}

@Test
void checkGreaterVersion() {
NativeImageUtils.checkVersion("22", "GraalVM 23.2.1");
NativeImageUtils.checkVersion("23.1", "GraalVM 23.2.1");
NativeImageUtils.checkVersion("23.2.0", "GraalVM 23.2.1");
}

@Test
void checkLowerVersion() {
Assertions.assertThrows(IllegalStateException.class, () ->
NativeImageUtils.checkVersion("23", "GraalVM 22.2.1")
);
Assertions.assertThrows(IllegalStateException.class, () ->
NativeImageUtils.checkVersion("22.3", "GraalVM 22.2.1")
);
Assertions.assertThrows(IllegalStateException.class, () ->
NativeImageUtils.checkVersion("22.2.2", "GraalVM 22.2.1")
);
}

}
5 changes: 5 additions & 0 deletions docs/src/docs/asciidoc/index.adoc
Expand Up @@ -19,6 +19,11 @@ If you are using alternative build systems, see <<alternative-build-systems.adoc
[[changelog]]
== Changelog

=== Release 0.9.17

* Add a `requiredVersion` property to check the minimal GraalVM version
* Make GraalVM installation check lazy

=== Release 0.9.16

* Fixed regression with a reachability-metadata repository
Expand Down
7 changes: 7 additions & 0 deletions docs/src/docs/asciidoc/maven-plugin.adoc
Expand Up @@ -252,6 +252,13 @@ For example, to build a native image named `executable-name` that uses
</configuration>
----

`<requiredVersion>`::
If you want to define the minimum GraalVM version, can be `MAJOR`, `MAJOR.MINOR` or `MAJOR.MINOR.PATCH`:
[source,xml]
----
<requiredVersion>22.3</requiredVersion>
----

NOTE: Most of the aforementioned properties can also be set from command line as a part of Maven invocation -- for example if you want to temporarily enable verbose mode you can append `-Dverbose` to your Maven invocation.

NOTE: If you use GraalVM Enterprise as the `JAVA_HOME` environment, the plugin builds a native image with enterprise features enabled -- for example, an executable will automatically be built with https://medium.com/graalvm/isolates-and-compressed-references-more-flexible-and-efficient-memory-management-for-graalvm-a044cc50b67e[compressed references] and other optimizations enabled.
Expand Down
1 change: 1 addition & 0 deletions docs/src/docs/snippets/gradle/groovy/build.gradle
Expand Up @@ -121,6 +121,7 @@ graalvmNative {
sharedLibrary = false // Determines if image is a shared library, defaults to false if `java-library` plugin isn't included
quickBuild = false // Determines if image is being built in quick build mode (alternatively use GRAALVM_QUICK_BUILD environment variable, or add --native-quick-build to the CLI)
richOutput = false // Determines if native-image building should be done with rich output
requiredVersion = '22.3' // The minimal GraalVM version, can be `MAJOR`, `MAJOR.MINOR` or `MAJOR.MINOR.PATCH`

systemProperties = [name1: 'value1', name2: 'value2'] // Sets the system properties to use for the native image builder
configurationFileDirectories.from(file('src/my-config')) // Adds a native image configuration file directory, containing files like reflection configuration
Expand Down
1 change: 1 addition & 0 deletions docs/src/docs/snippets/gradle/kotlin/build.gradle.kts
Expand Up @@ -122,6 +122,7 @@ graalvmNative {
sharedLibrary.set(false) // Determines if image is a shared library, defaults to false if `java-library` plugin isn't included
quickBuild.set(false) // Determines if image is being built in quick build mode (alternatively use GRAALVM_QUICK_BUILD environment variable, or add --native-quick-build to the CLI)
richOutput.set(false) // Determines if native-image building should be done with rich output
requiredVersion.set('22.3') // The minimal GraalVM version, can be `MAJOR`, `MAJOR.MINOR` or `MAJOR.MINOR.PATCH`

systemProperties.putAll(mapOf("name1" to "value1", "name2" to "value2")) // Sets the system properties to use for the native image builder
configurationFileDirectories.from(file("src/my-config")) // Adds a native image configuration file directory, containing files like reflection configuration
Expand Down
@@ -0,0 +1,48 @@
package org.graalvm.buildtools.gradle;

import org.graalvm.buildtools.gradle.fixtures.AbstractFunctionalTest;

class RequiredVersionTest extends AbstractFunctionalTest {

def "can build a native image with a valid required version"() {
def nativeApp = file("build/native/nativeCompile/java-application")

given:
withSample("java-application")

buildFile << """
graalvmNative.binaries.all {
requiredVersion = '22.2'
}
""".stripIndent()

when:
run 'nativeCompile'

then:
tasks {
succeeded ':jar', ':nativeCompile'
doesNotContain ':build', ':run'
}

and:
nativeApp.exists()
}

def "can't build a native image with an invalid required version"() {
given:
withSample("java-application")

buildFile << """
graalvmNative.binaries.all {
requiredVersion = '100'
}
""".stripIndent()

when:
fails 'nativeCompile'

then:
errorOutputContains "GraalVM version 100 is required but 22 has been detected, please upgrade."
}
}
Expand Up @@ -314,4 +314,13 @@ public interface NativeImageOptions extends Named {

@Input
ListProperty<String> getExcludeConfigArgs();

/**
* Specify the minimal GraalVM version, can be {@code MAJOR}, {@code MAJOR.MINOR} or {@code MAJOR.MINOR.PATCH}.
*
* @return the required version property.
*/
@Input
@Optional
Property<String> getRequiredVersion();
}
Expand Up @@ -41,11 +41,19 @@

package org.graalvm.buildtools.gradle.tasks;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.List;

import javax.inject.Inject;

import org.graalvm.buildtools.gradle.NativeImagePlugin;
import org.graalvm.buildtools.gradle.dsl.NativeImageOptions;
import org.graalvm.buildtools.gradle.internal.GraalVMLogger;
import org.graalvm.buildtools.gradle.internal.NativeImageCommandLineProvider;
import org.graalvm.buildtools.gradle.internal.NativeImageExecutableLocator;
import org.graalvm.buildtools.utils.NativeImageUtils;
import org.gradle.api.DefaultTask;
import org.gradle.api.file.Directory;
import org.gradle.api.file.DirectoryProperty;
Expand All @@ -67,10 +75,7 @@
import org.gradle.api.tasks.TaskAction;
import org.gradle.api.tasks.options.Option;
import org.gradle.process.ExecOperations;

import javax.inject.Inject;
import java.io.File;
import java.util.List;
import org.gradle.process.ExecResult;

import static org.graalvm.buildtools.gradle.internal.NativeImageExecutableLocator.graalvmHomeProvider;
import static org.graalvm.buildtools.utils.SharedConstants.EXECUTABLE_EXTENSION;
Expand Down Expand Up @@ -195,6 +200,7 @@ public void exec() {
getExecOperations(),
logger,
diagnostics);
checkRequiredVersionIfNeeded(executablePath, options);
for (String diagnostic : diagnostics.getDiagnostics()) {
logger.lifecycle(diagnostic);
}
Expand All @@ -220,4 +226,19 @@ public void exec() {
}
}

private void checkRequiredVersionIfNeeded(File executablePath, NativeImageOptions options) {
if (!options.getRequiredVersion().isPresent()) {
return;
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ExecResult execResult = getExecOperations().exec(spec -> {
spec.setStandardOutput(outputStream);
spec.args("--version");
spec.setExecutable(executablePath.getAbsolutePath());
});
execResult.assertNormalExitValue();
String versionToCheck = new String(outputStream.toByteArray(), StandardCharsets.UTF_8).replace("\n", "");
NativeImageUtils.checkVersion(options.getRequiredVersion().get(), versionToCheck);
}

}

0 comments on commit c4762a7

Please sign in to comment.