blob: b54d1049fc198adcd247080538fc4679c8ab2977 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2008, 2019 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.net.InetAddress;
import java.net.MalformedURLException;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.core.model.IProcess;
import org.eclipse.debug.core.model.LaunchConfigurationDelegate;
import org.eclipse.debug.ui.IDebugUIConstants;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.statushandlers.StatusManager;
import org.eclipse.statet.jcommons.rmi.RMIAddress;
import org.eclipse.statet.jcommons.rmi.RMIRegistry;
import org.eclipse.statet.jcommons.rmi.RMIRegistryManager;
import org.eclipse.statet.jcommons.status.ProgressMonitor;
import org.eclipse.statet.jcommons.status.StatusException;
import org.eclipse.statet.jcommons.status.WarningStatus;
import org.eclipse.statet.jcommons.ts.core.SystemRunnable;
import org.eclipse.statet.jcommons.ts.core.Tool;
import org.eclipse.statet.jcommons.ts.core.ToolService;
import org.eclipse.statet.ecommons.debug.core.util.LaunchUtils;
import org.eclipse.statet.ecommons.debug.ui.util.UnterminatedLaunchAlerter;
import org.eclipse.statet.ecommons.runtime.core.util.StatusUtils;
import org.eclipse.statet.ecommons.ui.util.UIAccess;
import org.eclipse.statet.internal.r.console.ui.Messages;
import org.eclipse.statet.internal.r.console.ui.RConsoleUIPlugin;
import org.eclipse.statet.nico.core.runtime.ILogOutput;
import org.eclipse.statet.nico.core.runtime.ToolRunner;
import org.eclipse.statet.nico.core.util.HistoryTrackingConfiguration;
import org.eclipse.statet.nico.core.util.TrackingConfiguration;
import org.eclipse.statet.nico.ui.NicoUITools;
import org.eclipse.statet.nico.ui.console.NIConsoleColorAdapter;
import org.eclipse.statet.nico.ui.util.WorkbenchStatusHandler;
import org.eclipse.statet.r.console.core.IRDataAdapter;
import org.eclipse.statet.r.console.core.RProcess;
import org.eclipse.statet.r.console.core.RWorkspace;
import org.eclipse.statet.r.console.ui.RConsole;
import org.eclipse.statet.r.console.ui.launching.RConsoleLaunching;
import org.eclipse.statet.r.console.ui.tools.REnvAutoUpdater;
import org.eclipse.statet.r.console.ui.tools.REnvIndexAutoUpdater;
import org.eclipse.statet.r.core.RCore;
import org.eclipse.statet.r.core.pkgmanager.IRPkgManager;
import org.eclipse.statet.r.core.renv.IREnvConfiguration;
import org.eclipse.statet.r.launching.core.ILaunchDelegateAddon;
import org.eclipse.statet.r.launching.core.RLaunching;
import org.eclipse.statet.r.nico.RWorkspaceConfig;
import org.eclipse.statet.r.nico.impl.RjsController;
import org.eclipse.statet.r.nico.impl.RjsController.RjsConnection;
import org.eclipse.statet.rj.data.RDataUtils;
import org.eclipse.statet.rj.data.UnexpectedRDataException;
import org.eclipse.statet.rj.renv.core.REnvConfiguration;
import org.eclipse.statet.rj.server.RjsComConfig;
import org.eclipse.statet.rj.services.FunctionCall;
import org.eclipse.statet.rj.services.RVersion;
/**
* Launch delegate for RJ based R console using local RJ server
*/
public class RConsoleRJLaunchDelegate extends LaunchConfigurationDelegate {
static final long TIMEOUT= 60L * 1000000000L;
static final RVersion VERSION_2_12_0= new RVersion(2, 12, 0);
static class ConfigRunnable implements SystemRunnable {
private final Tool tool;
private final boolean enableRHelp;
private final boolean enableRGraphics;
private final boolean enableRDbgExt;
private final boolean enableRDbg;
public ConfigRunnable(final Tool tool, final boolean enableRHelp,
final boolean enableRGraphics,
final boolean enableRDbgExt, final boolean enableRDbg) {
this.tool= tool;
this.enableRHelp= enableRHelp;
this.enableRGraphics= enableRGraphics;
this.enableRDbgExt= enableRDbgExt;
this.enableRDbg= enableRDbg;
}
@Override
public String getTypeId() {
return "r/integration"; //$NON-NLS-1$
}
@Override
public String getLabel() {
return "Initialize R-StatET Tools";
}
@Override
public boolean canRunIn(final Tool tool) {
return (tool == this.tool);
}
@Override
public boolean changed(final int event, final Tool process) {
if ((event & MASK_EVENT_GROUP) == REMOVING_EVENT_GROUP) {
return false;
}
return true;
}
@Override
public void run(final ToolService service, final ProgressMonitor m) throws StatusException {
final IRDataAdapter r= (IRDataAdapter) service;
r.briefAboutToChange();
try {
final RVersion rVersion= r.getPlatform().getRVersion();
if (rVersion.compareTo(VERSION_2_12_0) < 0) {
r.evalVoid("library('rj')", m); //$NON-NLS-1$
}
else {
r.evalVoid("library('rj', quietly= TRUE)", m); //$NON-NLS-1$
}
if (this.enableRHelp) {
final FunctionCall fcall= r.createFunctionCall(".statet.initHelp"); //$NON-NLS-1$
fcall.evalVoid(m);
}
if (this.enableRGraphics) {
try {
final FunctionCall fcall= r.createFunctionCall(".rj.initGD"); //$NON-NLS-1$
fcall.addLogi("default", true); //$NON-NLS-1$
RDataUtils.checkSingleLogiValue(fcall.evalData(m));
}
catch (final StatusException | UnexpectedRDataException e) {
r.handleStatus(new WarningStatus(RConsoleUIPlugin.BUNDLE_ID,
"The graphic device for the R Graphic view cannot be initialized.", e),
m );
}
}
{ final FunctionCall fcall= r.createFunctionCall(".statet.initDebug"); //$NON-NLS-1$
fcall.addLogi("mode", this.enableRDbg); //$NON-NLS-1$
fcall.addLogi("ext", this.enableRDbgExt); //$NON-NLS-1$
fcall.evalVoid(m);
}
}
finally {
r.briefChanged(RWorkspace.REFRESH_COMPLETE);
}
}
}
static RWorkspaceConfig createWorkspaceConfig(final ILaunchConfiguration configuration) throws CoreException {
final RWorkspaceConfig config= new RWorkspaceConfig();
config.setEnableObjectDB(configuration.getAttribute(RConsoleLaunching.ATTR_OBJECTDB_ENABLED, true));
config.setEnableAutoRefresh(configuration.getAttribute(RConsoleLaunching.ATTR_OBJECTDB_AUTOREFRESH_ENABLED, true));
return config;
}
static void initConsoleOptions(final RjsController controller,
final REnvConfiguration rEnvConfig,
final ILaunchConfiguration configuration, final String mode,
final boolean isStartup) throws CoreException {
if (rEnvConfig != null) {
final IRPkgManager manager= RCore.getRPkgManager(rEnvConfig.getREnv());
REnvAutoUpdater.connect(controller, manager);
REnvIndexAutoUpdater.connect(controller.getTool(), manager);
}
controller.addStartupRunnable(new ConfigRunnable(
controller.getTool(),
(rEnvConfig != null && configuration.getAttribute(RConsoleOptionsTab.ATTR_INTEGRATION_RHELP_ENABLED, true)),
configuration.getAttribute(RConsoleOptionsTab.ATTR_INTEGRATION_RGRAPHICS_ASDEFAULT, true),
configuration.getAttribute(RConsoleOptionsTab.ATTR_INTEGRATION_RDBGEXT_ENABLED, true),
mode.equals(ILaunchManager.DEBUG_MODE) ));
if (isStartup) {
RConsoleLaunching.scheduleStartupSnippet(controller, configuration);
}
}
private ILaunchDelegateAddon addon;
public RConsoleRJLaunchDelegate() {
}
public RConsoleRJLaunchDelegate(final ILaunchDelegateAddon addon) {
this.addon= addon;
}
@Override
public void launch(final ILaunchConfiguration configuration, final String mode,
final ILaunch launch, final IProgressMonitor monitor) throws CoreException {
final SubMonitor m= LaunchUtils.initProgressMonitor(configuration, monitor, 25);
final long timestamp= System.currentTimeMillis();
final IWorkbenchPage page= UIAccess.getActiveWorkbenchPage(false);
m.worked(1);
if (m.isCanceled()) {
return;
}
// load tracking configurations
final List<TrackingConfiguration> trackingConfigs;
{ final List<String> trackingIds= configuration.getAttribute(RConsoleOptionsTab.TRACKING_ENABLED_IDS, Collections.EMPTY_LIST);
trackingConfigs= new ArrayList<>(trackingIds.size());
for (final String id : trackingIds) {
final TrackingConfiguration trackingConfig;
if (id.equals(HistoryTrackingConfiguration.HISTORY_TRACKING_ID)) {
trackingConfig= new HistoryTrackingConfiguration(id);
}
else {
trackingConfig= new TrackingConfiguration(id);
}
RConsoleOptionsTab.TRACKING_UTIL.load(trackingConfig, configuration);
trackingConfigs.add(trackingConfig);
}
}
m.worked(1);
if (m.isCanceled()) {
return;
}
// r env
final IREnvConfiguration rEnv= RLaunching.getREnvConfig(configuration, true);
final RMIRegistry registry;
boolean requireCodebase;
{ final String s= System.getProperty("org.eclipse.statet.r.console.rmiRegistryPort");
int port= -1;
if (s != null && s.length() > 0) {
try {
m.subTask(Messages.LaunchDelegate_CheckingRegistry_subtask);
port= Integer.parseInt(s);
final RMIAddress registryAddress= new RMIAddress(InetAddress.getLoopbackAddress(), port, null);
registry= new RMIRegistry(registryAddress, true);
requireCodebase= true;
}
catch (final NumberFormatException e) {
throw new CoreException(new Status(IStatus.ERROR, RConsoleUIPlugin.BUNDLE_ID, 0,
"The registry port specified by 'org.eclipse.statet.r.console.rmiRegistryPort' is invalid.", e ));
}
catch (final MalformedURLException e) {
throw new CoreException(new Status(IStatus.ERROR, RConsoleUIPlugin.BUNDLE_ID, 0,
"The registry port specified by 'org.eclipse.statet.r.console.rmiRegistryPort' is invalid.", e ));
}
catch (final RemoteException e) {
throw new CoreException(new Status(IStatus.ERROR, RConsoleUIPlugin.BUNDLE_ID, 0,
"Connection setup to the registry specified by 'org.eclipse.statet.r.console.rmiRegistryPort' failed.", e ));
}
}
else {
try {
registry= RMIRegistryManager.INSTANCE.getEmbeddedPrivateRegistry(
StatusUtils.convertChild(m.newChild(1)) );
}
catch (final StatusException e) {
throw StatusUtils.convert(e);
}
requireCodebase= false;
}
}
m.worked(1);
if (m.isCanceled()) {
return;
}
// start server
m.subTask(Messages.LaunchDelegate_StartREngine_subtask);
try {
final RMIAddress rmiAddress= new RMIAddress(InetAddress.getLoopbackAddress(), registry.getAddress().getPort(),
"rjs-local-" + System.currentTimeMillis() ); //$NON-NLS-1$
final RJEngineLaunchDelegate engineLaunchDelegate= new RJEngineLaunchDelegate(
rmiAddress.getAddress(), requireCodebase, rEnv);
RjsComConfig.setRMIClientSocketFactory(null);
engineLaunchDelegate.launch(configuration, mode, launch, m.newChild(10));
final IProcess[] processes= launch.getProcesses();
if (processes.length == 0) {
return;
}
m.worked(1);
if (m.isCanceled()) {
return;
}
// arguments
final String[] rArgs= LaunchUtils.getProcessArguments(configuration, RConsoleLaunching.ATTR_OPTIONS);
m.worked(1);
if (m.isCanceled()) {
return;
}
// create process
UnterminatedLaunchAlerter.registerLaunchType(RConsoleLaunching.R_CONSOLE_CONFIGURATION_TYPE_ID);
final RProcess process= new RProcess(launch, rEnv,
LaunchUtils.createLaunchPrefix(configuration),
rEnv.getName() + " / RJ " + LaunchUtils.createProcessTimestamp(timestamp), //$NON-NLS-1$
rmiAddress.toString(),
null, // wd is set at rjs startup
timestamp );
process.setAttribute(IProcess.ATTR_CMDLINE, rmiAddress.toString() + '\n' + Arrays.toString(rArgs));
// Wait until the engine is started or died
m.subTask(Messages.LaunchDelegate_WaitForR_subtask);
final long t= System.nanoTime();
WAIT: for (int i= 0; true; i++) {
if (processes[0].isTerminated()) {
final boolean silent= configuration.getAttribute(IDebugUIConstants.ATTR_CAPTURE_IN_CONSOLE, true);
final IStatus logStatus= ToolRunner.createOutputLogStatus(
processes[0].getAdapter(ILogOutput.class) );
// move to R server?
final StringBuilder sb= new StringBuilder();
sb.append("Launching the R Console was cancelled, because it seems starting the R engine failed.");
sb.append(engineLaunchDelegate.getDebugInfo());
StatusManager.getManager().handle(new Status(silent ? IStatus.INFO : IStatus.ERROR,
RConsoleUIPlugin.BUNDLE_ID, sb.toString(),
(logStatus != null) ? new CoreException(logStatus) : null ),
silent ? (StatusManager.LOG) : (StatusManager.LOG | StatusManager.SHOW) );
return;
}
if (m.isCanceled()) {
processes[0].terminate();
throw new CoreException(Status.CANCEL_STATUS);
}
try {
final String[] list= registry.getRegistry().list();
for (final String entry : list) {
if (entry.equals(rmiAddress.getName())) {
break WAIT;
}
}
if (i > 1 && System.nanoTime() - t > TIMEOUT) {
break WAIT;
}
}
catch (final RemoteException e) {
if (i > 0 && System.nanoTime() - t > TIMEOUT / 3) {
break WAIT;
}
}
try {
Thread.sleep(333);
}
catch (final InterruptedException e) {
// continue, monitor and process is checked
}
}
m.worked(5);
final RjsConnection connection= RjsController.lookup(registry.getRegistry(), null, rmiAddress);
final HashMap<String, Object> rjsProperties= new HashMap<>();
rjsProperties.put(RjsComConfig.RJ_DATA_STRUCTS_LISTS_MAX_LENGTH_PROPERTY_ID,
configuration.getAttribute(RConsoleLaunching.ATTR_OBJECTDB_LISTS_MAX_LENGTH, 10000));
rjsProperties.put(RjsComConfig.RJ_DATA_STRUCTS_ENVS_MAX_LENGTH_PROPERTY_ID,
configuration.getAttribute(RConsoleLaunching.ATTR_OBJECTDB_ENVS_MAX_LENGTH, 10000));
rjsProperties.put("rj.session.startup.time", timestamp); //$NON-NLS-1$
final RjsController controller= new RjsController(process, rmiAddress, connection, null,
(RjsController.RJS_LOCAL | RjsController.RJS_SETUP_CONSOLE), rArgs, rjsProperties,
engineLaunchDelegate.getWorkingDirectory(),
createWorkspaceConfig(configuration), trackingConfigs);
process.init(controller);
RConsoleLaunching.registerDefaultHandlerTo(controller);
m.worked(5);
initConsoleOptions(controller, rEnv, configuration, mode, true);
if (this.addon != null) {
this.addon.init(configuration, mode, controller, m);
}
final RConsole console= new RConsole(process, new NIConsoleColorAdapter());
NicoUITools.startConsoleLazy(console, page,
configuration.getAttribute(RConsoleLaunching.ATTR_PIN_CONSOLE, false));
new ToolRunner().runInBackgroundThread(process, new WorkbenchStatusHandler());
}
finally {
RjsComConfig.clearRMIClientSocketFactory();
}
m.done();
}
}