Upgrade to Pro — share decks privately, control downloads, hide ads and more …

There is 1 Broken API Among Us

Nicola Corti
November 29, 2020

There is 1 Broken API Among Us

As a library user, you probably know that versions don't always tell the truth. Have you ever experienced a broken build after doing a minor bump of a library?

As a library author, maintaining a clean API is a challenge. There is always the risk that a breaking change sneaks in by accident.

Wouldn't it be nice to get notified if you're accidentally breaking your users' code? A tool to inspect the binary API of your Kotlin/Java code can help you exactly with that. In this talk, we will see some of those tools and how that simplifies the life of library & SDK developers.

Nicola Corti

November 29, 2020
Tweet

More Decks by Nicola Corti

Other Decks in Technology

Transcript

  1. WHO IS THIS TALK FOR? • Library Devs • SDK

    Devs • Open Source Devs • Mobile Devs in a Modularized world
  2. GOOD API CITIZENS @Deprecate @Deprecated( message = "This method is

    affected by a bug" ) fun replace(with: String)
  3. GOOD API CITIZENS @Deprecate @Deprecated( message = "This method is

    affected by a bug", replaceWith = ReplaceWith("newReplace(with)") ) fun replace(with: String)
  4. GOOD API CITIZENS @Deprecate @Deprecated( message = "This method is

    affected by a bug", replaceWith = ReplaceWith("newReplace(with)"), level = DeprecationLevel.ERROR ) fun replace(with: String)
  5. GOOD API CITIZENS @OptIn @RequiresOptIn(message = "This API is experimental")

    annotation class MyExperimentalApi @MyExperimentalApi fun getAnswer() = 42
  6. GOOD API CITIZENS @OptIn @RequiresOptIn(message = "This API is experimental")

    annotation class MyExperimentalApi @MyExperimentalApi fun getAnswer() = 42 fun main() { println(getAnswer()) }
  7. GOOD API CITIZENS @OptIn @RequiresOptIn(message = "This API is experimental")

    annotation class MyExperimentalApi @MyExperimentalApi fun getAnswer() = 42 @MyExperimentalApi fun main() { println(getAnswer()) }
  8. GOOD API CITIZENS @OptIn @RequiresOptIn(message = "This API is experimental")

    annotation class MyExperimentalApi @MyExperimentalApi fun getAnswer() = 42 @OptIn(MyExperimentalApi::class) fun main() { println(getAnswer()) }
  9. BINARY COMPATIBILITY VALIDATOR buildscript { dependencies { classpath("org.jetbrains.kotlinx:binary-compatibility-validator:0.2.4") } }

    apply plugin: "binary-compatibility-validator" Setup apiValidation { ignoredProjects += ["sample"] }
  10. BINARY COMPATIBILITY VALIDATOR buildscript { dependencies { classpath("org.jetbrains.kotlinx:binary-compatibility-validator:0.2.4") } }

    apply plugin: "binary-compatibility-validator" Setup apiValidation { ignoredProjects += ["sample"] ignoredPackages += ["com.chuckerteam.chucker.internal"] }
  11. BINARY COMPATIBILITY VALIDATOR buildscript { dependencies { classpath("org.jetbrains.kotlinx:binary-compatibility-validator:0.2.4") } }

    apply plugin: "binary-compatibility-validator" Setup apiValidation { ignoredProjects += ["sample"] ignoredPackages += ["com.chuckerteam.chucker.internal"] nonPublicMarkers += ["com.chuckerteam.chucker.InternalApiAnnotation"] }
  12. BINARY COMPATIBILITY VALIDATOR buildscript { dependencies { classpath("org.jetbrains.kotlinx:binary-compatibility-validator:0.2.4") } }

    apply plugin: "binary-compatibility-validator" Setup apiValidation { ignoredProjects += ["sample"] ignoredPackages += ["com.chuckerteam.chucker.internal"] nonPublicMarkers += ["com.chuckerteam.chucker.InternalApiAnnotation"] } $ ./gradlew apiDump
  13. BINARY COMPATIBILITY VALIDATOR .api File public final class com/chuckerteam/chucker/BuildConfig {

    public static final field BUILD_TYPE Ljava/lang/String; public static final field DEBUG Z public static final field LIBRARY_PACKAGE_NAME Ljava/lang/String; public fun <init> ()V } public final class com/chuckerteam/chucker/api/Chucker { public static final field INSTANCE Lcom/chuckerteam/chucker/api/Chucker; public static final fun dismissNotifications (Landroid/content/Context;)V public static final fun getLaunchIntent (Landroid/content/Context;)Landroid/content/Intent; public final fun isOp ()Z } public final class com/chuckerteam/chucker/api/ChuckerCollector { public fun <init> (Landroid/content/Context;)V public fun <init> (Landroid/content/Context;Z)V public fun <init> (Landroid/content/Context;ZLcom/chuckerteam/chucker/api/RetentionManager$Period;)V public synthetic fun <init> (Landroid/content/Context;ZLcom/chuckerteam/chucker/api/ RetentionManager$Period;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getShowNotification ()Z public final fun setShowNotification (Z)V } public final class com/chuckerteam/chucker/api/ChuckerInterceptor : okhttp3/Interceptor {
  14. BINARY COMPATIBILITY VALIDATOR .api File public final class com/chuckerteam/chucker/BuildConfig {

    public static final field BUILD_TYPE Ljava/lang/String; public static final field DEBUG Z public static final field LIBRARY_PACKAGE_NAME Ljava/lang/String; public fun <init> ()V } public final class com/chuckerteam/chucker/api/Chucker { public static final field INSTANCE Lcom/chuckerteam/chucker/api/Chucker; public static final fun dismissNotifications (Landroid/content/Context;)V public static final fun getLaunchIntent (Landroid/content/Context;)Landroid/content/Intent; public final fun isOp ()Z } public final class com/chuckerteam/chucker/api/ChuckerCollector { public fun <init> (Landroid/content/Context;)V public fun <init> (Landroid/content/Context;Z)V public fun <init> (Landroid/content/Context;ZLcom/chuckerteam/chucker/api/RetentionManager$Period;)V public synthetic fun <init> (Landroid/content/Context;ZLcom/chuckerteam/chucker/api/ RetentionManager$Period;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getShowNotification ()Z public final fun setShowNotification (Z)V } public final class com/chuckerteam/chucker/api/ChuckerInterceptor : okhttp3/Interceptor {
  15. BINARY COMPATIBILITY VALIDATOR .api File public final class com/chuckerteam/chucker/BuildConfig {

    public static final field BUILD_TYPE Ljava/lang/String; public static final field DEBUG Z public static final field LIBRARY_PACKAGE_NAME Ljava/lang/String; public fun <init> ()V } public final class com/chuckerteam/chucker/api/Chucker { public static final field INSTANCE Lcom/chuckerteam/chucker/api/Chucker; public static final fun dismissNotifications (Landroid/content/Context;)V public static final fun getLaunchIntent (Landroid/content/Context;)Landroid/content/Intent; public final fun isOp ()Z } public final class com/chuckerteam/chucker/api/ChuckerCollector { public fun <init> (Landroid/content/Context;)V public fun <init> (Landroid/content/Context;Z)V public fun <init> (Landroid/content/Context;ZLcom/chuckerteam/chucker/api/RetentionManager$Period;)V public synthetic fun <init> (Landroid/content/Context;ZLcom/chuckerteam/chucker/api/ RetentionManager$Period;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getShowNotification ()Z public final fun setShowNotification (Z)V } public final class com/chuckerteam/chucker/api/ChuckerInterceptor : okhttp3/Interceptor {
  16. BINARY COMPATIBILITY VALIDATOR .api File public final class com/chuckerteam/chucker/BuildConfig {

    public static final field BUILD_TYPE Ljava/lang/String; public static final field DEBUG Z public static final field LIBRARY_PACKAGE_NAME Ljava/lang/String; public fun <init> ()V } public final class com/chuckerteam/chucker/api/Chucker { public static final field INSTANCE Lcom/chuckerteam/chucker/api/Chucker; public static final fun dismissNotifications (Landroid/content/Context;)V public static final fun getLaunchIntent (Landroid/content/Context;)Landroid/content/Intent; public final fun isOp ()Z } public final class com/chuckerteam/chucker/api/ChuckerCollector { public fun <init> (Landroid/content/Context;)V public fun <init> (Landroid/content/Context;Z)V public fun <init> (Landroid/content/Context;ZLcom/chuckerteam/chucker/api/RetentionManager$Period;)V public synthetic fun <init> (Landroid/content/Context;ZLcom/chuckerteam/chucker/api/ RetentionManager$Period;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getShowNotification ()Z public final fun setShowNotification (Z)V } public final class com/chuckerteam/chucker/api/ChuckerInterceptor : okhttp3/Interceptor {
  17. BINARY COMPATIBILITY VALIDATOR .api File public final class com/chuckerteam/chucker/BuildConfig {

    public static final field BUILD_TYPE Ljava/lang/String; public static final field DEBUG Z public static final field LIBRARY_PACKAGE_NAME Ljava/lang/String; public fun <init> ()V } public final class com/chuckerteam/chucker/api/Chucker { public static final field INSTANCE Lcom/chuckerteam/chucker/api/Chucker; public static final fun dismissNotifications (Landroid/content/Context;)V public static final fun getLaunchIntent (Landroid/content/Context;)Landroid/content/Intent; public final fun isOp ()Z } public final class com/chuckerteam/chucker/api/ChuckerCollector { public fun <init> (Landroid/content/Context;)V public fun <init> (Landroid/content/Context;Z)V public fun <init> (Landroid/content/Context;ZLcom/chuckerteam/chucker/api/RetentionManager$Period;)V public synthetic fun <init> (Landroid/content/Context;ZLcom/chuckerteam/chucker/api/ RetentionManager$Period;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getShowNotification ()Z public final fun setShowNotification (Z)V } public final class com/chuckerteam/chucker/api/ChuckerInterceptor : okhttp3/Interceptor {
  18. BINARY COMPATIBILITY VALIDATOR .api File public final class com/chuckerteam/chucker/BuildConfig {

    public static final field BUILD_TYPE Ljava/lang/String; public static final field DEBUG Z public static final field LIBRARY_PACKAGE_NAME Ljava/lang/String; public fun <init> ()V } public final class com/chuckerteam/chucker/api/Chucker { public static final field INSTANCE Lcom/chuckerteam/chucker/api/Chucker; public static final fun dismissNotifications (Landroid/content/Context;)V public static final fun getLaunchIntent (Landroid/content/Context;)Landroid/content/Intent; public final fun isOp ()Z } public final class com/chuckerteam/chucker/api/ChuckerCollector { public fun <init> (Landroid/content/Context;)V public fun <init> (Landroid/content/Context;Z)V public fun <init> (Landroid/content/Context;ZLcom/chuckerteam/chucker/api/RetentionManager$Period;)V public synthetic fun <init> (Landroid/content/Context;ZLcom/chuckerteam/chucker/api/ RetentionManager$Period;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getShowNotification ()Z public final fun setShowNotification (Z)V } public final class com/chuckerteam/chucker/api/ChuckerInterceptor : okhttp3/Interceptor {
  19. BINARY COMPATIBILITY VALIDATOR .api File public final class com/chuckerteam/chucker/BuildConfig {

    public static final field BUILD_TYPE Ljava/lang/String; public static final field DEBUG Z public static final field LIBRARY_PACKAGE_NAME Ljava/lang/String; public fun <init> ()V } public final class com/chuckerteam/chucker/api/Chucker { public static final field INSTANCE Lcom/chuckerteam/chucker/api/Chucker; public static final fun dismissNotifications (Landroid/content/Context;)V public static final fun getLaunchIntent (Landroid/content/Context;)Landroid/content/Intent; public final fun isOp ()Z } public final class com/chuckerteam/chucker/api/ChuckerCollector { public fun <init> (Landroid/content/Context;)V public fun <init> (Landroid/content/Context;Z)V public fun <init> (Landroid/content/Context;ZLcom/chuckerteam/chucker/api/RetentionManager$Period;)V public synthetic fun <init> (Landroid/content/Context;ZLcom/chuckerteam/chucker/api/ RetentionManager$Period;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getShowNotification ()Z public final fun setShowNotification (Z)V } public final class com/chuckerteam/chucker/api/ChuckerInterceptor : okhttp3/Interceptor {
  20. BINARY COMPATIBILITY VALIDATOR Normal Workflow $ ./gradlew apiCheck # OR

    $ ./gradlew check # Computed API has not changed BUILD SUCCESSFUL
  21. BINARY COMPATIBILITY VALIDATOR Normal Workflow $ ./gradlew apiCheck # OR

    $ ./gradlew check # Computed API has not changed BUILD SUCCESSFUL # Computed API has changed BUILD FAILED
  22. JAPICMP CLI $ java -jar japicmp-0.14.4-jar-with-dependencies.jar -h SYNOPSIS java -jar

    japicmp.jar [-a <accessModifier>] [(-b | --only-incompatible)] [(-e <excludes> | --exclude <excludes>)] [--exclude-exclusively] [(-h | --help)] [--html-file <pathToHtmlOutputFile>] [--html-stylesheet <pathToHtmlStylesheet>] [(-i <includes> | --include <includes>)] [--ignore-missing-classes] [--ignore-missing-classes-by-regex <ignoreMissingClassesByRegEx>...] [--include-exclusively] [--include-synthetic] [(-m | --only-modified)] [(-n <pathToNewVersionJar> | --new <pathToNewVersionJar>)] [--new-classpath <newClassPath>] [--no-annotations] [(-o <pathToOldVersionJar> | --old <pathToOldVersionJar>)] [--old-classpath <oldClassPath>] [--report-only-filename] [(-s | --semantic-versioning)] [(-x <pathToXml> | --xml-file <pathToXml>)] [--error-on-binary-incompatibility] [--error-on-source-incompatibility] [--error-on-modifications] [--no-error-on-exclusion-incompatibility]
  23. JAPICMP CLI $ java -jar japicmp-0.14.4-jar-with-dependencies.jar -h SYNOPSIS java -jar

    japicmp.jar [-a <accessModifier>] [(-b | --only-incompatible)] [(-e <excludes> | --exclude <excludes>)] [--exclude-exclusively] [(-h | --help)] [--html-file <pathToHtmlOutputFile>] [--html-stylesheet <pathToHtmlStylesheet>] [(-i <includes> | --include <includes>)] [--ignore-missing-classes] [--ignore-missing-classes-by-regex <ignoreMissingClassesByRegEx>...] [--include-exclusively] [--include-synthetic] [(-m | --only-modified)] [(-n <pathToNewVersionJar> | --new <pathToNewVersionJar>)] [--new-classpath <newClassPath>] [--no-annotations] [(-o <pathToOldVersionJar> | --old <pathToOldVersionJar>)] [--old-classpath <oldClassPath>] [--report-only-filename] [(-s | --semantic-versioning)] [(-x <pathToXml> | --xml-file <pathToXml>)] [--error-on-binary-incompatibility] [--error-on-source-incompatibility] [--error-on-modifications] [--no-error-on-exclusion-incompatibility]
  24. JAPICMP CLI $ java -jar japicmp-0.14.4-jar-with-dependencies.jar -h SYNOPSIS java -jar

    japicmp.jar [-a <accessModifier>] [(-b | --only-incompatible)] [(-e <excludes> | --exclude <excludes>)] [--exclude-exclusively] [(-h | --help)] [--html-file <pathToHtmlOutputFile>] [--html-stylesheet <pathToHtmlStylesheet>] [(-i <includes> | --include <includes>)] [--ignore-missing-classes] [--ignore-missing-classes-by-regex <ignoreMissingClassesByRegEx>...] [--include-exclusively] [--include-synthetic] [(-m | --only-modified)] [(-n <pathToNewVersionJar> | --new <pathToNewVersionJar>)] [--new-classpath <newClassPath>] [--no-annotations] [(-o <pathToOldVersionJar> | --old <pathToOldVersionJar>)] [--old-classpath <oldClassPath>] [--report-only-filename] [(-s | --semantic-versioning)] [(-x <pathToXml> | --xml-file <pathToXml>)] [--error-on-binary-incompatibility] [--error-on-source-incompatibility] [--error-on-modifications] [--no-error-on-exclusion-incompatibility]
  25. JAPICMP CLI $ java -jar japicmp-0.14.4-jar-with-dependencies.jar -h SYNOPSIS java -jar

    japicmp.jar [-a <accessModifier>] [(-b | --only-incompatible)] [(-e <excludes> | --exclude <excludes>)] [--exclude-exclusively] [(-h | --help)] [--html-file <pathToHtmlOutputFile>] [--html-stylesheet <pathToHtmlStylesheet>] [(-i <includes> | --include <includes>)] [--ignore-missing-classes] [--ignore-missing-classes-by-regex <ignoreMissingClassesByRegEx>...] [--include-exclusively] [--include-synthetic] [(-m | --only-modified)] [(-n <pathToNewVersionJar> | --new <pathToNewVersionJar>)] [--new-classpath <newClassPath>] [--no-annotations] [(-o <pathToOldVersionJar> | --old <pathToOldVersionJar>)] [--old-classpath <oldClassPath>] [--report-only-filename] [(-s | --semantic-versioning)] [(-x <pathToXml> | --xml-file <pathToXml>)] [--error-on-binary-incompatibility] [--error-on-source-incompatibility] [--error-on-modifications] [--no-error-on-exclusion-incompatibility]
  26. JAPICMP CLI $ java -jar japicmp-0.14.4-jar-with-dependencies.jar -h SYNOPSIS java -jar

    japicmp.jar [-a <accessModifier>] [(-b | --only-incompatible)] [(-e <excludes> | --exclude <excludes>)] [--exclude-exclusively] [(-h | --help)] [--html-file <pathToHtmlOutputFile>] [--html-stylesheet <pathToHtmlStylesheet>] [(-i <includes> | --include <includes>)] [--ignore-missing-classes] [--ignore-missing-classes-by-regex <ignoreMissingClassesByRegEx>...] [--include-exclusively] [--include-synthetic] [(-m | --only-modified)] [(-n <pathToNewVersionJar> | --new <pathToNewVersionJar>)] [--new-classpath <newClassPath>] [--no-annotations] [(-o <pathToOldVersionJar> | --old <pathToOldVersionJar>)] [--old-classpath <oldClassPath>] [--report-only-filename] [(-s | --semantic-versioning)] [(-x <pathToXml> | --xml-file <pathToXml>)] [--error-on-binary-incompatibility] [--error-on-source-incompatibility] [--error-on-modifications] [--no-error-on-exclusion-incompatibility]
  27. JAPICMP CLI $ java -jar japicmp-0.14.4-jar-with-dependencies.jar -h SYNOPSIS java -jar

    japicmp.jar [-a <accessModifier>] [(-b | --only-incompatible)] [(-e <excludes> | --exclude <excludes>)] [--exclude-exclusively] [(-h | --help)] [--html-file <pathToHtmlOutputFile>] [--html-stylesheet <pathToHtmlStylesheet>] [(-i <includes> | --include <includes>)] [--ignore-missing-classes] [--ignore-missing-classes-by-regex <ignoreMissingClassesByRegEx>...] [--include-exclusively] [--include-synthetic] [(-m | --only-modified)] [(-n <pathToNewVersionJar> | --new <pathToNewVersionJar>)] [--new-classpath <newClassPath>] [--no-annotations] [(-o <pathToOldVersionJar> | --old <pathToOldVersionJar>)] [--old-classpath <oldClassPath>] [--report-only-filename] [(-s | --semantic-versioning)] [(-x <pathToXml> | --xml-file <pathToXml>)] [--error-on-binary-incompatibility] [--error-on-source-incompatibility] [--error-on-modifications] [--no-error-on-exclusion-incompatibility] [--error-on-semantic-incompatibility]
  28. JAPICMP CLI $ java -jar japicmp-0.14.4-jar-with-dependencies.jar -h SYNOPSIS java -jar

    japicmp.jar [-a <accessModifier>] [(-b | --only-incompatible)] [(-e <excludes> | --exclude <excludes>)] [--exclude-exclusively] [(-h | --help)] [--html-file <pathToHtmlOutputFile>] [--html-stylesheet <pathToHtmlStylesheet>] [(-i <includes> | --include <includes>)] [--ignore-missing-classes] [--ignore-missing-classes-by-regex <ignoreMissingClassesByRegEx>...] [--include-exclusively] [--include-synthetic] [(-m | --only-modified)] [(-n <pathToNewVersionJar> | --new <pathToNewVersionJar>)] [--new-classpath <newClassPath>] [--no-annotations] [(-o <pathToOldVersionJar> | --old <pathToOldVersionJar>)] [--old-classpath <oldClassPath>] [--report-only-filename] [(-s | --semantic-versioning)] [(-x <pathToXml> | --xml-file <pathToXml>)] [--error-on-binary-incompatibility] [--error-on-source-incompatibility] [--error-on-modifications] [--no-error-on-exclusion-incompatibility]
  29. JAPICMP CLI java -jar japicmp-0.14.4-jar-with-dependencies.jar \ --old library-3.3.0/classes.jar \ --new

    library-3.4.0/classes.jar \ --html-file index.html \ --include “com.chuckerteam.chucker.api” \ --include-exclusively
  30. JAPICMP CLI java -jar japicmp-0.14.4-jar-with-dependencies.jar \ --old library-3.3.0/classes.jar \ --new

    library-3.4.0/classes.jar \ --html-file index.html \ --include “com.chuckerteam.chucker.api” \ --include-exclusively java -jar japicmp-0.14.4-jar-with-dependencies.jar \ --old library-3.3.0/classes.jar \ --new library-3.4.0/classes.jar \ --html-file index.html \
  31. JAPICMP CLI java -jar japicmp-0.14.4-jar-with-dependencies.jar \ --old library-3.3.0/classes.jar \ --new

    library-3.4.0/classes.jar \ --html-file index.html \ --include “com.chuckerteam.chucker.api” \ --include-exclusively java -jar japicmp-0.14.4-jar-with-dependencies.jar \ --old library-3.3.0/classes.jar \ --new library-3.4.0/classes.jar \ --html-file index.html \ --ignore-missing-classes \
  32. JAPICMP CLI java -jar japicmp-0.14.4-jar-with-dependencies.jar \ --old library-3.3.0/classes.jar \ --new

    library-3.4.0/classes.jar \ --html-file index.html \ --include “com.chuckerteam.chucker.api” \ --include-exclusively java -jar japicmp-0.14.4-jar-with-dependencies.jar \ --old library-3.3.0/classes.jar \ --new library-3.4.0/classes.jar \ --html-file index.html \ --ignore-missing-classes \ --no-annotations \
  33. JAPICMP CLI java -jar japicmp-0.14.4-jar-with-dependencies.jar \ --old library-3.3.0/classes.jar \ --new

    library-3.4.0/classes.jar \ --html-file index.html \ --include “com.chuckerteam.chucker.api” \ --include-exclusively java -jar japicmp-0.14.4-jar-with-dependencies.jar \ --old library-3.3.0/classes.jar \ --new library-3.4.0/classes.jar \ --html-file index.html \ --ignore-missing-classes \ --no-annotations \ --exclude “com.chuckerteam.chucker.databinding; com.chuckerteam.chucker.internal"
  34. JAPICMP GRADLE SETUP apply plugin: "me.champeau.gradle.japicmp" configurations { latest baseline

    } dependencies { baseline project(path: ":library", configuration: "default") }
  35. JAPICMP GRADLE SETUP apply plugin: "me.champeau.gradle.japicmp" configurations { latest baseline

    } dependencies { baseline project(path: ":library", configuration: "default") latest("com.github.chuckerteam.chucker:library:3.4.0") }
  36. JAPICMP GRADLE SETUP apply plugin: "me.champeau.gradle.japicmp" configurations { latest baseline

    } dependencies { baseline project(path: ":library", configuration: "default") latest("com.github.chuckerteam.chucker:library:3.4.0") { transitive = false force = true } }
  37. apply plugin: "me.champeau.gradle.japicmp" configurations { latest baseline } dependencies {

    baseline project(path: ":library", configuration: "default") latest("com.github.chuckerteam.chucker:library:3.4.0") { transitive = false force = true } } tasks.register("japiCmp", JapicmpTask) { oldClasspath = configurations.latest newClasspath = configurations.baseline } JAPICMP GRADLE SETUP
  38. apply plugin: "me.champeau.gradle.japicmp" configurations { latest baseline } dependencies {

    baseline project(path: ":library", configuration: "default") latest("com.github.chuckerteam.chucker:library:3.4.0") { transitive = false force = true } } tasks.register("japiCmp", JapicmpTask) { oldClasspath = configurations.latest newClasspath = configurations.baseline htmlOutputFile = file("$buildDir/reports/japiCmp.html") } JAPICMP GRADLE SETUP
  39. apply plugin: "me.champeau.gradle.japicmp" configurations { latest baseline } dependencies {

    baseline project(path: ":library", configuration: "default") latest("com.github.chuckerteam.chucker:library:3.4.0") { transitive = false force = true } } tasks.register("japiCmp", JapicmpTask) { oldClasspath = configurations.latest newClasspath = configurations.baseline htmlOutputFile = file("$buildDir/reports/japiCmp.html") failOnModification = true } JAPICMP GRADLE SETUP
  40. apply plugin: "me.champeau.gradle.japicmp" configurations { latest baseline } dependencies {

    baseline project(path: ":library", configuration: "default") latest("com.github.chuckerteam.chucker:library:3.4.0") { transitive = false force = true } } tasks.register("japiCmp", JapicmpTask) { oldClasspath = configurations.latest newClasspath = configurations.baseline htmlOutputFile = file("$buildDir/reports/japiCmp.html") failOnModification = true ignoreMissingClasses = true } JAPICMP GRADLE SETUP
  41. configurations { latest baseline } dependencies { baseline project(path: ":library",

    configuration: "default") latest("com.github.chuckerteam.chucker:library:3.4.0") { transitive = false force = true } } tasks.register("japiCmp", JapicmpTask) { oldClasspath = configurations.latest newClasspath = configurations.baseline htmlOutputFile = file("$buildDir/reports/japiCmp.html") failOnModification = true ignoreMissingClasses = true packageIncludes = ["com.chuckerteam.chucker.api"] } JAPICMP GRADLE SETUP
  42. configurations { latest baseline } dependencies { baseline project(path: ":library",

    configuration: "default") latest("com.github.chuckerteam.chucker:library:3.4.0") { transitive = false force = true } } tasks.register("japiCmp", JapicmpTask) { oldClasspath = getJarClasses(project, configurations.latest) newClasspath = getJarClasses(project, configurations.baseline) htmlOutputFile = file("$buildDir/reports/japiCmp.html") failOnModification = true ignoreMissingClasses = true packageIncludes = ["com.chuckerteam.chucker.api"] } JAPICMP GRADLE SETUP
  43. latest("com.github.chuckerteam.chucker:library:3.4.0") { transitive = false force = true } }

    tasks.register("japiCmp", JapicmpTask) { oldClasspath = getJarClasses(project, configurations.latest) newClasspath = getJarClasses(project, configurations.baseline) htmlOutputFile = file("$buildDir/reports/japiCmp.html") failOnModification = true ignoreMissingClasses = true packageIncludes = ["com.chuckerteam.chucker.api"] } def getJarClasses(project, configuration) { return project.files( project.zipTree(configuration.files.first()) .matching {include "classes.jar"} .first() ) } JAPICMP GRADLE SETUP
  44. TO RECAP • binary-compatibility-validator • Safeguard your API during your

    workflow • Easier to integrate for Kotlin/Gradle projects • japicmp • Fine grain control over your API surface • Useful when making a new release • Be a good API citizen