Add possibility for flavor to patch command line before execution (https://github...
[idea/community.git] / python / src / com / jetbrains / python / sdk / flavors / PythonSdkFlavor.java
1 /*
2  * Copyright 2000-2014 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.sdk.flavors;
17
18 import com.google.common.collect.Lists;
19 import com.intellij.execution.configurations.GeneralCommandLine;
20 import com.intellij.execution.process.ProcessOutput;
21 import com.intellij.openapi.components.ServiceManager;
22 import com.intellij.openapi.diagnostic.Logger;
23 import com.intellij.openapi.extensions.Extensions;
24 import com.intellij.openapi.projectRoots.Sdk;
25 import com.intellij.openapi.projectRoots.SdkAdditionalData;
26 import com.intellij.openapi.util.SystemInfo;
27 import com.intellij.openapi.util.io.FileUtil;
28 import com.intellij.openapi.util.text.StringUtil;
29 import com.intellij.openapi.vfs.VirtualFile;
30 import com.intellij.util.PatternUtil;
31 import com.jetbrains.python.psi.LanguageLevel;
32 import com.jetbrains.python.run.CommandLinePatcher;
33 import com.jetbrains.python.sdk.PySdkUtil;
34 import com.jetbrains.python.sdk.PythonEnvUtil;
35 import com.jetbrains.python.sdk.PythonSdkAdditionalData;
36 import icons.PythonIcons;
37 import org.jetbrains.annotations.NotNull;
38 import org.jetbrains.annotations.Nullable;
39
40 import javax.swing.*;
41 import java.io.File;
42 import java.nio.charset.Charset;
43 import java.util.*;
44 import java.util.regex.Pattern;
45
46 /**
47  * @author yole
48  */
49 public abstract class PythonSdkFlavor {
50   private static final Pattern VERSION_RE = Pattern.compile("(Python \\S+).*");
51   private static final Logger LOG = Logger.getInstance(PythonSdkFlavor.class);
52
53   public static Collection<String> appendSystemPythonPath(@NotNull Collection<String> pythonPath) {
54     return appendSystemEnvPaths(pythonPath, PythonEnvUtil.PYTHONPATH);
55   }
56
57   protected static Collection<String> appendSystemEnvPaths(@NotNull Collection<String> pythonPath, String envname) {
58     String syspath = System.getenv(envname);
59     if (syspath != null) {
60       pythonPath.addAll(Lists.newArrayList(syspath.split(File.pathSeparator)));
61     }
62     return pythonPath;
63   }
64
65
66   public static void initPythonPath(@NotNull Map<String, String> envs, boolean passParentEnvs, @NotNull Collection<String> pythonPathList) {
67     if (passParentEnvs && !envs.containsKey(PythonEnvUtil.PYTHONPATH)) {
68       pythonPathList = appendSystemPythonPath(pythonPathList);
69     }
70     PythonEnvUtil.addToPythonPath(envs, pythonPathList);
71   }
72
73   public Collection<String> suggestHomePaths() {
74     return Collections.emptyList();
75   }
76
77   public static List<PythonSdkFlavor> getApplicableFlavors() {
78     return getApplicableFlavors(true);
79   }
80
81   public static List<PythonSdkFlavor> getApplicableFlavors(boolean addPlatformIndependent) {
82     List<PythonSdkFlavor> result = new ArrayList<>();
83
84     if (SystemInfo.isWindows) {
85       result.add(ServiceManager.getService(WinPythonSdkFlavor.class));
86     }
87     else if (SystemInfo.isMac) {
88       result.add(MacPythonSdkFlavor.INSTANCE);
89     }
90     else if (SystemInfo.isUnix) {
91       result.add(UnixPythonSdkFlavor.INSTANCE);
92     }
93
94     if (addPlatformIndependent) {
95       result.addAll(getPlatformIndependentFlavors());
96     }
97
98     result.addAll(getPlatformFlavorsFromExtensions(addPlatformIndependent));
99
100     return result;
101   }
102
103   public static List<PythonSdkFlavor> getPlatformFlavorsFromExtensions(boolean isInpedendent) {
104     List<PythonSdkFlavor> result = new ArrayList<>();
105     for (PythonFlavorProvider provider : Extensions.getExtensions(PythonFlavorProvider.EP_NAME)) {
106       PythonSdkFlavor flavor = provider.getFlavor(isInpedendent);
107       if (flavor != null) {
108         result.add(flavor);
109       }
110     }
111     return result;
112   }
113
114   public static List<PythonSdkFlavor> getPlatformIndependentFlavors() {
115     List<PythonSdkFlavor> result = Lists.newArrayList();
116     result.add(JythonSdkFlavor.INSTANCE);
117     result.add(IronPythonSdkFlavor.INSTANCE);
118     result.add(PyPySdkFlavor.INSTANCE);
119     result.add(VirtualEnvSdkFlavor.INSTANCE);
120     result.add(PyRemoteSdkFlavor.INSTANCE);
121
122     return result;
123   }
124
125   @Nullable
126   public static PythonSdkFlavor getFlavor(Sdk sdk) {
127     final SdkAdditionalData data = sdk.getSdkAdditionalData();
128     if (data instanceof PythonSdkAdditionalData) {
129       PythonSdkFlavor flavor = ((PythonSdkAdditionalData)data).getFlavor();
130       if (flavor != null) {
131         return flavor;
132       }
133     }
134     return getFlavor(sdk.getHomePath());
135   }
136
137   @Nullable
138   public static PythonSdkFlavor getFlavor(@Nullable String sdkPath) {
139     if (sdkPath == null) return null;
140
141     for (PythonSdkFlavor flavor : getApplicableFlavors()) {
142       if (flavor.isValidSdkHome(sdkPath)) {
143         return flavor;
144       }
145     }
146     return null;
147   }
148
149   @Nullable
150   public static PythonSdkFlavor getPlatformIndependentFlavor(@Nullable final String sdkPath) {
151     if (sdkPath == null) return null;
152
153     for (PythonSdkFlavor flavor : getPlatformIndependentFlavors()) {
154       if (flavor.isValidSdkHome(sdkPath)) {
155         return flavor;
156       }
157     }
158
159     for (PythonSdkFlavor flavor: getPlatformFlavorsFromExtensions(true)) {
160       if (flavor.isValidSdkHome(sdkPath)) {
161         return flavor;
162       }
163     }          
164     return null;
165   }
166
167   /**
168    * Checks if the path is the name of a Python interpreter of this flavor.
169    *
170    * @param path path to check.
171    * @return true if paths points to a valid home.
172    */
173   public boolean isValidSdkHome(String path) {
174     File file = new File(path);
175     return file.isFile() && isValidSdkPath(file);
176   }
177
178   public boolean isValidSdkPath(@NotNull File file) {
179     return FileUtil.getNameWithoutExtension(file).toLowerCase().startsWith("python");
180   }
181
182   @Nullable
183   public String getVersionString(@Nullable String sdkHome) {
184     if (sdkHome == null) {
185       return null;
186     }
187     final String runDirectory = new File(sdkHome).getParent();
188     final ProcessOutput processOutput = PySdkUtil.getProcessOutput(runDirectory, new String[]{sdkHome, getVersionOption()}, 10000);
189     return getVersionStringFromOutput(processOutput);
190   }
191
192   @Nullable
193   public String getVersionStringFromOutput(@NotNull ProcessOutput processOutput) {
194     if (processOutput.getExitCode() != 0) {
195       String errors = processOutput.getStderr();
196       if (StringUtil.isEmpty(errors)) {
197         errors = processOutput.getStdout();
198       }
199       LOG.warn("Couldn't get interpreter version: process exited with code " + processOutput.getExitCode() + "\n" + errors);
200       return null;
201     }
202     final String result = getVersionStringFromOutput(processOutput.getStderr());
203     if (result != null) {
204       return result;
205     }
206     return getVersionStringFromOutput(processOutput.getStdout());
207   }
208
209   @Nullable
210   public String getVersionStringFromOutput(@NotNull String output) {
211     return PatternUtil.getFirstMatch(Arrays.asList(StringUtil.splitByLines(output)), VERSION_RE);
212   }
213
214   public String getVersionOption() {
215     return "-V";
216   }
217
218   public Collection<String> getExtraDebugOptions() {
219     return Collections.emptyList();
220   }
221
222   public void initPythonPath(GeneralCommandLine cmd, Collection<String> path) {
223     initPythonPath(path, cmd.getEnvironment());
224   }
225
226   public static void addToEnv(final String key, String value, Map<String, String> envs) {
227     PythonEnvUtil.addPathToEnv(envs, key, value);
228   }
229
230   public static void setupEncodingEnvs(Map<String, String> envs, @NotNull Charset charset) {
231     final String encoding = charset.name();
232     PythonEnvUtil.setPythonIOEncoding(envs, encoding);
233   }
234
235   @NotNull
236   public abstract String getName();
237
238   @NotNull
239   public LanguageLevel getLanguageLevel(@NotNull Sdk sdk) {
240     final String version = sdk.getVersionString();
241     final String prefix = getName() + " ";
242     if (version != null && version.startsWith(prefix)) {
243       return LanguageLevel.fromPythonVersion(version.substring(prefix.length()));
244     }
245     return LanguageLevel.getDefault();
246   }
247
248   public Icon getIcon() {
249     return PythonIcons.Python.Python;
250   }
251
252   public void initPythonPath(Collection<String> path, Map<String, String> env) {
253     path = appendSystemPythonPath(path);
254     addToEnv(PythonEnvUtil.PYTHONPATH, StringUtil.join(path, File.pathSeparator), env);
255   }
256
257   public VirtualFile getSdkPath(VirtualFile path) {
258     return path;
259   }
260
261   @Nullable
262   public CommandLinePatcher commandLinePatcher() {
263     return null;
264   }
265 }