/**
* 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 August 20, 2013
*
* @author Andrew Ferrazzutti
*/
package org.python.pydev.ui.pythonpathconf;
import java.io.File;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.swt.widgets.Shell;
import org.python.copiedfromeclipsesrc.JDTNotAvailableException;
import org.python.pydev.core.IInterpreterInfo;
import org.python.pydev.core.IInterpreterManager;
import org.python.pydev.plugin.PydevPlugin;
import org.python.pydev.runners.SimpleJythonRunner;
import org.python.pydev.shared_core.SharedCorePlugin;
import org.python.pydev.shared_core.string.StringUtils;
import org.python.pydev.shared_core.structure.Tuple;
import org.python.pydev.shared_ui.utils.AsynchronousProgressMonitorDialog;
import org.python.pydev.ui.dialogs.PyDialogHelpers;
import org.python.pydev.ui.filetypes.FileTypesPreferencesPage;
/**
* Contains some static methods to be used for configuring PyDev interpreters.
* (Private code from {@link AbstractInterpreterEditor} was moved here & made
* static so as to be usable without needing an editor.)
*
* @author Andrew Ferrazzutti
*/
public class InterpreterConfigHelpers {
public final static int CONFIG_MANUAL = 0;
public final static int CONFIG_AUTO = 1;
public final static int CONFIG_ADV_AUTO = 2;
public final static String[] CONFIG_NAMES = new String[] { "Manual Config", "Quick Auto-Config",
"Advanced Auto-Config" };
public final static int NUM_CONFIG_TYPES = 3;
public final static String ERMSG_NOLIBS = "The interpreter's standard libraries (typically in a Lib/ folder) are missing: ";
/**
* Attempts to set up a provided interpreter.
*
* @param interpreterNameAndExecutable Information pertaining to the interpreter to prepare.
* @param interpreterManager
* @param autoSelectFolders If true, folders will be automatically added to the SYSTEM pythonpath.
* Otherwise, they must be selected manually with a dialog.
* @param displayErrors Set to true to display an error dialog on failure, or false to fail silently.
* @param logger
* @param shell A mandatory shell in which to display progress and errors.
* @return The interpreter config operation, or <code>null</code> if the operation is cancelled.
* @throws Exception Will be thrown if an operation fails.
*/
static ObtainInterpreterInfoOperation tryInterpreter(Tuple<String, String> interpreterNameAndExecutable,
IInterpreterManager interpreterManager, boolean autoSelectFolders, boolean displayErrors,
PrintWriter logger, Shell shell) throws Exception {
String executable = interpreterNameAndExecutable.o2;
logger.println("- Ok, file is non-null. Getting info on:" + executable);
ProgressMonitorDialog monitorDialog = new AsynchronousProgressMonitorDialog(shell);
monitorDialog.setBlockOnOpen(false);
ObtainInterpreterInfoOperation operation;
while (true) {
operation = new ObtainInterpreterInfoOperation(interpreterNameAndExecutable.o2, logger,
interpreterManager, autoSelectFolders);
monitorDialog.run(true, false, operation);
if (operation.e != null) {
logger.println("- Some error happened while getting info on the interpreter:");
operation.e.printStackTrace(logger);
String errorTitle = "Unable to get info on the interpreter: " + executable;
if (operation.e instanceof SimpleJythonRunner.JavaNotConfiguredException) {
SimpleJythonRunner.JavaNotConfiguredException javaNotConfiguredException = (SimpleJythonRunner.JavaNotConfiguredException) operation.e;
if (displayErrors) {
ErrorDialog.openError(shell, errorTitle,
javaNotConfiguredException.getMessage(), PydevPlugin.makeStatus(IStatus.ERROR,
"Java vm not configured.\n", javaNotConfiguredException));
}
throw new Exception(javaNotConfiguredException);
} else if (operation.e instanceof JDTNotAvailableException) {
JDTNotAvailableException noJdtException = (JDTNotAvailableException) operation.e;
if (displayErrors) {
ErrorDialog.openError(shell, errorTitle,
noJdtException.getMessage(),
PydevPlugin.makeStatus(IStatus.ERROR, "JDT not available.\n", noJdtException));
}
throw new Exception(noJdtException);
} else {
if (displayErrors) {
//show the user a message (so that it does not fail silently)...
String errorMsg = "Unable to get info on the interpreter: " + executable
+ "\n\n"
+ "Common reasons include:\n\n" + "- Using an unsupported version\n"
+ " (Python and Jython require at least version 2.1 and IronPython 2.6).\n"
+ "\n" + "- Specifying an invalid interpreter\n"
+ " (usually a link to the actual interpreter on Mac or Linux)" + "";
ErrorDialog.openError(shell, errorTitle,
errorMsg, PydevPlugin.makeStatus(IStatus.ERROR, "See error log for details.",
operation.e));
}
throw new Exception(operation.e);
}
} else if (operation.result == null) {
//Folder selection was canceled, exit
return null;
}
//Ok, we got the result, so, let's check if things are correct (i.e.: do we have threading.py, traceback.py?)
HashSet<String> hashSet = new HashSet<String>();
hashSet.add("threading");
hashSet.add("traceback");
String[] validSourceFiles = FileTypesPreferencesPage.getValidSourceFiles();
Set<String> extensions = new HashSet<String>(Arrays.asList(validSourceFiles));
for (String s : operation.result.libs) {
File file = new File(s);
if (file.isDirectory()) {
String[] directoryFiles = file.list();
if (directoryFiles != null) {
for (String found : directoryFiles) {
List<String> split = StringUtils.split(found, '.');
if (split.size() == 2) {
if (extensions.contains(split.get(1))) {
hashSet.remove(split.get(0));
}
}
}
} else {
logger.append("Warning: unable to get contents of directory: "
+ file
+ " (permission not available, it's not a dir or dir does not exist).");
}
} else if (file.isFile()) {
//Zip file?
try {
try (ZipFile zipFile = new ZipFile(file)) {
for (String extension : validSourceFiles) {
if (zipFile.getEntry("threading." + extension) != null) {
hashSet.remove("threading");
}
if (zipFile.getEntry("traceback." + extension) != null) {
hashSet.remove("traceback");
}
}
}
} catch (Exception e) {
//ignore (not zip file)
}
}
}
if (hashSet.size() > 0) {
if (displayErrors) {
//The /Lib folder wasn't there (or at least threading.py and traceback.py weren't found)
int choice = PyDialogHelpers
.openCriticalWithChoices(
"Error: Python stdlib source files not found.",
"Error: Python stdlib not found or stdlib found without .py files.\n"
+ "\n"
+ "It seems that the Python /Lib folder (which contains the standard library) "
+ "was not found/selected during the install process or the stdlib does not contain "
+ "the required .py files (i.e.: only has .pyc files).\n"
+ "\n"
+ "This folder (which contains files such as threading.py and traceback.py) is "
+ "required for PyDev to function properly, and it must contain the actual source files, not "
+ "only .pyc files. if you don't have the .py files in your install, please use an install from "
+ "python.org or grab the standard library for your install from there.\n"
+ "\n"
+ "If this is a virtualenv install, the /Lib folder from the base install needs to be selected "
+ "(unlike the site-packages which is optional).\n"
+ "\n"
+ "What do you want to do?\n\n"
+ "Note: if you choose to proceed, the /Lib with the standard library .py source files must "
+ "be added later on, otherwise PyDev may not function properly.",
new String[] { "Re-select folders", "Cancel", "Proceed anyways" });
if (choice == 0) {
//Keep on with outer while(true)
continue;
}
if (choice == 1) {
//Return nothing and exit quietly on a cancel
return null;
}
} else {
//Don't allow auto-selection of an interpreter missing these folders
logger.println("- Could not find /Lib folder, exiting with error.");
throw new Exception(ERMSG_NOLIBS + executable);
}
}
operation.result.setName(interpreterNameAndExecutable.o1);
logger.println("- Success getting the info. Result:" + operation.result);
return operation;
}
}
/**
* Performs various error checks on a given interpreter.
* @param interpreterNameAndExecutable The name & executable of the interpreter to check for correctness.
* @param logger
* @param errorMsg An error message to display if an error does occur.
* @param nameToInfo A map of names as keys to the IInterpreterInfos of existing interpreters. Set
* to null if no other interpreters exist at the time of the configuration attempt.
* @param shell An optional shell in which to display errors.
* @return <code>true</code> if the interpreter is valid, or <code>false</code> if it caused an error.
*/
static boolean checkInterpreterNameAndExecutable(Tuple<String, String> interpreterNameAndExecutable,
PrintWriter logger, String errorMsg, Map<String, IInterpreterInfo> nameToInfo, Shell shell) {
boolean foundError = false;
//Check auto config or dialog return.
if (interpreterNameAndExecutable == null) {
logger.println("- When trimmed, the chosen file was null (returning null).");
if (shell != null) {
ErrorDialog.openError(shell, errorMsg,
"interpreterNameAndExecutable == null",
PydevPlugin.makeStatus(IStatus.ERROR, "interpreterNameAndExecutable == null",
new RuntimeException()));
}
foundError = true;
}
if (!foundError) {
if (interpreterNameAndExecutable.o2.trim().length() == 0) {
logger.println("- When trimmed, the chosen file was empty (returning null).");
if (shell != null) {
ErrorDialog.openError(shell, errorMsg, "interpreterNameAndExecutable size == empty",
PydevPlugin.makeStatus(IStatus.ERROR, "interpreterNameAndExecutable size == empty",
new RuntimeException()));
}
foundError = true;
}
}
if (!foundError && nameToInfo != null) {
String error = getDuplicatedMessageError(interpreterNameAndExecutable.o1, interpreterNameAndExecutable.o2,
nameToInfo);
if (error != null) {
logger.println("- Duplicated interpreter found.");
if (shell != null) {
ErrorDialog.openError(shell, errorMsg, error, PydevPlugin.makeStatus(IStatus.ERROR,
"Duplicated interpreter information", new RuntimeException()));
}
foundError = true;
}
}
return foundError;
}
/**
* Gets a unique name for the interpreter based on an initial expected name.
*/
public static String getUniqueInterpreterName(final String expectedName, Map<String, IInterpreterInfo> nameToInfo) {
if (nameToInfo == null) {
return expectedName;
}
String additional = "";
int i = 0;
while (InterpreterConfigHelpers.getDuplicatedMessageError(
expectedName + additional, null, nameToInfo) != null) {
i++;
additional = String.valueOf(i);
}
return expectedName + additional;
}
/**
* Uses the passed name and executable to see if it'll match against one of the existing
*
* The null parameters are ignored.
*/
public static String getDuplicatedMessageError(String interpreterName, String executableOrJar,
Map<String, IInterpreterInfo> nameToInfo) {
if (nameToInfo == null) {
return null;
}
String error = null;
if (interpreterName != null) {
interpreterName = interpreterName.trim();
if (nameToInfo.containsKey(interpreterName)) {
error = "An interpreter is already configured with the name: " + interpreterName;
}
}
if (executableOrJar != null) {
executableOrJar = executableOrJar.trim();
for (IInterpreterInfo info : nameToInfo.values()) {
if (info.getExecutableOrJar().trim().equals(executableOrJar)) {
error = "An interpreter is already configured with the path: " + executableOrJar;
}
}
}
return error;
}
/**
* Creates a Set of the root paths of all projects (and the workspace root itself).
* @return A HashSet of root paths.
*/
public static HashSet<IPath> getRootPaths() {
HashSet<IPath> rootPaths = new HashSet<IPath>();
if (SharedCorePlugin.inTestMode()) {
return rootPaths;
}
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
IPath rootLocation = root.getLocation().makeAbsolute();
rootPaths.add(rootLocation);
IProject[] projects = root.getProjects();
for (IProject iProject : projects) {
IPath location = iProject.getLocation();
if (location != null) {
IPath abs = location.makeAbsolute();
if (!rootLocation.isPrefixOf(abs)) {
rootPaths.add(abs);
}
}
}
return rootPaths;
}
/**
* States whether or not a given path is the child of at least one root path of a set of root paths.
* @param data The path that will be checked for child status.
* @param rootPaths A set of root paths.
* @return True if the path of data is a child of any of the paths of rootPaths.
*/
public static boolean isChildOfRootPath(String data, Set<IPath> rootPaths) {
IPath path = Path.fromOSString(data);
for (IPath p : rootPaths) {
if (p.isPrefixOf(path)) {
return true;
}
}
return false;
}
}