1 /*
2  * 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.checkapi
18 
19 import androidx.build.Version
20 import androidx.build.version
21 import java.io.File
22 import java.io.Serializable
23 import org.gradle.api.Project
24 import org.gradle.api.file.Directory
25 import org.gradle.api.provider.Provider
26 
27 private const val BCV_DIR_NAME = "bcv"
28 
29 /**
30  * Contains information about the files used to record a library's API surfaces. This class may
31  * represent a versioned API txt file or the "current" API txt file.
32  *
33  * <p>
34  * This class is responsible for understanding the naming pattern used by various types of API
35  * files:
36  * <ul>
37  * <li>public
38  * <li>restricted
39  * <li>resource
40  * </ul>
41  */
42 data class ApiLocation(
43     // Directory where the library's API files are stored
44     val apiFileDirectory: File,
45     // File where the library's public API surface is recorded
46     val publicApiFile: File,
47     // File where the library's public plus restricted (see @RestrictTo) API surfaces are recorded
48     val restrictedApiFile: File,
49     // File where the library's public resources are recorded
50     val resourceFile: File,
51     // Directory where the library's stable AIDL surface is recorded
52     val aidlApiDirectory: File,
53     // File where the API version history is recorded, for use in docs
54     val apiLevelsFile: File
55 ) : Serializable {
56 
57     /**
58      * Returns the library version represented by this API location, or {@code null} if this is a
59      * current API file.
60      */
versionnull61     fun version(): Version? {
62         val baseName = publicApiFile.nameWithoutExtension
63         if (baseName == CURRENT) {
64             return null
65         }
66         return Version(baseName)
67     }
68 
69     companion object {
fromPublicApiFilenull70         fun fromPublicApiFile(f: File): ApiLocation {
71             return fromBaseName(f.parentFile, f.nameWithoutExtension)
72         }
73 
fromVersionnull74         fun fromVersion(apiFileDir: File, version: Version): ApiLocation {
75             return fromBaseName(apiFileDir, version.toApiFileBaseName())
76         }
77 
fromCurrentnull78         fun fromCurrent(apiFileDir: File): ApiLocation {
79             return fromBaseName(apiFileDir, CURRENT)
80         }
81 
isResourceApiFilenamenull82         fun isResourceApiFilename(filename: String): Boolean {
83             return filename.startsWith(PREFIX_RESOURCE)
84         }
85 
fromBaseNamenull86         private fun fromBaseName(apiFileDir: File, baseName: String): ApiLocation {
87             return ApiLocation(
88                 apiFileDirectory = apiFileDir,
89                 publicApiFile = File(apiFileDir, "$baseName$EXTENSION"),
90                 restrictedApiFile = File(apiFileDir, "$PREFIX_RESTRICTED$baseName$EXTENSION"),
91                 resourceFile = File(apiFileDir, "$PREFIX_RESOURCE$baseName$EXTENSION"),
92                 aidlApiDirectory = File(apiFileDir, AIDL_API_DIRECTORY_NAME).resolve(baseName),
93                 apiLevelsFile = File(apiFileDir, API_LEVELS)
94             )
95         }
96 
97         /** File name extension used by API files. */
98         private const val EXTENSION = ".txt"
99 
100         /** Base file name used by current API files. */
101         private const val CURRENT = "current"
102 
103         /** Prefix used for restricted API surface files. */
104         private const val PREFIX_RESTRICTED = "restricted_"
105 
106         /** Prefix used for resource-type API files. */
107         private const val PREFIX_RESOURCE = "res-"
108 
109         /** Directory name for location of AIDL API files */
110         private const val AIDL_API_DIRECTORY_NAME = "aidl"
111 
112         /** File name for API version history file. */
113         private const val API_LEVELS = "apiLevels.json"
114     }
115 }
116 
117 /** Converts the version to a valid API file base name. */
Versionnull118 private fun Version.toApiFileBaseName(): String {
119     return getApiFileVersion(this).toString()
120 }
121 
122 /** Returns the directory containing the project's versioned and current ABI files. */
Projectnull123 fun Project.getBcvFileDirectory(): Directory = project.layout.projectDirectory.dir(BCV_DIR_NAME)
124 
125 /** Returns the directory containing the project's versioned and current API files. */
126 fun Project.getApiFileDirectory(): File {
127     return File(project.projectDir, "api")
128 }
129 
130 /** Returns whether the project's API file directory exists. */
Projectnull131 fun Project.hasApiFileDirectory(): Boolean {
132     return project.getApiFileDirectory().exists()
133 }
134 
135 /** Returns the directory containing the project's built current API file. */
Projectnull136 private fun Project.getBuiltApiFileDirectory(): File {
137     @Suppress("DEPRECATION") return File(project.buildDir, "api")
138 }
139 
140 /** Returns the directory containing the project's built current ABI file. */
Projectnull141 fun Project.getBuiltBcvFileDirectory(): Provider<Directory> =
142     project.layout.buildDirectory.dir(BCV_DIR_NAME)
143 
144 /**
145  * Returns an ApiLocation with the given version, or with the project's current version if not
146  * specified. This method is guaranteed to return an ApiLocation that represents a versioned API txt
147  * and not a current API txt.
148  *
149  * @param version the project version for which an API file should be returned
150  * @return an ApiLocation representing a versioned API file
151  */
152 fun Project.getVersionedApiLocation(version: Version = project.version()): ApiLocation {
153     return ApiLocation.fromVersion(project.getApiFileDirectory(), version)
154 }
155 
156 /**
157  * Returns an ApiLocation for the current version. This method is guaranteed to return an
158  * ApiLocation that represents a current API txt and not a versioned API txt.
159  */
Projectnull160 fun Project.getCurrentApiLocation(): ApiLocation {
161     return ApiLocation.fromCurrent(project.getApiFileDirectory())
162 }
163 
164 /**
165  * Returns an ApiLocation for the "work-in-progress" current version which is built from tip-of-tree
166  * and lives in the build output directory.
167  */
Projectnull168 fun Project.getBuiltApiLocation(): ApiLocation {
169     return ApiLocation.fromCurrent(project.getBuiltApiFileDirectory())
170 }
171 
172 /**
173  * Contains information about the files used to record a library's API compatibility and lint
174  * violation baselines.
175  *
176  * <p>
177  * This class is responsible for understanding the naming pattern used by various types of API
178  * compatibility and linting violation baseline files:
179  * <ul>
180  * <li>public API compatibility
181  * <li>restricted API compatibility
182  * <li>API lint
183  * </ul>
184  */
185 data class ApiBaselinesLocation(
186     val ignoreFileDirectory: File,
187     val publicApiFile: File,
188     val restrictedApiFile: File,
189     val apiLintFile: File
190 ) : Serializable {
191 
192     companion object {
fromApiLocationnull193         fun fromApiLocation(apiLocation: ApiLocation): ApiBaselinesLocation {
194             val ignoreFileDirectory = apiLocation.apiFileDirectory
195             return ApiBaselinesLocation(
196                 ignoreFileDirectory = ignoreFileDirectory,
197                 publicApiFile =
198                     File(
199                         ignoreFileDirectory,
200                         apiLocation.publicApiFile.nameWithoutExtension + EXTENSION
201                     ),
202                 restrictedApiFile =
203                     File(
204                         ignoreFileDirectory,
205                         apiLocation.restrictedApiFile.nameWithoutExtension + EXTENSION
206                     ),
207                 apiLintFile = File(ignoreFileDirectory, "api_lint$EXTENSION")
208             )
209         }
210 
211         private const val EXTENSION = ".ignore"
212     }
213 }
214