Correctly propagate dependencies in multiplatform projects with Android

This commit is contained in:
Dmitry Jemerov
2017-10-22 15:50:18 +02:00
parent cc85ac44b3
commit 81615fa551
5 changed files with 154 additions and 16 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ dependencies {
compile(ideaSdkDeps("openapi", "idea"))
compile(ideaPluginDeps("gradle-tooling-api", plugin = "gradle"))
compile(ideaPluginDeps("android", "android-common", "sdklib", "sdk-common", "layoutlib-api", plugin = "android"))
compile(ideaPluginDeps("android", "android-common", "sdklib", "sdk-common", "sdk-tools", "layoutlib-api", plugin = "android"))
compile(preloadedDeps("dx", subdir = "android-5.0/lib"))
testCompile(projectDist(":kotlin-test:kotlin-test-jvm"))
@@ -34,8 +34,12 @@ package org.jetbrains.kotlin.android.configure
import com.android.tools.idea.gradle.project.sync.idea.data.service.AndroidProjectKeys
import com.intellij.openapi.externalSystem.model.DataNode
import com.intellij.openapi.externalSystem.model.project.ModuleData
import com.intellij.openapi.externalSystem.model.project.ProjectData
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
import org.gradle.tooling.model.idea.IdeaProject
import org.jetbrains.kotlin.idea.inspections.gradle.KotlinGradleModelFacade
import org.jetbrains.kotlin.idea.inspections.gradle.findModulesByNames
class AndroidGradleModelFacade : KotlinGradleModelFacade {
override fun getResolvedKotlinStdlibVersionByModuleData(moduleData: DataNode<*>, libraryIds: List<String>): String? {
@@ -51,4 +55,24 @@ class AndroidGradleModelFacade : KotlinGradleModelFacade {
}
return null
}
}
override fun getDependencyModules(ideModule: DataNode<ModuleData>, gradleIdeaProject: IdeaProject): Collection<DataNode<ModuleData>> {
val ideProject = ideModule.parent as DataNode<ProjectData>
ExternalSystemApiUtil.find(ideModule, AndroidProjectKeys.JAVA_MODULE_MODEL)?.let { javaModuleModel ->
val moduleNames = javaModuleModel.data.javaModuleDependencies.map { it.moduleName }
return findModulesByNames(moduleNames, gradleIdeaProject, ideProject)
}
ExternalSystemApiUtil.find(ideModule, AndroidProjectKeys.ANDROID_MODEL)?.let { androidModel ->
val libraries = androidModel.data.mainArtifact.dependencies.javaLibraries
val projects = androidModel.data.mainArtifact.dependencies.projects
val projectIds = libraries.mapNotNull { it.project } + projects
return projectIds.mapNotNullTo(LinkedHashSet()) { projectId ->
ExternalSystemApiUtil.findFirstRecursively(ideProject) {
(it.data as? ModuleData)?.id == projectId
} as DataNode<ModuleData>?
}
}
return emptyList()
}
}
@@ -26,10 +26,10 @@ import com.intellij.openapi.roots.DependencyScope
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.io.FileUtil
import org.gradle.tooling.model.idea.IdeaModule
import org.gradle.tooling.model.idea.IdeaModuleDependency
import org.jetbrains.kotlin.gradle.CompilerArgumentsBySourceSet
import org.jetbrains.kotlin.gradle.KotlinGradleModel
import org.jetbrains.kotlin.gradle.KotlinGradleModelBuilder
import org.jetbrains.kotlin.idea.inspections.gradle.getDependencyModules
import org.jetbrains.kotlin.psi.NotNullableUserDataProperty
import org.jetbrains.kotlin.psi.UserDataProperty
import org.jetbrains.plugins.gradle.model.ExternalProjectDependency
@@ -81,10 +81,7 @@ class KotlinGradleProjectResolverExtension : AbstractProjectResolverExtension()
val gradleIdeaProject = gradleModule.project
fun DataNode<out ModuleData>.getGradleModule() =
if (this != ideModule) gradleIdeaProject.modules.firstOrNull { it.gradleProject.path == data.id } else gradleModule
fun DataNode<out ModuleData>.getDependencies(): Set<DataNode<out ModuleData>> {
fun DataNode<out ModuleData>.getDependencies(): Collection<DataNode<out ModuleData>> {
if (useModulePerSourceSet()) {
if (sourceSetByName == null) return emptySet()
val externalSourceSet = sourceSetByName[data.id]?.second ?: return emptySet()
@@ -110,14 +107,7 @@ class KotlinGradleProjectResolverExtension : AbstractProjectResolverExtension()
}
}
val dependencies = getGradleModule()?.dependencies
return dependencies?.mapNotNullTo(LinkedHashSet()) {
val targetModuleName = (it as? IdeaModuleDependency)?.targetModuleName ?: return@mapNotNullTo null
val targetGradleModule = gradleIdeaProject.modules.firstOrNull { it.name == targetModuleName } ?: return@mapNotNullTo null
ExternalSystemApiUtil.findFirstRecursively(ideProject) {
(it.data as? ModuleData)?.id == targetGradleModule.gradleProject.path
} as DataNode<ModuleData>?
} ?: emptySet()
return getDependencyModules(ideModule, gradleIdeaProject)
}
fun addTransitiveDependenciesOnImplementedModules() {
@@ -17,9 +17,14 @@
package org.jetbrains.kotlin.idea.inspections.gradle
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.extensions.Extensions
import com.intellij.openapi.externalSystem.model.DataNode
import com.intellij.openapi.externalSystem.model.ProjectKeys
import com.intellij.openapi.externalSystem.model.project.ModuleData
import com.intellij.openapi.externalSystem.model.project.ProjectData
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
import org.gradle.tooling.model.idea.IdeaModuleDependency
import org.gradle.tooling.model.idea.IdeaProject
interface KotlinGradleModelFacade {
companion object {
@@ -27,6 +32,7 @@ interface KotlinGradleModelFacade {
}
fun getResolvedKotlinStdlibVersionByModuleData(moduleData: DataNode<*>, libraryIds: List<String>): String?
fun getDependencyModules(ideModule: DataNode<ModuleData>, gradleIdeaProject: IdeaProject): Collection<DataNode<ModuleData>>
}
class DefaultGradleModelFacade : KotlinGradleModelFacade {
@@ -41,4 +47,30 @@ class DefaultGradleModelFacade : KotlinGradleModelFacade {
}
return null
}
}
override fun getDependencyModules(ideModule: DataNode<ModuleData>, gradleIdeaProject: IdeaProject): Collection<DataNode<ModuleData>> {
val ideProject = ideModule.parent as DataNode<ProjectData>
val gradleModule = gradleIdeaProject.modules.firstOrNull { it.gradleProject.path == ideModule.data.id }
val dependencyModuleNames = gradleModule?.dependencies?.mapNotNull { (it as? IdeaModuleDependency)?.targetModuleName } ?: return emptyList()
return findModulesByNames(dependencyModuleNames, gradleIdeaProject, ideProject)
}
}
fun getDependencyModules(moduleData: DataNode<ModuleData>, gradleIdeaProject: IdeaProject): Collection<DataNode<ModuleData>> {
for (modelFacade in Extensions.getExtensions(KotlinGradleModelFacade.EP_NAME)) {
val dependencies = modelFacade.getDependencyModules(moduleData, gradleIdeaProject)
if (dependencies.isNotEmpty()) {
return dependencies
}
}
return emptyList()
}
fun findModulesByNames(dependencyModuleNames: List<String>, gradleIdeaProject: IdeaProject, ideProject: DataNode<ProjectData>): LinkedHashSet<DataNode<ModuleData>> {
return dependencyModuleNames.mapNotNullTo(LinkedHashSet()) { targetModuleName ->
val targetGradleModule = gradleIdeaProject.modules.firstOrNull { it.name == targetModuleName } ?: return@mapNotNullTo null
ExternalSystemApiUtil.findFirstRecursively(ideProject) {
(it.data as? ModuleData)?.id == targetGradleModule.gradleProject.path
} as DataNode<ModuleData>?
}
}
@@ -17,8 +17,10 @@
package org.jetbrains.kotlin.gradle
import com.intellij.openapi.roots.DependencyScope
import com.intellij.openapi.util.text.StringUtil
import org.jetbrains.kotlin.idea.codeInsight.gradle.GradleImportingTestCase
import org.junit.Test
import java.io.File
class MultiplatformProjectImportingTest : GradleImportingTestCase() {
@Test
@@ -385,4 +387,94 @@ class MultiplatformProjectImportingTest : GradleImportingTestCase() {
assertModuleModuleDepScope("project3_custom", "project1_custom", DependencyScope.COMPILE)
assertModuleModuleDepScope("project3_custom", "project2_main", DependencyScope.COMPILE)
}
@Test
fun testTransitiveImplementWithAndroid() {
createProjectSubFile(
"settings.gradle",
"include ':project1', ':project2', ':project3'"
)
val kotlinVersion = "1.1.51"
createProjectSubFile("build.gradle", """
buildscript {
repositories {
jcenter()
maven { url 'https://maven.google.com' }
}
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
classpath 'com.android.tools.build:gradle:2.3.3'
}
}
project('project1') {
apply plugin: 'kotlin-platform-common'
sourceSets {
custom
}
}
project('project2') {
repositories {
mavenCentral()
}
apply plugin: 'kotlin-platform-jvm'
sourceSets {
custom
}
dependencies {
implement project(':project1')
}
}
project('project3') {
repositories {
mavenCentral()
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
sourceSets {
custom
}
android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
defaultConfig {
applicationId "org.jetbrains.kotlin"
minSdkVersion 18
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
}
dependencies {
compile project(':project2')
customCompile project(':project2')
testCompile(project(':project2').sourceSets.test.output)
}
}
""")
createProjectSubFile("local.properties", """
sdk.dir=/${StringUtil.escapeBackSlashes(File(homePath).parent + "/dependencies/androidSDK")}
""")
importProject()
assertModuleModuleDepScope("project3", "project2", DependencyScope.COMPILE)
assertModuleModuleDepScope("project3", "project1", DependencyScope.COMPILE)
}
}