• 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 package com.android.ide.eclipse.adt.internal.lint;
17 
18 import static com.android.ide.eclipse.adt.AdtConstants.DOT_XML;
19 
20 import com.android.ide.eclipse.adt.AdtPlugin;
21 import com.android.ide.eclipse.adt.AdtUtils;
22 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
23 import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
24 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
25 import com.android.sdklib.SdkConstants;
26 import com.android.tools.lint.client.api.IssueRegistry;
27 import com.android.tools.lint.client.api.LintDriver;
28 import com.android.tools.lint.detector.api.Issue;
29 import com.android.tools.lint.detector.api.Scope;
30 
31 import org.eclipse.core.resources.IFile;
32 import org.eclipse.core.resources.IMarker;
33 import org.eclipse.core.resources.IProject;
34 import org.eclipse.core.resources.IResource;
35 import org.eclipse.core.runtime.IProgressMonitor;
36 import org.eclipse.core.runtime.IStatus;
37 import org.eclipse.core.runtime.Status;
38 import org.eclipse.core.runtime.jobs.IJobManager;
39 import org.eclipse.core.runtime.jobs.Job;
40 import org.eclipse.jface.dialogs.MessageDialog;
41 import org.eclipse.jface.text.IDocument;
42 import org.eclipse.swt.widgets.Shell;
43 
44 import java.io.File;
45 import java.util.ArrayList;
46 import java.util.Collections;
47 import java.util.EnumSet;
48 import java.util.IdentityHashMap;
49 import java.util.List;
50 import java.util.Map;
51 
52 /**
53  * Eclipse implementation for running lint on workspace files and projects.
54  */
55 public class EclipseLintRunner {
56     static final String MARKER_CHECKID_PROPERTY = "checkid";    //$NON-NLS-1$
57 
58     /**
59      * Runs lint and updates the markers, and waits for the result. Returns
60      * true if fatal errors were found.
61      *
62      * @param resources the resources (project, folder or file) to be analyzed
63      * @param doc the associated document, if known, or null
64      * @param fatalOnly if true, only report fatal issues (severity=error)
65      * @return true if any fatal errors were encountered.
66      */
runLint(List<? extends IResource> resources, IDocument doc, boolean fatalOnly)67     private static boolean runLint(List<? extends IResource> resources, IDocument doc,
68             boolean fatalOnly) {
69         resources = addLibraries(resources);
70         CheckFileJob job = (CheckFileJob) startLint(resources, doc, fatalOnly, false /*show*/);
71         try {
72             job.join();
73             boolean fatal = job.isFatal();
74 
75             if (fatal) {
76                 LintViewPart.show(resources);
77             }
78 
79             return fatal;
80         } catch (InterruptedException e) {
81             AdtPlugin.log(e, null);
82         }
83 
84         return false;
85     }
86 
87     /**
88      * Runs lint and updates the markers. Does not wait for the job to
89      * finish - just returns immediately.
90      *
91      * @param resources the resources (project, folder or file) to be analyzed
92      * @param doc the associated document, if known, or null
93      * @param fatalOnly if true, only report fatal issues (severity=error)
94      * @param show if true, show the results in a {@link LintViewPart}
95      * @return the job running lint in the background.
96      */
startLint(List<? extends IResource> resources, IDocument doc, boolean fatalOnly, boolean show)97     public static Job startLint(List<? extends IResource> resources,
98             IDocument doc, boolean fatalOnly, boolean show) {
99         if (resources != null && !resources.isEmpty()) {
100             resources = addLibraries(resources);
101 
102             cancelCurrentJobs(false);
103 
104             CheckFileJob job = new CheckFileJob(resources, doc, fatalOnly);
105             job.schedule();
106 
107             if (show) {
108                 // Show lint view where the results are listed
109                 LintViewPart.show(resources);
110             }
111             return job;
112         }
113 
114         return null;
115     }
116 
117     /**
118      * Run Lint for an Export APK action. If it succeeds (no fatal errors)
119      * returns true, and if it fails it will display an error message and return
120      * false.
121      *
122      * @param shell the parent shell to show error messages in
123      * @param project the project to run lint on
124      * @return true if the lint run succeeded with no fatal errors
125      */
runLintOnExport(Shell shell, IProject project)126     public static boolean runLintOnExport(Shell shell, IProject project) {
127         if (AdtPrefs.getPrefs().isLintOnExport()) {
128             boolean fatal = EclipseLintRunner.runLint(Collections.singletonList(project), null,
129                     true /*fatalOnly*/);
130             if (fatal) {
131                 MessageDialog.openWarning(shell,
132                         "Export Aborted",
133                         "Export aborted because fatal lint errors were found. These " +
134                         "are listed in the Lint View. Either fix these before " +
135                         "running Export again, or turn off \"Run full error check " +
136                         "when exporting app\" in the Android > Lint Error Checking " +
137                         "preference page.");
138                 return false;
139             }
140         }
141 
142         return true;
143     }
144 
145     /** Returns the current lint jobs, if any (never returns null but array may be empty) */
getCurrentJobs()146     static Job[] getCurrentJobs() {
147         IJobManager jobManager = Job.getJobManager();
148         return jobManager.find(CheckFileJob.FAMILY_RUN_LINT);
149     }
150 
151     /** Cancels the current lint jobs, if any, and optionally waits for them to finish */
cancelCurrentJobs(boolean wait)152     static void cancelCurrentJobs(boolean wait) {
153         // Cancel any current running jobs first
154         Job[] currentJobs = getCurrentJobs();
155         for (Job job : currentJobs) {
156             job.cancel();
157         }
158 
159         if (wait) {
160             for (Job job : currentJobs) {
161                 try {
162                     job.join();
163                 } catch (InterruptedException e) {
164                     AdtPlugin.log(e, null);
165                 }
166             }
167         }
168     }
169 
170     /** If the resource list contains projects, add in any library projects as well */
addLibraries(List<? extends IResource> resources)171     private static List<? extends IResource> addLibraries(List<? extends IResource> resources) {
172         if (resources != null && !resources.isEmpty()) {
173             boolean haveProjects = false;
174             for (IResource resource : resources) {
175                 if (resource instanceof IProject) {
176                     haveProjects = true;
177                     break;
178                 }
179             }
180 
181             if (haveProjects) {
182                 List<IResource> result = new ArrayList<IResource>();
183                 Map<IProject, IProject> allProjects = new IdentityHashMap<IProject, IProject>();
184                 List<IProject> projects = new ArrayList<IProject>();
185                 for (IResource resource : resources) {
186                     if (resource instanceof IProject) {
187                         IProject project = (IProject) resource;
188                         allProjects.put(project, project);
189                         projects.add(project);
190                     } else {
191                         result.add(resource);
192                     }
193                 }
194                 for (IProject project : projects) {
195                     ProjectState state = Sdk.getProjectState(project);
196                     if (state != null) {
197                         for (IProject library : state.getFullLibraryProjects()) {
198                             allProjects.put(library, library);
199                         }
200                     }
201                 }
202                 for (IProject project : allProjects.keySet()) {
203                     result.add(project);
204                 }
205 
206                 return result;
207             }
208         }
209 
210         return resources;
211     }
212 
213     private static final class CheckFileJob extends Job {
214         /** Job family */
215         private static final Object FAMILY_RUN_LINT = new Object();
216         private final List<? extends IResource> mResources;
217         private final IDocument mDocument;
218         private LintDriver mLint;
219         private boolean mFatal;
220         private boolean mFatalOnly;
221 
CheckFileJob(List<? extends IResource> resources, IDocument doc, boolean fatalOnly)222         private CheckFileJob(List<? extends IResource> resources, IDocument doc,
223                 boolean fatalOnly) {
224             super("Running Android Lint");
225             mResources = resources;
226             mDocument = doc;
227             mFatalOnly = fatalOnly;
228         }
229 
230         @Override
belongsTo(Object family)231         public boolean belongsTo(Object family) {
232             return family == FAMILY_RUN_LINT;
233         }
234 
235         @Override
canceling()236         protected void canceling() {
237             super.canceling();
238             if (mLint != null) {
239                 mLint.cancel();
240             }
241         }
242 
243         @Override
run(IProgressMonitor monitor)244         protected IStatus run(IProgressMonitor monitor) {
245             try {
246                 monitor.beginTask("Looking for errors", IProgressMonitor.UNKNOWN);
247                 IssueRegistry registry = EclipseLintClient.getRegistry();
248                 EnumSet<Scope> scope = Scope.ALL;
249                 List<File> files = new ArrayList<File>(mResources.size());
250                 for (IResource resource : mResources) {
251                     File file = AdtUtils.getAbsolutePath(resource).toFile();
252                     files.add(file);
253 
254                     if (resource instanceof IProject) {
255                         scope = Scope.ALL;
256                     } else if (resource instanceof IFile
257                             && AdtUtils.endsWithIgnoreCase(resource.getName(), DOT_XML)) {
258                         if (resource.getName().equals(SdkConstants.FN_ANDROID_MANIFEST_XML)) {
259                             scope = EnumSet.of(Scope.MANIFEST);
260                         } else {
261                             scope = Scope.RESOURCE_FILE_SCOPE;
262                         }
263                     } else {
264                         return new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, Status.ERROR,
265                                 "Only XML files are supported for single file lint", null); //$NON-NLS-1$
266                     }
267                 }
268                 if (Scope.checkSingleFile(scope)) {
269                     // Delete specific markers
270                     for (IResource resource : mResources) {
271                         IMarker[] markers = EclipseLintClient.getMarkers(resource);
272                         for (IMarker marker : markers) {
273                             String id = marker.getAttribute(MARKER_CHECKID_PROPERTY, ""); //$NON-NLS-1$
274                             Issue issue = registry.getIssue(id);
275                             if (issue == null || issue.getScope().equals(scope)) {
276                                 marker.delete();
277                             }
278                         }
279                     }
280                 } else {
281                     EclipseLintClient.clearMarkers(mResources);
282                 }
283 
284                 EclipseLintClient client = new EclipseLintClient(registry, mResources,
285                             mDocument, mFatalOnly);
286                 mLint = new LintDriver(registry, client);
287                 mLint.analyze(files, scope);
288                 mFatal = client.hasFatalErrors();
289                 return Status.OK_STATUS;
290             } catch (Exception e) {
291                 return new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, Status.ERROR,
292                                   "Failed", e); //$NON-NLS-1$
293             } finally {
294                 if (monitor != null) {
295                     monitor.done();
296                 }
297             }
298         }
299 
300         /**
301          * Returns true if a fatal error was encountered
302          */
isFatal()303         boolean isFatal() {
304             return mFatal;
305         }
306     }
307 }
308