/** * 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. */ /* * Author: atotic * Created on Mar 18, 2004 */ package org.python.pydev.debug.ui.launching; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Platform; import org.eclipse.core.variables.VariablesPlugin; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.ILaunch; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.debug.core.ILaunchManager; import org.python.copiedfromeclipsesrc.JDTNotAvailableException; import org.python.copiedfromeclipsesrc.JavaVmLocationFinder; import org.python.pydev.core.ExtensionHelper; import org.python.pydev.core.IInterpreterInfo; import org.python.pydev.core.IInterpreterManager; import org.python.pydev.core.IPythonNature; import org.python.pydev.core.MisconfigurationException; import org.python.pydev.core.PythonNatureWithoutProjectException; import org.python.pydev.core.docutils.StringSubstitution; import org.python.pydev.core.log.Log; import org.python.pydev.debug.codecoverage.PyCodeCoverageView; import org.python.pydev.debug.codecoverage.PyCoverage; import org.python.pydev.debug.codecoverage.PyCoveragePreferences; import org.python.pydev.debug.core.Constants; import org.python.pydev.debug.core.PydevDebugPlugin; import org.python.pydev.debug.model.remote.ListenConnector; import org.python.pydev.debug.profile.PyProfilePreferences; import org.python.pydev.debug.pyunit.PyUnitServer; import org.python.pydev.debug.ui.DebugPrefsPage; import org.python.pydev.debug.ui.RunPreferencesPage; import org.python.pydev.debug.ui.launching.PythonRunnerCallbacks.CreatedCommandLineParams; import org.python.pydev.editor.preferences.PydevEditorPrefs; import org.python.pydev.plugin.PydevPlugin; import org.python.pydev.plugin.nature.PythonNature; import org.python.pydev.plugin.preferences.PydevPrefs; import org.python.pydev.pyunit.preferences.PyUnitPrefsPage2; import org.python.pydev.runners.SimpleRunner; import org.python.pydev.shared_core.io.FileUtils; import org.python.pydev.shared_core.net.LocalHost; import org.python.pydev.shared_core.process.ProcessUtils; import org.python.pydev.shared_core.string.FastStringBuffer; import org.python.pydev.shared_core.string.StringUtils; import org.python.pydev.shared_core.structure.Tuple; import org.python.pydev.shared_core.utils.PlatformUtils; import org.python.pydev.shared_ui.utils.RunInUiThread; import org.python.pydev.ui.dialogs.PyDialogHelpers; import org.python.pydev.ui.pythonpathconf.InterpreterInfo; /** * Holds configuration for PythonRunner. * * It knows how to extract proper launching arguments from disparate sources. * Has many launch utility functions (getCommandLine & friends). */ public class PythonRunnerConfig { public static final String RUN_COVERAGE = "python code coverage run"; public static final String RUN_REGULAR = "python regular run"; public static final String RUN_UNITTEST = "pyton unittest run"; public static final String RUN_JYTHON_UNITTEST = "jython unittest run"; public static final String RUN_JYTHON = "jython regular run"; public static final String RUN_IRONPYTHON = "iron python regular run"; public static final String RUN_IRONPYTHON_UNITTEST = "iron python unittest run"; public final IProject project; public final IPath[] resource; public final IPath interpreter; public final IInterpreterInfo interpreterLocation; private final String arguments; public final File workingDirectory; public final String pythonpathUsed; // debugging public final boolean isDebug; public final boolean isInteractive; public int acceptTimeout = 5000; // miliseconds public String[] envp = null; /** One of RUN_ enums */ public final String run; private final ILaunchConfiguration configuration; private ListenConnector listenConnector; private PyUnitServer pyUnitServer; // public boolean isCoverage(){ // return this.run.equals(RUN_COVERAGE); // } public boolean isUnittest() { return this.run.equals(RUN_UNITTEST) || this.run.equals(RUN_JYTHON_UNITTEST) || this.run.equals(RUN_IRONPYTHON_UNITTEST); } public boolean isJython() { return this.run.equals(RUN_JYTHON) || this.run.equals(RUN_JYTHON_UNITTEST); } public boolean isIronpython() { return this.run.equals(RUN_IRONPYTHON) || this.run.equals(RUN_IRONPYTHON_UNITTEST); } public boolean isFile() throws CoreException { int resourceType = configuration.getAttribute(Constants.ATTR_RESOURCE_TYPE, -1); return resourceType == IResource.FILE; } /* * Expands and returns the location attribute of the given launch configuration. The location is verified to point * to an existing file, in the local file system. * * @param configuration launch configuration * * @return an absolute path to a file in the local file system * * @throws CoreException if unable to retrieve the associated launch configuration attribute, if unable to resolve * any variables, or if the resolved location does not point to an existing file in the local file system */ public static IPath[] getLocation(ILaunchConfiguration configuration, IPythonNature nature) throws CoreException { String locationsStr = configuration.getAttribute(Constants.ATTR_ALTERNATE_LOCATION, (String) null); if (locationsStr == null) { locationsStr = configuration.getAttribute(Constants.ATTR_LOCATION, (String) null); } if (locationsStr == null) { throw new CoreException(PydevDebugPlugin.makeStatus(IStatus.ERROR, "Unable to get location for run", null)); } List<String> locations = StringUtils.splitAndRemoveEmptyTrimmed(locationsStr, '|'); Path[] ret = new Path[locations.size()]; int i = 0; for (String location : locations) { String expandedLocation = getStringSubstitution(nature).performStringSubstitution(location); if (expandedLocation == null || expandedLocation.length() == 0) { throw new CoreException(PydevDebugPlugin.makeStatus(IStatus.ERROR, "Unable to get expanded location for run", null)); } else { ret[i] = new Path(expandedLocation); } i++; } return ret; } /** * Expands and returns the arguments attribute of the given launch * configuration. Returns <code>null</code> if arguments are not specified. * * @param configuration launch configuration * @return an array of resolved arguments, or <code>null</code> if * unspecified * @throws CoreException if unable to retrieve the associated launch * configuration attribute, or if unable to resolve any variables */ public static String getArguments(ILaunchConfiguration configuration, boolean makeArgumentsVariableSubstitution) throws CoreException { String arguments = configuration.getAttribute(Constants.ATTR_PROGRAM_ARGUMENTS, ""); if (makeArgumentsVariableSubstitution) { return VariablesPlugin.getDefault().getStringVariableManager().performStringSubstitution(arguments); } else { return arguments; } } /** * Parses the argument text into an array of individual * strings using the space character as the delimiter. * An individual argument containing spaces must have a * double quote (") at the start and end. Two double * quotes together is taken to mean an embedded double * quote in the argument text. * * @param arguments the arguments as one string * @return the array of arguments */ public static String[] parseStringIntoList(String arguments) { return ProcessUtils.parseArguments(arguments); } private static StringSubstitution getStringSubstitution(IPythonNature nature) { return new StringSubstitution(nature); } /** * Expands and returns the working directory attribute of the given launch * configuration. Returns <code>null</code> if a working directory is not * specified. If specified, the working is verified to point to an existing * directory in the local file system. * * @param configuration launch configuration * @return an absolute path to a directory in the local file system, or * <code>null</code> if unspecified * @throws CoreException if unable to retrieve the associated launch * configuration attribute, if unable to resolve any variables, or if the * resolved location does not point to an existing directory in the local * file system */ public static IPath getWorkingDirectory(ILaunchConfiguration configuration, IPythonNature nature) throws CoreException { IProject project = nature.getProject(); String location = configuration.getAttribute(Constants.ATTR_WORKING_DIRECTORY, "${project_loc:/" + project.getName() + "}"); if (location != null) { String expandedLocation = getStringSubstitution(nature).performStringSubstitution(location); if (expandedLocation.length() > 0) { File path = new File(expandedLocation); if (path.isDirectory()) { return new Path(expandedLocation); } throw new CoreException(PydevDebugPlugin.makeStatus(IStatus.ERROR, "Unable to get working location for the run \n(the location: '" + expandedLocation + "' is not a valid directory).", null)); } } return null; } /** * Returns the location of the selected interpreter in the launch configuration * @param conf * @return the string location of the selected interpreter in the launch configuration * @throws CoreException if unable to retrieve the launch configuration attribute or if unable to * resolve the default interpreter. * @throws MisconfigurationException */ public static IInterpreterInfo getInterpreterLocation(ILaunchConfiguration conf, IPythonNature nature, IInterpreterManager interpreterManager) throws InvalidRunException, CoreException, MisconfigurationException { String location = conf.getAttribute(Constants.ATTR_INTERPRETER, Constants.ATTR_INTERPRETER_DEFAULT); if (location != null && location.equals(Constants.ATTR_INTERPRETER_DEFAULT)) { if (nature != null && nature.getInterpreterType() == interpreterManager.getInterpreterType()) { //When both, the interpreter for the launch and the nature have the same type, let's get the //launch location from the project try { return nature.getProjectInterpreter(); } catch (PythonNatureWithoutProjectException e) { throw new RuntimeException(e); } } else { //When it doesn't have the same type it means that we're trying to run as jython a python //project (or vice-versa), so, we must get the interpreter from the interpreter manager! return interpreterManager.getDefaultInterpreterInfo(true); } } else { IInterpreterInfo interpreterInfo = interpreterManager.getInterpreterInfo(location, null); if (interpreterInfo != null) { return interpreterInfo; } else { File file = new File(location); if (!file.exists()) { throw new InvalidRunException("Error. The interprer: " + location + " does not exist"); } else { //it does not have information on the given interpreter!! if (nature == null) { throw new InvalidRunException("Error. The interpreter: >>" + location + "<< is not configured in the pydev preferences as a valid interpreter (null nature)."); } else { throw new InvalidRunException("Error. The interpreter: >>" + location + "<< is not configured in the pydev preferences as a valid '" + nature.getVersion() + "' interpreter."); } } } } } /** * Expands and returns the python interpreter attribute of the given launch * configuration. The interpreter path is verified to point to an existing * file in the local file system. * * @param configuration launch configuration * @return an absolute path to the interpreter in the local file system * @throws CoreException if unable to retrieve the associated launch * configuration attribute, if unable to resolve any variables, or if the * resolved location does not point to an existing directory in the local * file system * @throws InvalidRunException */ private IPath getInterpreter(IInterpreterInfo location, ILaunchConfiguration configuration, IPythonNature nature) throws CoreException, InvalidRunException { if (location == null) { throw new CoreException(PydevDebugPlugin.makeStatus(IStatus.ERROR, "Unable to get python interpreter for run", null)); } else { String expandedLocation = getStringSubstitution(nature).performStringSubstitution( location.getExecutableOrJar()); if (expandedLocation == null || expandedLocation.length() == 0) { throw new CoreException(PydevDebugPlugin.makeStatus(IStatus.ERROR, "Unable to get expanded interpreter for run", null)); } else { return new Path(expandedLocation); } } } /** * Gets the project that should be used for a launch configuration * @param conf the launch configuration from where the project should be gotten * @return the related IProject * @throws CoreException */ public static IProject getProjectFromConfiguration(ILaunchConfiguration conf) throws CoreException { String projName = conf.getAttribute(Constants.ATTR_PROJECT, ""); if (projName == null || projName.length() == 0) { throw new CoreException(PydevDebugPlugin.makeStatus(IStatus.ERROR, "Unable to get project for the run", null)); } IWorkspace w = ResourcesPlugin.getWorkspace(); IProject p = w.getRoot().getProject(projName); if (p == null || !p.exists()) { // Ok, we could not find it out throw new CoreException(PydevDebugPlugin.makeStatus(IStatus.ERROR, "Could not get project: " + projName, null)); } return p; } /** * Can be used to extract the pythonpath used from a given configuration. * * @param conf the configuration from where we want to get the pythonpath * @return a string with the pythonpath used (with | as a separator) * @throws CoreException * @throws InvalidRunException * @throws MisconfigurationException */ public static String getPythonpathFromConfiguration(ILaunchConfiguration conf, IInterpreterManager manager) throws CoreException, InvalidRunException, MisconfigurationException { IProject p = getProjectFromConfiguration(conf); PythonNature pythonNature = PythonNature.getPythonNature(p); if (pythonNature == null) { throw new CoreException(PydevDebugPlugin.makeStatus(IStatus.ERROR, "Project should have a python nature: " + p.getName(), null)); } IInterpreterInfo l = getInterpreterLocation(conf, pythonNature, manager); return SimpleRunner.makePythonPathEnvString(pythonNature, l, manager); } public PythonRunnerConfig(ILaunchConfiguration conf, String mode, String run) throws CoreException, InvalidRunException, MisconfigurationException { this(conf, mode, run, true); } /** * Sets defaults. * @throws InvalidRunException * @throws MisconfigurationException */ @SuppressWarnings("unchecked") public PythonRunnerConfig(ILaunchConfiguration conf, String mode, String run, boolean makeArgumentsVariableSubstitution) throws CoreException, InvalidRunException, MisconfigurationException { //1st thing, see if this is a valid run. project = getProjectFromConfiguration(conf); if (project == null) { //Ok, we could not find it out throw Log.log("Could not get project for configuration: " + conf); } // We need the project to find out the default interpreter from the InterpreterManager. IPythonNature pythonNature = PythonNature.getPythonNature(project); if (pythonNature == null) { CoreException e = Log.log("No python nature for project: " + project.getName()); throw e; } //now, go on configuring other things this.configuration = conf; this.run = run; isDebug = mode.equals(ILaunchManager.DEBUG_MODE); isInteractive = mode.equals("interactive"); resource = getLocation(conf, pythonNature); arguments = getArguments(conf, makeArgumentsVariableSubstitution); IPath workingPath = getWorkingDirectory(conf, pythonNature); workingDirectory = workingPath == null ? null : workingPath.toFile(); acceptTimeout = PydevPrefs.getPreferences().getInt(PydevEditorPrefs.CONNECT_TIMEOUT); interpreterLocation = getInterpreterLocation(conf, pythonNature, this.getRelatedInterpreterManager()); interpreter = getInterpreter(interpreterLocation, conf, pythonNature); //make the environment ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager(); envp = launchManager.getEnvironment(conf); IInterpreterManager manager; if (isJython()) { manager = PydevPlugin.getJythonInterpreterManager(); } else if (isIronpython()) { manager = PydevPlugin.getIronpythonInterpreterManager(); } else { manager = PydevPlugin.getPythonInterpreterManager(); } boolean win32 = PlatformUtils.isWindowsPlatform(); if (envp == null) { //ok, the user has done nothing to the environment, just get all the default environment which has the pythonpath in it envp = SimpleRunner.getEnvironment(pythonNature, interpreterLocation, manager); } else { //ok, the user has done something to configure it, so, just add the pythonpath to the //current env (if he still didn't do so) Map envMap = conf.getAttribute(ILaunchManager.ATTR_ENVIRONMENT_VARIABLES, (Map) null); String pythonpath = SimpleRunner.makePythonPathEnvString(pythonNature, interpreterLocation, manager); updateVar(pythonNature, manager, win32, envMap, "PYTHONPATH", pythonpath); if (isJython()) { //Also update the classpath env variable. updateVar(pythonNature, manager, win32, envMap, "CLASSPATH", pythonpath); // And the jythonpath env variable updateVar(pythonNature, manager, win32, envMap, "JYTHONPATH", pythonpath); } else if (isIronpython()) { //Also update the ironpythonpath env variable. updateVar(pythonNature, manager, win32, envMap, "IRONPYTHONPATH", pythonpath); } //And we also must get the environment variables specified in the interpreter manager. envp = interpreterLocation.updateEnv(envp, envMap.keySet()); } boolean hasDjangoNature = project.hasNature(PythonNature.DJANGO_NATURE_ID); String settingsModule = null; Map<String, String> variableSubstitution = null; final String djangoSettingsKey = "DJANGO_SETTINGS_MODULE"; String djangoSettingsEnvEntry = null; try { variableSubstitution = pythonNature.getPythonPathNature().getVariableSubstitution(); settingsModule = variableSubstitution.get(djangoSettingsKey); if (settingsModule != null) { if (settingsModule.trim().length() > 0) { djangoSettingsEnvEntry = djangoSettingsKey + "=" + settingsModule.trim(); } } } catch (Exception e1) { Log.log(e1); } if (djangoSettingsEnvEntry == null && hasDjangoNature) { //Default if not specified (only add it if the nature is there). djangoSettingsEnvEntry = djangoSettingsKey + "=" + project.getName() + ".settings"; } //Note: set flag even if not debugging as the user may use remote-debugging later on. boolean geventSupport = DebugPrefsPage.getGeventDebugging() && pythonNature.getInterpreterType() == IPythonNature.INTERPRETER_TYPE_PYTHON; //Now, set the pythonpathUsed according to what's in the environment. String p = ""; for (int i = 0; i < envp.length; i++) { String s = envp[i]; Tuple<String, String> tup = StringUtils.splitOnFirst(s, '='); String var = tup.o1; if (win32) { //On windows it doesn't matter, always consider uppercase. var = var.toUpperCase(); } if (var.equals("PYTHONPATH")) { p = tup.o2; } else if (var.equals(djangoSettingsKey)) { //Update it. if (djangoSettingsEnvEntry != null) { envp[i] = djangoSettingsEnvEntry; djangoSettingsEnvEntry = null; } } if (geventSupport) { if (var.equals("GEVENT_SUPPORT")) { //Flag already set in the environment geventSupport = false; } } } //Still not added, let's do that now. if (djangoSettingsEnvEntry != null) { envp = StringUtils.addString(envp, djangoSettingsEnvEntry); } if (geventSupport) { envp = StringUtils.addString(envp, "GEVENT_SUPPORT=True"); } this.pythonpathUsed = p; } @SuppressWarnings("unchecked") private void updateVar(IPythonNature pythonNature, IInterpreterManager manager, boolean win32, Map envMap, String var, String pythonpath) { if (!specifiedEnvVar(envMap, var)) { boolean addPythonpath = true; //override it if it was the ambient pythonpath for (int i = 0; i < envp.length; i++) { if (win32) { //case insensitive if (envp[i].toUpperCase().startsWith(var + "=")) { //OK, finish it. envp[i] = var + "=" + pythonpath; addPythonpath = false; break; } } else { if (envp[i].startsWith(var + "=")) { //OK, finish it. envp[i] = var + "=" + pythonpath; addPythonpath = false; break; } } } if (addPythonpath) { //there was no pythonpath, let's set it String[] s = new String[envp.length + 1]; System.arraycopy(envp, 0, s, 0, envp.length); s[s.length - 1] = var + "=" + pythonpath; envp = s; } } } /** * Check if map the passed env var key. * * Variables names are considered not case sensitive on Windows. * * @param envMap mapping of env variables and their values * @return {@code true} if passed map contain PYTHONPATH key. */ private boolean specifiedEnvVar(Map<String, String> envMap, String var) { if (envMap == null) { return false; } boolean win32 = Platform.getOS().equals(org.eclipse.osgi.service.environment.Constants.OS_WIN32); if (!win32) { return envMap.containsKey(var); } //it is windows (consider all uppercase) var = var.toUpperCase(); for (Iterator<String> iter = envMap.keySet().iterator(); iter.hasNext();) { String s = iter.next(); if (s.toUpperCase().equals(var)) { return true; } } return false; } /** * @return attribute value of {@code IProcess.ATTR_PROCESS_TYPE} */ public String getProcessType() { return isJython() ? "java" : Constants.PROCESS_TYPE; } public static String getRunningName(IPath[] paths) { if (paths == null || paths.length == 0) { return ""; } FastStringBuffer buf = new FastStringBuffer(20 * paths.length); for (IPath p : paths) { if (buf.length() > 0) { buf.append(" - "); } buf.append(p.lastSegment()); } return buf.toString(); } public String getConsoleLabel(String[] commandLine) { ArrayList<String> lst = new ArrayList<>(); lst.add(getRunningName(resource)); if (isDebug) { lst.add(" [debug]"); } if (isUnittest()) { lst.add(" [unittest]"); } if (isInteractive) { lst.add(" [interactive]"); } if (PyProfilePreferences.getAllRunsDoProfile()) { lst.add(" [profile]"); } if (PyCoveragePreferences.getAllRunsDoCoverage()) { lst.add(" [coverage]"); } if (commandLine.length > 0) { lst.add(" ["); lst.add(commandLine[0]); lst.add("]"); } return StringUtils.join("", lst); } /** * @return * @throws CoreException */ public static String getCoverageScript() throws CoreException { return FileUtils.getFileAbsolutePath(PydevDebugPlugin.getScriptWithinPySrc("pydev_coverage.py")); } /** * Gets location of pydevd.py * @note: Used on scripting (variables related to debugger location). */ public static String getDebugScript() throws CoreException { return FileUtils.getFileAbsolutePath(PydevDebugPlugin.getScriptWithinPySrc("pydevd.py")); } public static String getRunFilesScript() throws CoreException { return FileUtils.getFileAbsolutePath(PydevDebugPlugin.getScriptWithinPySrc("runfiles.py")); } /** * Create a command line for launching. * * @param actualRun if true it'll make the variable substitution and start the listen connector in the case * of a debug session. * * @return command line ready to be exec'd * @throws CoreException * @throws JDTNotAvailableException */ public String[] getCommandLine(boolean actualRun) throws CoreException, JDTNotAvailableException { List<String> cmdArgs = new ArrayList<String>(); boolean profileRun = PyProfilePreferences.getAllRunsDoProfile(); boolean coverageRun = PyCoveragePreferences.getAllRunsDoCoverage(); boolean addWithDashMFlag = false; String modName = null; if (resource.length == 1 && RunPreferencesPage.getLaunchWithMFlag() && !isUnittest() && !coverageRun && !isInteractive) { IPath p = resource[0]; String osString = p.toOSString(); PythonNature pythonNature = PythonNature.getPythonNature(project); modName = pythonNature.resolveModule(osString); if (modName != null) { addWithDashMFlag = true; } } if (isJython()) { //"java.exe" -classpath "C:\bin\jython21\jython.jar" org.python.util.jython script %ARGS% String javaLoc = JavaVmLocationFinder.findDefaultJavaExecutable().getAbsolutePath(); if (!InterpreterInfo.isJythonExecutable(interpreter.toOSString())) { throw new RuntimeException("The jython jar must be specified as the interpreter to run. Found: " + interpreter); } cmdArgs.add(javaLoc); //some nice things on the classpath config: http://mindprod.com/jgloss/classpath.html cmdArgs.add("-classpath"); String cpath; //TODO: add some option in the project so that the user can choose to use the //classpath specified in the java project instead of the pythonpath itself // if (project.getNature(Constants.JAVA_NATURE) != null){ // cpath = getClasspath(JavaCore.create(project)); // } else { cpath = interpreter + SimpleRunner.getPythonPathSeparator() + pythonpathUsed; // } cmdArgs.add(cpath); cmdArgs.add("-Dpython.path=" + pythonpathUsed); //will be added to the env variables in the run (check if this works on all platforms...) addVmArgs(cmdArgs); addProfileArgs(cmdArgs, profileRun, actualRun); if (isDebug) { //This was removed because it cannot be used. See: //http://bugs.jython.org/issue1438 //cmdArgs.add("-Dpython.security.respectJavaAccessibility=false"); cmdArgs.add("org.python.util.jython"); addDebugArgs(cmdArgs, "jython", actualRun, addWithDashMFlag, modName); } else { cmdArgs.add("org.python.util.jython"); } } else { //python or iron python cmdArgs.add(interpreter.toOSString()); // Next option is for unbuffered stdout, otherwise Eclipse will not see any output until done cmdArgs.add("-u"); addVmArgs(cmdArgs); addProfileArgs(cmdArgs, profileRun, actualRun); if (isDebug && isIronpython()) { addIronPythonDebugVmArgs(cmdArgs); } addDebugArgs(cmdArgs, "python", actualRun, addWithDashMFlag, modName); } //Check if we should do code-coverage... if (coverageRun && isDebug) { if (actualRun) { RunInUiThread.async(new Runnable() { @Override public void run() { PyDialogHelpers .openWarning( "Conflicting options: coverage with debug.", "Making a debug run with coverage enabled will not yield the expected results.\n\n" + "They'll conflict because both use the python tracing facility (i.e.: sys.settrace()).\n" + "\n" + "To debug a coverage run, do a regular run and use the remote debugger " + "(but note that the coverage will stop when it's enabled).\n" + "\n" + "Note: the run will be continued anyways."); } }); } } if (isUnittest()) { cmdArgs.add(getRunFilesScript()); } else { if (coverageRun) { //Separate support (unittest has the coverage support builtin). cmdArgs.add(getCoverageScript()); cmdArgs.add(PyCoverage.getCoverageFileLocation().getAbsolutePath()); cmdArgs.add("run"); cmdArgs.add("--source"); cmdArgs.add(PyCodeCoverageView.getChosenDir().getLocation().toOSString()); } } if (!addWithDashMFlag) { for (IPath p : resource) { cmdArgs.add(p.toOSString()); } } else { if (!isDebug) { cmdArgs.add("-m"); cmdArgs.add(modName); } } if (!isUnittest()) { //The program arguments are not used when running a unittest (excluded from the tab group in favor //of a way to overriding the default unittest arguments). String runArguments[] = null; if (actualRun && arguments != null) { String expanded = getStringSubstitution(PythonNature.getPythonNature(project)) .performStringSubstitution(arguments); runArguments = parseStringIntoList(expanded); } for (int i = 0; runArguments != null && i < runArguments.length; i++) { cmdArgs.add(runArguments[i]); } } else { //Last thing (first the files and last the special parameters the user passed -- i.e.: nose parameters) addUnittestArgs(cmdArgs, actualRun, coverageRun); } String[] retVal = new String[cmdArgs.size()]; cmdArgs.toArray(retVal); if (actualRun) { CreatedCommandLineParams createdCommandLineParams = new CreatedCommandLineParams(retVal, coverageRun); //Provide a way for clients to alter the command line. List<Object> participants = ExtensionHelper.getParticipants(ExtensionHelper.PYDEV_COMMAND_LINE_PARTICIPANT); for (Object object : participants) { try { IPyCommandLineParticipant c = (IPyCommandLineParticipant) object; createdCommandLineParams = c.updateCommandLine(createdCommandLineParams); } catch (Exception e) { Log.log(e); } } retVal = createdCommandLineParams.cmdLine; PythonRunnerCallbacks.onCreatedCommandLine.call(createdCommandLineParams); } return retVal; } private void addProfileArgs(List<String> cmdArgs, boolean profileRun, boolean actualRun) { PyProfilePreferences.addProfileArgs(cmdArgs, profileRun, actualRun); } private void addIronPythonDebugVmArgs(List<String> cmdArgs) { if (cmdArgs.contains("-X:Frames") || cmdArgs.contains("-X:FullFrames")) { return; } //The iron python debugger must have frames (preferably FullFrames), otherwise it won't work. cmdArgs.add("-X:FullFrames"); } /** * Adds a set of arguments used to wrap executed file with unittest runner. * @param actualRun in an actual run we'll start the xml-rpc server. * @param coverageRun whether we should add the flags to do a coverage run. */ private void addUnittestArgs(List<String> cmdArgs, boolean actualRun, boolean coverageRun) throws CoreException { if (isUnittest()) { //The tests are either written to a configuration file or passed as a parameter. String configurationFile = this.configuration.getAttribute(Constants.ATTR_UNITTEST_CONFIGURATION_FILE, ""); if (configurationFile.length() > 0) { cmdArgs.add("--config_file"); if (actualRun) { //We should write the contents to a temporary file (because it may be too long, so, always write //to a file and read from it later on). File tempFile = PydevPlugin.getDefault().getTempFile("custom_pydev_unittest_launch_"); try { OutputStream fileOutputStream = new FileOutputStream(tempFile); try { try { fileOutputStream.write(configurationFile.getBytes()); } catch (IOException e) { throw new CoreException(PydevPlugin.makeStatus(IStatus.ERROR, "Error writing to: " + tempFile, e)); } } finally { fileOutputStream.close(); } } catch (Exception e) { if (e instanceof CoreException) { throw (CoreException) e; } throw new CoreException(PydevPlugin.makeStatus(IStatus.ERROR, "Error writing to: " + tempFile, e)); } cmdArgs.add(tempFile.toString()); } else { cmdArgs.add(configurationFile); } } else { String tests = this.configuration.getAttribute(Constants.ATTR_UNITTEST_TESTS, ""); if (tests.length() > 0) { cmdArgs.add("--tests"); cmdArgs.add(tests); } } if (PyUnitPrefsPage2.getUsePyUnitView(project)) { //If we want to use the PyUnitView, we need to get the port used so that the python side can connect. cmdArgs.add("--port"); if (actualRun) { cmdArgs.add(String.valueOf(getPyUnitServer().getPort())); } else { cmdArgs.add("0"); } } if (coverageRun) { cmdArgs.add("--coverage_output_dir"); cmdArgs.add(PyCoverage.getCoverageDirLocation().getAbsolutePath()); cmdArgs.add("--coverage_include"); cmdArgs.add(PyCodeCoverageView.getChosenDir().getLocation().toOSString()); if (actualRun) { int testRunner = PyUnitPrefsPage2.getTestRunner(this.configuration, project); switch (testRunner) { case PyUnitPrefsPage2.TEST_RUNNER_NOSE: RunInUiThread.async(new Runnable() { @Override public void run() { PyDialogHelpers .openWarningWithIgnoreToggle( "Notes for coverage with the nose test runner.", "Note1: When using the coverage with the nose test runner, " + "please don't pass any specific parameter related to " + "the run in the arguments, as that's already handled by PyDev " + "(i.e.: don't use the builtin cover plugin from nose).\n" + "\n" + "Note2: It's currently not possible to use coverage with the multi-process " + "plugin in nose.", "KEY_COVERAGE_WITH_NOSE_TEST_RUNNER"); } }); break; case PyUnitPrefsPage2.TEST_RUNNER_PY_TEST: RunInUiThread.async(new Runnable() { @Override public void run() { PyDialogHelpers .openCritical( "PyUnit coverage not compatible with the Py.test test runner.", "Currently the PyDev PyUnit integration is not able to provide coverage " + "info using the py.test test runner (please enter a " + "feature request if you'd like that added)\n" + "\n" + "Note: the run will be continued anyways (without gathering coverage info)."); } }); break; } } } //Last thing: nose parameters or parameters the user configured. for (String s : parseStringIntoList(PyUnitPrefsPage2.getTestRunnerParameters(this.configuration, this.project))) { cmdArgs.add(s); } } } /** * Adds a set of arguments needed for debugging. * @param modName * @param addWithDashMFlag */ private void addDebugArgs(List<String> cmdArgs, String vmType, boolean actualRun, boolean addWithDashMFlag, String modName) throws CoreException { if (isDebug) { cmdArgs.add(getDebugScript()); if (DebugPrefsPage.getDebugMultiprocessingEnabled()) { cmdArgs.add("--multiprocess"); } cmdArgs.add("--print-in-debugger-startup"); String qtThreadsDebugMode = DebugPrefsPage.getQtThreadsDebugMode(); if (qtThreadsDebugMode != null && qtThreadsDebugMode.length() > 0 && !qtThreadsDebugMode.equals("none")) { switch (qtThreadsDebugMode) { case "auto": case "pyqt5": case "pyqt4": case "pyside": cmdArgs.add("--qt-support=" + qtThreadsDebugMode); break; } } cmdArgs.add("--vm_type"); cmdArgs.add(vmType); cmdArgs.add("--client"); cmdArgs.add(LocalHost.getLocalHost()); cmdArgs.add("--port"); if (actualRun) { try { cmdArgs.add(Integer.toString(getDebuggerListenConnector().getLocalPort())); } catch (IOException e) { throw new CoreException(PydevPlugin.makeStatus(IStatus.ERROR, "Unable to get port", e)); } } else { cmdArgs.add("0"); } if (addWithDashMFlag) { cmdArgs.add("--module"); cmdArgs.add("--file"); cmdArgs.add(modName); } else { cmdArgs.add("--file"); } } } /** * @param cmdArgs * @throws CoreException */ private void addVmArgs(List<String> cmdArgs) throws CoreException { String[] vmArguments = getVMArguments(configuration); if (vmArguments != null) { for (int i = 0; i < vmArguments.length; i++) { cmdArgs.add(vmArguments[i]); } } } /** * @return an array with the vm arguments in the given configuration. * @throws CoreException */ private String[] getVMArguments(ILaunchConfiguration configuration) throws CoreException { String args = configuration.getAttribute(Constants.ATTR_VM_ARGUMENTS, (String) null); if (args != null && args.trim().length() > 0) { String expanded = getStringSubstitution(PythonNature.getPythonNature(project)).performStringSubstitution( args); return parseStringIntoList(expanded); } return null; } /** * @return A command line to be shown to the user. Note that this command line should not actually be used for * an execution (only String[] should be passed to Runtie.exec) * @throws JDTNotAvailableException */ public String getCommandLineAsString() throws JDTNotAvailableException { String[] args; try { args = getCommandLine(false); return SimpleRunner.getArgumentsAsStr(args); } catch (CoreException e) { throw new RuntimeException(e); } } public IInterpreterManager getRelatedInterpreterManager() { if (isJython()) { return PydevPlugin.getJythonInterpreterManager(); } if (isIronpython()) { return PydevPlugin.getIronpythonInterpreterManager(); } return PydevPlugin.getPythonInterpreterManager(); } public PyUnitServer getPyUnitServer() { return this.pyUnitServer; } public synchronized ListenConnector getDebuggerListenConnector() throws IOException { if (this.listenConnector == null) { this.listenConnector = new ListenConnector(this.acceptTimeout); } return this.listenConnector; } public ILaunchConfiguration getLaunchConfiguration() { return this.configuration; } public PyUnitServer createPyUnitServer(PythonRunnerConfig config, ILaunch launch) throws IOException { if (this.pyUnitServer != null) { throw new AssertionError("PyUnitServer already created!"); } this.pyUnitServer = new PyUnitServer(config, launch); return this.pyUnitServer; } }