| /* |
| * 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 |
| ) |