blob: 1aecd2128e305ad56866ab3e7e6f0e55ff75a085 [file] [log] [blame]
/*
* Copyright (C) 2018 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.gradle.internal.scope
import com.android.build.api.artifact.ArtifactType
import org.gradle.api.file.Directory
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.FileSystemLocation
import org.gradle.api.file.RegularFile
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import java.io.File
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicInteger
/**
* Map of all the producers registered in this context.
*
* @param buildDirectory the project buildDirectory [DirectoryProperty]
* @param identifier a function to uniquely identify this context when creating files and folders.
*/
class ProducersMap<T: FileSystemLocation>(
val fileKind: ArtifactType.Kind,
val objectFactory: ObjectFactory,
val buildDirectory: DirectoryProperty,
val identifier: ()->String) {
private val producersMap = ConcurrentHashMap<ArtifactType, Producers<T>>()
// unset properties that represent a non satisfied file or directory dependency. This is ok
// when the dependency is Optional.
private val emptyFileProperty = objectFactory.fileProperty().also { it.finalizeValue() }
private val emptyDirectoryProperty = objectFactory.directoryProperty().also { it.finalizeValue() }
/**
* Returns true if there is at least one [Producer] registered for the passed [ArtifactType]
*
* @param artifactType the artifact type for looked up producers.
*/
fun hasProducers(artifactType: ArtifactType) = producersMap.containsKey(artifactType)
/**
* Returns a [Producers] instance (possibly empty of any [Producer]) for a passed
* [ArtifactType]
*
* @param artifactType the artifact type for looked up producers.
* @param buildLocation intended location for the artifact or not provided if using the default.
* @return a [Producers] instance for that [ArtifactType]
*/
internal fun getProducers(artifactType: ArtifactType, buildLocation: String? = null): Producers<T> {
val outputLocationResolver: (Producers<T>, Producer<T>) -> Provider<T> =
if (buildLocation != null) {
{ _, producer -> producer.resolve(buildDirectory.dir(buildLocation).get().asFile) }
} else {
{ producers, producer ->
val outputLocation = artifactType.getOutputDirectory(
buildDirectory,
identifier(),
if (producers.hasMultipleProducers()) producer.taskName else "")
producer.resolve(outputLocation) }
}
return producersMap.getOrPut(artifactType) {
Producers(
artifactType,
identifier,
outputLocationResolver,
when (artifactType.kind()) {
ArtifactType.Kind.DIRECTORY -> emptyDirectoryProperty
ArtifactType.Kind.FILE -> emptyFileProperty
} as Provider<T>,
objectFactory.listProperty(
when (artifactType.kind()) {
ArtifactType.Kind.DIRECTORY -> Directory::class.java
ArtifactType.Kind.FILE -> RegularFile::class.java
} as Class<T>
),
objectFactory.listProperty(Provider::class.java as Class<Provider<T>>)
)
}!!
}
/**
* Republishes an [ArtifactType] under a different type. This is useful when a level of
* indirection is used.
*
* @param from the original [ArtifactType] for the built artifacts.
* @param to the supplemental [ArtifactType] the same built artifacts will also be published
* under.
*/
fun republish(from: ArtifactType, to: ArtifactType) {
producersMap[to] = getProducers(from)
}
/**
* Copies all present and future [Producer] of [ArtifactType] from the source producers map.
*
* @param artifactType the artifact type for the producers to be copied in this map.
* @param source the originating producers map to copy from.
*/
fun copy(artifactType: ArtifactType, source: Producers<out FileSystemLocation>) {
producersMap[artifactType] = source as Producers<T>
}
fun entrySet() = producersMap.entries
/**
* possibly empty list of all the [org.gradle.api.Task]s (and decoration) producing this
* artifact type.
*/
class Producers<T : FileSystemLocation>(
val artifactType: ArtifactType,
val identifier: () -> String,
val resolver: (Producers<T>, Producer<T>) -> Provider<T>,
private val emptyProvider: Provider<T>,
private val listProperty: ListProperty<T>,
val dependencies: ListProperty<Provider<T>>) : ArrayList<Producer<T>>() {
// create a unique injectable value for this artifact type. This injectable value will be
// used for consuming the artifact. When the provider is resolved, which mean that the
// built artifact will be used, we must resolve all file locations which will in turn
// configure all the tasks producing this artifact type.
val injectable: Provider<T> =
dependencies.flatMap {
// once all resolution and task configuration has happened, return the empty
// provider if there are no producer registered.
resolveAllAndReturnLast() ?: emptyProvider
}
val lastProducerTaskName: Provider<String> =
injectable.map { _ -> get(size - 1).taskName }
// keep count of all producers. Even if a producer is replaced, we still need to remember
// its existence so we do not have overlapping output with different task.
// For instance Task1 outputs in O1, and Task2 comes around and want to replace the artifact
// with output at O2, we must make sure that O1 and O2 do not overlap.
private val producerCount = AtomicInteger(0)
private fun resolveAll(): List<Provider<T>> {
return synchronized(this) {
map {
resolver(this, it)
}
}
}
fun resolveAllAndReturnLast(): Provider<T>? = resolveAll().lastOrNull()
fun add(
settableProperty: Property<T>,
originalProperty: Provider<Property<T>>,
taskName: String,
fileName: String
) {
producerCount.incrementAndGet()
listProperty.add(originalProperty.map { it.get() })
dependencies.add(originalProperty)
add(PropertyBasedProducer(settableProperty, originalProperty, taskName, fileName))
}
fun add(
provider: Provider<Provider<T>>,
taskName: String) = add(ProviderBasedProducer(provider, taskName))
override fun clear() {
super.clear()
listProperty.empty()
}
fun getCurrent(): Provider<T>? {
val currentProduct = lastOrNull() ?: return null
return resolver(this, currentProduct)
}
fun getAllProducers(): ListProperty<T> {
return listProperty
}
fun resolve(producer: Producer<T>) =
resolver(this, producer)
// even if we currently have only one, but more than one was registered, return true so
// we do not have overlapping tasks output.
fun hasMultipleProducers() = producerCount.get() > 1
fun toProducersData() = BuildArtifactsHolder.ProducersData(
map { producer -> producer.toProducerData() }
)
}
/**
* Interface for all producer.
*/
interface Producer<T: FileSystemLocation> {
/**
* resolve this producer in the context of the passed [outputDirectory]
*/
fun resolve(outputDirectory: File): Provider<T>
/**
* build [BuildArtifactsHolder.ProducerData] from this producer.
*/
fun toProducerData(): BuildArtifactsHolder.ProducerData {
val originalProperty = asProvider()
return if (originalProperty.isPresent && originalProperty.get().isPresent) {
BuildArtifactsHolder.ProducerData(listOf(originalProperty.get().get().asFile.path), taskName)
} else {
BuildArtifactsHolder.ProducerData(listOf(), taskName)
}
}
/**
* Return the producer as a Provider<out Provider<T>>
*/
fun asProvider(): Provider<out Provider<T>>
/**
* task name producing the artifact.
*/
val taskName: String
}
/**
* A [Producer] that's based on a [Provider] which mean that its location cannot be reset or
* changed.
*/
private class ProviderBasedProducer<T: FileSystemLocation>(
private val provider: Provider<Provider<T>>,
override val taskName: String): Producer<T> {
override fun resolve(outputDirectory: File): Provider<T> {
return provider.get()
}
override fun asProvider(): Provider<out Provider<T>> {
return provider
}
}
/**
* A [Producer] that's based on a [Property] which mean that its location can be changed
* depending on factors like if there are more than one providers at execution time.
* The artifact is produced by a Task identified by its name and a requested file name.
*/
private class PropertyBasedProducer<T: FileSystemLocation>(
private val settableLocation: Property<T>,
private val originalProperty: Provider<Property<T>>,
override val taskName: String,
private val fileName: String): Producer<T> {
override fun resolve(outputDirectory: File): Provider<T> {
val fileLocation = File(outputDirectory, fileName)
when(settableLocation) {
is DirectoryProperty->
settableLocation.set(fileLocation)
is RegularFileProperty ->
settableLocation.set(fileLocation)
else -> throw RuntimeException(
"Property.get() is not a correct instance type : ${settableLocation.javaClass.name}")
}
return originalProperty.get()
}
override fun asProvider(): Provider<out Provider<T>> {
return originalProperty
}
}
}