-
Notifications
You must be signed in to change notification settings - Fork 42
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
52 changed files
with
1,466 additions
and
1,467 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,47 +1,46 @@ | ||
import java.util.concurrent.atomic.AtomicBoolean | ||
import org.gradle.api.Project | ||
import org.gradle.api.provider.Property | ||
import org.gradle.jvm.toolchain.JavaLanguageVersion | ||
import java.util.concurrent.atomic.AtomicBoolean | ||
|
||
enum class SupportedIJVersion { | ||
IJ_232, | ||
IJ_233 | ||
IJ_232, | ||
IJ_233 | ||
} | ||
|
||
private var warned = AtomicBoolean(false) | ||
|
||
fun Project.supportedIJVersion(): SupportedIJVersion { | ||
val prop = | ||
kotlin | ||
.runCatching { | ||
rootProject.findProperty("supported.ij.version")?.toString() | ||
?: localProperty("supported.ij.version") | ||
} | ||
.getOrNull() | ||
val prop = | ||
kotlin | ||
.runCatching { | ||
rootProject.findProperty("supported.ij.version")?.toString() | ||
?: localProperty("supported.ij.version") | ||
} | ||
.getOrNull() | ||
|
||
if (prop == null) { | ||
if (!warned.getAndSet(true)) { | ||
logger.warn( | ||
""" | ||
No 'supported.ij.version' property provided. Falling back to IJ 233. | ||
It is recommended to provide it using local.properties file or -Psupported.ij.version to avoid unexpected behavior. | ||
if (prop == null) { | ||
if (!warned.getAndSet(true)) { | ||
logger.warn( | ||
""" | ||
.trimIndent() | ||
) | ||
No 'supported.ij.version' property provided. Falling back to IJ 233. | ||
It is recommended to provide it using local.properties file or -Psupported.ij.version to | ||
avoid unexpected behavior. | ||
""".trimIndent() | ||
) | ||
} | ||
return SupportedIJVersion.IJ_233 | ||
} | ||
return SupportedIJVersion.IJ_233 | ||
} | ||
|
||
return when (prop) { | ||
"232" -> SupportedIJVersion.IJ_232 | ||
"233" -> SupportedIJVersion.IJ_233 | ||
else -> | ||
error( | ||
"Invalid 'supported.ij.version' with value '$prop' is provided. " + | ||
"It should be in set of these values: ('232', '233')" | ||
) | ||
} | ||
return when (prop) { | ||
"232" -> SupportedIJVersion.IJ_232 | ||
"233" -> SupportedIJVersion.IJ_233 | ||
else -> | ||
error( | ||
"Invalid 'supported.ij.version' with value '$prop' is provided. " + | ||
"It should be one of these values: ('232', '233')" | ||
) | ||
} | ||
} | ||
|
||
fun Property<JavaLanguageVersion>.assign(version: Int) = | ||
set(JavaLanguageVersion.of(version)) | ||
fun Property<JavaLanguageVersion>.assign(version: Int) = set(JavaLanguageVersion.of(version)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,12 @@ | ||
import java.util.Properties | ||
import org.gradle.api.Project | ||
import java.util.Properties | ||
|
||
internal fun Project.localProperty(propertyName: String): String? { | ||
val localPropertiesFile = rootProject.file("local.properties") | ||
if (!localPropertiesFile.exists()) { | ||
return null | ||
} | ||
val properties = Properties() | ||
localPropertiesFile.inputStream().use { properties.load(it) } | ||
return properties.getProperty(propertyName) | ||
val localPropertiesFile = rootProject.file("local.properties") | ||
if (!localPropertiesFile.exists()) { | ||
return null | ||
} | ||
val properties = Properties() | ||
localPropertiesFile.inputStream().use { properties.load(it) } | ||
return properties.getProperty(propertyName) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,103 +1,102 @@ | ||
import java.io.File | ||
import java.util.Stack | ||
import org.gradle.api.GradleException | ||
import org.gradle.api.tasks.CacheableTask | ||
import org.gradle.api.tasks.SourceTask | ||
import org.gradle.api.tasks.TaskAction | ||
import java.io.File | ||
import java.util.Stack | ||
|
||
@CacheableTask | ||
open class ValidatePublicApiTask : SourceTask() { | ||
|
||
init { | ||
group = "verification" | ||
init { | ||
group = "verification" | ||
|
||
// The output is never really used, it is here for cacheability reasons only | ||
outputs.file(project.layout.buildDirectory.file("apiValidationRun")) | ||
} | ||
// The output is never really used, it is here for cacheability reasons only | ||
outputs.file(project.layout.buildDirectory.file("apiValidationRun")) | ||
} | ||
|
||
private val classFqnRegex = "public (?:\\w+ )*class (\\S+)\\b".toRegex() | ||
private val classFqnRegex = "public (?:\\w+ )*class (\\S+)\\b".toRegex() | ||
|
||
@Suppress( | ||
"ConvertToStringTemplate" | ||
) // The odd concatenation is needed because of $; escapes get confused | ||
private val copyMethodRegex = | ||
("public static synthetic fun copy(-\\w+)?" + "\\$" + "default\\b").toRegex() | ||
@Suppress("ConvertToStringTemplate") // The odd concatenation is needed because of $; escapes get confused | ||
private val copyMethodRegex = | ||
("public static synthetic fun copy(-\\w+)?" + "\\$" + "default\\b").toRegex() | ||
|
||
@TaskAction | ||
fun validatePublicApi() { | ||
logger.info("Validating ${source.files.size} API file(s)...") | ||
@TaskAction | ||
fun validatePublicApi() { | ||
logger.info("Validating ${source.files.size} API file(s)...") | ||
|
||
val violations = mutableMapOf<File, Set<String>>() | ||
inputs.files.forEach { apiFile -> | ||
logger.lifecycle("Validating public API from file ${apiFile.path}") | ||
val violations = mutableMapOf<File, Set<String>>() | ||
inputs.files.forEach { apiFile -> | ||
logger.lifecycle("Validating public API from file ${apiFile.path}") | ||
|
||
apiFile.useLines { lines -> | ||
val actualDataClasses = findDataClasses(lines) | ||
apiFile.useLines { lines -> | ||
val actualDataClasses = findDataClasses(lines) | ||
|
||
if (actualDataClasses.isNotEmpty()) { | ||
violations[apiFile] = actualDataClasses | ||
if (actualDataClasses.isNotEmpty()) { | ||
violations[apiFile] = actualDataClasses | ||
} | ||
} | ||
} | ||
|
||
if (violations.isNotEmpty()) { | ||
val message = buildString { | ||
appendLine("Data classes found in public API.") | ||
appendLine() | ||
|
||
for ((file, dataClasses) in violations.entries) { | ||
appendLine("In file ${file.path}:") | ||
for (dataClass in dataClasses) { | ||
appendLine(" * ${dataClass.replace("/", ".")}") | ||
} | ||
appendLine() | ||
} | ||
} | ||
|
||
throw GradleException(message) | ||
} else { | ||
logger.lifecycle("No public API violations found.") | ||
} | ||
} | ||
} | ||
|
||
if (violations.isNotEmpty()) { | ||
val message = buildString { | ||
appendLine("Data classes found in public API.") | ||
appendLine() | ||
|
||
for ((file, dataClasses) in violations.entries) { | ||
appendLine("In file ${file.path}:") | ||
for (dataClass in dataClasses) { | ||
appendLine(" * ${dataClass.replace("/", ".")}") | ||
} | ||
appendLine() | ||
private fun findDataClasses(lines: Sequence<String>): Set<String> { | ||
val currentClassStack = Stack<String>() | ||
val dataClasses = mutableMapOf<String, DataClassInfo>() | ||
|
||
for (line in lines) { | ||
if (line.isBlank()) continue | ||
|
||
val matchResult = classFqnRegex.find(line) | ||
if (matchResult != null) { | ||
val classFqn = matchResult.groupValues[1] | ||
currentClassStack.push(classFqn) | ||
continue | ||
} | ||
|
||
if (line.contains("}")) { | ||
currentClassStack.pop() | ||
continue | ||
} | ||
|
||
val fqn = currentClassStack.peek() | ||
if (copyMethodRegex.find(line) != null) { | ||
val info = dataClasses.getOrPut(fqn) { DataClassInfo(fqn) } | ||
info.hasCopyMethod = true | ||
} else if (line.contains("public static final synthetic fun box-impl")) { | ||
val info = dataClasses.getOrPut(fqn) { DataClassInfo(fqn) } | ||
info.isLikelyValueClass = true | ||
} | ||
} | ||
} | ||
|
||
throw GradleException(message) | ||
} else { | ||
logger.lifecycle("No public API violations found.") | ||
val actualDataClasses = | ||
dataClasses.filterValues { it.hasCopyMethod && !it.isLikelyValueClass } | ||
.keys | ||
return actualDataClasses | ||
} | ||
} | ||
|
||
private fun findDataClasses(lines: Sequence<String>): Set<String> { | ||
val currentClassStack = Stack<String>() | ||
val dataClasses = mutableMapOf<String, DataClassInfo>() | ||
|
||
for (line in lines) { | ||
if (line.isBlank()) continue | ||
|
||
val matchResult = classFqnRegex.find(line) | ||
if (matchResult != null) { | ||
val classFqn = matchResult.groupValues[1] | ||
currentClassStack.push(classFqn) | ||
continue | ||
} | ||
|
||
if (line.contains("}")) { | ||
currentClassStack.pop() | ||
continue | ||
} | ||
|
||
val fqn = currentClassStack.peek() | ||
if (copyMethodRegex.find(line) != null) { | ||
val info = dataClasses.getOrPut(fqn) { DataClassInfo(fqn) } | ||
info.hasCopyMethod = true | ||
} else if (line.contains("public static final synthetic fun box-impl")) { | ||
val info = dataClasses.getOrPut(fqn) { DataClassInfo(fqn) } | ||
info.isLikelyValueClass = true | ||
} | ||
} | ||
|
||
val actualDataClasses = | ||
dataClasses.filterValues { it.hasCopyMethod && !it.isLikelyValueClass }.keys | ||
return actualDataClasses | ||
} | ||
} | ||
|
||
@Suppress("DataClassShouldBeImmutable") // Only used in a loop, saves memory and is faster | ||
private data class DataClassInfo( | ||
val fqn: String, | ||
var hasCopyMethod: Boolean = false, | ||
var isLikelyValueClass: Boolean = false, | ||
val fqn: String, | ||
var hasCopyMethod: Boolean = false, | ||
var isLikelyValueClass: Boolean = false, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.