diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.ndk/src')
30 files changed, 3437 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/Activator.java b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/Activator.java new file mode 100644 index 000000000..e165df1c5 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/Activator.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2011 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; + +import org.eclipse.core.runtime.FileLocator; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.ui.plugin.AbstractUIPlugin; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; + +import java.net.URL; + +/** + * The activator class controls the plug-in life cycle + */ +public class Activator extends AbstractUIPlugin { + + // The plug-in ID + public static final String PLUGIN_ID = "com.android.ide.eclipse.ndk"; //$NON-NLS-1$ + + // The shared instance + private static Activator mPlugin; + + @Override + public void start(BundleContext context) throws Exception { + super.start(context); + mPlugin = this; + } + + @Override + public void stop(BundleContext context) throws Exception { + mPlugin = null; + super.stop(context); + } + + public static Activator getDefault() { + return mPlugin; + } + + public static <T> T getService(Class<T> clazz) { + BundleContext context = mPlugin.getBundle().getBundleContext(); + ServiceReference ref = context.getServiceReference(clazz.getName()); + return (ref != null) ? (T) context.getService(ref) : null; + } + + public static Bundle getBundle(String id) { + for (Bundle bundle : mPlugin.getBundle().getBundleContext().getBundles()) { + if (bundle.getSymbolicName().equals(id)) { + return bundle; + } + } + return null; + } + + public static IStatus newStatus(Exception e) { + return new Status(IStatus.ERROR, PLUGIN_ID, e.getMessage(), e); + } + + public static void log(Exception e) { + mPlugin.getLog().log(newStatus(e)); + } + + public static URL findFile(IPath path) { + return FileLocator.find(mPlugin.getBundle(), path, null); + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/Messages.java b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/Messages.java new file mode 100644 index 000000000..cd6c4a31f --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/Messages.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2011 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; + +import org.eclipse.osgi.util.NLS; + +public class Messages extends NLS { + private static final String BUNDLE_NAME = "com.android.ide.eclipse.ndk.internal.messages"; //$NON-NLS-1$ + + public static String AddNativeWizardPage_Description; + + public static String AddNativeWizardPage_LibraryName; + + public static String AddNativeWizardPage_Location_not_valid; + + public static String AddNativeWizardPage_Title; + + public static String NDKPreferencePage_Location; + + public static String NDKPreferencePage_not_a_valid_directory; + + public static String NDKPreferencePage_not_a_valid_NDK_directory; + + public static String NDKPreferencePage_Preferences; + + public static String SetFolders_Missing_project_name; + + public static String SetFolders_No_folders; + + public static String SetFolders_Project_does_not_exist; + + public static String SimpleFile_Bad_file_operation; + + public static String SimpleFile_Bundle_not_found; + + public static String SimpleFile_Could_not_fine_source; + + public static String SimpleFile_No_project_name; + + public static String SimpleFile_Project_does_not_exist; + + 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/NativeAbi.java b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/NativeAbi.java new file mode 100644 index 000000000..3c7654266 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/NativeAbi.java @@ -0,0 +1,46 @@ +/* + * 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; + +import com.android.SdkConstants; + +public enum NativeAbi { + armeabi(SdkConstants.ABI_ARMEABI), + armeabi_v7a(SdkConstants.ABI_ARMEABI_V7A), + mips(SdkConstants.ABI_MIPS), + x86(SdkConstants.ABI_INTEL_ATOM); + + private final String mAbi; + + private NativeAbi(String abi) { + mAbi = abi; + } + + public String getAbi() { + return mAbi; + } + + public static NativeAbi getByString(String abi) { + for (NativeAbi a: values()) { + if (a.getAbi().equals(abi)) { + return a; + } + } + + throw new IllegalArgumentException("Unknown abi: " + abi); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/NdkHelper.java b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/NdkHelper.java new file mode 100644 index 000000000..8ad1f24c4 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/NdkHelper.java @@ -0,0 +1,184 @@ +/* + * 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; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.ndk.internal.launch.NdkLaunchConstants; + +import org.eclipse.cdt.core.CommandLauncher; +import org.eclipse.cdt.core.ICommandLauncher; +import org.eclipse.cdt.debug.core.ICDTLaunchConfigurationConstants; +import org.eclipse.cdt.dsf.gdb.IGDBLaunchConfigurationConstants; +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.runtime.Platform; +import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; + +@SuppressWarnings("restriction") +public class NdkHelper { + private static final String MAKE = "make"; //$NON-NLS-1$ + private static final String CORE_MAKEFILE_PATH = "/build/core/build-local.mk"; //$NON-NLS-1$ + + /** + * Obtain the ABI's the application is compatible with. + * The ABI's are obtained by reading the result of the following command: + * make --no-print-dir -f ${NdkRoot}/build/core/build-local.mk -C <project-root> DUMP_APP_ABI + */ + public static Collection<NativeAbi> getApplicationAbis(IProject project, + IProgressMonitor monitor) { + ICommandLauncher launcher = new CommandLauncher(); + launcher.setProject(project); + String[] args = new String[] { + "--no-print-dir", //$NON-NLS-1$ + "-f", //$NON-NLS-1$ + NdkManager.getNdkLocation() + CORE_MAKEFILE_PATH, + "-C", //$NON-NLS-1$ + project.getLocation().toOSString(), + "DUMP_APP_ABI", //$NON-NLS-1$ + }; + try { + launcher.execute(getPathToMake(), args, null, project.getLocation(), monitor); + } catch (CoreException e) { + AdtPlugin.printErrorToConsole(e.getLocalizedMessage()); + return Collections.emptyList(); + } + + ByteArrayOutputStream stdout = new ByteArrayOutputStream(); + ByteArrayOutputStream stderr = new ByteArrayOutputStream(); + launcher.waitAndRead(stdout, stderr, monitor); + + String abis = stdout.toString().trim(); + Set<NativeAbi> nativeAbis = EnumSet.noneOf(NativeAbi.class); + for (String abi: abis.split(" ")) { //$NON-NLS-1$ + if (abi.equals("all")) { //$NON-NLS-1$ + return EnumSet.allOf(NativeAbi.class); + } + + try { + nativeAbis.add(NativeAbi.getByString(abi)); + } catch (IllegalArgumentException e) { + AdtPlugin.printErrorToConsole(project, "Unknown Application ABI: ", abi); + } + } + + return nativeAbis; + } + + /** + * Obtain the toolchain prefix to use for given project and abi. + * The prefix is obtained by reading the result of: + * make --no-print-dir -f ${NdkRoot}/build/core/build-local.mk \ + * -C <project-root> \ + * DUMP_TOOLCHAIN_PREFIX APP_ABI=abi + */ + public static String getToolchainPrefix(IProject project, NativeAbi abi, + IProgressMonitor monitor) { + ICommandLauncher launcher = new CommandLauncher(); + launcher.setProject(project); + String[] args = new String[] { + "--no-print-dir", //$NON-NLS-1$ + "-f", //$NON-NLS-1$ + NdkManager.getNdkLocation() + CORE_MAKEFILE_PATH, + "-C", //$NON-NLS-1$ + project.getLocation().toOSString(), + "DUMP_TOOLCHAIN_PREFIX", //$NON-NLS-1$ + "APP_ABI=" + abi.getAbi(), //$NON-NLS-1$ + }; + try { + launcher.execute(getPathToMake(), args, null, project.getLocation(), monitor); + } catch (CoreException e) { + AdtPlugin.printErrorToConsole(e.getLocalizedMessage()); + return null; + } + + ByteArrayOutputStream stdout = new ByteArrayOutputStream(); + ByteArrayOutputStream stderr = new ByteArrayOutputStream(); + launcher.waitAndRead(stdout, stderr, monitor); + return stdout.toString().trim(); + } + + private static IPath getPathToMake() { + return getFullPathTo(MAKE); + } + + /** + * Obtain a path to the utilities prebuilt folder in NDK. This is typically + * "${NdkRoot}/prebuilt/<platform>/bin/". If the executable is not found, it simply returns + * the name of the executable (which is equal to assuming that it is available on the path). + */ + private static synchronized IPath getFullPathTo(String executable) { + if (Platform.getOS().equals(Platform.OS_WIN32)) { + executable += ".exe"; + } + + IPath ndkRoot = new Path(NdkManager.getNdkLocation()); + IPath prebuilt = ndkRoot.append("prebuilt"); //$NON-NLS-1$ + if (!prebuilt.toFile().exists() || !prebuilt.toFile().canRead()) { + return new Path(executable); + } + + File[] platforms = prebuilt.toFile().listFiles(); + if (platforms != null) { + for (File p: platforms) { + IPath exePath = prebuilt.append(p.getName()) + .append("bin") //$NON-NLS-1$ + .append(executable); + if (exePath.toFile().exists()) { + return exePath; + } + } + } + + return new Path(executable); + } + + public static void setLaunchConfigDefaults(ILaunchConfigurationWorkingCopy config) { + config.setAttribute(IGDBLaunchConfigurationConstants.ATTR_REMOTE_TCP, true); + config.setAttribute(NdkLaunchConstants.ATTR_NDK_GDB, NdkLaunchConstants.DEFAULT_GDB); + config.setAttribute(IGDBLaunchConfigurationConstants.ATTR_GDB_INIT, + NdkLaunchConstants.DEFAULT_GDBINIT); + config.setAttribute(IGDBLaunchConfigurationConstants.ATTR_PORT, + NdkLaunchConstants.DEFAULT_GDB_PORT); + config.setAttribute(IGDBLaunchConfigurationConstants.ATTR_HOST, "localhost"); //$NON-NLS-1$ + config.setAttribute(ICDTLaunchConfigurationConstants.ATTR_DEBUGGER_STOP_AT_MAIN, false); + config.setAttribute(ICDTLaunchConfigurationConstants.ATTR_DEBUGGER_START_MODE, + IGDBLaunchConfigurationConstants.DEBUGGER_MODE_REMOTE_ATTACH); + config.setAttribute(ICDTLaunchConfigurationConstants.ATTR_PROGRAM_NAME, + NdkLaunchConstants.DEFAULT_PROGRAM); + + config.setAttribute(ICDTLaunchConfigurationConstants.ATTR_DEBUGGER_START_MODE, + IGDBLaunchConfigurationConstants.DEBUGGER_MODE_REMOTE); + config.setAttribute(ICDTLaunchConfigurationConstants.ATTR_DEBUGGER_ID, + "gdbserver"); //$NON-NLS-1$ + + List<String> solibPaths = new ArrayList<String>(2); + solibPaths.add(NdkLaunchConstants.DEFAULT_SOLIB_PATH); + config.setAttribute(NdkLaunchConstants.ATTR_NDK_SOLIB, solibPaths); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/NdkManager.java b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/NdkManager.java new file mode 100644 index 000000000..98fccff02 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/NdkManager.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2010, 2011 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; + +import org.eclipse.cdt.core.templateengine.TemplateCore; +import org.eclipse.cdt.core.templateengine.TemplateEngine; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.SubProgressMonitor; + +import java.io.File; +import java.util.Map; + +public class NdkManager { + + public static final String NDK_LOCATION = "ndkLocation"; //$NON-NLS-1$ + + public static final String LIBRARY_NAME = "libraryName"; //$NON-NLS-1$ + + public static String getNdkLocation() { + return Activator.getDefault().getPreferenceStore().getString(NDK_LOCATION); + } + + public static boolean isNdkLocationValid() { + String location = getNdkLocation(); + if (location.length() == 0) + return false; + + return isValidNdkLocation(location); + } + + public static boolean isValidNdkLocation(String location) { + File dir = new File(location); + if (!dir.isDirectory()) + return false; + + // Must contain the ndk-build script which we call to build + if (!new File(dir, "ndk-build").isFile()) //$NON-NLS-1$ + return false; + + return true; + } + + public static void addNativeSupport(final IProject project, Map<String, String> templateArgs, + IProgressMonitor monitor) + throws CoreException { + // Launch our template to set up the project contents + TemplateCore template = TemplateEngine.getDefault().getTemplateById("AddNdkSupport"); //$NON-NLS-1$ + Map<String, String> valueStore = template.getValueStore(); + valueStore.put("projectName", project.getName()); //$NON-NLS-1$ + valueStore.putAll(templateArgs); + template.executeTemplateProcesses(monitor, false); + + // refresh project resources + project.refreshLocal(IResource.DEPTH_INFINITE, new SubProgressMonitor(monitor, 10)); + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/NdkVariables.java b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/NdkVariables.java new file mode 100644 index 000000000..0e1cd206c --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/NdkVariables.java @@ -0,0 +1,31 @@ +/* + * 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; + +/** Eclipse variables that are understood by the NDK while launching programs. */ +public class NdkVariables { + /** Variable that expands to the full path of NDK's ABI specific gdb. */ + public static final String NDK_GDB = "NdkGdb"; + + /** Variable that expands to point to the full path of the project used in the launch + * configuration. */ + public static final String NDK_PROJECT = "NdkProject"; + + /** Variable that indicates the ABI that is compatible between the device and the + * application being launched. */ + public static final String NDK_COMPAT_ABI = "NdkCompatAbi"; +} diff --git a/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/actions/AddNativeAction.java b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/actions/AddNativeAction.java new file mode 100644 index 000000000..11f65a4bf --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/actions/AddNativeAction.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2011 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.actions; + +import com.android.ide.eclipse.ndk.internal.wizards.AddNativeWizard; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.PlatformObject; +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.wizard.WizardDialog; +import org.eclipse.ui.IObjectActionDelegate; +import org.eclipse.ui.IWorkbenchPart; + +public class AddNativeAction implements IObjectActionDelegate { + + private IWorkbenchPart mPart; + private ISelection mSelection; + + @Override + public void run(IAction action) { + IProject project = null; + if (mSelection instanceof IStructuredSelection) { + IStructuredSelection ss = (IStructuredSelection) mSelection; + if (ss.size() == 1) { + Object obj = ss.getFirstElement(); + if (obj instanceof IProject) { + project = (IProject) obj; + } else if (obj instanceof PlatformObject) { + project = (IProject) ((PlatformObject) obj).getAdapter(IProject.class); + } + } + } + + if (project != null) { + AddNativeWizard wizard = new AddNativeWizard(project, mPart.getSite() + .getWorkbenchWindow()); + WizardDialog dialog = new WizardDialog(mPart.getSite().getShell(), wizard); + dialog.open(); + } + + } + + @Override + public void selectionChanged(IAction action, ISelection selection) { + mSelection = selection; + } + + @Override + public void setActivePart(IAction action, IWorkbenchPart targetPart) { + mPart = targetPart; + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/build/NdkCommandLauncher.java b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/build/NdkCommandLauncher.java new file mode 100644 index 000000000..0a1f7dc81 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/build/NdkCommandLauncher.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2011 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.build; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.ndk.internal.NdkManager; + +import org.eclipse.cdt.core.CommandLauncher; +import org.eclipse.cdt.utils.CygPath; +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.runtime.Platform; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +@SuppressWarnings("restriction") // for AdtPlugin internal classes +public class NdkCommandLauncher extends CommandLauncher { + private static CygPath sCygPath = null; + + private static final List<String> WINDOWS_NATIVE_EXECUTABLES = Arrays.asList( + "exe", //$NON-NLS-1$ + "cmd", //$NON-NLS-1$ + "bat" //$NON-NLS-1$ + ); + + static { + if (Platform.OS_WIN32.equals(Platform.getOS())) { + try { + sCygPath = new CygPath(); + } catch (IOException e) { + AdtPlugin.printErrorToConsole("Unable to launch cygpath. Is Cygwin on the path?", + e); + } + } + } + + @Override + public Process execute(IPath commandPath, String[] args, String[] env, IPath changeToDirectory, + IProgressMonitor monitor) + throws CoreException { + if (!commandPath.isAbsolute()) + commandPath = new Path(NdkManager.getNdkLocation()).append(commandPath); + + if (Platform.getOS().equals(Platform.OS_WIN32)) { + // convert cygwin paths to standard paths + if (sCygPath != null && commandPath.toString().startsWith("/cygdrive")) { //$NON-NLS-1$ + try { + String path = sCygPath.getFileName(commandPath.toString()); + commandPath = new Path(path); + } catch (IOException e) { + AdtPlugin.printErrorToConsole( + "Unexpected error while transforming cygwin path.", e); + } + } + + if (isWindowsExecutable(commandPath)) { + // append necessary extension + commandPath = appendExecutableExtension(commandPath); + } else { + // Invoke using Cygwin shell if this is not a native windows executable + String[] newargs = new String[args.length + 1]; + newargs[0] = commandPath.toOSString(); + System.arraycopy(args, 0, newargs, 1, args.length); + + commandPath = new Path("sh"); //$NON-NLS-1$ + args = newargs; + } + } + + return super.execute(commandPath, args, env, changeToDirectory, monitor); + } + + private boolean isWindowsExecutable(IPath commandPath) { + String ext = commandPath.getFileExtension(); + if (isWindowsExecutableExtension(ext)) { + return true; + } + + ext = findWindowsExecutableExtension(commandPath); + if (ext != null) { + return true; + } + + return false; + } + + private IPath appendExecutableExtension(IPath commandPath) { + if (isWindowsExecutableExtension(commandPath.getFileExtension())) { + return commandPath; + } + + String ext = findWindowsExecutableExtension(commandPath); + if (ext != null) { + return commandPath.addFileExtension(ext); + } + + return commandPath; + } + + private String findWindowsExecutableExtension(IPath command) { + for (String e: WINDOWS_NATIVE_EXECUTABLES) { + File exeFile = command.addFileExtension(e).toFile(); + if (exeFile.exists()) { + return e; + } + } + + return null; + } + + private boolean isWindowsExecutableExtension(String extension) { + return extension != null && WINDOWS_NATIVE_EXECUTABLES.contains(extension); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/build/NdkEnvSupplier.java b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/build/NdkEnvSupplier.java new file mode 100644 index 000000000..65645537f --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/build/NdkEnvSupplier.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2011 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.build; + +import org.eclipse.cdt.managedbuilder.core.IConfiguration; +import org.eclipse.cdt.managedbuilder.envvar.IBuildEnvironmentVariable; +import org.eclipse.cdt.managedbuilder.envvar.IConfigurationEnvironmentVariableSupplier; +import org.eclipse.cdt.managedbuilder.envvar.IEnvironmentVariableProvider; +import org.eclipse.core.runtime.Platform; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +public class NdkEnvSupplier implements IConfigurationEnvironmentVariableSupplier { + + private static Map<String, IBuildEnvironmentVariable> mEnvVars; + + private synchronized void init() { + if (mEnvVars != null) + return; + + mEnvVars = new HashMap<String, IBuildEnvironmentVariable>(); + + if (Platform.getOS().equals(Platform.OS_WIN32)) { + // For Windows, need to add a shell to the path + IBuildEnvironmentVariable path = new IBuildEnvironmentVariable() { + @Override + public String getName() { + return "PATH"; //$NON-NLS-1$ + } + + @Override + public String getValue() { + // I'm giving MSYS precedence over Cygwin. I'm biased that + // way :) + // TODO using the default paths for now, need smarter ways + // to get at them + // Alternatively the user can add the bin to their path + // themselves. + File bin = new File("C:\\MinGW\\msys\\1.0\\bin"); //$NON-NLS-1$ + if (bin.isDirectory()) { + return bin.getAbsolutePath(); + } + + bin = new File("C:\\cygwin\\bin"); //$NON-NLS-1$ + if (bin.isDirectory()) + return bin.getAbsolutePath(); + + return null; + } + + @Override + public int getOperation() { + return ENVVAR_PREPEND; + } + + @Override + public String getDelimiter() { + return ";"; //$NON-NLS-1$ + } + }; + if (path.getValue() != null) + mEnvVars.put(path.getName(), path); + + // Since we're using real paths, need to tell cygwin it's OK + IBuildEnvironmentVariable cygwin = new IBuildEnvironmentVariable() { + @Override + public String getName() { + return "CYGWIN"; //$NON-NLS-1$ + } + + @Override + public String getValue() { + return "nodosfilewarning"; //$NON-NLS-1$ + } + + @Override + public int getOperation() { + return ENVVAR_REPLACE; + } + + @Override + public String getDelimiter() { + return null; + } + }; + + mEnvVars.put(cygwin.getName(), cygwin); + } + } + + @Override + public IBuildEnvironmentVariable getVariable(String variableName, + IConfiguration configuration, IEnvironmentVariableProvider provider) { + init(); + return mEnvVars.get(variableName); + } + + @Override + public IBuildEnvironmentVariable[] getVariables( + IConfiguration configuration, IEnvironmentVariableProvider provider) { + init(); + return mEnvVars.values().toArray(new IBuildEnvironmentVariable[mEnvVars.size()]); + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/discovery/NdkDiscoveredPathInfo.java b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/discovery/NdkDiscoveredPathInfo.java new file mode 100644 index 000000000..83ce7f40e --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/discovery/NdkDiscoveredPathInfo.java @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2011 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.discovery; + +import com.android.ide.eclipse.ndk.internal.Activator; + +import org.eclipse.cdt.core.CCorePlugin; +import org.eclipse.cdt.core.model.CoreModel; +import org.eclipse.cdt.make.core.scannerconfig.IDiscoveredPathManager.IDiscoveredPathInfo; +import org.eclipse.cdt.make.core.scannerconfig.IDiscoveredPathManager.IDiscoveredScannerInfoSerializable; +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 java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +public class NdkDiscoveredPathInfo implements IDiscoveredPathInfo { + + private final IProject mProject; + private long mLastUpdate = IFile.NULL_STAMP; + private IPath[] mIncludePaths; + private Map<String, String> mSymbols; + private boolean mNeedReindexing = false; + private static final IPath ANDROID_MK = new Path("jni/Android.mk"); + + // Keys for preferences + public static final String LAST_UPDATE = "lastUpdate"; //$NON-NLS-1$ + + public NdkDiscoveredPathInfo(IProject project) { + this.mProject = project; + load(); + } + + @Override + public IProject getProject() { + return mProject; + } + + @Override + public IPath[] getIncludePaths() { + if (mNeedReindexing) { + // Call for a reindex + // TODO this is probably a bug. a new include path should trigger + // reindexing anyway, no? + // BTW, can't do this in the update since the indexer runs before + // this gets called + CCorePlugin.getIndexManager().reindex(CoreModel.getDefault().create(mProject)); + mNeedReindexing = false; + } + return mIncludePaths; + } + + void setIncludePaths(List<String> pathStrings) { + mIncludePaths = new IPath[pathStrings.size()]; + int i = 0; + for (String path : pathStrings) + mIncludePaths[i++] = new Path(path); + mNeedReindexing = true; + } + + @Override + public Map<String, String> getSymbols() { + if (mSymbols == null) + mSymbols = new HashMap<String, String>(); + return mSymbols; + } + + void setSymbols(Map<String, String> symbols) { + this.mSymbols = symbols; + } + + @Override + public IDiscoveredScannerInfoSerializable getSerializable() { + return null; + } + + public void update(IProgressMonitor monitor) throws CoreException { + if (!needUpdating()) + return; + + new NdkDiscoveryUpdater(this).runUpdate(monitor); + + if (mIncludePaths != null && mSymbols != null) { + recordUpdate(); + save(); + } + } + + private boolean needUpdating() { + if (mLastUpdate == IFile.NULL_STAMP) + return true; + return mProject.getFile(ANDROID_MK).getLocalTimeStamp() > mLastUpdate; + } + + private void recordUpdate() { + mLastUpdate = mProject.getFile(ANDROID_MK).getLocalTimeStamp(); + } + + public void delete() { + mLastUpdate = IFile.NULL_STAMP; + } + + private File getInfoFile() { + File stateLoc = Activator.getDefault().getStateLocation().toFile(); + return new File(stateLoc, mProject.getName() + ".pathInfo"); //$NON-NLS-1$ + } + + private void save() { + try { + File infoFile = getInfoFile(); + infoFile.getParentFile().mkdirs(); + PrintStream out = new PrintStream(infoFile); + + // timestamp + out.print("t,"); //$NON-NLS-1$ + out.print(mLastUpdate); + out.println(); + + for (IPath include : mIncludePaths) { + out.print("i,"); //$NON-NLS-1$ + out.print(include.toPortableString()); + out.println(); + } + + for (Entry<String, String> symbol : mSymbols.entrySet()) { + out.print("d,"); //$NON-NLS-1$ + out.print(symbol.getKey()); + out.print(","); //$NON-NLS-1$ + out.print(symbol.getValue()); + out.println(); + } + + out.close(); + } catch (IOException e) { + Activator.log(e); + } + + } + + private void load() { + try { + File infoFile = getInfoFile(); + if (!infoFile.exists()) + return; + + long timestamp = IFile.NULL_STAMP; + List<IPath> includes = new ArrayList<IPath>(); + Map<String, String> defines = new HashMap<String, String>(); + + BufferedReader reader = new BufferedReader(new FileReader(infoFile)); + for (String line = reader.readLine(); line != null; line = reader.readLine()) { + switch (line.charAt(0)) { + case 't': + timestamp = Long.valueOf(line.substring(2)); + break; + case 'i': + includes.add(Path.fromPortableString(line.substring(2))); + break; + case 'd': + int n = line.indexOf(',', 2); + if (n == -1) + defines.put(line.substring(2), ""); //$NON-NLS-1$ + else + defines.put(line.substring(2, n), line.substring(n + 1)); + break; + } + } + reader.close(); + + mLastUpdate = timestamp; + mIncludePaths = includes.toArray(new IPath[includes.size()]); + mSymbols = defines; + } catch (IOException e) { + Activator.log(e); + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/discovery/NdkDiscoveryUpdater.java b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/discovery/NdkDiscoveryUpdater.java new file mode 100644 index 000000000..a6b88f462 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/discovery/NdkDiscoveryUpdater.java @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2011 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.discovery; + +import com.android.ide.eclipse.ndk.internal.Activator; +import com.android.ide.eclipse.ndk.internal.build.NdkCommandLauncher; + +import org.eclipse.cdt.core.CCorePlugin; +import org.eclipse.cdt.core.envvar.IEnvironmentVariable; +import org.eclipse.cdt.core.envvar.IEnvironmentVariableManager; +import org.eclipse.cdt.core.settings.model.ICConfigurationDescription; +import org.eclipse.cdt.managedbuilder.core.IBuilder; +import org.eclipse.cdt.managedbuilder.core.IManagedBuildInfo; +import org.eclipse.cdt.managedbuilder.core.ManagedBuildManager; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.FileLocator; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.Path; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +public class NdkDiscoveryUpdater { + private final NdkDiscoveredPathInfo mPathInfo; + private final IProject mProject; + + private boolean mCPlusPlus = false; + private String mCommand; + private List<String> mArguments = new ArrayList<String>(); + + public NdkDiscoveryUpdater(NdkDiscoveredPathInfo pathInfo) { + mPathInfo = pathInfo; + mProject = pathInfo.getProject(); + } + + public void runUpdate(IProgressMonitor monitor) throws CoreException { + try { + // Run ndk-build -nB to get the list of commands + IPath commandPath = new Path("ndk-build"); //$NON-NLS-1$ + String[] args = { + "-nB"}; //$NON-NLS-1$ + String[] env = calcEnvironment(); + File projectDir = new File(mProject.getLocationURI()); + IPath changeToDirectory = new Path(projectDir.getAbsolutePath()); + Process proc = new NdkCommandLauncher().execute(commandPath, args, env, + changeToDirectory, monitor); + if (proc == null) + // proc failed to start + return; + BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream())); + String line = reader.readLine(); + while (line != null) { + checkBuildLine(line); + line = reader.readLine(); + } + + if (mCommand == null) { + return; + } + + // Run the unique commands with special gcc options to extract the + // symbols and paths + // -E -P -v -dD + mArguments.add("-E"); //$NON-NLS-1$ + mArguments.add("-P"); //$NON-NLS-1$ + mArguments.add("-v"); //$NON-NLS-1$ + mArguments.add("-dD"); //$NON-NLS-1$ + + URL url = Activator.findFile(new Path( + "discovery/" + (mCPlusPlus ? "test.cpp" : "test.c"))); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + File testFile = new File(FileLocator.toFileURL(url).toURI()); + String testFileName = testFile.getAbsolutePath().replace('\\', '/'); + mArguments.add(testFileName); + + args = mArguments.toArray(new String[mArguments.size()]); + proc = new NdkCommandLauncher().execute(new Path(mCommand), args, env, + changeToDirectory, monitor); + // Error stream has the includes + final InputStream errStream = proc.getErrorStream(); + new Thread() { + @Override + public void run() { + checkIncludes(errStream); + }; + }.start(); + + // Input stream has the defines + checkDefines(proc.getInputStream()); + } catch (IOException e) { + throw new CoreException(Activator.newStatus(e)); + } catch (URISyntaxException e) { + throw new CoreException(Activator.newStatus(e)); + } + } + + private String[] calcEnvironment() throws CoreException { + IManagedBuildInfo info = ManagedBuildManager.getBuildInfo(mProject); + IBuilder builder = info.getDefaultConfiguration().getBuilder(); + HashMap<String, String> envMap = new HashMap<String, String>(); + if (builder.appendEnvironment()) { + ICConfigurationDescription cfgDes = ManagedBuildManager + .getDescriptionForConfiguration(builder.getParent().getParent()); + IEnvironmentVariableManager mngr = CCorePlugin.getDefault() + .getBuildEnvironmentManager(); + IEnvironmentVariable[] vars = mngr.getVariables(cfgDes, true); + for (IEnvironmentVariable var : vars) { + envMap.put(var.getName(), var.getValue()); + } + } + // Add variables from build info + Map<String, String> builderEnv = builder.getExpandedEnvironment(); + if (builderEnv != null) + envMap.putAll(builderEnv); + List<String> strings = new ArrayList<String>(envMap.size()); + for (Entry<String, String> entry : envMap.entrySet()) { + StringBuffer buffer = new StringBuffer(entry.getKey()); + buffer.append('=').append(entry.getValue()); + strings.add(buffer.toString()); + } + return strings.toArray(new String[strings.size()]); + } + + private static class Line { + private final String line; + private int pos; + + public Line(String line) { + this.line = line; + } + + public Line(String line, int pos) { + this(line); + this.pos = pos; + } + + public String getToken() { + skipWhiteSpace(); + if (pos == line.length()) + return null; + + int start = pos; + boolean inQuote = false; + + while (true) { + char c = line.charAt(pos); + if (c == ' ') { + if (!inQuote) + return line.substring(start, pos); + } else if (c == '"') { + inQuote = !inQuote; + } + + if (++pos == line.length()) + return null; + } + + } + + private String getRemaining() { + if (pos == line.length()) + return null; + + skipWhiteSpace(); + String rc = line.substring(pos); + pos = line.length(); + return rc; + } + + private void skipWhiteSpace() { + while (true) { + if (pos == line.length()) + return; + char c = line.charAt(pos); + if (c == ' ') + pos++; + else + return; + } + } + } + + private void checkBuildLine(String text) { + Line line = new Line(text); + String cmd = line.getToken(); + if (cmd == null) { + return; + } else if (cmd.endsWith("g++")) { //$NON-NLS-1$ + if (mCommand == null || !mCPlusPlus) { + mCommand = cmd; + mCPlusPlus = true; + } + gatherOptions(line); + } else if (cmd.endsWith("gcc")) { //$NON-NLS-1$ + if (mCommand == null) + mCommand = cmd; + gatherOptions(line); + } + } + + private void gatherOptions(Line line) { + for (String option = line.getToken(); option != null; option = line.getToken()) { + if (option.startsWith("-")) { //$NON-NLS-1$ + // only look at options + if (option.equals("-I")) { //$NON-NLS-1$ + String dir = line.getToken(); + if (dir != null) + addArg(option + dir); + } else if (option.startsWith("-I")) { //$NON-NLS-1$ + addArg(option); + } else if (option.equals("-D")) { //$NON-NLS-1$ + String def = line.getToken(); + if (def != null) + addArg(option + def); + } else if (option.startsWith("-D")) { //$NON-NLS-1$ + addArg(option); + } else if (option.startsWith("-f")) { //$NON-NLS-1$ + addArg(option); + } else if (option.startsWith("-m")) { //$NON-NLS-1$ + addArg(option); + } else if (option.startsWith("--sysroot")) { //$NON-NLS-1$ + addArg(option); + } + } + } + } + + private void addArg(String arg) { + if (!mArguments.contains(arg)) + mArguments.add(arg); + } + + private void checkIncludes(InputStream in) { + try { + List<String> includes = new ArrayList<String>(); + boolean inIncludes1 = false; + boolean inIncludes2 = false; + BufferedReader reader = new BufferedReader(new InputStreamReader(in)); + String line = reader.readLine(); + while (line != null) { + if (!inIncludes1) { + if (line.equals("#include \"...\" search starts here:")) //$NON-NLS-1$ + inIncludes1 = true; + } else { + if (!inIncludes2) { + if (line.equals("#include <...> search starts here:")) //$NON-NLS-1$ + inIncludes2 = true; + else + includes.add(line.trim()); + } else { + if (line.equals("End of search list.")) { //$NON-NLS-1$ + mPathInfo.setIncludePaths(includes); + } else { + includes.add(line.trim()); + } + } + } + line = reader.readLine(); + } + } catch (IOException e) { + Activator.log(e); + } + } + + private void checkDefines(InputStream in) { + try { + Map<String, String> defines = new HashMap<String, String>(); + BufferedReader reader = new BufferedReader(new InputStreamReader(in)); + String line = reader.readLine(); + while (line != null) { + if (line.startsWith("#define")) { //$NON-NLS-1$ + Line l = new Line(line, 7); + String var = l.getToken(); + if (var == null) + continue; + String value = l.getRemaining(); + if (value == null) + value = ""; //$NON-NLS-1$ + defines.put(var, value); + } + line = reader.readLine(); + } + mPathInfo.setSymbols(defines); + } catch (IOException e) { + Activator.log(e); + } + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/discovery/NdkScannerInfoCollector.java b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/discovery/NdkScannerInfoCollector.java new file mode 100644 index 000000000..29f3e7f09 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/discovery/NdkScannerInfoCollector.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2011 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.discovery; + +import org.eclipse.cdt.make.core.scannerconfig.IDiscoveredPathManager.IDiscoveredPathInfo; +import org.eclipse.cdt.make.core.scannerconfig.IScannerInfoCollector3; +import org.eclipse.cdt.make.core.scannerconfig.IScannerInfoCollectorCleaner; +import org.eclipse.cdt.make.core.scannerconfig.InfoContext; +import org.eclipse.cdt.make.core.scannerconfig.ScannerInfoTypes; +import org.eclipse.cdt.managedbuilder.scannerconfig.IManagedScannerInfoCollector; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; + +import java.util.List; +import java.util.Map; + +public class NdkScannerInfoCollector implements IScannerInfoCollector3, + IScannerInfoCollectorCleaner, IManagedScannerInfoCollector { + + private NdkDiscoveredPathInfo mPathInfo; + + @Override + public void contributeToScannerConfig(Object resource, Map scannerInfo) { + throw new Error("Not implemented"); //$NON-NLS-1$ + } + + @Override + public List getCollectedScannerInfo(Object resource, ScannerInfoTypes type) { + throw new Error("Not implemented"); //$NON-NLS-1$ + } + + @Override + public void setProject(IProject project) { + throw new Error("Not implemented"); //$NON-NLS-1$ + } + + @Override + public void updateScannerConfiguration(IProgressMonitor monitor) throws CoreException { + mPathInfo.update(monitor); + } + + @Override + public IDiscoveredPathInfo createPathInfoObject() { + return mPathInfo; + } + + @Override + public Map<String, String> getDefinedSymbols() { + throw new Error("Not implemented"); //$NON-NLS-1$ + } + + @Override + public List getIncludePaths() { + throw new Error("Not implemented"); //$NON-NLS-1$ + } + + @Override + public void setInfoContext(InfoContext context) { + mPathInfo = new NdkDiscoveredPathInfo(context.getProject()); + } + + @Override + public void deleteAllPaths(IResource resource) { + throw new Error("Not implemented"); //$NON-NLS-1$ + } + + @Override + public void deleteAllSymbols(IResource resource) { + throw new Error("Not implemented"); //$NON-NLS-1$ + } + + @Override + public void deletePath(IResource resource, String path) { + throw new Error("Not implemented"); //$NON-NLS-1$ + } + + @Override + public void deleteSymbol(IResource resource, String symbol) { + throw new Error("Not implemented"); //$NON-NLS-1$ + } + + @Override + public void deleteAll(IResource resource) { + mPathInfo.delete(); + } + +} 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 diff --git a/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/messages.properties b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/messages.properties new file mode 100644 index 000000000..63400ffc7 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/messages.properties @@ -0,0 +1,16 @@ +AddNativeWizardPage_Description=Settings for generated native components for project. +AddNativeWizardPage_LibraryName=Library Name: +AddNativeWizardPage_Location_not_valid=NDK location not valid in preferences. +AddNativeWizardPage_Title=Add Android Native Support +NDKPreferencePage_Location=NDK Location +NDKPreferencePage_not_a_valid_directory=Not a valid directory +NDKPreferencePage_not_a_valid_NDK_directory=Not a valid NDK directory +NDKPreferencePage_Preferences=Android NDK Preferences +SetFolders_Missing_project_name=Missing project name +SetFolders_No_folders=No folders +SetFolders_Project_does_not_exist=Project does not exist +SimpleFile_Bad_file_operation=bad file operation +SimpleFile_Bundle_not_found=bundle not found +SimpleFile_Could_not_fine_source=could not find source file: +SimpleFile_No_project_name=no project name +SimpleFile_Project_does_not_exist=project does not exist diff --git a/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/preferences/NdkPreferenceInitializer.java b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/preferences/NdkPreferenceInitializer.java new file mode 100644 index 000000000..9c0350517 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/preferences/NdkPreferenceInitializer.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2011 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.preferences; + +import com.android.ide.eclipse.ndk.internal.Activator; +import com.android.ide.eclipse.ndk.internal.NdkManager; + +import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer; +import org.eclipse.jface.preference.IPreferenceStore; + +public class NdkPreferenceInitializer extends AbstractPreferenceInitializer { + + @Override + public void initializeDefaultPreferences() { + IPreferenceStore store = Activator.getDefault().getPreferenceStore(); + + store.setDefault(NdkManager.NDK_LOCATION, ""); //$NON-NLS-1$ + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/preferences/NdkPreferencePage.java b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/preferences/NdkPreferencePage.java new file mode 100644 index 000000000..e1ab24768 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/preferences/NdkPreferencePage.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2011 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.preferences; + +import com.android.ide.eclipse.ndk.internal.Activator; +import com.android.ide.eclipse.ndk.internal.Messages; +import com.android.ide.eclipse.ndk.internal.NdkManager; + +import org.eclipse.jface.preference.DirectoryFieldEditor; +import org.eclipse.jface.preference.FieldEditorPreferencePage; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPreferencePage; + +public class NdkPreferencePage extends FieldEditorPreferencePage implements + IWorkbenchPreferencePage { + + private NdkDirectoryFieldEditor mNdkDirectoryEditor; + + public NdkPreferencePage() { + super(GRID); + setPreferenceStore(Activator.getDefault().getPreferenceStore()); + setDescription(Messages.NDKPreferencePage_Preferences); + } + + @Override + protected void createFieldEditors() { + mNdkDirectoryEditor = new NdkDirectoryFieldEditor(NdkManager.NDK_LOCATION, + Messages.NDKPreferencePage_Location, getFieldEditorParent()); + addField(mNdkDirectoryEditor); + } + + private static class NdkDirectoryFieldEditor extends DirectoryFieldEditor { + public NdkDirectoryFieldEditor(String name, String labelText, Composite parent) { + super(name, labelText, parent); + setEmptyStringAllowed(true); + } + + @Override + protected boolean doCheckState() { + if (!super.doCheckState()) { + setErrorMessage(Messages.NDKPreferencePage_not_a_valid_directory); + return false; + } + + String dirname = getTextControl().getText().trim(); + if (!dirname.isEmpty() && !NdkManager.isValidNdkLocation(dirname)) { + setErrorMessage(Messages.NDKPreferencePage_not_a_valid_NDK_directory); + return false; + } + + return true; + } + + @Override + public Text getTextControl(Composite parent) { + setValidateStrategy(VALIDATE_ON_KEY_STROKE); + return super.getTextControl(parent); + } + + } + + @Override + public void init(IWorkbench workbench) { + // Nothing to do herea + } + + @Override + public void dispose() { + super.dispose(); + + if (mNdkDirectoryEditor != null) { + mNdkDirectoryEditor.dispose(); + mNdkDirectoryEditor = null; + } + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/templates/SetFolders.java b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/templates/SetFolders.java new file mode 100644 index 000000000..2e8f714bb --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/templates/SetFolders.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2011 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.templates; + +import com.android.ide.eclipse.ndk.internal.Messages; + +import org.eclipse.cdt.core.CCorePlugin; +import org.eclipse.cdt.core.model.CoreModel; +import org.eclipse.cdt.core.model.ICProject; +import org.eclipse.cdt.core.model.IPathEntry; +import org.eclipse.cdt.core.templateengine.TemplateCore; +import org.eclipse.cdt.core.templateengine.process.ProcessArgument; +import org.eclipse.cdt.core.templateengine.process.ProcessFailureException; +import org.eclipse.cdt.core.templateengine.process.ProcessRunner; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.Path; + +import java.util.ArrayList; +import java.util.List; + +public class SetFolders extends ProcessRunner { + + @Override + public void process(TemplateCore template, ProcessArgument[] args, String processId, + IProgressMonitor monitor) + throws ProcessFailureException { + String projectName = null; + String[] sourceFolders = null; + String[] outputFolders = null; + + for (ProcessArgument arg : args) { + String argName = arg.getName(); + if (argName.equals("projectName")) { //$NON-NLS-1$ + projectName = arg.getSimpleValue(); + } else if (argName.equals("sourceFolders")) { //$NON-NLS-1$ + sourceFolders = arg.getSimpleArrayValue(); + } else if (argName.equals("outputFolders")) { //$NON-NLS-1$ + outputFolders = arg.getSimpleArrayValue(); + } + } + + // Get the project + if (projectName == null) + throw new ProcessFailureException(Messages.SetFolders_Missing_project_name); + + IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName); + if (!project.exists()) + throw new ProcessFailureException(Messages.SetFolders_Project_does_not_exist); + + // Create the folders + if (sourceFolders == null && outputFolders == null) + throw new ProcessFailureException(Messages.SetFolders_No_folders); + + try { + // Add them in + ICProject cproject = CCorePlugin.getDefault().getCoreModel().create(project); + IPathEntry[] pathEntries = cproject.getRawPathEntries(); + List<IPathEntry> newEntries = new ArrayList<IPathEntry>(pathEntries.length); + for (IPathEntry pathEntry : pathEntries) { + // remove the old source and output entries + if (pathEntry.getEntryKind() != IPathEntry.CDT_SOURCE + && pathEntry.getEntryKind() != IPathEntry.CDT_OUTPUT) { + newEntries.add(pathEntry); + } + } + if (sourceFolders != null) + for (String sourceFolder : sourceFolders) { + IFolder folder = project.getFolder(new Path(sourceFolder)); + if (!folder.exists()) + folder.create(true, true, monitor); + newEntries.add(CoreModel.newSourceEntry(folder.getFullPath())); + } + if (outputFolders != null) + for (String outputFolder : outputFolders) { + IFolder folder = project.getFolder(new Path(outputFolder)); + if (!folder.exists()) + folder.create(true, true, monitor); + newEntries.add(CoreModel.newOutputEntry(folder.getFullPath())); + } + cproject.setRawPathEntries(newEntries.toArray(new IPathEntry[newEntries.size()]), + monitor); + } catch (CoreException e) { + throw new ProcessFailureException(e); + } + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/templates/SimpleFile.java b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/templates/SimpleFile.java new file mode 100644 index 000000000..7f249cacb --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/templates/SimpleFile.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2011 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.templates; + +import com.android.ide.eclipse.ndk.internal.Activator; +import com.android.ide.eclipse.ndk.internal.Messages; + +import org.eclipse.cdt.core.templateengine.TemplateCore; +import org.eclipse.cdt.core.templateengine.process.ProcessArgument; +import org.eclipse.cdt.core.templateengine.process.ProcessFailureException; +import org.eclipse.cdt.core.templateengine.process.ProcessRunner; +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.FileLocator; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.Path; +import org.osgi.framework.Bundle; + +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +public class SimpleFile extends ProcessRunner { + + private static final class FileOp { + public String source; + public String destination; + } + + @Override + public void process(TemplateCore template, ProcessArgument[] args, String processId, + IProgressMonitor monitor) + throws ProcessFailureException { + + // Fetch the args + String projectName = null; + List<FileOp> fileOps = new ArrayList<FileOp>(); + + for (ProcessArgument arg : args) { + if (arg.getName().equals("projectName")) //$NON-NLS-1$ + projectName = arg.getSimpleValue(); + else if (arg.getName().equals("files")) { //$NON-NLS-1$ + ProcessArgument[][] files = arg.getComplexArrayValue(); + for (ProcessArgument[] file : files) { + FileOp op = new FileOp(); + for (ProcessArgument fileArg : file) { + if (fileArg.getName().equals("source")) //$NON-NLS-1$ + op.source = fileArg.getSimpleValue(); + else if (fileArg.getName().equals("destination")) //$NON-NLS-1$ + op.destination = fileArg.getSimpleValue(); + } + if (op.source == null || op.destination == null) + throw new ProcessFailureException(Messages.SimpleFile_Bad_file_operation); + fileOps.add(op); + } + } + } + + if (projectName == null) + throw new ProcessFailureException(Messages.SimpleFile_No_project_name); + IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName); + if (!project.exists()) + throw new ProcessFailureException(Messages.SimpleFile_Project_does_not_exist); + + // Find bundle to find source files + Bundle bundle = Activator.getBundle(template.getTemplateInfo().getPluginId()); + if (bundle == null) + throw new ProcessFailureException(Messages.SimpleFile_Bundle_not_found); + + try { + for (FileOp op : fileOps) { + IFile destFile = project.getFile(new Path(op.destination)); + if (destFile.exists()) + // don't overwrite files if they exist already + continue; + + // Make sure parent folders are created + mkDirs(project, destFile.getParent(), monitor); + + URL sourceURL = FileLocator.find(bundle, new Path(op.source), null); + if (sourceURL == null) + throw new ProcessFailureException(Messages.SimpleFile_Could_not_fine_source + + op.source); + + TemplatedInputStream in = new TemplatedInputStream(sourceURL.openStream(), + template.getValueStore()); + destFile.create(in, true, monitor); + in.close(); + } + } catch (IOException e) { + throw new ProcessFailureException(e); + } catch (CoreException e) { + throw new ProcessFailureException(e); + } + + } + + private void mkDirs(IProject project, IContainer container, IProgressMonitor monitor) + throws CoreException { + if (container.exists()) + return; + mkDirs(project, container.getParent(), monitor); + ((IFolder) container).create(true, true, monitor); + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/templates/TemplatedInputStream.java b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/templates/TemplatedInputStream.java new file mode 100644 index 000000000..129caa327 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/templates/TemplatedInputStream.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2011 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.templates; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; + +/** + * Reads from a template substituting marked values from the supplied Map. + */ +public class TemplatedInputStream extends InputStream { + + private final InputStream mIn; + private final Map<String, String> mMap; + private char[] mSub; + private int mPos; + private int mMark; + + public TemplatedInputStream(InputStream in, Map<String, String> map) { + this.mIn = in; + this.mMap = map; + } + + @Override + public int read() throws IOException { + // if from a mark, return the char + if (mMark != 0) { + int c = mMark; + mMark = 0; + return c; + } + + // return char from sub layer if available + if (mSub != null) { + char c = mSub[mPos++]; + if (mPos >= mSub.length) + mSub = null; + return c; + } + + int c = mIn.read(); + if (c == '%') { + // check if it's a sub + c = mIn.read(); + if (c == '{') { + // it's a sub + StringBuffer buff = new StringBuffer(); + for (c = mIn.read(); c != '}' && c >= 0; c = mIn.read()) + buff.append((char) c); + String str = mMap.get(buff.toString()); + if (str != null) { + mSub = str.toCharArray(); + mPos = 0; + } + return read(); // recurse to get the real char + } else { + // not a sub + mMark = c; + return '%'; + } + } + + return c; + } + + @Override + public void close() throws IOException { + super.close(); + mIn.close(); + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/wizards/AddNativeWizard.java b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/wizards/AddNativeWizard.java new file mode 100644 index 000000000..b3675ed27 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/wizards/AddNativeWizard.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2011 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.wizards; + +import com.android.ide.eclipse.ndk.internal.Activator; +import com.android.ide.eclipse.ndk.internal.NdkManager; + +import org.eclipse.cdt.core.CCorePlugin; +import org.eclipse.cdt.make.core.MakeCorePlugin; +import org.eclipse.cdt.ui.CUIPlugin; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IWorkspace; +import org.eclipse.core.resources.IWorkspaceRunnable; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.jface.wizard.Wizard; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.WorkbenchException; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +public class AddNativeWizard extends Wizard { + + private final IProject mProject; + private final IWorkbenchWindow mWindow; + + private AddNativeWizardPage mAddNativeWizardPage; + private Map<String, String> mTemplateArgs = new HashMap<String, String>(); + + public AddNativeWizard(IProject project, IWorkbenchWindow window) { + mProject = project; + mWindow = window; + mTemplateArgs.put(NdkManager.LIBRARY_NAME, project.getName()); + } + + @Override + public void addPages() { + mAddNativeWizardPage = new AddNativeWizardPage(mTemplateArgs); + addPage(mAddNativeWizardPage); + } + + @Override + public boolean performFinish() { + // Switch to C/C++ Perspective + try { + mWindow.getWorkbench().showPerspective(CUIPlugin.ID_CPERSPECTIVE, mWindow); + } catch (WorkbenchException e1) { + Activator.log(e1); + } + + mAddNativeWizardPage.updateArgs(mTemplateArgs); + + IRunnableWithProgress op = new IRunnableWithProgress() { + @Override + public void run(IProgressMonitor monitor) throws InvocationTargetException, + InterruptedException { + IWorkspaceRunnable op1 = new IWorkspaceRunnable() { + @Override + public void run(IProgressMonitor monitor1) throws CoreException { + // Convert to CDT project + CCorePlugin.getDefault().convertProjectToCC(mProject, monitor1, + MakeCorePlugin.MAKE_PROJECT_ID); + // Set up build information + new NdkWizardHandler().convertProject(mProject, monitor1); + + // When using CDT 8.1.x, disable the language settings provider mechanism + // for scanner discovery. Use the classloader to load the class since it + // will not be available pre 8.1. + try { + @SuppressWarnings("rawtypes") + Class c = getClass().getClassLoader().loadClass( + "org.eclipse.cdt.core.language.settings.providers.ScannerDiscoveryLegacySupport"); //$NON-NLS-1$ + + @SuppressWarnings("unchecked") + Method m = c.getMethod( + "setLanguageSettingsProvidersFunctionalityEnabled", //$NON-NLS-1$ + IProject.class, boolean.class); + + m.invoke(null, mProject, false); + } catch (Exception e) { + // ignore all exceptions: On pre 8.1.x CDT, this class will not be + // found, but this options only needs to be set in 8.1.x + } + + // Run the template + NdkManager.addNativeSupport(mProject, mTemplateArgs, monitor1); + } + }; + // TODO run from a job + IWorkspace workspace = ResourcesPlugin.getWorkspace(); + try { + workspace.run(op1, workspace.getRoot(), 0, new NullProgressMonitor()); + } catch (CoreException e) { + throw new InvocationTargetException(e); + } + } + }; + try { + getContainer().run(false, true, op); + return true; + } catch (InterruptedException e) { + Activator.log(e); + return false; + } catch (InvocationTargetException e) { + Activator.log(e); + return false; + } + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/wizards/AddNativeWizardPage.java b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/wizards/AddNativeWizardPage.java new file mode 100644 index 000000000..65af270b0 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/wizards/AddNativeWizardPage.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2011 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.wizards; + +import com.android.ide.eclipse.ndk.internal.Messages; +import com.android.ide.eclipse.ndk.internal.NdkManager; + +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; + +import java.util.Map; + +public class AddNativeWizardPage extends WizardPage { + + private final String defaultLibraryName; + + private Text libraryNameText; + + public AddNativeWizardPage(Map<String, String> templateArgs) { + super("addNativeWizardPage"); //$NON-NLS-1$ + setDescription(Messages.AddNativeWizardPage_Description); + setTitle(Messages.AddNativeWizardPage_Title); + + defaultLibraryName = templateArgs.get(NdkManager.LIBRARY_NAME); + if (!NdkManager.isNdkLocationValid()) { + setErrorMessage(Messages.AddNativeWizardPage_Location_not_valid); + } + } + + @Override + public boolean isPageComplete() { + return NdkManager.isNdkLocationValid(); + } + + @Override + public void createControl(Composite parent) { + Composite container = new Composite(parent, SWT.NULL); + setControl(container); + container.setLayout(new GridLayout(2, false)); + + Label lblLibraryName = new Label(container, SWT.NONE); + lblLibraryName.setText(Messages.AddNativeWizardPage_LibraryName); + + Composite composite = new Composite(container, SWT.NONE); + composite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); + composite.setLayout(new GridLayout(3, false)); + + Label lblLib = new Label(composite, SWT.NONE); + lblLib.setText("lib"); //$NON-NLS-1$ + + libraryNameText = new Text(composite, SWT.BORDER); + libraryNameText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); + libraryNameText.setText(defaultLibraryName); + + Label lblso = new Label(composite, SWT.NONE); + lblso.setText(".so"); //$NON-NLS-1$ + } + + public void updateArgs(Map<String, String> templateArgs) { + templateArgs.put(NdkManager.LIBRARY_NAME, libraryNameText.getText()); + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/wizards/NdkWizardHandler.java b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/wizards/NdkWizardHandler.java new file mode 100644 index 000000000..fa0b92bb0 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.ndk/src/com/android/ide/eclipse/ndk/internal/wizards/NdkWizardHandler.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2011 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.wizards; + +import org.eclipse.cdt.managedbuilder.core.IToolChain; +import org.eclipse.cdt.managedbuilder.core.ManagedBuildManager; +import org.eclipse.cdt.managedbuilder.ui.wizards.STDWizardHandler; + +public class NdkWizardHandler extends STDWizardHandler { + + public NdkWizardHandler() { + super(null, null); + } + + @Override + public IToolChain[] getSelectedToolChains() { + IToolChain[] tcs = ManagedBuildManager.getRealToolChains(); + for (IToolChain tc : tcs) { + if (tc.getId().equals("com.android.toolchain.gcc")) //$NON-NLS-1$ + return new IToolChain[] { + tc + }; + } + return super.getSelectedToolChains(); + } + +} |