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

There is 1 Broken API Among Us

3dc29e8cfc6ef333e2b41a1b0e826b57?s=47 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.

3dc29e8cfc6ef333e2b41a1b0e826b57?s=128

Nicola Corti

November 29, 2020
Tweet

More Decks by Nicola Corti

Other Decks in Technology

Transcript

  1. THERE IS 1 BROKEN API AMONG US @CORTINICO

  2. NICOLA CORTI Kotlin GDE Android Infra Eng. @ Spotify twitter.com/cortinico

    github.com/cortinico ncorti.com
  3. WHO IS THIS TALK FOR?

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

    Devs • Open Source Devs • Mobile Devs in a Modularized world
  5. GOOD API CITIZENS

  6. GOOD API CITIZENS @Deprecate

  7. GOOD API CITIZENS @Deprecate fun replace(with: String)

  8. GOOD API CITIZENS @Deprecate @Deprecated fun replace(with: String)

  9. GOOD API CITIZENS @Deprecate @Deprecated( message = "This method is

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

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

    affected by a bug", replaceWith = ReplaceWith("newReplace(with)"), level = DeprecationLevel.ERROR ) fun replace(with: String)
  12. GOOD API CITIZENS @OptIn

  13. GOOD API CITIZENS @OptIn fun getAnswer() = 42

  14. GOOD API CITIZENS @OptIn @MyExperimentalApi fun getAnswer() = 42

  15. GOOD API CITIZENS @OptIn @RequiresOptIn(message = "This API is experimental")

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

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

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

    annotation class MyExperimentalApi @MyExperimentalApi fun getAnswer() = 42 @OptIn(MyExperimentalApi::class) fun main() { println(getAnswer()) }
  19. GOOD API CITIZENS Explicit API Mode kotlin { explicitApiWarning() }

  20. GOOD API CITIZENS Explicit API Mode kotlin { explicitApi() }

  21. GOOD API CITIZENS Explicit API Mode kotlin { explicitApi() }

    fun getAnswer() = 42
  22. GOOD API CITIZENS Explicit API Mode kotlin { explicitApi() }

    fun getAnswer() = 42.0
  23. GOOD API CITIZENS Explicit API Mode kotlin { explicitApi() }

    public fun getAnswer() : Int = 42
  24. GOOD API CITIZENS @SemVer

  25. GOOD API CITIZENS @SemVer MAJOR.

  26. GOOD API CITIZENS @SemVer MAJOR.MINOR.

  27. GOOD API CITIZENS @SemVer MAJOR.MINOR.PATCH

  28. GOOD API CITIZENS @SemVer MAJOR.MINOR.PATCH

  29. GOOD API CITIZENS @SemVer MAJOR.MINOR.PATCH

  30. GOOD API CITIZENS @SemVer MAJOR.MINOR.PATCH

  31. BINARY COMPATIBILITY VALIDATOR

  32. BINARY COMPATIBILITY VALIDATOR

  33. BINARY COMPATIBILITY VALIDATOR

  34. BINARY COMPATIBILITY VALIDATOR Setup

  35. BINARY COMPATIBILITY VALIDATOR buildscript { dependencies { classpath("org.jetbrains.kotlinx:binary-compatibility-validator:0.2.4") } }

    apply plugin: "binary-compatibility-validator" Setup
  36. BINARY COMPATIBILITY VALIDATOR buildscript { dependencies { classpath("org.jetbrains.kotlinx:binary-compatibility-validator:0.2.4") } }

    apply plugin: "binary-compatibility-validator" Setup apiValidation { }
  37. BINARY COMPATIBILITY VALIDATOR buildscript { dependencies { classpath("org.jetbrains.kotlinx:binary-compatibility-validator:0.2.4") } }

    apply plugin: "binary-compatibility-validator" Setup apiValidation { ignoredProjects += ["sample"] }
  38. 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"] }
  39. 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"] }
  40. 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
  41. BINARY COMPATIBILITY VALIDATOR .api File

  42. 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 {
  43. 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 {
  44. 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 {
  45. 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 {
  46. 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 {
  47. 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 {
  48. 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 {
  49. BINARY COMPATIBILITY VALIDATOR Normal Workflow $ ./gradlew apiCheck

  50. BINARY COMPATIBILITY VALIDATOR Normal Workflow $ ./gradlew apiCheck # OR

    $ ./gradlew check
  51. BINARY COMPATIBILITY VALIDATOR Normal Workflow $ ./gradlew apiCheck # OR

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

    $ ./gradlew check # Computed API has not changed BUILD SUCCESSFUL # Computed API has changed BUILD FAILED
  53. BINARY COMPATIBILITY VALIDATOR Non-Breaking

  54. BINARY COMPATIBILITY VALIDATOR Breaking

  55. JAPICMP

  56. JAPICMP

  57. JAPICMP

  58. JAPICMP search.maven.org/artifact/com.github.siom79.japicmp/japicmp Download

  59. 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]
  60. 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]
  61. 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]
  62. 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]
  63. 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]
  64. 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]
  65. 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]
  66. JAPICMP CLI java -jar japicmp-0.14.4-jar-with-dependencies.jar \

  67. JAPICMP CLI java -jar japicmp-0.14.4-jar-with-dependencies.jar \ --old library-3.3.0/classes.jar \

  68. 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 \
  69. 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 \
  70. 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
  71. 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 \
  72. 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 \
  73. 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 \
  74. 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"
  75. JAPICMP HTML REPORT

  76. JAPICMP HTML REPORT

  77. JAPICMP HTML REPORT

  78. JAPICMP HTML REPORT

  79. JAPICMP HTML REPORT

  80. JAPICMP HTML REPORT

  81. JAPICMP HTML REPORT

  82. JAPICMP HTML REPORT

  83. JAPICMP HTML REPORT

  84. JAPICMP HTML REPORT

  85. JAPICMP GRADLE SETUP apply plugin: "me.champeau.gradle.japicmp"

  86. JAPICMP GRADLE SETUP apply plugin: "me.champeau.gradle.japicmp" configurations { latest baseline

    }
  87. JAPICMP GRADLE SETUP apply plugin: "me.champeau.gradle.japicmp" configurations { latest baseline

    } dependencies { }
  88. JAPICMP GRADLE SETUP apply plugin: "me.champeau.gradle.japicmp" configurations { latest baseline

    } dependencies { baseline project(path: ":library", configuration: "default") }
  89. 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") }
  90. 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 } }
  91. 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
  92. 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
  93. 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
  94. 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
  95. 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
  96. 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
  97. 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
  98. TO RECAP

  99. TO RECAP Evolving APIs is HARD

  100. TO RECAP Evolving APIs is HARD

  101. TO RECAP Evolving APIs is HARD

  102. TO RECAP Evolving APIs is HARD

  103. 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
  104. THANKS @CORTINICO