1 /*
<lambda>null2  * Copyright 2024 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 package androidx.build.kythe
17 
18 import androidx.build.addToBuildOnServer
19 import androidx.build.checkapi.CompilationInputs
20 import androidx.build.getCheckoutRoot
21 import androidx.build.getPrebuiltsRoot
22 import java.io.File
23 import javax.inject.Inject
24 import org.gradle.api.DefaultTask
25 import org.gradle.api.Project
26 import org.gradle.api.artifacts.Configuration
27 import org.gradle.api.file.ConfigurableFileCollection
28 import org.gradle.api.file.DirectoryProperty
29 import org.gradle.api.file.RegularFileProperty
30 import org.gradle.api.provider.ListProperty
31 import org.gradle.api.tasks.CacheableTask
32 import org.gradle.api.tasks.Classpath
33 import org.gradle.api.tasks.Input
34 import org.gradle.api.tasks.InputFile
35 import org.gradle.api.tasks.InputFiles
36 import org.gradle.api.tasks.Internal
37 import org.gradle.api.tasks.OutputDirectory
38 import org.gradle.api.tasks.OutputFile
39 import org.gradle.api.tasks.PathSensitive
40 import org.gradle.api.tasks.PathSensitivity
41 import org.gradle.api.tasks.TaskAction
42 import org.gradle.api.tasks.compile.JavaCompile
43 import org.gradle.process.ExecOperations
44 
45 /** Generates kzip files that are used to index the Java source code in Kythe. */
46 @CacheableTask
47 abstract class GenerateJavaKzipTask
48 @Inject
49 constructor(private val execOperations: ExecOperations) : DefaultTask() {
50 
51     /** Must be run in the checkout root so as to be free of relative markers */
52     @get:Internal val checkoutRoot: File = project.getCheckoutRoot()
53 
54     @get:InputFile
55     @get:PathSensitive(PathSensitivity.NONE)
56     abstract val javaExtractorJar: RegularFileProperty
57 
58     @get:InputFiles
59     @get:PathSensitive(PathSensitivity.RELATIVE)
60     abstract val sourcePaths: ConfigurableFileCollection
61 
62     @get:Input abstract val javacCompilerArgs: ListProperty<String>
63 
64     /** Path to `vnames.json` file, used for name mappings within Kythe. */
65     @get:InputFiles
66     @get:PathSensitive(PathSensitivity.NONE)
67     abstract val vnamesJson: RegularFileProperty
68 
69     @get:Classpath abstract val dependencyClasspath: ConfigurableFileCollection
70 
71     @get:Classpath abstract val compiledSources: ConfigurableFileCollection
72 
73     @get:Classpath abstract val annotationProcessor: ConfigurableFileCollection
74 
75     @get:OutputFile abstract val kzipOutputFile: RegularFileProperty
76 
77     @get:OutputDirectory abstract val kytheBuildDirectory: DirectoryProperty
78 
79     @TaskAction
80     fun exec() {
81         val sourceFiles =
82             sourcePaths.asFileTree.files
83                 .filter { it.extension == "java" }
84                 .map { it.relativeTo(checkoutRoot) }
85 
86         if (sourceFiles.isEmpty()) {
87             return
88         }
89 
90         val dependencyClasspath =
91             dependencyClasspath
92                 .filter { it.extension == "jar" }
93                 .let { filteredClasspath ->
94                     if (sourcePaths.asFileTree.files.any { it.extension == "kt" }) {
95                         filteredClasspath + compiledSources
96                     } else {
97                         filteredClasspath
98                     }
99                 }
100 
101         val kytheBuildDirectory = kytheBuildDirectory.get().asFile.apply { mkdirs() }
102 
103         execOperations.javaexec {
104             it.mainClass.set("-jar")
105             it.args(javaExtractorJar.get().asFile)
106             it.args("--class-path", dependencyClasspath.joinToString(":"))
107             it.args("--processor-path", annotationProcessor.joinToString(":"))
108             it.args(javacCompilerArgs.get())
109             it.args("-d", kytheBuildDirectory)
110             it.args(sourceFiles)
111             it.jvmArgs(
112                 // Without all these flags, the extractor fails to run. Copied from:
113                 // https://github.com/kythe/kythe/blob/v0.0.67/kythe/release/release.BUILD#L99-L106
114                 "--add-opens=java.base/java.nio=ALL-UNNAMED",
115                 "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED",
116                 "--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
117                 "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED",
118                 "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED",
119                 "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED",
120                 "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
121                 "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
122                 "--add-exports=jdk.internal.opt/jdk.internal.opt=ALL-UNNAMED"
123             )
124             it.environment("KYTHE_CORPUS", ANDROIDX_CORPUS)
125             it.environment("KYTHE_KZIP_ENCODING", "proto")
126             it.environment(
127                 "KYTHE_OUTPUT_FILE",
128                 kzipOutputFile.get().asFile.relativeTo(checkoutRoot).path
129             )
130             it.environment("KYTHE_ROOT_DIRECTORY", checkoutRoot.path)
131             it.environment("KYTHE_VNAMES", vnamesJson.get().asFile.path)
132             it.workingDir = checkoutRoot
133         }
134     }
135 
136     internal companion object {
137         fun setupProject(
138             project: Project,
139             compilationInputs: CompilationInputs,
140             compiledSources: Configuration,
141         ) {
142             val annotationProcessorPaths =
143                 project.objects.fileCollection().apply {
144                     project.tasks.withType(JavaCompile::class.java).configureEach {
145                         it.options.annotationProcessorPath?.let { path -> from(path) }
146                     }
147                 }
148 
149             val javacCompilerArgs =
150                 project.objects.listProperty(String::class.java).apply {
151                     project.tasks.withType(JavaCompile::class.java).configureEach {
152                         addAll(it.options.compilerArgs)
153                     }
154                 }
155 
156             project.tasks
157                 .register("generateJavaKzip", GenerateJavaKzipTask::class.java) { task ->
158                     task.apply {
159                         javaExtractorJar.set(
160                             File(
161                                 project.getPrebuiltsRoot(),
162                                 "build-tools/common/javac_extractor.jar"
163                             )
164                         )
165                         sourcePaths.setFrom(compilationInputs.sourcePaths)
166                         vnamesJson.set(project.getVnamesJson())
167                         dependencyClasspath.setFrom(
168                             compilationInputs.dependencyClasspath + compilationInputs.bootClasspath
169                         )
170                         this.compiledSources.setFrom(compiledSources)
171                         kzipOutputFile.set(
172                             project.layout.buildDirectory.file(
173                                 "kzips/${project.group}-${project.name}.java.kzip"
174                             )
175                         )
176                         kytheBuildDirectory.set(
177                             project.layout.buildDirectory.dir("kythe-java-classes")
178                         )
179                         annotationProcessor.setFrom(annotationProcessorPaths)
180                         this.javacCompilerArgs.set(javacCompilerArgs)
181                         // Needed so generated files (e.g. protos) are present when generating kzip
182                         // Without this, javac_extractor will throw a compilation error
183                         dependsOn(project.tasks.withType(JavaCompile::class.java))
184                     }
185                 }
186                 .also { project.addToBuildOnServer(it) }
187         }
188     }
189 }
190