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 19 import com.android.annotations.NonNull; 20 import com.android.annotations.Nullable; 21 import com.android.ide.eclipse.adt.AdtPlugin; 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.tools.lint.client.api.IssueRegistry; 26 27 import org.eclipse.core.resources.IProject; 28 import org.eclipse.core.resources.IResource; 29 import org.eclipse.core.runtime.jobs.Job; 30 import org.eclipse.jface.dialogs.MessageDialog; 31 import org.eclipse.jface.text.IDocument; 32 import org.eclipse.swt.widgets.Shell; 33 34 import java.util.ArrayList; 35 import java.util.Collections; 36 import java.util.IdentityHashMap; 37 import java.util.List; 38 import java.util.Map; 39 40 /** 41 * Eclipse implementation for running lint on workspace files and projects. 42 */ 43 public class EclipseLintRunner { 44 static final String MARKER_CHECKID_PROPERTY = "checkid"; //$NON-NLS-1$ 45 46 /** 47 * Runs lint and updates the markers, and waits for the result. Returns 48 * true if fatal errors were found. 49 * 50 * @param resources the resources (project, folder or file) to be analyzed 51 * @param source if checking a single source file, the source file 52 * @param doc the associated document, if known, or null 53 * @param fatalOnly if true, only report fatal issues (severity=error) 54 * @return true if any fatal errors were encountered. 55 */ runLint( @onNull List<? extends IResource> resources, @Nullable IResource source, @Nullable IDocument doc, boolean fatalOnly)56 private static boolean runLint( 57 @NonNull List<? extends IResource> resources, 58 @Nullable IResource source, 59 @Nullable IDocument doc, 60 boolean fatalOnly) { 61 resources = addLibraries(resources); 62 LintJob job = (LintJob) startLint(resources, source, doc, fatalOnly, 63 false /*show*/); 64 try { 65 job.join(); 66 boolean fatal = job.isFatal(); 67 68 if (fatal) { 69 LintViewPart.show(resources); 70 } 71 72 return fatal; 73 } catch (InterruptedException e) { 74 AdtPlugin.log(e, null); 75 } 76 77 return false; 78 } 79 80 /** 81 * Runs lint and updates the markers. Does not wait for the job to finish - 82 * just returns immediately. 83 * 84 * @param resources the resources (project, folder or file) to be analyzed 85 * @param source if checking a single source file, the source file. When 86 * single checking an XML file, this is typically the same as the 87 * file passed in the list in the first parameter, but when 88 * checking the .class files of a Java file for example, the 89 * .class file and all the inner classes of the Java file are 90 * passed in the first parameter, and the corresponding .java 91 * source file is passed here. 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( @onNull List<? extends IResource> resources, @Nullable IResource source, @Nullable IDocument doc, boolean fatalOnly, boolean show)97 public static Job startLint( 98 @NonNull List<? extends IResource> resources, 99 @Nullable IResource source, 100 @Nullable IDocument doc, 101 boolean fatalOnly, 102 boolean show) { 103 IssueRegistry registry = EclipseLintClient.getRegistry(); 104 EclipseLintClient client = new EclipseLintClient(registry, resources, doc, fatalOnly); 105 return startLint(client, resources, source, show); 106 } 107 108 /** 109 * Runs lint and updates the markers. Does not wait for the job to finish - 110 * just returns immediately. 111 * 112 * @param client the lint client receiving issue reports etc 113 * @param resources the resources (project, folder or file) to be analyzed 114 * @param source if checking a single source file, the source file. When 115 * single checking an XML file, this is typically the same as the 116 * file passed in the list in the first parameter, but when 117 * checking the .class files of a Java file for example, the 118 * .class file and all the inner classes of the Java file are 119 * passed in the first parameter, and the corresponding .java 120 * source file is passed here. 121 * @param show if true, show the results in a {@link LintViewPart} 122 * @return the job running lint in the background. 123 */ startLint( @onNull EclipseLintClient client, @NonNull List<? extends IResource> resources, @Nullable IResource source, boolean show)124 public static Job startLint( 125 @NonNull EclipseLintClient client, 126 @NonNull List<? extends IResource> resources, 127 @Nullable IResource source, 128 boolean show) { 129 if (resources != null && !resources.isEmpty()) { 130 if (!AdtPrefs.getPrefs().getSkipLibrariesFromLint()) { 131 resources = addLibraries(resources); 132 } 133 134 cancelCurrentJobs(false); 135 136 LintJob job = new LintJob(client, resources, source); 137 job.schedule(); 138 139 if (show) { 140 // Show lint view where the results are listed 141 LintViewPart.show(resources); 142 } 143 return job; 144 } 145 146 return null; 147 } 148 149 /** 150 * Run Lint for an Export APK action. If it succeeds (no fatal errors) 151 * returns true, and if it fails it will display an error message and return 152 * false. 153 * 154 * @param shell the parent shell to show error messages in 155 * @param project the project to run lint on 156 * @return true if the lint run succeeded with no fatal errors 157 */ runLintOnExport(Shell shell, IProject project)158 public static boolean runLintOnExport(Shell shell, IProject project) { 159 if (AdtPrefs.getPrefs().isLintOnExport()) { 160 boolean fatal = EclipseLintRunner.runLint(Collections.singletonList(project), 161 null, null, true /*fatalOnly*/); 162 if (fatal) { 163 MessageDialog.openWarning(shell, 164 "Export Aborted", 165 "Export aborted because fatal lint errors were found. These " + 166 "are listed in the Lint View. Either fix these before " + 167 "running Export again, or turn off \"Run full error check " + 168 "when exporting app\" in the Android > Lint Error Checking " + 169 "preference page."); 170 return false; 171 } 172 } 173 174 return true; 175 } 176 177 /** Cancels the current lint jobs, if any, and optionally waits for them to finish */ cancelCurrentJobs(boolean wait)178 static void cancelCurrentJobs(boolean wait) { 179 // Cancel any current running jobs first 180 Job[] currentJobs = LintJob.getCurrentJobs(); 181 for (Job job : currentJobs) { 182 job.cancel(); 183 } 184 185 if (wait) { 186 for (Job job : currentJobs) { 187 try { 188 job.join(); 189 } catch (InterruptedException e) { 190 AdtPlugin.log(e, null); 191 } 192 } 193 } 194 } 195 196 /** If the resource list contains projects, add in any library projects as well */ addLibraries(List<? extends IResource> resources)197 private static List<? extends IResource> addLibraries(List<? extends IResource> resources) { 198 if (resources != null && !resources.isEmpty()) { 199 boolean haveProjects = false; 200 for (IResource resource : resources) { 201 if (resource instanceof IProject) { 202 haveProjects = true; 203 break; 204 } 205 } 206 207 if (haveProjects) { 208 List<IResource> result = new ArrayList<IResource>(); 209 Map<IProject, IProject> allProjects = new IdentityHashMap<IProject, IProject>(); 210 List<IProject> projects = new ArrayList<IProject>(); 211 for (IResource resource : resources) { 212 if (resource instanceof IProject) { 213 IProject project = (IProject) resource; 214 allProjects.put(project, project); 215 projects.add(project); 216 } else { 217 result.add(resource); 218 } 219 } 220 for (IProject project : projects) { 221 ProjectState state = Sdk.getProjectState(project); 222 if (state != null) { 223 for (IProject library : state.getFullLibraryProjects()) { 224 allProjects.put(library, library); 225 } 226 } 227 } 228 for (IProject project : allProjects.keySet()) { 229 result.add(project); 230 } 231 232 return result; 233 } 234 } 235 236 return resources; 237 } 238 } 239