package org.rubypeople.rdt.internal.launching;
import java.io.File;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.osgi.service.environment.Constants;
import org.rubypeople.rdt.core.util.Util;
import org.rubypeople.rdt.launching.AbstractVMInstallType;
import org.rubypeople.rdt.launching.IVMInstall;
public class StandardVMType extends AbstractVMInstallType
{
private static final String DEFAULT_MAJOR_MINOR_VERSION = "1.8";
private static final String DEFAULT_VERSION = "1.8.6";
private static final String USR = "/usr";
private static final String USR_BIN_RUBY = USR + "/bin/ruby";
private static final String USR_LOCAL_BIN_RUBY = "/usr/local/bin/ruby";
private static final String OPT_LOCAL_BIN_RUBY = "/opt/local/bin/ruby";
private static final String MAC_OSX_LEOPARD_RUBY_PATH = "/System/Library/Frameworks/Ruby.framework/Versions/Current/usr/bin/ruby";
/**
* Map of the install path for which we were unable to generate the library info during this session.
*/
private static Map<String, LibraryInfo> fgFailedInstallPath = new HashMap<String, LibraryInfo>();
/**
* Convenience handle to the system-specific file separator character
*/
private static final char fgSeparator = File.separatorChar;
/**
* The list of locations in which to look for the ruby executable in candidate VM install locations, relative to the
* VM install location.
*/
private static final String[] fgCandidateRubyFiles = { "rubyw", "rubyw.exe", "ruby", "ruby.exe" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
private static final String[] fgCandidateRubyLocations = { "", "bin" + fgSeparator }; //$NON-NLS-1$ //$NON-NLS-2$
@Override
protected IVMInstall doCreateVMInstall(String id)
{
return new StandardVM(this, id);
}
public IPath[] getDefaultLibraryLocations(File installLocation)
{
File rubyExecutable = findRubyExecutable(installLocation);
LibraryInfo info;
if (rubyExecutable == null)
{
LaunchingPlugin.logInfo("Unable to find ruby executable under: " + installLocation);
info = getDefaultLibraryInfo(installLocation);
}
else
{
info = getLibraryInfo(installLocation, rubyExecutable);
}
String[] loadpath = info.getBootpath();
IPath[] paths = new IPath[loadpath.length];
for (int i = 0; i < loadpath.length; i++)
{
paths[i] = new Path(loadpath[i]);
}
return paths;
}
public String getName()
{
return LaunchingMessages.StandardVMType_Standard_VM_3;
}
public IStatus validateInstallLocation(File rubyHome)
{
IStatus status = null;
File rubyExecutable = findRubyExecutable(rubyHome);
if (rubyExecutable == null)
{
status = new Status(IStatus.ERROR, LaunchingPlugin.getUniqueIdentifier(), 0,
LaunchingMessages.StandardVMType_Not_a_JDK_Root__Java_executable_was_not_found_1, null);
}
else
{
if (canDetectDefaultSystemLibraries(rubyHome, rubyExecutable))
{
status = new Status(IStatus.OK, LaunchingPlugin.getUniqueIdentifier(), 0,
LaunchingMessages.StandardVMType_ok_2, null);
}
else
{
status = new Status(IStatus.ERROR, LaunchingPlugin.getUniqueIdentifier(), 0,
LaunchingMessages.StandardVMType_Not_a_JDK_root__System_library_was_not_found__1, null);
}
}
return status;
}
/**
* Starting in the specified VM install location, attempt to find the 'ruby' executable file. If found, return the
* corresponding <code>File</code> object, otherwise return <code>null</code>.
*/
public static File findRubyExecutable(File vmInstallLocation)
{
// FIXME Yuck, what do we do if there's multiple VMs installed to the same base path (i.e. 1.8 and 1.9, with 1.9 installed with a suffix?)
// Try each candidate in order. The first one found wins. Thus, the order
// of fgCandidateRubyLocations and fgCandidateRubyFiles is significant.
for (int i = 0; i < fgCandidateRubyFiles.length; i++)
{
for (int j = 0; j < fgCandidateRubyLocations.length; j++)
{
File rubyFile = new File(vmInstallLocation, fgCandidateRubyLocations[j] + fgCandidateRubyFiles[i]);
rubyFile = Util.findFileWithOptionalSuffix(rubyFile.getAbsolutePath());
if (rubyFile != null && rubyFile.isFile())
{
return rubyFile;
}
}
}
return null;
}
/**
* Return <code>true</code> if the appropriate system libraries can be found for the specified ruby executable,
* <code>false</code> otherwise.
*/
protected boolean canDetectDefaultSystemLibraries(File rubyHome, File rubyExecutable)
{
File foundExecutable = findRubyExecutable(rubyHome);
if (foundExecutable == null || !foundExecutable.exists())
return false;
IPath[] locations = getDefaultLibraryLocations(rubyHome);
return locations != null && locations.length > 0;
}
/**
* Return library information corresponding to the specified install location. If the info does not exist, create it
* using the given Ruby executable.
*/
protected synchronized LibraryInfo getLibraryInfo(File rubyHome, File rubyExecutable)
{
// See if we already know the info for the requested VM. If not, generate it.
String installPath = rubyHome.getAbsolutePath();
LibraryInfo info = LaunchingPlugin.getLibraryInfo(this, installPath);
if (info == null)
{
info = fgFailedInstallPath.get(installPath);
if (info == null)
{
info = generateLibraryInfo(rubyHome, rubyExecutable);
if (info == null)
{
info = getDefaultLibraryInfo(rubyHome);
fgFailedInstallPath.put(installPath, info);
}
else
{
LaunchingPlugin.setLibraryInfo(this, installPath, info);
}
}
}
return info;
}
/**
* Returns default library info for the given install location.
*
* @param installLocation
* @return LibraryInfo
*/
protected LibraryInfo getDefaultLibraryInfo(File installLocation)
{
IPath[] dflts = getDefaultSystemLibrary(installLocation);
String[] strings = new String[dflts.length];
for (int i = 0; i < dflts.length; i++)
{
strings[i] = dflts[i].toOSString();
}
return new LibraryInfo(DEFAULT_VERSION, strings); //$NON-NLS-1$
}
/**
* Return an <code>IPath</code> corresponding to the single library file containing the standard Ruby classes for
* VMs version 1.8.x.
*/
protected IPath[] getDefaultSystemLibrary(File rubyHome)
{
String stdPath = rubyHome.getAbsolutePath() + fgSeparator + "lib" + fgSeparator + "ruby" + fgSeparator
+ DEFAULT_MAJOR_MINOR_VERSION;
String sitePath = rubyHome.getAbsolutePath() + fgSeparator + "lib" + fgSeparator + "ruby" + fgSeparator
+ "site_ruby" + fgSeparator + DEFAULT_MAJOR_MINOR_VERSION;
IPath[] paths = new IPath[2];
paths[0] = new Path(sitePath);
paths[1] = new Path(stdPath);
return paths;
}
/*
* (non-Javadoc)
* @see org.rubypeople.rdt.launching.IVMInstallType#detectInstallLocation()
*/
public File detectInstallLocation()
{
if (Platform.getOS().equals(Constants.OS_WIN32))
{
return tryLocation(detectInstallOnWindows());
}
File rubyExecutable = null;
// Fix for ROR-179
if (Platform.getOS().equals(Constants.OS_MACOSX))
{ // Mac OSX Leopard
File rubyHome = tryLocation(new File(MAC_OSX_LEOPARD_RUBY_PATH));
if (rubyHome != null)
return rubyHome;
}
File tentativeRubyHome = null;
// Mac, Linux - so let's just run 'which ruby' and parse out the result
try
{
rubyExecutable = parseRubyExecutableLocation(executeAndRead(new String[] { "which", "ruby" })); //$NON-NLS-1$ //$NON-NLS-2$
tentativeRubyHome = tryLocation(rubyExecutable);
}
catch (Exception e)
{
LaunchingPlugin.log(e);
}
// If we don't find ruby, or we find one at /usr/bin/ruby
// try to see if there's one at /usr/local or /opt/local. If so, then prefer one of those.
if (tentativeRubyHome == null || tentativeRubyHome.getAbsolutePath().equals(USR))
{
File rubyHome = tryLocation(new File(USR_LOCAL_BIN_RUBY));
if (rubyHome != null)
return rubyHome;
rubyHome = tryLocation(new File(OPT_LOCAL_BIN_RUBY));
if (rubyHome != null)
return rubyHome;
}
if (tentativeRubyHome != null)
return tentativeRubyHome;
// force trying /usr/bin/ruby if we didn't detect anything, and found nothing at /usr/local or /opt/local
return tryLocation(new File(USR_BIN_RUBY));
}
private File detectInstallOnWindows()
{
String winPath = System.getenv("Path"); // iterate through system path and try to find ruby.exe
String[] paths = winPath.split(";");
for (int i = 0; i < paths.length; i++)
{
String possibleExecutablePath = paths[i] + File.separator + "ruby.exe";
File possible = new File(possibleExecutablePath);
if (possible.exists())
{
return possible;
}
}
// if all else fails, just try "C:/ruby"
return new File("C:" + File.separator + "ruby" + File.separator + "bin" + File.separator + "ruby.exe");
}
private File tryLocation(File rubyExecutable)
{
if (rubyExecutable == null)
return null;
File bin = rubyExecutable.getParentFile();
if (!bin.exists())
return null;
File rubyHome = bin.getParentFile();
if (!rubyHome.exists())
return null;
if (!canDetectDefaultSystemLibraries(rubyHome, rubyExecutable))
{
LaunchingPlugin.logInfo("Was unable to detect default ruby system libraries at: "
+ rubyHome.getAbsolutePath());
return null;
}
return rubyHome;
}
public File findExecutable(File installLocation)
{
return findRubyExecutable(installLocation);
}
public String getVMPlatform(File rubyHome, File rubyExecutable)
{
String rubyExecutablePath = rubyExecutable.getAbsolutePath();
String[] cmdLine = new String[] { rubyExecutablePath, "-v" }; //$NON-NLS-1$
String platform = parsePlatform(executeAndRead(cmdLine));
if (platform == null)
{
// log error that we were unable to generate library info - see bug 70011
LaunchingPlugin
.log(MessageFormat.format("Failed to retrieve platform for {0}", rubyHome.getAbsolutePath())); //$NON-NLS-1$
}
return platform;
}
private String parsePlatform(List<String> lines)
{
if (lines == null || lines.size() == 0)
return null;
String firstLine = lines.remove(0);
Pattern pat = Pattern
.compile("ruby\\s\\d\\.\\d\\.\\d\\s\\(\\d\\d\\d\\d\\-\\d\\d\\-\\d\\d\\spatchlevel\\s\\d+\\)\\s\\[.+\\-(.+)\\]");
Matcher m = pat.matcher(firstLine);
if (m.find())
{
return m.group(1);
}
return null;
}
}