[Gradle, BT] Add support for outputs backup within build tools API compilation
#KT-61896 Fixed
This commit is contained in:
committed by
Space Team
parent
b460c06907
commit
3adb202eb6
+31
@@ -13,6 +13,8 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompilerExecutionStrategy
|
|||||||
import org.jetbrains.kotlin.gradle.tasks.USING_JVM_INCREMENTAL_COMPILATION_MESSAGE
|
import org.jetbrains.kotlin.gradle.tasks.USING_JVM_INCREMENTAL_COMPILATION_MESSAGE
|
||||||
import org.jetbrains.kotlin.gradle.testbase.*
|
import org.jetbrains.kotlin.gradle.testbase.*
|
||||||
import org.junit.jupiter.api.DisplayName
|
import org.junit.jupiter.api.DisplayName
|
||||||
|
import kotlin.io.path.deleteExisting
|
||||||
|
import kotlin.io.path.writeText
|
||||||
|
|
||||||
@DisplayName("JVM compilation via the Build Tools API")
|
@DisplayName("JVM compilation via the Build Tools API")
|
||||||
@JvmGradlePluginTests
|
@JvmGradlePluginTests
|
||||||
@@ -55,6 +57,35 @@ class BuildToolsApiJvmCompilationIT : KGPBaseTest() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GradleTest
|
||||||
|
@DisplayName("Gradle side outputs backup works")
|
||||||
|
fun outputsBackupWorks(gradleVersion: GradleVersion) {
|
||||||
|
project(
|
||||||
|
"simpleProject", gradleVersion, buildOptions = defaultBuildOptions.copy(
|
||||||
|
usePreciseOutputsBackup = false,
|
||||||
|
keepIncrementalCompilationCachesInMemory = false,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
build("compileKotlin") {
|
||||||
|
assertTasksExecuted(":compileKotlin")
|
||||||
|
}
|
||||||
|
val newBrokenSrc = kotlinSourcesDir().resolve("broken.kt")
|
||||||
|
newBrokenSrc.writeText(
|
||||||
|
//language=kt
|
||||||
|
"""
|
||||||
|
broken code
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
buildAndFail("compileKotlin") {
|
||||||
|
assertTasksFailed(":compileKotlin")
|
||||||
|
}
|
||||||
|
newBrokenSrc.deleteExisting()
|
||||||
|
build("compileKotlin") {
|
||||||
|
assertTasksUpToDate(":compileKotlin")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@GradleTest
|
@GradleTest
|
||||||
@DisplayName("Simple project non-incremental in-process compilation")
|
@DisplayName("Simple project non-incremental in-process compilation")
|
||||||
fun compileJvmInProcessNonIncremental(gradleVersion: GradleVersion) = testSimpleProject(
|
fun compileJvmInProcessNonIncremental(gradleVersion: GradleVersion) = testSimpleProject(
|
||||||
|
|||||||
+6
-5
@@ -15,6 +15,7 @@ import org.gradle.workers.WorkParameters
|
|||||||
import org.gradle.workers.WorkQueue
|
import org.gradle.workers.WorkQueue
|
||||||
import org.gradle.workers.WorkerExecutor
|
import org.gradle.workers.WorkerExecutor
|
||||||
import org.jetbrains.kotlin.build.report.metrics.*
|
import org.jetbrains.kotlin.build.report.metrics.*
|
||||||
|
import org.jetbrains.kotlin.gradle.logging.GradleKotlinLogger
|
||||||
import org.jetbrains.kotlin.gradle.tasks.*
|
import org.jetbrains.kotlin.gradle.tasks.*
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -27,11 +28,11 @@ internal class GradleCompilerRunnerWithWorkers(
|
|||||||
jdkToolsJar: File?,
|
jdkToolsJar: File?,
|
||||||
compilerExecutionSettings: CompilerExecutionSettings,
|
compilerExecutionSettings: CompilerExecutionSettings,
|
||||||
buildMetrics: BuildMetricsReporter<GradleBuildTime, GradleBuildPerformanceMetric>,
|
buildMetrics: BuildMetricsReporter<GradleBuildTime, GradleBuildPerformanceMetric>,
|
||||||
private val workerExecutor: WorkerExecutor
|
private val workerExecutor: WorkerExecutor,
|
||||||
) : GradleCompilerRunner(taskProvider, jdkToolsJar, compilerExecutionSettings, buildMetrics) {
|
) : GradleCompilerRunner(taskProvider, jdkToolsJar, compilerExecutionSettings, buildMetrics) {
|
||||||
override fun runCompilerAsync(
|
override fun runCompilerAsync(
|
||||||
workArgs: GradleKotlinCompilerWorkArguments,
|
workArgs: GradleKotlinCompilerWorkArguments,
|
||||||
taskOutputsBackup: TaskOutputsBackup?
|
taskOutputsBackup: TaskOutputsBackup?,
|
||||||
): WorkQueue {
|
): WorkQueue {
|
||||||
|
|
||||||
buildMetrics.addTimeMetric(GradleBuildPerformanceMetric.CALL_WORKER)
|
buildMetrics.addTimeMetric(GradleBuildPerformanceMetric.CALL_WORKER)
|
||||||
@@ -49,10 +50,10 @@ internal class GradleCompilerRunnerWithWorkers(
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal abstract class GradleKotlinCompilerWorkAction @Inject constructor(
|
internal abstract class GradleKotlinCompilerWorkAction @Inject constructor(
|
||||||
private val fileSystemOperations: FileSystemOperations
|
private val fileSystemOperations: FileSystemOperations,
|
||||||
) : WorkAction<GradleKotlinCompilerWorkParameters> {
|
) : WorkAction<GradleKotlinCompilerWorkParameters> {
|
||||||
|
|
||||||
private val logger = Logging.getLogger("kotlin-compile-worker")
|
private val logger = GradleKotlinLogger(Logging.getLogger("kotlin-compile-worker"))
|
||||||
|
|
||||||
override fun execute() {
|
override fun execute() {
|
||||||
val taskOutputsBackup = if (parameters.snapshotsDir.isPresent) {
|
val taskOutputsBackup = if (parameters.snapshotsDir.isPresent) {
|
||||||
@@ -61,7 +62,7 @@ internal class GradleCompilerRunnerWithWorkers(
|
|||||||
parameters.buildDir,
|
parameters.buildDir,
|
||||||
parameters.snapshotsDir,
|
parameters.snapshotsDir,
|
||||||
parameters.taskOutputsToRestore.get(),
|
parameters.taskOutputsToRestore.get(),
|
||||||
logger
|
logger,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
|
|||||||
+69
-7
@@ -6,6 +6,7 @@
|
|||||||
package org.jetbrains.kotlin.compilerRunner.btapi
|
package org.jetbrains.kotlin.compilerRunner.btapi
|
||||||
|
|
||||||
import org.gradle.api.file.DirectoryProperty
|
import org.gradle.api.file.DirectoryProperty
|
||||||
|
import org.gradle.api.file.FileSystemOperations
|
||||||
import org.gradle.api.provider.ListProperty
|
import org.gradle.api.provider.ListProperty
|
||||||
import org.gradle.api.provider.Property
|
import org.gradle.api.provider.Property
|
||||||
import org.gradle.workers.WorkAction
|
import org.gradle.workers.WorkAction
|
||||||
@@ -25,13 +26,21 @@ import org.jetbrains.kotlin.gradle.logging.SL4JKotlinLogger
|
|||||||
import org.jetbrains.kotlin.gradle.plugin.BuildFinishedListenerService
|
import org.jetbrains.kotlin.gradle.plugin.BuildFinishedListenerService
|
||||||
import org.jetbrains.kotlin.gradle.plugin.internal.BuildIdService
|
import org.jetbrains.kotlin.gradle.plugin.internal.BuildIdService
|
||||||
import org.jetbrains.kotlin.gradle.plugin.internal.state.TaskLoggers
|
import org.jetbrains.kotlin.gradle.plugin.internal.state.TaskLoggers
|
||||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilerExecutionStrategy
|
import org.jetbrains.kotlin.gradle.tasks.*
|
||||||
import org.jetbrains.kotlin.gradle.tasks.throwExceptionIfCompilationFailed
|
import org.jetbrains.kotlin.gradle.tasks.CompilationErrorException
|
||||||
|
import org.jetbrains.kotlin.gradle.tasks.FailedCompilationException
|
||||||
|
import org.jetbrains.kotlin.gradle.tasks.OOMErrorException
|
||||||
|
import org.jetbrains.kotlin.gradle.tasks.TaskOutputsBackup
|
||||||
import org.jetbrains.kotlin.incremental.ClasspathChanges
|
import org.jetbrains.kotlin.incremental.ClasspathChanges
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.rmi.RemoteException
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal abstract class BuildToolsApiCompilationWork : WorkAction<BuildToolsApiCompilationWork.BuildToolsApiCompilationParameters> {
|
internal abstract class BuildToolsApiCompilationWork @Inject constructor(
|
||||||
|
private val fileSystemOperations: FileSystemOperations,
|
||||||
|
) :
|
||||||
|
WorkAction<BuildToolsApiCompilationWork.BuildToolsApiCompilationParameters> {
|
||||||
internal interface BuildToolsApiCompilationParameters : WorkParameters {
|
internal interface BuildToolsApiCompilationParameters : WorkParameters {
|
||||||
val buildIdService: Property<BuildIdService>
|
val buildIdService: Property<BuildIdService>
|
||||||
val buildFinishedListenerService: Property<BuildFinishedListenerService>
|
val buildFinishedListenerService: Property<BuildFinishedListenerService>
|
||||||
@@ -63,7 +72,7 @@ internal abstract class BuildToolsApiCompilationWork : WorkAction<BuildToolsApiC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun execute() {
|
private fun performCompilation(): CompilationResult {
|
||||||
val executionStrategy = workArguments.compilerExecutionSettings.strategy
|
val executionStrategy = workArguments.compilerExecutionSettings.strategy
|
||||||
try {
|
try {
|
||||||
val classLoader = parameters.classLoadersCachingService.get()
|
val classLoader = parameters.classLoadersCachingService.get()
|
||||||
@@ -75,7 +84,9 @@ internal abstract class BuildToolsApiCompilationWork : WorkAction<BuildToolsApiC
|
|||||||
}
|
}
|
||||||
val executionConfig = compilationService.makeCompilerExecutionStrategyConfiguration().apply {
|
val executionConfig = compilationService.makeCompilerExecutionStrategyConfiguration().apply {
|
||||||
when (executionStrategy) {
|
when (executionStrategy) {
|
||||||
KotlinCompilerExecutionStrategy.DAEMON -> useDaemonStrategy(workArguments.compilerExecutionSettings.daemonJvmArgs ?: emptyList())
|
KotlinCompilerExecutionStrategy.DAEMON -> useDaemonStrategy(
|
||||||
|
workArguments.compilerExecutionSettings.daemonJvmArgs ?: emptyList()
|
||||||
|
)
|
||||||
KotlinCompilerExecutionStrategy.IN_PROCESS -> useInProcessStrategy()
|
KotlinCompilerExecutionStrategy.IN_PROCESS -> useInProcessStrategy()
|
||||||
else -> error("The \"$executionStrategy\" execution strategy is not supported by the Build Tools API")
|
else -> error("The \"$executionStrategy\" execution strategy is not supported by the Build Tools API")
|
||||||
}
|
}
|
||||||
@@ -110,19 +121,70 @@ internal abstract class BuildToolsApiCompilationWork : WorkAction<BuildToolsApiC
|
|||||||
classpathSnapshotsConfig,
|
classpathSnapshotsConfig,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val result = compilationService.compileJvm(
|
return compilationService.compileJvm(
|
||||||
buildId,
|
buildId,
|
||||||
executionConfig,
|
executionConfig,
|
||||||
jvmCompilationConfig,
|
jvmCompilationConfig,
|
||||||
emptyList(),
|
emptyList(),
|
||||||
workArguments.compilerArgs.toList(),
|
workArguments.compilerArgs.toList(),
|
||||||
)
|
)
|
||||||
throwExceptionIfCompilationFailed(result.asExitCode, executionStrategy)
|
} catch (e: Throwable) {
|
||||||
|
if (e is OutOfMemoryError || e.hasOOMCause()) {
|
||||||
|
val helpMessage = when (executionStrategy) {
|
||||||
|
KotlinCompilerExecutionStrategy.DAEMON -> kotlinDaemonOOMHelperMessage
|
||||||
|
KotlinCompilerExecutionStrategy.IN_PROCESS -> kotlinInProcessOOMHelperMessage
|
||||||
|
else -> error("The \"$executionStrategy\" execution strategy is not supported by the Build Tools API")
|
||||||
|
}
|
||||||
|
throw OOMErrorException(helpMessage)
|
||||||
|
} else if (e is RemoteException) {
|
||||||
|
throw DaemonCrashedException(e)
|
||||||
|
} else {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
log.info(executionStrategy.asFinishLogMessage)
|
log.info(executionStrategy.asFinishLogMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the files are backed up in the task action before any changes to the outputs
|
||||||
|
private fun initializeBackup(): TaskOutputsBackup? = if (parameters.snapshotsDir.isPresent) {
|
||||||
|
TaskOutputsBackup(
|
||||||
|
fileSystemOperations,
|
||||||
|
parameters.buildDir,
|
||||||
|
parameters.snapshotsDir,
|
||||||
|
parameters.taskOutputsToRestore.get(),
|
||||||
|
log,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun execute() {
|
||||||
|
val backup = initializeBackup()
|
||||||
|
val executionStrategy = workArguments.compilerExecutionSettings.strategy
|
||||||
|
try {
|
||||||
|
val result = performCompilation()
|
||||||
|
if (result == CompilationResult.COMPILATION_OOM_ERROR || result == CompilationResult.COMPILATION_ERROR) {
|
||||||
|
backup?.restoreOutputs()
|
||||||
|
}
|
||||||
|
throwExceptionIfCompilationFailed(result.asExitCode, executionStrategy)
|
||||||
|
} catch (e: FailedCompilationException) {
|
||||||
|
// Restore outputs only in cases where we expect that the user will make some changes to their project:
|
||||||
|
// - For a compilation error, the user will need to fix their source code
|
||||||
|
// - For an OOM error, the user will need to increase their memory settings
|
||||||
|
// In the other cases where there is nothing the user can fix in their project, we should not restore the outputs.
|
||||||
|
// Otherwise, the next build(s) will likely fail in exactly the same way as this build because their inputs and outputs are
|
||||||
|
// the same.
|
||||||
|
if (backup != null && (e is CompilationErrorException || e is OOMErrorException)) {
|
||||||
|
log.info("Restoring task outputs to pre-compilation state")
|
||||||
|
backup.restoreOutputs()
|
||||||
|
}
|
||||||
|
throw e
|
||||||
|
} finally {
|
||||||
|
backup?.deleteSnapshot()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// temporary adapter property
|
// temporary adapter property
|
||||||
private val CompilationResult.asExitCode
|
private val CompilationResult.asExitCode
|
||||||
get() = when (this) {
|
get() = when (this) {
|
||||||
|
|||||||
+1
-1
@@ -269,7 +269,7 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments> @Inject constr
|
|||||||
projectLayout.buildDirectory,
|
projectLayout.buildDirectory,
|
||||||
projectLayout.buildDirectory.dir("snapshot/kotlin/$name"),
|
projectLayout.buildDirectory.dir("snapshot/kotlin/$name"),
|
||||||
outputsToRestore = allOutputFiles() - taskOutputsBackupExcludes.get(),
|
outputsToRestore = allOutputFiles() - taskOutputsBackupExcludes.get(),
|
||||||
logger
|
GradleKotlinLogger(logger),
|
||||||
).also {
|
).also {
|
||||||
it.createSnapshot()
|
it.createSnapshot()
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -8,8 +8,8 @@ package org.jetbrains.kotlin.gradle.tasks
|
|||||||
import org.gradle.api.file.Directory
|
import org.gradle.api.file.Directory
|
||||||
import org.gradle.api.file.DirectoryProperty
|
import org.gradle.api.file.DirectoryProperty
|
||||||
import org.gradle.api.file.FileSystemOperations
|
import org.gradle.api.file.FileSystemOperations
|
||||||
import org.gradle.api.logging.Logger
|
|
||||||
import org.gradle.api.provider.Provider
|
import org.gradle.api.provider.Provider
|
||||||
|
import org.jetbrains.kotlin.buildtools.api.KotlinLogger
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.nio.file.FileSystems
|
import java.nio.file.FileSystems
|
||||||
@@ -34,7 +34,7 @@ internal class TaskOutputsBackup(
|
|||||||
*/
|
*/
|
||||||
val outputsToRestore: List<File>,
|
val outputsToRestore: List<File>,
|
||||||
|
|
||||||
val logger: Logger
|
val logger: KotlinLogger,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun createSnapshot() {
|
fun createSnapshot() {
|
||||||
|
|||||||
Reference in New Issue
Block a user