blob: 62bea4453af39fd469be5f6646cdf96fe6c1e6b5 [file] [log] [blame]
/*
* Copyright (C) 2012 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.tasks
import com.android.build.api.variant.BuildConfigField
import com.android.build.gradle.internal.generators.BuildConfigByteCodeGenerator
import com.android.build.gradle.internal.generators.BuildConfigData
import com.android.build.gradle.internal.component.ApkCreationConfig
import com.android.build.gradle.internal.component.ApplicationCreationConfig
import com.android.build.gradle.internal.component.BaseCreationConfig
import com.android.build.gradle.internal.component.VariantCreationConfig
import com.android.build.gradle.internal.scope.InternalArtifactType
import com.android.build.gradle.internal.tasks.NonIncrementalTask
import com.android.build.gradle.internal.tasks.factory.VariantTaskCreationAction
import com.android.build.gradle.internal.utils.setDisallowChanges
import com.android.build.gradle.options.BooleanOption
import com.android.builder.compiling.GeneratedCodeFileCreator
import com.android.build.gradle.internal.generators.BuildConfigGenerator
import com.android.utils.FileUtils
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskProvider
import java.io.Serializable
@CacheableTask
abstract class GenerateBuildConfig : NonIncrementalTask() {
// ----- PUBLIC TASK API -----
@get:OutputDirectory
@get:Optional
abstract val sourceOutputDir: DirectoryProperty
@get:OutputFile
@get:Optional
abstract val bytecodeOutputFile: RegularFileProperty
// ----- PRIVATE TASK API -----
@get:Input
@get:Optional
abstract val buildTypeName: Property<String>
@get:Input
var isLibrary: Boolean = false
private set
@get:Input
abstract val buildConfigPackageName: Property<String>
@get:Input
@get:Optional
abstract val appPackageName: Property<String>
@get:Input
abstract val debuggable: Property<Boolean>
@get:Input
abstract val flavorName: Property<String>
@get:Input
abstract val flavorNamesWithDimensionNames: ListProperty<String>
@get:Input
@get:Optional
abstract val versionName: Property<String?>
@get:Input
@get:Optional
abstract val versionCode: Property<Int?>
// just to know whether to set versionCode/Name in the build config field.
// For apps we want to put it whether the info is there or not because it might be set
// in release but not in debug but you need the code to compile in both variants.
// And we cannot rely on Provider.isPresent as it does not disambiguate between missing value
// and null value.
@get:Internal
abstract val hasVersionInfo: Property<Boolean>
@get:Input
abstract val items: MapProperty<String, BuildConfigField<out Serializable>>
@get:InputFiles
@get:Optional
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val mergedManifests: DirectoryProperty
override fun doTaskAction() {
val itemsToGenerate = items.get()
val buildConfigData = BuildConfigData.Builder()
.setBuildConfigPackageName(buildConfigPackageName.get())
.apply {
if (sourceOutputDir.isPresent) {
addBooleanField("DEBUG", debuggable.get())
}
if (isLibrary) {
addStringField("LIBRARY_PACKAGE_NAME", buildConfigPackageName.get())
} else {
addStringField("APPLICATION_ID", appPackageName.get())
}
buildTypeName.orNull?.let {
addStringField("BUILD_TYPE", it)
}
flavorName.get().let {
if (it.isNotEmpty()) {
addStringField("FLAVOR", it)
}
}
val flavors = flavorNamesWithDimensionNames.get()
val count = flavors.size
if (count > 1) {
var i = 0
while (i < count) {
addStringField(
"FLAVOR_${flavors[i + 1]}",
"${flavors[i]}"
)
i += 2
}
}
if (hasVersionInfo.get()) {
versionCode.orNull?.let {
addIntField("VERSION_CODE", it)
addStringField("VERSION_NAME", "${versionName.getOrElse("")}")
}
}
}
val generator: GeneratedCodeFileCreator =
if (bytecodeOutputFile.isPresent) {
FileUtils.deleteIfExists(bytecodeOutputFile.get().asFile)
val byteCodeBuildConfigData = buildConfigData
.setOutputPath(bytecodeOutputFile.get().asFile.parentFile.toPath())
.addBooleanField("DEBUG", debuggable.get())
.build()
BuildConfigByteCodeGenerator(byteCodeBuildConfigData)
} else {
// must clear the folder in case the packagename changed, otherwise,
// there'll be two classes.
val destinationDir = sourceOutputDir.get().asFile
FileUtils.cleanOutputDir(destinationDir)
val sourceCodeBuildConfigData = buildConfigData
.setOutputPath(sourceOutputDir.get().asFile.toPath())
.apply {
// user generated items, order them by field name so generation
// is stable.
itemsToGenerate.toSortedMap().forEach { (name, buildConfigField)
->
addItem(name, buildConfigField)
}
}
.build()
BuildConfigGenerator(sourceCodeBuildConfigData)
}
generator.generate()
}
// ----- Config Action -----
internal class CreationAction(creationConfig: BaseCreationConfig) :
VariantTaskCreationAction<GenerateBuildConfig, BaseCreationConfig>(
creationConfig
) {
override val name: String = computeTaskName("generate", "BuildConfig")
override val type: Class<GenerateBuildConfig> = GenerateBuildConfig::class.java
override fun handleProvider(
taskProvider: TaskProvider<GenerateBuildConfig>
) {
super.handleProvider(taskProvider)
val outputBytecode = creationConfig.services.projectOptions
.get(BooleanOption.ENABLE_BUILD_CONFIG_AS_BYTECODE)
val generateItems = creationConfig.variantDslInfo.getBuildConfigFields().any()
creationConfig.taskContainer.generateBuildConfigTask = taskProvider
if (outputBytecode && !generateItems) {
creationConfig.artifacts.setInitialProvider(
taskProvider,
GenerateBuildConfig::bytecodeOutputFile
).withName("BuildConfig.jar")
.on(InternalArtifactType.COMPILE_BUILD_CONFIG_JAR)
} else {
creationConfig.artifacts.setInitialProvider(
taskProvider,
GenerateBuildConfig::sourceOutputDir
).atLocation(creationConfig.paths.buildConfigSourceOutputDir.canonicalPath)
.on(InternalArtifactType.GENERATED_BUILD_CONFIG_JAVA)
}
}
override fun configure(
task: GenerateBuildConfig
) {
super.configure(task)
val variantDslInfo = creationConfig.variantDslInfo
val project = creationConfig.globalScope.project
task.buildConfigPackageName.setDisallowChanges(creationConfig.packageName)
if (creationConfig is ApkCreationConfig) {
task.appPackageName.setDisallowChanges(creationConfig.applicationId)
}
if (creationConfig is ApplicationCreationConfig) {
val mainSplit = creationConfig.outputs.getMainSplit()
// check the variant API property first (if there is one) in case the variant
// output version has been overridden, otherwise use the variant configuration
task.versionCode.setDisallowChanges(mainSplit.versionCode)
task.versionName.setDisallowChanges(mainSplit.versionName)
task.hasVersionInfo.setDisallowChanges(true)
} else {
task.hasVersionInfo.setDisallowChanges(false)
}
task.debuggable.setDisallowChanges(creationConfig.variantDslInfo.isDebuggable)
task.buildTypeName.setDisallowChanges(variantDslInfo.componentIdentity.buildType)
// no need to memoize, variant configuration does that already.
task.flavorName.setDisallowChanges(
project.provider { variantDslInfo.componentIdentity.flavorName })
task.flavorNamesWithDimensionNames.setDisallowChanges(project.provider {
variantDslInfo.flavorNamesWithDimensionNames
})
if (creationConfig is VariantCreationConfig) {
task.items.set(creationConfig.buildConfigFields)
}
if (creationConfig.variantType.isTestComponent) {
creationConfig.artifacts.setTaskInputToFinalProduct(
InternalArtifactType.PACKAGED_MANIFESTS, task.mergedManifests
)
}
task.isLibrary = creationConfig.variantType.isAar
}
}
}