From 6039d86388fd972e66ad7af8868f28a1167b3a47 Mon Sep 17 00:00:00 2001 From: Andrey Uskov Date: Sun, 28 Apr 2019 18:01:23 +0300 Subject: [PATCH] Importing projects with granular metadata is partially supported (except k-lib data format of metadata libraries and multiple target platforms per source set) --- .../src/KotlinMPPGradleModel.kt | 9 +- .../src/KotlinMPPGradleModelBuilder.kt | 160 +++++++++++++++--- 2 files changed, 138 insertions(+), 31 deletions(-) diff --git a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModel.kt b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModel.kt index 590eefa3aa5..2c25c23a23b 100644 --- a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModel.kt +++ b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModel.kt @@ -9,18 +9,17 @@ import org.jetbrains.plugins.gradle.model.ExternalDependency import org.jetbrains.plugins.gradle.model.ModelFactory import java.io.File import java.io.Serializable -import java.util.* typealias KotlinDependency = ExternalDependency fun KotlinDependency.deepCopy(cache: MutableMap): KotlinDependency { val cachedValue = cache[this] as? KotlinDependency - if (cachedValue != null) { - return cachedValue + return if (cachedValue != null) { + cachedValue } else { val result = ModelFactory.createCopy(this) cache[this] = result - return result + result } } @@ -41,6 +40,8 @@ interface KotlinSourceSet : KotlinModule { const val COMMON_MAIN_SOURCE_SET_NAME = "commonMain" const val COMMON_TEST_SOURCE_SET_NAME = "commonTest" + // Note. This method could not be deleted due to usage in KotlinAndroidGradleMPPModuleDataService from IDEA Core + @Suppress("unused") fun commonName(forTests: Boolean) = if (forTests) COMMON_TEST_SOURCE_SET_NAME else COMMON_MAIN_SOURCE_SET_NAME } } diff --git a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt index 5641fbf76a3..ce1513ad727 100644 --- a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt +++ b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt @@ -127,22 +127,25 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { dependencyResolver: DependencyResolver, configurationNameAccessor: String, scope: String, - project: Project + project: Project, + metadataDependencyTransformationBuilder: MetadataDependencyTransformationBuilder ): Collection { val dependencyHolderClass = dependencyHolder.javaClass val getConfigurationName = dependencyHolderClass.getMethodOrNull(configurationNameAccessor) ?: return emptyList() val configurationName = getConfigurationName(dependencyHolder) as? String ?: return emptyList() val configuration = project.configurations.findByName(configurationName) ?: return emptyList() + @Suppress("UnstableApiUsage") if (!configuration.isCanBeResolved) return emptyList() - val dependencyAdjuster = DependencyAdjuster(configuration, scope, project) + val dependencyAdjuster = + DependencyAdjuster(configuration, scope, project, metadataDependencyTransformationBuilder.getTransformations(configurationName)) val resolvedDependencies = dependencyResolver .resolveDependencies(configuration) .apply { forEach { (it as? AbstractExternalDependency)?.scope = scope } } - .flatMap(dependencyAdjuster::adjustDependency) + .flatMap { dependencyAdjuster.adjustDependency(it) } val singleDependencyFiles = resolvedDependencies.mapNotNullTo(LinkedHashSet()) { (it as? FileCollectionDependency)?.files?.singleOrNull() } @@ -178,13 +181,13 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { val platform = KotlinPlatform.byId(platformId) ?: return null val disambiguationClassifier = getDisambiguationClassifier(gradleTarget) as? String val getPreset = targetClass.getMethodOrNull("getPreset") - var targetPresetName: String? - try { + val targetPresetName: String? + targetPresetName = try { val targetPreset = getPreset?.invoke(gradleTarget) val getPresetName = targetPreset?.javaClass?.getMethodOrNull("getName") - targetPresetName = getPresetName?.invoke(targetPreset) as? String + getPresetName?.invoke(targetPreset) as? String } catch (e: Throwable) { - targetPresetName = "${e::class.java.name}:${e.message}" + "${e::class.java.name}:${e.message}" } @Suppress("UNCHECKED_CAST") val gradleCompilations = @@ -243,33 +246,86 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { project: Project ): Set { return LinkedHashSet().apply { + val transformationBuilder = MetadataDependencyTransformationBuilder(gradleCompilation) this += buildDependencies( - gradleCompilation, dependencyResolver, "getCompileDependencyConfigurationName", "COMPILE", project + gradleCompilation, dependencyResolver, "getCompileDependencyConfigurationName", "COMPILE", project, transformationBuilder ) this += buildDependencies( - gradleCompilation, dependencyResolver, "getRuntimeDependencyConfigurationName", "RUNTIME", project + gradleCompilation, dependencyResolver, "getRuntimeDependencyConfigurationName", "RUNTIME", project, transformationBuilder ) this += sourceSetMap[compilationFullName(gradleCompilation.name, classifier)]?.dependencies ?: emptySet() } } + private class MetadataDependencyTransformationBuilder(val sourceSet: Any) { + val transformationsMethod = sourceSet.javaClass.getMethodOrNull("getDependenciesTransformation", String::class.java) + + class KotlinMetadataDependencyTransformation( + val groupId: String?, + val moduleName: String, + val projectPath: String?, + val allVisibleSourceSets: Set, + val useFilesForSourceSets: Map> + ) { + constructor( + transformation: Any, + group: Method, + module: Method, + projectPath: Method, + visibleSourceSets: Method, + useFilesForSourceSets: Method + ) : this( + group(transformation) as String?, + module(transformation) as String, + projectPath(transformation) as String?, + visibleSourceSets(transformation) as Set, + useFilesForSourceSets(transformation) as Map> + ) + } + + fun getTransformations(configurationName: String): Collection { + val transformations = transformationsMethod?.invoke(sourceSet, configurationName) as? Iterable ?: return emptyList() + val transformationClass = transformations.firstOrNull()?.javaClass + ?: return emptyList() + + val getGroupId = transformationClass.getMethodOrNull("getGroupId") ?: return emptyList() + val getModuleName = transformationClass.getMethodOrNull("getModuleName") ?: return emptyList() + val getProjectPath = transformationClass.getMethodOrNull("getProjectPath") ?: return emptyList() + val getAllVisibleSourceSets = transformationClass.getMethodOrNull("getAllVisibleSourceSets") ?: return emptyList() + val getUseFilesForSourceSets = transformationClass.getMethodOrNull("getUseFilesForSourceSets") ?: return emptyList() + + return transformations.map { transformation -> + KotlinMetadataDependencyTransformation( + transformation, + getGroupId, + getModuleName, + getProjectPath, + getAllVisibleSourceSets, + getUseFilesForSourceSets + ) + }.filter { it.allVisibleSourceSets.isNotEmpty() } + } + } + + private fun buildSourceSetDependencies( gradleSourceSet: Named, dependencyResolver: DependencyResolver, project: Project ): Set { return LinkedHashSet().apply { + val transformationBuilder = MetadataDependencyTransformationBuilder(gradleSourceSet) this += buildDependencies( - gradleSourceSet, dependencyResolver, "getApiMetadataConfigurationName", "COMPILE", project + gradleSourceSet, dependencyResolver, "getApiMetadataConfigurationName", "COMPILE", project, transformationBuilder ) this += buildDependencies( - gradleSourceSet, dependencyResolver, "getImplementationMetadataConfigurationName", "COMPILE", project + gradleSourceSet, dependencyResolver, "getImplementationMetadataConfigurationName", "COMPILE", project, transformationBuilder ) this += buildDependencies( - gradleSourceSet, dependencyResolver, "getCompileOnlyMetadataConfigurationName", "COMPILE", project + gradleSourceSet, dependencyResolver, "getCompileOnlyMetadataConfigurationName", "COMPILE", project, transformationBuilder ) this += buildDependencies( - gradleSourceSet, dependencyResolver, "getRuntimeOnlyMetadataConfigurationName", "RUNTIME", project + gradleSourceSet, dependencyResolver, "getRuntimeOnlyMetadataConfigurationName", "RUNTIME", project, transformationBuilder ) } } @@ -300,6 +356,7 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { return (getCompileClasspath(compileKotlinTask) as? Collection)?.map { it.path } ?: emptyList() } + @Suppress("UnstableApiUsage") private fun buildCompilationOutput( gradleCompilation: Named, compileKotlinTask: Task @@ -359,13 +416,19 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { } } + @Suppress("UnstableApiUsage") private class DependencyAdjuster( private val configuration: Configuration, private val scope: String, - private val project: Project + private val project: Project, + transformations: Collection ) { private val adjustmentMap = HashMap>() + private val projectDependencyTransformation = + transformations.filter { it.projectPath != null }.associateBy { it.projectPath } + //TODO + val dependenciesByProjectPath by lazy { configuration .resolvedConfiguration @@ -384,17 +447,61 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { return DefaultExternalProjectDependency(dependency).apply { this.configurationName = newConfigurationName - val nestedDependencies = this.dependencies.flatMap(::adjustDependency) + val nestedDependencies = this.dependencies.flatMap { adjustDependency(it) } this.dependencies.clear() this.dependencies.addAll(nestedDependencies) } } - fun adjustDependency(dependency: ExternalDependency): List { + private val libraryDependencyTransformation = + transformations.filter { it.projectPath == null }.associateBy { it.groupId to it.moduleName } + + private fun adjustLibraryDependency(dependency: ExternalDependency, parentScope: String? = null): List = + when (dependency) { + is ExternalLibraryDependency -> { + val replaceFiles = libraryDependencyTransformation[dependency.id.group to dependency.id.name]?.useFilesForSourceSets + when { + replaceFiles != null -> replaceFiles.flatMap { (sourceSetName, replaceFiles) -> + replaceFiles.map { replaceFile -> + DefaultExternalLibraryDependency(dependency).apply { + // Transitive dependencies don't have their scope set properly; TODO investigate may be IJ bug? + scope = dependency.scope ?: parentScope + + classifier = sourceSetName + file = replaceFile + + val adjustedDependencies = + dependency.dependencies.flatMap { adjustDependency(it, dependency.scope ?: parentScope) } + + dependencies.clear() + dependencies.addAll(adjustedDependencies) + } + } + } + else -> + listOf( + // Do nothing but set the correct scope for this dependency if needed and adjust recursively: + DefaultExternalLibraryDependency(dependency).apply { + scope = dependency.scope ?: parentScope + + val adjustedDependencies = + dependency.dependencies.flatMap { adjustDependency(it, dependency.scope ?: parentScope) } + + dependencies.clear() + dependencies.addAll(adjustedDependencies) + } + ) + } + } + else -> listOf(dependency) + } + + fun adjustDependency(dependency: ExternalDependency, parentScope: String? = null): List { return adjustmentMap.getOrPut(dependency) { - if (dependency !is ExternalProjectDependency - || dependency.configurationName != Dependency.DEFAULT_CONFIGURATION - ) return@getOrPut listOf(dependency) + if (dependency !is ExternalProjectDependency) + return@getOrPut adjustLibraryDependency(dependency, parentScope) + if (dependency.configurationName != Dependency.DEFAULT_CONFIGURATION) + return@getOrPut listOf(dependency) val artifacts = dependenciesByProjectPath[dependency.projectPath] ?: return@getOrPut listOf(dependency) val artifactConfiguration = artifacts.mapTo(LinkedHashSet()) { it.first.configuration @@ -420,8 +527,13 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { val platformDependency = if (classifier != KotlinTarget.METADATA_TARGET_NAME) { wrapDependency(dependency, compilationFullName(KotlinCompilation.MAIN_COMPILATION_NAME, classifier)) } else null - val commonDependency = wrapDependency(dependency, KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME) - return if (platformDependency != null) listOf(platformDependency, commonDependency) else listOf(commonDependency) + val commonDependencies = if (dependencyProject.path in projectDependencyTransformation) { + val visibleSourceSets = projectDependencyTransformation.getValue(dependencyProject.path).allVisibleSourceSets + visibleSourceSets.map { sourceSetName -> wrapDependency(dependency, sourceSetName) } + } else { + listOf(wrapDependency(dependency, KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME)) + } + return if (platformDependency != null) listOf(platformDependency) + commonDependencies else commonDependencies } } } @@ -446,9 +558,3 @@ private fun Project.getChildProjectByPath(path: String): Project? { return project } -private fun Project.getTargets(): Collection? { - val kotlinExt = project.extensions.findByName("kotlin") ?: return null - val getTargets = kotlinExt.javaClass.getMethodOrNull("getTargets") ?: return null - @Suppress("UNCHECKED_CAST") - return (getTargets.invoke(kotlinExt) as? NamedDomainObjectContainer)?.asMap?.values ?: emptyList() -} \ No newline at end of file