/* * 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 myFilters = Lists.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 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 env = myConfig.getEnvs(); if (env == null) { env = new HashMap(); } else { env = new HashMap(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 env) { PythonEnvUtil.setPythonUnbuffered(env); env.put("PYCHARM_HOSTED", "1"); } public void addPredefinedEnvironmentVariables(Map 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 pathList = Lists.newArrayList(getAddedPaths(pythonSdk)); pathList.addAll(collectPythonPath()); initPythonPath(commandLine, passParentEnvs, pathList, myConfig.getInterpreterPath()); } } public static void initPythonPath(GeneralCommandLine commandLine, boolean passParentEnvs, List 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 getAddedPaths(Sdk pythonSdk) { List pathList = new ArrayList(); final SdkAdditionalData sdkAdditionalData = pythonSdk.getSdkAdditionalData(); if (sdkAdditionalData instanceof PythonSdkAdditionalData) { final Set addedPaths = ((PythonSdkAdditionalData)sdkAdditionalData).getAddedPathFiles(); for (VirtualFile file : addedPaths) { addToPythonPath(file, pathList); } } return pathList; } private static void addToPythonPath(VirtualFile file, Collection 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 pathList) { addIfNeeded(pathList, file.getPath()); } protected static void addIfNeeded(Collection pathList, String path) { final Set vals = Sets.newHashSet(pathList); final String filePath = FileUtil.toSystemDependentName(path); if (!vals.contains(filePath)) { pathList.add(filePath); } } protected Collection collectPythonPath() { final Module module = myConfig.getModule(); Set 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 collectPythonPath(@Nullable Module module) { return collectPythonPath(module, true, true); } @NotNull public static Collection collectPythonPath(@Nullable Module module, boolean addContentRoots, boolean addSourceRoots) { Collection pythonPathList = Sets.newLinkedHashSet(); if (module != null) { Set dependencies = new HashSet(); 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 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 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 more_paths = ((PythonPathContributingFacet)facet).getAdditionalPythonPath(); if (more_paths != null) pythonPathList.addAll(more_paths); } } } private static void addRoots(Collection 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; } }