From bc0126840a2099e2b4aac75688820e83bc1d6049 Mon Sep 17 00:00:00 2001 From: kareemessam09 Date: Fri, 4 Sep 2026 22:06:31 +0300 Subject: [PATCH 1/4] Add Kotlin 2.0+ support for Gradle and IR compiler plugin (#435) Migrate kermit-ir-plugin to Kotlin 2.0+ CompilerPluginRegistrar with supportsK2 enabled. - Replace ComponentRegistrar with CompilerPluginRegistrar - Register IR extension via ExtensionStorage and use ClassId and org.jetbrains.kotlin.ir.util.isSubtypeOfClass - Drop obsolete kermit-ir-plugin-native module (unneeded in Kotlin 2.0+) - Remove getPluginArtifactForNative from KermitGradlePlugin - Replace kapt and auto-service with direct META-INF/services declarations - Modernize test suite with kctfork and add runtime execution verification test - Add KermitGradlePluginTest unit tests and ensure ktlint passes --- build.gradle.kts | 3 +- gradle/libs.versions.toml | 4 + plugin/kermit-gradle-plugin/build.gradle.kts | 8 + .../kermit/gradle/KermitGradlePlugin.kt | 12 +- .../touchlab/kermit/gradle/StripSeverity.kt | 4 +- .../kermit/gradle/KermitGradlePluginTest.kt | 48 +++++ plugin/kermit-ir-plugin-native/.gitignore | 1 - .../kermit-ir-plugin-native/build.gradle.kts | 26 --- .../kermit-ir-plugin-native/gradle.properties | 12 -- plugin/kermit-ir-plugin/build.gradle.kts | 12 +- .../irplugin/KermitChiselTransformer.kt | 31 +-- .../irplugin/KermitCommandLineProcessor.kt | 16 +- ...ar.kt => KermitCompilerPluginRegistrar.kt} | 32 ++- .../irplugin/KermitIrGenerationExtension.kt | 19 +- ...otlin.compiler.plugin.CommandLineProcessor | 1 + ...in.compiler.plugin.CompilerPluginRegistrar | 1 + .../co/touchlab/kermit/irplugin/Helper.kt | 78 ++------ .../irplugin/KermitStripperIrPluginTest.kt | 183 ++++++++++++++---- settings.gradle.kts | 12 +- 19 files changed, 278 insertions(+), 225 deletions(-) create mode 100644 plugin/kermit-gradle-plugin/src/test/kotlin/co/touchlab/kermit/gradle/KermitGradlePluginTest.kt delete mode 100644 plugin/kermit-ir-plugin-native/.gitignore delete mode 100644 plugin/kermit-ir-plugin-native/build.gradle.kts delete mode 100644 plugin/kermit-ir-plugin-native/gradle.properties rename plugin/kermit-ir-plugin/src/main/kotlin/co/touchlab/kermit/irplugin/{KermitComponentRegistrar.kt => KermitCompilerPluginRegistrar.kt} (60%) create mode 100644 plugin/kermit-ir-plugin/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor create mode 100644 plugin/kermit-ir-plugin/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar diff --git a/build.gradle.kts b/build.gradle.kts index c4f16551..2c22b181 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -28,11 +28,12 @@ plugins { alias(libs.plugins.touchlab.docusaurus.template) alias(libs.plugins.android.library) apply false alias(libs.plugins.ktlint.gradle) apply false + alias(libs.plugins.mavenPublish) apply false } apiValidation { nonPublicMarkers.add("co.touchlab.kermit.ExperimentalKermitApi") -// ignoredProjects.addAll(listOf("kermit-gradle-plugin", "kermit-ir-plugin", "kermit-ir-plugin-native")) + ignoredProjects.addAll(listOf("kermit-gradle-plugin", "kermit-ir-plugin")) } val GROUP: String by project diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 553f9470..efe72827 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -29,6 +29,7 @@ kotlinx-js = "0.12.0" kotlinx-wasm = "0.3.1" roboelectric = "4.16.1" buildConfig = "5.6.7" +kctfork = "0.12.1" mavenPublish = "0.36.0" dokka = "2.2.0" @@ -65,6 +66,8 @@ kotlinx-io = { module = "org.jetbrains.kotlinx:kotlinx-io-core", version.ref = " kotlinx-js = { module = "org.jetbrains.kotlinx:kotlinx-html-js", version.ref = "kotlinx-js" } kotlinx-wasm = { module = "org.jetbrains.kotlinx:kotlinx-browser-wasm-js", version.ref = "kotlinx-wasm"} roboelectric = { module = "org.robolectric:robolectric", version.ref = "roboelectric" } +kotlin-compiler-embeddable = { module = "org.jetbrains.kotlin:kotlin-compiler-embeddable", version.ref = "kotlin" } +kctfork = { module = "dev.zacsweers.kctfork:core", version.ref = "kctfork" } # For convention-plugins android-library-gradlePlugin = { module = "com.android.tools.build:gradle", version.ref = "android-gradle-plugin" } @@ -80,6 +83,7 @@ dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" } touchlab-docusaurus-template = { id = "co.touchlab.touchlabtools.docusaurusosstemplate", version.ref = "touchlab-docusaurus-template" } bugsnag-gradle-plugin = { id = "com.bugsnag.android.gradle", version.ref = "bugsnag-gradle-plugin" } ktlint-gradle = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint-gradle" } +mavenPublish = { id = "com.vanniktech.maven.publish", version.ref = "mavenPublish" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } [bundles] diff --git a/plugin/kermit-gradle-plugin/build.gradle.kts b/plugin/kermit-gradle-plugin/build.gradle.kts index ba9875db..abffa94c 100644 --- a/plugin/kermit-gradle-plugin/build.gradle.kts +++ b/plugin/kermit-gradle-plugin/build.gradle.kts @@ -1,3 +1,5 @@ +import com.vanniktech.maven.publish.DeploymentValidation + plugins { id("java-gradle-plugin") kotlin("jvm") @@ -7,6 +9,7 @@ plugins { dependencies { implementation(kotlin("gradle-plugin-api")) + testImplementation(kotlin("test-junit")) } buildConfig { @@ -28,3 +31,8 @@ gradlePlugin { } } } + +mavenPublishing { + configureBasedOnAppliedPlugins() + publishToMavenCentral(true, DeploymentValidation.NONE) +} diff --git a/plugin/kermit-gradle-plugin/src/main/kotlin/co/touchlab/kermit/gradle/KermitGradlePlugin.kt b/plugin/kermit-gradle-plugin/src/main/kotlin/co/touchlab/kermit/gradle/KermitGradlePlugin.kt index ba41794e..b8478604 100644 --- a/plugin/kermit-gradle-plugin/src/main/kotlin/co/touchlab/kermit/gradle/KermitGradlePlugin.kt +++ b/plugin/kermit-gradle-plugin/src/main/kotlin/co/touchlab/kermit/gradle/KermitGradlePlugin.kt @@ -29,18 +29,10 @@ class KermitGradlePlugin : KotlinCompilerPluginSupportPlugin { override fun getPluginArtifact(): SubpluginArtifact = SubpluginArtifact( groupId = BuildConfig.KOTLIN_PLUGIN_GROUP, artifactId = BuildConfig.KOTLIN_PLUGIN_NAME, - version = BuildConfig.KOTLIN_PLUGIN_VERSION + version = BuildConfig.KOTLIN_PLUGIN_VERSION, ) - override fun getPluginArtifactForNative(): SubpluginArtifact = SubpluginArtifact( - groupId = BuildConfig.KOTLIN_PLUGIN_GROUP, - artifactId = BuildConfig.KOTLIN_PLUGIN_NAME + "-native", - version = BuildConfig.KOTLIN_PLUGIN_VERSION - ) - - override fun applyToCompilation( - kotlinCompilation: KotlinCompilation<*> - ): Provider> { + override fun applyToCompilation(kotlinCompilation: KotlinCompilation<*>): Provider> { val project = kotlinCompilation.target.project val extension = project.extensions.getByType(KermitGradleExtension::class.java) return project.provider { diff --git a/plugin/kermit-gradle-plugin/src/main/kotlin/co/touchlab/kermit/gradle/StripSeverity.kt b/plugin/kermit-gradle-plugin/src/main/kotlin/co/touchlab/kermit/gradle/StripSeverity.kt index d50b9f97..1b42e19b 100644 --- a/plugin/kermit-gradle-plugin/src/main/kotlin/co/touchlab/kermit/gradle/StripSeverity.kt +++ b/plugin/kermit-gradle-plugin/src/main/kotlin/co/touchlab/kermit/gradle/StripSeverity.kt @@ -18,5 +18,5 @@ enum class StripSeverity { Warn, Error, Assert, - All -} \ No newline at end of file + All, +} diff --git a/plugin/kermit-gradle-plugin/src/test/kotlin/co/touchlab/kermit/gradle/KermitGradlePluginTest.kt b/plugin/kermit-gradle-plugin/src/test/kotlin/co/touchlab/kermit/gradle/KermitGradlePluginTest.kt new file mode 100644 index 00000000..56459d77 --- /dev/null +++ b/plugin/kermit-gradle-plugin/src/test/kotlin/co/touchlab/kermit/gradle/KermitGradlePluginTest.kt @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2021 Touchlab + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + */ + +package co.touchlab.kermit.gradle + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import org.gradle.testfixtures.ProjectBuilder + +class KermitGradlePluginTest { + @Test + fun `plugin registers kermit extension with default stripBelow None`() { + val project = ProjectBuilder.builder().build() + project.plugins.apply("co.touchlab.kermit") + + val extension = project.extensions.findByType(KermitGradleExtension::class.java) + assertNotNull(extension) + assertEquals(StripSeverity.None, extension.stripBelow) + } + + @Test + fun `plugin allows configuring stripBelow`() { + val project = ProjectBuilder.builder().build() + project.plugins.apply("co.touchlab.kermit") + + val extension = project.extensions.getByType(KermitGradleExtension::class.java) + extension.stripBelow = StripSeverity.Error + assertEquals(StripSeverity.Error, extension.stripBelow) + } + + @Test + fun `compiler plugin id and artifact coordinates match build config`() { + val plugin = KermitGradlePlugin() + assertEquals(BuildConfig.KOTLIN_PLUGIN_ID, plugin.getCompilerPluginId()) + val artifact = plugin.getPluginArtifact() + assertEquals(BuildConfig.KOTLIN_PLUGIN_GROUP, artifact.groupId) + assertEquals(BuildConfig.KOTLIN_PLUGIN_NAME, artifact.artifactId) + assertEquals(BuildConfig.KOTLIN_PLUGIN_VERSION, artifact.version) + } +} diff --git a/plugin/kermit-ir-plugin-native/.gitignore b/plugin/kermit-ir-plugin-native/.gitignore deleted file mode 100644 index 8eba6c8d..00000000 --- a/plugin/kermit-ir-plugin-native/.gitignore +++ /dev/null @@ -1 +0,0 @@ -src/ diff --git a/plugin/kermit-ir-plugin-native/build.gradle.kts b/plugin/kermit-ir-plugin-native/build.gradle.kts deleted file mode 100644 index b198286c..00000000 --- a/plugin/kermit-ir-plugin-native/build.gradle.kts +++ /dev/null @@ -1,26 +0,0 @@ -plugins { - kotlin("jvm") - kotlin("kapt") -// id("org.jetbrains.dokka") - id("com.vanniktech.maven.publish") -} - -dependencies { - compileOnly("org.jetbrains.kotlin:kotlin-compiler") - - kapt("com.google.auto.service:auto-service:1.0-rc7") - compileOnly("com.google.auto.service:auto-service-annotations:1.0-rc7") -} - -tasks.named("compileKotlin") { dependsOn("syncSource") } -tasks.register("syncSource") { - from(project(":kermit-ir-plugin").sourceSets.main.get().allSource) - into("src/main/kotlin") - filter { - // Replace shadowed imports from plugin module - when (it) { - "import org.jetbrains.kotlin.com.intellij.mock.MockProject" -> "import com.intellij.mock.MockProject" - else -> it - } - } -} \ No newline at end of file diff --git a/plugin/kermit-ir-plugin-native/gradle.properties b/plugin/kermit-ir-plugin-native/gradle.properties deleted file mode 100644 index 442c024a..00000000 --- a/plugin/kermit-ir-plugin-native/gradle.properties +++ /dev/null @@ -1,12 +0,0 @@ -# -# Copyright (c) 2021 Touchlab -# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -# -POM_ARTIFACT_ID=kermit-ir-plugin-native -POM_NAME=kermit-ir-plugin-native -POM_PACKAGING=jar \ No newline at end of file diff --git a/plugin/kermit-ir-plugin/build.gradle.kts b/plugin/kermit-ir-plugin/build.gradle.kts index a6cb8c12..60bda380 100644 --- a/plugin/kermit-ir-plugin/build.gradle.kts +++ b/plugin/kermit-ir-plugin/build.gradle.kts @@ -1,6 +1,7 @@ +import com.vanniktech.maven.publish.DeploymentValidation + plugins { kotlin("jvm") - kotlin("kapt") id("com.github.gmazzo.buildconfig") id("com.vanniktech.maven.publish") } @@ -8,12 +9,9 @@ plugins { dependencies { compileOnly("org.jetbrains.kotlin:kotlin-compiler-embeddable") - kapt("com.google.auto.service:auto-service:1.0-rc7") - compileOnly("com.google.auto.service:auto-service-annotations:1.0-rc7") - testImplementation(kotlin("test-junit")) testImplementation("org.jetbrains.kotlin:kotlin-compiler-embeddable") - testImplementation("com.github.tschuchortdev:kotlin-compile-testing:1.4.8") + testImplementation("dev.zacsweers.kctfork:core:0.12.1") } buildConfig { @@ -21,3 +19,7 @@ buildConfig { buildConfigField("String", "KOTLIN_PLUGIN_ID", "\"${rootProject.extra["kotlin_plugin_id"]}\"") } +mavenPublishing { + configureBasedOnAppliedPlugins() + publishToMavenCentral(true, DeploymentValidation.NONE) +} diff --git a/plugin/kermit-ir-plugin/src/main/kotlin/co/touchlab/kermit/irplugin/KermitChiselTransformer.kt b/plugin/kermit-ir-plugin/src/main/kotlin/co/touchlab/kermit/irplugin/KermitChiselTransformer.kt index 3eaf91b7..5228086a 100644 --- a/plugin/kermit-ir-plugin/src/main/kotlin/co/touchlab/kermit/irplugin/KermitChiselTransformer.kt +++ b/plugin/kermit-ir-plugin/src/main/kotlin/co/touchlab/kermit/irplugin/KermitChiselTransformer.kt @@ -10,34 +10,35 @@ package co.touchlab.kermit.irplugin +import org.jetbrains.kotlin.DeprecatedForRemovalCompilerApi import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder -import org.jetbrains.kotlin.ir.builders.* +import org.jetbrains.kotlin.ir.builders.irUnit import org.jetbrains.kotlin.ir.expressions.IrCall import org.jetbrains.kotlin.ir.expressions.IrExpression - -import org.jetbrains.kotlin.ir.types.classFqName -import org.jetbrains.kotlin.ir.types.isSubtypeOfClass +import org.jetbrains.kotlin.ir.symbols.UnsafeDuringIrConstructionAPI +import org.jetbrains.kotlin.ir.util.isSubtypeOfClass +import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name -class KermitChiselTransformer( - private val pluginContext: IrPluginContext, - stripBelow: String -) : IrElementTransformerVoidWithContext() { +@OptIn(UnsafeDuringIrConstructionAPI::class, DeprecatedForRemovalCompilerApi::class) +class KermitChiselTransformer(private val pluginContext: IrPluginContext, stripBelow: String) : IrElementTransformerVoidWithContext() { private val classLogger = - pluginContext.referenceClass(FqName("co.touchlab.kermit.Logger"))!! + pluginContext.referenceClass(ClassId(FqName("co.touchlab.kermit"), Name.identifier("Logger"))) private val stripFunctionSet = makeStripFunctionNameSet(stripBelow) override fun visitCall(expression: IrCall): IrExpression { - val recType = expression.dispatchReceiver?.type + val logger = classLogger ?: return super.visitCall(expression) + val recType = expression.dispatchReceiver?.type ?: expression.extensionReceiver?.type - if (recType != null && recType.isSubtypeOfClass(classLogger)) { + if (recType != null && recType.isSubtypeOfClass(logger)) { val functionName = expression.symbol.owner.name.identifier - val stripCall = stripFunctionSet.contains(functionName) - if (stripCall) { - return DeclarationIrBuilder(pluginContext, expression.symbol).irUnit() + if (stripFunctionSet.contains(functionName)) { + val scopeSymbol = currentScope?.scope?.scopeOwnerSymbol ?: expression.symbol + return DeclarationIrBuilder(pluginContext, scopeSymbol).irUnit() } } @@ -54,4 +55,4 @@ class KermitChiselTransformer( "All" -> setOf("v", "d", "i", "w", "e", "a") else -> emptySet() } -} \ No newline at end of file +} diff --git a/plugin/kermit-ir-plugin/src/main/kotlin/co/touchlab/kermit/irplugin/KermitCommandLineProcessor.kt b/plugin/kermit-ir-plugin/src/main/kotlin/co/touchlab/kermit/irplugin/KermitCommandLineProcessor.kt index 264ec10a..2b53cb20 100644 --- a/plugin/kermit-ir-plugin/src/main/kotlin/co/touchlab/kermit/irplugin/KermitCommandLineProcessor.kt +++ b/plugin/kermit-ir-plugin/src/main/kotlin/co/touchlab/kermit/irplugin/KermitCommandLineProcessor.kt @@ -11,14 +11,14 @@ package co.touchlab.kermit.irplugin import co.touchlab.BuildConfig -import com.google.auto.service.AutoService import org.jetbrains.kotlin.compiler.plugin.AbstractCliOption import org.jetbrains.kotlin.compiler.plugin.CliOption import org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor +import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.config.CompilerConfigurationKey -@AutoService(CommandLineProcessor::class) +@OptIn(ExperimentalCompilerApi::class) class KermitCommandLineProcessor : CommandLineProcessor { companion object { private const val OPTION_STRIP_BELOW = "stripBelow" @@ -36,14 +36,8 @@ class KermitCommandLineProcessor : CommandLineProcessor { ), ) - override fun processOption( - option: AbstractCliOption, - value: String, - configuration: CompilerConfiguration - ) { - return when (option.optionName) { - OPTION_STRIP_BELOW -> configuration.put(ARG_STRIP_BELOW, value) - else -> throw IllegalArgumentException("Unexpected config option ${option.optionName}") - } + override fun processOption(option: AbstractCliOption, value: String, configuration: CompilerConfiguration) = when (option.optionName) { + OPTION_STRIP_BELOW -> configuration.put(ARG_STRIP_BELOW, value) + else -> throw IllegalArgumentException("Unexpected config option ${option.optionName}") } } diff --git a/plugin/kermit-ir-plugin/src/main/kotlin/co/touchlab/kermit/irplugin/KermitComponentRegistrar.kt b/plugin/kermit-ir-plugin/src/main/kotlin/co/touchlab/kermit/irplugin/KermitCompilerPluginRegistrar.kt similarity index 60% rename from plugin/kermit-ir-plugin/src/main/kotlin/co/touchlab/kermit/irplugin/KermitComponentRegistrar.kt rename to plugin/kermit-ir-plugin/src/main/kotlin/co/touchlab/kermit/irplugin/KermitCompilerPluginRegistrar.kt index b8ec71b1..d958d982 100644 --- a/plugin/kermit-ir-plugin/src/main/kotlin/co/touchlab/kermit/irplugin/KermitComponentRegistrar.kt +++ b/plugin/kermit-ir-plugin/src/main/kotlin/co/touchlab/kermit/irplugin/KermitCompilerPluginRegistrar.kt @@ -10,36 +10,32 @@ package co.touchlab.kermit.irplugin -import com.google.auto.service.AutoService +import co.touchlab.BuildConfig import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension -import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys import org.jetbrains.kotlin.cli.common.messages.MessageCollector -import org.jetbrains.kotlin.com.intellij.mock.MockProject -import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar +import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar +import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi +import org.jetbrains.kotlin.config.CommonConfigurationKeys import org.jetbrains.kotlin.config.CompilerConfiguration -@AutoService(ComponentRegistrar::class) -class KermitComponentRegistrar( - private val defaultStripBelow: String -) : ComponentRegistrar { +@OptIn(ExperimentalCompilerApi::class) +class KermitCompilerPluginRegistrar(private val defaultStripBelow: String) : CompilerPluginRegistrar() { @Suppress("unused") // Used by service loader constructor() : this( - defaultStripBelow = "None" + defaultStripBelow = "None", ) - override fun registerProjectComponents( - project: MockProject, - configuration: CompilerConfiguration - ) { - val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE) + override val supportsK2: Boolean = true + + override val pluginId: String = BuildConfig.KOTLIN_PLUGIN_ID + + override fun ExtensionStorage.registerExtensions(configuration: CompilerConfiguration) { + val messageCollector = configuration.get(CommonConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE) val stripBelow = configuration.get(KermitCommandLineProcessor.ARG_STRIP_BELOW, defaultStripBelow) IrGenerationExtension.registerExtension( - project, - KermitIrGenerationExtension(messageCollector, stripBelow) + KermitIrGenerationExtension(messageCollector, stripBelow), ) } } - - diff --git a/plugin/kermit-ir-plugin/src/main/kotlin/co/touchlab/kermit/irplugin/KermitIrGenerationExtension.kt b/plugin/kermit-ir-plugin/src/main/kotlin/co/touchlab/kermit/irplugin/KermitIrGenerationExtension.kt index ba4cbe62..bd1daeb1 100644 --- a/plugin/kermit-ir-plugin/src/main/kotlin/co/touchlab/kermit/irplugin/KermitIrGenerationExtension.kt +++ b/plugin/kermit-ir-plugin/src/main/kotlin/co/touchlab/kermit/irplugin/KermitIrGenerationExtension.kt @@ -15,27 +15,10 @@ import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext import org.jetbrains.kotlin.cli.common.messages.MessageCollector import org.jetbrains.kotlin.ir.declarations.IrModuleFragment -class KermitIrGenerationExtension( - private val messageCollector: MessageCollector, - private val stripBelow: String -) : IrGenerationExtension { +class KermitIrGenerationExtension(private val messageCollector: MessageCollector, private val stripBelow: String) : IrGenerationExtension { override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) { -// messageCollector.report(CompilerMessageSeverity.INFO, "Argument 'string' = $string") -// messageCollector.report(CompilerMessageSeverity.INFO, "Argument 'file' = $file") -// println(moduleFragment.dump()) -// moduleFragment.acceptChildrenVoid(PlayVisitor(pluginContext)) - dumpModule(moduleFragment) - if (stripBelow != "None" && stripBelow != "Verbose") { moduleFragment.transform(KermitChiselTransformer(pluginContext, stripBelow), null) } - - dumpModule(moduleFragment) - } - - private fun dumpModule(moduleFragment: IrModuleFragment) { -// println("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$") -// println(moduleFragment.dump()) -// println("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$") } } diff --git a/plugin/kermit-ir-plugin/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor b/plugin/kermit-ir-plugin/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor new file mode 100644 index 00000000..dd564c8f --- /dev/null +++ b/plugin/kermit-ir-plugin/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor @@ -0,0 +1 @@ +co.touchlab.kermit.irplugin.KermitCommandLineProcessor diff --git a/plugin/kermit-ir-plugin/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar b/plugin/kermit-ir-plugin/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar new file mode 100644 index 00000000..67137ef5 --- /dev/null +++ b/plugin/kermit-ir-plugin/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar @@ -0,0 +1 @@ +co.touchlab.kermit.irplugin.KermitCompilerPluginRegistrar diff --git a/plugin/kermit-ir-plugin/src/test/kotlin/co/touchlab/kermit/irplugin/Helper.kt b/plugin/kermit-ir-plugin/src/test/kotlin/co/touchlab/kermit/irplugin/Helper.kt index a6d4a29e..69acf937 100644 --- a/plugin/kermit-ir-plugin/src/test/kotlin/co/touchlab/kermit/irplugin/Helper.kt +++ b/plugin/kermit-ir-plugin/src/test/kotlin/co/touchlab/kermit/irplugin/Helper.kt @@ -16,70 +16,32 @@ import org.intellij.lang.annotations.Language val LoggerString = """ package co.touchlab.kermit -open class Logger( -) { - /* - * We could reduce the methods on this object, but the native objc - * export can't use default arguments, so we have a few extra methods defined. - */ - inline fun v(message: () -> String) { - } +open class Logger { + inline fun v(message: () -> String) { message() } + inline fun v(throwable: Throwable, message: () -> String) { message() } + fun v(message: String, throwable: Throwable? = null) {} - inline fun v(throwable: Throwable, message: () -> String) { - } + inline fun d(message: () -> String) { message() } + inline fun d(throwable: Throwable, message: () -> String) { message() } + fun d(message: String, throwable: Throwable? = null) {} - fun v(message: String, throwable: Throwable? = null) { - } + inline fun i(message: () -> String) { message() } + inline fun i(throwable: Throwable, message: () -> String) { message() } + fun i(message: String, throwable: Throwable? = null) {} - inline fun d(message: () -> String) { - } + inline fun w(message: () -> String) { message() } + inline fun w(throwable: Throwable, message: () -> String) { message() } + fun w(message: String, throwable: Throwable? = null) {} - inline fun d(throwable: Throwable, message: () -> String) { - } + inline fun e(message: () -> String) { message() } + inline fun e(throwable: Throwable, message: () -> String) { message() } + fun e(message: String, throwable: Throwable? = null) {} - fun d(message: String, throwable: Throwable? = null) { - } - - inline fun i(message: () -> String) { - } - - inline fun i(throwable: Throwable, message: () -> String) { - } - - fun i(message: String, throwable: Throwable? = null) { - } - - inline fun w(message: () -> String) { - } - - inline fun w(throwable: Throwable, message: () -> String) { - } - - fun w(message: String, throwable: Throwable? = null) { - } - - inline fun e(message: () -> String) { - } - - inline fun e(throwable: Throwable, message: () -> String) { - } - - fun e(message: String, throwable: Throwable? = null) { - } - - inline fun a(message: () -> String) { - } - - inline fun a(throwable: Throwable, message: () -> String) { - } - - fun a(message: String, throwable: Throwable? = null) { - } - - inline fun noop(){} + inline fun a(message: () -> String) { message() } + inline fun a(throwable: Throwable, message: () -> String) { message() } + fun a(message: String, throwable: Throwable? = null) {} @Suppress("unused") - companion object : Logger() { - } + companion object : Logger() } """.trimIndent() diff --git a/plugin/kermit-ir-plugin/src/test/kotlin/co/touchlab/kermit/irplugin/KermitStripperIrPluginTest.kt b/plugin/kermit-ir-plugin/src/test/kotlin/co/touchlab/kermit/irplugin/KermitStripperIrPluginTest.kt index 82f47b65..7142e4f1 100644 --- a/plugin/kermit-ir-plugin/src/test/kotlin/co/touchlab/kermit/irplugin/KermitStripperIrPluginTest.kt +++ b/plugin/kermit-ir-plugin/src/test/kotlin/co/touchlab/kermit/irplugin/KermitStripperIrPluginTest.kt @@ -10,59 +10,160 @@ package co.touchlab.kermit.irplugin -import co.touchlab.kermit.irplugin.KermitComponentRegistrar +import com.tschuchort.compiletesting.JvmCompilationResult import com.tschuchort.compiletesting.KotlinCompilation import com.tschuchort.compiletesting.SourceFile import kotlin.test.assertEquals -import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar +import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi import org.junit.Test -import kotlin.test.Ignore +@OptIn(ExperimentalCompilerApi::class) class IrPluginTest { @Test - @Ignore - fun `IR plugin success`() { - val sourceFiles = listOf( - SourceFile.kotlin( - "Logger.kt", LoggerString - ), SourceFile.kotlin( - "main.kt", """ -import co.touchlab.kermit.Logger -//class HeyoLogs:Logger() -fun main() { - println("abc") - Logger.v { "arst" } - println("def") -// val l = Logger() -// l.i { "ttt" } -// val heyo = HeyoLogs() -// heyo.w { "www" } -} - """.trimIndent() - ) + fun `compilation succeeds with no stripping`() { + val result = compile( + sourceFiles = listOf( + SourceFile.kotlin("Logger.kt", LoggerString), + SourceFile.kotlin( + "main.kt", + """ + import co.touchlab.kermit.Logger + fun main() { + Logger.v { "verbose" } + Logger.d { "debug" } + Logger.i { "info" } + Logger.w { "warn" } + Logger.e { "error" } + Logger.a { "assert" } + } + """.trimIndent(), + ), + ), + stripBelow = "None", ) + assertEquals(KotlinCompilation.ExitCode.OK, result.exitCode) + } + + @Test + fun `compilation succeeds with strip below Error`() { val result = compile( - sourceFiles = sourceFiles + sourceFiles = listOf( + SourceFile.kotlin("Logger.kt", LoggerString), + SourceFile.kotlin( + "main.kt", + """ + import co.touchlab.kermit.Logger + fun main() { + Logger.v { "verbose" } + Logger.d { "debug" } + Logger.i { "info" } + Logger.w { "warn" } + Logger.e { "error" } + Logger.a { "assert" } + } + """.trimIndent(), + ), + ), + stripBelow = "Error", ) assertEquals(KotlinCompilation.ExitCode.OK, result.exitCode) } -} -fun compile( - sourceFiles: List, - plugin: ComponentRegistrar = KermitComponentRegistrar(), -): KotlinCompilation.Result { - return KotlinCompilation().apply { - sources = sourceFiles - useIR = true - compilerPlugins = listOf(plugin) - inheritClassPath = true - }.compile() -} + @Test + fun `compilation succeeds with strip below All`() { + val result = compile( + sourceFiles = listOf( + SourceFile.kotlin("Logger.kt", LoggerString), + SourceFile.kotlin( + "main.kt", + """ + import co.touchlab.kermit.Logger + fun main() { + Logger.v { "verbose" } + Logger.d { "debug" } + Logger.i { "info" } + Logger.w { "warn" } + Logger.e { "error" } + Logger.a { "assert" } + } + """.trimIndent(), + ), + ), + stripBelow = "All", + ) + assertEquals(KotlinCompilation.ExitCode.OK, result.exitCode) + } -fun compile( - sourceFile: SourceFile, - plugin: ComponentRegistrar = KermitComponentRegistrar(), -): KotlinCompilation.Result { - return compile(listOf(sourceFile), plugin) + @Test + fun `compilation succeeds with instance logger`() { + val result = compile( + sourceFiles = listOf( + SourceFile.kotlin("Logger.kt", LoggerString), + SourceFile.kotlin( + "main.kt", + """ + import co.touchlab.kermit.Logger + fun main() { + val logger = Logger() + logger.v { "verbose" } + logger.d { "debug" } + logger.i { "info" } + } + """.trimIndent(), + ), + ), + stripBelow = "Warn", + ) + assertEquals(KotlinCompilation.ExitCode.OK, result.exitCode) + } + + @Test + fun `verify log calls below threshold are stripped at runtime`() { + val result = compile( + sourceFiles = listOf( + SourceFile.kotlin("Logger.kt", LoggerString), + SourceFile.kotlin( + "Main.kt", + """ + import co.touchlab.kermit.Logger + object Tracker { + var verboseCount = 0 + var debugCount = 0 + var infoCount = 0 + var errorCount = 0 + } + fun runLogs() { + Logger.v { Tracker.verboseCount++; "v" } + Logger.d { Tracker.debugCount++; "d" } + Logger.i { Tracker.infoCount++; "i" } + Logger.e { Tracker.errorCount++; "e" } + } + """.trimIndent(), + ), + ), + stripBelow = "Info", + ) + assertEquals(KotlinCompilation.ExitCode.OK, result.exitCode) + val kClass = result.classLoader.loadClass("MainKt") + kClass.getMethod("runLogs").invoke(null) + + val trackerClass = result.classLoader.loadClass("Tracker") + val instance = trackerClass.getField("INSTANCE").get(null) + val verboseCount = trackerClass.getMethod("getVerboseCount").invoke(instance) + val debugCount = trackerClass.getMethod("getDebugCount").invoke(instance) + val infoCount = trackerClass.getMethod("getInfoCount").invoke(instance) + val errorCount = trackerClass.getMethod("getErrorCount").invoke(instance) + + assertEquals(0, verboseCount) + assertEquals(0, debugCount) + assertEquals(1, infoCount) + assertEquals(1, errorCount) + } } + +@OptIn(ExperimentalCompilerApi::class) +fun compile(sourceFiles: List, stripBelow: String = "None"): JvmCompilationResult = KotlinCompilation().apply { + sources = sourceFiles + compilerPluginRegistrars = listOf(KermitCompilerPluginRegistrar(stripBelow)) + inheritClassPath = true +}.compile() diff --git a/settings.gradle.kts b/settings.gradle.kts index 67385de9..6f54b996 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -28,13 +28,11 @@ project(":kermit-koin").projectDir = File("extensions/kermit-koin") project(":kermit-ktor").projectDir = File("extensions/kermit-ktor") project(":kermit-coil").projectDir = File("extensions/kermit-coil") -// include(":kermit-gradle-plugin") -// include(":kermit-ir-plugin") -// include(":kermit-ir-plugin-native") -// -// project(":kermit-gradle-plugin").projectDir = File("plugin/kermit-gradle-plugin") -// project(":kermit-ir-plugin").projectDir = File("plugin/kermit-ir-plugin") -// project(":kermit-ir-plugin-native").projectDir = File("plugin/kermit-ir-plugin-native") +include(":kermit-gradle-plugin") +include(":kermit-ir-plugin") + +project(":kermit-gradle-plugin").projectDir = File("plugin/kermit-gradle-plugin") +project(":kermit-ir-plugin").projectDir = File("plugin/kermit-ir-plugin") pluginManagement { includeBuild("convention-plugins") From 5c77f552d217f10187db0abace8507b1642d6a8f Mon Sep 17 00:00:00 2001 From: kareemessam09 Date: Tue, 8 Sep 2026 19:32:55 +0300 Subject: [PATCH 2/4] test: add unit and runtime tests for withTag, subclasses, and CommandLineProcessor --- .../kermit/gradle/KermitGradlePluginTest.kt | 40 ++++++ .../co/touchlab/kermit/irplugin/Helper.kt | 2 + .../KermitCommandLineProcessorTest.kt | 54 ++++++++ .../irplugin/KermitStripperIrPluginTest.kt | 118 ++++++++++++++++++ 4 files changed, 214 insertions(+) create mode 100644 plugin/kermit-ir-plugin/src/test/kotlin/co/touchlab/kermit/irplugin/KermitCommandLineProcessorTest.kt diff --git a/plugin/kermit-gradle-plugin/src/test/kotlin/co/touchlab/kermit/gradle/KermitGradlePluginTest.kt b/plugin/kermit-gradle-plugin/src/test/kotlin/co/touchlab/kermit/gradle/KermitGradlePluginTest.kt index 56459d77..0f848e3c 100644 --- a/plugin/kermit-gradle-plugin/src/test/kotlin/co/touchlab/kermit/gradle/KermitGradlePluginTest.kt +++ b/plugin/kermit-gradle-plugin/src/test/kotlin/co/touchlab/kermit/gradle/KermitGradlePluginTest.kt @@ -45,4 +45,44 @@ class KermitGradlePluginTest { assertEquals(BuildConfig.KOTLIN_PLUGIN_NAME, artifact.artifactId) assertEquals(BuildConfig.KOTLIN_PLUGIN_VERSION, artifact.version) } + + @Test + fun `isApplicable returns true for any compilation`() { + val compilation = java.lang.reflect.Proxy.newProxyInstance( + org.jetbrains.kotlin.gradle.plugin.KotlinCompilation::class.java.classLoader, + arrayOf(org.jetbrains.kotlin.gradle.plugin.KotlinCompilation::class.java), + ) { _, _, _ -> null } as org.jetbrains.kotlin.gradle.plugin.KotlinCompilation<*> + + val plugin = KermitGradlePlugin() + kotlin.test.assertTrue(plugin.isApplicable(compilation)) + } + + @Test + fun `applyToCompilation returns provider of SubpluginOption matching extension configuration`() { + val project = ProjectBuilder.builder().build() + project.plugins.apply("co.touchlab.kermit") + val extension = project.extensions.getByType(KermitGradleExtension::class.java) + extension.stripBelow = StripSeverity.Warn + + val target = java.lang.reflect.Proxy.newProxyInstance( + org.jetbrains.kotlin.gradle.plugin.KotlinTarget::class.java.classLoader, + arrayOf(org.jetbrains.kotlin.gradle.plugin.KotlinTarget::class.java), + ) { _, method, _ -> + if (method.name == "getProject") project else null + } as org.jetbrains.kotlin.gradle.plugin.KotlinTarget + + val compilation = java.lang.reflect.Proxy.newProxyInstance( + org.jetbrains.kotlin.gradle.plugin.KotlinCompilation::class.java.classLoader, + arrayOf(org.jetbrains.kotlin.gradle.plugin.KotlinCompilation::class.java), + ) { _, method, _ -> + if (method.name == "getTarget") target else null + } as org.jetbrains.kotlin.gradle.plugin.KotlinCompilation<*> + + val plugin = KermitGradlePlugin() + val optionsProvider = plugin.applyToCompilation(compilation) + val options = optionsProvider.get() + assertEquals(1, options.size) + assertEquals("stripBelow", options[0].key) + assertEquals("Warn", options[0].value) + } } diff --git a/plugin/kermit-ir-plugin/src/test/kotlin/co/touchlab/kermit/irplugin/Helper.kt b/plugin/kermit-ir-plugin/src/test/kotlin/co/touchlab/kermit/irplugin/Helper.kt index 69acf937..0bf92f2c 100644 --- a/plugin/kermit-ir-plugin/src/test/kotlin/co/touchlab/kermit/irplugin/Helper.kt +++ b/plugin/kermit-ir-plugin/src/test/kotlin/co/touchlab/kermit/irplugin/Helper.kt @@ -41,6 +41,8 @@ open class Logger { inline fun a(throwable: Throwable, message: () -> String) { message() } fun a(message: String, throwable: Throwable? = null) {} + fun withTag(tag: String): Logger = this + @Suppress("unused") companion object : Logger() } diff --git a/plugin/kermit-ir-plugin/src/test/kotlin/co/touchlab/kermit/irplugin/KermitCommandLineProcessorTest.kt b/plugin/kermit-ir-plugin/src/test/kotlin/co/touchlab/kermit/irplugin/KermitCommandLineProcessorTest.kt new file mode 100644 index 00000000..df74b250 --- /dev/null +++ b/plugin/kermit-ir-plugin/src/test/kotlin/co/touchlab/kermit/irplugin/KermitCommandLineProcessorTest.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2021 Touchlab + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + */ + +package co.touchlab.kermit.irplugin + +import co.touchlab.BuildConfig +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertNotNull +import org.jetbrains.kotlin.compiler.plugin.CliOption +import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi +import org.jetbrains.kotlin.config.CompilerConfiguration + +@OptIn(ExperimentalCompilerApi::class) +class KermitCommandLineProcessorTest { + private val processor = KermitCommandLineProcessor() + + @Test + fun `pluginId matches BuildConfig`() { + assertEquals(BuildConfig.KOTLIN_PLUGIN_ID, processor.pluginId) + } + + @Test + fun `plugin exposes stripBelow option`() { + val option = processor.pluginOptions.find { it.optionName == "stripBelow" } + assertNotNull(option) + assertEquals("stripBelow", option.optionName) + } + + @Test + fun `processOption sets ARG_STRIP_BELOW in configuration`() { + val configuration = CompilerConfiguration() + val option = processor.pluginOptions.first { it.optionName == "stripBelow" } + processor.processOption(option, "Warn", configuration) + assertEquals("Warn", configuration.get(KermitCommandLineProcessor.ARG_STRIP_BELOW)) + } + + @Test + fun `processOption throws on unexpected option`() { + val configuration = CompilerConfiguration() + val invalidOption = CliOption("unknown", "", "desc") + assertFailsWith { + processor.processOption(invalidOption, "value", configuration) + } + } +} diff --git a/plugin/kermit-ir-plugin/src/test/kotlin/co/touchlab/kermit/irplugin/KermitStripperIrPluginTest.kt b/plugin/kermit-ir-plugin/src/test/kotlin/co/touchlab/kermit/irplugin/KermitStripperIrPluginTest.kt index 7142e4f1..efddc90b 100644 --- a/plugin/kermit-ir-plugin/src/test/kotlin/co/touchlab/kermit/irplugin/KermitStripperIrPluginTest.kt +++ b/plugin/kermit-ir-plugin/src/test/kotlin/co/touchlab/kermit/irplugin/KermitStripperIrPluginTest.kt @@ -159,6 +159,124 @@ class IrPluginTest { assertEquals(1, infoCount) assertEquals(1, errorCount) } + + @Test + fun `verify log calls with withTag are stripped at runtime`() { + val result = compile( + sourceFiles = listOf( + SourceFile.kotlin("Logger.kt", LoggerString), + SourceFile.kotlin( + "Main.kt", + """ + import co.touchlab.kermit.Logger + object TagTracker { + var verboseCount = 0 + var debugCount = 0 + var infoCount = 0 + } + fun runLogs() { + val tagged = Logger.withTag("MyTag") + tagged.v { TagTracker.verboseCount++; "v" } + tagged.d { TagTracker.debugCount++; "d" } + tagged.i { TagTracker.infoCount++; "i" } + Logger.withTag("Chained").d { TagTracker.debugCount++; "chained-d" } + } + """.trimIndent(), + ), + ), + stripBelow = "Info", + ) + assertEquals(KotlinCompilation.ExitCode.OK, result.exitCode) + val kClass = result.classLoader.loadClass("MainKt") + kClass.getMethod("runLogs").invoke(null) + + val trackerClass = result.classLoader.loadClass("TagTracker") + val instance = trackerClass.getField("INSTANCE").get(null) + val verboseCount = trackerClass.getMethod("getVerboseCount").invoke(instance) + val debugCount = trackerClass.getMethod("getDebugCount").invoke(instance) + val infoCount = trackerClass.getMethod("getInfoCount").invoke(instance) + + assertEquals(0, verboseCount) + assertEquals(0, debugCount) + assertEquals(1, infoCount) + } + + @Test + fun `verify log calls on Logger subclass are stripped at runtime`() { + val result = compile( + sourceFiles = listOf( + SourceFile.kotlin("Logger.kt", LoggerString), + SourceFile.kotlin( + "Main.kt", + """ + import co.touchlab.kermit.Logger + class CustomLogger : Logger() + object SubclassTracker { + var verboseCount = 0 + var debugCount = 0 + var infoCount = 0 + } + fun runLogs() { + val custom = CustomLogger() + custom.v { SubclassTracker.verboseCount++; "v" } + custom.d { SubclassTracker.debugCount++; "d" } + custom.i { SubclassTracker.infoCount++; "i" } + } + """.trimIndent(), + ), + ), + stripBelow = "Info", + ) + assertEquals(KotlinCompilation.ExitCode.OK, result.exitCode) + val kClass = result.classLoader.loadClass("MainKt") + kClass.getMethod("runLogs").invoke(null) + + val trackerClass = result.classLoader.loadClass("SubclassTracker") + val instance = trackerClass.getField("INSTANCE").get(null) + val verboseCount = trackerClass.getMethod("getVerboseCount").invoke(instance) + val debugCount = trackerClass.getMethod("getDebugCount").invoke(instance) + val infoCount = trackerClass.getMethod("getInfoCount").invoke(instance) + + assertEquals(0, verboseCount) + assertEquals(0, debugCount) + assertEquals(1, infoCount) + } + + @Test + fun `verify log calls with throwable are stripped at runtime`() { + val result = compile( + sourceFiles = listOf( + SourceFile.kotlin("Logger.kt", LoggerString), + SourceFile.kotlin( + "Main.kt", + """ + import co.touchlab.kermit.Logger + object ThrowableTracker { + var debugCount = 0 + var errorCount = 0 + } + fun runLogs() { + val ex = RuntimeException("test") + Logger.d(ex) { ThrowableTracker.debugCount++; "d with throwable" } + Logger.e(ex) { ThrowableTracker.errorCount++; "e with throwable" } + } + """.trimIndent(), + ), + ), + stripBelow = "Info", + ) + assertEquals(KotlinCompilation.ExitCode.OK, result.exitCode) + val kClass = result.classLoader.loadClass("MainKt") + kClass.getMethod("runLogs").invoke(null) + + val trackerClass = result.classLoader.loadClass("ThrowableTracker") + val instance = trackerClass.getField("INSTANCE").get(null) + val debugCount = trackerClass.getMethod("getDebugCount").invoke(instance) + val errorCount = trackerClass.getMethod("getErrorCount").invoke(instance) + + assertEquals(0, debugCount) + assertEquals(1, errorCount) + } } @OptIn(ExperimentalCompilerApi::class) From 5d1fbe4a32dcc9c680ce50a9752acd6701ae712c Mon Sep 17 00:00:00 2001 From: kareemessam09 Date: Fri, 11 Sep 2026 03:13:49 +0300 Subject: [PATCH 3/4] sample: add plugin usage to sample-production and use version catalog for dependencies --- plugin/kermit-ir-plugin/build.gradle.kts | 6 +-- samples/sample-production/README.md | 42 ++++++++++++++----- samples/sample-production/build.gradle.kts | 1 + .../sample-production/shared/build.gradle.kts | 7 ++++ 4 files changed, 42 insertions(+), 14 deletions(-) diff --git a/plugin/kermit-ir-plugin/build.gradle.kts b/plugin/kermit-ir-plugin/build.gradle.kts index 60bda380..4a59a314 100644 --- a/plugin/kermit-ir-plugin/build.gradle.kts +++ b/plugin/kermit-ir-plugin/build.gradle.kts @@ -7,11 +7,11 @@ plugins { } dependencies { - compileOnly("org.jetbrains.kotlin:kotlin-compiler-embeddable") + compileOnly(libs.kotlin.compiler.embeddable) testImplementation(kotlin("test-junit")) - testImplementation("org.jetbrains.kotlin:kotlin-compiler-embeddable") - testImplementation("dev.zacsweers.kctfork:core:0.12.1") + testImplementation(libs.kotlin.compiler.embeddable) + testImplementation(libs.kctfork) } buildConfig { diff --git a/samples/sample-production/README.md b/samples/sample-production/README.md index cca439fb..e9eef799 100644 --- a/samples/sample-production/README.md +++ b/samples/sample-production/README.md @@ -1,12 +1,32 @@ -# Kermit Basic Sample -This sample demonstrates the most basic usage of Kermit in a multiplatform project targeting Android, -iOS, and Browser. In this basic configuration we simply add the`api("co.touchlab:kermit:${KERMIT_VERSION}")` -dependency in the `commonMain` source set in `build.gradle` for the shared module. This makes the global -`Logger` instance available in the shared module and the kotlin modules that depend on it (`app` and `app-browser`). -This will use `Logcat` for android, console for the browser, and `println` for iOS (see [here](../../docs/IOS_CONSIDERATIONS.md) for why) - -NOTE: Kermit classes and functions can not be used from swift in this sample. To make kermit available in -Swift you have to export the dependency explicitly. Exporting dependencies should be considered cautiously -because it increases the header code that needs to be generated in and increases the binary size of the -Kotlin framework. For an example of how to export kermit and use it from Swift, see [this sample](../sample-swift-export) +# Kermit Production Sample + +This sample demonstrates a production-oriented multiplatform configuration for Kermit targeting Android and iOS, featuring: +1. **Crash Reporting Integration:** Writing breadcrumbs and crash reports using `kermit-bugsnag`. +2. **Kermit Compiler Plugin (Log Stripping):** Using the `co.touchlab.kermit` Gradle plugin to strip out log calls and lambdas below a configured severity level at compile time. + +## Kermit Compiler Plugin Configuration + +In the root `build.gradle.kts`: +```kotlin +plugins { + id("co.touchlab.kermit") version extra["KERMIT_VERSION"] as String apply false +} +``` + +In the multiplatform `shared/build.gradle.kts`: +```kotlin +import co.touchlab.kermit.gradle.StripSeverity + +plugins { + id("co.touchlab.kermit") +} + +kermit { + // Strips log calls below Info (i.e. Verbose and Debug) at compile time across all targets + stripBelow = StripSeverity.Info +} +``` + +Any log call below `stripBelow` (e.g. `logger.v { ... }` or `logger.d { ... }`) is removed from the compiled bytecode / binary, preventing string allocations and log overhead in production. Available severity levels: `None`, `Verbose`, `Debug`, `Info`, `Warn`, `Error`, `Assert`, `All`. + diff --git a/samples/sample-production/build.gradle.kts b/samples/sample-production/build.gradle.kts index c9140d45..18287e22 100644 --- a/samples/sample-production/build.gradle.kts +++ b/samples/sample-production/build.gradle.kts @@ -19,6 +19,7 @@ plugins { alias(libs.plugins.android.library) apply false alias(libs.plugins.kotlin.multiplatform) apply false alias(libs.plugins.bugsnag.gradle.plugin) apply false + id("co.touchlab.kermit") version (extra["KERMIT_VERSION"] as String) apply false } allprojects { diff --git a/samples/sample-production/shared/build.gradle.kts b/samples/sample-production/shared/build.gradle.kts index a1ac61c5..473cda9e 100644 --- a/samples/sample-production/shared/build.gradle.kts +++ b/samples/sample-production/shared/build.gradle.kts @@ -8,12 +8,14 @@ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ +import co.touchlab.kermit.gradle.StripSeverity import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { alias(libs.plugins.android.library) kotlin("multiplatform") kotlin("native.cocoapods") + id("co.touchlab.kermit") } val KERMIT_VERSION: String by project @@ -57,3 +59,8 @@ kotlin { } } } + +kermit { + stripBelow = StripSeverity.Info +} + From 61c87436ee7281ea3e71f7a2c373fe6f44614433 Mon Sep 17 00:00:00 2001 From: kareemessam09 Date: Sat, 12 Sep 2026 20:34:20 +0300 Subject: [PATCH 4/4] fix: normalize path separators in ktlint exclude filter for Windows compatibility --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 2c22b181..f455b330 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -62,7 +62,7 @@ subprojects { enableExperimentalRules.set(true) verbose.set(true) filter { - exclude { it.file.path.contains("build/") } + exclude { it.file.invariantSeparatorsPath.contains("build/") } } }