1 /*******************************************************************************
2 * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
8 *******************************************************************************/
10 package org
.eclipse
.tracecompass
.common
.core
.process
;
12 import static org
.eclipse
.tracecompass
.common
.core
.NonNullUtils
.checkNotNull
;
14 import java
.io
.BufferedReader
;
15 import java
.io
.IOException
;
16 import java
.io
.InputStreamReader
;
17 import java
.util
.LinkedList
;
18 import java
.util
.List
;
19 import java
.util
.stream
.Collectors
;
21 import org
.eclipse
.core
.runtime
.CoreException
;
22 import org
.eclipse
.core
.runtime
.IProgressMonitor
;
23 import org
.eclipse
.core
.runtime
.IStatus
;
24 import org
.eclipse
.core
.runtime
.MultiStatus
;
25 import org
.eclipse
.core
.runtime
.Status
;
26 import org
.eclipse
.jdt
.annotation
.Nullable
;
27 import org
.eclipse
.tracecompass
.internal
.common
.core
.Activator
;
30 * Common utility methods for launching external processes and retrieving their
33 * @author Alexandre Montplaisir
36 public final class ProcessUtils
{
38 private ProcessUtils() {}
41 * Simple output-getting command. Cannot be cancelled, and will return null
42 * if the external process exits with a non-zero return code.
45 * The command (executable + arguments) to launch
46 * @return The process's standard output upon completion
48 public static @Nullable List
<String
> getOutputFromCommand(List
<String
> command
) {
50 ProcessBuilder builder
= new ProcessBuilder(command
);
51 builder
.redirectErrorStream(true);
53 Process p
= builder
.start();
54 try (BufferedReader br
= new BufferedReader(new InputStreamReader(p
.getInputStream()));) {
55 List
<String
> output
= new LinkedList
<>();
58 * We must consume the output before calling Process.waitFor(),
59 * or else the buffers might fill and block the external program
60 * if there is a lot of output.
62 String line
= br
.readLine();
63 while (line
!= null) {
68 int ret
= p
.waitFor();
69 return (ret
== 0 ? output
: null);
71 } catch (IOException
| InterruptedException e
) {
77 * Interface defining what do to with a process's output. For use with
78 * {@link #getOutputFromCommandCancellable}.
81 public static interface OutputReaderFunction
{
84 * Handle the output of the process. This can include reporting progress
85 * to the monitor, and pre-processing the returned output.
88 * A buffered reader to the process's standard output.
89 * Managed internally, so you do not need to
90 * {@link BufferedReader#close()} it.
92 * The progress monitor. Implementation should check
93 * periodically if it is cancelled to end processing early.
94 * The monitor's start and end will be managed, but progress
95 * can be reported via the {@link IProgressMonitor#worked}
96 * method. The total is 1000 work units.
97 * @return The process's output
99 * If there was a read error. Letting throw all exception
100 * from the {@link BufferedReader} is recommended.
102 List
<String
> readOutput(BufferedReader reader
, IProgressMonitor monitor
) throws IOException
;
106 * Cancellable output-getting command. The processing, as well as the
107 * external process itself, can be stopped by cancelling the passed progress
111 * The command (executable + arguments) to execute
113 * The progress monitor to check for cancellation and optionally
115 * @param mainTaskName
116 * The main task name of the job
117 * @param readerFunction
118 * What to do with the output. See {@link OutputReaderFunction}.
119 * @return The process's standard output, upon normal completion
120 * @throws CoreException
121 * If a problem happened with the execution of the external
122 * process. It can be reported to the user with the help of an
125 public static List
<String
> getOutputFromCommandCancellable(List
<String
> command
,
126 IProgressMonitor monitor
,
128 OutputReaderFunction readerFunction
)
129 throws CoreException
{
131 CancellableRunnable cancellerRunnable
= null;
132 Thread cancellerThread
= null;
135 monitor
.beginTask(mainTaskName
, 1000);
137 ProcessBuilder builder
= new ProcessBuilder(command
);
138 builder
.redirectErrorStream(false);
140 Process p
= checkNotNull(builder
.start());
142 cancellerRunnable
= new CancellableRunnable(p
, monitor
);
143 cancellerThread
= new Thread(cancellerRunnable
);
144 cancellerThread
.start();
146 try (BufferedReader stdoutReader
= new BufferedReader(new InputStreamReader(p
.getInputStream()));) {
148 List
<String
> lines
= readerFunction
.readOutput(stdoutReader
, monitor
);
150 int ret
= p
.waitFor();
152 if (monitor
.isCanceled()) {
153 /* We were interrupted by the canceller thread. */
154 IStatus status
= new Status(IStatus
.CANCEL
, Activator
.instance().getPluginId(), null);
155 throw new CoreException(status
);
160 * Something went wrong running the external process. We
161 * will gather the stderr and report it to the user.
163 BufferedReader stderrReader
= new BufferedReader(new InputStreamReader(p
.getErrorStream()));
164 List
<String
> stderrOutput
= stderrReader
.lines().collect(Collectors
.toList());
166 MultiStatus status
= new MultiStatus(Activator
.instance().getPluginId(),
167 IStatus
.ERROR
, Messages
.ProcessUtils_ErrorDuringExecution
, null);
168 for (String str
: stderrOutput
) {
169 status
.add(new Status(IStatus
.ERROR
, Activator
.instance().getPluginId(), str
));
171 if (stderrOutput
.isEmpty()) {
173 * At least say "no output", so an error message actually
176 status
.add(new Status(IStatus
.ERROR
, Activator
.instance().getPluginId(), Messages
.ProcessUtils_ErrorNoOutput
));
178 throw new CoreException(status
);
184 } catch (IOException
| InterruptedException e
) {
185 IStatus status
= new Status(IStatus
.ERROR
, Activator
.instance().getPluginId(), Messages
.ProcessUtils_ExecutionInterrupted
, e
);
186 throw new CoreException(status
);
189 if (cancellerRunnable
!= null) {
190 cancellerRunnable
.setFinished();
192 if (cancellerThread
!= null) {
194 cancellerThread
.join();
195 } catch (InterruptedException e
) {
204 * Internal wrapper class that allows forcibly stopping a {@link Process}
205 * when its corresponding progress monitor is cancelled.
207 private static class CancellableRunnable
implements Runnable
{
209 private final Process fProcess
;
210 private final IProgressMonitor fMonitor
;
212 private boolean fIsFinished
= false;
214 public CancellableRunnable(Process process
, IProgressMonitor monitor
) {
219 public void setFinished() {
226 while (!fIsFinished
) {
228 if (fMonitor
.isCanceled()) {
233 } catch (InterruptedException e
) {