The tool allows to dump binary API of a Kotlin library

Overview

Kotlin Alpha JetBrains official project Apache license

Binary compatibility validator

The tool allows to dump binary API of a Kotlin library that is public in sense of Kotlin visibilities and ensures that the public binary API wasn't changed in a way that make this change binary incompatible.

Contents

Setup

Binary compatibility validator is a Gradle plugin that can be added to your build in the following way:

  • in build.gradle.kts
plugins {
    id("org.jetbrains.kotlinx.binary-compatibility-validator") version "0.10.0"
}
  • in build.gradle
plugins {
    id 'org.jetbrains.kotlinx.binary-compatibility-validator' version '0.10.0'
}

It is enough to apply the plugin only to the root project build file; all sub-projects will be configured automatically.

Tasks

The plugin provides two tasks:

  • apiDump — builds the project and dumps its public API in project api subfolder. API is dumped in a human-readable format. If API dump already exists, it will be overwritten.
  • apiCheck — builds the project and checks that project's public API is the same as golden value in project api subfolder. This task is automatically inserted into check pipeline, so both build and check tasks will start checking public API upon their execution.

For projects with multiple JVM targets, multiple subfolders will be created, e.g. api/jvm and api/android

Optional parameters

Binary compatibility validator can be additionally configured with the following DSL:

Groovy

apiValidation {
    /**
     * Packages that are excluded from public API dumps even if they
     * contain public API. 
     */
    ignoredPackages += ["kotlinx.coroutines.internal"]

    /**
     * Sub-projects that are excluded from API validation 
     */
    ignoredProjects += ["benchmarks", "examples"]

    /**
     * Classes (fully qualified) that are excluded from public API dumps even if they
     * contain public API.
     */
    ignoredClasses += ["com.company.BuildConfig"]

    /**
     * Set of annotations that exclude API from being public.
     * Typically, it is all kinds of `@InternalApi` annotations that mark 
     * effectively private API that cannot be actually private for technical reasons.
     */
    nonPublicMarkers += ["my.package.MyInternalApiAnnotation"]

    /**
     * Flag to programmatically disable compatibility validator
     */
    validationDisabled = true
}

Kotlin

apiValidation {
    /**
     * Packages that are excluded from public API dumps even if they
     * contain public API.
     */
    ignoredPackages.add("kotlinx.coroutines.internal")

    /**
     * Sub-projects that are excluded from API validation
     */
    ignoredProjects.addAll(listOf("benchmarks", "examples"))

    /**
     * Classes (fully qualified) that are excluded from public API dumps even if they
     * contain public API.
     */
    ignoredClasses.add("com.company.BuildConfig")
    
    /**
     * Set of annotations that exclude API from being public.
     * Typically, it is all kinds of `@InternalApi` annotations that mark
     * effectively private API that cannot be actually private for technical reasons.
     */
    nonPublicMarkers.add("my.package.MyInternalApiAnnotation")

    /**
     * Flag to programmatically disable compatibility validator
     */
    validationDisabled = false
}

Workflow

When starting to validate your library public API, we recommend the following workflow:

  • Preparation phase (one-time action):

    • As the first step, apply the plugin, configure it and execute apiDump.
    • Validate your public API manually.
    • Commit .api files to your VCS.
    • At this moment, default check task will validate public API along with test run and will fail the build if API differs.
  • Regular workflow

    • When doing code changes that do not imply any changes in public API, no additional actions should be performed. check task on your CI will validate everything.
    • When doing code changes that imply changes in public API, whether it is a new API or adjustments in existing one, check task will start to fail. apiDump should be executed manually, 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.

What constitutes the public API

Classes

A class is considered to be effectively public if all the following conditions are met:

  • it has public or protected JVM access (ACC_PUBLIC or ACC_PROTECTED)
  • it has one of the following visibilities in Kotlin:
    • no visibility (means no Kotlin declaration corresponds to this compiled class)
    • public
    • protected
    • internal, only in case if the class is annotated with PublishedApi
  • it isn't a local class
  • it isn't a synthetic class with mappings for when tableswitches ($WhenMappings)
  • it contains at least one effectively public member, in case if the class corresponds to a kotlin file with top-level members or a multifile facade
  • in case if the class is a member in another class, it is contained in the effectively public class
  • in case if the class is a protected member in another class, it is contained in the non-final class

Members

A member of the class (i.e. a field or a method) is considered to be effectively public if all the following conditions are met:

  • it has public or protected JVM access (ACC_PUBLIC or ACC_PROTECTED)

  • it has one of the following visibilities in Kotlin:

    • no visibility (means no Kotlin declaration corresponds to this class member)
    • public
    • protected
    • internal, only in case if the class is annotated with PublishedApi

    Note that Kotlin visibility of a field exposed by lateinit property is the visibility of its setter.

  • in case if the member is protected, it is contained in non-final class

  • it isn't a synthetic access method for a private field

What makes an incompatible change to the public binary API

Class changes

For a class a binary incompatible change is:

  • changing the full class name (including package and containing classes)
  • changing the superclass, so that the class no longer has the previous superclass in the inheritance chain
  • changing the set of implemented interfaces so that the class no longer implements interfaces it had implemented before
  • changing one of the following access flags:
    • ACC_PUBLIC, ACC_PROTECTED, ACC_PRIVATE — lessening the class visibility
    • ACC_FINAL — making non-final class final
    • ACC_ABSTRACT — making non-abstract class abstract
    • ACC_INTERFACE — changing class to interface and vice versa
    • ACC_ANNOTATION — changing annotation to interface and vice versa

Class member changes

For a class member a binary incompatible change is:

  • changing its name
  • changing its descriptor (erased return type and parameter types for methods); this includes changing field to method and vice versa
  • changing one of the following access flags:
    • ACC_PUBLIC, ACC_PROTECTED, ACC_PRIVATE — lessening the member visibility
    • ACC_FINAL — making non-final field or method final
    • ACC_ABSTRACT — making non-abstract method abstract
    • ACC_STATIC — changing instance member to static and vice versa

Building the project locally

In order to build and run tests in the project in IDE, two prerequisites are required:

  • Java 11 or above in order to use the latest ASM
  • All build actions in the IDE should be delegated to Gradle
Comments
  • Support MPP with multiple JVM targets

    Support MPP with multiple JVM targets

        jvm("androidJvm")
        jvm("desktopJvm")
    
    Cannot add task 'apiBuild' as a task with that name already exists.
    

    Using 0.3.0. Maybe we can create tasks based on the target names, e.g. androidJvmApiBuild, desktopJvmApiBuild

    bug enhancement 
    opened by Him188 9
  • Top-level projects don't seem to work on all OS's

    Top-level projects don't seem to work on all OS's

    I'm not entirely sure if I'm reading this all right, but in our single-project repo doesn't appear to work when running apiCheck on linux CI while it does work on macOS locally.

    Here's a repro - https://github.com/slackhq/EitherNet/pull/52. Fails on CI with a strange check that suggests it's expecting a subproject, but it's a single-project repo only.

    opened by ZacSweers 8
  • Add nullability support

    Add nullability support

    Hey, we're currently in a situation where we need API compatibility above binary compatibility. BCV is already a pretty good fit for our needs, but nullability support would make it near perfect. So I took a stab at #20. The general approach is to add a ? to every nullable type: Ljava/lang/Integer becomes Ljava/lang/Integer?.

    Please let me know what you think about this approach. Also since Kotlin isn't my main language any stylistic feedback is also appreciated 😅

    Open TODOs:

    • [x] Since this is a breaking change we should make it opt-in and add an option to activate it.
    opened by sebastianludwig 8
  • Is it possible to distinguish between compatible and incompatible changes?

    Is it possible to distinguish between compatible and incompatible changes?

    When monitoring public API, I can think of two use cases:

    1. Make sure that module.api files are always up-to-date and represent the latest state of the public API.
    2. Make sure that a new version of module.api is backward compatible with the previous one to avoid breaking changes.

    In the case of 1., it's important to detect additions as we want them to be taken into account in subsequents checks.

    But additions are typically not important for 2.: It is fine to release a new version of a lib with new APIs as it will not break backward compatibility.

    My current understanding is that ./gradlew apiCheck is focused at 1. and will fail on any change, even additions. Am I correct?

    If that's the case, it would be nice to have another task focused at 2. that will only succeed if the new API is backward compatible.

    Maybe ./gradlew apiDumpSafe ?

    It would fail on backward incompatible changes only and output a report with the problematic changes and whether they are source-level or binary-level incompatibilities.

    Is that currently possible? Or will it be?

    question 
    opened by martinbonnin 6
  • After upgrading to kotlin 1.5.0, the kotlin-parcelize generated code started to be reported as public by binary-compatibility-validator

    After upgrading to kotlin 1.5.0, the kotlin-parcelize generated code started to be reported as public by binary-compatibility-validator

    Hi,

    I'm using kotlin-parcelize plugin to generate the parcelable code in the internal data classes in my project.

    Let's consider the following kotlin data class, that is marked as internal.

    @Parcelize
    internal data class Country (
        val code: String,
        val name: String
    ) : Parcelable
    

    I have made the following tests with binary-compatibility-validator 0.5.0. And there is a difference when using kotlin 1.5.0 and 1.4.32.

    Before upgrading to kotlin 1.5.0, using kotlin 1.4.32, the command ./gradlew teleconsultationandroid:apiDump wouldn't return any output related to the class Country. And to help debug this issue, here is the compiled code of Country in kotlin 1.4.32.

    Compiled `Country` data class using kotlin 1.4.32. import android.os.Parcel; import android.os.Parcelable; import kotlin.Metadata; import kotlin.jvm.internal.Intrinsics; import kotlinx.parcelize.Parcelize; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable;

    @Parcelize @Metadata(...) /* compiled from: Country.kt */ public final class Country implements Parcelable { public static final Parcelable.Creator CREATOR = new Creator(); @NotNull private final String code; @NotNull private final String name;

    @Metadata(bv = {1, 0, 3}, d1 = {}, d2 = {}, k = 3, mv = {1, 4, 2})
    public static class Creator implements Parcelable.Creator<Country> {
        @Override // android.os.Parcelable.Creator
        @NotNull
        public final Country createFromParcel(@NotNull Parcel parcel) {
            Intrinsics.checkNotNullParameter(parcel, "in");
            return new Country(parcel.readString(), parcel.readString());
        }
    
        @Override // android.os.Parcelable.Creator
        @NotNull
        public final Country[] newArray(int i) {
            return new Country[i];
        }
    }
    
    public Country(@NotNull String str, @NotNull String str2) {
        Intrinsics.checkNotNullParameter(str, "code");
        Intrinsics.checkNotNullParameter(str2, "name");
        this.code = str;
        this.name = str2;
    }
    
    public static /* synthetic */ Country copy$default(Country country, String str, String str2, int i, Object obj) {
        if ((i & 1) != 0) {
            str = country.code;
        }
        if ((i & 2) != 0) {
            str2 = country.name;
        }
        return country.copy(str, str2);
    }
    
    @NotNull
    public final String component1() {
        return this.code;
    }
    
    @NotNull
    public final String component2() {
        return this.name;
    }
    
    @NotNull
    public final Country copy(@NotNull String str, @NotNull String str2) {
        Intrinsics.checkNotNullParameter(str, "code");
        Intrinsics.checkNotNullParameter(str2, "name");
        return new Country(str, str2);
    }
    
    public int describeContents() {
        return 0;
    }
    
    public boolean equals(@Nullable Object obj) {
        if (this != obj) {
            if (obj instanceof Country) {
                Country country = (Country) obj;
                if (!Intrinsics.areEqual(this.code, country.code) || !Intrinsics.areEqual(this.name, country.name)) {
                    return false;
                }
            }
            return false;
        }
        return true;
    }
    
    @NotNull
    public final String getCode() {
        return this.code;
    }
    
    @NotNull
    public final String getName() {
        return this.name;
    }
    
    public int hashCode() {
        int i = 0;
        String str = this.code;
        int hashCode = (str != null ? str.hashCode() : 0) * 31;
        String str2 = this.name;
        if (str2 != null) {
            i = str2.hashCode();
        }
        return hashCode + i;
    }
    
    @NotNull
    public String toString() {
        return "Country(code=" + this.code + ", name=" + this.name + ")";
    }
    
    public void writeToParcel(@NotNull Parcel parcel, int i) {
        Intrinsics.checkNotNullParameter(parcel, "parcel");
        parcel.writeString(this.code);
        parcel.writeString(this.name);
    }
    

    }

    But after upgrading to kotlin 1.5.0, the command ./gradlew teleconsultationandroid:apiDump started returing the following output.

    public final class com/sample/project/Country$Creator : android/os/Parcelable$Creator {
    	public fun <init> ()V
    	public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object;
    	public final fun createFromParcel (Landroid/os/Parcel;)L com/sample/project/Country;
    	public synthetic fun newArray (I)[Ljava/lang/Object;
    	public final fun newArray (I)[Lcom/sample/project/Country;
    }
    

    And here is the output of the compiled code in kotlin 1.5.0.

    Compiled `Country` data class using kotlin 1.5.0. import android.os.Parcel; import android.os.Parcelable; import kotlin.Metadata; import kotlin.jvm.internal.Intrinsics; import kotlinx.parcelize.Parcelize; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable;

    @Parcelize @Metadata(...) /* compiled from: Country.kt */ public final class Country implements Parcelable { @NotNull public static final Parcelable.Creator CREATOR = new Creator(); @NotNull private final String code; @NotNull private final String name;

    @Metadata(bv = {1, 0, 3}, d1 = {}, d2 = {}, k = 3, mv = {1, 5, 1})
    /* compiled from: Country.kt */
    public static final class Creator implements Parcelable.Creator<Country> {
        @Override // android.os.Parcelable.Creator
        @NotNull
        public final Country createFromParcel(@NotNull Parcel parcel) {
            Intrinsics.checkNotNullParameter(parcel, "parcel");
            return new Country(parcel.readString(), parcel.readString());
        }
    
        @Override // android.os.Parcelable.Creator
        @NotNull
        public final Country[] newArray(int i) {
            return new Country[i];
        }
    }
    
    public Country(@NotNull String str, @NotNull String str2) {
        Intrinsics.checkNotNullParameter(str, "code");
        Intrinsics.checkNotNullParameter(str2, "name");
        this.code = str;
        this.name = str2;
    }
    
    public static /* synthetic */ Country copy$default(Country country, String str, String str2, int i, Object obj) {
        if ((i & 1) != 0) {
            str = country.code;
        }
        if ((i & 2) != 0) {
            str2 = country.name;
        }
        return country.copy(str, str2);
    }
    
    @NotNull
    public final String component1() {
        return this.code;
    }
    
    @NotNull
    public final String component2() {
        return this.name;
    }
    
    @NotNull
    public final Country copy(@NotNull String str, @NotNull String str2) {
        Intrinsics.checkNotNullParameter(str, "code");
        Intrinsics.checkNotNullParameter(str2, "name");
        return new Country(str, str2);
    }
    
    public int describeContents() {
        return 0;
    }
    
    public boolean equals(@Nullable Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof Country)) {
            return false;
        }
        Country country = (Country) obj;
        if (!Intrinsics.areEqual(this.code, country.code)) {
            return false;
        }
        return Intrinsics.areEqual(this.name, country.name);
    }
    
    @NotNull
    public final String getCode() {
        return this.code;
    }
    
    @NotNull
    public final String getName() {
        return this.name;
    }
    
    public int hashCode() {
        return (this.code.hashCode() * 31) + this.name.hashCode();
    }
    
    @NotNull
    public String toString() {
        return "Country(code=" + this.code + ", name=" + this.name + ')';
    }
    
    public void writeToParcel(@NotNull Parcel parcel, int i) {
        Intrinsics.checkNotNullParameter(parcel, "out");
        parcel.writeString(this.code);
        parcel.writeString(this.name);
    }
    

    }

    Is this as issue of binary-compatibility-validator? Thanks

    opened by 4brunu 6
  • Handle TypeErasure

    Handle TypeErasure

    public suspend fun foo(): String
    // change to 
    public suspend fun foo(): Int
    

    The api is always public final fun foo (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; and the apiCheck will not fail.

    enhancement 
    opened by hfhbd 6
  • How to override the `apiBuild` task in Groovy

    How to override the `apiBuild` task in Groovy

    image

    title. I get the following error for:

    apiBuild {
      // "jar" here is the name of the default Jar task producing the resulting jar file
      // in a multiplatform project it can be named "jvmJar"
      // if you applied the shadow plugin, it creates the "shadowJar" task that produces the transformed jar
      inputJar.value(new File("android-sdk-base/build/outputs/sources/android-sdk-base-sources.jar"))
    }
    
    Caused by: org.gradle.internal.metaobject.AbstractDynamicObject$CustomMessageMissingMethodException: Could not find method apiBuild() for arguments [quality_3w8dpqhsp24rs3ntbbp280wfa$_run_closure5@7d0ce1f1] on project ':android-sdk-base' of type org.gradle.api.Project.
    

    in 0.12.1 of the library

    question 
    opened by radixdev 5
  • Plugin fails with DuplicateTaskException when used on Multiplatform projects with both JVM and Android targets

    Plugin fails with DuplicateTaskException when used on Multiplatform projects with both JVM and Android targets

    I have a Kotlin Multiplatform project that defines both jvm and android targets (this is a snippet of the gradle config, I can include the full one if it's helpful):

    plugins {
        kotlin("multiplatform")
        id("com.android.library")
        id("binary-compatibility-validator")
    }
    
    kotlin {
        android {
            publishLibraryVariants("release")
        }
        jvm()
        ...
    }
    

    If I apply v0.4.0 of the binary-compatibility-validator gradle plugin, I get the following error:

    FAILURE: Build failed with an exception.
    
    * What went wrong:
    A problem occurred configuring project ':runtime'.
    > org.gradle.api.internal.tasks.DefaultTaskContainer$DuplicateTaskException: Cannot add task 'apiBuild' as a task with that name already exists.
    

    This error didn't happen with v0.2.4 of the plugin. Looking at https://github.com/Kotlin/binary-compatibility-validator/blob/master/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt#L85, it seems that the plugin tries to create a task named apiDump for every jvm or android compilation in the project. This works fine if the project is configured with a jvm target or an android target, but not if the project is configured with both targets.

    Related to #38

    bug 
    opened by garyp 5
  • Non-public markers aren't handled correctly for properties

    Non-public markers aren't handled correctly for properties

    When declaring an opt-in annotation and adding that annotation to the nonPublicMarkers configuration, properties that have the annotation present are not correctly excluded from the api dump.

    Example annotation:

    @RequiresOptIn(level = RequiresOptIn.Level.WARNING)
    annotation class MyInternalApi
    

    A function like this will not show up in the dump (correctly):

    @MyInternalApi
    fun internalFun() {}
    

    However, this property will:

    @MyInternalApi
    var internalVar = 0
    

    ... in the form of a getter and a setter:

    public static final fun getInternalVar ()I
    public static final fun setInternalVar (I)V
    

    A workaround that removes these from the API dump is annotating the getter and setter directly, like so:

    var otherInternalVar = 0
        @MyInternalApi get
        @MyInternalApi set
    

    However, annotating the getter like that doesn't trigger warnings at the call site of the property, so you really need three annotations on each property to satisfy both the plugin and inspections:

    @MyInternalApi
    var combinedInternalVar = 0
        @MyInternalApi get
        @MyInternalApi set
    

    Project with examples and reproducing the issue can be found here.

    bug PR welcome 
    opened by zsmb13 5
  • In a multi module Gradle project, ignore root project by default

    In a multi module Gradle project, ignore root project by default

    Observed Behavior

    In a multi-module Gradle project, after running apiDump, the file api/<projectName>.api will appear with a blank line.

    Current workaround

    I need to specifically ignore the root projects to avoid the creation of this file. This looks quite unnecessary:

    For example,

    apiValidation {
        ignoredProjects.addAll(subprojects.filter { it.name != "detekt-api"}.map { it.name } + rootProject.name)
    }
    

    Environment

    The project uses a buildSrc and all scripts are in kts.

    opened by chao2zhang 5
  • Fixed multiple jvm targets

    Fixed multiple jvm targets

    This is a rework of the multiple-jvm-targets branch that actually works. The testing is "fixed" and the implementation is fixed. Note that this is not 100% compatible with existing projects as it uses target-specific subdirectories (only for multiplatform projects).

    opened by pdvrieze 4
  • After upgrading to kotlin 1.5.0, the kotlin-parcelize generated code started to be reported as public by binary-compatibility-validator

    After upgrading to kotlin 1.5.0, the kotlin-parcelize generated code started to be reported as public by binary-compatibility-validator

    Related to: https://github.com/Kotlin/binary-compatibility-validator/issues/55

    Hi,

    I'm using kotlin-parcelize plugin to generate the parcelable code in the internal data classes in my project.

    Let's consider the following kotlin data class, that is marked as internal.

    @Parcelize
    internal data class Country (
        val code: String,
        val name: String
    ) : Parcelable
    

    I have made the following tests with binary-compatibility-validator 0.5.0. And there is a difference when using kotlin 1.5.0 and 1.4.32.

    Before upgrading to kotlin 1.5.0, using kotlin 1.4.32, the command ./gradlew teleconsultationandroid:apiDump wouldn't return any output related to the class Country. And to help debug this issue, here is the compiled code of Country in kotlin 1.4.32.

    Compiled `Country` data class using kotlin 1.4.32. import android.os.Parcel; import android.os.Parcelable; import kotlin.Metadata; import kotlin.jvm.internal.Intrinsics; import kotlinx.parcelize.Parcelize; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable;

    @Parcelize @Metadata(...) /* compiled from: Country.kt */ public final class Country implements Parcelable { public static final Parcelable.Creator CREATOR = new Creator(); @NotNull private final String code; @NotNull private final String name;

    @Metadata(bv = {1, 0, 3}, d1 = {}, d2 = {}, k = 3, mv = {1, 4, 2})
    public static class Creator implements Parcelable.Creator<Country> {
        @Override // android.os.Parcelable.Creator
        @NotNull
        public final Country createFromParcel(@NotNull Parcel parcel) {
            Intrinsics.checkNotNullParameter(parcel, "in");
            return new Country(parcel.readString(), parcel.readString());
        }
    
        @Override // android.os.Parcelable.Creator
        @NotNull
        public final Country[] newArray(int i) {
            return new Country[i];
        }
    }
    
    public Country(@NotNull String str, @NotNull String str2) {
        Intrinsics.checkNotNullParameter(str, "code");
        Intrinsics.checkNotNullParameter(str2, "name");
        this.code = str;
        this.name = str2;
    }
    
    public static /* synthetic */ Country copy$default(Country country, String str, String str2, int i, Object obj) {
        if ((i & 1) != 0) {
            str = country.code;
        }
        if ((i & 2) != 0) {
            str2 = country.name;
        }
        return country.copy(str, str2);
    }
    
    @NotNull
    public final String component1() {
        return this.code;
    }
    
    @NotNull
    public final String component2() {
        return this.name;
    }
    
    @NotNull
    public final Country copy(@NotNull String str, @NotNull String str2) {
        Intrinsics.checkNotNullParameter(str, "code");
        Intrinsics.checkNotNullParameter(str2, "name");
        return new Country(str, str2);
    }
    
    public int describeContents() {
        return 0;
    }
    
    public boolean equals(@Nullable Object obj) {
        if (this != obj) {
            if (obj instanceof Country) {
                Country country = (Country) obj;
                if (!Intrinsics.areEqual(this.code, country.code) || !Intrinsics.areEqual(this.name, country.name)) {
                    return false;
                }
            }
            return false;
        }
        return true;
    }
    
    @NotNull
    public final String getCode() {
        return this.code;
    }
    
    @NotNull
    public final String getName() {
        return this.name;
    }
    
    public int hashCode() {
        int i = 0;
        String str = this.code;
        int hashCode = (str != null ? str.hashCode() : 0) * 31;
        String str2 = this.name;
        if (str2 != null) {
            i = str2.hashCode();
        }
        return hashCode + i;
    }
    
    @NotNull
    public String toString() {
        return "Country(code=" + this.code + ", name=" + this.name + ")";
    }
    
    public void writeToParcel(@NotNull Parcel parcel, int i) {
        Intrinsics.checkNotNullParameter(parcel, "parcel");
        parcel.writeString(this.code);
        parcel.writeString(this.name);
    }
    

    }

    But after upgrading to kotlin 1.5.0, the command ./gradlew teleconsultationandroid:apiDump started returing the following output.

    public final class com/sample/project/Country$Creator : android/os/Parcelable$Creator {
    	public fun <init> ()V
    	public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object;
    	public final fun createFromParcel (Landroid/os/Parcel;)L com/sample/project/Country;
    	public synthetic fun newArray (I)[Ljava/lang/Object;
    	public final fun newArray (I)[Lcom/sample/project/Country;
    }
    

    And here is the output of the compiled code in kotlin 1.5.0.

    Compiled `Country` data class using kotlin 1.5.0. import android.os.Parcel; import android.os.Parcelable; import kotlin.Metadata; import kotlin.jvm.internal.Intrinsics; import kotlinx.parcelize.Parcelize; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable;

    @Parcelize @Metadata(...) /* compiled from: Country.kt */ public final class Country implements Parcelable { @NotNull public static final Parcelable.Creator CREATOR = new Creator(); @NotNull private final String code; @NotNull private final String name;

    @Metadata(bv = {1, 0, 3}, d1 = {}, d2 = {}, k = 3, mv = {1, 5, 1})
    /* compiled from: Country.kt */
    public static final class Creator implements Parcelable.Creator<Country> {
        @Override // android.os.Parcelable.Creator
        @NotNull
        public final Country createFromParcel(@NotNull Parcel parcel) {
            Intrinsics.checkNotNullParameter(parcel, "parcel");
            return new Country(parcel.readString(), parcel.readString());
        }
    
        @Override // android.os.Parcelable.Creator
        @NotNull
        public final Country[] newArray(int i) {
            return new Country[i];
        }
    }
    
    public Country(@NotNull String str, @NotNull String str2) {
        Intrinsics.checkNotNullParameter(str, "code");
        Intrinsics.checkNotNullParameter(str2, "name");
        this.code = str;
        this.name = str2;
    }
    
    public static /* synthetic */ Country copy$default(Country country, String str, String str2, int i, Object obj) {
        if ((i & 1) != 0) {
            str = country.code;
        }
        if ((i & 2) != 0) {
            str2 = country.name;
        }
        return country.copy(str, str2);
    }
    
    @NotNull
    public final String component1() {
        return this.code;
    }
    
    @NotNull
    public final String component2() {
        return this.name;
    }
    
    @NotNull
    public final Country copy(@NotNull String str, @NotNull String str2) {
        Intrinsics.checkNotNullParameter(str, "code");
        Intrinsics.checkNotNullParameter(str2, "name");
        return new Country(str, str2);
    }
    
    public int describeContents() {
        return 0;
    }
    
    public boolean equals(@Nullable Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof Country)) {
            return false;
        }
        Country country = (Country) obj;
        if (!Intrinsics.areEqual(this.code, country.code)) {
            return false;
        }
        return Intrinsics.areEqual(this.name, country.name);
    }
    
    @NotNull
    public final String getCode() {
        return this.code;
    }
    
    @NotNull
    public final String getName() {
        return this.name;
    }
    
    public int hashCode() {
        return (this.code.hashCode() * 31) + this.name.hashCode();
    }
    
    @NotNull
    public String toString() {
        return "Country(code=" + this.code + ", name=" + this.name + ')';
    }
    
    public void writeToParcel(@NotNull Parcel parcel, int i) {
        Intrinsics.checkNotNullParameter(parcel, "out");
        parcel.writeString(this.code);
        parcel.writeString(this.name);
    }
    

    }

    Here is the sample project

    https://github.com/4brunu/kotlin-binary-compatibility-validator-issue

    To dump the API, just run make or ./gradlew mylibrary:apiDump and check the result file under mylibrary/api/mylibrary.api.

    Here is the api file with kotlin 1.4.32

    Commit

    mylibrary.api

    Here is the api file with kotlin 1.5.0

    Commit

    mylibrary.api

    This PR https://github.com/Kotlin/binary-compatibility-validator/pull/60 tried to fix this issue, but it seems that it didn't worked.

    I have update the sample project to Kotlin 1.7.20 and binary-compatibility-validator 0.12.1 but the issue is still there.

    Thanks

    opened by 4brunu 0
  • AndroidLibrarySupport: Add Java Android library support

    AndroidLibrarySupport: Add Java Android library support

    Fixes: #94

    This time I applied the plugin to the root project only and it worked as expected, added additional functional tests for the plugin-on-root cases, all seems to be fine on my side. I did not have time to clean-up the code as much as I wanted to, so it might look a bit messy right now. If needed, I can refactor after you confirm that the problem is indeed resolved for other libraries you've tested.

    opened by realbarisbasturk 0
  • BCV renders internal declarations public if their parameter types are relocated

    BCV renders internal declarations public if their parameter types are relocated

    There's this declaration in kotlinx-metadata: https://github.com/JetBrains/kotlin/blob/6abf14087c46e0b5c3015eec48b92162bc63c135/libraries/kotlinx-metadata/src/kotlinx/metadata/Flag.kt#L40

    It has one public constructor and two internal. 0.12.0 introduces new JAR dump mode. In this mode, constructors are getting into public dump:

     	public fun <init> (Lkotlinx/metadata/internal/metadata/deserialization/Flags$BooleanFlagField;)V
    	public fun <init> (Lkotlinx/metadata/internal/metadata/deserialization/Flags$FlagField;I)V
    

    While in 0.11.0 in regular mode they weren't there

    opened by sandwwraith 4
  • The task check won't run before task assembleRelease

    The task check won't run before task assembleRelease

    The README said When doing code changes that do not imply any changes in public API, no additional actions should be performed. check task on your CI will validate everything.. It seems not precisely when build with task assembleRelease and not run task check individually because assembleRelease doesn't depends on check. That could cause mistake for someone like me due to there still need additional action. I suggest whether change the readme or let the validator run before any assembleXXX tasks.

    documentation 
    opened by NamekMaster 0
  • Java Android Library Support

    Java Android Library Support

    Binary compatibility plugin configures necessary tasks like 'apiDump' and 'apiCheck' only if kotlin, kotlin-android or kotlin-multiplatform plugins are applied as can be seen here

    Although it can be used for Java libraries, due to missing the plugins listed above, necessary tasks are not created.

    Java Android libraries can be supported if the tasks are also created for projects where com.android.library plugin is applied.

    enhancement PR welcome 
    opened by realbarisbasturk 5
Releases(0.12.1)
  • 0.12.1(Oct 26, 2022)

  • 0.12.0(Oct 20, 2022)

    • Support of API dump and comparison directly from JAR (#99)
    • Support of Android Library Plugin (#94). Thanks @realbarisbasturk for the contribution!
    Source code(tar.gz)
    Source code(zip)
  • 0.11.1(Sep 5, 2022)

  • 0.11.0(Jul 12, 2022)

  • 0.10.1(Jun 14, 2022)

  • 0.10.0(May 18, 2022)

    • Stdlib is excluded from transitive dependencies and switched to Gradle auto-provided version (#80, #82). Thanks to @martinbonnin!
    • Getters and setters are properly excluded from public API when either field or property-based annotation marker is present (#36). Also thanks to @martinbonnin!
    • Files are now looked up in a case-insensitive manner (#76)
    Source code(tar.gz)
    Source code(zip)
  • 0.9.0(Apr 18, 2022)

    • Support of synthetic annotations (#71).
    • filterOutAnnotated is now part of ExternalApi (#75).
    • .api file checks are now all case-insensitive to provide seamless experience between case-sensitive and case-insensitive OSes as well as between Gradle versions (#76).
    • Kotlin is updated to 1.6.0.
    • kotlinx.metadata is updated to 0.4.2.
    Source code(tar.gz)
    Source code(zip)
  • 0.8.0(Nov 8, 2021)

  • 0.8.0-RC(Oct 4, 2021)

  • 0.7.1(Aug 26, 2021)

  • 0.7.0(Aug 18, 2021)

    • kotlinx-metadata-jvm updated to 0.3.0 in order to support classes produced with Kotlin 1.6.0
    • Fail-fast on unsupported version of Kotlin metadata
    Source code(tar.gz)
    Source code(zip)
  • 0.6.0(Jun 15, 2021)

    • Static final field of the class companion publicity now corresponds to the companion publicity (#53)
    • Local classes generated by Parcelize are detected properly (#55)
    • Kotlin and Gradle updated to 1.5.0 and 7.0.2 respectively
    • Gradle tasks are build-cache friendly now
    Source code(tar.gz)
    Source code(zip)
  • 0.5.0(Mar 18, 2021)

    • Kotlin is updated to 1.4.30
    • kotlinx-metadata is updated to 0.2.0 in order to work with Kotlin 1.5.0+ binaries
    • Better error reporting (#21)
    Source code(tar.gz)
    Source code(zip)
  • 0.4.0(Feb 1, 2021)

  • 0.3.0(Dec 21, 2020)

    • New ignoredClasses API to exclude some classes from the dump (#25). Thanks to @SergejIsbrecht!
    • Support multiplatform when android is only JVM target (#27). Thanks to @twyatt!
    • $default methods of internal API with @PublishedApi are properly dumped (#30).
    • Releases are now published directly to maven central.
    Source code(tar.gz)
    Source code(zip)
  • 0.2.4(Nov 16, 2020)

  • 0.2.3(Mar 16, 2020)

    • Ignore intermediate projects with empty source sets, do not create empty directories and api dumps for them
    • Better Gradle task management for complex projects
    Source code(tar.gz)
    Source code(zip)
  • 0.2.1(Feb 28, 2020)

  • 0.2.0(Feb 28, 2020)

    • Support for Android projects
    • Plugin can now be applied using both short and FQ name
    • inline reified functions are no longer considered as public ABI
    • Git history was rewritten to migrate all historical changes from the main Kotlin repository
    Source code(tar.gz)
    Source code(zip)
Owner
Kotlin
Kotlin Tools and Libraries
Kotlin
A Kotlin/Java library to connect directly to an Android device without an adb binary or an ADB server

dadb Blog Post: Our First Open-Source Project A Kotlin/Java library to connect directly to an Android device without an adb binary or an ADB server de

mobile.dev 791 Dec 20, 2022
An AutoValue extension that generates binary and source compatible equivalent Kotlin data classes of AutoValue models.

AutoValue Kotlin auto-value-kotlin (AVK) is an AutoValue extension that generates binary-and-source-compatible, equivalent Kotlin data classes. This i

Slack 19 Aug 5, 2022
intera.kt is a Kotlin library for interacting with the Discord Interactions API through a gateway service or a REST API.

?? Overview ⚠️ WARNING: intera.kt is a work in progress. It is not yet ready for use. You may encounter bugs and other issues, but please report if yo

Pedro Henrique 1 Nov 30, 2021
intera.kt is a Kotlin library for interacting with the Discord Interactions API through a gateway service or a REST API.

?? Overview ⚠️ WARNING: intera.kt is a work in progress. It is not yet ready for use. You may encounter bugs and other issues, but please report if yo

Pedro Henrique 1 Nov 30, 2021
A lightweight tool for managing and building Kotlin projects.

kpm kpm (Kotlin Project Manager) is a lightweight tool for managing and building Kotlin projects. What is kpm? Essentially, kpm is going to be a light

Conor Byrne 5 Apr 23, 2022
MangaDex V5 migration tool for Tachiyomi

Tachi Dex Migrator MangaDex V5 migration tool for Tachiyomi Download Make a backup of your backup! You have been warned… Get the app on the latest rel

Ivan Iskandar 22 Jun 11, 2022
Spring boot cloud tool usage trail and learning project

Spring boot cloud tool usage trail and learning project Todo Maven based multi-p

Yaoyu He 0 Dec 26, 2021
WriterAI is an AI-based content writing tool

WriterAI is an AI-based content writing tool that can turn your unstructured text into engaging content and generate up to 5 different paragraphs with an input of just 5 words! Using the power of artificial intelligence, this tool helps you write an engaging piece of content and end up with something professional. You can use it for writing emails, blogs, articles, letters, thesis and even e-books! It is completely free to use and open-source :)

Vaibhav Jaiswal 21 Dec 26, 2022
A simple tool used to check the users you follow that do not follow you back.

instafbchecker - Instagram no life guide Current Release: v1.0.1 (30/08/2022) A command line tool used to check which users dont follow you back on In

Nathan 2 Aug 30, 2022
Tool for exporting Old School RuneScape environments so they can be used in 3D modeling programs like Blender.

OSRS Environment Exporter Tool for exporting Old School RuneScape environments so that they can be used in 3D modeling programs like Blender. Download

Connor 21 Dec 29, 2022
Remote Administrator Tool [ RAT For Android ] No Port Forwarding

XHUNTER RAT for Android ?? · Telegram · ⚖️ Legal Disclaimer: For Educational Purpose Only Usage of XHUNTER for attacking targets without prior mutual

Anon 79 Dec 31, 2022
A tool to model and analyze the design of systems from .class files

sift A tool to model and analyze the design of systems from java class files. Features CLI tool for building, querying and diff-ing "system models" fr

Adrian Papari 4 Dec 2, 2022
Postman-API-Fest-22 - Project for Postman API Fest 22

Project for Postman API Fest 22 Team Moon With only two members on the board, we

Pradumna Saraf 9 Nov 27, 2022
Kotlin compiler plugin that allows class delegation to be dynamic like property delegations

kotlin-dynamic-delegation Kotlin compiler plugin that allows class delegation to be dynamic like property delegations. The plugin is working in progre

Him188 14 Sep 8, 2022
KVision allows you to build modern web applications with the Kotlin language

KVision allows you to build modern web applications with the Kotlin language, without any use of HTML, CSS or JavaScript. It gives you a rich hierarchy of ready to use GUI components, which can be used as builder blocks for the application UI.

Robert Jaros 985 Jan 1, 2023
An application that allows the user to update variety of smartphones that are used such as iPhone and Android

PhoneApplication An application that allows the user to update variety of smartphones such as iPhone and Android. This application allows users to add

Pankaj Singh 1 Nov 28, 2021
Gradle plugin which allows to use typed DSL for generating kubernetes/openshift YAML files

gr8s Gradle plugin which allows using typed DSL for generating kubernetes/openshift YAML files. Based on kuberig Usage import io.github.guai.gr8s.Gene

null 0 Jan 3, 2022
ViewModel-Lifecycle - ViewModel Lifecycle allows you to track and observe Jetpack ViewModel's lifecycle changes

ViewModel Lifecycle ?? ViewModel Lifecycle allows you to track and observe Jetpa

Jaewoong Eum 97 Nov 25, 2022