Skip to content

Commit ad1bea6

Browse files
fzhinkinsandwwraithilya-g
authored
Klib support (#183)
Added experimental Klib ABI dump validation support. Validation could be performed using the Gradle plugin which provides almost the same validation and dump workflow as for JVM ABI validation. Also, a public API was added to allow users who don't want or can't use the plugin to implement their own workflows. --------- Co-authored-by: Leonid Startsev <[email protected]> Co-authored-by: ilya-g <[email protected]>
1 parent d464f0b commit ad1bea6

File tree

107 files changed

+6696
-146
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

107 files changed

+6696
-146
lines changed

README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ The tool allows dumping binary API of a JVM part of a Kotlin library that is pub
1313
* [Tasks](#tasks)
1414
* [Optional parameters](#optional-parameters)
1515
* [Workflow](#workflow)
16+
* [Experimental KLib ABI validation support](#experimental-klib-abi-validation-support)
1617
* [What constitutes the public API](#what-constitutes-the-public-api)
1718
* [Classes](#classes)
1819
* [Members](#members)
@@ -175,6 +176,59 @@ When starting to validate your library public API, we recommend the following wo
175176
the resulting diff in `.api` file should be verified: only signatures you expected to change should be changed.
176177
* Commit the resulting `.api` diff along with code changes.
177178

179+
### Experimental KLib ABI validation support
180+
181+
The KLib validation support is experimental and is a subject to change (applies to both an API and the ABI dump format).
182+
A project has to use Kotlin 1.9.20 or newer to use this feature.
183+
184+
To validate public ABI of a Kotlin library (KLib) corresponding option should be enabled explicitly:
185+
```kotlin
186+
apiValidation {
187+
@OptIn(kotlinx.validation.ExperimentalBCVApi::class)
188+
klib {
189+
enabled = true
190+
}
191+
}
192+
```
193+
194+
When enabled, KLib support adds additional dependencies to existing `apiDump` and `apiCheck` tasks.
195+
Generate KLib ABI dumps are places alongside JVM dumps (in `api` subfolder, by default)
196+
in files named `<project name>.klib.api`.
197+
The dump file combines all dumps generated for individual targets with declarations specific to some targets being
198+
annotated with corresponding target names.
199+
During the validation phase, that file is compared to the dump extracted from the latest version of the library,
200+
and any differences between these two files are reported as errors.
201+
202+
Currently, all options described in [Optional parameters](#optional-parameters) section are supported for klibs too.
203+
The only caveat here is that all class names should be specified in the JVM-format,
204+
like `package.name.ClassName$SubclassName`.
205+
206+
Please refer to a [design document](docs/design/KLibSupport.md) for details on the format and rationale behind the
207+
current implementation.
208+
209+
#### KLib ABI dump generation and validation on Linux and Windows hosts
210+
211+
Currently, compilation to Apple-specific targets (like `iosArm64` or `watchosX86`) supported only on Apple hosts.
212+
To ease the development on Windows and Linux hosts, binary compatibility validator does not validate ABI for targets
213+
not supported on the current host, even if `.klib.api` file contains declarations for these targets.
214+
215+
This behavior could be altered to force an error when klibs for some targets could not be compiled:
216+
```kotlin
217+
apiValidation {
218+
@OptIn(kotlinx.validation.ExperimentalBCVApi::class)
219+
klib {
220+
enabled = true
221+
// treat a target being unsupported on a host as an error
222+
strictValidation = true
223+
}
224+
}
225+
```
226+
227+
When it comes to dump generation (`apiDump` task) on non-Apple hosts, binary compatibility validator attempts
228+
to infer an ABI from dumps generated for supported targets and an old dump from project's `api` folder (if any).
229+
Inferred dump may not match an actual dump,
230+
and it is recommended to update a dump on hosts supporting all required targets, if possible.
231+
178232
# What constitutes the public API
179233

180234
### Classes

api/binary-compatibility-validator.api

Lines changed: 120 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ public class kotlinx/validation/ApiValidationExtension {
55
public final fun getIgnoredClasses ()Ljava/util/Set;
66
public final fun getIgnoredPackages ()Ljava/util/Set;
77
public final fun getIgnoredProjects ()Ljava/util/Set;
8+
public final fun getKlib ()Lkotlinx/validation/KlibValidationSettings;
89
public final fun getNonPublicMarkers ()Ljava/util/Set;
910
public final fun getPublicClasses ()Ljava/util/Set;
1011
public final fun getPublicMarkers ()Ljava/util/Set;
1112
public final fun getPublicPackages ()Ljava/util/Set;
1213
public final fun getValidationDisabled ()Z
14+
public final fun klib (Lkotlin/jvm/functions/Function1;)V
1315
public final fun setAdditionalSourceSets (Ljava/util/Set;)V
1416
public final fun setApiDumpDirectory (Ljava/lang/String;)V
1517
public final fun setIgnoredClasses (Ljava/util/Set;)V
@@ -28,32 +30,59 @@ public final class kotlinx/validation/BinaryCompatibilityValidatorPlugin : org/g
2830
public fun apply (Lorg/gradle/api/Project;)V
2931
}
3032

33+
public abstract class kotlinx/validation/BuildTaskBase : org/gradle/api/DefaultTask {
34+
public field outputApiFile Ljava/io/File;
35+
public fun <init> ()V
36+
public final fun getIgnoredClasses ()Ljava/util/Set;
37+
public final fun getIgnoredPackages ()Ljava/util/Set;
38+
public final fun getNonPublicMarkers ()Ljava/util/Set;
39+
public final fun getOutputApiFile ()Ljava/io/File;
40+
public final fun getPublicClasses ()Ljava/util/Set;
41+
public final fun getPublicMarkers ()Ljava/util/Set;
42+
public final fun getPublicPackages ()Ljava/util/Set;
43+
public final fun setIgnoredClasses (Ljava/util/Set;)V
44+
public final fun setIgnoredPackages (Ljava/util/Set;)V
45+
public final fun setNonPublicMarkers (Ljava/util/Set;)V
46+
public final fun setOutputApiFile (Ljava/io/File;)V
47+
public final fun setPublicClasses (Ljava/util/Set;)V
48+
public final fun setPublicMarkers (Ljava/util/Set;)V
49+
public final fun setPublicPackages (Ljava/util/Set;)V
50+
}
51+
52+
public abstract interface annotation class kotlinx/validation/ExperimentalBCVApi : java/lang/annotation/Annotation {
53+
}
54+
3155
public abstract interface annotation class kotlinx/validation/ExternalApi : java/lang/annotation/Annotation {
3256
}
3357

34-
public class kotlinx/validation/KotlinApiBuildTask : org/gradle/api/DefaultTask {
58+
public class kotlinx/validation/KlibValidationSettings {
59+
public fun <init> ()V
60+
public final fun getEnabled ()Z
61+
public final fun getSignatureVersion ()Lkotlinx/validation/api/klib/KlibSignatureVersion;
62+
public final fun getStrictValidation ()Z
63+
public final fun setEnabled (Z)V
64+
public final fun setSignatureVersion (Lkotlinx/validation/api/klib/KlibSignatureVersion;)V
65+
public final fun setStrictValidation (Z)V
66+
}
67+
68+
public class kotlinx/validation/KotlinApiBuildTask : kotlinx/validation/BuildTaskBase {
3569
public field inputDependencies Lorg/gradle/api/file/FileCollection;
36-
public field outputApiDir Ljava/io/File;
3770
public fun <init> ()V
3871
public final fun getInputClassesDirs ()Lorg/gradle/api/file/FileCollection;
3972
public final fun getInputDependencies ()Lorg/gradle/api/file/FileCollection;
4073
public final fun getInputJar ()Lorg/gradle/api/file/RegularFileProperty;
41-
public final fun getOutputApiDir ()Ljava/io/File;
4274
public final fun setInputClassesDirs (Lorg/gradle/api/file/FileCollection;)V
4375
public final fun setInputDependencies (Lorg/gradle/api/file/FileCollection;)V
44-
public final fun setOutputApiDir (Ljava/io/File;)V
4576
}
4677

4778
public class kotlinx/validation/KotlinApiCompareTask : org/gradle/api/DefaultTask {
48-
public field apiBuildDir Ljava/io/File;
79+
public field generatedApiFile Ljava/io/File;
80+
public field projectApiFile Ljava/io/File;
4981
public fun <init> (Lorg/gradle/api/model/ObjectFactory;)V
50-
public final fun getApiBuildDir ()Ljava/io/File;
51-
public final fun getDummyOutputFile ()Ljava/io/File;
52-
public final fun getNonExistingProjectApiDir ()Ljava/lang/String;
53-
public final fun getProjectApiDir ()Ljava/io/File;
54-
public final fun setApiBuildDir (Ljava/io/File;)V
55-
public final fun setNonExistingProjectApiDir (Ljava/lang/String;)V
56-
public final fun setProjectApiDir (Ljava/io/File;)V
82+
public final fun getGeneratedApiFile ()Ljava/io/File;
83+
public final fun getProjectApiFile ()Ljava/io/File;
84+
public final fun setGeneratedApiFile (Ljava/io/File;)V
85+
public final fun setProjectApiFile (Ljava/io/File;)V
5786
}
5887

5988
public final class kotlinx/validation/api/ClassBinarySignature {
@@ -79,3 +108,82 @@ public final class kotlinx/validation/api/KotlinSignaturesLoadingKt {
79108
public static synthetic fun retainExplicitlyIncludedIfDeclared$default (Ljava/util/List;Ljava/util/Collection;Ljava/util/Collection;Ljava/util/Collection;ILjava/lang/Object;)Ljava/util/List;
80109
}
81110

111+
public final class kotlinx/validation/api/klib/KlibDump {
112+
public static final field Companion Lkotlinx/validation/api/klib/KlibDump$Companion;
113+
public fun <init> ()V
114+
public final fun copy ()Lkotlinx/validation/api/klib/KlibDump;
115+
public final fun getTargets ()Ljava/util/Set;
116+
public final fun merge (Ljava/io/File;Ljava/lang/String;)V
117+
public final fun merge (Lkotlinx/validation/api/klib/KlibDump;)V
118+
public static synthetic fun merge$default (Lkotlinx/validation/api/klib/KlibDump;Ljava/io/File;Ljava/lang/String;ILjava/lang/Object;)V
119+
public final fun remove (Ljava/lang/Iterable;)V
120+
public final fun retain (Ljava/lang/Iterable;)V
121+
public final fun saveTo (Ljava/lang/Appendable;)V
122+
}
123+
124+
public final class kotlinx/validation/api/klib/KlibDump$Companion {
125+
public final fun from (Ljava/io/File;Ljava/lang/String;)Lkotlinx/validation/api/klib/KlibDump;
126+
public static synthetic fun from$default (Lkotlinx/validation/api/klib/KlibDump$Companion;Ljava/io/File;Ljava/lang/String;ILjava/lang/Object;)Lkotlinx/validation/api/klib/KlibDump;
127+
public final fun fromKlib (Ljava/io/File;Ljava/lang/String;Lkotlinx/validation/api/klib/KlibDumpFilters;)Lkotlinx/validation/api/klib/KlibDump;
128+
public static synthetic fun fromKlib$default (Lkotlinx/validation/api/klib/KlibDump$Companion;Ljava/io/File;Ljava/lang/String;Lkotlinx/validation/api/klib/KlibDumpFilters;ILjava/lang/Object;)Lkotlinx/validation/api/klib/KlibDump;
129+
}
130+
131+
public final class kotlinx/validation/api/klib/KlibDumpFilters {
132+
public static final field Companion Lkotlinx/validation/api/klib/KlibDumpFilters$Companion;
133+
public final fun getIgnoredClasses ()Ljava/util/Set;
134+
public final fun getIgnoredPackages ()Ljava/util/Set;
135+
public final fun getNonPublicMarkers ()Ljava/util/Set;
136+
public final fun getSignatureVersion ()Lkotlinx/validation/api/klib/KlibSignatureVersion;
137+
}
138+
139+
public final class kotlinx/validation/api/klib/KlibDumpFilters$Builder {
140+
public fun <init> ()V
141+
public final fun build ()Lkotlinx/validation/api/klib/KlibDumpFilters;
142+
public final fun getIgnoredClasses ()Ljava/util/Set;
143+
public final fun getIgnoredPackages ()Ljava/util/Set;
144+
public final fun getNonPublicMarkers ()Ljava/util/Set;
145+
public final fun getSignatureVersion ()Lkotlinx/validation/api/klib/KlibSignatureVersion;
146+
public final fun setSignatureVersion (Lkotlinx/validation/api/klib/KlibSignatureVersion;)V
147+
}
148+
149+
public final class kotlinx/validation/api/klib/KlibDumpFilters$Companion {
150+
public final fun getDEFAULT ()Lkotlinx/validation/api/klib/KlibDumpFilters;
151+
}
152+
153+
public final class kotlinx/validation/api/klib/KlibDumpFiltersKt {
154+
public static final fun KLibDumpFilters (Lkotlin/jvm/functions/Function1;)Lkotlinx/validation/api/klib/KlibDumpFilters;
155+
}
156+
157+
public final class kotlinx/validation/api/klib/KlibDumpKt {
158+
public static final fun inferAbi (Lkotlinx/validation/api/klib/KlibTarget;Ljava/lang/Iterable;Lkotlinx/validation/api/klib/KlibDump;)Lkotlinx/validation/api/klib/KlibDump;
159+
public static synthetic fun inferAbi$default (Lkotlinx/validation/api/klib/KlibTarget;Ljava/lang/Iterable;Lkotlinx/validation/api/klib/KlibDump;ILjava/lang/Object;)Lkotlinx/validation/api/klib/KlibDump;
160+
public static final fun mergeFromKlib (Lkotlinx/validation/api/klib/KlibDump;Ljava/io/File;Ljava/lang/String;Lkotlinx/validation/api/klib/KlibDumpFilters;)V
161+
public static synthetic fun mergeFromKlib$default (Lkotlinx/validation/api/klib/KlibDump;Ljava/io/File;Ljava/lang/String;Lkotlinx/validation/api/klib/KlibDumpFilters;ILjava/lang/Object;)V
162+
public static final fun saveTo (Lkotlinx/validation/api/klib/KlibDump;Ljava/io/File;)V
163+
}
164+
165+
public final class kotlinx/validation/api/klib/KlibSignatureVersion {
166+
public static final field Companion Lkotlinx/validation/api/klib/KlibSignatureVersion$Companion;
167+
public fun equals (Ljava/lang/Object;)Z
168+
public fun hashCode ()I
169+
public fun toString ()Ljava/lang/String;
170+
}
171+
172+
public final class kotlinx/validation/api/klib/KlibSignatureVersion$Companion {
173+
public final fun getLATEST ()Lkotlinx/validation/api/klib/KlibSignatureVersion;
174+
public final fun of (I)Lkotlinx/validation/api/klib/KlibSignatureVersion;
175+
}
176+
177+
public final class kotlinx/validation/api/klib/KlibTarget {
178+
public static final field Companion Lkotlinx/validation/api/klib/KlibTarget$Companion;
179+
public fun equals (Ljava/lang/Object;)Z
180+
public final fun getConfigurableName ()Ljava/lang/String;
181+
public final fun getTargetName ()Ljava/lang/String;
182+
public fun hashCode ()I
183+
public fun toString ()Ljava/lang/String;
184+
}
185+
186+
public final class kotlinx/validation/api/klib/KlibTarget$Companion {
187+
public final fun parse (Ljava/lang/String;)Lkotlinx/validation/api/klib/KlibTarget;
188+
}
189+

build.gradle.kts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ val createClasspathManifest = tasks.register("createClasspathManifest") {
6262
dependencies {
6363
implementation(gradleApi())
6464
implementation(libs.kotlinx.metadata)
65+
compileOnly(libs.kotlin.compiler.embeddable)
6566
implementation(libs.ow2.asm)
6667
implementation(libs.ow2.asmTree)
6768
implementation(libs.javaDiffUtils)
@@ -78,7 +79,6 @@ dependencies {
7879

7980
tasks.compileKotlin {
8081
compilerOptions {
81-
freeCompilerArgs.add("-Xexplicit-api=strict")
8282
allWarningsAsErrors.set(true)
8383
@Suppress("DEPRECATION") // Compatibility with Gradle 7 requires Kotlin 1.4
8484
languageVersion.set(KotlinVersion.KOTLIN_1_4)
@@ -87,7 +87,9 @@ tasks.compileKotlin {
8787
// Suppressing "w: Language version 1.4 is deprecated and its support will be removed" message
8888
// because LV=1.4 in practice is mandatory as it is a default language version in Gradle 7.0+ for users' kts scripts.
8989
freeCompilerArgs.addAll(
90-
"-Xsuppress-version-warnings"
90+
"-Xexplicit-api=strict",
91+
"-Xsuppress-version-warnings",
92+
"-Xopt-in=kotlin.RequiresOptIn"
9193
)
9294
}
9395
}
@@ -162,6 +164,7 @@ testing {
162164
implementation(project())
163165
implementation(libs.assertJ.core)
164166
implementation(libs.kotlin.test)
167+
implementation(libs.kotlin.compiler.embeddable)
165168
}
166169
}
167170

0 commit comments

Comments
 (0)