From 28c059f25b4e344c211aa9006e15f23d43e8512e Mon Sep 17 00:00:00 2001 From: "Alexander.Likhachev" Date: Tue, 27 Jun 2023 22:43:48 +0200 Subject: [PATCH] [BT] Implement basic non-incremental in-process JVM compilation --- .../kotlin-build-tools-impl/build.gradle.kts | 3 + .../internal/CompilationServiceImpl.kt | 45 ++++++++++- ...pilerExecutionStrategyConfigurationImpl.kt | 14 +++- .../internal/DefaultKotlinLogger.kt | 39 ++++++++-- .../KotlinLoggerMessageCollectorAdapter.kt | 30 ++++++++ .../gradle/BuildToolsApiJvmCompilationIT.kt | 1 - .../btapi/BuildToolsApiCompilationWork.kt | 76 ++++++++++++++----- .../kotlin-build-tools-impl.pom | 6 ++ 8 files changed, 186 insertions(+), 28 deletions(-) create mode 100644 compiler/build-tools/kotlin-build-tools-impl/src/main/kotlin/org/jetbrains/kotlin/buildtools/internal/KotlinLoggerMessageCollectorAdapter.kt diff --git a/compiler/build-tools/kotlin-build-tools-impl/build.gradle.kts b/compiler/build-tools/kotlin-build-tools-impl/build.gradle.kts index a5d87c7c92e..f97fec9b15b 100644 --- a/compiler/build-tools/kotlin-build-tools-impl/build.gradle.kts +++ b/compiler/build-tools/kotlin-build-tools-impl/build.gradle.kts @@ -6,6 +6,9 @@ plugins { dependencies { api(project(":compiler:build-tools:kotlin-build-tools-api")) implementation(kotlinStdlib()) + compileOnly(project(":compiler:cli")) + compileOnly(project(":compiler:cli-js")) + runtimeOnly(project(":kotlin-compiler-embeddable")) } publish() diff --git a/compiler/build-tools/kotlin-build-tools-impl/src/main/kotlin/org/jetbrains/kotlin/buildtools/internal/CompilationServiceImpl.kt b/compiler/build-tools/kotlin-build-tools-impl/src/main/kotlin/org/jetbrains/kotlin/buildtools/internal/CompilationServiceImpl.kt index 84e23f73da6..7f59fdef6c2 100644 --- a/compiler/build-tools/kotlin-build-tools-impl/src/main/kotlin/org/jetbrains/kotlin/buildtools/internal/CompilationServiceImpl.kt +++ b/compiler/build-tools/kotlin-build-tools-impl/src/main/kotlin/org/jetbrains/kotlin/buildtools/internal/CompilationServiceImpl.kt @@ -7,10 +7,26 @@ package org.jetbrains.kotlin.buildtools.internal import org.jetbrains.kotlin.buildtools.api.CompilationResult import org.jetbrains.kotlin.buildtools.api.CompilationService +import org.jetbrains.kotlin.buildtools.api.CompilerArgumentsParseException import org.jetbrains.kotlin.buildtools.api.CompilerExecutionStrategyConfiguration import org.jetbrains.kotlin.buildtools.api.jvm.* +import org.jetbrains.kotlin.cli.common.ExitCode +import org.jetbrains.kotlin.cli.common.arguments.parseCommandLineArguments +import org.jetbrains.kotlin.cli.common.arguments.validateArguments +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity +import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler +import org.jetbrains.kotlin.config.Services import java.io.File +private val ExitCode.asCompilationResult + get() = when (this) { + ExitCode.OK -> CompilationResult.COMPILATION_SUCCESS + ExitCode.COMPILATION_ERROR -> CompilationResult.COMPILER_INTERNAL_ERROR + ExitCode.INTERNAL_ERROR -> CompilationResult.COMPILER_INTERNAL_ERROR + ExitCode.OOM_ERROR -> CompilationResult.COMPILATION_OOM_ERROR + else -> error("Unexpected exit code: $this") + } + internal class CompilationServiceImpl : CompilationService { override fun calculateClasspathSnapshot(classpathEntry: File): ClasspathEntrySnapshot { TODO("Calculating classpath snapshots via the Build Tools API is not yet implemented: KT-57565") @@ -26,7 +42,32 @@ internal class CompilationServiceImpl : CompilationService { sources: List, arguments: List ): CompilationResult { - println("I'm simulating compilation, nothing more yet") - return CompilationResult.COMPILATION_SUCCESS + check(strategyConfig is CompilerExecutionStrategyConfigurationImpl) { + "Initial strategy configuration object must be acquired from the `makeCompilerExecutionStrategyConfiguration` method." + } + check(compilationConfig is JvmCompilationConfigurationImpl) { + "Initial JVM compilation configuration object must be acquired from the `makeJvmCompilationConfiguration` method." + } + val loggerAdapter = KotlinLoggerMessageCollectorAdapter(compilationConfig.logger) + return when (strategyConfig.selectedStrategy) { + is CompilerExecutionStrategy.InProcess -> compileInProcess(loggerAdapter, sources, arguments) + is CompilerExecutionStrategy.Daemon -> TODO("The daemon strategy is not yet supported in the Build Tools API") + } + } + + private fun compileInProcess( + loggerAdapter: KotlinLoggerMessageCollectorAdapter, + sources: List, + arguments: List + ): CompilationResult { + val compiler = K2JVMCompiler() + val parsedArguments = compiler.createArguments() + parseCommandLineArguments(arguments, parsedArguments) + validateArguments(parsedArguments.errors)?.let { + throw CompilerArgumentsParseException(it) + } + parsedArguments.freeArgs += sources.map { it.absolutePath } // TODO: they're not actually passed yet + loggerAdapter.report(CompilerMessageSeverity.INFO, arguments.toString()) + return compiler.exec(loggerAdapter, Services.EMPTY, parsedArguments).asCompilationResult } } \ No newline at end of file diff --git a/compiler/build-tools/kotlin-build-tools-impl/src/main/kotlin/org/jetbrains/kotlin/buildtools/internal/CompilerExecutionStrategyConfigurationImpl.kt b/compiler/build-tools/kotlin-build-tools-impl/src/main/kotlin/org/jetbrains/kotlin/buildtools/internal/CompilerExecutionStrategyConfigurationImpl.kt index 41b82a5b176..ec81c78bda4 100644 --- a/compiler/build-tools/kotlin-build-tools-impl/src/main/kotlin/org/jetbrains/kotlin/buildtools/internal/CompilerExecutionStrategyConfigurationImpl.kt +++ b/compiler/build-tools/kotlin-build-tools-impl/src/main/kotlin/org/jetbrains/kotlin/buildtools/internal/CompilerExecutionStrategyConfigurationImpl.kt @@ -8,12 +8,22 @@ package org.jetbrains.kotlin.buildtools.internal import org.jetbrains.kotlin.buildtools.api.CompilerExecutionStrategyConfiguration import java.io.File -class CompilerExecutionStrategyConfigurationImpl : CompilerExecutionStrategyConfiguration { +internal sealed interface CompilerExecutionStrategy { + data object InProcess : CompilerExecutionStrategy + + data class Daemon(val sessionDir: File, val jvmArguments: List) : CompilerExecutionStrategy +} + +internal class CompilerExecutionStrategyConfigurationImpl : CompilerExecutionStrategyConfiguration { + internal var selectedStrategy: CompilerExecutionStrategy = CompilerExecutionStrategy.InProcess + override fun useInProcessStrategy(): CompilerExecutionStrategyConfiguration { + selectedStrategy = CompilerExecutionStrategy.InProcess return this } override fun useDaemonStrategy(sessionDir: File, jvmArguments: List): CompilerExecutionStrategyConfiguration { - TODO("Daemon strategy is not yet supported in the Build Tools API") + selectedStrategy = CompilerExecutionStrategy.Daemon(sessionDir, jvmArguments) + return this } } \ No newline at end of file diff --git a/compiler/build-tools/kotlin-build-tools-impl/src/main/kotlin/org/jetbrains/kotlin/buildtools/internal/DefaultKotlinLogger.kt b/compiler/build-tools/kotlin-build-tools-impl/src/main/kotlin/org/jetbrains/kotlin/buildtools/internal/DefaultKotlinLogger.kt index 2c182768b94..38aec0cd8bf 100644 --- a/compiler/build-tools/kotlin-build-tools-impl/src/main/kotlin/org/jetbrains/kotlin/buildtools/internal/DefaultKotlinLogger.kt +++ b/compiler/build-tools/kotlin-build-tools-impl/src/main/kotlin/org/jetbrains/kotlin/buildtools/internal/DefaultKotlinLogger.kt @@ -7,27 +7,54 @@ package org.jetbrains.kotlin.buildtools.internal import org.jetbrains.kotlin.buildtools.api.KotlinLogger +private enum class LogLevel { + ERROR, + WARN, + LIFECYCLE, + INFO, + DEBUG, + ; + + companion object { + fun fromString(rawValue: String) = entries.firstOrNull { it.name.equals(rawValue, ignoreCase = true) } + ?: error("Unknown log level for the DefaultKotlinLogger: $rawValue") + } +} + internal object DefaultKotlinLogger : KotlinLogger { + private val logLevel: LogLevel = System.getProperty("kotlin.build-tools-api.log.level")?.let { + LogLevel.fromString(it) + } ?: LogLevel.WARN + + private val LogLevel.isEnabled: Boolean + get() = logLevel >= this + override val isDebugEnabled: Boolean - get() = TODO("Default KotlinLogger is not yet implemented in the Build Tools API") + get() = LogLevel.DEBUG.isEnabled override fun error(msg: String, throwable: Throwable?) { - TODO("Default KotlinLogger is not yet implemented in the Build Tools API") + if (!LogLevel.ERROR.isEnabled) return + System.err.println("e: $msg") + throwable?.printStackTrace() } override fun warn(msg: String) { - TODO("Default KotlinLogger is not yet implemented in the Build Tools API") + if (!LogLevel.WARN.isEnabled) return + System.err.println("w: $msg") } override fun info(msg: String) { - TODO("Default KotlinLogger is not yet implemented in the Build Tools API") + if (!LogLevel.INFO.isEnabled) return + println("i: $msg") } override fun debug(msg: String) { - TODO("Default KotlinLogger is not yet implemented in the Build Tools API") + if (!LogLevel.DEBUG.isEnabled) return + println("d: $msg") } override fun lifecycle(msg: String) { - TODO("Default KotlinLogger is not yet implemented in the Build Tools API") + if (!LogLevel.LIFECYCLE.isEnabled) return + println("l: $msg") } } \ No newline at end of file diff --git a/compiler/build-tools/kotlin-build-tools-impl/src/main/kotlin/org/jetbrains/kotlin/buildtools/internal/KotlinLoggerMessageCollectorAdapter.kt b/compiler/build-tools/kotlin-build-tools-impl/src/main/kotlin/org/jetbrains/kotlin/buildtools/internal/KotlinLoggerMessageCollectorAdapter.kt new file mode 100644 index 00000000000..d7ac135874e --- /dev/null +++ b/compiler/build-tools/kotlin-build-tools-impl/src/main/kotlin/org/jetbrains/kotlin/buildtools/internal/KotlinLoggerMessageCollectorAdapter.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.buildtools.internal + +import org.jetbrains.kotlin.buildtools.api.KotlinLogger +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSourceLocation +import org.jetbrains.kotlin.cli.common.messages.MessageCollector +import org.jetbrains.kotlin.cli.common.messages.MessageRenderer + +internal class KotlinLoggerMessageCollectorAdapter(private val kotlinLogger: KotlinLogger) : MessageCollector { + private val messageRenderer = MessageRenderer.GRADLE_STYLE + + override fun clear() {} + + override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageSourceLocation?) { + val renderedMessage = messageRenderer.render(severity, message, location) // TODO: move rendering to the KotlinLogger side + when (severity) { + CompilerMessageSeverity.EXCEPTION, CompilerMessageSeverity.ERROR -> kotlinLogger.error(renderedMessage) + CompilerMessageSeverity.STRONG_WARNING, CompilerMessageSeverity.WARNING -> kotlinLogger.warn(renderedMessage) + CompilerMessageSeverity.INFO -> kotlinLogger.info(renderedMessage) + CompilerMessageSeverity.OUTPUT, CompilerMessageSeverity.LOGGING -> kotlinLogger.debug(renderedMessage) + } + } + + override fun hasErrors() = false +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildToolsApiJvmCompilationIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildToolsApiJvmCompilationIT.kt index 581fc80b2ad..a0d8ff37993 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildToolsApiJvmCompilationIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildToolsApiJvmCompilationIT.kt @@ -20,7 +20,6 @@ class BuildToolsApiJvmCompilationIT : KGPBaseTest() { override val defaultBuildOptions = super.defaultBuildOptions.copy(runViaBuildToolsApi = true) @GradleTest - @Disabled @DisplayName("Simple project non-incremental in-process compilation") fun compileJvmInProcessNonIncremental(gradleVersion: GradleVersion) = testSimpleProject( gradleVersion, defaultBuildOptions.copy( diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/compilerRunner/btapi/BuildToolsApiCompilationWork.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/compilerRunner/btapi/BuildToolsApiCompilationWork.kt index ebfcdd4777a..dff3d5e7e38 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/compilerRunner/btapi/BuildToolsApiCompilationWork.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/compilerRunner/btapi/BuildToolsApiCompilationWork.kt @@ -13,12 +13,18 @@ import org.gradle.workers.WorkParameters import org.jetbrains.kotlin.build.report.metrics.BuildMetricsReporter import org.jetbrains.kotlin.build.report.metrics.GradleBuildPerformanceMetric import org.jetbrains.kotlin.build.report.metrics.GradleBuildTime -import org.jetbrains.kotlin.buildtools.api.CompilationService -import org.jetbrains.kotlin.buildtools.api.ExperimentalBuildToolsApi -import org.jetbrains.kotlin.buildtools.api.SharedApiClassesClassLoader +import org.jetbrains.kotlin.buildtools.api.* +import org.jetbrains.kotlin.cli.common.ExitCode import org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWorkArguments +import org.jetbrains.kotlin.compilerRunner.asFinishLogMessage import org.jetbrains.kotlin.gradle.internal.ClassLoadersCachingBuildService import org.jetbrains.kotlin.gradle.internal.ParentClassLoaderProvider +import org.jetbrains.kotlin.gradle.logging.GradleKotlinLogger +import org.jetbrains.kotlin.gradle.logging.SL4JKotlinLogger +import org.jetbrains.kotlin.gradle.plugin.internal.state.TaskLoggers +import org.jetbrains.kotlin.gradle.tasks.KotlinCompilerExecutionStrategy +import org.jetbrains.kotlin.gradle.tasks.throwExceptionIfCompilationFailed +import org.slf4j.LoggerFactory import java.io.File @OptIn(ExperimentalBuildToolsApi::class) @@ -35,21 +41,57 @@ internal abstract class BuildToolsApiCompilationWork : WorkAction TODO("The daemon strategy is not yet supported in the Build Tools API") + KotlinCompilerExecutionStrategy.IN_PROCESS -> useInProcessStrategy() + else -> error("The \"$executionStrategy\" execution strategy is not supported by the Build Tools API") + } + } + val jvmCompilationConfig = compilationService.makeJvmCompilationConfiguration() + val result = compilationService.compileJvm( + executionConfig, + jvmCompilationConfig, + emptyList(), + workArguments.compilerArgs.toList(), + ) + throwExceptionIfCompilationFailed(result.asExitCode, executionStrategy) + } finally { + log.info(executionStrategy.asFinishLogMessage) + } + } + + // temporary adapter property + private val CompilationResult.asExitCode + get() = when (this) { + CompilationResult.COMPILATION_ERROR -> ExitCode.COMPILATION_ERROR + CompilationResult.COMPILER_INTERNAL_ERROR -> ExitCode.INTERNAL_ERROR + CompilationResult.COMPILATION_OOM_ERROR -> ExitCode.OOM_ERROR + else -> ExitCode.OK + } } @OptIn(ExperimentalBuildToolsApi::class) diff --git a/repo/artifacts-tests/src/test/resources/org/jetbrains/kotlin/kotlin-build-tools-impl/kotlin-build-tools-impl.pom b/repo/artifacts-tests/src/test/resources/org/jetbrains/kotlin/kotlin-build-tools-impl/kotlin-build-tools-impl.pom index bf8b82fa431..32cd9ac3cbd 100644 --- a/repo/artifacts-tests/src/test/resources/org/jetbrains/kotlin/kotlin-build-tools-impl/kotlin-build-tools-impl.pom +++ b/repo/artifacts-tests/src/test/resources/org/jetbrains/kotlin/kotlin-build-tools-impl/kotlin-build-tools-impl.pom @@ -38,5 +38,11 @@ ArtifactsTest.version runtime + + org.jetbrains.kotlin + kotlin-compiler-embeddable + ArtifactsTest.version + runtime +