Android lint variant filter support #79
Changes from all commits
06d45ae
b7027f0
d92177f
4a8e826
e4307ae
3a5b7bb
83fb0a3
ccff5d2
a3e1b75
69efef0
935adbb
98050dc
c85f80a
43c8b0e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package com.novoda.staticanalysis.internal | ||
|
||
import org.gradle.api.DomainObjectSet | ||
import org.gradle.api.NamedDomainObjectSet | ||
import org.gradle.api.Project | ||
|
||
final class VariantFilter { | ||
|
||
private final Project project | ||
Closure<Boolean> includeVariantsFilter | ||
|
||
VariantFilter(Project project) { | ||
this.project = project | ||
} | ||
|
||
DomainObjectSet<Object> getFilteredApplicationVariants() { | ||
filterVariants(project.android.applicationVariants) | ||
} | ||
|
||
NamedDomainObjectSet<Object> getFilteredApplicationAndTestVariants() { | ||
filterVariants(getAllVariants(project.android.applicationVariants)) | ||
} | ||
|
||
DomainObjectSet<Object> getFilteredLibraryVariants() { | ||
filterVariants(project.android.libraryVariants) | ||
} | ||
|
||
NamedDomainObjectSet<Object> getFilteredLibraryAndTestVariants() { | ||
filterVariants(getAllVariants(project.android.libraryVariants)) | ||
} | ||
|
||
private def filterVariants(variants) { | ||
includeVariantsFilter != null ? variants.matching { includeVariantsFilter(it) } : variants | ||
} | ||
|
||
private NamedDomainObjectSet<Object> getAllVariants(variants1) { | ||
NamedDomainObjectSet<Object> variants = project.container(Object) | ||
variants.addAll(variants1) | ||
variants.addAll(project.android.testVariants) | ||
variants.addAll(project.android.unitTestVariants) | ||
return variants | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,8 @@ package com.novoda.staticanalysis.internal.lint | |
import com.novoda.staticanalysis.StaticAnalysisExtension | ||
import com.novoda.staticanalysis.Violations | ||
import com.novoda.staticanalysis.internal.Configurator | ||
import com.novoda.staticanalysis.internal.VariantFilter | ||
import org.gradle.api.DomainObjectSet | ||
import org.gradle.api.NamedDomainObjectContainer | ||
import org.gradle.api.Project | ||
import org.gradle.api.Task | ||
|
@@ -12,6 +14,7 @@ class LintConfigurator implements Configurator { | |
private final Project project | ||
private final Violations violations | ||
private final Task evaluateViolations | ||
private final VariantFilter variantFilter | ||
|
||
static LintConfigurator create(Project project, | ||
NamedDomainObjectContainer<Violations> violationsContainer, | ||
|
@@ -24,20 +27,27 @@ class LintConfigurator implements Configurator { | |
this.project = project | ||
this.violations = violations | ||
this.evaluateViolations = evaluateViolations | ||
this.variantFilter = new VariantFilter(project) | ||
} | ||
|
||
@Override | ||
void execute() { | ||
project.extensions.findByType(StaticAnalysisExtension).ext."lintOptions" = { Closure config -> | ||
if (!isAndroidProject(project)) { | ||
return | ||
project.extensions.findByType(StaticAnalysisExtension).ext.lintOptions = { Closure config -> | ||
project.plugins.withId('com.android.application') { | ||
configureLint(config) | ||
configureWithVariants(variantFilter.filteredApplicationVariants) | ||
} | ||
project.plugins.withId('com.android.library') { | ||
configureLint(config) | ||
configureWithVariants(variantFilter.filteredLibraryVariants) | ||
} | ||
configureLint(config) | ||
configureToolTask() | ||
} | ||
} | ||
|
||
private void configureLint(Closure config) { | ||
project.android.lintOptions.ext.includeVariants = { Closure<Boolean> filter -> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mr-archano There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure what you're asking @tobiasheine (as I didn't write this), but I will try to explain what I can see going on here: L54 adds an extension method to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, he was asking because this is a copy paste from the other ones. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I understand that we define a extension |
||
variantFilter.includeVariantsFilter = filter | ||
} | ||
project.android.lintOptions(config) | ||
project.android.lintOptions { | ||
xmlReport = true | ||
|
@@ -46,32 +56,41 @@ class LintConfigurator implements Configurator { | |
} | ||
} | ||
|
||
private void configureToolTask() { | ||
// evaluate violations after lint | ||
def collectViolations = createCollectViolationsTask(violations) | ||
private void configureWithVariants(DomainObjectSet variants) { | ||
if (variantFilter.includeVariantsFilter != null) { | ||
variants.all { | ||
configureCollectViolationsTask(it.name, "lint-results-${it.name}") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. me happy! 🐶 |
||
} | ||
} else { | ||
configureCollectViolationsTask('lint-results') | ||
} | ||
} | ||
|
||
private void configureCollectViolationsTask(String taskSuffix = '', String reportFileName) { | ||
def collectViolations = createCollectViolationsTask(taskSuffix, reportFileName, violations).with { | ||
it.dependsOn project.tasks.findByName("lint${taskSuffix.capitalize()}") | ||
} | ||
evaluateViolations.dependsOn collectViolations | ||
collectViolations.dependsOn project.tasks['lint'] | ||
} | ||
|
||
private CollectLintViolationsTask createCollectViolationsTask(Violations violations) { | ||
project.tasks.create('collectLintViolations', CollectLintViolationsTask) { collectViolations -> | ||
collectViolations.xmlReportFile = xmlOutputFile | ||
collectViolations.htmlReportFile = new File(defaultOutputFolder, 'lint-results.html') | ||
collectViolations.violations = violations | ||
private CollectLintViolationsTask createCollectViolationsTask(String taskSuffix, String reportFileName, Violations violations) { | ||
project.tasks.create("collectLint${taskSuffix.capitalize()}Violations", CollectLintViolationsTask) { task -> | ||
task.xmlReportFile = xmlOutputFileFor(reportFileName) | ||
task.htmlReportFile = htmlOutputFileFor(reportFileName) | ||
task.violations = violations | ||
} | ||
} | ||
|
||
private File getXmlOutputFile() { | ||
project.android.lintOptions.xmlOutput ?: new File(defaultOutputFolder, 'lint-results.xml') | ||
private File xmlOutputFileFor(reportFileName) { | ||
project.android.lintOptions.xmlOutput ?: new File(defaultOutputFolder, "${reportFileName}.xml") | ||
} | ||
|
||
private File htmlOutputFileFor(reportFileName) { | ||
project.android.lintOptions.htmlOutput ?: new File(defaultOutputFolder, "${reportFileName}.html") | ||
} | ||
|
||
private File getDefaultOutputFolder() { | ||
new File(project.buildDir, 'reports') | ||
} | ||
|
||
private static boolean isAndroidProject(final Project project) { | ||
final boolean isAndroidApplication = project.plugins.hasPlugin('com.android.application') | ||
final boolean isAndroidLibrary = project.plugins.hasPlugin('com.android.library') | ||
return isAndroidApplication || isAndroidLibrary | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
package com.novoda.staticanalysis.internal.lint | ||
|
||
import com.google.common.truth.Truth | ||
import com.novoda.test.Fixtures | ||
import com.novoda.test.TestAndroidProject | ||
import com.novoda.test.TestProject | ||
import com.novoda.test.TestProjectRule | ||
import org.junit.Rule | ||
import org.junit.Test | ||
|
||
import static com.novoda.test.LogsSubject.assertThat | ||
|
||
class LintAndroidVariantIntegrationTest { | ||
|
||
@Rule | ||
public final TestProjectRule<TestAndroidProject> projectRule = TestProjectRule.forAndroidProject() | ||
|
||
@Test | ||
void shouldFailBuildWhenLintViolationsOverThreshold() { | ||
TestProject.Result result = starterProject() | ||
.withSourceSet('demo', Fixtures.Lint.SOURCES_WITH_ERRORS) | ||
.withToolsConfig(DEFAULT_CONFIG) | ||
.buildAndFail('evaluateViolations') | ||
|
||
assertThat(result.logs).containsLimitExceeded(1, 1) | ||
assertThat(result.logs).containsLintViolations(1, 1, | ||
'reports/lint-results.html') | ||
} | ||
|
||
@Test | ||
void givenVariantsFilteredShouldFailBuildWithDuplicatedNumbers() { | ||
TestProject.Result result = starterProject() | ||
.withSourceSet('demo', Fixtures.Lint.SOURCES_WITH_ERRORS) | ||
.withToolsConfig(configWithVariants('demoDebug', 'fullRelease')) | ||
.buildAndFail('evaluateViolations') | ||
|
||
assertThat(result.logs).containsLimitExceeded(1, 2) | ||
assertThat(result.logs).containsLintViolations(1, 2, | ||
'reports/lint-results-demoDebug.html', | ||
'reports/lint-results-fullRelease.html' | ||
) | ||
} | ||
|
||
@Test | ||
void shouldIgnoreErrorsFromInactiveVariant() { | ||
TestProject.Result result = starterProject(maxWarnings: 1) | ||
.withSourceSet('demo', Fixtures.Lint.SOURCES_WITH_ERRORS) | ||
.withToolsConfig(configWithVariants('fullRelease')) | ||
.build('evaluateViolations') | ||
|
||
assertThat(result.logs).containsLintViolations(0, 1) | ||
} | ||
|
||
@Test | ||
void shouldContainCollectLintTasks() { | ||
TestProject.Result result = starterProject(maxWarnings: 1) | ||
.withToolsConfig(DEFAULT_CONFIG) | ||
.build('evaluateViolations') | ||
|
||
Truth.assertThat(result.tasksPaths).containsAllOf( | ||
':lint', | ||
':collectLintViolations', | ||
) | ||
} | ||
|
||
@Test | ||
void givenVariantsFilteredShouldContainTasksForIncludedVariantsOnly() { | ||
TestProject.Result result = starterProject(maxWarnings: 1) | ||
.withToolsConfig(configWithVariants('demoDebug')) | ||
.build('evaluateViolations') | ||
|
||
Truth.assertThat(result.tasksPaths).containsAllOf( | ||
':lintDemoDebug', | ||
':collectLintDemoDebugViolations') | ||
Truth.assertThat(result.tasksPaths).containsNoneOf( | ||
':lint', | ||
':lintDemoRelease', | ||
':collectLintDemoReleaseViolations', | ||
':lintFullDebug', | ||
':collectLintFullDebugViolations', | ||
':lintFullRelease', | ||
':collectLintFullReleaseViolations') | ||
} | ||
|
||
private static final String DEFAULT_CONFIG = | ||
""" | ||
lintOptions { | ||
lintConfig = file("${Fixtures.Lint.RULES}") | ||
} | ||
""" | ||
|
||
private static configWithVariants(String... variantNames) { | ||
def commaSeparatedVariants = variantNames.collect { "'$it'" }.join(', ') | ||
""" | ||
lintOptions { | ||
checkReleaseBuilds false | ||
lintConfig = file("${Fixtures.Lint.RULES}") | ||
includeVariants { it.name in [$commaSeparatedVariants] } | ||
} | ||
""" | ||
} | ||
|
||
private TestAndroidProject starterProject(def args = [:]) { | ||
projectRule.newProject() | ||
.withSourceSet('main', Fixtures.Lint.SOURCES_WITH_WARNINGS) | ||
.withPenalty("""{ | ||
maxErrors = ${args.maxErrors ?: 0} | ||
maxWarnings = ${args.maxWarnings ?: 0} | ||
}""") | ||
.withAdditionalAndroidConfig(''' | ||
flavorDimensions 'tier' | ||
|
||
productFlavors { | ||
demo { dimension 'tier' } | ||
full { dimension 'tier' } | ||
} | ||
''') | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✨