blob: 04196589913f4bcc46d96487492ed01246699fd9 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2017 IBM Corporation 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
*
*******************************************************************************/
package org.eclipse.dltk.internal.launching;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExecutableExtension;
import org.eclipse.core.runtime.ILog;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.dltk.core.DLTKCore;
import org.eclipse.dltk.core.environment.EnvironmentPathUtils;
import org.eclipse.dltk.core.environment.IDeployment;
import org.eclipse.dltk.core.environment.IEnvironment;
import org.eclipse.dltk.core.environment.IExecutionEnvironment;
import org.eclipse.dltk.core.environment.IFileHandle;
import org.eclipse.dltk.launching.EnvironmentVariable;
import org.eclipse.dltk.launching.IInterpreterInstall;
import org.eclipse.dltk.launching.IInterpreterInstallType;
import org.eclipse.dltk.launching.LaunchingMessages;
import org.eclipse.dltk.launching.LibraryLocation;
import org.eclipse.dltk.launching.ScriptRuntime;
import org.eclipse.osgi.util.NLS;
/**
* Abstract implementation of a interpreter install type. Subclasses should
* implement
* <ul>
* <li><code>IInterpreterInstall doCreateInterpreterInstall(String id)</code>
* </li>
* <li><code>String getName()</code></li>
* <li><code>IStatus validateInstallLocation(File installLocation)</code></li>
* <li><code>String getLanguageId()</code></li>
* </ul>
* <p>
* Clients implementing Interpreter install types should subclass this class.
* </p>
*/
public abstract class AbstractInterpreterInstallType
implements IInterpreterInstallType, IExecutableExtension {
public interface ILookupRunnable {
public void run(IProgressMonitor monitor)
throws InvocationTargetException, InterruptedException;
}
private static final int NOT_WORK_COUNT = -2;
private static final String DLTK_TOTAL_WORK_START = "%DLTK_TOTAL_WORK_START%:"; //$NON-NLS-1$
private static final String DLTK_TOTAL_WORK_END = "%DLTK_TOTAL_WORK_END%"; //$NON-NLS-1$
private static final String DLTK_TOTAL_WORK_INC = "%DLTK_TOTAL_WORK_INCREMENT%"; //$NON-NLS-1$
public static final String DLTK_PATH_PREFIX = "DLTK:"; //$NON-NLS-1$
private List<IInterpreterInstall> fInterpreters;
private String fId;
private static HashMap<Object, LibraryLocation[]> fCachedLocations = new HashMap<>();
protected AbstractInterpreterInstallType() {
fInterpreters = new ArrayList<>();
}
@Override
public IInterpreterInstall[] getInterpreterInstalls() {
return fInterpreters
.toArray(new IInterpreterInstall[fInterpreters.size()]);
}
@Override
public void disposeInterpreterInstall(String id) {
Iterator<IInterpreterInstall> it = fInterpreters.iterator();
while (it.hasNext()) {
IInterpreterInstall install = it.next();
if (install.getId().equals(id)) {
it.remove();
ScriptRuntime.fireInterpreterRemoved(install);
return;
}
}
}
@Override
public IInterpreterInstall findInterpreterInstall(String id) {
for (IInterpreterInstall install : fInterpreters) {
if (install.getId().equals(id)) {
return install;
}
}
return null;
}
@Override
public IInterpreterInstall createInterpreterInstall(String id)
throws IllegalArgumentException {
if (findInterpreterInstall(id) != null) {
String format = LaunchingMessages.InterpreterInstallType_duplicateInterpreter;
throw new IllegalArgumentException(NLS.bind(format, id));
}
IInterpreterInstall install = doCreateInterpreterInstall(id);
fInterpreters.add(install);
return install;
}
/**
* Subclasses should return a new instance of the appropriate
* <code>IInterpreterInstall</code> subclass from this method.
*
* @param id
* The Interpreter's id. The <code>IInterpreterInstall</code>
* instance that is created must return <code>id</code> from its
* <code>getId()</code> method. Must not be <code>null</code>.
* @return the newly created IInterpreterInstall instance. Must not return
* <code>null</code>.
*/
protected abstract IInterpreterInstall doCreateInterpreterInstall(
String id);
/**
* Initializes the id parameter from the "id" attribute in the configuration
* markup. Subclasses should not override this method.
*
* @param config
* the configuration element used to trigger this execution. It
* can be queried by the executable extension for specific
* configuration properties
* @param propertyName
* the name of an attribute of the configuration element used on
* the <code>createExecutableExtension(String)</code> call. This
* argument can be used in the cases where a single configuration
* element is used to define multiple executable extensions.
* @param data
* adapter data in the form of a <code>String</code>, a
* <code>Hashtable</code>, or <code>null</code>.
* @see org.eclipse.core.runtime.IExecutableExtension#setInitializationData(org.eclipse.core.runtime.IConfigurationElement,
* java.lang.String, java.lang.Object)
*/
@Override
public void setInitializationData(IConfigurationElement config,
String propertyName, Object data) {
fId = config.getAttribute("id"); //$NON-NLS-1$
}
@Override
public String getId() {
return fId;
}
@Override
public IInterpreterInstall findInterpreterInstallByName(String name) {
for (IInterpreterInstall install : fInterpreters) {
if (install.getName().equals(name)) {
return install;
}
}
return null;
}
protected static void storeFile(File dest, URL url) throws IOException {
try (InputStream input = new BufferedInputStream(url.openStream());
OutputStream output = new BufferedOutputStream(
new FileOutputStream(dest));) {
// Simple copy
int ch = -1;
while ((ch = input.read()) != -1) {
output.write(ch);
}
}
}
protected String[] extractEnvironment(IExecutionEnvironment exeEnv,
EnvironmentVariable[] variables) {
Map<String, String> env = exeEnv.getEnvironmentVariables(false);
if (env == null) {
return null;
}
filterEnvironment(env);
// Overwrite from variables with updates values.
if (variables != null) {
EnvironmentVariable[] vars = EnvironmentResolver.resolve(env,
variables);
for (int i = 0; i < vars.length; i++) {
env.put(vars[i].getName(), vars[i].getValue());
}
}
List<String> list = new ArrayList<>();
for (Map.Entry<String, String> entry : env.entrySet()) {
list.add(entry.getKey() + "=" + entry.getValue()); //$NON-NLS-1$
}
return list.toArray(new String[list.size()]);
}
/**
* filter out any undesirable entries from the system environment
*
* <p>
* default implementation does nothing. subclasses are free to override.
* </p>
*
* @param environment
* system environment
*/
protected void filterEnvironment(Map<String, String> environment) {
// Nothing to do
}
/**
* Process should write one line into console with format 'path1 path2
* path3'
*
* @param monitor
* @param process
* @return
*/
protected String[] readPathsFromProcess(final IProgressMonitor monitor,
final Process process) {
// DLTKLaunchingPlugin.log(new Status(IStatus.INFO,
// DLTKLaunchingPlugin.PLUGIN_ID, IStatus.INFO,
// "Start reading discovery script library paths", null));
final BufferedReader dataIn = new BufferedReader(
new InputStreamReader(process.getInputStream()));
final List<String> result = new ArrayList<>();
// final Object lock = new Object();
Thread tReading = new Thread(() -> {
boolean workReceived = false;
try {
while (true) {
if (monitor != null && monitor.isCanceled()) {
monitor.worked(1);
process.destroy();
break;
}
String line = dataIn.readLine();
if (line != null && monitor != null && !workReceived) {
int work = extractWorkFromLine(line);
if (work != NOT_WORK_COUNT) {
monitor.beginTask(
LaunchingMessages.AbstractInterpreterInstallType_fetchingInterpreterLibraryLocations,
work);
// monitor.subTask("Featching interpeter library
// locations");
workReceived = true;
}
}
if (line != null && monitor != null
&& detectWorkInc(line)) {
monitor.worked(1);
}
if (line != null) {
result.add(line);
} else {
break;
}
}
} catch (IOException e) {
DLTKLaunchingPlugin.log(new Status(IStatus.INFO,
DLTKLaunchingPlugin.PLUGIN_ID, IStatus.INFO,
NLS.bind(
LaunchingMessages.AbstractInterpreterInstallType_failedToReadFromDiscoverScriptOutputStream,
e.getMessage()),
e));
} finally {
if (monitor != null) {
if (!workReceived) {
monitor.beginTask(
LaunchingMessages.AbstractInterpreterInstallType_fetchingInterpreterLibraryLocations,
1);
}
monitor.done();
}
}
});
tReading.start();
try {
tReading.join(10000);
} catch (InterruptedException e) {
if (DLTKCore.DEBUG) {
e.printStackTrace();
}
}
return result.toArray(new String[result.size()]);
}
private boolean detectWorkInc(String line) {
return line.indexOf(DLTK_TOTAL_WORK_INC) != -1;
}
/**
* Extract work from specified line.
*/
private int extractWorkFromLine(String line) {
int pos1 = line.indexOf(DLTK_TOTAL_WORK_START);
int pos2 = line.indexOf(DLTK_TOTAL_WORK_END);
if (pos1 != -1 && pos2 != -1) {
String totalWork = line
.substring(pos1 + DLTK_TOTAL_WORK_START.length(), pos2);
int intValue = Integer.parseInt(totalWork);
if (intValue == -1) {
return IProgressMonitor.UNKNOWN;
}
return intValue;
}
return NOT_WORK_COUNT;
}
public static LibraryLocation[] correctLocations(
final List<LibraryLocation> locs) {
return correctLocations(locs, null);
}
public static LibraryLocation[] correctLocations(
final List<LibraryLocation> locs, IProgressMonitor monitor) {
List<LibraryLocation> resolvedLocs = new ArrayList<>();
if (monitor != null) {
monitor.beginTask(
LaunchingMessages.AbstractInterpreterInstallType_correctingLocations,
locs.size());
}
for (LibraryLocation n : locs) {
// String res;
// try {
// File f = l.getLibraryPath().toFile();
// if (f != null)
// res = f.getCanonicalPath();
// else
// continue;
// } catch (IOException e) {
// continue;
// }
// LibraryLocation n = new LibraryLocation(new Path(res));
if (!resolvedLocs.contains(n))
resolvedLocs.add(n);
if (monitor != null) {
monitor.worked(1);
}
}
LibraryLocation[] libs = resolvedLocs
.toArray(new LibraryLocation[resolvedLocs.size()]);
if (monitor != null) {
monitor.done();
}
return libs;
}
protected void fillLocationsExceptOne(IEnvironment env,
final List<LibraryLocation> locs, String[] paths, IPath path) {
String sPath = path.toOSString();
for (int i = 0; i < paths.length; i++) {
if (!paths[i].equals(sPath)) {
IFileHandle f = env.getFile(new Path(paths[i]));
if (f.exists()) {
LibraryLocation l = new LibraryLocation(
EnvironmentPathUtils.getFullPath(env, f.getPath()));
if (!locs.contains(l)) {
locs.add(l);
}
}
}
}
}
/**
* run the interpreter library lookup in a
* <code>ProgressMonitorDialog</code>
*/
protected void runLibraryLookup(final ILookupRunnable runnable,
IProgressMonitor monitor)
throws InvocationTargetException, InterruptedException {
/*
* ProgressMonitorDialog progress = new ProgressMonitorDialog(null);
* Display current = Display.getCurrent(); if (current != null) { try {
* progress.run(true, false, runnable); } catch (SWTException ex) {
* runnable.run(new NullProgressMonitor()); } } else { runnable.run(new
* NullProgressMonitor()); }
*/
runnable.run(monitor);
}
protected abstract String[] getPossibleInterpreterNames();
protected abstract String getPluginId();
protected abstract ILog getLog();
protected abstract IPath createPathFile(IDeployment deployment)
throws IOException;
protected String[] buildCommandLine(IFileHandle installLocation,
IFileHandle pathFile) {
String interpreterPath = installLocation.getCanonicalPath();
String scriptPath = pathFile.getCanonicalPath();
return new String[] { interpreterPath, scriptPath };
}
protected String getBuildPathDelimeter() {
return " "; //$NON-NLS-1$
}
protected String[] parsePaths(String result) {
String res = result;
if (res.startsWith(DLTK_PATH_PREFIX)) {
res = res.substring(DLTK_PATH_PREFIX.length());
}
String[] paths = res.split(getBuildPathDelimeter());
List<String> filtered = new ArrayList<>();
for (int i = 0; i < paths.length; ++i) {
if (!paths[i].equals(".")) { //$NON-NLS-1$
filtered.add(paths[i].trim());
}
}
return filtered.toArray(new String[filtered.size()]);
}
/**
* Then multiple lines of output are provided, we parse only paths started
* from "DLTK:" sequence.
*
* @param result
* @return
*/
protected String[] parsePaths(String[] result) {
List<String> filtered = new ArrayList<>();
for (int k = 0; k < result.length; ++k) {
String res = result[k];
if (res.startsWith(DLTK_PATH_PREFIX)) {
res = res.substring(DLTK_PATH_PREFIX.length());
String[] paths = parsePaths(res);
for (int i = 0; i < paths.length; ++i) {
if (!paths[i].equals(".")) { //$NON-NLS-1$
filtered.add(paths[i].trim());
}
}
}
}
return filtered.toArray(new String[filtered.size()]);
}
/**
* Please override the following method instead
* <b>validateInstallLocation</b>(iFileHandle, EnvironmentVariable[],
* LibraryLocation[])
*
* @param installLocation
* @return
*/
@Deprecated
public IStatus validateInstallLocation(IFileHandle installLocation) {
if (!installLocation.exists() || !installLocation.isFile()) {
return createStatus(IStatus.ERROR,
InterpreterMessages.errNonExistentOrInvalidInstallLocation,
null);
}
return validatePossiblyName(installLocation);
}
/**
* @since 2.0
*/
@Override
public IStatus validateInstallLocation(IFileHandle installLocation,
EnvironmentVariable[] variables, LibraryLocation[] libraryLocations,
IProgressMonitor monitor) {
return validateInstallLocation(installLocation);
}
/**
* @since 3.0
*/
@Override
public IFileHandle[] detectInstallLocations() {
return null;
}
/**
* @since 3.0
*/
@Override
public String generateDetectedInterpreterName(IFileHandle install) {
String name = install.getName();
name = name.trim();
if (name.length() == 0) {
name = this.getName();
}
return name;
}
@Override
public IStatus validatePossiblyName(IFileHandle installLocation) {
String possibleNames[] = getPossibleInterpreterNames();
boolean matchFound = false;
final String name = installLocation.getName();
IPath nPath = new Path(name);
IExecutionEnvironment execEnv = installLocation.getEnvironment()
.getAdapter(IExecutionEnvironment.class);
if (execEnv != null) {
for (int i = 0; i < possibleNames.length; ++i) {
final String possibleName = possibleNames[i].toLowerCase();
if (execEnv.isValidExecutableAndEquals(possibleName, nPath)) {
matchFound = true;
break;
}
}
}
if (matchFound) {
return createStatus(IStatus.OK, "", null); //$NON-NLS-1$
} else {
return createStatus(IStatus.ERROR,
InterpreterMessages.errNoInterpreterExecutablesFound, null);
}
}
protected String retrivePaths(IExecutionEnvironment exeEnv,
final IFileHandle installLocation,
final List<LibraryLocation> locations, IProgressMonitor monitor,
IFileHandle locator, EnvironmentVariable[] variables) {
Process process = null;
try {
if (monitor != null) {
// monitor.beginTask(InterpreterMessages.statusFetchingLibs, 1);
if (monitor.isCanceled()) {
return null;
}
}
String[] cmdLine;
String[] env = extractEnvironment(exeEnv, variables);
cmdLine = buildCommandLine(installLocation, locator);
try {
if (DLTKLaunchingPlugin.TRACE_EXECUTION) {
traceExecution(
LaunchingMessages.AbstractInterpreterInstallType_libraryDiscoveryScript,
cmdLine, env);
}
process = exeEnv.exec(cmdLine, null, env);
if (process != null) {
String result[] = readPathsFromProcess(monitor, process);
if (result == null) {
throw new IOException(
LaunchingMessages.AbstractInterpreterInstallType_nullResultFromProcess);
}
if (DLTKLaunchingPlugin.TRACE_EXECUTION) {
traceDiscoveryOutput(result);
}
String[] paths = null;
if (result.length == 1) {
paths = parsePaths(result[0]);
} else {
paths = parsePaths(result);
}
IPath path = new Path(locator.getCanonicalPath())
.removeLastSegments(1);
fillLocationsExceptOne(exeEnv.getEnvironment(), locations,
paths, path);
StringBuffer resultBuffer = new StringBuffer();
for (int i = 0; i < result.length; i++) {
resultBuffer.append(result[i]).append("\n"); //$NON-NLS-1$
}
return resultBuffer.toString();
}
} catch (CoreException e) {
DLTKLaunchingPlugin.log(e);
if (DLTKCore.DEBUG) {
e.printStackTrace();
}
}
} catch (IOException e) {
if (DLTKCore.VERBOSE) {
getLog().log(createStatus(IStatus.ERROR,
LaunchingMessages.AbstractInterpreterInstallType_unableToLookupLibraryPaths,
e));
}
} finally {
if (process != null) {
process.destroy();
}
if (monitor != null) {
monitor.done();
}
}
return null;
}
private void traceDiscoveryOutput(String[] result) {
StringBuffer sb = new StringBuffer();
sb.append("-----------------------------------------------\n"); //$NON-NLS-1$
sb.append("Discovery script output:").append('\n'); //$NON-NLS-1$
sb.append("Output Result:"); //$NON-NLS-1$
if (result != null) {
for (int i = 0; i < result.length; i++) {
sb.append(" " + result[i]); //$NON-NLS-1$
}
} else {
sb.append("Null"); //$NON-NLS-1$
}
sb.append("\n-----------------------------------------------\n"); //$NON-NLS-1$
System.out.println(sb);
}
private void traceExecution(String processLabel, String[] cmdLineLabel,
String[] environment) {
StringBuffer sb = new StringBuffer();
sb.append("-----------------------------------------------\n"); //$NON-NLS-1$
sb.append("Running ").append(processLabel).append('\n'); //$NON-NLS-1$
// sb.append("Command line: ").append(cmdLineLabel).append('\n');
sb.append("Command line: "); //$NON-NLS-1$
for (int i = 0; i < cmdLineLabel.length; i++) {
sb.append(" " + cmdLineLabel[i]); //$NON-NLS-1$
}
sb.append("\n"); //$NON-NLS-1$
sb.append("Environment:\n"); //$NON-NLS-1$
if (environment != null) {
for (int i = 0; i < environment.length; i++) {
sb.append('\t').append(environment[i]).append('\n');
}
}
sb.append("-----------------------------------------------\n"); //$NON-NLS-1$
System.out.println(sb);
}
protected ILookupRunnable createLookupRunnable(
final IFileHandle installLocation,
final List<LibraryLocation> locations,
final EnvironmentVariable[] variables) {
return monitor -> {
try {
IEnvironment env = installLocation.getEnvironment();
IExecutionEnvironment exeEnv = env
.getAdapter(IExecutionEnvironment.class);
if (exeEnv == null)
return;
IDeployment deployment = exeEnv.createDeployment();
// handle case where rse is missing required plugins
if (deployment == null) {
DLTKLaunchingPlugin.logWarning(
LaunchingMessages.AbstractInterpreterInstallType_failedToDeployLibraryLocationsScript);
return;
}
try {
IPath deploymentPath = createPathFile(deployment);
IFileHandle locator = deployment.getFile(deploymentPath);
String result = retrivePaths(exeEnv, installLocation,
locations, monitor, locator, variables);
String message = NLS.bind(
LaunchingMessages.AbstractInterpreterInstallType_failedToResolveLibraryLocationsForWith,
installLocation.getName(), locator.toOSString());
if (locations.size() == 0) {
if (result == null) {
DLTKLaunchingPlugin.log(message);
} else {
DLTKLaunchingPlugin.logWarning(message,
new Exception(NLS.bind(
LaunchingMessages.AbstractInterpreterInstallType_output,
result)));
}
}
} finally {
// if (deployment != null) {
deployment.dispose();
// }
}
} catch (IOException e) {
DLTKLaunchingPlugin.log(
LaunchingMessages.AbstractInterpreterInstallType_problemWhileResolvingInterpreterLibraries,
e);
if (DLTKCore.DEBUG) {
e.printStackTrace();
}
}
};
}
@Override
public synchronized LibraryLocation[] getDefaultLibraryLocations(
final IFileHandle installLocation) {
return getDefaultLibraryLocations(installLocation, null);
}
@Override
public synchronized LibraryLocation[] getDefaultLibraryLocations(
final IFileHandle installLocation,
EnvironmentVariable[] variables) {
return getDefaultLibraryLocations(installLocation, variables, null);
}
@Override
public synchronized LibraryLocation[] getDefaultLibraryLocations(
final IFileHandle installLocation, EnvironmentVariable[] variables,
IProgressMonitor monitor) {
if (monitor != null) {
monitor.beginTask(NLS.bind(
LaunchingMessages.AbstractInterpreterInstallType_resolvingLibraryPaths,
this.getName()), 100);
}
Object cacheKey = makeKey(installLocation, variables);
if (fCachedLocations.containsKey(cacheKey)) {
return fCachedLocations.get(cacheKey);
}
final ArrayList<LibraryLocation> locations = new ArrayList<>();
final ILookupRunnable runnable = createLookupRunnable(installLocation,
locations, variables);
try {
runLibraryLookup(runnable,
monitor != null ? new SubProgressMonitor(monitor, 95)
: null);
} catch (InvocationTargetException e) {
getLog().log(createStatus(IStatus.ERROR,
LaunchingMessages.AbstractInterpreterInstallType_errorResolvingDefaultLibraries,
e));
} catch (InterruptedException e) {
getLog().log(createStatus(IStatus.ERROR,
LaunchingMessages.AbstractInterpreterInstallType_errorResolvingDefaultLibraries,
e));
}
LibraryLocation[] libs = correctLocations(locations,
monitor != null ? new SubProgressMonitor(monitor, 5) : null);
if (libs.length != 0) {
fCachedLocations.put(cacheKey, libs);
}
if (monitor != null) {
monitor.done();
}
return libs;
}
public static Object makeKey(IFileHandle installLocation,
EnvironmentVariable[] variables) {
String key = installLocation.getFullPath().toString();
if (variables != null) {
for (int i = 0; i < variables.length; i++) {
key += "|" + variables[i].getName() + ":" //$NON-NLS-1$ //$NON-NLS-2$
+ variables[i].getValue();
}
}
return key;
}
protected IStatus createStatus(int severity, String message,
Throwable throwable) {
return new Status(severity, getPluginId(), 0, message, throwable);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((fId == null) ? 0 : fId.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
AbstractInterpreterInstallType other = (AbstractInterpreterInstallType) obj;
if (fId == null) {
if (other.fId != null)
return false;
} else if (!fId.equals(other.fId))
return false;
return true;
}
}