blob: 9fe55185d5b939d66aa0b938b1c8ecf15a910d7f [file] [log] [blame]
/*
* Copyright 2000-2013 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.intellij.util;
import com.intellij.execution.process.UnixProcessManager;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.AtomicNotNullLazyValue;
import com.intellij.openapi.util.NotNullLazyValue;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.StreamUtil;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.concurrency.FixedFuture;
import com.intellij.util.text.CaseInsensitiveStringHashingStrategy;
import gnu.trove.THashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class EnvironmentUtil {
private static final Logger LOG = Logger.getInstance("#com.intellij.util.EnvironmentUtil");
private static final int SHELL_ENV_READING_TIMEOUT = 10000;
private static final Future<Map<String, String>> ourEnvGetter;
static {
if (SystemInfo.isMac && Registry.is("idea.fix.mac.env")) {
ExecutorService executor = Executors.newSingleThreadExecutor();
ourEnvGetter = executor.submit(new Callable<Map<String, String>>() {
@Override
public Map<String, String> call() throws Exception {
try {
return getShellEnv();
}
catch (Throwable t) {
LOG.warn("can't get shell environment", t);
return System.getenv();
}
}
});
executor.shutdown();
}
else {
ourEnvGetter = new FixedFuture<Map<String, String>>(System.getenv());
}
}
private static final NotNullLazyValue<Map<String, String>> ourEnvironment = new AtomicNotNullLazyValue<Map<String, String>>() {
@NotNull
@Override
protected Map<String, String> compute() {
try {
return ourEnvGetter.get();
}
catch (Exception e) {
LOG.warn(e);
return System.getenv();
}
}
};
private static final NotNullLazyValue<Map<String, String>> ourEnvironmentOsSpecific = new AtomicNotNullLazyValue<Map<String, String>>() {
@NotNull
@Override
protected Map<String, String> compute() {
Map<String, String> env = ourEnvironment.getValue();
if (SystemInfo.isWindows) {
env = Collections.unmodifiableMap(new THashMap<String, String>(env, CaseInsensitiveStringHashingStrategy.INSTANCE));
}
return env;
}
};
private EnvironmentUtil() { }
public static boolean isEnvironmentReady() {
return ourEnvGetter.isDone();
}
/**
* Returns the process environment.
* On Mac OS X a shell (Terminal.app) environment is returned (unless disabled by a system property).
*
* @return unmodifiable map of the process environment.
*/
@NotNull
public static Map<String, String> getEnvironmentMap() {
return ourEnvironment.getValue();
}
/**
* Returns value for the passed environment variable name.
* The passed environment variable name is handled in a case-sensitive or case-insensitive manner depending on OS.<p>
* For example, on Windows <code>getValue("Path")</code> will return the same result as <code>getValue("PATH")</code>.
*
* @param name environment variable name
* @return value of the environment variable or null if no such variable found
*/
@Nullable
public static String getValue(@NotNull String name) {
return ourEnvironmentOsSpecific.getValue().get(name);
}
public static String[] getEnvironment() {
return flattenEnvironment(getEnvironmentMap());
}
public static String[] flattenEnvironment(Map<String, String> environment) {
String[] array = new String[environment.size()];
int i = 0;
for (Map.Entry<String, String> entry : environment.entrySet()) {
array[i++] = entry.getKey() + "=" + entry.getValue();
}
return array;
}
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);
}
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", null, false);
try {
String[] command = {shell, "-l", "-c", "'" + reader.getAbsolutePath() + "' '" + envFile.getAbsolutePath() + "'"};
LOG.info("loading shell env: " + StringUtil.join(command, " "));
Process process = Runtime.getRuntime().exec(command);
ProcessKiller processKiller = new ProcessKiller(process);
processKiller.killAfter(SHELL_ENV_READING_TIMEOUT);
int rv = process.waitFor();
processKiller.stopWaiting();
String lines = FileUtil.loadFile(envFile);
if (rv != 0 || lines.isEmpty()) {
throw new Exception("rv:" + rv + " text:" + lines.length());
}
return parseEnv(lines);
}
finally {
FileUtil.delete(envFile);
}
}
private static Map<String, String> parseEnv(String text) throws Exception {
Set<String> toIgnore = new HashSet<String>(Arrays.asList("_", "PWD", "SHLVL"));
Map<String, String> env = System.getenv();
Map<String, String> newEnv = new HashMap<String, String>();
String[] lines = text.split("\0");
for (String line : lines) {
int pos = line.indexOf('=');
if (pos <= 0) {
throw new Exception("malformed:" + line);
}
String name = line.substring(0, pos);
if (!toIgnore.contains(name)) {
newEnv.put(name, line.substring(pos + 1));
}
else if (env.containsKey(name)) {
newEnv.put(name, env.get(name));
}
}
LOG.info("shell environment loaded (" + newEnv.size() + " vars)");
return Collections.unmodifiableMap(newEnv);
}
public static String getProcessList() {
String diagnostics;
try {
Process p = Runtime.getRuntime().exec(SystemInfo.isWindows ? System.getenv("windir") +"\\system32\\tasklist.exe /v" : "ps a");
diagnostics = StreamUtil.readText(p.getInputStream());
}
catch (IOException e) {
diagnostics = ExceptionUtil.getThrowableText(e);
}
return diagnostics;
}
private static class ProcessKiller {
private final Process myProcess;
private final Object myWaiter = new Object();
public ProcessKiller(Process process) {
myProcess = process;
}
public void killAfter(long timeout) {
final long stop = System.currentTimeMillis() + timeout;
new Thread() {
@Override
public void run() {
synchronized (myWaiter) {
while (System.currentTimeMillis() < stop) {
try {
myProcess.exitValue();
break;
}
catch (IllegalThreadStateException ignore) { }
try {
myWaiter.wait(100);
}
catch (InterruptedException ignore) { }
}
}
try {
myProcess.exitValue();
}
catch (IllegalThreadStateException e) {
UnixProcessManager.sendSigIntToProcessTree(myProcess);
LOG.warn("timed out");
}
}
}.start();
}
public void stopWaiting() {
synchronized (myWaiter) {
myWaiter.notifyAll();
}
}
}
/** @deprecated use {@link #getEnvironmentMap()} (to remove in IDEA 14) */
@SuppressWarnings({"UnusedDeclaration", "SpellCheckingInspection"})
public static Map<String, String> getEnviromentProperties() {
return getEnvironmentMap();
}
/** @deprecated use {@link #getEnvironmentMap()} (to remove in IDEA 14) */
@SuppressWarnings({"UnusedDeclaration", "SpellCheckingInspection"})
public static Map<String, String> getEnvironmentProperties() {
return getEnvironmentMap();
}
@TestOnly
static Map<String, String> testLoader() {
try {
return getShellEnv();
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
@TestOnly
static Map<String, String> testParser(@NotNull String lines) {
try {
return parseEnv(lines);
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
}