/**
* Copyright (c) 2005-2013 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Eclipse Public License (EPL).
* Please see the license.txt included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
/*
* Created on 05/08/2005
*/
package org.python.pydev.runners;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.StringTokenizer;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunchManager;
import org.python.pydev.core.IInterpreterInfo;
import org.python.pydev.core.IInterpreterManager;
import org.python.pydev.core.IPythonNature;
import org.python.pydev.core.IPythonPathNature;
import org.python.pydev.core.MisconfigurationException;
import org.python.pydev.core.docutils.StringSubstitution;
import org.python.pydev.core.log.Log;
import org.python.pydev.plugin.PydevPlugin;
import org.python.pydev.shared_core.SharedCorePlugin;
import org.python.pydev.shared_core.callbacks.ICallback;
import org.python.pydev.shared_core.io.FileUtils;
import org.python.pydev.shared_core.process.ProcessUtils;
import org.python.pydev.shared_core.string.StringUtils;
import org.python.pydev.shared_core.structure.Tuple;
import org.python.pydev.shared_core.utils.PlatformUtils;
public class SimpleRunner {
/**
* Passes the commands directly to Runtime.exec (with the passed envp)
*/
public static Process createProcess(String[] cmdarray, String[] envp, File workingDir) throws IOException {
return ProcessUtils.createProcess(cmdarray, envp, workingDir);
}
/**
* THIS CODE IS COPIED FROM org.eclipse.debug.internal.core.LaunchManager
*
* changed so that we always set the PYTHONPATH in the environment
*
* @return the system environment with the PYTHONPATH env variable added for a given project (if it is null, return it with the
* default PYTHONPATH added).
*/
public static String[] getEnvironment(IPythonNature pythonNature, IInterpreterInfo interpreter,
IInterpreterManager manager) throws CoreException {
String[] env;
String pythonPathEnvStr = "";
try {
if (interpreter != null) { //check if we have a default interpreter.
pythonPathEnvStr = makePythonPathEnvString(pythonNature, interpreter, manager);
}
env = createEnvWithPythonpath(pythonPathEnvStr, pythonNature, manager);
} catch (Exception e) {
Log.log(e);
//We cannot get it. Log it and keep with the default.
env = getDefaultSystemEnvAsArray(pythonNature);
}
if (interpreter != null) {
env = interpreter.updateEnv(env);
}
return env;
}
/**
* Same as the getEnvironment, but with a pre-specified pythonpath.
* @throws MisconfigurationException
*/
public static String[] createEnvWithPythonpath(String pythonPathEnvStr, String interpreter,
IInterpreterManager manager, IPythonNature nature) throws CoreException, MisconfigurationException {
String[] env = createEnvWithPythonpath(pythonPathEnvStr, nature, manager);
IInterpreterInfo info = manager.getInterpreterInfo(interpreter, new NullProgressMonitor());
env = info.updateEnv(env);
return env;
}
private static String[] createEnvWithPythonpath(String pythonPathEnvStr, IPythonNature nature,
IInterpreterManager manager) throws CoreException {
if (SharedCorePlugin.inTestMode()) {
return null;
}
DebugPlugin defaultPlugin = DebugPlugin.getDefault();
Map<String, String> env = getDefaultSystemEnv(defaultPlugin, nature); //no need to remove as it'll be updated
env.put("PYTHONPATH", pythonPathEnvStr); //put the environment
switch (manager.getInterpreterType()) {
case IPythonNature.INTERPRETER_TYPE_JYTHON:
env.put("CLASSPATH", pythonPathEnvStr); //put the environment
env.put("JYTHONPATH", pythonPathEnvStr); //put the environment
break;
case IPythonNature.INTERPRETER_TYPE_IRONPYTHON:
env.put("IRONPYTHONPATH", pythonPathEnvStr); //put the environment
break;
}
return getMapEnvAsArray(env);
}
/**
* @return an array with the env variables for the system with the format xx=yy
*/
public static String[] getDefaultSystemEnvAsArray(IPythonNature nature) throws CoreException {
Map<String, String> defaultSystemEnv = getDefaultSystemEnv(nature);
if (defaultSystemEnv != null) {
return getMapEnvAsArray(defaultSystemEnv);
}
return null;
}
/**
* @return a map with the env variables for the system
*/
public static Map<String, String> getDefaultSystemEnv(IPythonNature nature) throws CoreException {
if (SharedCorePlugin.inTestMode()) {
return null;
}
DebugPlugin defaultPlugin = DebugPlugin.getDefault();
return getDefaultSystemEnv(defaultPlugin, nature);
}
/**
* @return a map with the env variables for the system
*/
private static Map<String, String> getDefaultSystemEnv(DebugPlugin defaultPlugin, IPythonNature nature)
throws CoreException {
ILaunchManager launchManager = defaultPlugin.getLaunchManager();
// build base environment
Map<String, String> env = new HashMap<String, String>();
env.putAll(launchManager.getNativeEnvironment());
// Add variables from config
boolean win32 = PlatformUtils.isWindowsPlatform();
for (Iterator<Map.Entry<String, String>> iter = env.entrySet().iterator(); iter.hasNext();) {
Entry<String, String> entry = iter.next();
String key = entry.getKey();
if (win32) {
// Win32 vars are case insensitive. Uppercase everything so
// that (for example) "pAtH" will correctly replace "PATH"
key = key.toUpperCase();
}
String value = entry.getValue();
// translate any string substitution variables
String translated = value;
try {
StringSubstitution stringSubstitution = new StringSubstitution(nature);
translated = stringSubstitution.performStringSubstitution(value, false);
} catch (Exception e) {
Log.log(e);
}
env.put(key, translated);
}
//Always remove PYTHONHOME from the default system env, as it doesn't work well with multiple interpreters.
env.remove("PYTHONHOME");
// PyDev-495 Remove VIRTUAL_ENV as it cause IPython to munge the PYTHON_PATH
env.remove("VIRTUAL_ENV");
return env;
}
/**
* copied from org.eclipse.jdt.internal.launching.StandardVMRunner
* @param args - other arguments to be added to the command line (may be null)
* @return
*/
public static String getArgumentsAsStr(String[] commandLine, String... args) {
return ProcessUtils.getArgumentsAsStr(commandLine, args);
}
/**
* Creates a string that can be passed as the PYTHONPATH
*
* @param project the project we want to get the settings from. If it is null, the system pythonpath is returned
* @param interpreter this is the interpreter to be used to create the env.
* @return a string that can be used as the PYTHONPATH env variable
*/
public static String makePythonPathEnvString(IPythonNature pythonNature, IInterpreterInfo interpreter,
IInterpreterManager manager) {
if (pythonNature == null) {
if (interpreter == null) {
return makePythonPathEnvFromPaths(new ArrayList<String>()); //no pythonpath can be gotten (set to empty, so that the default is gotten)
} else {
List<String> pythonPath = interpreter.getPythonPath();
return makePythonPathEnvFromPaths(pythonPath);
}
}
List<String> paths;
//if we have a project, get its complete pythonpath
IPythonPathNature pythonPathNature = pythonNature.getPythonPathNature();
if (pythonPathNature == null) {
IProject project = pythonNature.getProject();
String projectName;
if (project == null) {
projectName = "null?";
} else {
projectName = project.getName();
}
throw new RuntimeException("The project " + projectName + " does not have the pythonpath configured, \n"
+ "please configure it correcly (please check the pydev getting started guide at \n"
+ "http://pydev.org/manual_101_root.html for better information on how to do it).");
}
paths = pythonPathNature.getCompleteProjectPythonPath(interpreter, manager);
return makePythonPathEnvFromPaths(paths);
}
/**
* @param paths the paths to be added
* @return a String suitable to be added to the PYTHONPATH environment variable.
*/
public static String makePythonPathEnvFromPaths(Collection<String> inPaths) {
ArrayList<String> paths = new ArrayList<String>(inPaths);
try {
//whenever we launch a file from pydev, we must add the sitecustomize to the pythonpath so that
//the default encoding (for the console) can be set.
//see: http://sourceforge.net/tracker/index.php?func=detail&aid=1580766&group_id=85796&atid=577329
paths.add(0, FileUtils.getFileAbsolutePath(PydevPlugin.getScriptWithinPySrc("pydev_sitecustomize")));
} catch (CoreException e) {
Log.log(e);
}
String separator = getPythonPathSeparator();
return StringUtils.join(separator, paths);
}
/**
* @return the separator for the pythonpath variables (system dependent)
*/
public static String getPythonPathSeparator() {
return System.getProperty("path.separator"); //is system dependent, and should cover for all cases...
// boolean win32= isWindowsPlatform();
// String separator = ";";
// if(!win32){
// separator = ":"; //system dependent
// }
// return separator;
}
/**
* @param env a map that will have its values formatted to xx=yy, so that it can be passed in an exec
* @return an array with the formatted map
*/
public static String[] getMapEnvAsArray(Map<String, String> env) {
return ProcessUtils.getMapEnvAsArray(env);
}
public Tuple<Process, String> run(String[] cmdarray, File workingDir, IPythonNature nature,
IProgressMonitor monitor) {
return run(cmdarray, workingDir, nature, monitor, null);
}
/**
* @return a tuple with the process created and a string representation of the cmdarray.
*/
public Tuple<Process, String> run(String[] cmdarray, File workingDir, IPythonNature nature,
IProgressMonitor monitor, ICallback<String[], String[]> updateEnv) {
if (monitor == null) {
monitor = new NullProgressMonitor();
}
String executionString = getArgumentsAsStr(cmdarray);
monitor.setTaskName("Executing: " + executionString);
monitor.worked(5);
Process process = null;
try {
monitor.setTaskName("Making pythonpath environment..." + executionString);
String[] envp = null;
if (nature != null) {
envp = getEnvironment(nature, nature.getProjectInterpreter(), nature.getRelatedInterpreterManager()); //Don't remove as it *should* be updated based on the nature)
}
//Otherwise, use default (used when configuring the interpreter for instance).
monitor.setTaskName("Making exec..." + executionString);
if (workingDir != null) {
if (!workingDir.isDirectory()) {
throw new RuntimeException(StringUtils.format(
"Working dir must be an existing directory (received: %s)", workingDir));
}
}
if (updateEnv != null) {
envp = updateEnv.call(envp);
}
process = createProcess(cmdarray, envp, workingDir);
} catch (Exception e) {
throw new RuntimeException(e);
}
return new Tuple<Process, String>(process, executionString);
}
/**
* Runs the given command line and returns a tuple with the output (stdout and stderr) of executing it.
*
* @param cmdarray array with the commands to be passed to Runtime.exec
* @param workingDir the working dir (may be null)
* @param project the project (used to get the pythonpath and put it into the environment) -- if null, no environment is passed.
* @param monitor the progress monitor to be used -- may be null
*
* @return a tuple with stdout and stderr
*/
public Tuple<String, String> runAndGetOutput(String[] cmdarray, File workingDir, IPythonNature nature,
IProgressMonitor monitor, String encoding) {
Tuple<Process, String> r = run(cmdarray, workingDir, nature, monitor);
return getProcessOutput(r.o1, r.o2, monitor, encoding);
}
/**
* @param process process from where the output should be gotten
* @param executionString string to execute (only for errors)
* @param monitor monitor for giving progress
* @return a tuple with the output of stdout and stderr
*/
public static Tuple<String, String> getProcessOutput(Process process, String executionString,
IProgressMonitor monitor, String encoding) {
return ProcessUtils.getProcessOutput(process, executionString, monitor, encoding);
}
/**
* @param pythonpath the pythonpath string to be used
* @return a list of strings with the elements of the pythonpath
*/
public static List<String> splitPythonpath(String pythonpath) {
ArrayList<String> splitted = new ArrayList<String>();
StringTokenizer tokenizer = new StringTokenizer(pythonpath, getPythonPathSeparator());
while (tokenizer.hasMoreTokens()) {
splitted.add(tokenizer.nextToken());
}
return splitted;
}
}