blob: df72c23a11d422793f2e53883df8137f71ac0a3b [file] [log] [blame]
/*
* Copyright (C) 2017 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 com.android.build.api.artifact.ArtifactKind
import com.android.build.api.artifact.impl.OperationsImpl
import com.android.build.gradle.internal.utils.setDisallowChanges
import com.google.gson.JsonParser
import com.google.gson.annotations.SerializedName
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.file.Directory
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.FileCollection
import org.gradle.api.file.FileSystemLocation
import org.gradle.api.file.RegularFile
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.TaskProvider
import java.io.File
import java.io.FileReader
typealias Report = Map<String, BuildArtifactsHolder.ProducersData>
/**
* Buildable artifact holder.
*
* This class manages buildable artifacts, allowing users to transform [ArtifactType].
*
* @param project the Gradle [Project]
* @param rootOutputDir a supplier for the intermediate directories to place output files.
* @parma dslScope the scope for dsl parsing issue raising.
*/
abstract class BuildArtifactsHolder(
private val project: Project,
private val rootOutputDir: () -> File,
identifier: String
) {
private val operations= OperationsImpl(project.objects, identifier,
project.layout.buildDirectory)
fun getOperations(): OperationsImpl = operations
// because of existing public APIs, we cannot move [AnchorOutputType.ALL_CLASSES] to Provider<>
private val allClasses= project.files()
/**
* Republishes an [ArtifactType] under a different type. This is useful when a level of
* indirection is used.
*
* @param sourceType the original [ArtifactType] for the built artifacts.
* @param targetType the supplemental [ArtifactType] the same built artifacts will also be
* published under.
*/
fun <T: FileSystemLocation, U> republish(sourceType: U, targetType: U)
where U: ArtifactType<T>, U : ArtifactType.Single {
operations.republish(sourceType, targetType)
}
/**
* Copies a published [ArtifactType] from another instance of [BuildArtifactsHolder] to this
* instance.
* This does not remove the original elements from the source [BuildArtifactsHolder].
*
* @param artifactType artifact type to copy to this holder.
* @param from source [BuildArtifactsHolder] to copy the produced artifacts from.
*/
fun <T: FileSystemLocation> copy(artifactType: SingleArtifactType<T>, from: BuildArtifactsHolder) {
copy(artifactType, from, artifactType)
}
/**
* Copies a published [ArtifactType] from another instance of [BuildArtifactsHolder] to this
* instance.
* This does not remove the original elements from the source [BuildArtifactsHolder].
*
* @param artifactType artifact type to copy to this holder.
* @param from source [BuildArtifactsHolder] to copy the produced artifacts from.
* @param originalArtifactType artifact type under which the producers are registered in the
* source [BuildArtifactsHolder], by default is the same [artifactType]
*/
fun <T: FileSystemLocation> copy(artifactType: SingleArtifactType<T>, from: BuildArtifactsHolder, originalArtifactType: SingleArtifactType<T> = artifactType) {
val artifactContainer = from.operations.getArtifactContainer(originalArtifactType)
operations.copy(artifactType, artifactContainer)
}
/**
* Registers a new [RegularFile] producer for a particular [ArtifactType]. The producer is
* identified by a [TaskProvider] to avoid configuring the task until the produced [RegularFile]
* is required by another [Task].
*
* The simplest way to use the mechanism is as follow :
* <pre>
* open class MyTask(objectFactory: ObjectFactory): Task() {
* val outputFile = objectFactory.fileProperty()
* }
*
* val myTaskProvider = taskFactory.register("myTask", MyTask::class.java)
*
* scope.artifacts.producesFile(InternalArtifactType.SOME_ID,
* OperationType.INITIAL,
* myTaskProvider,
* myTaskProvider.map { it -> it.outputFile }
* "some_file_name")
*
* </pre>
*
* Consumers should use [getFinalProduct] or [OperationsImpl.getAll] to get a [Provider] instance
* for registered products which ensures that [Task]s don't get initialized until the
* [Provider.get] method is invoked during a consumer task configuration execution for instance.
*
* @param artifactType the produced artifact type
* @param taskProvider the [TaskProvider] for the task ultimately responsible for producing the
* artifact.
* @param productProvider the [Provider] of the artifact [RegularFile]
* @param buildDirectory the destination directory of the produced artifact or not provided if
* using the default location.
* @param fileName the desired file name, must be provided.
*/
fun <T: Task, ARTIFACT_TYPE> producesFile(
artifactType: ARTIFACT_TYPE,
taskProvider: TaskProvider<out T>,
productProvider: (T) -> RegularFileProperty,
buildDirectory: String? = null,
fileName: String
) where ARTIFACT_TYPE : ArtifactType<RegularFile>,
ARTIFACT_TYPE : ArtifactType.Single {
operations.setInitialProvider(
taskProvider,
productProvider)
.atLocation(buildDirectory)
.withName(fileName)
.on(artifactType)
}
/**
* Registers a new [RegularFile] producer for a particular [ArtifactType]. The producer is
* identified by a [TaskProvider] to avoid configuring the task until the produced [RegularFile]
* is required by another [Task].
*
* The passed [productProvider] returns a [Provider] which mean that the output location cannot
* be changed and will be set by the task itself or during its configuration.
*/
fun <T: Task, ARTIFACT_TYPE> producesFile(
artifactType: ARTIFACT_TYPE,
taskProvider: TaskProvider<out T>,
productProvider: (T) -> Provider<RegularFile>
)
where ARTIFACT_TYPE : ArtifactType<RegularFile>,
ARTIFACT_TYPE : ArtifactType.Single {
val propertyProvider = { task : T ->
val property = project.objects.fileProperty()
property.set(productProvider(task))
property
}
producesFile(
artifactType,
taskProvider,
propertyProvider,
"",
""
)
}
/**
* Registers a new [Directory] producer for a particular [ArtifactType]. The producer is
* identified by a [TaskProvider] to avoid configuring the task until the produced [Directory]
* is required by another [Task].
*
* The simplest way to use the mechanism is as follow :
* <pre>
* open class MyTask(objectFactory: ObjectFactory): Task() {
* val outputFile = objectFactory.directoryProperty()
* }
*
* val myTaskProvider = taskFactory.register("myTask", MyTask::class.java)
*
* scope.artifacts.producesDir(InternalArtifactType.SOME_ID,
* OperationType.INITIAL,
* myTaskProvider,
* myTaskProvider.map { it -> it.outputFile }
* "some_file_name")
*
* </pre>
*
* Consumers should use [getFinalProduct] or [OperationsImpl.getAll] to get a [Provider] instance
* for registered products which ensures that [Task]s don't get initialized until the
* [Provider.get] method is invoked during a consumer task configuration execution for instance.
*
* @param artifactType the produced artifact type
* @param taskProvider the [TaskProvider] for the task ultimately responsible for producing the
* artifact.
* @param productProvider the [Provider] of the artifact [Directory]
* @param buildDirectory the destination directory of the produced artifact or not provided if
* using the default location.
* @param fileName the desired directory name or empty string if no sub-directory should be
* used.
*/
fun <T: Task, ARTIFACT_TYPE> producesDir(
artifactType: ARTIFACT_TYPE,
taskProvider: TaskProvider<out T>,
productProvider: (T) -> DirectoryProperty,
buildDirectory: String? = null,
fileName: String = "out"
) where ARTIFACT_TYPE : ArtifactType<Directory>,
ARTIFACT_TYPE : ArtifactType.Single {
operations.setInitialProvider(
taskProvider,
productProvider
).atLocation(buildDirectory).withName(fileName).on(artifactType)
}
// TODO : remove these 2 APIs once all java tasks stopped using those after Kotlin translation.
fun <T: Task, ARTIFACT_TYPE> producesFile(
artifactType: ARTIFACT_TYPE,
taskProvider: TaskProvider<out T>,
productProvider: (T) -> RegularFileProperty,
fileName: String = "out"
)
where ARTIFACT_TYPE : ArtifactType<RegularFile>,
ARTIFACT_TYPE : ArtifactType.Single
= producesFile(artifactType, taskProvider, productProvider, null, fileName)
fun <T: Task, ARTIFACT_TYPE> producesDir(
artifactType: ARTIFACT_TYPE,
taskProvider: TaskProvider<out T>,
propertyProvider: (T) -> DirectoryProperty,
fileName: String = "out"
) where ARTIFACT_TYPE : ArtifactType<Directory>,
ARTIFACT_TYPE : ArtifactType.Single
= producesDir(artifactType, taskProvider, propertyProvider, null, fileName)
/**
* Returns true if there is at least one producer for the passed [ArtifactType]
*
* @param artifactType the identifier for the built artifact.
*/
fun <T: FileSystemLocation, ARTIFACT_TYPE> hasFinalProduct(artifactType: ARTIFACT_TYPE)
where ARTIFACT_TYPE: ArtifactType<T>,
ARTIFACT_TYPE: ArtifactType.Single
= !operations.getArtifactContainer(artifactType).needInitialProducer().get()
fun <T: FileSystemLocation, ARTIFACT_TYPE> hasFinalProducts(artifactType: ARTIFACT_TYPE)
where ARTIFACT_TYPE: ArtifactType<T>,
ARTIFACT_TYPE: ArtifactType.Multiple
= !operations.getArtifactContainer(artifactType).needInitialProducer().get()
/**
* Returns a [Provider] of either a [Directory] or a [RegularFile] depending on the passed
* [ArtifactKind]. The [Provider] will represent the final value of the built artifact
* irrespective of when this call is made.
*
* The simplest way to use the mechanism is as follow :
* <pre>
* open class MyTask(objectFactory: ObjectFactory): Task() {
* val inputFile: Provider<RegularFile>
* }
*
* val myTaskProvider = taskFactory.register("myTask", MyTask::class.java) {
* it.inputFile = scope.artifacts.getFinalProduct(InternalArtifactTYpe.SOME_ID)
* }
* </pre>
*
* @param artifactType the identifier for the built artifact.
*/
fun <T: FileSystemLocation, ARTIFACT_TYPE> getFinalProduct(
artifactType: ARTIFACT_TYPE): Provider<T>
where ARTIFACT_TYPE: ArtifactType<T>,
ARTIFACT_TYPE: ArtifactType.Single {
return operations.get(artifactType)
}
/**
* Returns a [Provider] of a [FileCollection] for the passed [ArtifactType].
* The [FileCollection] will represent the final value of the built artifact irrespective of
* when this call is made as long as the [Provider] is resolved at execution time.
*
* @param artifactType the identifier for thje built artifact.
*/
fun <T: FileSystemLocation, ARTIFACT_TYPE> getFinalProductAsFileCollection(artifactType: ARTIFACT_TYPE): Provider<FileCollection>
where ARTIFACT_TYPE: ArtifactType<T>,
ARTIFACT_TYPE: ArtifactType.Single {
return project.provider {
if (artifactType == AnchorOutputType.ALL_CLASSES) {
getAllClasses()
} else {
if (hasFinalProduct(artifactType)) {
project.files(getFinalProduct(artifactType))
} else project.files()
}
}
}
/**
* Sets a [Property] value to the final producer for the given artifact type.
*
* If there are more than one producer appending artifacts for the passed type, calling this
* method will generate an error and [setTaskInputToFinalProducts] should be used instead.
*
* The simplest way to use the mechanism is as follow :
* <pre>
* abstract class MyTask: Task() {
* @InputFile
* abstract val inputFile: RegularFileProperty
* }
*
* val myTaskProvider = taskFactory.register("myTask", MyTask::class.java) {
* scope.artifacts.setTaskInputToFinalProduct(InternalArtifactTYpe.SOME_ID, it.inputFile)
* }
* </pre>
*
* @param artifactType requested artifact type
* @param taskInputProperty the [Property] to set the final producer on.
*/
fun <T: FileSystemLocation, ARTIFACT_TYPE> setTaskInputToFinalProduct(
artifactType: ARTIFACT_TYPE, taskInputProperty: Property<T>)
where ARTIFACT_TYPE: ArtifactType<T>, ARTIFACT_TYPE: ArtifactType.Single {
val finalProduct = getFinalProduct(artifactType)
taskInputProperty.setDisallowChanges(finalProduct)
}
/**
* Appends a [FileCollection] to the [AnchorOutputType.ALL_CLASSES] artifact.
*
* @param files the [FileCollection] to add.
*/
fun appendToAllClasses(files: FileCollection) {
synchronized(allClasses) {
allClasses.from(files)
}
}
/**
* Returns the current [FileCollection] for [AnchorOutputType.ALL_CLASSES] as of now.
* The returned file collection is final but its content can change.
*/
fun getAllClasses(): FileCollection = allClasses
// TODO: Reimplement or remove feature.
fun createReport(): Report = mapOf()
/** A data class for use with GSON. */
data class ProducerData(
@SerializedName("files")
val files: List<String>,
@SerializedName("builtBy")
val builtBy : String
)
data class ProducersData(
@SerializedName("producer")
val producers: List<ProducerData>
)
companion object {
fun parseReport(file : File) : Report {
val result = mutableMapOf<String, ProducersData>()
val parser = JsonParser()
FileReader(file).use { reader ->
for ((key, value) in parser.parse(reader).asJsonObject.entrySet()) {
val obj = value.asJsonObject
val producers = obj.getAsJsonArray("producer").map {
ProducerData(
it.asJsonObject.getAsJsonArray("files").map {
it.asString
},
it.asJsonObject.get("builtBy").asString
)
}
result[key] = ProducersData(producers)
}
}
return result
}
}
}