mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-07 05:09:37 +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 String LC_CTYPE = "LC_CTYPE";
|
||||||
|
|
||||||
private static final Future<Map<String, String>> ourEnvGetter;
|
private static final Future<Map<String, String>> ourEnvGetter;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
if (SystemInfo.isMac && "unlocked".equals(System.getProperty("__idea.mac.env.lock")) && Registry.is("idea.fix.mac.env")) {
|
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>>() {
|
ourEnvGetter = AppExecutorUtil.getAppExecutorService().submit(new Callable<Map<String, String>>() {
|
||||||
@@ -90,7 +91,8 @@ public class EnvironmentUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private EnvironmentUtil() { }
|
private EnvironmentUtil() {
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isEnvironmentReady() {
|
public static boolean isEnvironmentReady() {
|
||||||
return ourEnvGetter.isDone();
|
return ourEnvGetter.isDone();
|
||||||
@@ -152,39 +154,60 @@ public class EnvironmentUtil {
|
|||||||
private static final String DISABLE_OMZ_AUTO_UPDATE = "DISABLE_AUTO_UPDATE";
|
private static final String DISABLE_OMZ_AUTO_UPDATE = "DISABLE_AUTO_UPDATE";
|
||||||
|
|
||||||
private static Map<String, String> getShellEnv() throws Exception {
|
private static Map<String, String> getShellEnv() throws Exception {
|
||||||
String shell = System.getenv("SHELL");
|
return new ShellEnvReader().readShellEnv();
|
||||||
if (shell == null || !new File(shell).canExecute()) {
|
}
|
||||||
throw new Exception("shell:" + shell);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
public static class ShellEnvReader {
|
||||||
try {
|
|
||||||
String[] command = {shell, "-l", "-i", "-c", ("'" + reader.getAbsolutePath() + "' '" + envFile.getAbsolutePath() + "'")};
|
|
||||||
LOG.info("loading shell env: " + StringUtil.join(command, " "));
|
|
||||||
|
|
||||||
ProcessBuilder builder = new ProcessBuilder(command).redirectErrorStream(true);
|
public Map<String, String> readShellEnv() throws Exception {
|
||||||
builder.environment().put(DISABLE_OMZ_AUTO_UPDATE, "true");
|
File reader = FileUtil.findFirstThatExist(
|
||||||
Process process = builder.start();
|
PathManager.getBinPath() + "/printenv.py",
|
||||||
StreamGobbler gobbler = new StreamGobbler(process.getInputStream());
|
PathManager.getHomePath() + "/community/bin/mac/printenv.py",
|
||||||
int rv = waitAndTerminateAfter(process, SHELL_ENV_READING_TIMEOUT);
|
PathManager.getHomePath() + "/bin/mac/printenv.py");
|
||||||
gobbler.stop();
|
if (reader == null) {
|
||||||
|
throw new Exception("bin:" + PathManager.getBinPath());
|
||||||
String lines = FileUtil.loadFile(envFile);
|
}
|
||||||
if (rv != 0 || lines.isEmpty()) {
|
|
||||||
throw new Exception("rv:" + rv + " text:" + lines.length() + " out:" + StringUtil.trimEnd(gobbler.getText(), '\n'));
|
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 {
|
try {
|
||||||
return process.exitValue();
|
return process.exitValue();
|
||||||
}
|
}
|
||||||
catch (IllegalThreadStateException ignore) { }
|
catch (IllegalThreadStateException ignore) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -303,8 +327,15 @@ public class EnvironmentUtil {
|
|||||||
|
|
||||||
private static class StreamGobbler extends BaseOutputReader {
|
private static class StreamGobbler extends BaseOutputReader {
|
||||||
private static final Options OPTIONS = new Options() {
|
private static final Options OPTIONS = new Options() {
|
||||||
@Override public SleepingPolicy policy() { return SleepingPolicy.BLOCKING; }
|
@Override
|
||||||
@Override public boolean splitToLines() { return false; }
|
public SleepingPolicy policy() {
|
||||||
|
return SleepingPolicy.BLOCKING;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean splitToLines() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private final StringBuffer myBuffer;
|
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
|
#generate escape sequence after command is executed to notify jediterm emulator
|
||||||
trap "generate_command_executed_sequence" DEBUG
|
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;
|
import java.util.Map;
|
||||||
|
|
||||||
public interface LocalTerminalCustomizer {
|
public abstract class LocalTerminalCustomizer {
|
||||||
ExtensionPointName<LocalTerminalCustomizer> EP_NAME = ExtensionPointName.create("org.jetbrains.plugins.terminal.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
|
@Nullable
|
||||||
String getDefaultFolder();
|
protected String getDefaultFolder() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,8 +102,10 @@ public class LocalTerminalDirectRunner extends AbstractTerminalRunner<PtyProcess
|
|||||||
}
|
}
|
||||||
EncodingEnvironmentUtil.setLocaleEnvironmentIfMac(envs, myDefaultCharset);
|
EncodingEnvironmentUtil.setLocaleEnvironmentIfMac(envs, myDefaultCharset);
|
||||||
|
|
||||||
|
String[] command = getCommand();
|
||||||
|
|
||||||
for (LocalTerminalCustomizer customizer : LocalTerminalCustomizer.EP_NAME.getExtensions()) {
|
for (LocalTerminalCustomizer customizer : LocalTerminalCustomizer.EP_NAME.getExtensions()) {
|
||||||
customizer.setupEnvironment(myProject, envs);
|
command = customizer.customizeCommandAndEnvironment(myProject, command, envs);
|
||||||
|
|
||||||
if (directory == null) {
|
if (directory == null) {
|
||||||
directory = customizer.getDefaultFolder();
|
directory = customizer.getDefaultFolder();
|
||||||
@@ -111,7 +113,7 @@ public class LocalTerminalDirectRunner extends AbstractTerminalRunner<PtyProcess
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return PtyProcess.exec(getCommand(), envs, directory != null ? directory : currentProjectFolder());
|
return PtyProcess.exec(command, envs, directory != null ? directory : currentProjectFolder());
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
catch (IOException e) {
|
||||||
throw new ExecutionException(e);
|
throw new ExecutionException(e);
|
||||||
@@ -184,9 +186,7 @@ public class LocalTerminalDirectRunner extends AbstractTerminalRunner<PtyProcess
|
|||||||
if (hasLoginArgument(shellName) && SystemInfo.isMac) {
|
if (hasLoginArgument(shellName) && SystemInfo.isMac) {
|
||||||
result.add("--login");
|
result.add("--login");
|
||||||
}
|
}
|
||||||
else {
|
result.add("-i");
|
||||||
result.add("-i");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result.addAll(command);
|
result.addAll(command);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ hg4idea
|
|||||||
tasks-core
|
tasks-core
|
||||||
github
|
github
|
||||||
terminal
|
terminal
|
||||||
|
python-terminal
|
||||||
IntelliLang
|
IntelliLang
|
||||||
IntelliLang-xml
|
IntelliLang-xml
|
||||||
IntelliLang-python
|
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="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="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/*)"/>
|
<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="ShortcutPromoter" />
|
||||||
<orderEntry type="module" module-name="ipnb" />
|
<orderEntry type="module" module-name="ipnb" />
|
||||||
<orderEntry type="module" module-name="settings-repository" />
|
<orderEntry type="module" module-name="settings-repository" />
|
||||||
|
<orderEntry type="module" module-name="python-terminal" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
21
python/python-terminal/python-terminal.iml
Normal file
21
python/python-terminal/python-terminal.iml
Normal file
@@ -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>
|
||||||
11
python/python-terminal/resources/META-INF/plugin.xml
Normal file
11
python/python-terminal/resources/META-INF/plugin.xml
Normal file
@@ -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>
|
||||||
@@ -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