blob: 786396cedab03956b26146b52a21d47a98d818c1 [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.EvalIssueReporter
import com.android.builder.errors.EvalIssueReporter.Severity
import com.android.builder.errors.EvalIssueReporter.Type
import java.io.File
class DeprecationReporterImpl(
private val issueReporter: EvalIssueReporter,
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.reportIssue(
Type.DEPRECATED_DSL,
Severity.WARNING,
"DSL element '$oldDslElement' is obsolete and has been replaced with '$newDslElement'.\n" +
"It will be removed ${deprecationTarget.removalTime}.",
"$oldDslElement::$newDslElement::${deprecationTarget.name}")
}
override fun reportDeprecatedUsage(
newDslElement: String,
oldDslElement: String,
url: String,
deprecationTarget: DeprecationTarget) {
issueReporter.reportIssue(
Type.DEPRECATED_DSL,
Severity.WARNING,
"DSL element '$oldDslElement' is obsolete and has been replaced with '$newDslElement'.\n" +
"It will be removed ${deprecationTarget.removalTime}.\n" +
"For more information, see $url.",
"$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" +
"It will be removed ${deprecationTarget.removalTime}.\n" +
"For 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.reportIssue(
Type.DEPRECATED_DSL,
Severity.WARNING,
"$messageStart\n$messageEnd"
)
}
}
override fun reportObsoleteUsage(oldDslElement: String,
deprecationTarget: DeprecationTarget) {
issueReporter.reportIssue(
Type.DEPRECATED_DSL,
Severity.WARNING,
"DSL element '$oldDslElement' is obsolete and will be removed ${deprecationTarget.removalTime}.",
"$oldDslElement::::${deprecationTarget.name}")
}
override fun reportObsoleteUsage(
oldDslElement: String,
url: String,
deprecationTarget: DeprecationTarget) {
issueReporter.reportIssue(
Type.DEPRECATED_DSL,
Severity.WARNING,
"DSL element '$oldDslElement' is obsolete and will be removed ${deprecationTarget.removalTime}.\n" +
"For more information, see $url.",
"$oldDslElement::::${deprecationTarget.name}")
}
override fun reportRenamedConfiguration(
newConfiguration: String,
oldConfiguration: String,
deprecationTarget: DeprecationTarget,
url: String?) {
val msg =
"Configuration '$oldConfiguration' is obsolete and has been replaced with '$newConfiguration'.\n" +
"It will be removed ${deprecationTarget.removalTime}."
issueReporter.reportIssue(
Type.DEPRECATED_CONFIGURATION,
Severity.WARNING,
if (url != null) "$msg For more information see: $url" else msg,
"$oldConfiguration::$newConfiguration::${deprecationTarget.name}")
}
override fun reportDeprecatedConfiguration(
newDslElement: String,
oldConfiguration: String,
deprecationTarget: DeprecationTarget
) {
issueReporter.reportIssue(
Type.DEPRECATED_CONFIGURATION,
Severity.WARNING,
"Configuration '$oldConfiguration' is obsolete and has been replaced with DSL element '$newDslElement'.\n" +
"It will be removed ${deprecationTarget.removalTime}.",
"$oldConfiguration::$newDslElement::${deprecationTarget.name}")
}
override fun reportDeprecatedValue(dslElement: String,
oldValue: String,
newValue: String?,
url: String?,
deprecationTarget: DeprecationReporter.DeprecationTarget) {
issueReporter.reportIssue(Type.DEPRECATED_DSL_VALUE,
Severity.WARNING,
"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" +
"It will be removed ${deprecationTarget.removalTime}.\n",
url)
}
override fun reportDeprecatedOption(
option: String,
value: String?,
deprecationTarget: DeprecationTarget) {
if (suppressedOptionWarnings.contains(option)) {
return
}
if (!checkAndSet(option, value)) {
issueReporter.reportIssue(
Type.UNSUPPORTED_PROJECT_OPTION_USE,
Severity.WARNING,
"The option '$option' is deprecated and should not be used anymore.\n" +
(if (value != null) "Use '$option=$value' to remove this warning.\n" else "") +
"It will be removed ${deprecationTarget.removalTime}."
)
}
}
override fun reportExperimentalOption(option: Option<*>, value: String) {
if (suppressedOptionWarnings.contains(option.propertyName)) {
return
}
if (!checkAndSet(option, value)) {
issueReporter.reportIssue(
Type.UNSUPPORTED_PROJECT_OPTION_USE,
Severity.WARNING,
"The option setting '${option.propertyName}=$value' is experimental and unsupported.\n" +
(if (option.defaultValue != null) "The current default is '${option.defaultValue.toString()}'.\n" else "") +
option.additionalInfo,
option.propertyName
)
}
}
companion object {
/**
* Set of obsolete APIs that have been warned already.
*/
private val obsoleteApis = mutableSetOf<String>()
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 warned about, and adds it if not.
*
* @return true if the Option is already part of the set.
*/
fun checkAndSet(option: Any, value: String?): 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: String?
)