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

Klib support #183

Merged
merged 54 commits into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
3c57419
Introduce target hierarchy mirroring default template from KMP
fzhinkin Jan 31, 2024
4e50f6d
Implement KLib dump merger
fzhinkin Jan 31, 2024
ba998f0
Added Klib-related settings
fzhinkin Jan 31, 2024
51f7756
Added a more precise Copy task
fzhinkin Jan 31, 2024
c824926
Implemented Klib validation
fzhinkin Jan 31, 2024
cb87220
Rebased to current dev
fzhinkin Jan 31, 2024
91903af
Added a design doc
fzhinkin Feb 1, 2024
5a6cf13
Cleanup
fzhinkin Feb 1, 2024
d58121c
Mark an experimental API with a dedicated annotation
fzhinkin Feb 1, 2024
34313b1
Cleanup
fzhinkin Feb 1, 2024
4bc9d2a
Add some kdoc
fzhinkin Feb 1, 2024
a987909
Improved test coverage
fzhinkin Feb 5, 2024
5ba0dbe
Described a klib validation pipeline, fixed task names, improved erro…
fzhinkin Feb 5, 2024
9d9f1e7
Fixed klib merge task's dependencies
fzhinkin Feb 9, 2024
cf65ddf
Added shortcuts to reduce the amount of boilerplate code in the klib …
fzhinkin Feb 9, 2024
3f93275
Fixed a test
fzhinkin Feb 19, 2024
6b17087
Store children decls as a map, get rid of a separate cache
fzhinkin Feb 21, 2024
f5fdacf
Fixed typos
fzhinkin Feb 22, 2024
b274e64
Opt-in experimental annotations instead of propagating it further
fzhinkin Feb 22, 2024
369d791
Made signature version optional, improved docs, fixed typos
fzhinkin Feb 28, 2024
9006c1a
Simplify rules for target group aliases inclusion
fzhinkin Feb 28, 2024
00d3821
Merge branch 'develop' into klib-support
fzhinkin Feb 28, 2024
05c424d
Support compound target names
fzhinkin Feb 29, 2024
617189f
Fix unsupported targets filtration
fzhinkin Feb 29, 2024
5c0c764
Partially reconstruct a manifest when extracting a single target from…
fzhinkin Mar 1, 2024
4ec6a11
Add a compound target name proposal to the design doc
fzhinkin Mar 1, 2024
87c35f4
Update the doc
fzhinkin Mar 1, 2024
e7bf172
Make klib-related Gradle tasks internal
fzhinkin Mar 1, 2024
e6171f9
Use canonical target names when filtering out targets to validate
fzhinkin Mar 1, 2024
8621b3e
Use fully qualified names of supported targets
fzhinkin Mar 1, 2024
672901b
Made KlibTarget public, renamed its components and reordered them in …
fzhinkin Mar 6, 2024
a4768c6
Use KlibSignatureVersion instead of int to represent a signature version
fzhinkin Mar 6, 2024
f3cf046
Remove an option disabling target name grouping
fzhinkin Mar 6, 2024
75363a9
Fixed java class name to AbiQualifiedName transformation
fzhinkin Mar 7, 2024
a69f204
Get rid of dump setting
fzhinkin Mar 7, 2024
a21f4a9
Left a single method to load all types of dumps, supported multi-targ…
fzhinkin Mar 7, 2024
f51611d
Renaming and doc update
fzhinkin Mar 7, 2024
9aa5be8
Added public API prototype and switched Gradle tasks to it, fixed the…
fzhinkin Mar 7, 2024
28cc85a
Improved kdoc and added samples
fzhinkin Mar 7, 2024
68ce7af
Update README
fzhinkin Mar 7, 2024
4f4512e
Update docs
fzhinkin Mar 7, 2024
ba9fcf1
Update API dump and target aliasing rule
fzhinkin Mar 7, 2024
5e01cf4
Improved test coverage, clarified some corner cases
fzhinkin Mar 11, 2024
ba29177
Fixed typo in a filename
fzhinkin Mar 11, 2024
dd776f2
Update the design doc
fzhinkin Mar 11, 2024
489eefa
Addressed review comments and suggestions
fzhinkin Mar 12, 2024
a68f661
Use a length of path from a target hierarchy root to a node as a meas…
fzhinkin Mar 12, 2024
20b7fd9
Consolidated all klib dump related classes in a single package
fzhinkin Mar 12, 2024
4b19820
Use root locale instead of a default one
fzhinkin Mar 12, 2024
077c7ec
Rephrased kdoc
fzhinkin Mar 12, 2024
c1c9921
Reworked target group aliasing
fzhinkin Mar 12, 2024
081fdb0
Decapitalized L in KLib
fzhinkin Mar 12, 2024
4f63bae
Update api dump
fzhinkin Mar 12, 2024
259ad7c
Improved kdoc, cleaned up the code
fzhinkin Mar 18, 2024
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
54 changes: 54 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ The tool allows dumping binary API of a JVM part of a Kotlin library that is pub
* [Tasks](#tasks)
* [Optional parameters](#optional-parameters)
* [Workflow](#workflow)
* [Experimental KLib ABI validation support](#experimental-klib-abi-validation-support)
* [What constitutes the public API](#what-constitutes-the-public-api)
* [Classes](#classes)
* [Members](#members)
Expand Down Expand Up @@ -175,6 +176,59 @@ When starting to validate your library public API, we recommend the following wo
the resulting diff in `.api` file should be verified: only signatures you expected to change should be changed.
* Commit the resulting `.api` diff along with code changes.

### Experimental KLib ABI validation support

The KLib validation support is experimental and is a subject to change (applies to both an API and the ABI dump format).
A project has to use Kotlin 1.9.20 or newer to use this feature.

To validate public ABI of a Kotlin library (KLib) corresponding option should be enabled explicitly:
```kotlin
apiValidation {
@OptIn(kotlinx.validation.ExperimentalBCVApi::class)
klib {
enabled = true
}
}
```

When enabled, KLib support adds additional dependencies to existing `apiDump` and `apiCheck` tasks.
Generate KLib ABI dumps are places alongside JVM dumps (in `api` subfolder, by default)
in files named `<project name>.klib.api`.
The dump file combines all dumps generated for individual targets with declarations specific to some targets being
annotated with corresponding target names.
During the validation phase, that file is compared to the dump extracted from the latest version of the library,
and any differences between these two files are reported as errors.

Currently, all options described in [Optional parameters](#optional-parameters) section are supported for klibs too.
The only caveat here is that all class names should be specified in the JVM-format,
like `package.name.ClassName$SubclassName`.

Please refer to a [design document](docs/design/KLibSupport.md) for details on the format and rationale behind the
current implementation.

#### KLib ABI dump generation and validation on Linux and Windows hosts

Currently, compilation to Apple-specific targets (like `iosArm64` or `watchosX86`) supported only on Apple hosts.
To ease the development on Windows and Linux hosts, binary compatibility validator does not validate ABI for targets
not supported on the current host, even if `.klib.api` file contains declarations for these targets.

This behavior could be altered to force an error when klibs for some targets could not be compiled:
```kotlin
apiValidation {
@OptIn(kotlinx.validation.ExperimentalBCVApi::class)
klib {
enabled = true
// treat a target being unsupported on a host as an error
strictValidation = true
}
}
```

When it comes to dump generation (`apiDump` task) on non-Apple hosts, binary compatibility validator attempts
to infer an ABI from dumps generated for supported targets and an old dump from project's `api` folder (if any).
Inferred dump may not match an actual dump,
and it is recommended to update a dump on hosts supporting all required targets, if possible.

# What constitutes the public API

### Classes
Expand Down
131 changes: 119 additions & 12 deletions api/binary-compatibility-validator.api
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ public class kotlinx/validation/ApiValidationExtension {
public final fun getIgnoredClasses ()Ljava/util/Set;
public final fun getIgnoredPackages ()Ljava/util/Set;
public final fun getIgnoredProjects ()Ljava/util/Set;
public final fun getKlib ()Lkotlinx/validation/KlibValidationSettings;
public final fun getNonPublicMarkers ()Ljava/util/Set;
public final fun getPublicClasses ()Ljava/util/Set;
public final fun getPublicMarkers ()Ljava/util/Set;
public final fun getPublicPackages ()Ljava/util/Set;
public final fun getValidationDisabled ()Z
public final fun klib (Lkotlin/jvm/functions/Function1;)V
public final fun setAdditionalSourceSets (Ljava/util/Set;)V
public final fun setApiDumpDirectory (Ljava/lang/String;)V
public final fun setIgnoredClasses (Ljava/util/Set;)V
Expand All @@ -28,32 +30,59 @@ public final class kotlinx/validation/BinaryCompatibilityValidatorPlugin : org/g
public fun apply (Lorg/gradle/api/Project;)V
}

public abstract class kotlinx/validation/BuildTaskBase : org/gradle/api/DefaultTask {
public field outputApiFile Ljava/io/File;
public fun <init> ()V
public final fun getIgnoredClasses ()Ljava/util/Set;
public final fun getIgnoredPackages ()Ljava/util/Set;
public final fun getNonPublicMarkers ()Ljava/util/Set;
public final fun getOutputApiFile ()Ljava/io/File;
public final fun getPublicClasses ()Ljava/util/Set;
public final fun getPublicMarkers ()Ljava/util/Set;
public final fun getPublicPackages ()Ljava/util/Set;
public final fun setIgnoredClasses (Ljava/util/Set;)V
public final fun setIgnoredPackages (Ljava/util/Set;)V
public final fun setNonPublicMarkers (Ljava/util/Set;)V
public final fun setOutputApiFile (Ljava/io/File;)V
public final fun setPublicClasses (Ljava/util/Set;)V
public final fun setPublicMarkers (Ljava/util/Set;)V
public final fun setPublicPackages (Ljava/util/Set;)V
}

public abstract interface annotation class kotlinx/validation/ExperimentalBCVApi : java/lang/annotation/Annotation {
}

public abstract interface annotation class kotlinx/validation/ExternalApi : java/lang/annotation/Annotation {
}

public class kotlinx/validation/KotlinApiBuildTask : org/gradle/api/DefaultTask {
public class kotlinx/validation/KlibValidationSettings {
public fun <init> ()V
public final fun getEnabled ()Z
public final fun getSignatureVersion ()Lkotlinx/validation/api/klib/KlibSignatureVersion;
public final fun getStrictValidation ()Z
public final fun setEnabled (Z)V
public final fun setSignatureVersion (Lkotlinx/validation/api/klib/KlibSignatureVersion;)V
public final fun setStrictValidation (Z)V
}

public class kotlinx/validation/KotlinApiBuildTask : kotlinx/validation/BuildTaskBase {
public field inputDependencies Lorg/gradle/api/file/FileCollection;
public field outputApiDir Ljava/io/File;
public fun <init> ()V
public final fun getInputClassesDirs ()Lorg/gradle/api/file/FileCollection;
public final fun getInputDependencies ()Lorg/gradle/api/file/FileCollection;
public final fun getInputJar ()Lorg/gradle/api/file/RegularFileProperty;
public final fun getOutputApiDir ()Ljava/io/File;
public final fun setInputClassesDirs (Lorg/gradle/api/file/FileCollection;)V
public final fun setInputDependencies (Lorg/gradle/api/file/FileCollection;)V
public final fun setOutputApiDir (Ljava/io/File;)V
}

public class kotlinx/validation/KotlinApiCompareTask : org/gradle/api/DefaultTask {
public field apiBuildDir Ljava/io/File;
public field generatedApiFile Ljava/io/File;
public field projectApiFile Ljava/io/File;
public fun <init> (Lorg/gradle/api/model/ObjectFactory;)V
public final fun getApiBuildDir ()Ljava/io/File;
public final fun getDummyOutputFile ()Ljava/io/File;
public final fun getNonExistingProjectApiDir ()Ljava/lang/String;
public final fun getProjectApiDir ()Ljava/io/File;
public final fun setApiBuildDir (Ljava/io/File;)V
public final fun setNonExistingProjectApiDir (Ljava/lang/String;)V
public final fun setProjectApiDir (Ljava/io/File;)V
public final fun getGeneratedApiFile ()Ljava/io/File;
public final fun getProjectApiFile ()Ljava/io/File;
public final fun setGeneratedApiFile (Ljava/io/File;)V
public final fun setProjectApiFile (Ljava/io/File;)V
}

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

public final class kotlinx/validation/api/klib/KLibDumpFilters {
public static final field Companion Lkotlinx/validation/api/klib/KLibDumpFilters$Companion;
public final fun getIgnoredClasses ()Ljava/util/Set;
public final fun getIgnoredPackages ()Ljava/util/Set;
public final fun getNonPublicMarkers ()Ljava/util/Set;
public final fun getSignatureVersion ()Lkotlinx/validation/api/klib/KlibSignatureVersion;
}

public final class kotlinx/validation/api/klib/KLibDumpFilters$Builder {
public fun <init> ()V
public final fun build ()Lkotlinx/validation/api/klib/KLibDumpFilters;
public final fun getIgnoredClasses ()Ljava/util/Set;
public final fun getIgnoredPackages ()Ljava/util/Set;
public final fun getNonPublicMarkers ()Ljava/util/Set;
public final fun getSignatureVersion ()Lkotlinx/validation/api/klib/KlibSignatureVersion;
public final fun setSignatureVersion (Lkotlinx/validation/api/klib/KlibSignatureVersion;)V
}

public final class kotlinx/validation/api/klib/KLibDumpFilters$Companion {
public final fun getDEFAULT ()Lkotlinx/validation/api/klib/KLibDumpFilters;
}

public final class kotlinx/validation/api/klib/KlibDump {
public static final field Companion Lkotlinx/validation/api/klib/KlibDump$Companion;
public fun <init> ()V
public final fun copy ()Lkotlinx/validation/api/klib/KlibDump;
public final fun getTargets ()Ljava/util/Set;
public final fun merge (Ljava/io/File;Ljava/lang/String;)V
public final fun merge (Lkotlinx/validation/api/klib/KlibDump;)V
public static synthetic fun merge$default (Lkotlinx/validation/api/klib/KlibDump;Ljava/io/File;Ljava/lang/String;ILjava/lang/Object;)V
public final fun remove (Ljava/lang/Iterable;)V
public final fun retain (Ljava/lang/Iterable;)V
public final fun saveTo (Ljava/lang/Appendable;)V
}

public final class kotlinx/validation/api/klib/KlibDump$Companion {
public final fun from (Ljava/io/File;Ljava/lang/String;)Lkotlinx/validation/api/klib/KlibDump;
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;
public final fun fromKlib (Ljava/io/File;Ljava/lang/String;Lkotlinx/validation/api/klib/KLibDumpFilters;)Lkotlinx/validation/api/klib/KlibDump;
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;
}

public final class kotlinx/validation/api/klib/KlibDumpFiltersKt {
public static final fun KLibDumpFilters (Lkotlin/jvm/functions/Function1;)Lkotlinx/validation/api/klib/KLibDumpFilters;
}

public final class kotlinx/validation/api/klib/KlibDumpKt {
public static final fun inferAbi (Lkotlinx/validation/api/klib/KlibTarget;Ljava/lang/Iterable;Lkotlinx/validation/api/klib/KlibDump;)Lkotlinx/validation/api/klib/KlibDump;
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;
public static final fun mergeFromKlib (Lkotlinx/validation/api/klib/KlibDump;Ljava/io/File;Ljava/lang/String;Lkotlinx/validation/api/klib/KLibDumpFilters;)V
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
}

public final class kotlinx/validation/api/klib/KlibSignatureVersion {
public static final field Companion Lkotlinx/validation/api/klib/KlibSignatureVersion$Companion;
public fun equals (Ljava/lang/Object;)Z
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class kotlinx/validation/api/klib/KlibSignatureVersion$Companion {
public final fun getLATEST ()Lkotlinx/validation/api/klib/KlibSignatureVersion;
public final fun of (I)Lkotlinx/validation/api/klib/KlibSignatureVersion;
}

public final class kotlinx/validation/api/klib/KlibTarget {
public static final field Companion Lkotlinx/validation/api/klib/KlibTarget$Companion;
public fun equals (Ljava/lang/Object;)Z
public final fun getConfigurableName ()Ljava/lang/String;
public final fun getTargetName ()Ljava/lang/String;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class kotlinx/validation/api/klib/KlibTarget$Companion {
public final fun parse (Ljava/lang/String;)Lkotlinx/validation/api/klib/KlibTarget;
}

7 changes: 5 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ val createClasspathManifest = tasks.register("createClasspathManifest") {
dependencies {
implementation(gradleApi())
implementation(libs.kotlinx.metadata)
compileOnly(libs.kotlin.compiler.embeddable)
implementation(libs.ow2.asm)
implementation(libs.ow2.asmTree)
implementation(libs.javaDiffUtils)
Expand All @@ -78,7 +79,6 @@ dependencies {

tasks.compileKotlin {
compilerOptions {
freeCompilerArgs.add("-Xexplicit-api=strict")
allWarningsAsErrors.set(true)
@Suppress("DEPRECATION") // Compatibility with Gradle 7 requires Kotlin 1.4
languageVersion.set(KotlinVersion.KOTLIN_1_4)
Expand All @@ -87,7 +87,9 @@ tasks.compileKotlin {
// Suppressing "w: Language version 1.4 is deprecated and its support will be removed" message
// because LV=1.4 in practice is mandatory as it is a default language version in Gradle 7.0+ for users' kts scripts.
freeCompilerArgs.addAll(
"-Xsuppress-version-warnings"
"-Xexplicit-api=strict",
"-Xsuppress-version-warnings",
"-Xopt-in=kotlin.RequiresOptIn"
)
}
}
Expand Down Expand Up @@ -162,6 +164,7 @@ testing {
implementation(project())
implementation(libs.assertJ.core)
implementation(libs.kotlin.test)
implementation(libs.kotlin.compiler.embeddable)
}
}

Expand Down
Loading