GithubHelp home page GithubHelp logo

kotlin / binary-compatibility-validator Goto Github PK

View Code? Open in Web Editor NEW
729.0 23.0 53.0 1.26 MB

Public API management tool

License: Apache License 2.0

Kotlin 99.48% Java 0.52%
kotlin api-management binary-compatibility change-management

binary-compatibility-validator's People

Contributors

3flex avatar alikhachev avatar anoop44 avatar benedekh avatar bnorm avatar bryanlogan avatar chao2zhang avatar cortinico avatar dnpetrov avatar etolstoy avatar fzhinkin avatar goooler avatar ilya-g avatar joffrey-bion avatar kirpichenkovpavel avatar ligee avatar martinbonnin avatar movshin avatar pdvrieze avatar qwwdfsad avatar sandwwraith avatar sellmair avatar sergejisbrecht avatar twyatt avatar udalov avatar xtrm-en avatar ychescale9 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

binary-compatibility-validator's Issues

Support Android projects

Currently, the plugin will ignore any subprojects that are built with the Android Gradle Plugin.

Nested classes is present in dump when outer class is annotated with nonPublicMarker

apiValidation {
    nonPublicMarkers += ["com.example.InternalApi"]
}
package com.example

@InternalApi
class Example {
    class Nested {
        fun foo() {}
    }
}

produces following dump:

public final class com/example/Example$Nested {
	public fun <init> ()V
	public final fun foo ()V
}

In Kotlin, an opt-in annotations is propagated to an all class members, including a nested classes.
But binary-compatibility-validator filters only classes that are directly annotated with one of the annotations from nonPublicMarkers.

I think this behaviour should be aligned with opt-in annotations as they are often used as nonPublicMarkers.

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

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

Should override functions be included in the API spec?

Adding or removing an override function is binary compatible in Java so why are these functions included in the public API spec? I suppose they are technically public or protected but it does add some extra work when making binary compatible changes.

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.

Companion object 'const val' properties generate false positive if they have a non-public marker

The following:

@Target(AnnotationTarget.PROPERTY)
annotation class HiddenProperty

public class Foo {
    companion object {
        @HiddenProperty
        const val bar = "barValue"
    }
}

Generates the following pseudo Java code:

public final class Foo {
   @NotNull
   public static final String bar = "barValue";
   @NotNull
   public static final Companion Companion = new Companion((DefaultConstructorMarker)null);

   public static final class Companion {
      // $FF: synthetic method
      @HiddenProperty
      public static void getBar$annotations() {
      }

      private Companion() {
      }
   }
}

Because the synthetic method carrying the annotations is not in the same class as the field, it is ignored and the field is marked as public API even though it shouldn't.

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.

Support ignoring groups of projects

For example, using wildcards. I have a library with a samples/ directory that contains a bunch of subprojects that aren't published to Maven and don't have any public API, and it would be nice to be able to exclude everything in that directory with a single configuration.

Inconsistent Gradle behavior across different Java versions

I have a simple setup in this PR: ZacSweers/redacted-compiler-plugin#39

apply plugin: "binary-compatibility-validator"

apiValidation {
    ignoredProjects += ["sample", "sample-android"]
}

With JDK 8, the sample-android project is seen just fine. With JDK 11+, it fails to find it for some reason.

* What went wrong:
A problem occurred configuring root project 'redacted-compiler-plugin'.
> Cannot find excluded project sample-android in all projects: [redacted-compiler-plugin, redacted-compiler-plugin, redacted-compiler-plugin-annotations, sample]

Plugin marker is missing on Gradle Portal

A follow up from #44

I suppose the plugin is now published on Gradle Portal πŸŽ‰

However, it looks like the publishing is missing the Gradle marker. The only way to use this plugin with the plugins{} block that you mention in the README is by providing a rule in the resolutionStrategy on the settings.gradle.kts file:

pluginManagement {
    resolutionStrategy {
        eachPlugin {
            if (requested.id.id == "binary-compatibility-validator") {
                useModule("org.jetbrains.kotlinx:binary-compatibility-validator:${requested.version}")
            }
        }
    }
}

You can find a real world example here: https://github.com/cortinico/ktfmt-gradle/blob/b6f8a55bfd8334dccacb7f28335176534cb8ee27/plugin-build/settings.gradle.kts#L2-L8

I'm not entirely sure what is happening as it seems like the plugin is actually on the Gradle portal here: https://plugins.gradle.org/m2/org/jetbrains/kotlinx/binary-compatibility-validator/0.4.0/

While the WebUI will show just a blank page, the artifacts will still resolve correctly.

Can I ask you to clarify how you published the plugin on Gradle Portal? Also having a badge on the readme would be useful :)

Build fails if api is not dumped

The build fails with non-informative error (directory not found) if the initial API dump is not done before the build.
I think that the default behavior should be to give a warning and ignore the plugin altogether.

`apiDump` task is always in `SKIPPED` state when plugin applied on Android project.

This check is always false https://github.com/Kotlin/binary-compatibility-validator/blob/master/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt#L87 for Android project.
This is because source files are located in src/main/java or src/main/kotlin by convention, but sourceSets list points only to src/release/kotlin and src/release/java.
There's no KotlinJvmAndroidCompilation with name main.

If I remove compilation.allKotlinSourceSets.any { it.kotlin.srcDirs.any { it.exists() } } check, API dump is generated.

Add support of semantic minor change

I am building SDKs and follow semantic versioning. It would be nice to have an option to detect the minor API update during apiCheck command. At this moment the added class would fail the apiCheck command.

But from SDK consumer point of view this change is compatible with the previous version because it does not require any effort in order to update to it.

Feature request:

  • add an option to mark minor change as a non-breaking change. apiCheck should succeed
  • print the added API to the console in this case

private constructor shows up in API dump

The following code:

class FooBar private constructor(val id: String) {
  class Builder {
    fun build() = FooBar("")
  }
}

dumps the following API:

public final class com/apollographql/apollo3/api/FooBar {
	public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
	public final fun getId ()Ljava/lang/String;
}

public final class com/apollographql/apollo3/api/FooBar$Builder {
	public fun <init> ()V
	public final fun build ()Lcom/apollographql/apollo3/api/FooBar;
}

I'm curious as to why <init> is listed in the dump. If I were to remove the id parameter, would that be considered a breaking change even if it should be an implementation detail?

I'm using 0.8.0-RC

module-info.java in compilation source causes IllegalStateException

Reproducer: Kotlin/kotlinx.serialization#1624
Exception:

Caused by: java.lang.IllegalStateException: superName must not be null
        at kotlinx.validation.api.KotlinSignaturesLoadingKt$loadApiFromJvmClasses$4.invoke(KotlinSignaturesLoading.kt:56)
        at kotlinx.validation.api.KotlinSignaturesLoadingKt$loadApiFromJvmClasses$4.invoke(KotlinSignaturesLoading.kt)
        at kotlin.sequences.TransformingSequence$iterator$1.next(Sequences.kt:172)
        at kotlin.collections.CollectionsKt___CollectionsKt.toCollection(_Collections.kt:1206)
        at kotlin.collections.CollectionsKt___CollectionsKt.toMutableList(_Collections.kt:1239)
        at kotlin.collections.CollectionsKt___CollectionsKt.sortedWith(_Collections.kt:988)
        at kotlinx.validation.api.KotlinSignaturesLoadingKt.loadApiFromJvmClasses(KotlinSignaturesLoading.kt:162)
        at kotlinx.validation.api.KotlinSignaturesLoadingKt.loadApiFromJvmClasses$default(KotlinSignaturesLoading.kt:20)
        at kotlinx.validation.KotlinApiBuildTask.generate(KotlinApiBuildTask.kt:49)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:104)
        at org.gradle.api.internal.project.taskfactory.StandardTaskAction.doExecute(StandardTaskAction.java:58)

Cause:
https://github.com/Kotlin/binary-compatibility-validator/blob/master/src/main/kotlin/api/KotlinSignaturesLoading.kt#L41
superName is null for module-info class. ASM and validator do not expect that.
Possible solutions: make superName nullable or just filter out such classes.

Note that visibilityFilter is actually not used anywhere, filtering by fq name (ignoredClasses) happens after loading ASM.

Allow applying the plugin to a subproject

At the moment, the plugin must be applied to the root project. Otherwise, you get Extension of type 'ApiValidationExtension' does not exist error during the root project configuration. In my case, there is a Gradle multi-project with a hierarchy of hundreds of sub-projects and I want to validate only a single one. It seems to be quite natural to apply the plugin only to this single subproject.

As a workaround, I can apply the plugin to the root project and add everything to the ignoredProjects except the needed project. It works but doesn't look nice.

Non-public markers aren't handled correctly for functions with default parameters

When declaring an opt-in annotation and adding that annotation to the nonPublicMarkers configuration, functions that have default parameters are not correctly excluded from the api dump - the generated $default function is still included. A very similar issue to the one fixed in #30.

Example annotation:

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

An example function:

@MyInternalApi
fun defParamInternalFun(x: Int = 0) {}

The incorrect API dump:

public final class co/zsmb/example/MainKt {
	public static synthetic fun defParamInternalFun$default (IILjava/lang/Object;)V
}

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

introduce ignoredClasses in gradle plugin

Type: Feat. request

Issue:
Android-Projects create a BuildConfig for android-libraries, which is public. It is not possible to exclude the package, because other needed classes are in given package. Also it is not possible to use an annotation, because it is generated.

Suggestion:
Introduce new ignoredClasses flag in gradle

Code

ignoredClasses += ["com.my.package.MyClazz"]

Note:
When this feat. is approved, I would implement it quickly and create a PR.

Is an added 'synthetic' considered to be a compatible change?

(This is not a bug, just a question)
To one of my functions, a synthetic was added in the .api file.

Before:
public final fun myFunction (Ljava/util/List;)I
After:
public final synthetic fun myFunction (Ljava/util/List;)I

Would this be considered to be a non-breaking change?

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

Support API comparison with published artifact

Hi! Thanks for publishing this as separate plugin!

In our team, we use binary-compatibility-validator from Kotlin repository for internal shared libraries but suggested workflow (committing .api files to repository) is too strict for us, so instead we implemented comparison with the latest published maven artifact ${project.group}:${project.name}:latest.release.

At this time we just post an API diff to PRs, but we also have a plans to check only for a binary-incompatible changes by comparing ClassBinarySignature/MemberBinarySignature contents.

It would be great if similar functionality available in this plugin.

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?

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.

Unable to apply to ktor

Cannot add task 'apiBuild' as a task with that name already exists.

Steps:

  1. Checkout ktor master
  2. Apply the following patch
Index: build.gradle
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- build.gradle	(revision e16afa03df5721646c7daa4233fe980c6c1ca391)
+++ build.gradle	(date 1583323513139)
@@ -54,6 +54,7 @@
         classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
         classpath "me.champeau.gradle:jmh-gradle-plugin:$jmh_plugin_version"
         classpath "org.jetbrains.kotlinx:kotlinx.benchmark.gradle:$benchmarks_version"
+        classpath "org.jetbrains.kotlinx:binary-compatibility-validator:$validator_version"
     }
 }
 
@@ -95,6 +96,8 @@
     }
 }
 
+apply plugin: 'binary-compatibility-validator'
+
 allprojects {
     group = "io.ktor"
     version = configuredVersion
Index: gradle.properties
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- gradle.properties	(revision e16afa03df5721646c7daa4233fe980c6c1ca391)
+++ gradle.properties	(date 1583323144984)
@@ -22,6 +22,7 @@
 serialization_version=0.14.0
 coroutines_version=1.3.3
 atomicfu_version=0.14.1
+validator_version=0.2.1
 
 # server
 netty_version=4.1.44.Final

Support public external JS declarations

Currently, the validator does not create .api files or api declarations for public external declarations, which results into not checking the JS declarations, which could be called from library consumer in Kotlin.

Allow ignoring project by path

I have a project where multiple modules have the same name but different path (:feature1:sample, :feature2:sample) and currently I cannot ignore just one of them since you specify the name of the module which in both cases is sample.

This could be done in a backwards compatible way by checking if the ignored project starts with : and if so project.path is used instead.

Ignore synthetic annotation classes produced by directly instantiated annotations

In Kotlin 1.6.0, synthetic annotation classes are present in the API dump and I think they should be automatically excluded.

I can manually ignore them, but it's tedious and prone to breaking due to the mangled names.

Example from here: slackhq/EitherNet#29

public synthetic class com/slack/eithernet/AnnotationsKt$annotationImpl$com_slack_eithernet_StatusCode$0 : com/slack/eithernet/StatusCode {
	public fun <init> (I)V
	public final synthetic fun annotationType ()Ljava/lang/Class;
	public final fun equals (Ljava/lang/Object;)Z
	public final fun hashCode ()I
	public final fun toString ()Ljava/lang/String;
	public final synthetic fun value ()I
}

With kotlin 1.5.0: internal and inline definitions are included in api dump

Kotlin version: 1.5.0
BCV version: 0.2.3

It seems that when this tool is used with Kotlin 1.5.0, inline functions and internal classes and functions are included in the api dumps. This was not happening before, and seems incorrect - internal things aren't part of a library's public api, that's the whole point of internal.

See this PR for an example: square/workflow-kotlin#417 - the PR simply updates the kotlin version (and some core kotlin libraries), and there's a huge number of lines of diff for the .api files.

I just realized that project is still using a much older version of this tool, updating first to see if that fixes it…

Prevent `apiCheck` task fail until tests are finished.

We often run CI integration tests without running apiDump. The apiCheck task can fail the build without running tests or showing the results.

Please consider delaying running(or failing) apiCheck task until tests are finished so we can observe test results.

Internal class ComposeSingletons is presented in the api dump

I'm using kotlin 1.5.31 with compose 1.0.5.

My api dump contains a lot of following classes

public final class cloud/jablotron/apps/lib/auth/oauth2/ui/screens/login/ComposableSingletons$LoginScreenKt {
	public static final field INSTANCE Lcloud/jablotron/apps/lib/auth/oauth2/ui/screens/login/ComposableSingletons$LoginScreenKt;
	public static field lambda-1 Lkotlin/jvm/functions/Function3;
	public static field lambda-2 Lkotlin/jvm/functions/Function2;
	public fun <init> ()V
	public final fun getLambda-1$library_release ()Lkotlin/jvm/functions/Function3;
	public final fun getLambda-2$library_release ()Lkotlin/jvm/functions/Function2;
}

I'm not sure whether the issue is in this library or in the compose.

After brief look at the compose code, I assume that these classes should be internal so they shouldn't be presented in the api dump. Since compose code is not generated and is not possible to take a look at the code. It is really hard to get rid of these. Do you think that the issue is in the library? Or do you have an idea how to get rid of it?
https://github.com/androidx/androidx/blob/4356507bc51920e48d01612f4e6f69f907055456/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/LambdaMemoizationRegressionTests.kt

Thanks

Is `runtime` vs `compile` time scope in a POM considered?

Our use case is that we have a multi-module project that we ship as an SDK - so many modules in it are effectively public - but our "clients" consume from only a specific module point - so many of those public classes in upstream projects are not on the compile (in the .pom) scope, but in the runtime scope.

We make breaking changes to our runtime API frequently, but can we use this tool to limit changes to our compile API as our SDK is consumed from the point of view of a specific module?

Example:

:a - consumed by client
:b - consumed by :a as an implementation dependency
:c - consumed by :a as an implementation dependency
:d - consumed by :a as an api dependency

We want to be notified of breaking public API changes to :a and :d, but not :b and :c

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 - slackhq/EitherNet#52. Fails on CI with a strange check that suggests it's expecting a subproject, but it's a single-project repo only.

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.

Execution failed for task 'apiBuild'.

Execution failed for task ':mirai-core-qqandroid:apiBuild'.
> Key net/mamoe/mirai/qqandroid/network/protocol/data/proto/SubMsgType0xc1 is missing in the map.

To reproduce:

  1. Check out https://github.com/mamoe/mirai
  2. Run task apiDump

I have these classes in the same package

internal class SubMsgType0xc1 

internal class Submsgtype0xc1 {
    internal class Submsgtype0xc1 : ProtoBuf {
         // ...
    }
}

Add an option to disable `allprojects` behavior.

allprojects is considered a bad practice in Gradle and it will break future features like project isolation.

Should be great if one of these options was available before the final release:

  • Add a flag to disable allprojects configuration
  • Remove allprojects configuration and force it to apply to each project individually. To avoid duplicating code, the recommended practice nowadays is using convention plugins, so a simple example can be added to the readme.

apiDump task incompatible with configuration cache

FAILURE: Build failed with an exception.

* What went wrong:
Configuration cache problems found in this build.

10 problems were found storing the configuration cache.
- Task `:detekt-api:apiDump` of type `org.gradle.api.tasks.Sync`: cannot serialize object of type 'kotlinx.validation.KotlinApiBuildTask', a subtype of 'org.gradle.api.Task', as these are not supported with the configuration cache.
  See https://docs.gradle.org/7.5/userguide/configuration_cache.html#config_cache:requirements:task_access
- Task `:detekt-api:apiDump` of type `org.gradle.api.tasks.Sync`: cannot serialize object of type 'org.gradle.api.internal.project.DefaultProject', a subtype of 'org.gradle.api.Project', as these are not supported with the configuration cache.
  See https://docs.gradle.org/7.5/userguide/configuration_cache.html#config_cache:requirements:disallowed_types

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.