diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildReportsIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildReportsIT.kt index 8312ae4bf42..93cc85104e8 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildReportsIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildReportsIT.kt @@ -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()) } } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/compilerRunner/GradleKotlinCompilerRunner.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/compilerRunner/GradleKotlinCompilerRunner.kt index cc6c3d5a8b6..e1bf154c9b9 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/compilerRunner/GradleKotlinCompilerRunner.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/compilerRunner/GradleKotlinCompilerRunner.kt @@ -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) diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/compilerRunner/GradleKotlinCompilerWork.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/compilerRunner/GradleKotlinCompilerWork.kt index 6e4158e3362..95feedff56b 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/compilerRunner/GradleKotlinCompilerWork.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/compilerRunner/GradleKotlinCompilerWork.kt @@ -74,7 +74,7 @@ internal class GradleKotlinCompilerWorkArguments( val kotlinScriptExtensions: Array, val allWarningsAsErrors: Boolean, val compilerExecutionSettings: CompilerExecutionSettings, - val errorsFile: File?, + val errorsFiles: Set?, 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 = 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 { diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/logging/GradleErrorMessageCollector.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/logging/GradleErrorMessageCollector.kt index bd60d93b935..9ec193a9333 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/logging/GradleErrorMessageCollector.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/logging/GradleErrorMessageCollector.kt @@ -52,19 +52,22 @@ class GradleErrorMessageCollector( return errors.isNotEmpty() } - fun flush(file: File) { + fun flush(files: Set) { 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() } } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt index 2e686026f6a..8426795b559 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt @@ -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 `/.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 diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/GradleCompileTaskProvider.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/GradleCompileTaskProvider.kt index 4bc94ce2359..46716f5645d 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/GradleCompileTaskProvider.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/GradleCompileTaskProvider.kt @@ -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 = objectFactory - .property( - project.kotlinErrorsDir.also { it.mkdirs() } - .resolve("errors-${System.currentTimeMillis()}.log") + val errorsFiles: SetProperty = objectFactory + .setPropertyWithValue( + setOfNotNull( + project.kotlinErrorsDir + .errorFile, + if (!project.kotlinPropertiesProvider.kotlinProjectPersistentDirGradleDisableWrite) { + project.rootDir + .resolve(".gradle/kotlin/errors/") + .errorFile + } else null, + ) ) -} \ No newline at end of file + .chainedDisallowChanges() +} + +private val File.errorFile get() = resolve("errors-${System.currentTimeMillis()}.log") \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/Kotlin2JsCompile.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/Kotlin2JsCompile.kt index c3b23e4f67f..f06c2e26af4 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/Kotlin2JsCompile.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/Kotlin2JsCompile.kt @@ -348,7 +348,7 @@ abstract class Kotlin2JsCompile @Inject constructor( environment, taskOutputsBackup ) - compilerRunner.errorsFile?.also { gradleMessageCollector.flush(it) } + compilerRunner.errorsFiles?.let { gradleMessageCollector.flush(it) } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/KotlinCompile.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/KotlinCompile.kt index 77c1bc3a3d6..99fee392533 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/KotlinCompile.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/KotlinCompile.kt @@ -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( diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/KotlinCompileCommon.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/KotlinCompileCommon.kt index bf6d975bda3..e42acfafc2d 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/KotlinCompileCommon.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/KotlinCompileCommon.kt @@ -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) } } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/providerApiUtils.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/providerApiUtils.kt index 8dfd2623951..50d642a04d2 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/providerApiUtils.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/providerApiUtils.kt @@ -48,6 +48,10 @@ internal inline fun ObjectFactory.setPropertyWithValue( initialValue: Provider> ) = setProperty().value(initialValue) +internal inline fun ObjectFactory.setPropertyWithValue( + initialValue: Iterable +) = setProperty().value(initialValue) + internal inline fun ObjectFactory.setPropertyWithLazyValue( noinline lazyValue: () -> Iterable ) = setPropertyWithValue(providerWithLazyConvention(lazyValue)) @@ -97,6 +101,12 @@ internal fun > T.chainedDisallowChanges( disallowChanges() } +internal fun > 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 {