1735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets/*
2735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets * Copyright 2017 The Android Open Source Project
3735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets *
4735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets * Licensed under the Apache License, Version 2.0 (the "License");
5735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets * you may not use this file except in compliance with the License.
6735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets * You may obtain a copy of the License at
7735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets *
8735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets *      http://www.apache.org/licenses/LICENSE-2.0
9735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets *
10735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets * Unless required by applicable law or agreed to in writing, software
11735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets * distributed under the License is distributed on an "AS IS" BASIS,
12735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets * See the License for the specific language governing permissions and
14735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets * limitations under the License.
15735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets */
16735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets
17526389b5eb93f99eaf4dba0b0c75b0b7df9a0f65Aurimas Liutikaspackage androidx.build.checkapi
18735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets
19a14b834943469c91933a93da68c81f0ebc0a5719Sergey Vasilinetsimport androidx.build.doclava.ChecksConfig
20735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinetsimport org.gradle.api.DefaultTask
21735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinetsimport org.gradle.api.GradleException
22735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinetsimport org.gradle.api.tasks.Input
23735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinetsimport org.gradle.api.tasks.InputFile
24735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinetsimport org.gradle.api.tasks.InputFiles
25735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinetsimport org.gradle.api.tasks.Optional
26735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinetsimport org.gradle.api.tasks.OutputFile
27735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinetsimport org.gradle.api.tasks.TaskAction
2836bbc1da69c861fb6ee0e2f26a2ba2c36f949069Aurimas Liutikasimport java.io.ByteArrayInputStream
2936bbc1da69c861fb6ee0e2f26a2ba2c36f949069Aurimas Liutikasimport java.io.ByteArrayOutputStream
3036bbc1da69c861fb6ee0e2f26a2ba2c36f949069Aurimas Liutikasimport java.io.File
31735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinetsimport java.security.MessageDigest
32735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets
33735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets/** Character that resets console output color. */
34735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinetsprivate const val ANSI_RESET = "\u001B[0m"
35735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets
36735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets/** Character that sets console output color to red. */
37735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinetsprivate const val ANSI_RED = "\u001B[31m"
38735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets
39735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets/** Character that sets console output color to yellow. */
40735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinetsprivate const val ANSI_YELLOW = "\u001B[33m"
41735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets
42735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinetsprivate val ERROR_REGEX = Regex("^(.+):(.+): (\\w+) (\\d+): (.+)$")
43735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets
44735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinetsprivate fun ByteArray.encodeHex() = fold(StringBuilder(), { builder, byte ->
45735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    val hexString = Integer.toHexString(byte.toInt() and 0xFF)
46735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    if (hexString.length < 2) {
47735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets        builder.append("0")
48735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    }
49735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    builder.append(hexString)
50735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets}).toString()
51735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets
52735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinetsprivate fun getShortHash(src: String): String {
53735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    val str = MessageDigest.getInstance("SHA-1")
54735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets            .digest(src.toByteArray()).encodeHex()
55735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    val len = str.length
56735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    return str.substring(len - 7, len)
57735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets}
58735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets
59735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets/**
60735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets * Task used to verify changes between two API files.
61735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets * <p>
62735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets * This task may be configured to ignore, warn, or fail with a message for a specific set of
63735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets * Doclava-defined error codes. See {@link com.google.doclava.Errors} for a complete list of
64735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets * supported error codes.
65735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets * <p>
66735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets * Specific failures may be ignored by specifying a list of SHAs in {@link #whitelistErrors}. Each
67735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets * SHA is unique to a specific API change and is logged to the error output on failure.
68735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets */
69735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinetsopen class CheckApiTask : DefaultTask() {
70735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets
71735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    /** API file that represents the existing API surface. */
72735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    @Optional
73735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    @InputFile
74735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    var oldApiFile: File? = null
75735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets
76735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    /** API file that represents the existing API surface's removals. */
77735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    @Optional
78735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    @InputFile
79735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    var oldRemovedApiFile: File? = null
80735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets
81735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    /** API file that represents the candidate API surface. */
82735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    @InputFile
8336bbc1da69c861fb6ee0e2f26a2ba2c36f949069Aurimas Liutikas    lateinit var newApiFile: File
84735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets
85735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    /** API file that represents the candidate API surface's removals. */
86735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    @Optional
87735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    @InputFile
88735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    var newRemovedApiFile: File? = null
89735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets
90735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    /** Optional file containing a newline-delimited list of error SHAs to ignore. */
91735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    var whitelistErrorsFile: File? = null
92735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets
93735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    @Optional
94735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    @InputFile
95735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    fun getWhiteListErrorsFileInput(): File? {
96735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets        // Gradle requires non-null InputFiles to exist -- even with Optional -- so work around that
97735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets        // by returning null for this field if the file doesn't exist.
98735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets        if (whitelistErrorsFile?.exists() == true) {
99735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets            return whitelistErrorsFile
100735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets        }
101735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets        return null
102735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    }
103735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets
104735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    /**
105735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets     * Optional set of error SHAs to ignore.
106735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets     * <p>
107735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets     * Each error SHA is unique to a specific API change.
108735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets     */
109735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    @Optional
110735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    @Input
111735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    var whitelistErrors = emptySet<String>()
112735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets
113735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    var detectedWhitelistErrors = mutableSetOf<String>()
114735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets
115735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    @InputFiles
116735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    var doclavaClasspath: Collection<File> = emptyList()
117735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets
118735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    // A dummy output file meant only to tag when this check was last ran.
119735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    // Without any outputs, Gradle will run this task every time.
120735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    @Optional
121735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    private var mOutputFile: File? = null
122735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets
123735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    @OutputFile
124735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    fun getOutputFile(): File {
12536bbc1da69c861fb6ee0e2f26a2ba2c36f949069Aurimas Liutikas        return if (mOutputFile != null) {
126735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets            mOutputFile!!
12736bbc1da69c861fb6ee0e2f26a2ba2c36f949069Aurimas Liutikas        } else {
12836bbc1da69c861fb6ee0e2f26a2ba2c36f949069Aurimas Liutikas            File(project.buildDir, "checkApi/$name-completed")
12936bbc1da69c861fb6ee0e2f26a2ba2c36f949069Aurimas Liutikas        }
130735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    }
131735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets
132735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    @Optional
133735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    fun setOutputFile(outputFile: File) {
134735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets        mOutputFile = outputFile
135735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    }
136735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets
137735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    @Input
138a14b834943469c91933a93da68c81f0ebc0a5719Sergey Vasilinets    lateinit var checksConfig: ChecksConfig
139735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets
140735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    init {
141735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets        group = "Verification"
142735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets        description = "Invoke Doclava\'s ApiCheck tool to make sure current.txt is up to date."
143735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    }
144735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets
145735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    private fun collectAndVerifyInputs(): Set<File> {
146735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets        if (oldRemovedApiFile != null && newRemovedApiFile != null) {
14736bbc1da69c861fb6ee0e2f26a2ba2c36f949069Aurimas Liutikas            return setOf(oldApiFile!!, newApiFile, oldRemovedApiFile!!, newRemovedApiFile!!)
148735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets        } else {
14936bbc1da69c861fb6ee0e2f26a2ba2c36f949069Aurimas Liutikas            return setOf(oldApiFile!!, newApiFile)
150735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets        }
151735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    }
152735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets
153735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    @TaskAction
154735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    fun exec() {
155735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets        if (oldApiFile == null) {
156735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets            // Nothing to do.
157735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets            return
158735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets        }
159735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets
160735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets        val apiFiles = collectAndVerifyInputs()
161735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets
162735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets        val errStream = ByteArrayOutputStream()
163735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets
164735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets        // If either of those gets tweaked, then this should be refactored to extend JavaExec.
165735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets        project.javaexec { spec ->
166735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets            spec.apply {
167735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets                // Put Doclava on the classpath so we can get the ApiCheck class.
168735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets                classpath(doclavaClasspath)
169735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets                main = "com.google.doclava.apicheck.ApiCheck"
170735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets
171735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets                minHeapSize = "128m"
172735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets                maxHeapSize = "1024m"
173735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets
174735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets                // add -error LEVEL for every error level we want to fail the build on.
175a14b834943469c91933a93da68c81f0ebc0a5719Sergey Vasilinets                checksConfig.errors.forEach { args("-error", it) }
176a14b834943469c91933a93da68c81f0ebc0a5719Sergey Vasilinets                checksConfig.warnings.forEach { args("-warning", it) }
177a14b834943469c91933a93da68c81f0ebc0a5719Sergey Vasilinets                checksConfig.hidden.forEach { args("-hide", it) }
178735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets
179735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets                spec.args(apiFiles.map { it.absolutePath })
180735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets
181735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets                // Redirect error output so that we can whitelist specific errors.
182735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets                errorOutput = errStream
183735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets                // We will be handling failures ourselves with a custom message.
184735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets                setIgnoreExitValue(true)
185735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets            }
186735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets        }
187735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets
188735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets        // Load the whitelist file, if present.
189735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets        val whitelistFile = whitelistErrorsFile
190735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets        if (whitelistFile?.exists() == true) {
191735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets            whitelistErrors += whitelistFile.readLines()
192735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets        }
193735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets
194735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets        // Parse the error output.
195735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets        val unparsedErrors = mutableSetOf<String>()
196735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets        val detectedErrors = mutableSetOf<List<String>>()
197735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets        val parsedErrors = mutableSetOf<List<String>>()
198735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets        ByteArrayInputStream(errStream.toByteArray()).bufferedReader().lines().forEach {
199735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets            val match = ERROR_REGEX.matchEntire(it)
200735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets
201735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets            if (match == null) {
202735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets                unparsedErrors.add(it)
203735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets            } else if (match.groups[3]?.value == "error") {
204735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets                val hash = getShortHash(match.groups[5]?.value!!)
205735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets                val error = match.groupValues.subList(1, match.groupValues.size) + listOf(hash)
206735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets                if (hash in whitelistErrors) {
207735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets                    detectedErrors.add(error)
208735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets                    detectedWhitelistErrors.add(error[5])
209735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets                } else {
210735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets                    parsedErrors.add(error)
211735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets                }
212735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets            }
213735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets        }
214735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets
215735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets        unparsedErrors.forEach { error -> logger.error("$ANSI_RED$error$ANSI_RESET") }
216735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets        parsedErrors.forEach { logger.error("$ANSI_RED${it[5]}$ANSI_RESET ${it[4]}") }
217735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets        detectedErrors.forEach { logger.warn("$ANSI_YELLOW${it[5]}$ANSI_RESET ${it[4]}") }
218735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets
219735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets        if (unparsedErrors.isNotEmpty() || parsedErrors.isNotEmpty()) {
220a14b834943469c91933a93da68c81f0ebc0a5719Sergey Vasilinets            throw GradleException(checksConfig.onFailMessage ?: "")
221735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets        }
222735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets
223735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets        // Just create a dummy file upon completion. Without any outputs, Gradle will run this task
224735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets        // every time.
225735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets        val outputFile = getOutputFile()
226735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets        outputFile.parentFile.mkdirs()
227735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets        outputFile.createNewFile()
228735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets    }
229735e0f039737982de5f3b22e451c53cc2baa1656Sergey Vasilinets}