Home | History | Annotate | Download | only in lint
      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      */
     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      */
     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      */
    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      */
    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 */
    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 */
    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