common: Add a ProcessUtils for external process launching
authorAlexandre Montplaisir <alexmonthy@efficios.com>
Tue, 25 Oct 2016 00:32:36 +0000 (20:32 -0400)
committerAlexandre Montplaisir <alexmonthy@efficios.com>
Fri, 9 Dec 2016 19:15:24 +0000 (14:15 -0500)
Consolidate the existing external-process-launching utility
methods into a new common class. Right now this includes the
FileOffsetMapper, which calls addr2line for debug-info symbol
resolution, as well as the LamiAnalysis class which calls
the LAMI analysis scripts.

Bug: 508406

Change-Id: I685fb461a93cd6726575b5df771233f37e423e5f
Signed-off-by: Alexandre Montplaisir <alexmonthy@efficios.com>
Reviewed-on: https://git.eclipse.org/r/85973
Reviewed-by: Hudson CI
analysis/org.eclipse.tracecompass.analysis.lami.core/src/org/eclipse/tracecompass/internal/provisional/analysis/lami/core/module/LamiAnalysis.java
common/org.eclipse.tracecompass.common.core/META-INF/MANIFEST.MF
common/org.eclipse.tracecompass.common.core/src/org/eclipse/tracecompass/common/core/process/Messages.java [new file with mode: 0644]
common/org.eclipse.tracecompass.common.core/src/org/eclipse/tracecompass/common/core/process/ProcessUtils.java [new file with mode: 0644]
common/org.eclipse.tracecompass.common.core/src/org/eclipse/tracecompass/common/core/process/messages.properties [new file with mode: 0644]
common/org.eclipse.tracecompass.common.core/src/org/eclipse/tracecompass/common/core/process/package-info.java [new file with mode: 0644]
lttng/org.eclipse.tracecompass.lttng2.ust.core/src/org/eclipse/tracecompass/internal/lttng2/ust/core/analysis/debuginfo/FileOffsetMapper.java

index b71391edaa1b07897f88a14f16db90c052f6781c..019b206caabebdadae52fdb0bf5e8d41b328dafe 100644 (file)
@@ -13,10 +13,7 @@ import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
 import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNullContents;
 import static org.eclipse.tracecompass.common.core.NonNullUtils.nullToEmptyString;
 
-import java.io.BufferedReader;
 import java.io.File;
-import java.io.IOException;
-import java.io.InputStreamReader;
 import java.nio.file.Files;
 import java.nio.file.Paths;
 import java.util.ArrayList;
@@ -35,11 +32,12 @@ import java.util.stream.Stream;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.IStatus;
-import org.eclipse.core.runtime.MultiStatus;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
 import org.eclipse.tracecompass.common.core.log.TraceCompassLog;
+import org.eclipse.tracecompass.common.core.process.ProcessUtils;
+import org.eclipse.tracecompass.common.core.process.ProcessUtils.OutputReaderFunction;
 import org.eclipse.tracecompass.internal.analysis.lami.core.Activator;
 import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.LamiStrings;
 import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.ShellUtils;
@@ -830,21 +828,11 @@ public class LamiAnalysis implements IOnDemandAnalysis {
     @VisibleForTesting
     protected @Nullable String getOutputFromCommand(List<String> command) {
         LOGGER.info(() -> LOG_RUNNING_MESSAGE + ' ' + command.toString());
-
-        try {
-            ProcessBuilder builder = new ProcessBuilder(command);
-            builder.redirectErrorStream(true);
-
-            Process p = builder.start();
-            try (BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));) {
-                int ret = p.waitFor();
-                String output = br.lines().collect(Collectors.joining());
-
-                return (ret == 0 ? output : null);
-            }
-        } catch (IOException | InterruptedException e) {
+        List<String> lines = ProcessUtils.getOutputFromCommand(command);
+        if (lines == null) {
             return null;
         }
+        return String.join("", lines); //$NON-NLS-1$
     }
 
     /**
@@ -865,165 +853,69 @@ public class LamiAnalysis implements IOnDemandAnalysis {
     @VisibleForTesting
     protected String getResultsFromCommand(List<String> command, IProgressMonitor monitor)
             throws CoreException {
+        List<String> lines = ProcessUtils.getOutputFromCommandCancellable(command, monitor, nullToEmptyString(Messages.LamiAnalysis_MainTaskName), OUTPUT_READER);
+        return checkNotNull(String.join("", lines)); //$NON-NLS-1$
+    }
 
-        final int scale = 1000;
+    private static final OutputReaderFunction OUTPUT_READER = (reader, monitor) -> {
         double workedSoFar = 0.0;
 
-        ProcessCanceller cancellerRunnable = null;
-        Thread cancellerThread = null;
-
-        try {
-            monitor.beginTask(Messages.LamiAnalysis_MainTaskName, scale);
-
-            ProcessBuilder builder = new ProcessBuilder(command);
-            builder.redirectErrorStream(false);
-
-            Process p = checkNotNull(builder.start());
-
-            cancellerRunnable = new ProcessCanceller(p, monitor);
-            cancellerThread = new Thread(cancellerRunnable);
-            cancellerThread.start();
+        String line = reader.readLine();
+        while (line != null && !line.matches("\\s*\\{.*")) { //$NON-NLS-1$
+            /*
+             * This is a line indicating progress, it has the form:
+             *
+             * 0.123 3000 of 5000 events processed
+             *
+             * The first part indicates the estimated fraction (out of 1.0) of
+             * work done. The second part is status text.
+             */
 
-            List<String> results = new ArrayList<>();
+            // Trim the line first to make sure the first character is
+            // significant
+            line = line.trim();
 
-            try (BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));) {
-                String line = in.readLine();
-                while (line != null && !line.matches("\\s*\\{.*")) { //$NON-NLS-1$
-                    /*
-                     * This is a line indicating progress, it has the form:
-                     *
-                     * 0.123 3000 of 5000 events processed
-                     *
-                     * The first part indicates the estimated fraction (out of
-                     * 1.0) of work done. The second part is status text.
-                     */
+            // Split at the first space
+            String[] elems = line.split(" ", 2); //$NON-NLS-1$
 
-                    // Trim the line first to make sure the first character is
-                    // significant
-                    line = line.trim();
-
-                    // Split at the first space
-                    String[] elems = line.split(" ", 2); //$NON-NLS-1$
-
-                    if (elems[0].matches("\\d.*")) { //$NON-NLS-1$
-                        // It looks like we have a progress indication
-                        try {
-                            // Try parsing the number
-                            double cumulativeWork = Double.parseDouble(elems[0]) * scale;
-                            double workedThisLoop = cumulativeWork - workedSoFar;
-
-                            // We're going backwards? Do not update the
-                            // monitor's value
-                            if (workedThisLoop > 0) {
-                                monitor.internalWorked(workedThisLoop);
-                                workedSoFar = cumulativeWork;
-                            }
-
-                            // There is a message: update the monitor's task name
-                            if (elems.length >= 2) {
-                                monitor.setTaskName(elems[1].trim());
-                            }
-                        } catch (NumberFormatException e) {
-                            // Continue reading progress lines anyway
-                        }
+            if (elems[0].matches("\\d.*")) { //$NON-NLS-1$
+                // It looks like we have a progress indication
+                try {
+                    // Try parsing the number
+                    double cumulativeWork = Double.parseDouble(elems[0]) * 1000;
+                    double workedThisLoop = cumulativeWork - workedSoFar;
+
+                    // We're going backwards? Do not update the
+                    // monitor's value
+                    if (workedThisLoop > 0) {
+                        monitor.internalWorked(workedThisLoop);
+                        workedSoFar = cumulativeWork;
                     }
 
-                    line = in.readLine();
-                }
-                while (line != null) {
-                    /*
-                     * We have seen the first line containing a '{', this is our
-                     * JSON output!
-                     */
-                    results.add(line);
-                    line = in.readLine();
-                }
-            }
-            int ret = p.waitFor();
-
-            if (monitor.isCanceled()) {
-                /* We were interrupted by the canceller thread. */
-                IStatus status = new Status(IStatus.CANCEL, Activator.instance().getPluginId(), null);
-                throw new CoreException(status);
-            }
-
-            if (ret != 0) {
-                /*
-                 * Something went wrong running the external script. We will
-                 * gather the stderr and report it to the user.
-                 */
-                BufferedReader br = new BufferedReader(new InputStreamReader(p.getErrorStream()));
-                List<String> stdErrOutput = br.lines().collect(Collectors.toList());
-
-                MultiStatus status = new MultiStatus(Activator.instance().getPluginId(),
-                        IStatus.ERROR, Messages.LamiAnalysis_ErrorDuringExecution, null);
-                for (String str : stdErrOutput) {
-                    status.add(new Status(IStatus.ERROR, Activator.instance().getPluginId(), str));
-                }
-                if (stdErrOutput.isEmpty()) {
-                    /*
-                     * At least say "no output", so an error message actually
-                     * shows up.
-                     */
-                    status.add(new Status(IStatus.ERROR, Activator.instance().getPluginId(), Messages.LamiAnalysis_ErrorNoOutput));
-                }
-                throw new CoreException(status);
-            }
-
-            /* External script ended successfully, all is fine! */
-            String resultsStr = results.stream().collect(Collectors.joining());
-            return checkNotNull(resultsStr);
-
-        } catch (IOException | InterruptedException e) {
-            IStatus status = new Status(IStatus.ERROR, Activator.instance().getPluginId(), Messages.LamiAnalysis_ExecutionInterrupted, e);
-            throw new CoreException(status);
-
-        } finally {
-            if (cancellerRunnable != null) {
-                cancellerRunnable.setFinished();
-            }
-            if (cancellerThread != null) {
-                try {
-                    cancellerThread.join();
-                } catch (InterruptedException e) {
+                    // There is a message: update the monitor's task name
+                    if (elems.length >= 2) {
+                        monitor.setTaskName(elems[1].trim());
+                    }
+                } catch (NumberFormatException e) {
+                    // Continue reading progress lines anyway
                 }
             }
 
-            monitor.done();
-        }
-    }
-
-    private static class ProcessCanceller implements Runnable {
-
-        private final Process fProcess;
-        private final IProgressMonitor fMonitor;
-
-        private boolean fIsFinished = false;
-
-        public ProcessCanceller(Process process, IProgressMonitor monitor) {
-            fProcess = process;
-            fMonitor = monitor;
-        }
-
-        public void setFinished() {
-            fIsFinished = true;
+            line = reader.readLine();
         }
 
-        @Override
-        public void run() {
-            try {
-                while (!fIsFinished) {
-                    Thread.sleep(500);
-                    if (fMonitor.isCanceled()) {
-                        fProcess.destroy();
-                        return;
-                    }
-                }
-            } catch (InterruptedException e) {
-            }
+        List<String> results = new ArrayList<>();
+        while (line != null) {
+            /*
+             * We have seen the first line containing a '{', this is our JSON
+             * output!
+             */
+            results.add(line);
+            line = reader.readLine();
         }
+        return results;
+    };
 
-    }
 
     @Override
     public @NonNull String getName() {
index eca20a17c83fa05108c98caf9aa12f219e17407e..afb10aff6dc0d6759b27eb8da6a5f3f4619f8697 100644 (file)
@@ -15,5 +15,6 @@ Export-Package: org.eclipse.tracecompass.common.core,
  org.eclipse.tracecompass.common.core.format,
  org.eclipse.tracecompass.common.core.log,
  org.eclipse.tracecompass.common.core.math,
+ org.eclipse.tracecompass.common.core.process,
  org.eclipse.tracecompass.internal.common.core;x-internal:=true
 Import-Package: com.google.common.collect
diff --git a/common/org.eclipse.tracecompass.common.core/src/org/eclipse/tracecompass/common/core/process/Messages.java b/common/org.eclipse.tracecompass.common.core/src/org/eclipse/tracecompass/common/core/process/Messages.java
new file mode 100644 (file)
index 0000000..4953b24
--- /dev/null
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.common.core.process;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Message bundle for the package
+ *
+ * @noreference Messages class
+ */
+@NonNullByDefault({})
+@SuppressWarnings("javadoc")
+public class Messages extends NLS {
+
+    private static final String BUNDLE_NAME = Messages.class.getPackage().getName() + ".messages"; //$NON-NLS-1$
+
+//    public static String LamiAnalysis_MainTaskName;
+//    public static String LamiAnalysis_NoResults;
+    public static String ProcessUtils_ErrorDuringExecution;
+    public static String ProcessUtils_ErrorNoOutput;
+    public static String ProcessUtils_ExecutionInterrupted;
+
+    static {
+        NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+    }
+
+    private Messages() {}
+}
diff --git a/common/org.eclipse.tracecompass.common.core/src/org/eclipse/tracecompass/common/core/process/ProcessUtils.java b/common/org.eclipse.tracecompass.common.core/src/org/eclipse/tracecompass/common/core/process/ProcessUtils.java
new file mode 100644 (file)
index 0000000..479988c
--- /dev/null
@@ -0,0 +1,238 @@
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.common.core.process;
+
+import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.MultiStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.internal.common.core.Activator;
+
+/**
+ * Common utility methods for launching external processes and retrieving their
+ * output.
+ *
+ * @author Alexandre Montplaisir
+ * @since 2.2
+ */
+public final class ProcessUtils {
+
+    private ProcessUtils() {}
+
+    /**
+     * Simple output-getting command. Cannot be cancelled, and will return null
+     * if the external process exits with a non-zero return code.
+     *
+     * @param command
+     *            The command (executable + arguments) to launch
+     * @return The process's standard output upon completion
+     */
+    public static @Nullable List<String> getOutputFromCommand(List<String> command) {
+        try {
+            ProcessBuilder builder = new ProcessBuilder(command);
+            builder.redirectErrorStream(true);
+
+            Process p = builder.start();
+            try (BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));) {
+                List<String> output = new LinkedList<>();
+
+                /*
+                 * We must consume the output before calling Process.waitFor(),
+                 * or else the buffers might fill and block the external program
+                 * if there is a lot of output.
+                 */
+                String line = br.readLine();
+                while (line != null) {
+                    output.add(line);
+                    line = br.readLine();
+                }
+
+                int ret = p.waitFor();
+                return (ret == 0 ? output : null);
+            }
+        } catch (IOException | InterruptedException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Interface defining what do to with a process's output. For use with
+     * {@link #getOutputFromCommandCancellable}.
+     */
+    @FunctionalInterface
+    public static interface OutputReaderFunction {
+
+        /**
+         * Handle the output of the process. This can include reporting progress
+         * to the monitor, and pre-processing the returned output.
+         *
+         * @param reader
+         *            A buffered reader to the process's standard output.
+         *            Managed internally, so you do not need to
+         *            {@link BufferedReader#close()} it.
+         * @param monitor
+         *            The progress monitor. Implementation should check
+         *            periodically if it is cancelled to end processing early.
+         *            The monitor's start and end will be managed, but progress
+         *            can be reported via the {@link IProgressMonitor#worked}
+         *            method. The total is 1000 work units.
+         * @return The process's output
+         * @throws IOException
+         *             If there was a read error. Letting throw all exception
+         *             from the {@link BufferedReader} is recommended.
+         */
+        List<String> readOutput(BufferedReader reader, IProgressMonitor monitor) throws IOException;
+    }
+
+    /**
+     * Cancellable output-getting command. The processing, as well as the
+     * external process itself, can be stopped by cancelling the passed progress
+     * monitor.
+     *
+     * @param command
+     *            The command (executable + arguments) to execute
+     * @param monitor
+     *            The progress monitor to check for cancellation and optionally
+     *            progress
+     * @param mainTaskName
+     *            The main task name of the job
+     * @param readerFunction
+     *            What to do with the output. See {@link OutputReaderFunction}.
+     * @return The process's standard output, upon normal completion
+     * @throws CoreException
+     *             If a problem happened with the execution of the external
+     *             process. It can be reported to the user with the help of an
+     *             ErrorDialog.
+     */
+    public static List<String> getOutputFromCommandCancellable(List<String> command,
+            IProgressMonitor monitor,
+            String mainTaskName,
+            OutputReaderFunction readerFunction)
+            throws CoreException {
+
+        CancellableRunnable cancellerRunnable = null;
+        Thread cancellerThread = null;
+
+        try {
+            monitor.beginTask(mainTaskName, 1000);
+
+            ProcessBuilder builder = new ProcessBuilder(command);
+            builder.redirectErrorStream(false);
+
+            Process p = checkNotNull(builder.start());
+
+            cancellerRunnable = new CancellableRunnable(p, monitor);
+            cancellerThread = new Thread(cancellerRunnable);
+            cancellerThread.start();
+
+            try (BufferedReader stdoutReader = new BufferedReader(new InputStreamReader(p.getInputStream()));) {
+
+                List<String> lines = readerFunction.readOutput(stdoutReader, monitor);
+
+                int ret = p.waitFor();
+
+                if (monitor.isCanceled()) {
+                    /* We were interrupted by the canceller thread. */
+                    IStatus status = new Status(IStatus.CANCEL, Activator.instance().getPluginId(), null);
+                    throw new CoreException(status);
+                }
+
+                if (ret != 0) {
+                    /*
+                     * Something went wrong running the external process. We
+                     * will gather the stderr and report it to the user.
+                     */
+                    BufferedReader stderrReader = new BufferedReader(new InputStreamReader(p.getErrorStream()));
+                    List<String> stderrOutput = stderrReader.lines().collect(Collectors.toList());
+
+                    MultiStatus status = new MultiStatus(Activator.instance().getPluginId(),
+                            IStatus.ERROR, Messages.ProcessUtils_ErrorDuringExecution, null);
+                    for (String str : stderrOutput) {
+                        status.add(new Status(IStatus.ERROR, Activator.instance().getPluginId(), str));
+                    }
+                    if (stderrOutput.isEmpty()) {
+                        /*
+                         * At least say "no output", so an error message actually
+                         * shows up.
+                         */
+                        status.add(new Status(IStatus.ERROR, Activator.instance().getPluginId(), Messages.ProcessUtils_ErrorNoOutput));
+                    }
+                    throw new CoreException(status);
+                }
+
+                return lines;
+            }
+
+        } catch (IOException | InterruptedException e) {
+            IStatus status = new Status(IStatus.ERROR, Activator.instance().getPluginId(), Messages.ProcessUtils_ExecutionInterrupted, e);
+            throw new CoreException(status);
+
+        } finally {
+            if (cancellerRunnable != null) {
+                cancellerRunnable.setFinished();
+            }
+            if (cancellerThread != null) {
+                try {
+                    cancellerThread.join();
+                } catch (InterruptedException e) {
+                }
+            }
+
+            monitor.done();
+        }
+    }
+
+    /**
+     * Internal wrapper class that allows forcibly stopping a {@link Process}
+     * when its corresponding progress monitor is cancelled.
+     */
+    private static class CancellableRunnable implements Runnable {
+
+        private final Process fProcess;
+        private final IProgressMonitor fMonitor;
+
+        private boolean fIsFinished = false;
+
+        public CancellableRunnable(Process process, IProgressMonitor monitor) {
+            fProcess = process;
+            fMonitor = monitor;
+        }
+
+        public void setFinished() {
+            fIsFinished = true;
+        }
+
+        @Override
+        public void run() {
+            try {
+                while (!fIsFinished) {
+                    Thread.sleep(500);
+                    if (fMonitor.isCanceled()) {
+                        fProcess.destroy();
+                        return;
+                    }
+                }
+            } catch (InterruptedException e) {
+            }
+        }
+
+    }
+}
diff --git a/common/org.eclipse.tracecompass.common.core/src/org/eclipse/tracecompass/common/core/process/messages.properties b/common/org.eclipse.tracecompass.common.core/src/org/eclipse/tracecompass/common/core/process/messages.properties
new file mode 100644 (file)
index 0000000..c6078d9
--- /dev/null
@@ -0,0 +1,13 @@
+###############################################################################
+# Copyright (c) 2016 EfficiOS Inc. and others
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+###############################################################################
+
+ProcessUtils_NoResults = No results were returned.
+ProcessUtils_ErrorDuringExecution = Error during execution of the script.
+ProcessUtils_ErrorNoOutput = (No output)
+ProcessUtils_ExecutionInterrupted = Execution was interrupted.
diff --git a/common/org.eclipse.tracecompass.common.core/src/org/eclipse/tracecompass/common/core/process/package-info.java b/common/org.eclipse.tracecompass.common.core/src/org/eclipse/tracecompass/common/core/process/package-info.java
new file mode 100644 (file)
index 0000000..a93108b
--- /dev/null
@@ -0,0 +1,11 @@
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.eclipse.tracecompass.common.core.process;
index d6700046d216c57145fa101fa2138b8ee10141d5..8b5b9396827c84227f98615d1345cf86bfb68a5b 100644 (file)
@@ -11,21 +11,18 @@ package org.eclipse.tracecompass.internal.lttng2.ust.core.analysis.debuginfo;
 
 import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
 
-import java.io.BufferedReader;
 import java.io.File;
-import java.io.IOException;
-import java.io.InputStreamReader;
 import java.nio.file.Files;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.logging.Logger;
-import java.util.stream.Collectors;
 
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
 import org.eclipse.tracecompass.common.core.log.TraceCompassLog;
+import org.eclipse.tracecompass.common.core.process.ProcessUtils;
 import org.eclipse.tracecompass.tmf.core.event.lookup.TmfCallsite;
 
 import com.google.common.base.Objects;
@@ -235,8 +232,8 @@ public final class FileOffsetMapper {
         List<Addr2lineInfo> callsites = new LinkedList<>();
 
         // FIXME Could eventually use CDT's Addr2line class once it implements --inlines
-        List<String> output = getOutputFromCommand(Arrays.asList(
-                ADDR2LINE_EXECUTABLE, "-i", "-f", "-C", "-e", filePath, "0x" + Long.toHexString(offset)));  //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
+        List<String> command = Arrays.asList(ADDR2LINE_EXECUTABLE, "-i", "-f", "-C", "-e", filePath, "0x" + Long.toHexString(offset)); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
+        List<String> output = ProcessUtils.getOutputFromCommand(command);
 
         if (output == null) {
             /* Command returned an error */
@@ -283,21 +280,4 @@ public final class FileOffsetMapper {
 
         return callsites;
     }
-
-    private static @Nullable List<String> getOutputFromCommand(List<String> command) {
-        try {
-            ProcessBuilder builder = new ProcessBuilder(command);
-            builder.redirectErrorStream(true);
-
-            Process p = builder.start();
-            try (BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));) {
-                int ret = p.waitFor();
-                List<String> lines = br.lines().collect(Collectors.toList());
-
-                return (ret == 0 ? lines : null);
-            }
-        } catch (IOException | InterruptedException e) {
-            return null;
-        }
-    }
 }
This page took 0.037294 seconds and 5 git commands to generate.