blob: 6f0275ed9abf724ebca7ca4c4fb724356d18c431 [file] [log] [blame]
/*
* 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.jetbrains.python.run;
import com.google.common.collect.Lists;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.RunContentExecutor;
import com.intellij.execution.configurations.EncodingEnvironmentUtil;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.configurations.ParamsGroup;
import com.intellij.execution.process.ProcessHandler;
import com.intellij.execution.process.ProcessTerminatedListener;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.NotNullFunction;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.buildout.BuildoutFacet;
import com.jetbrains.python.sdk.PySdkUtil;
import com.jetbrains.python.sdk.PythonEnvUtil;
import com.jetbrains.python.sdk.PythonSdkType;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* TODO: Use {@link com.jetbrains.python.run.PythonRunner} instead of this class? At already supports rerun and other things
* Base class for tasks which are run from PyCharm with results displayed in a toolwindow (manage.py, setup.py, Sphinx etc).
*
* @author yole
*/
public class PythonTask {
/**
* Mils we wait to process to be stopped when "rerun" called
*/
private static final long TIME_TO_WAIT_PROCESS_STOP = 2000L;
protected final Module myModule;
private final Sdk mySdk;
private String myWorkingDirectory;
private String myRunnerScript;
private List<String> myParameters = new ArrayList<String>();
private final String myRunTabTitle;
private String myHelpId;
private Runnable myAfterCompletion;
public PythonTask(Module module, String runTabTitle) throws ExecutionException {
this(module, runTabTitle, PythonSdkType.findPythonSdk(module));
}
public PythonTask(final Module module, final String runTabTitle, @Nullable final Sdk sdk) throws ExecutionException {
myModule = module;
myRunTabTitle = runTabTitle;
mySdk = sdk;
if (mySdk == null) {
throw new ExecutionException("Cannot find Python interpreter for selected module");
}
}
public String getWorkingDirectory() {
return myWorkingDirectory;
}
public void setWorkingDirectory(String workingDirectory) {
myWorkingDirectory = workingDirectory;
}
public void setRunnerScript(String script) {
myRunnerScript = script;
}
public void setParameters(List<String> parameters) {
myParameters = parameters;
}
public void setHelpId(String helpId) {
myHelpId = helpId;
}
public void setAfterCompletion(Runnable afterCompletion) {
myAfterCompletion = afterCompletion;
}
/**
* @param env environment variables to be passed to process or null if nothing should be passed
*/
public ProcessHandler createProcess(@Nullable final Map<String, String> env) throws ExecutionException {
final GeneralCommandLine commandLine = createCommandLine();
if (env != null) {
commandLine.getEnvironment().putAll(env);
}
ProcessHandler handler;
if (PySdkUtil.isRemote(mySdk)) {
assert mySdk != null;
handler = new PyRemoteProcessStarter().startRemoteProcess(mySdk, commandLine, myModule.getProject(), null);
}
else {
EncodingEnvironmentUtil.fixDefaultEncodingIfMac(commandLine, myModule.getProject());
handler = PythonProcessRunner.createProcessHandlingCtrlC(commandLine);
ProcessTerminatedListener.attach(handler);
}
return handler;
}
public GeneralCommandLine createCommandLine() {
GeneralCommandLine cmd = new GeneralCommandLine();
if (myWorkingDirectory != null) {
cmd.setWorkDirectory(myWorkingDirectory);
}
String homePath = mySdk.getHomePath();
if (homePath != null) {
homePath = FileUtil.toSystemDependentName(homePath);
}
PythonCommandLineState.createStandardGroupsIn(cmd);
ParamsGroup scriptParams = cmd.getParametersList().getParamsGroup(PythonCommandLineState.GROUP_SCRIPT);
assert scriptParams != null;
cmd.setPassParentEnvironment(true);
Map<String, String> env = cmd.getEnvironment();
if (!SystemInfo.isWindows && !PySdkUtil.isRemote(mySdk)) {
cmd.setExePath("bash");
ParamsGroup bashParams = cmd.getParametersList().addParamsGroupAt(0, "Bash");
bashParams.addParameter("-cl");
NotNullFunction<String, String> escaperFunction = StringUtil.escaper(false, "|>$\"'& ");
StringBuilder paramString = new StringBuilder(escaperFunction.fun(homePath) + " " + escaperFunction.fun(myRunnerScript));
for (String p : myParameters) {
paramString.append(" ").append(p);
}
bashParams.addParameter(paramString.toString());
}
else {
cmd.setExePath(homePath);
scriptParams.addParameter(myRunnerScript);
scriptParams.addParameters(myParameters);
}
PythonEnvUtil.setPythonUnbuffered(env);
List<String> pythonPath = setupPythonPath();
PythonCommandLineState.initPythonPath(cmd, true, pythonPath, homePath);
BuildoutFacet facet = BuildoutFacet.getInstance(myModule);
if (facet != null) {
facet.patchCommandLineForBuildout(cmd);
}
return cmd;
}
protected List<String> setupPythonPath() {
return setupPythonPath(true, true);
}
protected List<String> setupPythonPath(final boolean addContent, final boolean addSource) {
final List<String> pythonPath = Lists.newArrayList(PythonCommandLineState.getAddedPaths(mySdk));
pythonPath.addAll(PythonCommandLineState.collectPythonPath(myModule, addContent, addSource));
return pythonPath;
}
/**
* @param env environment variables to be passed to process or null if nothing should be passed
*/
public void run(@Nullable final Map<String, String> env) throws ExecutionException {
final ProcessHandler process = createProcess(env);
final Project project = myModule.getProject();
new RunContentExecutor(project, process)
.withFilter(new PythonTracebackFilter(project))
.withTitle(myRunTabTitle)
.withRerun(new Runnable() {
@Override
public void run() {
try {
process.destroyProcess(); // Stop process before rerunning it
if (process.waitFor(TIME_TO_WAIT_PROCESS_STOP)) {
PythonTask.this.run(env);
}else {
Messages.showErrorDialog(PyBundle.message("unable.to.stop"), myRunTabTitle);
}
}
catch (ExecutionException e) {
Messages.showErrorDialog(e.getMessage(), myRunTabTitle);
}
}
})
.withStop(new Runnable() {
@Override
public void run() {
process.destroyProcess();
}
}, new Computable<Boolean>() {
@Override
public Boolean compute() {
return !process.isProcessTerminated();
}
}
)
.withAfterCompletion(myAfterCompletion)
.withHelpId(myHelpId)
.run();
}
}