/* * 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.lint; import com.android.annotations.NonNull; import com.android.annotations.Nullable; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; import com.android.ide.eclipse.adt.internal.sdk.ProjectState; import com.android.ide.eclipse.adt.internal.sdk.Sdk; import com.android.tools.lint.client.api.IssueRegistry; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.text.IDocument; import org.eclipse.swt.widgets.Shell; import java.util.ArrayList; import java.util.Collections; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; /** * Eclipse implementation for running lint on workspace files and projects. */ public class EclipseLintRunner { static final String MARKER_CHECKID_PROPERTY = "checkid"; //$NON-NLS-1$ /** * Runs lint and updates the markers, and waits for the result. Returns * true if fatal errors were found. * * @param resources the resources (project, folder or file) to be analyzed * @param source if checking a single source file, the source file * @param doc the associated document, if known, or null * @param fatalOnly if true, only report fatal issues (severity=error) * @return true if any fatal errors were encountered. */ private static boolean runLint( @NonNull List<? extends IResource> resources, @Nullable IResource source, @Nullable IDocument doc, boolean fatalOnly) { resources = addLibraries(resources); LintJob job = (LintJob) startLint(resources, source, doc, fatalOnly, false /*show*/); try { job.join(); boolean fatal = job.isFatal(); if (fatal) { LintViewPart.show(resources); } return fatal; } catch (InterruptedException e) { AdtPlugin.log(e, null); } return false; } /** * Runs lint and updates the markers. Does not wait for the job to finish - * just returns immediately. * * @param resources the resources (project, folder or file) to be analyzed * @param source if checking a single source file, the source file. When * single checking an XML file, this is typically the same as the * file passed in the list in the first parameter, but when * checking the .class files of a Java file for example, the * .class file and all the inner classes of the Java file are * passed in the first parameter, and the corresponding .java * source file is passed here. * @param doc the associated document, if known, or null * @param fatalOnly if true, only report fatal issues (severity=error) * @param show if true, show the results in a {@link LintViewPart} * @return the job running lint in the background. */ public static Job startLint( @NonNull List<? extends IResource> resources, @Nullable IResource source, @Nullable IDocument doc, boolean fatalOnly, boolean show) { IssueRegistry registry = EclipseLintClient.getRegistry(); EclipseLintClient client = new EclipseLintClient(registry, resources, doc, fatalOnly); return startLint(client, resources, source, show); } /** * Runs lint and updates the markers. Does not wait for the job to finish - * just returns immediately. * * @param client the lint client receiving issue reports etc * @param resources the resources (project, folder or file) to be analyzed * @param source if checking a single source file, the source file. When * single checking an XML file, this is typically the same as the * file passed in the list in the first parameter, but when * checking the .class files of a Java file for example, the * .class file and all the inner classes of the Java file are * passed in the first parameter, and the corresponding .java * source file is passed here. * @param show if true, show the results in a {@link LintViewPart} * @return the job running lint in the background. */ public static Job startLint( @NonNull EclipseLintClient client, @NonNull List<? extends IResource> resources, @Nullable IResource source, boolean show) { if (resources != null && !resources.isEmpty()) { if (!AdtPrefs.getPrefs().getSkipLibrariesFromLint()) { resources = addLibraries(resources); } cancelCurrentJobs(false); LintJob job = new LintJob(client, resources, source); job.schedule(); if (show) { // Show lint view where the results are listed LintViewPart.show(resources); } return job; } return null; } /** * Run Lint for an Export APK action. If it succeeds (no fatal errors) * returns true, and if it fails it will display an error message and return * false. * * @param shell the parent shell to show error messages in * @param project the project to run lint on * @return true if the lint run succeeded with no fatal errors */ public static boolean runLintOnExport(Shell shell, IProject project) { if (AdtPrefs.getPrefs().isLintOnExport()) { boolean fatal = EclipseLintRunner.runLint(Collections.singletonList(project), null, null, true /*fatalOnly*/); if (fatal) { MessageDialog.openWarning(shell, "Export Aborted", "Export aborted because fatal lint errors were found. These " + "are listed in the Lint View. Either fix these before " + "running Export again, or turn off \"Run full error check " + "when exporting app\" in the Android > Lint Error Checking " + "preference page."); return false; } } return true; } /** Cancels the current lint jobs, if any, and optionally waits for them to finish */ static void cancelCurrentJobs(boolean wait) { // Cancel any current running jobs first Job[] currentJobs = LintJob.getCurrentJobs(); for (Job job : currentJobs) { job.cancel(); } if (wait) { for (Job job : currentJobs) { try { job.join(); } catch (InterruptedException e) { AdtPlugin.log(e, null); } } } } /** If the resource list contains projects, add in any library projects as well */ private static List<? extends IResource> addLibraries(List<? extends IResource> resources) { if (resources != null && !resources.isEmpty()) { boolean haveProjects = false; for (IResource resource : resources) { if (resource instanceof IProject) { haveProjects = true; break; } } if (haveProjects) { List<IResource> result = new ArrayList<IResource>(); Map<IProject, IProject> allProjects = new IdentityHashMap<IProject, IProject>(); List<IProject> projects = new ArrayList<IProject>(); for (IResource resource : resources) { if (resource instanceof IProject) { IProject project = (IProject) resource; allProjects.put(project, project); projects.add(project); } else { result.add(resource); } } for (IProject project : projects) { ProjectState state = Sdk.getProjectState(project); if (state != null) { for (IProject library : state.getFullLibraryProjects()) { allProjects.put(library, library); } } } for (IProject project : allProjects.keySet()) { result.add(project); } return result; } } return resources; } }