diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java index de7083d94b1cd2..b5be5baf330fca 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java @@ -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); @@ -137,6 +138,7 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa if (nativeConfig.cleanupServer && !graalVMVersion.isMandrel()) { List 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()); @@ -145,7 +147,12 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa List 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()); @@ -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); @@ -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); @@ -199,6 +212,15 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa } } + private static List maybeRunInContainer(List containerCommand, List command) { + if (containerCommand != null) { + String joinedCommand = String.join(" ", command).replace("$", "\\$"); + command = new ArrayList<>(containerCommand); + command.add(joinedCommand); + } + return command; + } + private List getBuildCommand(NativeConfig nativeConfig, List nativeImageProperties, Path outputDir, String runnerJarName, List nativeImage, String noPIE, boolean isContainerBuild, GraalVM.Version graalVMVersion, @@ -389,40 +411,40 @@ private static List getNativeImage(NativeConfig nativeConfig, boolean sh public static List setupContainerBuild(NativeConfig nativeConfig, boolean shouldRedirectIO, Path outputDir) { - List nativeImage; + List 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) { @@ -719,23 +741,20 @@ private boolean objcopyExists(Map 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 command = new ArrayList<>(args.length + 1); + private static void objcopy(List containerCommand, Path outputDir, String... args) { + List 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 { @@ -753,6 +772,7 @@ List graalVmWorkaround(NativeConfig nativeCo Path outputDir = nativeImageSourceJarBuildItem.getPath().getParent(); HashMap env = new HashMap<>(System.getenv()); boolean isContainerBuild = nativeConfig.containerRuntime.isPresent() || nativeConfig.containerBuild; + List containerCommand = null; List nativeImage = null; if (!isContainerBuild) { nativeImage = getNativeImage(nativeConfig, processInheritIODisabled.isPresent(), outputDir, env); @@ -766,9 +786,10 @@ List 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(); @@ -868,13 +889,13 @@ static Version of(Stream lines) { return UNVERSIONED; } - private static Version ofBinary(List nativeImage) { + private static Version ofBinary(List containerCommand, List nativeImage) { final Version graalVMVersion; try { List 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();