1 /*
2  * Copyright 2020 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
18 
19 import androidx.build.SoftwareType.Companion.BENCHMARK
20 import androidx.build.SoftwareType.Companion.SAMPLES
21 import androidx.build.SoftwareType.Companion.TEST_APPLICATION
22 import androidx.build.SoftwareType.Companion.UNSET
23 import kotlin.collections.contains
24 
25 /**
26  * Represents the purpose and configuration of a software project, including how it is published,
27  * whether it enforces API compatibility checks, and which environment it targets. By using
28  * [SoftwareType], developers can select from predefined library configurations or create their own
29  * through [ConfigurableSoftwareType]. This reduces complexity by capturing a library's behavior and
30  * rationale in one place, rather than requiring manual configuration of multiple independent
31  * properties.
32  *
33  * The key properties controlled by [SoftwareType] are:
34  * - [publish]: Defines how (or if) the software is published to external repositories (e.g.,
35  *   GMaven).
36  * - [checkApi]: Determines whether API compatibility tasks are run, which enforce semantic
37  *   versioning and API stability.
38  * - [compilationTarget]: Specifies whether the software runs on a host machine or an Android
39  *   device.
40  * - [allowCallingVisibleForTestsApis]: Indicates whether calling `@VisibleForTesting` APIs is
41  *   allowed, useful for test libraries.
42  * - [targetsKotlinConsumersOnly]: When `true`, the software is intended for Kotlin consumers only,
43  *   allowing for more Kotlin-centric API design.
44  * - [isForTesting]: When `true`, the library is intended to serve as a testing artifact only, not
45  *   meant for usage in production.
46  *
47  * [SoftwareType] includes a variety of predefined configurations commonly used in Android projects:
48  * - Conventional published libraries ([PUBLISHED_LIBRARY], [PUBLISHED_PROTO_LIBRARY], etc.)
49  * - Internal libraries not published externally ([INTERNAL_TEST_LIBRARY],
50  *   [INTERNAL_HOST_TEST_LIBRARY])
51  * - Test libraries that allow testing internal or unstable APIs ([PUBLISHED_TEST_LIBRARY],
52  *   [INTERNAL_TEST_LIBRARY])
53  * - Lint rule sets ([LINT], [STANDALONE_PUBLISHED_LINT]) for guiding correct usage of a library
54  * - Libraries containing samples to supplement documentation ([SAMPLES])
55  * - Host-only libraries such as Gradle plugins, annotation processors, and code generators
56  *   ([GRADLE_PLUGIN], [ANNOTATION_PROCESSOR], [OTHER_CODE_PROCESSOR])
57  * - Libraries specifically meant for IDE consumption ([IDE_PLUGIN])
58  * - Snapshot-only libraries for early access or development use cases
59  *   ([SNAPSHOT_ONLY_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS], etc.)
60  * - Libraries that do not publish artifacts but still run API tasks, or vice versa
61  *   ([INTERNAL_LIBRARY_WITH_API_TASKS], [SNAPSHOT_ONLY_LIBRARY_WITH_API_TASKS])
62  * - [UNSET]: a default or transitional state indicating the library's type isn't fully determined
63  *
64  * Although predefined software types cover many common scenarios, you can create new
65  * [ConfigurableSoftwareType] instances if your project requires a unique combination of publish
66  * settings, API checking, and compilation targeting. In doing so, you ensure the project's
67  * configuration is concise, clear, and consistently applied.
68  */
69 sealed class SoftwareType(
70     val name: String,
71     val publish: Publish = Publish.NONE,
72     val checkApi: RunApiTasks = RunApiTasks.No("Unknown Software Type"),
73     val compilationTarget: CompilationTarget = CompilationTarget.DEVICE,
74     val allowCallingVisibleForTestsApis: Boolean = false,
75     val targetsKotlinConsumersOnly: Boolean = false,
76     val isForTesting: Boolean = false,
77 ) {
78     class ConfigurableSoftwareType(
79         name: String,
80         publish: Publish = Publish.NONE,
81         checkApi: RunApiTasks = RunApiTasks.No("Unknown Software Type"),
82         compilationTarget: CompilationTarget = CompilationTarget.DEVICE,
83         allowCallingVisibleForTestsApis: Boolean = false,
84         targetsKotlinConsumersOnly: Boolean = false,
85         isForTesting: Boolean = true
86     ) :
87         SoftwareType(
88             name,
89             publish,
90             checkApi,
91             compilationTarget,
92             allowCallingVisibleForTestsApis,
93             targetsKotlinConsumersOnly,
94             isForTesting,
95         )
96 
97     companion object {
98         // Host-only tooling libraries
99         @JvmStatic
100         val ANNOTATION_PROCESSOR =
101             ConfigurableSoftwareType(
102                 name = "ANNOTATION_PROCESSOR",
103                 publish = Publish.SNAPSHOT_AND_RELEASE,
104                 checkApi = RunApiTasks.No("Annotation Processor"),
105                 compilationTarget = CompilationTarget.HOST
106             )
107 
108         @JvmStatic
109         val ANNOTATION_PROCESSOR_UTILS =
110             ConfigurableSoftwareType(
111                 name = "ANNOTATION_PROCESSOR_UTILS",
112                 publish = Publish.SNAPSHOT_AND_RELEASE,
113                 checkApi = RunApiTasks.No("Annotation Processor Helper Library"),
114                 compilationTarget = CompilationTarget.HOST
115             )
116 
117         @JvmStatic
118         val GRADLE_PLUGIN =
119             ConfigurableSoftwareType(
120                 name = "GRADLE_PLUGIN",
121                 publish = Publish.SNAPSHOT_AND_RELEASE,
122                 checkApi = RunApiTasks.No("Gradle Plugin (Host-only)"),
123                 compilationTarget = CompilationTarget.HOST
124             )
125 
126         @JvmStatic
127         val OTHER_CODE_PROCESSOR =
128             ConfigurableSoftwareType(
129                 name = "OTHER_CODE_PROCESSOR",
130                 publish = Publish.SNAPSHOT_AND_RELEASE,
131                 checkApi = RunApiTasks.No("Code Processor (Host-only)"),
132                 compilationTarget = CompilationTarget.HOST
133             )
134 
135         // Lint libraries
136         @JvmStatic
137         val LINT =
138             ConfigurableSoftwareType(
139                 name = "LINT",
140                 checkApi = RunApiTasks.No("Lint Library"),
141                 compilationTarget = CompilationTarget.HOST
142             )
143 
144         @JvmStatic
145         val STANDALONE_PUBLISHED_LINT =
146             ConfigurableSoftwareType(
147                 name = "STANDALONE_PUBLISHED_LINT",
148                 publish = Publish.SNAPSHOT_AND_RELEASE,
149                 checkApi = RunApiTasks.No("Lint Library"),
150                 compilationTarget = CompilationTarget.HOST
151             )
152 
153         // Published libraries
154         @JvmStatic
155         val PUBLISHED_LIBRARY =
156             ConfigurableSoftwareType(
157                 name = "PUBLISHED_LIBRARY",
158                 publish = Publish.SNAPSHOT_AND_RELEASE,
159                 checkApi = RunApiTasks.Yes()
160             )
161 
162         @JvmStatic
163         val PUBLISHED_PROTO_LIBRARY =
164             ConfigurableSoftwareType(
165                 name = "PUBLISHED_PROTO_LIBRARY",
166                 publish = Publish.SNAPSHOT_AND_RELEASE,
167                 checkApi =
168                     RunApiTasks.No("Metalava doesn't properly parse the proto sources b/180579063")
169             )
170 
171         @JvmStatic
172         val PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS =
173             ConfigurableSoftwareType(
174                 name = "PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS",
175                 publish = Publish.SNAPSHOT_AND_RELEASE,
176                 checkApi = RunApiTasks.Yes(),
177                 targetsKotlinConsumersOnly = true
178             )
179 
180         // Published test libraries
181         @JvmStatic
182         val PUBLISHED_TEST_LIBRARY =
183             ConfigurableSoftwareType(
184                 name = "PUBLISHED_TEST_LIBRARY",
185                 publish = Publish.SNAPSHOT_AND_RELEASE,
186                 checkApi = RunApiTasks.Yes(),
187                 allowCallingVisibleForTestsApis = true,
188                 isForTesting = true,
189             )
190 
191         @JvmStatic
192         val PUBLISHED_KOTLIN_ONLY_TEST_LIBRARY =
193             ConfigurableSoftwareType(
194                 name = "PUBLISHED_KOTLIN_ONLY_TEST_LIBRARY",
195                 publish = Publish.SNAPSHOT_AND_RELEASE,
196                 checkApi = RunApiTasks.Yes(),
197                 allowCallingVisibleForTestsApis = true,
198                 targetsKotlinConsumersOnly = true,
199                 isForTesting = true,
200             )
201 
202         // Snapshot-only libraries
203         @JvmStatic
204         val SNAPSHOT_ONLY_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS =
205             ConfigurableSoftwareType(
206                 name = "SNAPSHOT_ONLY_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS",
207                 publish = Publish.SNAPSHOT_ONLY,
208                 checkApi = RunApiTasks.Yes(),
209                 targetsKotlinConsumersOnly = true
210             )
211 
212         @JvmStatic
213         val SNAPSHOT_ONLY_TEST_LIBRARY_WITH_API_TASKS =
214             ConfigurableSoftwareType(
215                 name = "SNAPSHOT_ONLY_TEST_LIBRARY_WITH_API_TASKS",
216                 publish = Publish.SNAPSHOT_ONLY,
217                 checkApi = RunApiTasks.Yes(),
218                 allowCallingVisibleForTestsApis = true
219             )
220 
221         @JvmStatic
222         val SNAPSHOT_ONLY_LIBRARY_WITH_API_TASKS =
223             ConfigurableSoftwareType(
224                 name = "SNAPSHOT_ONLY_LIBRARY_WITH_API_TASKS",
225                 publish = Publish.SNAPSHOT_ONLY,
226                 checkApi = RunApiTasks.Yes("Snapshot-only library that runs API tasks")
227             )
228 
229         @JvmStatic
230         val SNAPSHOT_ONLY_LIBRARY =
231             ConfigurableSoftwareType(
232                 name = "SNAPSHOT_ONLY_LIBRARY",
233                 publish = Publish.SNAPSHOT_ONLY,
234                 checkApi = RunApiTasks.No("Snapshot-only library that does not run API tasks")
235             )
236 
237         // Samples library
238         @JvmStatic
239         val SAMPLES =
240             ConfigurableSoftwareType(
241                 name = "SAMPLES",
242                 publish = Publish.SNAPSHOT_AND_RELEASE,
243                 checkApi = RunApiTasks.No("Sample Library")
244             )
245 
246         // IDE libraries
247         @JvmStatic
248         val IDE_PLUGIN =
249             ConfigurableSoftwareType(
250                 name = "IDE_PLUGIN",
251                 checkApi = RunApiTasks.No("IDE Plugin (consumed only by Android Studio)"),
252                 compilationTarget = CompilationTarget.DEVICE
253             )
254 
255         // Internal libraries
256         @JvmStatic
257         val INTERNAL_GRADLE_PLUGIN =
258             ConfigurableSoftwareType(
259                 name = "INTERNAL_GRADLE_PLUGIN",
260                 checkApi = RunApiTasks.No("Internal Gradle Plugin"),
261                 compilationTarget = CompilationTarget.HOST
262             )
263 
264         @JvmStatic
265         val INTERNAL_HOST_TEST_LIBRARY =
266             ConfigurableSoftwareType(
267                 name = "INTERNAL_HOST_TEST_LIBRARY",
268                 checkApi = RunApiTasks.No("Internal Library"),
269                 compilationTarget = CompilationTarget.HOST,
270                 isForTesting = true,
271             )
272 
273         @JvmStatic
274         val INTERNAL_LIBRARY_WITH_API_TASKS =
275             ConfigurableSoftwareType(
276                 name = "INTERNAL_LIBRARY_WITH_API_TASKS",
277                 checkApi = RunApiTasks.Yes("Always run API tasks even if not published")
278             )
279 
280         @JvmStatic
281         val INTERNAL_OTHER_CODE_PROCESSOR =
282             ConfigurableSoftwareType(
283                 name = "INTERNAL_OTHER_CODE_PROCESSOR",
284                 checkApi = RunApiTasks.No("Code Processor (Host-only)"),
285                 compilationTarget = CompilationTarget.HOST
286             )
287 
288         @JvmStatic
289         val INTERNAL_TEST_LIBRARY =
290             ConfigurableSoftwareType(
291                 name = "INTERNAL_TEST_LIBRARY",
292                 checkApi = RunApiTasks.No("Internal Library"),
293                 allowCallingVisibleForTestsApis = true,
294                 isForTesting = true,
295             )
296 
297         // Misc libraries
298         @JvmStatic
299         val BENCHMARK =
300             ConfigurableSoftwareType(
301                 name = "BENCHMARK",
302                 checkApi = RunApiTasks.No("Benchmark Library"),
303                 allowCallingVisibleForTestsApis = true
304             )
305 
306         @JvmStatic
307         val TEST_APPLICATION =
308             ConfigurableSoftwareType(
309                 name = "TEST_APPLICATION",
310                 checkApi = RunApiTasks.No("Test App")
311             )
312 
313         val UNSET = ConfigurableSoftwareType(name = "UNSET")
314 
<lambda>null315         private val allTypes: Map<String, SoftwareType> by lazy {
316             listOf(
317                     PUBLISHED_LIBRARY,
318                     PUBLISHED_PROTO_LIBRARY,
319                     PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS,
320                     PUBLISHED_TEST_LIBRARY,
321                     PUBLISHED_KOTLIN_ONLY_TEST_LIBRARY,
322                     INTERNAL_GRADLE_PLUGIN,
323                     INTERNAL_HOST_TEST_LIBRARY,
324                     INTERNAL_LIBRARY_WITH_API_TASKS,
325                     INTERNAL_OTHER_CODE_PROCESSOR,
326                     INTERNAL_TEST_LIBRARY,
327                     SAMPLES,
328                     SNAPSHOT_ONLY_LIBRARY,
329                     SNAPSHOT_ONLY_LIBRARY_WITH_API_TASKS,
330                     SNAPSHOT_ONLY_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS,
331                     SNAPSHOT_ONLY_TEST_LIBRARY_WITH_API_TASKS,
332                     TEST_APPLICATION,
333                     LINT,
334                     STANDALONE_PUBLISHED_LINT,
335                     GRADLE_PLUGIN,
336                     ANNOTATION_PROCESSOR,
337                     ANNOTATION_PROCESSOR_UTILS,
338                     BENCHMARK,
339                     OTHER_CODE_PROCESSOR,
340                     IDE_PLUGIN,
341                     UNSET
342                 )
343                 .associateBy { it.name }
344         }
345 
valueOfnull346         fun valueOf(name: String): SoftwareType {
347             return requireNotNull(allTypes[name]) { "SoftwareType with name $name not found" }
348         }
349     }
350 }
351 
requiresDependencyVerificationnull352 fun SoftwareType.requiresDependencyVerification(): Boolean =
353     this !in listOf(BENCHMARK, SAMPLES, TEST_APPLICATION, UNSET)
354 
355 enum class CompilationTarget {
356     /** This library is meant to run on the host machine (like an annotation processor). */
357     HOST,
358     /** This library is meant to run on an Android device. */
359     DEVICE
360 }
361 
362 /**
363  * Publish Enum: Publish.NONE -> Generates no artifacts; does not generate snapshot artifacts or
364  * releasable maven artifacts Publish.SNAPSHOT_ONLY -> Only generates snapshot artifacts
365  * Publish.SNAPSHOT_AND_RELEASE -> Generates both snapshot artifacts and releasable maven artifact
366  */
367 enum class Publish {
368     NONE,
369     SNAPSHOT_ONLY,
370     SNAPSHOT_AND_RELEASE;
371 
shouldReleasenull372     fun shouldRelease() = this == SNAPSHOT_AND_RELEASE
373 
374     fun shouldPublish() = shouldRelease() || this == SNAPSHOT_ONLY
375 }
376 
377 sealed class RunApiTasks {
378 
379     /** Always run API tasks regardless of other project properties. */
380     data class Yes(val reason: String? = null) : RunApiTasks()
381 
382     /** Do not run any API tasks. */
383     data class No(val reason: String) : RunApiTasks()
384 }
385 
isLintnull386 fun SoftwareType.isLint() =
387     this == SoftwareType.LINT || this == SoftwareType.STANDALONE_PUBLISHED_LINT
388