Skip to content
This repository has been archived by the owner on Feb 11, 2022. It is now read-only.

Android lint variant filter support #79

Merged
merged 14 commits into from Feb 13, 2018
Merged

Conversation

tasomaniac
Copy link
Contributor

@tasomaniac tasomaniac commented Feb 9, 2018

Fixes #66
Fixes #77

Problem

The lint setup didn't have filters around Android variants. In a big project with 10+ variants, the evaluateViolations task take too long. (around 20 mins)

Solution

This PR make lintOptions variant aware.

  • Introduced a trait to encapsulate Android variant awareness in Configurators.
    • A follow-up will be done to this to make it cleaner.
  • Instead of skipping non-android projects, use project.plugins.withId('com.android.application') to wait for potential plugin addition. This will fix the timing issue described in Timing issue with lint configuration #77
  • When lint runs on a single variant, the output names change. That's why introduced Nullable variant param to task creation and change the report file path accordingly.

Note: When filter is not provided, it will keep the old behavior. When filter is used, we may get duplicated results when the same problem is available in an intersection of the sourceSets of multiple variants. This problem is available in all tasks and fix will be introduced later.

Tests

Lots of tests added including the duplicate number behavior.
A test is also added to check if we really ignore problems when the variant is ignored.

Copy link
Contributor

@mr-archano mr-archano left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a couple of remarks. We can discuss this IRL if you prefer.


trait VariantAware {

private boolean hasFilter = false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why don't we keep out of the trait API and we just keep the getFiltered*()? this way we can choose in the configurator how to deal with that, for instance only Lint needs to know whether the filter is set or not, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trait allows me to have implementation and fields here. I don't want to lose that.

The other option is to make it nullable. Let me try and see how that goes actually. hasFilter is not necessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, addressing here, I pushed to clean-up branch. That looks much nicer.

trait VariantAware {

private boolean hasFilter = false
Closure<Boolean> includeVariantsFilter = { true }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can be just Closure<Boolean> getIncludeVariantsFilter() that we'll leave to the configurators to implement.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setter is also used in subclasses. I can of course do this but the beauty of a trait is to encapsulate the variant behavior all here.
If I implement getIncludeVariantsFilter in the subclasses, then I would need a property there.

@tasomaniac tasomaniac changed the title Android lint improves Android lint variant filter support Feb 9, 2018

private void configureWithVariants(Closure config, DomainObjectSet variants) {
configureLint(config)
if (hasFilter) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where hasFilter is coming from now? not sure how this is working now - if it's working even

}
}

private File getXmlOutputFile() {
project.android.lintOptions.xmlOutput ?: new File(defaultOutputFolder, 'lint-results.xml')
private File xmlOutputFileFor(reportSuffix) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer to push the report name directly and take the decision up two levels from here, so to keep it close to where we call configureCollectViolationsTask()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what you mean by 2 level. Do mean the with clause above just after we create the task. The xmlReportFile property of the task is better to be configured while it is created.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what I mean is that instead of adding this suffix logic you can move the decision of specifying what task name and what report name to use up to L36 and L38 (in that case no need of if anymore, right?)

@tasomaniac
Copy link
Contributor Author

retest this please

tobiasheine
tobiasheine previously approved these changes Feb 12, 2018
Copy link
Contributor

@tobiasheine tobiasheine left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm, just some questions

import org.gradle.api.DomainObjectSet
import org.gradle.api.NamedDomainObjectSet

trait VariantAware {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I would haven chosen composition over inheritance here since we mostly expose fields and methods to be shared.

project.plugins.withId('com.android.library') {
configureWithVariants(config, filteredLibraryVariants)
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: empty line

}
}

private void configureLint(Closure config) {
project.android.lintOptions.ext.includeVariants = { Closure<Boolean> filter ->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mr-archano
Could u explain us what exactly happens in this closure?

Copy link
Contributor

Choose a reason for hiding this comment

The 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 lintOptions that takes a filter predicate, used by VariantAware.variantSelector to actually filter in only the variants for which filter is evaluated as true

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.
I was also wondering how this works exactly. One question comes to mind, there is no mention of the input of the closure. We expect the input to be a variant but I guess there is no limit? Can it be anything?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand that we define a extension includeVariants with a Closure<Boolean> and what it is used for.
What is confusing to me is the assignment includeVariantsFilter = filter within the closure or when it is executed. Is it like a callback that is triggered when the user specifies the extension?


Closure<Boolean> includeVariantsFilter

final variantSelector = { variants ->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't this just be a normal private method then? it's weird to see it defined it like this...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method didn't work how I wanted it to be with this trait thingy. I guess I can do Tobi's suggestion and make this a real collaborator class.

private File getXmlOutputFile() {
project.android.lintOptions.xmlOutput ?: new File(defaultOutputFolder, 'lint-results.xml')
private File xmlOutputFileFor(reportSuffix) {
project.android.lintOptions.xmlOutput ?: new File(defaultOutputFolder, "lint-results${reportSuffix}.xml")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this mean that when the user sets xmlOutput then every variant task will output to the same report file?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No idea. ☺ Good question. Only way to find out is to try. I don't think the documentation mentions that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It overrides the files. If you have xmlOuput defined, you get a single output no matter which lint task you run. So this seems to be the right way to do. But also we should do it for HTML report actually. Same happens for that too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's weird behaviour. I agree that this is in line with the default reporting, but I wonder if we should provide a better way to handle that when using variant-aware lint tasks (ie: log a warning/error, or ignore that, or try to generate variant-aware reports from xmlOutput).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. We can talk to Android team about this.

I don't think it is possible to do variant-aware custom xmlOutput at the moment. It is because this configuration is done only once in the configuration phase. Whatever you do overrides the file name. You can only have 1 single name for the output. If it is null, they generate file names with variant name in it.

Said Tahsin Dane added 2 commits February 13, 2018 09:32
to have less logic in the task creation
Also changed html output to be the same as the xml one
tobiasheine
tobiasheine previously approved these changes Feb 13, 2018
@tasomaniac
Copy link
Contributor Author

retest this please

@tasomaniac
Copy link
Contributor Author

@mr-archano please have another look.

import org.gradle.api.NamedDomainObjectSet
import org.gradle.api.Project

final class VariantFilter {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private void configureWithVariants(DomainObjectSet variants) {
if (variantFilter.includeVariantsFilter != null) {
variants.all {
configureCollectViolationsTask(it.name, "lint-results-${it.name}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

me happy! 🐶

@tasomaniac tasomaniac merged commit 5c4e39b into develop Feb 13, 2018
@tasomaniac tasomaniac deleted the android-lint-improves branch February 13, 2018 15:17
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants