blob: a18fe351b4b154beda853c14da27daba13a46da4 [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.errors
import com.android.build.gradle.internal.errors.DeprecationReporter.DeprecationTarget
import com.android.build.gradle.options.BooleanOption
import com.android.build.gradle.options.Option
import com.android.build.gradle.options.ProjectOptions
import com.android.build.gradle.options.StringOption
import com.android.builder.errors.IssueReporter
import com.android.builder.errors.IssueReporter.Type
import java.io.File
class DeprecationReporterImpl(
private val issueReporter: IssueReporter,
private val projectOptions: ProjectOptions,
private val projectPath: String) : DeprecationReporter {
private val suppressedOptionWarnings: Set<String> =
projectOptions[StringOption.SUPPRESS_UNSUPPORTED_OPTION_WARNINGS]?.splitToSequence(',')?.toSet()
?: setOf()
override fun reportDeprecatedUsage(
newDslElement: String,
oldDslElement: String,
deprecationTarget: DeprecationTarget) {
issueReporter.reportWarning(
Type.DEPRECATED_DSL,
"DSL element '$oldDslElement' is obsolete and has been replaced with '$newDslElement'.\n" +
deprecationTarget.getDeprecationTargetMessage(),
"$oldDslElement::$newDslElement::${deprecationTarget.name}")
}
override fun reportDeprecatedApi(
newApiElement: String,
oldApiElement: String,
url: String,
deprecationTarget: DeprecationTarget
) {
if (!checkAndSet(oldApiElement)) {
val debugApi = projectOptions.get(BooleanOption.DEBUG_OBSOLETE_API)
val messageStart = "API '$oldApiElement' is obsolete and has been replaced with '$newApiElement'.\n" +
deprecationTarget.getDeprecationTargetMessage() +
"\nFor more information, see $url."
var messageEnd = ""
if (debugApi) {
val traces = Thread.currentThread().stackTrace
// special check for the Kotlin plugin.
val kotlin = traces.filter {
it.className.startsWith("org.jetbrains.kotlin.gradle.plugin.")
}
messageEnd = if (kotlin.isNotEmpty()) {
"REASON: The Kotlin plugin is currently calling this deprecated API." +
" Watch https://youtrack.jetbrains.com/issue/KT-25428 and, if possible," +
" use a newer version of the Kotlin plugin that has fixed this issue."
} else {
// other cases.
// look to see if we get a fileName that's a full path and is a known gradle file.
val gradleFile = traces.asSequence().filter {
it?.fileName?.let { fileName ->
val file = File(fileName)
file.isAbsolute && file.isFile && (fileName.endsWith(".gradle") || fileName.endsWith(
".gradle.kts"
))
} ?: false
}.map {
"${it.fileName}:${it.lineNumber}"
}.firstOrNull()
if (gradleFile != null) {
"REASON: Called from: $gradleFile"
} else {
val formattedTraces = traces.map { "${it.className}.${it.methodName}(${it.fileName}:${it.lineNumber})\n" }
"REASON: It is currently called from the following trace:\n" + formattedTraces.joinToString(
separator = "",
prefix = "",
postfix = ""
)
}
} + "\nWARNING: Debugging obsolete API calls can take time during configuration. It's recommended to not keep it on at all times."
} else {
messageEnd = "To determine what is calling $oldApiElement, use -P${BooleanOption.DEBUG_OBSOLETE_API.propertyName}=true on the command line to display more information."
}
issueReporter.reportWarning(
Type.DEPRECATED_DSL,
"$messageStart\n$messageEnd")
}
}
override fun reportObsoleteUsage(oldDslElement: String,
deprecationTarget: DeprecationTarget) {
issueReporter.reportWarning(
Type.DEPRECATED_DSL,
"DSL element '$oldDslElement' is obsolete.\n" +
deprecationTarget.getDeprecationTargetMessage(),
"$oldDslElement::::${deprecationTarget.name}")
}
override fun reportRenamedConfiguration(
newConfiguration: String,
oldConfiguration: String,
deprecationTarget: DeprecationTarget) {
issueReporter.reportWarning(
Type.USING_DEPRECATED_CONFIGURATION,
"Configuration '$oldConfiguration' is obsolete and has been replaced with '$newConfiguration'.\n" +
deprecationTarget.getDeprecationTargetMessage(),
"$oldConfiguration::$newConfiguration::${deprecationTarget.name}")
}
override fun reportDeprecatedConfiguration(
newDslElement: String,
oldConfiguration: String,
deprecationTarget: DeprecationTarget
) {
issueReporter.reportWarning(
Type.USING_DEPRECATED_CONFIGURATION,
"Configuration '$oldConfiguration' is obsolete and has been replaced with DSL element '$newDslElement'.\n" +
deprecationTarget.getDeprecationTargetMessage(),
"$oldConfiguration::$newDslElement::${deprecationTarget.name}")
}
override fun reportDeprecatedValue(dslElement: String,
oldValue: String,
newValue: String?,
deprecationTarget: DeprecationTarget) {
issueReporter.reportWarning(Type.USING_DEPRECATED_DSL_VALUE,
"DSL element '$dslElement' has a value '$oldValue' which is obsolete " +
(if (newValue != null)
"and has been replaced with '$newValue'.\n"
else
"and has not been replaced.\n") +
deprecationTarget.getDeprecationTargetMessage())
}
override fun reportOptionIssuesIfAny(option: Option<*>, value: Any) {
if (checkAndSet(option, value)) {
return
}
if (suppressedOptionWarnings.contains(option.propertyName)) {
return
}
val defaultValueMessage =
option.defaultValue?.let { "\nThe current default is '$it'." } ?: ""
when (val status = option.status) {
is Option.Status.EXPERIMENTAL -> {
if (option.defaultValue != value) {
issueReporter.reportWarning(
Type.UNSUPPORTED_PROJECT_OPTION_USE,
"The option setting '${option.propertyName}=$value' is experimental."
+ defaultValueMessage,
option.propertyName
)
}
}
Option.Status.STABLE -> { // No issues
}
is Option.Status.Deprecated -> {
if (option.defaultValue != value) {
issueReporter.reportWarning(
Type.UNSUPPORTED_PROJECT_OPTION_USE,
"The option setting '${option.propertyName}=$value' is deprecated."
+ defaultValueMessage
+ "\n" + status.getDeprecationTargetMessage(),
option.propertyName
)
}
}
is Option.Status.Removed -> {
// Many tests still use BooleanOption.ENABLE_DEPRECATED_NDK even though the feature
// has been removed, so we always produce a warning for that option to avoid
// breaking tests. TODO: Remove those tests and remove the special treatment for
// ENABLE_DEPRECATED_NDK.
if (option.defaultValue == value || option == BooleanOption.ENABLE_DEPRECATED_NDK) {
issueReporter.reportWarning(
Type.UNSUPPORTED_PROJECT_OPTION_USE,
"The option '${option.propertyName}' is deprecated."
+ defaultValueMessage
+ "\n" + status.getRemovedVersionMessage(),
option.propertyName
)
} else {
issueReporter.reportError(
Type.UNSUPPORTED_PROJECT_OPTION_USE,
"The option '${option.propertyName}' is deprecated."
+ defaultValueMessage
+ "\n" + status.getRemovedVersionMessage(),
option.propertyName
)
}
}
}
}
companion object {
/**
* Set of obsolete APIs that have been warned already.
*/
private val obsoleteApis = mutableSetOf<String>()
/** Options that have already been checked for issues. */
private val options = mutableSetOf<OptionInfo>()
/**
* Checks if the given API is part of the set already and adds it if not.
*
* @return true if the api is already part of the set.
*/
fun checkAndSet(api: String): Boolean = synchronized(obsoleteApis) {
return if (obsoleteApis.contains(api)) {
true
} else {
obsoleteApis.add(api)
false
}
}
/**
* Checks if the given Option has already been checked for issues, and records it if not.
*
* @return true if the Option has already been checked for issues
*/
fun checkAndSet(option: Any, value: Any): Boolean = synchronized(options) {
val info = OptionInfo(option, value)
return if (options.contains(info)) {
true
} else {
options.add(info)
false
}
}
fun clean() {
synchronized(obsoleteApis) {
obsoleteApis.clear()
}
synchronized(options) {
options.clear()
}
}
}
}
data class OptionInfo(
val option: Any,
val value: Any
)