1 /*
<lambda>null2  * Copyright 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.build.metalava
18 
19 import androidx.build.checkapi.ApiLocation
20 import java.io.File
21 import java.util.concurrent.TimeUnit
22 import org.apache.commons.io.FileUtils
23 import org.gradle.api.DefaultTask
24 import org.gradle.api.GradleException
25 import org.gradle.api.provider.ListProperty
26 import org.gradle.api.provider.Property
27 import org.gradle.api.tasks.Input
28 import org.gradle.api.tasks.InputFiles
29 import org.gradle.api.tasks.PathSensitive
30 import org.gradle.api.tasks.PathSensitivity
31 import org.gradle.api.tasks.TaskAction
32 import org.gradle.work.DisableCachingByDefault
33 
34 /** Compares two API txt files against each other. */
35 @DisableCachingByDefault(because = "Doesn't benefit from caching")
36 abstract class CheckApiEquivalenceTask : DefaultTask() {
37     /** Api file (in the build dir) to check */
38     @get:Input abstract val builtApi: Property<ApiLocation>
39 
40     /** Api file (in source control) to compare against */
41     @get:Input abstract val checkedInApis: ListProperty<ApiLocation>
42 
43     @InputFiles
44     @PathSensitive(PathSensitivity.RELATIVE)
45     fun getTaskInputs(): List<File> {
46         val checkedInApiLocations = checkedInApis.get()
47         val checkedInApiFiles =
48             checkedInApiLocations.flatMap { checkedInApiLocation ->
49                 listOf(checkedInApiLocation.publicApiFile, checkedInApiLocation.restrictedApiFile)
50             }
51 
52         val builtApiLocation = builtApi.get()
53         val builtApiFiles =
54             listOf(builtApiLocation.publicApiFile, builtApiLocation.restrictedApiFile)
55 
56         return checkedInApiFiles + builtApiFiles
57     }
58 
59     @TaskAction
60     fun exec() {
61         val builtApiLocation = builtApi.get()
62         for (checkedInApi in checkedInApis.get()) {
63             checkEqual(checkedInApi.publicApiFile, builtApiLocation.publicApiFile)
64             checkEqual(checkedInApi.restrictedApiFile, builtApiLocation.restrictedApiFile)
65         }
66     }
67 }
68 
69 /**
70  * Returns the output of running the `diff` command-line tool on files [a] and [b], truncated to
71  * [maxSummaryLines] lines.
72  */
summarizeDiffnull73 fun summarizeDiff(a: File, b: File, maxSummaryLines: Int = 50): String {
74     if (!a.exists()) {
75         return "$a does not exist"
76     }
77     if (!b.exists()) {
78         return "$b does not exist"
79     }
80     val process =
81         ProcessBuilder(listOf("diff", a.toString(), b.toString()))
82             .redirectOutput(ProcessBuilder.Redirect.PIPE)
83             .start()
84     process.waitFor(5, TimeUnit.SECONDS)
85     var diffLines = process.inputStream.bufferedReader().readLines().toMutableList()
86     if (diffLines.size > maxSummaryLines) {
87         diffLines = diffLines.subList(0, maxSummaryLines)
88         diffLines.plusAssign("[long diff was truncated]")
89     }
90     return diffLines.joinToString("\n")
91 }
92 
checkEqualnull93 fun checkEqual(expected: File, actual: File, updateTaskName: String = "updateApi") {
94     if (!FileUtils.contentEquals(expected, actual)) {
95         val diff = summarizeDiff(expected, actual)
96         val message =
97             """API definition has changed
98 
99                     Declared definition is $expected
100                     True     definition is $actual
101 
102                     Please run `./gradlew ${updateTaskName}` to confirm these changes are
103                     intentional by updating the API definition.
104 
105                     Difference between these files:
106                     $diff"""
107         throw GradleException(message)
108     }
109 }
110