Skip to content
Open
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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@

### Changes

* Добавлена валидация публичного ABI публикуемых модулей: слепок каждого модуля хранится в `api/<модуль>.api`, таск `apiCheck` входит в `check`, слепки обновляются командой `./gradlew apiDump`. Объявления, помеченные `@DebugPanelInternal`, в слепок не попадают. См. [plugin development](docs/plugin_development.md).
* Добавлена валидация полноты `panel-no-op`: таск `checkNoopApi` проверяет, что модуль покрывает весь публичный API `panel-core` и плагинов.
* **Breaking changes:** Приведены в соответствие с оригиналом no-op объявления: `DebugEvent` перенесён из `com.redmadrobot.debug.core.internal` в `com.redmadrobot.debug.core`, `AboutAppAction` и `AboutAppInfo` — в `com.redmadrobot.debug.plugin.aboutapp.model`. См. [migration guide](docs/migration_guide.md).
* В `panel-no-op` добавлены отсутствовавшие `DebugPanel.isInitialized`, `AboutAppInfo.id`, `AboutAppAction.Event.debugEvent`, `ServersPlugin.getSelectedServer()` и `ServersPlugin.getDefaultServer()`; удалён `DebugPanel.showPanel(FragmentManager)`, которого нет в `panel-core`.
* Обновлён каталог версий зависимостей (2026.07.10 → 2026.07.31).
* Gradle обновлён с 9.4.1 до 9.6.1.

## [1.3.0] (2026-07-30)

### Changes

* **Breaking changes:** `plugin-konfeature` переведён на публичную библиотеку [`konfeature-ui`][konfeature-ui]. Собственная реализация экрана, `ViewModel`, диалога редактирования и `JsonConverter` удалены — UI, состояние и хранение переопределений теперь предоставляет `konfeature-ui`. См. [migration guide](docs/migration_guide.md).
* **Breaking changes:** Удалён `KonfeatureDebugPanelInterceptor`. Вместо него используется `KonfeatureDebugPanelConfig`, который объединяет хранилище переопределений (DataStore) и интерцептор. Создайте конфиг через `KonfeatureDebugPanelConfig.create(context)`, подключите его к `Konfeature` через `applyDebugPanelConfig(config)` и передайте тот же конфиг в `KonfeaturePlugin(konfeature, config)`.
* **Breaking changes:** Изменена сигнатура конструктора `KonfeaturePlugin`: параметр `debugPanelInterceptor` заменён на `config: KonfeatureDebugPanelConfig`.
Expand Down
2 changes: 1 addition & 1 deletion buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ dependencies {
implementation(stack.kotlin.composeCompiler.gradlePlugin)
implementation(stack.detekt.gradlePlugin)
implementation(stack.android.tools.build.gradle)

implementation(stack.kotlinx.binaryCompatibilityValidator)
// Hack-around to access version catalogs inside precompiled script plugins
// See: https://github.com/gradle/gradle/issues/15383#issuecomment-779893192
implementation(files(androidx.javaClass.superclass.protectionDomain.codeSource.location))
Expand Down
2 changes: 1 addition & 1 deletion buildSrc/settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ dependencyResolutionManagement {
}

versionCatalogs {
val version = "2026.07.10" // Keep it in sync with root settings.gradle.kts
val version = "2026.07.31" // Keep it in sync with root settings.gradle.kts
create("rmr") {
from("com.redmadrobot.versions:versions-redmadrobot:$version")
}
Expand Down
84 changes: 84 additions & 0 deletions buildSrc/src/main/kotlin/convention.abi.validation.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import internal.Versions
import internal.stack
import kotlinx.validation.KotlinApiBuildTask
import kotlinx.validation.KotlinApiCompareTask
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile

/*
* Public ABI validation for Android library modules.
*
* The reference dump lives in `<module>/api/<module>.api` and is verified by `check`.
* Run `./gradlew apiDump` to update dumps after an intentional API change.
*
* Note: the ABI validation built into the Kotlin Gradle plugin (`kotlin { abiValidation { } }`)
* cannot be used here. KGP registers its setup actions only from `org.jetbrains.kotlin.android`,
* and since AGP 9 provides Kotlin support itself that plugin must not be applied — so the DSL is
* present but inert. The tasks are wired manually instead; the dump format is identical, so the
* files stay valid once the built-in validation becomes usable.
*/

val abiTools = configurations.dependencyScope("abiTools")
val abiToolsClasspath = configurations.resolvable("abiToolsClasspath") {
extendsFrom(abiTools.get())
}

dependencies {
add(abiTools.name, "org.ow2.asm:asm:${Versions.ASM}")
add(abiTools.name, "org.ow2.asm:asm-tree:${Versions.ASM}")
add(abiTools.name, "org.jetbrains.kotlin:kotlin-metadata-jvm:${stack.versions.kotlin.get()}")
}

val apiFileName = "${project.name}.api"
val referenceApiDir = layout.projectDirectory.dir("api")
// Taken from the compile tasks rather than from `kotlin.target.compilations`: with AGP's built-in
// Kotlin support the compilation outputs are not populated, so the collection would come out empty.
val releaseClasses = files(
provider { tasks.named<KotlinJvmCompile>("compileReleaseKotlin").flatMap { it.destinationDirectory } },
provider { tasks.named<JavaCompile>("compileReleaseJavaWithJavac").flatMap { it.destinationDirectory } },
)

val apiBuild = tasks.register<KotlinApiBuildTask>("apiBuild") {
description = "Dumps the public ABI of the 'release' variant into the build directory."
runtimeClasspath.from(abiToolsClasspath)
inputClassesDirs.from(releaseClasses)
outputApiFile.set(layout.buildDirectory.file("api/$apiFileName"))
nonPublicMarkers.add("com.redmadrobot.debug.core.annotation.DebugPanelInternal")
ignoredClasses.addAll(composableSingletons(releaseClasses))
}

/*
* Names of the holder classes the Compose compiler generates, one per file, for the composable
* lambdas of that file which capture nothing. They are `public` in bytecode and carry no Kotlin
* metadata to filter on, so `nonPublicMarkers` and `internal` have no effect on them -- yet they
* are pure codegen and no consumer can call them. Left in the dump, every new such lambda would
* show up as a public API change.
*
* The names are collected from the compiled classes rather than listed by hand: `ignoredClasses`
* matches exact names only, so a hardcoded list would need an entry per Compose file.
*/
fun composableSingletons(classes: FileCollection): Provider<List<String>> = providers.provider {
classes.files.filter(File::isDirectory).flatMap { root ->
root.walkTopDown()
.filter { it.isFile && it.extension == "class" && it.name.startsWith("ComposableSingletons$") }
.map { it.relativeTo(root).path.removeSuffix(".class").replace(File.separatorChar, '.') }
.toList()
}
}

val apiCheck = tasks.register<KotlinApiCompareTask>("apiCheck") {
description = "Checks that the public ABI matches the reference dump in the 'api' directory."
group = LifecycleBasePlugin.VERIFICATION_GROUP
projectApiFile.set(referenceApiDir.file(apiFileName))
generatedApiFile.set(apiBuild.flatMap { it.outputApiFile })
}

tasks.register<Sync>("apiDump") {
description = "Overwrites the reference ABI dump with the ABI of the current code."
group = LifecycleBasePlugin.VERIFICATION_GROUP
from(apiBuild.flatMap { it.outputApiFile })
into(referenceApiDir)
}

tasks.named(LifecycleBasePlugin.CHECK_TASK_NAME) {
dependsOn(apiCheck)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ plugins {
id("convention-publish")
id("convention.compose")
id("convention.detekt")
id("convention.abi.validation")
}

android {
Expand Down
Loading
Loading