From a70707b72b9faeebd5c7cf8950e51922025e5fc2 Mon Sep 17 00:00:00 2001 From: Yan Zhulanow Date: Thu, 17 May 2018 23:36:38 +0300 Subject: [PATCH] Android Extensions: Allow to access library project resources in Gradle setup (#KT-22430) --- .../AbstractKotlinAndroidGradleTests.kt | 9 +- .../app/build.gradle | 1 + .../app/src/main/java/MainActivity.kt | 2 + .../library/build.gradle | 24 ++++ .../library/src/main/AndroidManifest.xml | 2 + .../src/main/res/layout/layout_in_library.xml | 11 ++ .../library/src/main/res/values/strings.xml | 3 + .../settings.gradle | 2 +- .../gradle/internal/AndroidSubplugin.kt | 108 +++++++++++------- .../kotlin/gradle/plugin/KotlinPlugin.kt | 2 +- 10 files changed, 117 insertions(+), 47 deletions(-) create mode 100755 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/AndroidExtensionsManyVariants/library/build.gradle create mode 100755 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/AndroidExtensionsManyVariants/library/src/main/AndroidManifest.xml create mode 100755 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/AndroidExtensionsManyVariants/library/src/main/res/layout/layout_in_library.xml create mode 100755 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/AndroidExtensionsManyVariants/library/src/main/res/values/strings.xml diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/AbstractKotlinAndroidGradleTests.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/AbstractKotlinAndroidGradleTests.kt index aa4b89faf40..b4ac8578c64 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/AbstractKotlinAndroidGradleTests.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/AbstractKotlinAndroidGradleTests.kt @@ -335,7 +335,14 @@ fun getSomething() = 10 val options = defaultBuildOptions().copy(incremental = false) project.build("assemble", options = options) { - assertSuccessful() + if (isLegacyAndroidGradleVersion(androidGradlePluginVersion)) { + // Library dependencies are not supported in older versions of Android Gradle plugin (< 3.0) + assertFailed() + assertContains("Unresolved reference: layout_in_library") + assertContains("Unresolved reference: text_view") + } else { + assertSuccessful() + } } } diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/AndroidExtensionsManyVariants/app/build.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/AndroidExtensionsManyVariants/app/build.gradle index c80ff935863..7092441fe4d 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/AndroidExtensionsManyVariants/app/build.gradle +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/AndroidExtensionsManyVariants/app/build.gradle @@ -41,4 +41,5 @@ androidExtensions { dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + compile project(":library") } diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/AndroidExtensionsManyVariants/app/src/main/java/MainActivity.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/AndroidExtensionsManyVariants/app/src/main/java/MainActivity.kt index aab20643e48..9ccfe49f352 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/AndroidExtensionsManyVariants/app/src/main/java/MainActivity.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/AndroidExtensionsManyVariants/app/src/main/java/MainActivity.kt @@ -3,11 +3,13 @@ package org.example.manyvariants import android.app.Activity import android.os.Bundle import kotlinx.android.synthetic.main.activity_main.* +import kotlinx.android.synthetic.main.layout_in_library.text_view class MainActivity : Activity() { override fun onCreate(savedInstanceState: Bundle) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) viewMain + text_view } } diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/AndroidExtensionsManyVariants/library/build.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/AndroidExtensionsManyVariants/library/build.gradle new file mode 100755 index 00000000000..4fb0d31e713 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/AndroidExtensionsManyVariants/library/build.gradle @@ -0,0 +1,24 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + compileSdkVersion 23 + buildToolsVersion "25.0.2" + + defaultConfig { + minSdkVersion 15 + targetSdkVersion 23 + versionCode 1 + versionName "1.0" + + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/AndroidExtensionsManyVariants/library/src/main/AndroidManifest.xml b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/AndroidExtensionsManyVariants/library/src/main/AndroidManifest.xml new file mode 100755 index 00000000000..78138528809 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/AndroidExtensionsManyVariants/library/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/AndroidExtensionsManyVariants/library/src/main/res/layout/layout_in_library.xml b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/AndroidExtensionsManyVariants/library/src/main/res/layout/layout_in_library.xml new file mode 100755 index 00000000000..1e6ceceb4b7 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/AndroidExtensionsManyVariants/library/src/main/res/layout/layout_in_library.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/AndroidExtensionsManyVariants/library/src/main/res/values/strings.xml b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/AndroidExtensionsManyVariants/library/src/main/res/values/strings.xml new file mode 100755 index 00000000000..6d5185635a6 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/AndroidExtensionsManyVariants/library/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Library + diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/AndroidExtensionsManyVariants/settings.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/AndroidExtensionsManyVariants/settings.gradle index e7b4def49cb..b0c8eaa09e0 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/AndroidExtensionsManyVariants/settings.gradle +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/AndroidExtensionsManyVariants/settings.gradle @@ -1 +1 @@ -include ':app' +include ':app', ':library' \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/AndroidSubplugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/AndroidSubplugin.kt index baacabf9686..687c16971b6 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/AndroidSubplugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/AndroidSubplugin.kt @@ -28,7 +28,6 @@ import org.gradle.api.UnknownDomainObjectException import org.gradle.api.tasks.SourceSet import org.gradle.api.tasks.compile.AbstractCompile import org.jetbrains.kotlin.gradle.plugin.* -import org.jetbrains.kotlin.gradle.plugin.android.AndroidGradleWrapper import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.w3c.dom.Document import java.io.File @@ -160,74 +159,95 @@ class AndroidSubplugin : KotlinGradleSubplugin { val pluginOptions = arrayListOf() pluginOptions += SubpluginOption("experimental", "true") - pluginOptions += SubpluginOption("defaultCacheImplementation", - androidExtensionsExtension.defaultCacheImplementation.optionName) + pluginOptions += SubpluginOption("defaultCacheImplementation", androidExtensionsExtension.defaultCacheImplementation.optionName) val mainSourceSet = androidExtension.sourceSets.getByName("main") pluginOptions += SubpluginOption("package", getApplicationPackage(project, mainSourceSet)) + val enabledVariants = getVariantComponentNames(variantData) + val allResDirectories = mutableMapOf>() + + androidProjectHandler.forEachVariant(project) { variant -> + val variantName = androidProjectHandler.getVariantName(variant) + if (androidProjectHandler.getTestedVariantData(variant) != null) { + return@forEachVariant + } + + allResDirectories[variantName] = androidProjectHandler.getResDirectories(variant) + } + fun addVariant(name: String, resDirectories: List) { val optionValue = buildString { - append(name) - append(';') + append(name).append(';') resDirectories.joinTo(this, separator = ";") { it.canonicalPath } } - pluginOptions += CompositeSubpluginOption("variant", optionValue, listOf( + + pluginOptions += CompositeSubpluginOption( + "variant", optionValue, listOf( SubpluginOption("variantName", name), // use INTERNAL option kind since the resources are tracked as sources (see below) - FilesSubpluginOption("resDirs", resDirectories))) + FilesSubpluginOption("resDirs", resDirectories) + ) + ) kotlinCompile.source(project.files(getLayoutDirectories(resDirectories))) } - fun addSourceSetAsVariant(name: String) { - val sourceSet = androidExtension.sourceSets.findByName(name) ?: return - val srcDirs = sourceSet.res.srcDirs.toList() - if (srcDirs.isNotEmpty()) { - addVariant(sourceSet.name, srcDirs) - } - } + val actualResDirectories = allResDirectories.filterKeys { it in enabledVariants } + val localProjectResDirs = actualResDirectories.values + .flatMap { it } + .filter { it.isProjectLibraryResDirectory() || it.isPackageResDirectory() } - val resDirectoriesForAllVariants = mutableListOf>() + val commonResDirectories = getCommonResDirectories(allResDirectories.values) + localProjectResDirs + addVariant("main", commonResDirectories.distinct().sorted()) - androidProjectHandler.forEachVariant(project) { variant -> - if (androidProjectHandler.getTestedVariantData(variant) != null) return@forEachVariant - resDirectoriesForAllVariants += androidProjectHandler.getResDirectories(variant) - } + for (sourceSetName in enabledVariants) { + val srcDirs = androidExtension.sourceSets.findByName(sourceSetName) + ?.res?.srcDirs?.toList()?.takeIf { it.isNotEmpty() } ?: continue - val commonResDirectories = getCommonResDirectories(resDirectoriesForAllVariants) - - addVariant("main", commonResDirectories.toList()) - - getVariantComponentNames(variantData)?.let { (variantName, flavorName, buildTypeName) -> - addSourceSetAsVariant(buildTypeName) - - if (flavorName.isNotEmpty()) { - addSourceSetAsVariant(flavorName) - } - - if (buildTypeName != variantName && buildTypeName != flavorName) { - addSourceSetAsVariant(variantName) - } + addVariant(sourceSetName, (srcDirs - commonResDirectories).sorted()) } return wrapPluginOptions(pluginOptions, "configuration") } - // Android25ProjectHandler.KaptVariant actually contains BaseVariant, not BaseVariantData - private fun getVariantComponentNames(flavorData: Any?): VariantComponentNames? = when(flavorData) { - is KaptVariantData<*> -> getVariantComponentNames(flavorData.variantData) - is TestVariantData -> getVariantComponentNames(flavorData.testedVariantData) - is TestVariant -> getVariantComponentNames(flavorData.testedVariant) - is BaseVariant -> VariantComponentNames(flavorData.name, flavorData.flavorName, flavorData.buildType.name) - is BaseVariantData<*> -> VariantComponentNames(flavorData.name, flavorData.variantConfiguration.flavorName, - flavorData.variantConfiguration.buildType.name) - else -> null + private fun File.isPackageResDirectory(): Boolean { + val packagedResDir = parentFile ?: return false + val intermediatesDir = packagedResDir.parentFile ?: return false + val buildDir = intermediatesDir.parentFile ?: return false + + return packagedResDir.name == "packaged_res" && intermediatesDir.name == "intermediates" && buildDir.name == "build" } - private data class VariantComponentNames(val variantName: String, val flavorName: String, val buildTypeName: String) + private fun File.isProjectLibraryResDirectory(): Boolean { + if (name != "res") { + return false + } - private fun getCommonResDirectories(resDirectories: List>): Set { + val bundlesDir = parentFile?.parentFile ?: return false + val intermediatesDir = bundlesDir.parentFile ?: return false + val buildDir = intermediatesDir.parentFile ?: return false + + return bundlesDir.name == "bundles" && intermediatesDir.name == "intermediates" && buildDir.name == "build" + } + + // Android25ProjectHandler.KaptVariant actually contains BaseVariant, not BaseVariantData + private fun getVariantComponentNames(variantData: Any?): List = when(variantData) { + is KaptVariantData<*> -> getVariantComponentNames(variantData.variantData) + is TestVariantData -> getVariantComponentNames(variantData.testedVariantData) + is TestVariant -> getVariantComponentNames(variantData.testedVariant) + is BaseVariant -> listOfNotNull(variantData.name, variantData.flavorName, variantData.buildType.name).distinct() + is BaseVariantData<*> -> { + listOfNotNull( + variantData.name, + variantData.variantConfiguration.flavorName, + variantData.variantConfiguration.buildType.name + ).distinct() + } + else -> emptyList() + } + + private fun getCommonResDirectories(resDirectories: Iterable>): Set { var common = resDirectories.firstOrNull()?.toSet() ?: return emptySet() for (resDirs in resDirectories.drop(1)) { 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 4949db58e64..6e073e686cd 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 @@ -473,12 +473,12 @@ abstract class AbstractAndroidProjectHandler(private val kotlinConfigurationT protected val logger = Logging.getLogger(this.javaClass) abstract fun forEachVariant(project: Project, action: (V) -> Unit): Unit + abstract fun getVariantName(variant: V): String abstract fun getTestedVariantData(variantData: V): V? abstract fun getResDirectories(variantData: V): List protected abstract fun getSourceProviders(variantData: V): Iterable protected abstract fun getAllJavaSources(variantData: V): Iterable - protected abstract fun getVariantName(variant: V): String protected abstract fun getJavaTask(variantData: V): AbstractCompile? protected abstract fun addJavaSourceDirectoryToVariantModel(variantData: V, javaSourceDirectory: File): Unit