From 792fae4afc4177d016a5a1e5c9728de063dbb8f5 Mon Sep 17 00:00:00 2001 From: Andrey Uskov Date: Tue, 2 Apr 2019 10:40:46 +0300 Subject: [PATCH] Optimize memory usage during import of MPP projects #KT-30767 Fixed --- .../KotlinGradleProjectResolverExtension.kt | 2 +- .../KotlinMPPGradleProjectResolver.kt | 29 ++++++++++---- .../src/KotlinMPPGradleModel.kt | 12 +++++- .../src/KotlinMPPGradleModelImpl.kt | 38 ++++++++++++++----- 4 files changed, 61 insertions(+), 20 deletions(-) diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinGradleProjectResolverExtension.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinGradleProjectResolverExtension.kt index 0e0c6588443..340ec58da80 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinGradleProjectResolverExtension.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinGradleProjectResolverExtension.kt @@ -196,7 +196,7 @@ class KotlinGradleProjectResolverExtension : AbstractProjectResolverExtension() if (LOG.isDebugEnabled) { LOG.debug("Start populate module dependencies. Gradle module: [$gradleModule], Ide module: [$ideModule], Ide project: [$ideProject]") } - val mppModel = resolverCtx.getExtraProject(gradleModule, KotlinMPPGradleModel::class.java)?.let { KotlinMPPGradleModelImpl(it) } + val mppModel = resolverCtx.getMppModel(gradleModule) if (mppModel != null) { mppModel.targets.forEach { target -> KotlinStatisticsTrigger.trigger( diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinMPPGradleProjectResolver.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinMPPGradleProjectResolver.kt index e4947eb9270..3a85fd44a2a 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinMPPGradleProjectResolver.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinMPPGradleProjectResolver.kt @@ -45,6 +45,7 @@ import org.jetbrains.plugins.gradle.service.project.GradleProjectResolverUtil.ge import org.jetbrains.plugins.gradle.service.project.ProjectResolverContext import org.jetbrains.plugins.gradle.util.GradleConstants import java.io.File +import java.util.* @Order(ExternalSystemConstants.UNORDERED + 1) open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { @@ -83,7 +84,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { super.populateModuleCompileOutputSettings(gradleModule, ideModule) } - val mppModel = resolverCtx.getExtraProject(gradleModule, KotlinMPPGradleModel::class.java)?.let { KotlinMPPGradleModelImpl(it) } ?: return + val mppModel = resolverCtx.getMppModel(gradleModule) ?: return val ideaOutDir = File(ideModule.data.linkedExternalProjectPath, "out") val projectDataNode = ideModule.getDataNode(ProjectKeys.PROJECT)!! val moduleOutputsMap = projectDataNode.getUserData(MODULES_OUTPUTS)!! @@ -153,6 +154,8 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { } companion object { + val proxyObjectCloningCache = WeakHashMap() + fun initializeModuleData( gradleModule: IdeaModule, mainModuleNode: DataNode, @@ -164,7 +167,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { val mainModuleFileDirectoryPath = mainModuleData.moduleFileDirectoryPath val externalProject = resolverCtx.getExtraProject(gradleModule, ExternalProject::class.java) - val mppModel = resolverCtx.getExtraProject(gradleModule, KotlinMPPGradleModel::class.java)?.let { KotlinMPPGradleModelImpl(it) } + val mppModel = resolverCtx.getMppModel(gradleModule) if (mppModel == null || externalProject == null) return val jdkName = gradleModule.jdkNameIfAny @@ -311,8 +314,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { ideModule: DataNode, resolverCtx: ProjectResolverContext ) { - val mppModel = - resolverCtx.getExtraProject(gradleModule, KotlinMPPGradleModel::class.java)?.let { KotlinMPPGradleModelImpl(it) } ?: return + val mppModel = resolverCtx.getMppModel(gradleModule) ?: return if (resolverCtx.getExtraProject(gradleModule, ExternalProject::class.java) == null) return processSourceSets(gradleModule, mppModel, ideModule, resolverCtx) { dataNode, sourceSet -> if (dataNode == null || sourceSet.platform == KotlinPlatform.ANDROID) return@processSourceSets @@ -339,8 +341,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { ideModule: DataNode, resolverCtx: ProjectResolverContext ) { - val mppModel = - resolverCtx.getExtraProject(gradleModule, KotlinMPPGradleModel::class.java)?.let { KotlinMPPGradleModelImpl(it) } ?: return + val mppModel = resolverCtx.getMppModel(gradleModule) ?: return val sourceSetMap = ideProject.getUserData(GradleProjectResolver.RESOLVED_SOURCE_SETS) ?: return val artifactsMap = ideProject.getUserData(CONFIGURATION_ARTIFACTS) ?: return val substitutor = KotlinNativeLibrariesDependencySubstitutor(mppModel, gradleModule, resolverCtx) @@ -615,7 +616,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { resolverCtx: ProjectResolverContext ): KotlinSourceSetInfo? { if (sourceSet.platform.isNotSupported()) return null - return KotlinSourceSetInfo(KotlinSourceSetImpl(sourceSet)).also { info -> + return KotlinSourceSetInfo(sourceSet).also { info -> val languageSettings = sourceSet.languageSettings info.moduleId = getKotlinModuleId(gradleModule, sourceSet, resolverCtx) info.gradleModuleId = getModuleId(resolverCtx, gradleModule) @@ -645,7 +646,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { resolverCtx: ProjectResolverContext ): KotlinSourceSetInfo? { if (compilation.platform.isNotSupported()) return null - return KotlinSourceSetInfo(KotlinCompilationImpl(compilation)).also { sourceSetInfo -> + return KotlinSourceSetInfo(compilation).also { sourceSetInfo -> sourceSetInfo.moduleId = getKotlinModuleId(gradleModule, compilation, resolverCtx) sourceSetInfo.gradleModuleId = getModuleId(resolverCtx, gradleModule) sourceSetInfo.platform = compilation.platform @@ -697,3 +698,15 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { ) ?: false } } + +fun ProjectResolverContext.getMppModel(gradleModule: IdeaModule): KotlinMPPGradleModel? = + this.getExtraProject(gradleModule, KotlinMPPGradleModel::class.java) + ?.let { kotlinMppModel -> + KotlinMPPGradleProjectResolver.proxyObjectCloningCache[kotlinMppModel] as? KotlinMPPGradleModelImpl ?: KotlinMPPGradleModelImpl( + kotlinMppModel, + KotlinMPPGradleProjectResolver.proxyObjectCloningCache + ).also { + KotlinMPPGradleProjectResolver.proxyObjectCloningCache[kotlinMppModel] = it + } + } + diff --git a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModel.kt b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModel.kt index a30e998d1bd..94c4cb238dd 100644 --- a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModel.kt +++ b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModel.kt @@ -9,10 +9,20 @@ 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(): KotlinDependency = ModelFactory.createCopy(this) +fun KotlinDependency.deepCopy(cache: MutableMap): KotlinDependency { + val cachedValue = cache[this] as? KotlinDependency + if (cachedValue != null) { + return cachedValue + } else { + val result = ModelFactory.createCopy(this) + cache[this] = result + return result + } +} interface KotlinModule : Serializable { val name: String diff --git a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelImpl.kt b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelImpl.kt index c47c59cf1ca..2f10a9de0d3 100644 --- a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelImpl.kt +++ b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelImpl.kt @@ -6,6 +6,7 @@ package org.jetbrains.kotlin.gradle import java.io.File +import java.util.* data class KotlinSourceSetImpl( override val name: String, @@ -18,12 +19,12 @@ data class KotlinSourceSetImpl( val defaultIsTestModule: Boolean = false ) : KotlinSourceSet { - constructor(kotlinSourceSet: KotlinSourceSet) : this( + constructor(kotlinSourceSet: KotlinSourceSet, cloningCache: MutableMap) : this( kotlinSourceSet.name, KotlinLanguageSettingsImpl(kotlinSourceSet.languageSettings), HashSet(kotlinSourceSet.sourceDirs), HashSet(kotlinSourceSet.resourceDirs), - kotlinSourceSet.dependencies.map { it.deepCopy() }.toSet(), + kotlinSourceSet.dependencies.map { it.deepCopy(cloningCache) }.toSet(), HashSet(kotlinSourceSet.dependsOnSourceSets), kotlinSourceSet.platform, kotlinSourceSet.isTestModule @@ -87,10 +88,14 @@ data class KotlinCompilationImpl( ) : KotlinCompilation { // create deep copy - constructor(kotlinCompilation: KotlinCompilation) : this( + constructor(kotlinCompilation: KotlinCompilation, cloningCache: MutableMap) : this( kotlinCompilation.name, - kotlinCompilation.sourceSets.map { KotlinSourceSetImpl(it) }.toList(), - kotlinCompilation.dependencies.map { it.deepCopy() }.toSet(), + kotlinCompilation.sourceSets.map { initialSourceSet -> + (cloningCache[initialSourceSet] as? KotlinSourceSet) ?: KotlinSourceSetImpl(initialSourceSet, cloningCache).also { + cloningCache[initialSourceSet] = it + } + }.toList(), + kotlinCompilation.dependencies.map { it.deepCopy(cloningCache) }.toSet(), KotlinCompilationOutputImpl(kotlinCompilation.output), KotlinCompilationArgumentsImpl(kotlinCompilation.arguments), ArrayList(kotlinCompilation.dependencyClasspath) @@ -126,12 +131,16 @@ data class KotlinTargetImpl( ) : KotlinTarget { override fun toString() = name - constructor(target: KotlinTarget) : this( + constructor(target: KotlinTarget, cloningCache: MutableMap) : this( target.name, target.presetName, target.disambiguationClassifier, KotlinPlatform.byId(target.platform.id) ?: KotlinPlatform.COMMON, - target.compilations.map { KotlinCompilationImpl(it) }.toList(), + target.compilations.map { initialCompilation -> + (cloningCache[initialCompilation] as? KotlinCompilation) ?: KotlinCompilationImpl(initialCompilation, cloningCache).also { + cloningCache[initialCompilation] = it + } + }.toList(), KotlinTargetJarImpl(target.jar?.archiveFile) ) } @@ -147,9 +156,18 @@ data class KotlinMPPGradleModelImpl( override val kotlinNativeHome: String ) : KotlinMPPGradleModel { - constructor(mppModel: KotlinMPPGradleModel) : this( - mppModel.sourceSets.mapValues { KotlinSourceSetImpl(it.value) }, - mppModel.targets.map { KotlinTargetImpl(it) }.toList(), + constructor(mppModel: KotlinMPPGradleModel, cloningCache: MutableMap) : this( + mppModel.sourceSets.mapValues { initialSourceSet -> + (cloningCache[initialSourceSet] as? KotlinSourceSet) ?: KotlinSourceSetImpl( + initialSourceSet.value, + cloningCache + ).also { cloningCache[initialSourceSet] = it } + }, + mppModel.targets.map { initialTarget -> + (cloningCache[initialTarget] as? KotlinTarget) ?: KotlinTargetImpl(initialTarget, cloningCache).also { + cloningCache[initialTarget] = it + } + }.toList(), ExtraFeaturesImpl(mppModel.extraFeatures.coroutinesState), mppModel.kotlinNativeHome )