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.metalava
18 
19 import androidx.build.checkapi.ApiBaselinesLocation
20 import androidx.build.checkapi.SourceSetInputs
21 import java.io.File
22 import javax.inject.Inject
23 import org.gradle.api.DefaultTask
24 import org.gradle.api.file.ConfigurableFileCollection
25 import org.gradle.api.file.FileCollection
26 import org.gradle.api.file.RegularFileProperty
27 import org.gradle.api.provider.ListProperty
28 import org.gradle.api.provider.Property
29 import org.gradle.api.tasks.CacheableTask
30 import org.gradle.api.tasks.Classpath
31 import org.gradle.api.tasks.Input
32 import org.gradle.api.tasks.InputFile
33 import org.gradle.api.tasks.Internal
34 import org.gradle.api.tasks.Optional
35 import org.gradle.api.tasks.PathSensitive
36 import org.gradle.api.tasks.PathSensitivity
37 import org.gradle.workers.WorkerExecutor
38 import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
39 
40 /** Base class for invoking Metalava. */
41 @CacheableTask
42 abstract class MetalavaTask
43 @Inject
44 constructor(@Internal protected val workerExecutor: WorkerExecutor) : DefaultTask() {
45     /** Classpath containing Metalava and its dependencies. */
46     @get:Classpath abstract val metalavaClasspath: ConfigurableFileCollection
47 
48     /** Android's boot classpath */
49     @get:Classpath lateinit var bootClasspath: FileCollection
50 
51     /** Dependencies (compiled classes) of the project. */
52     @get:Classpath lateinit var dependencyClasspath: FileCollection
53 
54     @get:Input abstract val k2UastEnabled: Property<Boolean>
55 
56     @get:Input abstract val kotlinSourceLevel: Property<KotlinVersion>
57 
runWithArgsnull58     fun runWithArgs(args: List<String>) {
59         runMetalavaWithArgs(
60             metalavaClasspath,
61             args,
62             k2UastEnabled.get(),
63             kotlinSourceLevel.get(),
64             workerExecutor
65         )
66     }
67 }
68 
69 /** A metalava task that takes source code as input (other tasks take signature files). */
70 @CacheableTask
71 internal abstract class SourceMetalavaTask(workerExecutor: WorkerExecutor) :
72     MetalavaTask(workerExecutor) {
73     /**
74      * Specifies both the source files and their corresponding compiled class files
75      *
76      * We specify the source files to pass to Metalava because that's the format that Metalava
77      * needs.
78      *
79      * However, Metalava is only supposed to read the public API, so we don't need to rerun Metalava
80      * if no API changes occurred.
81      *
82      * Gradle doesn't offer all of the same abilities as Metalava for writing a signature file and
83      * validating its compatibility, but Gradle does offer the ability to check whether two sets of
84      * classes have the same API.
85      *
86      * So, we ask Gradle to rerun this task only if the public API changes, which we implement by
87      * declaring the compiled classes as inputs rather than the sources
88      */
89     /** Source files against which API signatures will be validated. */
90     @get:Internal // UP-TO-DATE checking is done based on the compiled classes
91     var sourcePaths: FileCollection = project.files()
92 
93     /** Class files compiled from sourcePaths */
94     @get:Classpath var compiledSources: FileCollection = project.files()
95 
96     @get:[Optional InputFile PathSensitive(PathSensitivity.NONE)]
97     abstract val manifestPath: RegularFileProperty
98 
99     @get:Internal // already expressed by getApiLintBaseline()
100     abstract val baselines: Property<ApiBaselinesLocation>
101 
102     @Optional
103     @PathSensitive(PathSensitivity.NONE)
104     @InputFile
getInputApiLintBaselinenull105     fun getInputApiLintBaseline(): File? {
106         val baseline = baselines.get().apiLintFile
107         return if (baseline.exists()) baseline else null
108     }
109 
110     @get:Input abstract val targetsJavaConsumers: Property<Boolean>
111 
112     /**
113      * Information about all source sets for multiplatform projects. Non-multiplatform projects
114      * should be represented as a list with one source set.
115      *
116      * This is marked as [Internal] because [compiledSources] is what should determine whether to
117      * rerun metalava.
118      */
119     @get:Internal abstract val sourceSets: ListProperty<SourceSetInputs>
120 
121     /**
122      * Creates an XML file representing the project structure.
123      *
124      * This should only be called during task execution.
125      */
createProjectXmlFilenull126     protected fun createProjectXmlFile(): File {
127         val sourceSets = sourceSets.get()
128         check(sourceSets.isNotEmpty()) { "Project must have at least one source set." }
129         val outputFile = File(temporaryDir, "project.xml")
130         ProjectXml.create(
131             sourceSets,
132             bootClasspath.files,
133             compiledSources.singleFile,
134             outputFile,
135         )
136         return outputFile
137     }
138 }
139