Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The big refactor — step 1: prep work #192

Merged
merged 8 commits into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
name: Publish artifacts in Space

on:
release:
types: [ published ]
push:
branches: [ main ]

jobs:
publish-core:
name: Publish Jewel Core
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions .idea/runConfigurations/Run_checks__IJ_23_2_.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions .idea/runConfigurations/Update_API_signatures.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 7 additions & 4 deletions buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
plugins {
`kotlin-dsl`
alias(libs.plugins.kotlinSerialization)
alias(libs.plugins.kotlinx.serialization)
}

gradlePlugin {
Expand All @@ -27,13 +27,16 @@ kotlin {
}

dependencies {
implementation(libs.kotlin.gradlePlugin)
implementation(libs.kotlinter.gradlePlugin)
implementation(libs.detekt.gradlePlugin)
implementation(libs.kotlinSarif)
implementation(libs.dokka.gradlePlugin)
implementation(libs.kotlin.gradlePlugin)
implementation(libs.kotlinSarif)
implementation(libs.kotlinpoet)
implementation(libs.kotlinter.gradlePlugin)
implementation(libs.kotlinx.binaryCompatValidator.gradlePlugin)
implementation(libs.kotlinx.serialization.json)
implementation(libs.poko.gradlePlugin)

// Enables using type-safe accessors to reference plugins from the [plugins] block defined in version catalogs.
// Context: https://github.com/gradle/gradle/issues/15383#issuecomment-779893192
implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location))
Expand Down
4 changes: 2 additions & 2 deletions buildSrc/src/main/kotlin/IdeaConfiguration.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ private var warned = AtomicBoolean(false)

fun Project.supportedIJVersion(): SupportedIJVersion {
val prop = kotlin.runCatching {
localProperty("supported.ij.version")
?: rootProject.property("supported.ij.version")?.toString()
rootProject.findProperty("supported.ij.version")?.toString() ?:
localProperty("supported.ij.version")
}.getOrNull()

if (prop == null) {
Expand Down
6 changes: 0 additions & 6 deletions buildSrc/src/main/kotlin/MergeSarifTask.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,9 @@ import io.github.detekt.sarif4k.Version
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import kotlinx.serialization.json.encodeToStream
import org.gradle.api.file.FileTree
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.IgnoreEmptyDirectories
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.SkipWhenEmpty
import org.gradle.api.tasks.SourceTask
import org.gradle.api.tasks.TaskAction

Expand Down
97 changes: 97 additions & 0 deletions buildSrc/src/main/kotlin/ValidatePublicApiTask.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
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"
}

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()

@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}")

apiFile.useLines { lines ->
val actualDataClasses = findDataClasses(lines)

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.")
}
}

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,
)
29 changes: 29 additions & 0 deletions buildSrc/src/main/kotlin/jewel-check-public-api.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
plugins {
id("org.jetbrains.kotlinx.binary-compatibility-validator")
id("dev.drewhamilton.poko")
}

apiValidation {
/**
* Set of annotations that exclude API from being public. Typically, it is
* all kinds of `@InternalApi` annotations that mark effectively private
* API that cannot be actually private for technical reasons.
*/
nonPublicMarkers.add("org.jetbrains.jewel.InternalJewelApi")
}

poko {
pokoAnnotation.set("org.jetbrains.jewel.GenerateDataFunctions")
}

tasks {
val validatePublicApi = register<ValidatePublicApiTask>("validatePublicApi") {
include { it.file.extension == "api" }
source(project.fileTree("api"))
dependsOn(named("apiCheck"))
}

named("check") {
dependsOn(validatePublicApi)
}
}
8 changes: 7 additions & 1 deletion buildSrc/src/main/kotlin/jewel.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import io.gitlab.arturbosch.detekt.Detekt
import org.gradle.api.attributes.Usage
import org.jmailen.gradle.kotlinter.tasks.FormatTask
import org.jmailen.gradle.kotlinter.tasks.LintTask

plugins {
Expand Down Expand Up @@ -48,7 +49,7 @@ detekt {
buildUponDefaultConfig = true
}

val sarif by configurations.creating {
val sarif: Configuration by configurations.creating {
isCanBeConsumed = true
attributes {
attribute(Usage.USAGE_ATTRIBUTE, objects.named("sarif"))
Expand All @@ -69,6 +70,11 @@ tasks {
}
}
}

withType<FormatTask> {
exclude { it.file.absolutePath.contains("build/generated") }
}

withType<LintTask> {
exclude { it.file.absolutePath.contains("build/generated") }

Expand Down
Loading