refactor: convert the Android-specific layer to Kotlin - #388
Draft
tony19 wants to merge 2 commits into
Draft
Conversation
Owner
Author
|
Status: GitHub Actions is fully green (assemble + all 993 release-variant unit tests + lint) as of CircleCI
🤖 Generated with Claude Code Generated by Claude Code |
This was referenced Jul 7, 2026
tony19
force-pushed
the
claude/kotlin-android-refactor-f8cwzg
branch
from
July 11, 2026 23:59
fad499b to
7114177
Compare
tony19
force-pushed
the
claude/kotlin-android-refactor-f8cwzg
branch
6 times, most recently
from
July 13, 2026 04:51
d9a36ea to
aabe4fb
Compare
…ilt-in support This project publishes JDK-specific artifacts whose flavors pin different bytecode targets (jdk8 -> Java 8, jdk11 -> Java 11). AGP 9's built-in Kotlin support pins a single jvmTarget per project, validated against the DSL-level compileOptions (issuetracker.google.com/408242956), so it cannot express that layout: aligning Kotlin with either flavor breaks the other, and raising javac per-task via options.release detaches the task from the Android bootclasspath. Instead, opt out of built-in Kotlin (android.builtInKotlin=false + android.newDsl=false — JetBrains' documented escape hatch, scheduled for removal in AGP 10; revisit when built-in Kotlin gains per-variant JVM targets) and use the standalone Kotlin Android plugin (2.2.20): - Kotlin bytecode targets mirror the existing per-flavor javac override exactly: Java 8 for the published jdk8 library classes, Java 11 for the jdk11 variants and all unit tests. The javac override itself is untouched. - The standalone plugin's task-pairing JVM-target validation is demoted to a warning (kotlin.jvm.target.validation.mode): it reads the paired Java task's DSL-derived targetCompatibility, which intentionally diverges for jdk8. The published class-file versions are enforced independently by the CI workflow's bytecode check. - Library Kotlin sources compile in explicit-API mode, so every public declaration states its visibility and types deliberately. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UCK17vyzha2TXKfm6UJCom
Convert the Android integration layer (the code unique to logback-android, as opposed to the upstream logback port) to modern, idiomatic Kotlin under src/main/kotlin: - LogcatAppender, SQLiteAppender, BasicLogcatConfigurator, SQLiteLogCleaner, Clock, SystemClock (ch.qos.logback.classic.android) - AndroidContextUtil, SystemPropertiesProxy (ch.qos.logback.core.android) The conversion preserves current behavior and API, including the recent LayoutWrappingEncoder support in LogcatAppender (issue #376, with @defaultclass retained on the setters for Joran), the application-context holder and setApplicationContext() (issue #383), the EXT_FILES_DIR / EXT_CACHE_DIR properties (issue #181), and the broken-external-storage tolerance (issue #315), and the platform-mediated creation of app-specific external dirs (createAppExternalStorageDirs, issue #228) that FileUtil calls into. Java interop is preserved: property accessors compile to the original getter/setter signatures (Joran reflection unaffected), @JvmStatic/@jvmoverloads retain the static entry points, @JvmName keeps the package-private setClock test hook unmangled, and SQLiteLogCleaner is a fun interface so Java anonymous classes still work. All existing Java tests compile and pass unmodified against the converted classes. One deliberate change: AndroidContextUtil.getContext() is now public (Kotlin has no package-private; it pairs naturally with the public setApplicationContext()). Minor modernization: getPackageInfo uses PackageInfoFlags on API 33+, and SQLiteAppender.stop()/finalize() no longer NPE when the appender never started. The upstream-derived logback core/classic port intentionally stays in Java to keep diffs against upstream reviewable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UCK17vyzha2TXKfm6UJCom
tony19
force-pushed
the
claude/kotlin-android-refactor-f8cwzg
branch
from
July 13, 2026 21:42
aabe4fb to
f19fd4f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Rebased onto current
main(AGP 9.2.1 / Gradle 9.6.1, including the new in-repo static analysis from #436). Main has since absorbed everything this PR originally carried for build/CI/test modernization — often in improved form (Mockito 5 inline mockmaker, test-retry plugin, JDK-matrix CI, dynamic syslog packet count, retired CircleCI) — so the PR is now reduced to its unique contribution in two commits:1.
build:Kotlin support with per-flavor bytecode targetsWires the standalone Kotlin Android plugin (2.2.20) into the existing Groovy build:
android.builtInKotlin=false+android.newDsl=false, JetBrains' documented escape hatch): built-in Kotlin validates a single project-widejvmTargetagainst the DSL-levelcompileOptions, which cannot express this project's per-flavor bytecode targets. Revisit when built-in Kotlin gains per-variant JVM targets (the flags are scheduled for removal in AGP 10).jdk8library classes, Java 11 for thejdk11variants and all unit tests2.
refactor:Android layer converted to idiomatic KotlinConverted under
src/main/kotlin(properties,when,use, fun interfaces, null-safety):ch.qos.logback.classic.android:LogcatAppender,SQLiteAppender,BasicLogcatConfigurator,SQLiteLogCleaner,Clock,SystemClockch.qos.logback.core.android:AndroidContextUtil,SystemPropertiesProxyThe conversion preserves current behavior and API, including all the recent main-branch work:
LayoutWrappingEncodersupport inLogcatAppender(#376,@DefaultClassretained on setters for Joran), the application-context holder +setApplicationContext()(#383),EXT_FILES_DIR/EXT_CACHE_DIRproperties (#181), broken-external-storage tolerance (#315), and platform-driven creation of the app-specific external dirs (#435, ported asAndroidContextUtil.createAppExternalStorageDirs()with subclassability preserved forFileUtilTest).Java/XML-config interop preserved: property accessors compile to the original getter/setter signatures (Joran reflection unaffected);
@JvmStatic/@JvmOverloadskeep the static entry points;@JvmNamekeeps the package-privatesetClocktest hook unmangled;SQLiteLogCleaneris afun interfaceso Java anonymous classes still work. All existing Java tests compile and pass unmodified. One deliberate change:AndroidContextUtil.getContext()is nowpublic(Kotlin has no package-private; it pairs naturally with the publicsetApplicationContext()).Minor modernization:
getPackageInfousesPackageInfoFlagson API 33+, andSQLiteAppender.stop()/finalize()no longer NPE when the appender never started.The upstream-derived logback core/classic port (~400 files) intentionally stays in Java so future diffs against upstream logback remain reviewable. This also means the new Checkstyle/PMD static analysis (#436), which scans
src/main/java, keeps covering the entire upstream-derived tree; the CodeQL workflow's Java extractor picks up the Kotlin sources as well.Verification
This sandbox can't reach
dl.google.com/maven.google.com, so AGP couldn't run here; verified with the same compilers CI uses, against Maven-Central artifacts:-Xexplicit-api=strictat both-jvm-target 1.8(jdk8 flavor) and11, plus javac--release 11for the 422 Java filesLogcatAppenderTest,SQLiteAppenderTest,AndroidContextUtilTest— including the newsetApplicationContexttests) plus Joran/OptionHelper/ContextInitializer suites, andFileUtilTestagainst the portedcreateAppExternalStorageDirs(): all pass except the one case that requires AGP's generated test manifest, which passes in real CIThe CI gate to watch: the built-in-Kotlin opt-out under AGP 9.2.1 hasn't been exercised in CI yet — this run confirms the standalone plugin loads and the per-flavor bytecode check (major 52 for jdk8, 55 for jdk11) still passes.
Relation to #211 (closed WIP "logback-android next")
Reviewed for pull-in candidates: its discrete bug fixes are already on
main, and its partial Kotlin conversions are a subset of this PR's. Its unmerged WIP config DSL is better suited to a dedicated follow-up.🤖 Generated with Claude Code
https://claude.ai/code/session_01UCK17vyzha2TXKfm6UJCom