2

Last year we successfully implemented some custom lint rules for our multi-module Android project by following https://proandroiddev.com/implementing-your-first-android-lint-rule-6e572383b292. At some point the lint rules stopped being applied, which I discovered by chance. By using git bisect on my repository, I was able to determine that the rules stopped being applied in a commit which upgraded the following:

  1. Gradle in gradle-wrapper.properties: distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip => gradle-6.7.1-all.zip
  2. Android Gradle plugin in our top-level build.gradle: classpath "com.android.tools.build:gradle:4.1.3"' => gradle:4.2.2

Someone asked a similarly worded question 3 years ago but in their case they did not isolate a potential cause as the upgrade of Gradle or Android Gradle plugin.

Update 12/29/2021

I figured out that this has to do with multi-module projects. I compared the call stack that calls my custom lint Detector.GradleScanner and discovered that it changed from Android Gradle Plugin 4.1.3 to 4.2.2:

com.android.tools.build:gradle:4.1.3

        at com.locuslabs.lintrules.GradleLibraryNonStableVersionDetector.checkDslPropertyAssignment(GradleLibraryNonStableVersionDetector.kt:22)
        ...
        at com.android.tools.lint.client.api.LintDriver.checkBuildScripts(LintDriver.kt:1203)
        at com.android.tools.lint.client.api.LintDriver.runFileDetectors(LintDriver.kt:1151)
        at com.android.tools.lint.client.api.LintDriver.checkProject(LintDriver.kt:926)
        at com.android.tools.lint.client.api.LintDriver.analyze(LintDriver.kt:446)
        at com.android.tools.lint.LintCliClient.run(LintCliClient.kt:256)
        at com.android.tools.lint.LintCliClient.run(LintCliClient.kt:237)
        at com.android.tools.lint.gradle.LintGradleClient.run(LintGradleClient.kt:255)
        at com.android.tools.lint.gradle.LintGradleExecution.runLint(LintGradleExecution.kt:259)
        at com.android.tools.lint.gradle.LintGradleExecution.lintAllVariants(LintGradleExecution.kt:318)
        at com.android.tools.lint.gradle.LintGradleExecution.analyze(LintGradleExecution.kt:68)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:564)
        at com.android.tools.lint.gradle.api.ReflectiveLintRunner.runLint(ReflectiveLintRunner.kt:38)
        at com.android.build.gradle.tasks.LintBaseTask.runLint(LintBaseTask.java:117)
        at com.android.build.gradle.tasks.LintGlobalTask.lint(LintGlobalTask.java:51)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:564)
        at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:104)
        ...

com.android.tools.build:gradle:4.2.2

        at com.locuslabs.lintrules.GradleLibraryNonStableVersionDetector.checkDslPropertyAssignment(GradleLibraryNonStableVersionDetector.kt:22)
        ...
        at com.android.tools.lint.client.api.LintDriver$checkBuildScripts$2.run(LintDriver.kt:1490)
        at com.android.tools.lint.client.api.LintClient.runReadAction(LintClient.kt:1780)
        at com.android.tools.lint.client.api.LintDriver$LintClientWrapper.runReadAction(LintDriver.kt:2634)
        at com.android.tools.lint.client.api.LintDriver.checkBuildScripts(LintDriver.kt:1483)
        at com.android.tools.lint.client.api.LintDriver.runFileDetectors(LintDriver.kt:1401)
        at com.android.tools.lint.client.api.LintDriver.checkProject(LintDriver.kt:1151)
        at com.android.tools.lint.client.api.LintDriver.checkProjectRoot(LintDriver.kt:628)
        at com.android.tools.lint.client.api.LintDriver.access$checkProjectRoot(LintDriver.kt:153)
        at com.android.tools.lint.client.api.LintDriver$analyzeOnly$1.invoke(LintDriver.kt:442)
        at com.android.tools.lint.client.api.LintDriver$analyzeOnly$1.invoke(LintDriver.kt:435)
        at com.android.tools.lint.client.api.LintDriver.doAnalyze(LintDriver.kt:502)
        at com.android.tools.lint.client.api.LintDriver.analyzeOnly(LintDriver.kt:434)
        at com.android.tools.lint.LintCliClient$analyzeOnly$1.invoke(LintCliClient.kt:241)
        at com.android.tools.lint.LintCliClient$analyzeOnly$1.invoke(LintCliClient.kt:241)
        at com.android.tools.lint.LintCliClient.run(LintCliClient.kt:283)
        at com.android.tools.lint.LintCliClient.run$default(LintCliClient.kt:266)
        at com.android.tools.lint.LintCliClient.analyzeOnly(LintCliClient.kt:241)
        at com.android.tools.lint.Main.run(Main.java:1536)
        at com.android.tools.lint.Main.run(Main.java:269)
        ...

So the analyze() function was changed to start calling a new function checkProjectRoot(). I investigated when the function checkProjectRoot() was introduced by going though the Android Gradle Plugin source code. The command git log -S checkProjectRoot indicates the following commit introduced checkProjectRoot() which implies that multi-module analysis changed:

base[mirror-goog-studio-main]10:06:10 git log -S checkProjectRoot
commit 1d01e75bf984568ecdf198cd35bfe05c8b0cce9f
Author: Tor Norbye <tnorbye@google.com>
Date:   Tue Jan 19 16:18:19 2021 -0800

    178514993: Support module-independent analysis in Lint
    
    This CL adds support into lint for "provisional reporting"; this is a
    big architectural change which is key to significantly speeding up
    the performance (and incrementality) of lint in Gradle.
    
    The key idea is that, for lint embedding clients which support it such
    as Gradle, modules are analyzed one at a time, independently (and
    possibly in parallel), without access to sources from any upstream
    dependencies. In some cases that means they can't conclusively report
    issues (for example, if they depend on the consuming module's
    minSdkVersion), but the detectors record enough state such that in a
    merging phase (reporting), they can look at their previously computed
    results and map it into definite warnings.
    
    This requires many detectors to be updated to explicitly handle this new
    mode. There are a handful of typical scenarios where a detector needs to
    be updated: accessing the minSdkVersion of the consuming module, or
    depending on the type of the consuming mode (e.g. is it also a library?
    or an Android module?).
    
    To simplify this, most lint checks can simply pass in one of a number of
    built-in constraints, e.g.
        context.report(incident, minSdkAtLeast(minSdk))
    
    There are several mechanisms included to help catch detectors that need
    to be updated. First, various lint APIs are not valid to be called when
    in analysis rather than reporting mode, and if a detector calls them,
    lint will emit a warning message.
      Private.java: Error: The lint detector
        com.android.tools.lint.checks.PrivateResourceDetector
        called context.getMainProject() during module analysis.
      This does not work correctly when running in Lint Unit Tests.
      In particular, there may be false positives or false negatives
      [...]
    
    Second, there's a new TestMode for lint's unit tests which causes all
    unit tests to be analyzed both with and without module-independent
    analysis. To catch common problems, in addition to running the
    infrastructure with module-independent analysis enabled and in separate
    and analysis phases, it also varies the minSdkVersion between the two
    phases to ensure that the lint checks aren't just basing their analysis
    on the module's minSdkVersion.
    
    NOTE: This CL only adds support for module-independent analysis in lint
    itself, and the detectors, and the unit testing infrastructure. It does
    not hook this up to for example AGP, which will be done separately.
    
    Test: Existing
    Bug: 178514993
    Change-Id: Iffa25707792e416eab3ce446c1f8d9d8cc830295

Does anyone know what changes are needed to make custom lint rules apply to all modules of a project, not just the top-level? Currently, I've implemented my rule as follows:

internal const val PRIORITY = 10

@Suppress("UnstableApiUsage")
class GradleLibraryNonStableVersionDetector : Detector(), Detector.GradleScanner {

    override fun checkDslPropertyAssignment(
        context: GradleContext,
        property: String,
        value: String,
        parent: String,
        parentParent: String?,
        propertyCookie: Any,
        valueCookie: Any,
        statementCookie: Any
    ) {
        super.checkDslPropertyAssignment(context, property, value, parent, parentParent, propertyCookie, valueCookie, statementCookie)

        if (property == "implementation"
            && (value.contains("-alpha")
                    || value.contains("-beta")
                    || value.contains("-rc"))) {
            context.report(ISSUE,
                statementCookie, context.getLocation(statementCookie),
                "Use only stable versions of the library")
        }
    }

    companion object {
        @JvmField
        val ISSUE: Issue = Issue.create("NonStableLibraryVersion",
            "Marks usage of unstable version of dependency library.",
            "It's not recommended to use alpha, beta or release candidate library versions in production",
            CORRECTNESS, PRIORITY, Severity.ERROR,
            Implementation(GradleLibraryNonStableVersionDetector::class.java,
                Scope.GRADLE_SCOPE))
    }
}

And I expect when I call the lint tool as follows, it should apply to all build.gradle files across all modules:

./gradlew -Pci --console=plain :sdk:lint -PbuildDir=lint

Update 1/7/2022

I reported a similar issue on the Google Sample project for lint rules.

Michael Osofsky
  • 11,429
  • 16
  • 68
  • 113

0 Answers0