• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.eclipse.org/org/documents/epl-v10.php
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.ide.eclipse.adt.internal.actions;
18 
19 import com.android.SdkConstants;
20 import com.android.annotations.Nullable;
21 import com.android.ide.eclipse.adt.AdtConstants;
22 import com.android.ide.eclipse.adt.AdtPlugin;
23 import com.android.ide.eclipse.adt.AdtUtils;
24 import com.android.ide.eclipse.adt.internal.sdk.AdtConsoleSdkLog;
25 import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
26 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
27 import com.android.sdklib.SdkManager;
28 import com.android.sdklib.internal.project.ProjectProperties;
29 import com.android.sdklib.internal.project.ProjectProperties.PropertyType;
30 import com.android.sdklib.internal.project.ProjectPropertiesWorkingCopy;
31 import com.android.sdklib.io.FileOp;
32 import com.android.sdkuilib.internal.repository.ui.AdtUpdateDialog;
33 import com.android.utils.NullLogger;
34 import com.android.utils.Pair;
35 
36 import org.eclipse.core.filesystem.EFS;
37 import org.eclipse.core.filesystem.IFileStore;
38 import org.eclipse.core.filesystem.IFileSystem;
39 import org.eclipse.core.resources.IFile;
40 import org.eclipse.core.resources.IFolder;
41 import org.eclipse.core.resources.IProject;
42 import org.eclipse.core.resources.IProjectDescription;
43 import org.eclipse.core.resources.IResource;
44 import org.eclipse.core.resources.IWorkspace;
45 import org.eclipse.core.resources.IWorkspaceRoot;
46 import org.eclipse.core.resources.ResourcesPlugin;
47 import org.eclipse.core.runtime.CoreException;
48 import org.eclipse.core.runtime.IAdaptable;
49 import org.eclipse.core.runtime.IPath;
50 import org.eclipse.core.runtime.IProgressMonitor;
51 import org.eclipse.core.runtime.IStatus;
52 import org.eclipse.core.runtime.NullProgressMonitor;
53 import org.eclipse.core.runtime.Status;
54 import org.eclipse.core.runtime.jobs.Job;
55 import org.eclipse.jdt.core.IJavaProject;
56 import org.eclipse.jdt.core.JavaCore;
57 import org.eclipse.jface.action.IAction;
58 import org.eclipse.jface.viewers.ISelection;
59 import org.eclipse.jface.viewers.IStructuredSelection;
60 import org.eclipse.ui.IObjectActionDelegate;
61 import org.eclipse.ui.IWorkbenchPart;
62 import org.eclipse.ui.IWorkbenchWindow;
63 import org.eclipse.ui.IWorkbenchWindowActionDelegate;
64 
65 import java.io.File;
66 import java.io.IOException;
67 import java.util.Iterator;
68 import java.util.Map;
69 
70 /**
71  * An action to add the android-support-v4.jar support library
72  * to the selected project.
73  * <p/>
74  * This should be used by the GLE. The action itself is currently more
75  * like an example of how to invoke the new {@link AdtUpdateDialog}.
76  * <p/>
77  * TODO: make this more configurable.
78  */
79 public class AddSupportJarAction implements IObjectActionDelegate {
80 
81     /** The vendor ID of the support library. */
82     private static final String VENDOR_ID = "android";                             //$NON-NLS-1$
83     /** The path ID of the support library. */
84     private static final String SUPPORT_ID = "support";                            //$NON-NLS-1$
85     /** The path ID of the compatibility library (which was its id for releases 1-3). */
86     private static final String COMPATIBILITY_ID = "compatibility";                //$NON-NLS-1$
87     private static final String FD_GRIDLAYOUT = "gridlayout";                      //$NON-NLS-1$
88     private static final String FD_V7 = "v7";                                      //$NON-NLS-1$
89     private static final String FD_V4 = "v4";                                      //$NON-NLS-1$
90     private static final String ANDROID_SUPPORT_V4_JAR = "android-support-v4.jar"; //$NON-NLS-1$
91     private ISelection mSelection;
92 
93     /**
94      * @see IObjectActionDelegate#setActivePart(IAction, IWorkbenchPart)
95      */
96     @Override
setActivePart(IAction action, IWorkbenchPart targetPart)97     public void setActivePart(IAction action, IWorkbenchPart targetPart) {
98     }
99 
100     @Override
run(IAction action)101     public void run(IAction action) {
102         if (mSelection instanceof IStructuredSelection) {
103 
104             for (Iterator<?> it = ((IStructuredSelection) mSelection).iterator();
105                     it.hasNext();) {
106                 Object element = it.next();
107                 IProject project = null;
108                 if (element instanceof IProject) {
109                     project = (IProject) element;
110                 } else if (element instanceof IAdaptable) {
111                     project = (IProject) ((IAdaptable) element)
112                             .getAdapter(IProject.class);
113                 }
114                 if (project != null) {
115                     install(project);
116                 }
117             }
118         }
119     }
120 
121     @Override
selectionChanged(IAction action, ISelection selection)122     public void selectionChanged(IAction action, ISelection selection) {
123         mSelection = selection;
124     }
125 
126     /**
127      * Install the support jar into the given project.
128      *
129      * @param project The Android project to install the support jar into
130      * @return true if the installation was successful
131      */
install(final IProject project)132     public static boolean install(final IProject project) {
133         File jarPath = installSupport(-1);
134         if (jarPath != null) {
135             try {
136                 return copyJarIntoProject(project, jarPath) != null;
137             } catch (Exception e) {
138                 AdtPlugin.log(e, null);
139             }
140         }
141 
142         return false;
143     }
144 
145     /**
146      * Installs the Android Support library into the SDK extras/ folder. If a minimum
147      * revision number is specified, this method will check whether the package is already
148      * installed, and if the installed revision is at least as high as the requested revision,
149      * this method will exit without performing an update.
150      *
151      * @param minimumRevision a minimum revision, or -1 to upgrade
152      *            unconditionally. Note that this does <b>NOT</b> specify which
153      *            revision to install; the latest version will always be
154      *            installed.
155      * @return the location of the support jar file, or null if something went
156      *            wrong
157      */
158     @Nullable
installSupport(int minimumRevision)159     public static File installSupport(int minimumRevision) {
160 
161         final Sdk sdk = Sdk.getCurrent();
162         if (sdk == null) {
163             AdtPlugin.printErrorToConsole(
164                     AddSupportJarAction.class.getSimpleName(),   // tag
165                     "Error: Android SDK is not loaded yet."); //$NON-NLS-1$
166             return null;
167         }
168 
169         String sdkLocation = sdk.getSdkLocation();
170         if (minimumRevision > 0) {
171             File path = getSupportJarFile();
172             if (path != null) {
173                 assert path.exists(); // guaranteed by the getSupportJarFile call
174                 int installedRevision = getInstalledRevision();
175                 if (installedRevision != -1 && minimumRevision <= installedRevision) {
176                     return path;
177                 }
178             }
179         }
180 
181         // TODO: For the generic action, check the library isn't in the project already.
182 
183         // First call the package manager to make sure the package is installed
184         // and get the installation path of the library.
185 
186         AdtUpdateDialog window = new AdtUpdateDialog(
187                 AdtPlugin.getShell(),
188                 new AdtConsoleSdkLog(),
189                 sdkLocation);
190 
191         Pair<Boolean, File> result = window.installExtraPackage(VENDOR_ID, SUPPORT_ID);
192 
193         // TODO: Make sure the version is at the required level; we know we need at least one
194         // containing the v7 support
195 
196         if (!result.getFirst().booleanValue()) {
197             AdtPlugin.printErrorToConsole("Failed to install Android Support library");
198             return null;
199         }
200 
201         // TODO these "v4" values needs to be dynamic, e.g. we could try to match
202         // vN/android-support-vN.jar. Eventually we'll want to rely on info from the
203         // package manifest anyway so this is irrelevant.
204 
205         File path = new File(result.getSecond(), FD_V4);
206         final File jarPath = new File(path, ANDROID_SUPPORT_V4_JAR);
207 
208         if (!jarPath.isFile()) {
209             AdtPlugin.printErrorToConsole("Android Support Jar not found:",
210                     jarPath.getAbsolutePath());
211             return null;
212         }
213 
214         return jarPath;
215     }
216 
217     /**
218      * Returns the installed revision number of the Android Support
219      * library, or -1 if the package is not installed.
220      *
221      * @return the installed revision number, or -1
222      */
getInstalledRevision()223     public static int getInstalledRevision() {
224         final Sdk sdk = Sdk.getCurrent();
225         if (sdk != null) {
226             String sdkLocation = sdk.getSdkLocation();
227             SdkManager manager = SdkManager.createManager(sdkLocation, NullLogger.getLogger());
228             Map<String, Integer> versions = manager.getExtrasVersions();
229             Integer version = versions.get(VENDOR_ID + '/' + SUPPORT_ID);
230             if (version == null) {
231                 // Check the old compatibility library. When the library is updated in-place
232                 // the manager doesn't change its folder name (since that is a source of
233                 // endless issues on Windows.)
234                 version = versions.get(VENDOR_ID + '/' + COMPATIBILITY_ID);
235             }
236             if (version != null) {
237                 return version.intValue();
238             }
239         }
240 
241        return -1;
242     }
243 
244     /**
245      * Similar to {@link #install}, but rather than copy a jar into the given
246      * project, it creates a new library project in the workspace for the
247      * support library, and adds a library dependency on the newly
248      * installed library from the given project.
249      *
250      * @param project the project to add a dependency on the library to
251      * @param waitForFinish If true, block until the task has finished
252      * @return true if the installation was successful (or if
253      *         <code>waitForFinish</code> is false, if the installation is
254      *         likely to be successful - e.g. the user has at least agreed to
255      *         all installation prompts.)
256      */
installGridLayoutLibrary(final IProject project, boolean waitForFinish)257     public static boolean installGridLayoutLibrary(final IProject project, boolean waitForFinish) {
258         final IJavaProject javaProject = JavaCore.create(project);
259         if (javaProject != null) {
260 
261             File supportPath = getSupportPackageDir();
262             if (!supportPath.isDirectory()) {
263                 File path = installSupport(8); // GridLayout arrived in rev 7 and fixed in rev 8
264                 if (path == null) {
265                     return false;
266                 }
267                 assert path.equals(supportPath);
268             }
269             File libraryPath = new File(supportPath, FD_V7 + File.separator + FD_GRIDLAYOUT);
270             if (!libraryPath.isDirectory()) {
271                 // Upgrade support package: it's out of date. The SDK manager will
272                 // perform an upgrade to the latest version if the package is already installed.
273                 File path = installSupport(-1);
274                 if (path == null) {
275                     return false;
276                 }
277                 assert path.equals(libraryPath) : path;
278             }
279 
280             // Create workspace copy of the project and add library dependency
281             IProject libraryProject = createLibraryProject(libraryPath, project, waitForFinish);
282             if (libraryProject != null) {
283                 return addLibraryDependency(libraryProject, project, waitForFinish);
284             }
285         }
286 
287         return false;
288     }
289 
290     /**
291      * Returns the directory containing the support libraries (v4, v7, v13,
292      * ...), which may or may not exist
293      *
294      * @return a path to the support library or null
295      */
getSupportPackageDir()296     private static File getSupportPackageDir() {
297         final Sdk sdk = Sdk.getCurrent();
298         if (sdk != null) {
299             String sdkLocation = sdk.getSdkLocation();
300             SdkManager manager = SdkManager.createManager(sdkLocation, NullLogger.getLogger());
301             Map<String, Integer> versions = manager.getExtrasVersions();
302             Integer version = versions.get(VENDOR_ID + '/' + SUPPORT_ID);
303             if (version != null) {
304                 File supportPath = new File(sdkLocation,
305                         SdkConstants.FD_EXTRAS + File.separator
306                         + VENDOR_ID + File.separator
307                         + SUPPORT_ID);
308                 return supportPath;
309             }
310 
311             // Check the old compatibility library. When the library is updated in-place
312             // the manager doesn't change its folder name (since that is a source of
313             // endless issues on Windows.)
314             version = versions.get(VENDOR_ID + '/' + COMPATIBILITY_ID);
315             if (version != null) {
316                 File supportPath = new File(sdkLocation,
317                         SdkConstants.FD_EXTRAS + File.separator
318                         + VENDOR_ID + File.separator
319                         + COMPATIBILITY_ID);
320                 return supportPath;
321             }
322         }
323         return null;
324     }
325 
326     /**
327      * Returns a path to the installed jar file for the support library,
328      * or null if it does not exist
329      *
330      * @return a path to the v4.jar or null
331      */
332     @Nullable
getSupportJarFile()333     public static File getSupportJarFile() {
334         File supportDir = getSupportPackageDir();
335         if (supportDir != null) {
336             File path = new File(supportDir, FD_V4 + File.separator + ANDROID_SUPPORT_V4_JAR);
337             if (path.exists()) {
338                 return path;
339             }
340         }
341 
342         return null;
343     }
344 
345     /**
346      * Creates a library project in the Eclipse workspace out of the grid layout project
347      * in the SDK tree.
348      *
349      * @param libraryPath the path to the directory tree containing the project contents
350      * @param project the project to copy the SDK target out of
351      * @param waitForFinish whether the operation should finish before this method returns
352      * @return a library project, or null if it fails for some reason
353      */
createLibraryProject( final File libraryPath, final IProject project, boolean waitForFinish)354     private static IProject createLibraryProject(
355             final File libraryPath,
356             final IProject project,
357             boolean waitForFinish) {
358 
359         // Install a new library into the workspace. This is a copy rather than
360         // a reference to the support library version such that modifications
361         // do not modify the pristine copy in the SDK install area.
362 
363         final IProject newProject;
364         try {
365             IProgressMonitor monitor = new NullProgressMonitor();
366             IWorkspace workspace = ResourcesPlugin.getWorkspace();
367             IWorkspaceRoot root = workspace.getRoot();
368 
369             String name = AdtUtils.getUniqueProjectName(
370                     "gridlayout_v7", "_"); //$NON-NLS-1$ //$NON-NLS-2$
371             newProject = root.getProject(name);
372             IProjectDescription description = workspace.newProjectDescription(name);
373             String[] natures = new String[] { AdtConstants.NATURE_DEFAULT, JavaCore.NATURE_ID };
374             description.setNatureIds(natures);
375             newProject.create(description, monitor);
376 
377             // Copy in the files recursively
378             IFileSystem fileSystem = EFS.getLocalFileSystem();
379             IFileStore sourceDir = fileSystem.getStore(libraryPath.toURI());
380             IFileStore destDir = fileSystem.getStore(newProject.getLocationURI());
381             sourceDir.copy(destDir, EFS.OVERWRITE, null);
382 
383             // Make sure the src folder exists
384             destDir.getChild("src").mkdir(0, null /*monitor*/);
385 
386             // Set the android platform to the same level as the calling project
387             ProjectState state = Sdk.getProjectState(project);
388             String target = state.getProperties().getProperty(ProjectProperties.PROPERTY_TARGET);
389             if (target != null && target.length() > 0) {
390                 ProjectProperties properties = ProjectProperties.load(libraryPath.getPath(),
391                         PropertyType.PROJECT);
392                 ProjectPropertiesWorkingCopy copy = properties.makeWorkingCopy();
393                 copy.setProperty(ProjectProperties.PROPERTY_TARGET, target);
394                 try {
395                     copy.save();
396                 } catch (Exception e) {
397                     AdtPlugin.log(e, null);
398                 }
399             }
400 
401             newProject.open(monitor);
402 
403             return newProject;
404         } catch (CoreException e) {
405             AdtPlugin.log(e, null);
406             return null;
407         }
408     }
409 
410     /**
411      * Adds a library dependency on the given library into the given project.
412      *
413      * @param libraryProject the library project to depend on
414      * @param dependentProject the project to write the dependency into
415      * @param waitForFinish whether this method should wait for the job to
416      *            finish
417      * @return true if the operation succeeded
418      */
addLibraryDependency( final IProject libraryProject, final IProject dependentProject, boolean waitForFinish)419     public static boolean addLibraryDependency(
420             final IProject libraryProject,
421             final IProject dependentProject,
422             boolean waitForFinish) {
423 
424         // Now add library dependency
425 
426         // Run an Eclipse asynchronous job to update the project
427         Job job = new Job("Add Support Library Dependency to Project") {
428             @Override
429             protected IStatus run(IProgressMonitor monitor) {
430                 try {
431                     monitor.beginTask("Add library dependency to project build path", 3);
432                     monitor.worked(1);
433 
434                     // TODO: Add library project to the project.properties file!
435                     ProjectState state = Sdk.getProjectState(dependentProject);
436                     ProjectPropertiesWorkingCopy mPropertiesWorkingCopy =
437                             state.getProperties().makeWorkingCopy();
438 
439                     // Get the highest version number of the libraries; there cannot be any
440                     // gaps so we will assign the next library the next number
441                     int nextVersion = 1;
442                     for (String property : mPropertiesWorkingCopy.keySet()) {
443                         if (property.startsWith(ProjectProperties.PROPERTY_LIB_REF)) {
444                             String s = property.substring(
445                                     ProjectProperties.PROPERTY_LIB_REF.length());
446                             int version = Integer.parseInt(s);
447                             if (version >= nextVersion) {
448                                 nextVersion = version + 1;
449                             }
450                         }
451                     }
452 
453                     IPath relativePath = libraryProject.getLocation().makeRelativeTo(
454                             dependentProject.getLocation());
455 
456                     mPropertiesWorkingCopy.setProperty(
457                             ProjectProperties.PROPERTY_LIB_REF + nextVersion,
458                             relativePath.toString());
459                     try {
460                         mPropertiesWorkingCopy.save();
461                         IResource projectProp = dependentProject.findMember(
462                                 SdkConstants.FN_PROJECT_PROPERTIES);
463                         projectProp.refreshLocal(IResource.DEPTH_ZERO, new NullProgressMonitor());
464                     } catch (Exception e) {
465                         String msg = String.format(
466                                 "Failed to save %1$s for project %2$s",
467                                 SdkConstants.FN_PROJECT_PROPERTIES, dependentProject.getName());
468                         AdtPlugin.log(e, msg);
469                     }
470 
471                     // Project fix-ups
472                     Job fix = FixProjectAction.createFixProjectJob(libraryProject);
473                     fix.schedule();
474                     fix.join();
475 
476                     monitor.worked(1);
477 
478                     return Status.OK_STATUS;
479                 } catch (Exception e) {
480                     return new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, Status.ERROR,
481                                       "Failed", e); //$NON-NLS-1$
482                 } finally {
483                     if (monitor != null) {
484                         monitor.done();
485                     }
486                 }
487             }
488         };
489         job.schedule();
490 
491         if (waitForFinish) {
492             try {
493                 job.join();
494                 return job.getState() == IStatus.OK;
495             } catch (InterruptedException e) {
496                 AdtPlugin.log(e, null);
497             }
498         }
499 
500         return true;
501     }
502 
copyJarIntoProject( IProject project, File jarPath)503     private static IResource copyJarIntoProject(
504             IProject project,
505             File jarPath) throws IOException, CoreException {
506         IFolder resFolder = project.getFolder(SdkConstants.FD_NATIVE_LIBS);
507         if (!resFolder.exists()) {
508             resFolder.create(IResource.FORCE, true /*local*/, null);
509         }
510 
511         IFile destFile = resFolder.getFile(jarPath.getName());
512         IPath loc = destFile.getLocation();
513         File destPath = loc.toFile();
514 
515         // Only modify the file if necessary so that we don't trigger unnecessary recompilations
516         FileOp f = new FileOp();
517         if (!f.isFile(destPath) || !f.isSameFile(jarPath, destPath)) {
518             f.copyFile(jarPath, destPath);
519             // Make sure Eclipse discovers java.io file changes
520             resFolder.refreshLocal(1, new NullProgressMonitor());
521         }
522 
523         return destFile;
524     }
525 
526     /**
527      * @see IWorkbenchWindowActionDelegate#init
528      */
init(IWorkbenchWindow window)529     public void init(IWorkbenchWindow window) {
530         // pass
531     }
532 
533 }
534