| /* |
| * Copyright 2000-2013 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.execution.process; |
| |
| import com.intellij.execution.ExecutionBundle; |
| import com.intellij.execution.ExecutionException; |
| import com.intellij.execution.KillableProcess; |
| import com.intellij.execution.configurations.GeneralCommandLine; |
| import com.intellij.execution.configurations.PathEnvironmentVariableUtil; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.util.Condition; |
| import com.intellij.openapi.util.Conditions; |
| import com.intellij.openapi.util.Key; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.openapi.vfs.CharsetToolkit; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.openapi.vfs.encoding.EncodingManager; |
| import com.intellij.util.ObjectUtils; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.nio.charset.Charset; |
| |
| /** |
| * @author Elena Shaverdova |
| * @author Nikolay Matveev |
| */ |
| public final class ScriptRunnerUtil { |
| |
| private static final Logger LOG = Logger.getInstance("com.intellij.execution.process.ScriptRunnerUtil"); |
| |
| public static final Condition<Key> STDOUT_OUTPUT_KEY_FILTER = new Condition<Key>() { |
| @Override |
| public boolean value(Key key) { |
| return ProcessOutputTypes.STDOUT.equals(key); |
| } |
| }; |
| |
| public static final Condition<Key> STDERR_OUTPUT_KEY_FILTER = new Condition<Key>() { |
| @Override |
| public boolean value(Key key) { |
| return ProcessOutputTypes.STDERR.equals(key); |
| } |
| }; |
| |
| public static final Condition<Key> STDOUT_OR_STDERR_OUTPUT_KEY_FILTER = Conditions.or(STDOUT_OUTPUT_KEY_FILTER, STDERR_OUTPUT_KEY_FILTER); |
| |
| private static final int DEFAULT_TIMEOUT = 30000; |
| |
| private ScriptRunnerUtil() { |
| } |
| |
| public static String getProcessOutput(@NotNull GeneralCommandLine commandLine) |
| throws ExecutionException { |
| return getProcessOutput(commandLine, STDOUT_OUTPUT_KEY_FILTER, DEFAULT_TIMEOUT); |
| } |
| |
| public static String getProcessOutput(@NotNull GeneralCommandLine commandLine, @NotNull Condition<Key> outputTypeFilter, long timeout) |
| throws ExecutionException { |
| return getProcessOutput(new OSProcessHandler(commandLine.createProcess(), commandLine.getCommandLineString()), outputTypeFilter, |
| timeout); |
| } |
| |
| public static String getProcessOutput(@NotNull final ProcessHandler processHandler, |
| @NotNull final Condition<Key> outputTypeFilter, |
| final long timeout) |
| throws ExecutionException { |
| LOG.assertTrue(!processHandler.isStartNotified()); |
| final StringBuilder outputBuilder = new StringBuilder(); |
| processHandler.addProcessListener(new ProcessAdapter() { |
| @Override |
| public void onTextAvailable(ProcessEvent event, Key outputType) { |
| if (outputTypeFilter.value(outputType)) { |
| final String text = event.getText(); |
| outputBuilder.append(text); |
| LOG.debug(text); |
| } |
| } |
| }); |
| processHandler.startNotify(); |
| if (!processHandler.waitFor(timeout)) { |
| throw new ExecutionException(ExecutionBundle.message("script.execution.timeout", String.valueOf(timeout / 1000))); |
| } |
| return outputBuilder.toString(); |
| } |
| |
| @NotNull |
| public static OSProcessHandler execute(@NotNull String exePath, |
| @Nullable String workingDirectory, |
| @Nullable VirtualFile scriptFile, |
| String[] parameters) throws ExecutionException { |
| return execute(exePath, workingDirectory, scriptFile, parameters, null); |
| } |
| |
| @NotNull |
| public static OSProcessHandler execute(@NotNull String exePath, |
| @Nullable String workingDirectory, |
| @Nullable VirtualFile scriptFile, |
| String[] parameters, |
| @Nullable Charset charset) throws ExecutionException { |
| exePath = PathEnvironmentVariableUtil.findAbsolutePathOnMac(exePath); |
| return doExecute(exePath, workingDirectory, scriptFile, parameters, charset); |
| } |
| |
| @NotNull |
| private static OSProcessHandler doExecute(@NotNull String exePath, |
| @Nullable String workingDirectory, |
| @Nullable VirtualFile scriptFile, |
| String[] parameters, |
| @Nullable Charset charset) throws ExecutionException { |
| GeneralCommandLine commandLine = new GeneralCommandLine(); |
| commandLine.setExePath(exePath); |
| commandLine.setPassParentEnvironment(true); |
| if (scriptFile != null) { |
| commandLine.addParameter(scriptFile.getPresentableUrl()); |
| } |
| commandLine.addParameters(parameters); |
| |
| if (workingDirectory != null) { |
| commandLine.setWorkDirectory(workingDirectory); |
| } |
| |
| LOG.debug("Command line: ", commandLine.getCommandLineString()); |
| LOG.debug("Command line env: ", commandLine.getEnvironment()); |
| |
| if (charset == null) { |
| charset = ObjectUtils.notNull(EncodingManager.getInstance().getDefaultCharset(), CharsetToolkit.UTF8_CHARSET); |
| } |
| final OSProcessHandler processHandler = new ColoredProcessHandler(commandLine.createProcess(), |
| commandLine.getCommandLineString(), |
| charset); |
| if (LOG.isDebugEnabled()) { |
| processHandler.addProcessListener(new ProcessAdapter() { |
| @Override |
| public void onTextAvailable(ProcessEvent event, Key outputType) { |
| LOG.debug(outputType + ": " + event.getText()); |
| } |
| }); |
| } |
| |
| return processHandler; |
| } |
| |
| public static ScriptOutput executeScriptInConsoleWithFullOutput(String exePathString, |
| @Nullable VirtualFile scriptFile, |
| @Nullable String workingDirectory, |
| long timeout, |
| Condition<Key> scriptOutputType, |
| @NonNls String... parameters) throws ExecutionException { |
| final OSProcessHandler processHandler = execute(exePathString, workingDirectory, scriptFile, parameters); |
| |
| ScriptOutput output = new ScriptOutput(scriptOutputType); |
| processHandler.addProcessListener(output); |
| processHandler.startNotify(); |
| |
| if (!processHandler.waitFor(timeout)) { |
| LOG.warn("Process did not complete in " + timeout / 1000 + "s"); |
| throw new ExecutionException(ExecutionBundle.message("script.execution.timeout", String.valueOf(timeout / 1000))); |
| } |
| LOG.debug("script output: ", output.myFilteredOutput); |
| return output; |
| } |
| |
| public static class ScriptOutput extends ProcessAdapter { |
| private final Condition<Key> myScriptOutputType; |
| public final StringBuilder myFilteredOutput; |
| public final StringBuffer myMergedOutput; |
| |
| private ScriptOutput(Condition<Key> scriptOutputType) { |
| myScriptOutputType = scriptOutputType; |
| myFilteredOutput = new StringBuilder(); |
| myMergedOutput = new StringBuffer(); |
| } |
| |
| public String getFilteredOutput() { |
| return myFilteredOutput.toString(); |
| } |
| |
| public String getMergedOutput() { |
| return myMergedOutput.toString(); |
| } |
| |
| public String[] getOutputToParseArray() { |
| return getFilteredOutput().split("\n"); |
| } |
| |
| public String getDescriptiveOutput() { |
| String outputToParse = getFilteredOutput(); |
| return StringUtil.isEmpty(outputToParse) ? getMergedOutput() : outputToParse; |
| } |
| |
| @Override |
| public void onTextAvailable(ProcessEvent event, Key outputType) { |
| final String text = event.getText(); |
| if (myScriptOutputType.value(outputType)) { |
| myFilteredOutput.append(text); |
| } |
| myMergedOutput.append(text); |
| } |
| } |
| |
| /** |
| * Gracefully terminates a process handler. |
| * Initially, 'soft kill' is performed (on UNIX it's equivalent to SIGINT signal sending). |
| * If the process isn't terminated within a given timeout, 'force quite' is performed (on UNIX it's equivalent to SIGKILL |
| * signal sending). |
| * |
| * @param processHandler {@link ProcessHandler} instance |
| * @param millisTimeout timeout in milliseconds between 'soft kill' and 'force quite' |
| * @param commandLine command line |
| */ |
| public static void terminateProcessHandler(@NotNull ProcessHandler processHandler, |
| long millisTimeout, |
| @Nullable String commandLine) { |
| if (processHandler.isProcessTerminated()) { |
| if (commandLine == null && processHandler instanceof BaseOSProcessHandler) { |
| commandLine = ((BaseOSProcessHandler) processHandler).getCommandLine(); |
| } |
| LOG.warn("Process '" + commandLine + "' is already terminated!"); |
| return; |
| } |
| processHandler.destroyProcess(); |
| if (processHandler instanceof KillableProcess) { |
| KillableProcess killableProcess = (KillableProcess) processHandler; |
| if (killableProcess.canKillProcess()) { |
| if (!processHandler.waitFor(millisTimeout)) { |
| // doing 'force quite' |
| killableProcess.killProcess(); |
| } |
| } |
| } |
| } |
| |
| } |