Skip to content

Commit

Permalink
Use builder image's objcopy when using containers
Browse files Browse the repository at this point in the history
  • Loading branch information
zakkak committed Dec 18, 2020
1 parent 066adf4 commit c7e95e5
Showing 1 changed file with 62 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,11 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa
}
}
if (isContainerBuild) {
nativeImage = setupContainerBuild(nativeConfig, processInheritIODisabled.isPresent(), outputDir);
containerCommand = setupContainerBuild(nativeConfig, processInheritIODisabled.isPresent(), outputDir);
nativeImage = Collections.singletonList("native-image");
}

final GraalVM.Version graalVMVersion = GraalVM.Version.ofBinary(nativeImage);
final GraalVM.Version graalVMVersion = GraalVM.Version.ofBinary(containerCommand, nativeImage);

if (graalVMVersion.isDetected()) {
checkGraalVMVersion(graalVMVersion);
Expand All @@ -137,6 +138,7 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa
if (nativeConfig.cleanupServer && !graalVMVersion.isMandrel()) {
List<String> cleanup = new ArrayList<>(nativeImage);
cleanup.add("--server-shutdown");
cleanup = maybeRunInContainer(containerCommand, cleanup);
final ProcessBuilder pb = new ProcessBuilder(cleanup);
pb.directory(outputDir.toFile());
final Process process = ProcessUtil.launchProcess(pb, processInheritIODisabled.isPresent());
Expand All @@ -145,7 +147,12 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa
List<String> buildCommand = getBuildCommand(nativeConfig, nativeImageProperties,
outputDir, runnerJarName, nativeImage, noPIE, isContainerBuild, graalVMVersion,
executableName);
log.info(String.join(" ", buildCommand).replace("$", "\\$"));
buildCommand = maybeRunInContainer(containerCommand, buildCommand);
if (isContainerBuild) {
log.info(String.join(" ", buildCommand).replace("$", "\\$"));
} else {
log.info(String.join(" ", buildCommand));
}
CountDownLatch errorReportLatch = new CountDownLatch(1);
final ProcessBuilder processBuilder = new ProcessBuilder(buildCommand).directory(outputDir.toFile());
final Process process = ProcessUtil.launchProcessStreamStdOut(processBuilder, processInheritIODisabled.isPresent());
Expand All @@ -162,11 +169,28 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa
//once image is generated it gets added .exe on Windows
executableName = executableName + ".exe";
}

String symbols = String.format("%s.debug", executableName);
if (!isContainerBuild && !objcopyExists(env)) {
log.warn("objcopy executable not found in PATH. Debug symbols will not be separated from executable.");
log.warn("That will result in a larger native image with debug symbols embedded in it.");
} else {
assert !(isContainerBuild && containerCommand == null);
if (nativeConfig.debug.enabled) {
objcopy(containerCommand, outputDir, "--only-keep-debug", executableName, symbols);
objcopy(containerCommand, outputDir, String.format("--add-gnu-debuglink=%s", symbols), executableName);
}
// Strip debug symbols regardless, because the underlying JDK might contain them
objcopy(containerCommand, outputDir, "--strip-debug", executableName);
}

Path generatedImage = outputDir.resolve(executableName);
Path finalPath = outputTargetBuildItem.getOutputDirectory().resolve(executableName);
IoUtils.copy(generatedImage, finalPath);
Files.delete(generatedImage);
if (nativeConfig.debug.enabled) {
IoUtils.copy(outputDir.resolve(symbols), outputTargetBuildItem.getOutputDirectory().resolve(symbols));
Files.delete(outputDir.resolve(symbols));
if (graalVMVersion.isMandrel() || graalVMVersion.isNewerThan(GraalVM.Version.VERSION_20_1)) {
final String sources = "sources";
final Path generatedSources = outputDir.resolve(sources);
Expand All @@ -177,17 +201,6 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa
}
System.setProperty("native.image.path", finalPath.toAbsolutePath().toString());

if (objcopyExists(env)) {
if (nativeConfig.debug.enabled) {
splitDebugSymbols(finalPath);
}
// Strip debug symbols regardless, because the underlying JDK might contain them
objcopy("--strip-debug", finalPath.toString());
} else {
log.warn("objcopy executable not found in PATH. Debug symbols will not be separated from executable.");
log.warn("That will result in a larger native image with debug symbols embedded in it.");
}

return new NativeImageBuildItem(finalPath);
} catch (Exception e) {
throw new RuntimeException("Failed to build native image", e);
Expand All @@ -199,6 +212,15 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa
}
}

private static List<String> maybeRunInContainer(List<String> containerCommand, List<String> command) {
if (containerCommand != null) {
String joinedCommand = String.join(" ", command).replace("$", "\\$");
command = new ArrayList<>(containerCommand);
command.add(joinedCommand);
}
return command;
}

private List<String> getBuildCommand(NativeConfig nativeConfig,
List<NativeImageSystemPropertyBuildItem> nativeImageProperties, Path outputDir, String runnerJarName,
List<String> nativeImage, String noPIE, boolean isContainerBuild, GraalVM.Version graalVMVersion,
Expand Down Expand Up @@ -389,40 +411,40 @@ private static List<String> getNativeImage(NativeConfig nativeConfig, boolean sh

public static List<String> setupContainerBuild(NativeConfig nativeConfig,
boolean shouldRedirectIO, Path outputDir) {
List<String> nativeImage;
List<String> containerCommand;
final ContainerRuntime containerRuntime = nativeConfig.containerRuntime
.orElseGet(NativeImageBuildStep::detectContainerRuntime);
log.infof("Using %s to run the native image builder", containerRuntime.getExecutableName());
// E.g. "/usr/bin/docker run -v {{PROJECT_DIR}}:/project --rm quarkus/graalvm-native-image"
nativeImage = new ArrayList<>();
containerCommand = new ArrayList<>();

String outputPath = outputDir.toAbsolutePath().toString();
if (SystemUtils.IS_OS_WINDOWS) {
outputPath = FileUtil.translateToVolumePath(outputPath);
}
Collections.addAll(nativeImage, containerRuntime.getExecutableName(), "run", "-v",
Collections.addAll(containerCommand, containerRuntime.getExecutableName(), "run", "-v",
outputPath + ":" + CONTAINER_BUILD_VOLUME_PATH + ":z", "--env", "LANG=C");

if (SystemUtils.IS_OS_LINUX) {
String uid = getLinuxID("-ur");
String gid = getLinuxID("-gr");
if (uid != null && gid != null && !uid.isEmpty() && !gid.isEmpty()) {
Collections.addAll(nativeImage, "--user", uid + ":" + gid);
Collections.addAll(containerCommand, "--user", uid + ":" + gid);
if (containerRuntime == ContainerRuntime.PODMAN) {
// Needed to avoid AccessDeniedExceptions
nativeImage.add("--userns=keep-id");
containerCommand.add("--userns=keep-id");
}
}
}
nativeConfig.containerRuntimeOptions.ifPresent(nativeImage::addAll);
nativeConfig.containerRuntimeOptions.ifPresent(containerCommand::addAll);
if (nativeConfig.debugBuildProcess && nativeConfig.publishDebugBuildProcessPort) {
// publish the debug port onto the host if asked for
nativeImage.add("--publish=" + DEBUG_BUILD_PROCESS_PORT + ":" + DEBUG_BUILD_PROCESS_PORT);
containerCommand.add("--publish=" + DEBUG_BUILD_PROCESS_PORT + ":" + DEBUG_BUILD_PROCESS_PORT);
}
Collections.addAll(nativeImage, "--rm", nativeConfig.builderImage);
Collections.addAll(containerCommand, "--entrypoint", "/bin/bash", "--rm", nativeConfig.builderImage, "-c");

pullBuilderImage(nativeConfig.builderImage, shouldRedirectIO, containerRuntime);
return nativeImage;
return containerCommand;
}

private static void pullBuilderImage(String builderImage, boolean shouldRedirectIO, ContainerRuntime containerRuntime) {
Expand Down Expand Up @@ -719,23 +741,20 @@ private boolean objcopyExists(Map<String, String> env) {
return false;
}

private void splitDebugSymbols(Path executable) {
Path symbols = Paths.get(String.format("%s.debug", executable.toString()));
objcopy("--only-keep-debug", executable.toString(), symbols.toString());
objcopy(String.format("--add-gnu-debuglink=%s", symbols.toString()), executable.toString());
}

private static void objcopy(String... args) {
final List<String> command = new ArrayList<>(args.length + 1);
private static void objcopy(List<String> containerCommand, Path outputDir, String... args) {
List<String> command = new ArrayList<>(args.length + 1);
command.add("objcopy");
command.addAll(Arrays.asList(args));
if (log.isDebugEnabled()) {
log.debugf("Execute %s", String.join(" ", command));
}
command = maybeRunInContainer(containerCommand, command);
log.debugf("Execute %s", String.join(" ", command));
Process process = null;
try {
process = new ProcessBuilder(command).start();
process.waitFor();
final ProcessBuilder processBuilder = new ProcessBuilder(command).directory(outputDir.toFile());
process = processBuilder.start();
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new RuntimeException("objcopy failed with exit code " + exitCode);
}
} catch (IOException | InterruptedException e) {
throw new RuntimeException("Unable to invoke objcopy", e);
} finally {
Expand All @@ -753,6 +772,7 @@ List<RuntimeReinitializedClassBuildItem> graalVmWorkaround(NativeConfig nativeCo
Path outputDir = Paths.get("."); // The path is not important for getting the version
HashMap<String, String> env = new HashMap<>(System.getenv());
boolean isContainerBuild = nativeConfig.containerRuntime.isPresent() || nativeConfig.containerBuild;
List<String> containerCommand = null;
List<String> nativeImage = null;
if (!isContainerBuild) {
nativeImage = getNativeImage(nativeConfig, processInheritIODisabled.isPresent(), outputDir, env);
Expand All @@ -766,9 +786,10 @@ List<RuntimeReinitializedClassBuildItem> graalVmWorkaround(NativeConfig nativeCo
}
}
if (isContainerBuild) {
nativeImage = setupContainerBuild(nativeConfig, processInheritIODisabled.isPresent(), outputDir);
containerCommand = setupContainerBuild(nativeConfig, processInheritIODisabled.isPresent(), outputDir);
nativeImage = Collections.singletonList("native-image");
}
GraalVM.Version version = GraalVM.Version.ofBinary(nativeImage);
GraalVM.Version version = GraalVM.Version.ofBinary(containerCommand, nativeImage);
if (version.isNewerThan(GraalVM.Version.VERSION_20_2)) {
// https://github.com/oracle/graal/issues/2841
return Collections.emptyList();
Expand Down Expand Up @@ -868,13 +889,13 @@ static Version of(Stream<String> lines) {
return UNVERSIONED;
}

private static Version ofBinary(List<String> nativeImage) {
private static Version ofBinary(List<String> containerCommand, List<String> nativeImage) {
final Version graalVMVersion;
try {
List<String> versionCommand = new ArrayList<>(nativeImage);
versionCommand.add("--version");

Process versionProcess = new ProcessBuilder(versionCommand.toArray(new String[0]))
versionCommand = maybeRunInContainer(containerCommand, versionCommand);
Process versionProcess = new ProcessBuilder(versionCommand)
.redirectErrorStream(true)
.start();
versionProcess.waitFor();
Expand Down

0 comments on commit c7e95e5

Please sign in to comment.