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
;
29 import com
.google
.common
.base
.Charsets
;
32 * Common utility methods for launching external processes and retrieving their
35 * @author Alexandre Montplaisir
38 public final class ProcessUtils
{
40 private static final int PROGRESS_DURATION
= 1000;
42 private ProcessUtils() {}
45 * Simple output-getting command. Cannot be cancelled, and will return null
46 * if the external process exits with a non-zero return code.
49 * The command (executable + arguments) to launch
50 * @return The process's standard output upon completion
52 public static @Nullable List
<String
> getOutputFromCommand(List
<String
> command
) {
54 ProcessBuilder builder
= new ProcessBuilder(command
);
55 builder
.redirectErrorStream(true);
57 Process p
= builder
.start();
58 try (BufferedReader br
= new BufferedReader(new InputStreamReader(p
.getInputStream(), Charsets
.UTF_8
));) {
59 List
<String
> output
= new LinkedList
<>();
62 * We must consume the output before calling Process.waitFor(),
63 * or else the buffers might fill and block the external program
64 * if there is a lot of output.
66 String line
= br
.readLine();
67 while (line
!= null) {
72 int ret
= p
.waitFor();
73 return (ret
== 0 ? output
: null);
75 } catch (IOException
| InterruptedException e
) {
81 * Interface defining what do to with a process's output. For use with
82 * {@link #getOutputFromCommandCancellable}.
85 public interface OutputReaderFunction
{
88 * Handle the output of the process. This can include reporting progress
89 * to the monitor, and pre-processing the returned output.
92 * A buffered reader to the process's standard output.
93 * Managed internally, so you do not need to
94 * {@link BufferedReader#close()} it.
96 * The progress monitor. Implementation should check
97 * periodically if it is cancelled to end processing early.
98 * The monitor's start and end will be managed, but progress
99 * can be reported via the {@link IProgressMonitor#worked}
100 * method. The total is 1000 work units.
101 * @return The process's output
102 * @throws IOException
103 * If there was a read error. Letting throw all exception
104 * from the {@link BufferedReader} is recommended.
106 List
<String
> readOutput(BufferedReader reader
, IProgressMonitor monitor
) throws IOException
;
110 * Cancellable output-getting command. The processing, as well as the
111 * external process itself, can be stopped by cancelling the passed progress
115 * The command (executable + arguments) to execute
117 * The progress monitor to check for cancellation and optionally
119 * @param mainTaskName
120 * The main task name of the job
121 * @param readerFunction
122 * What to do with the output. See {@link OutputReaderFunction}.
123 * @return The process's standard output, upon normal completion
124 * @throws CoreException
125 * If a problem happened with the execution of the external
126 * process. It can be reported to the user with the help of an
129 public static List
<String
> getOutputFromCommandCancellable(List
<String
> command
,
130 IProgressMonitor monitor
,
132 OutputReaderFunction readerFunction
)
133 throws CoreException
{
135 CancellableRunnable cancellerRunnable
= null;
136 Thread cancellerThread
= null;
139 monitor
.beginTask(mainTaskName
, PROGRESS_DURATION
);
141 ProcessBuilder builder
= new ProcessBuilder(command
);
142 builder
.redirectErrorStream(false);
144 Process p
= checkNotNull(builder
.start());
146 cancellerRunnable
= new CancellableRunnable(p
, monitor
);
147 cancellerThread
= new Thread(cancellerRunnable
);
148 cancellerThread
.start();
150 try (BufferedReader stdoutReader
= new BufferedReader(new InputStreamReader(p
.getInputStream(), Charsets
.UTF_8
));) {
152 List
<String
> lines
= readerFunction
.readOutput(stdoutReader
, monitor
);
154 int ret
= p
.waitFor();
156 if (monitor
.isCanceled()) {
157 /* We were interrupted by the canceller thread. */
158 IStatus status
= new Status(IStatus
.CANCEL
, Activator
.instance().getPluginId(), null);
159 throw new CoreException(status
);
164 * Something went wrong running the external process. We
165 * will gather the stderr and report it to the user.
167 BufferedReader stderrReader
= new BufferedReader(new InputStreamReader(p
.getErrorStream()));
168 List
<String
> stderrOutput
= stderrReader
.lines().collect(Collectors
.toList());
170 MultiStatus status
= new MultiStatus(Activator
.instance().getPluginId(),
171 IStatus
.ERROR
, Messages
.ProcessUtils_ErrorDuringExecution
, null);
172 for (String str
: stderrOutput
) {
173 status
.add(new Status(IStatus
.ERROR
, Activator
.instance().getPluginId(), str
));
175 if (stderrOutput
.isEmpty()) {
177 * At least say "no output", so an error message
180 status
.add(new Status(IStatus
.ERROR
, Activator
.instance().getPluginId(), Messages
.ProcessUtils_ErrorNoOutput
));
182 throw new CoreException(status
);
188 } catch (IOException
| InterruptedException e
) {
189 IStatus status
= new Status(IStatus
.ERROR
, Activator
.instance().getPluginId(), Messages
.ProcessUtils_ExecutionInterrupted
, e
);
190 throw new CoreException(status
);
193 if (cancellerRunnable
!= null) {
194 cancellerRunnable
.setFinished();
196 if (cancellerThread
!= null) {
198 cancellerThread
.join();
199 } catch (InterruptedException e
) {
201 * If it is interrupted, process is terminated.
211 * Internal wrapper class that allows forcibly stopping a {@link Process}
212 * when its corresponding progress monitor is cancelled.
214 private static class CancellableRunnable
implements Runnable
{
216 private static final int SLEEP_DURATION
= 500;
217 private final Process fProcess
;
218 private final IProgressMonitor fMonitor
;
220 private boolean fIsFinished
= false;
222 public CancellableRunnable(Process process
, IProgressMonitor monitor
) {
227 public void setFinished() {
234 while (!fIsFinished
) {
235 Thread
.sleep(SLEEP_DURATION
);
236 if (fMonitor
.isCanceled()) {
241 } catch (InterruptedException e
) {
243 * If it is interrupted, process is terminated.