From 17059682e9cdc3a7e51945b8684aa30057cb8570 Mon Sep 17 00:00:00 2001 From: Vyacheslav Karpukhin Date: Wed, 9 Oct 2019 16:58:58 +0200 Subject: [PATCH 01/11] Android import: initial implementation --- .../KotlinMPPGradleProjectResolver.kt | 12 +- .../src/KotlinMPPGradleModelBuilder.kt | 16 ++- .../kotlin-gradle-plugin/build.gradle.kts | 22 ++- .../dsl/KotlinMultiplatformExtension.kt | 127 ++++++++++++++++++ 4 files changed, 160 insertions(+), 17 deletions(-) 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 f4c0e9b9022..a2767c06f20 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 @@ -276,7 +276,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { val sourceSetToCompilationData = LinkedHashMap>() for (target in mppModel.targets) { - if (target.platform == KotlinPlatform.ANDROID) continue +// if (target.platform == KotlinPlatform.ANDROID) continue if (target.name == KotlinTarget.METADATA_TARGET_NAME) continue val targetData = KotlinTargetData(target.name).also { it.archiveFile = target.jar?.archiveFile @@ -350,7 +350,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { val ignoreCommonSourceSets by lazy { externalProject.notImportedCommonSourceSets() } for (sourceSet in mppModel.sourceSets.values) { - if (sourceSet.actualPlatforms.platforms.singleOrNull() == KotlinPlatform.ANDROID) continue +// if (sourceSet.actualPlatforms.platforms.singleOrNull() == KotlinPlatform.ANDROID) continue if (sourceSet.actualPlatforms.supports(KotlinPlatform.COMMON) && ignoreCommonSourceSets) continue val moduleId = getKotlinModuleId(gradleModule, sourceSet, resolverCtx) val existingSourceSetDataNode = sourceSetMap[moduleId]?.first @@ -448,7 +448,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { .toMap() if (resolverCtx.getExtraProject(gradleModule, ExternalProject::class.java) == null) return processSourceSets(gradleModule, mppModel, ideModule, resolverCtx) { dataNode, sourceSet -> - if (dataNode == null || sourceSet.actualPlatforms.platforms.singleOrNull() == KotlinPlatform.ANDROID) return@processSourceSets + if (dataNode == null /*|| sourceSet.actualPlatforms.platforms.singleOrNull() == KotlinPlatform.ANDROID*/) return@processSourceSets createContentRootData( sourceSet.sourceDirs, sourceSet.sourceType, @@ -558,7 +558,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { } val closedSourceSetGraph = Graphs.transitiveClosure(sourceSetGraph) for (sourceSet in closedSourceSetGraph.nodes()) { - val isAndroid = sourceSet.actualPlatforms.platforms.singleOrNull() == KotlinPlatform.ANDROID + val isAndroid = false //sourceSet.actualPlatforms.platforms.singleOrNull() == KotlinPlatform.ANDROID val fromDataNode = if (isAndroid) { ideModule } else { @@ -578,7 +578,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { sourceSetInfo.addSourceSets(dependeeSourceSets, selfName, gradleModule, resolverCtx) } } - if (sourceSet.actualPlatforms.platforms.singleOrNull() == KotlinPlatform.ANDROID) continue +// if (sourceSet.actualPlatforms.platforms.singleOrNull() == KotlinPlatform.ANDROID) continue for (dependeeSourceSet in dependeeSourceSets) { val toDataNode = getSiblingKotlinModuleData(dependeeSourceSet, gradleModule, ideModule, resolverCtx) ?: continue addDependency(fromDataNode, toDataNode, dependeeSourceSet.isTestModule) @@ -747,7 +747,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { } } for (target in mppModel.targets) { - if (target.platform == KotlinPlatform.ANDROID) continue +// if (target.platform == KotlinPlatform.ANDROID) continue for (compilation in target.compilations) { val moduleId = getKotlinModuleId(gradleModule, compilation, resolverCtx) val moduleDataNode = sourceSetsMap[moduleId] ?: continue diff --git a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt index 2dd27f2920c..4f9c0110c4a 100644 --- a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt +++ b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt @@ -141,11 +141,15 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { val sourceSets = (getSourceSets(kotlinExt) as? NamedDomainObjectContainer)?.asMap?.values ?: emptyList() + val getAndroidSourceSetDependencies = kotlinExt.javaClass.getMethodOrNull("getAndroidSourceSetDependencies", Project::class.java) + @Suppress("UNCHECKED_CAST") val androidDeps = + getAndroidSourceSetDependencies?.let { it(kotlinExt, project) } as Map>? + // Some performance optimisation: do not build metadata dependencies if source set is not common val doBuildMetadataDependencies = project.properties["build_metadata_dependencies_for_actualised_source_sets"]?.toString()?.toBoolean() ?: DEFAULT_BUILD_METADATA_DEPENDENCIES_FOR_ACTUALISED_SOURCE_SETS - val allSourceSetsProtos = sourceSets.mapNotNull { buildSourceSet(it, dependencyResolver, project, dependencyMapper) } + val allSourceSetsProtos = sourceSets.mapNotNull { buildSourceSet(it, dependencyResolver, project, dependencyMapper, androidDeps) } val allSourceSets = if (doBuildMetadataDependencies) { allSourceSetsProtos.map { proto -> proto.buildKotlinSourceSetImpl(true) } } else { @@ -173,7 +177,8 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { gradleSourceSet: Named, dependencyResolver: DependencyResolver, project: Project, - dependencyMapper: KotlinDependencyMapper + dependencyMapper: KotlinDependencyMapper, + androidDeps: Map>? ): KotlinSourceSetProto? { val sourceSetClass = gradleSourceSet.javaClass val getLanguageSettings = sourceSetClass.getMethodOrNull("getLanguageSettings") ?: return null @@ -188,7 +193,7 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { val dependsOnSourceSets = (getDependsOn(gradleSourceSet) as? Set)?.mapTo(LinkedHashSet()) { it.name } ?: emptySet() val sourceSetDependenciesBuilder: () -> Array = { - buildSourceSetDependencies(gradleSourceSet, dependencyResolver, project).map { dependencyMapper.getId(it) }.distinct() + buildSourceSetDependencies(gradleSourceSet, dependencyResolver, project, androidDeps).map { dependencyMapper.getId(it) }.distinct() .toTypedArray() } return KotlinSourceSetProto( @@ -592,7 +597,8 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { private fun buildSourceSetDependencies( gradleSourceSet: Named, dependencyResolver: DependencyResolver, - project: Project + project: Project, + androidDeps: Map>? ): List { return ArrayList().apply { val transformationBuilder = MetadataDependencyTransformationBuilder(gradleSourceSet) @@ -608,6 +614,8 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { this += buildDependencies( gradleSourceSet, dependencyResolver, "getRuntimeOnlyMetadataConfigurationName", "RUNTIME", project, transformationBuilder ) + + androidDeps?.get(gradleSourceSet.name)?.let { this += DefaultFileCollectionDependency(it) } } } diff --git a/libraries/tools/kotlin-gradle-plugin/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin/build.gradle.kts index b93f4a92351..02e9c99c275 100644 --- a/libraries/tools/kotlin-gradle-plugin/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-plugin/build.gradle.kts @@ -48,10 +48,6 @@ dependencies { compile("com.google.code.gson:gson:${rootProject.extra["versions.jar.gson"]}") compile("de.undercouch:gradle-download-task:4.0.2") - compileOnly("com.android.tools.build:gradle:2.0.0") - compileOnly("com.android.tools.build:gradle-core:2.0.0") - compileOnly("com.android.tools.build:builder:2.0.0") - compileOnly("com.android.tools.build:builder-model:2.0.0") compileOnly("org.codehaus.groovy:groovy-all:2.4.12") compileOnly(gradleApi()) @@ -70,9 +66,21 @@ dependencies { }) // com.android.tools.build:gradle has ~50 unneeded transitive dependencies - compileOnly("com.android.tools.build:gradle:3.0.0") { isTransitive = false } - compileOnly("com.android.tools.build:gradle-core:3.0.0") { isTransitive = false } - compileOnly("com.android.tools.build:builder-model:3.0.0") { isTransitive = false } + compileOnly("com.android.tools:common:26.6.0-alpha12") { isTransitive = false } + compileOnly("com.android.tools:repository:26.6.0-alpha12") { isTransitive = false } + compileOnly("com.android.tools:sdklib:26.6.0-alpha12") { isTransitive = false } + compileOnly("com.android.tools.lint:lint-gradle-api:26.6.0-alpha12") { isTransitive = false } + compileOnly("com.android.tools.build:gradle:3.6.0-alpha12") //{ isTransitive = false } + compileOnly("com.android.tools.build:gradle-api:3.6.0-alpha12") { isTransitive = false } + compileOnly("com.android.tools.build:builder-model:3.6.0-alpha12") { isTransitive = false } + compileOnly("com.android.tools.build:builder:3.6.0-alpha12") { isTransitive = false } +// compileOnly("com.android.tools.build:builder:3.5.0") { isTransitive = false } +// compileOnly("com.android.tools:common:26.6.0-alpha12") { isTransitive = false } +// compileOnly("com.android.tools:repository:26.6.0-alpha12") { isTransitive = false } +// compileOnly("com.android.tools.build:gradle:3.6.0-alpha12") { isTransitive = false } +// compileOnly("com.android.tools.build:gradle-api:3.6.0-alpha12") { isTransitive = false } +// compileOnly("com.android.tools.build:builder-model:3.6.0-alpha12") { isTransitive = false } +// compileOnly("com.android.tools.build:builder:3.6.0-alpha12") { isTransitive = false } testCompile(intellijDep()) { includeJars( "junit", "serviceMessages", rootProject = rootProject) } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt index d8fa203821f..cdb08d578ff 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt @@ -5,13 +5,30 @@ package org.jetbrains.kotlin.gradle.dsl +import com.android.build.gradle.BaseExtension +import com.android.build.gradle.internal.LoggerWrapper +import com.android.build.gradle.internal.SdkLocationSourceSet +import com.android.build.gradle.internal.SdkLocator +import com.android.build.gradle.internal.publishing.AndroidArtifacts +import com.android.build.gradle.internal.publishing.AndroidArtifacts.ARTIFACT_TYPE +import com.android.sdklib.IAndroidTarget +import com.android.sdklib.repository.AndroidSdkHandler +import com.android.sdklib.repository.LoggerProgressIndicatorWrapper import groovy.lang.Closure import org.gradle.api.InvalidUserCodeException import org.gradle.api.NamedDomainObjectCollection +import org.gradle.api.Project +import org.gradle.api.artifacts.* +import org.gradle.api.artifacts.component.ModuleComponentSelector +import org.gradle.api.artifacts.result.ResolvedComponentResult +import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency +import org.gradle.internal.component.external.model.DefaultModuleComponentIdentifier import org.gradle.util.ConfigureUtil import org.jetbrains.kotlin.gradle.plugin.* import org.jetbrains.kotlin.gradle.plugin.mpp.* import org.jetbrains.kotlin.gradle.utils.isGradleVersionAtLeast +import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName +import java.io.File open class KotlinMultiplatformExtension : KotlinProjectExtension(), @@ -53,6 +70,116 @@ open class KotlinMultiplatformExtension : internal val rootSoftwareComponent: KotlinSoftwareComponent by lazy { KotlinSoftwareComponentWithCoordinatesAndPublication("kotlin", targets) } + + private fun getAndroidSdkJar(project: Project): File? { + val androidExtension = project.extensions.findByName("android") as BaseExtension? ?: return null + val sdkLocation = SdkLocator.getSdkLocation(SdkLocationSourceSet(project.rootDir)).directory ?: return null + val sdkHandler = AndroidSdkHandler.getInstance(sdkLocation) + val logger = LoggerProgressIndicatorWrapper(LoggerWrapper(project.logger)) + val androidTarget = sdkHandler.getAndroidTargetManager(logger).getTargetFromHashString(androidExtension.compileSdkVersion, logger) + return File(androidTarget.getPath(IAndroidTarget.ANDROID_JAR)) + } + + fun getAndroidSourceSetDependencies(project: Project): Map?> { + val ext = project.extensions.getByName("android") as BaseExtension + val androidSdkJar = getAndroidSdkJar(project) ?: return emptyMap() + + val sourceSet2Impl = ext.sourceSets.map { + lowerCamelCaseName("android", it.name) to project.configurations.getByName(it.implementationConfigurationName) + }.toMap() + + val allImplConfigs = sourceSet2Impl.values.toSet() + val impl2CompileClasspath = mapImplementationToCompileClasspathConfiguration(project, allImplConfigs) + + return sourceSet2Impl.mapValues { entry -> + val compileClasspathConf = impl2CompileClasspath[entry.value] ?: return@mapValues null + + val dependencies = findDependencies(allImplConfigs, entry.value) + + val selfResolved = dependencies.filterIsInstance().flatMap { it.resolve() } + val resolvedExternal = dependencies.filterIsInstance() + .flatMap { collectDependencies(it.module, compileClasspathConf) } + + selfResolved + resolvedExternal + androidSdkJar + }.toMap() + } + + @Suppress("UnstableApiUsage") + private fun collectDependencies( + module: ModuleIdentifier, + compileClasspathConf: Configuration + ): List { + val viewConfig: (ArtifactView.ViewConfiguration) -> Unit = { config -> + config.attributes { it.attribute(ARTIFACT_TYPE, AndroidArtifacts.ArtifactType.JAR.type) } + config.isLenient = true + } + + val resolvedArtifacts = compileClasspathConf.incoming.artifactView(viewConfig).artifacts.mapNotNull { + val id = (it.id.componentIdentifier as? DefaultModuleComponentIdentifier)?.moduleIdentifier ?: return@mapNotNull null + id to it + }.toMap() + + val resolutionResults = compileClasspathConf.incoming.resolutionResult.allComponents.mapNotNull { + val id = it.moduleVersion?.module ?: return@mapNotNull null + id to it + }.toMap() + + val deps = HashSet().also { doCollectDependencies(listOf(module), resolutionResults, it) } + return deps.mapNotNull { resolvedArtifacts[it] }.map { it.file } + } + + @Suppress("UnstableApiUsage") + private tailrec fun doCollectDependencies( + modules: List, + resolutionResults: Map, + result: MutableSet + ) { + if (modules.isEmpty()) return + val newModules = modules.filter { result.add(it) }.mapNotNull { resolutionResults[it] }.flatMap { it.dependencies } + .mapNotNull { (it.requested as? ModuleComponentSelector)?.moduleIdentifier } + doCollectDependencies(newModules, resolutionResults, result) + } + + private fun findDependencies(implConfigs: Set, conf: Configuration): Set { + return HashSet().also { doFindDependencies(implConfigs, listOf(conf), it) } + } + + private tailrec fun doFindDependencies( + implConfigs: Set, configs: List, result: MutableSet, + visited: MutableSet = HashSet() + ) { + if (configs.isEmpty()) return + result.addAll(configs.flatMap { it.dependencies }) + doFindDependencies(implConfigs, configs.flatMap { it.extendsFrom }.filter { it !in implConfigs && visited.add(it) }, result) + } + + private fun mapImplementationToCompileClasspathConfiguration( + project: Project, + allImplConfigs: Set + ): Map { + val compileClasspathConfigurations = project.configurations.filter { it.name.endsWith("CompileClasspath", true) } + return compileClasspathConfigurations.flatMap { ccConf -> + findImplementationConfigurations(allImplConfigs, ccConf).map { it to ccConf } + }.toMap() + } + + private fun findImplementationConfigurations( + allImplConfigs: Set, + compileClasspathConf: Configuration + ): Set { + return HashSet().also { doFindImplementationConfigurations(allImplConfigs, listOf(compileClasspathConf), it) } + } + + private tailrec fun doFindImplementationConfigurations( + allImplConfigs: Set, + conf: List, + result: MutableSet, + visited: MutableSet = HashSet() + ) { + if (conf.isEmpty()) return + result.addAll(conf.flatMap { it.extendsFrom.filter { newConf -> allImplConfigs.contains(newConf) } }) + doFindImplementationConfigurations(allImplConfigs, conf.flatMap { it.extendsFrom }.filter { visited.add(it) }, result) + } } internal fun KotlinTarget.isProducedFromPreset(kotlinTargetPreset: KotlinTargetPreset<*>): Boolean = From 45a0aa04a1534e4176ee52a9d90eac14408450d5 Mon Sep 17 00:00:00 2001 From: Vyacheslav Karpukhin Date: Fri, 25 Oct 2019 15:19:58 +0200 Subject: [PATCH 02/11] Android import: import R.jar --- .../gradle/dsl/KotlinMultiplatformExtension.kt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt index cdb08d578ff..acc4fca91a6 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt @@ -11,6 +11,7 @@ import com.android.build.gradle.internal.SdkLocationSourceSet import com.android.build.gradle.internal.SdkLocator import com.android.build.gradle.internal.publishing.AndroidArtifacts import com.android.build.gradle.internal.publishing.AndroidArtifacts.ARTIFACT_TYPE +import com.android.build.gradle.internal.res.LinkApplicationAndroidResourcesTask import com.android.sdklib.IAndroidTarget import com.android.sdklib.repository.AndroidSdkHandler import com.android.sdklib.repository.LoggerProgressIndicatorWrapper @@ -100,7 +101,16 @@ open class KotlinMultiplatformExtension : val resolvedExternal = dependencies.filterIsInstance() .flatMap { collectDependencies(it.module, compileClasspathConf) } - selfResolved + resolvedExternal + androidSdkJar + val result = (selfResolved + resolvedExternal + androidSdkJar).toMutableList() + + if (entry.key == "androidMain") { + // this is a terrible hack, but looks like the only way, other than proper support via light-classes + val task = project.tasks.findByName("processDebugResources") as? LinkApplicationAndroidResourcesTask + @Suppress("UnstableApiUsage") + task?.rClassOutputJar?.orNull?.asFile?.let { result += it } + } + + result }.toMap() } From a6e488e2b4e0a76b13998e2927fe2b4377db39b5 Mon Sep 17 00:00:00 2001 From: Vyacheslav Karpukhin Date: Thu, 7 Nov 2019 20:18:37 +0100 Subject: [PATCH 03/11] Android import: simplified configuration matching --- .../dsl/KotlinMultiplatformExtension.kt | 55 +++++-------------- .../kotlin/gradle/plugin/KotlinPlugin.kt | 30 +++++++--- .../android/Android25ProjectHandler.kt | 21 +------ .../targets/android/KotlinAndroidTarget.kt | 4 +- .../android/internal/AndroidSubplugin.kt | 2 +- 5 files changed, 43 insertions(+), 69 deletions(-) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt index acc4fca91a6..6e947c242fe 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt @@ -6,6 +6,7 @@ package org.jetbrains.kotlin.gradle.dsl import com.android.build.gradle.BaseExtension +import com.android.build.gradle.api.AndroidSourceSet import com.android.build.gradle.internal.LoggerWrapper import com.android.build.gradle.internal.SdkLocationSourceSet import com.android.build.gradle.internal.SdkLocator @@ -27,7 +28,6 @@ import org.gradle.internal.component.external.model.DefaultModuleComponentIdenti import org.gradle.util.ConfigureUtil import org.jetbrains.kotlin.gradle.plugin.* import org.jetbrains.kotlin.gradle.plugin.mpp.* -import org.jetbrains.kotlin.gradle.utils.isGradleVersionAtLeast import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName import java.io.File @@ -82,24 +82,27 @@ open class KotlinMultiplatformExtension : } fun getAndroidSourceSetDependencies(project: Project): Map?> { - val ext = project.extensions.getByName("android") as BaseExtension + data class SourceSetConfigs(val implConfig: Configuration, val compileConfig: Configuration) + val androidSdkJar = getAndroidSdkJar(project) ?: return emptyMap() + val sourceSet2Impl = HashMap() + val allImplConfigs = HashSet() - val sourceSet2Impl = ext.sourceSets.map { - lowerCamelCaseName("android", it.name) to project.configurations.getByName(it.implementationConfigurationName) - }.toMap() - - val allImplConfigs = sourceSet2Impl.values.toSet() - val impl2CompileClasspath = mapImplementationToCompileClasspathConfiguration(project, allImplConfigs) + project.forEachVariant { variant -> + val compileConfig = variant.compileConfiguration + variant.sourceSets.filterIsInstance(AndroidSourceSet::class.java).map { + val implConfig = project.configurations.getByName(it.implementationConfigurationName) + allImplConfigs.add(implConfig) + sourceSet2Impl[lowerCamelCaseName("android", it.name)] = SourceSetConfigs(implConfig, compileConfig) + } + } return sourceSet2Impl.mapValues { entry -> - val compileClasspathConf = impl2CompileClasspath[entry.value] ?: return@mapValues null - - val dependencies = findDependencies(allImplConfigs, entry.value) + val dependencies = findDependencies(allImplConfigs, entry.value.implConfig) val selfResolved = dependencies.filterIsInstance().flatMap { it.resolve() } val resolvedExternal = dependencies.filterIsInstance() - .flatMap { collectDependencies(it.module, compileClasspathConf) } + .flatMap { collectDependencies(it.module, entry.value.compileConfig) } val result = (selfResolved + resolvedExternal + androidSdkJar).toMutableList() @@ -162,34 +165,6 @@ open class KotlinMultiplatformExtension : result.addAll(configs.flatMap { it.dependencies }) doFindDependencies(implConfigs, configs.flatMap { it.extendsFrom }.filter { it !in implConfigs && visited.add(it) }, result) } - - private fun mapImplementationToCompileClasspathConfiguration( - project: Project, - allImplConfigs: Set - ): Map { - val compileClasspathConfigurations = project.configurations.filter { it.name.endsWith("CompileClasspath", true) } - return compileClasspathConfigurations.flatMap { ccConf -> - findImplementationConfigurations(allImplConfigs, ccConf).map { it to ccConf } - }.toMap() - } - - private fun findImplementationConfigurations( - allImplConfigs: Set, - compileClasspathConf: Configuration - ): Set { - return HashSet().also { doFindImplementationConfigurations(allImplConfigs, listOf(compileClasspathConf), it) } - } - - private tailrec fun doFindImplementationConfigurations( - allImplConfigs: Set, - conf: List, - result: MutableSet, - visited: MutableSet = HashSet() - ) { - if (conf.isEmpty()) return - result.addAll(conf.flatMap { it.extendsFrom.filter { newConf -> allImplConfigs.contains(newConf) } }) - doFindImplementationConfigurations(allImplConfigs, conf.flatMap { it.extendsFrom }.filter { visited.add(it) }, result) - } } internal fun KotlinTarget.isProducedFromPreset(kotlinTargetPreset: KotlinTargetPreset<*>): Boolean = diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt index 155e2a615a4..6967638d085 100755 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt @@ -1,7 +1,6 @@ package org.jetbrains.kotlin.gradle.plugin -import com.android.build.gradle.BaseExtension -import com.android.build.gradle.BasePlugin +import com.android.build.gradle.* import com.android.build.gradle.api.AndroidSourceSet import com.android.build.gradle.api.BaseVariant import com.android.builder.model.SourceProvider @@ -707,7 +706,6 @@ class KotlinConfigurationTools internal constructor( abstract class AbstractAndroidProjectHandler(private val kotlinConfigurationTools: KotlinConfigurationTools) { protected val logger = Logging.getLogger(this.javaClass) - abstract fun forEachVariant(project: Project, action: (BaseVariant) -> Unit): Unit abstract fun getResDirectories(variantData: BaseVariant): FileCollection abstract fun getFlavorNames(variant: BaseVariant): List abstract fun getBuildTypeName(variant: BaseVariant): String @@ -781,7 +779,7 @@ abstract class AbstractAndroidProjectHandler(private val kotlinConfigurationTool androidPluginIds.joinToString("\n\t") { "* $it" }) } - forEachVariant(project) { variant -> + project.forEachVariant { variant -> val variantName = getVariantName(variant) // Create the compilation and configure it first, then add to the compilations container. As this code is executed @@ -801,7 +799,7 @@ abstract class AbstractAndroidProjectHandler(private val kotlinConfigurationTool } project.whenEvaluated { - forEachVariant(project) { variant -> + forEachVariant { variant -> val compilation = kotlinAndroidTarget.compilations.getByName(getVariantName(variant)) postprocessVariant(variant, compilation, project, ext, plugin) @@ -868,7 +866,7 @@ abstract class AbstractAndroidProjectHandler(private val kotlinConfigurationTool if (kotlinAndroidTarget.disambiguationClassifier != null) { val sourceSetToVariants = mutableMapOf>().apply { - forEachVariant(project) { variant -> + project.forEachVariant { variant -> for (sourceSet in getSourceProviders(variant)) { val androidSourceSet = sourceSet as? AndroidSourceSet ?: continue getOrPut(androidSourceSet) { mutableListOf() }.add(variant) @@ -1144,4 +1142,22 @@ internal fun compareVersionNumbers(v1: String?, v2: String?): Int { } return 0 } -} \ No newline at end of file +} + +internal fun Project.forEachVariant(action: (BaseVariant) -> Unit) { + val androidExtension = this.extensions.getByName("android") + when (androidExtension) { + is AppExtension -> androidExtension.applicationVariants.all(action) + is LibraryExtension -> { + androidExtension.libraryVariants.all(action) + if (androidExtension is FeatureExtension) { + androidExtension.featureVariants.all(action) + } + } + is TestExtension -> androidExtension.applicationVariants.all(action) + } + if (androidExtension is TestedExtension) { + androidExtension.testVariants.all(action) + androidExtension.unitTestVariants.all(action) + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/Android25ProjectHandler.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/Android25ProjectHandler.kt index e36c8ccfcef..b014b3d8381 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/Android25ProjectHandler.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/Android25ProjectHandler.kt @@ -6,7 +6,8 @@ @file:Suppress("PackageDirectoryMismatch") // Old package for compatibility package org.jetbrains.kotlin.gradle.plugin -import com.android.build.gradle.* +import com.android.build.gradle.BaseExtension +import com.android.build.gradle.BasePlugin import com.android.build.gradle.api.* import com.android.build.gradle.tasks.MergeResources import com.android.builder.model.SourceProvider @@ -29,24 +30,6 @@ class Android25ProjectHandler( kotlinConfigurationTools: KotlinConfigurationTools ) : AbstractAndroidProjectHandler(kotlinConfigurationTools) { - override fun forEachVariant(project: Project, action: (BaseVariant) -> Unit) { - val androidExtension = project.extensions.getByName("android") - when (androidExtension) { - is AppExtension -> androidExtension.applicationVariants.all(action) - is LibraryExtension -> { - androidExtension.libraryVariants.all(action) - if (androidExtension is FeatureExtension) { - androidExtension.featureVariants.all(action) - } - } - is TestExtension -> androidExtension.applicationVariants.all(action) - } - if (androidExtension is TestedExtension) { - androidExtension.testVariants.all(action) - androidExtension.unitTestVariants.all(action) - } - } - override fun wireKotlinTasks( project: Project, compilation: KotlinJvmAndroidCompilation, diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/KotlinAndroidTarget.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/KotlinAndroidTarget.kt index 1aa6f617d50..d7c80ae6f4c 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/KotlinAndroidTarget.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/KotlinAndroidTarget.kt @@ -64,7 +64,7 @@ open class KotlinAndroidTarget( private fun checkPublishLibraryVariantsExist() { fun AbstractAndroidProjectHandler.getLibraryVariantNames() = mutableSetOf().apply { - forEachVariant(project) { + project.forEachVariant { if (getLibraryOutputTask(it) != null) add(getVariantName(it)) } @@ -94,7 +94,7 @@ open class KotlinAndroidTarget( private fun AbstractAndroidProjectHandler.doCreateComponents(): Set { val publishableVariants = mutableListOf() - .apply { forEachVariant(project) { add(it) } } + .apply { project.forEachVariant { add(it) } } .toList() // Defensive copy against unlikely modification by the lambda that captures the list above in forEachVariant { } .filter { getLibraryOutputTask(it) != null && publishLibraryVariants?.contains(getVariantName(it)) ?: true } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/internal/AndroidSubplugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/internal/AndroidSubplugin.kt index 955e8ff5dbc..8df1cdb4010 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/internal/AndroidSubplugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/internal/AndroidSubplugin.kt @@ -208,7 +208,7 @@ class AndroidSubplugin : KotlinGradleSubplugin { val resDirectoriesForAllVariants = mutableListOf() - androidProjectHandler.forEachVariant(project) { variant -> + project.forEachVariant { variant -> if (getTestedVariantData(variant) != null) return@forEachVariant resDirectoriesForAllVariants += androidProjectHandler.getResDirectories(variant) } From ad314e93eb321b8173905013d93e09f7662ea03b Mon Sep 17 00:00:00 2001 From: Vyacheslav Karpukhin Date: Tue, 21 Jan 2020 20:18:45 +0100 Subject: [PATCH 04/11] Do not explicitly depend on prerelease Android Gradle Plugin --- .../kotlin-gradle-plugin/build.gradle.kts | 22 +++---- .../dsl/KotlinMultiplatformExtension.kt | 57 ++++++++++++++++--- 2 files changed, 57 insertions(+), 22 deletions(-) diff --git a/libraries/tools/kotlin-gradle-plugin/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin/build.gradle.kts index 02e9c99c275..b93f4a92351 100644 --- a/libraries/tools/kotlin-gradle-plugin/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-plugin/build.gradle.kts @@ -48,6 +48,10 @@ dependencies { compile("com.google.code.gson:gson:${rootProject.extra["versions.jar.gson"]}") compile("de.undercouch:gradle-download-task:4.0.2") + compileOnly("com.android.tools.build:gradle:2.0.0") + compileOnly("com.android.tools.build:gradle-core:2.0.0") + compileOnly("com.android.tools.build:builder:2.0.0") + compileOnly("com.android.tools.build:builder-model:2.0.0") compileOnly("org.codehaus.groovy:groovy-all:2.4.12") compileOnly(gradleApi()) @@ -66,21 +70,9 @@ dependencies { }) // com.android.tools.build:gradle has ~50 unneeded transitive dependencies - compileOnly("com.android.tools:common:26.6.0-alpha12") { isTransitive = false } - compileOnly("com.android.tools:repository:26.6.0-alpha12") { isTransitive = false } - compileOnly("com.android.tools:sdklib:26.6.0-alpha12") { isTransitive = false } - compileOnly("com.android.tools.lint:lint-gradle-api:26.6.0-alpha12") { isTransitive = false } - compileOnly("com.android.tools.build:gradle:3.6.0-alpha12") //{ isTransitive = false } - compileOnly("com.android.tools.build:gradle-api:3.6.0-alpha12") { isTransitive = false } - compileOnly("com.android.tools.build:builder-model:3.6.0-alpha12") { isTransitive = false } - compileOnly("com.android.tools.build:builder:3.6.0-alpha12") { isTransitive = false } -// compileOnly("com.android.tools.build:builder:3.5.0") { isTransitive = false } -// compileOnly("com.android.tools:common:26.6.0-alpha12") { isTransitive = false } -// compileOnly("com.android.tools:repository:26.6.0-alpha12") { isTransitive = false } -// compileOnly("com.android.tools.build:gradle:3.6.0-alpha12") { isTransitive = false } -// compileOnly("com.android.tools.build:gradle-api:3.6.0-alpha12") { isTransitive = false } -// compileOnly("com.android.tools.build:builder-model:3.6.0-alpha12") { isTransitive = false } -// compileOnly("com.android.tools.build:builder:3.6.0-alpha12") { isTransitive = false } + compileOnly("com.android.tools.build:gradle:3.0.0") { isTransitive = false } + compileOnly("com.android.tools.build:gradle-core:3.0.0") { isTransitive = false } + compileOnly("com.android.tools.build:builder-model:3.0.0") { isTransitive = false } testCompile(intellijDep()) { includeJars( "junit", "serviceMessages", rootProject = rootProject) } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt index 6e947c242fe..ae65af38ec6 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt @@ -8,14 +8,12 @@ package org.jetbrains.kotlin.gradle.dsl import com.android.build.gradle.BaseExtension import com.android.build.gradle.api.AndroidSourceSet import com.android.build.gradle.internal.LoggerWrapper -import com.android.build.gradle.internal.SdkLocationSourceSet -import com.android.build.gradle.internal.SdkLocator import com.android.build.gradle.internal.publishing.AndroidArtifacts import com.android.build.gradle.internal.publishing.AndroidArtifacts.ARTIFACT_TYPE -import com.android.build.gradle.internal.res.LinkApplicationAndroidResourcesTask import com.android.sdklib.IAndroidTarget import com.android.sdklib.repository.AndroidSdkHandler import com.android.sdklib.repository.LoggerProgressIndicatorWrapper +import com.intellij.openapi.util.Version import groovy.lang.Closure import org.gradle.api.InvalidUserCodeException import org.gradle.api.NamedDomainObjectCollection @@ -23,13 +21,16 @@ import org.gradle.api.Project import org.gradle.api.artifacts.* import org.gradle.api.artifacts.component.ModuleComponentSelector import org.gradle.api.artifacts.result.ResolvedComponentResult +import org.gradle.api.file.FileSystemLocation import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency +import org.gradle.api.provider.Provider import org.gradle.internal.component.external.model.DefaultModuleComponentIdentifier import org.gradle.util.ConfigureUtil import org.jetbrains.kotlin.gradle.plugin.* import org.jetbrains.kotlin.gradle.plugin.mpp.* import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName import java.io.File +import kotlin.reflect.full.findParameterByName open class KotlinMultiplatformExtension : KotlinProjectExtension(), @@ -74,14 +75,52 @@ open class KotlinMultiplatformExtension : private fun getAndroidSdkJar(project: Project): File? { val androidExtension = project.extensions.findByName("android") as BaseExtension? ?: return null - val sdkLocation = SdkLocator.getSdkLocation(SdkLocationSourceSet(project.rootDir)).directory ?: return null + val sdkLocation = getClassOrNull("com.android.build.gradle.internal.SdkLocationSourceSet")?.let { sdkLocationSourceSetClass -> + val sdkLocationSourceSet = sdkLocationSourceSetClass.kotlin.constructors.firstOrNull()?.let { callable -> + callable.findParameterByName("projectRoot")?.let { param -> + callable.callBy(mapOf(param to project.rootDir)) + } + } + + val sdkLocatorClass = getClassOrNull("com.android.build.gradle.internal.SdkLocator") + val sdkLocation = + sdkLocatorClass?.getMethodOrNull("getSdkLocation", sdkLocationSourceSetClass)?.invoke(sdkLocatorClass, sdkLocationSourceSet) + sdkLocation?.javaClass?.getMethodOrNull("getDirectory")?.invoke(sdkLocation) as? File + } ?: return null val sdkHandler = AndroidSdkHandler.getInstance(sdkLocation) val logger = LoggerProgressIndicatorWrapper(LoggerWrapper(project.logger)) val androidTarget = sdkHandler.getAndroidTargetManager(logger).getTargetFromHashString(androidExtension.compileSdkVersion, logger) return File(androidTarget.getPath(IAndroidTarget.ANDROID_JAR)) } + private fun getClassOrNull(s: String) = + try { + Class.forName(s) + } catch (e: Exception) { + null + } + + private fun Class<*>.getMethodOrNull(name: String, vararg parameterTypes: Class<*>) = + try { + getMethod(name, *parameterTypes) + } catch (e: Exception) { + null + } + + private fun isAndroidPluginCompatible(): Boolean { + val version = getClassOrNull("com.android.Version")?.let { + try { + it.getField("ANDROID_GRADLE_PLUGIN_VERSION").get(null) as String + } catch (e: Exception) { + return false + } + } ?: return false + return Version.parseVersion(version)?.isOrGreaterThan(3, 6) ?: false + } + fun getAndroidSourceSetDependencies(project: Project): Map?> { + if (!isAndroidPluginCompatible()) return emptyMap() + data class SourceSetConfigs(val implConfig: Configuration, val compileConfig: Configuration) val androidSdkJar = getAndroidSdkJar(project) ?: return emptyMap() @@ -108,9 +147,13 @@ open class KotlinMultiplatformExtension : if (entry.key == "androidMain") { // this is a terrible hack, but looks like the only way, other than proper support via light-classes - val task = project.tasks.findByName("processDebugResources") as? LinkApplicationAndroidResourcesTask - @Suppress("UnstableApiUsage") - task?.rClassOutputJar?.orNull?.asFile?.let { result += it } + val task = project.tasks.findByName("processDebugResources") + getClassOrNull("com.android.build.gradle.internal.res.LinkApplicationAndroidResourcesTask")?.let { linkAppClass -> + @Suppress("UNCHECKED_CAST") + val rClassOutputJar = + linkAppClass.getMethodOrNull("getRClassOutputJar")?.invoke(task) as Provider? + rClassOutputJar?.orNull?.asFile?.let { result += it } + } } result From 663c8e5a4675c99009b7eb6da5c7b99f2d36df07 Mon Sep 17 00:00:00 2001 From: Vyacheslav Karpukhin Date: Wed, 22 Jan 2020 18:11:22 +0100 Subject: [PATCH 05/11] Import Android modules only if Android Plugin for IDEA is missing --- .../KotlinMPPGradleProjectResolver.kt | 23 +++++++++++++------ .../src/KotlinMPPGradleModelBuilder.kt | 17 ++++++++++---- 2 files changed, 29 insertions(+), 11 deletions(-) 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 a2767c06f20..01827d4c970 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 @@ -7,6 +7,8 @@ package org.jetbrains.kotlin.idea.configuration import com.google.common.graph.GraphBuilder import com.google.common.graph.Graphs +import com.intellij.ide.plugins.PluginManager +import com.intellij.openapi.extensions.PluginId import com.intellij.openapi.externalSystem.model.DataNode import com.intellij.openapi.externalSystem.model.ProjectKeys import com.intellij.openapi.externalSystem.model.project.* @@ -34,7 +36,6 @@ import org.jetbrains.kotlin.config.ExternalSystemTestTask import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.gradle.* import org.jetbrains.kotlin.idea.configuration.GradlePropertiesFileFacade.Companion.KOTLIN_NOT_IMPORTED_COMMON_SOURCE_SETS_SETTING -import org.jetbrains.kotlin.idea.configuration.KotlinMPPGradleProjectResolver.Companion.fullName import org.jetbrains.kotlin.idea.platform.IdePlatformKindTooling import org.jetbrains.kotlin.util.removeSuffixIfPresent import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult @@ -64,6 +65,10 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { return setOf(KotlinMPPGradleModel::class.java) } + override fun getExtraCommandLineArgs(): List { + return if (!androidPluginPresent) listOf("-Pkotlin.include.android.dependencies=true") else emptyList() + } + override fun createModule(gradleModule: IdeaModule, projectDataNode: DataNode): DataNode { return super.createModule(gradleModule, projectDataNode).also { initializeModuleData(gradleModule, it, projectDataNode, resolverCtx) @@ -225,6 +230,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { val proxyObjectCloningCache = WeakHashMap() private var nativeDebugAdvertised = false + private val androidPluginPresent = PluginManager.getPlugin(PluginId.findId("org.jetbrains.android"))?.isEnabled ?: false fun initializeModuleData( gradleModule: IdeaModule, @@ -276,7 +282,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { val sourceSetToCompilationData = LinkedHashMap>() for (target in mppModel.targets) { -// if (target.platform == KotlinPlatform.ANDROID) continue + if (androidPluginPresent && target.platform == KotlinPlatform.ANDROID) continue if (target.name == KotlinTarget.METADATA_TARGET_NAME) continue val targetData = KotlinTargetData(target.name).also { it.archiveFile = target.jar?.archiveFile @@ -350,7 +356,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { val ignoreCommonSourceSets by lazy { externalProject.notImportedCommonSourceSets() } for (sourceSet in mppModel.sourceSets.values) { -// if (sourceSet.actualPlatforms.platforms.singleOrNull() == KotlinPlatform.ANDROID) continue + if (androidPluginPresent && sourceSet.actualPlatforms.platforms.singleOrNull() == KotlinPlatform.ANDROID) continue if (sourceSet.actualPlatforms.supports(KotlinPlatform.COMMON) && ignoreCommonSourceSets) continue val moduleId = getKotlinModuleId(gradleModule, sourceSet, resolverCtx) val existingSourceSetDataNode = sourceSetMap[moduleId]?.first @@ -448,7 +454,10 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { .toMap() if (resolverCtx.getExtraProject(gradleModule, ExternalProject::class.java) == null) return processSourceSets(gradleModule, mppModel, ideModule, resolverCtx) { dataNode, sourceSet -> - if (dataNode == null /*|| sourceSet.actualPlatforms.platforms.singleOrNull() == KotlinPlatform.ANDROID*/) return@processSourceSets + if (dataNode == null || androidPluginPresent && + sourceSet.actualPlatforms.platforms.singleOrNull() == KotlinPlatform.ANDROID + ) return@processSourceSets + createContentRootData( sourceSet.sourceDirs, sourceSet.sourceType, @@ -558,7 +567,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { } val closedSourceSetGraph = Graphs.transitiveClosure(sourceSetGraph) for (sourceSet in closedSourceSetGraph.nodes()) { - val isAndroid = false //sourceSet.actualPlatforms.platforms.singleOrNull() == KotlinPlatform.ANDROID + val isAndroid = androidPluginPresent && sourceSet.actualPlatforms.platforms.singleOrNull() == KotlinPlatform.ANDROID val fromDataNode = if (isAndroid) { ideModule } else { @@ -578,7 +587,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { sourceSetInfo.addSourceSets(dependeeSourceSets, selfName, gradleModule, resolverCtx) } } -// if (sourceSet.actualPlatforms.platforms.singleOrNull() == KotlinPlatform.ANDROID) continue + if (androidPluginPresent && sourceSet.actualPlatforms.platforms.singleOrNull() == KotlinPlatform.ANDROID) continue for (dependeeSourceSet in dependeeSourceSets) { val toDataNode = getSiblingKotlinModuleData(dependeeSourceSet, gradleModule, ideModule, resolverCtx) ?: continue addDependency(fromDataNode, toDataNode, dependeeSourceSet.isTestModule) @@ -747,7 +756,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { } } for (target in mppModel.targets) { -// if (target.platform == KotlinPlatform.ANDROID) continue + if (androidPluginPresent && target.platform == KotlinPlatform.ANDROID) continue for (compilation in target.compilations) { val moduleId = getKotlinModuleId(gradleModule, compilation, resolverCtx) val moduleDataNode = sourceSetsMap[moduleId] ?: continue diff --git a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt index 4f9c0110c4a..18ee563a14e 100644 --- a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt +++ b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt @@ -140,10 +140,7 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { @Suppress("UNCHECKED_CAST") val sourceSets = (getSourceSets(kotlinExt) as? NamedDomainObjectContainer)?.asMap?.values ?: emptyList() - - val getAndroidSourceSetDependencies = kotlinExt.javaClass.getMethodOrNull("getAndroidSourceSetDependencies", Project::class.java) - @Suppress("UNCHECKED_CAST") val androidDeps = - getAndroidSourceSetDependencies?.let { it(kotlinExt, project) } as Map>? + val androidDeps = buildAndroidDeps(kotlinExt, project) // Some performance optimisation: do not build metadata dependencies if source set is not common val doBuildMetadataDependencies = @@ -173,6 +170,18 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { } } + private fun buildAndroidDeps(kotlinExt: Any, project: Project): Map>? { + val includeAndroidDeps = project.properties["kotlin.include.android.dependencies"]?.toString()?.toBoolean() == true + if (includeAndroidDeps) { + val getAndroidSourceSetDependencies = + kotlinExt.javaClass.getMethodOrNull("getAndroidSourceSetDependencies", Project::class.java) + @Suppress("UNCHECKED_CAST") + return getAndroidSourceSetDependencies?.let { it(kotlinExt, project) } as Map>? + } else { + return emptyMap() + } + } + private fun buildSourceSet( gradleSourceSet: Named, dependencyResolver: DependencyResolver, From 9788525ec7ba0e5169850a5f8658591a3f885e73 Mon Sep 17 00:00:00 2001 From: Vyacheslav Karpukhin Date: Fri, 24 Jan 2020 17:19:20 +0100 Subject: [PATCH 06/11] Import sources for Android SDK, include proper identifiers for Gradle dependencies --- .../src/KotlinMPPGradleModelBuilder.kt | 35 +++++++++++++++--- .../dsl/KotlinMultiplatformExtension.kt | 37 +++++++++++++------ 2 files changed, 54 insertions(+), 18 deletions(-) diff --git a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt index 18ee563a14e..9af1518638e 100644 --- a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt +++ b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt @@ -20,6 +20,7 @@ import org.gradle.api.provider.Provider import org.gradle.api.tasks.Exec import org.gradle.api.tasks.TaskProvider import org.jetbrains.kotlin.gradle.KotlinMPPGradleModel.Companion.NO_KOTLIN_NATIVE_HOME +import org.jetbrains.plugins.gradle.DefaultExternalDependencyId import org.jetbrains.plugins.gradle.model.* import org.jetbrains.plugins.gradle.tooling.ErrorMessageBuilder import org.jetbrains.plugins.gradle.tooling.ModelBuilderService @@ -170,13 +171,13 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { } } - private fun buildAndroidDeps(kotlinExt: Any, project: Project): Map>? { + private fun buildAndroidDeps(kotlinExt: Any, project: Project): Map>? { val includeAndroidDeps = project.properties["kotlin.include.android.dependencies"]?.toString()?.toBoolean() == true if (includeAndroidDeps) { val getAndroidSourceSetDependencies = kotlinExt.javaClass.getMethodOrNull("getAndroidSourceSetDependencies", Project::class.java) @Suppress("UNCHECKED_CAST") - return getAndroidSourceSetDependencies?.let { it(kotlinExt, project) } as Map>? + return getAndroidSourceSetDependencies?.let { it(kotlinExt, project) } as Map>? } else { return emptyMap() } @@ -187,7 +188,7 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { dependencyResolver: DependencyResolver, project: Project, dependencyMapper: KotlinDependencyMapper, - androidDeps: Map>? + androidDeps: Map>? ): KotlinSourceSetProto? { val sourceSetClass = gradleSourceSet.javaClass val getLanguageSettings = sourceSetClass.getMethodOrNull("getLanguageSettings") ?: return null @@ -607,7 +608,7 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { gradleSourceSet: Named, dependencyResolver: DependencyResolver, project: Project, - androidDeps: Map>? + androidDeps: Map>? ): List { return ArrayList().apply { val transformationBuilder = MetadataDependencyTransformationBuilder(gradleSourceSet) @@ -623,11 +624,33 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { this += buildDependencies( gradleSourceSet, dependencyResolver, "getRuntimeOnlyMetadataConfigurationName", "RUNTIME", project, transformationBuilder ) - - androidDeps?.get(gradleSourceSet.name)?.let { this += DefaultFileCollectionDependency(it) } + this += buildAndroidSourceSetDependencies(androidDeps, gradleSourceSet) } } + private fun buildAndroidSourceSetDependencies( + androidDeps: Map>?, + gradleSourceSet: Named + ): Collection { + return androidDeps?.get(gradleSourceSet.name)?.map { it -> + @Suppress("UNCHECKED_CAST") + val collection = it["getCollection"] as Set? + if (collection == null) { + DefaultExternalLibraryDependency().apply { + (id as? DefaultExternalDependencyId)?.apply { + name = it["getName"] as String? + group = it["getGroup"] as String? + version = it["getVersion"] as String? + } + file = it["getJar"] as File + source = it["getSource"] as File? + } + } else { + DefaultFileCollectionDependency(collection) + } + } ?: emptyList() + } + @Suppress("UNCHECKED_CAST") private fun safelyGetArguments(compileKotlinTask: Task, accessor: Method?) = try { accessor?.invoke(compileKotlinTask) as? List diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt index ae65af38ec6..f1dde142656 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt @@ -19,12 +19,11 @@ import org.gradle.api.InvalidUserCodeException import org.gradle.api.NamedDomainObjectCollection import org.gradle.api.Project import org.gradle.api.artifacts.* +import org.gradle.api.artifacts.component.ModuleComponentIdentifier import org.gradle.api.artifacts.component.ModuleComponentSelector import org.gradle.api.artifacts.result.ResolvedComponentResult import org.gradle.api.file.FileSystemLocation -import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency import org.gradle.api.provider.Provider -import org.gradle.internal.component.external.model.DefaultModuleComponentIdentifier import org.gradle.util.ConfigureUtil import org.jetbrains.kotlin.gradle.plugin.* import org.jetbrains.kotlin.gradle.plugin.mpp.* @@ -73,7 +72,16 @@ open class KotlinMultiplatformExtension : KotlinSoftwareComponentWithCoordinatesAndPublication("kotlin", targets) } - private fun getAndroidSdkJar(project: Project): File? { + data class AndroidDependency( + val name: String? = null, + val jar: File? = null, + val source: File? = null, + val group: String? = null, + val version: String? = null, + val collection: Set? = null + ) + + private fun getAndroidSdkJar(project: Project): AndroidDependency? { val androidExtension = project.extensions.findByName("android") as BaseExtension? ?: return null val sdkLocation = getClassOrNull("com.android.build.gradle.internal.SdkLocationSourceSet")?.let { sdkLocationSourceSetClass -> val sdkLocationSourceSet = sdkLocationSourceSetClass.kotlin.constructors.firstOrNull()?.let { callable -> @@ -90,7 +98,11 @@ open class KotlinMultiplatformExtension : val sdkHandler = AndroidSdkHandler.getInstance(sdkLocation) val logger = LoggerProgressIndicatorWrapper(LoggerWrapper(project.logger)) val androidTarget = sdkHandler.getAndroidTargetManager(logger).getTargetFromHashString(androidExtension.compileSdkVersion, logger) - return File(androidTarget.getPath(IAndroidTarget.ANDROID_JAR)) + return AndroidDependency( + androidTarget.fullName, + File(androidTarget.getPath(IAndroidTarget.ANDROID_JAR)), + File(androidTarget.getPath(IAndroidTarget.SOURCES)) + ) } private fun getClassOrNull(s: String) = @@ -118,7 +130,7 @@ open class KotlinMultiplatformExtension : return Version.parseVersion(version)?.isOrGreaterThan(3, 6) ?: false } - fun getAndroidSourceSetDependencies(project: Project): Map?> { + fun getAndroidSourceSetDependencies(project: Project): Map?> { if (!isAndroidPluginCompatible()) return emptyMap() data class SourceSetConfigs(val implConfig: Configuration, val compileConfig: Configuration) @@ -139,8 +151,8 @@ open class KotlinMultiplatformExtension : return sourceSet2Impl.mapValues { entry -> val dependencies = findDependencies(allImplConfigs, entry.value.implConfig) - val selfResolved = dependencies.filterIsInstance().flatMap { it.resolve() } - val resolvedExternal = dependencies.filterIsInstance() + val selfResolved = dependencies.filterIsInstance().map { AndroidDependency(collection = it.resolve()) } + val resolvedExternal = dependencies.filterIsInstance() .flatMap { collectDependencies(it.module, entry.value.compileConfig) } val result = (selfResolved + resolvedExternal + androidSdkJar).toMutableList() @@ -152,7 +164,7 @@ open class KotlinMultiplatformExtension : @Suppress("UNCHECKED_CAST") val rClassOutputJar = linkAppClass.getMethodOrNull("getRClassOutputJar")?.invoke(task) as Provider? - rClassOutputJar?.orNull?.asFile?.let { result += it } + rClassOutputJar?.orNull?.asFile?.let { result += AndroidDependency("R.jar", it) } } } @@ -164,15 +176,16 @@ open class KotlinMultiplatformExtension : private fun collectDependencies( module: ModuleIdentifier, compileClasspathConf: Configuration - ): List { + ): List { val viewConfig: (ArtifactView.ViewConfiguration) -> Unit = { config -> config.attributes { it.attribute(ARTIFACT_TYPE, AndroidArtifacts.ArtifactType.JAR.type) } config.isLenient = true } val resolvedArtifacts = compileClasspathConf.incoming.artifactView(viewConfig).artifacts.mapNotNull { - val id = (it.id.componentIdentifier as? DefaultModuleComponentIdentifier)?.moduleIdentifier ?: return@mapNotNull null - id to it + val componentIdentifier = it.id.componentIdentifier as? ModuleComponentIdentifier ?: return@mapNotNull null + val id = componentIdentifier.moduleIdentifier ?: return@mapNotNull null + id to AndroidDependency(id.name, it.file, null, id.group, componentIdentifier.version) }.toMap() val resolutionResults = compileClasspathConf.incoming.resolutionResult.allComponents.mapNotNull { @@ -181,7 +194,7 @@ open class KotlinMultiplatformExtension : }.toMap() val deps = HashSet().also { doCollectDependencies(listOf(module), resolutionResults, it) } - return deps.mapNotNull { resolvedArtifacts[it] }.map { it.file } + return deps.mapNotNull { resolvedArtifacts[it] } } @Suppress("UnstableApiUsage") From aaf939a650bb85e1d329aabc3f2dde17e132679a Mon Sep 17 00:00:00 2001 From: Vyacheslav Karpukhin Date: Fri, 24 Jan 2020 18:50:13 +0100 Subject: [PATCH 07/11] Android dependency resolving stuff moved out of KotlinMultiplatformExtension.kt --- .../gradle/dsl/AndroidDependencyResolver.kt | 178 ++++++++++++++++++ .../dsl/KotlinMultiplatformExtension.kt | 178 +----------------- 2 files changed, 186 insertions(+), 170 deletions(-) create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/AndroidDependencyResolver.kt diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/AndroidDependencyResolver.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/AndroidDependencyResolver.kt new file mode 100644 index 00000000000..3454c8c25b4 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/AndroidDependencyResolver.kt @@ -0,0 +1,178 @@ +/* + * 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.dsl + +import com.android.build.gradle.BaseExtension +import com.android.build.gradle.api.AndroidSourceSet +import com.android.build.gradle.internal.LoggerWrapper +import com.android.build.gradle.internal.publishing.AndroidArtifacts +import com.android.sdklib.IAndroidTarget +import com.android.sdklib.repository.AndroidSdkHandler +import com.android.sdklib.repository.LoggerProgressIndicatorWrapper +import com.intellij.openapi.util.Version +import org.gradle.api.Project +import org.gradle.api.artifacts.* +import org.gradle.api.artifacts.component.ModuleComponentIdentifier +import org.gradle.api.artifacts.component.ModuleComponentSelector +import org.gradle.api.artifacts.result.ResolvedComponentResult +import org.gradle.api.file.FileSystemLocation +import org.gradle.api.provider.Provider +import org.jetbrains.kotlin.gradle.plugin.forEachVariant +import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName +import java.io.File +import kotlin.reflect.full.findParameterByName + +data class AndroidDependency( + val name: String? = null, + val jar: File? = null, + val source: File? = null, + val group: String? = null, + val version: String? = null, + val collection: Set? = null +) + +object AndroidDependencyResolver { + private fun getAndroidSdkJar(project: Project): AndroidDependency? { + val androidExtension = project.extensions.findByName("android") as BaseExtension? ?: return null + val sdkLocation = getClassOrNull("com.android.build.gradle.internal.SdkLocationSourceSet")?.let { sdkLocationSourceSetClass -> + val sdkLocationSourceSet = sdkLocationSourceSetClass.kotlin.constructors.firstOrNull()?.let { callable -> + callable.findParameterByName("projectRoot")?.let { param -> + callable.callBy(mapOf(param to project.rootDir)) + } + } + + val sdkLocatorClass = getClassOrNull("com.android.build.gradle.internal.SdkLocator") + val sdkLocation = + sdkLocatorClass?.getMethodOrNull("getSdkLocation", sdkLocationSourceSetClass)?.invoke(sdkLocatorClass, sdkLocationSourceSet) + sdkLocation?.javaClass?.getMethodOrNull("getDirectory")?.invoke(sdkLocation) as? File + } ?: return null + val sdkHandler = AndroidSdkHandler.getInstance(sdkLocation) + val logger = LoggerProgressIndicatorWrapper(LoggerWrapper(project.logger)) + val androidTarget = sdkHandler.getAndroidTargetManager(logger).getTargetFromHashString(androidExtension.compileSdkVersion, logger) + return AndroidDependency( + androidTarget.fullName, + File(androidTarget.getPath(IAndroidTarget.ANDROID_JAR)), + File(androidTarget.getPath(IAndroidTarget.SOURCES)) + ) + } + + private fun getClassOrNull(s: String) = + try { + Class.forName(s) + } catch (e: Exception) { + null + } + + private fun Class<*>.getMethodOrNull(name: String, vararg parameterTypes: Class<*>) = + try { + getMethod(name, *parameterTypes) + } catch (e: Exception) { + null + } + + private fun isAndroidPluginCompatible(): Boolean { + val version = getClassOrNull("com.android.Version")?.let { + try { + it.getField("ANDROID_GRADLE_PLUGIN_VERSION").get(null) as String + } catch (e: Exception) { + return false + } + } ?: return false + return Version.parseVersion(version)?.isOrGreaterThan(3, 6) ?: false + } + + fun getAndroidSourceSetDependencies(project: Project): Map?> { + if (!isAndroidPluginCompatible()) return emptyMap() + + data class SourceSetConfigs(val implConfig: Configuration, val compileConfig: Configuration) + + val androidSdkJar = getAndroidSdkJar(project) ?: return emptyMap() + val sourceSet2Impl = HashMap() + val allImplConfigs = HashSet() + + project.forEachVariant { variant -> + val compileConfig = variant.compileConfiguration + variant.sourceSets.filterIsInstance(AndroidSourceSet::class.java).map { + val implConfig = project.configurations.getByName(it.implementationConfigurationName) + allImplConfigs.add(implConfig) + sourceSet2Impl[lowerCamelCaseName("android", it.name)] = SourceSetConfigs(implConfig, compileConfig) + } + } + + return sourceSet2Impl.mapValues { entry -> + val dependencies = findDependencies(allImplConfigs, entry.value.implConfig) + + val selfResolved = dependencies.filterIsInstance().map { AndroidDependency(collection = it.resolve()) } + val resolvedExternal = dependencies.filterIsInstance() + .flatMap { collectDependencies(it.module, entry.value.compileConfig) } + + val result = (selfResolved + resolvedExternal + androidSdkJar).toMutableList() + + if (entry.key == "androidMain") { + // this is a terrible hack, but looks like the only way, other than proper support via light-classes + val task = project.tasks.findByName("processDebugResources") + getClassOrNull("com.android.build.gradle.internal.res.LinkApplicationAndroidResourcesTask")?.let { linkAppClass -> + @Suppress("UNCHECKED_CAST") + val rClassOutputJar = + linkAppClass.getMethodOrNull("getRClassOutputJar")?.invoke(task) as Provider? + rClassOutputJar?.orNull?.asFile?.let { result += AndroidDependency("R.jar", it) } + } + } + + result + }.toMap() + } + + @Suppress("UnstableApiUsage") + private fun collectDependencies( + module: ModuleIdentifier, + compileClasspathConf: Configuration + ): List { + val viewConfig: (ArtifactView.ViewConfiguration) -> Unit = { config -> + config.attributes { it.attribute(AndroidArtifacts.ARTIFACT_TYPE, AndroidArtifacts.ArtifactType.JAR.type) } + config.isLenient = true + } + + val resolvedArtifacts = compileClasspathConf.incoming.artifactView(viewConfig).artifacts.mapNotNull { + val componentIdentifier = it.id.componentIdentifier as? ModuleComponentIdentifier ?: return@mapNotNull null + val id = componentIdentifier.moduleIdentifier ?: return@mapNotNull null + id to AndroidDependency(id.name, it.file, null, id.group, componentIdentifier.version) + }.toMap() + + val resolutionResults = compileClasspathConf.incoming.resolutionResult.allComponents.mapNotNull { + val id = it.moduleVersion?.module ?: return@mapNotNull null + id to it + }.toMap() + + val deps = HashSet().also { doCollectDependencies(listOf(module), resolutionResults, it) } + return deps.mapNotNull { resolvedArtifacts[it] } + } + + @Suppress("UnstableApiUsage") + private tailrec fun doCollectDependencies( + modules: List, + resolutionResults: Map, + result: MutableSet + ) { + if (modules.isEmpty()) return + val newModules = modules.filter { result.add(it) }.mapNotNull { resolutionResults[it] }.flatMap { it.dependencies } + .mapNotNull { (it.requested as? ModuleComponentSelector)?.moduleIdentifier } + doCollectDependencies(newModules, resolutionResults, result) + } + + private fun findDependencies(implConfigs: Set, conf: Configuration): Set { + return HashSet().also { doFindDependencies(implConfigs, listOf(conf), it) } + } + + private tailrec fun doFindDependencies( + implConfigs: Set, configs: List, result: MutableSet, + visited: MutableSet = HashSet() + ) { + if (configs.isEmpty()) return + result.addAll(configs.flatMap { it.dependencies }) + doFindDependencies(implConfigs, configs.flatMap { it.extendsFrom }.filter { it !in implConfigs && visited.add(it) }, result) + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt index f1dde142656..33f24aefb2d 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt @@ -5,37 +5,21 @@ package org.jetbrains.kotlin.gradle.dsl -import com.android.build.gradle.BaseExtension -import com.android.build.gradle.api.AndroidSourceSet -import com.android.build.gradle.internal.LoggerWrapper -import com.android.build.gradle.internal.publishing.AndroidArtifacts -import com.android.build.gradle.internal.publishing.AndroidArtifacts.ARTIFACT_TYPE -import com.android.sdklib.IAndroidTarget -import com.android.sdklib.repository.AndroidSdkHandler -import com.android.sdklib.repository.LoggerProgressIndicatorWrapper -import com.intellij.openapi.util.Version import groovy.lang.Closure import org.gradle.api.InvalidUserCodeException import org.gradle.api.NamedDomainObjectCollection import org.gradle.api.Project -import org.gradle.api.artifacts.* -import org.gradle.api.artifacts.component.ModuleComponentIdentifier -import org.gradle.api.artifacts.component.ModuleComponentSelector -import org.gradle.api.artifacts.result.ResolvedComponentResult -import org.gradle.api.file.FileSystemLocation -import org.gradle.api.provider.Provider import org.gradle.util.ConfigureUtil -import org.jetbrains.kotlin.gradle.plugin.* +import org.jetbrains.kotlin.gradle.plugin.KotlinTarget +import org.jetbrains.kotlin.gradle.plugin.KotlinTargetPreset +import org.jetbrains.kotlin.gradle.plugin.KotlinTargetWithTests +import org.jetbrains.kotlin.gradle.plugin.KotlinTargetsContainerWithPresets import org.jetbrains.kotlin.gradle.plugin.mpp.* -import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName -import java.io.File -import kotlin.reflect.full.findParameterByName open class KotlinMultiplatformExtension : KotlinProjectExtension(), KotlinTargetContainerWithPresetFunctions, - KotlinTargetContainerWithNativeShortcuts -{ + KotlinTargetContainerWithNativeShortcuts { override lateinit var presets: NamedDomainObjectCollection> internal set @@ -72,155 +56,9 @@ open class KotlinMultiplatformExtension : KotlinSoftwareComponentWithCoordinatesAndPublication("kotlin", targets) } - data class AndroidDependency( - val name: String? = null, - val jar: File? = null, - val source: File? = null, - val group: String? = null, - val version: String? = null, - val collection: Set? = null - ) - - private fun getAndroidSdkJar(project: Project): AndroidDependency? { - val androidExtension = project.extensions.findByName("android") as BaseExtension? ?: return null - val sdkLocation = getClassOrNull("com.android.build.gradle.internal.SdkLocationSourceSet")?.let { sdkLocationSourceSetClass -> - val sdkLocationSourceSet = sdkLocationSourceSetClass.kotlin.constructors.firstOrNull()?.let { callable -> - callable.findParameterByName("projectRoot")?.let { param -> - callable.callBy(mapOf(param to project.rootDir)) - } - } - - val sdkLocatorClass = getClassOrNull("com.android.build.gradle.internal.SdkLocator") - val sdkLocation = - sdkLocatorClass?.getMethodOrNull("getSdkLocation", sdkLocationSourceSetClass)?.invoke(sdkLocatorClass, sdkLocationSourceSet) - sdkLocation?.javaClass?.getMethodOrNull("getDirectory")?.invoke(sdkLocation) as? File - } ?: return null - val sdkHandler = AndroidSdkHandler.getInstance(sdkLocation) - val logger = LoggerProgressIndicatorWrapper(LoggerWrapper(project.logger)) - val androidTarget = sdkHandler.getAndroidTargetManager(logger).getTargetFromHashString(androidExtension.compileSdkVersion, logger) - return AndroidDependency( - androidTarget.fullName, - File(androidTarget.getPath(IAndroidTarget.ANDROID_JAR)), - File(androidTarget.getPath(IAndroidTarget.SOURCES)) - ) - } - - private fun getClassOrNull(s: String) = - try { - Class.forName(s) - } catch (e: Exception) { - null - } - - private fun Class<*>.getMethodOrNull(name: String, vararg parameterTypes: Class<*>) = - try { - getMethod(name, *parameterTypes) - } catch (e: Exception) { - null - } - - private fun isAndroidPluginCompatible(): Boolean { - val version = getClassOrNull("com.android.Version")?.let { - try { - it.getField("ANDROID_GRADLE_PLUGIN_VERSION").get(null) as String - } catch (e: Exception) { - return false - } - } ?: return false - return Version.parseVersion(version)?.isOrGreaterThan(3, 6) ?: false - } - - fun getAndroidSourceSetDependencies(project: Project): Map?> { - if (!isAndroidPluginCompatible()) return emptyMap() - - data class SourceSetConfigs(val implConfig: Configuration, val compileConfig: Configuration) - - val androidSdkJar = getAndroidSdkJar(project) ?: return emptyMap() - val sourceSet2Impl = HashMap() - val allImplConfigs = HashSet() - - project.forEachVariant { variant -> - val compileConfig = variant.compileConfiguration - variant.sourceSets.filterIsInstance(AndroidSourceSet::class.java).map { - val implConfig = project.configurations.getByName(it.implementationConfigurationName) - allImplConfigs.add(implConfig) - sourceSet2Impl[lowerCamelCaseName("android", it.name)] = SourceSetConfigs(implConfig, compileConfig) - } - } - - return sourceSet2Impl.mapValues { entry -> - val dependencies = findDependencies(allImplConfigs, entry.value.implConfig) - - val selfResolved = dependencies.filterIsInstance().map { AndroidDependency(collection = it.resolve()) } - val resolvedExternal = dependencies.filterIsInstance() - .flatMap { collectDependencies(it.module, entry.value.compileConfig) } - - val result = (selfResolved + resolvedExternal + androidSdkJar).toMutableList() - - if (entry.key == "androidMain") { - // this is a terrible hack, but looks like the only way, other than proper support via light-classes - val task = project.tasks.findByName("processDebugResources") - getClassOrNull("com.android.build.gradle.internal.res.LinkApplicationAndroidResourcesTask")?.let { linkAppClass -> - @Suppress("UNCHECKED_CAST") - val rClassOutputJar = - linkAppClass.getMethodOrNull("getRClassOutputJar")?.invoke(task) as Provider? - rClassOutputJar?.orNull?.asFile?.let { result += AndroidDependency("R.jar", it) } - } - } - - result - }.toMap() - } - - @Suppress("UnstableApiUsage") - private fun collectDependencies( - module: ModuleIdentifier, - compileClasspathConf: Configuration - ): List { - val viewConfig: (ArtifactView.ViewConfiguration) -> Unit = { config -> - config.attributes { it.attribute(ARTIFACT_TYPE, AndroidArtifacts.ArtifactType.JAR.type) } - config.isLenient = true - } - - val resolvedArtifacts = compileClasspathConf.incoming.artifactView(viewConfig).artifacts.mapNotNull { - val componentIdentifier = it.id.componentIdentifier as? ModuleComponentIdentifier ?: return@mapNotNull null - val id = componentIdentifier.moduleIdentifier ?: return@mapNotNull null - id to AndroidDependency(id.name, it.file, null, id.group, componentIdentifier.version) - }.toMap() - - val resolutionResults = compileClasspathConf.incoming.resolutionResult.allComponents.mapNotNull { - val id = it.moduleVersion?.module ?: return@mapNotNull null - id to it - }.toMap() - - val deps = HashSet().also { doCollectDependencies(listOf(module), resolutionResults, it) } - return deps.mapNotNull { resolvedArtifacts[it] } - } - - @Suppress("UnstableApiUsage") - private tailrec fun doCollectDependencies( - modules: List, - resolutionResults: Map, - result: MutableSet - ) { - if (modules.isEmpty()) return - val newModules = modules.filter { result.add(it) }.mapNotNull { resolutionResults[it] }.flatMap { it.dependencies } - .mapNotNull { (it.requested as? ModuleComponentSelector)?.moduleIdentifier } - doCollectDependencies(newModules, resolutionResults, result) - } - - private fun findDependencies(implConfigs: Set, conf: Configuration): Set { - return HashSet().also { doFindDependencies(implConfigs, listOf(conf), it) } - } - - private tailrec fun doFindDependencies( - implConfigs: Set, configs: List, result: MutableSet, - visited: MutableSet = HashSet() - ) { - if (configs.isEmpty()) return - result.addAll(configs.flatMap { it.dependencies }) - doFindDependencies(implConfigs, configs.flatMap { it.extendsFrom }.filter { it !in implConfigs && visited.add(it) }, result) - } + @Suppress("unused") + fun getAndroidSourceSetDependencies(project: Project) = + AndroidDependencyResolver.getAndroidSourceSetDependencies(project) } internal fun KotlinTarget.isProducedFromPreset(kotlinTargetPreset: KotlinTargetPreset<*>): Boolean = From bdb8811f95a9790a22372ea1cee2eea72fbc84bf Mon Sep 17 00:00:00 2001 From: Vyacheslav Karpukhin Date: Mon, 3 Feb 2020 20:23:42 +0100 Subject: [PATCH 08/11] Nullability check --- .../jetbrains/kotlin/gradle/dsl/AndroidDependencyResolver.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/AndroidDependencyResolver.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/AndroidDependencyResolver.kt index 3454c8c25b4..91e3c712abd 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/AndroidDependencyResolver.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/AndroidDependencyResolver.kt @@ -51,7 +51,8 @@ object AndroidDependencyResolver { } ?: return null val sdkHandler = AndroidSdkHandler.getInstance(sdkLocation) val logger = LoggerProgressIndicatorWrapper(LoggerWrapper(project.logger)) - val androidTarget = sdkHandler.getAndroidTargetManager(logger).getTargetFromHashString(androidExtension.compileSdkVersion, logger) + val androidTarget = + sdkHandler.getAndroidTargetManager(logger).getTargetFromHashString(androidExtension.compileSdkVersion, logger) ?: return null return AndroidDependency( androidTarget.fullName, File(androidTarget.getPath(IAndroidTarget.ANDROID_JAR)), From 082c358f9de3a346f0a465997da891f6ef34ea69 Mon Sep 17 00:00:00 2001 From: Vyacheslav Karpukhin Date: Wed, 12 Feb 2020 19:12:01 +0100 Subject: [PATCH 09/11] Removed references to AndroidDependencyResolver from KotlinMultiplatformExtension --- .../src/KotlinMPPGradleModelBuilder.kt | 20 +++++++++++-------- .../dsl/KotlinMultiplatformExtension.kt | 5 ----- .../internal}/AndroidDependencyResolver.kt | 3 ++- 3 files changed, 14 insertions(+), 14 deletions(-) rename libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/{dsl => targets/android/internal}/AndroidDependencyResolver.kt (99%) diff --git a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt index 9af1518638e..74073c440e3 100644 --- a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt +++ b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt @@ -141,7 +141,7 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { @Suppress("UNCHECKED_CAST") val sourceSets = (getSourceSets(kotlinExt) as? NamedDomainObjectContainer)?.asMap?.values ?: emptyList() - val androidDeps = buildAndroidDeps(kotlinExt, project) + val androidDeps = buildAndroidDeps(kotlinExt.javaClass.classLoader, project) // Some performance optimisation: do not build metadata dependencies if source set is not common val doBuildMetadataDependencies = @@ -171,16 +171,20 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { } } - private fun buildAndroidDeps(kotlinExt: Any, project: Project): Map>? { + private fun buildAndroidDeps(classLoader: ClassLoader, project: Project): Map>? { val includeAndroidDeps = project.properties["kotlin.include.android.dependencies"]?.toString()?.toBoolean() == true if (includeAndroidDeps) { - val getAndroidSourceSetDependencies = - kotlinExt.javaClass.getMethodOrNull("getAndroidSourceSetDependencies", Project::class.java) - @Suppress("UNCHECKED_CAST") - return getAndroidSourceSetDependencies?.let { it(kotlinExt, project) } as Map>? - } else { - return emptyMap() + try { + val resolverClass = classLoader.loadClass("org.jetbrains.kotlin.gradle.targets.android.internal.AndroidDependencyResolver") + val getAndroidSourceSetDependencies = resolverClass.getMethodOrNull("getAndroidSourceSetDependencies", Project::class.java) + val resolver = resolverClass.getField("INSTANCE").get(null) + @Suppress("UNCHECKED_CAST") + return getAndroidSourceSetDependencies?.let { it(resolver, project) } as Map>? + } catch (e: Exception) { + logger.info("Unexpected exception", e) + } } + return null } private fun buildSourceSet( diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt index 33f24aefb2d..a32324669d8 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt @@ -8,7 +8,6 @@ package org.jetbrains.kotlin.gradle.dsl import groovy.lang.Closure import org.gradle.api.InvalidUserCodeException import org.gradle.api.NamedDomainObjectCollection -import org.gradle.api.Project import org.gradle.util.ConfigureUtil import org.jetbrains.kotlin.gradle.plugin.KotlinTarget import org.jetbrains.kotlin.gradle.plugin.KotlinTargetPreset @@ -55,10 +54,6 @@ open class KotlinMultiplatformExtension : internal val rootSoftwareComponent: KotlinSoftwareComponent by lazy { KotlinSoftwareComponentWithCoordinatesAndPublication("kotlin", targets) } - - @Suppress("unused") - fun getAndroidSourceSetDependencies(project: Project) = - AndroidDependencyResolver.getAndroidSourceSetDependencies(project) } internal fun KotlinTarget.isProducedFromPreset(kotlinTargetPreset: KotlinTargetPreset<*>): Boolean = diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/AndroidDependencyResolver.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/internal/AndroidDependencyResolver.kt similarity index 99% rename from libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/AndroidDependencyResolver.kt rename to libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/internal/AndroidDependencyResolver.kt index 91e3c712abd..8c8bb66dd9c 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/AndroidDependencyResolver.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/internal/AndroidDependencyResolver.kt @@ -3,7 +3,7 @@ * 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.dsl +package org.jetbrains.kotlin.gradle.targets.android.internal import com.android.build.gradle.BaseExtension import com.android.build.gradle.api.AndroidSourceSet @@ -34,6 +34,7 @@ data class AndroidDependency( val collection: Set? = null ) +@Suppress("unused") object AndroidDependencyResolver { private fun getAndroidSdkJar(project: Project): AndroidDependency? { val androidExtension = project.extensions.findByName("android") as BaseExtension? ?: return null From d9f9c93dc89eb4948f7d3e563f2ca99ca9317ee4 Mon Sep 17 00:00:00 2001 From: Vyacheslav Karpukhin Date: Thu, 13 Feb 2020 15:06:39 +0100 Subject: [PATCH 10/11] KotlinMPPGradleProjectResolver: extracted similar Android checks --- .../KotlinMPPGradleProjectResolver.kt | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) 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 01827d4c970..86211048604 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 @@ -282,7 +282,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { val sourceSetToCompilationData = LinkedHashMap>() for (target in mppModel.targets) { - if (androidPluginPresent && target.platform == KotlinPlatform.ANDROID) continue + if (delegateToAndroidPlugin(target)) continue if (target.name == KotlinTarget.METADATA_TARGET_NAME) continue val targetData = KotlinTargetData(target.name).also { it.archiveFile = target.jar?.archiveFile @@ -356,7 +356,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { val ignoreCommonSourceSets by lazy { externalProject.notImportedCommonSourceSets() } for (sourceSet in mppModel.sourceSets.values) { - if (androidPluginPresent && sourceSet.actualPlatforms.platforms.singleOrNull() == KotlinPlatform.ANDROID) continue + if (delegateToAndroidPlugin(sourceSet)) continue if (sourceSet.actualPlatforms.supports(KotlinPlatform.COMMON) && ignoreCommonSourceSets) continue val moduleId = getKotlinModuleId(gradleModule, sourceSet, resolverCtx) val existingSourceSetDataNode = sourceSetMap[moduleId]?.first @@ -454,9 +454,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { .toMap() if (resolverCtx.getExtraProject(gradleModule, ExternalProject::class.java) == null) return processSourceSets(gradleModule, mppModel, ideModule, resolverCtx) { dataNode, sourceSet -> - if (dataNode == null || androidPluginPresent && - sourceSet.actualPlatforms.platforms.singleOrNull() == KotlinPlatform.ANDROID - ) return@processSourceSets + if (dataNode == null || delegateToAndroidPlugin(sourceSet)) return@processSourceSets createContentRootData( sourceSet.sourceDirs, @@ -567,7 +565,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { } val closedSourceSetGraph = Graphs.transitiveClosure(sourceSetGraph) for (sourceSet in closedSourceSetGraph.nodes()) { - val isAndroid = androidPluginPresent && sourceSet.actualPlatforms.platforms.singleOrNull() == KotlinPlatform.ANDROID + val isAndroid = delegateToAndroidPlugin(sourceSet) val fromDataNode = if (isAndroid) { ideModule } else { @@ -587,7 +585,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { sourceSetInfo.addSourceSets(dependeeSourceSets, selfName, gradleModule, resolverCtx) } } - if (androidPluginPresent && sourceSet.actualPlatforms.platforms.singleOrNull() == KotlinPlatform.ANDROID) continue + if (delegateToAndroidPlugin(sourceSet)) continue for (dependeeSourceSet in dependeeSourceSets) { val toDataNode = getSiblingKotlinModuleData(dependeeSourceSet, gradleModule, ideModule, resolverCtx) ?: continue addDependency(fromDataNode, toDataNode, dependeeSourceSet.isTestModule) @@ -756,7 +754,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { } } for (target in mppModel.targets) { - if (androidPluginPresent && target.platform == KotlinPlatform.ANDROID) continue + if (delegateToAndroidPlugin(target)) continue for (compilation in target.compilations) { val moduleId = getKotlinModuleId(gradleModule, compilation, resolverCtx) val moduleDataNode = sourceSetsMap[moduleId] ?: continue @@ -976,6 +974,12 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { "true", ignoreCase = true ) ?: false + + private fun delegateToAndroidPlugin(kotlinTarget: KotlinTarget): Boolean = + androidPluginPresent && kotlinTarget.platform == KotlinPlatform.ANDROID + + private fun delegateToAndroidPlugin(kotlinSourceSet: KotlinSourceSet): Boolean = + androidPluginPresent && kotlinSourceSet.actualPlatforms.platforms.singleOrNull() == KotlinPlatform.ANDROID } } From 4462ecb28004f4f9962defbc5ea385a45aa0dd23 Mon Sep 17 00:00:00 2001 From: Vyacheslav Karpukhin Date: Thu, 13 Feb 2020 16:24:08 +0100 Subject: [PATCH 11/11] Simplified SdkLocator usage in AndroidDependencyResolver --- .../android/internal/AndroidDependencyResolver.kt | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/internal/AndroidDependencyResolver.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/internal/AndroidDependencyResolver.kt index 8c8bb66dd9c..9f6e65c6ebb 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/internal/AndroidDependencyResolver.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/internal/AndroidDependencyResolver.kt @@ -38,16 +38,9 @@ data class AndroidDependency( object AndroidDependencyResolver { private fun getAndroidSdkJar(project: Project): AndroidDependency? { val androidExtension = project.extensions.findByName("android") as BaseExtension? ?: return null - val sdkLocation = getClassOrNull("com.android.build.gradle.internal.SdkLocationSourceSet")?.let { sdkLocationSourceSetClass -> - val sdkLocationSourceSet = sdkLocationSourceSetClass.kotlin.constructors.firstOrNull()?.let { callable -> - callable.findParameterByName("projectRoot")?.let { param -> - callable.callBy(mapOf(param to project.rootDir)) - } - } - - val sdkLocatorClass = getClassOrNull("com.android.build.gradle.internal.SdkLocator") + val sdkLocation = getClassOrNull("com.android.build.gradle.internal.SdkLocator")?.let { sdkLocatorClass -> val sdkLocation = - sdkLocatorClass?.getMethodOrNull("getSdkLocation", sdkLocationSourceSetClass)?.invoke(sdkLocatorClass, sdkLocationSourceSet) + sdkLocatorClass.getMethodOrNull("getSdkLocation", File::class.java)?.invoke(sdkLocatorClass, project.rootDir) sdkLocation?.javaClass?.getMethodOrNull("getDirectory")?.invoke(sdkLocation) as? File } ?: return null val sdkHandler = AndroidSdkHandler.getInstance(sdkLocation)