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}