Fix conda activation on Windows (PY-21923)
[idea/community.git] / python / src / com / jetbrains / python / run / PyVirtualEnvReader.kt
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.jetbrains.python.run
17
18 import com.intellij.openapi.diagnostic.Logger
19 import com.intellij.openapi.util.SystemInfo
20 import com.intellij.openapi.util.io.FileUtil
21 import com.intellij.util.EnvironmentUtil
22 import com.intellij.util.LineSeparator
23 import com.jetbrains.python.sdk.PythonSdkType
24 import java.io.File
25
26 /**
27  * @author traff
28  */
29
30
31 class PyVirtualEnvReader(val virtualEnvSdkPath: String) : EnvironmentUtil.ShellEnvReader() {
32   private val LOG = Logger.getInstance("#com.jetbrains.python.run.PyVirtualEnvReader")
33
34   // in case of Conda we need to pass an argument to an activate script that tells which exactly environment to activate
35   val activate: Pair<String, String?>? = findActivateScript(virtualEnvSdkPath, shell)
36
37   override fun getShell(): String? {
38     if (File("/bin/bash").exists()) {
39       return "/bin/bash";
40     }
41     else
42       if (File("/bin/sh").exists()) {
43         return "/bin/sh";
44       }
45       else {
46         return super.getShell();
47       }
48   }
49
50   override fun readShellEnv(): MutableMap<String, String> {
51     if (SystemInfo.isUnix) {
52       return super.readShellEnv()
53     }
54     else {
55       if (activate != null) {
56         return readVirtualEnvOnWindows(activate);
57       }
58       else {
59         LOG.error("Can't find activate script for $virtualEnvSdkPath")
60         return mutableMapOf();
61       }
62     }
63   }
64
65   private fun readVirtualEnvOnWindows(activate: Pair<String, String?>): MutableMap<String, String> {
66     val activateFile = FileUtil.createTempFile("pycharm-virualenv-activate.", ".bat", false)
67     val envFile = FileUtil.createTempFile("pycharm-virualenv-envs.", ".tmp", false)
68     try {
69       FileUtil.copy(File(activate.first), activateFile);
70       FileUtil.appendToFile(activateFile, "\n\nset >" + envFile.absoluteFile)
71
72       val command = if (activate.second != null) listOf<String>(activateFile.path, activate.second!!)
73       else listOf<String>(activateFile.path)
74
75       return runProcessAndReadEnvs(command, envFile, LineSeparator.CRLF.separatorString)
76     }
77     finally {
78       FileUtil.delete(activateFile)
79       FileUtil.delete(envFile)
80     }
81
82   }
83
84   override fun getShellProcessCommand(): MutableList<String>? {
85     val shellPath = shell
86
87     if (shellPath == null || !File(shellPath).canExecute()) {
88       throw Exception("shell:" + shellPath)
89     }
90
91     return if (activate != null) {
92       val activateArg = if (activate.second != null) "'${activate.first}' '${activate.second}'" else "'${activate.first}'"
93       mutableListOf(shellPath, "-c", ". $activateArg")
94     }
95     else super.getShellProcessCommand()
96   }
97
98 }
99
100 fun findActivateScript(path: String?, shellPath: String?): Pair<String, String?>? {
101   val shellName = if (shellPath != null) File(shellPath).name else null
102   val activate = if (SystemInfo.isWindows) findActivateOnWindows(path)
103   else if (shellName == "fish" || shellName == "csh") File(File(path).parentFile, "activate." + shellName)
104   else File(File(path).parentFile, "activate")
105
106   return if (activate != null && activate.exists()) {
107     val sdk = PythonSdkType.findSdkByPath(path)
108     if (sdk != null && PythonSdkType.isCondaVirtualEnv(sdk)) Pair(activate.absolutePath, condaEnvFolder(path))
109     else Pair(activate.absolutePath, null)
110   }
111   else null
112 }
113
114 private fun condaEnvFolder(path: String?) = if (SystemInfo.isWindows) File(path).parent else File(path).parentFile.parent
115
116 private fun findActivateOnWindows(path: String?): File? {
117   for (location in arrayListOf("activate.bat", "Scripts/activate.bat")) {
118     val file = File(File(path).parentFile, location)
119     if (file.exists()) {
120       return file
121     }
122   }
123
124   return null
125 }