Skip to content

Commit 572e0d1

Browse files
Use ContainerSupport for building containerized native image bundles
1 parent 2f4ddd7 commit 572e0d1

File tree

4 files changed

+153
-304
lines changed

4 files changed

+153
-304
lines changed

substratevm/src/com.oracle.svm.driver.launcher/src/com/oracle/svm/driver/launcher/BundleLauncher.java

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@
4747
import java.util.jar.JarFile;
4848
import java.util.stream.Stream;
4949

50-
import static com.oracle.svm.driver.launcher.ContainerSupport.CONTAINER_GRAAL_VM_HOME;
50+
import static com.oracle.svm.driver.launcher.ContainerSupport.replaceContainerPaths;
51+
import static com.oracle.svm.driver.launcher.ContainerSupport.mountMappingFor;
52+
import static com.oracle.svm.driver.launcher.ContainerSupport.TargetPath;
5153

5254

5355
public class BundleLauncher {
@@ -106,21 +108,18 @@ public static void main(String[] args) {
106108
new BundleEnvironmentParser(launcherEnvironment).parseAndRegister(reader);
107109
pb.environment().putAll(launcherEnvironment);
108110
} catch (IOException e) {
109-
throw new RuntimeException("Failed to read bundle-file " + environmentFile, e);
111+
throw new Error("Failed to read bundle-file " + environmentFile, e);
110112
}
111113
}
112114

113115
if (useContainer()) {
114116
Path javaHome = getJavaExecutable().getParent().getParent();
115-
ContainerSupport.replaceContainerPaths(command, javaHome, rootDir);
117+
replaceContainerPaths(command, javaHome, rootDir);
116118

117-
// TODO also mount agentDir if necessary
118-
119-
Map<Path, ContainerSupport.TargetPath> mountMapping = new HashMap<>();
120-
Path containerRoot = Paths.get("/");
121-
mountMapping.put(javaHome, new ContainerSupport.TargetPath(containerRoot.resolve(CONTAINER_GRAAL_VM_HOME),true));
122-
mountMapping.put(inputDir, new ContainerSupport.TargetPath(containerRoot.resolve(INPUT_DIR_NAME),true));
123-
mountMapping.put(outputDir, new ContainerSupport.TargetPath(containerRoot.resolve(OUTPUT_DIR_NAME),false));
119+
Map<Path, TargetPath> mountMapping = mountMappingFor(javaHome, inputDir, outputDir);
120+
if (Files.isDirectory(agentOutputDir)) {
121+
mountMapping.put(agentOutputDir, TargetPath.of(agentOutputDir, false));
122+
}
124123

125124
containerSupport.initializeContainerImage();
126125
command.addAll(0, containerSupport.createContainerCommand(launcherEnvironment, mountMapping));
@@ -145,7 +144,7 @@ public static void main(String[] args) {
145144
p = pb.inheritIO().start();
146145
exitCode = p.waitFor();
147146
} catch (IOException | InterruptedException e) {
148-
throw new RuntimeException("Failed to run bundled application");
147+
throw new Error("Failed to run bundled application");
149148
} finally {
150149
if (p != null) {
151150
p.destroy();
@@ -180,7 +179,7 @@ private static List<String> createLaunchCommand(String[] args) {
180179
.map(Path::toString)
181180
.forEach(classpath::add);
182181
} catch (IOException e) {
183-
throw new RuntimeException("Failed to iterate through directory " + classPathDir, e);
182+
throw new Error("Failed to iterate through directory " + classPathDir, e);
184183
}
185184

186185
command.add("-cp");
@@ -196,7 +195,7 @@ private static List<String> createLaunchCommand(String[] args) {
196195
.map(Path::toString)
197196
.forEach(modulePath::add);
198197
} catch (IOException e) {
199-
throw new RuntimeException("Failed to iterate through directory " + modulePathDir, e);
198+
throw new Error("Failed to iterate through directory " + modulePathDir, e);
200199
}
201200

202201
if (!modulePath.isEmpty()) {
@@ -211,7 +210,7 @@ private static List<String> createLaunchCommand(String[] args) {
211210
new BundleArgsParser(argsFromFile).parseAndRegister(reader);
212211
command.addAll(argsFromFile);
213212
} catch (IOException e) {
214-
throw new RuntimeException("Failed to read bundle-file " + argsFile, e);
213+
throw new Error("Failed to read bundle-file " + argsFile, e);
215214
}
216215

217216
command.addAll(applicationArgs);
@@ -238,7 +237,7 @@ private static int updateBundle() {
238237
p = pb.inheritIO().start();
239238
return p.waitFor();
240239
} catch (IOException | InterruptedException e) {
241-
throw new RuntimeException("Failed to create updated bundle.");
240+
throw new Error("Failed to create updated bundle.");
242241
} finally {
243242
if (p != null) {
244243
p.destroy();
@@ -262,7 +261,7 @@ private static List<String> parseBundleLauncherArgs(String[] args, List<String>
262261
newBundleName = option.substring(option.indexOf('=')).replace(BUNDLE_FILE_EXTENSION, "");
263262
}
264263
} else {
265-
throw new RuntimeException(String.format("Unknown option %s. Valid option is: update-bundle[=<new-bundle-name>].", option));
264+
throw new Error(String.format("Unknown option %s. Valid option is: update-bundle[=<new-bundle-name>].", option));
266265
}
267266
}
268267

@@ -276,7 +275,7 @@ private static List<String> parseBundleLauncherArgs(String[] args, List<String>
276275
}
277276
} else if (arg.startsWith("--container")) {
278277
if (useContainer()) {
279-
throw new RuntimeException("native-image bundle allows option container to be specified only once.");
278+
throw new Error("native-image launcher allows option container to be specified only once.");
280279
}
281280
Path dockerfile;
282281
if (arg.indexOf(',') != -1) {
@@ -331,14 +330,14 @@ private static Path getJavaExecutable() {
331330
private static Path getJavaHomeExecutable(Path executable) {
332331
String javaHome = System.getenv("JAVA_HOME");
333332
if (javaHome == null) {
334-
throw new RuntimeException("Environment variable JAVA_HOME is not set");
333+
throw new Error("Environment variable JAVA_HOME is not set");
335334
}
336335
Path javaHomeDir = Paths.get(javaHome);
337336
if (!Files.isDirectory(javaHomeDir)) {
338-
throw new RuntimeException("Environment variable JAVA_HOME does not refer to a directory");
337+
throw new Error("Environment variable JAVA_HOME does not refer to a directory");
339338
}
340339
if (!Files.isExecutable(javaHomeDir.resolve(executable))) {
341-
throw new RuntimeException("Environment variable JAVA_HOME does not refer to a directory with a " + executable + " executable");
340+
throw new Error("Environment variable JAVA_HOME does not refer to a directory with a " + executable + " executable");
342341
}
343342
return javaHomeDir.resolve(executable);
344343
}
@@ -407,12 +406,12 @@ private static void unpackBundle(Path bundleFilePath) {
407406
}
408407
Files.copy(archive.getInputStream(jarEntry), bundleEntry);
409408
} catch (IOException e) {
410-
throw new RuntimeException("Unable to copy " + jarEntry.getName() + " from bundle " + bundleEntry + " to " + bundleEntry, e);
409+
throw new Error("Unable to copy " + jarEntry.getName() + " from bundle " + bundleEntry + " to " + bundleEntry, e);
411410
}
412411
}
413412
}
414413
} catch (IOException e) {
415-
throw new RuntimeException("Unable to expand bundle directory layout from bundle file " + bundleFilePath, e);
414+
throw new Error("Unable to expand bundle directory layout from bundle file " + bundleFilePath, e);
416415
}
417416

418417
if (deleteBundleRoot.get()) {
@@ -427,7 +426,7 @@ private static void unpackBundle(Path bundleFilePath) {
427426
modulePathDir = Files.createDirectories(classesDir.resolve(MODULE_PATH_DIR_NAME));
428427
outputDir = Files.createDirectories(rootDir.resolve(OUTPUT_DIR_NAME));
429428
} catch (IOException e) {
430-
throw new RuntimeException("Unable to create bundle directory layout", e);
429+
throw new Error("Unable to create bundle directory layout", e);
431430
}
432431
}
433432
}

substratevm/src/com.oracle.svm.driver.launcher/src/com/oracle/svm/driver/launcher/ContainerSupport.java

Lines changed: 53 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,16 @@
3232
import java.io.Reader;
3333
import java.nio.file.Files;
3434
import java.nio.file.Path;
35+
import java.nio.file.Paths;
3536
import java.util.ArrayList;
3637
import java.util.Arrays;
3738
import java.util.HashMap;
3839
import java.util.List;
3940
import java.util.Map;
41+
import java.util.function.BiFunction;
42+
import java.util.function.Consumer;
4043

4144
public class ContainerSupport {
42-
// TODO put anything container related in here, maybe even with function references(for logging), take care
43-
4445
public String containerTool;
4546
public String bundleContainerTool;
4647
public String containerToolVersion;
@@ -49,17 +50,28 @@ public class ContainerSupport {
4950
public String bundleContainerImage;
5051
public Path dockerfile;
5152

52-
private static final List<String> SUPPORTED_CONTAINER_TOOLS = List.of("podman", "docker");
53-
private static final String CONTAINER_TOOL_JSON_KEY = "containerTool";
54-
private static final String CONTAINER_TOOL_VERSION_JSON_KEY = "containerToolVersion";
55-
private static final String CONTAINER_IMAGE_JSON_KEY = "containerImage";
53+
public static final List<String> SUPPORTED_CONTAINER_TOOLS = List.of("podman", "docker");
54+
public static final String CONTAINER_TOOL_JSON_KEY = "containerTool";
55+
public static final String CONTAINER_TOOL_VERSION_JSON_KEY = "containerToolVersion";
56+
public static final String CONTAINER_IMAGE_JSON_KEY = "containerImage";
5657

5758
private static final String BUNDLE_INFO_MESSAGE_PREFIX = "Native Image Bundles: ";
5859

5960
public static final Path CONTAINER_GRAAL_VM_HOME = Path.of("/graalvm");
6061

62+
private final BiFunction<String, Throwable, Error> errorFunction;
63+
private final Consumer<String> warningPrinter;
64+
private final Consumer<String> messagePrinter;
65+
6166
public ContainerSupport(Path dockerfile, Path bundleStageDir) {
67+
this(dockerfile, bundleStageDir, Error::new, (msg) -> System.out.println("Warning: " + msg), System.out::println);
68+
}
69+
70+
public ContainerSupport(Path dockerfile, Path bundleStageDir, BiFunction<String, Throwable, Error> errorFunction, Consumer<String> warningPrinter, Consumer<String> messagePrinter) {
6271
this.dockerfile = dockerfile;
72+
this.errorFunction = errorFunction;
73+
this.warningPrinter = warningPrinter;
74+
this.messagePrinter = messagePrinter;
6375

6476
if (bundleStageDir != null) {
6577
Path containerFile = bundleStageDir.resolve("container.json");
@@ -71,11 +83,11 @@ public ContainerSupport(Path dockerfile, Path bundleStageDir) {
7183
bundleContainerTool = containerSettings.getOrDefault(CONTAINER_TOOL_JSON_KEY, bundleContainerTool);
7284
bundleContainerToolVersion = containerSettings.getOrDefault(CONTAINER_TOOL_VERSION_JSON_KEY, bundleContainerToolVersion);
7385
} catch (IOException e) {
74-
throw new Error("Failed to read bundle-file " + containerFile, e);
86+
throw errorFunction.apply("Failed to read bundle-file " + containerFile, e);
7587
}
7688
if (bundleContainerTool != null) {
7789
String containerToolVersionString = bundleContainerToolVersion == null ? "" : String.format(" (%s)", bundleContainerToolVersion);
78-
System.out.printf("%sBundled native-image was created in a container with %s%s.%n", BUNDLE_INFO_MESSAGE_PREFIX, bundleContainerTool, containerToolVersionString);
90+
messagePrinter.accept(String.format("%sBundled native-image was created in a container with %s%s.%n", BUNDLE_INFO_MESSAGE_PREFIX, bundleContainerTool, containerToolVersionString));
7991
}
8092
}
8193
}
@@ -85,11 +97,11 @@ public int initializeContainerImage() {
8597
try {
8698
containerImage = BundleLauncherUtil.digest(Files.readString(dockerfile));
8799
} catch (IOException e) {
88-
throw new Error("Could not read Dockerfile " + dockerfile);
100+
throw errorFunction.apply("Could not read Dockerfile " + dockerfile, e);
89101
}
90102

91103
if (bundleContainerImage != null && !bundleContainerImage.equals(containerImage)) {
92-
System.out.println("Warning: The bundled image was created with a different dockerfile.");
104+
warningPrinter.accept("The bundled image was created with a different dockerfile.");
93105
}
94106

95107
if (bundleContainerTool != null && containerTool == null) {
@@ -98,24 +110,24 @@ public int initializeContainerImage() {
98110

99111
if (containerTool != null) {
100112
if (!isToolAvailable(containerTool)) {
101-
throw new Error("Configured container tool not available.");
113+
throw errorFunction.apply("Configured container tool not available.", null);
102114
} else if (containerTool.equals("docker") && !isRootlessDocker()) {
103-
throw new Error("Only rootless docker is supported for containerized builds.");
115+
throw errorFunction.apply("Only rootless docker is supported for containerized builds.", null);
104116
}
105117
containerToolVersion = getContainerToolVersion(containerTool);
106118

107119
if (bundleContainerTool != null) {
108120
if (!containerTool.equals(bundleContainerTool)) {
109-
System.out.printf("Warning: The bundled image was created with container tool '%s' (using '%s').%n", bundleContainerTool, containerTool);
121+
warningPrinter.accept(String.format("The bundled image was created with container tool '%s' (using '%s').%n", bundleContainerTool, containerTool));
110122
} else if (containerToolVersion != null && bundleContainerToolVersion != null && !containerToolVersion.equals(bundleContainerToolVersion)) {
111-
System.out.printf("Warning: The bundled image was created with different %s version '%s' (installed '%s').%n", containerTool, bundleContainerToolVersion, containerToolVersion);
123+
warningPrinter.accept(String.format("The bundled image was created with different %s version '%s' (installed '%s').%n", containerTool, bundleContainerToolVersion, containerToolVersion));
112124
}
113125
}
114126
} else {
115127
for (String tool : SUPPORTED_CONTAINER_TOOLS) {
116128
if (isToolAvailable(tool)) {
117129
if (tool.equals("docker") && !isRootlessDocker()) {
118-
System.out.println(BUNDLE_INFO_MESSAGE_PREFIX + "Rootless context missing for docker.");
130+
messagePrinter.accept(BUNDLE_INFO_MESSAGE_PREFIX + "Rootless context missing for docker.");
119131
continue;
120132
}
121133
containerTool = tool;
@@ -124,7 +136,7 @@ public int initializeContainerImage() {
124136
}
125137
}
126138
if (containerTool == null) {
127-
throw new Error(String.format("Please install one of the following tools before running containerized native image builds: %s", SUPPORTED_CONTAINER_TOOLS));
139+
throw errorFunction.apply(String.format("Please install one of the following tools before running containerized native image builds: %s", SUPPORTED_CONTAINER_TOOLS), null);
128140
}
129141
}
130142

@@ -139,7 +151,7 @@ private int createContainer() {
139151
if (imageId == null) {
140152
pb.inheritIO();
141153
} else {
142-
System.out.printf("%sReusing container image %s.%n", BUNDLE_INFO_MESSAGE_PREFIX, containerImage);
154+
messagePrinter.accept(String.format("%sReusing container image %s.%n", BUNDLE_INFO_MESSAGE_PREFIX, containerImage));
143155
}
144156

145157
Process p = null;
@@ -148,37 +160,37 @@ private int createContainer() {
148160
int status = p.waitFor();
149161
if (status == 0 && imageId != null && !imageId.equals(getFirstProcessResultLine(pbCheckForImage))) {
150162
try (var processResult = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
151-
System.out.printf("%sUpdated container image %s.%n", BUNDLE_INFO_MESSAGE_PREFIX, containerImage);
152-
processResult.lines().forEach(System.out::println);
163+
messagePrinter.accept(String.format("%sUpdated container image %s.%n", BUNDLE_INFO_MESSAGE_PREFIX, containerImage));
164+
processResult.lines().forEach(messagePrinter);
153165
}
154166
}
155167
return status;
156168
} catch (IOException | InterruptedException e) {
157-
throw new Error(e.getMessage());
169+
throw errorFunction.apply(e.getMessage(), e);
158170
} finally {
159171
if (p != null) {
160172
p.destroy();
161173
}
162174
}
163175
}
164176

165-
private static boolean isToolAvailable(String tool) {
177+
private boolean isToolAvailable(String tool) {
166178
return Arrays.stream(System.getenv("PATH").split(":"))
167179
.map(str -> Path.of(str).resolve(tool))
168180
.anyMatch(Files::isExecutable);
169181
}
170182

171-
private static String getContainerToolVersion(String tool) {
183+
private String getContainerToolVersion(String tool) {
172184
ProcessBuilder pb = new ProcessBuilder(tool, "--version");
173185
return getFirstProcessResultLine(pb);
174186
}
175187

176-
private static boolean isRootlessDocker() {
188+
private boolean isRootlessDocker() {
177189
ProcessBuilder pb = new ProcessBuilder("docker", "context", "show");
178190
return getFirstProcessResultLine(pb).equals("rootless");
179191
}
180192

181-
private static String getFirstProcessResultLine(ProcessBuilder pb) {
193+
private String getFirstProcessResultLine(ProcessBuilder pb) {
182194
Process p = null;
183195
try {
184196
p = pb.start();
@@ -187,7 +199,7 @@ private static String getFirstProcessResultLine(ProcessBuilder pb) {
187199
return processResult.readLine();
188200
}
189201
} catch (IOException | InterruptedException e) {
190-
throw new RuntimeException(e.getMessage());
202+
throw errorFunction.apply(e.getMessage(), e);
191203
} finally {
192204
if (p != null) {
193205
p.destroy();
@@ -196,6 +208,22 @@ private static String getFirstProcessResultLine(ProcessBuilder pb) {
196208
}
197209

198210
public record TargetPath(Path path, boolean readonly) {
211+
public static TargetPath readonly(Path target) {
212+
return of(target, true);
213+
}
214+
215+
public static TargetPath of(Path target, boolean readonly) {
216+
return new TargetPath(target, readonly);
217+
}
218+
}
219+
220+
public static Map<Path, TargetPath> mountMappingFor(Path javaHome, Path inputDir, Path outputDir) {
221+
Map<Path, TargetPath> mountMapping = new HashMap<>();
222+
Path containerRoot = Paths.get("/");
223+
mountMapping.put(javaHome, TargetPath.readonly(containerRoot.resolve(CONTAINER_GRAAL_VM_HOME)));
224+
mountMapping.put(inputDir, ContainerSupport.TargetPath.readonly(containerRoot.resolve(BundleLauncher.INPUT_DIR_NAME)));
225+
mountMapping.put(outputDir, ContainerSupport.TargetPath.of(containerRoot.resolve(BundleLauncher.OUTPUT_DIR_NAME), false));
226+
return mountMapping;
199227
}
200228

201229
public List<String> createContainerCommand(Map<String, String> containerEnvironment, Map<Path, TargetPath> mountMapping) {

0 commit comments

Comments
 (0)