Compare commits
37 Commits
1.0.0-beta
...
main
Author | SHA1 | Date |
---|---|---|
|
5a53247bc4 | |
|
8125093686 | |
|
6bd6c50372 | |
|
df605cde29 | |
|
f0f110af87 | |
|
c96dec7076 | |
|
fda21d444a | |
|
43cede763f | |
|
652582337f | |
|
364041d312 | |
|
a547cfec11 | |
|
ac5100ae48 | |
|
c47ee1b774 | |
|
5edbc18f51 | |
|
49bdd1232c | |
|
6830cf0ea5 | |
|
63982ca29f | |
|
237ca5f91c | |
|
9cd9d4e618 | |
|
8726dda0b5 | |
|
108c131067 | |
|
2ba29cc3ee | |
|
64f97e95a8 | |
|
e426ad8924 | |
|
7af6b403c5 | |
|
bdbc8320f1 | |
|
d35611014e | |
|
219783a584 | |
|
4bb3bea121 | |
|
44b2b6f65d | |
|
4153adaff5 | |
|
7695d98c4d | |
|
7d129cecc3 | |
|
c991945c2b | |
|
05334bbf44 | |
|
0e6d93b8aa | |
|
b036851fb8 |
|
@ -13,6 +13,37 @@ jobs:
|
||||||
with:
|
with:
|
||||||
linux_5_9_enabled: false
|
linux_5_9_enabled: false
|
||||||
linux_5_10_enabled: false
|
linux_5_10_enabled: false
|
||||||
linux_6_0_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
|
linux_6_0_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -warnings-as-errors -Xswiftc -require-explicit-availability"
|
||||||
linux_nightly_6_0_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
|
linux_6_1_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -warnings-as-errors -Xswiftc -require-explicit-availability"
|
||||||
linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
|
linux_nightly_next_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-availability"
|
||||||
|
linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-availability"
|
||||||
|
|
||||||
|
construct-plugin-tests-matrix:
|
||||||
|
name: Construct plugin tests matrix
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
plugin-tests-matrix: '${{ steps.generate-matrix.outputs.plugin-tests-matrix }}'
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
- id: generate-matrix
|
||||||
|
run: echo "plugin-tests-matrix=$(curl -s https://raw.githubusercontent.com/apple/swift-nio/main/scripts/generate_matrix.sh | bash)" >> "$GITHUB_OUTPUT"
|
||||||
|
env:
|
||||||
|
MATRIX_LINUX_5_9_ENABLED: false
|
||||||
|
MATRIX_LINUX_5_10_ENABLED: false
|
||||||
|
MATRIX_LINUX_COMMAND: "./dev/plugin-tests.sh"
|
||||||
|
MATRIX_LINUX_SETUP_COMMAND: "apt-get update -y -q && apt-get install -y -q curl protobuf-compiler"
|
||||||
|
|
||||||
|
plugin-tests-matrix:
|
||||||
|
name: Plugin tests
|
||||||
|
needs: construct-plugin-tests-matrix
|
||||||
|
uses: apple/swift-nio/.github/workflows/swift_test_matrix.yml@main
|
||||||
|
with:
|
||||||
|
name: "Plugin tests"
|
||||||
|
matrix_string: '${{ needs.construct-plugin-tests-matrix.outputs.plugin-tests-matrix }}'
|
||||||
|
|
||||||
|
static-sdk:
|
||||||
|
name: Static SDK
|
||||||
|
uses: apple/swift-nio/.github/workflows/static_sdk.yml@main
|
||||||
|
|
|
@ -11,6 +11,9 @@ jobs:
|
||||||
uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main
|
uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main
|
||||||
with:
|
with:
|
||||||
license_header_check_project_name: "gRPC"
|
license_header_check_project_name: "gRPC"
|
||||||
|
# This is done by a similar job defined in soundness.yml. It needs to be
|
||||||
|
# separate in order to export an environment variable.
|
||||||
|
api_breakage_check_enabled: false
|
||||||
|
|
||||||
grpc-soundness:
|
grpc-soundness:
|
||||||
name: Soundness
|
name: Soundness
|
||||||
|
@ -22,9 +25,36 @@ jobs:
|
||||||
with:
|
with:
|
||||||
linux_5_9_enabled: false
|
linux_5_9_enabled: false
|
||||||
linux_5_10_enabled: false
|
linux_5_10_enabled: false
|
||||||
linux_6_0_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
|
linux_6_0_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -warnings-as-errors -Xswiftc -require-explicit-availability"
|
||||||
linux_nightly_6_0_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
|
linux_6_1_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -warnings-as-errors -Xswiftc -require-explicit-availability"
|
||||||
linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
|
linux_nightly_next_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-availability"
|
||||||
|
linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-availability"
|
||||||
|
|
||||||
|
construct-plugin-tests-matrix:
|
||||||
|
name: Construct plugin tests matrix
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
plugin-tests-matrix: '${{ steps.generate-matrix.outputs.plugin-tests-matrix }}'
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
- id: generate-matrix
|
||||||
|
run: echo "plugin-tests-matrix=$(curl -s https://raw.githubusercontent.com/apple/swift-nio/main/scripts/generate_matrix.sh | bash)" >> "$GITHUB_OUTPUT"
|
||||||
|
env:
|
||||||
|
MATRIX_LINUX_5_9_ENABLED: false
|
||||||
|
MATRIX_LINUX_5_10_ENABLED: false
|
||||||
|
MATRIX_LINUX_COMMAND: "./dev/plugin-tests.sh"
|
||||||
|
MATRIX_LINUX_SETUP_COMMAND: "apt-get update -y -q && apt-get install -y -q curl protobuf-compiler"
|
||||||
|
|
||||||
|
plugin-tests-matrix:
|
||||||
|
name: Plugin tests
|
||||||
|
needs: construct-plugin-tests-matrix
|
||||||
|
uses: apple/swift-nio/.github/workflows/swift_test_matrix.yml@main
|
||||||
|
with:
|
||||||
|
name: "Plugin tests"
|
||||||
|
matrix_string: '${{ needs.construct-plugin-tests-matrix.outputs.plugin-tests-matrix }}'
|
||||||
|
|
||||||
cxx-interop:
|
cxx-interop:
|
||||||
name: Cxx interop
|
name: Cxx interop
|
||||||
|
@ -32,3 +62,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
linux_5_9_enabled: false
|
linux_5_9_enabled: false
|
||||||
linux_5_10_enabled: false
|
linux_5_10_enabled: false
|
||||||
|
|
||||||
|
static-sdk:
|
||||||
|
name: Static SDK
|
||||||
|
uses: apple/swift-nio/.github/workflows/static_sdk.yml@main
|
||||||
|
|
|
@ -35,3 +35,28 @@ jobs:
|
||||||
- name: Check generated code
|
- name: Check generated code
|
||||||
run: |
|
run: |
|
||||||
./dev/check-generated-code.sh
|
./dev/check-generated-code.sh
|
||||||
|
|
||||||
|
api-breakage-check:
|
||||||
|
name: API breakage check
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: swift:latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
fetch-depth: 0 # Fetching tags requires fetch-depth: 0 (https://github.com/actions/checkout/issues/1471)
|
||||||
|
- name: Mark the workspace as safe
|
||||||
|
# https://github.com/actions/checkout/issues/766
|
||||||
|
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
||||||
|
- name: Run API breakage check
|
||||||
|
shell: bash
|
||||||
|
# See package.swift for why we set GRPC_SWIFT_PROTOBUF_NO_VERSION=1
|
||||||
|
run: |
|
||||||
|
export GRPC_SWIFT_PROTOBUF_NO_VERSION=1
|
||||||
|
|
||||||
|
git fetch ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY} ${GITHUB_BASE_REF}:pull-base-ref
|
||||||
|
BASELINE_REF='pull-base-ref'
|
||||||
|
echo "Using baseline: $BASELINE_REF"
|
||||||
|
swift package diagnose-api-breaking-changes "$BASELINE_REF"
|
||||||
|
|
|
@ -36,6 +36,7 @@ dev/git.commit.template
|
||||||
dev/version-bump.commit.template
|
dev/version-bump.commit.template
|
||||||
dev/protos/local/*
|
dev/protos/local/*
|
||||||
dev/protos/upstream/*
|
dev/protos/upstream/*
|
||||||
|
IntegrationTests/PluginTests/**/*.proto
|
||||||
.unacceptablelanguageignore
|
.unacceptablelanguageignore
|
||||||
LICENSE
|
LICENSE
|
||||||
**/*.swift
|
**/*.swift
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"generatedSource": {
|
||||||
|
"accessLevel": "internal"
|
||||||
|
},
|
||||||
|
"protoc": {
|
||||||
|
"importPaths": [
|
||||||
|
".",
|
||||||
|
"../directory_1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"generatedSource": {
|
||||||
|
"accessLevel": "internal"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"generatedSource": {
|
||||||
|
"accessLevel": "public"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
// Leading trivia.
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package foo;
|
||||||
|
|
||||||
|
message FooInput {}
|
||||||
|
message FooOutput {}
|
|
@ -0,0 +1,13 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
import "Foo/foo-messages.proto";
|
||||||
|
|
||||||
|
package foo;
|
||||||
|
|
||||||
|
service FooService1 {
|
||||||
|
rpc Foo (FooInput) returns (FooOutput) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
service FooService2 {
|
||||||
|
rpc Foo (FooInput) returns (FooOutput) {}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
// Copyright 2015, gRPC Authors All rights reserved.
|
||||||
|
//
|
||||||
|
// 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";
|
||||||
|
|
||||||
|
option java_multiple_files = true;
|
||||||
|
option java_package = "io.grpc.examples.helloworld";
|
||||||
|
option java_outer_classname = "HelloWorldProto";
|
||||||
|
option objc_class_prefix = "HLW";
|
||||||
|
|
||||||
|
package helloworld;
|
||||||
|
|
||||||
|
// The greeting service definition.
|
||||||
|
service Greeter {
|
||||||
|
// Sends a greeting
|
||||||
|
rpc SayHello (HelloRequest) returns (HelloReply) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The request message containing the user's name.
|
||||||
|
message HelloRequest {
|
||||||
|
string name = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The response message containing the greetings
|
||||||
|
message HelloReply {
|
||||||
|
string message = 1;
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright 2015, gRPC Authors All rights reserved.
|
||||||
|
//
|
||||||
|
// 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";
|
||||||
|
|
||||||
|
option java_multiple_files = true;
|
||||||
|
option java_package = "io.grpc.examples.helloworld";
|
||||||
|
option java_outer_classname = "HelloWorldProto";
|
||||||
|
option objc_class_prefix = "HLW";
|
||||||
|
|
||||||
|
package helloworld;
|
||||||
|
|
||||||
|
// The request message containing the user's name.
|
||||||
|
message HelloRequest {
|
||||||
|
string name = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The response message containing the greetings
|
||||||
|
message HelloReply {
|
||||||
|
string message = 1;
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
// Copyright 2015, gRPC Authors All rights reserved.
|
||||||
|
//
|
||||||
|
// 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";
|
||||||
|
|
||||||
|
option java_multiple_files = true;
|
||||||
|
option java_package = "io.grpc.examples.helloworld";
|
||||||
|
option java_outer_classname = "HelloWorldProto";
|
||||||
|
option objc_class_prefix = "HLW";
|
||||||
|
|
||||||
|
package helloworld;
|
||||||
|
|
||||||
|
import "Messages.proto";
|
||||||
|
|
||||||
|
// The greeting service definition.
|
||||||
|
service Greeter {
|
||||||
|
// Sends a greeting
|
||||||
|
rpc SayHello (HelloRequest) returns (HelloReply) {}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
import "google/protobuf/empty.proto";
|
||||||
|
|
||||||
|
package noop;
|
||||||
|
|
||||||
|
service NoOpService {
|
||||||
|
rpc NoOp(google.protobuf.Empty) returns (google.protobuf.Empty);
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
import "google/protobuf/empty.proto";
|
||||||
|
|
||||||
|
package noop2;
|
||||||
|
|
||||||
|
service NoOpService {
|
||||||
|
rpc NoOp(google.protobuf.Empty) returns (google.protobuf.Empty);
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024, gRPC Authors All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import GRPCCore
|
||||||
|
import GRPCInProcessTransport
|
||||||
|
import GRPCProtobuf
|
||||||
|
|
||||||
|
@main
|
||||||
|
struct PluginAdopter {
|
||||||
|
static func main() async throws {
|
||||||
|
let inProcess = InProcessTransport()
|
||||||
|
try await withGRPCServer(transport: inProcess.server, services: [Greeter()]) { server in
|
||||||
|
try await withGRPCClient(transport: inProcess.client) { client in
|
||||||
|
try await Self.doRPC(Helloworld_Greeter.Client(wrapping: client))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try await withGRPCServer(transport: inProcess.server, services: [FooService1()]) { server in
|
||||||
|
try await withGRPCClient(transport: inProcess.client) { client in
|
||||||
|
try await Self.doRPC(Foo_FooService1.Client(wrapping: client))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func doRPC<Transport>(_ greeter: Helloworld_Greeter.Client<Transport>) async throws {
|
||||||
|
do {
|
||||||
|
let reply = try await greeter.sayHello(.with { $0.name = "(ignored)" })
|
||||||
|
print("Reply: \(reply.message)")
|
||||||
|
} catch {
|
||||||
|
print("Error: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func doRPC<Transport>(_ fooService1: Foo_FooService1.Client<Transport>) async throws {
|
||||||
|
do {
|
||||||
|
let reply = try await fooService1.foo(.with { _ in () })
|
||||||
|
print("Reply: \(reply.hashValue)")
|
||||||
|
} catch {
|
||||||
|
print("Error: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Greeter: Helloworld_Greeter.SimpleServiceProtocol {
|
||||||
|
func sayHello(
|
||||||
|
request: Helloworld_HelloRequest,
|
||||||
|
context: ServerContext
|
||||||
|
) async throws -> Helloworld_HelloReply {
|
||||||
|
return .with { reply in
|
||||||
|
reply.message = "Hello, world!"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FooService1: Foo_FooService1.SimpleServiceProtocol {
|
||||||
|
func foo(request: Foo_FooInput, context: GRPCCore.ServerContext) async throws -> Foo_FooOutput {
|
||||||
|
return .with { _ in
|
||||||
|
()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024, gRPC Authors All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import GRPCCore
|
||||||
|
import GRPCInProcessTransport
|
||||||
|
import GRPCProtobuf
|
||||||
|
|
||||||
|
@main
|
||||||
|
struct PluginAdopter {
|
||||||
|
static func main() async throws {
|
||||||
|
let inProcess = InProcessTransport()
|
||||||
|
try await withGRPCServer(transport: inProcess.server, services: [Greeter()]) { server in
|
||||||
|
try await withGRPCClient(transport: inProcess.client) { client in
|
||||||
|
try await Self.doRPC(Helloworld_Greeter.Client(wrapping: client))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func doRPC<Transport>(_ greeter: Helloworld_Greeter.Client<Transport>) async throws {
|
||||||
|
do {
|
||||||
|
let reply = try await greeter.sayHello(.with { $0.name = "(ignored)" })
|
||||||
|
print("Reply: \(reply.message)")
|
||||||
|
} catch {
|
||||||
|
print("Error: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Greeter: Helloworld_Greeter.SimpleServiceProtocol {
|
||||||
|
func sayHello(
|
||||||
|
request: Helloworld_HelloRequest,
|
||||||
|
context: ServerContext
|
||||||
|
) async throws -> Helloworld_HelloReply {
|
||||||
|
return .with { reply in
|
||||||
|
reply.message = "Hello, world!"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025, gRPC Authors All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import GRPCCore
|
||||||
|
import GRPCProtobuf
|
||||||
|
import SwiftProtobuf
|
||||||
|
|
||||||
|
@main
|
||||||
|
struct PluginAdopter {
|
||||||
|
static func main() async throws {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct NoOp: Noop_NoOpService.SimpleServiceProtocol {
|
||||||
|
func noOp(
|
||||||
|
request: Google_Protobuf_Empty,
|
||||||
|
context: ServerContext
|
||||||
|
) async throws -> Google_Protobuf_Empty {
|
||||||
|
return Google_Protobuf_Empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct NoOp2: Noop2_NoOpService.SimpleServiceProtocol {
|
||||||
|
func noOp(
|
||||||
|
request: Google_Protobuf_Empty,
|
||||||
|
context: ServerContext
|
||||||
|
) async throws -> Google_Protobuf_Empty {
|
||||||
|
return Google_Protobuf_Empty()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
// swift-tools-version: 6.0
|
||||||
|
/*
|
||||||
|
* Copyright 2024, gRPC Authors All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import PackageDescription
|
||||||
|
|
||||||
|
let package = Package(
|
||||||
|
name: "grpc-adopter",
|
||||||
|
platforms: [
|
||||||
|
.macOS(.v15),
|
||||||
|
.iOS(.v18),
|
||||||
|
.tvOS(.v18),
|
||||||
|
.watchOS(.v11),
|
||||||
|
.visionOS(.v2),
|
||||||
|
],
|
||||||
|
dependencies: [
|
||||||
|
// Dependency on grpc-swift-protobuf to be added by setup-plugin-tests.sh script
|
||||||
|
.package(
|
||||||
|
url: "https://github.com/grpc/grpc-swift-2.git",
|
||||||
|
from: "2.0.0"
|
||||||
|
)
|
||||||
|
],
|
||||||
|
targets: [
|
||||||
|
.executableTarget(
|
||||||
|
name: "grpc-adopter",
|
||||||
|
dependencies: [
|
||||||
|
.product(name: "GRPCCore", package: "grpc-swift-2"),
|
||||||
|
.product(name: "GRPCInProcessTransport", package: "grpc-swift-2"),
|
||||||
|
.product(name: "GRPCProtobuf", package: "grpc-swift-protobuf"),
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
.plugin(name: "GRPCProtobufGenerator", package: "grpc-swift-protobuf")
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
148
Package.swift
148
Package.swift
|
@ -23,15 +23,23 @@ let products: [Product] = [
|
||||||
targets: ["GRPCProtobuf"]
|
targets: ["GRPCProtobuf"]
|
||||||
),
|
),
|
||||||
.executable(
|
.executable(
|
||||||
name: "protoc-gen-grpc-swift",
|
name: "protoc-gen-grpc-swift-2",
|
||||||
targets: ["protoc-gen-grpc-swift"]
|
targets: ["protoc-gen-grpc-swift-2"]
|
||||||
|
),
|
||||||
|
.plugin(
|
||||||
|
name: "GRPCProtobufGenerator",
|
||||||
|
targets: ["GRPCProtobufGenerator"]
|
||||||
|
),
|
||||||
|
.plugin(
|
||||||
|
name: "generate-grpc-code-from-protos",
|
||||||
|
targets: ["generate-grpc-code-from-protos"]
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
let dependencies: [Package.Dependency] = [
|
let dependencies: [Package.Dependency] = [
|
||||||
.package(
|
.package(
|
||||||
url: "https://github.com/grpc/grpc-swift.git",
|
url: "https://github.com/grpc/grpc-swift-2.git",
|
||||||
exact: "2.0.0-beta.3"
|
from: "2.0.0"
|
||||||
),
|
),
|
||||||
.package(
|
.package(
|
||||||
url: "https://github.com/apple/swift-protobuf.git",
|
url: "https://github.com/apple/swift-protobuf.git",
|
||||||
|
@ -39,20 +47,36 @@ let dependencies: [Package.Dependency] = [
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
let defaultSwiftSettings: [SwiftSetting] = [
|
// -------------------------------------------------------------------------------------------------
|
||||||
.swiftLanguageMode(.v6),
|
|
||||||
.enableUpcomingFeature("ExistentialAny"),
|
|
||||||
.enableUpcomingFeature("InternalImportsByDefault"),
|
|
||||||
.enableUpcomingFeature("MemberImportVisibility"),
|
|
||||||
]
|
|
||||||
|
|
||||||
let targets: [Target] = [
|
// This adds some build settings which allow us to map "@available(gRPCSwiftProtobuf 2.x, *)" to
|
||||||
// protoc plugin for grpc-swift
|
// the appropriate OS platforms.
|
||||||
|
let nextMinorVersion = 1
|
||||||
|
let availabilitySettings: [SwiftSetting] = (0 ... nextMinorVersion).map { minor in
|
||||||
|
let name = "gRPCSwiftProtobuf"
|
||||||
|
let version = "2.\(minor)"
|
||||||
|
let platforms = "macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0"
|
||||||
|
let setting = "AvailabilityMacro=\(name) \(version):\(platforms)"
|
||||||
|
return .enableExperimentalFeature(setting)
|
||||||
|
}
|
||||||
|
|
||||||
|
let defaultSwiftSettings: [SwiftSetting] =
|
||||||
|
availabilitySettings + [
|
||||||
|
.swiftLanguageMode(.v6),
|
||||||
|
.enableUpcomingFeature("ExistentialAny"),
|
||||||
|
.enableUpcomingFeature("InternalImportsByDefault"),
|
||||||
|
.enableUpcomingFeature("MemberImportVisibility"),
|
||||||
|
]
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
var targets: [Target] = [
|
||||||
|
// protoc plugin for grpc-swift-2
|
||||||
.executableTarget(
|
.executableTarget(
|
||||||
name: "protoc-gen-grpc-swift",
|
name: "protoc-gen-grpc-swift-2",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.target(name: "GRPCProtobufCodeGen"),
|
.target(name: "GRPCProtobufCodeGen"),
|
||||||
.product(name: "GRPCCodeGen", package: "grpc-swift"),
|
.product(name: "GRPCCodeGen", package: "grpc-swift-2"),
|
||||||
.product(name: "SwiftProtobuf", package: "swift-protobuf"),
|
.product(name: "SwiftProtobuf", package: "swift-protobuf"),
|
||||||
.product(name: "SwiftProtobufPluginLibrary", package: "swift-protobuf"),
|
.product(name: "SwiftProtobufPluginLibrary", package: "swift-protobuf"),
|
||||||
],
|
],
|
||||||
|
@ -63,7 +87,7 @@ let targets: [Target] = [
|
||||||
.target(
|
.target(
|
||||||
name: "GRPCProtobuf",
|
name: "GRPCProtobuf",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.product(name: "GRPCCore", package: "grpc-swift"),
|
.product(name: "GRPCCore", package: "grpc-swift-2"),
|
||||||
.product(name: "SwiftProtobuf", package: "swift-protobuf"),
|
.product(name: "SwiftProtobuf", package: "swift-protobuf"),
|
||||||
],
|
],
|
||||||
swiftSettings: defaultSwiftSettings
|
swiftSettings: defaultSwiftSettings
|
||||||
|
@ -72,18 +96,18 @@ let targets: [Target] = [
|
||||||
name: "GRPCProtobufTests",
|
name: "GRPCProtobufTests",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.target(name: "GRPCProtobuf"),
|
.target(name: "GRPCProtobuf"),
|
||||||
.product(name: "GRPCCore", package: "grpc-swift"),
|
.product(name: "GRPCCore", package: "grpc-swift-2"),
|
||||||
.product(name: "GRPCInProcessTransport", package: "grpc-swift"),
|
.product(name: "GRPCInProcessTransport", package: "grpc-swift-2"),
|
||||||
.product(name: "SwiftProtobuf", package: "swift-protobuf"),
|
.product(name: "SwiftProtobuf", package: "swift-protobuf"),
|
||||||
],
|
],
|
||||||
swiftSettings: defaultSwiftSettings
|
swiftSettings: defaultSwiftSettings
|
||||||
),
|
),
|
||||||
|
|
||||||
// Code generator library for protoc-gen-grpc-swift
|
// Code generator library for protoc-gen-grpc-swift-2
|
||||||
.target(
|
.target(
|
||||||
name: "GRPCProtobufCodeGen",
|
name: "GRPCProtobufCodeGen",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.product(name: "GRPCCodeGen", package: "grpc-swift"),
|
.product(name: "GRPCCodeGen", package: "grpc-swift-2"),
|
||||||
.product(name: "SwiftProtobufPluginLibrary", package: "swift-protobuf"),
|
.product(name: "SwiftProtobufPluginLibrary", package: "swift-protobuf"),
|
||||||
],
|
],
|
||||||
swiftSettings: defaultSwiftSettings
|
swiftSettings: defaultSwiftSettings
|
||||||
|
@ -92,7 +116,7 @@ let targets: [Target] = [
|
||||||
name: "GRPCProtobufCodeGenTests",
|
name: "GRPCProtobufCodeGenTests",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.target(name: "GRPCProtobufCodeGen"),
|
.target(name: "GRPCProtobufCodeGen"),
|
||||||
.product(name: "GRPCCodeGen", package: "grpc-swift"),
|
.product(name: "GRPCCodeGen", package: "grpc-swift-2"),
|
||||||
.product(name: "SwiftProtobuf", package: "swift-protobuf"),
|
.product(name: "SwiftProtobuf", package: "swift-protobuf"),
|
||||||
.product(name: "SwiftProtobufPluginLibrary", package: "swift-protobuf"),
|
.product(name: "SwiftProtobufPluginLibrary", package: "swift-protobuf"),
|
||||||
],
|
],
|
||||||
|
@ -101,17 +125,87 @@ let targets: [Target] = [
|
||||||
],
|
],
|
||||||
swiftSettings: defaultSwiftSettings
|
swiftSettings: defaultSwiftSettings
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// Code generator build plugin
|
||||||
|
.plugin(
|
||||||
|
name: "GRPCProtobufGenerator",
|
||||||
|
capability: .buildTool(),
|
||||||
|
dependencies: [
|
||||||
|
.target(name: "protoc-gen-grpc-swift-2"),
|
||||||
|
.product(name: "protoc-gen-swift", package: "swift-protobuf"),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
|
||||||
|
// Code generator SwiftPM command
|
||||||
|
.plugin(
|
||||||
|
name: "generate-grpc-code-from-protos",
|
||||||
|
capability: .command(
|
||||||
|
intent: .custom(
|
||||||
|
verb: "generate-grpc-code-from-protos",
|
||||||
|
description: "Generate Swift code for gRPC services from protobuf definitions."
|
||||||
|
),
|
||||||
|
permissions: [
|
||||||
|
.writeToPackageDirectory(
|
||||||
|
reason:
|
||||||
|
"To write the generated Swift files back into the source directory of the package."
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
dependencies: [
|
||||||
|
.target(name: "protoc-gen-grpc-swift-2"),
|
||||||
|
.product(name: "protoc-gen-swift", package: "swift-protobuf"),
|
||||||
|
],
|
||||||
|
path: "Plugins/GRPCProtobufGeneratorCommand"
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
extension Context {
|
||||||
|
fileprivate static var versionString: String {
|
||||||
|
guard let git = Self.gitInformation else { return "" }
|
||||||
|
|
||||||
|
if let tag = git.currentTag {
|
||||||
|
return tag
|
||||||
|
} else {
|
||||||
|
let suffix = git.hasUncommittedChanges ? " (modified)" : ""
|
||||||
|
return git.currentCommit + suffix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate static var buildCGRPCProtobuf: Bool {
|
||||||
|
let noVersion = Context.environment.keys.contains("GRPC_SWIFT_PROTOBUF_NO_VERSION")
|
||||||
|
return !noVersion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Having a C module as a transitive dependency of a plugin seems to trip up the API breakage
|
||||||
|
// checking tool. See also https://github.com/swiftlang/swift-package-manager/issues/8081
|
||||||
|
//
|
||||||
|
// The CGRPCProtobuf module (which only includes package version information) is conditionally
|
||||||
|
// compiled and included based on an environment variable. This is set in CI only for the API
|
||||||
|
// breakage checking job to avoid tripping up SwiftPM.
|
||||||
|
if Context.buildCGRPCProtobuf {
|
||||||
|
targets.append(
|
||||||
|
.target(
|
||||||
|
name: "CGRPCProtobuf",
|
||||||
|
cSettings: [
|
||||||
|
.define("CGRPC_GRPC_SWIFT_PROTOBUF_VERSION", to: "\"\(Context.versionString)\"")
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
for target in targets {
|
||||||
|
if target.name == "protoc-gen-grpc-swift-2" {
|
||||||
|
target.dependencies.append(.target(name: "CGRPCProtobuf"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
let package = Package(
|
let package = Package(
|
||||||
name: "grpc-swift-protobuf",
|
name: "grpc-swift-protobuf",
|
||||||
platforms: [
|
|
||||||
.macOS(.v15),
|
|
||||||
.iOS(.v18),
|
|
||||||
.tvOS(.v18),
|
|
||||||
.watchOS(.v11),
|
|
||||||
.visionOS(.v2),
|
|
||||||
],
|
|
||||||
products: products,
|
products: products,
|
||||||
dependencies: dependencies,
|
dependencies: dependencies,
|
||||||
targets: targets
|
targets: targets
|
||||||
|
|
|
@ -0,0 +1,210 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024, gRPC Authors All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// The config of the build plugin.
|
||||||
|
struct BuildPluginConfig: Codable {
|
||||||
|
/// Config defining which components should be considered when generating source.
|
||||||
|
struct Generate {
|
||||||
|
/// Whether server code is generated.
|
||||||
|
///
|
||||||
|
/// Defaults to `true`.
|
||||||
|
var servers: Bool
|
||||||
|
/// Whether client code is generated.
|
||||||
|
///
|
||||||
|
/// Defaults to `true`.
|
||||||
|
var clients: Bool
|
||||||
|
/// Whether message code is generated.
|
||||||
|
///
|
||||||
|
/// Defaults to `true`.
|
||||||
|
var messages: Bool
|
||||||
|
|
||||||
|
static let defaults = Self(
|
||||||
|
servers: true,
|
||||||
|
clients: true,
|
||||||
|
messages: true
|
||||||
|
)
|
||||||
|
|
||||||
|
private init(servers: Bool, clients: Bool, messages: Bool) {
|
||||||
|
self.servers = servers
|
||||||
|
self.clients = clients
|
||||||
|
self.messages = messages
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Config relating to the generated code itself.
|
||||||
|
struct GeneratedSource {
|
||||||
|
/// The visibility of the generated files.
|
||||||
|
///
|
||||||
|
/// Defaults to `Internal`.
|
||||||
|
var accessLevel: GenerationConfig.AccessLevel
|
||||||
|
/// Whether imports should have explicit access levels.
|
||||||
|
///
|
||||||
|
/// Defaults to `false`.
|
||||||
|
var accessLevelOnImports: Bool
|
||||||
|
|
||||||
|
static let defaults = Self(
|
||||||
|
accessLevel: .internal,
|
||||||
|
accessLevelOnImports: false
|
||||||
|
)
|
||||||
|
|
||||||
|
private init(accessLevel: GenerationConfig.AccessLevel, accessLevelOnImports: Bool) {
|
||||||
|
self.accessLevel = accessLevel
|
||||||
|
self.accessLevelOnImports = accessLevelOnImports
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Config relating to the protoc invocation.
|
||||||
|
struct Protoc {
|
||||||
|
/// Specify the directory in which to search for imports.
|
||||||
|
///
|
||||||
|
/// Paths are relative to the location of the specifying config file.
|
||||||
|
/// Build plugins only have access to files within the target's source directory.
|
||||||
|
/// May be specified multiple times; directories will be searched in order.
|
||||||
|
/// The target source directory is always appended
|
||||||
|
/// to the import paths.
|
||||||
|
var importPaths: [String]
|
||||||
|
|
||||||
|
/// The path to the `protoc` executable binary.
|
||||||
|
///
|
||||||
|
/// If this is not set, Swift Package Manager will try to find the tool itself.
|
||||||
|
var executablePath: String?
|
||||||
|
|
||||||
|
static let defaults = Self(
|
||||||
|
importPaths: [],
|
||||||
|
executablePath: nil
|
||||||
|
)
|
||||||
|
|
||||||
|
private init(importPaths: [String], executablePath: String?) {
|
||||||
|
self.importPaths = importPaths
|
||||||
|
self.executablePath = executablePath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Config defining which components should be considered when generating source.
|
||||||
|
var generate: Generate
|
||||||
|
/// Config relating to the nature of the generated code.
|
||||||
|
var generatedSource: GeneratedSource
|
||||||
|
/// Config relating to the protoc invocation.
|
||||||
|
var protoc: Protoc
|
||||||
|
|
||||||
|
static let defaults = Self(
|
||||||
|
generate: Generate.defaults,
|
||||||
|
generatedSource: GeneratedSource.defaults,
|
||||||
|
protoc: Protoc.defaults
|
||||||
|
)
|
||||||
|
private init(generate: Generate, generatedSource: GeneratedSource, protoc: Protoc) {
|
||||||
|
self.generate = generate
|
||||||
|
self.generatedSource = generatedSource
|
||||||
|
self.protoc = protoc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Codable conformance with defaults
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case generate
|
||||||
|
case generatedSource
|
||||||
|
case protoc
|
||||||
|
}
|
||||||
|
|
||||||
|
init(from decoder: any Decoder) throws {
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
|
||||||
|
self.generate =
|
||||||
|
try container.decodeIfPresent(Generate.self, forKey: .generate) ?? Self.defaults.generate
|
||||||
|
self.generatedSource =
|
||||||
|
try container.decodeIfPresent(GeneratedSource.self, forKey: .generatedSource)
|
||||||
|
?? Self.defaults.generatedSource
|
||||||
|
self.protoc =
|
||||||
|
try container.decodeIfPresent(Protoc.self, forKey: .protoc) ?? Self.defaults.protoc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension BuildPluginConfig.Generate: Codable {
|
||||||
|
// Codable conformance with defaults
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case servers
|
||||||
|
case clients
|
||||||
|
case messages
|
||||||
|
}
|
||||||
|
|
||||||
|
init(from decoder: any Decoder) throws {
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
|
||||||
|
self.servers =
|
||||||
|
try container.decodeIfPresent(Bool.self, forKey: .servers) ?? Self.defaults.servers
|
||||||
|
self.clients =
|
||||||
|
try container.decodeIfPresent(Bool.self, forKey: .clients) ?? Self.defaults.clients
|
||||||
|
self.messages =
|
||||||
|
try container.decodeIfPresent(Bool.self, forKey: .messages) ?? Self.defaults.messages
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension BuildPluginConfig.GeneratedSource: Codable {
|
||||||
|
// Codable conformance with defaults
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case accessLevel
|
||||||
|
case accessLevelOnImports
|
||||||
|
}
|
||||||
|
|
||||||
|
init(from decoder: any Decoder) throws {
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
|
||||||
|
self.accessLevel =
|
||||||
|
try container.decodeIfPresent(GenerationConfig.AccessLevel.self, forKey: .accessLevel)
|
||||||
|
?? Self.defaults.accessLevel
|
||||||
|
self.accessLevelOnImports =
|
||||||
|
try container.decodeIfPresent(Bool.self, forKey: .accessLevelOnImports)
|
||||||
|
?? Self.defaults.accessLevelOnImports
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension BuildPluginConfig.Protoc: Codable {
|
||||||
|
// Codable conformance with defaults
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case importPaths
|
||||||
|
case executablePath
|
||||||
|
}
|
||||||
|
|
||||||
|
init(from decoder: any Decoder) throws {
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
|
||||||
|
self.importPaths =
|
||||||
|
try container.decodeIfPresent([String].self, forKey: .importPaths)
|
||||||
|
?? Self.defaults.importPaths
|
||||||
|
self.executablePath = try container.decodeIfPresent(String.self, forKey: .executablePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension GenerationConfig {
|
||||||
|
init(buildPluginConfig: BuildPluginConfig, configFilePath: URL, outputPath: URL) {
|
||||||
|
self.servers = buildPluginConfig.generate.servers
|
||||||
|
self.clients = buildPluginConfig.generate.clients
|
||||||
|
self.messages = buildPluginConfig.generate.messages
|
||||||
|
// Use path to underscores as it ensures output files are unique (files generated from
|
||||||
|
// "foo/bar.proto" won't collide with those generated from "bar/bar.proto" as they'll be
|
||||||
|
// uniquely named "foo_bar.(grpc|pb).swift" and "bar_bar.(grpc|pb).swift".
|
||||||
|
self.fileNaming = .pathToUnderscores
|
||||||
|
self.accessLevel = buildPluginConfig.generatedSource.accessLevel
|
||||||
|
self.accessLevelOnImports = buildPluginConfig.generatedSource.accessLevelOnImports
|
||||||
|
// Generate absolute paths for the imports relative to the config file in which they are specified
|
||||||
|
self.importPaths = buildPluginConfig.protoc.importPaths.map { relativePath in
|
||||||
|
configFilePath.deletingLastPathComponent().absoluteStringNoScheme + "/" + relativePath
|
||||||
|
}
|
||||||
|
self.protocPath = buildPluginConfig.protoc.executablePath
|
||||||
|
self.outputPath = outputPath.absoluteStringNoScheme
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025, gRPC Authors All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
enum BuildPluginError: Error {
|
||||||
|
case incompatibleTarget(String)
|
||||||
|
case noConfigFilesFound
|
||||||
|
}
|
||||||
|
|
||||||
|
extension BuildPluginError: CustomStringConvertible {
|
||||||
|
var description: String {
|
||||||
|
switch self {
|
||||||
|
case .incompatibleTarget(let target):
|
||||||
|
"Build plugin applied to incompatible target (\(target))."
|
||||||
|
case .noConfigFilesFound:
|
||||||
|
"No config files found. The build plugin relies on the existence of one or more '\(configFileName)' files in the target source."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,305 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024, gRPC Authors All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import PackagePlugin
|
||||||
|
|
||||||
|
// Entry-point when using Package manifest
|
||||||
|
extension GRPCProtobufGenerator: BuildToolPlugin {
|
||||||
|
func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] {
|
||||||
|
guard let swiftTarget = target as? SwiftSourceModuleTarget else {
|
||||||
|
throw BuildPluginError.incompatibleTarget(target.name)
|
||||||
|
}
|
||||||
|
let configFiles = swiftTarget.sourceFiles(withSuffix: configFileName).map { $0.url }
|
||||||
|
let inputFiles = swiftTarget.sourceFiles(withSuffix: ".proto").map { $0.url }
|
||||||
|
return try createBuildCommands(
|
||||||
|
pluginWorkDirectory: context.pluginWorkDirectoryURL,
|
||||||
|
tool: context.tool,
|
||||||
|
inputFiles: inputFiles,
|
||||||
|
configFiles: configFiles,
|
||||||
|
targetName: target.name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if canImport(XcodeProjectPlugin)
|
||||||
|
import XcodeProjectPlugin
|
||||||
|
|
||||||
|
// Entry-point when using Xcode projects
|
||||||
|
extension GRPCProtobufGenerator: XcodeBuildToolPlugin {
|
||||||
|
func createBuildCommands(context: XcodePluginContext, target: XcodeTarget) throws -> [Command] {
|
||||||
|
let configFiles = target.inputFiles.filter {
|
||||||
|
$0.url.lastPathComponent == configFileName
|
||||||
|
}.map { $0.url }
|
||||||
|
let inputFiles = target.inputFiles.filter { $0.url.lastPathComponent.hasSuffix(".proto") }.map {
|
||||||
|
$0.url
|
||||||
|
}
|
||||||
|
return try createBuildCommands(
|
||||||
|
pluginWorkDirectory: context.pluginWorkDirectoryURL,
|
||||||
|
tool: context.tool,
|
||||||
|
inputFiles: inputFiles,
|
||||||
|
configFiles: configFiles,
|
||||||
|
targetName: target.displayName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
@main
|
||||||
|
struct GRPCProtobufGenerator {
|
||||||
|
/// Build plugin common code
|
||||||
|
func createBuildCommands(
|
||||||
|
pluginWorkDirectory: URL,
|
||||||
|
tool: (String) throws -> PluginContext.Tool,
|
||||||
|
inputFiles: [URL],
|
||||||
|
configFiles: [URL],
|
||||||
|
targetName: String
|
||||||
|
) throws -> [Command] {
|
||||||
|
let configs = try readConfigFiles(configFiles, pluginWorkDirectory: pluginWorkDirectory)
|
||||||
|
|
||||||
|
let protocGenGRPCSwiftPath = try tool("protoc-gen-grpc-swift-2").url
|
||||||
|
let protocGenSwiftPath = try tool("protoc-gen-swift").url
|
||||||
|
|
||||||
|
var commands: [Command] = []
|
||||||
|
for inputFile in inputFiles {
|
||||||
|
guard let (configFilePath, config) = configs.findApplicableConfig(for: inputFile) else {
|
||||||
|
throw BuildPluginError.noConfigFilesFound
|
||||||
|
}
|
||||||
|
|
||||||
|
let protocPath = try deriveProtocPath(using: config, tool: tool)
|
||||||
|
let protoDirectoryPaths: [String]
|
||||||
|
if config.importPaths.isEmpty {
|
||||||
|
protoDirectoryPaths = [configFilePath.deletingLastPathComponent().absoluteStringNoScheme]
|
||||||
|
} else {
|
||||||
|
protoDirectoryPaths = config.importPaths
|
||||||
|
}
|
||||||
|
|
||||||
|
// unless *explicitly* opted-out
|
||||||
|
if config.clients || config.servers {
|
||||||
|
let grpcCommand = try protocGenGRPCSwiftCommand(
|
||||||
|
inputFile: inputFile,
|
||||||
|
config: config,
|
||||||
|
baseDirectoryPath: configFilePath.deletingLastPathComponent(),
|
||||||
|
protoDirectoryPaths: protoDirectoryPaths,
|
||||||
|
protocPath: protocPath,
|
||||||
|
protocGenGRPCSwiftPath: protocGenGRPCSwiftPath,
|
||||||
|
configFilePath: configFilePath
|
||||||
|
)
|
||||||
|
commands.append(grpcCommand)
|
||||||
|
}
|
||||||
|
|
||||||
|
// unless *explicitly* opted-out
|
||||||
|
if config.messages {
|
||||||
|
let protoCommand = try protocGenSwiftCommand(
|
||||||
|
inputFile: inputFile,
|
||||||
|
config: config,
|
||||||
|
baseDirectoryPath: configFilePath.deletingLastPathComponent(),
|
||||||
|
protoDirectoryPaths: protoDirectoryPaths,
|
||||||
|
protocPath: protocPath,
|
||||||
|
protocGenSwiftPath: protocGenSwiftPath,
|
||||||
|
configFilePath: configFilePath
|
||||||
|
)
|
||||||
|
commands.append(protoCommand)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return commands
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads the config files at the supplied URLs into memory
|
||||||
|
/// - Parameter configFilePaths: URLs from which to load config
|
||||||
|
/// - Returns: A map of source URLs to loaded config
|
||||||
|
func readConfigFiles(
|
||||||
|
_ configFilePaths: [URL],
|
||||||
|
pluginWorkDirectory: URL
|
||||||
|
) throws -> [URL: GenerationConfig] {
|
||||||
|
var configs: [URL: GenerationConfig] = [:]
|
||||||
|
for configFilePath in configFilePaths {
|
||||||
|
let data = try Data(contentsOf: configFilePath)
|
||||||
|
let config = try JSONDecoder().decode(BuildPluginConfig.self, from: data)
|
||||||
|
|
||||||
|
// the output directory mandated by the plugin system
|
||||||
|
configs[configFilePath] = GenerationConfig(
|
||||||
|
buildPluginConfig: config,
|
||||||
|
configFilePath: configFilePath,
|
||||||
|
outputPath: pluginWorkDirectory
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return configs
|
||||||
|
}
|
||||||
|
|
||||||
|
extension [URL: GenerationConfig] {
|
||||||
|
/// Finds the most relevant config file for a given proto file URL.
|
||||||
|
///
|
||||||
|
/// The most relevant config file is the lowest of config files which are either a sibling or a parent in the file heirarchy.
|
||||||
|
/// - Parameters:
|
||||||
|
/// - file: The path to the proto file to be matched.
|
||||||
|
/// - Returns: The path to the most precisely relevant config file if one is found and the config itself, otherwise `nil`.
|
||||||
|
func findApplicableConfig(for file: URL) -> (URL, GenerationConfig)? {
|
||||||
|
let filePathComponents = file.pathComponents
|
||||||
|
for endComponent in (0 ..< filePathComponents.count).reversed() {
|
||||||
|
for (configFilePath, config) in self {
|
||||||
|
if filePathComponents[..<endComponent]
|
||||||
|
== configFilePath.pathComponents[..<(configFilePath.pathComponents.count - 1)]
|
||||||
|
{
|
||||||
|
return (configFilePath, config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct the command to invoke `protoc` with the `protoc-gen-grpc-swift-2` plugin.
|
||||||
|
/// - Parameters:
|
||||||
|
/// - inputFile: The input `.proto` file.
|
||||||
|
/// - config: The config for this operation.
|
||||||
|
/// - baseDirectoryPath: The root path to the source `.proto` files used as the reference for relative path naming schemes.
|
||||||
|
/// - protoDirectoryPaths: The paths passed to `protoc` in which to look for imported proto files.
|
||||||
|
/// - protocPath: The path to `protoc`
|
||||||
|
/// - protocGenGRPCSwiftPath: The path to `protoc-gen-grpc-swift-2`.
|
||||||
|
/// - configFilePath: The path to the config file in use.
|
||||||
|
/// - Returns: The command to invoke `protoc` with the `protoc-gen-grpc-swift-2` plugin.
|
||||||
|
func protocGenGRPCSwiftCommand(
|
||||||
|
inputFile: URL,
|
||||||
|
config: GenerationConfig,
|
||||||
|
baseDirectoryPath: URL,
|
||||||
|
protoDirectoryPaths: [String],
|
||||||
|
protocPath: URL,
|
||||||
|
protocGenGRPCSwiftPath: URL,
|
||||||
|
configFilePath: URL
|
||||||
|
) throws -> PackagePlugin.Command {
|
||||||
|
let outputPathURL = URL(fileURLWithPath: config.outputPath)
|
||||||
|
|
||||||
|
let outputFilePath = deriveOutputFilePath(
|
||||||
|
protoFile: inputFile,
|
||||||
|
baseDirectoryPath: baseDirectoryPath,
|
||||||
|
outputDirectory: outputPathURL,
|
||||||
|
outputExtension: "grpc.swift"
|
||||||
|
)
|
||||||
|
|
||||||
|
let arguments = constructProtocGenGRPCSwiftArguments(
|
||||||
|
config: config,
|
||||||
|
fileNaming: config.fileNaming,
|
||||||
|
inputFiles: [inputFile],
|
||||||
|
protoDirectoryPaths: protoDirectoryPaths,
|
||||||
|
protocGenGRPCSwiftPath: protocGenGRPCSwiftPath,
|
||||||
|
outputDirectory: outputPathURL
|
||||||
|
)
|
||||||
|
|
||||||
|
return Command.buildCommand(
|
||||||
|
displayName: "Generating gRPC Swift files for \(inputFile.absoluteStringNoScheme)",
|
||||||
|
executable: protocPath,
|
||||||
|
arguments: arguments,
|
||||||
|
inputFiles: [
|
||||||
|
inputFile,
|
||||||
|
protocGenGRPCSwiftPath,
|
||||||
|
configFilePath,
|
||||||
|
],
|
||||||
|
outputFiles: [outputFilePath]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct the command to invoke `protoc` with the `protoc-gen-swift` plugin.
|
||||||
|
/// - Parameters:
|
||||||
|
/// - inputFile: The input `.proto` file.
|
||||||
|
/// - config: The config for this operation.
|
||||||
|
/// - baseDirectoryPath: The root path to the source `.proto` files used as the reference for relative path naming schemes.
|
||||||
|
/// - protoDirectoryPaths: The paths passed to `protoc` in which to look for imported proto files.
|
||||||
|
/// - protocPath: The path to `protoc`
|
||||||
|
/// - protocGenSwiftPath: The path to `protoc-gen-grpc-swift-2`.
|
||||||
|
/// - configFilePath: The path to the config file in use.
|
||||||
|
/// - Returns: The command to invoke `protoc` with the `protoc-gen-swift` plugin.
|
||||||
|
func protocGenSwiftCommand(
|
||||||
|
inputFile: URL,
|
||||||
|
config: GenerationConfig,
|
||||||
|
baseDirectoryPath: URL,
|
||||||
|
protoDirectoryPaths: [String],
|
||||||
|
protocPath: URL,
|
||||||
|
protocGenSwiftPath: URL,
|
||||||
|
configFilePath: URL
|
||||||
|
) throws -> PackagePlugin.Command {
|
||||||
|
let outputPathURL = URL(fileURLWithPath: config.outputPath)
|
||||||
|
|
||||||
|
let outputFilePath = deriveOutputFilePath(
|
||||||
|
protoFile: inputFile,
|
||||||
|
baseDirectoryPath: baseDirectoryPath,
|
||||||
|
outputDirectory: outputPathURL,
|
||||||
|
outputExtension: "pb.swift"
|
||||||
|
)
|
||||||
|
|
||||||
|
let arguments = constructProtocGenSwiftArguments(
|
||||||
|
config: config,
|
||||||
|
fileNaming: config.fileNaming,
|
||||||
|
inputFiles: [inputFile],
|
||||||
|
protoDirectoryPaths: protoDirectoryPaths,
|
||||||
|
protocGenSwiftPath: protocGenSwiftPath,
|
||||||
|
outputDirectory: outputPathURL
|
||||||
|
)
|
||||||
|
|
||||||
|
return Command.buildCommand(
|
||||||
|
displayName: "Generating Swift Protobuf files for \(inputFile.absoluteStringNoScheme)",
|
||||||
|
executable: protocPath,
|
||||||
|
arguments: arguments,
|
||||||
|
inputFiles: [
|
||||||
|
inputFile,
|
||||||
|
protocGenSwiftPath,
|
||||||
|
configFilePath,
|
||||||
|
],
|
||||||
|
outputFiles: [outputFilePath]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Derive the expected output file path to match the behavior of the `protoc-gen-swift`
|
||||||
|
/// and `protoc-gen-grpc-swift-2` `protoc` plugins using the `PathToUnderscores` naming scheme.
|
||||||
|
///
|
||||||
|
/// This means the generated file for an input proto file called "foo/bar/baz.proto" will
|
||||||
|
/// have the name "foo\_bar\_baz.proto".
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - protoFile: The path of the input `.proto` file.
|
||||||
|
/// - baseDirectoryPath: The root path to the source `.proto` files used as the reference for
|
||||||
|
/// relative path naming schemes.
|
||||||
|
/// - outputDirectory: The directory in which generated source files are created.
|
||||||
|
/// - outputExtension: The file extension to be appended to generated files in-place of `.proto`.
|
||||||
|
/// - Returns: The expected output file path.
|
||||||
|
func deriveOutputFilePath(
|
||||||
|
protoFile: URL,
|
||||||
|
baseDirectoryPath: URL,
|
||||||
|
outputDirectory: URL,
|
||||||
|
outputExtension: String
|
||||||
|
) -> URL {
|
||||||
|
// Replace the extension (".proto") with the new extension (".grpc.swift"
|
||||||
|
// or ".pb.swift").
|
||||||
|
precondition(protoFile.pathExtension == "proto")
|
||||||
|
let fileName = String(protoFile.lastPathComponent.dropLast(5) + outputExtension)
|
||||||
|
|
||||||
|
// find the inputFile path relative to the proto directory
|
||||||
|
var relativePathComponents = protoFile.deletingLastPathComponent().pathComponents
|
||||||
|
for protoDirectoryPathComponent in baseDirectoryPath.pathComponents {
|
||||||
|
if relativePathComponents.first == protoDirectoryPathComponent {
|
||||||
|
relativePathComponents.removeFirst()
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
relativePathComponents.append(fileName)
|
||||||
|
let path = relativePathComponents.joined(separator: "_")
|
||||||
|
return outputDirectory.appending(path: path)
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
../PluginsShared
|
|
@ -0,0 +1,255 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024, gRPC Authors All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import PackagePlugin
|
||||||
|
|
||||||
|
struct CommandConfig {
|
||||||
|
var common: GenerationConfig
|
||||||
|
|
||||||
|
var verbose: Bool
|
||||||
|
var dryRun: Bool
|
||||||
|
|
||||||
|
static let defaults = Self(
|
||||||
|
common: .init(
|
||||||
|
accessLevel: .internal,
|
||||||
|
servers: true,
|
||||||
|
clients: true,
|
||||||
|
messages: true,
|
||||||
|
fileNaming: .fullPath,
|
||||||
|
accessLevelOnImports: false,
|
||||||
|
importPaths: [],
|
||||||
|
outputPath: ""
|
||||||
|
),
|
||||||
|
verbose: false,
|
||||||
|
dryRun: false
|
||||||
|
)
|
||||||
|
|
||||||
|
static let parameterGroupSeparator = "--"
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CommandConfig {
|
||||||
|
static func parse(
|
||||||
|
argumentExtractor argExtractor: inout ArgumentExtractor,
|
||||||
|
pluginWorkDirectory: URL
|
||||||
|
) throws -> CommandConfig {
|
||||||
|
var config = CommandConfig.defaults
|
||||||
|
|
||||||
|
for flag in OptionsAndFlags.allCases {
|
||||||
|
switch flag {
|
||||||
|
case .accessLevel:
|
||||||
|
if let value = argExtractor.extractSingleOption(named: flag.rawValue) {
|
||||||
|
if let accessLevel = GenerationConfig.AccessLevel(rawValue: value) {
|
||||||
|
config.common.accessLevel = accessLevel
|
||||||
|
} else {
|
||||||
|
throw CommandPluginError.unknownAccessLevel(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case .noServers:
|
||||||
|
// Handled by `.servers`
|
||||||
|
continue
|
||||||
|
case .servers:
|
||||||
|
let servers = argExtractor.extractFlag(named: OptionsAndFlags.servers.rawValue)
|
||||||
|
let noServers = argExtractor.extractFlag(named: OptionsAndFlags.noServers.rawValue)
|
||||||
|
if servers > 0 && noServers > 0 {
|
||||||
|
throw CommandPluginError.conflictingFlags(
|
||||||
|
OptionsAndFlags.servers.rawValue,
|
||||||
|
OptionsAndFlags.noServers.rawValue
|
||||||
|
)
|
||||||
|
} else if servers > 0 {
|
||||||
|
config.common.servers = true
|
||||||
|
} else if noServers > 0 {
|
||||||
|
config.common.servers = false
|
||||||
|
}
|
||||||
|
|
||||||
|
case .noClients:
|
||||||
|
// Handled by `.clients`
|
||||||
|
continue
|
||||||
|
case .clients:
|
||||||
|
let clients = argExtractor.extractFlag(named: OptionsAndFlags.clients.rawValue)
|
||||||
|
let noClients = argExtractor.extractFlag(named: OptionsAndFlags.noClients.rawValue)
|
||||||
|
if clients > 0 && noClients > 0 {
|
||||||
|
throw CommandPluginError.conflictingFlags(
|
||||||
|
OptionsAndFlags.clients.rawValue,
|
||||||
|
OptionsAndFlags.noClients.rawValue
|
||||||
|
)
|
||||||
|
} else if clients > 0 {
|
||||||
|
config.common.clients = true
|
||||||
|
} else if noClients > 0 {
|
||||||
|
config.common.clients = false
|
||||||
|
}
|
||||||
|
|
||||||
|
case .noMessages:
|
||||||
|
// Handled by `.messages`
|
||||||
|
continue
|
||||||
|
case .messages:
|
||||||
|
let messages = argExtractor.extractFlag(named: OptionsAndFlags.messages.rawValue)
|
||||||
|
let noMessages = argExtractor.extractFlag(named: OptionsAndFlags.noMessages.rawValue)
|
||||||
|
if messages > 0 && noMessages > 0 {
|
||||||
|
throw CommandPluginError.conflictingFlags(
|
||||||
|
OptionsAndFlags.messages.rawValue,
|
||||||
|
OptionsAndFlags.noMessages.rawValue
|
||||||
|
)
|
||||||
|
} else if messages > 0 {
|
||||||
|
config.common.messages = true
|
||||||
|
} else if noMessages > 0 {
|
||||||
|
config.common.messages = false
|
||||||
|
}
|
||||||
|
|
||||||
|
case .fileNaming:
|
||||||
|
if let value = argExtractor.extractSingleOption(named: flag.rawValue) {
|
||||||
|
if let fileNaming = GenerationConfig.FileNaming(rawValue: value) {
|
||||||
|
config.common.fileNaming = fileNaming
|
||||||
|
} else {
|
||||||
|
throw CommandPluginError.unknownFileNamingStrategy(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case .accessLevelOnImports:
|
||||||
|
if argExtractor.extractFlag(named: flag.rawValue) > 0 {
|
||||||
|
config.common.accessLevelOnImports = true
|
||||||
|
}
|
||||||
|
|
||||||
|
case .importPath:
|
||||||
|
config.common.importPaths = argExtractor.extractOption(named: flag.rawValue)
|
||||||
|
|
||||||
|
case .protocPath:
|
||||||
|
config.common.protocPath = argExtractor.extractSingleOption(named: flag.rawValue)
|
||||||
|
|
||||||
|
case .outputPath:
|
||||||
|
config.common.outputPath =
|
||||||
|
argExtractor.extractSingleOption(named: flag.rawValue)
|
||||||
|
?? pluginWorkDirectory.absoluteStringNoScheme
|
||||||
|
|
||||||
|
case .verbose:
|
||||||
|
let verbose = argExtractor.extractFlag(named: flag.rawValue)
|
||||||
|
config.verbose = verbose != 0
|
||||||
|
|
||||||
|
case .dryRun:
|
||||||
|
let dryRun = argExtractor.extractFlag(named: flag.rawValue)
|
||||||
|
config.dryRun = dryRun != 0
|
||||||
|
|
||||||
|
case .help:
|
||||||
|
() // handled elsewhere
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let argument = argExtractor.remainingArguments.first {
|
||||||
|
throw CommandPluginError.unknownOption(argument)
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ArgumentExtractor {
|
||||||
|
mutating func extractSingleOption(named optionName: String) -> String? {
|
||||||
|
let values = self.extractOption(named: optionName)
|
||||||
|
if values.count > 1 {
|
||||||
|
Diagnostics.warning(
|
||||||
|
"'--\(optionName)' was unexpectedly repeated, the first value will be used."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return values.first
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// All valid input options/flags
|
||||||
|
enum OptionsAndFlags: String, CaseIterable {
|
||||||
|
case servers
|
||||||
|
case noServers = "no-servers"
|
||||||
|
case clients
|
||||||
|
case noClients = "no-clients"
|
||||||
|
case messages
|
||||||
|
case noMessages = "no-messages"
|
||||||
|
case fileNaming = "file-naming"
|
||||||
|
case accessLevel = "access-level"
|
||||||
|
case accessLevelOnImports = "access-level-on-imports"
|
||||||
|
case importPath = "import-path"
|
||||||
|
case protocPath = "protoc-path"
|
||||||
|
case outputPath = "output-path"
|
||||||
|
case verbose
|
||||||
|
case dryRun = "dry-run"
|
||||||
|
|
||||||
|
case help
|
||||||
|
}
|
||||||
|
|
||||||
|
extension OptionsAndFlags {
|
||||||
|
func usageDescription() -> String {
|
||||||
|
switch self {
|
||||||
|
case .servers:
|
||||||
|
return "Generate server code. Generated by default."
|
||||||
|
case .noServers:
|
||||||
|
return "Do not generate server code. Generated by default."
|
||||||
|
case .clients:
|
||||||
|
return "Generate client code. Generated by default."
|
||||||
|
case .noClients:
|
||||||
|
return "Do not generate client code. Generated by default."
|
||||||
|
case .messages:
|
||||||
|
return "Generate message code. Generated by default."
|
||||||
|
case .noMessages:
|
||||||
|
return "Do not generate message code. Generated by default."
|
||||||
|
case .fileNaming:
|
||||||
|
return
|
||||||
|
"The naming scheme for output files [fullPath/pathToUnderscores/dropPath]. Defaults to fullPath."
|
||||||
|
case .accessLevel:
|
||||||
|
return
|
||||||
|
"The access level of the generated source [internal/public/package]. Defaults to internal."
|
||||||
|
case .accessLevelOnImports:
|
||||||
|
return "Whether imports should have explicit access levels. Defaults to false."
|
||||||
|
case .importPath:
|
||||||
|
return
|
||||||
|
"The directory in which to search for imports. May be specified multiple times. If none are specified the current working directory is used."
|
||||||
|
case .protocPath:
|
||||||
|
return "The path to the protoc binary."
|
||||||
|
case .dryRun:
|
||||||
|
return "Print but do not execute the protoc commands."
|
||||||
|
case .outputPath:
|
||||||
|
return "The directory into which the generated source files are created."
|
||||||
|
case .verbose:
|
||||||
|
return "Emit verbose output."
|
||||||
|
case .help:
|
||||||
|
return "Print this help."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func printHelp(requested: Bool) {
|
||||||
|
let printMessage: (String) -> Void
|
||||||
|
if requested {
|
||||||
|
printMessage = { message in print(message) }
|
||||||
|
} else {
|
||||||
|
printMessage = Stderr.print
|
||||||
|
}
|
||||||
|
|
||||||
|
printMessage(
|
||||||
|
"Usage: swift package generate-grpc-code-from-protos [flags] [\(CommandConfig.parameterGroupSeparator)] [input files]"
|
||||||
|
)
|
||||||
|
printMessage("")
|
||||||
|
printMessage("Flags:")
|
||||||
|
printMessage("")
|
||||||
|
|
||||||
|
let spacing = 3
|
||||||
|
let maxLength =
|
||||||
|
(OptionsAndFlags.allCases.map(\.rawValue).max(by: { $0.count < $1.count })?.count ?? 0)
|
||||||
|
+ spacing
|
||||||
|
for flag in OptionsAndFlags.allCases {
|
||||||
|
printMessage(
|
||||||
|
" --\(flag.rawValue.padding(toLength: maxLength, withPad: " ", startingAt: 0))\(flag.usageDescription())"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025, gRPC Authors All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
enum CommandPluginError: Error {
|
||||||
|
case invalidArgumentValue(name: String, value: String)
|
||||||
|
case missingInputFile
|
||||||
|
case unknownOption(String)
|
||||||
|
case unknownAccessLevel(String)
|
||||||
|
case unknownFileNamingStrategy(String)
|
||||||
|
case conflictingFlags(String, String)
|
||||||
|
case generationFailure(
|
||||||
|
errorDescription: String,
|
||||||
|
executable: String,
|
||||||
|
arguments: [String],
|
||||||
|
stdErr: String?
|
||||||
|
)
|
||||||
|
case tooManyParameterSeparators
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CommandPluginError: CustomStringConvertible {
|
||||||
|
var description: String {
|
||||||
|
switch self {
|
||||||
|
case .invalidArgumentValue(let name, let value):
|
||||||
|
return "Invalid value '\(value)', for '\(name)'."
|
||||||
|
case .missingInputFile:
|
||||||
|
return "No input file(s) specified."
|
||||||
|
case .unknownOption(let name):
|
||||||
|
return "Provided option is unknown: \(name)."
|
||||||
|
case .unknownAccessLevel(let value):
|
||||||
|
return "Provided access level is unknown: \(value)."
|
||||||
|
case .unknownFileNamingStrategy(let value):
|
||||||
|
return "Provided file naming strategy is unknown: \(value)."
|
||||||
|
case .conflictingFlags(let flag1, let flag2):
|
||||||
|
return "Provided flags conflict: '\(flag1)' and '\(flag2)'."
|
||||||
|
case .generationFailure(let errorDescription, let executable, let arguments, let stdErr):
|
||||||
|
var message = """
|
||||||
|
Code generation failed with: \(errorDescription).
|
||||||
|
\tExecutable: \(executable)
|
||||||
|
\tArguments: \(arguments.joined(separator: " "))
|
||||||
|
"""
|
||||||
|
if let stdErr {
|
||||||
|
message += """
|
||||||
|
\n\tprotoc error output:
|
||||||
|
\t\(stdErr)
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
return message
|
||||||
|
case .tooManyParameterSeparators:
|
||||||
|
return "Unexpected parameter structure, too many '--' separators."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,243 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024, gRPC Authors All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import PackagePlugin
|
||||||
|
|
||||||
|
extension GRPCProtobufGeneratorCommandPlugin: CommandPlugin {
|
||||||
|
func performCommand(context: PluginContext, arguments: [String]) async throws {
|
||||||
|
try self.performCommand(
|
||||||
|
arguments: arguments,
|
||||||
|
tool: context.tool,
|
||||||
|
pluginWorkDirectoryURL: context.pluginWorkDirectoryURL
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if canImport(XcodeProjectPlugin)
|
||||||
|
import XcodeProjectPlugin
|
||||||
|
|
||||||
|
// Entry-point when using Xcode projects
|
||||||
|
extension GRPCProtobufGeneratorCommandPlugin: XcodeCommandPlugin {
|
||||||
|
func performCommand(context: XcodeProjectPlugin.XcodePluginContext, arguments: [String]) throws {
|
||||||
|
try self.performCommand(
|
||||||
|
arguments: arguments,
|
||||||
|
tool: context.tool,
|
||||||
|
pluginWorkDirectoryURL: context.pluginWorkDirectoryURL
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
@main
|
||||||
|
struct GRPCProtobufGeneratorCommandPlugin {
|
||||||
|
/// Command plugin common code
|
||||||
|
func performCommand(
|
||||||
|
arguments: [String],
|
||||||
|
tool: (String) throws -> PluginContext.Tool,
|
||||||
|
pluginWorkDirectoryURL: URL
|
||||||
|
) throws {
|
||||||
|
let flagsAndOptions: [String]
|
||||||
|
let inputFiles: [String]
|
||||||
|
|
||||||
|
let separatorCount = arguments.count { $0 == CommandConfig.parameterGroupSeparator }
|
||||||
|
switch separatorCount {
|
||||||
|
case 0:
|
||||||
|
var argExtractor = ArgumentExtractor(arguments)
|
||||||
|
// check if help requested
|
||||||
|
if argExtractor.extractFlag(named: OptionsAndFlags.help.rawValue) > 0 {
|
||||||
|
OptionsAndFlags.printHelp(requested: true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
inputFiles = arguments
|
||||||
|
flagsAndOptions = []
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
let splitIndex = arguments.firstIndex(of: CommandConfig.parameterGroupSeparator)!
|
||||||
|
flagsAndOptions = Array(arguments[..<splitIndex])
|
||||||
|
inputFiles = Array(arguments[splitIndex.advanced(by: 1)...])
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw CommandPluginError.tooManyParameterSeparators
|
||||||
|
}
|
||||||
|
|
||||||
|
var argExtractor = ArgumentExtractor(flagsAndOptions)
|
||||||
|
// help requested
|
||||||
|
if argExtractor.extractFlag(named: OptionsAndFlags.help.rawValue) > 0 {
|
||||||
|
OptionsAndFlags.printHelp(requested: true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Configuration
|
||||||
|
let commandConfig: CommandConfig
|
||||||
|
do {
|
||||||
|
commandConfig = try CommandConfig.parse(
|
||||||
|
argumentExtractor: &argExtractor,
|
||||||
|
pluginWorkDirectory: pluginWorkDirectoryURL
|
||||||
|
)
|
||||||
|
} catch {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
if commandConfig.verbose {
|
||||||
|
Stderr.print("InputFiles: \(inputFiles.joined(separator: ", "))")
|
||||||
|
}
|
||||||
|
|
||||||
|
let config = commandConfig.common
|
||||||
|
let protocPath = try deriveProtocPath(using: config, tool: tool)
|
||||||
|
let protocGenGRPCSwiftPath = try tool("protoc-gen-grpc-swift-2").url
|
||||||
|
let protocGenSwiftPath = try tool("protoc-gen-swift").url
|
||||||
|
|
||||||
|
let outputDirectory = URL(fileURLWithPath: config.outputPath)
|
||||||
|
if commandConfig.verbose {
|
||||||
|
Stderr.print(
|
||||||
|
"Generated files will be written to: '\(outputDirectory.absoluteStringNoScheme)'"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let inputFileURLs = inputFiles.map { URL(fileURLWithPath: $0) }
|
||||||
|
|
||||||
|
// MARK: protoc-gen-grpc-swift-2
|
||||||
|
if config.clients || config.servers {
|
||||||
|
let arguments = constructProtocGenGRPCSwiftArguments(
|
||||||
|
config: config,
|
||||||
|
fileNaming: config.fileNaming,
|
||||||
|
inputFiles: inputFileURLs,
|
||||||
|
protoDirectoryPaths: config.importPaths,
|
||||||
|
protocGenGRPCSwiftPath: protocGenGRPCSwiftPath,
|
||||||
|
outputDirectory: outputDirectory
|
||||||
|
)
|
||||||
|
|
||||||
|
try executeProtocInvocation(
|
||||||
|
executableURL: protocPath,
|
||||||
|
arguments: arguments,
|
||||||
|
verbose: commandConfig.verbose,
|
||||||
|
dryRun: commandConfig.dryRun
|
||||||
|
)
|
||||||
|
|
||||||
|
if !commandConfig.dryRun, commandConfig.verbose {
|
||||||
|
Stderr.print("Generated gRPC Swift files for \(inputFiles.joined(separator: ", ")).")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: protoc-gen-swift
|
||||||
|
if config.messages {
|
||||||
|
let arguments = constructProtocGenSwiftArguments(
|
||||||
|
config: config,
|
||||||
|
fileNaming: config.fileNaming,
|
||||||
|
inputFiles: inputFileURLs,
|
||||||
|
protoDirectoryPaths: config.importPaths,
|
||||||
|
protocGenSwiftPath: protocGenSwiftPath,
|
||||||
|
outputDirectory: outputDirectory
|
||||||
|
)
|
||||||
|
|
||||||
|
try executeProtocInvocation(
|
||||||
|
executableURL: protocPath,
|
||||||
|
arguments: arguments,
|
||||||
|
verbose: commandConfig.verbose,
|
||||||
|
dryRun: commandConfig.dryRun
|
||||||
|
)
|
||||||
|
|
||||||
|
if !commandConfig.dryRun, commandConfig.verbose {
|
||||||
|
Stderr.print(
|
||||||
|
"Generated protobuf message Swift files for \(inputFiles.joined(separator: ", "))."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute a single invocation of `protoc`, printing output and if in verbose mode the invocation
|
||||||
|
/// - Parameters:
|
||||||
|
/// - executableURL: The path to the `protoc` executable.
|
||||||
|
/// - arguments: The arguments to be passed to `protoc`.
|
||||||
|
/// - verbose: Whether or not to print verbose output
|
||||||
|
/// - dryRun: If this invocation is a dry-run, i.e. will not actually be executed
|
||||||
|
func executeProtocInvocation(
|
||||||
|
executableURL: URL,
|
||||||
|
arguments: [String],
|
||||||
|
verbose: Bool,
|
||||||
|
dryRun: Bool
|
||||||
|
) throws {
|
||||||
|
if verbose {
|
||||||
|
Stderr.print("\(executableURL.absoluteStringNoScheme) \\")
|
||||||
|
Stderr.print(" \(arguments.joined(separator: " \\\n "))")
|
||||||
|
}
|
||||||
|
|
||||||
|
if dryRun {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let process = Process()
|
||||||
|
process.executableURL = executableURL
|
||||||
|
process.arguments = arguments
|
||||||
|
|
||||||
|
let outputPipe = Pipe()
|
||||||
|
let errorPipe = Pipe()
|
||||||
|
process.standardOutput = outputPipe
|
||||||
|
process.standardError = errorPipe
|
||||||
|
|
||||||
|
do {
|
||||||
|
try process.run()
|
||||||
|
} catch {
|
||||||
|
try printProtocOutput(outputPipe, verbose: verbose)
|
||||||
|
let stdErr: String?
|
||||||
|
if let errorData = try errorPipe.fileHandleForReading.readToEnd() {
|
||||||
|
stdErr = String(decoding: errorData, as: UTF8.self)
|
||||||
|
} else {
|
||||||
|
stdErr = nil
|
||||||
|
}
|
||||||
|
throw CommandPluginError.generationFailure(
|
||||||
|
errorDescription: "\(error)",
|
||||||
|
executable: executableURL.absoluteStringNoScheme,
|
||||||
|
arguments: arguments,
|
||||||
|
stdErr: stdErr
|
||||||
|
)
|
||||||
|
}
|
||||||
|
process.waitUntilExit()
|
||||||
|
|
||||||
|
try printProtocOutput(outputPipe, verbose: verbose)
|
||||||
|
|
||||||
|
if process.terminationReason == .exit && process.terminationStatus == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let stdErr: String?
|
||||||
|
if let errorData = try errorPipe.fileHandleForReading.readToEnd() {
|
||||||
|
stdErr = String(decoding: errorData, as: UTF8.self)
|
||||||
|
} else {
|
||||||
|
stdErr = nil
|
||||||
|
}
|
||||||
|
let problem = "\(process.terminationReason):\(process.terminationStatus)"
|
||||||
|
throw CommandPluginError.generationFailure(
|
||||||
|
errorDescription: problem,
|
||||||
|
executable: executableURL.absoluteStringNoScheme,
|
||||||
|
arguments: arguments,
|
||||||
|
stdErr: stdErr
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func printProtocOutput(_ stdOut: Pipe, verbose: Bool) throws {
|
||||||
|
if verbose, let outputData = try stdOut.fileHandleForReading.readToEnd() {
|
||||||
|
let output = String(decoding: outputData, as: UTF8.self)
|
||||||
|
let lines = output.split { $0.isNewline }
|
||||||
|
print("protoc output:")
|
||||||
|
for line in lines {
|
||||||
|
print("\t\(line)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
../PluginsShared
|
|
@ -0,0 +1,100 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024, gRPC Authors All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// The config used when generating code whether called from the build or command plugin.
|
||||||
|
struct GenerationConfig {
|
||||||
|
/// The access level (i.e. visibility) of the generated files.
|
||||||
|
enum AccessLevel: String {
|
||||||
|
/// The generated files should have `internal` access level.
|
||||||
|
case `internal` = "Internal"
|
||||||
|
/// The generated files should have `public` access level.
|
||||||
|
case `public` = "Public"
|
||||||
|
/// The generated files should have `package` access level.
|
||||||
|
case `package` = "Package"
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The naming of output files with respect to the path of the source file.
|
||||||
|
///
|
||||||
|
/// For an input of `foo/bar/baz.proto` the following output file will be generated:
|
||||||
|
/// - `FullPath`: `foo/bar/baz.grpc.swift`
|
||||||
|
/// - `PathToUnderscore`: `foo_bar_baz.grpc.swift`
|
||||||
|
/// - `DropPath`: `baz.grpc.swift`
|
||||||
|
enum FileNaming: String {
|
||||||
|
/// Replicate the input file path with the output file(s).
|
||||||
|
case fullPath = "FullPath"
|
||||||
|
/// Convert path directory delimiters to underscores.
|
||||||
|
case pathToUnderscores = "PathToUnderscores"
|
||||||
|
/// Generate output files using only the base name of the inout file, ignoring the path.
|
||||||
|
case dropPath = "DropPath"
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The visibility of the generated files.
|
||||||
|
var accessLevel: AccessLevel
|
||||||
|
/// Whether server code is generated.
|
||||||
|
var servers: Bool
|
||||||
|
/// Whether client code is generated.
|
||||||
|
var clients: Bool
|
||||||
|
/// Whether message code is generated.
|
||||||
|
var messages: Bool
|
||||||
|
/// The naming of output files with respect to the path of the source file.
|
||||||
|
var fileNaming: FileNaming
|
||||||
|
/// Whether imports should have explicit access levels.
|
||||||
|
var accessLevelOnImports: Bool
|
||||||
|
|
||||||
|
/// Specify the directory in which to search for imports.
|
||||||
|
///
|
||||||
|
/// May be specified multiple times; directories will be searched in order.
|
||||||
|
/// The target source directory is always appended to the import paths.
|
||||||
|
var importPaths: [String]
|
||||||
|
|
||||||
|
/// The path to the `protoc` binary.
|
||||||
|
///
|
||||||
|
/// If this is not set, Swift Package Manager will try to find the tool itself.
|
||||||
|
var protocPath: String?
|
||||||
|
|
||||||
|
/// The path into which the generated source files are created.
|
||||||
|
var outputPath: String
|
||||||
|
}
|
||||||
|
|
||||||
|
extension GenerationConfig.AccessLevel: Codable {
|
||||||
|
init?(rawValue: String) {
|
||||||
|
switch rawValue.lowercased() {
|
||||||
|
case "internal":
|
||||||
|
self = .internal
|
||||||
|
case "public":
|
||||||
|
self = .public
|
||||||
|
case "package":
|
||||||
|
self = .package
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension GenerationConfig.FileNaming: Codable {
|
||||||
|
init?(rawValue: String) {
|
||||||
|
switch rawValue.lowercased() {
|
||||||
|
case "fullpath":
|
||||||
|
self = .fullPath
|
||||||
|
case "pathtounderscores":
|
||||||
|
self = .pathToUnderscores
|
||||||
|
case "droppath":
|
||||||
|
self = .dropPath
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,132 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024, gRPC Authors All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import PackagePlugin
|
||||||
|
|
||||||
|
let configFileName = "grpc-swift-proto-generator-config.json"
|
||||||
|
|
||||||
|
/// Derive the path to the instance of `protoc` to be used.
|
||||||
|
/// - Parameters:
|
||||||
|
/// - config: The supplied config. If no path is supplied then one is discovered using the `PROTOC_PATH` environment variable or the `findTool`.
|
||||||
|
/// - findTool: The context-supplied tool which is used to attempt to discover the path to a `protoc` binary.
|
||||||
|
/// - Returns: The path to the instance of `protoc` to be used.
|
||||||
|
func deriveProtocPath(
|
||||||
|
using config: GenerationConfig,
|
||||||
|
tool findTool: (String) throws -> PackagePlugin.PluginContext.Tool
|
||||||
|
) throws -> URL {
|
||||||
|
if let configuredProtocPath = config.protocPath {
|
||||||
|
return URL(fileURLWithPath: configuredProtocPath)
|
||||||
|
} else if let environmentPath = ProcessInfo.processInfo.environment["PROTOC_PATH"] {
|
||||||
|
// The user set the env variable, so let's take that
|
||||||
|
return URL(fileURLWithPath: environmentPath)
|
||||||
|
} else {
|
||||||
|
// The user didn't set anything so let's try see if Swift Package Manager can find a binary for us
|
||||||
|
return try findTool("protoc").url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct the arguments to be passed to `protoc` when invoking the `protoc-gen-swift` `protoc` plugin.
|
||||||
|
/// - Parameters:
|
||||||
|
/// - config: The config for this operation.
|
||||||
|
/// - fileNaming: The file naming scheme to be used.
|
||||||
|
/// - inputFiles: The input `.proto` files.
|
||||||
|
/// - protoDirectoryPaths: The directories in which `protoc` will look for imports.
|
||||||
|
/// - protocGenSwiftPath: The path to the `protoc-gen-swift` `protoc` plugin.
|
||||||
|
/// - outputDirectory: The directory in which generated source files are created.
|
||||||
|
/// - Returns: The constructed arguments to be passed to `protoc` when invoking the `protoc-gen-swift` `protoc` plugin.
|
||||||
|
func constructProtocGenSwiftArguments(
|
||||||
|
config: GenerationConfig,
|
||||||
|
fileNaming: GenerationConfig.FileNaming?,
|
||||||
|
inputFiles: [URL],
|
||||||
|
protoDirectoryPaths: [String],
|
||||||
|
protocGenSwiftPath: URL,
|
||||||
|
outputDirectory: URL
|
||||||
|
) -> [String] {
|
||||||
|
var protocArgs = [
|
||||||
|
"--plugin=protoc-gen-swift=\(protocGenSwiftPath.absoluteStringNoScheme)",
|
||||||
|
"--swift_out=\(outputDirectory.absoluteStringNoScheme)",
|
||||||
|
]
|
||||||
|
|
||||||
|
for path in protoDirectoryPaths {
|
||||||
|
protocArgs.append("--proto_path=\(path)")
|
||||||
|
}
|
||||||
|
|
||||||
|
protocArgs.append("--swift_opt=Visibility=\(config.accessLevel.rawValue)")
|
||||||
|
protocArgs.append("--swift_opt=FileNaming=\(config.fileNaming.rawValue)")
|
||||||
|
protocArgs.append("--swift_opt=UseAccessLevelOnImports=\(config.accessLevelOnImports)")
|
||||||
|
protocArgs.append(contentsOf: inputFiles.map { $0.absoluteStringNoScheme })
|
||||||
|
|
||||||
|
return protocArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct the arguments to be passed to `protoc` when invoking the `protoc-gen-grpc-swift-2` `protoc` plugin.
|
||||||
|
/// - Parameters:
|
||||||
|
/// - config: The config for this operation.
|
||||||
|
/// - fileNaming: The file naming scheme to be used.
|
||||||
|
/// - inputFiles: The input `.proto` files.
|
||||||
|
/// - protoDirectoryPaths: The directories in which `protoc` will look for imports.
|
||||||
|
/// - protocGenGRPCSwiftPath: The path to the `protoc-gen-grpc-swift-2` `protoc` plugin.
|
||||||
|
/// - outputDirectory: The directory in which generated source files are created.
|
||||||
|
/// - Returns: The constructed arguments to be passed to `protoc` when invoking the `protoc-gen-grpc-swift-2` `protoc` plugin.
|
||||||
|
func constructProtocGenGRPCSwiftArguments(
|
||||||
|
config: GenerationConfig,
|
||||||
|
fileNaming: GenerationConfig.FileNaming?,
|
||||||
|
inputFiles: [URL],
|
||||||
|
protoDirectoryPaths: [String],
|
||||||
|
protocGenGRPCSwiftPath: URL,
|
||||||
|
outputDirectory: URL
|
||||||
|
) -> [String] {
|
||||||
|
var protocArgs = [
|
||||||
|
"--plugin=protoc-gen-grpc-swift=\(protocGenGRPCSwiftPath.absoluteStringNoScheme)",
|
||||||
|
"--grpc-swift_out=\(outputDirectory.absoluteStringNoScheme)",
|
||||||
|
]
|
||||||
|
|
||||||
|
for path in protoDirectoryPaths {
|
||||||
|
protocArgs.append("--proto_path=\(path)")
|
||||||
|
}
|
||||||
|
|
||||||
|
protocArgs.append("--grpc-swift_opt=Visibility=\(config.accessLevel.rawValue.capitalized)")
|
||||||
|
protocArgs.append("--grpc-swift_opt=Server=\(config.servers)")
|
||||||
|
protocArgs.append("--grpc-swift_opt=Client=\(config.clients)")
|
||||||
|
protocArgs.append("--grpc-swift_opt=FileNaming=\(config.fileNaming.rawValue)")
|
||||||
|
protocArgs.append("--grpc-swift_opt=UseAccessLevelOnImports=\(config.accessLevelOnImports)")
|
||||||
|
protocArgs.append(contentsOf: inputFiles.map { $0.absoluteStringNoScheme })
|
||||||
|
|
||||||
|
return protocArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
extension URL {
|
||||||
|
/// Returns `URL.absoluteString` with the `file://` scheme prefix removed
|
||||||
|
///
|
||||||
|
/// Note: This method also removes percent-encoded UTF-8 characters
|
||||||
|
var absoluteStringNoScheme: String {
|
||||||
|
var absoluteString = self.absoluteString.removingPercentEncoding ?? self.absoluteString
|
||||||
|
absoluteString.trimPrefix("file://")
|
||||||
|
return absoluteString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Stderr {
|
||||||
|
private static let newLine = "\n".data(using: .utf8)!
|
||||||
|
|
||||||
|
static func print(_ message: String) {
|
||||||
|
if let data = message.data(using: .utf8) {
|
||||||
|
FileHandle.standardError.write(data)
|
||||||
|
FileHandle.standardError.write(Self.newLine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,5 +14,5 @@ for [gRPC Swift][gh-grpc-swift-protobuf].
|
||||||
|
|
||||||
[gh-swift-protobuf]: https://github.com/apple/swift-protobuf
|
[gh-swift-protobuf]: https://github.com/apple/swift-protobuf
|
||||||
[gh-grpc-swift-protobuf]: https://github.com/grpc/grpc-swift-protobuf
|
[gh-grpc-swift-protobuf]: https://github.com/grpc/grpc-swift-protobuf
|
||||||
[spi-grpc-swift]: https://swiftpackageindex.com/grpc/grpc-swift/documentation
|
[spi-grpc-swift]: https://swiftpackageindex.com/grpc/grpc-swift-2/documentation
|
||||||
[spi-grpc-swift-protobuf]: https://swiftpackageindex.com/grpc/grpc-swift-protobuf/documentation
|
[spi-grpc-swift-protobuf]: https://swiftpackageindex.com/grpc/grpc-swift-protobuf/documentation
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
// Copyright 2025, gRPC Authors All rights reserved.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#include "CGRPCProtobuf.h"
|
||||||
|
|
||||||
|
const char *cgrprc_grpc_swift_protobuf_version() {
|
||||||
|
return CGRPC_GRPC_SWIFT_PROTOBUF_VERSION;
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Copyright 2025, gRPC Authors All rights reserved.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#ifndef CGRPC_PROTOBUF_H_
|
||||||
|
#define CGRPC_PROTOBUF_H_
|
||||||
|
|
||||||
|
const char *cgrprc_grpc_swift_protobuf_version();
|
||||||
|
|
||||||
|
#endif // CGRPC_PROTOBUF_H_
|
|
@ -18,6 +18,7 @@ public import GRPCCore
|
||||||
public import SwiftProtobuf
|
public import SwiftProtobuf
|
||||||
|
|
||||||
/// Serializes a Protobuf message into a sequence of bytes.
|
/// Serializes a Protobuf message into a sequence of bytes.
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
public struct ProtobufSerializer<Message: SwiftProtobuf.Message>: GRPCCore.MessageSerializer {
|
public struct ProtobufSerializer<Message: SwiftProtobuf.Message>: GRPCCore.MessageSerializer {
|
||||||
public init() {}
|
public init() {}
|
||||||
|
|
||||||
|
@ -41,6 +42,7 @@ public struct ProtobufSerializer<Message: SwiftProtobuf.Message>: GRPCCore.Messa
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deserializes a sequence of bytes into a Protobuf message.
|
/// Deserializes a sequence of bytes into a Protobuf message.
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
public struct ProtobufDeserializer<Message: SwiftProtobuf.Message>: GRPCCore.MessageDeserializer {
|
public struct ProtobufDeserializer<Message: SwiftProtobuf.Message>: GRPCCore.MessageDeserializer {
|
||||||
public init() {}
|
public init() {}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ public import SwiftProtobuf // internal but @usableFromInline
|
||||||
/// it'd require a dependency on Protobuf in the core package), and `GRPCContiguousBytes` can't
|
/// it'd require a dependency on Protobuf in the core package), and `GRPCContiguousBytes` can't
|
||||||
/// refine `SwiftProtobufContiguousBytes` for the same reason.
|
/// refine `SwiftProtobufContiguousBytes` for the same reason.
|
||||||
@usableFromInline
|
@usableFromInline
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
struct ContiguousBytesAdapter<
|
struct ContiguousBytesAdapter<
|
||||||
Bytes: GRPCContiguousBytes
|
Bytes: GRPCContiguousBytes
|
||||||
>: GRPCContiguousBytes, SwiftProtobufContiguousBytes {
|
>: GRPCContiguousBytes, SwiftProtobufContiguousBytes {
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
# API stability of generated code
|
||||||
|
|
||||||
|
Understand the impact of changes you make to your Protocol Buffers files on the
|
||||||
|
generated Swift code.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The API of the generated code depends on three factors:
|
||||||
|
|
||||||
|
- The contents of the source `.proto` file.
|
||||||
|
- The options you use when generating the code.
|
||||||
|
- The code generator (the `protoc-gen-grpc-swift-2` plugin for `protoc`).
|
||||||
|
|
||||||
|
While this document applies specifically to the gRPC code generated and *not*
|
||||||
|
code for messages used as inputs and outputs of each method, the concepts still
|
||||||
|
broadly apply.
|
||||||
|
|
||||||
|
Some of the concepts used in this document are described in more detail in
|
||||||
|
<doc:Understanding-the-generated-code>.
|
||||||
|
|
||||||
|
## The source .proto file
|
||||||
|
|
||||||
|
The source `.proto` file defines the API of your service. You'll likely provide
|
||||||
|
it to users so that they can generate clients from it. In order to maintain API
|
||||||
|
stability for your service and for clients you must adhere to the following
|
||||||
|
rules:
|
||||||
|
|
||||||
|
1. You mustn't change the `package` the service is defined in.
|
||||||
|
2. You mustn't change or add the `swift_prefix` option.
|
||||||
|
3. You mustn't remove or change the name of any services in your `.proto` file.
|
||||||
|
4. You mustn't remove or change the name of any RPCs in your `.proto` file.
|
||||||
|
5. You mustn't change the message types used by any RPCs in your `.proto` file.
|
||||||
|
|
||||||
|
Failure to follow these will result in changes in the generated code which can
|
||||||
|
result in build failures for users.
|
||||||
|
|
||||||
|
Whilst this sounds restrictive you may do the following:
|
||||||
|
|
||||||
|
1. You may add a new RPC to an existing service in your `.proto` file.
|
||||||
|
2. You may add a new service to your `.proto` file (however it is recommended
|
||||||
|
that you define a single service per `.proto` file).
|
||||||
|
|
||||||
|
## The options you use for generating code
|
||||||
|
|
||||||
|
Code you generate into your Swift Package becomes part of the API of your
|
||||||
|
package. You must therefore ensure that downstream users of your package aren't
|
||||||
|
impacted by the options you use when generating code.
|
||||||
|
|
||||||
|
By default code is generated at the `internal` access level and therefore not
|
||||||
|
part of the public API. You must explicitly opt in to generating code at the
|
||||||
|
`public` access level. If you do this then you must be aware that changing what
|
||||||
|
is generated (clients, servers) affects the public API, as does the access level
|
||||||
|
of the generated code.
|
||||||
|
|
||||||
|
If you need to validate whether your API has changed you can use tools like
|
||||||
|
Swift Package Manager's API breakage diagnostic (`swift package
|
||||||
|
diagnose-api-breaking-changes`.) In general you should prefer providing users
|
||||||
|
with the service's `.proto` file so that they can generate clients, or provide a
|
||||||
|
library which wraps the client to hide the API of the generated code.
|
||||||
|
|
||||||
|
## The code generator
|
||||||
|
|
||||||
|
The gRPC Swift maintainers may need to evolve the generated code over time. This
|
||||||
|
will be done in a source-compatible way.
|
||||||
|
|
||||||
|
If APIs are no longer suitable then they may be deprecated in favour of new
|
||||||
|
ones. Within a major version of the package existing API won't be removed
|
||||||
|
and deprecated APIs will continue to function.
|
||||||
|
|
||||||
|
If the generator introduces new ways to generate code which are incompatible
|
||||||
|
with the previously generated code then they will require explicit opt-in via an
|
||||||
|
option.
|
||||||
|
|
||||||
|
As gRPC Swift is developed the generated code may need to rely on newer
|
||||||
|
functionality from its runtime counterparts (`GRPCCore` and `GRPCProtobuf`).
|
||||||
|
This means that you should use the versions of `protoc-gen-grpc-swift-2` and
|
||||||
|
`protoc-gen-swift` resolved with your package rather than getting them from an
|
||||||
|
out-of-band (such as `homebrew`).
|
|
@ -0,0 +1,178 @@
|
||||||
|
# Generating stubs
|
||||||
|
|
||||||
|
Learn how to generate stubs for gRPC Swift from a service defined using the Protocol Buffers IDL.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
If you've used Protocol Buffers before then generating gRPC Swift stubs should be simple. If you're
|
||||||
|
unfamiliar with Protocol Buffers then you should get comfortable with the concepts before
|
||||||
|
continuing; the [Protocol Buffers website](https://protobuf.dev/) is a great place to start.
|
||||||
|
|
||||||
|
You can use the `protoc` plugin from the command line directly, or you can make use of a
|
||||||
|
[Swift Package Manager build plugin](https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/Plugins.md)
|
||||||
|
convenience which adds the stub generation to the Swift build graph.
|
||||||
|
You may use the build plugin either from the command line or from Xcode.
|
||||||
|
|
||||||
|
## Using the build plugin
|
||||||
|
|
||||||
|
The build plugin (`GRPCProtobufGenerator`) is a great choice for convenient dynamic code generation, however it does come with some limitations.
|
||||||
|
Because it generates the gRPC Swift stubs as part of the build it has the requirement that `protoc` must be available
|
||||||
|
at compile time. This requirement means it is not a good fit for library authors who do not have
|
||||||
|
direct control over this.
|
||||||
|
|
||||||
|
The build plugin detects `.proto` files in the source tree and invokes `protoc` once for each file
|
||||||
|
(caching results and performing the generation as necessary).
|
||||||
|
|
||||||
|
### Adoption
|
||||||
|
You must adopt Swift Package Manager build plugins on a per-target basis by modifying your package manifest
|
||||||
|
(`Package.swift` file). To do this, declare the grpc-swift-protobuf package as a dependency and add the plugin
|
||||||
|
to your desired targets.
|
||||||
|
|
||||||
|
For example, to make use of the plugin for generating gRPC Swift stubs as part of the
|
||||||
|
`echo-server` target:
|
||||||
|
```swift
|
||||||
|
targets: [
|
||||||
|
.executableTarget(
|
||||||
|
name: "echo-server",
|
||||||
|
dependencies: [
|
||||||
|
// ...
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
.plugin(name: "GRPCProtobufGenerator", package: "grpc-swift-protobuf")
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
```
|
||||||
|
Once this is done you need to ensure that the `.proto` files to be used for generation
|
||||||
|
are included in the target's source directory and that you have defined at least one configuration file.
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
You must provide a configuration file in the directory which encloses all `.proto` files (in the same directory or a parent).
|
||||||
|
Configuration files, written in JSON, tell the build plugin about the options used for `protoc` invocations.
|
||||||
|
You must name the file `grpc-swift-proto-generator-config.json` and structure it in the following format:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"generate": {
|
||||||
|
"clients": true,
|
||||||
|
"servers": true,
|
||||||
|
"messages": true
|
||||||
|
},
|
||||||
|
"generatedSource": {
|
||||||
|
"accessLevelOnImports": false,
|
||||||
|
"accessLevel": "internal"
|
||||||
|
},
|
||||||
|
"protoc": {
|
||||||
|
"executablePath": "/opt/homebrew/bin/protoc",
|
||||||
|
"importPaths": [
|
||||||
|
"../directory_1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The options do not need to be specified and each have default values.
|
||||||
|
|
||||||
|
| Name | Possible Values | Default | Description |
|
||||||
|
|----------------------------------------|--------------------------------------------|--------------|-----------------------------------------------------|
|
||||||
|
| `generate.servers` | `true`, `false` | `true` | Generate server stubs |
|
||||||
|
| `generate.clients` | `true`, `false` | `true` | Generate client stubs |
|
||||||
|
| `generate.messages` | `true`, `false` | `true` | Generate message stubs |
|
||||||
|
| `generatedSource.accessLevelOnImports` | `true`, `false` | `false` | Whether imports should have explicit access levels |
|
||||||
|
| `generatedSource.accessLevel` | `"public"`, `"package"`, `"internal"` | `"internal"` | Access level for generated stubs |
|
||||||
|
| `protoc.executablePath` | N/A | `null`† | Path to the `protoc` executable |
|
||||||
|
| `protoc.importPaths` | N/A | `null`‡ | Import paths passed to `protoc` |
|
||||||
|
|
||||||
|
† The Swift Package Manager build plugin infrastructure will attempt to discover the executable's location if you don't provide one.
|
||||||
|
|
||||||
|
‡ If you don't provide any import paths then the path to the configuration file will be used on a per-source-file basis.
|
||||||
|
|
||||||
|
Many of these options map to `protoc-gen-grpc-swift-2` and `protoc-gen-swift` options.
|
||||||
|
|
||||||
|
If you require greater flexibility you may specify more than one configuration file.
|
||||||
|
Configuration files apply to all `.proto` files equal to or below it in the file hierarchy. A configuration file
|
||||||
|
lower in the file hierarchy supersedes one above it.
|
||||||
|
|
||||||
|
### Using protoc
|
||||||
|
|
||||||
|
The [`grpc-swift-protobuf`](https://github.com/grpc/grpc-swift-protobuf) package provides
|
||||||
|
`protoc-gen-grpc-swift-2`, a program which is a plugin for the Protocol Buffers compiler, `protoc`.
|
||||||
|
To generate gRPC stubs for your `.proto` files directly you must run the `protoc` command with
|
||||||
|
the `--grpc-swift-2_out=<DIRECTORY>` option:
|
||||||
|
|
||||||
|
```console
|
||||||
|
protoc --grpc-swift-2_out=. my-service.proto
|
||||||
|
```
|
||||||
|
|
||||||
|
> `protoc-gen-grpc-swift-2` only generates gRPC stubs, it doesn't generate messages. You must use
|
||||||
|
> `protoc-gen-swift` to generate messages in addition to gRPC Stubs.
|
||||||
|
|
||||||
|
The presence of `--grpc-swift-2_out` tells `protoc` to use the `protoc-gen-grpc-swift-2` plugin. By
|
||||||
|
default it'll look for the plugin in your `PATH`. You can also specify the path to the plugin
|
||||||
|
explicitly:
|
||||||
|
|
||||||
|
```console
|
||||||
|
protoc --plugin=/path/to/protoc-gen-grpc-swift-2 --grpc-swift-2_out=. my-service.proto
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also specify various option the `protoc-gen-grpc-swift-2` via `protoc` using
|
||||||
|
the `--grpc-swift-2_opt` argument:
|
||||||
|
|
||||||
|
```console
|
||||||
|
protoc --grpc-swift-2_opt=<OPTION_NAME>=<OPTION_VALUE> --grpc-swift-2_out=.
|
||||||
|
```
|
||||||
|
|
||||||
|
You can specify multiple options by passing the `--grpc-swift-2_opt` argument multiple times:
|
||||||
|
|
||||||
|
```console
|
||||||
|
protoc \
|
||||||
|
--grpc-swift-2_opt=<OPTION_NAME1>=<OPTION_VALUE1> \
|
||||||
|
--grpc-swift-2_opt=<OPTION_NAME2>=<OPTION_VALUE2> \
|
||||||
|
--grpc-swift-2_out=.
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Generator options
|
||||||
|
|
||||||
|
| Name | Possible Values | Default | Description |
|
||||||
|
|---------------------------|---------------------------------------------|-----------------|----------------------------------------------------------|
|
||||||
|
| `Visibility` | `Public`, `Package`, `Internal` | `Internal` | Access level for generated stubs |
|
||||||
|
| `Server` | `True`, `False` | `True` | Generate server stubs |
|
||||||
|
| `Client` | `True`, `False` | `True` | Generate client stubs |
|
||||||
|
| `FileNaming` | `FullPath`, `PathToUnderscores`, `DropPath` | `FullPath` | How generated source files should be named. † |
|
||||||
|
| `ProtoPathModuleMappings` | | | Path to module map `.asciipb` file. ‡ |
|
||||||
|
| `UseAccessLevelOnImports` | `True`, `False` | `False` | Whether imports should have explicit access levels. |
|
||||||
|
| `GRPCModuleName` | | `GRPCCore` | The name of the `GRPCCore` module. |
|
||||||
|
| `GRPCProtobufModuleName` | | `GRPCProtobuf` | The name of the `GRPCProtobuf` module. |
|
||||||
|
| `SwiftProtobufModuleName` | | `SwiftProtobuf` | The name of the `SwiftProtobuf` module. |
|
||||||
|
| `Availability` | String, in the form `OS Version` | | Platform availability to use in generated code. § |
|
||||||
|
|
||||||
|
† The `FileNaming` option has three possible values, for an input of `foo/bar/baz.proto` the following
|
||||||
|
output file will be generated:
|
||||||
|
- `FullPath`: `foo/bar/baz.grpc.swift`.
|
||||||
|
- `PathToUnderscores`: `foo_bar_baz.grpc.swift`
|
||||||
|
- `DropPath`: `baz.grpc.swift`
|
||||||
|
|
||||||
|
‡ The code generator assumes all inputs are generated into the same module, `ProtoPathModuleMappings`
|
||||||
|
allows you to specify a mapping from `.proto` files to the Swift module they are generated in. This
|
||||||
|
allows the code generator to add appropriate imports to your generated stubs. This is described in
|
||||||
|
more detail in the [SwiftProtobuf documentation](https://github.com/apple/swift-protobuf/blob/main/Documentation/PLUGIN.md).
|
||||||
|
|
||||||
|
§ If unspecified the following availability is used: macOS 15, iOS 18, tvOS 18,
|
||||||
|
watchOS 11, visionOS 2. The `Availability` option may be specified multiple
|
||||||
|
times, where each value is a space delimited pair of platform and version, e.g.
|
||||||
|
`Availability=macOS 15.0`.
|
||||||
|
|
||||||
|
#### Building the protoc plugin
|
||||||
|
|
||||||
|
> The version of `protoc-gen-grpc-swift-2` you use mustn't be newer than the version of
|
||||||
|
> the `grpc-swift-protobuf` you're using.
|
||||||
|
|
||||||
|
If your package depends on `grpc-swift-protobuf` then you can get a copy of `protoc-gen-grpc-swift-2`
|
||||||
|
by building it directly:
|
||||||
|
|
||||||
|
```console
|
||||||
|
swift build --product protoc-gen-grpc-swift-2
|
||||||
|
```
|
||||||
|
|
||||||
|
This command will build the plugin into `.build/debug` directory. You can get the full path using
|
||||||
|
`swift build --show-bin-path`.
|
|
@ -0,0 +1,59 @@
|
||||||
|
# Installing protoc
|
||||||
|
|
||||||
|
Learn how to install `protoc`, the Protocol Buffers compiler.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Protocol Buffers compiler is a command line tool for generating source code from `.proto`
|
||||||
|
files and is required to generate gRPC stubs and messages. You can learn more about it on the
|
||||||
|
[Protocol Buffers website](https://protobuf.dev/).
|
||||||
|
|
||||||
|
You can install `protoc` in a number of ways including:
|
||||||
|
|
||||||
|
1. Via a package manager,
|
||||||
|
2. By downloading the binary.
|
||||||
|
|
||||||
|
### Install via a package manager
|
||||||
|
|
||||||
|
Using a package manager is the easiest way to install `protoc`.
|
||||||
|
|
||||||
|
On macOS you can use [Homebrew](https://brew.sh):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
brew install protobuf
|
||||||
|
```
|
||||||
|
|
||||||
|
On Ubuntu and Debian you can use `apt`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
apt update && apt install -y protobuf-compiler
|
||||||
|
```
|
||||||
|
|
||||||
|
On Fedora you can use `dnf`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dnf install -y protobuf-compiler
|
||||||
|
```
|
||||||
|
|
||||||
|
### Installing a pre-built binary
|
||||||
|
|
||||||
|
If you're unable to use a package manager to install `protoc` then you may be able
|
||||||
|
to download a pre-built binary from the [Protocol Buffers GitHub
|
||||||
|
repository](https://github.com/protocolbuffers/protobuf).
|
||||||
|
|
||||||
|
First, find and download the appropriate binary for your system from the
|
||||||
|
[releases](https://github.com/protocolbuffers/protobuf/releases) page.
|
||||||
|
|
||||||
|
Next, unzip the artifact to a directory called `protoc`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
unzip /path/to/downloaded/protoc-{VERSION}-{OS}.zip -d protoc
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, move `protoc/bin/protoc` to somewhere in your `$PATH` such as `/usr/local/bin`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mv protoc/bin/protoc /usr/local/bin
|
||||||
|
```
|
||||||
|
|
||||||
|
You can now remove the `protoc` directory.
|
|
@ -0,0 +1,45 @@
|
||||||
|
# Public services with private implementations
|
||||||
|
|
||||||
|
Learn how to create a `public` gRPC service with private implementation details.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
It's not uncommon for a library to provide a gRPC service as part of its API.
|
||||||
|
For example, the gRPC Swift Extras package provides implementations of the gRPC
|
||||||
|
health and reflection services. Making the implementation of a service `public`
|
||||||
|
would require its generated gRPC and message types to also be `public`. This is
|
||||||
|
undesirable as it leaks implementation details into the public API of the
|
||||||
|
package. This article explains how to keep the generated types private while
|
||||||
|
making the service available as part of the public API.
|
||||||
|
|
||||||
|
## Hiding the implementation
|
||||||
|
|
||||||
|
You can hide the implementation details of your service by providing a wrapper
|
||||||
|
type conforming to `RegistrableRPCService`. This is the protocol used by
|
||||||
|
`GRPCServer` to register service methods with the server's router. Implementing
|
||||||
|
`RegistrableRPCService` is straightforward and can delegate to the underlying
|
||||||
|
service. This is demonstrated in the following code:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
public struct GreeterService: RegistrableRPCService {
|
||||||
|
private var base: Greeter
|
||||||
|
|
||||||
|
public init() {
|
||||||
|
self.base = Greeter()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func registerMethods<Transport>(
|
||||||
|
with router: inout RPCRouter<Transport>
|
||||||
|
) where Transport: ServerTransport {
|
||||||
|
self.base.registerMethods(with: &router)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In this example `Greeter` implements the underlying service and would conform to
|
||||||
|
the generated service protocol but would have a non-public access level.
|
||||||
|
`GreeterService` is a public wrapper type conforming to `RegistrableRPCService`
|
||||||
|
which implements its only requirement, `registerMethods(with:)`, by calling
|
||||||
|
through to the underlying implementation. The result is a service which can be
|
||||||
|
registered with a server where none of the generated types are part of the
|
||||||
|
public API.
|
|
@ -0,0 +1,143 @@
|
||||||
|
# Understanding the generated code
|
||||||
|
|
||||||
|
Understand what code is generated by `protoc-gen-grpc-swift-2` from a `.proto`
|
||||||
|
file and how to use it.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The gRPC Swift Protobuf package provides a plugin to the Protocol Buffers
|
||||||
|
Compiler (`protoc`) called `protoc-gen-grpc-swift-2`. The plugin is responsible
|
||||||
|
for generating the gRPC specific code for services defined in a `.proto` file.
|
||||||
|
|
||||||
|
### Package namespace
|
||||||
|
|
||||||
|
Most `.proto` files contain a `package` directive near the start of the file
|
||||||
|
describing the namespace it belongs to. Here's an example:
|
||||||
|
|
||||||
|
```proto
|
||||||
|
package foo.bar.v1;
|
||||||
|
```
|
||||||
|
|
||||||
|
The package name "foo.bar.v1" is important as it is used as a prefix for
|
||||||
|
generated types. The default behaviour is to replace periods with underscores
|
||||||
|
and to capitalize each word and add a trailing underscore. For this package the
|
||||||
|
prefix is "Foo\_Bar\_V1\_". If you don't declare a package then the prefix will be
|
||||||
|
the empty string.
|
||||||
|
|
||||||
|
You can override the prefix by setting the `swift_prefix` option:
|
||||||
|
|
||||||
|
```proto
|
||||||
|
option swift_prefix = "FooBarV1";
|
||||||
|
|
||||||
|
package foo.bar.v1;
|
||||||
|
```
|
||||||
|
|
||||||
|
The prefix for types in this file would be "FooBarV1" instead of "Foo\_Bar\_V1\_".
|
||||||
|
|
||||||
|
### Service namespace
|
||||||
|
|
||||||
|
For each service declared in your `.proto` file, gRPC will generate a caseless
|
||||||
|
`enum` which is a namespace holding the generated protocols and types. The name
|
||||||
|
of this `enum` is `{PREFIX}{SERVICE}` where `{PREFIX}` is as described in the
|
||||||
|
previous section and `{SERVICE}` is the name of the service as declared in the
|
||||||
|
`.proto` file.
|
||||||
|
|
||||||
|
As an example the following definition creates a service namespace `enum` called
|
||||||
|
`Foo_Bar_V1_BazService` (the `{PREFIX}` is "Foo_Bar_V1_" and `{SERVICE}` is
|
||||||
|
"BazService"):
|
||||||
|
|
||||||
|
```proto
|
||||||
|
package foo.bar.v1;
|
||||||
|
|
||||||
|
service BazService {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Code generated for each service falls into three categories:
|
||||||
|
|
||||||
|
1. Service metadata,
|
||||||
|
2. Service code, and
|
||||||
|
3. Client code.
|
||||||
|
|
||||||
|
#### Service metadata
|
||||||
|
|
||||||
|
gRPC generates metadata for each service including a descriptor identifying the
|
||||||
|
fully qualified name of the service and information about each method in the
|
||||||
|
service. You likely won't need to interact directly with this information but
|
||||||
|
it's available should you need to.
|
||||||
|
|
||||||
|
#### Service code
|
||||||
|
|
||||||
|
Within a service namespace gRPC generates three service protocols:
|
||||||
|
|
||||||
|
1. `StreamingServiceProtocol`,
|
||||||
|
2. `ServiceProtocol`, and
|
||||||
|
3. `SimpleServiceProtocol`.
|
||||||
|
|
||||||
|
The full name of each protocol includes the service namespace.
|
||||||
|
|
||||||
|
> Example:
|
||||||
|
>
|
||||||
|
> For the `BazService` in the `foo.bar.v1` package the protocols would be:
|
||||||
|
>
|
||||||
|
> - `Foo_Bar_V1_BazService.StreamingServiceProtocol`,
|
||||||
|
> - `Foo_Bar_V1_BazService.ServiceProtocol`, and
|
||||||
|
> - `Foo_Bar_V1_BazService.SimpleServiceProtocol`.
|
||||||
|
|
||||||
|
Each of these protocols mirror the `service` defined in your `.proto` file with
|
||||||
|
one requirement per RPC. To implement your service you must implement one of
|
||||||
|
these protocols.
|
||||||
|
|
||||||
|
The protocols form a hierarchy with `StreamingServiceProtocol` at the bottom and
|
||||||
|
`SimpleServiceProtocol` at the top. `ServiceProtocol` refines
|
||||||
|
`StreamingServiceProtocol`, and `SimpleServiceProtocol` refines
|
||||||
|
`ServiceProtocol` (and `StreamingServiceProtocol` in turn).
|
||||||
|
|
||||||
|
The `StreamingServiceProtocol` implements each RPC as if it were a bidirectional
|
||||||
|
streaming RPC. This gives you the most flexibility at the cost of a harder to
|
||||||
|
implement API. It also puts the responsibility on you to ensure that each RPC
|
||||||
|
sends and receives the correct number of messages.
|
||||||
|
|
||||||
|
The `ServiceProtocol` enforces that the correct number of messages are sent and
|
||||||
|
received via its API. It also allows you to read request metadata and send both
|
||||||
|
initial and trailing metadata. The request and response types for these
|
||||||
|
requirements are in terms of `ServerRequest` and `ServerResponse`.
|
||||||
|
|
||||||
|
The `SimpleServiceProtocol` also enforces the correct number of messages are
|
||||||
|
sent and received via its API. However, it isn't defined in terms of
|
||||||
|
`ServerRequest` or `ServerResponse` so it doesn't allow you access metadata.
|
||||||
|
This limitation allows it to have the simplest API and is preferred if you don't
|
||||||
|
need access to metadata.
|
||||||
|
|
||||||
|
| Protocol | Enforces number of messages | Access to metadata
|
||||||
|
|----------------------------|-----------------------------|-------------------
|
||||||
|
| `StreamingServiceProtocol` | ✗ | ✓
|
||||||
|
| `ServiceProtocol` | ✓ | ✓
|
||||||
|
| `SimpleServiceProtocol` | ✓ | ✗
|
||||||
|
|
||||||
|
#### Client code
|
||||||
|
|
||||||
|
gRPC generates two types for the client within a service namespace:
|
||||||
|
|
||||||
|
1. `ClientProtocol`, and
|
||||||
|
2. `Client`.
|
||||||
|
|
||||||
|
Like the service code, the full name includes the namespace.
|
||||||
|
|
||||||
|
> Example:
|
||||||
|
>
|
||||||
|
> For the `BazService` in the `foo.bar.v1` package the client types would be:
|
||||||
|
>
|
||||||
|
> - `Foo_Bar_V1_BazService.ClientProtocol`, and
|
||||||
|
> - `Foo_Bar_V1_BazService.Client`.
|
||||||
|
|
||||||
|
The `ClientProtocol` defines one requirement for each RPC in terms of
|
||||||
|
`ClientRequest` and `ClientResponse`. You don't need to implement the protocol
|
||||||
|
as `Client` provides a concrete implementation.
|
||||||
|
|
||||||
|
gRPC also generates extensions on `ClientProtocol` to provide more ergonomic
|
||||||
|
APIs. These include versions which provide default arguments for various
|
||||||
|
parameters (like the message serializer and deserializers; call options and
|
||||||
|
response handler) and versions which don't use `ClientRequest` and
|
||||||
|
`ClientResponse` directly.
|
|
@ -4,16 +4,27 @@ A package integrating Swift Protobuf with gRPC Swift.
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
This package provides two products:
|
This package provides three products:
|
||||||
- ``GRPCProtobuf``, a module providing runtime serialization and deserialization components for
|
- ``GRPCProtobuf``, a module providing runtime serialization and deserialization components for
|
||||||
[SwiftProtobuf](https://github.com/apple/swift-protobuf).
|
[SwiftProtobuf](https://github.com/apple/swift-protobuf).
|
||||||
- `protoc-gen-grpc-swift`, an executable which is a plugin for `protoc`, the Protocol Buffers
|
- `protoc-gen-grpc-swift-2`, an executable which is a plugin for `protoc`, the Protocol Buffers
|
||||||
compiler. An article describing how to generate gRPC Swift stubs using it is available with the
|
compiler. An article describing how to generate gRPC Swift stubs using it is available with the
|
||||||
`grpc-swift` documentation on the [Swift Package
|
`grpc-swift` documentation on the [Swift Package
|
||||||
Index](https://swiftpackageindex.com/grpc/grpc-swift/documentation).
|
Index](https://swiftpackageindex.com/grpc/grpc-swift/documentation).
|
||||||
|
- `GRPCProtobufGenerator`, a Swift Package build plugin for generating stubs as part of the build
|
||||||
|
process.
|
||||||
|
|
||||||
|
|
||||||
## Topics
|
## Topics
|
||||||
|
|
||||||
|
### Essentials
|
||||||
|
|
||||||
|
- <doc:Installing-protoc>
|
||||||
|
- <doc:Generating-stubs>
|
||||||
|
- <doc:API-stability-of-generated-code>
|
||||||
|
- <doc:Understanding-the-generated-code>
|
||||||
|
- <doc:Public-services-with-private-implementations>
|
||||||
|
|
||||||
### Serialization
|
### Serialization
|
||||||
|
|
||||||
- ``ProtobufSerializer``
|
- ``ProtobufSerializer``
|
||||||
|
|
|
@ -61,6 +61,7 @@ extension Google_Protobuf_Any {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
extension ErrorDetails {
|
extension ErrorDetails {
|
||||||
// Note: this type isn't packable into an 'Any' protobuf so doesn't conform
|
// Note: this type isn't packable into an 'Any' protobuf so doesn't conform
|
||||||
// to 'GoogleProtobufAnyPackable' despite holding types which are packable.
|
// to 'GoogleProtobufAnyPackable' despite holding types which are packable.
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
extension ErrorDetails: CustomStringConvertible {
|
extension ErrorDetails: CustomStringConvertible {
|
||||||
public var description: String {
|
public var description: String {
|
||||||
switch self.wrapped {
|
switch self.wrapped {
|
||||||
|
@ -46,54 +47,63 @@ extension ErrorDetails: CustomStringConvertible {
|
||||||
// Some errors use protobuf messages as their storage so the default description isn't
|
// Some errors use protobuf messages as their storage so the default description isn't
|
||||||
// representative
|
// representative
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
extension ErrorDetails.ErrorInfo: CustomStringConvertible {
|
extension ErrorDetails.ErrorInfo: CustomStringConvertible {
|
||||||
public var description: String {
|
public var description: String {
|
||||||
"\(Self.self)(reason: \"\(self.reason)\", domain: \"\(self.domain)\", metadata: \(self.metadata))"
|
"\(Self.self)(reason: \"\(self.reason)\", domain: \"\(self.domain)\", metadata: \(self.metadata))"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
extension ErrorDetails.DebugInfo: CustomStringConvertible {
|
extension ErrorDetails.DebugInfo: CustomStringConvertible {
|
||||||
public var description: String {
|
public var description: String {
|
||||||
"\(Self.self)(stack: \(self.stack), detail: \"\(self.detail)\")"
|
"\(Self.self)(stack: \(self.stack), detail: \"\(self.detail)\")"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
extension ErrorDetails.QuotaFailure.Violation: CustomStringConvertible {
|
extension ErrorDetails.QuotaFailure.Violation: CustomStringConvertible {
|
||||||
public var description: String {
|
public var description: String {
|
||||||
"\(Self.self)(subject: \"\(self.subject)\", violationDescription: \"\(self.violationDescription)\")"
|
"\(Self.self)(subject: \"\(self.subject)\", violationDescription: \"\(self.violationDescription)\")"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
extension ErrorDetails.PreconditionFailure.Violation: CustomStringConvertible {
|
extension ErrorDetails.PreconditionFailure.Violation: CustomStringConvertible {
|
||||||
public var description: String {
|
public var description: String {
|
||||||
"\(Self.self)(subject: \"\(self.subject)\", type: \"\(self.type)\", violationDescription: \"\(self.violationDescription)\")"
|
"\(Self.self)(subject: \"\(self.subject)\", type: \"\(self.type)\", violationDescription: \"\(self.violationDescription)\")"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
extension ErrorDetails.BadRequest.FieldViolation: CustomStringConvertible {
|
extension ErrorDetails.BadRequest.FieldViolation: CustomStringConvertible {
|
||||||
public var description: String {
|
public var description: String {
|
||||||
"\(Self.self)(field: \"\(self.field)\", violationDescription: \"\(self.violationDescription)\")"
|
"\(Self.self)(field: \"\(self.field)\", violationDescription: \"\(self.violationDescription)\")"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
extension ErrorDetails.RequestInfo: CustomStringConvertible {
|
extension ErrorDetails.RequestInfo: CustomStringConvertible {
|
||||||
public var description: String {
|
public var description: String {
|
||||||
"\(Self.self)(requestID: \"\(self.requestID)\", servingData: \"\(self.servingData)\")"
|
"\(Self.self)(requestID: \"\(self.requestID)\", servingData: \"\(self.servingData)\")"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
extension ErrorDetails.ResourceInfo: CustomStringConvertible {
|
extension ErrorDetails.ResourceInfo: CustomStringConvertible {
|
||||||
public var description: String {
|
public var description: String {
|
||||||
"\(Self.self)(name: \"\(self.name)\", owner: \"\(self.owner)\", type: \"\(self.type)\", errorDescription: \"\(self.errorDescription)\")"
|
"\(Self.self)(name: \"\(self.name)\", owner: \"\(self.owner)\", type: \"\(self.type)\", errorDescription: \"\(self.errorDescription)\")"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
extension ErrorDetails.Help.Link: CustomStringConvertible {
|
extension ErrorDetails.Help.Link: CustomStringConvertible {
|
||||||
public var description: String {
|
public var description: String {
|
||||||
"\(Self.self)(url: \"\(self.url)\", linkDescription: \"\(self.linkDescription)\")"
|
"\(Self.self)(url: \"\(self.url)\", linkDescription: \"\(self.linkDescription)\")"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
extension ErrorDetails.LocalizedMessage: CustomStringConvertible {
|
extension ErrorDetails.LocalizedMessage: CustomStringConvertible {
|
||||||
public var description: String {
|
public var description: String {
|
||||||
"\(Self.self)(locale: \"\(self.locale)\", message: \"\(self.message)\")"
|
"\(Self.self)(locale: \"\(self.locale)\", message: \"\(self.message)\")"
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
internal import SwiftProtobuf
|
internal import SwiftProtobuf
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
extension ErrorDetails {
|
extension ErrorDetails {
|
||||||
/// Describes the cause of the error with structured details.
|
/// Describes the cause of the error with structured details.
|
||||||
///
|
///
|
||||||
|
|
|
@ -24,6 +24,7 @@ public import SwiftProtobuf
|
||||||
///
|
///
|
||||||
/// This type also allows you to provide wrap your own error details up as an "Any"
|
/// This type also allows you to provide wrap your own error details up as an "Any"
|
||||||
/// protobuf (`Google_Protobuf_Any`).
|
/// protobuf (`Google_Protobuf_Any`).
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
public struct ErrorDetails: Sendable, Hashable {
|
public struct ErrorDetails: Sendable, Hashable {
|
||||||
enum Wrapped: Sendable, Hashable {
|
enum Wrapped: Sendable, Hashable {
|
||||||
case errorInfo(ErrorInfo)
|
case errorInfo(ErrorInfo)
|
||||||
|
@ -198,6 +199,7 @@ public struct ErrorDetails: Sendable, Hashable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
extension ErrorDetails {
|
extension ErrorDetails {
|
||||||
/// Returns error info if set.
|
/// Returns error info if set.
|
||||||
public var errorInfo: ErrorInfo? {
|
public var errorInfo: ErrorInfo? {
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public import GRPCCore
|
public import GRPCCore
|
||||||
internal import SwiftProtobuf
|
public import SwiftProtobuf
|
||||||
|
|
||||||
/// An error containing structured details which can be delivered to the client.
|
/// An error containing structured details which can be delivered to the client.
|
||||||
///
|
///
|
||||||
|
@ -33,10 +33,10 @@ internal import SwiftProtobuf
|
||||||
/// >
|
/// >
|
||||||
/// > The error information is transmitted to clients in the trailing metadata of an RPC. It is
|
/// > The error information is transmitted to clients in the trailing metadata of an RPC. It is
|
||||||
/// > inserted into the metadata keyed by "grpc-status-details-bin". The value of the metadata is
|
/// > inserted into the metadata keyed by "grpc-status-details-bin". The value of the metadata is
|
||||||
/// > the serialized bytes of a "google.protobuf.Any" protocol buffers message. The content of which
|
/// > the serialized bytes of a "google.rpc.Status" protocol buffers message containing the status
|
||||||
/// > is a "google.rpc.Status" protocol buffers message containing the status code, message, and
|
/// > code, message, and details.
|
||||||
/// > details.
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
public struct GoogleRPCStatus: Error {
|
public struct GoogleRPCStatus: Error, Hashable {
|
||||||
/// A code representing the high-level domain of the error.
|
/// A code representing the high-level domain of the error.
|
||||||
public var code: RPCError.Code
|
public var code: RPCError.Code
|
||||||
|
|
||||||
|
@ -74,13 +74,33 @@ public struct GoogleRPCStatus: Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension GoogleRPCStatus: GoogleProtobufAnyPackable {
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
// See https://protobuf.dev/programming-guides/proto3/#any
|
extension GoogleRPCStatus {
|
||||||
internal static var typeURL: String { "type.googleapis.com/google.rpc.Status" }
|
/// Creates a new message by decoding the given `SwiftProtobufContiguousBytes` value
|
||||||
|
/// containing a serialized message in Protocol Buffer binary format.
|
||||||
init?(unpacking any: Google_Protobuf_Any) throws {
|
///
|
||||||
guard any.isA(Google_Rpc_Status.self) else { return nil }
|
/// - Parameters:
|
||||||
let status = try Google_Rpc_Status(serializedBytes: any.value)
|
/// - bytes: The binary-encoded message data to decode.
|
||||||
|
/// - extensions: An `ExtensionMap` used to look up and decode any
|
||||||
|
/// extensions in this message or messages nested within this message's
|
||||||
|
/// fields.
|
||||||
|
/// - partial: If `false` (the default), this method will check if the `Message`
|
||||||
|
/// is initialized after decoding to verify that all required fields are present.
|
||||||
|
/// If any are missing, this method throws `BinaryDecodingError`.
|
||||||
|
/// - options: The `BinaryDecodingOptions` to use.
|
||||||
|
/// - Throws: `BinaryDecodingError` if decoding fails.
|
||||||
|
public init<Bytes: SwiftProtobufContiguousBytes>(
|
||||||
|
serializedBytes bytes: Bytes,
|
||||||
|
extensions: (any ExtensionMap)? = nil,
|
||||||
|
partial: Bool = false,
|
||||||
|
options: BinaryDecodingOptions = BinaryDecodingOptions()
|
||||||
|
) throws {
|
||||||
|
let status = try Google_Rpc_Status(
|
||||||
|
serializedBytes: bytes,
|
||||||
|
extensions: extensions,
|
||||||
|
partial: partial,
|
||||||
|
options: options
|
||||||
|
)
|
||||||
|
|
||||||
let statusCode = Status.Code(rawValue: Int(status.code))
|
let statusCode = Status.Code(rawValue: Int(status.code))
|
||||||
self.code = statusCode.flatMap { RPCError.Code($0) } ?? .unknown
|
self.code = statusCode.flatMap { RPCError.Code($0) } ?? .unknown
|
||||||
|
@ -88,27 +108,40 @@ extension GoogleRPCStatus: GoogleProtobufAnyPackable {
|
||||||
self.details = try status.details.map { try ErrorDetails(unpacking: $0) }
|
self.details = try status.details.map { try ErrorDetails(unpacking: $0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
func pack() throws -> Google_Protobuf_Any {
|
/// Returns a `SwiftProtobufContiguousBytes` instance containing the Protocol Buffer binary
|
||||||
|
/// format serialization of the message.
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - partial: If `false` (the default), this method will check
|
||||||
|
/// `Message.isInitialized` before encoding to verify that all required
|
||||||
|
/// fields are present. If any are missing, this method throws.
|
||||||
|
/// `BinaryEncodingError/missingRequiredFields`.
|
||||||
|
/// - options: The `BinaryEncodingOptions` to use.
|
||||||
|
/// - Returns: A `SwiftProtobufContiguousBytes` instance containing the binary serialization
|
||||||
|
/// of the message.
|
||||||
|
///
|
||||||
|
/// - Throws: `SwiftProtobufError` or `BinaryEncodingError` if encoding fails.
|
||||||
|
public func serializedBytes<Bytes: SwiftProtobufContiguousBytes>(
|
||||||
|
partial: Bool = false,
|
||||||
|
options: BinaryEncodingOptions = BinaryEncodingOptions()
|
||||||
|
) throws -> Bytes {
|
||||||
let status = try Google_Rpc_Status.with {
|
let status = try Google_Rpc_Status.with {
|
||||||
$0.code = Int32(self.code.rawValue)
|
$0.code = Int32(self.code.rawValue)
|
||||||
$0.message = self.message
|
$0.message = self.message
|
||||||
$0.details = try self.details.map { try $0.pack() }
|
$0.details = try self.details.map { try $0.pack() }
|
||||||
}
|
}
|
||||||
|
|
||||||
return try .with {
|
return try status.serializedBytes(partial: partial, options: options)
|
||||||
$0.typeURL = Self.typeURL
|
|
||||||
$0.value = try status.serializedBytes()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
extension GoogleRPCStatus: RPCErrorConvertible {
|
extension GoogleRPCStatus: RPCErrorConvertible {
|
||||||
public var rpcErrorCode: RPCError.Code { self.code }
|
public var rpcErrorCode: RPCError.Code { self.code }
|
||||||
public var rpcErrorMessage: String { self.message }
|
public var rpcErrorMessage: String { self.message }
|
||||||
public var rpcErrorMetadata: Metadata {
|
public var rpcErrorMetadata: Metadata {
|
||||||
do {
|
do {
|
||||||
let any = try self.pack()
|
let bytes: [UInt8] = try self.serializedBytes()
|
||||||
let bytes: [UInt8] = try any.serializedBytes()
|
|
||||||
return [Metadata.statusDetailsBinKey: .binary(bytes)]
|
return [Metadata.statusDetailsBinKey: .binary(bytes)]
|
||||||
} catch {
|
} catch {
|
||||||
// Failed to serialize error details. Not a lot can be done here.
|
// Failed to serialize error details. Not a lot can be done here.
|
||||||
|
|
|
@ -17,10 +17,12 @@
|
||||||
public import GRPCCore
|
public import GRPCCore
|
||||||
internal import SwiftProtobuf
|
internal import SwiftProtobuf
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
extension Metadata {
|
extension Metadata {
|
||||||
static let statusDetailsBinKey = "grpc-status-details-bin"
|
static let statusDetailsBinKey = "grpc-status-details-bin"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
extension RPCError {
|
extension RPCError {
|
||||||
/// Unpack a ``GoogleRPCStatus`` error from the error metadata.
|
/// Unpack a ``GoogleRPCStatus`` error from the error metadata.
|
||||||
///
|
///
|
||||||
|
@ -31,8 +33,6 @@ extension RPCError {
|
||||||
public func unpackGoogleRPCStatus() throws -> GoogleRPCStatus? {
|
public func unpackGoogleRPCStatus() throws -> GoogleRPCStatus? {
|
||||||
let values = self.metadata[binaryValues: Metadata.statusDetailsBinKey]
|
let values = self.metadata[binaryValues: Metadata.statusDetailsBinKey]
|
||||||
guard let bytes = values.first(where: { _ in true }) else { return nil }
|
guard let bytes = values.first(where: { _ in true }) else { return nil }
|
||||||
|
return try GoogleRPCStatus(serializedBytes: bytes)
|
||||||
let any = try Google_Protobuf_Any(serializedBytes: bytes)
|
|
||||||
return try GoogleRPCStatus(unpacking: any)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,11 +18,12 @@ internal import SwiftProtobuf
|
||||||
package import SwiftProtobufPluginLibrary
|
package import SwiftProtobufPluginLibrary
|
||||||
|
|
||||||
package import struct GRPCCodeGen.CodeGenerationRequest
|
package import struct GRPCCodeGen.CodeGenerationRequest
|
||||||
|
package import struct GRPCCodeGen.CodeGenerator
|
||||||
package import struct GRPCCodeGen.Dependency
|
package import struct GRPCCodeGen.Dependency
|
||||||
package import struct GRPCCodeGen.MethodDescriptor
|
package import struct GRPCCodeGen.MethodDescriptor
|
||||||
package import struct GRPCCodeGen.Name
|
package import struct GRPCCodeGen.MethodName
|
||||||
package import struct GRPCCodeGen.ServiceDescriptor
|
package import struct GRPCCodeGen.ServiceDescriptor
|
||||||
package import struct GRPCCodeGen.SourceGenerator
|
package import struct GRPCCodeGen.ServiceName
|
||||||
|
|
||||||
#if canImport(FoundationEssentials)
|
#if canImport(FoundationEssentials)
|
||||||
internal import struct FoundationEssentials.IndexPath
|
internal import struct FoundationEssentials.IndexPath
|
||||||
|
@ -31,19 +32,23 @@ internal import struct Foundation.IndexPath
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/// Parses a ``FileDescriptor`` object into a ``CodeGenerationRequest`` object.
|
/// Parses a ``FileDescriptor`` object into a ``CodeGenerationRequest`` object.
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
package struct ProtobufCodeGenParser {
|
package struct ProtobufCodeGenParser {
|
||||||
let extraModuleImports: [String]
|
let extraModuleImports: [String]
|
||||||
let protoToModuleMappings: ProtoFileToModuleMappings
|
let protoToModuleMappings: ProtoFileToModuleMappings
|
||||||
let accessLevel: SourceGenerator.Config.AccessLevel
|
let accessLevel: CodeGenerator.Config.AccessLevel
|
||||||
|
let moduleNames: ProtobufCodeGenerator.Config.ModuleNames
|
||||||
|
|
||||||
package init(
|
package init(
|
||||||
protoFileModuleMappings: ProtoFileToModuleMappings,
|
protoFileModuleMappings: ProtoFileToModuleMappings,
|
||||||
extraModuleImports: [String],
|
extraModuleImports: [String],
|
||||||
accessLevel: SourceGenerator.Config.AccessLevel
|
accessLevel: CodeGenerator.Config.AccessLevel,
|
||||||
|
moduleNames: ProtobufCodeGenerator.Config.ModuleNames
|
||||||
) {
|
) {
|
||||||
self.extraModuleImports = extraModuleImports
|
self.extraModuleImports = extraModuleImports
|
||||||
self.protoToModuleMappings = protoFileModuleMappings
|
self.protoToModuleMappings = protoFileModuleMappings
|
||||||
self.accessLevel = accessLevel
|
self.accessLevel = accessLevel
|
||||||
|
self.moduleNames = moduleNames
|
||||||
}
|
}
|
||||||
|
|
||||||
package func parse(descriptor: FileDescriptor) throws -> CodeGenerationRequest {
|
package func parse(descriptor: FileDescriptor) throws -> CodeGenerationRequest {
|
||||||
|
@ -61,6 +66,7 @@ package struct ProtobufCodeGenParser {
|
||||||
let leadingTrivia = """
|
let leadingTrivia = """
|
||||||
// DO NOT EDIT.
|
// DO NOT EDIT.
|
||||||
// swift-format-ignore-file
|
// swift-format-ignore-file
|
||||||
|
// swiftlint:disable all
|
||||||
//
|
//
|
||||||
// Generated by the gRPC Swift generator plugin for the protocol buffer compiler.
|
// Generated by the gRPC Swift generator plugin for the protocol buffer compiler.
|
||||||
// Source: \(descriptor.name)
|
// Source: \(descriptor.name)
|
||||||
|
@ -69,12 +75,6 @@ package struct ProtobufCodeGenParser {
|
||||||
// https://github.com/grpc/grpc-swift
|
// https://github.com/grpc/grpc-swift
|
||||||
|
|
||||||
"""
|
"""
|
||||||
let lookupSerializer: (String) -> String = { messageType in
|
|
||||||
"GRPCProtobuf.ProtobufSerializer<\(messageType)>()"
|
|
||||||
}
|
|
||||||
let lookupDeserializer: (String) -> String = { messageType in
|
|
||||||
"GRPCProtobuf.ProtobufDeserializer<\(messageType)>()"
|
|
||||||
}
|
|
||||||
|
|
||||||
let services = descriptor.services.map {
|
let services = descriptor.services.map {
|
||||||
GRPCCodeGen.ServiceDescriptor(
|
GRPCCodeGen.ServiceDescriptor(
|
||||||
|
@ -90,12 +90,17 @@ package struct ProtobufCodeGenParser {
|
||||||
leadingTrivia: header + leadingTrivia,
|
leadingTrivia: header + leadingTrivia,
|
||||||
dependencies: self.codeDependencies(file: descriptor),
|
dependencies: self.codeDependencies(file: descriptor),
|
||||||
services: services,
|
services: services,
|
||||||
lookupSerializer: lookupSerializer,
|
makeSerializerCodeSnippet: { messageType in
|
||||||
lookupDeserializer: lookupDeserializer
|
"\(self.moduleNames.grpcProtobuf).ProtobufSerializer<\(messageType)>()"
|
||||||
|
},
|
||||||
|
makeDeserializerCodeSnippet: { messageType in
|
||||||
|
"\(self.moduleNames.grpcProtobuf).ProtobufDeserializer<\(messageType)>()"
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
extension ProtobufCodeGenParser {
|
extension ProtobufCodeGenParser {
|
||||||
fileprivate func codeDependencies(file: FileDescriptor) -> [Dependency] {
|
fileprivate func codeDependencies(file: FileDescriptor) -> [Dependency] {
|
||||||
guard file.services.count > 0 else {
|
guard file.services.count > 0 else {
|
||||||
|
@ -103,7 +108,7 @@ extension ProtobufCodeGenParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
var codeDependencies: [Dependency] = [
|
var codeDependencies: [Dependency] = [
|
||||||
Dependency(module: "GRPCProtobuf", accessLevel: .internal)
|
Dependency(module: self.moduleNames.grpcProtobuf, accessLevel: .internal)
|
||||||
]
|
]
|
||||||
// If there's a dependency on a bundled proto then add the SwiftProtobuf import.
|
// If there's a dependency on a bundled proto then add the SwiftProtobuf import.
|
||||||
//
|
//
|
||||||
|
@ -114,7 +119,11 @@ extension ProtobufCodeGenParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
if dependsOnBundledProto {
|
if dependsOnBundledProto {
|
||||||
codeDependencies.append(Dependency(module: "SwiftProtobuf", accessLevel: self.accessLevel))
|
let dependency = Dependency(
|
||||||
|
module: self.moduleNames.swiftProtobuf,
|
||||||
|
accessLevel: self.accessLevel
|
||||||
|
)
|
||||||
|
codeDependencies.append(dependency)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adding as dependencies the modules containing generated code or types for
|
// Adding as dependencies the modules containing generated code or types for
|
||||||
|
@ -134,6 +143,7 @@ extension ProtobufCodeGenParser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
extension GRPCCodeGen.ServiceDescriptor {
|
extension GRPCCodeGen.ServiceDescriptor {
|
||||||
fileprivate init(
|
fileprivate init(
|
||||||
descriptor: SwiftProtobufPluginLibrary.ServiceDescriptor,
|
descriptor: SwiftProtobufPluginLibrary.ServiceDescriptor,
|
||||||
|
@ -147,35 +157,31 @@ extension GRPCCodeGen.ServiceDescriptor {
|
||||||
protobufNamer: protobufNamer
|
protobufNamer: protobufNamer
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
let name = Name(
|
|
||||||
base: descriptor.name,
|
let typePrefix = protobufNamer.typePrefix(forFile: file)
|
||||||
|
let name = ServiceName(
|
||||||
|
identifyingName: descriptor.fullName,
|
||||||
// The service name from the '.proto' file is expected to be in upper camel case
|
// The service name from the '.proto' file is expected to be in upper camel case
|
||||||
generatedUpperCase: descriptor.name,
|
typeName: typePrefix + descriptor.name,
|
||||||
generatedLowerCase: CamelCaser.toLowerCamelCase(descriptor.name)
|
propertyName: protobufNamer.typePrefixProperty(file: file) + descriptor.name
|
||||||
)
|
)
|
||||||
|
|
||||||
// Packages that are based on the path of the '.proto' file usually
|
|
||||||
// contain dots. For example: "grpc.test".
|
|
||||||
let namespace = Name(
|
|
||||||
base: package,
|
|
||||||
generatedUpperCase: protobufNamer.formattedUpperCasePackage(file: file),
|
|
||||||
generatedLowerCase: protobufNamer.formattedLowerCasePackage(file: file)
|
|
||||||
)
|
|
||||||
let documentation = descriptor.protoSourceComments()
|
let documentation = descriptor.protoSourceComments()
|
||||||
self.init(documentation: documentation, name: name, namespace: namespace, methods: methods)
|
self.init(documentation: documentation, name: name, methods: methods)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
extension GRPCCodeGen.MethodDescriptor {
|
extension GRPCCodeGen.MethodDescriptor {
|
||||||
fileprivate init(
|
fileprivate init(
|
||||||
descriptor: SwiftProtobufPluginLibrary.MethodDescriptor,
|
descriptor: SwiftProtobufPluginLibrary.MethodDescriptor,
|
||||||
protobufNamer: SwiftProtobufNamer
|
protobufNamer: SwiftProtobufNamer
|
||||||
) {
|
) {
|
||||||
let name = Name(
|
let name = MethodName(
|
||||||
base: descriptor.name,
|
identifyingName: descriptor.name,
|
||||||
// The method name from the '.proto' file is expected to be in upper camel case
|
// The method name from the '.proto' file is expected to be in upper camel case
|
||||||
generatedUpperCase: descriptor.name,
|
typeName: descriptor.name,
|
||||||
generatedLowerCase: CamelCaser.toLowerCamelCase(descriptor.name)
|
functionName: CamelCaser.toLowerCamelCase(descriptor.name)
|
||||||
)
|
)
|
||||||
let documentation = descriptor.protoSourceComments()
|
let documentation = descriptor.protoSourceComments()
|
||||||
self.init(
|
self.init(
|
||||||
|
@ -208,17 +214,19 @@ extension FileDescriptor {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SwiftProtobufNamer {
|
extension SwiftProtobufNamer {
|
||||||
internal func formattedUpperCasePackage(file: FileDescriptor) -> String {
|
internal func typePrefixProperty(file: FileDescriptor) -> String {
|
||||||
let unformattedPackage = self.typePrefix(forFile: file)
|
let typePrefix = self.typePrefix(forFile: file)
|
||||||
return unformattedPackage.trimTrailingUnderscores()
|
let lowercased = typePrefix.split(separator: "_").map { component in
|
||||||
}
|
|
||||||
|
|
||||||
internal func formattedLowerCasePackage(file: FileDescriptor) -> String {
|
|
||||||
let upperCasePackage = self.formattedUpperCasePackage(file: file)
|
|
||||||
let lowerCaseComponents = upperCasePackage.split(separator: "_").map { component in
|
|
||||||
NamingUtils.toLowerCamelCase(String(component))
|
NamingUtils.toLowerCamelCase(String(component))
|
||||||
}
|
}
|
||||||
return lowerCaseComponents.joined(separator: "_")
|
|
||||||
|
let joined = lowercased.joined(separator: "_")
|
||||||
|
if typePrefix.hasSuffix("_"), !joined.hasSuffix("_") {
|
||||||
|
// Add the trailing "_" if it was dropped.
|
||||||
|
return joined + "_"
|
||||||
|
} else {
|
||||||
|
return joined
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,11 +17,12 @@
|
||||||
package import GRPCCodeGen
|
package import GRPCCodeGen
|
||||||
package import SwiftProtobufPluginLibrary
|
package import SwiftProtobufPluginLibrary
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
package struct ProtobufCodeGenerator {
|
package struct ProtobufCodeGenerator {
|
||||||
internal var config: SourceGenerator.Config
|
internal var config: ProtobufCodeGenerator.Config
|
||||||
|
|
||||||
package init(
|
package init(
|
||||||
config: SourceGenerator.Config
|
config: ProtobufCodeGenerator.Config
|
||||||
) {
|
) {
|
||||||
self.config = config
|
self.config = config
|
||||||
}
|
}
|
||||||
|
@ -29,17 +30,76 @@ package struct ProtobufCodeGenerator {
|
||||||
package func generateCode(
|
package func generateCode(
|
||||||
fileDescriptor: FileDescriptor,
|
fileDescriptor: FileDescriptor,
|
||||||
protoFileModuleMappings: ProtoFileToModuleMappings,
|
protoFileModuleMappings: ProtoFileToModuleMappings,
|
||||||
extraModuleImports: [String]
|
extraModuleImports: [String],
|
||||||
|
availabilityOverrides: [(os: String, version: String)] = []
|
||||||
) throws -> String {
|
) throws -> String {
|
||||||
let parser = ProtobufCodeGenParser(
|
let parser = ProtobufCodeGenParser(
|
||||||
protoFileModuleMappings: protoFileModuleMappings,
|
protoFileModuleMappings: protoFileModuleMappings,
|
||||||
extraModuleImports: extraModuleImports,
|
extraModuleImports: extraModuleImports,
|
||||||
accessLevel: self.config.accessLevel
|
accessLevel: self.config.accessLevel,
|
||||||
|
moduleNames: self.config.moduleNames
|
||||||
)
|
)
|
||||||
let sourceGenerator = SourceGenerator(config: self.config)
|
|
||||||
|
var codeGeneratorConfig = GRPCCodeGen.CodeGenerator.Config(
|
||||||
|
accessLevel: self.config.accessLevel,
|
||||||
|
accessLevelOnImports: self.config.accessLevelOnImports,
|
||||||
|
client: self.config.generateClient,
|
||||||
|
server: self.config.generateServer,
|
||||||
|
indentation: self.config.indentation
|
||||||
|
)
|
||||||
|
codeGeneratorConfig.grpcCoreModuleName = self.config.moduleNames.grpcCore
|
||||||
|
|
||||||
|
if availabilityOverrides.isEmpty {
|
||||||
|
codeGeneratorConfig.availability = .default
|
||||||
|
} else {
|
||||||
|
codeGeneratorConfig.availability = .custom(
|
||||||
|
availabilityOverrides.map { (os, version) in
|
||||||
|
.init(os: os, version: version)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let codeGenerator = GRPCCodeGen.CodeGenerator(config: codeGeneratorConfig)
|
||||||
|
|
||||||
let codeGenerationRequest = try parser.parse(descriptor: fileDescriptor)
|
let codeGenerationRequest = try parser.parse(descriptor: fileDescriptor)
|
||||||
let sourceFile = try sourceGenerator.generate(codeGenerationRequest)
|
let sourceFile = try codeGenerator.generate(codeGenerationRequest)
|
||||||
return sourceFile.contents
|
return sourceFile.contents
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
|
extension ProtobufCodeGenerator {
|
||||||
|
package struct Config {
|
||||||
|
package var accessLevel: GRPCCodeGen.CodeGenerator.Config.AccessLevel
|
||||||
|
package var accessLevelOnImports: Bool
|
||||||
|
|
||||||
|
package var generateClient: Bool
|
||||||
|
package var generateServer: Bool
|
||||||
|
|
||||||
|
package var indentation: Int
|
||||||
|
package var moduleNames: ModuleNames
|
||||||
|
|
||||||
|
package struct ModuleNames {
|
||||||
|
package var grpcCore: String
|
||||||
|
package var grpcProtobuf: String
|
||||||
|
package var swiftProtobuf: String
|
||||||
|
|
||||||
|
package static let defaults = Self(
|
||||||
|
grpcCore: "GRPCCore",
|
||||||
|
grpcProtobuf: "GRPCProtobuf",
|
||||||
|
swiftProtobuf: "SwiftProtobuf"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
package static var defaults: Self {
|
||||||
|
Self(
|
||||||
|
accessLevel: .internal,
|
||||||
|
accessLevelOnImports: false,
|
||||||
|
generateClient: true,
|
||||||
|
generateServer: true,
|
||||||
|
indentation: 4,
|
||||||
|
moduleNames: .defaults
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -26,7 +26,8 @@ import Foundation
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@main
|
@main
|
||||||
final class GenerateGRPC: CodeGenerator {
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
|
final class GenerateGRPC: SwiftProtobufPluginLibrary.CodeGenerator {
|
||||||
var version: String? {
|
var version: String? {
|
||||||
Version.versionString
|
Version.versionString
|
||||||
}
|
}
|
||||||
|
@ -55,37 +56,10 @@ final class GenerateGRPC: CodeGenerator {
|
||||||
let options = try GeneratorOptions(parameter: parameter)
|
let options = try GeneratorOptions(parameter: parameter)
|
||||||
|
|
||||||
for descriptor in fileDescriptors {
|
for descriptor in fileDescriptors {
|
||||||
if options.generateReflectionData {
|
|
||||||
try self.generateReflectionData(
|
|
||||||
descriptor,
|
|
||||||
options: options,
|
|
||||||
outputs: outputs
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
try self.generateV2Stubs(descriptor, options: options, outputs: outputs)
|
try self.generateV2Stubs(descriptor, options: options, outputs: outputs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func generateReflectionData(
|
|
||||||
_ descriptor: FileDescriptor,
|
|
||||||
options: GeneratorOptions,
|
|
||||||
outputs: any GeneratorOutputs
|
|
||||||
) throws {
|
|
||||||
let fileName = self.uniqueOutputFileName(
|
|
||||||
fileDescriptor: descriptor,
|
|
||||||
fileNamingOption: options.fileNaming,
|
|
||||||
extension: "reflection"
|
|
||||||
)
|
|
||||||
|
|
||||||
var options = ExtractProtoOptions()
|
|
||||||
options.includeSourceCodeInfo = true
|
|
||||||
let proto = descriptor.extractProto(options: options)
|
|
||||||
let serializedProto = try proto.serializedData()
|
|
||||||
let reflectionData = serializedProto.base64EncodedString()
|
|
||||||
try outputs.add(fileName: fileName, contents: reflectionData)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func generateV2Stubs(
|
private func generateV2Stubs(
|
||||||
_ descriptor: FileDescriptor,
|
_ descriptor: FileDescriptor,
|
||||||
options: GeneratorOptions,
|
options: GeneratorOptions,
|
||||||
|
@ -96,18 +70,19 @@ final class GenerateGRPC: CodeGenerator {
|
||||||
fileNamingOption: options.fileNaming
|
fileNamingOption: options.fileNaming
|
||||||
)
|
)
|
||||||
|
|
||||||
let config = SourceGenerator.Config(options: options)
|
let fileGenerator = ProtobufCodeGenerator(config: options.config)
|
||||||
let fileGenerator = ProtobufCodeGenerator(config: config)
|
|
||||||
let contents = try fileGenerator.generateCode(
|
let contents = try fileGenerator.generateCode(
|
||||||
fileDescriptor: descriptor,
|
fileDescriptor: descriptor,
|
||||||
protoFileModuleMappings: options.protoToModuleMappings,
|
protoFileModuleMappings: options.protoToModuleMappings,
|
||||||
extraModuleImports: options.extraModuleImports
|
extraModuleImports: options.extraModuleImports,
|
||||||
|
availabilityOverrides: options.availabilityOverrides
|
||||||
)
|
)
|
||||||
|
|
||||||
try outputs.add(fileName: fileName, contents: contents)
|
try outputs.add(fileName: fileName, contents: contents)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
extension GenerateGRPC {
|
extension GenerateGRPC {
|
||||||
private func uniqueOutputFileName(
|
private func uniqueOutputFileName(
|
||||||
fileDescriptor: FileDescriptor,
|
fileDescriptor: FileDescriptor,
|
||||||
|
@ -181,24 +156,3 @@ private func splitPath(pathname: String) -> (dir: String, base: String, suffix:
|
||||||
}
|
}
|
||||||
return (dir: dir, base: base, suffix: suffix)
|
return (dir: dir, base: base, suffix: suffix)
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SourceGenerator.Config {
|
|
||||||
init(options: GeneratorOptions) {
|
|
||||||
let accessLevel: SourceGenerator.Config.AccessLevel
|
|
||||||
switch options.visibility {
|
|
||||||
case .internal:
|
|
||||||
accessLevel = .internal
|
|
||||||
case .package:
|
|
||||||
accessLevel = .package
|
|
||||||
case .public:
|
|
||||||
accessLevel = .public
|
|
||||||
}
|
|
||||||
|
|
||||||
self.init(
|
|
||||||
accessLevel: accessLevel,
|
|
||||||
accessLevelOnImports: options.useAccessLevelOnImports,
|
|
||||||
client: options.generateClient,
|
|
||||||
server: options.generateServer
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -14,6 +14,8 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import GRPCCodeGen
|
||||||
|
import GRPCProtobufCodeGen
|
||||||
import SwiftProtobufPluginLibrary
|
import SwiftProtobufPluginLibrary
|
||||||
|
|
||||||
enum GenerationError: Error, CustomStringConvertible {
|
enum GenerationError: Error, CustomStringConvertible {
|
||||||
|
@ -23,6 +25,8 @@ enum GenerationError: Error, CustomStringConvertible {
|
||||||
case invalidParameterValue(name: String, value: String)
|
case invalidParameterValue(name: String, value: String)
|
||||||
/// Raised to wrap another error but provide a context message.
|
/// Raised to wrap another error but provide a context message.
|
||||||
case wrappedError(message: String, error: any Error)
|
case wrappedError(message: String, error: any Error)
|
||||||
|
/// The parameter isn't supported.
|
||||||
|
case unsupportedParameter(name: String, message: String)
|
||||||
|
|
||||||
var description: String {
|
var description: String {
|
||||||
switch self {
|
switch self {
|
||||||
|
@ -32,6 +36,8 @@ enum GenerationError: Error, CustomStringConvertible {
|
||||||
return "Unknown value for generation parameter '\(name)': '\(value)'"
|
return "Unknown value for generation parameter '\(name)': '\(value)'"
|
||||||
case let .wrappedError(message, error):
|
case let .wrappedError(message, error):
|
||||||
return "\(message): \(error)"
|
return "\(message): \(error)"
|
||||||
|
case let .unsupportedParameter(name, message):
|
||||||
|
return "Unsupported parameter '\(name)': \(message)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,36 +48,14 @@ enum FileNaming: String {
|
||||||
case dropPath = "DropPath"
|
case dropPath = "DropPath"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
struct GeneratorOptions {
|
struct GeneratorOptions {
|
||||||
enum Visibility: String {
|
|
||||||
case `internal` = "Internal"
|
|
||||||
case `public` = "Public"
|
|
||||||
case `package` = "Package"
|
|
||||||
|
|
||||||
var sourceSnippet: String {
|
|
||||||
switch self {
|
|
||||||
case .internal:
|
|
||||||
return "internal"
|
|
||||||
case .public:
|
|
||||||
return "public"
|
|
||||||
case .package:
|
|
||||||
return "package"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private(set) var visibility = Visibility.internal
|
|
||||||
|
|
||||||
private(set) var generateServer = true
|
|
||||||
private(set) var generateClient = true
|
|
||||||
|
|
||||||
private(set) var protoToModuleMappings = ProtoFileToModuleMappings()
|
private(set) var protoToModuleMappings = ProtoFileToModuleMappings()
|
||||||
private(set) var fileNaming = FileNaming.fullPath
|
private(set) var fileNaming = FileNaming.fullPath
|
||||||
private(set) var extraModuleImports: [String] = []
|
private(set) var extraModuleImports: [String] = []
|
||||||
private(set) var gRPCModuleName = "GRPC"
|
private(set) var availabilityOverrides: [(os: String, version: String)] = []
|
||||||
private(set) var swiftProtobufModuleName = "SwiftProtobuf"
|
|
||||||
private(set) var generateReflectionData = false
|
private(set) var config: ProtobufCodeGenerator.Config = .defaults
|
||||||
private(set) var useAccessLevelOnImports = false
|
|
||||||
|
|
||||||
init(parameter: any CodeGeneratorParameter) throws {
|
init(parameter: any CodeGeneratorParameter) throws {
|
||||||
try self.init(pairs: parameter.parsedPairs)
|
try self.init(pairs: parameter.parsedPairs)
|
||||||
|
@ -81,22 +65,22 @@ struct GeneratorOptions {
|
||||||
for pair in pairs {
|
for pair in pairs {
|
||||||
switch pair.key {
|
switch pair.key {
|
||||||
case "Visibility":
|
case "Visibility":
|
||||||
if let value = Visibility(rawValue: pair.value) {
|
if let value = GRPCCodeGen.CodeGenerator.Config.AccessLevel(protocOption: pair.value) {
|
||||||
self.visibility = value
|
self.config.accessLevel = value
|
||||||
} else {
|
} else {
|
||||||
throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
|
throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
case "Server":
|
case "Server":
|
||||||
if let value = Bool(pair.value.lowercased()) {
|
if let value = Bool(pair.value.lowercased()) {
|
||||||
self.generateServer = value
|
self.config.generateServer = value
|
||||||
} else {
|
} else {
|
||||||
throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
|
throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
case "Client":
|
case "Client":
|
||||||
if let value = Bool(pair.value.lowercased()) {
|
if let value = Bool(pair.value.lowercased()) {
|
||||||
self.generateClient = value
|
self.config.generateClient = value
|
||||||
} else {
|
} else {
|
||||||
throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
|
throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
|
||||||
}
|
}
|
||||||
|
@ -129,28 +113,49 @@ struct GeneratorOptions {
|
||||||
|
|
||||||
case "GRPCModuleName":
|
case "GRPCModuleName":
|
||||||
if !pair.value.isEmpty {
|
if !pair.value.isEmpty {
|
||||||
self.gRPCModuleName = pair.value
|
self.config.moduleNames.grpcCore = pair.value
|
||||||
|
} else {
|
||||||
|
throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
case "GRPCProtobufModuleName":
|
||||||
|
if !pair.value.isEmpty {
|
||||||
|
self.config.moduleNames.grpcProtobuf = pair.value
|
||||||
} else {
|
} else {
|
||||||
throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
|
throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
case "SwiftProtobufModuleName":
|
case "SwiftProtobufModuleName":
|
||||||
if !pair.value.isEmpty {
|
if !pair.value.isEmpty {
|
||||||
self.swiftProtobufModuleName = pair.value
|
self.config.moduleNames.swiftProtobuf = pair.value
|
||||||
|
} else {
|
||||||
|
throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
case "Availability":
|
||||||
|
if !pair.value.isEmpty {
|
||||||
|
let parts = pair.value.split(separator: " ", maxSplits: 1)
|
||||||
|
if parts.count == 2 {
|
||||||
|
self.availabilityOverrides.append((os: String(parts[0]), version: String(parts[1])))
|
||||||
|
} else {
|
||||||
|
throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
|
throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
case "ReflectionData":
|
case "ReflectionData":
|
||||||
if let value = Bool(pair.value.lowercased()) {
|
throw GenerationError.unsupportedParameter(
|
||||||
self.generateReflectionData = value
|
name: pair.key,
|
||||||
} else {
|
message: """
|
||||||
throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
|
The reflection service uses descriptor sets. Refer to the protoc docs and the \
|
||||||
}
|
'--descriptor_set_out' option for more information.
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
case "UseAccessLevelOnImports":
|
case "UseAccessLevelOnImports":
|
||||||
if let value = Bool(pair.value.lowercased()) {
|
if let value = Bool(pair.value.lowercased()) {
|
||||||
self.useAccessLevelOnImports = value
|
self.config.accessLevelOnImports = value
|
||||||
} else {
|
} else {
|
||||||
throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
|
throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
|
||||||
}
|
}
|
||||||
|
@ -187,6 +192,7 @@ struct GeneratorOptions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
extension String.SubSequence {
|
extension String.SubSequence {
|
||||||
func trimmingWhitespaceAndNewlines() -> String {
|
func trimmingWhitespaceAndNewlines() -> String {
|
||||||
let trimmedSuffix = self.drop(while: { $0.isNewline || $0.isWhitespace })
|
let trimmedSuffix = self.drop(while: { $0.isNewline || $0.isWhitespace })
|
||||||
|
@ -194,3 +200,19 @@ extension String.SubSequence {
|
||||||
return String(trimmed)
|
return String(trimmed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
|
extension GRPCCodeGen.CodeGenerator.Config.AccessLevel {
|
||||||
|
fileprivate init?(protocOption value: String) {
|
||||||
|
switch value {
|
||||||
|
case "Internal":
|
||||||
|
self = .internal
|
||||||
|
case "Public":
|
||||||
|
self = .public
|
||||||
|
case "Package":
|
||||||
|
self = .package
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,26 +14,17 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#if canImport(CGRPCProtobuf)
|
||||||
|
private import CGRPCProtobuf
|
||||||
|
#endif
|
||||||
|
|
||||||
internal enum Version {
|
internal enum Version {
|
||||||
/// The major version.
|
|
||||||
internal static let major = 1
|
|
||||||
|
|
||||||
/// The minor version.
|
|
||||||
internal static let minor = 0
|
|
||||||
|
|
||||||
/// The patch version.
|
|
||||||
internal static let patch = 0
|
|
||||||
|
|
||||||
/// Any additional label.
|
|
||||||
internal static let label = "development"
|
|
||||||
|
|
||||||
/// The version string.
|
/// The version string.
|
||||||
internal static var versionString: String {
|
internal static var versionString: String {
|
||||||
let version = "\(Self.major).\(Self.minor).\(Self.patch)"
|
#if canImport(CGRPCProtobuf)
|
||||||
if Self.label.isEmpty {
|
String(cString: cgrprc_grpc_swift_protobuf_version())
|
||||||
return version
|
#else
|
||||||
} else {
|
"unknown"
|
||||||
return version + "-" + Self.label
|
#endif
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
Binary file not shown.
|
@ -27,25 +27,29 @@ struct ProtobufCodeGenParserTests {
|
||||||
static let descriptorSetName = "test-service"
|
static let descriptorSetName = "test-service"
|
||||||
static let fileDescriptorName = "test-service"
|
static let fileDescriptorName = "test-service"
|
||||||
|
|
||||||
let codeGen: CodeGenerationRequest
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
|
var codeGen: CodeGenerationRequest {
|
||||||
init() throws {
|
get throws {
|
||||||
let descriptor = try #require(try Self.fileDescriptor)
|
let descriptor = try Self.fileDescriptor
|
||||||
self.codeGen = try parseDescriptor(descriptor)
|
return try parseDescriptor(descriptor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("Filename")
|
@Test("Filename")
|
||||||
func fileName() {
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
#expect(self.codeGen.fileName == "test-service.proto")
|
func fileName() throws {
|
||||||
|
#expect(try self.codeGen.fileName == "test-service.proto")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("Leading trivia")
|
@Test("Leading trivia")
|
||||||
func leadingTrivia() {
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
|
func leadingTrivia() throws {
|
||||||
let expected = """
|
let expected = """
|
||||||
/// Leading trivia.
|
/// Leading trivia.
|
||||||
|
|
||||||
// DO NOT EDIT.
|
// DO NOT EDIT.
|
||||||
// swift-format-ignore-file
|
// swift-format-ignore-file
|
||||||
|
// swiftlint:disable all
|
||||||
//
|
//
|
||||||
// Generated by the gRPC Swift generator plugin for the protocol buffer compiler.
|
// Generated by the gRPC Swift generator plugin for the protocol buffer compiler.
|
||||||
// Source: test-service.proto
|
// Source: test-service.proto
|
||||||
|
@ -55,99 +59,112 @@ struct ProtobufCodeGenParserTests {
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
#expect(self.codeGen.leadingTrivia == expected)
|
#expect(try self.codeGen.leadingTrivia == expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("Dependencies")
|
@Test("Dependencies")
|
||||||
func dependencies() {
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
|
func dependencies() throws {
|
||||||
let expected: [GRPCCodeGen.Dependency] = [
|
let expected: [GRPCCodeGen.Dependency] = [
|
||||||
.init(module: "GRPCProtobuf", accessLevel: .internal) // Always an internal import
|
.init(module: "GRPCProtobuf", accessLevel: .internal), // Always an internal import
|
||||||
|
.init(module: "SwiftProtobuf", accessLevel: .internal),
|
||||||
]
|
]
|
||||||
#expect(self.codeGen.dependencies == expected)
|
#expect(try self.codeGen.dependencies == expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suite("Service")
|
@Suite("Service")
|
||||||
struct Service {
|
struct Service {
|
||||||
let service: GRPCCodeGen.ServiceDescriptor
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
|
var service: GRPCCodeGen.ServiceDescriptor {
|
||||||
init() throws {
|
get throws {
|
||||||
let request = try parseDescriptor(try #require(try TestService.fileDescriptor))
|
let request = try parseDescriptor(try TestService.fileDescriptor)
|
||||||
try #require(request.services.count == 1)
|
try #require(request.services.count == 1)
|
||||||
self.service = try #require(request.services.first)
|
return try #require(request.services.first)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("Name")
|
@Test("Name")
|
||||||
func name() {
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
#expect(self.service.name.base == "TestService")
|
func name() throws {
|
||||||
}
|
#expect(try self.service.name.identifyingName == "test.TestService")
|
||||||
|
|
||||||
@Test("Namespace")
|
|
||||||
func namespace() {
|
|
||||||
#expect(self.service.namespace.base == "test")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suite("Methods")
|
@Suite("Methods")
|
||||||
struct Methods {
|
struct Methods {
|
||||||
let unary: GRPCCodeGen.MethodDescriptor
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
let clientStreaming: GRPCCodeGen.MethodDescriptor
|
var service: GRPCCodeGen.ServiceDescriptor {
|
||||||
let serverStreaming: GRPCCodeGen.MethodDescriptor
|
get throws {
|
||||||
let bidiStreaming: GRPCCodeGen.MethodDescriptor
|
let request = try parseDescriptor(try TestService.fileDescriptor)
|
||||||
|
try #require(request.services.count == 1)
|
||||||
|
return try #require(request.services.first)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init() throws {
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
let request = try parseDescriptor(try #require(try TestService.fileDescriptor))
|
var unary: GRPCCodeGen.MethodDescriptor {
|
||||||
#expect(request.services.count == 1)
|
get throws { try self.service.methods[0] }
|
||||||
let service = try #require(request.services.first)
|
}
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
self.unary = service.methods[0]
|
var clientStreaming: GRPCCodeGen.MethodDescriptor {
|
||||||
self.clientStreaming = service.methods[1]
|
get throws { try self.service.methods[1] }
|
||||||
self.serverStreaming = service.methods[2]
|
}
|
||||||
self.bidiStreaming = service.methods[3]
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
|
var serverStreaming: GRPCCodeGen.MethodDescriptor {
|
||||||
|
get throws { try self.service.methods[2] }
|
||||||
|
}
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
|
var bidiStreaming: GRPCCodeGen.MethodDescriptor {
|
||||||
|
get throws { try self.service.methods[3] }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("Documentation")
|
@Test("Documentation")
|
||||||
func documentation() {
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
#expect(self.unary.documentation == "/// Unary docs.\n")
|
func documentation() throws {
|
||||||
#expect(self.clientStreaming.documentation == "/// Client streaming docs.\n")
|
#expect(try self.unary.documentation == "/// Unary docs.\n")
|
||||||
#expect(self.serverStreaming.documentation == "/// Server streaming docs.\n")
|
#expect(try self.clientStreaming.documentation == "/// Client streaming docs.\n")
|
||||||
#expect(self.bidiStreaming.documentation == "/// Bidirectional streaming docs.\n")
|
#expect(try self.serverStreaming.documentation == "/// Server streaming docs.\n")
|
||||||
|
#expect(try self.bidiStreaming.documentation == "/// Bidirectional streaming docs.\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("Name")
|
@Test("Name")
|
||||||
func name() {
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
#expect(self.unary.name.base == "Unary")
|
func name() throws {
|
||||||
#expect(self.clientStreaming.name.base == "ClientStreaming")
|
try #expect(self.unary.name.identifyingName == "Unary")
|
||||||
#expect(self.serverStreaming.name.base == "ServerStreaming")
|
try #expect(self.clientStreaming.name.identifyingName == "ClientStreaming")
|
||||||
#expect(self.bidiStreaming.name.base == "BidirectionalStreaming")
|
try #expect(self.serverStreaming.name.identifyingName == "ServerStreaming")
|
||||||
|
try #expect(self.bidiStreaming.name.identifyingName == "BidirectionalStreaming")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("Input")
|
@Test("Input")
|
||||||
func input() {
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
#expect(self.unary.inputType == "Test_TestInput")
|
func input() throws {
|
||||||
#expect(!self.unary.isInputStreaming)
|
#expect(try self.unary.inputType == "Test_TestInput")
|
||||||
|
#expect(try !self.unary.isInputStreaming)
|
||||||
|
|
||||||
#expect(self.clientStreaming.inputType == "Test_TestInput")
|
#expect(try self.clientStreaming.inputType == "Test_TestInput")
|
||||||
#expect(self.clientStreaming.isInputStreaming)
|
#expect(try self.clientStreaming.isInputStreaming)
|
||||||
|
|
||||||
#expect(self.serverStreaming.inputType == "Test_TestInput")
|
#expect(try self.serverStreaming.inputType == "Test_TestInput")
|
||||||
#expect(!self.serverStreaming.isInputStreaming)
|
#expect(try !self.serverStreaming.isInputStreaming)
|
||||||
|
|
||||||
#expect(self.bidiStreaming.inputType == "Test_TestInput")
|
#expect(try self.bidiStreaming.inputType == "Test_TestInput")
|
||||||
#expect(self.bidiStreaming.isInputStreaming)
|
#expect(try self.bidiStreaming.isInputStreaming)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("Output")
|
@Test("Output")
|
||||||
func output() {
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
#expect(self.unary.outputType == "Test_TestOutput")
|
func output() throws {
|
||||||
#expect(!self.unary.isOutputStreaming)
|
#expect(try self.unary.outputType == "Test_TestOutput")
|
||||||
|
#expect(try !self.unary.isOutputStreaming)
|
||||||
|
|
||||||
#expect(self.clientStreaming.outputType == "Test_TestOutput")
|
#expect(try self.clientStreaming.outputType == "Test_TestOutput")
|
||||||
#expect(!self.clientStreaming.isOutputStreaming)
|
#expect(try !self.clientStreaming.isOutputStreaming)
|
||||||
|
|
||||||
#expect(self.serverStreaming.outputType == "Test_TestOutput")
|
#expect(try self.serverStreaming.outputType == "Test_TestOutput")
|
||||||
#expect(self.serverStreaming.isOutputStreaming)
|
#expect(try self.serverStreaming.isOutputStreaming)
|
||||||
|
|
||||||
#expect(self.bidiStreaming.outputType == "Test_TestOutput")
|
#expect(try self.bidiStreaming.outputType == "Test_TestOutput")
|
||||||
#expect(self.bidiStreaming.isOutputStreaming)
|
#expect(try self.bidiStreaming.isOutputStreaming)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -158,54 +175,59 @@ struct ProtobufCodeGenParserTests {
|
||||||
static let descriptorSetName = "foo-service"
|
static let descriptorSetName = "foo-service"
|
||||||
static let fileDescriptorName = "foo-service"
|
static let fileDescriptorName = "foo-service"
|
||||||
|
|
||||||
let codeGen: CodeGenerationRequest
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
|
var codeGen: CodeGenerationRequest {
|
||||||
init() throws {
|
get throws {
|
||||||
let descriptor = try #require(try Self.fileDescriptor)
|
let descriptor = try Self.fileDescriptor
|
||||||
self.codeGen = try parseDescriptor(descriptor)
|
return try parseDescriptor(descriptor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("Name")
|
@Test("Name")
|
||||||
func name() {
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
#expect(self.codeGen.fileName == "foo-service.proto")
|
func name() throws {
|
||||||
|
#expect(try self.codeGen.fileName == "foo-service.proto")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("Dependencies")
|
@Test("Dependencies")
|
||||||
func dependencies() {
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
|
func dependencies() throws {
|
||||||
let expected: [GRPCCodeGen.Dependency] = [
|
let expected: [GRPCCodeGen.Dependency] = [
|
||||||
.init(module: "GRPCProtobuf", accessLevel: .internal) // Always an internal import
|
.init(module: "GRPCProtobuf", accessLevel: .internal) // Always an internal import
|
||||||
]
|
]
|
||||||
#expect(self.codeGen.dependencies == expected)
|
#expect(try self.codeGen.dependencies == expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("Service1")
|
@Test("Service1")
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
func service1() throws {
|
func service1() throws {
|
||||||
let service = self.codeGen.services[0]
|
let service = try self.codeGen.services[0]
|
||||||
#expect(service.name.base == "FooService1")
|
#expect(service.name.identifyingName == "foo.FooService1")
|
||||||
#expect(service.namespace.base == "foo")
|
|
||||||
#expect(service.methods.count == 1)
|
#expect(service.methods.count == 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("Service1.Method")
|
@Test("Service1.Method")
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
func service1Method() throws {
|
func service1Method() throws {
|
||||||
let method = self.codeGen.services[0].methods[0]
|
let method = try self.codeGen.services[0].methods[0]
|
||||||
#expect(method.name.base == "Foo")
|
#expect(method.name.identifyingName == "Foo")
|
||||||
#expect(method.inputType == "Foo_FooInput")
|
#expect(method.inputType == "Foo_FooInput")
|
||||||
#expect(method.outputType == "Foo_FooOutput")
|
#expect(method.outputType == "Foo_FooOutput")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("Service2")
|
@Test("Service2")
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
func service2() throws {
|
func service2() throws {
|
||||||
let service = self.codeGen.services[1]
|
let service = try self.codeGen.services[1]
|
||||||
#expect(service.name.base == "FooService2")
|
#expect(service.name.identifyingName == "foo.FooService2")
|
||||||
#expect(service.namespace.base == "foo")
|
|
||||||
#expect(service.methods.count == 1)
|
#expect(service.methods.count == 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("Service2.Method")
|
@Test("Service2.Method")
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
func service2Method() throws {
|
func service2Method() throws {
|
||||||
let method = self.codeGen.services[1].methods[0]
|
let method = try self.codeGen.services[1].methods[0]
|
||||||
#expect(method.name.base == "Foo")
|
#expect(method.name.identifyingName == "Foo")
|
||||||
#expect(method.inputType == "Foo_FooInput")
|
#expect(method.inputType == "Foo_FooInput")
|
||||||
#expect(method.outputType == "Foo_FooOutput")
|
#expect(method.outputType == "Foo_FooOutput")
|
||||||
}
|
}
|
||||||
|
@ -216,23 +238,14 @@ struct ProtobufCodeGenParserTests {
|
||||||
static var descriptorSetName: String { "bar-service" }
|
static var descriptorSetName: String { "bar-service" }
|
||||||
static var fileDescriptorName: String { "bar-service" }
|
static var fileDescriptorName: String { "bar-service" }
|
||||||
|
|
||||||
let codeGen: CodeGenerationRequest
|
|
||||||
let service: GRPCCodeGen.ServiceDescriptor
|
|
||||||
|
|
||||||
init() throws {
|
|
||||||
let descriptor = try #require(try Self.fileDescriptor)
|
|
||||||
self.codeGen = try parseDescriptor(descriptor)
|
|
||||||
self.service = try #require(self.codeGen.services.first)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test("Service name")
|
@Test("Service name")
|
||||||
func serviceName() {
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
#expect(self.service.name.base == "BarService")
|
func serviceName() throws {
|
||||||
}
|
let descriptor = try Self.fileDescriptor
|
||||||
|
let codeGen = try parseDescriptor(descriptor)
|
||||||
|
let service = try #require(codeGen.services.first)
|
||||||
|
|
||||||
@Test("Service namespace")
|
#expect(service.name.identifyingName == "BarService")
|
||||||
func serviceNamespace() {
|
|
||||||
#expect(self.service.namespace.base == "")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,20 +254,17 @@ struct ProtobufCodeGenParserTests {
|
||||||
static let descriptorSetName = "wkt-service"
|
static let descriptorSetName = "wkt-service"
|
||||||
static let fileDescriptorName = "wkt-service"
|
static let fileDescriptorName = "wkt-service"
|
||||||
|
|
||||||
let codeGen: CodeGenerationRequest
|
|
||||||
|
|
||||||
init() throws {
|
|
||||||
let descriptor = try #require(try Self.fileDescriptor)
|
|
||||||
self.codeGen = try parseDescriptor(descriptor)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test("Dependencies")
|
@Test("Dependencies")
|
||||||
func dependencies() {
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
|
func dependencies() throws {
|
||||||
|
let descriptor = try Self.fileDescriptor
|
||||||
|
let codeGen = try parseDescriptor(descriptor)
|
||||||
|
|
||||||
let expected: [Dependency] = [
|
let expected: [Dependency] = [
|
||||||
Dependency(module: "GRPCProtobuf", accessLevel: .internal),
|
Dependency(module: "GRPCProtobuf", accessLevel: .internal),
|
||||||
Dependency(module: "SwiftProtobuf", accessLevel: .internal),
|
Dependency(module: "SwiftProtobuf", accessLevel: .internal),
|
||||||
]
|
]
|
||||||
#expect(self.codeGen.dependencies == expected)
|
#expect(codeGen.dependencies == expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,17 +25,43 @@ struct ProtobufCodeGeneratorTests {
|
||||||
static let descriptorSetName = "test-service"
|
static let descriptorSetName = "test-service"
|
||||||
static let fileDescriptorName = "test-service"
|
static let fileDescriptorName = "test-service"
|
||||||
|
|
||||||
@Test("Generate", arguments: [SourceGenerator.Config.AccessLevel.internal, .public, .package])
|
enum Availability {
|
||||||
func generate(accessLevel: SourceGenerator.Config.AccessLevel) throws {
|
case `default`
|
||||||
let generator = ProtobufCodeGenerator(
|
case fooOS
|
||||||
config: SourceGenerator.Config(
|
|
||||||
accessLevel: accessLevel,
|
var override: [(String, String)] {
|
||||||
accessLevelOnImports: false,
|
switch self {
|
||||||
client: true,
|
case .default:
|
||||||
server: true,
|
return []
|
||||||
indentation: 2
|
case .fooOS:
|
||||||
)
|
return [("fooOS", "42.0")]
|
||||||
)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var expected: String {
|
||||||
|
switch self {
|
||||||
|
case .default:
|
||||||
|
return "macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0"
|
||||||
|
case .fooOS:
|
||||||
|
return "fooOS 42.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
|
@Test(
|
||||||
|
"Generate",
|
||||||
|
arguments: [CodeGenerator.Config.AccessLevel.internal],
|
||||||
|
[Availability.default, Availability.fooOS]
|
||||||
|
)
|
||||||
|
func generate(
|
||||||
|
accessLevel: GRPCCodeGen.CodeGenerator.Config.AccessLevel,
|
||||||
|
availability: Availability
|
||||||
|
) throws {
|
||||||
|
var config = ProtobufCodeGenerator.Config.defaults
|
||||||
|
config.accessLevel = accessLevel
|
||||||
|
config.indentation = 2
|
||||||
|
let generator = ProtobufCodeGenerator(config: config)
|
||||||
|
|
||||||
let access: String
|
let access: String
|
||||||
switch accessLevel {
|
switch accessLevel {
|
||||||
|
@ -49,10 +75,13 @@ struct ProtobufCodeGeneratorTests {
|
||||||
fatalError()
|
fatalError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let expectedAvailability = availability.expected
|
||||||
|
|
||||||
let generated = try generator.generateCode(
|
let generated = try generator.generateCode(
|
||||||
fileDescriptor: Self.fileDescriptor,
|
fileDescriptor: Self.fileDescriptor,
|
||||||
protoFileModuleMappings: ProtoFileToModuleMappings(),
|
protoFileModuleMappings: ProtoFileToModuleMappings(),
|
||||||
extraModuleImports: []
|
extraModuleImports: [],
|
||||||
|
availabilityOverrides: availability.override
|
||||||
)
|
)
|
||||||
|
|
||||||
let expected = """
|
let expected = """
|
||||||
|
@ -60,6 +89,7 @@ struct ProtobufCodeGeneratorTests {
|
||||||
|
|
||||||
// DO NOT EDIT.
|
// DO NOT EDIT.
|
||||||
// swift-format-ignore-file
|
// swift-format-ignore-file
|
||||||
|
// swiftlint:disable all
|
||||||
//
|
//
|
||||||
// Generated by the gRPC Swift generator plugin for the protocol buffer compiler.
|
// Generated by the gRPC Swift generator plugin for the protocol buffer compiler.
|
||||||
// Source: test-service.proto
|
// Source: test-service.proto
|
||||||
|
@ -69,10 +99,12 @@ struct ProtobufCodeGeneratorTests {
|
||||||
|
|
||||||
import GRPCCore
|
import GRPCCore
|
||||||
import GRPCProtobuf
|
import GRPCProtobuf
|
||||||
|
import SwiftProtobuf
|
||||||
|
|
||||||
// MARK: - test.TestService
|
// MARK: - test.TestService
|
||||||
|
|
||||||
/// Namespace containing generated types for the "test.TestService" service.
|
/// Namespace containing generated types for the "test.TestService" service.
|
||||||
|
@available(\(expectedAvailability), *)
|
||||||
\(access) enum Test_TestService {
|
\(access) enum Test_TestService {
|
||||||
/// Service descriptor for the "test.TestService" service.
|
/// Service descriptor for the "test.TestService" service.
|
||||||
\(access) static let descriptor = GRPCCore.ServiceDescriptor(fullyQualifiedService: "test.TestService")
|
\(access) static let descriptor = GRPCCore.ServiceDescriptor(fullyQualifiedService: "test.TestService")
|
||||||
|
@ -136,6 +168,7 @@ struct ProtobufCodeGeneratorTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(\(expectedAvailability), *)
|
||||||
extension GRPCCore.ServiceDescriptor {
|
extension GRPCCore.ServiceDescriptor {
|
||||||
/// Service descriptor for the "test.TestService" service.
|
/// Service descriptor for the "test.TestService" service.
|
||||||
\(access) static let test_TestService = GRPCCore.ServiceDescriptor(fullyQualifiedService: "test.TestService")
|
\(access) static let test_TestService = GRPCCore.ServiceDescriptor(fullyQualifiedService: "test.TestService")
|
||||||
|
@ -143,6 +176,7 @@ struct ProtobufCodeGeneratorTests {
|
||||||
|
|
||||||
// MARK: test.TestService (server)
|
// MARK: test.TestService (server)
|
||||||
|
|
||||||
|
@available(\(expectedAvailability), *)
|
||||||
extension Test_TestService {
|
extension Test_TestService {
|
||||||
/// Streaming variant of the service protocol for the "test.TestService" service.
|
/// Streaming variant of the service protocol for the "test.TestService" service.
|
||||||
///
|
///
|
||||||
|
@ -404,6 +438,7 @@ struct ProtobufCodeGeneratorTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default implementation of 'registerMethods(with:)'.
|
// Default implementation of 'registerMethods(with:)'.
|
||||||
|
@available(\(expectedAvailability), *)
|
||||||
extension Test_TestService.StreamingServiceProtocol {
|
extension Test_TestService.StreamingServiceProtocol {
|
||||||
\(access) func registerMethods<Transport>(with router: inout GRPCCore.RPCRouter<Transport>) where Transport: GRPCCore.ServerTransport {
|
\(access) func registerMethods<Transport>(with router: inout GRPCCore.RPCRouter<Transport>) where Transport: GRPCCore.ServerTransport {
|
||||||
router.registerHandler(
|
router.registerHandler(
|
||||||
|
@ -454,6 +489,7 @@ struct ProtobufCodeGeneratorTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default implementation of streaming methods from 'StreamingServiceProtocol'.
|
// Default implementation of streaming methods from 'StreamingServiceProtocol'.
|
||||||
|
@available(\(expectedAvailability), *)
|
||||||
extension Test_TestService.ServiceProtocol {
|
extension Test_TestService.ServiceProtocol {
|
||||||
\(access) func unary(
|
\(access) func unary(
|
||||||
request: GRPCCore.StreamingServerRequest<Test_TestInput>,
|
request: GRPCCore.StreamingServerRequest<Test_TestInput>,
|
||||||
|
@ -490,6 +526,7 @@ struct ProtobufCodeGeneratorTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default implementation of methods from 'ServiceProtocol'.
|
// Default implementation of methods from 'ServiceProtocol'.
|
||||||
|
@available(\(expectedAvailability), *)
|
||||||
extension Test_TestService.SimpleServiceProtocol {
|
extension Test_TestService.SimpleServiceProtocol {
|
||||||
\(access) func unary(
|
\(access) func unary(
|
||||||
request: GRPCCore.ServerRequest<Test_TestInput>,
|
request: GRPCCore.ServerRequest<Test_TestInput>,
|
||||||
|
@ -554,6 +591,7 @@ struct ProtobufCodeGeneratorTests {
|
||||||
|
|
||||||
// MARK: test.TestService (client)
|
// MARK: test.TestService (client)
|
||||||
|
|
||||||
|
@available(\(expectedAvailability), *)
|
||||||
extension Test_TestService {
|
extension Test_TestService {
|
||||||
/// Generated client protocol for the "test.TestService" service.
|
/// Generated client protocol for the "test.TestService" service.
|
||||||
///
|
///
|
||||||
|
@ -812,6 +850,7 @@ struct ProtobufCodeGeneratorTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helpers providing default arguments to 'ClientProtocol' methods.
|
// Helpers providing default arguments to 'ClientProtocol' methods.
|
||||||
|
@available(\(expectedAvailability), *)
|
||||||
extension Test_TestService.ClientProtocol {
|
extension Test_TestService.ClientProtocol {
|
||||||
/// Call the "Unary" method.
|
/// Call the "Unary" method.
|
||||||
///
|
///
|
||||||
|
@ -927,6 +966,7 @@ struct ProtobufCodeGeneratorTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helpers providing sugared APIs for 'ClientProtocol' methods.
|
// Helpers providing sugared APIs for 'ClientProtocol' methods.
|
||||||
|
@available(\(expectedAvailability), *)
|
||||||
extension Test_TestService.ClientProtocol {
|
extension Test_TestService.ClientProtocol {
|
||||||
/// Call the "Unary" method.
|
/// Call the "Unary" method.
|
||||||
///
|
///
|
||||||
|
@ -1062,6 +1102,36 @@ struct ProtobufCodeGeneratorTests {
|
||||||
|
|
||||||
#expect(generated == expected)
|
#expect(generated == expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test("Generate with different module names")
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
|
func generateWithDifferentModuleNames() throws {
|
||||||
|
var config = ProtobufCodeGenerator.Config.defaults
|
||||||
|
let defaultNames = config.moduleNames
|
||||||
|
|
||||||
|
config.accessLevel = .public
|
||||||
|
config.indentation = 2
|
||||||
|
config.moduleNames.grpcCore = String(config.moduleNames.grpcCore.reversed())
|
||||||
|
config.moduleNames.grpcProtobuf = String(config.moduleNames.grpcProtobuf.reversed())
|
||||||
|
config.moduleNames.swiftProtobuf = String(config.moduleNames.swiftProtobuf.reversed())
|
||||||
|
|
||||||
|
let generator = ProtobufCodeGenerator(config: config)
|
||||||
|
let generated = try generator.generateCode(
|
||||||
|
fileDescriptor: Self.fileDescriptor,
|
||||||
|
protoFileModuleMappings: ProtoFileToModuleMappings(),
|
||||||
|
extraModuleImports: []
|
||||||
|
)
|
||||||
|
|
||||||
|
// Mustn't contain the default names.
|
||||||
|
#expect(!generated.contains(defaultNames.grpcCore))
|
||||||
|
#expect(!generated.contains(defaultNames.grpcProtobuf))
|
||||||
|
#expect(!generated.contains(defaultNames.swiftProtobuf))
|
||||||
|
|
||||||
|
// Must contain the configured names.
|
||||||
|
#expect(generated.contains(config.moduleNames.grpcCore))
|
||||||
|
#expect(generated.contains(config.moduleNames.grpcProtobuf))
|
||||||
|
#expect(generated.contains(config.moduleNames.swiftProtobuf))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suite("File-without-services (foo-messages.proto)")
|
@Suite("File-without-services (foo-messages.proto)")
|
||||||
|
@ -1070,16 +1140,13 @@ struct ProtobufCodeGeneratorTests {
|
||||||
static let fileDescriptorName = "foo-messages"
|
static let fileDescriptorName = "foo-messages"
|
||||||
|
|
||||||
@Test("Generate")
|
@Test("Generate")
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
func generate() throws {
|
func generate() throws {
|
||||||
let generator = ProtobufCodeGenerator(
|
var config: ProtobufCodeGenerator.Config = .defaults
|
||||||
config: SourceGenerator.Config(
|
config.accessLevel = .public
|
||||||
accessLevel: .public,
|
config.indentation = 2
|
||||||
accessLevelOnImports: false,
|
|
||||||
client: true,
|
let generator = ProtobufCodeGenerator(config: config)
|
||||||
server: true,
|
|
||||||
indentation: 2
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
let generated = try generator.generateCode(
|
let generated = try generator.generateCode(
|
||||||
fileDescriptor: Self.fileDescriptor,
|
fileDescriptor: Self.fileDescriptor,
|
||||||
|
@ -1092,6 +1159,7 @@ struct ProtobufCodeGeneratorTests {
|
||||||
|
|
||||||
// DO NOT EDIT.
|
// DO NOT EDIT.
|
||||||
// swift-format-ignore-file
|
// swift-format-ignore-file
|
||||||
|
// swiftlint:disable all
|
||||||
//
|
//
|
||||||
// Generated by the gRPC Swift generator plugin for the protocol buffer compiler.
|
// Generated by the gRPC Swift generator plugin for the protocol buffer compiler.
|
||||||
// Source: foo-messages.proto
|
// Source: foo-messages.proto
|
||||||
|
|
|
@ -21,7 +21,7 @@ import SwiftProtobufPluginLibrary
|
||||||
import Testing
|
import Testing
|
||||||
|
|
||||||
import struct GRPCCodeGen.CodeGenerationRequest
|
import struct GRPCCodeGen.CodeGenerationRequest
|
||||||
import struct GRPCCodeGen.SourceGenerator
|
import struct GRPCCodeGen.CodeGenerator
|
||||||
|
|
||||||
protocol UsesDescriptorSet {
|
protocol UsesDescriptorSet {
|
||||||
static var descriptorSetName: String { get }
|
static var descriptorSetName: String { get }
|
||||||
|
@ -63,20 +63,22 @@ private func loadDescriptorSet(
|
||||||
)
|
)
|
||||||
|
|
||||||
let url = try #require(maybeURL)
|
let url = try #require(maybeURL)
|
||||||
let data = try #require(try Data(contentsOf: url))
|
let data = try Data(contentsOf: url)
|
||||||
let descriptorSet = try Google_Protobuf_FileDescriptorSet(serializedBytes: data)
|
let descriptorSet = try Google_Protobuf_FileDescriptorSet(serializedBytes: data)
|
||||||
return DescriptorSet(proto: descriptorSet)
|
return DescriptorSet(proto: descriptorSet)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
func parseDescriptor(
|
func parseDescriptor(
|
||||||
_ descriptor: FileDescriptor,
|
_ descriptor: FileDescriptor,
|
||||||
extraModuleImports: [String] = [],
|
extraModuleImports: [String] = [],
|
||||||
accessLevel: SourceGenerator.Config.AccessLevel = .internal
|
accessLevel: CodeGenerator.Config.AccessLevel = .internal
|
||||||
) throws -> CodeGenerationRequest {
|
) throws -> CodeGenerationRequest {
|
||||||
let parser = ProtobufCodeGenParser(
|
let parser = ProtobufCodeGenParser(
|
||||||
protoFileModuleMappings: .init(),
|
protoFileModuleMappings: .init(),
|
||||||
extraModuleImports: extraModuleImports,
|
extraModuleImports: extraModuleImports,
|
||||||
accessLevel: accessLevel
|
accessLevel: accessLevel,
|
||||||
|
moduleNames: .defaults
|
||||||
)
|
)
|
||||||
return try parser.parse(descriptor: descriptor)
|
return try parser.parse(descriptor: descriptor)
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,7 @@ struct DetailedErrorTests {
|
||||||
(["Help", "Help", "Help"], [.help(.testValue), .help(.testValue), .help(.testValue)]),
|
(["Help", "Help", "Help"], [.help(.testValue), .help(.testValue), .help(.testValue)]),
|
||||||
] as [([String], [ErrorDetails])]
|
] as [([String], [ErrorDetails])]
|
||||||
)
|
)
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
func rpcStatus(details: [String], expected: [ErrorDetails]) async throws {
|
func rpcStatus(details: [String], expected: [ErrorDetails]) async throws {
|
||||||
let inProcess = InProcessTransport()
|
let inProcess = InProcessTransport()
|
||||||
try await withGRPCServer(transport: inProcess.server, services: [ErrorThrowingService()]) { _ in
|
try await withGRPCServer(transport: inProcess.server, services: [ErrorThrowingService()]) { _ in
|
||||||
|
@ -95,11 +96,27 @@ struct DetailedErrorTests {
|
||||||
(.localizedMessage(.testValue), #"LocalizedMessage(locale: "l", message: "m")"#),
|
(.localizedMessage(.testValue), #"LocalizedMessage(locale: "l", message: "m")"#),
|
||||||
] as [(ErrorDetails, String)]
|
] as [(ErrorDetails, String)]
|
||||||
)
|
)
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
func errorInfoDescription(_ details: ErrorDetails, expected: String) {
|
func errorInfoDescription(_ details: ErrorDetails, expected: String) {
|
||||||
#expect(String(describing: details) == expected)
|
#expect(String(describing: details) == expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test("Round-trip encoding of GoogleRPCStatus")
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
|
func googleRPCStatusRoundTripCoding() throws {
|
||||||
|
let detail = ErrorDetails.BadRequest(violations: [.init(field: "foo", description: "bar")])
|
||||||
|
let status = GoogleRPCStatus(code: .dataLoss, message: "Uh oh", details: [.badRequest(detail)])
|
||||||
|
|
||||||
|
let serialized: [UInt8] = try status.serializedBytes()
|
||||||
|
let deserialized = try GoogleRPCStatus(serializedBytes: serialized)
|
||||||
|
#expect(deserialized.code == status.code)
|
||||||
|
#expect(deserialized.message == status.message)
|
||||||
|
#expect(deserialized.details.count == status.details.count)
|
||||||
|
#expect(deserialized.details.first?.badRequest == detail)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
private struct ErrorThrowingService: ErrorService.SimpleServiceProtocol {
|
private struct ErrorThrowingService: ErrorService.SimpleServiceProtocol {
|
||||||
func throwError(
|
func throwError(
|
||||||
request: ThrowInput,
|
request: ThrowInput,
|
||||||
|
@ -170,14 +187,17 @@ private struct ErrorThrowingService: ErrorService.SimpleServiceProtocol {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
extension ErrorDetails.ErrorInfo {
|
extension ErrorDetails.ErrorInfo {
|
||||||
fileprivate static let testValue = Self(reason: "r", domain: "d", metadata: ["k": "v"])
|
fileprivate static let testValue = Self(reason: "r", domain: "d", metadata: ["k": "v"])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
extension ErrorDetails.RetryInfo {
|
extension ErrorDetails.RetryInfo {
|
||||||
fileprivate static let testValue = Self(delay: .seconds(1))
|
fileprivate static let testValue = Self(delay: .seconds(1))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
extension ErrorDetails.DebugInfo {
|
extension ErrorDetails.DebugInfo {
|
||||||
fileprivate static let testValue = Self(
|
fileprivate static let testValue = Self(
|
||||||
stack: ["foo.foo()", "foo.bar()"],
|
stack: ["foo.foo()", "foo.bar()"],
|
||||||
|
@ -185,6 +205,7 @@ extension ErrorDetails.DebugInfo {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
extension ErrorDetails.QuotaFailure {
|
extension ErrorDetails.QuotaFailure {
|
||||||
fileprivate static let testValue = Self(
|
fileprivate static let testValue = Self(
|
||||||
violations: [
|
violations: [
|
||||||
|
@ -193,6 +214,7 @@ extension ErrorDetails.QuotaFailure {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
extension ErrorDetails.PreconditionFailure {
|
extension ErrorDetails.PreconditionFailure {
|
||||||
fileprivate static let testValue = Self(
|
fileprivate static let testValue = Self(
|
||||||
violations: [
|
violations: [
|
||||||
|
@ -201,6 +223,7 @@ extension ErrorDetails.PreconditionFailure {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
extension ErrorDetails.BadRequest {
|
extension ErrorDetails.BadRequest {
|
||||||
fileprivate static let testValue = Self(
|
fileprivate static let testValue = Self(
|
||||||
violations: [
|
violations: [
|
||||||
|
@ -209,14 +232,17 @@ extension ErrorDetails.BadRequest {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
extension ErrorDetails.RequestInfo {
|
extension ErrorDetails.RequestInfo {
|
||||||
fileprivate static let testValue = Self(requestID: "id", servingData: "d")
|
fileprivate static let testValue = Self(requestID: "id", servingData: "d")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
extension ErrorDetails.ResourceInfo {
|
extension ErrorDetails.ResourceInfo {
|
||||||
fileprivate static let testValue = Self(type: "t", name: "n", errorDescription: "d")
|
fileprivate static let testValue = Self(type: "t", name: "n", errorDescription: "d")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
extension ErrorDetails.Help {
|
extension ErrorDetails.Help {
|
||||||
fileprivate static let testValue = Self(
|
fileprivate static let testValue = Self(
|
||||||
links: [
|
links: [
|
||||||
|
@ -225,6 +251,7 @@ extension ErrorDetails.Help {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
extension ErrorDetails.LocalizedMessage {
|
extension ErrorDetails.LocalizedMessage {
|
||||||
fileprivate static let testValue = Self(locale: "l", message: "m")
|
fileprivate static let testValue = Self(locale: "l", message: "m")
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
// DO NOT EDIT.
|
// DO NOT EDIT.
|
||||||
// swift-format-ignore-file
|
// swift-format-ignore-file
|
||||||
|
// swiftlint:disable all
|
||||||
//
|
//
|
||||||
// Generated by the gRPC Swift generator plugin for the protocol buffer compiler.
|
// Generated by the gRPC Swift generator plugin for the protocol buffer compiler.
|
||||||
// Source: error-service.proto
|
// Source: error-service.proto
|
||||||
|
@ -29,6 +30,7 @@ internal import SwiftProtobuf
|
||||||
// MARK: - ErrorService
|
// MARK: - ErrorService
|
||||||
|
|
||||||
/// Namespace containing generated types for the "ErrorService" service.
|
/// Namespace containing generated types for the "ErrorService" service.
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
internal enum ErrorService {
|
internal enum ErrorService {
|
||||||
/// Service descriptor for the "ErrorService" service.
|
/// Service descriptor for the "ErrorService" service.
|
||||||
internal static let descriptor = GRPCCore.ServiceDescriptor(fullyQualifiedService: "ErrorService")
|
internal static let descriptor = GRPCCore.ServiceDescriptor(fullyQualifiedService: "ErrorService")
|
||||||
|
@ -53,6 +55,7 @@ internal enum ErrorService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
extension GRPCCore.ServiceDescriptor {
|
extension GRPCCore.ServiceDescriptor {
|
||||||
/// Service descriptor for the "ErrorService" service.
|
/// Service descriptor for the "ErrorService" service.
|
||||||
internal static let ErrorService = GRPCCore.ServiceDescriptor(fullyQualifiedService: "ErrorService")
|
internal static let ErrorService = GRPCCore.ServiceDescriptor(fullyQualifiedService: "ErrorService")
|
||||||
|
@ -60,6 +63,7 @@ extension GRPCCore.ServiceDescriptor {
|
||||||
|
|
||||||
// MARK: ErrorService (server)
|
// MARK: ErrorService (server)
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
extension ErrorService {
|
extension ErrorService {
|
||||||
/// Streaming variant of the service protocol for the "ErrorService" service.
|
/// Streaming variant of the service protocol for the "ErrorService" service.
|
||||||
///
|
///
|
||||||
|
@ -133,6 +137,7 @@ extension ErrorService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default implementation of 'registerMethods(with:)'.
|
// Default implementation of 'registerMethods(with:)'.
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
extension ErrorService.StreamingServiceProtocol {
|
extension ErrorService.StreamingServiceProtocol {
|
||||||
internal func registerMethods<Transport>(with router: inout GRPCCore.RPCRouter<Transport>) where Transport: GRPCCore.ServerTransport {
|
internal func registerMethods<Transport>(with router: inout GRPCCore.RPCRouter<Transport>) where Transport: GRPCCore.ServerTransport {
|
||||||
router.registerHandler(
|
router.registerHandler(
|
||||||
|
@ -150,6 +155,7 @@ extension ErrorService.StreamingServiceProtocol {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default implementation of streaming methods from 'StreamingServiceProtocol'.
|
// Default implementation of streaming methods from 'StreamingServiceProtocol'.
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
extension ErrorService.ServiceProtocol {
|
extension ErrorService.ServiceProtocol {
|
||||||
internal func throwError(
|
internal func throwError(
|
||||||
request: GRPCCore.StreamingServerRequest<ThrowInput>,
|
request: GRPCCore.StreamingServerRequest<ThrowInput>,
|
||||||
|
@ -164,6 +170,7 @@ extension ErrorService.ServiceProtocol {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default implementation of methods from 'ServiceProtocol'.
|
// Default implementation of methods from 'ServiceProtocol'.
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
extension ErrorService.SimpleServiceProtocol {
|
extension ErrorService.SimpleServiceProtocol {
|
||||||
internal func throwError(
|
internal func throwError(
|
||||||
request: GRPCCore.ServerRequest<ThrowInput>,
|
request: GRPCCore.ServerRequest<ThrowInput>,
|
||||||
|
@ -181,6 +188,7 @@ extension ErrorService.SimpleServiceProtocol {
|
||||||
|
|
||||||
// MARK: ErrorService (client)
|
// MARK: ErrorService (client)
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
extension ErrorService {
|
extension ErrorService {
|
||||||
/// Generated client protocol for the "ErrorService" service.
|
/// Generated client protocol for the "ErrorService" service.
|
||||||
///
|
///
|
||||||
|
@ -256,6 +264,7 @@ extension ErrorService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helpers providing default arguments to 'ClientProtocol' methods.
|
// Helpers providing default arguments to 'ClientProtocol' methods.
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
extension ErrorService.ClientProtocol {
|
extension ErrorService.ClientProtocol {
|
||||||
/// Call the "ThrowError" method.
|
/// Call the "ThrowError" method.
|
||||||
///
|
///
|
||||||
|
@ -284,6 +293,7 @@ extension ErrorService.ClientProtocol {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helpers providing sugared APIs for 'ClientProtocol' methods.
|
// Helpers providing sugared APIs for 'ClientProtocol' methods.
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
extension ErrorService.ClientProtocol {
|
extension ErrorService.ClientProtocol {
|
||||||
/// Call the "ThrowError" method.
|
/// Call the "ThrowError" method.
|
||||||
///
|
///
|
||||||
|
|
|
@ -19,6 +19,7 @@ import GRPCProtobuf
|
||||||
import SwiftProtobuf
|
import SwiftProtobuf
|
||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
final class ProtobufCodingTests: XCTestCase {
|
final class ProtobufCodingTests: XCTestCase {
|
||||||
func testSerializeDeserializeRoundtrip() throws {
|
func testSerializeDeserializeRoundtrip() throws {
|
||||||
let message = Google_Protobuf_Timestamp.with {
|
let message = Google_Protobuf_Timestamp.with {
|
||||||
|
@ -73,6 +74,7 @@ final class ProtobufCodingTests: XCTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(gRPCSwiftProtobuf 2.0, *)
|
||||||
struct TestMessage: SwiftProtobuf.Message {
|
struct TestMessage: SwiftProtobuf.Message {
|
||||||
var text: String = ""
|
var text: String = ""
|
||||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
#!/bin/bash
|
||||||
|
## Copyright 2024, gRPC Authors All rights reserved.
|
||||||
|
##
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
log() { printf -- "** %s\n" "$*" >&2; }
|
||||||
|
error() { printf -- "** ERROR: %s\n" "$*" >&2; }
|
||||||
|
fatal() { error "$@"; exit 1; }
|
||||||
|
|
||||||
|
tests_directory="${PLUGIN_TESTS_DIRECTORY:=""}"
|
||||||
|
if [[ -z "$tests_directory" ]]; then
|
||||||
|
fatal "Tests parent directory must be specified."
|
||||||
|
fi
|
||||||
|
|
||||||
|
for dir in "$tests_directory"/test_*/ ; do
|
||||||
|
if [[ -f "$dir/Package.swift" ]]; then
|
||||||
|
plugin_test=$(basename "$dir")
|
||||||
|
log "Building '$plugin_test' plugin test"
|
||||||
|
|
||||||
|
if ! build_output=$(swift build --package-path "$dir" 2>&1); then
|
||||||
|
# Only print the build output on failure.
|
||||||
|
echo "$build_output"
|
||||||
|
fatal "Build failed for '$plugin_test'"
|
||||||
|
else
|
||||||
|
log "Build succeeded for '$plugin_test'"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
|
@ -0,0 +1,33 @@
|
||||||
|
#!/bin/bash
|
||||||
|
## Copyright 2024, gRPC Authors All rights reserved.
|
||||||
|
##
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
log() { printf -- "** %s\n" "$*" >&2; }
|
||||||
|
error() { printf -- "** ERROR: %s\n" "$*" >&2; }
|
||||||
|
fatal() { error "$@"; exit 1; }
|
||||||
|
|
||||||
|
if [[ -n ${GITHUB_ACTIONS:=""} ]]; then
|
||||||
|
# we will have been piped to bash and won't know the location of the script
|
||||||
|
echo "Running in GitHub Actions"
|
||||||
|
source_directory="$(pwd)"
|
||||||
|
else
|
||||||
|
here="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
|
source_directory="$(readlink -f "${here}/..")"
|
||||||
|
fi
|
||||||
|
tests_directory="${PLUGIN_TESTS_OUTPUT_DIRECTORY:=$(mktemp -d)}"
|
||||||
|
|
||||||
|
PLUGIN_TESTS_OUTPUT_DIRECTORY="$tests_directory" "${source_directory}/dev/setup-plugin-tests.sh"
|
||||||
|
PLUGIN_TESTS_DIRECTORY="$tests_directory" "${source_directory}/dev/execute-plugin-tests.sh"
|
|
@ -21,12 +21,12 @@ protoc=$(which protoc)
|
||||||
|
|
||||||
# Checkout and build the plugins.
|
# Checkout and build the plugins.
|
||||||
swift build --package-path "$root" --product protoc-gen-swift
|
swift build --package-path "$root" --product protoc-gen-swift
|
||||||
swift build --package-path "$root" --product protoc-gen-grpc-swift
|
swift build --package-path "$root" --product protoc-gen-grpc-swift-2
|
||||||
|
|
||||||
# Grab the plugin paths.
|
# Grab the plugin paths.
|
||||||
bin_path=$(swift build --package-path "$root" --show-bin-path)
|
bin_path=$(swift build --package-path "$root" --show-bin-path)
|
||||||
protoc_gen_swift="$bin_path/protoc-gen-swift"
|
protoc_gen_swift="$bin_path/protoc-gen-swift"
|
||||||
protoc_gen_grpc_swift="$bin_path/protoc-gen-grpc-swift"
|
protoc_gen_grpc_swift="$bin_path/protoc-gen-grpc-swift-2"
|
||||||
|
|
||||||
# Generates gRPC by invoking protoc with the gRPC Swift plugin.
|
# Generates gRPC by invoking protoc with the gRPC Swift plugin.
|
||||||
# Parameters:
|
# Parameters:
|
||||||
|
@ -36,10 +36,10 @@ protoc_gen_grpc_swift="$bin_path/protoc-gen-grpc-swift"
|
||||||
# - $4 onwards: options to forward to the plugin
|
# - $4 onwards: options to forward to the plugin
|
||||||
function generate_grpc {
|
function generate_grpc {
|
||||||
local proto=$1
|
local proto=$1
|
||||||
local args=("--plugin=$protoc_gen_grpc_swift" "--proto_path=${2}" "--grpc-swift_out=${3}")
|
local args=("--plugin=$protoc_gen_grpc_swift" "--proto_path=${2}" "--grpc-swift-2_out=${3}")
|
||||||
|
|
||||||
for option in "${@:4}"; do
|
for option in "${@:4}"; do
|
||||||
args+=("--grpc-swift_opt=$option")
|
args+=("--grpc-swift-2_opt=$option")
|
||||||
done
|
done
|
||||||
|
|
||||||
invoke_protoc "${args[@]}" "$proto"
|
invoke_protoc "${args[@]}" "$proto"
|
||||||
|
@ -94,7 +94,7 @@ function generate_error_service {
|
||||||
output="$root/Tests/GRPCProtobufTests/Errors/Generated"
|
output="$root/Tests/GRPCProtobufTests/Errors/Generated"
|
||||||
|
|
||||||
generate_message "$proto" "$(dirname "$proto")" "$output" "Visibility=Internal" "UseAccessLevelOnImports=true"
|
generate_message "$proto" "$(dirname "$proto")" "$output" "Visibility=Internal" "UseAccessLevelOnImports=true"
|
||||||
generate_grpc "$proto" "$(dirname "$proto")" "$output" "Visibility=Internal" "UseAccessLevelOnImports=true"
|
generate_grpc "$proto" "$(dirname "$proto")" "$output" "Visibility=Internal" "UseAccessLevelOnImports=true" "Availability=gRPCSwiftProtobuf 2.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
#- DESCRIPTOR SETS ------------------------------------------------------------
|
#- DESCRIPTOR SETS ------------------------------------------------------------
|
||||||
|
@ -105,7 +105,9 @@ function generate_test_service_descriptor_set {
|
||||||
proto_path="$(dirname "$proto")"
|
proto_path="$(dirname "$proto")"
|
||||||
output="$root/Tests/GRPCProtobufCodeGenTests/Generated/test-service.pb"
|
output="$root/Tests/GRPCProtobufCodeGenTests/Generated/test-service.pb"
|
||||||
|
|
||||||
invoke_protoc --descriptor_set_out="$output" "$proto" -I "$proto_path" --include_source_info
|
invoke_protoc --descriptor_set_out="$output" "$proto" -I "$proto_path" \
|
||||||
|
--include_imports \
|
||||||
|
--include_source_info
|
||||||
}
|
}
|
||||||
|
|
||||||
function generate_foo_service_descriptor_set {
|
function generate_foo_service_descriptor_set {
|
||||||
|
|
|
@ -3,6 +3,9 @@ syntax = "proto3";
|
||||||
|
|
||||||
package test;
|
package test;
|
||||||
|
|
||||||
|
// Using a WKT forces an "SwiftProtobuf" to be imported in generated code.
|
||||||
|
import "google/protobuf/any.proto";
|
||||||
|
|
||||||
// Service docs.
|
// Service docs.
|
||||||
service TestService {
|
service TestService {
|
||||||
// Unary docs.
|
// Unary docs.
|
||||||
|
@ -15,5 +18,8 @@ service TestService {
|
||||||
rpc BidirectionalStreaming (stream TestInput) returns (stream TestOutput) {}
|
rpc BidirectionalStreaming (stream TestInput) returns (stream TestOutput) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
message TestInput {}
|
message TestInput {
|
||||||
|
google.protobuf.Any any = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message TestOutput {}
|
message TestOutput {}
|
||||||
|
|
|
@ -0,0 +1,208 @@
|
||||||
|
#!/bin/bash
|
||||||
|
## Copyright 2024, gRPC Authors All rights reserved.
|
||||||
|
##
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
log() { printf -- "** %s\n" "$*" >&2; }
|
||||||
|
error() { printf -- "** ERROR: %s\n" "$*" >&2; }
|
||||||
|
fatal() { error "$@"; exit 1; }
|
||||||
|
|
||||||
|
output_directory="${PLUGIN_TESTS_OUTPUT_DIRECTORY:=$(mktemp -d)}"
|
||||||
|
|
||||||
|
here="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
|
grpc_swift_protobuf_directory="$(readlink -f "${here}/..")"
|
||||||
|
resources_directory="$(readlink -f "${grpc_swift_protobuf_directory}/IntegrationTests/PluginTests/Resources")"
|
||||||
|
config="${resources_directory}/Config"
|
||||||
|
sources="${resources_directory}/Sources"
|
||||||
|
protos="${resources_directory}/Protos"
|
||||||
|
scratch_directory="$(mktemp -d)"
|
||||||
|
package_manifest="${scratch_directory}/Package.swift"
|
||||||
|
|
||||||
|
echo "Output directory: $output_directory"
|
||||||
|
echo "grpc-swift-protobuf directory: $grpc_swift_protobuf_directory"
|
||||||
|
|
||||||
|
# modify Package.swift
|
||||||
|
cp "${resources_directory}/Sources/Package.swift" "${scratch_directory}/"
|
||||||
|
cat >> "${package_manifest}" <<- EOM
|
||||||
|
package.dependencies.append(
|
||||||
|
.package(path: "$grpc_swift_protobuf_directory")
|
||||||
|
)
|
||||||
|
EOM
|
||||||
|
|
||||||
|
function test_dir_name {
|
||||||
|
# $FUNCNAME is a stack of function names. The 0th element is the name of this
|
||||||
|
# function, so the 1st element is the calling function.
|
||||||
|
echo "${output_directory}/${FUNCNAME[1]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_01_top_level_config_file {
|
||||||
|
# .
|
||||||
|
# ├── Package.swift
|
||||||
|
# └── Sources
|
||||||
|
# ├── HelloWorldAdopter.swift
|
||||||
|
# ├── Protos
|
||||||
|
# │ └── HelloWorld.proto
|
||||||
|
# └── grpc-swift-proto-generator-config.json
|
||||||
|
|
||||||
|
local -r test_dir=$(test_dir_name)
|
||||||
|
mkdir -p "${test_dir}/Sources/Protos"
|
||||||
|
cp "${package_manifest}" "${test_dir}/"
|
||||||
|
cp "${sources}/HelloWorldAdopter.swift" "${test_dir}/Sources/"
|
||||||
|
cp "${protos}/HelloWorld/HelloWorld.proto" "${test_dir}/Sources/Protos"
|
||||||
|
cp "${config}/internal-grpc-swift-proto-generator-config.json" "${test_dir}/Sources/grpc-swift-proto-generator-config.json"
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_02_peer_config_file {
|
||||||
|
# .
|
||||||
|
# ├── Package.swift
|
||||||
|
# └── Sources
|
||||||
|
# ├── HelloWorldAdopter.swift
|
||||||
|
# └── Protos
|
||||||
|
# ├── HelloWorld.proto
|
||||||
|
# └── grpc-swift-proto-generator-config.json
|
||||||
|
|
||||||
|
local -r test_dir=$(test_dir_name)
|
||||||
|
mkdir -p "${test_dir}/Sources/Protos"
|
||||||
|
cp "${package_manifest}" "${test_dir}/"
|
||||||
|
cp "${sources}/HelloWorldAdopter.swift" "${test_dir}/Sources/"
|
||||||
|
cp "${protos}/HelloWorld/HelloWorld.proto" "${test_dir}/Sources/Protos/"
|
||||||
|
cp "${config}/internal-grpc-swift-proto-generator-config.json" "${test_dir}/Sources/Protos/grpc-swift-proto-generator-config.json"
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_03_separate_service_message_protos {
|
||||||
|
# .
|
||||||
|
# ├── Package.swift
|
||||||
|
# └── Sources
|
||||||
|
# ├── HelloWorldAdopter.swift
|
||||||
|
# └── Protos
|
||||||
|
# ├── Messages.proto
|
||||||
|
# ├── Service.proto
|
||||||
|
# └── grpc-swift-proto-generator-config.json
|
||||||
|
|
||||||
|
local -r test_dir=$(test_dir_name)
|
||||||
|
mkdir -p "${test_dir}/Sources/Protos"
|
||||||
|
cp "${package_manifest}" "${test_dir}/"
|
||||||
|
cp "${sources}/HelloWorldAdopter.swift" "${test_dir}/Sources/"
|
||||||
|
cp "${config}/internal-grpc-swift-proto-generator-config.json" "${test_dir}/Sources/Protos/grpc-swift-proto-generator-config.json"
|
||||||
|
cp "${protos}/HelloWorld/Service.proto" "${test_dir}/Sources/Protos/"
|
||||||
|
cp "${protos}/HelloWorld/Messages.proto" "${test_dir}/Sources/Protos/"
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_04_cross_directory_imports {
|
||||||
|
# .
|
||||||
|
# ├── Package.swift
|
||||||
|
# └── Sources
|
||||||
|
# ├── HelloWorldAdopter.swift
|
||||||
|
# └── Protos
|
||||||
|
# ├── directory_1
|
||||||
|
# │ ├── Messages.proto
|
||||||
|
# │ └── grpc-swift-proto-generator-config.json
|
||||||
|
# └── directory_2
|
||||||
|
# ├── Service.proto
|
||||||
|
# └── grpc-swift-proto-generator-config.json
|
||||||
|
|
||||||
|
local -r test_dir=$(test_dir_name)
|
||||||
|
mkdir -p "${test_dir}/Sources/Protos/directory_1"
|
||||||
|
mkdir -p "${test_dir}/Sources/Protos/directory_2"
|
||||||
|
|
||||||
|
cp "${package_manifest}" "${test_dir}/"
|
||||||
|
cp "${sources}/HelloWorldAdopter.swift" "${test_dir}/Sources/"
|
||||||
|
cp "${config}/internal-grpc-swift-proto-generator-config.json" "${test_dir}/Sources/Protos/directory_1/grpc-swift-proto-generator-config.json"
|
||||||
|
cp "${config}/import-directory-1-grpc-swift-proto-generator-config.json" "${test_dir}/Sources/Protos/directory_2/grpc-swift-proto-generator-config.json"
|
||||||
|
cp "${protos}/HelloWorld/Service.proto" "${test_dir}/Sources/Protos/directory_2/"
|
||||||
|
cp "${protos}/HelloWorld/Messages.proto" "${test_dir}/Sources/Protos/directory_1/"
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_05_two_definitions {
|
||||||
|
# .
|
||||||
|
# ├── Package.swift
|
||||||
|
# └── Sources
|
||||||
|
# ├── FooHelloWorldAdopter.swift
|
||||||
|
# └── Protos
|
||||||
|
# ├── Foo
|
||||||
|
# │ ├── foo-messages.proto
|
||||||
|
# │ └── foo-service.proto
|
||||||
|
# ├── HelloWorld
|
||||||
|
# │ └── HelloWorld.proto
|
||||||
|
# └── grpc-swift-proto-generator-config.json
|
||||||
|
|
||||||
|
local -r test_dir=$(test_dir_name)
|
||||||
|
mkdir -p "${test_dir}/Sources/Protos/HelloWorld"
|
||||||
|
mkdir -p "${test_dir}/Sources/Protos/Foo"
|
||||||
|
|
||||||
|
cp "${package_manifest}" "${test_dir}/"
|
||||||
|
cp "${sources}/FooHelloWorldAdopter.swift" "${test_dir}/Sources/"
|
||||||
|
cp "${protos}/HelloWorld/HelloWorld.proto" "${test_dir}/Sources/Protos/HelloWorld/"
|
||||||
|
cp "${config}/internal-grpc-swift-proto-generator-config.json" "${test_dir}/Sources/Protos/grpc-swift-proto-generator-config.json"
|
||||||
|
cp "${protos}/Foo/foo-messages.proto" "${test_dir}/Sources/Protos/Foo/"
|
||||||
|
cp "${protos}/Foo/foo-service.proto" "${test_dir}/Sources/Protos/Foo/"
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_06_nested_definitions {
|
||||||
|
# .
|
||||||
|
# ├── Package.swift
|
||||||
|
# └── Sources
|
||||||
|
# ├── FooHelloWorldAdopter.swift
|
||||||
|
# └── Protos
|
||||||
|
# └── HelloWorld
|
||||||
|
# ├── FooDefinitions
|
||||||
|
# │ ├── Foo
|
||||||
|
# │ │ ├── foo-messages.proto
|
||||||
|
# │ │ └── foo-service.proto
|
||||||
|
# │ └── grpc-swift-proto-generator-config.json
|
||||||
|
# ├── HelloWorld.proto
|
||||||
|
# └── grpc-swift-proto-generator-config.json
|
||||||
|
|
||||||
|
local -r test_dir=$(test_dir_name)
|
||||||
|
mkdir -p "${test_dir}/Sources/Protos/HelloWorld/FooDefinitions/Foo"
|
||||||
|
cp "${package_manifest}" "${test_dir}/"
|
||||||
|
cp "${sources}/FooHelloWorldAdopter.swift" "${test_dir}/Sources/"
|
||||||
|
cp "${protos}/HelloWorld/HelloWorld.proto" "${test_dir}/Sources/Protos/HelloWorld/"
|
||||||
|
cp "${config}/internal-grpc-swift-proto-generator-config.json" "${test_dir}/Sources/Protos/HelloWorld/grpc-swift-proto-generator-config.json"
|
||||||
|
cp "${config}/public-grpc-swift-proto-generator-config.json" "${test_dir}/Sources/Protos/HelloWorld/FooDefinitions/grpc-swift-proto-generator-config.json"
|
||||||
|
cp "${protos}/Foo/foo-messages.proto" "${test_dir}/Sources/Protos/HelloWorld/FooDefinitions/Foo/"
|
||||||
|
cp "${protos}/Foo/foo-service.proto" "${test_dir}/Sources/Protos/HelloWorld/FooDefinitions/Foo/"
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_07_duplicated_proto_file_name {
|
||||||
|
# .
|
||||||
|
# ├── Package.swift
|
||||||
|
# └── Sources
|
||||||
|
# ├── NoOp.swift
|
||||||
|
# └── Protos
|
||||||
|
# ├── grpc-swift-proto-generator-config.json
|
||||||
|
# ├── noop
|
||||||
|
# │ └── noop.proto
|
||||||
|
# └── noop2
|
||||||
|
# └── noop.proto
|
||||||
|
|
||||||
|
local -r test_dir=$(test_dir_name)
|
||||||
|
mkdir -p "${test_dir}/Sources/Protos"
|
||||||
|
|
||||||
|
cp "${package_manifest}" "${test_dir}/"
|
||||||
|
mkdir -p "${test_dir}/Sources/Protos"
|
||||||
|
cp -rp "${protos}/noop" "${test_dir}/Sources/Protos"
|
||||||
|
cp -rp "${protos}/noop2" "${test_dir}/Sources/Protos"
|
||||||
|
cp "${sources}/NoOp.swift" "${test_dir}/Sources"
|
||||||
|
cp "${config}/internal-grpc-swift-proto-generator-config.json" "${test_dir}/Sources/Protos/grpc-swift-proto-generator-config.json"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_01_top_level_config_file
|
||||||
|
test_02_peer_config_file
|
||||||
|
test_03_separate_service_message_protos
|
||||||
|
test_04_cross_directory_imports
|
||||||
|
test_05_two_definitions
|
||||||
|
test_06_nested_definitions
|
||||||
|
test_07_duplicated_proto_file_name
|
Loading…
Reference in New Issue