[Gradle] Restore writing errors into <project_dir>/.gradle directory

This is required for compatibility with older IDEA releases where it
expects errors only in <project_dir>/.gradle/kotlin/errors directory.

It is possible to disable such behaviour via
"kotlin.project.persistent.dir.gradle.disableWrite" property.

^KT-58223 Fixed
This commit is contained in:
Yahor Berdnikau
2023-11-03 11:53:41 +01:00
committed by Space Team
parent 070de9cc85
commit 656b61b945
10 changed files with 134 additions and 38 deletions
@@ -277,7 +277,10 @@ class BuildReportsIT : KGPBaseTest() {
) {
val lookupsTab = projectPath.resolve("build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/lookups.tab")
val kotlinErrorPath = projectPersistentCache.resolve("errors")
val kotlinErrorPaths = setOf(
projectPersistentCache.resolve("errors"),
projectPath.resolve(".gradle/kotlin/errors")
)
buildGradle.appendText(
"""
@@ -290,26 +293,29 @@ class BuildReportsIT : KGPBaseTest() {
)
build("compileKotlin") {
assertTrue { kotlinErrorPath.listDirectoryEntries().isEmpty() }
for (kotlinErrorPath in kotlinErrorPaths) {
assertDirectoryDoesNotExist(kotlinErrorPath)
}
assertOutputDoesNotContain("errors were stored into file")
}
val kotlinFile = kotlinSourcesDir().resolve("helloWorld.kt")
kotlinFile.modify { it.replace("ArrayList", "skjfghsjk") }
buildAndFail("compileKotlin", buildOptions = defaultBuildOptions.copy(logLevel = LogLevel.DEBUG)) {
assertOutputContains("errors were stored into file")
val files = kotlinErrorPath.listDirectoryEntries()
assertTrue { files.first().exists() }
files.first().bufferedReader().use { reader ->
val kotlinVersion = reader.readLine()
assertTrue("kotlin version should be in the error file") {
kotlinVersion != null && kotlinVersion.trim().equals("kotlin version: ${buildOptions.kotlinVersion}")
}
val errorMessage = reader.readLine()
assertTrue("Error message should start with 'error message: ' to parse it on IDEA side") {
errorMessage != null && errorMessage.trim().startsWith("error message:")
kotlinErrorPaths.forEach { kotlinErrorPath ->
val files = kotlinErrorPath.listDirectoryEntries()
assertFileExists(files.single())
files.single().bufferedReader().use { reader ->
val kotlinVersion = reader.readLine()
assertTrue("kotlin version should be in the error file") {
kotlinVersion != null && kotlinVersion.trim().equals("kotlin version: ${buildOptions.kotlinVersion}")
}
val errorMessage = reader.readLine()
assertTrue("Error message should start with 'error message: ' to parse it on IDEA side") {
errorMessage != null && errorMessage.trim().startsWith("error message:")
}
}
}
}
}
}
@@ -326,17 +332,72 @@ class BuildReportsIT : KGPBaseTest() {
projectName = "simpleProject",
gradleVersion = gradleVersion,
) {
val kotlinErrorPath = projectPersistentCache.resolve("errors")
val kotlinErrorPaths = setOf(
projectPersistentCache.resolve("errors"),
projectPath.resolve(".gradle/kotlin/errors")
)
build("compileKotlin", buildOptions = defaultBuildOptions.copy(logLevel = LogLevel.DEBUG)) {
assertOutputDoesNotContain("errors were stored into file")
assertTrue { kotlinErrorPath.listDirectoryEntries().isEmpty() }
for (kotlinErrorPath in kotlinErrorPaths) {
assertDirectoryDoesNotExist(kotlinErrorPath)
}
}
val kotlinFile = kotlinSourcesDir().resolve("helloWorld.kt")
kotlinFile.modify { it.replace("ArrayList", "skjfghsjk") }
buildAndFail("compileKotlin", buildOptions = defaultBuildOptions.copy(logLevel = LogLevel.DEBUG)) {
assertOutputDoesNotContain("errors were stored into file")
assertTrue { kotlinErrorPath.listDirectoryEntries().isEmpty() }
for (kotlinErrorPath in kotlinErrorPaths) {
assertDirectoryDoesNotExist(kotlinErrorPath)
}
}
}
}
@DisplayName("Error file is not written into .gradle/kotlin/errors")
@GradleTest
fun testDisableWritingErrorsIntoGradleProjectDir(
gradleVersion: GradleVersion,
) {
project(
projectName = "simpleProject",
gradleVersion = gradleVersion,
) {
val kotlinErrorPath = projectPersistentCache.resolve("errors")
val gradleErrorPath = projectPath.resolve(".gradle/kotlin/errors")
gradleProperties.appendText(
"""
|
|kotlin.project.persistent.dir.gradle.disableWrite=true
""".trimMargin()
)
val lookupsTab = projectPath.resolve("build/kotlin/compileKotlin/cacheable/caches-jvm/lookups/lookups.tab")
buildGradle.appendText(
//language=groovy
"""
|tasks.named("compileKotlin") {
| doLast {
| new File("${lookupsTab.toUri().path}").write("Invalid contents")
| }
|}
""".trimMargin()
)
build("compileKotlin") {
assertDirectoryDoesNotExist(kotlinErrorPath.toAbsolutePath())
assertDirectoryDoesNotExist(gradleErrorPath.toAbsolutePath())
}
val kotlinFile = kotlinSourcesDir().resolve("helloWorld.kt")
kotlinFile.modify { it.replace("ArrayList", "skjfghsjk") }
buildAndFail("compileKotlin", buildOptions = defaultBuildOptions.copy(logLevel = LogLevel.DEBUG)) {
assertOutputContains("errors were stored into file")
assertDirectoryExists(kotlinErrorPath.toAbsolutePath())
val errorFiles = kotlinErrorPath.listDirectoryEntries()
assertFileExists(errorFiles.single())
assertDirectoryDoesNotExist(gradleErrorPath.toAbsolutePath())
}
}
}
@@ -110,7 +110,7 @@ internal open class GradleCompilerRunner(
internal val sessionDirProvider = taskProvider.sessionsDir.get()
internal val projectNameProvider = taskProvider.projectName.get()
internal val incrementalModuleInfoProvider = taskProvider.buildModulesInfo
internal val errorsFile = taskProvider.errorsFile.get()
internal val errorsFiles = taskProvider.errorsFiles.get()
/**
* Compiler might be executed asynchronously. Do not do anything requiring end of compilation after this function is called.
@@ -231,7 +231,7 @@ internal open class GradleCompilerRunner(
kotlinScriptExtensions = environment.kotlinScriptExtensions,
allWarningsAsErrors = compilerArgs.allWarningsAsErrors,
compilerExecutionSettings = compilerExecutionSettings,
errorsFile = errorsFile,
errorsFiles = errorsFiles,
kotlinPluginVersion = getKotlinPluginVersion(loggerProvider),
//no need to log warnings in MessageCollector hear it will be logged by compiler
kotlinLanguageVersion = parseLanguageVersion(compilerArgs.languageVersion, compilerArgs.useK2)
@@ -74,7 +74,7 @@ internal class GradleKotlinCompilerWorkArguments(
val kotlinScriptExtensions: Array<String>,
val allWarningsAsErrors: Boolean,
val compilerExecutionSettings: CompilerExecutionSettings,
val errorsFile: File?,
val errorsFiles: Set<File>?,
val kotlinPluginVersion: String,
val kotlinLanguageVersion: KotlinVersion,
) : Serializable {
@@ -112,7 +112,7 @@ internal class GradleKotlinCompilerWork @Inject constructor(
private val metrics = if (reportingSettings.buildReportOutputs.isNotEmpty()) BuildMetricsReporterImpl() else DoNothingBuildMetricsReporter
private var icLogLines: List<String> = emptyList()
private val compilerExecutionSettings = config.compilerExecutionSettings
private val errorsFile = config.errorsFile
private val errorsFiles = config.errorsFiles
private val kotlinPluginVersion = config.kotlinPluginVersion
private val kotlinLanguageVersion = config.kotlinLanguageVersion
@@ -131,7 +131,9 @@ internal class GradleKotlinCompilerWork @Inject constructor(
if (incrementalCompilationEnvironment?.disableMultiModuleIC == true) {
incrementalCompilationEnvironment.multiModuleICSettings.buildHistoryFile.delete()
}
errorsFile?.also { gradleMessageCollector.flush(it) }
errorsFiles?.let {
gradleMessageCollector.flush(it)
}
throwExceptionIfCompilationFailed(exitCode, executionStrategy)
} finally {
@@ -52,19 +52,22 @@ class GradleErrorMessageCollector(
return errors.isNotEmpty()
}
fun flush(file: File) {
fun flush(files: Set<File>) {
if (!hasErrors()) {
return
}
file.createNewFile()
FileWriter(file).use {
kotlinPluginVersion?.also { version -> it.append("kotlin version: $version\n") }
for (error in errors) {
it.append("error message: $error\n\n")
for (file in files) {
file.parentFile.mkdirs()
file.createNewFile()
FileWriter(file).use {
kotlinPluginVersion?.also { version -> it.append("kotlin version: $version\n") }
for (error in errors) {
it.append("error message: $error\n\n")
}
it.flush()
}
it.flush()
logger.debug("${errors.count()} errors were stored into file ${file.absolutePath}")
}
logger.debug("${errors.count()} errors were stored into file ${file.absolutePath}")
clear()
}
}
@@ -554,6 +554,12 @@ internal class PropertiesProvider private constructor(private val project: Proje
val kotlinProjectPersistentDir: String?
get() = get(PropertyNames.KOTLIN_PROJECT_PERSISTENT_DIR)
/**
* Disable writing into `<project_dir>/.gradle` directory.
*/
val kotlinProjectPersistentDirGradleDisableWrite: Boolean
get() = booleanProperty(PropertyNames.KOTLIN_PROJECT_PERSISTENT_DIR_GRADLE_DISABLE_WRITE) ?: false
/**
* Retrieves a comma-separated list of browsers to use when running karma tests for [target]
* @see KOTLIN_JS_KARMA_BROWSERS
@@ -646,6 +652,7 @@ internal class PropertiesProvider private constructor(private val project: Proje
property("kotlin.internal.suppress.buildToolsApiVersionConsistencyChecks")
val KOTLIN_USER_HOME_DIR = property("kotlin.user.home")
val KOTLIN_PROJECT_PERSISTENT_DIR = property("kotlin.project.persistent.dir")
val KOTLIN_PROJECT_PERSISTENT_DIR_GRADLE_DISABLE_WRITE = property("kotlin.project.persistent.dir.gradle.disableWrite")
/**
* Internal properties: builds get big non-suppressible warning when such properties are used
@@ -12,12 +12,15 @@ import org.gradle.api.file.ProjectLayout
import org.gradle.api.logging.Logger
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Provider
import org.gradle.api.provider.SetProperty
import org.gradle.api.tasks.Internal
import org.jetbrains.kotlin.compilerRunner.GradleCompilerRunner.Companion.normalizeForFlagFile
import org.jetbrains.kotlin.gradle.incremental.IncrementalModuleInfoProvider
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.Companion.kotlinPropertiesProvider
import org.jetbrains.kotlin.gradle.utils.*
import org.jetbrains.kotlin.gradle.utils.kotlinErrorsDir
import org.jetbrains.kotlin.gradle.utils.property
import org.jetbrains.kotlin.gradle.utils.kotlinSessionsDir
import org.jetbrains.kotlin.gradle.utils.property
import java.io.File
import javax.inject.Inject
@@ -55,9 +58,19 @@ abstract class GradleCompileTaskProvider @Inject constructor(
.property(incrementalModuleInfoProvider)
@get:Internal
val errorsFile: Provider<File> = objectFactory
.property(
project.kotlinErrorsDir.also { it.mkdirs() }
.resolve("errors-${System.currentTimeMillis()}.log")
val errorsFiles: SetProperty<File> = objectFactory
.setPropertyWithValue<File>(
setOfNotNull(
project.kotlinErrorsDir
.errorFile,
if (!project.kotlinPropertiesProvider.kotlinProjectPersistentDirGradleDisableWrite) {
project.rootDir
.resolve(".gradle/kotlin/errors/")
.errorFile
} else null,
)
)
}
.chainedDisallowChanges()
}
private val File.errorFile get() = resolve("errors-${System.currentTimeMillis()}.log")
@@ -348,7 +348,7 @@ abstract class Kotlin2JsCompile @Inject constructor(
environment,
taskOutputsBackup
)
compilerRunner.errorsFile?.also { gradleMessageCollector.flush(it) }
compilerRunner.errorsFiles?.let { gradleMessageCollector.flush(it) }
}
@@ -350,7 +350,7 @@ abstract class KotlinCompile @Inject constructor(
defaultKotlinJavaToolchain.get().buildJvm.get().javaHome,
taskOutputsBackup
)
compilerRunner.errorsFile?.also { gradleMessageCollector.flush(it) }
compilerRunner.errorsFiles?.let { gradleMessageCollector.flush(it) }
}
private fun validateKotlinAndJavaHasSameTargetCompatibility(
@@ -144,6 +144,6 @@ abstract class KotlinCompileCommon @Inject constructor(
outputFiles = allOutputFiles()
)
compilerRunner.runMetadataCompilerAsync(args, environment)
compilerRunner.errorsFile?.also { gradleMessageCollector.flush(it) }
compilerRunner.errorsFiles?.let { gradleMessageCollector.flush(it) }
}
}
@@ -48,6 +48,10 @@ internal inline fun <reified T : Any?> ObjectFactory.setPropertyWithValue(
initialValue: Provider<Iterable<T>>
) = setProperty<T>().value(initialValue)
internal inline fun <reified T : Any?> ObjectFactory.setPropertyWithValue(
initialValue: Iterable<T>
) = setProperty<T>().value(initialValue)
internal inline fun <reified T : Any?> ObjectFactory.setPropertyWithLazyValue(
noinline lazyValue: () -> Iterable<T>
) = setPropertyWithValue(providerWithLazyConvention(lazyValue))
@@ -97,6 +101,12 @@ internal fun <PropType : Any?, T : Property<PropType>> T.chainedDisallowChanges(
disallowChanges()
}
internal fun <PropType : Any?, T : SetProperty<PropType>> T.chainedDisallowChanges(): T =
apply {
disallowChanges()
}
// Before 5.0 fileProperty is created via ProjectLayout
// https://docs.gradle.org/current/javadoc/org/gradle/api/model/ObjectFactory.html#fileProperty--
internal fun Project.newFileProperty(initialize: (() -> File)? = null): RegularFileProperty {