File tree

6 files changed

+298
-52
lines changed

6 files changed

+298
-52
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/*
2+
* Copyright (c) 2020, 2022 Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* The Universal Permissive License (UPL), Version 1.0
6+
*
7+
* Subject to the condition set forth below, permission is hereby granted to any
8+
* person obtaining a copy of this software, associated documentation and/or
9+
* data (collectively the "Software"), free of charge and under any and all
10+
* copyright rights in the Software, and any and all patent rights owned or
11+
* freely licensable by each licensor hereunder covering either (i) the
12+
* unmodified Software as contributed to or provided by such licensor, or (ii)
13+
* the Larger Works (as defined below), to deal in both
14+
*
15+
* (a) the Software, and
16+
*
17+
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
18+
* one is included with the Software each a "Larger Work" to which the Software
19+
* is contributed by such licensors),
20+
*
21+
* without restriction, including without limitation the rights to copy, create
22+
* derivative works of, display, perform, and distribute the Software and make,
23+
* use, sell, offer for sale, import, export, have made, and have sold the
24+
* Software and the Larger Work(s), and to sublicense the foregoing rights on
25+
* either these or other terms.
26+
*
27+
* This license is subject to the following condition:
28+
*
29+
* The above copyright notice and either this complete permission notice or at a
30+
* minimum a reference to the UPL must be included in all copies or substantial
31+
* portions of the Software.
32+
*
33+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39+
* SOFTWARE.
40+
*/
41+
package org.graalvm.buildtools.gradle;
42+
43+
import org.graalvm.buildtools.gradle.dsl.NativeImageOptions;
44+
import org.graalvm.buildtools.gradle.internal.GraalVMLogger;
45+
import org.gradle.api.Action;
46+
import org.gradle.api.Task;
47+
import org.gradle.api.file.Directory;
48+
import org.gradle.api.file.FileSystemOperations;
49+
import org.gradle.api.logging.Logger;
50+
import org.gradle.api.provider.Provider;
51+
import org.gradle.process.ExecOperations;
52+
import org.gradle.process.ExecResult;
53+
54+
import java.io.File;
55+
import java.util.ArrayList;
56+
import java.util.Arrays;
57+
import java.util.List;
58+
import java.util.stream.Stream;
59+
60+
import static org.graalvm.buildtools.gradle.internal.NativeImageExecutableLocator.findNativeImageExecutable;
61+
import static org.graalvm.buildtools.utils.SharedConstants.GRAALVM_EXE_EXTENSION;
62+
63+
class MergeAgentFiles implements Action<Task> {
64+
private final Provider<Boolean> agent;
65+
private final Provider<String> graalvmHomeProvider;
66+
private final Provider<Directory> outputDir;
67+
private final Provider<Boolean> disableToolchainDetection;
68+
private final ExecOperations execOperations;
69+
private final NativeImageOptions options;
70+
private final FileSystemOperations fileOperations;
71+
private final Logger logger;
72+
73+
MergeAgentFiles(Provider<Boolean> agent,
74+
Provider<String> graalvmHomeProvider,
75+
Provider<Directory> outputDir,
76+
Provider<Boolean> disableToolchainDetection,
77+
NativeImageOptions options,
78+
ExecOperations execOperations,
79+
FileSystemOperations fileOperations,
80+
Logger logger) {
81+
this.agent = agent;
82+
this.graalvmHomeProvider = graalvmHomeProvider;
83+
this.outputDir = outputDir;
84+
this.disableToolchainDetection = disableToolchainDetection;
85+
this.options = options;
86+
this.execOperations = execOperations;
87+
this.fileOperations = fileOperations;
88+
this.logger = logger;
89+
}
90+
91+
@Override
92+
public void execute(Task task) {
93+
if (agent.get()) {
94+
File nativeImage = findNativeImageExecutable(options, disableToolchainDetection, graalvmHomeProvider, execOperations, GraalVMLogger.of(logger));
95+
File workingDir = nativeImage.getParentFile();
96+
File launcher = new File(workingDir, nativeImageConfigureFileName());
97+
if (!launcher.exists()) {
98+
logger.info("Installing native-image-configure");
99+
execOperations.exec(spec -> {
100+
spec.executable(nativeImage);
101+
spec.args("--macro:native-image-configure-launcher");
102+
});
103+
}
104+
if (launcher.exists()) {
105+
File[] files = outputDir.get().getAsFile().listFiles();
106+
List<String> args = new ArrayList<>(files.length + 2);
107+
args.add("generate");
108+
sessionDirectoriesFrom(files)
109+
.map(f -> "--input-dir=" + f.getAbsolutePath())
110+
.forEach(args::add);
111+
if (args.size() > 1) {
112+
logger.info("Merging agent files");
113+
args.add("--output-dir=" + outputDir.get().getAsFile().getAbsolutePath());
114+
ExecResult exec = execOperations.exec(spec -> {
115+
spec.executable(launcher);
116+
spec.args(args);
117+
spec.setStandardOutput(System.out);
118+
spec.setErrorOutput(System.err);
119+
});
120+
if (exec.getExitValue() == 0) {
121+
fileOperations.delete(spec -> sessionDirectoriesFrom(files).forEach(spec::delete));
122+
} else {
123+
exec.rethrowFailure();
124+
}
125+
}
126+
} else {
127+
logger.warn("Cannot merge agent files because native-image-configure is not installed. Please upgrade to a newer version of GraalVM.");
128+
}
129+
}
130+
}
131+
132+
private String nativeImageConfigureFileName() {
133+
return "native-image-configure" + GRAALVM_EXE_EXTENSION;
134+
}
135+
136+
private Stream<File> sessionDirectoriesFrom(File[] files) {
137+
return Arrays.stream(files)
138+
.filter(File::isDirectory)
139+
.filter(f -> f.getName().startsWith("session-"));
140+
}
141+
}
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
import org.gradle.api.file.DuplicatesStrategy;
7575
import org.gradle.api.file.FileCollection;
7676
import org.gradle.api.file.FileSystemLocation;
77+
import org.gradle.api.file.FileSystemOperations;
7778
import org.gradle.api.model.ObjectFactory;
7879
import org.gradle.api.plugins.ApplicationPlugin;
7980
import org.gradle.api.plugins.JavaApplication;
@@ -95,6 +96,7 @@
9596
import org.gradle.api.tasks.testing.Test;
9697
import org.gradle.jvm.toolchain.JavaToolchainService;
9798
import org.gradle.language.base.plugins.LifecycleBasePlugin;
99+
import org.gradle.process.ExecOperations;
98100
import org.gradle.process.CommandLineArgumentProvider;
99101
import org.gradle.process.JavaForkOptions;
100102
import org.gradle.util.GFileUtils;
@@ -110,6 +112,7 @@
110112
import java.util.stream.Collectors;
111113

112114
import static org.graalvm.buildtools.gradle.internal.GradleUtils.transitiveProjectArtifacts;
115+
import static org.graalvm.buildtools.gradle.internal.NativeImageExecutableLocator.graalvmHomeProvider;
113116
import static org.graalvm.buildtools.utils.SharedConstants.AGENT_OUTPUT_FOLDER;
114117
import static org.graalvm.buildtools.utils.SharedConstants.AGENT_PROPERTY;
115118

@@ -151,6 +154,17 @@ public ArchiveOperations getArchiveOperations() {
151154
throw new UnsupportedOperationException();
152155
}
153156

157+
@Inject
158+
public ExecOperations getExecOperations() {
159+
throw new UnsupportedOperationException();
160+
}
161+
162+
@Inject
163+
public FileSystemOperations getFileOperations() {
164+
throw new UnsupportedOperationException();
165+
}
166+
167+
154168
@Override
155169
public void apply(Project project) {
156170
Provider<NativeImageService> nativeImageServiceProvider = NativeImageService.registerOn(project);
@@ -164,7 +178,7 @@ public void apply(Project project) {
164178
graalExtension.getBinaries().all(options -> {
165179
AgentConfiguration agentConfiguration = options.getAgent();
166180
if (agentConfiguration.getInstrumentedTask().isPresent()) {
167-
configureAgent(p, agents, options);
181+
configureAgent(p, agents, graalExtension.getToolchainDetection().map(b -> !b), options, getExecOperations(), getFileOperations());
168182
}
169183
});
170184
});
@@ -218,7 +232,7 @@ private void configureJavaProject(Project project, Provider<NativeImageService>
218232
config.forTestTask(tasks.named("test", Test.class));
219233
config.usingSourceSet(GradleUtils.findSourceSet(project, SourceSet.TEST_SOURCE_SET_NAME));
220234
});
221-
}
235+
}
222236

223237
private void configureAutomaticTaskCreation(Project project,
224238
GraalVMExtension graalExtension,
@@ -342,8 +356,8 @@ private TaskProvider<GenerateResourcesConfigFile> registerResourcesConfigTask(Pr
342356
}
343357

344358
public void registerTestBinary(Project project,
345-
DefaultGraalVmExtension graalExtension,
346-
DefaultTestBinaryConfig config) {
359+
DefaultGraalVmExtension graalExtension,
360+
DefaultTestBinaryConfig config) {
347361
NativeImageOptions mainOptions = graalExtension.getBinaries().getByName("main");
348362
String name = config.getName();
349363
boolean isPrimaryTest = "test".equals(name);
@@ -506,7 +520,10 @@ private static NativeImageOptions createTestOptions(GraalVMExtension graalExtens
506520

507521
private static void configureAgent(Project project,
508522
Map<String, Provider<Boolean>> agents,
509-
NativeImageOptions nativeImageOptions) {
523+
Provider<Boolean> disableToolchainDetection,
524+
NativeImageOptions nativeImageOptions,
525+
ExecOperations execOperations,
526+
FileSystemOperations fileOperations) {
510527
String postProcessTaskName = PROCESS_AGENT_RESOURCES_TASK_NAME_PREFIX + capitalize(nativeImageOptions.getName()) + PROCESS_AGENT_RESOURCES_TASK_NAME_SUFFIX;
511528
TaskProvider<ProcessGeneratedGraalResourceFiles> postProcessingTask = registerProcessAgentFilesTask(project, postProcessTaskName);
512529
TaskProvider<? extends JavaForkOptions> instrumentedTask = nativeImageOptions.getAgent().getInstrumentedTask().get();
@@ -517,6 +534,15 @@ private static void configureAgent(Project project,
517534
cliProvider.getOutputDirectory().set(outputDir);
518535
cliProvider.getAgentOptions().set(nativeImageOptions.getAgent().getOptions());
519536
instrumentedTask.get().getJvmArgumentProviders().add(cliProvider);
537+
instrumentedTask.configure(task -> task.doLast(new MergeAgentFiles(
538+
agent,
539+
graalvmHomeProvider(project.getProviders()),
540+
outputDir,
541+
disableToolchainDetection,
542+
nativeImageOptions,
543+
execOperations,
544+
fileOperations,
545+
project.getLogger())));
520546
// Gradle won't let us configure from configure so we have to eagerly create the post-processing task :(
521547
postProcessingTask.get().getGeneratedFilesDir().set(
522548
instrumentedTask.map(t -> outputDir.get())
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@
5858

5959
public abstract class AgentCommandLineProvider implements CommandLineArgumentProvider {
6060

61+
public static final String SESSION_SUBDIR = "session-{pid}-{datetime}";
62+
6163
@Inject
6264
@SuppressWarnings("checkstyle:redundantmodifier")
6365
public AgentCommandLineProvider() {
@@ -82,7 +84,7 @@ public Iterable<String> asArguments() {
8284
if (agentOptions.stream().map(s -> s.split("=")[0]).anyMatch(s -> s.contains("config-output-dir"))) {
8385
throw new IllegalStateException("config-output-dir cannot be supplied as an agent option");
8486
}
85-
agentOptions.add("config-output-dir=" + outputDir.getAbsolutePath());
87+
agentOptions.add("config-output-dir=" + outputDir.getAbsolutePath() + File.separator + SESSION_SUBDIR);
8688
return Arrays.asList(
8789
"-agentlib:native-image-agent=" + String.join(",", agentOptions),
8890
"-Dorg.graalvm.nativeimage.imagecode=agent"
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ public void lifecycle(String s) {
6969
delegate.lifecycle("[native-image-plugin] {}", s);
7070
}
7171

72+
public void lifecycle(String pattern, Object... args) {
73+
delegate.lifecycle("[native-image-plugin] " + pattern, args);
74+
}
75+
7276
public void error(String s) {
7377
delegate.error("[native-image-plugin] {}", s);
7478
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
* Copyright (c) 2020, 2022 Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* The Universal Permissive License (UPL), Version 1.0
6+
*
7+
* Subject to the condition set forth below, permission is hereby granted to any
8+
* person obtaining a copy of this software, associated documentation and/or
9+
* data (collectively the "Software"), free of charge and under any and all
10+
* copyright rights in the Software, and any and all patent rights owned or
11+
* freely licensable by each licensor hereunder covering either (i) the
12+
* unmodified Software as contributed to or provided by such licensor, or (ii)
13+
* the Larger Works (as defined below), to deal in both
14+
*
15+
* (a) the Software, and
16+
*
17+
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
18+
* one is included with the Software each a "Larger Work" to which the Software
19+
* is contributed by such licensors),
20+
*
21+
* without restriction, including without limitation the rights to copy, create
22+
* derivative works of, display, perform, and distribute the Software and make,
23+
* use, sell, offer for sale, import, export, have made, and have sold the
24+
* Software and the Larger Work(s), and to sublicense the foregoing rights on
25+
* either these or other terms.
26+
*
27+
* This license is subject to the following condition:
28+
*
29+
* The above copyright notice and either this complete permission notice or at a
30+
* minimum a reference to the UPL must be included in all copies or substantial
31+
* portions of the Software.
32+
*
33+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39+
* SOFTWARE.
40+
*/
41+
package org.graalvm.buildtools.gradle.internal;
42+
43+
import org.graalvm.buildtools.gradle.dsl.NativeImageOptions;
44+
import org.gradle.api.GradleException;
45+
import org.gradle.api.provider.Provider;
46+
import org.gradle.api.provider.ProviderFactory;
47+
import org.gradle.jvm.toolchain.JavaInstallationMetadata;
48+
import org.gradle.process.ExecOperations;
49+
import org.gradle.process.ExecResult;
50+
51+
import java.io.File;
52+
import java.nio.file.Paths;
53+
54+
import static org.graalvm.buildtools.utils.SharedConstants.GU_EXE;
55+
import static org.graalvm.buildtools.utils.SharedConstants.NATIVE_IMAGE_EXE;
56+
57+
public class NativeImageExecutableLocator {
58+
59+
public static Provider<String> graalvmHomeProvider(ProviderFactory providers) {
60+
return providers.environmentVariable("GRAALVM_HOME")
61+
.forUseAtConfigurationTime()
62+
.orElse(providers.environmentVariable("JAVA_HOME").forUseAtConfigurationTime());
63+
}
64+
65+
public static File findNativeImageExecutable(NativeImageOptions options,
66+
Provider<Boolean> disableToolchainDetection,
67+
Provider<String> graalvmHomeProvider,
68+
ExecOperations execOperations,
69+
GraalVMLogger logger) {
70+
File executablePath = null;
71+
if (disableToolchainDetection.get() || !options.getJavaLauncher().isPresent()) {
72+
if (graalvmHomeProvider.isPresent()) {
73+
String graalvmHome = graalvmHomeProvider.get();
74+
logger.lifecycle("Toolchain detection is disabled, will use GraalVM from {}.", graalvmHome);
75+
executablePath = Paths.get(graalvmHome).resolve("bin/" + NATIVE_IMAGE_EXE).toFile();
76+
}
77+
}
78+
if (executablePath == null) {
79+
JavaInstallationMetadata metadata = options.getJavaLauncher().get().getMetadata();
80+
executablePath = metadata.getInstallationPath().file("bin/" + NATIVE_IMAGE_EXE).getAsFile();
81+
if (!executablePath.exists() && graalvmHomeProvider.isPresent()) {
82+
executablePath = Paths.get(graalvmHomeProvider.get()).resolve("bin").resolve(NATIVE_IMAGE_EXE).toFile();
83+
}
84+
}
85+
86+
try {
87+
if (!executablePath.exists()) {
88+
logger.log("Native Image executable wasn't found. We will now try to download it. ");
89+
File graalVmHomeGuess = executablePath.getParentFile();
90+
91+
if (!graalVmHomeGuess.toPath().resolve(GU_EXE).toFile().exists()) {
92+
throw new GradleException("'" + GU_EXE + "' tool wasn't found. This probably means that JDK at isn't a GraalVM distribution.");
93+
}
94+
ExecResult res = execOperations.exec(spec -> {
95+
spec.args("install", "native-image");
96+
spec.setExecutable(Paths.get(graalVmHomeGuess.getAbsolutePath(), GU_EXE));
97+
});
98+
if (res.getExitValue() != 0) {
99+
throw new GradleException("Native Image executable wasn't found, and '" + GU_EXE + "' tool failed to install it.");
100+
}
101+
}
102+
} catch (GradleException e) {
103+
throw new GradleException("Determining GraalVM installation failed with message: " + e.getMessage() + "\n\n"
104+
+ "Make sure to declare the GRAALVM_HOME environment variable or install GraalVM with " +
105+
"native-image in a standard location recognized by Gradle Java toolchain support");
106+
}
107+
return executablePath;
108+
}
109+
}

0 commit comments

Comments
 (0)