diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/AddSupportJarAction.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/AddSupportJarAction.java | 626 |
1 files changed, 626 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/AddSupportJarAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/AddSupportJarAction.java new file mode 100644 index 000000000..f0af8bbd2 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/AddSupportJarAction.java @@ -0,0 +1,626 @@ +/* + * 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.adt.internal.actions; + +import com.android.SdkConstants; +import com.android.annotations.Nullable; +import com.android.ide.eclipse.adt.AdtConstants; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.AdtUtils; +import com.android.ide.eclipse.adt.internal.sdk.AdtConsoleSdkLog; +import com.android.ide.eclipse.adt.internal.sdk.ProjectState; +import com.android.ide.eclipse.adt.internal.sdk.Sdk; +import com.android.sdklib.SdkManager; +import com.android.sdklib.internal.project.ProjectProperties; +import com.android.sdklib.internal.project.ProjectProperties.PropertyType; +import com.android.sdklib.internal.project.ProjectPropertiesWorkingCopy; +import com.android.sdklib.io.FileOp; +import com.android.sdkuilib.internal.repository.ui.AdtUpdateDialog; +import com.android.utils.NullLogger; +import com.android.utils.Pair; + +import org.eclipse.core.filesystem.EFS; +import org.eclipse.core.filesystem.IFileStore; +import org.eclipse.core.filesystem.IFileSystem; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IProjectDescription; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IWorkspace; +import org.eclipse.core.resources.IWorkspaceRoot; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.ui.IObjectActionDelegate; +import org.eclipse.ui.IWorkbenchPart; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.IWorkbenchWindowActionDelegate; + +import java.io.File; +import java.io.IOException; +import java.util.Iterator; +import java.util.Map; + +/** + * An action to add the android-support-v4.jar support library + * to the selected project. + * <p/> + * This should be used by the GLE. The action itself is currently more + * like an example of how to invoke the new {@link AdtUpdateDialog}. + * <p/> + * TODO: make this more configurable. + */ +public class AddSupportJarAction implements IObjectActionDelegate { + + /** The vendor ID of the support library. */ + private static final String VENDOR_ID = "android"; //$NON-NLS-1$ + /** The path ID of the support library. */ + private static final String SUPPORT_ID = "support"; //$NON-NLS-1$ + /** The path ID of the compatibility library (which was its id for releases 1-3). */ + private static final String COMPATIBILITY_ID = "compatibility"; //$NON-NLS-1$ + private static final String FD_GRIDLAYOUT = "gridlayout"; //$NON-NLS-1$ + private static final String FD_V7 = "v7"; //$NON-NLS-1$ + private static final String FD_V4 = "v4"; //$NON-NLS-1$ + private static final String FD_V13 = "v13"; //$NON-NLS-1$ + private static final String FD_APPCOMPAT = "appcompat"; //$NON-NLS-1$ + private static final String FD_LIBS = "libs"; //$NON-NLS-1$ + private static final String ANDROID_SUPPORT_V4_JAR = "android-support-v4.jar"; //$NON-NLS-1$ + private static final String ANDROID_SUPPORT_V13_JAR = "android-support-v13.jar";//$NON-NLS-1$ + private static final String APPCOMPAT_V7_JAR = "android-support-v7-appcompat.jar";//$NON-NLS-1$ + private static final String APP_COMPAT_LIB_NAME = "appcompat_v7"; //$NON-NLS-1$ + private ISelection mSelection; + + /** + * @see IObjectActionDelegate#setActivePart(IAction, IWorkbenchPart) + */ + @Override + public void setActivePart(IAction action, IWorkbenchPart targetPart) { + } + + @Override + public void run(IAction action) { + if (mSelection instanceof IStructuredSelection) { + + for (Iterator<?> it = ((IStructuredSelection) mSelection).iterator(); + it.hasNext();) { + Object element = it.next(); + IProject project = null; + if (element instanceof IProject) { + project = (IProject) element; + } else if (element instanceof IAdaptable) { + project = (IProject) ((IAdaptable) element) + .getAdapter(IProject.class); + } + if (project != null) { + install(project); + } + } + } + } + + @Override + public void selectionChanged(IAction action, ISelection selection) { + mSelection = selection; + } + + /** + * Install the support jar into the given project. + * + * @param project The Android project to install the support jar into + * @return true if the installation was successful + */ + public static boolean install(final IProject project) { + File jarPath = installSupport(-1); + if (jarPath != null) { + try { + return copyJarIntoProject(project, jarPath) != null; + } catch (Exception e) { + AdtPlugin.log(e, null); + } + } + + return false; + } + + /** + * Installs the Android Support library into the SDK extras/ folder. If a minimum + * revision number is specified, this method will check whether the package is already + * installed, and if the installed revision is at least as high as the requested revision, + * this method will exit without performing an update. + * + * @param minimumRevision a minimum revision, or -1 to upgrade + * unconditionally. Note that this does <b>NOT</b> specify which + * revision to install; the latest version will always be + * installed. + * @return the location of the support jar file, or null if something went + * wrong + */ + @Nullable + public static File installSupport(int minimumRevision) { + + final Sdk sdk = Sdk.getCurrent(); + if (sdk == null) { + AdtPlugin.printErrorToConsole( + AddSupportJarAction.class.getSimpleName(), // tag + "Error: Android SDK is not loaded yet."); //$NON-NLS-1$ + return null; + } + + String sdkLocation = sdk.getSdkOsLocation(); + if (minimumRevision > 0) { + File path = getSupportJarFile(); + if (path != null) { + assert path.exists(); // guaranteed by the getSupportJarFile call + int installedRevision = getInstalledRevision(); + if (installedRevision != -1 && minimumRevision <= installedRevision) { + return path; + } + } + } + + // TODO: For the generic action, check the library isn't in the project already. + + // First call the package manager to make sure the package is installed + // and get the installation path of the library. + + AdtUpdateDialog window = new AdtUpdateDialog( + AdtPlugin.getShell(), + new AdtConsoleSdkLog(), + sdkLocation); + + Pair<Boolean, File> result = window.installExtraPackage(VENDOR_ID, SUPPORT_ID); + + // TODO: Make sure the version is at the required level; we know we need at least one + // containing the v7 support + + if (!result.getFirst().booleanValue()) { + AdtPlugin.printErrorToConsole("Failed to install Android Support library"); + return null; + } + + // TODO these "v4" values needs to be dynamic, e.g. we could try to match + // vN/android-support-vN.jar. Eventually we'll want to rely on info from the + // package manifest anyway so this is irrelevant. + + File path = new File(result.getSecond(), FD_V4); + final File jarPath = new File(path, ANDROID_SUPPORT_V4_JAR); + + if (!jarPath.isFile()) { + AdtPlugin.printErrorToConsole("Android Support Jar not found:", + jarPath.getAbsolutePath()); + return null; + } + + return jarPath; + } + + /** + * Returns the installed revision number of the Android Support + * library, or -1 if the package is not installed. + * + * @return the installed revision number, or -1 + */ + public static int getInstalledRevision() { + final Sdk sdk = Sdk.getCurrent(); + if (sdk != null) { + String sdkLocation = sdk.getSdkOsLocation(); + SdkManager manager = SdkManager.createManager(sdkLocation, NullLogger.getLogger()); + Map<String, Integer> versions = manager.getExtrasVersions(); + Integer version = versions.get(VENDOR_ID + '/' + SUPPORT_ID); + if (version == null) { + // Check the old compatibility library. When the library is updated in-place + // the manager doesn't change its folder name (since that is a source of + // endless issues on Windows.) + version = versions.get(VENDOR_ID + '/' + COMPATIBILITY_ID); + } + if (version != null) { + return version.intValue(); + } + } + + return -1; + } + + /** + * Similar to {@link #install}, but rather than copy a jar into the given + * project, it creates a new library project in the workspace for the + * support library, and adds a library dependency on the newly + * installed library from the given project. + * + * @param project the project to add a dependency on the library to + * @param waitForFinish If true, block until the task has finished + * @return true if the installation was successful (or if + * <code>waitForFinish</code> is false, if the installation is + * likely to be successful - e.g. the user has at least agreed to + * all installation prompts.) + */ + public static boolean installGridLayoutLibrary(final IProject project, boolean waitForFinish) { + final IJavaProject javaProject = JavaCore.create(project); + if (javaProject != null) { + + File supportPath = getSupportPackageDir(); + if (!supportPath.isDirectory()) { + File path = installSupport(8); // GridLayout arrived in rev 7 and fixed in rev 8 + if (path == null) { + return false; + } + assert path.equals(supportPath); + } + File libraryPath = new File(supportPath, FD_V7 + File.separator + FD_GRIDLAYOUT); + if (!libraryPath.isDirectory()) { + // Upgrade support package: it's out of date. The SDK manager will + // perform an upgrade to the latest version if the package is already installed. + File path = installSupport(-1); + if (path == null) { + return false; + } + assert path.equals(libraryPath) : path; + } + + // Create workspace copy of the project and add library dependency + IProject libraryProject = createLibraryProject(libraryPath, project, + "gridlayout_v7", waitForFinish); //$NON-NLS-1$ + if (libraryProject != null) { + return addLibraryDependency(libraryProject, project, waitForFinish); + } + } + + return false; + } + + /** + * Similar to {@link #install}, but rather than copy a jar into the given + * project, it creates a new library project in the workspace for the + * support library, and adds a library dependency on the newly + * installed library from the given project. + * + * @param project the project to add a dependency on the library to + * @param waitForFinish If true, block until the task has finished + * @return true if the installation was successful (or if + * <code>waitForFinish</code> is false, if the installation is + * likely to be successful - e.g. the user has at least agreed to + * all installation prompts.) + */ + public static boolean installAppCompatLibrary(final IProject project, boolean waitForFinish) { + final IJavaProject javaProject = JavaCore.create(project); + if (javaProject != null) { + + // Don't add in the library if it already exists + ProjectState state = Sdk.getProjectState(project); + ProjectPropertiesWorkingCopy copy = state.getProperties().makeWorkingCopy(); + for (String property : copy.keySet()) { + if (property.startsWith(ProjectProperties.PROPERTY_LIB_REF)) { + String libraryReference = copy.getProperty(property); + if (libraryReference != null && libraryReference.contains(APP_COMPAT_LIB_NAME)) { + return true; + } + } + } + + File supportPath = getSupportPackageDir(); + if (!supportPath.isDirectory()) { + File path = installSupport(7); + if (path == null) { + return false; + } + assert path.equals(supportPath); + } + File libraryPath = new File(supportPath, FD_V7 + File.separator + FD_APPCOMPAT); + if (!libraryPath.isDirectory()) { + // Upgrade support package: it's out of date. The SDK manager will + // perform an upgrade to the latest version if the package is already installed. + File path = installSupport(-1); + if (path == null) { + return false; + } + assert path.equals(libraryPath) : path; + } + + // Check to see if there's already a version of the library available + IWorkspace workspace = ResourcesPlugin.getWorkspace(); + IWorkspaceRoot root = workspace.getRoot(); + IProject libraryProject = root.getProject(APP_COMPAT_LIB_NAME); + if (!libraryProject.exists()) { + // Create workspace copy of the project and add library dependency + libraryProject = createLibraryProject(libraryPath, project, + APP_COMPAT_LIB_NAME, waitForFinish); + } + if (libraryProject != null) { + return addLibraryDependency(libraryProject, project, waitForFinish); + } + } + + return false; + } + + /** + * Returns the directory containing the support libraries (v4, v7, v13, + * ...), which may or may not exist + * + * @return a path to the support library or null + */ + private static File getSupportPackageDir() { + final Sdk sdk = Sdk.getCurrent(); + if (sdk != null) { + String sdkLocation = sdk.getSdkOsLocation(); + SdkManager manager = SdkManager.createManager(sdkLocation, NullLogger.getLogger()); + Map<String, Integer> versions = manager.getExtrasVersions(); + Integer version = versions.get(VENDOR_ID + '/' + SUPPORT_ID); + if (version != null) { + File supportPath = new File(sdkLocation, + SdkConstants.FD_EXTRAS + File.separator + + VENDOR_ID + File.separator + + SUPPORT_ID); + return supportPath; + } + + // Check the old compatibility library. When the library is updated in-place + // the manager doesn't change its folder name (since that is a source of + // endless issues on Windows.) + version = versions.get(VENDOR_ID + '/' + COMPATIBILITY_ID); + if (version != null) { + File supportPath = new File(sdkLocation, + SdkConstants.FD_EXTRAS + File.separator + + VENDOR_ID + File.separator + + COMPATIBILITY_ID); + return supportPath; + } + } + return null; + } + + /** + * Returns a path to the installed jar file for the support library, + * or null if it does not exist + * + * @return a path to the v4.jar or null + */ + @Nullable + public static File getSupportJarFile() { + File supportDir = getSupportPackageDir(); + if (supportDir != null) { + File path = new File(supportDir, FD_V4 + File.separator + ANDROID_SUPPORT_V4_JAR); + if (path.exists()) { + return path; + } + } + + return null; + } + + /** + * Returns a path to the installed jar file for the support library, + * or null if it does not exist + * + * @return a path to the v13.jar or null + */ + @Nullable + public static File getSupport13JarFile() { + File supportDir = getSupportPackageDir(); + if (supportDir != null) { + File path = new File(supportDir, FD_V13 + File.separator + ANDROID_SUPPORT_V13_JAR); + if (path.exists()) { + return path; + } + } + + return null; + } + + /** + * Creates a library project in the Eclipse workspace out of the grid layout project + * in the SDK tree. + * + * @param libraryPath the path to the directory tree containing the project contents + * @param project the project to copy the SDK target out of + * @param waitForFinish whether the operation should finish before this method returns + * @return a library project, or null if it fails for some reason + */ + private static IProject createLibraryProject( + final File libraryPath, + final IProject project, + final String libraryName, + boolean waitForFinish) { + + // Install a new library into the workspace. This is a copy rather than + // a reference to the support library version such that modifications + // do not modify the pristine copy in the SDK install area. + + final IProject newProject; + try { + IProgressMonitor monitor = new NullProgressMonitor(); + IWorkspace workspace = ResourcesPlugin.getWorkspace(); + IWorkspaceRoot root = workspace.getRoot(); + + String name = AdtUtils.getUniqueProjectName( + libraryName, "_"); //$NON-NLS-1$ + newProject = root.getProject(name); + IProjectDescription description = workspace.newProjectDescription(name); + String[] natures = new String[] { AdtConstants.NATURE_DEFAULT, JavaCore.NATURE_ID }; + description.setNatureIds(natures); + newProject.create(description, monitor); + + // Copy in the files recursively + IFileSystem fileSystem = EFS.getLocalFileSystem(); + IFileStore sourceDir = fileSystem.getStore(libraryPath.toURI()); + IFileStore destDir = fileSystem.getStore(newProject.getLocationURI()); + sourceDir.copy(destDir, EFS.OVERWRITE, null); + + // Make sure the src folder exists + destDir.getChild(SdkConstants.SRC_FOLDER).mkdir(0, null /*monitor*/); + + // Set the android platform to the same level as the calling project + ProjectState state = Sdk.getProjectState(project); + String target = state.getProperties().getProperty(ProjectProperties.PROPERTY_TARGET); + if (target != null && target.length() > 0) { + ProjectProperties properties = ProjectProperties.load( + destDir.toLocalFile(EFS.NONE, new NullProgressMonitor()).getPath(), + PropertyType.PROJECT); + ProjectPropertiesWorkingCopy copy = properties.makeWorkingCopy(); + copy.setProperty(ProjectProperties.PROPERTY_TARGET, target); + try { + copy.save(); + } catch (Exception e) { + AdtPlugin.log(e, null); + } + } + + newProject.open(monitor); + + return newProject; + } catch (CoreException e) { + AdtPlugin.log(e, null); + return null; + } + } + + /** + * Adds a library dependency on the given library into the given project. + * + * @param libraryProject the library project to depend on + * @param dependentProject the project to write the dependency into + * @param waitForFinish whether this method should wait for the job to + * finish + * @return true if the operation succeeded + */ + public static boolean addLibraryDependency( + final IProject libraryProject, + final IProject dependentProject, + boolean waitForFinish) { + + // Now add library dependency + + // Run an Eclipse asynchronous job to update the project + Job job = new Job("Add Support Library Dependency to Project") { + @Override + protected IStatus run(IProgressMonitor monitor) { + try { + monitor.beginTask("Add library dependency to project build path", 3); + monitor.worked(1); + + // TODO: Add library project to the project.properties file! + ProjectState state = Sdk.getProjectState(dependentProject); + ProjectPropertiesWorkingCopy mPropertiesWorkingCopy = + state.getProperties().makeWorkingCopy(); + + // Get the highest version number of the libraries; there cannot be any + // gaps so we will assign the next library the next number + int nextVersion = 1; + for (String property : mPropertiesWorkingCopy.keySet()) { + if (property.startsWith(ProjectProperties.PROPERTY_LIB_REF)) { + String s = property.substring( + ProjectProperties.PROPERTY_LIB_REF.length()); + int version = Integer.parseInt(s); + if (version >= nextVersion) { + nextVersion = version + 1; + } + } + } + + IPath relativePath = libraryProject.getLocation().makeRelativeTo( + dependentProject.getLocation()); + + mPropertiesWorkingCopy.setProperty( + ProjectProperties.PROPERTY_LIB_REF + nextVersion, + relativePath.toString()); + try { + mPropertiesWorkingCopy.save(); + IResource projectProp = dependentProject.findMember( + SdkConstants.FN_PROJECT_PROPERTIES); + projectProp.refreshLocal(IResource.DEPTH_ZERO, new NullProgressMonitor()); + } catch (Exception e) { + String msg = String.format( + "Failed to save %1$s for project %2$s", + SdkConstants.FN_PROJECT_PROPERTIES, dependentProject.getName()); + AdtPlugin.log(e, msg); + } + + // Project fix-ups + Job fix = FixProjectAction.createFixProjectJob(libraryProject); + fix.schedule(); + fix.join(); + + monitor.worked(1); + + return Status.OK_STATUS; + } catch (Exception e) { + return new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, Status.ERROR, + "Failed", e); //$NON-NLS-1$ + } finally { + if (monitor != null) { + monitor.done(); + } + } + } + }; + job.schedule(); + + if (waitForFinish) { + try { + job.join(); + return job.getState() == IStatus.OK; + } catch (InterruptedException e) { + AdtPlugin.log(e, null); + } + } + + return true; + } + + private static IResource copyJarIntoProject( + IProject project, + File jarPath) throws IOException, CoreException { + IFolder resFolder = project.getFolder(SdkConstants.FD_NATIVE_LIBS); + if (!resFolder.exists()) { + resFolder.create(IResource.FORCE, true /*local*/, null); + } + + IFile destFile = resFolder.getFile(jarPath.getName()); + IPath loc = destFile.getLocation(); + File destPath = loc.toFile(); + + // Only modify the file if necessary so that we don't trigger unnecessary recompilations + FileOp f = new FileOp(); + if (!f.isFile(destPath) || !f.isSameFile(jarPath, destPath)) { + f.copyFile(jarPath, destPath); + // Make sure Eclipse discovers java.io file changes + resFolder.refreshLocal(1, new NullProgressMonitor()); + } + + return destFile; + } + + /** + * @see IWorkbenchWindowActionDelegate#init + */ + public void init(IWorkbenchWindow window) { + // pass + } + +} |