blob: 93094fc5e019606423811deef5a1598b840bc610 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2008, 2021 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.internal.r.console.ui.launching;
import java.io.File;
import java.net.InetAddress;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.filesystem.URIUtil;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.launching.IVMInstall;
import org.eclipse.jdt.launching.IVMInstall3;
import org.eclipse.jdt.launching.JavaLaunchDelegate;
import org.eclipse.jdt.launching.JavaRuntime;
import org.eclipse.statet.jcommons.collections.CollectionUtils;
import org.eclipse.statet.jcommons.collections.ImCollections;
import org.eclipse.statet.jcommons.collections.ImList;
import org.eclipse.statet.jcommons.lang.ObjectUtils;
import org.eclipse.statet.jcommons.runtime.bundle.BundleEntry;
import org.eclipse.statet.jcommons.runtime.bundle.BundleSpec;
import org.eclipse.statet.jcommons.status.StatusException;
import org.eclipse.statet.jcommons.status.eplatform.EStatusUtils;
import org.eclipse.statet.ecommons.debug.core.util.LaunchUtils;
import org.eclipse.statet.internal.r.console.ui.RConsoleUIPlugin;
import org.eclipse.statet.nico.core.runtime.ToolRunner;
import org.eclipse.statet.r.core.renv.IREnvConfiguration;
import org.eclipse.statet.r.launching.ui.REnvTab;
import org.eclipse.statet.rj.RjException;
import org.eclipse.statet.rj.RjInvalidConfigurationException;
import org.eclipse.statet.rj.renv.core.RLibGroup;
import org.eclipse.statet.rj.renv.core.RLibLocation;
import org.eclipse.statet.rj.server.util.LocalREnv;
import org.eclipse.statet.rj.server.util.RJContext;
import org.eclipse.statet.rj.server.util.ServerUtils;
/**
* Launches RJ Server using JDT java launch mechanism
*/
public class RJEngineLaunchDelegate extends JavaLaunchDelegate {
private static final ImList<BundleSpec> CLASSPATH_LIB_SPECS= ImCollections.addElement(
ServerUtils.MIN_CLASSPATH_SPECS,
new BundleSpec("org.eclipse.swt") );
private static final ImList<BundleSpec> CODEBASE_LIB_SPECS=
ServerUtils.MIN_RMI_CODEBASE_SPECS;
private static final String PATH_SPLITTER= Pattern.compile(File.pathSeparator, Pattern.LITERAL).pattern();
private final String address;
private final IREnvConfiguration rEnvConfig;
private final List<BundleSpec> codebaseLibs;
private final RJContext serverContext;
private final LocalREnv serverREnv;
private final Path rjPkgPath;
private File workingDirectory;
private IProgressMonitor monitor;
private String libPreloadVar;
private String libPreloadFile;
public RJEngineLaunchDelegate(final String address, final boolean requireCodebase,
final IREnvConfiguration rEnvConfig) throws CoreException {
this.address= address;
this.rEnvConfig= rEnvConfig;
this.codebaseLibs= (requireCodebase) ? CODEBASE_LIB_SPECS : null;
this.serverContext= new RJContext();
try {
final List<Path> rLibPaths= new ArrayList<>();
for (final RLibGroup group : this.rEnvConfig.getRLibGroups()) {
for (final RLibLocation library : group.getLibLocations()) {
final Path path= library.getDirectoryPath();
if (path != null) {
rLibPaths.add(path);
}
}
}
this.serverREnv= new LocalREnv(Paths.get(this.rEnvConfig.getRHomeDirectory()), rLibPaths);
this.rjPkgPath= this.serverREnv.searchRPkg("rj");
if (this.rjPkgPath == null) {
throw new CoreException(new Status(IStatus.ERROR, RConsoleUIPlugin.BUNDLE_ID,
"Could not find the R package 'rj' in the R library path:" +
CollectionUtils.toString(rLibPaths, "\n\t") ));
}
}
catch (final RjInvalidConfigurationException e) {
throw new CoreException(new Status(IStatus.ERROR, RConsoleUIPlugin.BUNDLE_ID,
"An error occurred when initializing the R library paths.",
e ));
}
setLibPreload(true);
}
@Override
public void launch(final ILaunchConfiguration configuration, String mode,
final ILaunch launch, final IProgressMonitor monitor) throws CoreException {
this.monitor= (monitor != null) ? monitor : new NullProgressMonitor();
if (mode.equals(ILaunchManager.DEBUG_MODE)) {
// TODO add configuration option (-> source lookup support for Java)
mode= ILaunchManager.RUN_MODE;
}
super.launch(configuration, mode, launch, this.monitor);
}
@Override
public IPath getWorkingDirectoryPath(final ILaunchConfiguration configuration) throws CoreException {
final IFileStore workingDirectory= REnvTab.getWorkingDirectory(configuration);
return URIUtil.toPath(workingDirectory.toURI());
}
@Override
public File verifyWorkingDirectory(final ILaunchConfiguration configuration) throws CoreException {
return this.workingDirectory= super.verifyWorkingDirectory(configuration);
}
public IFileStore getWorkingDirectory() {
if (this.workingDirectory != null) {
return EFS.getLocalFileSystem().fromLocalFile(this.workingDirectory);
}
return null;
}
public void setLibPreload(final boolean enable) {
if (enable) {
if (Platform.getOS().equals(Platform.OS_WIN32)) {
this.libPreloadVar= null;
this.libPreloadFile= null;
}
else if (Platform.getOS().equals(Platform.OS_MACOSX)) {
this.libPreloadVar= null;
// fLibPreloadFile= "DYLD_INSERT_LIBRARIES"; //$NON-NLS-1$
this.libPreloadFile= "libjsig.dylib"; //$NON-NLS-1$
}
else { // *nix
this.libPreloadVar= "LD_PRELOAD"; //$NON-NLS-1$
this.libPreloadFile= "libjsig.so"; //$NON-NLS-1$
}
}
else {
this.libPreloadVar= null;
this.libPreloadFile= null;
}
}
@Override
public String[] getEnvironment(final ILaunchConfiguration configuration) throws CoreException {
final IVMInstall vmInstall= getVMInstall(configuration); // already verified
final Map<String, String> additional= new HashMap<>();
final File location= vmInstall.getInstallLocation();
if (location != null) {
additional.put("JAVA_HOME", location.getAbsolutePath()); //$NON-NLS-1$
}
@SuppressWarnings("unchecked")
final Map<String, String> envp= LaunchUtils.createEnvironment(configuration,
new Map[] { additional, this.rEnvConfig.getEnvironmentsVariables() });
if (this.libPreloadVar != null) {
String value= envp.get(this.libPreloadVar);
if (value == null || !value.contains("libjsig")) { //$NON-NLS-1$
final String path= ((IVMInstall3) vmInstall).evaluateSystemProperties(
new String[] { "java.library.path" }, this.monitor).get("java.library.path"); //$NON-NLS-1$ //$NON-NLS-2$
if (path != null) {
final String[] pathList= path.split(PATH_SPLITTER);
for (int i= 0; i < pathList.length; i++) {
final File file= new File(pathList[i], this.libPreloadFile);
if (file.exists()) {
final String s= file.getAbsolutePath();
if (s.indexOf(' ') < 0) { // whitespace is separator char
if (value != null && value.length() > 0) {
value= s + ' ' + value;
}
else {
value= s;
}
envp.put(this.libPreloadVar, value);
}
break;
}
}
}
}
}
return LaunchUtils.toKeyValueStrings(envp);
}
@Override
public String[][] getClasspathAndModulepath(final ILaunchConfiguration configuration) throws CoreException {
final String[][] paths= new String[2][];
paths[0]= getClasspath(configuration);
paths[1]= new String[0];
return paths;
}
@Override
public String[] getClasspath(final ILaunchConfiguration configuration) throws CoreException {
final LinkedHashSet<String> classpath= new LinkedHashSet<>();
classpath.add(this.rjPkgPath.resolve(Paths.get("server/rj-boot.jar")).toString());
classpath.addAll(Arrays.asList(super.getClasspath(configuration)));
return classpath.toArray(new String[classpath.size()]);
}
@Override
public String getVMArguments(final ILaunchConfiguration configuration) throws CoreException {
try {
final String args= super.getVMArguments(configuration);
final StringBuilder s= new StringBuilder(" "); //$NON-NLS-1$
if (args != null) {
s.append(args);
}
if (s.indexOf(" -D" + RJContext.RJ_SERVER_CLASS_PATH_PROPERTY_KEY + "=") < 0) { //$NON-NLS-1$ //$NON-NLS-2$
s.append(" -D" + RJContext.RJ_SERVER_CLASS_PATH_PROPERTY_KEY + "=\""); //$NON-NLS-1$ //$NON-NLS-2$
final List<BundleEntry> rjLibs= this.serverContext.resolveBundles(CLASSPATH_LIB_SPECS);
s.append(ServerUtils.concatRJClassPath(rjLibs));
s.append("\""); //$NON-NLS-1$
}
if (s.indexOf(" -Djava.security.policy=") < 0) { //$NON-NLS-1$
s.append(" -Djava.security.policy="); //$NON-NLS-1$
s.append('"');
s.append(this.serverContext.getServerPolicyFilePath());
s.append('"');
}
// RMI
if (s.indexOf(" -Djava.rmi.server.hostname=") < 0) { //$NON-NLS-1$
s.append(" -Djava.rmi.server.hostname="); //$NON-NLS-1$
s.append(InetAddress.getLoopbackAddress().getHostAddress());
}
if (s.indexOf(" -Dorg.eclipse.statet.rj.rmi.disableSocketFactory=") < 0) {
s.append(" -Dorg.eclipse.statet.rj.rmi.disableSocketFactory=true");
}
if (this.codebaseLibs != null && s.indexOf(" -Djava.rmi.server.codebase=") < 0) { //$NON-NLS-1$
s.append(" -Djava.rmi.server.codebase=\""); //$NON-NLS-1$
final List<BundleEntry> rjLibs= this.serverContext.resolveBundles(this.codebaseLibs);
s.append(ServerUtils.concatCodebase(rjLibs));
s.append("\""); //$NON-NLS-1$
}
if (!ToolRunner.captureLogOnly(configuration)
&& s.indexOf(" -Dorg.eclipse.statet.rj.verbose=") < 0 ) { //$NON-NLS-1$
s.append(" -Dorg.eclipse.statet.rj.verbose=true"); //$NON-NLS-1$
}
if (Platform.getOS().equals(Platform.OS_MACOSX)
&& JavaRuntime.compareJavaVersions(JavaRuntime.computeVMInstall(configuration),
JavaCore.VERSION_1_8) == 0
&& s.indexOf(" -d32") < 0 && s.indexOf(" -d64") < 0) { //$NON-NLS-1$ //$NON-NLS-2$
final String rArch= this.rEnvConfig.getRArch();
if (rArch != null) {
if (rArch.equals("i386") || rArch.equals("i586") || rArch.equals("i686")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
s.append(" -d32"); //$NON-NLS-1$
}
else if (rArch.equals("x86_64")) { //$NON-NLS-1$
s.append(" -d64"); //$NON-NLS-1$
}
}
}
return s.substring(1);
}
catch (final StatusException e) {
throw new CoreException(new Status(IStatus.ERROR, RConsoleUIPlugin.BUNDLE_ID,
"An error occurred when configuring start of Java based R server.",
EStatusUtils.convert(e) ));
}
catch (final RjException e) {
throw new CoreException(new Status(IStatus.ERROR, RConsoleUIPlugin.BUNDLE_ID,
"An error occurred when configuring start of Java based R server.",
e ));
}
}
@Override
public String getMainTypeName(final ILaunchConfiguration configuration) throws CoreException {
return "RJSrv"; //$NON-NLS-1$
}
@Override
public String getProgramArguments(final ILaunchConfiguration configuration) throws CoreException {
final StringBuilder args= new StringBuilder("start"); //$NON-NLS-1$
args.append(' ');
args.append(this.address);
args.append(" -auth=none"); //$NON-NLS-1$
args.append(" -plugins="); //$NON-NLS-1$
args.append("awt,"); //$NON-NLS-1$
if (Platform.getOS().equals(Platform.OS_WIN32)) {
args.append("swt,"); //$NON-NLS-1$
}
return args.toString();
}
@Override
protected void prepareStopInMain(final ILaunchConfiguration configuration) throws CoreException {
}
public String getDebugInfo() {
final ObjectUtils.ToStringBuilder sb= new ObjectUtils.ToStringBuilder();
sb.addProp("R environment name", this.rEnvConfig.getName());
sb.addProp("R package 'rj' path", this.rjPkgPath.toString());
return sb.toString();
}
}