From 465e04ec704dc02826b679fd999ec0f490ca77a8 Mon Sep 17 00:00:00 2001 From: Jonathan Meier Date: Mon, 21 Dec 2020 17:57:07 +0100 Subject: [PATCH 1/7] No need to pass around a map for the system environment, just use System.getenv --- .../pkg/steps/NativeImageBuildStep.java | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) 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 dd27266b26810..c67b873fc0b64 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 @@ -15,10 +15,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; import java.util.Iterator; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.concurrent.CountDownLatch; @@ -52,7 +50,6 @@ public class NativeImageBuildStep { private static final Logger log = Logger.getLogger(NativeImageBuildStep.class); private static final String DEBUG_BUILD_PROCESS_PORT = "5005"; - private static final String GRAALVM_HOME = "GRAALVM_HOME"; /** * Name of the system property to retrieve JAVA_HOME @@ -99,7 +96,6 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa final String runnerJarName = runnerJar.getFileName().toString(); - HashMap env = new HashMap<>(System.getenv()); List nativeImage; String noPIE = ""; @@ -109,7 +105,7 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa noPIE = detectNoPIE(); } - nativeImage = getNativeImage(nativeConfig, processInheritIODisabled, outputDir, env); + nativeImage = getNativeImage(nativeConfig, processInheritIODisabled, outputDir); final GraalVM.Version graalVMVersion = GraalVM.Version.ofBinary(nativeImage); if (graalVMVersion.isDetected()) { @@ -293,7 +289,7 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa } System.setProperty("native.image.path", finalPath.toAbsolutePath().toString()); - if (objcopyExists(env)) { + if (objcopyExists()) { if (nativeConfig.debug.enabled) { splitDebugSymbols(finalPath); } @@ -317,30 +313,27 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa private static List getNativeImage(NativeConfig nativeConfig, Optional processInheritIODisabled, - Path outputDir, Map env) { + Path outputDir) { boolean isContainerBuild = nativeConfig.containerRuntime.isPresent() || nativeConfig.containerBuild; if (isContainerBuild) { return setupContainerBuild(nativeConfig, processInheritIODisabled, outputDir); } else { Optional graal = nativeConfig.graalvmHome; File java = nativeConfig.javaHome; - if (graal.isPresent()) { - env.put(GRAALVM_HOME, graal.get()); - } if (java == null) { // try system property first - it will be the JAVA_HOME used by the current JVM String home = System.getProperty(JAVA_HOME_SYS); if (home == null) { // No luck, somewhat a odd JVM not enforcing this property // try with the JAVA_HOME environment variable - home = env.get(JAVA_HOME_ENV); + home = System.getenv(JAVA_HOME_ENV); } if (home != null) { java = new File(home); } } - return getNativeImageExecutable(graal, java, env, nativeConfig, processInheritIODisabled, outputDir); + return getNativeImageExecutable(graal, java, nativeConfig, processInheritIODisabled, outputDir); } } @@ -587,7 +580,7 @@ private void checkGraalVMVersion(GraalVM.Version version) { } } - private static List getNativeImageExecutable(Optional graalVmHome, File javaHome, Map env, + private static List getNativeImageExecutable(Optional graalVmHome, File javaHome, NativeConfig nativeConfig, Optional processInheritIODisabled, Path outputDir) { String imageName = SystemUtils.IS_OS_WINDOWS ? "native-image.cmd" : "native-image"; if (graalVmHome.isPresent()) { @@ -605,7 +598,7 @@ private static List getNativeImageExecutable(Optional graalVmHom } // System path - String systemPath = env.get(PATH); + String systemPath = System.getenv(PATH); if (systemPath != null) { String[] pathDirs = systemPath.split(File.pathSeparator); for (String pathDir : pathDirs) { @@ -650,9 +643,9 @@ private static String testGCCArgument(String argument) { return ""; } - private boolean objcopyExists(Map env) { + private boolean objcopyExists() { // System path - String systemPath = env.get(PATH); + String systemPath = System.getenv(PATH); if (systemPath != null) { String[] pathDirs = systemPath.split(File.pathSeparator); for (String pathDir : pathDirs) { From 9fe111afa23d81d1b6f2f7823a8b2ba933ba6d3c Mon Sep 17 00:00:00 2001 From: Jonathan Meier Date: Mon, 21 Dec 2020 17:12:51 +0100 Subject: [PATCH 2/7] Invert if-else branches --- .../quarkus/deployment/pkg/steps/NativeImageBuildStep.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) 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 c67b873fc0b64..7b3690551a873 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 @@ -315,9 +315,7 @@ private static List getNativeImage(NativeConfig nativeConfig, Optional processInheritIODisabled, Path outputDir) { boolean isContainerBuild = nativeConfig.containerRuntime.isPresent() || nativeConfig.containerBuild; - if (isContainerBuild) { - return setupContainerBuild(nativeConfig, processInheritIODisabled, outputDir); - } else { + if (!isContainerBuild) { Optional graal = nativeConfig.graalvmHome; File java = nativeConfig.javaHome; if (java == null) { @@ -335,6 +333,7 @@ private static List getNativeImage(NativeConfig nativeConfig, } return getNativeImageExecutable(graal, java, nativeConfig, processInheritIODisabled, outputDir); } + return setupContainerBuild(nativeConfig, processInheritIODisabled, outputDir); } public static List setupContainerBuild(NativeConfig nativeConfig, From e7c18138c5854d5f58b4a629fd9eaf1e868c802e Mon Sep 17 00:00:00 2001 From: Jonathan Meier Date: Mon, 21 Dec 2020 17:42:08 +0100 Subject: [PATCH 3/7] Move error handling up to avoid calling setupContainerBuild from within getNativeImageExecutable --- .../pkg/steps/NativeImageBuildStep.java | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) 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 7b3690551a873..f413f6117b1f6 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 @@ -331,7 +331,17 @@ private static List getNativeImage(NativeConfig nativeConfig, java = new File(home); } } - return getNativeImageExecutable(graal, java, nativeConfig, processInheritIODisabled, outputDir); + List nativeImageExecutable = getNativeImageExecutable(graal, java); + if (nativeImageExecutable != null) { + return nativeImageExecutable; + } + String executableName = getNativeImageExecutableName(); + String errorMessage = "Cannot find the `" + executableName + + "` in the GRAALVM_HOME, JAVA_HOME and System PATH. Install it using `gu install native-image`"; + if (!SystemUtils.IS_OS_LINUX) { + throw new RuntimeException(errorMessage); + } + log.warn(errorMessage + " Attempting to fall back to container build."); } return setupContainerBuild(nativeConfig, processInheritIODisabled, outputDir); } @@ -579,18 +589,17 @@ private void checkGraalVMVersion(GraalVM.Version version) { } } - private static List getNativeImageExecutable(Optional graalVmHome, File javaHome, - NativeConfig nativeConfig, Optional processInheritIODisabled, Path outputDir) { - String imageName = SystemUtils.IS_OS_WINDOWS ? "native-image.cmd" : "native-image"; + private static List getNativeImageExecutable(Optional graalVmHome, File javaHome) { + String executableName = getNativeImageExecutableName(); if (graalVmHome.isPresent()) { - File file = Paths.get(graalVmHome.get(), "bin", imageName).toFile(); + File file = Paths.get(graalVmHome.get(), "bin", executableName).toFile(); if (file.exists()) { return Collections.singletonList(file.getAbsolutePath()); } } if (javaHome != null) { - File file = new File(javaHome, "bin/" + imageName); + File file = new File(javaHome, "bin/" + executableName); if (file.exists()) { return Collections.singletonList(file.getAbsolutePath()); } @@ -603,7 +612,7 @@ private static List getNativeImageExecutable(Optional graalVmHom for (String pathDir : pathDirs) { File dir = new File(pathDir); if (dir.isDirectory()) { - File file = new File(dir, imageName); + File file = new File(dir, executableName); if (file.exists()) { return Collections.singletonList(file.getAbsolutePath()); } @@ -611,14 +620,11 @@ private static List getNativeImageExecutable(Optional graalVmHom } } - if (SystemUtils.IS_OS_LINUX) { - log.warn("Cannot find the `" + imageName + "` in the GRAALVM_HOME, JAVA_HOME and System " + - "PATH. Install it using `gu install native-image`. Attempting to fall back to docker."); - return setupContainerBuild(nativeConfig, processInheritIODisabled, outputDir); - } else { - throw new RuntimeException("Cannot find the `" + imageName + "` in the GRAALVM_HOME, JAVA_HOME and System " + - "PATH. Install it using `gu install native-image`"); - } + return null; + } + + private static String getNativeImageExecutableName() { + return SystemUtils.IS_OS_WINDOWS ? "native-image.cmd" : "native-image"; } private static String detectNoPIE() { From a9a167bbc9218e7a151bafeef962c1a9fb9e3431 Mon Sep 17 00:00:00 2001 From: Jonathan Meier Date: Mon, 21 Dec 2020 18:08:35 +0100 Subject: [PATCH 4/7] Move detection of java home path into getNativeImageExecutable --- .../pkg/steps/NativeImageBuildStep.java | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) 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 f413f6117b1f6..306b75bc4a683 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 @@ -316,22 +316,7 @@ private static List getNativeImage(NativeConfig nativeConfig, Path outputDir) { boolean isContainerBuild = nativeConfig.containerRuntime.isPresent() || nativeConfig.containerBuild; if (!isContainerBuild) { - Optional graal = nativeConfig.graalvmHome; - File java = nativeConfig.javaHome; - if (java == null) { - // try system property first - it will be the JAVA_HOME used by the current JVM - String home = System.getProperty(JAVA_HOME_SYS); - if (home == null) { - // No luck, somewhat a odd JVM not enforcing this property - // try with the JAVA_HOME environment variable - home = System.getenv(JAVA_HOME_ENV); - } - - if (home != null) { - java = new File(home); - } - } - List nativeImageExecutable = getNativeImageExecutable(graal, java); + List nativeImageExecutable = getNativeImageExecutable(nativeConfig); if (nativeImageExecutable != null) { return nativeImageExecutable; } @@ -589,15 +574,30 @@ private void checkGraalVMVersion(GraalVM.Version version) { } } - private static List getNativeImageExecutable(Optional graalVmHome, File javaHome) { + private static List getNativeImageExecutable(NativeConfig nativeConfig) { String executableName = getNativeImageExecutableName(); - if (graalVmHome.isPresent()) { - File file = Paths.get(graalVmHome.get(), "bin", executableName).toFile(); + if (nativeConfig.graalvmHome.isPresent()) { + File file = Paths.get(nativeConfig.graalvmHome.get(), "bin", executableName).toFile(); if (file.exists()) { return Collections.singletonList(file.getAbsolutePath()); } } + File javaHome = nativeConfig.javaHome; + if (javaHome == null) { + // try system property first - it will be the JAVA_HOME used by the current JVM + String home = System.getProperty(JAVA_HOME_SYS); + if (home == null) { + // No luck, somewhat a odd JVM not enforcing this property + // try with the JAVA_HOME environment variable + home = System.getenv(JAVA_HOME_ENV); + } + + if (home != null) { + javaHome = new File(home); + } + } + if (javaHome != null) { File file = new File(javaHome, "bin/" + executableName); if (file.exists()) { From 24f83c195e38ecbed1d61db192599bb340871dbc Mon Sep 17 00:00:00 2001 From: Jonathan Meier Date: Mon, 21 Dec 2020 20:05:08 +0100 Subject: [PATCH 5/7] Introduce native image build runners to separate local and containerized native image builds --- .../NativeImageBuildContainerRunner.java | 156 +++++++++++++++ .../steps/NativeImageBuildLocalRunner.java | 40 ++++ .../pkg/steps/NativeImageBuildRunner.java | 62 ++++++ .../pkg/steps/NativeImageBuildStep.java | 182 ++---------------- 4 files changed, 275 insertions(+), 165 deletions(-) create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildContainerRunner.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildLocalRunner.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildRunner.java diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildContainerRunner.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildContainerRunner.java new file mode 100644 index 0000000000000..be2a75f22bfce --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildContainerRunner.java @@ -0,0 +1,156 @@ +package io.quarkus.deployment.pkg.steps; + +import static io.quarkus.deployment.pkg.steps.LinuxIDUtil.getLinuxID; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Stream; + +import org.apache.commons.lang3.SystemUtils; +import org.jboss.logging.Logger; + +import io.quarkus.deployment.pkg.NativeConfig; +import io.quarkus.deployment.util.FileUtil; +import io.quarkus.deployment.util.ProcessUtil; + +public class NativeImageBuildContainerRunner extends NativeImageBuildRunner { + + private static final Logger log = Logger.getLogger(NativeImageBuildContainerRunner.class); + + private final NativeConfig nativeConfig; + private final NativeConfig.ContainerRuntime containerRuntime; + private final String[] baseContainerRuntimeArgs; + private final String outputPath; + + public NativeImageBuildContainerRunner(NativeConfig nativeConfig, Path outputDir) { + this.nativeConfig = nativeConfig; + containerRuntime = nativeConfig.containerRuntime.orElseGet(NativeImageBuildContainerRunner::detectContainerRuntime); + log.infof("Using %s to run the native image builder", containerRuntime.getExecutableName()); + + List containerRuntimeArgs = new ArrayList<>(); + Collections.addAll(containerRuntimeArgs, "run", "--env", "LANG=C"); + + String outputPath = outputDir.toAbsolutePath().toString(); + if (SystemUtils.IS_OS_WINDOWS) { + outputPath = FileUtil.translateToVolumePath(outputPath); + } + this.outputPath = outputPath; + + if (SystemUtils.IS_OS_LINUX) { + String uid = getLinuxID("-ur"); + String gid = getLinuxID("-gr"); + if (uid != null && gid != null && !uid.isEmpty() && !gid.isEmpty()) { + Collections.addAll(containerRuntimeArgs, "--user", uid + ":" + gid); + if (containerRuntime == NativeConfig.ContainerRuntime.PODMAN) { + // Needed to avoid AccessDeniedExceptions + containerRuntimeArgs.add("--userns=keep-id"); + } + } + } + Collections.addAll(containerRuntimeArgs, "--rm"); + this.baseContainerRuntimeArgs = containerRuntimeArgs.toArray(new String[0]); + } + + @Override + public void setup(boolean processInheritIODisabled) { + if (containerRuntime == NativeConfig.ContainerRuntime.DOCKER + || containerRuntime == NativeConfig.ContainerRuntime.PODMAN) { + // we pull the docker image in order to give users an indication of which step the process is at + // it's not strictly necessary we do this, however if we don't the subsequent version command + // will appear to block and no output will be shown + log.info("Checking image status " + nativeConfig.builderImage); + Process pullProcess = null; + try { + final ProcessBuilder pb = new ProcessBuilder( + Arrays.asList(containerRuntime.getExecutableName(), "pull", nativeConfig.builderImage)); + pullProcess = ProcessUtil.launchProcess(pb, processInheritIODisabled); + pullProcess.waitFor(); + } catch (IOException | InterruptedException e) { + throw new RuntimeException("Failed to pull builder image " + nativeConfig.builderImage, e); + } finally { + if (pullProcess != null) { + pullProcess.destroy(); + } + } + } + } + + @Override + protected String[] getGraalVMVersionCommand(List args) { + return buildCommand(Collections.emptyList(), args); + } + + @Override + protected String[] getBuildCommand(List args) { + List containerRuntimeArgs = new ArrayList<>(); + Collections.addAll(containerRuntimeArgs, "-v", + outputPath + ":" + NativeImageBuildStep.CONTAINER_BUILD_VOLUME_PATH + ":z"); + nativeConfig.containerRuntimeOptions.ifPresent(containerRuntimeArgs::addAll); + if (nativeConfig.debugBuildProcess && nativeConfig.publishDebugBuildProcessPort) { + // publish the debug port onto the host if asked for + containerRuntimeArgs.add("--publish=" + NativeImageBuildStep.DEBUG_BUILD_PROCESS_PORT + ":" + + NativeImageBuildStep.DEBUG_BUILD_PROCESS_PORT); + } + return buildCommand(containerRuntimeArgs, args); + } + + private String[] buildCommand(List containerRuntimeArgs, List command) { + return Stream + .of(Stream.of(containerRuntime.getExecutableName()), Stream.of(baseContainerRuntimeArgs), + containerRuntimeArgs.stream(), Stream.of(nativeConfig.builderImage), command.stream()) + .flatMap(Function.identity()).toArray(String[]::new); + } + + /** + * @return {@link NativeConfig.ContainerRuntime#DOCKER} if it's available, or {@link NativeConfig.ContainerRuntime#PODMAN} + * if the podman + * executable exists in the environment or if the docker executable is an alias to podman + * @throws IllegalStateException if no container runtime was found to build the image + */ + private static NativeConfig.ContainerRuntime detectContainerRuntime() { + // Docker version 19.03.14, build 5eb3275d40 + String dockerVersionOutput = getVersionOutputFor(NativeConfig.ContainerRuntime.DOCKER); + boolean dockerAvailable = dockerVersionOutput.contains("Docker version"); + // Check if Podman is installed + // podman version 2.1.1 + String podmanVersionOutput = getVersionOutputFor(NativeConfig.ContainerRuntime.PODMAN); + boolean podmanAvailable = podmanVersionOutput.startsWith("podman version"); + if (dockerAvailable) { + // Check if "docker" is an alias to "podman" + if (dockerVersionOutput.equals(podmanVersionOutput)) { + return NativeConfig.ContainerRuntime.PODMAN; + } + return NativeConfig.ContainerRuntime.DOCKER; + } else if (podmanAvailable) { + return NativeConfig.ContainerRuntime.PODMAN; + } else { + throw new IllegalStateException("No container runtime was found to run the native image builder"); + } + } + + private static String getVersionOutputFor(NativeConfig.ContainerRuntime containerRuntime) { + Process versionProcess = null; + try { + ProcessBuilder pb = new ProcessBuilder(containerRuntime.getExecutableName(), "--version") + .redirectErrorStream(true); + versionProcess = pb.start(); + versionProcess.waitFor(); + return new String(FileUtil.readFileContents(versionProcess.getInputStream()), StandardCharsets.UTF_8); + } catch (IOException | InterruptedException e) { + // If an exception is thrown in the process, just return an empty String + log.debugf(e, "Failure to read version output from %s", containerRuntime.getExecutableName()); + return ""; + } finally { + if (versionProcess != null) { + versionProcess.destroy(); + } + } + } + +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildLocalRunner.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildLocalRunner.java new file mode 100644 index 0000000000000..99de90bc4259d --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildLocalRunner.java @@ -0,0 +1,40 @@ +package io.quarkus.deployment.pkg.steps; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.stream.Stream; + +import io.quarkus.deployment.util.ProcessUtil; + +public class NativeImageBuildLocalRunner extends NativeImageBuildRunner { + + private final String nativeImageExecutable; + + public NativeImageBuildLocalRunner(String nativeImageExecutable) { + this.nativeImageExecutable = nativeImageExecutable; + } + + @Override + public void cleanupServer(File outputDir, boolean processInheritIODisabled) throws InterruptedException, IOException { + final ProcessBuilder pb = new ProcessBuilder(nativeImageExecutable, "--server-shutdown"); + pb.directory(outputDir); + final Process process = ProcessUtil.launchProcess(pb, processInheritIODisabled); + process.waitFor(); + } + + @Override + protected String[] getGraalVMVersionCommand(List args) { + return buildCommand(args); + } + + @Override + protected String[] getBuildCommand(List args) { + return buildCommand(args); + } + + private String[] buildCommand(List args) { + return Stream.concat(Stream.of(nativeImageExecutable), args.stream()).toArray(String[]::new); + } + +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildRunner.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildRunner.java new file mode 100644 index 0000000000000..f09c94133dcc5 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildRunner.java @@ -0,0 +1,62 @@ +package io.quarkus.deployment.pkg.steps; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import io.quarkus.deployment.pkg.steps.NativeImageBuildStep.GraalVM; +import io.quarkus.deployment.util.ProcessUtil; + +public abstract class NativeImageBuildRunner { + + public GraalVM.Version getGraalVMVersion() { + final GraalVM.Version graalVMVersion; + try { + String[] versionCommand = getGraalVMVersionCommand(Collections.singletonList("--version")); + Process versionProcess = new ProcessBuilder(versionCommand) + .redirectErrorStream(true) + .start(); + versionProcess.waitFor(); + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(versionProcess.getInputStream(), StandardCharsets.UTF_8))) { + graalVMVersion = GraalVM.Version.of(reader.lines()); + } + } catch (Exception e) { + throw new RuntimeException("Failed to get GraalVM version", e); + } + return graalVMVersion; + } + + public void setup(boolean processInheritIODisabled) { + } + + public void cleanupServer(File outputDir, boolean processInheritIODisabled) throws InterruptedException, IOException { + } + + public int build(List args, Path outputDir, boolean processInheritIODisabled) + throws InterruptedException, IOException { + CountDownLatch errorReportLatch = new CountDownLatch(1); + final ProcessBuilder processBuilder = new ProcessBuilder(getBuildCommand(args)) + .directory(outputDir.toFile()); + final Process process = ProcessUtil.launchProcessStreamStdOut(processBuilder, processInheritIODisabled); + ExecutorService executor = Executors.newSingleThreadExecutor(); + executor.submit(new ErrorReplacingProcessReader(process.getErrorStream(), outputDir.resolve("reports").toFile(), + errorReportLatch)); + executor.shutdown(); + errorReportLatch.await(); + return process.waitFor(); + } + + protected abstract String[] getGraalVMVersionCommand(List args); + + protected abstract String[] getBuildCommand(List args); + +} 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 306b75bc4a683..037137af02367 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 @@ -1,13 +1,8 @@ package io.quarkus.deployment.pkg.steps; -import static io.quarkus.deployment.pkg.steps.LinuxIDUtil.getLinuxID; - -import java.io.BufferedReader; import java.io.File; import java.io.IOException; -import java.io.InputStreamReader; import java.io.UncheckedIOException; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -19,9 +14,6 @@ import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Stream; @@ -35,7 +27,6 @@ import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.nativeimage.NativeImageSystemPropertyBuildItem; import io.quarkus.deployment.pkg.NativeConfig; -import io.quarkus.deployment.pkg.NativeConfig.ContainerRuntime; import io.quarkus.deployment.pkg.PackageConfig; import io.quarkus.deployment.pkg.builditem.ArtifactResultBuildItem; import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; @@ -43,13 +34,11 @@ import io.quarkus.deployment.pkg.builditem.NativeImageSourceJarBuildItem; import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem; import io.quarkus.deployment.pkg.builditem.ProcessInheritIODisabled; -import io.quarkus.deployment.util.FileUtil; -import io.quarkus.deployment.util.ProcessUtil; public class NativeImageBuildStep { private static final Logger log = Logger.getLogger(NativeImageBuildStep.class); - private static final String DEBUG_BUILD_PROCESS_PORT = "5005"; + public static final String DEBUG_BUILD_PROCESS_PORT = "5005"; /** * Name of the system property to retrieve JAVA_HOME @@ -68,7 +57,7 @@ public class NativeImageBuildStep { private static final int OOM_ERROR_VALUE = 137; private static final String QUARKUS_XMX_PROPERTY = "quarkus.native.native-image-xmx"; - private static final String CONTAINER_BUILD_VOLUME_PATH = "/project"; + public static final String CONTAINER_BUILD_VOLUME_PATH = "/project"; private static final String TRUST_STORE_SYSTEM_PROPERTY_MARKER = "-Djavax.net.ssl.trustStore="; private static final String MOVED_TRUST_STORE_NAME = "trustStore"; public static final String APP_SOURCES = "app-sources"; @@ -96,8 +85,6 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa final String runnerJarName = runnerJar.getFileName().toString(); - List nativeImage; - String noPIE = ""; boolean isContainerBuild = nativeConfig.containerRuntime.isPresent() || nativeConfig.containerBuild; @@ -105,8 +92,9 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa noPIE = detectNoPIE(); } - nativeImage = getNativeImage(nativeConfig, processInheritIODisabled, outputDir); - final GraalVM.Version graalVMVersion = GraalVM.Version.ofBinary(nativeImage); + NativeImageBuildRunner buildRunner = getNativeImageBuildRunner(nativeConfig, outputDir); + buildRunner.setup(processInheritIODisabled.isPresent()); + final GraalVM.Version graalVMVersion = buildRunner.getGraalVMVersion(); if (graalVMVersion.isDetected()) { checkGraalVMVersion(graalVMVersion); @@ -115,14 +103,9 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa } try { - List command = new ArrayList<>(nativeImage); + List command = new ArrayList<>(); if (nativeConfig.cleanupServer && !graalVMVersion.isMandrel()) { - List cleanup = new ArrayList<>(nativeImage); - cleanup.add("--server-shutdown"); - final ProcessBuilder pb = new ProcessBuilder(cleanup.toArray(new String[0])); - pb.directory(outputDir.toFile()); - final Process process = ProcessUtil.launchProcess(pb, processInheritIODisabled.isPresent()); - process.waitFor(); + buildRunner.cleanupServer(outputDir.toFile(), processInheritIODisabled.isPresent()); } boolean enableSslNative = false; for (NativeImageSystemPropertyBuildItem prop : nativeImageProperties) { @@ -260,15 +243,7 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa command.add(executableName); log.info(String.join(" ", command).replace("$", "\\$")); - CountDownLatch errorReportLatch = new CountDownLatch(1); - final ProcessBuilder processBuilder = new ProcessBuilder(command).directory(outputDir.toFile()); - final Process process = ProcessUtil.launchProcessStreamStdOut(processBuilder, processInheritIODisabled.isPresent()); - ExecutorService executor = Executors.newSingleThreadExecutor(); - executor.submit(new ErrorReplacingProcessReader(process.getErrorStream(), outputDir.resolve("reports").toFile(), - errorReportLatch)); - executor.shutdown(); - errorReportLatch.await(); - int exitCode = process.waitFor(); + int exitCode = buildRunner.build(command, outputDir, processInheritIODisabled.isPresent()); if (exitCode != 0) { throw imageGenerationFailed(exitCode, command); } @@ -311,14 +286,12 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa } } - private static List getNativeImage(NativeConfig nativeConfig, - Optional processInheritIODisabled, - Path outputDir) { + private static NativeImageBuildRunner getNativeImageBuildRunner(NativeConfig nativeConfig, Path outputDir) { boolean isContainerBuild = nativeConfig.containerRuntime.isPresent() || nativeConfig.containerBuild; if (!isContainerBuild) { - List nativeImageExecutable = getNativeImageExecutable(nativeConfig); - if (nativeImageExecutable != null) { - return nativeImageExecutable; + NativeImageBuildLocalRunner localRunner = getNativeImageBuildLocalRunner(nativeConfig); + if (localRunner != null) { + return localRunner; } String executableName = getNativeImageExecutableName(); String errorMessage = "Cannot find the `" + executableName @@ -328,108 +301,7 @@ private static List getNativeImage(NativeConfig nativeConfig, } log.warn(errorMessage + " Attempting to fall back to container build."); } - return setupContainerBuild(nativeConfig, processInheritIODisabled, outputDir); - } - - public static List setupContainerBuild(NativeConfig nativeConfig, - Optional processInheritIODisabled, Path outputDir) { - List nativeImage; - 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<>(); - - String outputPath = outputDir.toAbsolutePath().toString(); - if (SystemUtils.IS_OS_WINDOWS) { - outputPath = FileUtil.translateToVolumePath(outputPath); - } - Collections.addAll(nativeImage, 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); - if (containerRuntime == ContainerRuntime.PODMAN) { - // Needed to avoid AccessDeniedExceptions - nativeImage.add("--userns=keep-id"); - } - } - } - nativeConfig.containerRuntimeOptions.ifPresent(nativeImage::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); - } - Collections.addAll(nativeImage, "--rm", nativeConfig.builderImage); - - if (containerRuntime == ContainerRuntime.DOCKER || containerRuntime == ContainerRuntime.PODMAN) { - // we pull the docker image in order to give users an indication of which step the process is at - // it's not strictly necessary we do this, however if we don't the subsequent version command - // will appear to block and no output will be shown - log.info("Checking image status " + nativeConfig.builderImage); - Process pullProcess = null; - try { - final ProcessBuilder pb = new ProcessBuilder( - Arrays.asList(containerRuntime.getExecutableName(), "pull", nativeConfig.builderImage)); - pullProcess = ProcessUtil.launchProcess(pb, processInheritIODisabled.isPresent()); - pullProcess.waitFor(); - } catch (IOException | InterruptedException e) { - throw new RuntimeException("Failed to pull builder image " + nativeConfig.builderImage, e); - } finally { - if (pullProcess != null) { - pullProcess.destroy(); - } - } - } - return nativeImage; - } - - /** - * @return {@link ContainerRuntime#DOCKER} if it's available, or {@link ContainerRuntime#PODMAN} if the podman - * executable exists in the environment or if the docker executable is an alias to podman - * @throws IllegalStateException if no container runtime was found to build the image - */ - private static ContainerRuntime detectContainerRuntime() { - // Docker version 19.03.14, build 5eb3275d40 - String dockerVersionOutput = getVersionOutputFor(ContainerRuntime.DOCKER); - boolean dockerAvailable = dockerVersionOutput.contains("Docker version"); - // Check if Podman is installed - // podman version 2.1.1 - String podmanVersionOutput = getVersionOutputFor(ContainerRuntime.PODMAN); - boolean podmanAvailable = podmanVersionOutput.startsWith("podman version"); - if (dockerAvailable) { - // Check if "docker" is an alias to "podman" - if (dockerVersionOutput.equals(podmanVersionOutput)) { - return ContainerRuntime.PODMAN; - } - return ContainerRuntime.DOCKER; - } else if (podmanAvailable) { - return ContainerRuntime.PODMAN; - } else { - throw new IllegalStateException("No container runtime was found to run the native image builder"); - } - } - - private static String getVersionOutputFor(ContainerRuntime containerRuntime) { - Process versionProcess = null; - try { - ProcessBuilder pb = new ProcessBuilder(containerRuntime.getExecutableName(), "--version") - .redirectErrorStream(true); - versionProcess = pb.start(); - versionProcess.waitFor(); - return new String(FileUtil.readFileContents(versionProcess.getInputStream()), StandardCharsets.UTF_8); - } catch (IOException | InterruptedException e) { - // If an exception is thrown in the process, just return an empty String - log.debugf(e, "Failure to read version output from %s", containerRuntime.getExecutableName()); - return ""; - } finally { - if (versionProcess != null) { - versionProcess.destroy(); - } - } + return new NativeImageBuildContainerRunner(nativeConfig, outputDir); } private void copyJarSourcesToLib(OutputTargetBuildItem outputTargetBuildItem, @@ -574,12 +446,12 @@ private void checkGraalVMVersion(GraalVM.Version version) { } } - private static List getNativeImageExecutable(NativeConfig nativeConfig) { + private static NativeImageBuildLocalRunner getNativeImageBuildLocalRunner(NativeConfig nativeConfig) { String executableName = getNativeImageExecutableName(); if (nativeConfig.graalvmHome.isPresent()) { File file = Paths.get(nativeConfig.graalvmHome.get(), "bin", executableName).toFile(); if (file.exists()) { - return Collections.singletonList(file.getAbsolutePath()); + return new NativeImageBuildLocalRunner(file.getAbsolutePath()); } } @@ -601,7 +473,7 @@ private static List getNativeImageExecutable(NativeConfig nativeConfig) if (javaHome != null) { File file = new File(javaHome, "bin/" + executableName); if (file.exists()) { - return Collections.singletonList(file.getAbsolutePath()); + return new NativeImageBuildLocalRunner(file.getAbsolutePath()); } } @@ -614,7 +486,7 @@ private static List getNativeImageExecutable(NativeConfig nativeConfig) if (dir.isDirectory()) { File file = new File(dir, executableName); if (file.exists()) { - return Collections.singletonList(file.getAbsolutePath()); + return new NativeImageBuildLocalRunner(file.getAbsolutePath()); } } } @@ -784,26 +656,6 @@ static Version of(Stream lines) { return UNVERSIONED; } - private static Version ofBinary(List nativeImage) { - final Version graalVMVersion; - try { - List versionCommand = new ArrayList<>(nativeImage); - versionCommand.add("--version"); - - Process versionProcess = new ProcessBuilder(versionCommand.toArray(new String[0])) - .redirectErrorStream(true) - .start(); - versionProcess.waitFor(); - try (BufferedReader reader = new BufferedReader( - new InputStreamReader(versionProcess.getInputStream(), StandardCharsets.UTF_8))) { - graalVMVersion = of(reader.lines()); - } - } catch (Exception e) { - throw new RuntimeException("Failed to get GraalVM version", e); - } - return graalVMVersion; - } - private static boolean isSnapshot(String s) { return s == null; } From fb7b257419c0b3c74ee2657ab33742ccebf62b75 Mon Sep 17 00:00:00 2001 From: Jonathan Meier Date: Tue, 22 Dec 2020 02:11:43 +0100 Subject: [PATCH 6/7] Introduce runner for building native images on remote container hosts --- .../quarkus/deployment/pkg/NativeConfig.java | 6 +++ .../NativeImageBuildContainerRunner.java | 29 +++++----- .../NativeImageBuildLocalContainerRunner.java | 29 ++++++++++ ...NativeImageBuildRemoteContainerRunner.java | 53 +++++++++++++++++++ .../pkg/steps/NativeImageBuildRunner.java | 31 +++++++---- .../pkg/steps/NativeImageBuildStep.java | 27 ++++++---- 6 files changed, 138 insertions(+), 37 deletions(-) create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildLocalContainerRunner.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildRemoteContainerRunner.java diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java index 519f056968394..548483363da16 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java @@ -157,6 +157,12 @@ public class NativeConfig { @ConfigItem public boolean containerBuild; + /** + * If this build is done using a remote docker daemon. + */ + @ConfigItem + public boolean remoteContainerBuild; + /** * The docker image to use to do the image build */ diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildContainerRunner.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildContainerRunner.java index be2a75f22bfce..0e282fbd4eb48 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildContainerRunner.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildContainerRunner.java @@ -19,14 +19,14 @@ import io.quarkus.deployment.util.FileUtil; import io.quarkus.deployment.util.ProcessUtil; -public class NativeImageBuildContainerRunner extends NativeImageBuildRunner { +public abstract class NativeImageBuildContainerRunner extends NativeImageBuildRunner { private static final Logger log = Logger.getLogger(NativeImageBuildContainerRunner.class); private final NativeConfig nativeConfig; - private final NativeConfig.ContainerRuntime containerRuntime; + protected final NativeConfig.ContainerRuntime containerRuntime; private final String[] baseContainerRuntimeArgs; - private final String outputPath; + protected final String outputPath; public NativeImageBuildContainerRunner(NativeConfig nativeConfig, Path outputDir) { this.nativeConfig = nativeConfig; @@ -34,13 +34,9 @@ public NativeImageBuildContainerRunner(NativeConfig nativeConfig, Path outputDir log.infof("Using %s to run the native image builder", containerRuntime.getExecutableName()); List containerRuntimeArgs = new ArrayList<>(); - Collections.addAll(containerRuntimeArgs, "run", "--env", "LANG=C"); + Collections.addAll(containerRuntimeArgs, "--env", "LANG=C"); - String outputPath = outputDir.toAbsolutePath().toString(); - if (SystemUtils.IS_OS_WINDOWS) { - outputPath = FileUtil.translateToVolumePath(outputPath); - } - this.outputPath = outputPath; + outputPath = outputDir == null ? null : outputDir.toAbsolutePath().toString(); if (SystemUtils.IS_OS_LINUX) { String uid = getLinuxID("-ur"); @@ -53,7 +49,6 @@ public NativeImageBuildContainerRunner(NativeConfig nativeConfig, Path outputDir } } } - Collections.addAll(containerRuntimeArgs, "--rm"); this.baseContainerRuntimeArgs = containerRuntimeArgs.toArray(new String[0]); } @@ -83,26 +78,28 @@ public void setup(boolean processInheritIODisabled) { @Override protected String[] getGraalVMVersionCommand(List args) { - return buildCommand(Collections.emptyList(), args); + return buildCommand("run", Collections.singletonList("--rm"), args); } @Override protected String[] getBuildCommand(List args) { + return buildCommand("run", getContainerRuntimeBuildArgs(), args); + } + + protected List getContainerRuntimeBuildArgs() { List containerRuntimeArgs = new ArrayList<>(); - Collections.addAll(containerRuntimeArgs, "-v", - outputPath + ":" + NativeImageBuildStep.CONTAINER_BUILD_VOLUME_PATH + ":z"); nativeConfig.containerRuntimeOptions.ifPresent(containerRuntimeArgs::addAll); if (nativeConfig.debugBuildProcess && nativeConfig.publishDebugBuildProcessPort) { // publish the debug port onto the host if asked for containerRuntimeArgs.add("--publish=" + NativeImageBuildStep.DEBUG_BUILD_PROCESS_PORT + ":" + NativeImageBuildStep.DEBUG_BUILD_PROCESS_PORT); } - return buildCommand(containerRuntimeArgs, args); + return containerRuntimeArgs; } - private String[] buildCommand(List containerRuntimeArgs, List command) { + protected String[] buildCommand(String dockerCmd, List containerRuntimeArgs, List command) { return Stream - .of(Stream.of(containerRuntime.getExecutableName()), Stream.of(baseContainerRuntimeArgs), + .of(Stream.of(containerRuntime.getExecutableName()), Stream.of(dockerCmd), Stream.of(baseContainerRuntimeArgs), containerRuntimeArgs.stream(), Stream.of(nativeConfig.builderImage), command.stream()) .flatMap(Function.identity()).toArray(String[]::new); } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildLocalContainerRunner.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildLocalContainerRunner.java new file mode 100644 index 0000000000000..301ff34d40e2f --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildLocalContainerRunner.java @@ -0,0 +1,29 @@ +package io.quarkus.deployment.pkg.steps; + +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; + +import org.apache.commons.lang3.SystemUtils; + +import io.quarkus.deployment.pkg.NativeConfig; +import io.quarkus.deployment.util.FileUtil; + +public class NativeImageBuildLocalContainerRunner extends NativeImageBuildContainerRunner { + + public NativeImageBuildLocalContainerRunner(NativeConfig nativeConfig, Path outputDir) { + super(nativeConfig, outputDir); + } + + @Override + protected List getContainerRuntimeBuildArgs() { + List containerRuntimeArgs = super.getContainerRuntimeBuildArgs(); + String volumeOutputPath = outputPath; + if (SystemUtils.IS_OS_WINDOWS) { + volumeOutputPath = FileUtil.translateToVolumePath(volumeOutputPath); + } + Collections.addAll(containerRuntimeArgs, "--rm", "-v", + volumeOutputPath + ":" + NativeImageBuildStep.CONTAINER_BUILD_VOLUME_PATH + ":z"); + return containerRuntimeArgs; + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildRemoteContainerRunner.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildRemoteContainerRunner.java new file mode 100644 index 0000000000000..19fce431f0693 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildRemoteContainerRunner.java @@ -0,0 +1,53 @@ +package io.quarkus.deployment.pkg.steps; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.Path; +import java.util.List; + +import io.quarkus.deployment.pkg.NativeConfig; + +public class NativeImageBuildRemoteContainerRunner extends NativeImageBuildContainerRunner { + + private final String nativeImageName; + private String containerId; + + public NativeImageBuildRemoteContainerRunner(NativeConfig nativeConfig, Path outputDir, String nativeImageName) { + super(nativeConfig, outputDir); + this.nativeImageName = nativeImageName; + } + + @Override + protected void preBuild(List buildArgs) throws InterruptedException, IOException { + List containerRuntimeArgs = getContainerRuntimeBuildArgs(); + String[] createContainerCommand = buildCommand("create", containerRuntimeArgs, buildArgs); + Process createContainerProcess = new ProcessBuilder(createContainerCommand).start(); + createContainerProcess.waitFor(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(createContainerProcess.getInputStream()))) { + containerId = reader.readLine(); + } + String[] copyCommand = new String[] { containerRuntime.getExecutableName(), "cp", outputPath + "/.", + containerId + ":" + NativeImageBuildStep.CONTAINER_BUILD_VOLUME_PATH }; + Process copyProcess = new ProcessBuilder(copyCommand).start(); + copyProcess.waitFor(); + super.preBuild(buildArgs); + } + + @Override + protected String[] getBuildCommand(List args) { + return new String[] { containerRuntime.getExecutableName(), "start", "--attach", containerId }; + } + + @Override + protected void postBuild() throws InterruptedException, IOException { + String[] copyCommand = new String[] { containerRuntime.getExecutableName(), "cp", + containerId + ":" + NativeImageBuildStep.CONTAINER_BUILD_VOLUME_PATH + "/" + nativeImageName, outputPath }; + Process copyProcess = new ProcessBuilder(copyCommand).start(); + copyProcess.waitFor(); + String[] removeCommand = new String[] { containerRuntime.getExecutableName(), "container", "rm", "--volumes", + containerId }; + Process removeProcess = new ProcessBuilder(removeCommand).start(); + removeProcess.waitFor(); + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildRunner.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildRunner.java index f09c94133dcc5..af492ea1458cb 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildRunner.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildRunner.java @@ -43,20 +43,31 @@ public void cleanupServer(File outputDir, boolean processInheritIODisabled) thro public int build(List args, Path outputDir, boolean processInheritIODisabled) throws InterruptedException, IOException { - CountDownLatch errorReportLatch = new CountDownLatch(1); - final ProcessBuilder processBuilder = new ProcessBuilder(getBuildCommand(args)) - .directory(outputDir.toFile()); - final Process process = ProcessUtil.launchProcessStreamStdOut(processBuilder, processInheritIODisabled); - ExecutorService executor = Executors.newSingleThreadExecutor(); - executor.submit(new ErrorReplacingProcessReader(process.getErrorStream(), outputDir.resolve("reports").toFile(), - errorReportLatch)); - executor.shutdown(); - errorReportLatch.await(); - return process.waitFor(); + preBuild(args); + try { + CountDownLatch errorReportLatch = new CountDownLatch(1); + final ProcessBuilder processBuilder = new ProcessBuilder(getBuildCommand(args)) + .directory(outputDir.toFile()); + final Process process = ProcessUtil.launchProcessStreamStdOut(processBuilder, processInheritIODisabled); + ExecutorService executor = Executors.newSingleThreadExecutor(); + executor.submit(new ErrorReplacingProcessReader(process.getErrorStream(), outputDir.resolve("reports").toFile(), + errorReportLatch)); + executor.shutdown(); + errorReportLatch.await(); + return process.waitFor(); + } finally { + postBuild(); + } } protected abstract String[] getGraalVMVersionCommand(List args); protected abstract String[] getBuildCommand(List args); + protected void preBuild(List buildArgs) throws IOException, InterruptedException { + } + + protected void postBuild() throws InterruptedException, IOException { + } + } 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 037137af02367..092d11e68f267 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 @@ -92,7 +92,13 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa noPIE = detectNoPIE(); } - NativeImageBuildRunner buildRunner = getNativeImageBuildRunner(nativeConfig, outputDir); + String nativeImageName = outputTargetBuildItem.getBaseName() + packageConfig.runnerSuffix; + if (SystemUtils.IS_OS_WINDOWS && !(isContainerBuild)) { + //once image is generated it gets added .exe on Windows + nativeImageName = nativeImageName + ".exe"; + } + + NativeImageBuildRunner buildRunner = getNativeImageBuildRunner(nativeConfig, outputDir, nativeImageName); buildRunner.setup(processInheritIODisabled.isPresent()); final GraalVM.Version graalVMVersion = buildRunner.getGraalVMVersion(); @@ -239,20 +245,15 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa command.add("-H:+DashboardAll"); } - String executableName = outputTargetBuildItem.getBaseName() + packageConfig.runnerSuffix; - command.add(executableName); + command.add(nativeImageName); log.info(String.join(" ", command).replace("$", "\\$")); int exitCode = buildRunner.build(command, outputDir, processInheritIODisabled.isPresent()); if (exitCode != 0) { throw imageGenerationFailed(exitCode, command); } - if (SystemUtils.IS_OS_WINDOWS && !(isContainerBuild)) { - //once image is generated it gets added .exe on Windows - executableName = executableName + ".exe"; - } - Path generatedImage = outputDir.resolve(executableName); - Path finalPath = outputTargetBuildItem.getOutputDirectory().resolve(executableName); + Path generatedImage = outputDir.resolve(nativeImageName); + Path finalPath = outputTargetBuildItem.getOutputDirectory().resolve(nativeImageName); IoUtils.copy(generatedImage, finalPath); Files.delete(generatedImage); if (nativeConfig.debug.enabled) { @@ -286,7 +287,8 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa } } - private static NativeImageBuildRunner getNativeImageBuildRunner(NativeConfig nativeConfig, Path outputDir) { + private static NativeImageBuildRunner getNativeImageBuildRunner(NativeConfig nativeConfig, Path outputDir, + String nativeImageName) { boolean isContainerBuild = nativeConfig.containerRuntime.isPresent() || nativeConfig.containerBuild; if (!isContainerBuild) { NativeImageBuildLocalRunner localRunner = getNativeImageBuildLocalRunner(nativeConfig); @@ -301,7 +303,10 @@ private static NativeImageBuildRunner getNativeImageBuildRunner(NativeConfig nat } log.warn(errorMessage + " Attempting to fall back to container build."); } - return new NativeImageBuildContainerRunner(nativeConfig, outputDir); + if (nativeConfig.remoteContainerBuild) { + return new NativeImageBuildRemoteContainerRunner(nativeConfig, outputDir, nativeImageName); + } + return new NativeImageBuildLocalContainerRunner(nativeConfig, outputDir); } private void copyJarSourcesToLib(OutputTargetBuildItem outputTargetBuildItem, From a7e8a558c151d3cb7cdd08ef1495ae78bfa52f4f Mon Sep 17 00:00:00 2001 From: Jonathan Meier Date: Fri, 29 Jan 2021 19:16:14 +0100 Subject: [PATCH 7/7] Enable a container build when remoteContainerBuild is enabled independent of containerBuild --- .../deployment/pkg/steps/NativeImageBuildStep.java | 9 ++++++--- .../kafka/streams/deployment/KafkaStreamsProcessor.java | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) 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 092d11e68f267..502bbcb1d5ccf 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 @@ -87,7 +87,7 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa String noPIE = ""; - boolean isContainerBuild = nativeConfig.containerRuntime.isPresent() || nativeConfig.containerBuild; + boolean isContainerBuild = isContainerBuild(nativeConfig); if (!isContainerBuild && SystemUtils.IS_OS_LINUX) { noPIE = detectNoPIE(); } @@ -287,10 +287,13 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa } } + public static boolean isContainerBuild(NativeConfig nativeConfig) { + return nativeConfig.containerRuntime.isPresent() || nativeConfig.containerBuild || nativeConfig.remoteContainerBuild; + } + private static NativeImageBuildRunner getNativeImageBuildRunner(NativeConfig nativeConfig, Path outputDir, String nativeImageName) { - boolean isContainerBuild = nativeConfig.containerRuntime.isPresent() || nativeConfig.containerBuild; - if (!isContainerBuild) { + if (!isContainerBuild(nativeConfig)) { NativeImageBuildLocalRunner localRunner = getNativeImageBuildLocalRunner(nativeConfig); if (localRunner != null) { return localRunner; diff --git a/extensions/kafka-streams/deployment/src/main/java/io/quarkus/kafka/streams/deployment/KafkaStreamsProcessor.java b/extensions/kafka-streams/deployment/src/main/java/io/quarkus/kafka/streams/deployment/KafkaStreamsProcessor.java index 68ccf8c335302..9c7b887baf997 100644 --- a/extensions/kafka-streams/deployment/src/main/java/io/quarkus/kafka/streams/deployment/KafkaStreamsProcessor.java +++ b/extensions/kafka-streams/deployment/src/main/java/io/quarkus/kafka/streams/deployment/KafkaStreamsProcessor.java @@ -32,6 +32,7 @@ import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.RuntimeReinitializedClassBuildItem; import io.quarkus.deployment.pkg.NativeConfig; +import io.quarkus.deployment.pkg.steps.NativeImageBuildStep; import io.quarkus.kafka.streams.runtime.KafkaStreamsProducer; import io.quarkus.kafka.streams.runtime.KafkaStreamsRecorder; import io.quarkus.kafka.streams.runtime.KafkaStreamsRuntimeConfig; @@ -116,7 +117,7 @@ private void registerClassesThatAreAccessedViaJni(BuildProducer nativeLibs, NativeConfig nativeConfig) { // for RocksDB, either add linux64 native lib when targeting containers - if (nativeConfig.containerRuntime.isPresent() || nativeConfig.containerBuild) { + if (NativeImageBuildStep.isContainerBuild(nativeConfig)) { nativeLibs.produce(new NativeImageResourceBuildItem("librocksdbjni-linux64.so")); } // otherwise the native lib of the platform this build runs on