Store processed metadata artifacts in .gradle/ rather than build/

This allows the files to survive Gradle's `clean` task that deletes the
`build/` directory with all its contents, which leads to the IDE losing
dependencies until next re-import.

The `.gradle` directory is chosen because Gradle creates it in every
project and it is normally included in .gitignore (or otherwise ignored
in VCS).

Related to issue #KT-39568
This commit is contained in:
Sergey Igushkin
2020-10-08 22:33:35 +03:00
parent dca1f4631c
commit 53004f97b1
4 changed files with 68 additions and 8 deletions
@@ -103,6 +103,14 @@ class HierarchicalMppIT : BaseGradleIT() {
assertTrue("$it") { it.newVisibleSourceSets == emptySet<String>() }
}
}
// ALso check that the files produced by dependency transformations survive a clean build:
val existingFilesFromReports = reports.flatMap { it.useFiles }.filter { it.isFile }
assertTrue { existingFilesFromReports.isNotEmpty() }
build("clean") {
assertSuccessful()
existingFilesFromReports.forEach { assertTrue("Expected that $it exists after clean build.") { it.isFile } }
}
}
// --- Move the dependency from jvmAndJsMain to commonMain, expect that it is now propagated to commonTest:
@@ -582,7 +590,8 @@ class HierarchicalMppIT : BaseGradleIT() {
scope,
it.groupId + ":" + it.moduleName,
it.allVisibleSourceSets.joinToString(","),
it.useFilesForSourceSets.keys.joinToString(",")
it.useFilesForSourceSets.keys.joinToString(","),
it.useFilesForSourceSets.values.flatten().joinToString(",")
)
println(" " + line.joinToString(" :: "))
@@ -613,7 +622,8 @@ class HierarchicalMppIT : BaseGradleIT() {
val scope: String,
val groupAndModule: String,
val allVisibleSourceSets: Set<String>,
val newVisibleSourceSets: Set<String> // those which the dependsOn parents don't see
val newVisibleSourceSets: Set<String>, // those which the dependsOn parents don't see
val useFiles: List<File>
) {
val isExcluded: Boolean get() = allVisibleSourceSets.isEmpty()
@@ -622,14 +632,17 @@ class HierarchicalMppIT : BaseGradleIT() {
const val TEST_OUTPUT_COMPONENT_SEPARATOR = " :: "
const val TEST_OUTPUT_ITEMS_SEPARATOR = ","
private operator fun <T> List<T>.component6() = this[5]
fun parseTestOutputLine(line: String): DependencyTransformationReport {
val tail = line.substringAfter(TEST_OUTPUT_MARKER + TEST_OUTPUT_COMPONENT_SEPARATOR)
val (sourceSetName, scope, groupAndModule, allVisibleSourceSets, newVisibleSourceSets) =
val (sourceSetName, scope, groupAndModule, allVisibleSourceSets, newVisibleSourceSets, useFiles) =
tail.split(TEST_OUTPUT_COMPONENT_SEPARATOR)
return DependencyTransformationReport(
sourceSetName, scope, groupAndModule,
allVisibleSourceSets.split(TEST_OUTPUT_ITEMS_SEPARATOR).filter { it.isNotEmpty() }.toSet(),
newVisibleSourceSets.split(TEST_OUTPUT_ITEMS_SEPARATOR).filter { it.isNotEmpty() }.toSet()
newVisibleSourceSets.split(TEST_OUTPUT_ITEMS_SEPARATOR).filter { it.isNotEmpty() }.toSet(),
useFiles.split(TEST_OUTPUT_ITEMS_SEPARATOR).map { File(it) }
)
}
}
@@ -29,18 +29,16 @@ import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.internal.customizeKotlinDependencies
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMultiplatformPlugin.Companion.sourceSetFreeCompilerArgsPropertyName
import org.jetbrains.kotlin.gradle.plugin.sources.*
import org.jetbrains.kotlin.gradle.plugin.sources.DefaultLanguageSettingsBuilder
import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope
import org.jetbrains.kotlin.gradle.plugin.sources.checkSourceSetVisibilityRequirements
import org.jetbrains.kotlin.gradle.plugin.sources.sourceSetDependencyConfigurationByScope
import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService
import org.jetbrains.kotlin.gradle.scripting.internal.ScriptingGradleSubplugin
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTargetPreset
import org.jetbrains.kotlin.gradle.targets.metadata.isKotlinGranularMetadataEnabled
import org.jetbrains.kotlin.gradle.tasks.locateOrRegisterTask
import org.jetbrains.kotlin.gradle.tasks.locateTask
import org.jetbrains.kotlin.gradle.tasks.registerTask
import org.jetbrains.kotlin.gradle.tasks.withType
import org.jetbrains.kotlin.gradle.utils.*
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.konan.target.KonanTarget.*
@@ -111,6 +109,12 @@ class KotlinMultiplatformPlugin(
project.pluginManager.apply(ScriptingGradleSubplugin::class.java)
exportProjectStructureMetadataForOtherBuilds(project)
SingleActionPerBuild.run(project.rootProject, "cleanup-processed-metadata") {
project.gradle.buildFinished {
SourceSetMetadataStorageForIde.cleanupStaleEntries(project)
}
}
}
private fun exportProjectStructureMetadataForOtherBuilds(
@@ -155,7 +155,8 @@ class DefaultKotlinSourceSet(
?.associateBy { ModuleIds.fromComponent(project, it.dependency) }
?: emptyMap()
val baseDir = project.buildDir.resolve("tmp/kotlinMetadata/$name/${scope.scopeName}")
val baseDir = SourceSetMetadataStorageForIde.sourceSetStorage(project, this@DefaultKotlinSourceSet.name)
if (metadataDependencyResolutionByModule.values.any { it is MetadataDependencyResolution.ChooseVisibleSourceSets }) {
if (baseDir.isDirectory) {
baseDir.deleteRecursively()
@@ -0,0 +1,42 @@
/*
* Copyright 2010-2020 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.gradle.plugin.sources
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull
import java.io.File
object SourceSetMetadataStorageForIde {
fun cleanupStaleEntries(project: Project) {
val projectStorageDirectories = project.rootProject.allprojects.associateBy { projectStorage(it) }
getStorageRoot(project).listFiles().orEmpty().filter { it.isDirectory }.forEach { directory ->
// If no project corresponds to the directory, remove the directory
if (directory !in projectStorageDirectories) {
directory.deleteRecursively()
} else {
// Under the project's directory, delete subdirectories that don't correspond to any source set:
val sourceSets = projectStorageDirectories.getValue(directory)?.project?.multiplatformExtensionOrNull?.sourceSets.orEmpty()
val sourceSetNames = sourceSets.map { it.name }
directory.listFiles().orEmpty().filter { it.isDirectory }.forEach { subdirectory ->
if (subdirectory.name !in sourceSetNames)
subdirectory.deleteRecursively()
}
}
}
}
private fun getStorageRoot(project: Project): File = project.rootDir.resolve(".gradle/kotlin/sourceSetMetadata")
private fun projectStorage(project: Project): File {
val projectPathSegments = generateSequence(project) { it.parent }.map { it.name }
return getStorageRoot(project).resolve(
// Escape dots in project names to avoid ambiguous paths.
projectPathSegments.joinToString(".") { it.replace(".", "_.") }
)
}
fun sourceSetStorage(project: Project, sourceSetName: String) = projectStorage(project).resolve(sourceSetName)
}