Repository navigation
Conversation
📲 Install BuildsAndroid
|
runningcode
left a comment
There was a problem hiding this comment.
i'm all for this! i gave it a first round of reviews, ping me for another round!
| try { | ||
| final double enqueuedTimeSeconds = Double.parseDouble(enqueuedTimeStr); | ||
| final double nowSeconds = DateUtils.millisToSeconds(System.currentTimeMillis()); | ||
| final double nowSeconds = DateUtils.millisToSeconds((double) System.currentTimeMillis()); |
There was a problem hiding this comment.
unrelated to the PR what's going on here? why are we using a double? i don't see a comment
There was a problem hiding this comment.
yeah it's super odd. I assume it's because the incoming enqueuedTimeStr is already in seconds, so we go along with this format here.
Many timestamps within the sentry universe are transmitted as floating point numbers: seconds since unix time (e.g. event.timestamp). I guess this is due to the python's time.time() giving you exactly that.
| final @NotNull Object bean, @NotNull final String beanName) throws BeansException { | ||
| if (bean instanceof SentryOptions) { | ||
| final SentryOptions options = (SentryOptions) bean; | ||
| if (bean instanceof SentryOptions options) { |
There was a problem hiding this comment.
cool java syntax! does this at all affect the minimum java for this code? what even is the minimum java here?
There was a problem hiding this comment.
Yeah, it does. But as this module is already pinned to Java 17 features anyway, it will fine here.
It all comes down to us specifying -Werror. Here's the full log:
SentryInitBeanPostProcessor.java:45: warning: [PatternMatchingInstanceof] This code can be simplified to use a pattern-matching instanceof.
error: warnings found and -Werror specified
| id("com.apollographql.apollo") version "4.1.1" | ||
| } | ||
|
|
||
| // Not published. Mockito 5 and mockito-kotlin 5+ are built for Java 11. |
There was a problem hiding this comment.
if it isn't published why not bump to 21?
| // share the Java 8 target of the published code; it compiles for the build JDK instead. | ||
| // Registered once each plugin is applied, so this afterEvaluate runs after the plugins and the | ||
| // module have pinned the JVM target of all compile tasks. | ||
| listOf("java", "com.android.library", "com.android.application").forEach { pluginId -> |
There was a problem hiding this comment.
we shouldn't be adding any logic to the build.gradle.kts file. this should go in a convention plugin like we already have in build-logic
also don't we already have some code that sets this same type of logic to jdk 11 ? why do we set it to 21 here?
also: this seems like we should be using gradle's toolchains support here. https://docs.gradle.org/current/userguide/toolchains.html
There was a problem hiding this comment.
I've added toolchain support, but it hasn't as many benefits as I was hoping for. The Gradle docs mention that the --release flag should be used in favor of source/target compat, but that isn't supported by AGP as --release seems to nuke the bootclasspath and removes android.jar provided by AGP for compilation.
| targetCompatibility = JavaVersion.VERSION_21.toString() | ||
| } | ||
| tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile>().matching(isTestTask).configureEach { | ||
| compilerOptions.jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21) |
There was a problem hiding this comment.
same, we should probably be using toolchains here: https://docs.gradle.org/current/userguide/toolchains.html
| // JDK's, so newer JDK APIs (e.g. List.getFirst()) can't leak into published bytecode. | ||
| val javaExtension = the<JavaPluginExtension>() | ||
| tasks.named<JavaCompile>("compileJava") { | ||
| options.release.set( |
There was a problem hiding this comment.
| */ | ||
| fun Project.applyToolchainToCompileTasks(taskFilter: (Task) -> Boolean) { | ||
| // AGP assigns these task properties while it creates the variants, so this has to run later. | ||
| afterEvaluate { |
There was a problem hiding this comment.
can we remove the afterEvaluate here? afterEvaluate is almost always a bad practice. here's a blog post: https://www.liutikas.net/2022/12/09/Evaluate-Your-AfterEvaluate.html
There was a problem hiding this comment.
Unfortunately we can't for the Java compile tasks. Since the AGP plugin would overwrite the setting within their own .configure block. I've tried a few more options (e.g. onVariants, etc..) but none of them worked.
runningcode
left a comment
There was a problem hiding this comment.
gave it another round of review! it looks much better!
| // Modules that publish a higher level override this; test code and samples are never published and | ||
| // follow the toolchain. | ||
| // Keep this in sync with the publication filter in the root build script. | ||
| val isPublished = |
There was a problem hiding this comment.
this works right now but it relies on us always following this naming convention and silently breaking if we forget to follow it.
a better way would be to move this logic to a separate plugin and then only apply the plugin to those builds.
otherwise another way would be to set a gradle property in those builds and then this plugin detects that property.
There was a problem hiding this comment.
Removed this in favor of checking if the maven publishing plugin is applied or not.
| dependencies { | ||
| implementation(libs.animalsniffer.gradle.plugin) | ||
| implementation(libs.spotlessLib) | ||
| // Only for the Kotlin task types the jvm-targets plugin configures. |
There was a problem hiding this comment.
i don't think we need this comment.
Separately, do you know why it is compileOnly here?
There was a problem hiding this comment.
cleaned it up, compileOnly as we just need it in order to access classes like KotlinJvmCompile within our plugin.
There was a problem hiding this comment.
yes but it would fail without having that code there? we're relying on the fact that someone else bundles it without handling its absence.
| } | ||
|
|
||
| tasks.withType<KotlinCompile>().configureEach { | ||
| tasks.named<KotlinCompile>("compileKotlin") { |
There was a problem hiding this comment.
do you know the reason this was changed to be named? is it so that we don't affect the test tasks?
| } | ||
|
|
||
| tasks.withType<KotlinCompile>().configureEach { | ||
| tasks.named<KotlinCompile>("compileKotlin") { |
There was a problem hiding this comment.
do you know why all these were changed from withType to named?
There was a problem hiding this comment.
yeah it's to define different targets for unit tests (targeting 21) and main compilation (targeting 1.8), because otherwise Mockito 5 (which requires Java 11) would fail.
# Conflicts: # .github/workflows/build.yml
…ompilation plugin
# Conflicts: # build-logic/build.gradle.kts # buildSrc/build.gradle.kts
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit e387384. Configure here.
| options.release.set(PUBLISHED_JAVA_VERSION.majorVersion.toInt()) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Kotlin lacks JDK API release pin
Medium Severity
The new JDK 21 toolchain pins published Java with --release, but Kotlin main compilation only sets jvmTarget. Compiling Kotlin against JDK 21 can resolve APIs such as List.getFirst() and emit them into Java 8 or 17 bytecode with no compile error, which then fails at runtime on Java 8 and older Android.
Additional Locations (1)
Triggered by project rule: PR Review Guidelines for Cursor Bugbot
Reviewed by Cursor Bugbot for commit e387384. Configure here.
runningcode
left a comment
There was a problem hiding this comment.
you're right toolchains don't solve this problem at all. i feel adding it only makes this more complicated.
|
|
||
| // Only works for non-android modules | ||
| // See https://issuetracker.google.com/issues/278800528 | ||
| project.plugins.withId("com.vanniktech.maven.publish") { |
There was a problem hiding this comment.
i was going to ask why this line exists but then i saw your other comment.
i think this is fragile since it relies on precise task namings. i don't think this would match compileReleaseJava for example meaning that these tasks are not set properly: https://scans.gradle.com/s/mom72s36qrefi/timeline?name=compileReleaseJava
| } | ||
| } | ||
|
|
||
| private fun Project.minSdk(): Int = |
| // source/targetCompatibility are plain fields, so the last write wins, and AGP writes its | ||
| // compileOptions while it creates the compile tasks. Registering earlier - at apply time or | ||
| // in onVariants - is silently overwritten, so right now there's no way around afterEvaluate. | ||
| afterEvaluate { |
There was a problem hiding this comment.
here, we don't have any strict requirements on sourcecompatibilty or targetcompatibility since this is test code. i think we should just set the toolchain here (and hopefully also remove the afterEvaluate in the process since I'm pretty sure AGP doesn't set the toolchain)
| * - published Android modules main code targets Java 1.8 | ||
| * - minSdk is set for Android libraries | ||
| */ | ||
| class SentryCompilationPlugin : Plugin<Project> { |
There was a problem hiding this comment.
i think this is about "java compatibility" so i think a name like JdkTargetConvention or something like that would make more sense.
| } | ||
|
|
||
| project.plugins.withId("com.android.library") { | ||
| val android = project.extensions.getByType(BaseExtension::class.java) |
There was a problem hiding this comment.
we have some production kotlin code. do you know if this sets the JDK compatibility on that?
I looked at how ANdroidx does this might be worth copying. They notably don't set the toolchain anywhere in code. https://github.com/androidx/androidx/blob/95347b245f1527ec6856a3daa580a1c0abbddc11/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt#L492
| dependencies { | ||
| implementation(libs.animalsniffer.gradle.plugin) | ||
| implementation(libs.spotlessLib) | ||
| // Only for the Kotlin task types the jvm-targets plugin configures. |
There was a problem hiding this comment.
yes but it would fail without having that code there? we're relying on the fact that someone else bundles it without handling its absence.
| // share the Java 8 target of the published code; it compiles for the build JDK instead. | ||
| // Registered once each plugin is applied, so this afterEvaluate runs after the plugins and the | ||
| // module have pinned the JVM target of all compile tasks. | ||
| listOf("java", "com.android.library", "com.android.application").forEach { pluginId -> |


📜 Description
Switches the build and CI from JDK 17 to JDK 21, and upgrades the tools that the new JDK forces us to move: Mockito, Error Prone and NullAway. Compilation and tests run on a JDK 21 toolchain. The bytecode we publish still targets Java 8 (Spring Boot 4 / Spring 7 modules target 17).
Just changing to 21 has a few ripple effects, so there's quite a broad set of changes involved across the codebase.
JDK 21
CONTRIBUTING.mdis updated.io.sentry.jvm-targetsconvention plugin (build-logic) compiles the code and runs the tests, so a contributor on a newer JDK gets the same output as CI. The foojay resolver fetches a JDK 21 when no local one matches.--release <target>. A newer JDK could otherwise let APIs such asList.getFirst()get into Java 8 bytecode without a compile error, reducing the risk of introducing breaking changes for Android.Mockito 4.8 → 5.24, mockito-kotlin 4.1 → 6.4
net.bytebuddy.experimental.mockito-inlineis replaced withmockito-core. Mockito 5 uses the inline mock maker by default.anyVararg(), because in Mockito 5any()on a varargs parameter matches exactly one argument.Error Prone 2.11 → 2.50, NullAway 0.9.5 → 0.14.2, Gradle plugin 3.0.1 → 5.1.1
NoSuchMethodError: TreeMaker.Select, and NullAway 0.9.5 cannot load against a newer Error Prone. Neither bump can be split out of this PR.Scope.executeBeforeBreadcrumbcould throw an NPE. This happens when the callback returnsnulland theclose()of the reentrancy guard then throws.MethodInvocation.getThis(), which isnullfor static methods. They now pass anulltarget class toAopUtils.getMostSpecificMethod.invoke()is now@Nullable, the same as the Spring 7 contract.SentryAppender(logback) passes an empty array instead ofnullvarargs. The behavior is the same.instanceof(Java 17 modules only),Locale.ROOTfortoUpperCase, explicitlong→doublecasts,/**on non-Javadoc comments, and modifiers on effectively-private members.JdkObsoletereplacements that need Java 10+,this-escapein public non-final classes,serial, and the vendored Gson.Samples
sentry-samples-spring), as otherwise those would stop working because ofUnsupported class file major version 65errors.Other
SentryEnvelopeItemTest."fromAttachment with file SecurityManager denies read access"installs aSecurityManager. Since JDK 18 this needs-Djava.security.manager=allow, which thesentrytests now set.Buildjob name no longer carries a Java version that goes stale at the next bump.💡 Motivation and Context
Preparation for SDK 37 support (for example
ActivityManager.registerAnrWarningListener). Robolectric 4.17 is the first version that supports SDK 37, and SDK 37 needs Java 21, so JDK 21 has to land first.💚 How did you test it?
Tests
📝 Checklist
sendDefaultPIIis enabled.🔮 Next steps
sentry-samples-android,-ktor-client,-openfeign,-servlet,-netflix-dgs) to main compilation, so every sample treats its test code the same wayInternalSentrySdkTest, which readssession.jsonright after an asynchronous write#skip-changelog