From ddc59956f59e08708a140a3e0d39e98241a906df Mon Sep 17 00:00:00 2001 From: Timo Stamm Date: Wed, 2 Oct 2024 13:47:22 +0200 Subject: [PATCH 1/5] Generate @Deprecated annotations Signed-off-by: Timo Stamm --- .../buf/deprecation/v1/fle_deprecated.proto | 27 ++++++++++ .../deprecation/v1/method_deprecated.proto | 27 ++++++++++ .../deprecation/v1/service_deprecated.proto | 26 ++++++++++ .../connectrpc/protocgen/connect/Generator.kt | 52 ++++++++++++++++++- 4 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 protoc-gen-connect-kotlin/proto/buf/deprecation/v1/fle_deprecated.proto create mode 100644 protoc-gen-connect-kotlin/proto/buf/deprecation/v1/method_deprecated.proto create mode 100644 protoc-gen-connect-kotlin/proto/buf/deprecation/v1/service_deprecated.proto diff --git a/protoc-gen-connect-kotlin/proto/buf/deprecation/v1/fle_deprecated.proto b/protoc-gen-connect-kotlin/proto/buf/deprecation/v1/fle_deprecated.proto new file mode 100644 index 00000000..1928113e --- /dev/null +++ b/protoc-gen-connect-kotlin/proto/buf/deprecation/v1/fle_deprecated.proto @@ -0,0 +1,27 @@ +// Copyright 2022-2023 The Connect Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package buf.deprecation.v1; + +option deprecated = true; + +message DeprecatedByFileRequest {} + +message DeprecatedByFileResponse {} + +service FileDeprecatedService { + rpc DeprecatedByFile(DeprecatedByFileRequest) returns (DeprecatedByFileResponse) {} +} diff --git a/protoc-gen-connect-kotlin/proto/buf/deprecation/v1/method_deprecated.proto b/protoc-gen-connect-kotlin/proto/buf/deprecation/v1/method_deprecated.proto new file mode 100644 index 00000000..2ed11748 --- /dev/null +++ b/protoc-gen-connect-kotlin/proto/buf/deprecation/v1/method_deprecated.proto @@ -0,0 +1,27 @@ +// Copyright 2022-2023 The Connect Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package buf.deprecation.v1; + +message DeprecatedMethodRequest {} + +message DeprecatedMethodResponse {} + +service MethodDeprecatedService { + rpc DeprecatedMethod(DeprecatedMethodRequest) returns (DeprecatedMethodResponse) { + option deprecated = true; + } +} diff --git a/protoc-gen-connect-kotlin/proto/buf/deprecation/v1/service_deprecated.proto b/protoc-gen-connect-kotlin/proto/buf/deprecation/v1/service_deprecated.proto new file mode 100644 index 00000000..83f5abb3 --- /dev/null +++ b/protoc-gen-connect-kotlin/proto/buf/deprecation/v1/service_deprecated.proto @@ -0,0 +1,26 @@ +// Copyright 2022-2023 The Connect Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package buf.deprecation.v1; + +message DeprecatedByServiceRequest {} + +message DeprecatedByServiceResponse {} + +service ServiceDeprecatedService { + option deprecated = true; + rpc DeprecatedByService(DeprecatedByServiceRequest) returns (DeprecatedByServiceResponse) {} +} diff --git a/protoc-gen-connect-kotlin/src/main/kotlin/com/connectrpc/protocgen/connect/Generator.kt b/protoc-gen-connect-kotlin/src/main/kotlin/com/connectrpc/protocgen/connect/Generator.kt index f7ede2f6..c8214d88 100644 --- a/protoc-gen-connect-kotlin/src/main/kotlin/com/connectrpc/protocgen/connect/Generator.kt +++ b/protoc-gen-connect-kotlin/src/main/kotlin/com/connectrpc/protocgen/connect/Generator.kt @@ -36,6 +36,7 @@ import com.google.protobuf.DescriptorProtos.FileDescriptorProto import com.google.protobuf.DescriptorProtos.MethodOptions.IdempotencyLevel import com.google.protobuf.Descriptors import com.google.protobuf.compiler.PluginProtos +import com.squareup.kotlinpoet.AnnotationSpec import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.CodeBlock import com.squareup.kotlinpoet.FileSpec @@ -122,7 +123,7 @@ class Generator : CodeGenerator { .addFileComment("Code generated by connect-kotlin. DO NOT EDIT.\n") .addFileComment("\n") .addFileComment("Source: ${file.name}\n") - .addType(serviceClientInterface(packageName, service, sourceInfo)) + .addType(serviceClientInterface(packageName, service, file, sourceInfo)) .build() fileSpecs[serviceClientInterfaceClassName(packageName, service)] = interfaceFileSpec @@ -133,7 +134,7 @@ class Generator : CodeGenerator { .addFileComment("\n") .addFileComment("Source: ${file.name}\n") // Set the file package for the generated methods. - .addType(serviceClientImplementation(packageName, service, sourceInfo)) + .addType(serviceClientImplementation(packageName, service, file, sourceInfo)) for (method in service.methods) { if (method.options.hasIdempotencyLevel()) { implementationFileSpecBuilder.addImport(Idempotency::class.java.`package`.name, "Idempotency") @@ -149,11 +150,13 @@ class Generator : CodeGenerator { private fun serviceClientInterface( packageName: String, service: Descriptors.ServiceDescriptor, + file: Descriptors.FileDescriptor, sourceInfo: SourceInfo, ): TypeSpec { val interfaceBuilder = TypeSpec.interfaceBuilder(serviceClientInterfaceClassName(packageName, service)) val functionSpecs = interfaceMethods(service.methods, sourceInfo) return interfaceBuilder + .addServiceDeprecation(service, file) .addKdoc(sourceInfo.comment().sanitizeKdoc()) .addFunctions(functionSpecs) .build() @@ -176,6 +179,7 @@ class Generator : CodeGenerator { if (method.isClientStreaming && method.isServerStreaming) { val streamingBuilder = FunSpec.builder(method.name.lowerCamelCase()) .addKdoc(sourceInfo.comment().sanitizeKdoc()) + .addMethodDeprecation(method) .addModifiers(KModifier.ABSTRACT) .addModifiers(KModifier.SUSPEND) .addParameter(headerParameterSpec) @@ -187,6 +191,7 @@ class Generator : CodeGenerator { } else if (method.isServerStreaming) { val serverStreamingFunction = FunSpec.builder(method.name.lowerCamelCase()) .addKdoc(sourceInfo.comment().sanitizeKdoc()) + .addMethodDeprecation(method) .addModifiers(KModifier.ABSTRACT) .addModifiers(KModifier.SUSPEND) .addParameter(headerParameterSpec) @@ -198,6 +203,7 @@ class Generator : CodeGenerator { } else if (method.isClientStreaming) { val clientStreamingFunction = FunSpec.builder(method.name.lowerCamelCase()) .addKdoc(sourceInfo.comment().sanitizeKdoc()) + .addMethodDeprecation(method) .addModifiers(KModifier.ABSTRACT) .addModifiers(KModifier.SUSPEND) .addParameter(headerParameterSpec) @@ -210,6 +216,7 @@ class Generator : CodeGenerator { if (configuration.generateCoroutineMethods) { val unarySuspendFunction = FunSpec.builder(method.name.lowerCamelCase()) .addKdoc(sourceInfo.comment().sanitizeKdoc()) + .addMethodDeprecation(method) .addModifiers(KModifier.ABSTRACT) .addModifiers(KModifier.SUSPEND) .addParameter("request", inputClassName) @@ -230,6 +237,7 @@ class Generator : CodeGenerator { ) val unaryCallbackFunction = FunSpec.builder(method.name.lowerCamelCase()) .addKdoc(sourceInfo.comment().sanitizeKdoc()) + .addMethodDeprecation(method) .addModifiers(KModifier.ABSTRACT) .addParameter("request", inputClassName) .addParameter(headerParameterSpec) @@ -256,6 +264,7 @@ class Generator : CodeGenerator { private fun serviceClientImplementation( javaPackageName: String, service: Descriptors.ServiceDescriptor, + file: Descriptors.FileDescriptor, sourceInfo: SourceInfo, ): TypeSpec { // The javaPackageName is used instead of the package name for imports and code references. @@ -274,6 +283,7 @@ class Generator : CodeGenerator { val functionSpecs = implementationMethods(service.methods, sourceInfo) return classBuilder .addKdoc(sourceInfo.comment().sanitizeKdoc()) + .addServiceDeprecation(service, file) .addFunctions(functionSpecs) .build() } @@ -318,6 +328,7 @@ class Generator : CodeGenerator { if (method.isClientStreaming && method.isServerStreaming) { val streamingFunction = FunSpec.builder(method.name.lowerCamelCase()) .addKdoc(sourceInfo.comment().sanitizeKdoc()) + .addMethodDeprecation(method) .addModifiers(KModifier.OVERRIDE) .addModifiers(KModifier.SUSPEND) .addParameter("headers", HEADERS_CLASS_NAME) @@ -344,6 +355,7 @@ class Generator : CodeGenerator { } else if (method.isServerStreaming) { val serverStreamingFunction = FunSpec.builder(method.name.lowerCamelCase()) .addKdoc(sourceInfo.comment().sanitizeKdoc()) + .addMethodDeprecation(method) .addModifiers(KModifier.OVERRIDE) .addModifiers(KModifier.SUSPEND) .addParameter("headers", HEADERS_CLASS_NAME) @@ -366,6 +378,7 @@ class Generator : CodeGenerator { } else if (method.isClientStreaming) { val clientStreamingFunction = FunSpec.builder(method.name.lowerCamelCase()) .addKdoc(sourceInfo.comment().sanitizeKdoc()) + .addMethodDeprecation(method) .addModifiers(KModifier.OVERRIDE) .addModifiers(KModifier.SUSPEND) .addParameter("headers", HEADERS_CLASS_NAME) @@ -389,6 +402,7 @@ class Generator : CodeGenerator { if (configuration.generateCoroutineMethods) { val unarySuspendFunction = FunSpec.builder(method.name.lowerCamelCase()) .addKdoc(sourceInfo.comment().sanitizeKdoc()) + .addMethodDeprecation(method) .addModifiers(KModifier.SUSPEND) .addModifiers(KModifier.OVERRIDE) .addParameter("request", inputClassName) @@ -421,6 +435,7 @@ class Generator : CodeGenerator { ) val unaryCallbackFunction = FunSpec.builder(method.name.lowerCamelCase()) .addKdoc(sourceInfo.comment().sanitizeKdoc()) + .addMethodDeprecation(method) .addModifiers(KModifier.OVERRIDE) .addParameter("request", inputClassName) .addParameter("headers", HEADERS_CLASS_NAME) @@ -524,3 +539,36 @@ private fun String.packageToDirectory(): String { } return dir } + +private fun TypeSpec.Builder.addServiceDeprecation( + service: Descriptors.ServiceDescriptor, + file: Descriptors.FileDescriptor +): TypeSpec.Builder { + if (service.options.deprecated) { + this.addAnnotation( + AnnotationSpec.builder(Deprecated::class) + .addMember("%S", "The underlying service is marked deprecated.") + .build() + ) + } else if (file.options.deprecated) { + this.addAnnotation( + AnnotationSpec.builder(Deprecated::class) + .addMember("%S", "The underlying file is marked deprecated.") + .build() + ) + } + return this +} + +private fun FunSpec.Builder.addMethodDeprecation( + method: Descriptors.MethodDescriptor, +): FunSpec.Builder { + if (method.options.deprecated) { + this.addAnnotation( + AnnotationSpec.builder(Deprecated::class) + .addMember("%S", "The underlying service method is marked deprecated.") + .build() + ) + } + return this +} From e8f00fbc99566315875967c34d8cbe4f0d4a88bd Mon Sep 17 00:00:00 2001 From: Timo Stamm Date: Wed, 2 Oct 2024 14:07:03 +0200 Subject: [PATCH 2/5] Fix file name Signed-off-by: Timo Stamm --- .../v1/{fle_deprecated.proto => file_deprecated.proto} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename protoc-gen-connect-kotlin/proto/buf/deprecation/v1/{fle_deprecated.proto => file_deprecated.proto} (100%) diff --git a/protoc-gen-connect-kotlin/proto/buf/deprecation/v1/fle_deprecated.proto b/protoc-gen-connect-kotlin/proto/buf/deprecation/v1/file_deprecated.proto similarity index 100% rename from protoc-gen-connect-kotlin/proto/buf/deprecation/v1/fle_deprecated.proto rename to protoc-gen-connect-kotlin/proto/buf/deprecation/v1/file_deprecated.proto From cf73fcafa9ef179de76decb5d210e9585c8d3aa4 Mon Sep 17 00:00:00 2001 From: Timo Stamm Date: Wed, 2 Oct 2024 15:57:13 +0200 Subject: [PATCH 3/5] ./gradlew :spotlessApply Signed-off-by: Timo Stamm --- .../kotlin/com/connectrpc/protocgen/connect/Generator.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/protoc-gen-connect-kotlin/src/main/kotlin/com/connectrpc/protocgen/connect/Generator.kt b/protoc-gen-connect-kotlin/src/main/kotlin/com/connectrpc/protocgen/connect/Generator.kt index c8214d88..45544f18 100644 --- a/protoc-gen-connect-kotlin/src/main/kotlin/com/connectrpc/protocgen/connect/Generator.kt +++ b/protoc-gen-connect-kotlin/src/main/kotlin/com/connectrpc/protocgen/connect/Generator.kt @@ -542,19 +542,19 @@ private fun String.packageToDirectory(): String { private fun TypeSpec.Builder.addServiceDeprecation( service: Descriptors.ServiceDescriptor, - file: Descriptors.FileDescriptor + file: Descriptors.FileDescriptor, ): TypeSpec.Builder { if (service.options.deprecated) { this.addAnnotation( AnnotationSpec.builder(Deprecated::class) .addMember("%S", "The underlying service is marked deprecated.") - .build() + .build(), ) } else if (file.options.deprecated) { this.addAnnotation( AnnotationSpec.builder(Deprecated::class) .addMember("%S", "The underlying file is marked deprecated.") - .build() + .build(), ) } return this @@ -567,7 +567,7 @@ private fun FunSpec.Builder.addMethodDeprecation( this.addAnnotation( AnnotationSpec.builder(Deprecated::class) .addMember("%S", "The underlying service method is marked deprecated.") - .build() + .build(), ) } return this From 1a37603b993b3039f6156ddeda439569a3e634c1 Mon Sep 17 00:00:00 2001 From: Timo Stamm Date: Wed, 2 Oct 2024 16:14:54 +0200 Subject: [PATCH 4/5] Reword deprecation messages Signed-off-by: Timo Stamm --- .../kotlin/com/connectrpc/protocgen/connect/Generator.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/protoc-gen-connect-kotlin/src/main/kotlin/com/connectrpc/protocgen/connect/Generator.kt b/protoc-gen-connect-kotlin/src/main/kotlin/com/connectrpc/protocgen/connect/Generator.kt index 45544f18..216e979f 100644 --- a/protoc-gen-connect-kotlin/src/main/kotlin/com/connectrpc/protocgen/connect/Generator.kt +++ b/protoc-gen-connect-kotlin/src/main/kotlin/com/connectrpc/protocgen/connect/Generator.kt @@ -547,13 +547,13 @@ private fun TypeSpec.Builder.addServiceDeprecation( if (service.options.deprecated) { this.addAnnotation( AnnotationSpec.builder(Deprecated::class) - .addMember("%S", "The underlying service is marked deprecated.") + .addMember("%S", "The service is deprecated in the Protobuf source file.") .build(), ) } else if (file.options.deprecated) { this.addAnnotation( AnnotationSpec.builder(Deprecated::class) - .addMember("%S", "The underlying file is marked deprecated.") + .addMember("%S", "The Protobuf source file that defines this service is deprecated.") .build(), ) } @@ -566,7 +566,7 @@ private fun FunSpec.Builder.addMethodDeprecation( if (method.options.deprecated) { this.addAnnotation( AnnotationSpec.builder(Deprecated::class) - .addMember("%S", "The underlying service method is marked deprecated.") + .addMember("%S", "The method is deprecated in the Protobuf source file.") .build(), ) } From eec519e293503a0a7fe11e31871a6e5099e1f8d1 Mon Sep 17 00:00:00 2001 From: Timo Stamm Date: Wed, 2 Oct 2024 19:01:18 +0200 Subject: [PATCH 5/5] Generate @file:Suppress("DEPRECATION") in files that use @Deprecated Signed-off-by: Timo Stamm --- .../connectrpc/protocgen/connect/Generator.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/protoc-gen-connect-kotlin/src/main/kotlin/com/connectrpc/protocgen/connect/Generator.kt b/protoc-gen-connect-kotlin/src/main/kotlin/com/connectrpc/protocgen/connect/Generator.kt index 216e979f..13b29a25 100644 --- a/protoc-gen-connect-kotlin/src/main/kotlin/com/connectrpc/protocgen/connect/Generator.kt +++ b/protoc-gen-connect-kotlin/src/main/kotlin/com/connectrpc/protocgen/connect/Generator.kt @@ -123,6 +123,7 @@ class Generator : CodeGenerator { .addFileComment("Code generated by connect-kotlin. DO NOT EDIT.\n") .addFileComment("\n") .addFileComment("Source: ${file.name}\n") + .suppressDeprecationWarnings(file) .addType(serviceClientInterface(packageName, service, file, sourceInfo)) .build() fileSpecs[serviceClientInterfaceClassName(packageName, service)] = interfaceFileSpec @@ -133,6 +134,7 @@ class Generator : CodeGenerator { .addFileComment("Code generated by connect-kotlin. DO NOT EDIT.\n") .addFileComment("\n") .addFileComment("Source: ${file.name}\n") + .suppressDeprecationWarnings(file) // Set the file package for the generated methods. .addType(serviceClientImplementation(packageName, service, file, sourceInfo)) for (method in service.methods) { @@ -572,3 +574,17 @@ private fun FunSpec.Builder.addMethodDeprecation( } return this } + +private fun FileSpec.Builder.suppressDeprecationWarnings( + file: Descriptors.FileDescriptor, +): FileSpec.Builder { + val hasDeprecated = file.options.deprecated || file.services.find { s -> s.options.deprecated || s.methods.find { m -> m.options.deprecated } != null } != null + if (hasDeprecated) { + this.addAnnotation( + AnnotationSpec.builder(Suppress::class) + .addMember("%S", "DEPRECATION") + .build(), + ) + } + return this +}