blob: 99e60699bc085ed5ab1739f22bfe43435e0ed8da [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.build.api.variant.impl
import com.android.SdkConstants
import com.android.build.api.variant.SourceDirectories
import com.android.build.gradle.internal.services.VariantServices
import com.android.builder.core.BuilderConstants
import com.android.ide.common.resources.AssetSet
import org.gradle.api.file.Directory
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.util.PatternFilterable
import java.io.File
open class LayeredSourceDirectoriesImpl(
_name: String,
private val variantServices: VariantServices,
variantDslFilters: PatternFilterable?
): SourceDirectoriesImpl(_name, variantServices, variantDslFilters),
SourceDirectories.Layered {
// For compatibility with the old variant API, we must allow reading the content of this list
// before it is finalized.
@Suppress("UNCHECKED_CAST")
protected val variantSources: ListProperty<DirectoryEntries> =
variantServices.newListPropertyForInternalUse(DirectoryEntries::class.java)
// need this to support layered structure for non generated/source directories
@Suppress("UNCHECKED_CAST")
private val variantStaticSources: ListProperty<DirectoryEntries> =
variantServices.newListPropertyForInternalUse(DirectoryEntries::class.java)
// this will contain all the directories
@Suppress("UNCHECKED_CAST")
private val directories: ListProperty<Collection<Directory>> =
variantServices.newListPropertyForInternalUse(Collection::class.java) as ListProperty<Collection<Directory>>
// collection for static directories (non generated by a Task) only
@Suppress("UNCHECKED_CAST")
private val staticDirectories: ListProperty<Collection<Directory>> =
variantServices.newListPropertyForInternalUse(Collection::class.java) as ListProperty<Collection<Directory>>
override val all: Provider<List<Collection<Directory>>> = directories.map {
it.reversed()
}
override val static: Provider<List<Collection<Directory>>> = staticDirectories.map {
it.reversed()
}
//
// Internal APIs
//
override fun addSource(directoryEntry: DirectoryEntry) {
checkAndAdd(directoryEntry, variantSources, directories)
}
override fun addStaticSource(directoryEntry: DirectoryEntry) {
addSource(directoryEntry)
checkAndAdd(directoryEntry, variantStaticSources, staticDirectories)
}
/**
* We check first if we have existing instance of [DirectoryEntries] under the name
* provided by the passed [DirectoryEntry]. If we do, we just add it to the list of
* directories under that name to respect the priority.
* This is dependent on the DirectoryEntry object for e.g. main being distinct from the
* DirectoryEntries objects for each variant
* otherwise, we just add a new one.
*
* @param directoryEntry - directory to add
* @param sources - list of existing `DirectoryEntries` to find by name and add there or to add
* new Directory Entry if there is no entry with such name
* @param directories - collection of directories we need to update if entry with such name is new
*/
private fun checkAndAdd(
directoryEntry: DirectoryEntry,
sources: ListProperty<DirectoryEntries>,
directories: ListProperty<Collection<Directory>>
) {
val existingDirectories =
sources.get().find { entries -> entries.name == directoryEntry.name }
if (existingDirectories != null) {
existingDirectories.directoryEntries.add(directoryEntry)
} else {
sources.add(
DirectoryEntries(
directoryEntry.name, mutableListOf(directoryEntry)
)
)
variantServices.newListPropertyForInternalUse(Directory::class.java).also {
it.addAll(directoryEntry)
directories.add(it)
}
}
}
private fun ListProperty<Directory>.addAll(directoryEntry: DirectoryEntry) {
addAll(
directoryEntry.asFiles(
variantServices.provider {
variantServices.projectInfo.projectDirectory
}
)
)
}
internal fun addStaticSources(sources: DirectoryEntries) {
variantSources.add(sources)
variantStaticSources.add(sources)
variantServices.newListPropertyForInternalUse(Directory::class.java).also {
sources.directoryEntries.forEach { directoryEntry ->
it.addAll(directoryEntry)
}
directories.add(it)
staticDirectories.add(it)
}
}
/**
* Returns the [List] of [DirectoryEntries] for these sources. This [List] can be
* queried at configuration time provided it is after all variant APIs ran (during
* task configuration basically). It is better to use this method at execution time if
* possible.
*/
fun getVariantSources(): List<DirectoryEntries> = variantSources.get()
override fun forAllSources(action:(DirectoryEntry) -> Unit) {
getVariantSources().forEach { directoryEntries ->
directoryEntries.directoryEntries.forEach { directoryEntry ->
action(directoryEntry)
}
}
}
/**
* Returns the list of local source directories matching the given [filter]
*/
fun getVariantSourcesWithFilter(filter: (DirectoryEntry) -> Boolean = { _ -> true } ): Map<String, Provider<out Collection<Directory>>> =
getVariantSources().associate { directoryEntries ->
val projectDir = variantServices.provider {
variantServices.projectInfo.projectDirectory
}
// each [DirectoryEntries] contains a list of [DirectoryEntry] but we need
// to return a [Provider] on a single collection of [Directory].
//
// In order to achieve that, basically, use [Provider]'s zip method to zip
// up providers together and flatten the list of list into just one list.
var currentZippedValue: Provider<out Collection<Directory>>? = null
directoryEntries.directoryEntries
.filter(filter)
.forEach {
currentZippedValue = if (currentZippedValue == null) {
it.asFiles(projectDir)
} else {
currentZippedValue!!.zip(it.asFiles(projectDir)) {
d1: Collection<Directory>, d2: Collection<Directory> ->
mutableListOf<Directory>().also { result ->
result.addAll(d1)
result.addAll(d2)
}
}
}
}
directoryEntries.name to (currentZippedValue ?:
variantServices.provider { listOf() })
}
/*
* Internal API that can only be used by the model.
*/
override fun variantSourcesForModel(filter: (DirectoryEntry) -> Boolean ): List<File> {
val files = mutableListOf<File>()
variantSources.get()
.map { it.directoryEntries}
.flatten()
.filter { filter.invoke(it) }
.forEach {
if (it is TaskProviderBasedDirectoryEntryImpl) {
files.add(it.directoryProvider.get().asFile)
} else {
val asDirectoriesProperty = it.asFiles(
variantServices.provider {
variantServices.projectInfo.projectDirectory
}
)
if (asDirectoriesProperty.isPresent) {
files.addAll(asDirectoriesProperty.get().map { directory ->
directory.asFile
})
}
}
}
return files
}
/**
* Returns the dynamic list of [AssetSet] based on the current list of [DirectoryEntry]
*
* The list is ordered in ascending order of importance, meaning the first set is meant to be
* overridden by the 2nd one and so on. This is meant to facilitate usage of the list in an
* asset merger
*
* @param aaptEnv the value of "ANDROID_AAPT_IGNORE" environment variable.
* @return a [Provider] of a [List] of [Provider] of [AssetSet].
*/
fun getAscendingOrderAssetSets(
aaptEnv: Provider<String>
): Provider<List<Provider<AssetSet>>> {
return variantSources.map { allDirectories ->
allDirectories.map { directoryEntries ->
val assetName = if (directoryEntries.name == SdkConstants.FD_MAIN)
BuilderConstants.MAIN else directoryEntries.name
directoryEntries.directoryEntries.map { directoryEntry ->
directoryEntry.asFiles(
variantServices.provider {
variantServices.projectInfo.projectDirectory
}
).map {
AssetSet(assetName, aaptEnv.orNull).also { assetSet ->
assetSet.addSources(it.map { it.asFile })
}
}
}
}.flatten()
}
}
}