mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Auto activate of virtualenv in terminal for projects with Python Virtualenv interpreters (PY-10498)
This commit is contained in:
@@ -53,6 +53,7 @@ public class EnvironmentUtil {
|
||||
private static final String LC_CTYPE = "LC_CTYPE";
|
||||
|
||||
private static final Future<Map<String, String>> ourEnvGetter;
|
||||
|
||||
static {
|
||||
if (SystemInfo.isMac && "unlocked".equals(System.getProperty("__idea.mac.env.lock")) && Registry.is("idea.fix.mac.env")) {
|
||||
ourEnvGetter = AppExecutorUtil.getAppExecutorService().submit(new Callable<Map<String, String>>() {
|
||||
@@ -90,7 +91,8 @@ public class EnvironmentUtil {
|
||||
}
|
||||
}
|
||||
|
||||
private EnvironmentUtil() { }
|
||||
private EnvironmentUtil() {
|
||||
}
|
||||
|
||||
public static boolean isEnvironmentReady() {
|
||||
return ourEnvGetter.isDone();
|
||||
@@ -152,39 +154,60 @@ public class EnvironmentUtil {
|
||||
private static final String DISABLE_OMZ_AUTO_UPDATE = "DISABLE_AUTO_UPDATE";
|
||||
|
||||
private static Map<String, String> getShellEnv() throws Exception {
|
||||
String shell = System.getenv("SHELL");
|
||||
if (shell == null || !new File(shell).canExecute()) {
|
||||
throw new Exception("shell:" + shell);
|
||||
}
|
||||
return new ShellEnvReader().readShellEnv();
|
||||
}
|
||||
|
||||
File reader = FileUtil.findFirstThatExist(
|
||||
PathManager.getBinPath() + "/printenv.py",
|
||||
PathManager.getHomePath() + "/community/bin/mac/printenv.py",
|
||||
PathManager.getHomePath() + "/bin/mac/printenv.py");
|
||||
if (reader == null) {
|
||||
throw new Exception("bin:" + PathManager.getBinPath());
|
||||
}
|
||||
|
||||
File envFile = FileUtil.createTempFile("intellij-shell-env.", ".tmp", false);
|
||||
try {
|
||||
String[] command = {shell, "-l", "-i", "-c", ("'" + reader.getAbsolutePath() + "' '" + envFile.getAbsolutePath() + "'")};
|
||||
LOG.info("loading shell env: " + StringUtil.join(command, " "));
|
||||
public static class ShellEnvReader {
|
||||
|
||||
ProcessBuilder builder = new ProcessBuilder(command).redirectErrorStream(true);
|
||||
builder.environment().put(DISABLE_OMZ_AUTO_UPDATE, "true");
|
||||
Process process = builder.start();
|
||||
StreamGobbler gobbler = new StreamGobbler(process.getInputStream());
|
||||
int rv = waitAndTerminateAfter(process, SHELL_ENV_READING_TIMEOUT);
|
||||
gobbler.stop();
|
||||
|
||||
String lines = FileUtil.loadFile(envFile);
|
||||
if (rv != 0 || lines.isEmpty()) {
|
||||
throw new Exception("rv:" + rv + " text:" + lines.length() + " out:" + StringUtil.trimEnd(gobbler.getText(), '\n'));
|
||||
public Map<String, String> readShellEnv() throws Exception {
|
||||
File reader = FileUtil.findFirstThatExist(
|
||||
PathManager.getBinPath() + "/printenv.py",
|
||||
PathManager.getHomePath() + "/community/bin/mac/printenv.py",
|
||||
PathManager.getHomePath() + "/bin/mac/printenv.py");
|
||||
if (reader == null) {
|
||||
throw new Exception("bin:" + PathManager.getBinPath());
|
||||
}
|
||||
|
||||
File envFile = FileUtil.createTempFile("intellij-shell-env.", ".tmp", false);
|
||||
try {
|
||||
List<String> command = getShellProcessCommand();
|
||||
command.add("-c");
|
||||
command.add("'" + reader.getAbsolutePath() + "' '" + envFile.getAbsolutePath() + "'");
|
||||
|
||||
LOG.info("loading shell env: " + StringUtil.join(command, " "));
|
||||
|
||||
ProcessBuilder builder = new ProcessBuilder(command).redirectErrorStream(true);
|
||||
builder.environment().put(DISABLE_OMZ_AUTO_UPDATE, "true");
|
||||
Process process = builder.start();
|
||||
StreamGobbler gobbler = new StreamGobbler(process.getInputStream());
|
||||
int rv = waitAndTerminateAfter(process, SHELL_ENV_READING_TIMEOUT);
|
||||
gobbler.stop();
|
||||
|
||||
String lines = FileUtil.loadFile(envFile);
|
||||
if (rv != 0 || lines.isEmpty()) {
|
||||
throw new Exception("rv:" + rv + " text:" + lines.length() + " out:" + StringUtil.trimEnd(gobbler.getText(), '\n'));
|
||||
}
|
||||
return parseEnv(lines);
|
||||
}
|
||||
finally {
|
||||
FileUtil.delete(envFile);
|
||||
}
|
||||
return parseEnv(lines);
|
||||
}
|
||||
finally {
|
||||
FileUtil.delete(envFile);
|
||||
|
||||
protected List<String> getShellProcessCommand() throws Exception {
|
||||
String shell = getShell();
|
||||
|
||||
return new ArrayList<String>(Arrays.asList(shell, "-l", "-i"));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected String getShell() throws Exception {
|
||||
String shell = System.getenv("SHELL");
|
||||
if (shell == null || !new File(shell).canExecute()) {
|
||||
throw new Exception("shell:" + shell);
|
||||
}
|
||||
return shell;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,7 +264,8 @@ public class EnvironmentUtil {
|
||||
try {
|
||||
return process.exitValue();
|
||||
}
|
||||
catch (IllegalThreadStateException ignore) { }
|
||||
catch (IllegalThreadStateException ignore) {
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -303,8 +327,15 @@ public class EnvironmentUtil {
|
||||
|
||||
private static class StreamGobbler extends BaseOutputReader {
|
||||
private static final Options OPTIONS = new Options() {
|
||||
@Override public SleepingPolicy policy() { return SleepingPolicy.BLOCKING; }
|
||||
@Override public boolean splitToLines() { return false; }
|
||||
@Override
|
||||
public SleepingPolicy policy() {
|
||||
return SleepingPolicy.BLOCKING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean splitToLines() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
private final StringBuffer myBuffer;
|
||||
|
||||
@@ -52,3 +52,8 @@ export -f generate_command_executed_sequence
|
||||
|
||||
#generate escape sequence after command is executed to notify jediterm emulator
|
||||
trap "generate_command_executed_sequence" DEBUG
|
||||
|
||||
if [ -n "$JEDITERM_SOURCE" ]
|
||||
then
|
||||
source $JEDITERM_SOURCE
|
||||
fi
|
||||
|
||||
@@ -22,11 +22,16 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public interface LocalTerminalCustomizer {
|
||||
ExtensionPointName<LocalTerminalCustomizer> EP_NAME = ExtensionPointName.create("org.jetbrains.plugins.terminal.localTerminalCustomizer");
|
||||
public abstract class LocalTerminalCustomizer {
|
||||
public static ExtensionPointName<LocalTerminalCustomizer> EP_NAME = ExtensionPointName.create("org.jetbrains.plugins.terminal.localTerminalCustomizer");
|
||||
|
||||
void setupEnvironment(@NotNull Project project, @NotNull Map<String, String> envs);
|
||||
public String[] customizeCommandAndEnvironment(Project project, String[] command, Map<String, String> envs) {
|
||||
return command;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
String getDefaultFolder();
|
||||
protected String getDefaultFolder() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -102,8 +102,10 @@ public class LocalTerminalDirectRunner extends AbstractTerminalRunner<PtyProcess
|
||||
}
|
||||
EncodingEnvironmentUtil.setLocaleEnvironmentIfMac(envs, myDefaultCharset);
|
||||
|
||||
String[] command = getCommand();
|
||||
|
||||
for (LocalTerminalCustomizer customizer : LocalTerminalCustomizer.EP_NAME.getExtensions()) {
|
||||
customizer.setupEnvironment(myProject, envs);
|
||||
command = customizer.customizeCommandAndEnvironment(myProject, command, envs);
|
||||
|
||||
if (directory == null) {
|
||||
directory = customizer.getDefaultFolder();
|
||||
@@ -111,7 +113,7 @@ public class LocalTerminalDirectRunner extends AbstractTerminalRunner<PtyProcess
|
||||
}
|
||||
|
||||
try {
|
||||
return PtyProcess.exec(getCommand(), envs, directory != null ? directory : currentProjectFolder());
|
||||
return PtyProcess.exec(command, envs, directory != null ? directory : currentProjectFolder());
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new ExecutionException(e);
|
||||
@@ -184,9 +186,7 @@ public class LocalTerminalDirectRunner extends AbstractTerminalRunner<PtyProcess
|
||||
if (hasLoginArgument(shellName) && SystemInfo.isMac) {
|
||||
result.add("--login");
|
||||
}
|
||||
else {
|
||||
result.add("-i");
|
||||
}
|
||||
result.add("-i");
|
||||
}
|
||||
|
||||
result.addAll(command);
|
||||
|
||||
@@ -6,6 +6,7 @@ hg4idea
|
||||
tasks-core
|
||||
github
|
||||
terminal
|
||||
python-terminal
|
||||
IntelliLang
|
||||
IntelliLang-xml
|
||||
IntelliLang-python
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
-->
|
||||
<depends optional="true" config-file="python-rest-plugin.xml">org.jetbrains.plugins.rest</depends>
|
||||
<depends optional="true" config-file="intellilang-python-support.xml">org.intellij.intelliLang</depends>
|
||||
<depends optional="true" config-file="python-terminal-plugin.xml">org.jetbrains.plugins.terminal</depends>
|
||||
|
||||
<xi:include href="/META-INF/ipython-notebook.xml" xpointer="xpointer(/idea-plugin/*)"/>
|
||||
|
||||
|
||||
@@ -21,5 +21,6 @@
|
||||
<orderEntry type="module" module-name="ShortcutPromoter" />
|
||||
<orderEntry type="module" module-name="ipnb" />
|
||||
<orderEntry type="module" module-name="settings-repository" />
|
||||
<orderEntry type="module" module-name="python-terminal" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="module" module-name="python-community" />
|
||||
<orderEntry type="module" module-name="terminal" />
|
||||
<orderEntry type="library" scope="TEST" name="JUnit4" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="mockito" level="project" />
|
||||
<orderEntry type="module" module-name="core-api" />
|
||||
<orderEntry type="module" module-name="projectModel-api" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="module" module-name="lang-api" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -0,0 +1,11 @@
|
||||
<idea-plugin version="2" xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
<name>Python Terminal</name>
|
||||
<id>org.jetbrains.plugins.python-terminal</id>
|
||||
<version>VERSION</version>
|
||||
<vendor>JetBrains</vendor>
|
||||
|
||||
<depends>com.intellij.modules.python</depends>
|
||||
|
||||
<xi:include href="/META-INF/python-terminal-plugin.xml" xpointer="xpointer(/idea-plugin/*)"/>
|
||||
|
||||
</idea-plugin>
|
||||
@@ -0,0 +1,7 @@
|
||||
<idea-plugin version="2">
|
||||
<extensions defaultExtensionNs="org.jetbrains.plugins.terminal">
|
||||
<localTerminalCustomizer implementation="com.jetbrains.python.sdk.PyVirtualEnvTerminalCustomizer"/>
|
||||
</extensions>
|
||||
|
||||
|
||||
</idea-plugin>
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2000-2016 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.sdk
|
||||
|
||||
import com.intellij.openapi.module.ModuleManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.util.SystemInfo
|
||||
import com.intellij.util.EnvironmentUtil
|
||||
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor
|
||||
import com.jetbrains.python.sdk.flavors.VirtualEnvSdkFlavor
|
||||
import org.jetbrains.plugins.terminal.LocalTerminalCustomizer
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* @author traff
|
||||
*/
|
||||
|
||||
class PyVirtualEnvTerminalCustomizer : LocalTerminalCustomizer() {
|
||||
|
||||
|
||||
override fun customizeCommandAndEnvironment(project: Project,
|
||||
command: Array<out String>,
|
||||
envs: MutableMap<String, String>): Array<out String> {
|
||||
val sdk: Sdk? = findSdk(project)
|
||||
|
||||
if (sdk != null && PythonSdkType.isVirtualEnv(sdk) && SystemInfo.isUnix) {
|
||||
// in case of virtualenv sdk on unix we activate virtualenv
|
||||
val path = sdk.homePath
|
||||
|
||||
if (path != null) {
|
||||
|
||||
val shellPath = command[0]
|
||||
val shellName = File(shellPath).name
|
||||
|
||||
if (shellName == "bash" || shellName == "sh") {
|
||||
//for bash and sh we pass activate script to jediterm shell integration (see jediterm-sh.in)
|
||||
findActivateScript(path, shellPath)?.let { activate -> envs.put("JEDITERM_SOURCE", activate) }
|
||||
}
|
||||
else {
|
||||
//for other shells we read envs from activate script by the default shell and pass it to the process
|
||||
val pyVirtualEnvReader = PyVirtualEnvReader(path)
|
||||
pyVirtualEnvReader.activate?.let { envs.putAll(pyVirtualEnvReader.readShellEnv()) }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// for some reason virtualenv isn't activated in the rcfile for the login shell, so we make it non-login
|
||||
return command.filter { arg -> arg != "--login" }.toTypedArray()
|
||||
}
|
||||
|
||||
|
||||
private fun findSdk(project: Project): Sdk? {
|
||||
for (m in ModuleManager.getInstance(project).modules) {
|
||||
val sdk: Sdk? = PythonSdkType.findPythonSdk(m)
|
||||
if (sdk != null && !PythonSdkType.isRemote(sdk)) {
|
||||
return sdk
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
override fun getDefaultFolder(): String? {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
class PyVirtualEnvReader(virtualEnvSdkPath: String) : EnvironmentUtil.ShellEnvReader() {
|
||||
val activate = findActivateScript(virtualEnvSdkPath, shell)
|
||||
|
||||
override fun getShellProcessCommand(): MutableList<String>? {
|
||||
|
||||
return if (activate != null) mutableListOf(shell, "--rcfile", activate, "-i")
|
||||
else super.getShellProcessCommand()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun findActivateScript(path: String?, shellPath: String): String? {
|
||||
val shellName = File(shellPath).name
|
||||
val activate = if (shellName == "fish" || shellName == "csh") File(File(path).parentFile, "activate." + shellName)
|
||||
else File(File(path).parentFile, "activate")
|
||||
|
||||
return if (activate.exists()) activate.absolutePath else null
|
||||
}
|
||||
Reference in New Issue
Block a user