Importing projects with granular metadata is partially supported

(except k-lib data format of metadata libraries and multiple target
platforms per source set)
This commit is contained in:
Andrey Uskov
2019-04-28 18:01:23 +03:00
parent 998a24414a
commit 6039d86388
2 changed files with 138 additions and 31 deletions
@@ -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<Any, Any>): 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
}
}
@@ -127,22 +127,25 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService {
dependencyResolver: DependencyResolver,
configurationNameAccessor: String,
scope: String,
project: Project
project: Project,
metadataDependencyTransformationBuilder: MetadataDependencyTransformationBuilder
): Collection<KotlinDependency> {
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<ExternalDependency?> { (it as? AbstractExternalDependency)?.scope = scope }
}
.flatMap(dependencyAdjuster::adjustDependency)
.flatMap { dependencyAdjuster.adjustDependency(it) }
val singleDependencyFiles = resolvedDependencies.mapNotNullTo(LinkedHashSet<File>()) {
(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<KotlinDependency> {
return LinkedHashSet<KotlinDependency>().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<String>,
val useFilesForSourceSets: Map<String, Iterable<File>>
) {
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<String>,
useFilesForSourceSets(transformation) as Map<String, Iterable<File>>
)
}
fun getTransformations(configurationName: String): Collection<KotlinMetadataDependencyTransformation> {
val transformations = transformationsMethod?.invoke(sourceSet, configurationName) as? Iterable<Any> ?: 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<KotlinDependency> {
return LinkedHashSet<KotlinDependency>().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<File>)?.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<MetadataDependencyTransformationBuilder.KotlinMetadataDependencyTransformation>
) {
private val adjustmentMap = HashMap<ExternalDependency, List<ExternalDependency>>()
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<ExternalDependency> {
private val libraryDependencyTransformation =
transformations.filter { it.projectPath == null }.associateBy { it.groupId to it.moduleName }
private fun adjustLibraryDependency(dependency: ExternalDependency, parentScope: String? = null): List<ExternalDependency> =
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<ExternalDependency> {
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<Named>? {
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<Named>)?.asMap?.values ?: emptyList()
}