| /* |
| * 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.google.common.collect.Sets; |
| import com.intellij.execution.DefaultExecutionResult; |
| import com.intellij.execution.ExecutionException; |
| import com.intellij.execution.ExecutionResult; |
| import com.intellij.execution.Executor; |
| import com.intellij.execution.configurations.*; |
| import com.intellij.execution.filters.Filter; |
| import com.intellij.execution.filters.TextConsoleBuilder; |
| import com.intellij.execution.filters.TextConsoleBuilderFactory; |
| import com.intellij.execution.filters.UrlFilter; |
| import com.intellij.execution.process.ProcessHandler; |
| import com.intellij.execution.process.ProcessTerminatedListener; |
| import com.intellij.execution.runners.ExecutionEnvironment; |
| import com.intellij.execution.runners.ProgramRunner; |
| import com.intellij.execution.ui.ConsoleView; |
| import com.intellij.facet.Facet; |
| import com.intellij.facet.FacetManager; |
| import com.intellij.openapi.actionSystem.AnAction; |
| import com.intellij.openapi.module.Module; |
| import com.intellij.openapi.module.ModuleUtilCore; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.projectRoots.Sdk; |
| import com.intellij.openapi.projectRoots.SdkAdditionalData; |
| import com.intellij.openapi.roots.*; |
| import com.intellij.openapi.roots.impl.libraries.LibraryImpl; |
| import com.intellij.openapi.roots.libraries.Library; |
| import com.intellij.openapi.roots.libraries.PersistentLibraryKind; |
| import com.intellij.openapi.util.io.FileUtil; |
| import com.intellij.openapi.util.registry.Registry; |
| import com.intellij.openapi.vfs.JarFileSystem; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.remote.RemoteProcessHandlerBase; |
| import com.intellij.util.PlatformUtils; |
| import com.intellij.util.containers.HashMap; |
| import com.jetbrains.python.PythonHelpersLocator; |
| import com.jetbrains.python.console.PyDebugConsoleBuilder; |
| import com.jetbrains.python.debugger.PyDebugRunner; |
| import com.jetbrains.python.debugger.PyDebuggerOptionsProvider; |
| import com.jetbrains.python.facet.LibraryContributingFacet; |
| import com.jetbrains.python.facet.PythonPathContributingFacet; |
| import com.jetbrains.python.library.PythonLibraryType; |
| import com.jetbrains.python.sdk.PySdkUtil; |
| import com.jetbrains.python.sdk.PythonEnvUtil; |
| import com.jetbrains.python.sdk.PythonSdkAdditionalData; |
| import com.jetbrains.python.sdk.PythonSdkType; |
| import com.jetbrains.python.sdk.flavors.JythonSdkFlavor; |
| import com.jetbrains.python.sdk.flavors.PythonSdkFlavor; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.io.IOException; |
| import java.net.ServerSocket; |
| import java.util.*; |
| |
| /** |
| * @author Leonid Shalupov |
| */ |
| public abstract class PythonCommandLineState extends CommandLineState { |
| |
| // command line has a number of fixed groups of parameters; patchers should only operate on them and not the raw list. |
| |
| public static final String GROUP_EXE_OPTIONS = "Exe Options"; |
| public static final String GROUP_DEBUGGER = "Debugger"; |
| public static final String GROUP_SCRIPT = "Script"; |
| private final AbstractPythonRunConfiguration myConfig; |
| |
| private final List<Filter> myFilters = Lists.<Filter>newArrayList(new UrlFilter()); |
| |
| private Boolean myMultiprocessDebug = null; |
| |
| public boolean isDebug() { |
| return PyDebugRunner.PY_DEBUG_RUNNER.equals(getEnvironment().getRunner().getRunnerId()); |
| } |
| |
| public static ServerSocket createServerSocket() throws ExecutionException { |
| final ServerSocket serverSocket; |
| try { |
| //noinspection SocketOpenedButNotSafelyClosed |
| serverSocket = new ServerSocket(0); |
| } |
| catch (IOException e) { |
| throw new ExecutionException("Failed to find free socket port", e); |
| } |
| return serverSocket; |
| } |
| |
| public PythonCommandLineState(AbstractPythonRunConfiguration runConfiguration, ExecutionEnvironment env) { |
| super(env); |
| myConfig = runConfiguration; |
| } |
| |
| @Nullable |
| public PythonSdkFlavor getSdkFlavor() { |
| return PythonSdkFlavor.getFlavor(myConfig.getInterpreterPath()); |
| } |
| |
| @NotNull |
| @Override |
| public ExecutionResult execute(@NotNull Executor executor, @NotNull ProgramRunner runner) throws ExecutionException { |
| return execute(executor, (CommandLinePatcher[])null); |
| } |
| |
| public ExecutionResult execute(Executor executor, CommandLinePatcher... patchers) throws ExecutionException { |
| final ProcessHandler processHandler = startProcess(patchers); |
| final ConsoleView console = createAndAttachConsole(myConfig.getProject(), processHandler, executor); |
| |
| List<AnAction> actions = Lists.newArrayList(createActions(console, processHandler)); |
| |
| return new DefaultExecutionResult(console, processHandler, actions.toArray(new AnAction[actions.size()])); |
| } |
| |
| @NotNull |
| protected ConsoleView createAndAttachConsole(Project project, ProcessHandler processHandler, Executor executor) |
| throws ExecutionException { |
| final ConsoleView consoleView = createConsoleBuilder(project).filters(myFilters).getConsole(); |
| |
| addTracebackFilter(project, consoleView, processHandler); |
| |
| consoleView.attachToProcess(processHandler); |
| return consoleView; |
| } |
| |
| protected void addTracebackFilter(Project project, ConsoleView consoleView, ProcessHandler processHandler) { |
| if (PySdkUtil.isRemote(myConfig.getSdk())) { |
| assert processHandler instanceof RemoteProcessHandlerBase; |
| consoleView |
| .addMessageFilter(new PyRemoteTracebackFilter(project, myConfig.getWorkingDirectory(), (RemoteProcessHandlerBase)processHandler)); |
| } |
| else { |
| consoleView.addMessageFilter(new PythonTracebackFilter(project, myConfig.getWorkingDirectory())); |
| } |
| } |
| |
| private TextConsoleBuilder createConsoleBuilder(Project project) { |
| if (isDebug()) { |
| return new PyDebugConsoleBuilder(project, PythonSdkType.findSdkByPath(myConfig.getInterpreterPath())); |
| } |
| else { |
| return TextConsoleBuilderFactory.getInstance().createBuilder(project); |
| } |
| } |
| |
| @Override |
| @NotNull |
| protected ProcessHandler startProcess() throws ExecutionException { |
| return startProcess(new CommandLinePatcher[]{}); |
| } |
| |
| /** |
| * Patches the command line parameters applying patchers from first to last, and then runs it. |
| * |
| * @param patchers any number of patchers; any patcher may be null, and the whole argument may be null. |
| * @return handler of the started process |
| * @throws ExecutionException |
| */ |
| protected ProcessHandler startProcess(CommandLinePatcher... patchers) throws ExecutionException { |
| GeneralCommandLine commandLine = generateCommandLine(patchers); |
| |
| // Extend command line |
| PythonRunConfigurationExtensionsManager.getInstance() |
| .patchCommandLine(myConfig, getRunnerSettings(), commandLine, getEnvironment().getRunner().getRunnerId()); |
| Sdk sdk = PythonSdkType.findSdkByPath(myConfig.getInterpreterPath()); |
| final ProcessHandler processHandler; |
| if (PySdkUtil.isRemote(sdk)) { |
| processHandler = |
| createRemoteProcessStarter().startRemoteProcess(sdk, commandLine, myConfig.getProject(), myConfig.getMappingSettings()); |
| } |
| else { |
| EncodingEnvironmentUtil.fixDefaultEncodingIfMac(commandLine, myConfig.getProject()); |
| processHandler = doCreateProcess(commandLine); |
| ProcessTerminatedListener.attach(processHandler); |
| } |
| |
| // attach extensions |
| PythonRunConfigurationExtensionsManager.getInstance().attachExtensionsToProcess(myConfig, processHandler, getRunnerSettings()); |
| |
| return processHandler; |
| } |
| |
| protected PyRemoteProcessStarter createRemoteProcessStarter() { |
| return new PyRemoteProcessStarter(); |
| } |
| |
| |
| public GeneralCommandLine generateCommandLine(CommandLinePatcher[] patchers) throws ExecutionException { |
| GeneralCommandLine commandLine = generateCommandLine(); |
| if (patchers != null) { |
| for (CommandLinePatcher patcher : patchers) { |
| if (patcher != null) patcher.patchCommandLine(commandLine); |
| } |
| } |
| return commandLine; |
| } |
| |
| protected ProcessHandler doCreateProcess(GeneralCommandLine commandLine) throws ExecutionException { |
| return PythonProcessRunner.createProcess(commandLine); |
| } |
| |
| public GeneralCommandLine generateCommandLine() throws ExecutionException { |
| GeneralCommandLine commandLine = createCommandLine(); |
| |
| setRunnerPath(commandLine); |
| |
| // define groups |
| createStandardGroupsIn(commandLine); |
| |
| buildCommandLineParameters(commandLine); |
| |
| initEnvironment(commandLine); |
| return commandLine; |
| } |
| |
| private static GeneralCommandLine createCommandLine() { |
| return Registry.is("run.processes.with.pty") ? new PtyCommandLine() : new GeneralCommandLine(); |
| } |
| |
| /** |
| * Creates a number of parameter groups in the command line: |
| * GROUP_EXE_OPTIONS, GROUP_DEBUGGER, GROUP_SCRIPT. |
| * These are necessary for command line patchers to work properly. |
| * |
| * @param commandLine |
| */ |
| public static void createStandardGroupsIn(GeneralCommandLine commandLine) { |
| ParametersList params = commandLine.getParametersList(); |
| params.addParamsGroup(GROUP_EXE_OPTIONS); |
| params.addParamsGroup(GROUP_DEBUGGER); |
| params.addParamsGroup(GROUP_SCRIPT); |
| } |
| |
| protected void initEnvironment(GeneralCommandLine commandLine) { |
| Map<String, String> env = myConfig.getEnvs(); |
| if (env == null) { |
| env = new HashMap<String, String>(); |
| } |
| else { |
| env = new HashMap<String, String>(env); |
| } |
| |
| addPredefinedEnvironmentVariables(env, myConfig.isPassParentEnvs()); |
| addCommonEnvironmentVariables(env); |
| |
| commandLine.getEnvironment().clear(); |
| commandLine.getEnvironment().putAll(env); |
| commandLine.setPassParentEnvironment(myConfig.isPassParentEnvs()); |
| |
| buildPythonPath(commandLine, myConfig.isPassParentEnvs()); |
| } |
| |
| protected static void addCommonEnvironmentVariables(Map<String, String> env) { |
| PythonEnvUtil.setPythonUnbuffered(env); |
| env.put("PYCHARM_HOSTED", "1"); |
| } |
| |
| public void addPredefinedEnvironmentVariables(Map<String, String> envs, boolean passParentEnvs) { |
| final PythonSdkFlavor flavor = PythonSdkFlavor.getFlavor(myConfig.getInterpreterPath()); |
| if (flavor != null) { |
| flavor.addPredefinedEnvironmentVariables(envs); |
| } |
| } |
| |
| private void buildPythonPath(GeneralCommandLine commandLine, boolean passParentEnvs) { |
| Sdk pythonSdk = PythonSdkType.findSdkByPath(myConfig.getInterpreterPath()); |
| if (pythonSdk != null) { |
| List<String> pathList = Lists.newArrayList(getAddedPaths(pythonSdk)); |
| pathList.addAll(collectPythonPath()); |
| initPythonPath(commandLine, passParentEnvs, pathList, myConfig.getInterpreterPath()); |
| } |
| } |
| |
| public static void initPythonPath(GeneralCommandLine commandLine, |
| boolean passParentEnvs, |
| List<String> pathList, |
| final String interpreterPath) { |
| final PythonSdkFlavor flavor = PythonSdkFlavor.getFlavor(interpreterPath); |
| if (flavor != null) { |
| flavor.initPythonPath(commandLine, pathList); |
| } |
| else { |
| PythonSdkFlavor.initPythonPath(commandLine.getEnvironment(), passParentEnvs, pathList); |
| } |
| } |
| |
| public static List<String> getAddedPaths(Sdk pythonSdk) { |
| List<String> pathList = new ArrayList<String>(); |
| final SdkAdditionalData sdkAdditionalData = pythonSdk.getSdkAdditionalData(); |
| if (sdkAdditionalData instanceof PythonSdkAdditionalData) { |
| final Set<VirtualFile> addedPaths = ((PythonSdkAdditionalData)sdkAdditionalData).getAddedPathFiles(); |
| for (VirtualFile file : addedPaths) { |
| addToPythonPath(file, pathList); |
| } |
| } |
| return pathList; |
| } |
| |
| private static void addToPythonPath(VirtualFile file, Collection<String> pathList) { |
| if (file.getFileSystem() instanceof JarFileSystem) { |
| final VirtualFile realFile = JarFileSystem.getInstance().getVirtualFileForJar(file); |
| if (realFile != null) { |
| addIfNeeded(realFile, pathList); |
| } |
| } |
| else { |
| addIfNeeded(file, pathList); |
| } |
| } |
| |
| private static void addIfNeeded(@NotNull final VirtualFile file, @NotNull final Collection<String> pathList) { |
| addIfNeeded(pathList, file.getPath()); |
| } |
| |
| protected static void addIfNeeded(Collection<String> pathList, String path) { |
| final Set<String> vals = Sets.newHashSet(pathList); |
| final String filePath = FileUtil.toSystemDependentName(path); |
| if (!vals.contains(filePath)) { |
| pathList.add(filePath); |
| } |
| } |
| |
| protected Collection<String> collectPythonPath() { |
| final Module module = myConfig.getModule(); |
| Set<String> pythonPath = Sets.newHashSet(collectPythonPath(module, myConfig.addContentRoots(), myConfig.addSourceRoots())); |
| |
| if (isDebug() && getSdkFlavor() instanceof JythonSdkFlavor) { //that fixes Jython problem changing sys.argv on execfile, see PY-8164 |
| pythonPath.add(PythonHelpersLocator.getHelperPath("pycharm")); |
| pythonPath.add(PythonHelpersLocator.getHelperPath("pydev")); |
| } |
| |
| return pythonPath; |
| } |
| |
| @NotNull |
| public static Collection<String> collectPythonPath(@Nullable Module module) { |
| return collectPythonPath(module, true, true); |
| } |
| |
| @NotNull |
| public static Collection<String> collectPythonPath(@Nullable Module module, boolean addContentRoots, |
| boolean addSourceRoots) { |
| Collection<String> pythonPathList = Sets.newLinkedHashSet(); |
| if (module != null) { |
| Set<Module> dependencies = new HashSet<Module>(); |
| ModuleUtilCore.getDependencies(module, dependencies); |
| |
| if (addContentRoots) { |
| addRoots(pythonPathList, ModuleRootManager.getInstance(module).getContentRoots()); |
| for (Module dependency : dependencies) { |
| addRoots(pythonPathList, ModuleRootManager.getInstance(dependency).getContentRoots()); |
| } |
| } |
| if (addSourceRoots) { |
| addRoots(pythonPathList, ModuleRootManager.getInstance(module).getSourceRoots()); |
| for (Module dependency : dependencies) { |
| addRoots(pythonPathList, ModuleRootManager.getInstance(dependency).getSourceRoots()); |
| } |
| } |
| |
| addLibrariesFromModule(module, pythonPathList); |
| addRootsFromModule(module, pythonPathList); |
| for (Module dependency : dependencies) { |
| addLibrariesFromModule(dependency, pythonPathList); |
| addRootsFromModule(dependency, pythonPathList); |
| } |
| } |
| return pythonPathList; |
| } |
| |
| private static void addLibrariesFromModule(Module module, Collection<String> list) { |
| final OrderEntry[] entries = ModuleRootManager.getInstance(module).getOrderEntries(); |
| for (OrderEntry entry : entries) { |
| if (entry instanceof LibraryOrderEntry) { |
| final String name = ((LibraryOrderEntry)entry).getLibraryName(); |
| if (name != null && name.endsWith(LibraryContributingFacet.PYTHON_FACET_LIBRARY_NAME_SUFFIX)) { |
| // skip libraries from Python facet |
| continue; |
| } |
| for (VirtualFile root : ((LibraryOrderEntry)entry).getRootFiles(OrderRootType.CLASSES)) { |
| final Library library = ((LibraryOrderEntry)entry).getLibrary(); |
| if (!PlatformUtils.isPyCharm()) { |
| addToPythonPath(root, list); |
| } |
| else if (library instanceof LibraryImpl) { |
| final PersistentLibraryKind<?> kind = ((LibraryImpl)library).getKind(); |
| if (kind == PythonLibraryType.getInstance().getKind()) { |
| addToPythonPath(root, list); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private static void addRootsFromModule(Module module, Collection<String> pythonPathList) { |
| |
| // for Jython |
| final CompilerModuleExtension extension = CompilerModuleExtension.getInstance(module); |
| if (extension != null) { |
| final VirtualFile path = extension.getCompilerOutputPath(); |
| if (path != null) { |
| pythonPathList.add(path.getPath()); |
| } |
| final VirtualFile pathForTests = extension.getCompilerOutputPathForTests(); |
| if (pathForTests != null) { |
| pythonPathList.add(pathForTests.getPath()); |
| } |
| } |
| |
| //additional paths from facets (f.e. buildout) |
| final Facet[] facets = FacetManager.getInstance(module).getAllFacets(); |
| for (Facet facet : facets) { |
| if (facet instanceof PythonPathContributingFacet) { |
| List<String> more_paths = ((PythonPathContributingFacet)facet).getAdditionalPythonPath(); |
| if (more_paths != null) pythonPathList.addAll(more_paths); |
| } |
| } |
| } |
| |
| private static void addRoots(Collection<String> pythonPathList, VirtualFile[] roots) { |
| for (VirtualFile root : roots) { |
| addToPythonPath(root, pythonPathList); |
| } |
| } |
| |
| protected void setRunnerPath(GeneralCommandLine commandLine) throws ExecutionException { |
| String interpreterPath = getInterpreterPath(); |
| commandLine.setExePath(FileUtil.toSystemDependentName(interpreterPath)); |
| } |
| |
| protected String getInterpreterPath() throws ExecutionException { |
| String interpreterPath = myConfig.getInterpreterPath(); |
| if (interpreterPath == null) { |
| throw new ExecutionException("Cannot find Python interpreter for this run configuration"); |
| } |
| return interpreterPath; |
| } |
| |
| protected void buildCommandLineParameters(GeneralCommandLine commandLine) { |
| } |
| |
| public boolean isMultiprocessDebug() { |
| if (myMultiprocessDebug != null) { |
| return myMultiprocessDebug; |
| } |
| else { |
| return PyDebuggerOptionsProvider.getInstance(myConfig.getProject()).isAttachToSubprocess(); |
| } |
| } |
| |
| public void setMultiprocessDebug(boolean multiprocessDebug) { |
| myMultiprocessDebug = multiprocessDebug; |
| } |
| } |