Skip to content

Configure output directory for dumps #170

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

Merged
merged 10 commits into from
Jan 19, 2024
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
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ apiValidation {
* Flag to programmatically disable compatibility validator
*/
validationDisabled = true

/**
* A path to a subdirectory inside the project root directory where dumps should be stored.
*/
apiDumpDirectory = "api"
}
```

Expand Down Expand Up @@ -123,6 +128,11 @@ apiValidation {
* Flag to programmatically disable compatibility validator
*/
validationDisabled = false

/**
* A path to a subdirectory inside the project root directory where dumps should be stored.
*/
apiDumpDirectory = "aux/validation"
}
```

Expand Down
2 changes: 2 additions & 0 deletions api/binary-compatibility-validator.api
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
public class kotlinx/validation/ApiValidationExtension {
public fun <init> ()V
public final fun getAdditionalSourceSets ()Ljava/util/Set;
public final fun getApiDumpDirectory ()Ljava/lang/String;
public final fun getIgnoredClasses ()Ljava/util/Set;
public final fun getIgnoredPackages ()Ljava/util/Set;
public final fun getIgnoredProjects ()Ljava/util/Set;
Expand All @@ -10,6 +11,7 @@ public class kotlinx/validation/ApiValidationExtension {
public final fun getPublicPackages ()Ljava/util/Set;
public final fun getValidationDisabled ()Z
public final fun setAdditionalSourceSets (Ljava/util/Set;)V
public final fun setApiDumpDirectory (Ljava/lang/String;)V
public final fun setIgnoredClasses (Ljava/util/Set;)V
public final fun setIgnoredPackages (Ljava/util/Set;)V
public final fun setIgnoredProjects (Ljava/util/Set;)V
Expand Down
3 changes: 2 additions & 1 deletion src/functionalTest/kotlin/kotlinx/validation/api/TestDsl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@

package kotlinx.validation.api

import kotlinx.validation.ApiValidationExtension
import java.io.*
import org.gradle.testkit.runner.GradleRunner
import org.intellij.lang.annotations.Language

public const val API_DIR: String = "api"
public val API_DIR: String = ApiValidationExtension().apiDumpDirectory

internal fun BaseKotlinGradleTest.test(fn: BaseKotlinScope.() -> Unit): GradleRunner {
val baseKotlinScope = BaseKotlinScope()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/*
* Copyright 2016-2024 JetBrains s.r.o.
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
*/

package kotlinx.validation.test

import kotlinx.validation.api.*
import kotlinx.validation.api.buildGradleKts
import kotlinx.validation.api.resolve
import kotlinx.validation.api.test
import org.assertj.core.api.Assertions
import org.junit.Test
import kotlin.test.assertTrue

class OutputDirectoryTests : BaseKotlinGradleTest() {
@Test
fun dumpIntoCustomDirectory() {
val runner = test {
buildGradleKts {
resolve("/examples/gradle/base/withPlugin.gradle.kts")
resolve("/examples/gradle/configuration/outputDirectory/different.gradle.kts")
}

kotlin("AnotherBuildConfig.kt") {
resolve("/examples/classes/AnotherBuildConfig.kt")
}
dir("api") {
file("letMeBe.txt") {
}
}

runner {
arguments.add(":apiDump")
}
}

runner.build().apply {
assertTaskSuccess(":apiDump")

val dumpFile = rootProjectDir.resolve("custom").resolve("${rootProjectDir.name}.api")
assertTrue(dumpFile.exists(), "api dump file ${dumpFile.path} should exist")

val expected = readFileList("/examples/classes/AnotherBuildConfig.dump")
Assertions.assertThat(dumpFile.readText()).isEqualToIgnoringNewLines(expected)

val fileInsideDir = rootProjectDir.resolve("api").resolve("letMeBe.txt")
assertTrue(fileInsideDir.exists(), "existing api directory should not be overridden")
}
}

@Test
fun validateDumpFromACustomDirectory() {
val runner = test {
buildGradleKts {
resolve("/examples/gradle/base/withPlugin.gradle.kts")
resolve("/examples/gradle/configuration/outputDirectory/different.gradle.kts")
}

kotlin("AnotherBuildConfig.kt") {
resolve("/examples/classes/AnotherBuildConfig.kt")
}
dir("custom") {
file("${rootProjectDir.name}.api") {
resolve("/examples/classes/AnotherBuildConfig.dump")
}
}

runner {
arguments.add(":apiCheck")
}
}

runner.build().apply {
assertTaskSuccess(":apiCheck")
}
}

@Test
fun dumpIntoSubdirectory() {
val runner = test {
buildGradleKts {
resolve("/examples/gradle/base/withPlugin.gradle.kts")
resolve("/examples/gradle/configuration/outputDirectory/subdirectory.gradle.kts")
}

kotlin("AnotherBuildConfig.kt") {
resolve("/examples/classes/AnotherBuildConfig.kt")
}

runner {
arguments.add(":apiDump")
}
}

runner.build().apply {
assertTaskSuccess(":apiDump")

val dumpFile = rootProjectDir.resolve("validation")
.resolve("api")
.resolve("${rootProjectDir.name}.api")

assertTrue(dumpFile.exists(), "api dump file ${dumpFile.path} should exist")

val expected = readFileList("/examples/classes/AnotherBuildConfig.dump")
Assertions.assertThat(dumpFile.readText()).isEqualToIgnoringNewLines(expected)
}
}

@Test
fun validateDumpFromASubdirectory() {
val runner = test {
buildGradleKts {
resolve("/examples/gradle/base/withPlugin.gradle.kts")
resolve("/examples/gradle/configuration/outputDirectory/subdirectory.gradle.kts")
}

kotlin("AnotherBuildConfig.kt") {
resolve("/examples/classes/AnotherBuildConfig.kt")
}
dir("validation") {
dir("api") {
file("${rootProjectDir.name}.api") {
resolve("/examples/classes/AnotherBuildConfig.dump")
}
}
}

runner {
arguments.add(":apiCheck")
}
}

runner.build().apply {
assertTaskSuccess(":apiCheck")
}
}

@Test
fun dumpIntoParentDirectory() {
val runner = test {
buildGradleKts {
resolve("/examples/gradle/base/withPlugin.gradle.kts")
resolve("/examples/gradle/configuration/outputDirectory/outer.gradle.kts")
}

kotlin("AnotherBuildConfig.kt") {
resolve("/examples/classes/AnotherBuildConfig.kt")
}

runner {
arguments.add(":apiDump")
}
}

runner.buildAndFail().apply {
Assertions.assertThat(output).contains("apiDumpDirectory (\"../api\") should be inside the project directory")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* Copyright 2016-2024 JetBrains s.r.o.
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
*/

configure<kotlinx.validation.ApiValidationExtension> {
apiDumpDirectory = "custom"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* Copyright 2016-2024 JetBrains s.r.o.
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
*/

configure<kotlinx.validation.ApiValidationExtension> {
apiDumpDirectory = "../api"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* Copyright 2016-2024 JetBrains s.r.o.
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
*/

configure<kotlinx.validation.ApiValidationExtension> {
apiDumpDirectory = "validation/api"
}
7 changes: 7 additions & 0 deletions src/main/kotlin/ApiValidationExtension.kt
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,11 @@ public open class ApiValidationExtension {
* By default, only the `main` source set is checked.
*/
public var additionalSourceSets: MutableSet<String> = HashSet()

/**
* A path to a directory containing an API dump.
* The path should be relative to the project's root directory and should resolve to its subdirectory.
* By default, it's `api`.
*/
public var apiDumpDirectory: String = "api"
}
46 changes: 29 additions & 17 deletions src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ import org.jetbrains.kotlin.gradle.dsl.*
import org.jetbrains.kotlin.gradle.plugin.*
import java.io.*

private const val API_DIR = "api" // Mirrored in functional tests

public class BinaryCompatibilityValidatorPlugin : Plugin<Project> {

override fun apply(target: Project): Unit = with(target) {
Expand Down Expand Up @@ -85,7 +83,7 @@ public class BinaryCompatibilityValidatorPlugin : Plugin<Project> {
kotlin.targets.matching {
it.platformType == KotlinPlatformType.jvm || it.platformType == KotlinPlatformType.androidJvm
}.all { target ->
val targetConfig = TargetConfig(project, target.name, dirConfig)
val targetConfig = TargetConfig(project, extension, target.name, dirConfig)
if (target.platformType == KotlinPlatformType.jvm) {
target.compilations.matching { it.name == "main" }.all {
project.configureKotlinCompilation(it, extension, targetConfig, commonApiDump, commonApiCheck)
Expand Down Expand Up @@ -130,30 +128,43 @@ public class BinaryCompatibilityValidatorPlugin : Plugin<Project> {
project: Project,
extension: ApiValidationExtension
) = configurePlugin("kotlin", project, extension) {
project.configureApiTasks(extension, TargetConfig(project))
project.configureApiTasks(extension, TargetConfig(project, extension))
}
}

private class TargetConfig constructor(
project: Project,
extension: ApiValidationExtension,
val targetName: String? = null,
private val dirConfig: Provider<DirConfig>? = null,
dirConfig: Provider<DirConfig>? = null,
) {
private val apiDirProvider = project.provider {
val dir = extension.apiDumpDirectory

val root = project.layout.projectDirectory.asFile.toPath().toAbsolutePath().normalize()
val resolvedDir = root.resolve(dir).normalize()
if (!resolvedDir.startsWith(root)) {
throw IllegalArgumentException(
"apiDumpDirectory (\"$dir\") should be inside the project directory, " +
"but it resolves to a path outside the project root.\n" +
"Project's root path: $root\nResolved apiDumpDirectory: $resolvedDir"
)
}

private val API_DIR_PROVIDER = project.provider { API_DIR }
dir
}

val apiDir = dirConfig?.map { dirConfig ->
when (dirConfig) {
DirConfig.COMMON -> apiDirProvider.get()
else -> "${apiDirProvider.get()}/$targetName"
}
} ?: apiDirProvider

fun apiTaskName(suffix: String) = when (targetName) {
null, "" -> "api$suffix"
else -> "${targetName}Api$suffix"
else -> "${targetName}Api$suffix"
}

val apiDir
get() = dirConfig?.map { dirConfig ->
when (dirConfig) {
DirConfig.COMMON -> API_DIR
else -> "$API_DIR/$targetName"
}
} ?: API_DIR_PROVIDER
}

private enum class DirConfig {
Expand All @@ -162,6 +173,7 @@ private enum class DirConfig {
* Used in single target projects
*/
COMMON,

/**
* Target-based directory, used in multitarget setups.
* E.g. for the project with targets jvm and android,
Expand All @@ -174,7 +186,7 @@ private enum class DirConfig {
private fun Project.configureKotlinCompilation(
compilation: KotlinCompilation<KotlinCommonOptions>,
extension: ApiValidationExtension,
targetConfig: TargetConfig = TargetConfig(this),
targetConfig: TargetConfig = TargetConfig(this, extension),
commonApiDump: TaskProvider<Task>? = null,
commonApiCheck: TaskProvider<Task>? = null,
useOutput: Boolean = false,
Expand Down Expand Up @@ -221,7 +233,7 @@ private fun apiCheckEnabled(projectName: String, extension: ApiValidationExtensi

private fun Project.configureApiTasks(
extension: ApiValidationExtension,
targetConfig: TargetConfig = TargetConfig(this),
targetConfig: TargetConfig = TargetConfig(this, extension),
) {
val projectName = project.name
val apiBuildDir = targetConfig.apiDir.map { layout.buildDirectory.asFile.get().resolve(it) }
Expand Down