Hi!
I'm not quite sure the protocol on this, but here's my problem: We are
building executables that do not run native on the box hosting eclipse.
You have to use a simulator to run the executable that was built with
Eclipse/CDT.
At a practical level, that means I need to get ahold of the command
that's being sent to the shell before it's sent to the shell. At an even
baser level it means that the following code in
LocalCLauchConfigurationDelegate needs to have a hook:
IPath projectPath = verifyProgramFile(config);
String arguments[] = getProgramArgumentsArray(config);
ArrayList command = new ArrayList(1 + arguments.length);
command.add(projectPath.toOSString());
command.addAll(Arrays.asList(arguments));
At first I just hacked the code to put in what I needed, but when what I
was working on was blocked by someone else here, I decided the CDT
change was somewhat lame. So, this evening I added a new extension point
to the org.eclipse.cdt.launch that lets me change the command to be
executed from a plugin. The idea is that someone who wants to use the
CDT for cross compilation and execution can simply add an extension to
the CDT and get a hold of the command before the it gets sent to the
shell or the debugger.
To do this, I added a new interface in org.eclipse.cdt.launch called
IRunCommandInitializer. I chose the name because it's just not java if
the interface name does not take half the line. I added a new factory
class in org.eclipse.cdt.launch.internal called LaunchFactory that makes
these things (and little else at the moment, though I feel confident
that we will think of other things to extend in launch.) Finally, I
changed the cited code above to create an IRunCommandInitializer and use it.
All the code's attached including changes to the call site and the
launch plugin-xml. I was starting with the GA base from 8 Nov 02.
Comments are appreciated. I generally stole a lot of factory stuff from
the jdt. Interestingly, I had to fix a bug or two and it's a little
hokey because it does not check the name of the tags it is scanning for
class attributes (just like the JDT).
All this works and I'm off and working on some other goofy thing, but I
would like input on how far away this is from something you all would be
willing to add to the CDT because in the end, we have to have something
like this and it does seem generally useful.
Thanks!
-Chris
songer@xxxxxxxxxxxxx
Voice: 408 327 7341 Fax 408 986 8919
Director of Platform Engineering, Tensilica Inc
------------------------------------------------------------------------
/**********************************************************************
Copyright (c) Nov 2002 Tensilica and others.
All rights reserved. This program and the accompanying materials
are made available under the terms of the Common Public License v1.0
which accompanies this distribution, and is available at
http://www.eclipse.org/legal/cpl-v10.html
Contributors:
csonger Initial version.
**********************************************************************/
package org.eclipse.cdt.launch;
import java.util.ArrayList;
import org.eclipse.core.runtime.IPath;
/**
* Interface added via extension point to massage the command
* sent to execute to run an executable. The CommandInitializer
* is given access to the command array before the command array
* is executed.
*
* @author songer
*/
public interface IRunCommandInitializer
{
/**
* Adds commands to the start of the command array. This method
* is invoked by the launcher before commands have been added to
* the command array. This method modifies commands in place.
*
* @param commands Strings in an array list that are passed
* to either the debugger or the exec().
* @param executable path to the executable that is being launched.
* @param debugMode true if the run is going to be under the
* debugger.
*/
public void addToStart( ArrayList commands, IPath executable, boolean debugMode );
/**
* Adds commands to the end of the command array. This method is
* invoked by the launcher after commands have been added to the
* command array. This method modifies commands in place.
*
* @param commands Strings in an array list that are passed
* to either the debugger or the exec().
* @param executable path to the executable that is being launched.
* @param debugMode true if the run is going to be under the
* debugger.
*/
public void addToEnd( ArrayList commands, IPath executable, boolean debugMode );
}
------------------------------------------------------------------------
/**********************************************************************
Copyright (c) Nov 2002 Tensilica and others.
All rights reserved. This program and the accompanying materials
are made available under the terms of the Common Public License v1.0
which accompanies this distribution, and is available at
http://www.eclipse.org/legal/cpl-v10.html
Contributors:
csonger Initial version.
**********************************************************************/
package org.eclipse.cdt.launch.internal;
import org.eclipse.cdt.launch.IRunCommandInitializer;
import org.eclipse.cdt.launch.internal.ui.LaunchUIPlugin;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.Plugin;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IPluginDescriptor;
import org.eclipse.core.runtime.CoreException;
/**
* Factory class for extensions used by the launcher. At the moment
* this is limited only to RunCommandInitializers.
*
* @author songer
*/
public class LaunchFactory
{
/** magic string for RunCommandInitializer extension points */
public static final String RUN_COMMAND_INITIALIZER_ID =
"RunCommandInitializer";
/**
* Creates a RunCommandInitializer from the environment. In the event
* that multiple plugins have extended this, the first one wins. (It
* does not seem to make sense to have multiple RunCommandInitializers
* since they would likely step on each other. At some point it may well
* make sense to have a qualifier for tool chain or architecture to
* select among them.) Perhaps a CDT "architecture" setting
* that we use to load all of these kinds of things with?
*
* @return the new IRunCommandInitializer object. null if no extension
* of this exists.
*/
public static IRunCommandInitializer newRunCommandInitializer()
{
/* much of this is stolen pretty shamelessly from the jdt
* ToolFactory class, though there did seem to be a few bugs in
* ToolFactory that this corrects.*/
Plugin launchPlugin = LaunchUIPlugin.getDefault();
IRunCommandInitializer ret = null;
if( launchPlugin == null )
return null;
IExtensionPoint ext = launchPlugin.getDescriptor().getExtensionPoint( RUN_COMMAND_INITIALIZER_ID );
if( ext != null )
{
IExtension exts[] = ext.getExtensions();
for( int i = 0 ; i < exts.length ; i++ )
{
IPluginDescriptor extPlugin = exts[i].getDeclaringPluginDescriptor();
if( extPlugin.isPluginActivated() )
{
IConfigurationElement elements[] = exts[i].getConfigurationElements();
for( int j = 0 ; j < elements.length ; j++ )
{
try
{
Object loadedObj = elements[j].createExecutableExtension( "class" );
if( loadedObj instanceof IRunCommandInitializer )
return (IRunCommandInitializer)loadedObj;
}
catch( CoreException e )
{
}
}
}
}
}
return null;
}
}
------------------------------------------------------------------------
package org.eclipse.cdt.launch.internal;
/*
* (c) Copyright QNX Software System 2002.
* All Rights Reserved.
*/
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.IProcessInfo;
import org.eclipse.cdt.core.IProcessList;
import org.eclipse.cdt.debug.core.CDebugModel;
import org.eclipse.cdt.debug.core.ICDebugConfiguration;
import org.eclipse.cdt.debug.core.cdi.CDIException;
import org.eclipse.cdt.debug.core.cdi.ICDIRuntimeOptions;
import org.eclipse.cdt.debug.core.cdi.ICDISession;
import org.eclipse.cdt.debug.core.cdi.model.ICDITarget;
import org.eclipse.cdt.launch.AbstractCLaunchDelegate;
import org.eclipse.cdt.launch.ICDTLaunchConfigurationConstants;
import org.eclipse.cdt.launch.IRunCommandInitializer;
import org.eclipse.cdt.launch.internal.ui.LaunchUIPlugin;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.ResourcesPlugin;
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.Status;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.core.IStatusHandler;
import org.eclipse.debug.core.model.IProcess;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.dialogs.ElementListSelectionDialog;
/**
* Insert the type's description here.
* @see ILaunchConfigurationDelegate
*/
public class LocalCLaunchConfigurationDelegate extends AbstractCLaunchDelegate {
public void launch(ILaunchConfiguration config, String mode, ILaunch launch, IProgressMonitor monitor) throws CoreException {
if (monitor == null) {
monitor = new NullProgressMonitor();
}
monitor.beginTask("Launching Local C Application", IProgressMonitor.UNKNOWN);
// check for cancellation
if (monitor.isCanceled()) {
return;
}
/* assemble the command to launch */
boolean launchDebug= mode.equals(ILaunchManager.DEBUG_MODE);
IPath projectPath = verifyProgramFile(config);
String arguments[] = getProgramArgumentsArray(config);
ArrayList command = new ArrayList(1 + arguments.length);
/* preface with commands if there is an initializer */
IRunCommandInitializer cmdInit = LaunchFactory.newRunCommandInitializer();
if( cmdInit != null )
cmdInit.addToStart( command, projectPath, launchDebug );
/* put in the meat of the command */
command.add(projectPath.toOSString());
command.addAll(Arrays.asList(arguments));
/* add any trailing options */
if( cmdInit != null )
cmdInit.addToEnd( command, projectPath, launchDebug );
String[] commandArray = (String[]) command.toArray(new String[command.size()]);
if (launchDebug) {
IProcess debuggerProcess = null;
Process debugger;
ICDebugConfiguration debugConfig = getDebugConfig(config);
IFile exe = ResourcesPlugin.getWorkspace().getRoot().getFileForLocation(projectPath);
ICDISession dsession = null;
try {
String debugMode =
config.getAttribute(
ICDTLaunchConfigurationConstants.ATTR_DEBUGGER_START_MODE,
ICDTLaunchConfigurationConstants.DEBUGGER_MODE_RUN);
if (debugMode.equals(ICDTLaunchConfigurationConstants.DEBUGGER_MODE_RUN)) {
dsession = debugConfig.getDebugger().createLaunchSession(config, exe);
ICDIRuntimeOptions opt = dsession.getRuntimeOptions();
opt.setArguments(getProgramArgumentsArray(config));
File wd = getWorkingDir(config);
if (wd != null) {
opt.setWorkingDirectory(wd.toString());
}
opt.setEnvironment(getEnvironmentProperty(config));
ICDITarget dtarget = dsession.getTargets()[0];
Process process = dtarget.getProcess();
IProcess iprocess = DebugPlugin.newProcess(launch, process, renderProcessLabel(commandArray[0]));
debugger = dsession.getSessionProcess();
if ( debugger != null ) {
debuggerProcess = DebugPlugin.newProcess(launch, debugger, "Debug Console");
launch.removeProcess(debuggerProcess);
}
boolean stopInMain = config.getAttribute(ICDTLaunchConfigurationConstants.ATTR_DEBUGGER_STOP_AT_MAIN, false);
CDebugModel.newDebugTarget(
launch,
dsession.getCurrentTarget(),
renderTargetLabel(debugConfig),
iprocess,
debuggerProcess,
exe,
true,
false,
stopInMain);
} else if (debugMode.equals(ICDTLaunchConfigurationConstants.DEBUGGER_MODE_ATTACH)) {
int pid = getProcessID();
if (pid == -1) {
cancel("No Process ID selected", ICDTLaunchConfigurationConstants.ERR_NO_PROCESSID);
}
dsession = debugConfig.getDebugger().createAttachSession(config, exe, pid);
debugger = dsession.getSessionProcess();
if ( debugger != null ) {
debuggerProcess = DebugPlugin.newProcess(launch, debugger, "Debug Console");
launch.removeProcess(debuggerProcess);
}
CDebugModel.newAttachDebugTarget(
launch,
dsession.getCurrentTarget(),
renderTargetLabel(debugConfig),
debuggerProcess,
exe);
}
} catch (CDIException e) {
abort("Failed Launching CDI Debugger", e, ICDTLaunchConfigurationConstants.ERR_INTERNAL_ERROR);
}
} else {
Process process = exec(commandArray, getEnvironmentArray(config), getWorkingDir(config));
DebugPlugin.getDefault().newProcess(launch, process, renderProcessLabel(commandArray[0]));
}
monitor.done();
}
private int getProcessID() throws CoreException {
final Shell shell = LaunchUIPlugin.getShell();
final int pid[] = { -1 };
if (shell == null) {
abort("No Shell availible in Launch", null, ICDTLaunchConfigurationConstants.ERR_INTERNAL_ERROR);
}
Display display = shell.getDisplay();
display.syncExec(new Runnable() {
public void run() {
ElementListSelectionDialog dialog = new ElementListSelectionDialog(shell, new LabelProvider() {
public String getText(Object element) {
IProcessInfo info = (IProcessInfo) element;
return info.getPid() + " " + info.getName();
}
});
dialog.setTitle("Select Process");
dialog.setMessage("Select a Process to attach debugger to:");
IProcessList plist = CCorePlugin.getDefault().getProcessList();
if (plist == null) {
MessageDialog.openError(shell, "CDT Launch Error", "Current platform does not support listing processes");
return;
}
dialog.setElements(plist.getProcessList());
if (dialog.open() == dialog.OK) {
IProcessInfo info = (IProcessInfo) dialog.getFirstResult();
pid[0] = info.getPid();
}
}
});
return pid[0];
}
/**
* Performs a runtime exec on the given command line in the context
* of the specified working directory, and returns
* the resulting process. If the current runtime does not support the
* specification of a working directory, the status handler for error code
* <code>ERR_WORKING_DIRECTORY_NOT_SUPPORTED</code> is queried to see if the
* exec should be re-executed without specifying a working directory.
*
* @param cmdLine the command line
* @param workingDirectory the working directory, or <code>null</code>
* @return the resulting process or <code>null</code> if the exec is
* cancelled
* @see Runtime
*/
protected Process exec(String[] cmdLine, String[] envp, File workingDirectory) throws CoreException {
Process p = null;
try {
if (workingDirectory == null) {
p = Runtime.getRuntime().exec(cmdLine, envp);
} else {
p = Runtime.getRuntime().exec(cmdLine, envp, workingDirectory);
}
} catch (IOException e) {
if (p != null) {
p.destroy();
}
abort("Error starting process", e, ICDTLaunchConfigurationConstants.ERR_INTERNAL_ERROR);
} catch (NoSuchMethodError e) {
//attempting launches on 1.2.* - no ability to set working directory
IStatus status =
new Status(
IStatus.ERROR,
LaunchUIPlugin.getUniqueIdentifier(),
ICDTLaunchConfigurationConstants.ERR_WORKING_DIRECTORY_NOT_SUPPORTED,
"Eclipse runtime does not support working directory",
e);
IStatusHandler handler = DebugPlugin.getDefault().getStatusHandler(status);
if (handler != null) {
Object result = handler.handleStatus(status, this);
if (result instanceof Boolean && ((Boolean) result).booleanValue()) {
p = exec(cmdLine, envp, null);
}
}
}
return p;
}
protected String getPluginID() {
return LaunchUIPlugin.getUniqueIdentifier();
}
}
------------------------------------------------------------------------