Add lintOptions to the gradle plugin

By adding a lintOptions section to your build.gradle you can now
customize how lint is run with all the same flags available to
the command line runner (except for the flags querying available
lint checks etc).

For example:

android {
  ..
  lintOptions {
    // set to true to turn off analysis progress reporting by lint
    quiet true
    // if true, stop the gradle build if errors are found
    abortOnError false
    // if true, only report errors
    ignoreWarnings true
    // if true, emit full/absolute paths to files with errors (true by default)
    //absolutePaths true
    // if true, check all issues, including those that are off by default
    checkAllWarnings true
    // if true, treat all warnings as errors
    warningsAsErrors true
    // turn off checking the given issue id's
    disable 'TypographyFractions','TypographyQuotes'
    // turn on the given issue id's
    enable 'RtlHardcoded','RtlCompat', 'RtlEnabled'
    // check *only* the given issue id's
    check 'NewApi', 'InlinedApi'
    // if true, don't include source code lines in the error output
    noLines true
    // if true, show all locations for an error, do not truncate lists, etc.
    showAll true
    // Fallback lint configuration (default severities, etc.)
    lintConfig file("default-lint.xml")
    // if true, generate a text report of issues (false by default)
    textReport true
    // location to write the output; can be a file or 'stdout'
    textOutput 'stdout'
    // if true, generate an XML report for use by for example Jenkins
    xmlReport true
    // file to write report to (if not specified, defaults to lint-results.xml)
    xmlOutput file("lint-report.xml")
    // if true, generate an HTML report (with issue explanations, sourcecode, etc)
    htmlReport false
    // optional path to report (default will be lint-results.html in the builddir)
    htmlOutput file("lint-report.html")
  }
}

Change-Id: I2c14282165c3c772b7c4e6fbcb2cae978ebd3eb0
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/AndroidProject.java b/build-system/builder-model/src/main/java/com/android/builder/model/AndroidProject.java
index f1118f4..9a630d3 100644
--- a/build-system/builder-model/src/main/java/com/android/builder/model/AndroidProject.java
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/AndroidProject.java
@@ -138,6 +138,14 @@
     AaptOptions getAaptOptions();
 
     /**
+     * Returns the lint options.
+     *
+     * @return the lint options.
+     */
+    @NonNull
+    LintOptions getLintOptions();
+
+    /**
      * Returns the dependencies that were not successfully resolved. The returned list gets
      * populated only if the system property {@link #BUILD_MODEL_ONLY_SYSTEM_PROPERTY} has been
      * set to {@code true}.
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
new file mode 100644
index 0000000..c2ae6de
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/LintOptions.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.builder.model;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import java.io.File;
+import java.util.Set;
+
+/**
+ * Options for lint.
+ * Example:
+ *
+ * <pre>
+ *
+ * android {
+ *    lintOptions {
+ *          // set to true to turn off analysis progress reporting by lint
+ *          quiet true
+ *          // if true, stop the gradle build if errors are found
+ *          abortOnError false
+ *          // if true, only report errors
+ *          ignoreWarnings true
+ *          // if true, emit full/absolute paths to files with errors (true by default)
+ *          //absolutePaths true
+ *          // if true, check all issues, including those that are off by default
+ *          checkAllWarnings true
+ *          // if true, treat all warnings as errors
+ *          warningsAsErrors true
+ *          // turn off checking the given issue id's
+ *          disable 'TypographyFractions','TypographyQuotes'
+ *          // turn on the given issue id's
+ *          enable 'RtlHardcoded','RtlCompat', 'RtlEnabled'
+ *          // check *only* the given issue id's
+ *          check 'NewApi', 'InlinedApi'
+ *          // if true, don't include source code lines in the error output
+ *          noLines true
+ *          // if true, show all locations for an error, do not truncate lists, etc.
+ *          showAll true
+ *          // Fallback lint configuration (default severities, etc.)
+ *          lintConfig file("default-lint.xml")
+ *          // if true, generate a text report of issues (false by default)
+ *          textReport true
+ *          // location to write the output; can be a file or 'stdout'
+ *          //textOutput 'stdout'
+ *          textOutput file("lint-results.txt")
+ *          // if true, generate an XML report for use by for example Jenkins
+ *          xmlReport true
+ *          // file to write report to (if not specified, defaults to lint-results.xml)
+ *          xmlOutput file("lint-report.xml")
+ *          // if true, generate an HTML report (with issue explanations, sourcecode, etc)
+ *          htmlReport true
+ *          // optional path to report (default will be lint-results.html in the builddir)
+ *          htmlOutput file("lint-report.html")
+ *     }
+ * }
+ * </pre>
+ */
+public interface LintOptions {
+    /**
+     * Returns the set of issue id's to suppress. Callers are allowed to modify this collection.
+     * To suppress a given issue, add the lint issue id to the returned set.
+     */
+    @NonNull
+    public Set<String> getDisable();
+
+    /**
+     * Returns the set of issue id's to enable. Callers are allowed to modify this collection.
+     * To enable a given issue, add the lint issue id to the returned set.
+     */
+    @NonNull
+    public Set<String> getEnable();
+
+    /**
+     * Returns the exact set of issues to check, or null to run the issues that are enabled
+     * by default plus any issues enabled via {@link #getEnable} and without issues disabled
+     * via {@link #getDisable}. If non-null, callers are allowed to modify this collection.
+     */
+    @Nullable
+    public Set<String> getCheck();
+
+    /** Whether lint should abort the build if errors are found */
+    public boolean isAbortOnError();
+
+    /**
+     * Whether lint should display full paths in the error output. By default the paths
+     * are relative to the path lint was invoked from.
+     */
+    public boolean isAbsolutePaths();
+
+    /**
+     * Whether lint should include the source lines in the output where errors occurred
+     * (true by default)
+     */
+    public boolean isNoLines();
+
+    /**
+     * Returns whether lint should be quiet (for example, not show progress dots for each analyzed
+     * file)
+     */
+    public boolean isQuiet();
+
+    /** Returns whether lint should check all warnings, including those off by default */
+    public boolean isCheckAllWarnings();
+
+    /** Returns whether lint will only check for errors (ignoring warnings) */
+    public boolean isIgnoreWarnings();
+
+    /** Returns whether lint should treat all warnings as errors */
+    public boolean isWarningsAsErrors();
+
+    /**
+     * Returns whether lint should include all output (e.g. include all alternate
+     * locations, not truncating long messages, etc.)
+     */
+    public boolean isShowAll();
+
+    /**
+     * Returns an optional path to a lint.xml configuration file
+     */
+    @Nullable
+    public File getLintConfig();
+
+    /** Whether we should write an text report. Default false. The location can be
+     * controlled by {@link #getTextOutput()}. */
+    public boolean getTextReport();
+
+    /**
+     * The optional path to where a text report should be written. The special value
+     * "stdout" can be used to point to standard output.
+     */
+    @Nullable
+    public File getTextOutput();
+
+    /** Whether we should write an HTML report. Default true. The location can be
+     * controlled by {@link #getHtmlOutput()}. */
+    public boolean getHtmlReport();
+
+    /** The optional path to where an HTML report should be written */
+    @Nullable
+    public File getHtmlOutput();
+
+    /** Whether we should write an XML report. Default true. The location can be
+     * controlled by {@link #getXmlOutput()}. */
+    public boolean getXmlReport();
+
+    /** The optional path to where an XML report should be written */
+    @Nullable
+    public File getXmlOutput();
+
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/AppPlugin.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/AppPlugin.groovy
index 32a13ad..c65544b74 100644
--- a/build-system/gradle/src/main/groovy/com/android/build/gradle/AppPlugin.groovy
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/AppPlugin.groovy
@@ -74,6 +74,11 @@
     }
 
     @Override
+    public AppExtension getExtension() {
+        return extension
+    }
+
+    @Override
     void apply(Project project) {
         super.apply(project)
 
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/BaseExtension.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/BaseExtension.groovy
index 974fe16..ba72ffc 100644
--- a/build-system/gradle/src/main/groovy/com/android/build/gradle/BaseExtension.groovy
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/BaseExtension.groovy
@@ -26,6 +26,7 @@
 import com.android.build.gradle.internal.dsl.AaptOptionsImpl
 import com.android.build.gradle.internal.dsl.AndroidSourceSetFactory
 import com.android.build.gradle.internal.dsl.DexOptionsImpl
+import com.android.build.gradle.internal.dsl.LintOptionsImpl
 import com.android.build.gradle.internal.dsl.ProductFlavorDsl
 import com.android.build.gradle.internal.test.TestOptions
 import com.android.builder.BuilderConstants
@@ -56,6 +57,7 @@
 
     final DefaultProductFlavor defaultConfig
     final AaptOptionsImpl aaptOptions
+    final LintOptionsImpl lintOptions
     final DexOptionsImpl dexOptions
     final TestOptions testOptions
     final CompileOptions compileOptions
@@ -82,6 +84,7 @@
 
         aaptOptions = instantiator.newInstance(AaptOptionsImpl.class)
         dexOptions = instantiator.newInstance(DexOptionsImpl.class)
+        lintOptions = instantiator.newInstance(LintOptionsImpl.class)
         testOptions = instantiator.newInstance(TestOptions.class)
         compileOptions = instantiator.newInstance(CompileOptions.class)
 
@@ -168,6 +171,11 @@
         action.execute(dexOptions)
     }
 
+    void lintOptions(Action<LintOptionsImpl> action) {
+        plugin.checkTasksAlreadyCreated();
+        action.execute(lintOptions)
+    }
+
     void testOptions(Action<TestOptions> action) {
         plugin.checkTasksAlreadyCreated();
         action.execute(testOptions)
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 34b4130..b06ed55 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
@@ -187,7 +187,7 @@
         }
     }
 
-    protected abstract BaseExtension getExtension()
+    public abstract BaseExtension getExtension()
     protected abstract void doCreateAndroidTasks()
 
     protected void apply(Project project) {
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/LibraryPlugin.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/LibraryPlugin.groovy
index 14cce5e..96f4654 100644
--- a/build-system/gradle/src/main/groovy/com/android/build/gradle/LibraryPlugin.groovy
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/LibraryPlugin.groovy
@@ -69,6 +69,11 @@
     }
 
     @Override
+    public LibraryExtension getExtension() {
+        return extension
+    }
+
+    @Override
     void apply(Project project) {
         super.apply(project)
 
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
new file mode 100644
index 0000000..46d7e5c
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/LintOptionsImpl.groovy
@@ -0,0 +1,536 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.build.gradle.internal.dsl
+
+import com.android.annotations.NonNull
+import com.android.annotations.Nullable;
+import com.android.builder.model.LintOptions
+import com.android.tools.lint.HtmlReporter
+import com.android.tools.lint.LintCliClient
+import com.android.tools.lint.LintCliFlags
+import com.android.tools.lint.Reporter
+import com.android.tools.lint.TextReporter
+import com.android.tools.lint.XmlReporter
+import com.google.common.collect.Sets
+import org.gradle.api.GradleException
+import org.gradle.api.Project
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputFile
+import org.gradle.api.tasks.OutputFile
+
+import static com.android.SdkConstants.DOT_XML
+
+public class LintOptionsImpl implements LintOptions {
+    public static final String STDOUT = "stdout"
+
+    @Input
+    private Set<String> disable = Sets.newHashSet()
+    @Input
+    private Set<String> enable = Sets.newHashSet()
+    @Input
+    private Set<String> check = Sets.newHashSet()
+    @Input
+    private boolean abortOnError
+    @Input
+    private boolean absolutePaths = true
+    @Input
+    private boolean noLines
+    @Input
+    private boolean quiet = true
+    @Input
+    private boolean checkAllWarnings
+    @Input
+    private boolean ignoreWarnings
+    @Input
+    private boolean warningsAsErrors
+    @Input
+    private boolean showAll
+    @InputFile
+    private File lintConfig
+    @Input
+    private boolean textReport
+    @OutputFile
+    private File textOutput
+    @Input
+    private boolean htmlReport = true
+    @OutputFile
+    private File htmlOutput
+    @Input
+    private boolean xmlReport = true
+    @OutputFile
+    private File xmlOutput
+
+    public LintOptionsImpl() {
+    }
+
+    public LintOptionsImpl(
+            @Nullable Set<String> disable,
+            @Nullable Set<String> enable,
+            @Nullable Set<String> check,
+            @Nullable File lintConfig,
+            boolean textReport,
+            @Nullable File textOutput,
+            boolean htmlReport,
+            @Nullable File htmlOutput,
+            boolean xmlReport,
+            @Nullable File xmlOutput,
+            boolean abortOnError,
+            boolean absolutePaths,
+            boolean noLines,
+            boolean quiet,
+            boolean checkAllWarnings,
+            boolean ignoreWarnings,
+            boolean warningsAsErrors,
+            boolean showAll) {
+        this.disable = disable
+        this.enable = enable
+        this.check = check
+        this.lintConfig = lintConfig
+        this.textReport = textReport
+        this.textOutput = textOutput
+        this.htmlReport = htmlReport
+        this.htmlOutput = htmlOutput
+        this.xmlReport = xmlReport
+        this.xmlOutput = xmlOutput
+        this.abortOnError = abortOnError
+        this.absolutePaths = absolutePaths
+        this.noLines = noLines
+        this.quiet = quiet
+        this.checkAllWarnings = checkAllWarnings
+        this.ignoreWarnings = ignoreWarnings
+        this.warningsAsErrors = warningsAsErrors
+        this.showAll = showAll
+    }
+
+    @NonNull
+    static LintOptions create(@NonNull LintOptions source) {
+        return new LintOptionsImpl(
+                source.getDisable(),
+                source.getEnable(),
+                source.getCheck(),
+                source.getLintConfig(),
+                source.getTextReport(),
+                source.getTextOutput(),
+                source.getHtmlReport(),
+                source.getHtmlOutput(),
+                source.getXmlReport(),
+                source.getXmlOutput(),
+                source.isAbortOnError(),
+                source.isAbsolutePaths(),
+                source.isNoLines(),
+                source.isQuiet(),
+                source.isCheckAllWarnings(),
+                source.isIgnoreWarnings(),
+                source.isWarningsAsErrors(),
+                source.isShowAll()
+        )
+    }
+
+    /**
+     * Returns the set of issue id's to suppress. Callers are allowed to modify this collection.
+     */
+    @NonNull
+    public Set<String> getDisable() {
+        return disable
+    }
+
+    /**
+     * Sets the set of issue id's to suppress. Callers are allowed to modify this collection.
+     * Note that these ids add to rather than replace the given set of ids.
+     */
+    public void setDisable(@Nullable Set<String> ids) {
+        disable.addAll(ids)
+    }
+
+    /**
+     * Returns the set of issue id's to enable. Callers are allowed to modify this collection.
+     * To enable a given issue, add the {@link com.android.tools.lint.detector.api.Issue#getId()} to the returned set.
+     */
+    @NonNull
+    public Set<String> getEnable() {
+        return enable
+    }
+
+    /**
+     * Sets the set of issue id's to enable. Callers are allowed to modify this collection.
+     * Note that these ids add to rather than replace the given set of ids.
+     */
+    public void setEnable(@Nullable Set<String> ids) {
+        enable.addAll(ids)
+    }
+
+    /**
+     * Returns the exact set of issues to check, or null to run the issues that are enabled
+     * by default plus any issues enabled via {@link #getEnable} and without issues disabled
+     * via {@link #getDisable}. If non-null, callers are allowed to modify this collection.
+     */
+    @Nullable
+    public Set<String> getCheck() {
+        return check
+    }
+
+    /**
+     * Sets the <b>exact</b> set of issues to check.
+     * @param ids the set of issue id's to check
+     */
+    public void setCheck(@Nullable Set<String> ids) {
+        check.addAll(ids)
+    }
+
+    /** Whether lint should set the exit code of the process if errors are found */
+    public boolean isAbortOnError() {
+        return this.abortOnError
+    }
+
+    /** Sets whether lint should set the exit code of the process if errors are found */
+    public void setAbortOnError(boolean abortOnError) {
+        this.abortOnError = abortOnError
+    }
+
+    /**
+     * Whether lint should display full paths in the error output. By default the paths
+     * are relative to the path lint was invoked from.
+     */
+    public boolean isAbsolutePaths() {
+        return absolutePaths
+    }
+
+    /**
+     * Sets whether lint should display full paths in the error output. By default the paths
+     * are relative to the path lint was invoked from.
+     */
+    public void setAbsolutePaths(boolean absolutePaths) {
+        this.absolutePaths = absolutePaths
+    }
+
+    /**
+     * Whether lint should include the source lines in the output where errors occurred
+     * (true by default)
+     */
+    public boolean isNoLines() {
+        return this.noLines
+    }
+
+    /**
+     * Sets whether lint should include the source lines in the output where errors occurred
+     * (true by default)
+     */
+    public void setNoLines(boolean noLines) {
+        this.noLines = noLines
+    }
+
+    /**
+     * Returns whether lint should be quiet (for example, not show progress dots for each analyzed
+     * file)
+     */
+    public boolean isQuiet() {
+        return quiet
+    }
+
+    /**
+     * Sets whether lint should be quiet (for example, not show progress dots for each analyzed
+     * file)
+     */
+    public void setQuiet(boolean quiet) {
+        this.quiet = quiet
+    }
+
+    /** Returns whether lint should check all warnings, including those off by default */
+    public boolean isCheckAllWarnings() {
+        return checkAllWarnings
+    }
+
+    /** Sets whether lint should check all warnings, including those off by default */
+    public void setCheckAllWarnings(boolean warnAll) {
+        this.checkAllWarnings = warnAll
+    }
+
+    /** Returns whether lint will only check for errors (ignoring warnings) */
+    public boolean isIgnoreWarnings() {
+        return ignoreWarnings
+    }
+
+    /** Sets whether lint will only check for errors (ignoring warnings) */
+    public void setIgnoreWarnings(boolean noWarnings) {
+        this.ignoreWarnings = noWarnings
+    }
+
+    /** Returns whether lint should treat all warnings as errors */
+    public boolean isWarningsAsErrors() {
+        return warningsAsErrors
+    }
+
+    /** Sets whether lint should treat all warnings as errors */
+    public void setWarningsAsErrors(boolean allErrors) {
+        this.warningsAsErrors = allErrors
+    }
+
+    /**
+     * Returns whether lint should include all output (e.g. include all alternate
+     * locations, not truncating long messages, etc.)
+     */
+    public boolean isShowAll() {
+        return showAll
+    }
+
+    /**
+     * Sets whether lint should include all output (e.g. include all alternate
+     * locations, not truncating long messages, etc.)
+     */
+    public void setShowAll(boolean showAll) {
+        this.showAll = showAll
+    }
+
+    /**
+     * Returns the default configuration file to use as a fallback
+     */
+    public File getLintConfig() {
+        return lintConfig
+    }
+
+    @Override
+    boolean getTextReport() {
+        return textReport
+    }
+
+    void setTextReport(boolean textReport) {
+        this.textReport = textReport
+    }
+
+    void setTextOutput(@NonNull File textOutput) {
+        this.textOutput = textOutput
+    }
+
+    void setHtmlReport(boolean htmlReport) {
+        this.htmlReport = htmlReport
+    }
+
+    void setHtmlOutput(@NonNull File htmlOutput) {
+        this.htmlOutput = htmlOutput
+    }
+
+    void setXmlReport(boolean xmlReport) {
+        this.xmlReport = xmlReport
+    }
+
+    void setXmlOutput(@NonNull File xmlOutput) {
+        this.xmlOutput = xmlOutput
+    }
+
+    @Override
+    File getTextOutput() {
+        return textOutput
+    }
+
+    @Override
+    boolean getHtmlReport() {
+        return htmlReport
+    }
+
+    @Override
+    File getHtmlOutput() {
+        return htmlOutput
+    }
+
+    @Override
+    boolean getXmlReport() {
+        return xmlReport
+    }
+
+    @Override
+    File getXmlOutput() {
+        return xmlOutput
+    }
+
+    /**
+     * Sets the default config file to use as a fallback. This corresponds to a {@code lint.xml}
+     * file with severities etc to use when a project does not have more specific information.
+     */
+    public void setLintConfig(@NonNull File lintConfig) {
+        this.lintConfig = lintConfig
+    }
+
+    public void syncTo(
+            @NonNull LintCliClient client,
+            @NonNull LintCliFlags flags,
+            @Nullable String variantName,
+            @Nullable Project project,
+            boolean report) {
+        if (disable != null) {
+            flags.getSuppressedIds().addAll(disable)
+        }
+        if (enable != null) {
+            flags.getEnabledIds().addAll(enable)
+        }
+        if (check != null && !check.isEmpty()) {
+            flags.setExactCheckedIds(check)
+        }
+        flags.setSetExitCode(this.abortOnError)
+        flags.setFullPath(absolutePaths)
+        flags.setShowSourceLines(!noLines)
+        flags.setQuiet(quiet)
+        flags.setCheckAllWarnings(checkAllWarnings)
+        flags.setIgnoreWarnings(ignoreWarnings)
+        flags.setWarningsAsErrors(warningsAsErrors)
+        flags.setShowEverything(showAll)
+        flags.setDefaultConfiguration(lintConfig)
+
+        if (report) {
+            if (textReport) {
+                File output = textOutput
+                if (output == null) {
+                    output = new File(STDOUT)
+                } else if (!output.isAbsolute() && !isStdOut(output)) {
+                    output = project.file(output.getPath())
+                }
+                output = validateOutputFile(output)
+
+                Writer writer
+                File file = null
+                boolean closeWriter
+                if (isStdOut(output)) {
+                    writer = new PrintWriter(System.out, true)
+                    closeWriter = false
+                } else {
+                    file = output
+                    try {
+                        writer = new BufferedWriter(new FileWriter(output))
+                    } catch (IOException e) {
+                        throw new GradleException("Text invalid argument.", e)
+                    }
+                    closeWriter = true
+                }
+                flags.getReporters().add(new TextReporter(client, flags, file, writer,
+                        closeWriter))
+            }
+            if (xmlReport) {
+                File output = xmlOutput
+                if (output == null) {
+                    output = createOutputPath(project, variantName, DOT_XML)
+                } else if (!output.isAbsolute()) {
+                    output = project.file(output.getPath())
+                }
+                output = validateOutputFile(output)
+                try {
+                    flags.getReporters().add(new XmlReporter(client, output))
+                } catch (IOException e) {
+                    throw new GradleException("XML invalid argument.", e)
+                }
+            }
+            if (htmlReport) {
+                File output = htmlOutput
+                if (output == null) {
+                    output = createOutputPath(project, variantName, ".html")
+                } else if (!output.isAbsolute()) {
+                    output = project.file(output.getPath())
+                }
+                output = validateOutputFile(output)
+                try {
+                    flags.getReporters().add(new HtmlReporter(client, output))
+                } catch (IOException e) {
+                    throw new GradleException("HTML invalid argument.", e)
+                }
+            }
+
+            Map<String, String> map = new HashMap<String, String>() {{
+                put("", "file://")
+            }}
+            for (Reporter reporter : flags.getReporters()) {
+                reporter.setUrlMap(map)
+            }
+        }
+    }
+
+    private static boolean isStdOut(@NonNull File output) {
+        return STDOUT.equals(output.getPath())
+    }
+
+    @NonNull
+    private static File validateOutputFile(@NonNull File output) {
+        if (isStdOut(output)) {
+            return output
+        }
+
+        File parent = output.parentFile
+        if (!parent.exists()) {
+            parent.mkdirs()
+        }
+
+        output = output.getAbsoluteFile()
+        if (output.exists()) {
+            boolean delete = output.delete()
+            if (!delete) {
+                throw new GradleException("Could not delete old " + output)
+            }
+        }
+        if (output.getParentFile() != null && !output.getParentFile().canWrite()) {
+            throw new GradleException("Cannot write output file " + output)
+        }
+
+        return output
+    }
+
+    private static File createOutputPath(
+            @NonNull Project project,
+            @NonNull String variantName,
+            @NonNull String extension) {
+        StringBuilder base = new StringBuilder()
+        base.append("lint-results")
+        if (variantName != null) {
+            base.append("-")
+            base.append(variantName)
+        }
+        base.append(extension)
+        return new File(project.buildDir, base.toString())
+    }
+
+    // -- DSL Methods. TODO remove once the instantiator does what I expect it to do.
+
+    public void check(String id) {
+        check.add(id)
+    }
+
+    public void check(String... ids) {
+        check.addAll(ids)
+    }
+
+    public void enable(String id) {
+        enable.add(id)
+    }
+
+    public void enable(String... ids) {
+        enable.addAll(ids)
+    }
+
+    public void disable(String id) {
+        disable.add(id)
+    }
+
+    public void disable(String... ids) {
+        disable.addAll(ids)
+    }
+
+    // For textOutput 'stdout' (normally a file)
+    void textOutput(String textOutput) {
+        this.textOutput = new File(textOutput)
+    }
+
+    // For textOutput file()
+    void textOutput(File textOutput) {
+        this.textOutput = textOutput;
+    }
+}
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 fab28d1..b8f7ec2 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,11 +18,13 @@
 
 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;
 import com.android.builder.model.BuildTypeContainer;
 import com.android.builder.model.JavaCompileOptions;
+import com.android.builder.model.LintOptions;
 import com.android.builder.model.ProductFlavorContainer;
 import com.android.builder.model.SigningConfig;
 import com.android.builder.model.Variant;
@@ -56,6 +58,8 @@
     private final Collection<String> unresolvedDependencies;
     @NonNull
     private final JavaCompileOptions javaCompileOptions;
+    @NonNull
+    private final LintOptions lintOptions;
     private final boolean isLibrary;
 
     private final Collection<BuildTypeContainer> buildTypes = Lists.newArrayList();
@@ -73,6 +77,7 @@
                           @NonNull Collection<ArtifactMetaData> extraArtifacts,
                           @NonNull Collection<String> unresolvedDependencies,
                           @NonNull CompileOptions compileOptions,
+                          @NonNull LintOptions lintOptions,
                           boolean isLibrary) {
         this.modelVersion = modelVersion;
         this.name = name;
@@ -83,6 +88,7 @@
         this.extraArtifacts = extraArtifacts;
         this.unresolvedDependencies = unresolvedDependencies;
         javaCompileOptions = new DefaultJavaCompileOptions(compileOptions);
+        this.lintOptions = lintOptions;
         this.isLibrary = isLibrary;
     }
 
@@ -190,6 +196,12 @@
 
     @Override
     @NonNull
+    public LintOptions getLintOptions() {
+        return lintOptions;
+    }
+
+    @Override
+    @NonNull
     public Collection<String> getUnresolvedDependencies() {
         return unresolvedDependencies;
     }
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/ModelBuilder.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/ModelBuilder.groovy
index 41ec14f..e04678a 100644
--- a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/ModelBuilder.groovy
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/ModelBuilder.groovy
@@ -22,6 +22,7 @@
 import com.android.build.gradle.LibraryPlugin
 import com.android.build.gradle.internal.BuildTypeData
 import com.android.build.gradle.internal.ProductFlavorData
+import com.android.build.gradle.internal.dsl.LintOptionsImpl
 import com.android.build.gradle.internal.variant.ApplicationVariantData
 import com.android.build.gradle.internal.variant.BaseVariantData
 import com.android.build.gradle.internal.variant.LibraryVariantData
@@ -33,6 +34,7 @@
 import com.android.builder.model.AndroidProject
 import com.android.builder.model.ArtifactMetaData
 import com.android.builder.model.JavaArtifact
+import com.android.builder.model.LintOptions
 import com.android.builder.model.SigningConfig
 import com.android.builder.model.SourceProvider
 import com.android.builder.model.SourceProviderContainer
@@ -94,6 +96,8 @@
                         true /*isTest*/,
                         ArtifactMetaData.TYPE_ANDROID));
 
+        LintOptions lintOptions = LintOptionsImpl.create(basePlugin.extension.lintOptions)
+
         //noinspection GroovyVariableNotAssigned
         DefaultAndroidProject androidProject = new DefaultAndroidProject(
                 getModelVersion(),
@@ -105,6 +109,7 @@
                 artifactMetaDataList,
                 basePlugin.unresolvedDependencies,
                 basePlugin.extension.compileOptions,
+                lintOptions,
                 libPlugin != null)
                     .setDefaultConfig(ProductFlavorContainerImpl.createPFC(
                         basePlugin.defaultConfigData,
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 088ee72..60af0a4 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
@@ -41,11 +41,7 @@
 
 public class Lint extends DefaultTask {
     @NonNull private BasePlugin mPlugin
-    @Nullable private File mConfigFile
-    @Nullable private File mHtmlOutput
-    @Nullable private File mXmlOutput
     @Nullable private String mVariantName
-    private boolean mQuiet = true
 
     public void setPlugin(@NonNull BasePlugin plugin) {
         mPlugin = plugin
@@ -55,26 +51,12 @@
         mVariantName = variantName
     }
 
-    public void setQuiet() {
-        mQuiet = true
-    }
-
-    public void setConfig(@NonNull File configFile) {
-        mConfigFile = configFile
-    }
-
-    public void setHtmlOutput(@NonNull File htmlOutput) {
-        mHtmlOutput = htmlOutput
-    }
-
-    public void setXmlOutput(@NonNull File xmlOutput) {
-        mXmlOutput = xmlOutput
-    }
-
     @SuppressWarnings("GroovyUnusedDeclaration")
     @TaskAction
     public void lint() {
-        def modelProject = createAndroidProject(mPlugin.getProject())
+        assert project == mPlugin.getProject()
+
+        def modelProject = createAndroidProject(project)
         if (mVariantName != null) {
             lintSingleVariant(modelProject, mVariantName)
         } else {
@@ -90,7 +72,7 @@
         Map<Variant,List<Warning>> warningMap = Maps.newHashMap()
         for (Variant variant : modelProject.getVariants()) {
             try {
-                List<Warning> warnings = runLint(modelProject, variant.getName())
+                List<Warning> warnings = runLint(modelProject, variant.getName(), false)
                 warningMap.put(variant, warnings)
             } catch (IOException e) {
                 throw new GradleException("Invalid arguments.", e)
@@ -120,121 +102,46 @@
         LintCliFlags flags = new LintCliFlags()
         LintGradleClient client = new LintGradleClient(registry, flags, mPlugin, modelProject,
                 null)
-        configureReporters(client, flags, null)
+        mPlugin.getExtension().lintOptions.syncTo(client, flags, null, project, true)
         for (Reporter reporter : flags.getReporters()) {
             reporter.write(errorCount, warningCount, mergedWarnings)
         }
+
+        if (flags.isSetExitCode() && errorCount > 0) {
+            throw new GradleException("Lint found errors with abortOnError=true; aborting build.")
+        }
     }
 
     /**
      * Runs lint on a single specified variant
      */
     public void lintSingleVariant(@NonNull AndroidProject modelProject, String variantName) {
-        runLint(modelProject, variantName)
+        runLint(modelProject, variantName, true)
     }
 
     /** Runs lint on the given variant and returns the set of warnings */
     private List<Warning> runLint(
             @NonNull AndroidProject modelProject,
-            @NonNull String variantName) {
+            @NonNull String variantName,
+            boolean report) {
         IssueRegistry registry = new BuiltinIssueRegistry()
         LintCliFlags flags = new LintCliFlags()
         LintGradleClient client = new LintGradleClient(registry, flags, mPlugin, modelProject,
                 variantName)
+        mPlugin.getExtension().lintOptions.syncTo(client, flags, variantName, project, report)
 
-        // Configure Reporters
-        configureReporters(client, flags, variantName)
-
-        // Flags
-        if (mQuiet) {
-            flags.setQuiet(true)
-        }
-        if (mConfigFile != null) {
-            flags.setDefaultConfiguration(client.createConfigurationFromFile(mConfigFile))
-        }
-
-        // Finally perform lint run
+        List<Warning> warnings;
         try {
-            return client.run(registry)
+            warnings = client.run(registry)
         } catch (IOException e) {
             throw new GradleException("Invalid arguments.", e)
         }
-    }
 
-    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()
-            }
+        if (report && client.haveErrors() && flags.isSetExitCode()) {
+            throw new GradleException("Lint found errors with abortOnError=true; aborting build.")
         }
 
-        if (xmlOutput == null) {
-            xmlOutput = project.file(base.toString() + DOT_XML)
-            File parent = xmlOutput.parentFile
-            if (!parent.exists()) {
-                parent.mkdirs()
-            }
-        }
-
-        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)
-        }
-
-        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 {
-            flags.getReporters().add(new XmlReporter(client, xmlOutput))
-        } catch (IOException 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)
-        }
+        return warnings;
     }
 
     private static AndroidProject createAndroidProject(@NonNull Project gradleProject) {
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 49211a6..ae004a2 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
@@ -83,11 +83,13 @@
     protected IssueRegistry mRegistry;
     protected LintDriver mDriver;
     protected final LintCliFlags mFlags;
+    private Configuration mConfiguration;
 
     /** Creates a CLI driver */
     public LintCliClient() {
         mFlags = new LintCliFlags();
-        TextReporter reporter = new TextReporter(this, new PrintWriter(System.out, true), false);
+        TextReporter reporter = new TextReporter(this, mFlags, new PrintWriter(System.out, true),
+                false);
         mFlags.getReporters().add(reporter);
     }
 
@@ -154,7 +156,7 @@
 
     @Override
     public Configuration getConfiguration(@NonNull Project project) {
-        return new CliConfiguration(mFlags.getDefaultConfiguration(), project);
+        return new CliConfiguration(getConfiguration(), project);
     }
 
     /** File content cache */
@@ -576,7 +578,18 @@
 
     /** Returns the configuration used by this client */
     Configuration getConfiguration() {
-        return mFlags.getDefaultConfiguration();
+        if (mConfiguration == null) {
+            File configFile = mFlags.getDefaultConfiguration();
+            if (configFile != null) {
+                if (!configFile.exists()) {
+                    log(Severity.ERROR, null, "Warning: Configuration file %1$s does not exist",
+                            configFile);
+                }
+                mConfiguration = createConfigurationFromFile(configFile);
+            }
+        }
+
+        return mConfiguration;
     }
 
     /** Returns true if the given issue has been explicitly disabled */
@@ -613,4 +626,8 @@
 
         return null;
     }
+
+    public boolean haveErrors() {
+        return mErrorCount > 0;
+    }
 }
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 6eaa45c..caf74ce 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
@@ -53,7 +53,7 @@
     private List<File> mLibraries;
     private List<File> mResources;
 
-    private Configuration mDefaultConfiguration;
+    private File mDefaultConfiguration;
     private boolean mShowAll;
 
     public static final int ERRNO_SUCCESS = 0;
@@ -213,20 +213,20 @@
     }
 
     /**
-     * Returns the default configuration to use as a fallback
+     * Returns the default configuration file to use as a fallback
      */
     @Nullable
-    public Configuration getDefaultConfiguration() {
+    public File getDefaultConfiguration() {
         return mDefaultConfiguration;
     }
 
     /**
-     * Sets the default configuration to use as a fallback. This corresponds to a {@code lint.xml}
+     * Sets the default config file to use as a fallback. This corresponds to a {@code lint.xml}
      * file with severities etc to use when a project does not have more specific information.
      * To construct a configuration from a {@link java.io.File}, use
      * {@link LintCliClient#createConfigurationFromFile(java.io.File)}.
      */
-    public void setDefaultConfiguration(@Nullable Configuration defaultConfiguration) {
+    public void setDefaultConfiguration(@Nullable File defaultConfiguration) {
         mDefaultConfiguration = defaultConfiguration;
     }
 
diff --git a/lint/cli/src/main/java/com/android/tools/lint/Main.java b/lint/cli/src/main/java/com/android/tools/lint/Main.java
index d29748f..d5f9507 100644
--- a/lint/cli/src/main/java/com/android/tools/lint/Main.java
+++ b/lint/cli/src/main/java/com/android/tools/lint/Main.java
@@ -265,7 +265,7 @@
                     System.err.println(file.getAbsolutePath() + " does not exist");
                     System.exit(ERRNO_INVALID_ARGS);
                 }
-                mFlags.setDefaultConfiguration(client.createConfigurationFromFile(file));
+                mFlags.setDefaultConfiguration(file);
             } else if (arg.equals(ARG_HTML) || arg.equals(ARG_SIMPLE_HTML)) {
                 if (index == args.length - 1) {
                     System.err.println("Missing HTML output file name");
@@ -347,7 +347,7 @@
                 }
             } else if (arg.equals(ARG_TEXT)) {
                 if (index == args.length - 1) {
-                    System.err.println("Missing XML output file name");
+                    System.err.println("Missing text output file name");
                     System.exit(ERRNO_INVALID_ARGS);
                 }
 
@@ -383,7 +383,7 @@
                     }
                     closeWriter = true;
                 }
-                mFlags.getReporters().add(new TextReporter(client, writer, closeWriter));
+                mFlags.getReporters().add(new TextReporter(client, mFlags, writer, closeWriter));
             } else if (arg.equals(ARG_DISABLE) || arg.equals(ARG_IGNORE)) {
                 if (index == args.length - 1) {
                     System.err.println("Missing categories or id's to disable");
@@ -586,7 +586,8 @@
                             ARG_URL, ARG_HTML));
             }
 
-            reporters.add(new TextReporter(client, new PrintWriter(System.out, true), false));
+            reporters.add(new TextReporter(client, mFlags,
+                    new PrintWriter(System.out, true), false));
         } else {
             if (urlMap == null) {
                 // By default just map from /foo to file:///foo
diff --git a/lint/cli/src/main/java/com/android/tools/lint/TextReporter.java b/lint/cli/src/main/java/com/android/tools/lint/TextReporter.java
index d615ce8..87ae189 100644
--- a/lint/cli/src/main/java/com/android/tools/lint/TextReporter.java
+++ b/lint/cli/src/main/java/com/android/tools/lint/TextReporter.java
@@ -22,6 +22,7 @@
 import com.google.common.annotations.Beta;
 import com.google.common.base.Joiner;
 
+import java.io.File;
 import java.io.IOException;
 import java.io.Writer;
 import java.util.List;
@@ -36,23 +37,40 @@
 public class TextReporter extends Reporter {
     private final Writer mWriter;
     private final boolean mClose;
+    private final LintCliFlags mFlags;
 
     /**
      * Constructs a new {@link TextReporter}
      *
      * @param client the client
+     * @param flags the flags
      * @param writer the writer to write into
      * @param close whether the writer should be closed when done
      */
-    public TextReporter(LintCliClient client, Writer writer, boolean close) {
-        super(client, null);
+    public TextReporter(LintCliClient client, LintCliFlags flags, Writer writer, boolean close) {
+        this(client, flags, null, writer, close);
+    }
+
+    /**
+     * Constructs a new {@link TextReporter}
+     *
+     * @param client the client
+     * @param flags the flags
+     * @param file the file corresponding to the writer, if any
+     * @param writer the writer to write into
+     * @param close whether the writer should be closed when done
+     */
+    public TextReporter(LintCliClient client, LintCliFlags flags, File file, Writer writer,
+            boolean close) {
+        super(client, file);
+        mFlags = flags;
         mWriter = writer;
         mClose = close;
     }
 
     @Override
     public void write(int errorCount, int warningCount, List<Warning> issues) throws IOException {
-        boolean abbreviate = mClient.getDriver().isAbbreviating();
+        boolean abbreviate = !mFlags.isShowEverything();
 
         StringBuilder output = new StringBuilder(issues.size() * 200);
         if (issues.isEmpty()) {
@@ -187,6 +205,11 @@
             mWriter.flush();
             if (mClose) {
                 mWriter.close();
+
+                if (mOutput != null) {
+                    String path = mOutput.getAbsolutePath();
+                    System.out.println(String.format("Wrote text report to %1$s", path));
+                }
             }
         }
     }
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/AbstractCheckTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/AbstractCheckTest.java
index 8274dc7..b575061 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/AbstractCheckTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/AbstractCheckTest.java
@@ -271,7 +271,7 @@
 
         public TestLintClient() {
             super(new LintCliFlags());
-            mFlags.getReporters().add(new TextReporter(this, mWriter, false));
+            mFlags.getReporters().add(new TextReporter(this, mFlags, mWriter, false));
         }
 
         @Override