diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/launch')
9 files changed, 1300 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/launch/GdbServerTask.java b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/launch/GdbServerTask.java new file mode 100644 index 000000000..23486ee6f --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/launch/GdbServerTask.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.ndk.internal.launch; + +import com.android.ddmlib.IDevice; +import com.android.ddmlib.IShellOutputReceiver; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * The {@link GdbServerTask} launches gdbserver on the given device and attaches it to + * provided pid. + */ +public class GdbServerTask implements Runnable { + private IDevice mDevice; + private String mRunAs; + private String mSocket; + private int mPid; + private CountDownLatch mAttachLatch; + + private GdbServerOutputReceiver mOutputReceiver; + private Exception mLaunchException; + + private AtomicBoolean mCancelled = new AtomicBoolean(false); + private AtomicBoolean mHasCompleted = new AtomicBoolean(false); + + /** + * Construct a gdbserver task. + * @param device device to run gdbserver on + * @param runAsPackage name of the package in which gdbserver resides + * @param socketName name of the local socket on which the server will listen + * @param pid pid of task to attach to + * @param attachLatch latch to notify when gdbserver gets attached to the task + */ + public GdbServerTask(IDevice device, String runAsPackage, String socketName, int pid, + CountDownLatch attachLatch) { + mDevice = device; + mRunAs = runAsPackage; + mSocket = socketName; + mPid = pid; + mAttachLatch = attachLatch; + + mOutputReceiver = new GdbServerOutputReceiver(); + } + + /** + * Runs gdbserver on the device and connects to the given task. If gdbserver manages to + * successfully attach itself to the process, then it counts down on its attach latch. + */ + @Override + public void run() { + // Launch gdbserver on the device. + String command = String.format("run-as %s lib/gdbserver +%s --attach %d", + mRunAs, mSocket, mPid); + try { + mDevice.executeShellCommand(command, mOutputReceiver, 0); + } catch (Exception e) { + mLaunchException = e; + } + } + + /** Returns any exceptions that might have occurred while launching gdbserver. */ + public Exception getLaunchException() { + return mLaunchException; + } + + /** Cancel gdbserver if it is running. */ + public void setCancelled() { + mCancelled.set(true); + } + + public String getShellOutput() { + return mOutputReceiver.getOutput(); + } + + private class GdbServerOutputReceiver implements IShellOutputReceiver { + private StringBuffer mOutput = new StringBuffer(100); + + @Override + public synchronized void addOutput(byte[] data, int offset, int length) { + mOutput.append(new String(data, offset, length)); + + // notify other threads that gdbserver has attached to the task + if (mOutput.toString().contains("Attached")) { + mAttachLatch.countDown(); + } + } + + @Override + public void flush() { + mHasCompleted.set(true); + } + + @Override + public boolean isCancelled() { + return mCancelled.get(); + } + + public synchronized String getOutput() { + return mOutput.toString(); + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/launch/Messages.java b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/launch/Messages.java new file mode 100644 index 000000000..10f68e48e --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/launch/Messages.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.ndk.internal.launch; + +import org.eclipse.osgi.util.NLS; + +public class Messages extends NLS { + private static final String BUNDLE_NAME = "com.android.ide.eclipse.ndk.internal.launch.messages"; //$NON-NLS-1$ + public static String NdkGdbLaunchDelegate_LaunchError_gdbserverOutput; + public static String NdkGdbLaunchDelegate_Action_ActivityLaunch; + public static String NdkGdbLaunchDelegate_Action_CheckAndroidDeviceVersion; + public static String NdkGdbLaunchDelegate_Action_KillExistingGdbServer; + public static String NdkGdbLaunchDelegate_Action_LaunchHostGdb; + public static String NdkGdbLaunchDelegate_Action_LaunchingGdbServer; + public static String NdkGdbLaunchDelegate_Action_ObtainAppAbis; + public static String NdkGdbLaunchDelegate_Action_ObtainDevice; + public static String NdkGdbLaunchDelegate_Action_ObtainDeviceABI; + public static String NdkGdbLaunchDelegate_Action_PerformIncrementalBuild; + public static String NdkGdbLaunchDelegate_Action_SettingUpPortForward; + public static String NdkGdbLaunchDelegate_Action_SyncAppToDevice; + public static String NdkGdbLaunchDelegate_Action_WaitGdbServerAttach; + public static String NdkGdbLaunchDelegate_Action_WaitingForActivity; + public static String NdkGdbLaunchDelegate_LaunchError_ActivityLaunchError; + public static String NdkGdbLaunchDelegate_LaunchError_Api8Needed; + public static String NdkGdbLaunchDelegate_LaunchError_CouldNotGetProject; + public static String NdkGdbLaunchDelegate_LaunchError_gdbserverLaunchException; + public static String NdkGdbLaunchDelegate_LaunchError_InstallError; + public static String NdkGdbLaunchDelegate_LaunchError_InterruptedWaitingForGdbserver; + public static String NdkGdbLaunchDelegate_LaunchError_NoActivityInManifest; + public static String NdkGdbLaunchDelegate_LaunchError_NoCompatibleAbi; + public static String NdkGdbLaunchDelegate_LaunchError_NoLauncherActivity; + public static String NdkGdbLaunchDelegate_LaunchError_NoSuchActivity; + public static String NdkGdbLaunchDelegate_LaunchError_NullApk; + public static String NdkGdbLaunchDelegate_LaunchError_ObtainingAppFolder; + public static String NdkGdbLaunchDelegate_LaunchError_PortForwarding; + public static String NdkGdbLaunchDelegate_LaunchError_ProjectHasErrors; + public static String NdkGdbLaunchDelegate_LaunchError_PullFileError; + public static String NdkGdbLaunchDelegate_LaunchError_UnableToDetectAppAbi; + public static String NdkGdbLaunchDelegate_LaunchError_UnknownAndroidDeviceVersion; + public static String NdkGdbLaunchDelegate_LaunchError_VerifyIfDebugBuild; + static { + // initialize resource bundle + NLS.initializeMessages(BUNDLE_NAME, Messages.class); + } + + private Messages() { + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/launch/NdkDebuggerTab.java b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/launch/NdkDebuggerTab.java new file mode 100644 index 000000000..64e7dd80d --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/launch/NdkDebuggerTab.java @@ -0,0 +1,311 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.ndk.internal.launch; + +import com.android.ide.eclipse.ndk.internal.NdkHelper; +import com.android.ide.eclipse.ndk.internal.NdkManager; + +import org.eclipse.cdt.dsf.gdb.IGDBLaunchConfigurationConstants; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; +import org.eclipse.debug.ui.AbstractLaunchConfigurationTab; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.layout.RowLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.DirectoryDialog; +import org.eclipse.swt.widgets.FileDialog; +import org.eclipse.swt.widgets.Group; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +import java.io.File; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class NdkDebuggerTab extends AbstractLaunchConfigurationTab { + private static String sLastGdbPath; + private static String sLastSolibPath; + + private Text mGdbPathText; + private Text mGdbInitPathText; + private Text mGdbRemotePortText; + + private org.eclipse.swt.widgets.List mSoliblist; + private Button mAddSolibButton; + private Button mDeleteSolibButton; + + /** + * @wbp.parser.entryPoint (Window Builder Entry Point) + */ + @Override + public void createControl(Composite parent) { + Composite comp = new Composite(parent, SWT.NONE); + setControl(comp); + comp.setLayout(new GridLayout(1, false)); + + Group grpGdb = new Group(comp, SWT.NONE); + grpGdb.setText("Launch Options"); + grpGdb.setLayout(new GridLayout(3, false)); + grpGdb.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + + Label lblDebugger = new Label(grpGdb, SWT.NONE); + lblDebugger.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); + lblDebugger.setText("Debugger:"); + + mGdbPathText = new Text(grpGdb, SWT.BORDER); + mGdbPathText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); + + final Button btnBrowseGdb = new Button(grpGdb, SWT.NONE); + btnBrowseGdb.setText("Browse..."); + + Label lblNewLabel = new Label(grpGdb, SWT.NONE); + lblNewLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); + lblNewLabel.setText("GDB Command File:"); + + mGdbInitPathText = new Text(grpGdb, SWT.BORDER); + mGdbInitPathText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); + + final Button btnBrowseGdbInit = new Button(grpGdb, SWT.NONE); + btnBrowseGdbInit.setText("Browse..."); + + SelectionListener browseListener = new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + Shell shell = ((Control) e.getSource()).getShell(); + if (e.getSource() == btnBrowseGdb) { + browseForGdb(shell); + } else { + browseForGdbInit(shell); + } + checkParameters(); + updateLaunchConfigurationDialog(); + } + }; + btnBrowseGdb.addSelectionListener(browseListener); + btnBrowseGdbInit.addSelectionListener(browseListener); + + Label lblPort = new Label(grpGdb, SWT.NONE); + lblPort.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); + lblPort.setText("Port:"); + + mGdbRemotePortText = new Text(grpGdb, SWT.BORDER); + GridData gd_text_2 = new GridData(SWT.LEFT, SWT.CENTER, false, false, 1, 1); + gd_text_2.widthHint = 100; + mGdbRemotePortText.setLayoutData(gd_text_2); + + ModifyListener m = new ModifyListener() { + @Override + public void modifyText(ModifyEvent e) { + checkParameters(); + updateLaunchConfigurationDialog(); + } + }; + mGdbPathText.addModifyListener(m); + mGdbInitPathText.addModifyListener(m); + mGdbRemotePortText.addModifyListener(m); + + Group grpSharedLibraries = new Group(comp, SWT.NONE); + grpSharedLibraries.setText("Shared Libraries"); + grpSharedLibraries.setLayout(new GridLayout(2, false)); + GridData gd_grpSharedLibraries = new GridData(GridData.FILL_BOTH); + gd_grpSharedLibraries.verticalAlignment = SWT.TOP; + gd_grpSharedLibraries.grabExcessVerticalSpace = true; + grpSharedLibraries.setLayoutData(gd_grpSharedLibraries); + + mSoliblist = new org.eclipse.swt.widgets.List(grpSharedLibraries, + SWT.BORDER | SWT.V_SCROLL | SWT.SINGLE); + GridData gd_list = new GridData(GridData.FILL_BOTH); + gd_list.heightHint = 133; + gd_list.grabExcessVerticalSpace = false; + gd_list.verticalSpan = 1; + mSoliblist.setLayoutData(gd_list); + + Composite composite = new Composite(grpSharedLibraries, SWT.NONE); + composite.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false, 1, 1)); + composite.setLayout(new RowLayout(SWT.VERTICAL)); + + mAddSolibButton = new Button(composite, SWT.NONE); + mAddSolibButton.setText("Add..."); + + mDeleteSolibButton = new Button(composite, SWT.NONE); + mDeleteSolibButton.setText("Remove"); + mDeleteSolibButton.setEnabled(false); + + SelectionListener l = new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + Control c = (Control) e.getSource(); + if (c == mSoliblist) { + // enable delete only if there is a selection + mDeleteSolibButton.setEnabled(mSoliblist.getSelectionCount() > 0); + } else if (c == mAddSolibButton) { + addSolib(c.getShell()); + } else { + // delete current selection + int index = mSoliblist.getSelectionIndex(); + if (index >= 0) { + mSoliblist.remove(index); + } + } + updateLaunchConfigurationDialog(); + } + }; + + mSoliblist.addSelectionListener(l); + mAddSolibButton.addSelectionListener(l); + mDeleteSolibButton.addSelectionListener(l); + } + + private void addSolib(Shell shell) { + DirectoryDialog dd = new DirectoryDialog(shell); + if (sLastSolibPath != null) { + dd.setFilterPath(sLastSolibPath); + } + String solibPath = dd.open(); + + if (solibPath != null) { + mSoliblist.add(solibPath); + sLastSolibPath = new File(solibPath).getParent(); + } + } + + private void browseForGdb(Shell shell) { + if (sLastGdbPath == null) { + sLastGdbPath = NdkManager.getNdkLocation(); + } + + FileDialog fd = new FileDialog(shell); + fd.setFilterPath(sLastGdbPath); + + String gdbPath = fd.open(); + if (gdbPath != null) { + mGdbPathText.setText(gdbPath); + sLastGdbPath = new File(gdbPath).getParent(); + } + } + + private void browseForGdbInit(Shell shell) { + FileDialog fd = new FileDialog(shell); + String gdbInit = fd.open(); + if (gdbInit != null) { + mGdbInitPathText.setText(gdbInit); + } + } + + private void checkParameters() { + // check gdb path + String gdb = mGdbPathText.getText().trim(); + if (!gdb.equals(NdkLaunchConstants.DEFAULT_GDB)) { + File f = new File(gdb); + if (!f.exists() || !f.canExecute()) { + setErrorMessage("Invalid gdb location."); + return; + } + } + + // check gdb init path + String gdbInit = mGdbInitPathText.getText().trim(); + if (!gdbInit.isEmpty()) { + File f = new File(gdbInit); + if (!f.exists() || !f.isFile()) { + setErrorMessage("Invalid gdbinit location."); + return; + } + } + + // port should be a valid integer + String port = mGdbRemotePortText.getText().trim(); + try { + Integer.parseInt(port, 10); + } catch (NumberFormatException e) { + setErrorMessage("Port should be a valid integer"); + return; + } + + // no errors + setErrorMessage(null); + setMessage(null); + } + + @Override + public void setDefaults(ILaunchConfigurationWorkingCopy config) { + NdkHelper.setLaunchConfigDefaults(config); + } + + @Override + public void initializeFrom(ILaunchConfiguration config) { + mGdbPathText.setText(getAttribute(config, NdkLaunchConstants.ATTR_NDK_GDB, + NdkLaunchConstants.DEFAULT_GDB)); + mGdbInitPathText.setText(getAttribute(config, + IGDBLaunchConfigurationConstants.ATTR_GDB_INIT, + NdkLaunchConstants.DEFAULT_GDBINIT)); + mGdbRemotePortText.setText(getAttribute(config, IGDBLaunchConfigurationConstants.ATTR_PORT, + NdkLaunchConstants.DEFAULT_GDB_PORT)); + + List<String> solibs = getAttribute(config, NdkLaunchConstants.ATTR_NDK_SOLIB, + Collections.EMPTY_LIST); + mSoliblist.removeAll(); + for (String s: solibs) { + mSoliblist.add(s); + } + } + + private String getAttribute(ILaunchConfiguration config, String key, String defaultValue) { + try { + return config.getAttribute(key, defaultValue); + } catch (CoreException e) { + return defaultValue; + } + } + + private List<String> getAttribute(ILaunchConfiguration config, String key, + List<String> defaultValue) { + try { + return config.getAttribute(key, defaultValue); + } catch (CoreException e) { + return defaultValue; + } + } + + @Override + public void performApply(ILaunchConfigurationWorkingCopy config) { + config.setAttribute(NdkLaunchConstants.ATTR_NDK_GDB, mGdbPathText.getText().trim()); + config.setAttribute(IGDBLaunchConfigurationConstants.ATTR_GDB_INIT, + mGdbInitPathText.getText().trim()); + config.setAttribute(IGDBLaunchConfigurationConstants.ATTR_PORT, + mGdbRemotePortText.getText().trim()); + config.setAttribute(NdkLaunchConstants.ATTR_NDK_SOLIB, + Arrays.asList(mSoliblist.getItems())); + } + + @Override + public String getName() { + return "Debugger"; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/launch/NdkGdbLaunchConfigTabGroups.java b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/launch/NdkGdbLaunchConfigTabGroups.java new file mode 100644 index 000000000..f8cf73c9f --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/launch/NdkGdbLaunchConfigTabGroups.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.ndk.internal.launch; + +import org.eclipse.debug.ui.AbstractLaunchConfigurationTabGroup; +import org.eclipse.debug.ui.CommonTab; +import org.eclipse.debug.ui.ILaunchConfigurationDialog; +import org.eclipse.debug.ui.ILaunchConfigurationTab; +import org.eclipse.debug.ui.sourcelookup.SourceLookupTab; + +public class NdkGdbLaunchConfigTabGroups extends AbstractLaunchConfigurationTabGroup { + public NdkGdbLaunchConfigTabGroups() { + } + + @Override + public void createTabs(ILaunchConfigurationDialog dialog, String mode) { + ILaunchConfigurationTab[] tabs = new ILaunchConfigurationTab[] { + new NdkMainLaunchConfigTab(), + new NdkDebuggerTab(), + new SourceLookupTab(), + new CommonTab() + }; + setTabs(tabs); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/launch/NdkGdbLaunchDelegate.java b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/launch/NdkGdbLaunchDelegate.java new file mode 100644 index 000000000..0b124f249 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/launch/NdkGdbLaunchDelegate.java @@ -0,0 +1,506 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.ndk.internal.launch; + +import com.android.annotations.NonNull; +import com.android.annotations.Nullable; +import com.android.ddmlib.AdbCommandRejectedException; +import com.android.ddmlib.AndroidDebugBridge; +import com.android.ddmlib.Client; +import com.android.ddmlib.CollectingOutputReceiver; +import com.android.ddmlib.IDevice; +import com.android.ddmlib.IDevice.DeviceUnixSocketNamespace; +import com.android.ddmlib.InstallException; +import com.android.ddmlib.ShellCommandUnresponsiveException; +import com.android.ddmlib.SyncException; +import com.android.ddmlib.TimeoutException; +import com.android.ide.common.xml.ManifestData; +import com.android.ide.common.xml.ManifestData.Activity; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo; +import com.android.ide.eclipse.adt.internal.launch.AndroidLaunchController; +import com.android.ide.eclipse.adt.internal.launch.DeviceChooserDialog; +import com.android.ide.eclipse.adt.internal.launch.DeviceChooserDialog.DeviceChooserResponse; +import com.android.ide.eclipse.adt.internal.launch.LaunchConfigDelegate; +import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper; +import com.android.ide.eclipse.adt.internal.project.ProjectHelper; +import com.android.ide.eclipse.adt.internal.sdk.Sdk; +import com.android.ide.eclipse.ndk.internal.NativeAbi; +import com.android.ide.eclipse.ndk.internal.NdkHelper; +import com.android.ide.eclipse.ndk.internal.NdkVariables; +import com.android.sdklib.AndroidVersion; +import com.android.sdklib.IAndroidTarget; +import com.google.common.base.Joiner; + +import org.eclipse.cdt.core.model.ICProject; +import org.eclipse.cdt.debug.core.CDebugUtils; +import org.eclipse.cdt.debug.core.ICDTLaunchConfigurationConstants; +import org.eclipse.cdt.dsf.gdb.IGDBLaunchConfigurationConstants; +import org.eclipse.cdt.dsf.gdb.launching.GdbLaunchDelegate; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.Path; +import org.eclipse.core.variables.IStringVariableManager; +import org.eclipse.core.variables.IValueVariable; +import org.eclipse.core.variables.VariablesPlugin; +import org.eclipse.debug.core.DebugPlugin; +import org.eclipse.debug.core.ILaunch; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; +import org.eclipse.jface.dialogs.Dialog; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +@SuppressWarnings("restriction") +public class NdkGdbLaunchDelegate extends GdbLaunchDelegate { + public static final String LAUNCH_TYPE_ID = + "com.android.ide.eclipse.ndk.debug.LaunchConfigType"; //$NON-NLS-1$ + + private static final Joiner JOINER = Joiner.on(", ").skipNulls(); + + private static final String DEBUG_SOCKET = "debugsock"; //$NON-NLS-1$ + + @Override + public void launch(ILaunchConfiguration config, String mode, ILaunch launch, + IProgressMonitor monitor) throws CoreException { + boolean launched = doLaunch(config, mode, launch, monitor); + if (!launched) { + if (launch.canTerminate()) { + launch.terminate(); + } + DebugPlugin.getDefault().getLaunchManager().removeLaunch(launch); + } + } + + public boolean doLaunch(final ILaunchConfiguration config, String mode, ILaunch launch, + IProgressMonitor monitor) throws CoreException { + IProject project = null; + ICProject cProject = CDebugUtils.getCProject(config); + if (cProject != null) { + project = cProject.getProject(); + } + + if (project == null) { + AdtPlugin.printErrorToConsole( + Messages.NdkGdbLaunchDelegate_LaunchError_CouldNotGetProject); + return false; + } + + // make sure the project and its dependencies are built and PostCompilerBuilder runs. + // This is a synchronous call which returns when the build is done. + monitor.setTaskName(Messages.NdkGdbLaunchDelegate_Action_PerformIncrementalBuild); + ProjectHelper.doFullIncrementalDebugBuild(project, monitor); + + // check if the project has errors, and abort in this case. + if (ProjectHelper.hasError(project, true)) { + AdtPlugin.printErrorToConsole(project, + Messages.NdkGdbLaunchDelegate_LaunchError_ProjectHasErrors); + return false; + } + + final ManifestData manifestData = AndroidManifestHelper.parseForData(project); + final ManifestInfo manifestInfo = ManifestInfo.get(project); + final AndroidVersion minSdkVersion = new AndroidVersion( + manifestInfo.getMinSdkVersion(), + manifestInfo.getMinSdkCodeName()); + + // Get the activity name to launch + String activityName = getActivityToLaunch( + getActivityNameInLaunchConfig(config), + manifestData.getLauncherActivity(), + manifestData.getActivities(), + project); + + // Get ABI's supported by the application + monitor.setTaskName(Messages.NdkGdbLaunchDelegate_Action_ObtainAppAbis); + Collection<NativeAbi> appAbis = NdkHelper.getApplicationAbis(project, monitor); + if (appAbis.size() == 0) { + AdtPlugin.printErrorToConsole(project, + Messages.NdkGdbLaunchDelegate_LaunchError_UnableToDetectAppAbi); + return false; + } + + // Obtain device to use: + // - if there is only 1 device, just use that + // - if we have previously launched this config, and the device used is present, use that + // - otherwise show the DeviceChooserDialog + final String configName = config.getName(); + monitor.setTaskName(Messages.NdkGdbLaunchDelegate_Action_ObtainDevice); + IDevice device = null; + IDevice[] devices = AndroidDebugBridge.getBridge().getDevices(); + if (devices.length == 1) { + device = devices[0]; + } else if ((device = getLastUsedDevice(config, devices)) == null) { + final IAndroidTarget projectTarget = Sdk.getCurrent().getTarget(project); + final DeviceChooserResponse response = new DeviceChooserResponse(); + final boolean continueLaunch[] = new boolean[] { false }; + AdtPlugin.getDisplay().syncExec(new Runnable() { + @Override + public void run() { + DeviceChooserDialog dialog = new DeviceChooserDialog( + AdtPlugin.getDisplay().getActiveShell(), + response, + manifestData.getPackage(), + projectTarget, minSdkVersion, false /*** FIXME! **/); + if (dialog.open() == Dialog.OK) { + AndroidLaunchController.updateLaunchConfigWithLastUsedDevice(config, + response); + continueLaunch[0] = true; + } + }; + }); + + if (!continueLaunch[0]) { + return false; + } + + device = response.getDeviceToUse(); + } + + // ndk-gdb requires device > Froyo + monitor.setTaskName(Messages.NdkGdbLaunchDelegate_Action_CheckAndroidDeviceVersion); + AndroidVersion deviceVersion = Sdk.getDeviceVersion(device); + if (deviceVersion == null) { + AdtPlugin.printErrorToConsole(project, + Messages.NdkGdbLaunchDelegate_LaunchError_UnknownAndroidDeviceVersion); + return false; + } else if (!deviceVersion.isGreaterOrEqualThan(8)) { + AdtPlugin.printErrorToConsole(project, + Messages.NdkGdbLaunchDelegate_LaunchError_Api8Needed); + return false; + } + + // get Device ABI + monitor.setTaskName(Messages.NdkGdbLaunchDelegate_Action_ObtainDeviceABI); + String deviceAbi1 = device.getProperty("ro.product.cpu.abi"); //$NON-NLS-1$ + String deviceAbi2 = device.getProperty("ro.product.cpu.abi2"); //$NON-NLS-1$ + + // get the abi that is supported by both the device and the application + NativeAbi compatAbi = getCompatibleAbi(deviceAbi1, deviceAbi2, appAbis); + if (compatAbi == null) { + AdtPlugin.printErrorToConsole(project, + Messages.NdkGdbLaunchDelegate_LaunchError_NoCompatibleAbi); + AdtPlugin.printErrorToConsole(project, + String.format("ABI's supported by the application: %s", JOINER.join(appAbis))); + AdtPlugin.printErrorToConsole(project, + String.format("ABI's supported by the device: %s, %s", //$NON-NLS-1$ + deviceAbi1, + deviceAbi2)); + return false; + } + + // sync app + monitor.setTaskName(Messages.NdkGdbLaunchDelegate_Action_SyncAppToDevice); + IFile apk = ProjectHelper.getApplicationPackage(project); + if (apk == null) { + AdtPlugin.printErrorToConsole(project, + Messages.NdkGdbLaunchDelegate_LaunchError_NullApk); + return false; + } + try { + device.installPackage(apk.getLocation().toOSString(), true); + } catch (InstallException e1) { + AdtPlugin.printErrorToConsole(project, + Messages.NdkGdbLaunchDelegate_LaunchError_InstallError, e1); + return false; + } + + // launch activity + monitor.setTaskName(Messages.NdkGdbLaunchDelegate_Action_ActivityLaunch + activityName); + String command = String.format("am start -n %s/%s", manifestData.getPackage(), //$NON-NLS-1$ + activityName); + try { + CountDownLatch launchedLatch = new CountDownLatch(1); + CollectingOutputReceiver receiver = new CollectingOutputReceiver(launchedLatch); + device.executeShellCommand(command, receiver); + launchedLatch.await(5, TimeUnit.SECONDS); + String shellOutput = receiver.getOutput(); + if (shellOutput.contains("Error type")) { //$NON-NLS-1$ + throw new RuntimeException(receiver.getOutput()); + } + } catch (Exception e) { + AdtPlugin.printErrorToConsole(project, + Messages.NdkGdbLaunchDelegate_LaunchError_ActivityLaunchError, e); + return false; + } + + // kill existing gdbserver + monitor.setTaskName(Messages.NdkGdbLaunchDelegate_Action_KillExistingGdbServer); + for (Client c: device.getClients()) { + String description = c.getClientData().getClientDescription(); + if (description != null && description.contains("gdbserver")) { //$NON-NLS-1$ + c.kill(); + } + } + + // pull app_process & libc from the device + IPath solibFolder = project.getLocation().append("obj/local").append(compatAbi.getAbi()); + try { + pull(device, "/system/bin/app_process", solibFolder); //$NON-NLS-1$ + pull(device, "/system/lib/libc.so", solibFolder); //$NON-NLS-1$ + } catch (Exception e) { + AdtPlugin.printErrorToConsole(project, + Messages.NdkGdbLaunchDelegate_LaunchError_PullFileError, e); + return false; + } + + // wait for a couple of seconds for activity to be launched + monitor.setTaskName(Messages.NdkGdbLaunchDelegate_Action_WaitingForActivity); + try { + Thread.sleep(2000); + } catch (InterruptedException e1) { + // uninterrupted + } + + // get pid of activity + Client app = device.getClient(manifestData.getPackage()); + int pid = app.getClientData().getPid(); + + // launch gdbserver + monitor.setTaskName(Messages.NdkGdbLaunchDelegate_Action_LaunchingGdbServer); + CountDownLatch attachLatch = new CountDownLatch(1); + GdbServerTask gdbServer = new GdbServerTask(device, manifestData.getPackage(), + DEBUG_SOCKET, pid, attachLatch); + new Thread(gdbServer, + String.format("gdbserver for %s", manifestData.getPackage())).start(); //$NON-NLS-1$ + + // wait for gdbserver to attach + monitor.setTaskName(Messages.NdkGdbLaunchDelegate_Action_WaitGdbServerAttach); + boolean attached = false; + try { + attached = attachLatch.await(3, TimeUnit.SECONDS); + } catch (InterruptedException e) { + AdtPlugin.printErrorToConsole(project, + Messages.NdkGdbLaunchDelegate_LaunchError_InterruptedWaitingForGdbserver); + return false; + } + + // if gdbserver failed to attach, we report any errors that may have occurred + if (!attached) { + if (gdbServer.getLaunchException() != null) { + AdtPlugin.printErrorToConsole(project, + Messages.NdkGdbLaunchDelegate_LaunchError_gdbserverLaunchException, + gdbServer.getLaunchException()); + } else { + AdtPlugin.printErrorToConsole(project, + Messages.NdkGdbLaunchDelegate_LaunchError_gdbserverOutput, + gdbServer.getShellOutput()); + } + AdtPlugin.printErrorToConsole(project, + Messages.NdkGdbLaunchDelegate_LaunchError_VerifyIfDebugBuild); + + // shut down the gdbserver thread + gdbServer.setCancelled(); + return false; + } + + // Obtain application working directory + String appDir = null; + try { + appDir = getAppDirectory(device, manifestData.getPackage(), 5, TimeUnit.SECONDS); + } catch (Exception e) { + AdtPlugin.printErrorToConsole(project, + Messages.NdkGdbLaunchDelegate_LaunchError_ObtainingAppFolder, e); + return false; + } + + // setup port forwarding between local port & remote (device) unix domain socket + monitor.setTaskName(Messages.NdkGdbLaunchDelegate_Action_SettingUpPortForward); + String localport = config.getAttribute(IGDBLaunchConfigurationConstants.ATTR_PORT, + NdkLaunchConstants.DEFAULT_GDB_PORT); + try { + device.createForward(Integer.parseInt(localport), + String.format("%s/%s", appDir, DEBUG_SOCKET), //$NON-NLS-1$ + DeviceUnixSocketNamespace.FILESYSTEM); + } catch (Exception e) { + AdtPlugin.printErrorToConsole(project, + Messages.NdkGdbLaunchDelegate_LaunchError_PortForwarding, e); + return false; + } + + // update launch attributes based on device + ILaunchConfiguration config2 = performVariableSubstitutions(config, project, compatAbi, + monitor); + + // launch gdb + monitor.setTaskName(Messages.NdkGdbLaunchDelegate_Action_LaunchHostGdb); + super.launch(config2, mode, launch, monitor); + return true; + } + + @Nullable + private IDevice getLastUsedDevice(ILaunchConfiguration config, @NonNull IDevice[] devices) { + try { + boolean reuse = config.getAttribute(LaunchConfigDelegate.ATTR_REUSE_LAST_USED_DEVICE, + false); + if (!reuse) { + return null; + } + + String serial = config.getAttribute(LaunchConfigDelegate.ATTR_LAST_USED_DEVICE, + (String)null); + return AndroidLaunchController.getDeviceIfOnline(serial, devices); + } catch (CoreException e) { + return null; + } + } + + private void pull(IDevice device, String remote, IPath solibFolder) throws + SyncException, IOException, AdbCommandRejectedException, TimeoutException { + String remoteFileName = new Path(remote).toFile().getName(); + String targetFile = solibFolder.append(remoteFileName).toString(); + device.pullFile(remote, targetFile); + } + + private ILaunchConfiguration performVariableSubstitutions(ILaunchConfiguration config, + IProject project, NativeAbi compatAbi, IProgressMonitor monitor) throws CoreException { + ILaunchConfigurationWorkingCopy wcopy = config.getWorkingCopy(); + + String toolchainPrefix = NdkHelper.getToolchainPrefix(project, compatAbi, monitor); + String gdb = toolchainPrefix + "gdb"; //$NON-NLS-1$ + + IStringVariableManager manager = VariablesPlugin.getDefault().getStringVariableManager(); + IValueVariable ndkGdb = manager.newValueVariable(NdkVariables.NDK_GDB, + NdkVariables.NDK_GDB, true, gdb); + IValueVariable ndkProject = manager.newValueVariable(NdkVariables.NDK_PROJECT, + NdkVariables.NDK_PROJECT, true, project.getLocation().toOSString()); + IValueVariable ndkCompatAbi = manager.newValueVariable(NdkVariables.NDK_COMPAT_ABI, + NdkVariables.NDK_COMPAT_ABI, true, compatAbi.getAbi()); + + IValueVariable[] ndkVars = new IValueVariable[] { ndkGdb, ndkProject, ndkCompatAbi }; + manager.addVariables(ndkVars); + + // fix path to gdb + String userGdbPath = wcopy.getAttribute(NdkLaunchConstants.ATTR_NDK_GDB, + NdkLaunchConstants.DEFAULT_GDB); + wcopy.setAttribute(IGDBLaunchConfigurationConstants.ATTR_DEBUG_NAME, + elaborateExpression(manager, userGdbPath)); + + // setup program name + wcopy.setAttribute(ICDTLaunchConfigurationConstants.ATTR_PROGRAM_NAME, + elaborateExpression(manager, NdkLaunchConstants.DEFAULT_PROGRAM)); + + // fix solib paths + List<String> solibPaths = wcopy.getAttribute( + NdkLaunchConstants.ATTR_NDK_SOLIB, + Collections.singletonList(NdkLaunchConstants.DEFAULT_SOLIB_PATH)); + List<String> fixedSolibPaths = new ArrayList<String>(solibPaths.size()); + for (String u : solibPaths) { + fixedSolibPaths.add(elaborateExpression(manager, u)); + } + wcopy.setAttribute(IGDBLaunchConfigurationConstants.ATTR_DEBUGGER_SOLIB_PATH, + fixedSolibPaths); + + manager.removeVariables(ndkVars); + + return wcopy.doSave(); + } + + private String elaborateExpression(IStringVariableManager manager, String expr) + throws CoreException{ + boolean DEBUG = true; + + String eval = manager.performStringSubstitution(expr); + if (DEBUG) { + AdtPlugin.printToConsole("Substitute: ", expr, " --> ", eval); + } + + return eval; + } + + /** + * Returns the activity name to launch. If the user has requested a particular activity to + * be launched, then this method will confirm that the requested activity is defined in the + * manifest. If the user has not specified any activities, then it returns the default + * launcher activity. + * @param activityNameInLaunchConfig activity to launch as requested by the user. + * @param activities list of activities as defined in the application's manifest + * @param project android project + * @return activity name that should be launched, or null if no launchable activity. + */ + private String getActivityToLaunch(String activityNameInLaunchConfig, Activity launcherActivity, + Activity[] activities, IProject project) { + if (activities.length == 0) { + AdtPlugin.printErrorToConsole(project, + Messages.NdkGdbLaunchDelegate_LaunchError_NoActivityInManifest); + return null; + } else if (activityNameInLaunchConfig == null && launcherActivity != null) { + return launcherActivity.getName(); + } else { + for (Activity a : activities) { + if (a != null && a.getName().equals(activityNameInLaunchConfig)) { + return activityNameInLaunchConfig; + } + } + + AdtPlugin.printErrorToConsole(project, + Messages.NdkGdbLaunchDelegate_LaunchError_NoSuchActivity); + if (launcherActivity != null) { + return launcherActivity.getName(); + } else { + AdtPlugin.printErrorToConsole( + Messages.NdkGdbLaunchDelegate_LaunchError_NoLauncherActivity); + return null; + } + } + } + + private NativeAbi getCompatibleAbi(String deviceAbi1, String deviceAbi2, + Collection<NativeAbi> appAbis) { + for (NativeAbi abi: appAbis) { + if (abi.getAbi().equals(deviceAbi1) || abi.getAbi().equals(deviceAbi2)) { + return abi; + } + } + + return null; + } + + /** Returns the name of the activity as defined in the launch configuration. */ + private String getActivityNameInLaunchConfig(ILaunchConfiguration configuration) { + String empty = ""; //$NON-NLS-1$ + String activityName; + try { + activityName = configuration.getAttribute(LaunchConfigDelegate.ATTR_ACTIVITY, empty); + } catch (CoreException e) { + return null; + } + + return (activityName != empty) ? activityName : null; + } + + private String getAppDirectory(IDevice device, String app, long timeout, TimeUnit timeoutUnit) + throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException, + IOException, InterruptedException { + String command = String.format("run-as %s /system/bin/sh -c pwd", app); //$NON-NLS-1$ + + CountDownLatch commandCompleteLatch = new CountDownLatch(1); + CollectingOutputReceiver receiver = new CollectingOutputReceiver(commandCompleteLatch); + device.executeShellCommand(command, receiver); + commandCompleteLatch.await(timeout, timeoutUnit); + return receiver.getOutput().trim(); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/launch/NdkGdbLaunchShortcut.java b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/launch/NdkGdbLaunchShortcut.java new file mode 100644 index 000000000..22eb118a2 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/launch/NdkGdbLaunchShortcut.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.ndk.internal.launch; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.launch.AndroidLaunchController; +import com.android.ide.eclipse.adt.internal.sdk.ProjectState; +import com.android.ide.eclipse.adt.internal.sdk.Sdk; +import com.android.ide.eclipse.ndk.internal.NdkHelper; + +import org.eclipse.cdt.core.model.CoreModel; +import org.eclipse.cdt.debug.core.ICDTLaunchConfigurationConstants; +import org.eclipse.cdt.dsf.gdb.IGDBLaunchConfigurationConstants; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; +import org.eclipse.debug.ui.DebugUITools; +import org.eclipse.debug.ui.ILaunchShortcut; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.ui.IEditorPart; + +@SuppressWarnings("restriction") // for adt.internal classes +public class NdkGdbLaunchShortcut implements ILaunchShortcut { + @Override + public void launch(ISelection selection, String mode) { + if (!(selection instanceof IStructuredSelection)) { + return; + } + + Object s = ((IStructuredSelection) selection).getFirstElement(); + if (!(s instanceof IAdaptable)) { + return; + } + + IResource r = (IResource) ((IAdaptable) s).getAdapter(IResource.class); + if (r == null) { + return; + } + + IProject project = r.getProject(); + if (project == null) { + return; + } + + // verify that this is a non library Android project + ProjectState state = Sdk.getProjectState(project); + if (state == null || state.isLibrary()) { + return; + } + + // verify that this project has C/C++ nature + if (!CoreModel.hasCCNature(project) && !CoreModel.hasCNature(project)) { + AdtPlugin.printErrorToConsole(project, + String.format("Selected project (%s) does not have C/C++ nature. " + + "To add native support, right click on the project, " + + "Android Tools -> Add Native Support", + project.getName())); + return; + } + + debugProject(project, mode); + } + + @Override + public void launch(IEditorPart editor, String mode) { + } + + private void debugProject(IProject project, String mode) { + // obtain existing native debug config for project + ILaunchConfiguration config = AndroidLaunchController.getLaunchConfig(project, + NdkGdbLaunchDelegate.LAUNCH_TYPE_ID); + if (config == null) { + return; + } + + // Set the ndk gdb specific launch attributes in the config (if necessary) + if (!hasNdkAttributes(config)) { + try { + config = setNdkDefaults(config, project); + } catch (CoreException e) { + AdtPlugin.printErrorToConsole(project, + "Unable to create launch configuration for project."); + return; + } + } + + // launch + DebugUITools.launch(config, mode); + } + + private boolean hasNdkAttributes(ILaunchConfiguration config) { + try { + // All NDK launch configurations have ATTR_REMOTE_TCP set to true + boolean isRemote = config.getAttribute(IGDBLaunchConfigurationConstants.ATTR_REMOTE_TCP, + false); + return isRemote; + } catch (CoreException e) { + return false; + } + } + + private ILaunchConfiguration setNdkDefaults(ILaunchConfiguration config, IProject project) + throws CoreException { + ILaunchConfigurationWorkingCopy wc = config.getWorkingCopy(); + NdkHelper.setLaunchConfigDefaults(wc); + wc.setAttribute(ICDTLaunchConfigurationConstants.ATTR_PROJECT_NAME, project.getName()); + return wc.doSave(); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/launch/NdkLaunchConstants.java b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/launch/NdkLaunchConstants.java new file mode 100644 index 000000000..ff70b2e1f --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/launch/NdkLaunchConstants.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.ndk.internal.launch; + +import com.android.ide.eclipse.ndk.internal.Activator; +import com.android.ide.eclipse.ndk.internal.NdkVariables; + +public class NdkLaunchConstants { + private static final String PREFIX = Activator.PLUGIN_ID + ".ndklaunch."; //$NON-NLS-1$ + + public static final String ATTR_NDK_GDB = PREFIX + "gdb"; //$NON-NLS-1$ + public static final String ATTR_NDK_GDBINIT = PREFIX + "gdbinit"; //$NON-NLS-1$ + public static final String ATTR_NDK_SOLIB = PREFIX + "solib"; //$NON-NLS-1$ + + public static final String DEFAULT_GDB_PORT = "5039"; //$NON-NLS-1$ + public static final String DEFAULT_GDB = getVar(NdkVariables.NDK_GDB); + public static final String DEFAULT_GDBINIT = ""; //$NON-NLS-1$ + public static final String DEFAULT_PROGRAM = + String.format("%1$s/obj/local/%2$s/app_process", //$NON-NLS-1$ + getVar(NdkVariables.NDK_PROJECT), + getVar(NdkVariables.NDK_COMPAT_ABI)); + public static final String DEFAULT_SOLIB_PATH = + String.format("%1$s/obj/local/%2$s/", //$NON-NLS-1$ + getVar(NdkVariables.NDK_PROJECT), + getVar(NdkVariables.NDK_COMPAT_ABI)); + + private static String getVar(String varName) { + return "${" + varName + '}'; //$NON-NLS-1$ + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/launch/NdkMainLaunchConfigTab.java b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/launch/NdkMainLaunchConfigTab.java new file mode 100644 index 000000000..1e4b39e91 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/launch/NdkMainLaunchConfigTab.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.ndk.internal.launch; + +import com.android.ide.eclipse.adt.internal.launch.MainLaunchConfigTab; +import com.android.ide.eclipse.adt.internal.project.ProjectChooserHelper.IProjectChooserFilter; +import com.android.ide.eclipse.adt.internal.sdk.ProjectState; +import com.android.ide.eclipse.adt.internal.sdk.Sdk; + +import org.eclipse.cdt.core.model.CoreModel; +import org.eclipse.cdt.debug.core.ICDTLaunchConfigurationConstants; +import org.eclipse.core.resources.IProject; +import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; + +@SuppressWarnings("restriction") +public class NdkMainLaunchConfigTab extends MainLaunchConfigTab { + private static class NdkProjectOnlyFilter implements IProjectChooserFilter { + @Override + public boolean accept(IProject project) { + ProjectState state = Sdk.getProjectState(project); + if (state == null) { + return false; + } + + return !state.isLibrary() + && (CoreModel.hasCCNature(project) || CoreModel.hasCNature(project)); + } + + @Override + public boolean useCache() { + return true; + } + } + + @Override + protected IProjectChooserFilter getProjectFilter() { + return new NdkProjectOnlyFilter(); + } + + @Override + public void performApply(ILaunchConfigurationWorkingCopy configuration) { + super.performApply(configuration); + + configuration.setAttribute(ICDTLaunchConfigurationConstants.ATTR_PROJECT_NAME, + mProjText.getText().trim()); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/launch/messages.properties b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/launch/messages.properties new file mode 100644 index 000000000..6770f2de4 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/launch/messages.properties @@ -0,0 +1,32 @@ +NdkGdbLaunchDelegate_Action_ActivityLaunch=Launching activity +NdkGdbLaunchDelegate_Action_CheckAndroidDeviceVersion=Checking Android version on device +NdkGdbLaunchDelegate_Action_KillExistingGdbServer=Killing existing gdbserver +NdkGdbLaunchDelegate_Action_LaunchHostGdb=Launching host gdb +NdkGdbLaunchDelegate_Action_LaunchingGdbServer=Launching gdbserver +NdkGdbLaunchDelegate_Action_ObtainAppAbis=Obtaining ABI's supported by the application +NdkGdbLaunchDelegate_Action_ObtainDevice=Obtaining device to use +NdkGdbLaunchDelegate_Action_ObtainDeviceABI=Obtaining ABI's supported by the device +NdkGdbLaunchDelegate_Action_PerformIncrementalBuild=Performing Incremental Build +NdkGdbLaunchDelegate_Action_SettingUpPortForward=Setting up port forwarding +NdkGdbLaunchDelegate_Action_SyncAppToDevice=Syncing application to device +NdkGdbLaunchDelegate_Action_WaitGdbServerAttach=Waiting for gdbserver to attach to process +NdkGdbLaunchDelegate_Action_WaitingForActivity=Waiting for activity to be launched +NdkGdbLaunchDelegate_LaunchError_ActivityLaunchError=Error launching activity +NdkGdbLaunchDelegate_LaunchError_Api8Needed=Native debugging requires API level 8 or above. +NdkGdbLaunchDelegate_LaunchError_CouldNotGetProject=Couldn't get project object\! +NdkGdbLaunchDelegate_LaunchError_gdbserverLaunchException=Exception while launching gdbserver: +NdkGdbLaunchDelegate_LaunchError_gdbserverOutput=gdbserver output: +NdkGdbLaunchDelegate_LaunchError_InstallError=Installation error +NdkGdbLaunchDelegate_LaunchError_InterruptedWaitingForGdbserver=Interrupted while waiting for gdbserver to attach +NdkGdbLaunchDelegate_LaunchError_NoActivityInManifest=The Manifest defines no activity\! +NdkGdbLaunchDelegate_LaunchError_NoCompatibleAbi=Unable to find a compatible ABI +NdkGdbLaunchDelegate_LaunchError_NoLauncherActivity=No launcher activity specified. Aborting launch. +NdkGdbLaunchDelegate_LaunchError_NoSuchActivity=The specified activity does not exist\! Getting the launcher activity. +NdkGdbLaunchDelegate_LaunchError_NullApk=Null APK +NdkGdbLaunchDelegate_LaunchError_ObtainingAppFolder=Error while obtaining application data folder on device +NdkGdbLaunchDelegate_LaunchError_PortForwarding=Error while setting up port forwarding +NdkGdbLaunchDelegate_LaunchError_ProjectHasErrors=Your project contains error(s), please fix them before running your application. +NdkGdbLaunchDelegate_LaunchError_PullFileError=Error while obtaining file from device +NdkGdbLaunchDelegate_LaunchError_UnableToDetectAppAbi=Unable to detect application ABI's +NdkGdbLaunchDelegate_LaunchError_UnknownAndroidDeviceVersion=Unknown Android version on device. +NdkGdbLaunchDelegate_LaunchError_VerifyIfDebugBuild=Verify if the application was built with NDK_DEBUG=1 |