From 17059682e9cdc3a7e51945b8684aa30057cb8570 Mon Sep 17 00:00:00 2001 From: Vyacheslav Karpukhin Date: Wed, 9 Oct 2019 16:58:58 +0200 Subject: [PATCH] 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 =