src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/Executor.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/Executor.java Fri Nov 08 14:53:03 2019 -0500
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.incubator.jpackage.internal;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+final public class Executor {
+
+ Executor() {
+ }
+
+ Executor setOutputConsumer(Consumer<Stream<String>> v) {
+ outputConsumer = v;
+ return this;
+ }
+
+ Executor saveOutput(boolean v) {
+ saveOutput = v;
+ return this;
+ }
+
+ Executor setProcessBuilder(ProcessBuilder v) {
+ pb = v;
+ return this;
+ }
+
+ Executor setCommandLine(String... cmdline) {
+ return setProcessBuilder(new ProcessBuilder(cmdline));
+ }
+
+ List<String> getOutput() {
+ return output;
+ }
+
+ Executor executeExpectSuccess() throws IOException {
+ int ret = execute();
+ if (0 != ret) {
+ throw new IOException(
+ String.format("Command %s exited with %d code",
+ createLogMessage(pb), ret));
+ }
+ return this;
+ }
+
+ int execute() throws IOException {
+ output = null;
+
+ boolean needProcessOutput = outputConsumer != null || Log.isVerbose() || saveOutput;
+ if (needProcessOutput) {
+ pb.redirectErrorStream(true);
+ } else {
+ // We are not going to read process output, so need to notify
+ // ProcessBuilder about this. Otherwise some processes might just
+ // hang up (`ldconfig -p`).
+ pb.redirectError(ProcessBuilder.Redirect.DISCARD);
+ pb.redirectOutput(ProcessBuilder.Redirect.DISCARD);
+ }
+
+ Log.verbose(String.format("Running %s", createLogMessage(pb)));
+ Process p = pb.start();
+
+ if (needProcessOutput) {
+ try (var br = new BufferedReader(new InputStreamReader(
+ p.getInputStream()))) {
+ final List<String> savedOutput;
+ // Need to save output if explicitely requested (saveOutput=true) or
+ // if will be used used by multiple consumers
+ if ((outputConsumer != null && Log.isVerbose()) || saveOutput) {
+ savedOutput = br.lines().collect(Collectors.toList());
+ if (saveOutput) {
+ output = savedOutput;
+ }
+ } else {
+ savedOutput = null;
+ }
+
+ Supplier<Stream<String>> outputStream = () -> {
+ if (savedOutput != null) {
+ return savedOutput.stream();
+ }
+ return br.lines();
+ };
+
+ if (Log.isVerbose()) {
+ outputStream.get().forEach(Log::verbose);
+ }
+
+ if (outputConsumer != null) {
+ outputConsumer.accept(outputStream.get());
+ }
+
+ if (savedOutput == null) {
+ // For some processes on Linux if the output stream
+ // of the process is opened but not consumed, the process
+ // would exit with code 141.
+ // It turned out that reading just a single line of process
+ // output fixes the problem, but let's process
+ // all of the output, just in case.
+ br.lines().forEach(x -> {});
+ }
+ }
+ }
+
+ try {
+ return p.waitFor();
+ } catch (InterruptedException ex) {
+ Log.verbose(ex);
+ throw new RuntimeException(ex);
+ }
+ }
+
+ static Executor of(String... cmdline) {
+ return new Executor().setCommandLine(cmdline);
+ }
+
+ static Executor of(ProcessBuilder pb) {
+ return new Executor().setProcessBuilder(pb);
+ }
+
+ private static String createLogMessage(ProcessBuilder pb) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(String.format("%s", pb.command()));
+ if (pb.directory() != null) {
+ sb.append(String.format("in %s", pb.directory().getAbsolutePath()));
+ }
+ return sb.toString();
+ }
+
+ private ProcessBuilder pb;
+ private boolean saveOutput;
+ private List<String> output;
+ private Consumer<Stream<String>> outputConsumer;
+}