/*******************************************************************************
* Copyright (c) 2012, 2013 Andrew Gvozdev and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Andrew Gvozdev - Initial API and implementation
*******************************************************************************/
package org.eclipse.cdt.internal.core;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Collections;
import java.util.Map;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.envvar.IEnvironmentVariable;
import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
import org.eclipse.cdt.core.settings.model.util.CDataUtil;
import org.eclipse.cdt.utils.PathUtil;
import org.eclipse.cdt.utils.WindowsRegistry;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
/**
* A collection of cygwin-related utilities.
*/
public class Cygwin {
public static final String ENV_CYGWIN_HOME = "CYGWIN_HOME"; //$NON-NLS-1$
private static final String ENV_PATH = "PATH"; //$NON-NLS-1$
private static final String CYGPATH = "cygpath"; //$NON-NLS-1$
private static final String DEFAULT_ROOT = "C:\\cygwin"; //$NON-NLS-1$
private static final String CYGWIN_DLL = "cygwin1.dll"; //$NON-NLS-1$
private static final String REGISTRY_KEY_SETUP = "SOFTWARE\\Cygwin\\setup"; //$NON-NLS-1$
private static final String REGISTRY_KEY_SETUP_WIN64 = "SOFTWARE\\Wow6432Node\\Cygwin\\setup"; //$NON-NLS-1$
// note that in Cygwin 1.7 the mount point storage has been moved out of the registry
private static final String REGISTRY_KEY_MOUNTS = "SOFTWARE\\Cygnus Solutions\\Cygwin\\mounts v2\\"; //$NON-NLS-1$
private static final String PATH_NAME = "native"; //$NON-NLS-1$
private static final String ROOTPATTERN = "/"; //$NON-NLS-1$
private static final char SLASH = '/';
private static final char BACKSLASH = '\\';
private static final boolean isWindowsPlatform = Platform.getOS().equals(Platform.OS_WIN32);
private static String envPathValueCached = null;
private static String envCygwinHomeValueCached = null;
private static String cygwinLocation = null;
private static boolean isCygwinLocationCached = false;
private final static Map<String/*envPath*/, String /*cygpathLocation*/> cygpathLocationCache = Collections.synchronizedMap(new LRUCache<String, String>(1,20));
private final static Map<String/*command*/, String /*translatedPath*/> translatedPathsCache = Collections.synchronizedMap(new LRUCache<String, String>(10,500));
/**
* Find location of "cygpath" utility on the file system.
*/
private static String findCygpathLocation(String envPath) {
if (envPath == null) {
// $PATH from user preferences
IEnvironmentVariable varPath = CCorePlugin.getDefault().getBuildEnvironmentManager().getVariable(ENV_PATH, (ICConfigurationDescription) null, true);
if (varPath != null) {
envPath = varPath.getValue();
}
}
String cygpathLocation = cygpathLocationCache.get(envPath);
if (cygpathLocation == null) {
IPath loc = PathUtil.findProgramLocation(CYGPATH, envPath);
cygpathLocation = loc != null ? loc.toOSString() : null;
cygpathLocationCache.put(envPath, cygpathLocation);
}
return cygpathLocation;
}
/**
* Check if cygwin path conversion utilities are available in the path.
* Tells whether cygwin is installed in the path.
*
* @param envPath - list of directories to search for cygwin utilities separated
* by path separator (format of environment variable $PATH)
* or {@code null} to use current $PATH.
* @return {@code true} if cygwin is available, {@code false} otherwise.
*/
public static boolean isAvailable(String envPath) {
return isWindowsPlatform && findCygpathLocation(envPath) != null;
}
/**
* Check if cygwin path conversion utilities are available in $PATH.
* Tells whether cygwin is installed in the path.
*
* @return {@code true} if cygwin is available, {@code false} otherwise.
*/
public static boolean isAvailable() {
return isWindowsPlatform && findCygpathLocation(null) != null;
}
/**
* Run program (assuming cygpath) and return the translated path which is the first line of output.
*/
private static String runCygpath(String[] args) throws IOException {
String command = getCommand(args);
String translatedPath = translatedPathsCache.get(command);
if (translatedPath == null) {
Process cygpathProcess = Runtime.getRuntime().exec(args);
BufferedReader stdout = new BufferedReader(new InputStreamReader(cygpathProcess.getInputStream()));
String firstLine = null;
try {
firstLine = stdout.readLine();
} finally {
stdout.close();
}
if (firstLine == null) {
throw new IOException("Unable read output from command=[" + command + "]"); //$NON-NLS-1$ //$NON-NLS-2$
}
translatedPath = firstLine.trim();
translatedPathsCache.put(command, translatedPath);
}
return translatedPath;
}
/**
* Construct a command from arguments array.
*/
private static String getCommand(String[] args) {
String command = ""; //$NON-NLS-1$
for (String arg : args) {
command = command + arg + ' ';
}
return command.trim();
}
/**
* Conversion from Cygwin path to Windows path.
* Note that there is no need to cache results, they are already cached internally.
*
* @param cygwinPath - cygwin path.
* @param envPath - list of directories to search for cygwin utilities separated
* by path separator (format of environment variable $PATH).
* @return Windows style converted path. Note that that also converts cygwin links to their targets.
*
* @throws UnsupportedOperationException if Cygwin is unavailable.
* @throws IOException on IO problem.
*/
public static String cygwinToWindowsPath(String cygwinPath, String envPath) throws IOException, UnsupportedOperationException {
if (cygwinPath == null || cygwinPath.trim().length() == 0)
return cygwinPath;
if (!isWindowsPlatform) {
throw new UnsupportedOperationException("Not a Windows system, Cygwin is unavailable."); //$NON-NLS-1$
}
String cygpathLocation = findCygpathLocation(envPath);
if (cygpathLocation == null) {
throw new UnsupportedOperationException(CYGPATH + " is not in the system search path."); //$NON-NLS-1$
}
String windowsPath = runCygpath(new String[] {cygpathLocation, "-w", cygwinPath}); //$NON-NLS-1$
return windowsPath;
}
/**
* Conversion from Cygwin path to Windows path.
* Note that there is no need to cache results, they are already cached internally.
*
* @param cygwinPath - cygwin path.
* @return Windows style converted path. Note that that also converts cygwin links to their targets.
*
* @throws UnsupportedOperationException if Cygwin is unavailable.
* @throws IOException on IO problem.
*/
public static String cygwinToWindowsPath(String cygwinPath) throws IOException, UnsupportedOperationException {
return cygwinToWindowsPath(cygwinPath, null);
}
/**
* Conversion from Windows path to Cygwin path.
* Note that there is no need to cache results, they are already cached internally.
*
* @param windowsPath - Windows path.
* @param envPath - list of directories to search for cygwin utilities (value of environment variable $PATH).
* @return Cygwin style converted path.
*
* @throws UnsupportedOperationException if Cygwin is unavailable.
* @throws IOException on IO problem.
*/
public static String windowsToCygwinPath(String windowsPath, String envPath) throws IOException, UnsupportedOperationException {
if (windowsPath == null || windowsPath.trim().length() == 0)
return windowsPath;
if (!isWindowsPlatform) {
throw new UnsupportedOperationException("Not a Windows system, Cygwin is unavailable."); //$NON-NLS-1$
}
String cygpathLocation = findCygpathLocation(envPath);
if (cygpathLocation == null) {
throw new UnsupportedOperationException(CYGPATH + " is not in the system search path."); //$NON-NLS-1$
}
String cygwinPath = runCygpath(new String[] {cygpathLocation, "-u", windowsPath}); //$NON-NLS-1$
return cygwinPath;
}
/**
* Conversion from Windows path to Cygwin path.
* Note that there is no need to cache results, they are already cached internally.
*
* @param windowsPath - Windows path.
* @return Cygwin style converted path.
*
* @throws UnsupportedOperationException if Cygwin is unavailable.
* @throws IOException on IO problem.
*/
public static String windowsToCygwinPath(String windowsPath) throws IOException, UnsupportedOperationException {
return windowsToCygwinPath(windowsPath, null);
}
/**
* Find location where Cygwin is installed. A number of locations is being checked,
* such as environment variable $CYGWIN_HOME, $PATH, Windows registry et al.
* <br><br>
* If you use this do not cache results to ensure user preferences are accounted for.
* Please rely on internal caching.
*
* @return Location of Cygwin root folder "/" on file system in Windows format.
*/
public static String getCygwinHome() {
if (!isWindowsPlatform) {
return null;
}
IEnvironmentVariable varPath = CCorePlugin.getDefault().getBuildEnvironmentManager().getVariable(ENV_PATH, (ICConfigurationDescription) null, true);
String envPathValue = varPath != null ? varPath.getValue() : null;
IEnvironmentVariable varCygwinHome = CCorePlugin.getDefault().getBuildEnvironmentManager().getVariable(ENV_CYGWIN_HOME, (ICConfigurationDescription) null, true);
String envCygwinHomeValue = varCygwinHome != null ? varCygwinHome.getValue() : null;
// isCygwinLocationCached is used to figure fact of caching when all cached objects are null
if (isCygwinLocationCached && CDataUtil.objectsEqual(envPathValue, envPathValueCached) && CDataUtil.objectsEqual(envCygwinHomeValue, envCygwinHomeValueCached)) {
return cygwinLocation;
}
cygwinLocation = findCygwinRoot(envPathValue, envCygwinHomeValue);
envPathValueCached = envPathValue;
envCygwinHomeValueCached = envCygwinHomeValue;
isCygwinLocationCached = true;
return cygwinLocation;
}
/**
* Reads required value from registry. Looks in both
* HKEY_CURRENT_USER and HKEY_LOCAL_MACHINE
*
* @param key Registry key
* @param name Registry value to read
* @return corresponding string value or null if nothing found
*/
private static String readValueFromRegistry(String key, String name) {
WindowsRegistry registry = WindowsRegistry.getRegistry();
if (registry != null) {
String s = registry.getCurrentUserValue(key, name);
if(s == null) {
s = registry.getLocalMachineValue(key, name);
}
if (s != null) {
return (s.replace(BACKSLASH, SLASH));
}
}
return null;
}
/**
* @return The absolute path to cygwin's root or null if not found
*/
private static String findCygwinRoot(String envPathValue, String envCygwinHomeValue) {
String rootValue = null;
// Check $CYGWIN_HOME
if (envCygwinHomeValue != null && !envCygwinHomeValue.isEmpty()) {
IPath location = new Path(envCygwinHomeValue + "/bin/" + CYGWIN_DLL); //$NON-NLS-1$
if (location.toFile().exists()) {
// get rootValue from "rootValue\bin\cygwin1.dll"
rootValue = location.removeLastSegments(2).toOSString();
}
}
// Look in PATH values. Look for cygwin1.dll
if(rootValue == null) {
IPath location = PathUtil.findProgramLocation(CYGWIN_DLL, envPathValue);
if (location != null) {
// get rootValue from "rootValue\bin\cygwin1.dll"
rootValue = location.removeLastSegments(2).toOSString();
}
}
// Try to find the root dir in SOFTWARE\Cygwin\setup
if(rootValue == null) {
rootValue = readValueFromRegistry(REGISTRY_KEY_SETUP, "rootdir"); //$NON-NLS-1$
}
// Try to find the root dir in SOFTWARE\Wow6432Node\Cygwin\setup
if(rootValue == null) {
rootValue = readValueFromRegistry(REGISTRY_KEY_SETUP_WIN64, "rootdir"); //$NON-NLS-1$
}
// Try to find the root dir in SOFTWARE\Cygnus Solutions
if (rootValue == null) {
rootValue = readValueFromRegistry(REGISTRY_KEY_MOUNTS + ROOTPATTERN, PATH_NAME);
}
// Try the default Cygwin install dir
if(rootValue == null) {
File file = new File(DEFAULT_ROOT);
if (file.exists() && file.isDirectory())
rootValue = DEFAULT_ROOT;
}
if(rootValue != null) {
rootValue = rootValue.replace(BACKSLASH, SLASH);
}
return rootValue;
}
}