kotlin / binary-compatibility-validator Goto Github PK
View Code? Open in Web Editor NEWPublic API management tool
License: Apache License 2.0
Public API management tool
License: Apache License 2.0
In a multi-module Gradle project, after running apiDump
, the file api/<projectName>.api
will appear with a blank line.
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)
}
The project uses a buildSrc
and all scripts are in kts.
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.
@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.
@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
Sealed classes cannot be externally extended, so protected functions are effectively internal.
Cannot add task 'apiBuild' as a task with that name already exists.
Steps:
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
The plugin uses eager API to configure dependency of check
task
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.
It will help us to track binary & source compatibility
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.
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
Validator generates api dumps for root module(s), e.g. for ktor root module it produces an empty ktor.api
file. The other example is ktor-features
module that doesn't have any code.
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
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
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
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.
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:
allprojects
configurationallprojects
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.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.
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
}
Currently, the plugin will ignore any subprojects that are built with the Android Gradle Plugin.
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.
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.
For now, plugin only allows to ignore some packages, projects, classes and classes annotated with some annotation.
But some projects may want to include classes in Public API, if they are annotated with some annotation, that marks it's explicitly public.
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β¦
Kind of an edge case but something like this:
// -foo.kt
internal fun doStuff() {}
will show up in apiDump:
public final class -fooKt { }
I think since the code is not reachable from neither Java nor Kotlin, this is technically not public API?
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.
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
.
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
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.
Inline functions work as expected, but if they also have reified type parameters, they don't show up in the API dump output (presumably because there isn't a regular, callable method generated, just a synthetic one)
An example project showcasing this is available here, see the Main.kt
and kotlin-gradle-starter.api
files for the discrepancy.
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.
So that annotations like InternalCoroutinesApi
can be configured to be ignored from the dump.
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.
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.
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]
Looks like the plugin only gets applied to Android modules if they have a compilationTarget matching release
.
This makes it unusable if you have library flavors. Are there any plans to support multi flavored modules in the future? Would also be nice if you could notice this limitation in the docs.
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:
apiDump
I have these classes in the same package
internal class SubMsgType0xc1
internal class Submsgtype0xc1 {
internal class Submsgtype0xc1 : ProtoBuf {
// ...
}
}
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
If I have a class declared like:
package pbandk
class Foo internal constructor() {
internal companion object {
...
}
}
The API dump is incorrectly showing the Companion
field as part of the public API:
public final class pbandk/Foo {
public static final field Companion Lpbandk/Foo$Companion;
}
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.
When monitoring public API, I can think of two use cases:
module.api
files are always up-to-date and represent the latest state of the public API.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?
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:
minor
change as a non-breaking change. apiCheck
should succeedThe 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.
It gets automatically created by java-gradle-plugin
via gradlePlugin { ... }
DSL.
This allows users watching releases to be notified.
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 :)
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.
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.
(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?
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.
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.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. πππ
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google β€οΈ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.