blob: 6e50fd8b87a4f57b42a5db6da303bed97c3b3d0d [file] [log] [blame]
/*
* Copyright 2018 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 androidx.build.metalava
import androidx.build.checkapi.ApiLocation
import com.google.common.io.Files
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.logging.Logger
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.OutputFiles
import org.gradle.api.tasks.TaskAction
import java.io.File
/**
* Updates API signature text files.
* In practice, the values they will be updated to will match the APIs defined by the source code.
*/
abstract class UpdateApiTask : DefaultTask() {
/** Text file from which API signatures will be read. */
@get:Input
abstract val inputApiLocation: Property<ApiLocation>
/** Text files to which API signatures will be written. */
@get:Internal // outputs are declared in getTaskOutputs()
abstract val outputApiLocations: ListProperty<ApiLocation>
@InputFiles
fun getTaskInputs(): List<File>? {
val inputApi = inputApiLocation.get()
return listOf(
inputApi.publicApiFile,
inputApi.restrictedApiFile,
inputApi.experimentalApiFile
)
}
@OutputFiles
fun getTaskOutputs(): List<File> {
return outputApiLocations.get().flatMap { outputApiLocation ->
listOf(
outputApiLocation.publicApiFile,
outputApiLocation.restrictedApiFile,
outputApiLocation.experimentalApiFile
)
}
}
@TaskAction
fun exec() {
var permitOverwriting = true
for (outputApi in outputApiLocations.get()) {
val version = outputApi.version()
if (version != null && version.isFinalApi() &&
outputApi.publicApiFile.exists() &&
!project.hasProperty("force")) {
permitOverwriting = false
}
}
for (outputApi in outputApiLocations.get()) {
val inputApi = inputApiLocation.get()
copy(
inputApi.publicApiFile,
outputApi.publicApiFile,
permitOverwriting,
project.logger
)
copy(
inputApi.experimentalApiFile,
outputApi.experimentalApiFile,
// Experimental APIs are never locked down,
// so it's always okay to overwrite them.
true,
project.logger
)
copy(
inputApi.restrictedApiFile,
outputApi.restrictedApiFile,
permitOverwriting,
project.logger
)
}
}
fun copy(source: File, dest: File, permitOverwriting: Boolean, logger: Logger) {
val overwriting = (dest.exists() && source.readText() != dest.readText())
val changing = overwriting || !dest.exists()
if (changing) {
if (overwriting && !permitOverwriting) {
val message = "Modifying the API definition for a previously released artifact " +
"having a final API version (version not ending in '-alpha') is not " +
"allowed.\n\n" +
"Previously declared definition is $dest\n" +
"Current generated definition is $source\n\n" +
"Did you mean to increment the library version first?\n\n" +
"If you have reason to overwrite the API files for the previous release " +
"anyway, you can run `./gradlew updateApi -Pforce` to ignore this message"
throw GradleException(message)
}
Files.copy(source, dest)
logger.lifecycle("Copied $source to $dest")
}
}
}