aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXavier Ducrohet <xav@android.com>2013-12-04 02:00:18 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2013-12-04 02:00:19 +0000
commit7a05a1b055d6c1f18d592415230ea54bc7e56860 (patch)
treeceac76709b59c322fa7c8fb22c5eee2b599da857
parent39aa53c42ed944e08db1e69ad557756eb84a36ad (diff)
parente8a18c6ee8e4c93df093c00224fdd3b024ed5774 (diff)
downloadbuild-7a05a1b055d6c1f18d592415230ea54bc7e56860.tar.gz
Merge "Update lint support"
-rw-r--r--.gitignore1
-rw-r--r--gradle/src/main/groovy/com/android/build/gradle/BasePlugin.groovy110
-rw-r--r--gradle/src/main/groovy/com/android/build/gradle/internal/LintGradleClient.java119
-rw-r--r--gradle/src/main/groovy/com/android/build/gradle/internal/LintGradleProject.java411
-rw-r--r--gradle/src/main/groovy/com/android/build/gradle/internal/LintGradleRequest.java63
-rw-r--r--gradle/src/main/groovy/com/android/build/gradle/tasks/Lint.groovy273
6 files changed, 743 insertions, 234 deletions
diff --git a/.gitignore b/.gitignore
index 87388fa..233369b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,5 +27,6 @@ tests/repo/*/build
tests/sameNamedLibs/*/build
tests/sameNamedLibs/*/*/build
tests/tictactoe/*/build
+lint-results
/repo
/out
diff --git a/gradle/src/main/groovy/com/android/build/gradle/BasePlugin.groovy b/gradle/src/main/groovy/com/android/build/gradle/BasePlugin.groovy
index 3c206ed..1e3605e 100644
--- a/gradle/src/main/groovy/com/android/build/gradle/BasePlugin.groovy
+++ b/gradle/src/main/groovy/com/android/build/gradle/BasePlugin.groovy
@@ -15,6 +15,7 @@
*/
package com.android.build.gradle
+
import com.android.annotations.NonNull
import com.android.annotations.Nullable
import com.android.build.gradle.api.AndroidSourceSet
@@ -131,6 +132,7 @@ import static com.android.builder.BuilderConstants.FD_INSTRUMENT_TESTS
import static com.android.builder.BuilderConstants.FD_REPORTS
import static com.android.builder.BuilderConstants.INSTRUMENT_TEST
import static java.io.File.separator
+
/**
* Base class for all Android plugins
*/
@@ -216,9 +218,17 @@ public abstract class BasePlugin {
mainPreBuild = project.tasks.create("preBuild")
- lint = project.tasks.create("lint")
+ lint = project.tasks.create("lint", Lint)
lint.description = "Runs lint on all variants."
lint.group = JavaBasePlugin.VERIFICATION_GROUP
+ lint.setPlugin(this)
+ int count = variantDataList.size()
+ for (int i = 0 ; i < count ; i++) {
+ final BaseVariantData baseVariantData = variantDataList.get(i)
+ if (isLintVariant(baseVariantData)) {
+ lint.dependsOn baseVariantData.javaCompileTask
+ }
+ }
project.tasks.check.dependsOn lint
project.afterEvaluate {
@@ -918,89 +928,35 @@ public abstract class BasePlugin {
}
}
- protected void createLintTasks() {
- File configFile = project.file("$project.projectDir/lint.xml")
+ /** Is the given variant relevant for lint? */
+ private static boolean isLintVariant(@NonNull BaseVariantData baseVariantData) {
+ // Only create lint targets for variants like debug and release, not debugTest
+ VariantConfiguration config = baseVariantData.variantConfiguration
+ return config.getType() != VariantConfiguration.Type.TEST;
+ }
- // for each variant setup a lint task
+ // Add tasks for running lint on individual variants. We've already added a
+ // lint task earlier which runs on all variants.
+ protected void createLintTasks() {
int count = variantDataList.size()
for (int i = 0 ; i < count ; i++) {
final BaseVariantData baseVariantData = variantDataList.get(i)
+ if (!isLintVariant(baseVariantData)) {
+ continue;
+ }
+
String variantName = baseVariantData.variantConfiguration.fullName
- Lint lintCheck = project.tasks.create("lint" + variantName, Lint)
+ def capitalizedVariantName = variantName.capitalize()
+ Task lintCheck = project.tasks.create("lint" + capitalizedVariantName, Lint)
lintCheck.dependsOn baseVariantData.javaCompileTask, lintCompile
- lint.dependsOn lintCheck
+ // Note that we don't do "lint.dependsOn lintCheck"; the "lint" target will
+ // on its own run through all variants (and compare results), it doesn't delegate
+ // to the individual tasks (since it needs to coordinate data collection and
+ // reporting)
lintCheck.setPlugin(this)
-
- String outputName = "$project.buildDir/lint/" + variantName
- VariantConfiguration config = baseVariantData.variantConfiguration
- List<LibraryDependency> depList = config.getAllLibraries()
- List<Collection<File>> javaSource = Lists.newArrayList()
- List<Collection<File>> resourceSource = Lists.newArrayList()
-
- // set the java and res source of this variant's flavors
- Iterator<SourceProvider> flavorSources = config.flavorSourceProviders.iterator()
- SourceProvider source
- while (flavorSources.hasNext()) {
- source = flavorSources.next()
- javaSource.add(source.javaDirectories)
- resourceSource.add(source.resDirectories)
- }
-
- javaSource.add(config.defaultSourceSet.javaDirectories)
- if (config.getType() != VariantConfiguration.Type.TEST) {
- javaSource.add(config.buildTypeSourceSet.javaDirectories)
- }
- SourceProvider sourceProvider = config.variantSourceProvider
- if (sourceProvider != null) {
- javaSource.add(sourceProvider.javaDirectories)
- resourceSource.add(sourceProvider.resDirectories)
- }
- sourceProvider = config.multiFlavorSourceProvider
- if (sourceProvider != null) {
- javaSource.add(sourceProvider.javaDirectories)
- resourceSource.add(sourceProvider.resDirectories)
- }
-
- resourceSource.add(config.defaultSourceSet.resDirectories)
- if (config.getType() != VariantConfiguration.Type.TEST) {
- resourceSource.add(config.buildTypeSourceSet.resDirectories)
- }
- File genRes = baseVariantData.renderscriptCompileTask.getResOutputDir()
- if (genRes != null) {
- resourceSource.add(
- Collections.singleton(genRes)
- )
- }
-
- lintCheck.doFirst {
- File compiledLintJar = project.file("$project.buildDir/lint/lint.jar");
- if (compiledLintJar.exists()) {
- lintCheck.addCustomRule(compiledLintJar);
- }
- // set custom lint rules from library dependencies
- for (LibraryDependency dep : depList) {
- File depLintJar = dep.getLintJar()
- if (depLintJar.exists()) {
- lintCheck.addCustomRule(depLintJar)
- }
- }
-
- // set lint output options
- lintCheck.setHtmlOutput(project.file(outputName + "Output.html"))
- lintCheck.setXmlOutput(project.file(outputName + "Output.xml"))
- lintCheck.setQuiet()
-
- // set lint enabled checks
- if (configFile.exists()) {
- lintCheck.setConfig(configFile)
- }
-
- // set lint project options
- lintCheck.setSources(javaSource)
- lintCheck.setClasspath(
- "$project.buildDir/classes/$baseVariantData.variantConfiguration.dirName")
- lintCheck.setLintResources(resourceSource)
- }
+ lintCheck.setVariantName(variantName)
+ lintCheck.description = "Runs lint on the " + capitalizedVariantName + " build"
+ lintCheck.group = JavaBasePlugin.VERIFICATION_GROUP
}
}
diff --git a/gradle/src/main/groovy/com/android/build/gradle/internal/LintGradleClient.java b/gradle/src/main/groovy/com/android/build/gradle/internal/LintGradleClient.java
index 6ebb37f..3bf7b8d 100644
--- a/gradle/src/main/groovy/com/android/build/gradle/internal/LintGradleClient.java
+++ b/gradle/src/main/groovy/com/android/build/gradle/internal/LintGradleClient.java
@@ -17,24 +17,45 @@
package com.android.build.gradle.internal;
import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.build.gradle.BasePlugin;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.Variant;
import com.android.tools.lint.LintCliClient;
import com.android.tools.lint.LintCliFlags;
+import com.android.tools.lint.Warning;
+import com.android.tools.lint.client.api.IssueRegistry;
+import com.android.tools.lint.client.api.LintRequest;
+import com.android.tools.lint.detector.api.Issue;
import com.android.tools.lint.detector.api.Project;
import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
import java.util.List;
+import java.util.Map;
public class LintGradleClient extends LintCliClient {
+ private final AndroidProject mModelProject;
+ private final String mVariantName;
+ private final BasePlugin mPlugin;
private List<File> mCustomRules = Lists.newArrayList();
- private BasePlugin mPlugin;
- public LintGradleClient(@NonNull LintCliFlags flags, @NonNull BasePlugin plugin) {
+ public LintGradleClient(
+ @NonNull IssueRegistry registry,
+ @NonNull LintCliFlags flags,
+ @NonNull BasePlugin plugin,
+ @NonNull AndroidProject modelProject,
+ @Nullable String variantName) {
super(flags);
mPlugin = plugin;
+ mModelProject = modelProject;
+ mVariantName = variantName;
+ mRegistry = registry;
}
@NonNull
@@ -53,7 +74,9 @@ public class LintGradleClient extends LintCliClient {
@Override
protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
- return new LintGradleProject(this, dir, referenceDir);
+ // Should not be called by lint since we supply an explicit set of projects
+ // to the LintRequest
+ throw new IllegalStateException();
}
@Override
@@ -64,4 +87,94 @@ public class LintGradleClient extends LintCliClient {
}
return super.getSdkHome();
}
+
+ @Override
+ @NonNull
+ protected LintRequest createLintRequest(@NonNull List<File> files) {
+ return new LintGradleRequest(this, mModelProject, mPlugin, mVariantName, files);
+ }
+
+ /** Run lint with the given registry and return the resulting warnings */
+ @NonNull
+ public List<Warning> run(@NonNull IssueRegistry registry) throws IOException {
+ run(registry, Collections.<File>emptyList());
+ return mWarnings;
+ }
+
+ /**
+ * Given a list of results from separate variants, merge them into a single
+ * list of warnings, and mark their
+ * @param warningMap a map from variant to corresponding warnings
+ * @param project the project model
+ * @return a merged list of issues
+ */
+ @NonNull
+ public static List<Warning> merge(
+ @NonNull Map<Variant,List<Warning>> warningMap,
+ @NonNull AndroidProject project) {
+ // Easy merge?
+ if (warningMap.size() == 1) {
+ return warningMap.values().iterator().next();
+ }
+ int maxCount = 0;
+ for (List<Warning> warnings : warningMap.values()) {
+ int size = warnings.size();
+ maxCount = Math.max(size, maxCount);
+ }
+ if (maxCount == 0) {
+ return Collections.emptyList();
+ }
+
+ int totalVariantCount = project.getVariants().size();
+
+ List<Warning> merged = Lists.newArrayListWithExpectedSize(2 * maxCount);
+
+ // Map fro issue to message to line number to file name to canonical warning
+ Map<Issue,Map<String, Map<Integer, Map<String, Warning>>>> map =
+ Maps.newHashMapWithExpectedSize(2 * maxCount);
+
+ for (Map.Entry<Variant,List<Warning>> entry : warningMap.entrySet()) {
+ Variant variant = entry.getKey();
+ List<Warning> warnings = entry.getValue();
+ for (Warning warning : warnings) {
+ Map<String,Map<Integer,Map<String,Warning>>> messageMap = map.get(warning.issue);
+ if (messageMap == null) {
+ messageMap = Maps.newHashMap();
+ map.put(warning.issue, messageMap);
+ }
+ Map<Integer, Map<String, Warning>> lineMap = messageMap.get(warning.message);
+ if (lineMap == null) {
+ lineMap = Maps.newHashMap();
+ messageMap.put(warning.message, lineMap);
+ }
+ Map<String, Warning> fileMap = lineMap.get(warning.line);
+ if (fileMap == null) {
+ fileMap = Maps.newHashMap();
+ lineMap.put(warning.line, fileMap);
+ }
+ String fileName = warning.file != null ? warning.file.getName() : "<unknown>";
+ Warning canonical = fileMap.get(fileName);
+ if (canonical == null) {
+ canonical = warning;
+ fileMap.put(fileName, canonical);
+ canonical.variants = Sets.newHashSet();
+ canonical.gradleProject = project;
+ }
+ merged.add(canonical);
+ canonical.variants.add(variant);
+ }
+ }
+
+ // Clear out variants on any nodes that don't define all
+ for (Warning warning : merged) {
+ if (warning.variants != null && warning.variants.size() == totalVariantCount) {
+ // If this error is present in all variants, just clear it out
+ warning.variants = null;
+ }
+
+ }
+
+ Collections.sort(merged);
+ return merged;
+ }
}
diff --git a/gradle/src/main/groovy/com/android/build/gradle/internal/LintGradleProject.java b/gradle/src/main/groovy/com/android/build/gradle/internal/LintGradleProject.java
index 6724268..fb50f00 100644
--- a/gradle/src/main/groovy/com/android/build/gradle/internal/LintGradleProject.java
+++ b/gradle/src/main/groovy/com/android/build/gradle/internal/LintGradleProject.java
@@ -1,32 +1,86 @@
package com.android.build.gradle.internal;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
import com.android.annotations.NonNull;
-import com.android.build.gradle.BasePlugin;
-import com.android.build.gradle.LibraryPlugin;
-import com.android.build.gradle.internal.model.ModelBuilder;
+import com.android.annotations.Nullable;
import com.android.builder.model.AndroidLibrary;
import com.android.builder.model.AndroidProject;
+import com.android.builder.model.BuildTypeContainer;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.ProductFlavor;
+import com.android.builder.model.ProductFlavorContainer;
+import com.android.builder.model.SourceProvider;
import com.android.builder.model.Variant;
-import com.android.tools.lint.client.api.LintClient;
+import com.android.sdklib.AndroidTargetHash;
+import com.android.sdklib.AndroidVersion;
import com.android.tools.lint.detector.api.Project;
+import com.android.utils.Pair;
import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
+import java.util.List;
+import java.util.Set;
-public class LintGradleProject extends Project {
- private AndroidProject mProject;
+import static java.io.File.separatorChar;
- LintGradleProject(
- @NonNull LintClient client,
+/**
+ * An implementation of Lint's {@link Project} class wrapping a Gradle model (project or
+ * library)
+ */
+public class LintGradleProject extends Project {
+ private LintGradleProject(
+ @NonNull LintGradleClient client,
@NonNull File dir,
@NonNull File referenceDir) {
super(client, dir, referenceDir);
-
mGradleProject = true;
mMergeManifests = true;
- mDirectLibraries = Collections.emptyList();
+ mDirectLibraries = Lists.newArrayList();
+ }
+
+ /**
+ * Creates a {@link com.android.build.gradle.internal.LintGradleProject} from
+ * the given {@link com.android.builder.model.AndroidProject} definition for
+ * a given {@link com.android.builder.model.Variant}, and returns it along with
+ * a set of lint custom rule jars applicable for the given model project.
+ *
+ * @param client the client
+ * @param project the model project
+ * @param variant the variant
+ * @param gradleProject the gradle project
+ * @return a pair of new project and list of custom rule jars
+ */
+ @NonNull
+ public static Pair<LintGradleProject, List<File>> create(
+ @NonNull LintGradleClient client,
+ @NonNull AndroidProject project,
+ @NonNull Variant variant,
+ @NonNull org.gradle.api.Project gradleProject) {
+ File dir = gradleProject.getRootDir();
+ AppGradleProject lintProject = new AppGradleProject(client, dir,
+ dir, project, variant);
+
+ List<File> customRules = Lists.newArrayList();
+ File appLintJar = new File(gradleProject.getBuildDir(),
+ "lint" + separatorChar + "lint.jar");
+ if (appLintJar.exists()) {
+ customRules.add(appLintJar);
+ }
+
+ Set<AndroidLibrary> libraries = Sets.newHashSet();
+ Dependencies dependencies = variant.getMainArtifact().getDependencies();
+ for (AndroidLibrary library : dependencies.getLibraries()) {
+ lintProject.addDirectLibrary(createLibrary(client, library, libraries, customRules));
+ }
+
+ return Pair.<LintGradleProject,List<File>>of(lintProject, customRules);
}
+
@Override
protected void initialize() {
// Deliberately not calling super; that code is for ADT compatibility
@@ -37,33 +91,330 @@ public class LintGradleProject extends Project {
return true;
}
- @Override
- public boolean isLibrary() {
- LintGradleClient client = (LintGradleClient) mClient;
- BasePlugin plugin = client.getPlugin();
- return plugin instanceof LibraryPlugin;
+ void addDirectLibrary(@NonNull Project project) {
+ mDirectLibraries.add(project);
}
- @Override
- public AndroidProject getGradleProjectModel() {
- if (mProject == null) {
- LintGradleClient client = (LintGradleClient) mClient;
- BasePlugin plugin = client.getPlugin();
- String modelName = AndroidProject.class.getName();
- ModelBuilder builder = new ModelBuilder();
- mProject = (AndroidProject) builder.buildAll(modelName, plugin.getProject());
+ @NonNull
+ private static LibraryProject createLibrary(@NonNull LintGradleClient client,
+ @NonNull AndroidLibrary library,
+ @NonNull Set<AndroidLibrary> seen, List<File> customRules) {
+ seen.add(library);
+ File dir = library.getFolder();
+ LibraryProject project = new LibraryProject(client, dir, dir, library);
+
+ File ruleJar = library.getLintJar();
+ if (ruleJar.exists()) {
+ customRules.add(ruleJar);
+ }
+
+ for (AndroidLibrary dependent : library.getLibraryDependencies()) {
+ if (!seen.contains(dependent)) {
+ project.addDirectLibrary(createLibrary(client, dependent, seen, customRules));
+ }
}
- return mProject;
+ return project;
}
- @Override
- public AndroidLibrary getGradleLibraryModel() {
- return null;
+ private static class AppGradleProject extends LintGradleProject {
+ private AndroidProject mProject;
+ private Variant mVariant;
+ private List<SourceProvider> mProviders;
+
+ private AppGradleProject(
+ @NonNull LintGradleClient client,
+ @NonNull File dir,
+ @NonNull File referenceDir,
+ @NonNull AndroidProject project,
+ @NonNull Variant variant) {
+ super(client, dir, referenceDir);
+
+ mProject = project;
+ mVariant = variant;
+ }
+
+ @Override
+ public boolean isLibrary() {
+ return mProject.isLibrary();
+ }
+
+ @Override
+ public AndroidProject getGradleProjectModel() {
+ return mProject;
+ }
+
+ @Override
+ public Variant getCurrentVariant() {
+ return mVariant;
+ }
+
+ private List<SourceProvider> getSourceProviders() {
+ if (mProviders == null) {
+ List<SourceProvider> providers = Lists.newArrayList();
+
+ SourceProvider defaultProvider = mProject.getDefaultConfig().getSourceProvider();
+ if (defaultProvider != null) {
+ providers.add(defaultProvider);
+ }
+
+ SourceProvider sourceProvider = mVariant.getMainArtifact().getVariantSourceProvider();
+ if (sourceProvider != null) {
+ providers.add(sourceProvider);
+ }
+
+ sourceProvider = mVariant.getMainArtifact().getMultiFlavorSourceProvider();
+ if (sourceProvider != null) {
+ providers.add(sourceProvider);
+ }
+
+ for (String flavorName : mVariant.getProductFlavors()) {
+ for (ProductFlavorContainer flavor : mProject.getProductFlavors()) {
+ if (flavorName.equals(flavor.getProductFlavor().getName())) {
+ SourceProvider provider = flavor.getSourceProvider();
+ if (provider != null) {
+ providers.add(provider);
+ }
+ break;
+ }
+ }
+ }
+
+ String buildTypeName = mVariant.getBuildType();
+ for (BuildTypeContainer buildType : mProject.getBuildTypes()) {
+ if (buildTypeName.equals(buildType.getBuildType().getName())) {
+ SourceProvider provider = buildType.getSourceProvider();
+ if (provider != null) {
+ providers.add(provider);
+ }
+ break;
+ }
+ }
+ mProviders = providers;
+ }
+
+ return mProviders;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getManifestFiles() {
+ if (mManifestFiles == null) {
+ mManifestFiles = Lists.newArrayList();
+ for (SourceProvider provider : getSourceProviders()) {
+ File manifestFile = provider.getManifestFile();
+ if (manifestFile.exists()) { // model returns path whether or not it exists
+ mManifestFiles.add(manifestFile);
+ }
+ }
+ }
+
+ return mManifestFiles;
+ }
+
+ @Override
+ public List<File> getProguardFiles() {
+ if (mProguardFiles == null) {
+ ProductFlavor flavor = mProject.getDefaultConfig().getProductFlavor();
+ mProguardFiles = Lists.newArrayList();
+ mProguardFiles.addAll(flavor.getProguardFiles());
+ // TODO: This is currently broken:
+ //mProguardFiles.addAll(flavor.getConsumerProguardFiles());
+ // It will throw
+ //org.gradle.tooling.model.UnsupportedMethodException: Unsupported method: BaseConfig.getConsumerProguardFiles().
+ // The version of Gradle you connect to does not support that method.
+ // To resolve the problem you can change/upgrade the target version of Gradle you connect to.
+ // Alternatively, you can ignore this exception and read other information from the model.
+ //at org.gradle.tooling.model.internal.Exceptions.unsupportedMethod(Exceptions.java:33)
+ //at org.gradle.tooling.internal.adapter.ProtocolToModelAdapter$InvocationHandlerImpl.invoke(ProtocolToModelAdapter.java:239)
+ //at com.sun.proxy.$Proxy129.getConsumerProguardFiles(Unknown Source)
+ }
+
+ return mProguardFiles;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getResourceFolders() {
+ if (mResourceFolders == null) {
+ mResourceFolders = Lists.newArrayList();
+ for (SourceProvider provider : getSourceProviders()) {
+ Collection<File> resDirs = provider.getResDirectories();
+ for (File res : resDirs) {
+ if (res.exists()) { // model returns path whether or not it exists
+ mResourceFolders.add(res);
+ }
+ }
+ }
+ }
+
+ return mResourceFolders;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getJavaSourceFolders() {
+ if (mJavaSourceFolders == null) {
+ mJavaSourceFolders = Lists.newArrayList();
+ for (SourceProvider provider : getSourceProviders()) {
+ Collection<File> resDirs = provider.getJavaDirectories();
+ for (File res : resDirs) {
+ if (res.exists()) { // model returns path whether or not it exists
+ mJavaSourceFolders.add(res);
+ }
+ }
+ }
+ }
+
+ return mJavaSourceFolders;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getJavaClassFolders() {
+ if (mJavaClassFolders == null) {
+ mJavaClassFolders = new ArrayList<File>(1);
+ File outputClassFolder = mVariant.getMainArtifact().getClassesFolder();
+ if (outputClassFolder != null && outputClassFolder.exists()) {
+ mJavaClassFolders.add(outputClassFolder);
+ }
+ }
+
+ return mJavaClassFolders;
+ }
+
+ @Override
+ public List<File> getJavaLibraries() {
+ if (mJavaLibraries == null) {
+ Collection<File> jars = mVariant.getMainArtifact().getDependencies().getJars();
+ mJavaLibraries = Lists.newArrayListWithExpectedSize(jars.size());
+ for (File jar : jars) {
+ if (jar.exists()) {
+ mJavaLibraries.add(jar);
+ }
+ }
+ }
+ return mJavaLibraries;
+ }
+
+ @Nullable
+ @Override
+ public String getPackage() {
+ // TODO: Merge with manifest
+ return mProject.getDefaultConfig().getProductFlavor().getPackageName();
+ }
+
+ @Override
+ public int getMinSdk() {
+ // TODO: Merge with manifest
+ return mProject.getDefaultConfig().getProductFlavor().getMinSdkVersion();
+ }
+
+ @Override
+ public int getTargetSdk() {
+ // TODO: Merge with manifest
+ return mProject.getDefaultConfig().getProductFlavor().getTargetSdkVersion();
+ }
+
+ @Override
+ public int getBuildSdk() {
+ String compileTarget = mProject.getCompileTarget();
+ if (compileTarget != null) {
+ AndroidVersion version = AndroidTargetHash.getPlatformVersion(compileTarget);
+ if (version != null) {
+ return version.getApiLevel();
+ }
+ }
+
+ return super.getBuildSdk();
+ }
}
- @Override
- public Variant getCurrentVariant() {
- return null;
+ private static class LibraryProject extends LintGradleProject {
+ private AndroidLibrary mLibrary;
+
+ private LibraryProject(
+ @NonNull LintGradleClient client,
+ @NonNull File dir,
+ @NonNull File referenceDir,
+ @NonNull AndroidLibrary library) {
+ super(client, dir, referenceDir);
+ mLibrary = library;
+
+ // TODO: Make sure we don't use this project for any source library projects!
+ mReportIssues = false;
+ }
+
+ @Override
+ public boolean isLibrary() {
+ return true;
+ }
+
+ @Override
+ public AndroidLibrary getGradleLibraryModel() {
+ return mLibrary;
+ }
+
+ @Override
+ public Variant getCurrentVariant() {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getManifestFiles() {
+ if (mManifestFiles == null) {
+ File manifest = mLibrary.getManifest();
+ if (manifest.exists()) {
+ mManifestFiles = Collections.singletonList(manifest);
+ } else {
+ mManifestFiles = Collections.emptyList();
+ }
+ }
+
+ return mManifestFiles;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getResourceFolders() {
+ if (mResourceFolders == null) {
+ File folder = mLibrary.getResFolder();
+ if (folder.exists()) {
+ mResourceFolders = Collections.singletonList(folder);
+ } else {
+ mResourceFolders = Collections.emptyList();
+ }
+ }
+
+ return mResourceFolders;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getJavaSourceFolders() {
+ return Collections.emptyList();
+ }
+
+ @NonNull
+ @Override
+ public List<File> getJavaClassFolders() {
+ return Collections.emptyList();
+ }
+
+ @NonNull
+ @Override
+ public List<File> getJavaLibraries() {
+ if (mJavaLibraries == null) {
+ File jarFile = mLibrary.getJarFile();
+ if (jarFile.exists()) {
+ mJavaLibraries = Collections.singletonList(jarFile);
+ } else {
+ mJavaLibraries = Collections.emptyList();
+ }
+ }
+
+ return mJavaLibraries;
+ }
}
}
diff --git a/gradle/src/main/groovy/com/android/build/gradle/internal/LintGradleRequest.java b/gradle/src/main/groovy/com/android/build/gradle/internal/LintGradleRequest.java
new file mode 100644
index 0000000..b5674f9
--- /dev/null
+++ b/gradle/src/main/groovy/com/android/build/gradle/internal/LintGradleRequest.java
@@ -0,0 +1,63 @@
+package com.android.build.gradle.internal;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.BasePlugin;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.Variant;
+import com.android.tools.lint.client.api.LintRequest;
+import com.android.tools.lint.detector.api.Project;
+import com.android.utils.Pair;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+class LintGradleRequest extends LintRequest {
+ @NonNull private final LintGradleClient mLintClient;
+ @NonNull private final BasePlugin mPlugin;
+ @NonNull private final String mVariantName;
+ @NonNull private final AndroidProject mModelProject;
+
+ public LintGradleRequest(
+ @NonNull LintGradleClient client,
+ @NonNull AndroidProject modelProject,
+ @NonNull BasePlugin plugin,
+ @Nullable String variantName,
+ @NonNull List<File> files) {
+ super(client, files);
+ mLintClient = client;
+ mModelProject = modelProject;
+ mPlugin = plugin;
+ mVariantName = variantName;
+ }
+
+ @Nullable
+ @Override
+ public Collection<Project> getProjects() {
+ if (mProjects == null) {
+ Variant variant = findVariant(mModelProject, mVariantName);
+ assert variant != null : mVariantName;
+ Pair<LintGradleProject,List<File>> result = LintGradleProject.create(
+ mLintClient, mModelProject, variant, mPlugin.getProject());
+ mProjects = Collections.<Project>singletonList(result.getFirst());
+ mLintClient.setCustomRules(result.getSecond());
+ }
+
+ return mProjects;
+ }
+
+ private static Variant findVariant(@NonNull AndroidProject project,
+ @NonNull String variantName) {
+ if (variantName != null) {
+ for (Variant variant : project.getVariants()) {
+ if (variantName.equals(variant.getName())) {
+ return variant;
+ }
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/gradle/src/main/groovy/com/android/build/gradle/tasks/Lint.groovy b/gradle/src/main/groovy/com/android/build/gradle/tasks/Lint.groovy
index 17d62cf..088ee72 100644
--- a/gradle/src/main/groovy/com/android/build/gradle/tasks/Lint.groovy
+++ b/gradle/src/main/groovy/com/android/build/gradle/tasks/Lint.groovy
@@ -20,37 +20,39 @@ import com.android.annotations.NonNull
import com.android.annotations.Nullable
import com.android.build.gradle.BasePlugin
import com.android.build.gradle.internal.LintGradleClient
+import com.android.build.gradle.internal.model.ModelBuilder
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.Variant
import com.android.tools.lint.HtmlReporter
import com.android.tools.lint.LintCliFlags
import com.android.tools.lint.Reporter
+import com.android.tools.lint.Warning
import com.android.tools.lint.XmlReporter
import com.android.tools.lint.checks.BuiltinIssueRegistry
import com.android.tools.lint.client.api.IssueRegistry
-import com.android.tools.lint.detector.api.LintUtils
+import com.android.tools.lint.detector.api.Severity
+import com.google.common.collect.Maps
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
+import org.gradle.api.Project
import org.gradle.api.tasks.TaskAction
+import static com.android.SdkConstants.DOT_XML
+
public class Lint extends DefaultTask {
@NonNull private BasePlugin mPlugin
- @Nullable private List<File> mCustomRules
@Nullable private File mConfigFile
@Nullable private File mHtmlOutput
@Nullable private File mXmlOutput
- @Nullable private List<Collection<File>> mSourceSets
- @Nullable private String mClassPath
- @Nullable private List<Collection<File>> mResourceSets
- private boolean mQuiet
+ @Nullable private String mVariantName
+ private boolean mQuiet = true
public void setPlugin(@NonNull BasePlugin plugin) {
mPlugin = plugin
}
- public void addCustomRule(@NonNull File jar) {
- if (mCustomRules == null) {
- mCustomRules = new ArrayList<File>()
- }
- mCustomRules.add(jar)
+ public void setVariantName(@NonNull String variantName) {
+ mVariantName = variantName
}
public void setQuiet() {
@@ -66,95 +68,84 @@ public class Lint extends DefaultTask {
}
public void setXmlOutput(@NonNull File xmlOutput) {
- mXmlOutput = xmlOutput;
- }
-
- /**
- * Adds all files in sourceSets as a source file for lint.
- *
- * @param sourceSets files to be added to sources.
- */
- public void setSources(@NonNull List<Collection<File>> sourceSets) {
- mSourceSets = sourceSets
- }
-
- /**
- * Adds all class files in directory specified by paths for lint.
- *
- * @param paths A set of paths to class files separated with path separators
- */
- public void setClasspath(@NonNull String paths) {
- mClassPath = paths
- }
-
- /**
- * Adds all files in resourceSets as a resource file for lint.
- *
- * @param resourceSets files to be added to resources.
- */
- public void setLintResources(@NonNull List<Set<File>> resourceSets) {
- mResourceSets = resourceSets
+ mXmlOutput = xmlOutput
}
@SuppressWarnings("GroovyUnusedDeclaration")
@TaskAction
public void lint() {
- IssueRegistry registry = new BuiltinIssueRegistry()
- LintCliFlags flags = new LintCliFlags()
- LintGradleClient client = new LintGradleClient(flags, mPlugin)
-
- // Configure Reporters
+ def modelProject = createAndroidProject(mPlugin.getProject())
+ if (mVariantName != null) {
+ lintSingleVariant(modelProject, mVariantName)
+ } else {
+ lintAllVariants(modelProject)
+ }
+ }
- if (mHtmlOutput != null) {
- mHtmlOutput = mHtmlOutput.getAbsoluteFile()
- if (mHtmlOutput.exists()) {
- boolean delete = mHtmlOutput.delete()
- if (!delete) {
- throw new GradleException("Could not delete old " + mHtmlOutput)
- }
- }
- if (mHtmlOutput.getParentFile() != null && !mHtmlOutput.getParentFile().canWrite()) {
- throw new GradleException("Cannot write HTML output file " + mHtmlOutput)
- }
+ /**
+ * Runs lint individually on all the variants, and then compares the results
+ * across variants and reports these
+ */
+ public void lintAllVariants(@NonNull AndroidProject modelProject) {
+ Map<Variant,List<Warning>> warningMap = Maps.newHashMap()
+ for (Variant variant : modelProject.getVariants()) {
try {
- flags.getReporters().add(new HtmlReporter(client, mHtmlOutput))
+ List<Warning> warnings = runLint(modelProject, variant.getName())
+ warningMap.put(variant, warnings)
} catch (IOException e) {
- throw new GradleException("HTML invalid argument.", e)
+ throw new GradleException("Invalid arguments.", e)
}
}
- if (mXmlOutput != null) {
- mXmlOutput = mXmlOutput.getAbsoluteFile()
- if (mXmlOutput.exists()) {
- boolean delete = mXmlOutput.delete();
- if (!delete) {
- throw new GradleException("Could not delete old " + mXmlOutput)
- }
- }
- if (mXmlOutput.getParentFile() != null && !mXmlOutput.getParentFile().canWrite()) {
- throw new GradleException("Cannot write XML output file " + mXmlOutput)
- }
- try {
- flags.getReporters().add(new XmlReporter(client, mXmlOutput))
- } catch (IOException e) {
- throw new GradleException("XML invalid argument.", e)
- }
+ // Compute error matrix
+ for (Map.Entry<Variant,List<Warning>> entry : warningMap.entrySet()) {
+ def variant = entry.getKey()
+ def warnings = entry.getValue()
+ println "Ran lint on variant " + variant.getName() + ": " + warnings.size() +
+ " issues found"
}
- List<Reporter> reporters = flags.getReporters()
- if (reporters.isEmpty()) {
- throw new GradleException("No reporter specified.")
+ List<Warning> mergedWarnings = LintGradleClient.merge(warningMap, modelProject)
+ int errorCount = 0
+ int warningCount = 0
+ for (Warning warning : mergedWarnings) {
+ if (warning.severity == Severity.ERROR || warning.severity == Severity.FATAL) {
+ errorCount++
+ } else if (warning.severity == Severity.WARNING) {
+ warningCount++
+ }
}
- Map<String, String> map = new HashMap<String, String>(){{
- put("", "file://")
- }}
- for (Reporter reporter : reporters) {
- reporter.setUrlMap(map)
+ IssueRegistry registry = new BuiltinIssueRegistry()
+ LintCliFlags flags = new LintCliFlags()
+ LintGradleClient client = new LintGradleClient(registry, flags, mPlugin, modelProject,
+ null)
+ configureReporters(client, flags, null)
+ for (Reporter reporter : flags.getReporters()) {
+ reporter.write(errorCount, warningCount, mergedWarnings)
}
+ }
- // Flags
+ /**
+ * Runs lint on a single specified variant
+ */
+ public void lintSingleVariant(@NonNull AndroidProject modelProject, String variantName) {
+ runLint(modelProject, variantName)
+ }
+
+ /** Runs lint on the given variant and returns the set of warnings */
+ private List<Warning> runLint(
+ @NonNull AndroidProject modelProject,
+ @NonNull String variantName) {
+ IssueRegistry registry = new BuiltinIssueRegistry()
+ LintCliFlags flags = new LintCliFlags()
+ LintGradleClient client = new LintGradleClient(registry, flags, mPlugin, modelProject,
+ variantName)
+
+ // Configure Reporters
+ configureReporters(client, flags, variantName)
+ // Flags
if (mQuiet) {
flags.setQuiet(true)
}
@@ -162,59 +153,93 @@ public class Lint extends DefaultTask {
flags.setDefaultConfiguration(client.createConfigurationFromFile(mConfigFile))
}
- // Flags: sources, resources, classes
+ // Finally perform lint run
+ try {
+ return client.run(registry)
+ } catch (IOException e) {
+ throw new GradleException("Invalid arguments.", e)
+ }
+ }
- for (Collection<File> args : mSourceSets) {
- for (File input : args) {
- if (input.exists()) {
- List<File> sources = flags.getSourcesOverride()
- if (sources == null) {
- sources = new ArrayList<File>()
- flags.setSourcesOverride(sources)
- }
- sources.add(input)
- }
+ private void configureReporters(@NonNull LintGradleClient client, @NonNull LintCliFlags flags,
+ @Nullable String variantName) {
+ StringBuilder base = new StringBuilder()
+ base.append("lint-results/")
+ if (variantName != null) {
+ base.append(variantName)
+ base.append("/")
+ }
+ base.append("lint-results")
+ if (variantName != null) {
+ base.append("-")
+ base.append(variantName)
+ }
+ File htmlOutput = mHtmlOutput
+ File xmlOutput = mXmlOutput
+ if (htmlOutput == null) {
+ htmlOutput = project.file(base.toString() + ".html")
+ File parent = htmlOutput.parentFile
+ if (!parent.exists()) {
+ parent.mkdirs()
}
}
- for (String path : LintUtils.splitPath(mClassPath)) {
- File input = new File(path);
- if (!input.exists()) {
- throw new GradleException("Class path entry " + input + " does not exist.")
- }
- List<File> classes = flags.getClassesOverride();
- if (classes == null) {
- classes = new ArrayList<File>();
- flags.setClassesOverride(classes);
- }
- classes.add(input);
- }
-
- for (Collection<File> args : mResourceSets) {
- for (File input : args) {
- if (input.exists()) {
- List<File> resources = flags.getResourcesOverride()
- if (resources == null) {
- resources = new ArrayList<File>()
- flags.setResourcesOverride(resources)
- }
- resources.add(input)
- }
+ if (xmlOutput == null) {
+ xmlOutput = project.file(base.toString() + DOT_XML)
+ File parent = xmlOutput.parentFile
+ if (!parent.exists()) {
+ parent.mkdirs()
}
}
- // Client setup
-
- if (mCustomRules != null) {
- client.setCustomRules(mCustomRules)
+ htmlOutput = htmlOutput.getAbsoluteFile()
+ if (htmlOutput.exists()) {
+ boolean delete = htmlOutput.delete()
+ if (!delete) {
+ throw new GradleException("Could not delete old " + htmlOutput)
+ }
+ }
+ if (htmlOutput.getParentFile() != null && !htmlOutput.getParentFile().canWrite()) {
+ throw new GradleException("Cannot write HTML output file " + htmlOutput)
+ }
+ try {
+ flags.getReporters().add(new HtmlReporter(client, htmlOutput))
+ } catch (IOException e) {
+ throw new GradleException("HTML invalid argument.", e)
}
- // Finally perform lint run
-
+ xmlOutput = xmlOutput.getAbsoluteFile()
+ if (xmlOutput.exists()) {
+ boolean delete = xmlOutput.delete()
+ if (!delete) {
+ throw new GradleException("Could not delete old " + xmlOutput)
+ }
+ }
+ if (xmlOutput.getParentFile() != null && !xmlOutput.getParentFile().canWrite()) {
+ throw new GradleException("Cannot write XML output file " + xmlOutput)
+ }
try {
- client.run(registry, Arrays.asList(project.projectDir));
+ flags.getReporters().add(new XmlReporter(client, xmlOutput))
} catch (IOException e) {
- throw new GradleException("Invalid arguments.", e)
+ throw new GradleException("XML invalid argument.", e)
}
+
+ List<Reporter> reporters = flags.getReporters()
+ if (reporters.isEmpty()) {
+ throw new GradleException("No reporter specified.")
+ }
+
+ Map<String, String> map = new HashMap<String, String>() {{
+ put("", "file://")
+ }}
+ for (Reporter reporter : reporters) {
+ reporter.setUrlMap(map)
+ }
+ }
+
+ private static AndroidProject createAndroidProject(@NonNull Project gradleProject) {
+ String modelName = AndroidProject.class.getName()
+ ModelBuilder builder = new ModelBuilder()
+ return (AndroidProject) builder.buildAll(modelName, gradleProject)
}
}