blob: fc1d9a3d8cdd26e3ddaf2bc10e0d1c02b1763e4f [file] [log] [blame]
Sergey Vasilinets17a18902017-11-14 23:40:41 -08001/*
2 * Copyright 2017 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
Aurimas Liutikas526389b2018-02-27 14:01:24 -080017package androidx.build.doclava
Sergey Vasilinets17a18902017-11-14 23:40:41 -080018
Jeff Gaston8b24e082020-12-09 11:37:27 -050019import org.gradle.api.DefaultTask
Jeff Gaston8b24e082020-12-09 11:37:27 -050020import org.gradle.api.file.FileCollection
Jeff Gastona56b1c12020-12-10 18:25:15 -050021import org.gradle.api.provider.ListProperty
Jeff Gaston5e3e7de2022-04-21 14:49:47 -040022import org.gradle.api.tasks.CacheableTask
23import org.gradle.api.tasks.Classpath
Sergey Vasilinets17a18902017-11-14 23:40:41 -080024import org.gradle.api.tasks.Input
25import org.gradle.api.tasks.InputFiles
Jeff Gaston8b24e082020-12-09 11:37:27 -050026import org.gradle.api.tasks.Internal
Sergey Vasilinets17a18902017-11-14 23:40:41 -080027import org.gradle.api.tasks.Optional
28import org.gradle.api.tasks.OutputDirectory
29import org.gradle.api.tasks.OutputFile
Jeff Gaston5e3e7de2022-04-21 14:49:47 -040030import org.gradle.api.tasks.PathSensitive
31import org.gradle.api.tasks.PathSensitivity
Jeff Gaston8b24e082020-12-09 11:37:27 -050032import org.gradle.api.tasks.TaskAction
Jeff Gastona56b1c12020-12-10 18:25:15 -050033import org.gradle.process.ExecOperations
34import org.gradle.workers.WorkAction
35import org.gradle.workers.WorkParameters
36import org.gradle.workers.WorkerExecutor
Sergey Vasilinets17a18902017-11-14 23:40:41 -080037import java.io.File
Jeff Gastona56b1c12020-12-10 18:25:15 -050038import javax.inject.Inject
Sergey Vasilinets17a18902017-11-14 23:40:41 -080039
40// external/doclava/src/com/google/doclava/Errors.java
Sergey Vasilinetsa14b8342018-05-30 10:21:02 -070041val DEFAULT_DOCLAVA_CONFIG = ChecksConfig(
Jeff Gastonbc5a9b72020-09-18 14:06:14 -040042 errors = listOf(
43 101, // unresolved link
44 103, // unknown tag
45 104 // unknown param name
46 ),
47 warnings = listOf(121 /* hidden type param */),
48 hidden = listOf(
49 111, // hidden super class
50 113 // @deprecation mismatch
51 )
Sergey Vasilinets17a18902017-11-14 23:40:41 -080052)
53
Jeff Gaston5e3e7de2022-04-21 14:49:47 -040054@CacheableTask()
Jeff Gastona56b1c12020-12-10 18:25:15 -050055abstract class DoclavaTask @Inject constructor(
56 private val workerExecutor: WorkerExecutor
57) : DefaultTask() {
Sergey Vasilinets17a18902017-11-14 23:40:41 -080058
59 // All lowercase name to match MinimalJavadocOptions#docletpath
Jeff Gaston5e3e7de2022-04-21 14:49:47 -040060 @Classpath
Jeff Gaston62394412021-07-30 13:47:34 -040061 private lateinit var docletpath: FileCollection
Sergey Vasilinets17a18902017-11-14 23:40:41 -080062
Sergey Vasilinets17a18902017-11-14 23:40:41 -080063 @Input
Sergey Vasilinetsa14b8342018-05-30 10:21:02 -070064 var checksConfig: ChecksConfig = DEFAULT_DOCLAVA_CONFIG
Sergey Vasilinets17a18902017-11-14 23:40:41 -080065
66 /**
67 * If non-null, the list of packages that will be treated as if they were
68 * marked with {@literal @hide}.<br>
69 * Packages names will be matched exactly; sub-packages are not automatically recognized.
70 */
71 @Optional
72 @Input
73 var hiddenPackages: Collection<String>? = null
74
75 /**
Aurimas Liutikas0442cda2020-06-27 00:01:13 +000076 * If non-null and not-empty, the inclusion list of packages that will be present in the
77 * generated stubs; if null or empty, then all packages have stubs generated.<br>
Sergey Vasilinets17a18902017-11-14 23:40:41 -080078 * Wildcards are accepted.
79 */
80 @Optional
81 @Input
82 var stubPackages: Set<String>? = null
83
84 @Input
85 var generateDocs = true
86
87 /**
88 * If non-null, the location of where to place the generated api file.
89 * If this is non-null, then {@link #removedApiFile} must be non-null as well.
90 */
91 @Optional
92 @OutputFile
93 var apiFile: File? = null
94
95 /**
96 * If non-null, the location of where to place the generated removed api file.
Sergey Vasilinets17a18902017-11-14 23:40:41 -080097 */
98 @Optional
99 @OutputFile
100 var removedApiFile: File? = null
101
102 /**
Sergey Vasilinets17a18902017-11-14 23:40:41 -0800103 * If non-null, the location to put the generated stub sources.
104 */
105 @Optional
106 @OutputDirectory
107 var stubsDir: File? = null
108
109 init {
Jeff Gaston8b24e082020-12-09 11:37:27 -0500110 // If none of generateDocs, apiFile, or stubJarsDir are true, then there is
Sergey Vasilinets17a18902017-11-14 23:40:41 -0800111 // no work to do.
Jeff Gaston8b24e082020-12-09 11:37:27 -0500112 onlyIf({ generateDocs || apiFile != null || stubsDir != null })
Sergey Vasilinets17a18902017-11-14 23:40:41 -0800113 }
114
115 /**
Jeff Gaston8b24e082020-12-09 11:37:27 -0500116 * The doclet path which has the {@code com.google.doclava.Doclava} class.
Aurimas Liutikasdb838b12018-02-12 10:15:23 -0800117 * This option will override any doclet path set in this instance's
118 * {@link #options JavadocOptions}.
Sergey Vasilinets17a18902017-11-14 23:40:41 -0800119 * @see MinimalJavadocOptions#getDocletpath()
120 */
121 @InputFiles
122 fun getDocletpath(): List<File> {
Jeff Gaston62394412021-07-30 13:47:34 -0400123 return docletpath.files.toList()
Sergey Vasilinets17a18902017-11-14 23:40:41 -0800124 }
125
126 /**
127 * Sets the doclet path which has the {@code com.gogole.doclava.Doclava} class.
Aurimas Liutikasdb838b12018-02-12 10:15:23 -0800128 * This option will override any doclet path set in this instance's
129 * {@link #options JavadocOptions}.
Sergey Vasilinets17a18902017-11-14 23:40:41 -0800130 * @see MinimalJavadocOptions#setDocletpath(java.util.List)
131 */
Jeff Gaston62394412021-07-30 13:47:34 -0400132 fun setDocletpath(docletpath: FileCollection) {
133 this.docletpath = docletpath
Jeff Gaston8b24e082020-12-09 11:37:27 -0500134 }
135
136 @OutputDirectory
137 var destinationDir: File? = null
138
Jeff Gaston5e3e7de2022-04-21 14:49:47 -0400139 @InputFiles @Classpath
Jeff Gaston8b24e082020-12-09 11:37:27 -0500140 var classpath: FileCollection? = null
141
Jeff Gaston5e3e7de2022-04-21 14:49:47 -0400142 @[InputFiles PathSensitive(PathSensitivity.RELATIVE)]
Jeff Gaston8b24e082020-12-09 11:37:27 -0500143 val sources = mutableListOf<FileCollection>()
144
145 fun source(files: FileCollection) {
146 sources.add(files)
Sergey Vasilinets17a18902017-11-14 23:40:41 -0800147 }
148
Sergey Vasilinets17a18902017-11-14 23:40:41 -0800149 /**
Jeff Gaston8b24e082020-12-09 11:37:27 -0500150 * Builder containing extra arguments
Sergey Vasilinets17a18902017-11-14 23:40:41 -0800151 */
Jeff Gaston8b24e082020-12-09 11:37:27 -0500152 @Internal
153 val extraArgumentsBuilder = DoclavaArgumentBuilder()
Sergey Vasilinets17a18902017-11-14 23:40:41 -0800154
Jeff Gaston8b24e082020-12-09 11:37:27 -0500155 @Input
156 val extraArguments = extraArgumentsBuilder.build()
157
158 private fun computeArguments(): List<String> {
159 val args = DoclavaArgumentBuilder()
160
161 // classpath
Jeff Gaston6a8b7662021-12-16 10:55:04 -0500162 val classpathFile = File.createTempFile("doclavaClasspath", ".txt")
163 classpathFile.deleteOnExit()
164 classpathFile.bufferedWriter().use { writer ->
165 val classpathString = classpath!!.files.map({ f -> f.toString() }).joinToString(":")
166 writer.write(classpathString)
167 }
168 args.addStringOption("cp", "@$classpathFile")
Jeff Gaston8b24e082020-12-09 11:37:27 -0500169 args.addStringOption("doclet", "com.google.doclava.Doclava")
Jeff Gaston6a8b7662021-12-16 10:55:04 -0500170 args.addStringOption("docletpath", "@$classpathFile")
Jeff Gaston8b24e082020-12-09 11:37:27 -0500171
172 args.addOption("quiet")
173 args.addStringOption("encoding", "UTF-8")
Sergey Vasilinets17a18902017-11-14 23:40:41 -0800174
175 // configure doclava error/warning/hide levels
Jeff Gaston8b24e082020-12-09 11:37:27 -0500176 args.addRepeatableOption("hide", checksConfig.hidden)
177 args.addRepeatableOption("warning", checksConfig.warnings)
178 args.addRepeatableOption("error", checksConfig.errors)
Sergey Vasilinets17a18902017-11-14 23:40:41 -0800179
180 if (hiddenPackages != null) {
Jeff Gaston8b24e082020-12-09 11:37:27 -0500181 args.addRepeatableOption("hidePackage", hiddenPackages!!)
Sergey Vasilinets17a18902017-11-14 23:40:41 -0800182 }
183
184 if (!generateDocs) {
Jeff Gaston8b24e082020-12-09 11:37:27 -0500185 args.addOption("nodocs")
Sergey Vasilinets17a18902017-11-14 23:40:41 -0800186 }
187
188 // If requested, generate the API files.
189 if (apiFile != null) {
Jeff Gaston8b24e082020-12-09 11:37:27 -0500190 args.addFileOption("api", apiFile!!)
191 if (removedApiFile != null) {
192 args.addFileOption("removedApi", removedApiFile!!)
193 }
Sergey Vasilinets17a18902017-11-14 23:40:41 -0800194 }
195
Sergey Vasilinets17a18902017-11-14 23:40:41 -0800196 // If requested, generate stubs.
197 if (stubsDir != null) {
Jeff Gaston8b24e082020-12-09 11:37:27 -0500198 args.addFileOption("stubs", stubsDir!!)
Sergey Vasilinets17a18902017-11-14 23:40:41 -0800199 val stubs = stubPackages
200 if (stubs != null) {
Jeff Gaston8b24e082020-12-09 11:37:27 -0500201 args.addStringOption("stubpackages", stubs.joinToString(":"))
Sergey Vasilinets17a18902017-11-14 23:40:41 -0800202 }
203 }
204 // Always treat this as an Android docs task.
Jeff Gaston8b24e082020-12-09 11:37:27 -0500205 args.addOption("android")
Aurimas Liutikas0b31a3c2019-11-12 07:45:24 -0800206
Jeff Gaston8b24e082020-12-09 11:37:27 -0500207 // destination directory
208 args.addFileOption("d", destinationDir!!)
209
210 // source files
Jeff Gaston6a8b7662021-12-16 10:55:04 -0500211 val tmpArgs = File.createTempFile("doclavaSourceArgs", ".txt")
212 tmpArgs.deleteOnExit()
213 tmpArgs.bufferedWriter().use { writer ->
214 for (source in sources) {
215 for (file in source) {
216 val arg = file.toString()
217 // Doclava does not know how to parse Kotlin files
218 if (!arg.endsWith(".kt")) {
219 writer.write(arg)
220 writer.newLine()
221 }
Jeff Gaston8b24e082020-12-09 11:37:27 -0500222 }
223 }
224 }
Jeff Gaston6a8b7662021-12-16 10:55:04 -0500225 args.add("@$tmpArgs")
Jeff Gaston8b24e082020-12-09 11:37:27 -0500226
227 return args.build() + extraArgumentsBuilder.build()
Sergey Vasilinets17a18902017-11-14 23:40:41 -0800228 }
229
Jeff Gaston8b24e082020-12-09 11:37:27 -0500230 @TaskAction
231 fun generate() {
232 val args = computeArguments()
Jeff Gaston62394412021-07-30 13:47:34 -0400233 runDoclavaWithArgs(getDocletpath(), args, workerExecutor)
Sergey Vasilinets17a18902017-11-14 23:40:41 -0800234 }
235}
Jeff Gaston8b24e082020-12-09 11:37:27 -0500236
237class DoclavaArgumentBuilder {
238 fun add(value: String) {
239 args.add(value)
240 }
241
242 fun addOption(name: String) {
243 args.add("-" + name)
244 }
245
246 fun addStringOption(name: String, value: String) {
247 addOption(name)
248 args.add(value)
249 }
250
251 fun addBooleanOption(name: String, value: Boolean) {
252 addStringOption(name, value.toString())
253 }
254
255 fun addFileOption(name: String, value: File) {
256 addStringOption(name, value.toString())
257 }
258
259 fun addRepeatableOption(name: String, values: Collection<*>) {
260 for (value in values) {
261 addStringOption(name, value.toString())
262 }
263 }
264
265 fun addStringOption(name: String, values: Collection<String>) {
266 args.add("-" + name)
267 for (value in values) {
268 args.add(value)
269 }
270 }
271
272 fun build(): List<String> {
273 return args
274 }
275
276 private val args = mutableListOf<String>()
277}
278
Jeff Gastona56b1c12020-12-10 18:25:15 -0500279interface DoclavaParams : WorkParameters {
280 fun getClasspath(): ListProperty<File>
281 fun getArgs(): ListProperty<String>
282}
283
284fun runDoclavaWithArgs(classpath: List<File>, args: List<String>, workerExecutor: WorkerExecutor) {
285 val workQueue = workerExecutor.noIsolation()
286 workQueue.submit(DoclavaWorkAction::class.java) { parameters ->
287 parameters.getArgs().set(args)
288 parameters.getClasspath().set(classpath)
Jeff Gaston8b24e082020-12-09 11:37:27 -0500289 }
Jeff Gastona56b1c12020-12-10 18:25:15 -0500290}
291
Jim Sproche1b4e862021-10-06 00:48:22 -0700292abstract class DoclavaWorkAction @Inject constructor(
Jeff Gastona56b1c12020-12-10 18:25:15 -0500293 private val execOperations: ExecOperations
294) : WorkAction<DoclavaParams> {
295 override fun execute() {
296 val args = getParameters().getArgs().get()
297 val classpath = getParameters().getClasspath().get()
298
299 execOperations.javaexec {
300 it.classpath(classpath)
Aurimas Liutikas74dbab12021-06-08 13:46:02 -0700301 it.mainClass.set("com.google.doclava.Doclava")
Jeff Gastona56b1c12020-12-10 18:25:15 -0500302 it.args = args
303 }
304 }
Jeff Gaston8b24e082020-12-09 11:37:27 -0500305}