Status Update
Comments
rm...@google.com <rm...@google.com> #2
Thanks for your detailed post.
However, benchmark build type is under configured: at least isProfileable is not set to true for existing build type, probably there's more.
We should set isProfileable = true
by default when overriding an existing benchmark build type.
This issue is not about some specific configuration flag, but the general approach of dealing with external configuration. As a developer adopting baseline profiles, it seems extremely risky to me using a custom configuration due to how it's applied under the hood and the fact it may break default configuration.
I agree that is not great but this is a little tricky to do. For custom baseline profile build types we override all the properties. For benchmark I left it open to configure but it's mostly about these 2 properties:
isMinifyEnabled
isShrinkResources
I don't have a way to see if the user is setting them before overriding, so for this reason, I'd prefer not to. I agree with you that some other properties could be set by default to make this easier, i.e.:
isJniDebuggable = false
isDebuggable = false
isProfileable = true
The reason why I mentioned the release signing config in the beginning is because I want to use debug signing config.
In the specific of your issue, i.e. using a debug certificate can you override the benchmark and baseline profile setting? You should be able to do something like:
android {
buildTypes {
release { ... }
debug { ... }
benchmarkRelease {
...
signingConfig signingConfigs.debug
}
nonMinifiedRelease {
...
signingConfig signingConfigs.debug
}
}
}
fl...@gmail.com <fl...@gmail.com> #3
Project: platform/frameworks/support
Branch: androidx-main
Author: Marcello Albano <
Link:
Added override for debuggable and profileable for benchmark builds in bpgp
Expand for full commit details
Added override for debuggable and profileable for benchmark builds in bpgp
Test: ./gradlew :benchmark:benchmark-baseline-profile-gradle-plugin:test
Bug: 369213505
Relnote: "isProfileable is always overridden in benchmark builds,
and isDebuggable is also now always overridden in both benchmark and
nonMinified (baseline profile capture) builds."
Change-Id: I487fa71083921682173f04fcbb477be5baf165f8
Files:
- M
benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/apptarget/BaselineProfileAppTargetPlugin.kt
- M
benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/apptarget/BaselineProfileAppTargetPluginTest.kt
Hash: 1906bbe52ba7ccb9ca0e1c1d6de33e7c91b5c6f0
Date: Fri Oct 11 10:07:06 2024
fl...@gmail.com <fl...@gmail.com> #4
I've landed a change that will set the following properties also when the benchmark build type already exists:
isJniDebuggable = false
isDebuggable = false
isProfileable = true
As well as the following for agp 8.0:
isDebuggable = false
I'm going ahead and closing this - if you've further questions please answer here and will reopen. Thanks.
rm...@google.com <rm...@google.com> #5
The following release(s) address this bug.It is possible this bug has only been partially addressed:
androidx.benchmark:benchmark-baseline-profile-gradle-plugin:1.4.0-alpha04
fl...@gmail.com <fl...@gmail.com> #6
Since release signing config is used by default instead of debug
is not mentioned in release notes - maybe this is a bug?
Cause the last time I found it mentioned in the release notes was in version 1.1
signingConfig.debug is used as the default signing config (
) b/153583269
So, if the switch to the release one indeed happened - maybe it's an issue?
tn...@google.com <tn...@google.com> #8
Normally, when lint finds a method call or field reference that requires a higher API level than the current minSdkVersion, it looks to see if the call is inside a
if (SDK_INT > x) { ... }
body. (*: it's a little more complicated than that -- for example, it will also let you be in an else clause with a reverse condition, and you can also put a version check at the beginning of the method and exit early etc -- but the principle is the same).
Then I made the check more sophisticated, such that if your violating call is surrounded by say
if (foo()) { ... }
lint will check the "foo" method to see if it looks like it performs a version check.
In its simplest form, this simply peeks at the method body of the given method, "foo" above, and if that method simply does
"return SDK_INT > x"
then we're good.
I later extended this to be more clever, such that if you're surrounded by
if (foo_bar(x, y, z, 28)) { ... }
and the method body does
return SDK_INT > parameter4
then it will check the 4th argument to the method call and see if that one is >= X.
....anyway...
This whole mechanism relies on lint's ability to go and look at the method body. That works fine when the utility method is available as source.
But the second you extract the method body into a separate module, now it's just a call into a random method. Lint doesn't go and try to analyze the bytecode of methods (and soon that won't even make sense, since we plan to switch libraries to only contain the API surface, and remove method bodies (and private methods etc etc) for better compile time avoidance as well as other changes, similar to how android.jar currently is just exposing the Android SDK API surface, with none of the implementation details.
So that's why lint can't look at methods into binary libraries and figure out that an *arbitrarily* named function with a set of arbitrary methods implies that a given API has been verified.
---
There are two potential solutions here.
The first one is for us to add a new annotation to the AndroidX annotations library, perhaps
@ChecksApi(min=28)
fun foo_bar() { ... }
and perhaps a variation with parameters,
@CheckApi(apiLevelParameter=2)
fun foo_bar(something: Boolean, myLevel: Int)
Lint could use those annotations on foreign methods to tell whether a method represents an API check. This work isn't done yet so obviously isn't a solution for you.
The second solution is a workaround. Lint already uses some simple heuristics to guess if a method represents a version check method. So if you name your own version utility methods to conform to one of these patterns, lint should work. I realize that renaming methods may be out of the question if this is a commonly used method in your codebase with a lot of existing references, but I'm throwing it out there as a potential workaround.
Here's the prefixes and suffixes in method names that lint looks for:
private static final String[] VERSION_METHOD_NAME_PREFIXES = {
"isAtLeast", "isRunning", "is", "runningOn", "running"
};
private static final String[] VERSION_METHOD_NAME_SUFFIXES = {
"OrLater", "OrAbove", "OrHigher", "OrNewer", "Sdk"
};
and here are some examples from Lint's unit tests (VersionChecksTest, if you're curious)
assertEquals(19, VersionChecks.getMinSdkVersionFromMethodName("isAtLeastKitKat"));
assertEquals(19, VersionChecks.getMinSdkVersionFromMethodName("isKitKatSdk"));
assertEquals(19, VersionChecks.getMinSdkVersionFromMethodName("isKitKatSDK"));
assertEquals(19, VersionChecks.getMinSdkVersionFromMethodName("isRunningKitkatOrLater"));
assertEquals(19, VersionChecks.getMinSdkVersionFromMethodName("isKeyLimePieOrLater"));
assertEquals(19, VersionChecks.getMinSdkVersionFromMethodName("isKitKatOrHigher"));
assertEquals(19, VersionChecks.getMinSdkVersionFromMethodName("isKitKatOrNewer"));
assertEquals(
17, VersionChecks.getMinSdkVersionFromMethodName("isRunningJellyBeanMR1OrLater"));
assertEquals(20, VersionChecks.getMinSdkVersionFromMethodName("isAtLeastKitKatWatch"));
As you can see, the version numbers here are tied to the build version codes. (It also handles using just the dessert letter after isAtLeast; this is how it supports the AndroidX BuildCompat class which has a bunch of isAtLeast methods; those methods suffer from the same problem I've mentioned above (it's a binary/third party library version check, so lint doesn't see its method implementation, and instead it infers the requirement from the name.)
tn...@google.com <tn...@google.com> #9
Here's the additional unit test cases for the CL I just uploaded:
assertEquals(29, VersionChecks.getMinSdkVersionFromMethodName("hasQ"));
assertEquals(28, VersionChecks.getMinSdkVersionFromMethodName("hasApi28"));
assertEquals(28, VersionChecks.getMinSdkVersionFromMethodName("isAtLeastApi28"));
assertEquals(28, VersionChecks.getMinSdkVersionFromMethodName("isAtLeastAPI_28"));
assertEquals(28, VersionChecks.getMinSdkVersionFromMethodName("isApi28OrLater"));
(This obviously won't help you until this rolls out into an upcoming 4.0 beta; the above workaround or baseline is what you'll need to use until then.)
br...@capitalone.com <br...@capitalone.com> #10
1. In the sample project posted in
2. In our actual project we are seeing false positives within modules (not at module boundaries). I.e a method is declared like:
@TargetApi(Build.VERSION_CODES.M)
private static final foo(...) {
... uses some API from M ...
}
and this method is causing a `NewApi` error to be thrown.
For the second point -- I will say we use a fairly complex build plugin that, in short, wraps gradle and allows us to very easily switch between binary and source versions of our modules. Are there any considerations to be made besides just a module being source or binary that may cause false positives like this one? If it helps, we do NOT have `checkDependencies` set to true, and we are just checking release variants of our modules.
tn...@google.com <tn...@google.com> #11
implementation project(":core")
the build system will pass the compiled outputs rather than the sources to the downstream dependency. If it didn't do that, all sources in libraries would be compiled over and over and over again, for every downstream usage of the library!
For a while lint was actually processing the sources of libraries in this way; this was to work around some problems with Kotlin symbol resolution. But I think that was fixed a while back.
The second bug you're listing sounds quite different. Is there any way you can reproduce that? That code path does not at all depend on resolving calls and peeking at method bodies; it's actually a pretty easy outward check on all surrounding annotatable parents (e.g. surrounding methods, inner classes, classes and (for Kotlin) compilation units), to see if any of them implies a higher minSdkVersion than the one from the manifest. I'm not sure how that could go wrong; possibly it has a problem figuring out the API level of the expression in the annotation? Is it happening everywhere or just in a few places? And if so, any clues to what's special about those places -- static imports, or in generated code, etc etc?
dy...@gmail.com <dy...@gmail.com> #12
Hi there,
I'm having a very similar issue, if not the same. In the application I'm currently developing, I had a util class which allowed me to verify the Android version of the device. Here's an example:
fun isAtLeastQ(): Boolean {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
}
I had no issue when this class was in my application. But I decided to export this class into my personal lib, allowing me to use this class in multiple applications without having to copy and paste it. The class was absolutely unchanged, except that I moved it from my application to my lib.
After upgrading my lib in that app and replacing calls of isAtLeastQ()
from my app to isAtLeastQ()
from my lib, I noticed that Lint thought I didn't add verification of the API anymore.
In my screenshots, you can see the difference between both isAtLeastQ()
(AndroidVersionBugApp.jpg) of my app, and isAtLeastQ()
of my lib.
Additionally, I decided to try the BuildCompat class in which, even if isAtLeastQ()
is deprecated, it is still available. I first thought I would still have the issue, but not at all. As you can see in the last screenshot (AndroidVersionBugBuildCompat.jpg), Lint isn't detecting any issue.
I use AGP 3.5.3 for both my application and my lib.
tn...@google.com <tn...@google.com> #13
dy...@gmail.com <dy...@gmail.com> #14
I use Jitpack to import my lib from Github to my project, so Jitpack is added under repositories, and my lib is added like this:
implementation 'com.github.myusername:repo:version'
tn...@google.com <tn...@google.com> #15
dy...@gmail.com <dy...@gmail.com> #16
Thanks. I sent an email last Tuesday containing all the details.
br...@capitalone.com <br...@capitalone.com> #17
Because of our build system and how large our project is, I think it would be extremely difficult to reproduce in a sample project that I could share. However, I just ran into the issue with `NewApi` and `ObsoleteSdkInt` (as well as `ObjectAnimatorBinding` which is another one we see often, but let's stick to discussing the API-related rules, as I suspect we see these all pop up this way for the same reason).
Our project is, by default, set up so that every module aside from app modules are in binary form. To work on the source, we set a flag on that dependency which tells our build system to compile that module as source. When changes are ready to be merged, CI gathers all feature branches with the same name across all our modules, builds all the changed modules AND modules that can/will be impacted by these changes (based on a dependency tree analysis) from source, and runs a series of checks (including lint) on all modules in the workspace (any modules that are not impacted by the changes and are not changed themselves are dropped from the workspace).
Here is where the problem seems to start: A change is made to Module A. This change goes through CI, lint passes and everything seems to be OK. Later on, I go to update the module that contains our custom lint rules. Because ALL of our modules rely on this custom lint rules module, ALL modules are then built from source and checked. On that execution, we see lint errors thrown in Module A (the module that previously passed lint). When I have all modules set to binary and run lint on module A as source, there are no errors thrown. I'm able to reproduce this locally as well as on CI.
Here is the exact scenario we have run into today:
Module A contains a method `foo(...)` which is a public function and is called from module B. This public function then calls a private function that has this code
```
return if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
... <-- Code that tries to use `RecentTaskInfo#topActivity`
} else {
...
}
```
Again, when this module gets lint run on it by itself (all other modules in binary form), no error is produced. When ALL modules are built from SOURCE, we see:
src/main/java/example/MyClass.kt:48: Error: Field requires API level 23 (current min is 21): android.app.ActivityManager.RecentTaskInfo#topActivity [NewApi]
and
Error: Unnecessary; SDK_INT is never < 21 [ObsoleteSdkInt]
return if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M)
Both of these errors don't make sense when applied to the code above because the gate check does ensure the call is made only on API 23 and ...the ObsoleteSdkInt error just doesn't make sense in general. :)
This behavior actually seems to be opposite of what you explained in
To recap, we DO see the error only when all modules (including module A) are included as source, but we do NOT see the error when all modules (except module A) are included as binaries.
I'm hoping something in this brain dump will flag something in your mind that might help us get to the bottom of this. Literally ANY point in the right direction is super appreciated.
tn...@google.com <tn...@google.com> #18
Hi there, Dylan sent me steps to reproduce, and with that I was able to check in a fix (for 4.1 and 4.0) yesterday.
The root problem in his scenario is that there are cases where resolving a method call fails -- and that was happening in that particular scenario. That meant lint didn't get a chance to look at the method at all, to see if it's a version check. My fix was to more gracefully handle that, such that it now will still look at the method name and call the method name check even if it cannot find the associated method. So if the method is for example called "isAtLeastQ", it will from that conclude that the API level is at least 29 if that method returns true.
This all sounds different from your problem Brandon. The most likely explanation is the same: that somehow resolving the method calls is failing. The most common reason for that is a classpath setup. It's hard to say more without knowing how the build sets up your source build...
br...@capitalone.com <br...@capitalone.com> #19
tn...@google.com <tn...@google.com> #20
Started work on this in the form of a new annotation you can annotate custom API method checks (and fields and properties with) to teach lint that a given method represents an API constraint:
(not yet ready to use; that annotation hasn't been released and lint doesn't know about it yet, but if you have thoughts on how it would work and how you would annotate your own API version utilities with it, speak up. See the javadoc for examples.)
tn...@google.com <tn...@google.com> #21
I forgot to close this bug but this was fixed quite a while back -- Studio 4.2 and probably Studio 4.1 at least understand the new annotation -- but in 7.0 it will (a) automatically handle local binary dependencies (e.g. in your own libraries that lint runs on, it will recognize these API checks and make a note for itself which it uses downstream, and (b) can suggest actually annotating these methods such that other uses of the library can automatically see the inference as well.
Description
Strange issue with Lint indicating false positives on API errors (NewApi, InlinedApi or ObsoleteSdkInt) when not using directly Build.VERSION_CODES constants.
It's broken since AGP 3.3.0, worked with AGP 3.2.1 and previous versions.
Example:
```
object AndroidVersions {
const val API_28 = Build.VERSION_CODES.P
private val infoFlag: Int =
if (Build.VERSION.SDK_INT >= API_28)
PackageManager.GET_SIGNING_CERTIFICATES
else
PackageManager.GET_SIGNATURES
```
Lint report the following warning: `Warning: Unnecessary; SDK_INT is never < 21 [ObsoleteSdkInt]` which has nothing to do here.
If we wrap the condition into a method, it produces a wrong InlinedApi warning:
````
object AndroidVersions {
const val API_28 = Build.VERSION_CODES.P
inline fun hasApi28() = Build.VERSION.SDK_INT >= API_28
private val infoFlag: Int =
if (AndroidVersions.hasApi28())
PackageManager.GET_SIGNING_CERTIFICATES
else
PackageManager.GET_SIGNATURES
}
Produces `Warning: Field requires API level 28 (current min is 21): android.content.pm.PackageManager#GET_SIGNING_CERTIFICATES [InlinedApi]`
When calling a method that requires an higher API level than min SDK using this method it produces an NewApi error.
```
Configuration:
Android Studio 3.3 RC 1
Build #AI-182.5107.16.33.5138683, built on November 19, 2018
JRE: 1.8.0_152-release-1248-b01 x86_64
JVM: OpenJDK 64-Bit Server VM by JetBrains s.r.o
macOS 10.14.1