Android Extensions: Allow to access library project resources in Gradle setup (#KT-22430)

This commit is contained in:
Yan Zhulanow
2018-05-17 23:36:38 +03:00
parent 5eedbf4177
commit a70707b72b
10 changed files with 117 additions and 47 deletions
@@ -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()
}
}
}
@@ -41,4 +41,5 @@ androidExtensions {
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compile project(":library")
}
@@ -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
}
}
@@ -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'
}
}
}
@@ -0,0 +1,2 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.library" />
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
@@ -0,0 +1,3 @@
<resources>
<string name="app_name">Library</string>
</resources>
@@ -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<KotlinCompile> {
val pluginOptions = arrayListOf<SubpluginOption>()
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<String, List<File>>()
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<File>) {
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<List<File>>()
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<List<File>>): Set<File> {
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<String> = 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<Iterable<File>>): Set<File> {
var common = resDirectories.firstOrNull()?.toSet() ?: return emptySet()
for (resDirs in resDirectories.drop(1)) {
@@ -473,12 +473,12 @@ abstract class AbstractAndroidProjectHandler<V>(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<File>
protected abstract fun getSourceProviders(variantData: V): Iterable<SourceProvider>
protected abstract fun getAllJavaSources(variantData: V): Iterable<File>
protected abstract fun getVariantName(variant: V): String
protected abstract fun getJavaTask(variantData: V): AbstractCompile?
protected abstract fun addJavaSourceDirectoryToVariantModel(variantData: V, javaSourceDirectory: File): Unit