Android Unit Tests - no such property: bootClasspath - android

I'm trying to execute a simple test case for Android following just announced unit testing support - http://tools.android.com/tech-docs/unit-testing-support
After carefully following the walkthrough I'm trying to run ./gradlew test.
I'm getting this error:
Execution failed for task ':app:compileDebugGroovy'.
> No such property: bootClasspath for class: com.android.build.gradle.AppPlugin
while using com.android.tools.build:gradle:1.1.0-rc1.
Anyone else got stuck on that?

The problem is that Groovy Android Gradle plugin (to have Groovy working on Android) isn't simply working with Android plugin in version 1.1.0-rcX.
Here's a very interesting piece of code directly from groovyx.grooid.GroovyAndroidPlugin, version 0.3.5 (current latest, here's the source)
def getRuntimeJars(Project project, plugin) {
int index
switch (getAndroidPluginVersion(project)) {
case ~/0\.9\..*/:
index = 0
break
case ~/0\.10\..*/:
case ~/0\.11\..*/:
case ~/0\.12\..*/:
case ~/0\.13\..*/:
case ~/0\.14\..*/:
case ~/1\.0\..*/:
index = 1
break
default:
index = RUNTIMEJARS_COMPAT.size()-1
}
def fun = RUNTIMEJARS_COMPAT[index]
fun(plugin)
}
and definition of RUNTIMEJARS_COMPAT:
private static List RUNTIMEJARS_COMPAT = [
{ it.runtimeJars },
{ it.bootClasspath }
]
So that API must have changed in Android Gradle between 0.9.x and 0.10.0 (yeah, I know - those Google devs change everything there :[ ). So let's take a look at that problem making class in Android Plugin version 1.0.0:
> javap -cp [path to proper jar] com.android.build.gradle.AppPlugin:
public class com.android.build.gradle.AppPlugin extends com.android.build.gradle.BasePlugin implements org.gradle.api.Plugin<org.gradle.api.Project> {
...
public java.util.List super$2$getBootClasspath();
...
Yup! There's the method we need (coming from parent com.android.build.gradle.BasePlugin class). Now there's nothing like that in version 1.1.0-rc3. What's more, the API of com.android.build.gradle.AppPlugin is completely changed, so it's not a matter of simple if(version) to fix that.
I guess there's no chance to have Groovy Android Gradle plugin working with Unit Tests (since 1.1.0) until authors update the plugin.
Let's wait then.

That Testing Support feature is experimental. That said maybe there is no quick solution to your issue or might be a bug.
However, I would dig deeper into this, reading the message:
This line Execution failed for task ':app:compileDebugGroovy'. mentions the task, so I would go and figure what that task does. I suppose it's a delivered task. The error being that in that task there is a property missing > No such property: bootClasspath for class: com.android.build.gradle.AppPlugin
So maybe try to find that task and make sure the bootClasspath property is set for the AppPlugin class.

Try to upgrade to the new version (RC3)
dependencies {
classpath 'com.android.tools.build:gradle:1.1.0-rc3'
// ..
}
You can also take a look here in order to compare your current setup with a working example.

Related

Custom lint checks stopped running for multi-module Android project after upgrading to Android Gradle Plugin 4.2.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:
Gradle in gradle-wrapper.properties: distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip => gradle-6.7.1-all.zip
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.

What is `encode()` function detected in JaCoCo coverage report (for Kolin)?

Recently I upgrade my gradle from 6.5.1 to 7.0 i.e.
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-all.zip
After the upgrade, I notice my test coverage dropped.
After investigation, I found out that in Gradle 6.5.1, my JaCoCo test coverage report shows some of my class has an encode() function, but it no longer exists in Gradle 7.0.
I dig out the simplest example below. The code is as below
In Gradle 6.5, my JaCoCo report is as below (notice there's an additional encode() function.
However, in Gradle 7.0, my JaCoCo report is as below
Because of this missing covered function, hence my coverage dropped. Nonetheless, it looks like in Gradle 7.0, that is more correct, as my real code doesn't have encode().
I'm just trying to understand where encode() function is there in the first place, and why it is no longer in Gradle 7.0? And is it correct that my assumption that Gradle 7.0 result is correct?
Different versions of Gradle come with different values of jacoco.toolVersion which you also can change in your build.gradle, and which controls version of JaCoCo to use.
Gradle 6.5 by default uses JaCoCo 0.8.5, and starting from Gradle 6.7 default is 0.8.6.
And here is a list of changes in JaCoCo version 0.8.6 - https://www.jacoco.org/jacoco/trunk/doc/changes.html
Most likely your interface ContextData contains method encode with default implementation.
JVM supports default methods in interfaces starting from version 8, however Kotlin supports compilation into bytecode of version 6. To do so Kotlin compiler does a trick for such methods - it generates method in classes implementing this interface that merely delegates execution to default implementation:
for the following Example.kt
interface ContextData {
fun encode() = { /* something */ }
}
data class SearchRefinementModalOpenData(
val userAction: String?
) : ContextData
execution of
kotlin-compiler-1.4.32/bin/kotlinc Example.kt
javap -v -p SearchRefinementModalOpenData.class
shows following bytecode
public final class SearchRefinementModalOpenData implements ContextData
{
public kotlin.jvm.functions.Function0<kotlin.Unit> encode();
descriptor: ()Lkotlin/jvm/functions/Function0;
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokestatic #29 // Method ContextData$DefaultImpls.encode:(LContextData;)Lkotlin/jvm/functions/Function0;
4: areturn
JaCoCo starting from version 0.8.6 filters out such methods since they are compiler artifacts and not presented in original source code.
It looks like you are using kotlinx.Serialization.
That generates a few encodeToString and similar functions for your objects. I guess Gradle 7 now ignores those?

How to generate OpenAPI sources from gradle when building Android app

What I'm trying to achieve
I'm trying to generate my REST API client for Android using OpenAPI Generator from the build.gradle script. That way, I wouldn't have to run the generator command line every time the specs change. Ideally, this would be generated when I build/assemble my app, and the sources would end up in the java (generated) folder, where generated sources are then accessible from the code (this is what happens with the BuildConfig.java file for example).
What I've tried so far
Following this link from their official GitHub, here's the build.gradle file I ended up with:
apply plugin: 'com.android.application'
apply plugin: 'org.openapi.generator'
...
openApiValidate {
inputSpec = "$rootDir/app/src/main/openapi/my-api.yaml"
recommend = true
}
openApiGenerate {
generatorName = "java"
inputSpec = "$rootDir/app/src/main/openapi/my-api.yaml"
outputDir = "$buildDir/generated/openapi"
groupId = "$project.group"
id = "$project.name-openapi"
version = "$project.version"
apiPackage = "com.example.mypackage.api"
invokerPackage = "com.example.mypackage.invoker"
modelPackage = "com.example.mypackage.model"
configOptions = [
java8 : "true",
dateLibrary : "java8",
library : "retrofit2"
]
}
...
First, I've never managed to get the API generated with the build/assemble task, even when I tried adding:
compileJava.dependsOn tasks.openApiGenerate
or
assemble.dependsOn tasks.openApiGenerate
The only way I could generate the sources was by manually triggering the openApiGenerate task:
Then, when I do generate my sources this way, they end up in the build folder but aren't accessible from my code, and aren't visible in the java (generated) folder:
I then have to manually copy/paste the generated source files to my project sources in order to use the API.
Even though I'm able to work around these issues by adding manual procedures, it would be way more maintainable if the whole process was simply automatic. I was able to achieve a similar result with another tool, Protobuf. Indeed, my gradle task gets triggered every time I build the app, and the sources end up in the java (generated) folder, so I don't have to do any additional work. The task is much simpler though, so I assume the main work that I'm not able to replicate with OpenAPI Generator is handled by the Protobuf plugin itself.
You have to specify path to the generated sources as a custom source set for your Gradle module, which is app in this case, as described here – https://developer.android.com/studio/build/build-variants#configure-sourcesets. That way Gradle will treat your sources as accessible from your code.
Something like this:
android {
...
sourceSets {
main {
java.srcDirs = ['build/generated/openapi/src/main/java']
}
}
...
}
I solved the issue you described like this, I'm using gradle.kts however.
See my build.gradle.kts
plugins {
// Your other plugins
id("org.openapi.generator") version "5.3.0"
}
openApiGenerate {
generatorName.set("kotlin")
inputSpec.set("$rootDir/app/src/main/openapi/my-api.yaml")
outputDir.set("$buildDir/generated/api")
// Your other specification
}
application {
// Your other code
sourceSets {
main {
java {
// TODO: Set this path according to what was generated for you
srcDir("$buildDir/generated/api/src/main/kotlin")
}
}
}
}
tasks.compileKotlin {
dependsOn(tasks.openApiGenerate)
}
You need to build the application at least once for the IDE to detect the library (at least this is the case for me in Intellij)
Your build should automatically generate the open api classes , to refer the generated classes in your java project you should add the generated class path to your source directory like it was mentioned in the other answers
https://developer.android.com/studio/build/build-variants#configure-sourcesets
As far as the task dependency goes , in android tasks are generated after configuration thus for gradle to recognize the task , wrap it inside afterEvaluate block like
afterEvaluate {
tasks.compileDebugJavaWithJavac.dependsOn(tasks.openApiGenerate)
}
I had this issue, and this answer https://stackoverflow.com/a/55646891/14111809 led me to a more informative error:
error: incompatible types: Object cannot be converted to Annotation
#java.lang.Object()
Taking a look at the generated files that were causing this error, noticed:
import com.squareup.moshi.Json;
After including a Moshi in the app build.gradle, the build succeeded and the generated code was accessible.
implementation("com.squareup.moshi:moshi-kotlin:1.13.0")

How to resolve "Obsolete custom lint check" for my custom IssueRegistry written in Kotlin and make it work (Android)

I am trying to implement custom lint checks (using Kotlin). I have set up a module for my custom checks and added classes to test my first lew lint check, mostly following these two tutorials here and here.
So I now have a module, I have a custom IssueRegistry, I've created an issue and a Detector class for it. So far it seems complete. I've added a test to check if my lint check works and it looks alright.
I have added my module to the project by referencing it in settings.gradle like this: include ':app', ':somemodule', ':mylintmodule'
Now if I run the linter using ./gradlew lint I get a lint result file telling me this:
Lint found an issue registry (com.myproject.mylintmodule) which requires a newer API level. That means that the custom lint checks are intended for a newer lint version; please upgrade
Lint can be extended with "custom checks": additional checks implemented by developers and libraries to for example enforce specific API usages required by a library or a company coding style guideline.
The Lint APIs are not yet stable, so these checks may either cause a performance degradation, or stop working, or provide wrong results.
This warning flags custom lint checks that are found to be using obsolete APIs and will need to be updated to run in the current lint environment.
It may also flag issues found to be using a newer version of the API, meaning that you need to use a newer version of lint (or Android Studio or Gradle plugin etc) to work with these checks.
To suppress this error, use the issue id "ObsoleteLintCustomCheck" as explained in the Suppressing Warnings and Errors section.
So it tells me that I am using a newer API verion in my custom lint check, right? This is my custom IssueRegistry (minus some parts not relevant for this problem):
class MyCustomIssueRegistry : IssueRegistry() {
override val issues: List<Issue>
get() = listOf(ISSUE_NAMING_PATTERN)
override val api: Int = com.android.tools.lint.detector.api.CURRENT_API
override val minApi: Int = 1
}
From googling this problem and finding this issue I figured I have to override and set the right API version (and maybe the min API?) by overriding these properties like I did above (this version is my last attempt, directly taken from that issue).
So this property can be set to values between -1 and 5, meaning this (taken right out of the lint.detector.api class):
/** Describes the given API level */
fun describeApi(api: Int): String {
return when (api) {
5 -> "3.5+" // 3.5.0-alpha07
4 -> "3.4" // 3.4.0-alpha03
3 -> "3.3" // 3.3.0-alpha12
2 -> "3.2" // 3.2.0-alpha07
1 -> "3.1" // Initial; 3.1.0-alpha4
0 -> "3.0 and older"
-1 -> "Not specified"
else -> "Future: $api"
}
I have tried all of them, plus the one above adding a minApi override too, and I keep getting the exact same result for each of them.
Also I am unable to locate what other API version this is compared with. Is there a place where this is set for the regular linter in an Android project?
It's also unclear to me what I have to do to make sure my changes got applied - is it enough to change some code, then run lint, or do I have to compile the project first, or build & clean?
Following the tutorials, I added my custom lint check by adding this to the app's build.gradle: lintChecks project(":mylintmodule")
Is that even right? The API issue on my registry class shows up no matter if my lint check is referenced (and hopefully used) like that or not. I have also tried the other method described in the first tutorial, adding this task to the linter module build.gradle:
defaultTasks 'assemble'
task copyLintJar(type: Copy) {
description = 'Copies the lint jar file into the {user.home}/.android/lint folder.'
from('build/libs/')
into(System.getProperty("user.home") + '/.android/lint')
include("*.jar")
}
// Runs the copyLintJar task after build has completed.
build.finalizedBy(copyLintJar)
But since I can't figure out how to see if my custom checks are actually run, I don't know if that works as intended either.
So how do I get this warning resolved (since I interpret the text as "As long as the versions don't match I will not try to run your lint check"), and how can I make sure my lint check is actually run by the linter?

Getting the Android SDK directory within a gradle task

Recently the gradle plugin for android got updated (with android studio), after which the previous way of getting to the SDK directory ceased to work. The expression
${android.plugin.sdkDirectory}
which worked in an older version now returns the error
Error:(42, 0) No such property: sdkDirectory for class: com.android.build.gradle.LibraryPlugin
What would be the proper way of getting the android SDK directory being used, preferably independent of the user's configuration such as plugin and gradle version? The script needs to be shareable with several users.
Since all the previous answers depend on the environment or specific user intervention on top of normal configuration, I'll just post my technically messy fix.
if (android.hasProperty('plugin')) {
if (android.plugin.hasProperty('sdkHandler')) {
androidPath = android.plugin.sdkHandler.sdkFolder
} else {
androidPath = android.plugin.sdkDirectory
}
} else {
androidPath = android.sdkDirectory
}
Unlike all previous methods, this actually works, but it still looks hacky.
In gradle.properties set location sdkdir=/home/user/android-sdk and then in gradle you can use $sdkdir
I'm using Android gradle plugin v1.2.3 and this works fine:
${android.sdkDirectory}
You can use
$System.env.ANDROID_HOME
export ANDROID_HOME=/xxx/xxx/ in shell, then use it by System.env.ANDROID_HOME in gradle file.

Categories

Resources