61b79b2266927233d2cdf366ae0eb8f6d8674fbc
[idea/community.git] / platform / platform-api / src / com / intellij / execution / configurations / PathEnvironmentVariableUtil.java
1 /*
2  * Copyright 2000-2017 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.intellij.execution.configurations;
17
18 import com.intellij.openapi.util.SystemInfo;
19 import com.intellij.openapi.util.text.StringUtil;
20 import com.intellij.util.EnvironmentUtil;
21 import com.intellij.util.SmartList;
22 import com.intellij.util.containers.ContainerUtil;
23 import org.jetbrains.annotations.NotNull;
24 import org.jetbrains.annotations.Nullable;
25
26 import java.io.File;
27 import java.io.FileFilter;
28 import java.util.Collection;
29 import java.util.Collections;
30 import java.util.List;
31
32 /**
33  * A collection of utility methods for working with PATH environment variable.
34  */
35 public class PathEnvironmentVariableUtil {
36
37   private static final String PATH = "PATH";
38
39   private PathEnvironmentVariableUtil() { }
40
41   /**
42    * Finds an executable file with the specified base name, that is located in a directory
43    * listed in PATH environment variable.
44    *
45    * @param fileBaseName file base name
46    * @return {@link File} instance or null if not found
47    */
48   @Nullable
49   public static File findInPath(@NotNull String fileBaseName) {
50     return findInPath(fileBaseName, null);
51   }
52
53   /**
54    * Finds an executable file with the specified base name, that is located in a directory
55    * listed in PATH environment variable and is accepted by filter.
56    *
57    * @param fileBaseName file base name
58    * @param filter       exe file filter
59    * @return {@link File} instance or null if not found
60    */
61   @Nullable
62   public static File findInPath(@NotNull String fileBaseName, @Nullable FileFilter filter) {
63     return findInPath(fileBaseName, EnvironmentUtil.getValue(PATH), filter);
64   }
65
66   /**
67    * Finds an executable file with the specified base name, that is located in a directory
68    * listed in the passed PATH environment variable value and is accepted by filter.
69    *
70    * @param fileBaseName      file base name
71    * @param pathVariableValue value of PATH environment variable
72    * @param filter            exe file filter
73    * @return {@link File} instance or null if not found
74    */
75   @Nullable
76   public static File findInPath(@NotNull String fileBaseName, @Nullable String pathVariableValue, @Nullable FileFilter filter) {
77     List<File> exeFiles = findExeFilesInPath(true, filter, pathVariableValue, fileBaseName);
78     return ContainerUtil.getFirstItem(exeFiles);
79   }
80
81   /**
82    * Finds an executable file with the specified base name, that is located in a directory
83    * listed in an original PATH environment variable.
84    * Original PATH environment variable value is a value returned by <code>System.getenv("PATH")</code>.
85    *
86    * @param fileBaseName file base name
87    * @return {@link File} instance or null if not found
88    */
89   private static File findInOriginalPath(@NotNull String fileBaseName) {
90     List<File> exeFiles = findExeFilesInPath(true, null, System.getenv(PATH), fileBaseName);
91     return ContainerUtil.getFirstItem(exeFiles);
92   }
93
94   /**
95    * Finds all executable files with the specified base name, that are located in directories
96    * from PATH environment variable.
97    *
98    * @param fileBaseName file base name
99    * @return file list
100    */
101   @NotNull
102   public static List<File> findAllExeFilesInPath(@NotNull String fileBaseName) {
103     return findAllExeFilesInPath(fileBaseName, null);
104   }
105
106   @NotNull
107   public static List<File> findAllExeFilesInPath(@NotNull String fileBaseName, @Nullable FileFilter filter) {
108     return findExeFilesInPath(false, filter, EnvironmentUtil.getValue(PATH), fileBaseName);
109   }
110
111   @NotNull
112   private static List<File> findExeFilesInPath(boolean stopAfterFirstMatch,
113                                                @Nullable FileFilter filter,
114                                                @Nullable String pathEnvVarValue,
115                                                @NotNull String... fileBaseNames) {
116     if (pathEnvVarValue == null) {
117       return Collections.emptyList();
118     }
119     List<File> result = new SmartList<>();
120     List<String> dirPaths = getPathDirs(pathEnvVarValue);
121     for (String dirPath : dirPaths) {
122       File dir = new File(dirPath);
123       if (dir.isAbsolute() && dir.isDirectory()) {
124         for (String fileBaseName : fileBaseNames) {
125           File exeFile = new File(dir, fileBaseName);
126           if (exeFile.isFile() && exeFile.canExecute()) {
127             if (filter == null || filter.accept(exeFile)) {
128               result.add(exeFile);
129               if (stopAfterFirstMatch) {
130                 return result;
131               }
132             }
133           }
134         }
135       }
136     }
137     return result;
138   }
139
140   @NotNull
141   public static List<String> getPathDirs(@NotNull String pathEnvVarValue) {
142     return StringUtil.split(pathEnvVarValue, File.pathSeparator, true, true);
143   }
144
145   /**
146    * Alters the passed in exe path to increase probability of exe file success finding when
147    * spawning an external process. Modifications are performed iff the passed in exe path is
148    * a basename (i.e. it doesn't contain slashes). E.g. "java", "git" or "node".
149    * <p>
150    * The motivation behind this modification is as follows. When exe path is a basename,
151    * {@link ProcessBuilder#start} searches for the executable file in the original PATH
152    * environment variable (i.e. {@code System.getenv("PATH")}).
153    * The problem is that on MacOSX original PATH value can be different than the PATH
154    * value in Terminal (see {@link EnvironmentUtil#getEnvironmentMap()}.
155    *
156    * @param exePath String path to exe file (basename, relative path or absolute path)
157    * @return if an exe file can be found in {@code EnvironmentUtil.getValue("PATH")} and
158    * nothing found in original PATH (i.e. {@code System.getenv("PATH")}),
159    * return the found exe file absolute path.
160    * Otherwise, return the passed in exe path.
161    */
162   @NotNull
163   public static String toLocatableExePath(@NotNull String exePath) {
164     if (SystemInfo.isMac) {
165       if (!StringUtil.containsChar(exePath, '/') && !StringUtil.containsChar(exePath, '\\')) {
166         File originalResolvedExeFile = findInOriginalPath(exePath);
167         // don't modify exePath if the absolute path can be found in the original PATH
168         if (originalResolvedExeFile == null) {
169           File resolvedExeFile = findInPath(exePath);
170           if (resolvedExeFile != null) {
171             exePath = resolvedExeFile.getAbsolutePath();
172           }
173         }
174       }
175     }
176     return exePath;
177   }
178
179   @NotNull
180   public static List<String> getWindowsExecutableFileExtensions() {
181     if (SystemInfo.isWindows) {
182       String allExtensions = System.getenv("PATHEXT");
183       if (allExtensions != null) {
184         Collection<String> extensions = StringUtil.split(allExtensions, ";", true, true);
185         extensions = ContainerUtil.filter(extensions, s -> !StringUtil.isEmpty(s) && s.startsWith("."));
186         return ContainerUtil.map2List(extensions, s -> StringUtil.toLowerCase(s));
187       }
188     }
189     return Collections.emptyList();
190   }
191
192   public static String findExecutableInWindowsPath(@NotNull String exePath) {
193     if (SystemInfo.isWindows) {
194       if (!StringUtil.containsChar(exePath, '/') && !StringUtil.containsChar(exePath, '\\')) {
195         List<String> executableFileExtensions = getWindowsExecutableFileExtensions();
196
197         String[] baseNames = ContainerUtil.map2Array(executableFileExtensions, String.class, s -> exePath+s);
198         List<File> exeFiles = findExeFilesInPath(true, null, EnvironmentUtil.getValue(PATH), baseNames);
199         File foundFile = ContainerUtil.getFirstItem(exeFiles);
200         if(foundFile != null){
201           return foundFile.getAbsolutePath();
202         }
203       }
204     }
205     return exePath;
206   }
207 }