From 8230c43e5b5d0dbb466fe89cdea8327b36a0fdf2 Mon Sep 17 00:00:00 2001 From: EvgeniyKurinnoy Date: Wed, 3 May 2023 14:51:29 +0300 Subject: [PATCH] - added experimental support of [Rebugger](https://github.com/theapache64/rebugger) --- .editorconfig | 9 ++ CHANGELOG.md | 4 + README.md | 3 +- common.properties | 2 +- .../buildSrc/src/main/kotlin/PluginConfig.kt | 2 + .../kotlin/plugin-options-config.gradle.kts | 3 + .../PluginCommandLineProcessor.kt | 85 +++++++------- .../PluginComponentRegistrar.kt | 26 +++-- .../debug_logger/EmptyLogger.kt | 4 +- .../debug_logger/FileLogger.kt | 6 +- .../compiler_plugin/generations/Extensions.kt | 10 +- .../HighlightGenerationExtension.kt | 17 +-- .../generations/LogsGenerationExtension.kt | 104 +++++++----------- .../generations/logging/DefaultLogProvider.kt | 51 +++++++++ .../generations/logging/LogProvider.kt | 11 ++ .../logging/RebuggerLogProvider.kt | 75 +++++++++++++ .../gradle-plugin/build.gradle.kts | 1 + .../RecompositionLoggerGradlePlugin.kt | 54 +++++---- .../RecompositionLoggerGradleExtension.kt | 16 +-- .../highlight/HighlightOptions.kt | 2 +- .../highlight/HighlightComposition.kt | 9 +- sample/build.gradle | 3 +- .../MainActivity.kt | 22 ++-- 23 files changed, 339 insertions(+), 180 deletions(-) create mode 100644 .editorconfig create mode 100644 recomposition-logger-plugin/compiler-plugin/src/main/kotlin/com/welltech/compiler_plugin/generations/logging/DefaultLogProvider.kt create mode 100644 recomposition-logger-plugin/compiler-plugin/src/main/kotlin/com/welltech/compiler_plugin/generations/logging/LogProvider.kt create mode 100644 recomposition-logger-plugin/compiler-plugin/src/main/kotlin/com/welltech/compiler_plugin/generations/logging/RebuggerLogProvider.kt diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a3ab037 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root=true + +[*.{kt,kts}] +insert_final_newline = true +max_line_length = 140 +indent_size = 4 +indent_style = space +ij_kotlin_allow_trailing_comma = true +ij_kotlin_allow_trailing_comma_on_call_site = true \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 1158523..65a035d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.6.0-alpha01 + +- added experimental support of [Rebugger](https://github.com/theapache64/rebugger) + # 1.5.0 - updated kotlin to 1.8.20 and compose compiler to 1.4.6 diff --git a/README.md b/README.md index f3abd86..0279eaf 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ The plugin also highlights composable functions during recomposition ## requirements: -- v1.5.0: kotlin 1.8.20 +- v1.5.0+: kotlin 1.8.20 - v1.4.0: kotlin 1.8.10 - v1.3.0: kotlin 1.8.0 - v1.2.0: kotlin 1.7.20 @@ -48,6 +48,7 @@ Other plugin options: May be useful in multi-module project (for example: in app module use "implementation", in other modules "compileOnly") - `enabled [Boolean]` - when false, plugin don't add any additional code for debug functionality. By default it false for release and true for debug - `tag [String, default: "RecompositionLog"]` - tag for recomposition logs. +- `useRebugger [Boolean]` - use [Rebugger](https://github.com/theapache64/rebugger) for logging (experimental) # Features diff --git a/common.properties b/common.properties index 392f650..cf15c89 100644 --- a/common.properties +++ b/common.properties @@ -1,5 +1,5 @@ kotlin_version=1.8.20 -version=1.5.0 +version=1.6.0-alpha01 group_id=com.welltech runtime_artifact=recomposition-logger-runtime annotations_artifact=recomposition-logger-annotations diff --git a/recomposition-logger-plugin/buildSrc/src/main/kotlin/PluginConfig.kt b/recomposition-logger-plugin/buildSrc/src/main/kotlin/PluginConfig.kt index 8084aae..7dca3ed 100644 --- a/recomposition-logger-plugin/buildSrc/src/main/kotlin/PluginConfig.kt +++ b/recomposition-logger-plugin/buildSrc/src/main/kotlin/PluginConfig.kt @@ -22,4 +22,6 @@ class PluginConfig internal constructor(private val project: Project) { val runtimeLib = "$group:$runtimeLibArtifact:$version" val annotationsLib = "$group:$annotationsLibArtifact:$version" + + val rebuggerLib = "com.github.theapache64:rebugger:1.0.0-alpha02" } \ No newline at end of file diff --git a/recomposition-logger-plugin/buildSrc/src/main/kotlin/plugin-options-config.gradle.kts b/recomposition-logger-plugin/buildSrc/src/main/kotlin/plugin-options-config.gradle.kts index 6877b3d..43035de 100644 --- a/recomposition-logger-plugin/buildSrc/src/main/kotlin/plugin-options-config.gradle.kts +++ b/recomposition-logger-plugin/buildSrc/src/main/kotlin/plugin-options-config.gradle.kts @@ -9,5 +9,8 @@ buildConfig { buildConfigField("String", "KEY_RECOMPOSITION_LOGS_TAG", "\"recomposition_log_tag\"") buildConfigField("String", "DEFAULT_RECOMPOSITION_LOGS_TAG", "\"RecompositionLog\"") + buildConfigField("String", "KEY_USE_REBUGGER", "\"use_rebugger\"") + buildConfigField("boolean", "DEFAULT_USE_REBUGGER", "false") + buildConfigField("String", "KEY_LOG_FILE", "\"log_file\"") } \ No newline at end of file diff --git a/recomposition-logger-plugin/compiler-plugin/src/main/kotlin/com/welltech/compiler_plugin/PluginCommandLineProcessor.kt b/recomposition-logger-plugin/compiler-plugin/src/main/kotlin/com/welltech/compiler_plugin/PluginCommandLineProcessor.kt index 90878e9..58722eb 100644 --- a/recomposition-logger-plugin/compiler-plugin/src/main/kotlin/com/welltech/compiler_plugin/PluginCommandLineProcessor.kt +++ b/recomposition-logger-plugin/compiler-plugin/src/main/kotlin/com/welltech/compiler_plugin/PluginCommandLineProcessor.kt @@ -10,52 +10,61 @@ import org.jetbrains.kotlin.config.CompilerConfigurationKey import com.welltech.compiler_plugin.BuildConfig.KEY_RECOMPOSITION_LOGS_TAG import com.welltech.compiler_plugin.BuildConfig.KEY_LOG_FILE import com.welltech.compiler_plugin.BuildConfig.KOTLIN_PLUGIN_ID +import com.welltech.compiler_plugin.BuildConfig.KEY_USE_REBUGGER import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi import java.io.File @OptIn(ExperimentalCompilerApi::class) @AutoService(CommandLineProcessor::class) class PluginCommandLineProcessor : CommandLineProcessor { - object Keys { - val logFile = KEY_LOG_FILE.toConfigKey() - val logEnabled = KEY_RECOMPOSITION_LOGS_ENABLED.toConfigKey() - val logTag = KEY_RECOMPOSITION_LOGS_TAG.toConfigKey() - private inline fun String.toConfigKey() = CompilerConfigurationKey(this) - } + object Keys { + val logFile = KEY_LOG_FILE.toConfigKey() + val logEnabled = KEY_RECOMPOSITION_LOGS_ENABLED.toConfigKey() + val logTag = KEY_RECOMPOSITION_LOGS_TAG.toConfigKey() + val useRebugger = KEY_USE_REBUGGER.toConfigKey() + private inline fun String.toConfigKey() = CompilerConfigurationKey(this) + } - override val pluginId: String = KOTLIN_PLUGIN_ID + override val pluginId: String = KOTLIN_PLUGIN_ID - override val pluginOptions: Collection = listOf( - CliOption( - optionName = KEY_LOG_FILE, - valueDescription = "absolute file path", - description = "file for printing compiler debug logs", - required = false, - ), - CliOption( - optionName = KEY_RECOMPOSITION_LOGS_ENABLED, - valueDescription = "bool ", - description = "Add logging for @Composable functions", - required = false, - ), - CliOption( - optionName = KEY_RECOMPOSITION_LOGS_TAG, - valueDescription = "String", - description = "tag for android.util.Log", - required = false, - ), - ) + override val pluginOptions: Collection = listOf( + CliOption( + optionName = KEY_LOG_FILE, + valueDescription = "absolute file path", + description = "file for printing compiler debug logs", + required = false, + ), + CliOption( + optionName = KEY_RECOMPOSITION_LOGS_ENABLED, + valueDescription = "bool ", + description = "Add logging for @Composable functions", + required = false, + ), + CliOption( + optionName = KEY_RECOMPOSITION_LOGS_TAG, + valueDescription = "String", + description = "tag for android.util.Log", + required = false, + ), + CliOption( + optionName = KEY_USE_REBUGGER, + valueDescription = "bool ", + description = "use rebugger for logging recompositions. More details: https://github.com/theapache64/rebugger", + required = false, + ), + ) - override fun processOption( - option: AbstractCliOption, - value: String, - configuration: CompilerConfiguration - ) { - return when (option.optionName) { - KEY_RECOMPOSITION_LOGS_ENABLED -> configuration.put(Keys.logEnabled, value.toBoolean()) - KEY_RECOMPOSITION_LOGS_TAG -> configuration.put(Keys.logTag, value) - KEY_LOG_FILE -> configuration.put(Keys.logFile, File(value)) - else -> throw IllegalArgumentException("Unexpected config option ${option.optionName}") + override fun processOption( + option: AbstractCliOption, + value: String, + configuration: CompilerConfiguration, + ) { + return when (option.optionName) { + KEY_RECOMPOSITION_LOGS_ENABLED -> configuration.put(Keys.logEnabled, value.toBoolean()) + KEY_RECOMPOSITION_LOGS_TAG -> configuration.put(Keys.logTag, value) + KEY_LOG_FILE -> configuration.put(Keys.logFile, File(value)) + KEY_USE_REBUGGER -> configuration.put(Keys.useRebugger, value.toBoolean()) + else -> throw IllegalArgumentException("Unexpected config option ${option.optionName}") + } } - } } diff --git a/recomposition-logger-plugin/compiler-plugin/src/main/kotlin/com/welltech/compiler_plugin/PluginComponentRegistrar.kt b/recomposition-logger-plugin/compiler-plugin/src/main/kotlin/com/welltech/compiler_plugin/PluginComponentRegistrar.kt index 58dfcdf..121d725 100644 --- a/recomposition-logger-plugin/compiler-plugin/src/main/kotlin/com/welltech/compiler_plugin/PluginComponentRegistrar.kt +++ b/recomposition-logger-plugin/compiler-plugin/src/main/kotlin/com/welltech/compiler_plugin/PluginComponentRegistrar.kt @@ -17,30 +17,40 @@ class PluginComponentRegistrar : ComponentRegistrar { override fun registerProjectComponents( project: MockProject, - configuration: CompilerConfiguration + configuration: CompilerConfiguration, ) { val enabled = configuration.get( PluginCommandLineProcessor.Keys.logEnabled, - BuildConfig.DEFAULT_RECOMPOSITION_LOGS_ENABLED + BuildConfig.DEFAULT_RECOMPOSITION_LOGS_ENABLED, ) val logTag = configuration.get( PluginCommandLineProcessor.Keys.logTag, - BuildConfig.DEFAULT_RECOMPOSITION_LOGS_TAG + BuildConfig.DEFAULT_RECOMPOSITION_LOGS_TAG, ) val logFile = configuration.get( - PluginCommandLineProcessor.Keys.logFile + PluginCommandLineProcessor.Keys.logFile, ) - val logger = when(logFile) { + val useRebugger = configuration.get( + PluginCommandLineProcessor.Keys.useRebugger, + BuildConfig.DEFAULT_USE_REBUGGER, + ) + + val logger = when (logFile) { null -> EmptyLogger() else -> FileLogger(logFile) } - if (enabled) { - IrGenerationExtension.registerExtension(project, LogsGenerationExtension(tag = logTag)) - IrGenerationExtension.registerExtension(project, HighlightGenerationExtension(logger)) + IrGenerationExtension.registerExtension( + project = project, + extension = LogsGenerationExtension(tag = logTag, useRebugger = useRebugger), + ) + IrGenerationExtension.registerExtension( + project = project, + extension = HighlightGenerationExtension(logger), + ) } } } diff --git a/recomposition-logger-plugin/compiler-plugin/src/main/kotlin/com/welltech/compiler_plugin/debug_logger/EmptyLogger.kt b/recomposition-logger-plugin/compiler-plugin/src/main/kotlin/com/welltech/compiler_plugin/debug_logger/EmptyLogger.kt index 5dafd5a..0757d2d 100644 --- a/recomposition-logger-plugin/compiler-plugin/src/main/kotlin/com/welltech/compiler_plugin/debug_logger/EmptyLogger.kt +++ b/recomposition-logger-plugin/compiler-plugin/src/main/kotlin/com/welltech/compiler_plugin/debug_logger/EmptyLogger.kt @@ -1,7 +1,7 @@ package com.welltech.compiler_plugin.debug_logger -class EmptyLogger: Logger { +class EmptyLogger : Logger { override fun logMsg(msg: String) { // do nothing } -} \ No newline at end of file +} diff --git a/recomposition-logger-plugin/compiler-plugin/src/main/kotlin/com/welltech/compiler_plugin/debug_logger/FileLogger.kt b/recomposition-logger-plugin/compiler-plugin/src/main/kotlin/com/welltech/compiler_plugin/debug_logger/FileLogger.kt index bc1c5d2..304a564 100644 --- a/recomposition-logger-plugin/compiler-plugin/src/main/kotlin/com/welltech/compiler_plugin/debug_logger/FileLogger.kt +++ b/recomposition-logger-plugin/compiler-plugin/src/main/kotlin/com/welltech/compiler_plugin/debug_logger/FileLogger.kt @@ -4,8 +4,8 @@ import java.io.File import java.io.FileOutputStream class FileLogger( - logFile: File -): Logger { + logFile: File, +) : Logger { private val file = if (logFile.isDirectory) { File(logFile, "recomposition-compiler-logs.txt") @@ -27,4 +27,4 @@ class FileLogger( writer.write(msg.toByteArray()) writer.write("\n".toByteArray()) } -} \ No newline at end of file +} diff --git a/recomposition-logger-plugin/compiler-plugin/src/main/kotlin/com/welltech/compiler_plugin/generations/Extensions.kt b/recomposition-logger-plugin/compiler-plugin/src/main/kotlin/com/welltech/compiler_plugin/generations/Extensions.kt index a09436b..f694a88 100644 --- a/recomposition-logger-plugin/compiler-plugin/src/main/kotlin/com/welltech/compiler_plugin/generations/Extensions.kt +++ b/recomposition-logger-plugin/compiler-plugin/src/main/kotlin/com/welltech/compiler_plugin/generations/Extensions.kt @@ -14,8 +14,8 @@ fun IrFunction.shouldAddLogsAndHighlight(pluginContext: IrPluginContext): Boolea val isAnonymousFunction = this is IrSimpleFunction && (name.toString() == "") return body != null - && hasAnnotation(composableAnnotationName) - && !isAnonymousFunction - && !hasAnnotation(disableLogAnnotationName) - && returnType == pluginContext.irBuiltIns.unitType -} \ No newline at end of file + && hasAnnotation(composableAnnotationName) + && !isAnonymousFunction + && !hasAnnotation(disableLogAnnotationName) + && returnType == pluginContext.irBuiltIns.unitType +} diff --git a/recomposition-logger-plugin/compiler-plugin/src/main/kotlin/com/welltech/compiler_plugin/generations/HighlightGenerationExtension.kt b/recomposition-logger-plugin/compiler-plugin/src/main/kotlin/com/welltech/compiler_plugin/generations/HighlightGenerationExtension.kt index dec94f3..b5d2a8d 100644 --- a/recomposition-logger-plugin/compiler-plugin/src/main/kotlin/com/welltech/compiler_plugin/generations/HighlightGenerationExtension.kt +++ b/recomposition-logger-plugin/compiler-plugin/src/main/kotlin/com/welltech/compiler_plugin/generations/HighlightGenerationExtension.kt @@ -21,7 +21,7 @@ import com.welltech.compiler_plugin.debug_logger.Logger import org.jetbrains.kotlin.backend.common.extensions.FirIncompatiblePluginAPI class HighlightGenerationExtension( - private val logger: Logger + private val logger: Logger, ) : IrGenerationExtension { override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) { moduleFragment.transform(HighlightTransformer(pluginContext, logger), null) @@ -31,20 +31,21 @@ class HighlightGenerationExtension( @OptIn(FirIncompatiblePluginAPI::class) private class HighlightTransformer( private val pluginContext: IrPluginContext, - private val logger: Logger + private val logger: Logger, ) : IrElementTransformerVoidWithContext() { - private val funHighlight = pluginContext.referenceFunctions(FqName("com.welltech.recomposition_logger_runtime.highlight.highlightRecomposition")) + private val funHighlight = + pluginContext.referenceFunctions(FqName("com.welltech.recomposition_logger_runtime.highlight.highlightRecomposition")) .single { val parameters = it.owner.valueParameters parameters.size == 1 - && parameters[0].type == pluginContext.irBuiltIns.stringType + && parameters[0].type == pluginContext.irBuiltIns.stringType } private val initialModifier = pluginContext.referenceClass(FqName("androidx.compose.ui.Modifier.Companion"))!! override fun visitFunctionNew(declaration: IrFunction): IrStatement { - if (declaration.shouldAddLogsAndHighlight(pluginContext) ) { + if (declaration.shouldAddLogsAndHighlight(pluginContext)) { logger.logMsg("visit new composable fun: ${declaration.name}") DeclarationIrBuilder(pluginContext, declaration.symbol) .findAndModifyComposeFunctions(declaration.name.toString(), declaration.body!!.statements) @@ -67,8 +68,8 @@ private class HighlightTransformer( private fun IrStatement.isComposableCallWithModifier(): Boolean { return this is IrCall - && symbol.owner.hasAnnotation(composableAnnotationName) - && getModifierArgument() != null + && symbol.owner.hasAnnotation(composableAnnotationName) + && getModifierArgument() != null } private fun IrBuilderWithScope.addHighlightModifier(funName: String, irCallStatement: IrCall) { @@ -91,4 +92,4 @@ private class HighlightTransformer( it.type.isClassType(FqNameUnsafe("androidx.compose.ui.Modifier"), false) } } -} \ No newline at end of file +} diff --git a/recomposition-logger-plugin/compiler-plugin/src/main/kotlin/com/welltech/compiler_plugin/generations/LogsGenerationExtension.kt b/recomposition-logger-plugin/compiler-plugin/src/main/kotlin/com/welltech/compiler_plugin/generations/LogsGenerationExtension.kt index 51b534c..e2d2503 100644 --- a/recomposition-logger-plugin/compiler-plugin/src/main/kotlin/com/welltech/compiler_plugin/generations/LogsGenerationExtension.kt +++ b/recomposition-logger-plugin/compiler-plugin/src/main/kotlin/com/welltech/compiler_plugin/generations/LogsGenerationExtension.kt @@ -1,89 +1,59 @@ package com.welltech.compiler_plugin.generations +import com.welltech.compiler_plugin.generations.logging.DefaultLogProvider +import com.welltech.compiler_plugin.generations.logging.LogProvider +import com.welltech.compiler_plugin.generations.logging.RebuggerLogProvider import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext -import org.jetbrains.kotlin.backend.common.extensions.FirIncompatiblePluginAPI import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder import org.jetbrains.kotlin.ir.IrStatement -import org.jetbrains.kotlin.ir.builders.* +import org.jetbrains.kotlin.ir.builders.irBlockBody import org.jetbrains.kotlin.ir.declarations.IrFunction import org.jetbrains.kotlin.ir.declarations.IrModuleFragment import org.jetbrains.kotlin.ir.expressions.IrBlockBody -import org.jetbrains.kotlin.ir.expressions.IrCall -import org.jetbrains.kotlin.ir.expressions.addArgument -import org.jetbrains.kotlin.ir.util.hasAnnotation import org.jetbrains.kotlin.ir.util.statements -import org.jetbrains.kotlin.name.FqName -class LogsGenerationExtension(private val tag: String) : IrGenerationExtension { - override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) { +class LogsGenerationExtension( + private val tag: String, + private val useRebugger: Boolean, +) : IrGenerationExtension { + override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) { + val logProvider = if (useRebugger) { + RebuggerLogProvider(pluginContext) + } else { + DefaultLogProvider(tag, pluginContext) + } - val transformer = ComposeLogTransformer( - pluginContext = pluginContext, - tag = tag - ) - moduleFragment.transform(transformer, null) - } + val transformer = ComposeLogTransformer( + pluginContext = pluginContext, + logProvider = logProvider, + ) + moduleFragment.transform(transformer, null) + } } private class ComposeLogTransformer( - private val pluginContext: IrPluginContext, - private val tag: String + private val pluginContext: IrPluginContext, + private val logProvider: LogProvider, ) : IrElementTransformerVoidWithContext() { - - private val typeString = pluginContext.irBuiltIns.stringType - - @OptIn(FirIncompatiblePluginAPI::class) - private val funLog = pluginContext.referenceFunctions(FqName("com.welltech.recomposition_logger_runtime.LogComposition")) - .single { - val parameters = it.owner.valueParameters - parameters.size == 2 - && parameters[0].type == typeString - && parameters[1].type == typeString - } - - override fun visitFunctionNew(declaration: IrFunction): IrStatement { - if (declaration.shouldAddLogsAndHighlight(pluginContext)) { - declaration.body = withLogComposition(declaration) - } - return super.visitFunctionNew(declaration) - } - - private fun withLogComposition( - function: IrFunction, - ): IrBlockBody { - return DeclarationIrBuilder(pluginContext, function.symbol).irBlockBody { - +logCompositionCall(function) - function.body?.statements?.forEach { statement -> - +statement - } + override fun visitFunctionNew(declaration: IrFunction): IrStatement { + if (declaration.shouldAddLogsAndHighlight(pluginContext)) { + declaration.body = withLogComposition(declaration) + } + return super.visitFunctionNew(declaration) } - } - private fun IrBuilderWithScope.logCompositionCall( - function: IrFunction - ): IrCall { - return irCall(funLog).also { call -> - val concat = irConcat() - concat.addArgument(irString("${function.name}")) - - val logParameters = function.valueParameters - .filter { it.hasAnnotation(logArgumentAnnotationName) } - logParameters.forEachIndexed { index, valueParameter -> - if (index == 0) { - concat.addArgument(irString("(")) - } - concat.addArgument(irString("${valueParameter.name}=")) - concat.addArgument(irGet(valueParameter)) - if (index == logParameters.lastIndex) { - concat.addArgument(irString(")")) - } else { - concat.addArgument(irString(", ")) + private fun withLogComposition( + function: IrFunction, + ): IrBlockBody { + return DeclarationIrBuilder(pluginContext, function.symbol).irBlockBody { + with(logProvider) { + +logCompositionCall(function) + } + function.body?.statements?.forEach { statement -> + +statement + } } - } - call.putValueArgument(0, concat) - call.putValueArgument(1, irString(tag)) } - } } diff --git a/recomposition-logger-plugin/compiler-plugin/src/main/kotlin/com/welltech/compiler_plugin/generations/logging/DefaultLogProvider.kt b/recomposition-logger-plugin/compiler-plugin/src/main/kotlin/com/welltech/compiler_plugin/generations/logging/DefaultLogProvider.kt new file mode 100644 index 0000000..4e12572 --- /dev/null +++ b/recomposition-logger-plugin/compiler-plugin/src/main/kotlin/com/welltech/compiler_plugin/generations/logging/DefaultLogProvider.kt @@ -0,0 +1,51 @@ +package com.welltech.compiler_plugin.generations.logging + +import com.welltech.compiler_plugin.generations.logArgumentAnnotationName +import org.jetbrains.kotlin.backend.common.extensions.FirIncompatiblePluginAPI +import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext +import org.jetbrains.kotlin.ir.builders.* +import org.jetbrains.kotlin.ir.declarations.IrFunction +import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.expressions.addArgument +import org.jetbrains.kotlin.ir.util.hasAnnotation +import org.jetbrains.kotlin.name.FqName + +class DefaultLogProvider( + private val tag: String, + pluginContext: IrPluginContext, +) : LogProvider { + private val typeString = pluginContext.irBuiltIns.stringType + + @OptIn(FirIncompatiblePluginAPI::class) + private val funLog = pluginContext.referenceFunctions(FqName("com.welltech.recomposition_logger_runtime.LogComposition")) + .single { + val parameters = it.owner.valueParameters + parameters.size == 2 + && parameters[0].type == typeString + && parameters[1].type == typeString + } + + override fun IrBuilderWithScope.logCompositionCall(function: IrFunction): IrCall { + return irCall(funLog).also { call -> + val concat = irConcat() + concat.addArgument(irString("${function.name}")) + + val logParameters = function.valueParameters + .filter { it.hasAnnotation(logArgumentAnnotationName) } + logParameters.forEachIndexed { index, valueParameter -> + if (index == 0) { + concat.addArgument(irString("(")) + } + concat.addArgument(irString("${valueParameter.name}=")) + concat.addArgument(irGet(valueParameter)) + if (index == logParameters.lastIndex) { + concat.addArgument(irString(")")) + } else { + concat.addArgument(irString(", ")) + } + } + call.putValueArgument(0, concat) + call.putValueArgument(1, irString(tag)) + } + } +} diff --git a/recomposition-logger-plugin/compiler-plugin/src/main/kotlin/com/welltech/compiler_plugin/generations/logging/LogProvider.kt b/recomposition-logger-plugin/compiler-plugin/src/main/kotlin/com/welltech/compiler_plugin/generations/logging/LogProvider.kt new file mode 100644 index 0000000..076c189 --- /dev/null +++ b/recomposition-logger-plugin/compiler-plugin/src/main/kotlin/com/welltech/compiler_plugin/generations/logging/LogProvider.kt @@ -0,0 +1,11 @@ +package com.welltech.compiler_plugin.generations.logging + +import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope +import org.jetbrains.kotlin.ir.declarations.IrFunction +import org.jetbrains.kotlin.ir.expressions.IrCall + +interface LogProvider { + fun IrBuilderWithScope.logCompositionCall( + function: IrFunction, + ): IrCall +} diff --git a/recomposition-logger-plugin/compiler-plugin/src/main/kotlin/com/welltech/compiler_plugin/generations/logging/RebuggerLogProvider.kt b/recomposition-logger-plugin/compiler-plugin/src/main/kotlin/com/welltech/compiler_plugin/generations/logging/RebuggerLogProvider.kt new file mode 100644 index 0000000..777378d --- /dev/null +++ b/recomposition-logger-plugin/compiler-plugin/src/main/kotlin/com/welltech/compiler_plugin/generations/logging/RebuggerLogProvider.kt @@ -0,0 +1,75 @@ +package com.welltech.compiler_plugin.generations.logging + +import com.welltech.compiler_plugin.generations.logArgumentAnnotationName +import org.jetbrains.kotlin.backend.common.extensions.FirIncompatiblePluginAPI +import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext +import org.jetbrains.kotlin.ir.builders.* +import org.jetbrains.kotlin.ir.declarations.IrFunction +import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.addArgument +import org.jetbrains.kotlin.ir.types.createType +import org.jetbrains.kotlin.ir.types.impl.IrTypeBase +import org.jetbrains.kotlin.ir.util.constructors +import org.jetbrains.kotlin.ir.util.hasAnnotation +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name + +class RebuggerLogProvider( + private val pluginContext: IrPluginContext, +) : LogProvider { + + @OptIn(FirIncompatiblePluginAPI::class) + private val funLog = pluginContext.referenceFunctions(FqName("com.theapache64.rebugger.Rebugger")) + .single { + val parameters = it.owner.valueParameters + parameters.size == 2 + } + + @OptIn(FirIncompatiblePluginAPI::class) + private val createMapFun = pluginContext.referenceFunctions(FqName("kotlin.collections.mapOf")) + .single { + val parameters = it.owner.valueParameters + parameters.size == 1 + && parameters[0].varargElementType != null + } + + override fun IrBuilderWithScope.logCompositionCall(function: IrFunction): IrCall { + return irCall(funLog).also { call -> + call.putValueArgument(0, createArgumentsMap(function)) + val functionName = irConcat() + functionName.addArgument(irString(function.name.toString())) + function.valueParameters + .filter { it.hasAnnotation(logArgumentAnnotationName) } + .forEach { valueParameter -> + functionName.addArgument(irString(" ${valueParameter.name}=")) + functionName.addArgument(irGet(valueParameter)) + } + call.putValueArgument(1, functionName) + } + } + + private fun IrBuilderWithScope.createArgumentsMap(function: IrFunction): IrExpression { + return irCall(createMapFun).also { call -> + val pairClass = pluginContext.referenceClass(ClassId(FqName("kotlin"), Name.identifier("Pair")))!! + + val values = function.valueParameters.map { inputParameter -> + irCall(pairClass.constructors.first()).also { pairConstructor -> + pairConstructor.putValueArgument(0, irString(inputParameter.name.toString())) + pairConstructor.putValueArgument(1, irGet(inputParameter)) + } + } + + val varargType = pairClass.createType( + hasQuestionMark = false, + arguments = listOf(pluginContext.irBuiltIns.stringType, pluginContext.irBuiltIns.anyType).map { + it as IrTypeBase + }, + ) + + call.putValueArgument(0, irVararg(varargType, values)) + } + } + +} diff --git a/recomposition-logger-plugin/gradle-plugin/build.gradle.kts b/recomposition-logger-plugin/gradle-plugin/build.gradle.kts index c5f3338..10f8cfe 100644 --- a/recomposition-logger-plugin/gradle-plugin/build.gradle.kts +++ b/recomposition-logger-plugin/gradle-plugin/build.gradle.kts @@ -28,6 +28,7 @@ buildConfig { buildConfigField("String", "KOTLIN_PLUGIN_VERSION", "\"${pluginConfig.version}\"") buildConfigField("String", "RUNTIME_LIB", "\"${pluginConfig.runtimeLib}\"") buildConfigField("String", "ANNOTATIONS_LIB", "\"${pluginConfig.annotationsLib}\"") + buildConfigField("String", "REBUGGER_LIB", "\"${pluginConfig.rebuggerLib}\"") } tasks.withType { diff --git a/recomposition-logger-plugin/gradle-plugin/src/main/kotlin/com/welltech/gradle_plugin/RecompositionLoggerGradlePlugin.kt b/recomposition-logger-plugin/gradle-plugin/src/main/kotlin/com/welltech/gradle_plugin/RecompositionLoggerGradlePlugin.kt index a005d80..c6d3b3a 100644 --- a/recomposition-logger-plugin/gradle-plugin/src/main/kotlin/com/welltech/gradle_plugin/RecompositionLoggerGradlePlugin.kt +++ b/recomposition-logger-plugin/gradle-plugin/src/main/kotlin/com/welltech/gradle_plugin/RecompositionLoggerGradlePlugin.kt @@ -1,8 +1,8 @@ package com.welltech.gradle_plugin +import com.welltech.gradle_plugin.extension.RecompositionLoggerGradleExtension import org.gradle.api.Project import org.gradle.api.provider.Provider -import com.welltech.gradle_plugin.extension.RecompositionLoggerGradleExtension import org.jetbrains.kotlin.gradle.plugin.* class RecompositionLoggerGradlePlugin : KotlinCompilerPluginSupportPlugin { @@ -19,11 +19,11 @@ class RecompositionLoggerGradlePlugin : 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 applyToCompilation( - kotlinCompilation: KotlinCompilation<*> + kotlinCompilation: KotlinCompilation<*>, ): Provider> { val project = kotlinCompilation.target.project val extension = project.extensions.getByType(RecompositionLoggerGradleExtension::class.java) @@ -42,43 +42,55 @@ class RecompositionLoggerGradlePlugin : KotlinCompilerPluginSupportPlugin { } } val logsTag = extension.tag ?: BuildConfig.DEFAULT_RECOMPOSITION_LOGS_TAG + val useRebugger = extension.useRebugger ?: BuildConfig.DEFAULT_USE_REBUGGER val supportLibDependency = extension.supportLibDependency + val runtimeLibs = if (useRebugger) { + listOf(BuildConfig.RUNTIME_LIB, BuildConfig.REBUGGER_LIB) + } else { + listOf(BuildConfig.RUNTIME_LIB) + } + + if (useRebugger) { + project.repositories.maven { + it.setUrl("https://jitpack.io") + } + } kotlinCompilation.dependencies { - when(supportLibDependency) { - "none" -> { /*do nothing*/ } - "api" -> { - if (pluginEnabled) api(BuildConfig.RUNTIME_LIB) - api(BuildConfig.ANNOTATIONS_LIB) - } - "compileOnly" -> { - if (pluginEnabled) compileOnly(BuildConfig.RUNTIME_LIB) - compileOnly(BuildConfig.ANNOTATIONS_LIB) - } - else -> { - if (pluginEnabled) implementation(BuildConfig.RUNTIME_LIB) - implementation(BuildConfig.ANNOTATIONS_LIB) - } + val applyDependency: ((Any) -> Unit)? = when (supportLibDependency) { + "none" -> null + "api" -> ::api + "compileOnly" -> ::compileOnly + else -> ::implementation + } + + if (pluginEnabled) { + runtimeLibs.forEach { applyDependency?.invoke(it) } } + applyDependency?.invoke(BuildConfig.ANNOTATIONS_LIB) } return project.provider { listOfNotNull( SubpluginOption( key = BuildConfig.KEY_RECOMPOSITION_LOGS_ENABLED, - value = pluginEnabled.toString() + value = pluginEnabled.toString(), ), SubpluginOption( key = BuildConfig.KEY_RECOMPOSITION_LOGS_TAG, - value = logsTag + value = logsTag, ), logFile?.let { SubpluginOption( key = BuildConfig.KEY_LOG_FILE, - value = it.absolutePath + value = it.absolutePath, ) - } + }, + SubpluginOption( + key = BuildConfig.KEY_USE_REBUGGER, + value = useRebugger.toString(), + ), ) } } diff --git a/recomposition-logger-plugin/gradle-plugin/src/main/kotlin/com/welltech/gradle_plugin/extension/RecompositionLoggerGradleExtension.kt b/recomposition-logger-plugin/gradle-plugin/src/main/kotlin/com/welltech/gradle_plugin/extension/RecompositionLoggerGradleExtension.kt index f3581ad..42da6cb 100644 --- a/recomposition-logger-plugin/gradle-plugin/src/main/kotlin/com/welltech/gradle_plugin/extension/RecompositionLoggerGradleExtension.kt +++ b/recomposition-logger-plugin/gradle-plugin/src/main/kotlin/com/welltech/gradle_plugin/extension/RecompositionLoggerGradleExtension.kt @@ -4,16 +4,18 @@ import java.io.File abstract class RecompositionLoggerGradleExtension { - abstract var logFile: File? + abstract var logFile: File? - /** - * available: none, implementation, api, compileOnly. Default - implementation - */ - abstract var supportLibDependency: String? + /** + * available: none, implementation, api, compileOnly. Default - implementation + */ + abstract var supportLibDependency: String? - abstract var enabled: Boolean? + abstract var enabled: Boolean? - abstract var tag: String? + abstract var tag: String? + + abstract var useRebugger: Boolean? } diff --git a/recomposition-logger-support/recomposition-logger-annotations/src/main/java/com/welltech/recomposition_logger_annotations/highlight/HighlightOptions.kt b/recomposition-logger-support/recomposition-logger-annotations/src/main/java/com/welltech/recomposition_logger_annotations/highlight/HighlightOptions.kt index e1d70d7..f2667e1 100644 --- a/recomposition-logger-support/recomposition-logger-annotations/src/main/java/com/welltech/recomposition_logger_annotations/highlight/HighlightOptions.kt +++ b/recomposition-logger-support/recomposition-logger-annotations/src/main/java/com/welltech/recomposition_logger_annotations/highlight/HighlightOptions.kt @@ -11,5 +11,5 @@ data class HighlightOptions( val enabled: Boolean = false, val durationMillis: Long = 100, val normalColor: Color = Color.Green, - val recompositionColor: Color = Color.Red + val recompositionColor: Color = Color.Red, ) diff --git a/recomposition-logger-support/recomposition-logger-runtime/src/main/java/com/welltech/recomposition_logger_runtime/highlight/HighlightComposition.kt b/recomposition-logger-support/recomposition-logger-runtime/src/main/java/com/welltech/recomposition_logger_runtime/highlight/HighlightComposition.kt index 6690dab..680396e 100644 --- a/recomposition-logger-support/recomposition-logger-runtime/src/main/java/com/welltech/recomposition_logger_runtime/highlight/HighlightComposition.kt +++ b/recomposition-logger-support/recomposition-logger-runtime/src/main/java/com/welltech/recomposition_logger_runtime/highlight/HighlightComposition.kt @@ -14,9 +14,8 @@ import androidx.compose.ui.platform.debugInspectorInfo import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.welltech.recomposition_logger_annotations.highlight.debugHighlightOptions -import kotlinx.coroutines.delay import com.welltech.recomposition_logger_runtime.CompositionCounter -import kotlin.random.Random +import kotlinx.coroutines.delay /** * Draw rect around composable to which this modifier is applied. @@ -29,7 +28,7 @@ fun Modifier.highlightRecomposition(funName: String) = composed( inspectorInfo = debugInspectorInfo { name = "recomposeHighlighter" properties["funName"] = funName - } + }, ) { if (debugHighlightOptions.enabled.not()) { return@composed this @@ -77,9 +76,9 @@ fun Modifier.highlightRecomposition(funName: String) = composed( color = Color.Black, topLeft = Offset(size.width - textWidth, 0f), size = Size(textWidth, 10.sp.toPx()), - alpha = 0.5f + alpha = 0.5f, ) drawContext.canvas.nativeCanvas.drawText(funName, size.width - textWidth, 20f, paint) } } -} \ No newline at end of file +} diff --git a/sample/build.gradle b/sample/build.gradle index e4c6962..c64a875 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -13,7 +13,7 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:7.1.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath 'com.welltech:recomposition-logger-plugin:1.5.0' + classpath 'com.welltech:recomposition-logger-plugin:1.6.0-alpha01' } } @@ -71,6 +71,7 @@ recompositionLogger { enabled = true tag = "SampleRecomposition" // tag for recomposition logs logFile = projectDir + useRebugger = true } dependencies { diff --git a/sample/src/main/java/com/welltech/recomposition_logger_sample/MainActivity.kt b/sample/src/main/java/com/welltech/recomposition_logger_sample/MainActivity.kt index c829bd7..049e09a 100644 --- a/sample/src/main/java/com/welltech/recomposition_logger_sample/MainActivity.kt +++ b/sample/src/main/java/com/welltech/recomposition_logger_sample/MainActivity.kt @@ -2,17 +2,16 @@ package com.welltech.recomposition_logger_sample import android.os.Bundle import androidx.appcompat.app.AppCompatActivity -import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import com.welltech.recomposition_logger_sample.databinding.ActivityMainBinding import com.welltech.recomposition_logger_annotations.DisableLogs import com.welltech.recomposition_logger_annotations.LogArgument import com.welltech.recomposition_logger_annotations.highlight.debugHighlightOptions +import com.welltech.recomposition_logger_sample.databinding.ActivityMainBinding import kotlin.random.Random class MainActivity : AppCompatActivity() { @@ -35,29 +34,28 @@ class MainActivity : AppCompatActivity() { mutableStateOf(5) } Column( - modifier = Modifier.fillMaxSize() + modifier = Modifier.fillMaxSize(), ) { (0..itemCount).forEach { // set random string for force the Item to recompose Item( id = it, - string = randomString() + string = randomString(), ) } } Row( modifier = Modifier.align(Alignment.BottomCenter), - horizontalArrangement = Arrangement.spacedBy(16.dp) + horizontalArrangement = Arrangement.spacedBy(16.dp), ) { - Button( onClick = { itemCount += 1 }, - content = { Text(text = "Add") } + content = { Text(text = "Add") }, ) Button( onClick = { debugHighlightOptions = debugHighlightOptions.copy(enabled = !debugHighlightOptions.enabled) }, - content = { Text(text = "Change highlighting") } + content = { Text(text = "Change highlighting") }, ) } } @@ -81,21 +79,21 @@ class MainActivity : AppCompatActivity() { Row( modifier = modifier .fillMaxWidth() - .padding(10.dp) + .padding(10.dp), ) { Text( modifier = Modifier .padding(3.dp), text = string, - color = MaterialTheme.colors.onSurface + color = MaterialTheme.colors.onSurface, ) Spacer(modifier = Modifier.padding(10.dp)) Text( modifier = Modifier .padding(3.dp), text = "ipsum", - color = MaterialTheme.colors.onSurface + color = MaterialTheme.colors.onSurface, ) } } -} \ No newline at end of file +}