Skip to content
Open
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
Expand Up @@ -44,6 +44,8 @@ package org.graalvm.buildtools.gradle
import org.graalvm.buildtools.gradle.fixtures.AbstractFunctionalTest
import org.graalvm.buildtools.gradle.fixtures.GraalVMSupport
import org.graalvm.buildtools.utils.NativeImageUtils
import spock.lang.Issue
import spock.lang.Requires
import spock.lang.Unroll

class JavaApplicationWithAgentFunctionalTest extends AbstractFunctionalTest {
Expand Down Expand Up @@ -200,6 +202,131 @@ class JavaApplicationWithAgentFunctionalTest extends AbstractFunctionalTest {
junitVersion = System.getProperty('versions.junit')
}

@Issue("https://github.com/graalvm/native-build-tools/issues/581")
@Requires({
System.getenv("GRAALVM_HOME") != null
})
@Unroll("agent uses GraalVM's Java executable for run task with JUnit Platform #junitVersion")
def "agent uses GraalVM java executable"() {
given:
withSample("java-application-with-reflection")
buildFile << """
tasks.named('run') {
def javaExecutableName = System.getProperty('os.name').contains('Windows') ? 'java.exe' : 'java'
executable = new File(new File(System.getProperty('java.home'), 'bin'), javaExecutableName).absolutePath
doLast {
def expectedExecutable = new File(new File(System.getenv('GRAALVM_HOME'), 'bin'), javaExecutableName).absolutePath
def actualExecutable = executable ?: javaLauncher.get().executablePath.asFile.absolutePath
assert actualExecutable == expectedExecutable
}
}
""".stripIndent()

when:
run 'run', '-Pagent=standard'

then:
tasks {
succeeded ':run'
doesNotContain ':jar'
}

where:
junitVersion = System.getProperty('versions.junit')
}

@Issue("https://github.com/graalvm/native-build-tools/issues/581")
@Requires({
System.getenv("GRAALVM_HOME") != null
})
@Unroll("agent keeps explicit test executable in sync with GraalVM java executable for JUnit Platform #junitVersion")
def "agent keeps explicit test executable in sync with GraalVM java executable"() {
given:
withSample("java-application-with-reflection")
buildFile << """
tasks.named('test') {
def javaExecutableName = System.getProperty('os.name').contains('Windows') ? 'java.exe' : 'java'
executable = new File(new File(System.getProperty('java.home'), 'bin'), javaExecutableName).absolutePath
doLast {
def expectedExecutable = new File(new File(System.getenv('GRAALVM_HOME'), 'bin'), javaExecutableName).absolutePath
assert executable == expectedExecutable
assert javaLauncher.get().executablePath.asFile.absolutePath == expectedExecutable
}
}
""".stripIndent()

when:
run 'test', '-Pagent=standard'

then:
tasks {
succeeded ':test'
}

where:
junitVersion = System.getProperty('versions.junit')
}

@Issue("https://github.com/graalvm/native-build-tools/issues/581")
@Requires({
System.getenv("GRAALVM_HOME") != null && !System.getProperty("os.name", "unknown").contains("Windows")
})
@Unroll("agent does not replace configured launcher with regular JAVA_HOME for JUnit Platform #junitVersion")
def "agent does not replace configured launcher with regular JAVA_HOME"() {
given:
withSample("java-application-with-reflection")
def graalvmHome = System.getenv("GRAALVM_HOME")
def fakeJavaHome = file("fake-java-home")
def fakeJava = new File(fakeJavaHome, "bin/java")
fakeJava.parentFile.mkdirs()
fakeJava.text = """#!/bin/sh
exec "${System.getProperty('java.home')}/bin/java" "\$@"
"""
fakeJava.setExecutable(true)
new File(fakeJavaHome, "release").text = """JAVA_VERSION="${System.getProperty('java.version')}"
IMPLEMENTOR="Regular JDK"
"""
withEnvironmentOverrides([
"GRAALVM_HOME": null,
"JAVA_HOME": fakeJavaHome.absolutePath,
"GRADLE_OPTS": null
])
file("gradle.properties") << """
org.gradle.java.installations.paths=${graalvmHome}
org.gradle.java.installations.auto-detect=false
org.gradle.java.installations.auto-download=false
""".stripIndent()
buildFile << """
tasks.named('run') {
javaLauncher.set(javaToolchains.launcherFor {
languageVersion.set(JavaLanguageVersion.of(${getCurrentJDKVersion()}))
})
}

tasks.register('verifyRunLauncher') {
doLast {
def actualExecutable = tasks.named('run').get().javaLauncher.get().executablePath.asFile.canonicalFile
def expectedExecutable = new File('${graalvmHome}', 'bin/java').canonicalFile
def javaHomeExecutable = new File(new File(System.getenv('JAVA_HOME')), 'bin/java').canonicalFile
assert actualExecutable == expectedExecutable
assert actualExecutable != javaHomeExecutable
}
}
""".stripIndent()

when:
run 'verifyRunLauncher', '-Pagent=standard'

then:
tasks {
succeeded ':verifyRunLauncher'
doesNotContain ':run'
}

where:
junitVersion = System.getProperty('versions.junit')
}

@Unroll("plugin supports configuration cache (JUnit Platform #junitVersion)")
def "supports configuration cache"() {
given:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.file.DuplicatesStrategy;
import org.gradle.api.file.FileCollection;
import org.gradle.api.file.FileTree;
import org.gradle.api.file.FileSystemLocation;
import org.gradle.api.file.FileSystemOperations;
import org.gradle.api.file.RegularFile;
Expand All @@ -111,10 +112,14 @@
import org.gradle.api.tasks.SourceSetContainer;
import org.gradle.api.tasks.TaskCollection;
import org.gradle.api.tasks.TaskContainer;
import org.gradle.api.tasks.JavaExec;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.api.tasks.bundling.AbstractArchiveTask;
import org.gradle.api.tasks.bundling.Jar;
import org.gradle.api.tasks.testing.Test;
import org.gradle.jvm.toolchain.JavaInstallationMetadata;
import org.gradle.jvm.toolchain.JavaLanguageVersion;
import org.gradle.jvm.toolchain.JavaLauncher;
import org.gradle.jvm.toolchain.JavaToolchainService;
import org.gradle.language.base.plugins.LifecycleBasePlugin;
import org.gradle.process.CommandLineArgumentProvider;
Expand All @@ -124,6 +129,7 @@
import javax.annotation.Nonnull;
import javax.inject.Inject;
import java.io.File;
import java.io.Serializable;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Path;
Expand All @@ -137,6 +143,7 @@
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
Expand Down Expand Up @@ -927,12 +934,29 @@ private void configureAgent(Project project,
Task taskToInstrument,
JavaForkOptions javaForkOptions) {
Provider<AgentConfiguration> agentConfiguration = AgentConfigurationFactory.getAgentConfiguration(agentMode, graalExtension.getAgent());
Provider<JavaLauncher> javaLauncherForAgent = javaLauncherForAgent(project.getProviders());
if (agentConfiguration.get().isEnabled()) {
JavaLauncher javaLauncher = javaLauncherForAgent.getOrNull();
if (javaLauncher != null) {
if (taskToInstrument instanceof Test test) {
test.getJavaLauncher().set(javaLauncher);
javaForkOptions.setExecutable(javaLauncher.getExecutablePath().getAsFile().getAbsolutePath());
} else if (taskToInstrument instanceof JavaExec javaExec) {
javaExec.getJavaLauncher().set(javaLauncher);
javaForkOptions.setExecutable(javaLauncher.getExecutablePath().getAsFile().getAbsolutePath());
}
}
}
//noinspection Convert2Lambda
taskToInstrument.doFirst(new Action<Task>() {
@Override
public void execute(@Nonnull Task task) {
if (agentConfiguration.get().isEnabled()) {
logger.logOnce("Instrumenting task with the native-image-agent: " + task.getName());
JavaLauncher javaLauncher = javaLauncherForAgent.getOrNull();
if (javaLauncher != null && !(task instanceof Test) && !(task instanceof JavaExec)) {
javaForkOptions.setExecutable(javaLauncher.getExecutablePath().getAsFile().getAbsolutePath());
}
}
}
});
Expand Down Expand Up @@ -965,6 +989,38 @@ public void execute(@Nonnull Task task) {
taskToInstrument.doLast(new CleanupAgentFilesAction(mergeInputDirs, fileOperations));
}

private static Provider<JavaLauncher> javaLauncherForAgent(ProviderFactory providers) {
return providers.environmentVariable("GRAALVM_HOME")
.map(serializableTransformerOf(graalHomePath -> javaLauncherForAgentHome(graalHomePath, false)))
.orElse(providers.environmentVariable("JAVA_HOME")
.map(serializableTransformerOf(javaHomePath -> javaLauncherForAgentHome(javaHomePath, true))));
}

private static JavaLauncher javaLauncherForAgentHome(String javaHomePath, boolean requireGraalVM) {
File javaHome = new File(javaHomePath);
if (requireGraalVM && !isGraalVMHome(javaHome)) {
return null;
}
File javaExecutableFile = new File(javaHome, IS_WINDOWS ? "bin\\java.exe" : "bin/java");
return javaExecutableFile.exists() ? new FixedJavaLauncher(javaHome, javaExecutableFile) : null;
}

private static boolean isGraalVMHome(File javaHome) {
File release = new File(javaHome, "release");
if (!release.isFile()) {
return false;
}
Properties properties = new Properties();
try (var inputStream = java.nio.file.Files.newInputStream(release.toPath())) {
properties.load(inputStream);
} catch (Exception ignored) {
return false;
}
return properties.stringPropertyNames().stream()
.anyMatch(propertyName -> propertyName.toUpperCase(Locale.ROOT).contains("GRAALVM")
|| String.valueOf(properties.getProperty(propertyName)).toLowerCase(Locale.ROOT).contains("graalvm"));
}

private static void injectTestPluginDependencies(Project project, String binaryName, Property<Boolean> testSupportEnabled) {
project.afterEvaluate(p -> {
if (testSupportEnabled.get()) {
Expand Down Expand Up @@ -1051,6 +1107,149 @@ public Iterable<String> asArguments() {
}
}

private static final class FixedJavaLauncher implements JavaLauncher, Serializable {
private final File javaExecutable;
private final FixedJavaInstallationMetadata metadata;

private FixedJavaLauncher(File javaHome, File javaExecutable) {
this.javaExecutable = javaExecutable;
this.metadata = new FixedJavaInstallationMetadata(javaHome);
}

@Override
public JavaInstallationMetadata getMetadata() {
return metadata;
}

@Override
public RegularFile getExecutablePath() {
return new FixedRegularFile(javaExecutable);
}
}

private static final class FixedJavaInstallationMetadata implements JavaInstallationMetadata, Serializable {
private final File javaHome;
private final String javaVersion;

private FixedJavaInstallationMetadata(File javaHome) {
this.javaHome = javaHome;
this.javaVersion = javaVersion(javaHome);
}

@Override
public JavaLanguageVersion getLanguageVersion() {
return JavaLanguageVersion.of(javaVersion.replaceFirst("^1\\.", "").replaceFirst("[^0-9].*", ""));
}

@Override
public String getJavaRuntimeVersion() {
return javaVersion;
}

@Override
public String getJvmVersion() {
return javaVersion;
}

@Override
public String getVendor() {
return "GraalVM";
}

@Override
public Directory getInstallationPath() {
return new FixedDirectory(javaHome);
}

@Override
public boolean isCurrentJvm() {
return false;
}

private static String javaVersion(File javaHome) {
File release = new File(javaHome, "release");
if (release.isFile()) {
Properties properties = new Properties();
try (var inputStream = java.nio.file.Files.newInputStream(release.toPath())) {
properties.load(inputStream);
String version = properties.getProperty("JAVA_VERSION");
if (version != null) {
return version.replace("\"", "");
}
} catch (Exception ignored) {
// Fall through to the JVM running Gradle.
}
}
return System.getProperty("java.version");
}
}

private static final class FixedRegularFile implements RegularFile, Serializable {
private final File file;

private FixedRegularFile(File file) {
this.file = file;
}

@Override
public File getAsFile() {
return file;
}

@Override
public String toString() {
return file.getAbsolutePath();
}
}

private static final class FixedDirectory implements Directory, Serializable {
private final File directory;

private FixedDirectory(File directory) {
this.directory = directory;
}

@Override
public File getAsFile() {
return directory;
}

@Override
public FileTree getAsFileTree() {
throw new UnsupportedOperationException();
}

@Override
public Directory dir(String path) {
return new FixedDirectory(new File(directory, path));
}

@Override
public Provider<Directory> dir(Provider<? extends CharSequence> path) {
return path.map(serializableTransformerOf(value -> dir(value.toString())));
}

@Override
public RegularFile file(String path) {
return new FixedRegularFile(new File(directory, path));
}

@Override
public Provider<RegularFile> file(Provider<? extends CharSequence> path) {
return path.map(serializableTransformerOf(value -> file(value.toString())));
}

@Override
public FileCollection files(Object... paths) {
throw new UnsupportedOperationException();
}

@Override
public String toString() {
return directory.getAbsolutePath();
}
}

private static final class ExcludeEntry {
private final String gav;
private final List<String> excludes;
Expand Down
Loading
Loading