-
Notifications
You must be signed in to change notification settings - Fork 21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Initial KMP Restructure #15
base: main
Are you sure you want to change the base?
Initial KMP Restructure #15
Conversation
frenziedherring
commented
May 15, 2022
- Updated src into source sets for common/jvm/js/android/native
- Moved JVM code to JVM source set
- Extracted platform dependent pieces into expect/actual
- Implemented required 'actual's for JS & Native
- Moved AndroidLogcatLogger to android source set
- Updated src into source sets for common/jvm/js/android/native - Moved JVM code to JVM source set - Extracted platform dependent pieces into expect/actual - Implemented required 'actual's for JS & Native - Moved AndroidLogcatLogger to android source set
Thanks a lot, this is really cool! |
gradle.properties
Outdated
POM_LICENCE_DIST=repo | ||
|
||
POM_DEVELOPER_ID=square | ||
POM_DEVELOPER_NAME=Square, Inc. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit surprised you had to add all this, we've published releases just fine in the past, what's changed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I used a skeleton KMP project to help get the Android source setup with the build files - this must have come over with it. I'll remove these extra lines.
I have no experience publishing an AAR, so I'll have to dig in to learn about the publishing process.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These have been removed and I was able to run the Android build and sample project on the emulator. The publishing plugin was re-added when I restructured in 3c402d1 so I think the AAR publishing should be OK now
@@ -0,0 +1,3108 @@ | |||
git add src comm | |||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. | |||
# yarn lockfile v1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this file should not be committed, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've always committed the lock files that are generated from package managers in the past (package-lock.json on my past NPM projects) to ensure the dependencies are specific.
That being said - how did that stray git
command get in there? 🤦 At a minimum I'll fix that, but definitely can remove this file if you think it's not entirely necessary
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Check what other Square KMP projects do
printStackTrace(printWriter) | ||
printWriter.flush() | ||
return stringWriter.toString() | ||
actual fun Throwable.asLog(): String { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not super familiar with KMP: why is this in androidMain? This should work fine elsewhere, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to build out hierarchical source sets so that the Android source could leverage the JVM implementation since It should definitely work in the JVM. After digging into https://kotlinlang.org/docs/multiplatform-share-on-platforms.html, It looks like that isn't currently supported:
You can have a shared source set for the following combinations of targets:
- JVM + JS + Native
- JVM + Native
- JS + Native
- JVM + JS
- Native
Kotlin doesn't currently support sharing a source set for these combinations:
- Several JVM targets
- JVM + Android targets
- Several JS targets
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
huh that's sad.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably the saddest part of this whole effort - hopefully in the future the JVM + Android can be shared
src/androidTest/kotlin/LogcatTest.kt
Outdated
|
||
class LogcatTest { | ||
|
||
@After | ||
@AfterTest |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting, why migrate from junit to kotlin test? Is kotlin test able to run on multiple platforms?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exactly. In fact, I discovered that the @kotlin.test.AfterTest is just a typealias to @org.junit.After for JVM
package kotlin.test
public actual typealias Test = org.junit.Test
public actual typealias Ignore = org.junit.Ignore
public actual typealias BeforeTest = org.junit.Before
public actual typealias AfterTest = org.junit.After
But using that allowed me to port the tests to JS and Native (which was super helpful in figuring out the bit that were different between the Android implementation and other platforms, especially with stack traces, Companion objects and frozen references for native, to fill in the expect
/actual
. Running the Gradle check
target executes each of the platform's tests.
) | ||
|
||
companion object { | ||
// For expect functions above |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
above => below?
Why do these have to be defined below?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that should be "below". They're defined as extensions on the Companion so that they can have the expect modifier. I would have preferred them inline, but including them in the Companion directly as expect
complains that there's no backing field or delegate.
Modifier 'expect' is not applicable to 'member property without backing field or delegate'
I will definitely re-arrange to better co-locate the expected extensions in the file though
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rearranged to help with code readability
import kotlin.test.assertEquals | ||
import kotlin.test.assertTrue | ||
|
||
class LogcatTest { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this test duplicated in N places? Feels like the test should only be written once.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, there's some work I can to to collapse tests which don't have platform specific requirements. Primarily the ones that differ the most are the tests for Throwable, and the native tests require a TestLogger with AtomicReferences to prevent Immutability exceptions on non-frozen references.
I'll take another pass on the test sections to clean them up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As much as possible, commonTest includes test that can run on all platforms. They are executed once for each of the platforms (JVM/JS/Native) using the same test.
- Removed commented lines from build.gradle.kts - Removed extra lines from gradle.properties - Changed Gradle back to 7.2 in gradle-wrapper.properties - Fixed yarn.lock - Reformatted code to match style - Restructured Tests - Use actual/expect to inject ITestLogcatLogger - Pull platform-agnostic tests to CommonLogcatTest.kt - Put platform-specific tests in [Platform]LogcatTest.kt
@pyricau - I updated my branch based on the comments above. The biggest change was updating the test structure to prevent duplicating tests in N platforms. The common tests now leverage expect/actual to get an instance of the test logger for the platform, leaving the rest of the test intact. Let me know what you think about that. Thanks for the review! |
- Moved src under /logcat - Updated .api files - Updated Gradle plugin to 7.0.0 - Removed versionName and versionCode from android block in build.gradle.kts - Re-added the publishing and KtLint checks - Fixed lint errors - Removed jcenter repo
- Clean up build.gradle.kts
- Put Coroutines dependency into Dependencies.kt - Limit coroutines to only commonTest dependency set - Downgraded errant Coroutines 1.6 to 1.5.2 - Allow Kotlin 1.5.31 and Java 8 to pass build checks - Removed test case with multiple nested Companion functions - The use case was very edge case, and the multiple nesting was making JS Throwable fallback fail. Single companion function call (no nesting) still passes though
@pyricau - I downgraded some dependencies (and fixed tests that were failing) that should address Gradle failures in https://github.com/square/logcat/runs/6610273912?check_suite_focus=true. I downgraded the coroutines dependency to 1.5.2 which allowed going back to Kotlin 1.5.30 and Java 8. It's also only needed in commonTest. |
@@ -1,3 +1,7 @@ | |||
kotlin.code.style=official |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems off-topic.
@@ -1,7 +1,7 @@ | |||
#!/bin/sh |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's with the empty wrapper jar?!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know why it says it's empty - the file is about 58k on my branch (https://github.com/frenziedherring/logcat/blob/frenziedherring/logcatx/gradle/wrapper/gradle-wrapper.jar)
logcat/build.gradle.kts
Outdated
val nativeTarget = when { | ||
hostOs == "Mac OS X" -> macosX64("native") | ||
hostOs == "Linux" -> linuxX64("native") | ||
isMingwX64 -> mingwX64("native") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typically we include all native targets. Also this is missing many important targets like ARM Macs and iOS.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have added more targets that will come in the next push. I created the nativeMain
and nativeTest
source sets and made the other targets dependOn them. I only have a Mac, so I could not build some of the targets
|
||
val stackTraceStr = """ | ||
|RuntimeException: damn | ||
| at JsLogcatTest.Throwable_asLogMessage_has_stacktrace_logged |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this dependent on the JS engine?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Appears to be part of the Kotlin stdlib for JS, but does use the asDynamic().stack
which could be JS engine specific. FWIW, the test did pass when I added nodejs in addition to browser as targets in the build.gradle. I'll have to find time to dig in more to find more of an answer.
https://github.com/JetBrains/kotlin/tree/d8b0dbe71bbeb84ba0fbdac87801c8c6f4eed4c9/libraries/stdlib/js/src/kotlin/throwableExtensions.kt#L18 uses ExceptionTraceBuilder.dumpFullTrace/dumpSelfTrace.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like the .stack
is dependent on JS engine:
v8 - https://v8.dev/docs/stack-trace-api, https://nodejs.org/api/errors.html#errorstack
mdn - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/stack#examples
* - No silent swallowing of UnknownHostException. | ||
* - The buffer size is 256 bytes instead of the default 16 bytes. | ||
*/ | ||
actual fun Throwable.asLog(): String { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems duplicated?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider a jniMain that has Android and JVM sources.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was able to make androidMain
depend on jvmMain
and remove the duplication (even though based on #15 (comment), it looked like sharing Android + JVM wasn't supported), and that appears to build & pass tests. The IDE is showing unresolved references, though, so I'm working through that right now
|
||
val stackTraceStr = """ | ||
|kotlin.RuntimeException: damn | ||
| at 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we do better than this?
get() = loggerReference.value | ||
|
||
actual fun LogcatLogger.Companion.install(logger: LogcatLogger) { | ||
logger.freeze() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At this point, freezing should be optional and controlled by the runtime memory setting
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this referring to the new memory management for Kotlin/Native? Is there a way to check the memory setting at runtime to optionally freeze the logger?
return if (this.isNullOrEmpty() || this == "Companion") null else this | ||
} | ||
|
||
actual fun Any.outerClassSimpleNameInternalOnlyDoNotUseKThxBye(): String { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should do some visibility review. This gets exposed to the framework header, I'd assume unnecessarily
__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("OuterClassSimpleNameInternalOnlyDoNotUseKThxByeKt")))
@interface LogcatOuterClassSimpleNameInternalOnlyDoNotUseKThxByeKt : LogcatBase
+ (NSString *)outerClassSimpleNameInternalOnlyDoNotUseKThxBye:(id)receiver __attribute__((swift_name("outerClassSimpleNameInternalOnlyDoNotUseKThxBye(_:)")));
@end;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated that particular function to be @PublishedApi internal actual
. I don't see it showing up in my local build's framework header now, as far as I can tell (I'm not an iOS developer; primarily Android)
|
||
actual fun LogcatLogger.Companion.install(logger: LogcatLogger) { | ||
logger.freeze() | ||
loggerReference.compareAndSet(NoLog, logger).also { updated -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might be a dumb idea, but it's been on my mind forever with Kermit and others. You can't really reset the logger, or generally speaking, you don't have any use case that would need it. So, if you could configure the logger up front, possibly at build time (dev vs release), you could avoid every logging call going through an atomic reference. All global reference loggers have the same issue, and it seems avoidable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting. Are you thinking that you'd swap an impl JAR, i.e. both jars have the same class, one is a no-op and the other the real impl?
Or something else?
@@ -3,11 +3,15 @@ package logcat | |||
/** | |||
* A [LogcatLogger] that always logs and delegates to [println] concatenating | |||
* the tag and message, separated by a space. Alternative to [AndroidLogcatLogger] | |||
* when running on a JVM. | |||
* when running on a JVM/JS/Native. | |||
*/ | |||
object PrintLogger : LogcatLogger { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like the "other platforms" only get println. Consider iOS and JS specific loggers (touchlab/Kermit#183, https://github.com/touchlab/Kermit/blob/main/kermit/src/jsMain/kotlin/co/touchlab/kermit/ConsoleWriter.kt).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lots of good KMP stuff to learn from Kermit, for sure. Unfortunately a few things I wish were different (lack of auto tag and requiring devs to make a decision about severity instead of making that optional. See here https://github.com/square/logcat#motivations)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, I mean, I'm not saying "use Kermit", I'm just saying if it's multiplatform, I'd add some platform-specific loggers. The first link is to a thread where we took inspiration from Napier, which is another KMP logger. I'll definitely take a look at the motivations, as we're sort of redesigning Kermit right now. Tagging is tricky, for sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Certainly seems like we could make a JS specific logger that uses the console API. I'll give it a try.
- Create source set hierarchy - Removed println & printStackTrace - Made outerClassSimpleNameInternalOnlyDoNotUseKThxBye.kt internal with @PublishedApi across `actual`s to keep it out of headers - androidMain depends on jvmMain, removing duplication