Add lintOptions#checkReleaseBuilds which runs lint on release builds
If lintOptions.checkReleaseBuilds is true (which it is by default) the
assemble task for non-debug targets will now run lint and check all
issues that have severity fatal.
It also tweaks the error message shown when the build aborts due to
a lint error. If you run a lint target, the message will look like this:
> Lint found errors in the project; aborting build.
Fix the issues identified by lint, or add the following to your build
script to proceed with errors:
...
android {
lintOptions {
abortOnError false
}
}
...
If the build fails during release target assembly, it looks like this:
* What went wrong:
Execution failed for task ':lintVitalRelease'.
> Lint found fatal errors while assembling a release target.
To proceed, either fix the issues identified by lint, or modify your
build script as follows:
...
android {
lintOptions {
checkReleaseBuilds false
// Or, if you prefer, you can continue to check for errors
// in release builds, but continue the build even when
// errors are found:
abortOnError false
}
}
...
Change-Id: I87debf8df0ed88f327b4df7119a9dfb057c34945
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/LintOptions.java b/build-system/builder-model/src/main/java/com/android/builder/model/LintOptions.java
index c2ae6de..ae3f632 100644
--- a/build-system/builder-model/src/main/java/com/android/builder/model/LintOptions.java
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/LintOptions.java
@@ -34,6 +34,9 @@
* quiet true
* // if true, stop the gradle build if errors are found
* abortOnError false
+ * // set to true to have all release builds run lint on issues with severity=fatal
+ * // and abort the build (controlled by abortOnError above) if fatal issues are found
+ * checkReleaseBuilds true
* // if true, only report errors
* ignoreWarnings true
* // if true, emit full/absolute paths to files with errors (true by default)
@@ -163,4 +166,9 @@
@Nullable
public File getXmlOutput();
+ /**
+ * Returns whether lint should check for fatal errors during release builds. Default is true.
+ * If issues with severity "fatal" are found, the release build is aborted.
+ */
+ public boolean isCheckReleaseBuilds();
}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/BasePlugin.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/BasePlugin.groovy
index 6efd8e9..2ab63fb 100644
--- a/build-system/gradle/src/main/groovy/com/android/build/gradle/BasePlugin.groovy
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/BasePlugin.groovy
@@ -174,6 +174,8 @@
protected Task deviceCheck
protected Task connectedCheck
protected Task lintCompile
+ protected Task lintAll
+ protected Task lintVital
protected BasePlugin(Instantiator instantiator, ToolingModelBuilderRegistry registry) {
this.instantiator = instantiator
@@ -277,6 +279,14 @@
doCreateAndroidTasks()
createReportTasks()
+
+ if (lintVital != null) {
+ project.gradle.taskGraph.whenReady { taskGraph ->
+ if (taskGraph.hasTask(lintAll)) {
+ lintVital.setEnabled(false)
+ }
+ }
+ }
}
void checkTasksAlreadyCreated() {
@@ -933,6 +943,7 @@
lint.group = JavaBasePlugin.VERIFICATION_GROUP
lint.setPlugin(this)
project.tasks.check.dependsOn lint
+ lintAll = lint
int count = variantDataList.size()
for (int i = 0 ; i < count ; i++) {
@@ -959,6 +970,25 @@
}
}
+ private void createLintVitalTask(@NonNull ApkVariantData variantData) {
+ assert extension.lintOptions.checkReleaseBuilds
+ if (!variantData.variantConfiguration.buildType.debuggable) {
+ String variantName = variantData.variantConfiguration.fullName
+ def capitalizedVariantName = variantName.capitalize()
+ def taskName = "lintVital" + capitalizedVariantName
+ Lint lintReleaseCheck = project.tasks.create(taskName, Lint)
+ // TODO: Make this task depend on lintCompile too (resolve initialization order first)
+ lintReleaseCheck.dependsOn variantData.javaCompileTask
+ lintReleaseCheck.setPlugin(this)
+ lintReleaseCheck.setVariantName(variantName)
+ lintReleaseCheck.setFatalOnly(true)
+ lintReleaseCheck.description = "Runs lint on just the fatal issues in the " +
+ capitalizedVariantName + " build"
+ variantData.assembleTask.dependsOn lintReleaseCheck
+ lintVital = lintReleaseCheck
+ }
+ }
+
protected void createCheckTasks(boolean hasFlavors, boolean isLibraryTest) {
List<AndroidReportTask> reportTasks = Lists.newArrayListWithExpectedSize(2)
@@ -1368,6 +1398,9 @@
}
assembleTask.dependsOn appTask
variantData.assembleTask = assembleTask
+ if (extension.lintOptions.checkReleaseBuilds) {
+ createLintVitalTask(variantData)
+ }
variantData.outputFile = { outputFileTask.outputFile }
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/LintOptionsImpl.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/LintOptionsImpl.groovy
index 253cdd3..b04f801 100644
--- a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/LintOptionsImpl.groovy
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/LintOptionsImpl.groovy
@@ -60,6 +60,8 @@
private boolean warningsAsErrors
@Input
private boolean showAll
+ @Input
+ private boolean checkReleaseBuilds = true;
@InputFile
private File lintConfig
@Input
@@ -96,7 +98,8 @@
boolean checkAllWarnings,
boolean ignoreWarnings,
boolean warningsAsErrors,
- boolean showAll) {
+ boolean showAll,
+ boolean checkReleaseBuilds) {
this.disable = disable
this.enable = enable
this.check = check
@@ -115,6 +118,7 @@
this.ignoreWarnings = ignoreWarnings
this.warningsAsErrors = warningsAsErrors
this.showAll = showAll
+ this.checkReleaseBuilds = checkReleaseBuilds
}
@NonNull
@@ -137,7 +141,8 @@
source.isCheckAllWarnings(),
source.isIgnoreWarnings(),
source.isWarningsAsErrors(),
- source.isShowAll()
+ source.isShowAll(),
+ source.isCheckReleaseBuilds()
)
}
@@ -296,6 +301,15 @@
this.showAll = showAll
}
+ @Override
+ public boolean isCheckReleaseBuilds() {
+ return checkReleaseBuilds;
+ }
+
+ public void setCheckReleaseBuilds(boolean checkReleaseBuilds) {
+ this.checkReleaseBuilds = checkReleaseBuilds
+ }
+
/**
* Returns the default configuration file to use as a fallback
*/
@@ -390,8 +404,8 @@
flags.setShowEverything(showAll)
flags.setDefaultConfiguration(lintConfig)
- if (report) {
- if (textReport) {
+ if (report || flags.isFatalOnly()) {
+ if (textReport || flags.isFatalOnly()) {
File output = textOutput
if (output == null) {
output = new File(STDOUT)
@@ -420,8 +434,8 @@
}
if (xmlReport) {
File output = xmlOutput
- if (output == null) {
- output = createOutputPath(project, variantName, DOT_XML)
+ if (output == null || flags.isFatalOnly()) {
+ output = createOutputPath(project, variantName, DOT_XML, flags.isFatalOnly())
} else if (!output.isAbsolute()) {
output = project.file(output.getPath())
}
@@ -434,8 +448,8 @@
}
if (htmlReport) {
File output = htmlOutput
- if (output == null) {
- output = createOutputPath(project, variantName, ".html")
+ if (output == null || flags.isFatalOnly()) {
+ output = createOutputPath(project, variantName, ".html", flags.isFatalOnly())
} else if (!output.isAbsolute()) {
output = project.file(output.getPath())
}
@@ -488,13 +502,17 @@
private static File createOutputPath(
@NonNull Project project,
@NonNull String variantName,
- @NonNull String extension) {
+ @NonNull String extension,
+ boolean fatalOnly) {
StringBuilder base = new StringBuilder()
base.append("lint-results")
if (variantName != null) {
base.append("-")
base.append(variantName)
}
+ if (fatalOnly) {
+ base.append("-fatal")
+ }
base.append(extension)
return new File(project.buildDir, base.toString())
}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/DefaultAndroidProject.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/DefaultAndroidProject.java
index b8f7ec2..4c61d48 100644
--- a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/DefaultAndroidProject.java
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/DefaultAndroidProject.java
@@ -18,7 +18,6 @@
import com.android.annotations.NonNull;
import com.android.build.gradle.internal.CompileOptions;
-import com.android.build.gradle.internal.dsl.LintOptionsImpl;
import com.android.builder.model.AaptOptions;
import com.android.builder.model.AndroidProject;
import com.android.builder.model.ArtifactMetaData;
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/Lint.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/Lint.groovy
index 60af0a4..2e67f97 100644
--- a/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/Lint.groovy
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/Lint.groovy
@@ -37,11 +37,10 @@
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 String mVariantName
+ private boolean mFatalOnly
public void setPlugin(@NonNull BasePlugin plugin) {
mPlugin = plugin
@@ -51,6 +50,10 @@
mVariantName = variantName
}
+ public void setFatalOnly(boolean fatalOnly) {
+ mFatalOnly = fatalOnly
+ }
+
@SuppressWarnings("GroovyUnusedDeclaration")
@TaskAction
public void lint() {
@@ -108,10 +111,44 @@
}
if (flags.isSetExitCode() && errorCount > 0) {
- throw new GradleException("Lint found errors with abortOnError=true; aborting build.")
+ abort()
}
}
+ private void abort() {
+ def message;
+ if (mFatalOnly) {
+ message = "" +
+ "Lint found fatal errors while assembling a release target.\n" +
+ "\n" +
+ "To proceed, either fix the issues identified by lint, or modify your build script as follows:\n" +
+ "...\n" +
+ "android {\n" +
+ " lintOptions {\n" +
+ " checkReleaseBuilds false\n" +
+ " // Or, if you prefer, you can continue to check for errors in release builds,\n" +
+ " // but continue the build even when errors are found:\n" +
+ " abortOnError false\n" +
+ " }\n" +
+ "}\n" +
+ "..."
+ ""
+ } else {
+ message = "" +
+ "Lint found errors in the project; aborting build.\n" +
+ "\n" +
+ "Fix the issues identified by lint, or add the following to your build script to proceed with errors:\n" +
+ "...\n" +
+ "android {\n" +
+ " lintOptions {\n" +
+ " abortOnError false\n" +
+ " }\n" +
+ "}\n" +
+ "..."
+ }
+ throw new GradleException(message);
+ }
+
/**
* Runs lint on a single specified variant
*/
@@ -128,7 +165,14 @@
LintCliFlags flags = new LintCliFlags()
LintGradleClient client = new LintGradleClient(registry, flags, mPlugin, modelProject,
variantName)
- mPlugin.getExtension().lintOptions.syncTo(client, flags, variantName, project, report)
+ def options = mPlugin.getExtension().lintOptions
+ if (mFatalOnly) {
+ if (!options.isCheckReleaseBuilds()) {
+ return
+ }
+ flags.setFatalOnly(true)
+ }
+ options.syncTo(client, flags, variantName, project, report)
List<Warning> warnings;
try {
@@ -138,7 +182,7 @@
}
if (report && client.haveErrors() && flags.isSetExitCode()) {
- throw new GradleException("Lint found errors with abortOnError=true; aborting build.")
+ abort()
}
return warnings;
diff --git a/lint/cli/src/main/java/com/android/tools/lint/LintCliClient.java b/lint/cli/src/main/java/com/android/tools/lint/LintCliClient.java
index ae004a2..e440ba3 100644
--- a/lint/cli/src/main/java/com/android/tools/lint/LintCliClient.java
+++ b/lint/cli/src/main/java/com/android/tools/lint/LintCliClient.java
@@ -156,7 +156,7 @@
@Override
public Configuration getConfiguration(@NonNull Project project) {
- return new CliConfiguration(getConfiguration(), project);
+ return new CliConfiguration(getConfiguration(), project, mFlags.isFatalOnly());
}
/** File content cache */
@@ -374,12 +374,17 @@
* flags supplied on the command line
*/
class CliConfiguration extends DefaultConfiguration {
- CliConfiguration(@NonNull Configuration parent, @NonNull Project project) {
+ private boolean mFatalOnly;
+
+ CliConfiguration(@NonNull Configuration parent, @NonNull Project project,
+ boolean fatalOnly) {
super(LintCliClient.this, project, parent);
+ mFatalOnly = fatalOnly;
}
- CliConfiguration(File lintFile) {
+ CliConfiguration(File lintFile, boolean fatalOnly) {
super(LintCliClient.this, null /*project*/, null /*parent*/, lintFile);
+ mFatalOnly = fatalOnly;
}
@NonNull
@@ -387,7 +392,11 @@
public Severity getSeverity(@NonNull Issue issue) {
Severity severity = computeSeverity(issue);
- if (mFlags.isWarningsAsErrors() && severity != Severity.IGNORE) {
+ if (mFatalOnly && severity != Severity.FATAL) {
+ return Severity.IGNORE;
+ }
+
+ if (mFlags.isWarningsAsErrors() && severity.compareTo(Severity.ERROR) < 0) {
severity = Severity.ERROR;
}
@@ -598,7 +607,7 @@
}
public Configuration createConfigurationFromFile(File file) {
- return new CliConfiguration(file);
+ return new CliConfiguration(file, mFlags.isFatalOnly());
}
@SuppressWarnings("resource") // Eclipse doesn't know about Closeables.closeQuietly
diff --git a/lint/cli/src/main/java/com/android/tools/lint/LintCliFlags.java b/lint/cli/src/main/java/com/android/tools/lint/LintCliFlags.java
index caf74ce..6380e81 100644
--- a/lint/cli/src/main/java/com/android/tools/lint/LintCliFlags.java
+++ b/lint/cli/src/main/java/com/android/tools/lint/LintCliFlags.java
@@ -48,6 +48,7 @@
private boolean mWarnAll;
private boolean mNoWarnings;
private boolean mAllErrors;
+ private boolean mFatalOnly;
private List<File> mSources;
private List<File> mClasses;
private List<File> mLibraries;
@@ -337,4 +338,20 @@
public void setResourcesOverride(@Nullable List<File> resources) {
mResources = resources;
}
+
+ /**
+ * Returns true if we should only check fatal issues
+ * @return true if we should only check fatal issues
+ */
+ public boolean isFatalOnly() {
+ return mFatalOnly;
+ }
+
+ /**
+ * Sets whether we should only check fatal issues
+ * @param fatalOnly if true, only check fatal issues
+ */
+ public void setFatalOnly(boolean fatalOnly) {
+ mFatalOnly = fatalOnly;
+ }
}