1/*
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
17package androidx.build.doclava
18
19import org.gradle.api.tasks.Input
20import org.gradle.api.tasks.InputFiles
21import org.gradle.api.tasks.Optional
22import org.gradle.api.tasks.OutputDirectory
23import org.gradle.api.tasks.OutputFile
24import org.gradle.api.tasks.javadoc.Javadoc
25import org.gradle.external.javadoc.CoreJavadocOptions
26import java.io.File
27
28// external/doclava/src/com/google/doclava/Errors.java
29val DEFAULT_DOCLAVA_CONFIG = ChecksConfig(
30        errors = listOf(
31                101,  // unresolved link
32                103,  // unknown tag
33                104   // unknown param name
34        ),
35        warnings = listOf(121 /* hidden type param */),
36        hidden = listOf(
37                111,  // hidden super class
38                113   // @deprecation mismatch
39        )
40)
41
42private fun <E> CoreJavadocOptions.addMultilineMultiValueOption(
43    name: String,
44    values: Collection<E>
45) {
46    addMultilineMultiValueOption(name).value = values.map { listOf(it.toString()) }
47}
48
49open class DoclavaTask : Javadoc() {
50
51    // All lowercase name to match MinimalJavadocOptions#docletpath
52    private var docletpath: List<File> = emptyList()
53
54    @Input
55    var checksConfig: ChecksConfig = DEFAULT_DOCLAVA_CONFIG
56
57    /**
58     * If non-null, the list of packages that will be treated as if they were
59     * marked with {@literal @hide}.<br>
60     * Packages names will be matched exactly; sub-packages are not automatically recognized.
61     */
62    @Optional
63    @Input
64    var hiddenPackages: Collection<String>? = null
65
66    /**
67     * If non-null and not-empty, the whitelist of packages that will be present in the generated
68     * stubs; if null or empty, then all packages have stubs generated.<br>
69     * Wildcards are accepted.
70     */
71    @Optional
72    @Input
73    var stubPackages: Set<String>? = null
74
75    @Input
76    var generateDocs = true
77
78    /**
79     * If non-null, the location of where to place the generated api file.
80     * If this is non-null, then {@link #removedApiFile} must be non-null as well.
81     */
82    @Optional
83    @OutputFile
84    var apiFile: File? = null
85
86    /**
87     * If non-null, the location of where to place the generated removed api file.
88     * If this is non-null, then {@link #apiFile} must be non-null as well.
89     */
90    @Optional
91    @OutputFile
92    var removedApiFile: File? = null
93
94    /**
95     * If non-null, the location of the generated keep list.
96     */
97    @Optional
98    @OutputFile
99    var keepListFile: File? = null
100
101    /**
102     * If non-null, the location to put the generated stub sources.
103     */
104    @Optional
105    @OutputDirectory
106    var stubsDir: File? = null
107
108    init {
109        setFailOnError(true)
110        options.doclet = "com.google.doclava.Doclava"
111        options.encoding("UTF-8")
112        options.quiet()
113        // doclava doesn't understand '-doctitle'
114        title = null
115        maxMemory = "1280m"
116        // If none of generateDocs, apiFile, keepListFile, or stubJarsDir are true, then there is
117        // no work to do.
118        onlyIf({ generateDocs || apiFile != null || keepListFile != null || stubsDir != null })
119    }
120
121    /**
122     * The doclet path which has the {@code com.gogole.doclava.Doclava} class.
123     * This option will override any doclet path set in this instance's
124     * {@link #options JavadocOptions}.
125     * @see MinimalJavadocOptions#getDocletpath()
126     */
127    @InputFiles
128    fun getDocletpath(): List<File> {
129        return docletpath
130    }
131
132    /**
133     * Sets the doclet path which has the {@code com.gogole.doclava.Doclava} class.
134     * This option will override any doclet path set in this instance's
135     * {@link #options JavadocOptions}.
136     * @see MinimalJavadocOptions#setDocletpath(java.util.List)
137     */
138    fun setDocletpath(docletpath: Collection<File>) {
139        this.docletpath = docletpath.toList()
140        // Go ahead and keep the docletpath in our JavadocOptions object in sync.
141        options.docletpath = docletpath.toList()
142    }
143
144    /**
145     * "Configures" this DoclavaTask with parameters that might not be at their final values
146     * until this task is run.
147     */
148    private fun configureDoclava() = (options as CoreJavadocOptions).apply {
149
150        docletpath = this@DoclavaTask.docletpath
151
152        // configure doclava error/warning/hide levels
153        addMultilineMultiValueOption("hide", checksConfig.hidden)
154        addMultilineMultiValueOption("warning", checksConfig.warnings)
155        addMultilineMultiValueOption("error", checksConfig.errors)
156
157        if (hiddenPackages != null) {
158            addMultilineMultiValueOption("hidePackage", hiddenPackages!!)
159        }
160
161        if (!generateDocs) {
162            addBooleanOption("nodocs", true)
163        }
164
165        // If requested, generate the API files.
166        if (apiFile != null) {
167            addFileOption("api", apiFile)
168            addFileOption("removedApi", removedApiFile)
169        }
170
171        // If requested, generate the keep list.
172        addFileOption("proguard", keepListFile)
173
174        // If requested, generate stubs.
175        if (stubsDir != null) {
176            addFileOption("stubs", stubsDir)
177            val stubs = stubPackages
178            if (stubs != null) {
179                addStringOption("stubpackages", stubs.joinToString(":"))
180            }
181        }
182        // Always treat this as an Android docs task.
183        addBooleanOption("android", true)
184    }
185
186    fun coreJavadocOptions(configure: CoreJavadocOptions.() -> Unit) =
187            (options as CoreJavadocOptions).configure()
188
189    override fun generate() {
190        configureDoclava()
191        super.generate()
192    }
193}
194