Skip to content

Commit 27f2f01

Browse files
fzhinkinqwwdfsad
authored andcommitted
[ABI Validation] Configure output directory for dumps
* Support output directory configuration Fixes Kotlin/binary-compatibility-validator#127 --------- Co-authored-by: Vsevolod Tolstopyatov <[email protected]> Pull request Kotlin/binary-compatibility-validator#170
1 parent b7e287b commit 27f2f01

File tree

9 files changed

+234
-18
lines changed

9 files changed

+234
-18
lines changed

libraries/tools/abi-validation/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@ apiValidation {
8989
* Flag to programmatically disable compatibility validator
9090
*/
9191
validationDisabled = true
92+
93+
/**
94+
* A path to a subdirectory inside the project root directory where dumps should be stored.
95+
*/
96+
apiDumpDirectory = "api"
9297
}
9398
```
9499

@@ -123,6 +128,11 @@ apiValidation {
123128
* Flag to programmatically disable compatibility validator
124129
*/
125130
validationDisabled = false
131+
132+
/**
133+
* A path to a subdirectory inside the project root directory where dumps should be stored.
134+
*/
135+
apiDumpDirectory = "aux/validation"
126136
}
127137
```
128138

libraries/tools/abi-validation/api/binary-compatibility-validator.api

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
public class kotlinx/validation/ApiValidationExtension {
22
public fun <init> ()V
33
public final fun getAdditionalSourceSets ()Ljava/util/Set;
4+
public final fun getApiDumpDirectory ()Ljava/lang/String;
45
public final fun getIgnoredClasses ()Ljava/util/Set;
56
public final fun getIgnoredPackages ()Ljava/util/Set;
67
public final fun getIgnoredProjects ()Ljava/util/Set;
@@ -10,6 +11,7 @@ public class kotlinx/validation/ApiValidationExtension {
1011
public final fun getPublicPackages ()Ljava/util/Set;
1112
public final fun getValidationDisabled ()Z
1213
public final fun setAdditionalSourceSets (Ljava/util/Set;)V
14+
public final fun setApiDumpDirectory (Ljava/lang/String;)V
1315
public final fun setIgnoredClasses (Ljava/util/Set;)V
1416
public final fun setIgnoredPackages (Ljava/util/Set;)V
1517
public final fun setIgnoredProjects (Ljava/util/Set;)V

libraries/tools/abi-validation/src/functionalTest/kotlin/kotlinx/validation/api/TestDsl.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55

66
package kotlinx.validation.api
77

8+
import kotlinx.validation.ApiValidationExtension
89
import java.io.*
910
import org.gradle.testkit.runner.GradleRunner
1011
import org.intellij.lang.annotations.Language
1112

12-
public const val API_DIR: String = "api"
13+
public val API_DIR: String = ApiValidationExtension().apiDumpDirectory
1314

1415
internal fun BaseKotlinGradleTest.test(fn: BaseKotlinScope.() -> Unit): GradleRunner {
1516
val baseKotlinScope = BaseKotlinScope()
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
/*
2+
* Copyright 2016-2024 JetBrains s.r.o.
3+
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
4+
*/
5+
6+
package kotlinx.validation.test
7+
8+
import kotlinx.validation.api.*
9+
import kotlinx.validation.api.buildGradleKts
10+
import kotlinx.validation.api.resolve
11+
import kotlinx.validation.api.test
12+
import org.assertj.core.api.Assertions
13+
import org.junit.Test
14+
import kotlin.test.assertTrue
15+
16+
class OutputDirectoryTests : BaseKotlinGradleTest() {
17+
@Test
18+
fun dumpIntoCustomDirectory() {
19+
val runner = test {
20+
buildGradleKts {
21+
resolve("/examples/gradle/base/withPlugin.gradle.kts")
22+
resolve("/examples/gradle/configuration/outputDirectory/different.gradle.kts")
23+
}
24+
25+
kotlin("AnotherBuildConfig.kt") {
26+
resolve("/examples/classes/AnotherBuildConfig.kt")
27+
}
28+
dir("api") {
29+
file("letMeBe.txt") {
30+
}
31+
}
32+
33+
runner {
34+
arguments.add(":apiDump")
35+
}
36+
}
37+
38+
runner.build().apply {
39+
assertTaskSuccess(":apiDump")
40+
41+
val dumpFile = rootProjectDir.resolve("custom").resolve("${rootProjectDir.name}.api")
42+
assertTrue(dumpFile.exists(), "api dump file ${dumpFile.path} should exist")
43+
44+
val expected = readFileList("/examples/classes/AnotherBuildConfig.dump")
45+
Assertions.assertThat(dumpFile.readText()).isEqualToIgnoringNewLines(expected)
46+
47+
val fileInsideDir = rootProjectDir.resolve("api").resolve("letMeBe.txt")
48+
assertTrue(fileInsideDir.exists(), "existing api directory should not be overridden")
49+
}
50+
}
51+
52+
@Test
53+
fun validateDumpFromACustomDirectory() {
54+
val runner = test {
55+
buildGradleKts {
56+
resolve("/examples/gradle/base/withPlugin.gradle.kts")
57+
resolve("/examples/gradle/configuration/outputDirectory/different.gradle.kts")
58+
}
59+
60+
kotlin("AnotherBuildConfig.kt") {
61+
resolve("/examples/classes/AnotherBuildConfig.kt")
62+
}
63+
dir("custom") {
64+
file("${rootProjectDir.name}.api") {
65+
resolve("/examples/classes/AnotherBuildConfig.dump")
66+
}
67+
}
68+
69+
runner {
70+
arguments.add(":apiCheck")
71+
}
72+
}
73+
74+
runner.build().apply {
75+
assertTaskSuccess(":apiCheck")
76+
}
77+
}
78+
79+
@Test
80+
fun dumpIntoSubdirectory() {
81+
val runner = test {
82+
buildGradleKts {
83+
resolve("/examples/gradle/base/withPlugin.gradle.kts")
84+
resolve("/examples/gradle/configuration/outputDirectory/subdirectory.gradle.kts")
85+
}
86+
87+
kotlin("AnotherBuildConfig.kt") {
88+
resolve("/examples/classes/AnotherBuildConfig.kt")
89+
}
90+
91+
runner {
92+
arguments.add(":apiDump")
93+
}
94+
}
95+
96+
runner.build().apply {
97+
assertTaskSuccess(":apiDump")
98+
99+
val dumpFile = rootProjectDir.resolve("validation")
100+
.resolve("api")
101+
.resolve("${rootProjectDir.name}.api")
102+
103+
assertTrue(dumpFile.exists(), "api dump file ${dumpFile.path} should exist")
104+
105+
val expected = readFileList("/examples/classes/AnotherBuildConfig.dump")
106+
Assertions.assertThat(dumpFile.readText()).isEqualToIgnoringNewLines(expected)
107+
}
108+
}
109+
110+
@Test
111+
fun validateDumpFromASubdirectory() {
112+
val runner = test {
113+
buildGradleKts {
114+
resolve("/examples/gradle/base/withPlugin.gradle.kts")
115+
resolve("/examples/gradle/configuration/outputDirectory/subdirectory.gradle.kts")
116+
}
117+
118+
kotlin("AnotherBuildConfig.kt") {
119+
resolve("/examples/classes/AnotherBuildConfig.kt")
120+
}
121+
dir("validation") {
122+
dir("api") {
123+
file("${rootProjectDir.name}.api") {
124+
resolve("/examples/classes/AnotherBuildConfig.dump")
125+
}
126+
}
127+
}
128+
129+
runner {
130+
arguments.add(":apiCheck")
131+
}
132+
}
133+
134+
runner.build().apply {
135+
assertTaskSuccess(":apiCheck")
136+
}
137+
}
138+
139+
@Test
140+
fun dumpIntoParentDirectory() {
141+
val runner = test {
142+
buildGradleKts {
143+
resolve("/examples/gradle/base/withPlugin.gradle.kts")
144+
resolve("/examples/gradle/configuration/outputDirectory/outer.gradle.kts")
145+
}
146+
147+
kotlin("AnotherBuildConfig.kt") {
148+
resolve("/examples/classes/AnotherBuildConfig.kt")
149+
}
150+
151+
runner {
152+
arguments.add(":apiDump")
153+
}
154+
}
155+
156+
runner.buildAndFail().apply {
157+
Assertions.assertThat(output).contains("apiDumpDirectory (\"../api\") should be inside the project directory")
158+
}
159+
}
160+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*
2+
* Copyright 2016-2024 JetBrains s.r.o.
3+
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
4+
*/
5+
6+
configure<kotlinx.validation.ApiValidationExtension> {
7+
apiDumpDirectory = "custom"
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*
2+
* Copyright 2016-2024 JetBrains s.r.o.
3+
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
4+
*/
5+
6+
configure<kotlinx.validation.ApiValidationExtension> {
7+
apiDumpDirectory = "../api"
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*
2+
* Copyright 2016-2024 JetBrains s.r.o.
3+
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
4+
*/
5+
6+
configure<kotlinx.validation.ApiValidationExtension> {
7+
apiDumpDirectory = "validation/api"
8+
}

libraries/tools/abi-validation/src/main/kotlin/ApiValidationExtension.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,11 @@ public open class ApiValidationExtension {
6464
* By default, only the `main` source set is checked.
6565
*/
6666
public var additionalSourceSets: MutableSet<String> = HashSet()
67+
68+
/**
69+
* A path to a directory containing an API dump.
70+
* The path should be relative to the project's root directory and should resolve to its subdirectory.
71+
* By default, it's `api`.
72+
*/
73+
public var apiDumpDirectory: String = "api"
6774
}

libraries/tools/abi-validation/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ import org.jetbrains.kotlin.gradle.dsl.*
1313
import org.jetbrains.kotlin.gradle.plugin.*
1414
import java.io.*
1515

16-
private const val API_DIR = "api" // Mirrored in functional tests
17-
1816
public class BinaryCompatibilityValidatorPlugin : Plugin<Project> {
1917

2018
override fun apply(target: Project): Unit = with(target) {
@@ -85,7 +83,7 @@ public class BinaryCompatibilityValidatorPlugin : Plugin<Project> {
8583
kotlin.targets.matching {
8684
it.platformType == KotlinPlatformType.jvm || it.platformType == KotlinPlatformType.androidJvm
8785
}.all { target ->
88-
val targetConfig = TargetConfig(project, target.name, dirConfig)
86+
val targetConfig = TargetConfig(project, extension, target.name, dirConfig)
8987
if (target.platformType == KotlinPlatformType.jvm) {
9088
target.compilations.matching { it.name == "main" }.all {
9189
project.configureKotlinCompilation(it, extension, targetConfig, commonApiDump, commonApiCheck)
@@ -130,30 +128,43 @@ public class BinaryCompatibilityValidatorPlugin : Plugin<Project> {
130128
project: Project,
131129
extension: ApiValidationExtension
132130
) = configurePlugin("kotlin", project, extension) {
133-
project.configureApiTasks(extension, TargetConfig(project))
131+
project.configureApiTasks(extension, TargetConfig(project, extension))
134132
}
135133
}
136134

137135
private class TargetConfig constructor(
138136
project: Project,
137+
extension: ApiValidationExtension,
139138
val targetName: String? = null,
140-
private val dirConfig: Provider<DirConfig>? = null,
139+
dirConfig: Provider<DirConfig>? = null,
141140
) {
141+
private val apiDirProvider = project.provider {
142+
val dir = extension.apiDumpDirectory
143+
144+
val root = project.layout.projectDirectory.asFile.toPath().toAbsolutePath().normalize()
145+
val resolvedDir = root.resolve(dir).normalize()
146+
if (!resolvedDir.startsWith(root)) {
147+
throw IllegalArgumentException(
148+
"apiDumpDirectory (\"$dir\") should be inside the project directory, " +
149+
"but it resolves to a path outside the project root.\n" +
150+
"Project's root path: $root\nResolved apiDumpDirectory: $resolvedDir"
151+
)
152+
}
142153

143-
private val API_DIR_PROVIDER = project.provider { API_DIR }
154+
dir
155+
}
156+
157+
val apiDir = dirConfig?.map { dirConfig ->
158+
when (dirConfig) {
159+
DirConfig.COMMON -> apiDirProvider.get()
160+
else -> "${apiDirProvider.get()}/$targetName"
161+
}
162+
} ?: apiDirProvider
144163

145164
fun apiTaskName(suffix: String) = when (targetName) {
146165
null, "" -> "api$suffix"
147-
else -> "${targetName}Api$suffix"
166+
else -> "${targetName}Api$suffix"
148167
}
149-
150-
val apiDir
151-
get() = dirConfig?.map { dirConfig ->
152-
when (dirConfig) {
153-
DirConfig.COMMON -> API_DIR
154-
else -> "$API_DIR/$targetName"
155-
}
156-
} ?: API_DIR_PROVIDER
157168
}
158169

159170
private enum class DirConfig {
@@ -162,6 +173,7 @@ private enum class DirConfig {
162173
* Used in single target projects
163174
*/
164175
COMMON,
176+
165177
/**
166178
* Target-based directory, used in multitarget setups.
167179
* E.g. for the project with targets jvm and android,
@@ -174,7 +186,7 @@ private enum class DirConfig {
174186
private fun Project.configureKotlinCompilation(
175187
compilation: KotlinCompilation<KotlinCommonOptions>,
176188
extension: ApiValidationExtension,
177-
targetConfig: TargetConfig = TargetConfig(this),
189+
targetConfig: TargetConfig = TargetConfig(this, extension),
178190
commonApiDump: TaskProvider<Task>? = null,
179191
commonApiCheck: TaskProvider<Task>? = null,
180192
useOutput: Boolean = false,
@@ -221,7 +233,7 @@ private fun apiCheckEnabled(projectName: String, extension: ApiValidationExtensi
221233

222234
private fun Project.configureApiTasks(
223235
extension: ApiValidationExtension,
224-
targetConfig: TargetConfig = TargetConfig(this),
236+
targetConfig: TargetConfig = TargetConfig(this, extension),
225237
) {
226238
val projectName = project.name
227239
val apiBuildDir = targetConfig.apiDir.map { layout.buildDirectory.asFile.get().resolve(it) }

0 commit comments

Comments
 (0)