You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fory.apache.org by "yash-agarwa-l (via GitHub)" <gi...@apache.org> on 2026/06/21 13:28:50 UTC
[PR] Grpc swift [fory]
yash-agarwa-l opened a new pull request, #3776:
URL: https://github.com/apache/fory/pull/3776
## Why?
Swift users can generate Fory model types today, but schemas that define services
do not produce Swift gRPC companions. This leaves Swift out of the existing `--grpc`
workflow used by the other supported service-generation targets.
## What does this PR do?
- Adds Swift gRPC companion generation for Fory compiler services, including service
metadata descriptors, an `EventLoopFuture` provider, an async/await provider, an
async client, and Fory-backed request/response stream adapters, targeting grpc-swift 1.x.
- Serializes request and response bodies with Fory instead of protobuf through an
internal `GRPCPayload` wrapper. Because the Swift `Fory` runtime is single-threaded,
the wrapper builds one `Fory` per thread from the schema module's own configuration
and registrations, so concurrent RPCs are race-free (verified under ThreadSanitizer).
- Emits an async-only client; the `EventLoopFuture` client and interceptor hooks are
omitted because their generated types would expose the internal wrapper.
- Adds Swift preflight validation for generated output-path and top-level symbol
collisions, and reserves inherited provider/client member names (`handle`,
`serviceName`, `channel`, `defaultCallOptions`) so a clashing rpc fails codegen
with a clear message.
- Updates compiler and service-codegen tests to cover the four streaming shapes,
identifier escaping, imported and nested service types, the default package, the
protobuf and FlatBuffers frontends, collision handling, a SwiftPM build-and-run
fixture, and a marshaller concurrency test under ThreadSanitizer (gated behind
`FORY_SWIFT_TSAN`).
- Wires Swift output into the cross-language gRPC generation helper.
- Documents Swift gRPC support, dependencies, generated API shape, streaming, the
shared-top-level-package limitation, troubleshooting, and compiler guide updates.
Draft: Java<->Swift cross-language interop tests (all four modes, all three IDL
frontends) are in progress and will be added before this PR is marked ready for review.
## Related issues
#3266
#3370
## AI Contribution Checklist
- [ ] Substantial AI assistance was used in this PR: `yes`
- [ ] If `yes`, I included a completed AI Contribution Checklist in this PR description and the required `AI Usage Disclosure`.
- [ ] If `yes`, my PR description includes the required `ai_review` summary and screenshot evidence of the final clean AI review results from both fresh reviewers on the current PR diff or current HEAD after the latest code changes.
## Does this PR introduce any user-facing change?
- [x] Does this PR introduce any public API change?
- Adds generated Swift gRPC companion APIs when `foryc --swift_out=... --grpc` is used.
- [ ] Does this PR introduce any binary protocol compatibility change?
- The generated services use Fory-encoded gRPC message bodies, but this PR does not
change the Fory binary protocol.
## Benchmark
Not applicable.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For additional commands, e-mail: commits-help@fory.apache.org
Re: [PR] feat(compiler): Add grpc support for Swift [fory]
Posted by "chaokunyang (via GitHub)" <gi...@apache.org>.
chaokunyang commented on code in PR #3776:
URL: https://github.com/apache/fory/pull/3776#discussion_r3838863569
##########
compiler/fory_compiler/generators/swift.py:
##########
@@ -201,6 +202,21 @@ def output_file_path(self) -> str:
return f"{package_path}/{file_name}"
return file_name
+ def swift_model_top_level_symbols(self) -> set[str]:
+ # In enum style with a package, types live nested under a shared namespace
+ # enum, so there are no collidable top-level names. Flatten and default
+ # packages put each type and the module helper at file scope.
+ components = self._package_components_for_schema(self.schema)
+ if self.get_namespace_style() == "enum" and components:
+ return set()
+ symbols: set[str] = set()
Review Comment:
The top-level and helper cases are fixed, but nested IDL declarations still bypass the new walker. `swift_declared_symbols()` only iterates `schema.enums + schema.unions + schema.messages`; it never recurses through each message’s `nested_messages`, `nested_enums`, and `nested_unions`. For example, `message Parent { message my_type {} message MyType {} }` passes raw-name validation but emits two `Parent.MyType` declarations. The new test named `nested` covers package namespace nesting rather than an IDL nested type. Please collect declaration owners recursively by parent Swift scope and add a real nested-type case.
##########
docs/grpc/swift.md:
##########
@@ -0,0 +1,235 @@
+---
+title: Swift gRPC
+sidebar_position: 14
+id: swift
+license: |
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+---
+
+Fory can generate Swift gRPC service companions for schemas that define
+services. The companion provides the usual gRPC service providers, clients,
+method descriptors, and service metadata, while request and response objects are
+serialized with Fory instead of protobuf.
+
+Use this mode when both RPC peers are generated from the same Fory IDL, protobuf
+IDL, or FlatBuffers IDL and both sides expect Fory-encoded message bodies. Use
+normal protobuf gRPC generation for APIs that must be consumed by generic
+protobuf clients, reflection tools, or components that expect protobuf bytes.
+
+The companion targets [grpc-swift](https://github.com/grpc/grpc-swift) 1.x. That
+line keeps the same platform floor as the Fory Swift package (macOS 13, iOS 16);
+grpc-swift 2.x requires a newer floor.
+
+## Add Dependencies
+
+The `Fory` package does not depend on grpc-swift. Add grpc-swift in the package
+that compiles or runs the generated companions:
+
+```swift
+// Package.swift
+dependencies: [
+ .package(url: "https://github.com/apache/fory.git", from: "1.2.0"),
+ .package(url: "https://github.com/grpc/grpc-swift.git", from: "1.23.0"),
+],
+targets: [
+ .target(
+ name: "App",
+ dependencies: [
+ .product(name: "Fory", package: "fory"),
+ .product(name: "GRPC", package: "grpc-swift"),
+ ]
+ )
+]
+```
+
+## Define a Service
+
+Service definitions can come from Fory IDL, protobuf IDL, or FlatBuffers
+`rpc_service` definitions. A Fory IDL service looks like this:
+
+```protobuf
+package demo.greeter;
+
+message HelloRequest {
+ string name = 1;
+}
+
+message HelloReply {
+ string reply = 1;
+}
+
+service Greeter {
+ rpc SayHello (HelloRequest) returns (HelloReply);
+}
+```
+
+Generate Swift model and gRPC companion code with `--grpc`:
+
+```bash
+foryc service.fdl --swift_out=./Sources/App --grpc
+```
+
+For this schema the Swift generator emits:
+
+| File | Purpose |
+| -------------------------------- | -------------------------------------------- |
+| `demo/greeter/greeter.swift` | Fory model types and the `ForyModule` helper |
+| `demo/greeter/GreeterGrpc.swift` | gRPC providers, client, and service metadata |
+
+Generated gRPC symbols are prefixed with the package, so the schema above emits
+`Demo_Greeter_GreeterAsyncProvider`, `Demo_Greeter_GreeterAsyncClient`, and
+`Demo_Greeter_GreeterProvider`. A schema with no package drops the prefix
+(`GreeterAsyncProvider`).
+
+## Implement a Server
+
+Conform a type to the generated `async`/`await` provider and host it with a
+normal grpc-swift `Server`:
+
+```swift
+import Fory
+import GRPC
+import NIOPosix
+
+final class GreeterService: Demo_Greeter_GreeterAsyncProvider {
+ func sayHello(
+ request: Demo.Greeter.HelloRequest,
+ context: GRPCAsyncServerCallContext
+ ) async throws -> Demo.Greeter.HelloReply {
+ Demo.Greeter.HelloReply(reply: "Hello, " + request.name)
+ }
+}
+
+let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
+let server = try await Server.insecure(group: group)
+ .withServiceProviders([GreeterService()])
+ .bind(host: "127.0.0.1", port: 1234)
+ .get()
+```
+
+Request and response types are registered by the generated schema module that
+the companion uses, so server code does not register serializers by hand. An
+`EventLoopFuture`-based `Demo_Greeter_GreeterProvider` is also emitted for
+servers that do not use `async`/`await`.
+
+## Create a Client
+
+Use the generated async client over a grpc-swift channel:
+
+```swift
+import Fory
+import GRPC
+import NIOPosix
+
+let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
+let channel = try GRPCChannelPool.with(
+ target: .host("127.0.0.1", port: 1234),
+ transportSecurity: .plaintext,
+ eventLoopGroup: group)
+
+let client = Demo_Greeter_GreeterAsyncClient(channel: channel)
+let reply = try await client.sayHello(Demo.Greeter.HelloRequest(name: "Fory"))
+print(reply.reply)
+```
+
+## Streaming RPCs
+
+Fory service definitions can use the four gRPC streaming shapes:
+
+```protobuf
+service Greeter {
+ rpc SayHello (HelloRequest) returns (HelloReply);
+ rpc LotsOfReplies (HelloRequest) returns (stream HelloReply);
+ rpc LotsOfGreetings (stream HelloRequest) returns (HelloReply);
+ rpc BidiHello (stream HelloRequest) returns (stream HelloReply);
+}
+```
+
+Streaming methods present clean request and response types. The provider receives
+a response writer (`send(_:)`) for server output and an `AsyncSequence` for client
+input; the client returns an `AsyncSequence` of responses for server-streamed
+replies:
+
+```swift
+// Server side
+func lotsOfReplies(
+ request: Demo.Greeter.HelloRequest,
+ responseStream: Demo_Greeter_GreeterAsyncResponseStream<Demo.Greeter.HelloReply>,
+ context: GRPCAsyncServerCallContext
+) async throws {
+ try await responseStream.send(Demo.Greeter.HelloReply(reply: "Hi " + request.name))
+}
+
+// Client side
+for try await reply in client.lotsOfReplies(Demo.Greeter.HelloRequest(name: "Fory")) {
+ print(reply.reply)
+}
+```
+
+## gRPC Runtime Behavior
+
+Generated companions carry Fory-encoded bytes inside a private `GRPCPayload`
+wrapper. The Swift `Fory` instance is single-threaded, so the wrapper uses one
+`Fory` per thread, built from the schema module's configuration and registrations,
+which makes concurrent RPCs safe without sharing a single instance. Imported
+request and response types resolve to their own namespace and are registered
+transitively through the owning module, so a service that crosses an import
+boundary works without extra registration.
+
+## Swift Language Mode
+
+Compile generated companions in Swift 5 language mode (use
+`swift-tools-version:5.9`, or set `swiftLanguageMode(.v5)` on the target in a
+6.x manifest). The Fory wire wrapper is `Sendable`, but the async client's
+client-streaming and bidirectional methods pass your request and response model
+types through grpc-swift's `Sendable` streaming APIs, and the generated Fory
+Swift models are not yet `Sendable`. Server providers, the unary client, and the
+server-streaming client build under Swift 6 strict concurrency; full Swift 6
+support for the streaming client follows once generated models are `Sendable`.
+
+## Known Limitations
+
+The generated client is async/await only. grpc-swift's `EventLoopFuture` client
+returns call objects parameterized by the on-the-wire message type, which would
+expose the internal Fory wrapper, so it is not emitted. Both providers (async and
+`EventLoopFuture`) are generated.
+
+Interceptors are not generated. grpc-swift interceptors are typed on the
+on-the-wire message, which is the internal Fory wrapper; emitting interceptor
+hooks would expose that wrapper. Use a custom channel or server configuration for
+cross-cutting concerns instead.
+
+Swift models put each package under a nested `enum` namespace, so two schemas that
Review Comment:
The preflight wording is closer, but the separate-invocation workaround does not work for the service-import example above. `validate_swift_files()` recursively collects imports, so compiling `demo.greeter` still includes `demo.shared` in the graph and rejects the duplicate `Demo` even if the shared schema was generated in another invocation. Separate invocations only help unrelated schemas; an import graph currently needs disjoint top-level packages. Please narrow the workaround accordingly.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For additional commands, e-mail: commits-help@fory.apache.org
Re: [PR] feat(compiler): Add grpc support for Swift [fory]
Posted by "yash-agarwa-l (via GitHub)" <gi...@apache.org>.
yash-agarwa-l commented on code in PR #3776:
URL: https://github.com/apache/fory/pull/3776#discussion_r3838672302
##########
compiler/fory_compiler/generators/swift.py:
##########
@@ -201,6 +202,21 @@ def output_file_path(self) -> str:
return f"{package_path}/{file_name}"
return file_name
+ def swift_model_top_level_symbols(self) -> set[str]:
+ # In enum style with a package, types live nested under a shared namespace
+ # enum, so there are no collidable top-level names. Flatten and default
+ # packages put each type and the module helper at file scope.
+ components = self._package_components_for_schema(self.schema)
+ if self.get_namespace_style() == "enum" and components:
Review Comment:
I went with recording the namespace. Now, `demo.shared` + `demo.greeter` fails early with a Demo collision instead of reaching swiftc.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For additional commands, e-mail: commits-help@fory.apache.org
Re: [PR] feat(compiler): Add grpc support for Swift [fory]
Posted by "chaokunyang (via GitHub)" <gi...@apache.org>.
chaokunyang merged PR #3776:
URL: https://github.com/apache/fory/pull/3776
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For additional commands, e-mail: commits-help@fory.apache.org
Re: [PR] feat(swift): Add grpc support for Swift [fory]
Posted by "yash-agarwa-l (via GitHub)" <gi...@apache.org>.
yash-agarwa-l commented on PR #3776:
URL: https://github.com/apache/fory/pull/3776#issuecomment-4762224259
I already did that, so I'm not sure why it's still showing up. Let me mark it as ready for review and double-check.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For additional commands, e-mail: commits-help@fory.apache.org
Re: [PR] feat(compiler): Add grpc support for Swift [fory]
Posted by "yash-agarwa-l (via GitHub)" <gi...@apache.org>.
yash-agarwa-l commented on PR #3776:
URL: https://github.com/apache/fory/pull/3776#issuecomment-5088911395
Thanks, I have traced the failure, you were right that this is a reader-side issue and that NOT_NULL_VALUE is legal.
Under tracking, `tryPreserveRefId` pushes an id for `REF_VALUE` but not for `NOT_NULL_VALUE`. Swift sends `NOT_NULL_VALUE` for value-type structs, which the spec allows, so when the read path then calls `reference()` it pops an empty stack and we get `Index -1`.
The natural place to fix it is centrally, inside `tryPreserveRefId`, but that doesn't hold up. Not every caller pops. `readStringRef` goes through `tryPreserveRefId` and then into `readString`, which never calls `reference()`, so a push in the shared spot leaves a stray `-1` behind. Some later object pops that instead of its own, and the refs drift with nothing failing to show it. What works is a guarded push: `preserveRefId(-1)` on `NOT_NULL` only when the serializer actually tracks, gated on `needToWriteRef()`.
Checked on current main, including the string path (round-trips with no stray id) and a tracked compatible struct with polymorphic leaves plus a back-reference. It's proven at the byte level, Swift bytes into Java; the live process-to-process harness is still parked.
PTAL if this is fine? Also shall I create a separate PR for the java fix or is it fine here?
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For additional commands, e-mail: commits-help@fory.apache.org
Re: [PR] feat(compiler): Add grpc support for Swift [fory]
Posted by "chaokunyang (via GitHub)" <gi...@apache.org>.
chaokunyang commented on code in PR #3776:
URL: https://github.com/apache/fory/pull/3776#discussion_r3838738752
##########
docs/grpc/swift.md:
##########
@@ -0,0 +1,235 @@
+---
+title: Swift gRPC
+sidebar_position: 14
+id: swift
+license: |
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+---
+
+Fory can generate Swift gRPC service companions for schemas that define
+services. The companion provides the usual gRPC service providers, clients,
+method descriptors, and service metadata, while request and response objects are
+serialized with Fory instead of protobuf.
+
+Use this mode when both RPC peers are generated from the same Fory IDL, protobuf
+IDL, or FlatBuffers IDL and both sides expect Fory-encoded message bodies. Use
+normal protobuf gRPC generation for APIs that must be consumed by generic
+protobuf clients, reflection tools, or components that expect protobuf bytes.
+
+The companion targets [grpc-swift](https://github.com/grpc/grpc-swift) 1.x. That
+line keeps the same platform floor as the Fory Swift package (macOS 13, iOS 16);
+grpc-swift 2.x requires a newer floor.
+
+## Add Dependencies
+
+The `Fory` package does not depend on grpc-swift. Add grpc-swift in the package
+that compiles or runs the generated companions:
+
+```swift
+// Package.swift
+dependencies: [
+ .package(url: "https://github.com/apache/fory.git", from: "1.2.0"),
+ .package(url: "https://github.com/grpc/grpc-swift.git", from: "1.23.0"),
+],
+targets: [
+ .target(
+ name: "App",
+ dependencies: [
+ .product(name: "Fory", package: "fory"),
+ .product(name: "GRPC", package: "grpc-swift"),
+ ]
+ )
+]
+```
+
+## Define a Service
+
+Service definitions can come from Fory IDL, protobuf IDL, or FlatBuffers
+`rpc_service` definitions. A Fory IDL service looks like this:
+
+```protobuf
+package demo.greeter;
+
+message HelloRequest {
+ string name = 1;
+}
+
+message HelloReply {
+ string reply = 1;
+}
+
+service Greeter {
+ rpc SayHello (HelloRequest) returns (HelloReply);
+}
+```
+
+Generate Swift model and gRPC companion code with `--grpc`:
+
+```bash
+foryc service.fdl --swift_out=./Sources/App --grpc
+```
+
+For this schema the Swift generator emits:
+
+| File | Purpose |
+| -------------------------------- | -------------------------------------------- |
+| `demo/greeter/greeter.swift` | Fory model types and the `ForyModule` helper |
+| `demo/greeter/GreeterGrpc.swift` | gRPC providers, client, and service metadata |
+
+Generated gRPC symbols are prefixed with the package, so the schema above emits
+`Demo_Greeter_GreeterAsyncProvider`, `Demo_Greeter_GreeterAsyncClient`, and
+`Demo_Greeter_GreeterProvider`. A schema with no package drops the prefix
+(`GreeterAsyncProvider`).
+
+## Implement a Server
+
+Conform a type to the generated `async`/`await` provider and host it with a
+normal grpc-swift `Server`:
+
+```swift
+import Fory
+import GRPC
+import NIOPosix
+
+final class GreeterService: Demo_Greeter_GreeterAsyncProvider {
+ func sayHello(
+ request: Demo.Greeter.HelloRequest,
+ context: GRPCAsyncServerCallContext
+ ) async throws -> Demo.Greeter.HelloReply {
+ Demo.Greeter.HelloReply(reply: "Hello, " + request.name)
+ }
+}
+
+let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
+let server = try await Server.insecure(group: group)
+ .withServiceProviders([GreeterService()])
+ .bind(host: "127.0.0.1", port: 1234)
+ .get()
+```
+
+Request and response types are registered by the generated schema module that
+the companion uses, so server code does not register serializers by hand. An
+`EventLoopFuture`-based `Demo_Greeter_GreeterProvider` is also emitted for
+servers that do not use `async`/`await`.
+
+## Create a Client
+
+Use the generated async client over a grpc-swift channel:
+
+```swift
+import Fory
+import GRPC
+import NIOPosix
+
+let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
+let channel = try GRPCChannelPool.with(
+ target: .host("127.0.0.1", port: 1234),
+ transportSecurity: .plaintext,
+ eventLoopGroup: group)
+
+let client = Demo_Greeter_GreeterAsyncClient(channel: channel)
+let reply = try await client.sayHello(Demo.Greeter.HelloRequest(name: "Fory"))
+print(reply.reply)
+```
+
+## Streaming RPCs
+
+Fory service definitions can use the four gRPC streaming shapes:
+
+```protobuf
+service Greeter {
+ rpc SayHello (HelloRequest) returns (HelloReply);
+ rpc LotsOfReplies (HelloRequest) returns (stream HelloReply);
+ rpc LotsOfGreetings (stream HelloRequest) returns (HelloReply);
+ rpc BidiHello (stream HelloRequest) returns (stream HelloReply);
+}
+```
+
+Streaming methods present clean request and response types. The provider receives
+a response writer (`send(_:)`) for server output and an `AsyncSequence` for client
+input; the client returns an `AsyncSequence` of responses for server-streamed
+replies:
+
+```swift
+// Server side
+func lotsOfReplies(
+ request: Demo.Greeter.HelloRequest,
+ responseStream: Demo_Greeter_GreeterAsyncResponseStream<Demo.Greeter.HelloReply>,
+ context: GRPCAsyncServerCallContext
+) async throws {
+ try await responseStream.send(Demo.Greeter.HelloReply(reply: "Hi " + request.name))
+}
+
+// Client side
+for try await reply in client.lotsOfReplies(Demo.Greeter.HelloRequest(name: "Fory")) {
+ print(reply.reply)
+}
+```
+
+## gRPC Runtime Behavior
+
+Generated companions carry Fory-encoded bytes inside a private `GRPCPayload`
+wrapper. The Swift `Fory` instance is single-threaded, so the wrapper uses one
+`Fory` per thread, built from the schema module's configuration and registrations,
+which makes concurrent RPCs safe without sharing a single instance. Imported
+request and response types resolve to their own namespace and are registered
+transitively through the owning module, so a service that crosses an import
+boundary works without extra registration.
+
+## Swift Language Mode
+
+Compile generated companions in Swift 5 language mode (use
+`swift-tools-version:5.9`, or set `swiftLanguageMode(.v5)` on the target in a
+6.x manifest). The Fory wire wrapper is `Sendable`, but the async client's
+client-streaming and bidirectional methods pass your request and response model
+types through grpc-swift's `Sendable` streaming APIs, and the generated Fory
+Swift models are not yet `Sendable`. Server providers, the unary client, and the
+server-streaming client build under Swift 6 strict concurrency; full Swift 6
+support for the streaming client follows once generated models are `Sendable`.
+
+## Known Limitations
+
+The generated client is async/await only. grpc-swift's `EventLoopFuture` client
+returns call objects parameterized by the on-the-wire message type, which would
+expose the internal Fory wrapper, so it is not emitted. Both providers (async and
+`EventLoopFuture`) are generated.
+
+Interceptors are not generated. grpc-swift interceptors are typed on the
+on-the-wire message, which is the internal Fory wrapper; emitting interceptor
+hooks would expose that wrapper. Use a custom channel or server configuration for
+cross-cutting concerns instead.
+
+Swift models put each package under a nested `enum` namespace, so two schemas that
Review Comment:
Update the namespace limitation for the new preflight
The compiler now rejects schemas with the same root namespace before writing either file, so this section no longer describes the actual failure point. The separate-module workaround also requires separate generation invocations because one invocation runs the shared collision preflight before module assignment is known. Please document the preflight failure and that requirement.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For additional commands, e-mail: commits-help@fory.apache.org
Re: [PR] feat(compiler): Add grpc support for Swift [fory]
Posted by "yash-agarwa-l (via GitHub)" <gi...@apache.org>.
yash-agarwa-l commented on PR #3776:
URL: https://github.com/apache/fory/pull/3776#issuecomment-5256233860
Thanks @chaokunyang, I have made the revision, One thing to note: this does change Swift's tracked wire output 0xff to 0x00, so an older Swift peer won't match a new one under tracking.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For additional commands, e-mail: commits-help@fory.apache.org
Re: [PR] feat(swift): Add grpc support for Swift [fory]
Posted by "chaokunyang (via GitHub)" <gi...@apache.org>.
chaokunyang commented on PR #3776:
URL: https://github.com/apache/fory/pull/3776#issuecomment-4762198423
@yash-agarwa-l Please git merge apache/main first to address the conflicts
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For additional commands, e-mail: commits-help@fory.apache.org
Re: [PR] feat(compiler): Add grpc support for Swift [fory]
Posted by "chaokunyang (via GitHub)" <gi...@apache.org>.
chaokunyang commented on PR #3776:
URL: https://github.com/apache/fory/pull/3776#issuecomment-5175533074
@yash-agarwa-l You should only change swift code for this error, you should not change java code
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For additional commands, e-mail: commits-help@fory.apache.org
Re: [PR] feat(compiler): Add grpc support for Swift [fory]
Posted by "chaokunyang (via GitHub)" <gi...@apache.org>.
chaokunyang commented on code in PR #3776:
URL: https://github.com/apache/fory/pull/3776#discussion_r3821739982
##########
compiler/fory_compiler/generators/services/swift.py:
##########
@@ -0,0 +1,628 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+
+"""Swift gRPC service companion generator (grpc-swift v1)."""
+
+from fory_compiler.generators.base import GeneratedFile
+from fory_compiler.generators.services.base import StreamingMode, streaming_mode
+from fory_compiler.ir.ast import RpcMethod, Service
+
+# Availability gate matching grpc-swift's async/await APIs.
+_ASYNC_AVAILABLE = "@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)"
+
+# Members the generated provider and client expose through their protocols and
+# base types; an rpc whose Swift name matches one would override or clash with it.
+_SWIFT_GRPC_RESERVED_MEMBERS = {
+ "handle",
+ "serviceName",
+ "channel",
+ "defaultCallOptions",
+}
+
+
+class SwiftServiceMixin:
+ """Generates Swift gRPC service companions backed by Fory serialization."""
+
+ def generate_services(self) -> list[GeneratedFile]:
Review Comment:
Keep the compiler importable on Python 3.8 and 3.9
`fory-compiler` still declares Python 3.8+ support, but this module has no `from __future__ import annotations`; Python 3.8 evaluates `list[GeneratedFile]` during the eager `SwiftGenerator` import and raises `TypeError`. `cli.py` has the same problem, and its `list[Path] | None` annotations also break Python 3.9. Please retain compatible annotation syntax or postpone annotations in every affected module, and add Python 3.8/3.9 CLI-import coverage.
##########
compiler/fory_compiler/generators/services/swift.py:
##########
@@ -0,0 +1,628 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+
+"""Swift gRPC service companion generator (grpc-swift v1)."""
+
+from fory_compiler.generators.base import GeneratedFile
+from fory_compiler.generators.services.base import StreamingMode, streaming_mode
+from fory_compiler.ir.ast import RpcMethod, Service
+
+# Availability gate matching grpc-swift's async/await APIs.
+_ASYNC_AVAILABLE = "@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)"
+
+# Members the generated provider and client expose through their protocols and
+# base types; an rpc whose Swift name matches one would override or clash with it.
+_SWIFT_GRPC_RESERVED_MEMBERS = {
+ "handle",
+ "serviceName",
+ "channel",
+ "defaultCallOptions",
+}
+
+
+class SwiftServiceMixin:
+ """Generates Swift gRPC service companions backed by Fory serialization."""
+
+ def generate_services(self) -> list[GeneratedFile]:
+ services = [s for s in self.schema.services if not self.is_imported_type(s)]
+ if not services:
+ return []
+ self._check_swift_grpc_method_collisions(services)
+ return [self._generate_swift_service(service) for service in services]
+
+ def _grpc_prefix(self) -> str:
+ return "_".join(self._package_components_for_schema(self.schema))
+
+ def _service_symbol(self, service: Service) -> str:
+ name = self.to_pascal_case(service.name)
+ prefix = self._grpc_prefix()
+ return f"{prefix}_{name}" if prefix else name
+
+ def swift_grpc_output_path(self, service: Service) -> str:
+ package = self.schema.package
+ package_path = package.replace(".", "/") if package else ""
+ file_name = f"{self.to_pascal_case(service.name)}Grpc.swift"
+ return f"{package_path}/{file_name}" if package_path else file_name
+
+ def swift_grpc_service_symbols(self, service: Service) -> list[str]:
+ base = self._service_symbol(service)
+ modes = {streaming_mode(m) for m in service.methods}
+ symbols = [
+ f"{base}Message",
+ f"{base}Metadata",
+ f"{base}Provider",
+ f"{base}AsyncProvider",
+ f"{base}AsyncClient",
+ ]
+ if modes & {StreamingMode.SERVER_STREAMING, StreamingMode.BIDIRECTIONAL}:
+ symbols += [
+ f"{base}StreamingResponseContext",
+ f"{base}AsyncResponseStream",
+ f"{base}ResponseStream",
+ ]
+ if StreamingMode.CLIENT_STREAMING in modes:
+ symbols.append(f"{base}UnaryResponseContext")
+ if modes & {StreamingMode.CLIENT_STREAMING, StreamingMode.BIDIRECTIONAL}:
+ symbols.append(f"{base}AsyncRequestStream")
+ return symbols
+
+ def _swift_grpc_method_name(self, method: RpcMethod) -> str:
+ return self.safe_member_name(method.name)
+
+ def _request_type(self, method: RpcMethod) -> str:
+ return self._named_type_reference(method.request_type)
+
+ def _response_type(self, method: RpcMethod) -> str:
+ return self._named_type_reference(method.response_type)
+
+ def _check_swift_grpc_method_collisions(self, services: list[Service]) -> None:
+ for service in services:
+ seen: dict[str, str] = {}
+ for method in service.methods:
+ swift_name = self._swift_grpc_method_name(method).strip("`")
+ if swift_name in _SWIFT_GRPC_RESERVED_MEMBERS:
+ raise ValueError(
+ f"Swift gRPC method {service.name}.{method.name} generates "
+ f"{swift_name}, which collides with a generated provider or "
+ "client member; rename the rpc"
+ )
+ if swift_name in seen:
+ raise ValueError(
+ f"Swift gRPC method name collision in service {service.name}: "
+ f"{seen[swift_name]} and {method.name} both generate {swift_name}"
+ )
+ seen[swift_name] = method.name
+
+ def _generate_swift_service(self, service: Service) -> GeneratedFile:
+ base = self._service_symbol(service)
+ module = self.module_type_path()
+ methods = service.methods
+ modes = {streaming_mode(m) for m in methods}
+
+ lines: list[str] = []
+ lines.append(self.get_license_header("//"))
+ lines.append("")
+ # gRPC symbols are package-prefixed with underscores, matching grpc-swift.
+ lines.append("// swiftlint:disable type_name")
+ lines.append("")
+ lines.append("import Foundation")
+ lines.append("import GRPC")
+ lines.append("import NIOCore")
+ lines.append("import Fory")
+ lines.append("")
+
+ if methods:
+ lines.extend(self._marshaller(base, module))
+ lines.append("")
+ lines.extend(self._metadata(base, service))
+ lines.append("")
+ lines.extend(self._adapters(base, modes))
+ lines.extend(self._provider(base, service))
+ lines.append("")
+ lines.extend(self._async_provider(base, service))
+ lines.append("")
+ lines.extend(self._async_client(base, service))
+ lines.append("")
+ lines.append("// swiftlint:enable type_name")
+
+ content = "\n".join(lines).rstrip() + "\n"
+ package_path = (
+ self.schema.package.replace(".", "/") if self.schema.package else ""
+ )
+ file_name = f"{self.to_pascal_case(service.name)}Grpc.swift"
+ path = f"{package_path}/{file_name}" if package_path else file_name
+ return GeneratedFile(path=path, content=content)
+
+ def _marshaller(self, base: str, module: str) -> list[str]:
+ # The Swift Fory instance is single-threaded, so keep one per thread.
+ return [
+ "private enum ForyRuntime {",
+ f' private static let key = "org.apache.fory.grpc.{module}"',
Review Comment:
Make the thread-local key unique across Swift modules
This key contains only the generated textual type path, so every default-package schema uses `org.apache.fory.grpc.ForyModule`. If two generated schemas live in separate Swift modules, the second wrapper retrieves the first module's `Fory`, skips its own registrations, and serializes with the wrong resolver. Please include the runtime Swift module/type identity in the key and cover two generated targets round-tripping sequentially on the same thread.
##########
compiler/fory_compiler/generators/swift.py:
##########
@@ -201,6 +202,21 @@ def output_file_path(self) -> str:
return f"{package_path}/{file_name}"
return file_name
+ def swift_model_top_level_symbols(self) -> set[str]:
+ # In enum style with a package, types live nested under a shared namespace
+ # enum, so there are no collidable top-level names. Flatten and default
+ # packages put each type and the module helper at file scope.
+ components = self._package_components_for_schema(self.schema)
+ if self.get_namespace_style() == "enum" and components:
Review Comment:
Include the package namespace in collision preflight
Enum-style packages still emit a top-level namespace enum. For example, `demo.shared` and `demo.greeter` each emit `public enum Demo`, but this branch records no symbol, so preflight succeeds and Swift compilation later fails with `invalid redeclaration`. Please record the actual top-level namespace owner, or change generation so duplicate declarations are avoided.
##########
compiler/fory_compiler/generators/swift.py:
##########
@@ -201,6 +202,21 @@ def output_file_path(self) -> str:
return f"{package_path}/{file_name}"
return file_name
+ def swift_model_top_level_symbols(self) -> set[str]:
+ # In enum style with a package, types live nested under a shared namespace
+ # enum, so there are no collidable top-level names. Flatten and default
+ # packages put each type and the module helper at file scope.
+ components = self._package_components_for_schema(self.schema)
+ if self.get_namespace_style() == "enum" and components:
+ return set()
+ symbols: set[str] = set()
Review Comment:
Preserve duplicate symbol owners during preflight
Returning a `set` loses duplicate declarations produced within one schema. For a default-package schema declaring `message ForyModule {}`, the model and generated helper both become top-level `ForyModule`, but preflight sees only one symbol. Flattened helper collisions and normalized-name collisions have the same problem. Please preserve each declaration and its Swift scope instead of deduplicating before ownership validation.
##########
compiler/fory_compiler/generators/services/swift.py:
##########
@@ -0,0 +1,628 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+
+"""Swift gRPC service companion generator (grpc-swift v1)."""
+
+from fory_compiler.generators.base import GeneratedFile
+from fory_compiler.generators.services.base import StreamingMode, streaming_mode
+from fory_compiler.ir.ast import RpcMethod, Service
+
+# Availability gate matching grpc-swift's async/await APIs.
+_ASYNC_AVAILABLE = "@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)"
+
+# Members the generated provider and client expose through their protocols and
+# base types; an rpc whose Swift name matches one would override or clash with it.
+_SWIFT_GRPC_RESERVED_MEMBERS = {
+ "handle",
+ "serviceName",
+ "channel",
+ "defaultCallOptions",
+}
+
+
+class SwiftServiceMixin:
+ """Generates Swift gRPC service companions backed by Fory serialization."""
+
+ def generate_services(self) -> list[GeneratedFile]:
+ services = [s for s in self.schema.services if not self.is_imported_type(s)]
+ if not services:
+ return []
+ self._check_swift_grpc_method_collisions(services)
+ return [self._generate_swift_service(service) for service in services]
+
+ def _grpc_prefix(self) -> str:
+ return "_".join(self._package_components_for_schema(self.schema))
+
+ def _service_symbol(self, service: Service) -> str:
+ name = self.to_pascal_case(service.name)
+ prefix = self._grpc_prefix()
+ return f"{prefix}_{name}" if prefix else name
+
+ def swift_grpc_output_path(self, service: Service) -> str:
+ package = self.schema.package
+ package_path = package.replace(".", "/") if package else ""
+ file_name = f"{self.to_pascal_case(service.name)}Grpc.swift"
+ return f"{package_path}/{file_name}" if package_path else file_name
+
+ def swift_grpc_service_symbols(self, service: Service) -> list[str]:
+ base = self._service_symbol(service)
+ modes = {streaming_mode(m) for m in service.methods}
+ symbols = [
+ f"{base}Message",
Review Comment:
Do not reserve a marshaller symbol for empty services
`<Base>Message` is registered unconditionally, but the marshaller is emitted only when the service has methods. Consequently `message GreeterMessage {}; service Greeter {}` is rejected even though the service file contains no `GreeterMessage` wrapper. Please register this symbol only for non-empty services and add an empty-service preflight case.
##########
compiler/fory_compiler/generators/services/swift.py:
##########
@@ -0,0 +1,628 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+
+"""Swift gRPC service companion generator (grpc-swift v1)."""
+
+from fory_compiler.generators.base import GeneratedFile
+from fory_compiler.generators.services.base import StreamingMode, streaming_mode
+from fory_compiler.ir.ast import RpcMethod, Service
+
+# Availability gate matching grpc-swift's async/await APIs.
+_ASYNC_AVAILABLE = "@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)"
+
+# Members the generated provider and client expose through their protocols and
+# base types; an rpc whose Swift name matches one would override or clash with it.
+_SWIFT_GRPC_RESERVED_MEMBERS = {
+ "handle",
+ "serviceName",
+ "channel",
+ "defaultCallOptions",
+}
+
+
+class SwiftServiceMixin:
+ """Generates Swift gRPC service companions backed by Fory serialization."""
+
+ def generate_services(self) -> list[GeneratedFile]:
+ services = [s for s in self.schema.services if not self.is_imported_type(s)]
+ if not services:
+ return []
+ self._check_swift_grpc_method_collisions(services)
+ return [self._generate_swift_service(service) for service in services]
+
+ def _grpc_prefix(self) -> str:
+ return "_".join(self._package_components_for_schema(self.schema))
+
+ def _service_symbol(self, service: Service) -> str:
+ name = self.to_pascal_case(service.name)
+ prefix = self._grpc_prefix()
+ return f"{prefix}_{name}" if prefix else name
+
+ def swift_grpc_output_path(self, service: Service) -> str:
+ package = self.schema.package
+ package_path = package.replace(".", "/") if package else ""
+ file_name = f"{self.to_pascal_case(service.name)}Grpc.swift"
+ return f"{package_path}/{file_name}" if package_path else file_name
+
+ def swift_grpc_service_symbols(self, service: Service) -> list[str]:
+ base = self._service_symbol(service)
+ modes = {streaming_mode(m) for m in service.methods}
+ symbols = [
+ f"{base}Message",
+ f"{base}Metadata",
+ f"{base}Provider",
+ f"{base}AsyncProvider",
+ f"{base}AsyncClient",
+ ]
+ if modes & {StreamingMode.SERVER_STREAMING, StreamingMode.BIDIRECTIONAL}:
+ symbols += [
+ f"{base}StreamingResponseContext",
+ f"{base}AsyncResponseStream",
+ f"{base}ResponseStream",
+ ]
+ if StreamingMode.CLIENT_STREAMING in modes:
+ symbols.append(f"{base}UnaryResponseContext")
+ if modes & {StreamingMode.CLIENT_STREAMING, StreamingMode.BIDIRECTIONAL}:
+ symbols.append(f"{base}AsyncRequestStream")
+ return symbols
+
+ def _swift_grpc_method_name(self, method: RpcMethod) -> str:
+ return self.safe_member_name(method.name)
Review Comment:
Reject or rename an underscore-only RPC method
FDL accepts an RPC named `_`, but the generated member remains `_`, producing declarations and references such as `static let _`, `func _`, and `Methods._` that Swift cannot use as identifiers. Please generate a referencable name or reject this RPC name during preflight.
##########
compiler/fory_compiler/generators/services/swift.py:
##########
@@ -0,0 +1,628 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+
+"""Swift gRPC service companion generator (grpc-swift v1)."""
+
+from fory_compiler.generators.base import GeneratedFile
+from fory_compiler.generators.services.base import StreamingMode, streaming_mode
+from fory_compiler.ir.ast import RpcMethod, Service
+
+# Availability gate matching grpc-swift's async/await APIs.
+_ASYNC_AVAILABLE = "@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)"
+
+# Members the generated provider and client expose through their protocols and
+# base types; an rpc whose Swift name matches one would override or clash with it.
+_SWIFT_GRPC_RESERVED_MEMBERS = {
+ "handle",
+ "serviceName",
+ "channel",
+ "defaultCallOptions",
+}
+
+
+class SwiftServiceMixin:
+ """Generates Swift gRPC service companions backed by Fory serialization."""
+
+ def generate_services(self) -> list[GeneratedFile]:
+ services = [s for s in self.schema.services if not self.is_imported_type(s)]
+ if not services:
+ return []
+ self._check_swift_grpc_method_collisions(services)
+ return [self._generate_swift_service(service) for service in services]
+
+ def _grpc_prefix(self) -> str:
+ return "_".join(self._package_components_for_schema(self.schema))
+
+ def _service_symbol(self, service: Service) -> str:
+ name = self.to_pascal_case(service.name)
+ prefix = self._grpc_prefix()
+ return f"{prefix}_{name}" if prefix else name
+
+ def swift_grpc_output_path(self, service: Service) -> str:
+ package = self.schema.package
+ package_path = package.replace(".", "/") if package else ""
+ file_name = f"{self.to_pascal_case(service.name)}Grpc.swift"
+ return f"{package_path}/{file_name}" if package_path else file_name
+
+ def swift_grpc_service_symbols(self, service: Service) -> list[str]:
+ base = self._service_symbol(service)
+ modes = {streaming_mode(m) for m in service.methods}
+ symbols = [
+ f"{base}Message",
+ f"{base}Metadata",
+ f"{base}Provider",
+ f"{base}AsyncProvider",
+ f"{base}AsyncClient",
+ ]
+ if modes & {StreamingMode.SERVER_STREAMING, StreamingMode.BIDIRECTIONAL}:
+ symbols += [
+ f"{base}StreamingResponseContext",
+ f"{base}AsyncResponseStream",
+ f"{base}ResponseStream",
+ ]
+ if StreamingMode.CLIENT_STREAMING in modes:
+ symbols.append(f"{base}UnaryResponseContext")
+ if modes & {StreamingMode.CLIENT_STREAMING, StreamingMode.BIDIRECTIONAL}:
+ symbols.append(f"{base}AsyncRequestStream")
+ return symbols
+
+ def _swift_grpc_method_name(self, method: RpcMethod) -> str:
+ return self.safe_member_name(method.name)
+
+ def _request_type(self, method: RpcMethod) -> str:
+ return self._named_type_reference(method.request_type)
+
+ def _response_type(self, method: RpcMethod) -> str:
+ return self._named_type_reference(method.response_type)
+
+ def _check_swift_grpc_method_collisions(self, services: list[Service]) -> None:
+ for service in services:
+ seen: dict[str, str] = {}
+ for method in service.methods:
+ swift_name = self._swift_grpc_method_name(method).strip("`")
+ if swift_name in _SWIFT_GRPC_RESERVED_MEMBERS:
+ raise ValueError(
+ f"Swift gRPC method {service.name}.{method.name} generates "
+ f"{swift_name}, which collides with a generated provider or "
+ "client member; rename the rpc"
+ )
+ if swift_name in seen:
+ raise ValueError(
+ f"Swift gRPC method name collision in service {service.name}: "
+ f"{seen[swift_name]} and {method.name} both generate {swift_name}"
+ )
+ seen[swift_name] = method.name
+
+ def _generate_swift_service(self, service: Service) -> GeneratedFile:
+ base = self._service_symbol(service)
+ module = self.module_type_path()
+ methods = service.methods
+ modes = {streaming_mode(m) for m in methods}
+
+ lines: list[str] = []
+ lines.append(self.get_license_header("//"))
+ lines.append("")
+ # gRPC symbols are package-prefixed with underscores, matching grpc-swift.
+ lines.append("// swiftlint:disable type_name")
+ lines.append("")
+ lines.append("import Foundation")
+ lines.append("import GRPC")
+ lines.append("import NIOCore")
+ lines.append("import Fory")
+ lines.append("")
+
+ if methods:
+ lines.extend(self._marshaller(base, module))
+ lines.append("")
+ lines.extend(self._metadata(base, service))
+ lines.append("")
+ lines.extend(self._adapters(base, modes))
+ lines.extend(self._provider(base, service))
+ lines.append("")
+ lines.extend(self._async_provider(base, service))
+ lines.append("")
+ lines.extend(self._async_client(base, service))
+ lines.append("")
+ lines.append("// swiftlint:enable type_name")
+
+ content = "\n".join(lines).rstrip() + "\n"
+ package_path = (
+ self.schema.package.replace(".", "/") if self.schema.package else ""
+ )
+ file_name = f"{self.to_pascal_case(service.name)}Grpc.swift"
+ path = f"{package_path}/{file_name}" if package_path else file_name
+ return GeneratedFile(path=path, content=content)
+
+ def _marshaller(self, base: str, module: str) -> list[str]:
+ # The Swift Fory instance is single-threaded, so keep one per thread.
+ return [
+ "private enum ForyRuntime {",
+ f' private static let key = "org.apache.fory.grpc.{module}"',
+ " static func fory() throws -> Fory {",
+ " let storage = Thread.current.threadDictionary",
+ " if let existing = storage[key] as? Fory { return existing }",
+ f" let local = Fory(config: {module}.getFory().config)",
+ f" try {module}.install(local)",
+ " storage[key] = local",
+ " return local",
+ " }",
+ "}",
+ "",
+ "// Internal Fory wire wrapper for gRPC request and response messages.",
+ "// NIOCore.ByteBuffer is qualified because `import Fory` also exposes one.",
+ "// grpc-swift transfers this carrier between the calling task and the event",
+ "// loop, so the payload must be Sendable. The carrier itself only stores that",
+ "// payload, so @unchecked covers the wrapper while Value carries the guarantee.",
+ (
+ f"struct {base}Message<Value: Serializer & Sendable>: GRPCPayload,"
Review Comment:
Align the Sendable constraint with generated model types
This constraint applies to every request and response wrapper, but generated public structs, enums, unions, and classes do not conform to `Sendable`. Swift 6 therefore rejects even unary providers and clients when `Message<Request/Response>` is instantiated, not only client-streaming and bidi as the guide states. Please add a safe `Sendable` strategy plus Swift 6 build coverage, or state that the entire companion currently requires Swift 5 mode.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For additional commands, e-mail: commits-help@fory.apache.org
Re: [PR] feat(compiler): Add grpc support for Swift [fory]
Posted by "yash-agarwa-l (via GitHub)" <gi...@apache.org>.
yash-agarwa-l commented on PR #3776:
URL: https://github.com/apache/fory/pull/3776#issuecomment-4826220289
Hi @chaokunyang, small update and one thing I'd like your call on.
The Swift gRPC codegen is done and Swift↔Swift round-trips fine, but Java↔Swift interop fails. After digging in, it looks like a gap in the Fory Swift core serializer, not the gRPC code.
When refTracking is on, Swift writes `NOT_NULL_VALUE` (0xff) for a struct message, while Java/Python/Rust write `REF_VALUE` (0x00) and reserve a ref slot. (Go reaches `REF_VALUE` only because grpc-go marshals a pointer; its value-struct path opts out like Swift.) Without the reserved slot, the ref-id counters drift and Java's decode hits Index -1. I checked it's just that one flag byte by flipping it. The reason is that Swift structs are `isRefType` = `false`, so `foryWrite` skips tracking even when it's on. This might be on purpose, since structs are value types. Rust handles the same case by writing `REF_VALUE` + `reserve_ref_id()` for value-type structs under tracking.
If that sounds right to you, I'd like to do the same for Swift. One thing to flag: it changes tracking-mode output for all Swift structs, not just gRPC. Tracking is opt-in and off by default, so the impact should be small, but it's a core wire-format change, so I wanted to ask first. No gRPC module changes; the contract stays refTracking:true.
Two questions:
1. Would it be fine to make Swift value-type structs take part in ref tracking?
2. Is the current opt-out on purpose, or just not done yet? Want to be sure I'm not missing a reason it writes `NOT_NULL_VALUE`.
Since it's a wire-format change, I'm happy to do whatever works best for you here, a separate issue or PR, or just keep it on this PR. Let me know.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For additional commands, e-mail: commits-help@fory.apache.org
Re: [PR] feat(compiler): Add grpc support for Swift [fory]
Posted by "chaokunyang (via GitHub)" <gi...@apache.org>.
chaokunyang commented on code in PR #3776:
URL: https://github.com/apache/fory/pull/3776#discussion_r3821741952
##########
integration_tests/grpc_tests/run_tests.sh:
##########
@@ -56,6 +56,19 @@ if has_test_class "DartGrpcTest"; then
dart analyze bin lib/generated/*/*_grpc.dart
dart format --output=none --set-exit-if-changed bin lib/generated/*/*_grpc.dart
fi
+# Swift toolchain tests (generated marshaller round-trip and concurrency). These
+# need the Swift toolchain rather than the JVM, so they run in their own package.
+if command -v swift >/dev/null 2>&1 && [ -d "${SCRIPT_DIR}/swift/interop" ]; then
Review Comment:
Honor the selected test classes for Swift work
This block ignores `TEST_CLASSES`, so `./run_tests.sh GoGrpcTest` still resolves and runs the Swift package whenever Swift is installed. Please put both the Swift package tests and the `SwiftGrpcTest` build behind `has_test_class "SwiftGrpcTest"`, while retaining the tool-availability check before invoking Swift.
##########
integration_tests/grpc_tests/swift/interop/Tests/ForyGrpcTests/MarshallerThreadSafetyTests.swift:
##########
@@ -0,0 +1,85 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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 NIOCore
+import XCTest
+
+@testable import ForyGrpcGenerated
+
+// The generated marshaller uses one Fory per thread. Run it from many threads
+// (under `swift test --sanitize=thread` in CI) to prove there is no data race,
+// and confirm it stays wire-compatible with the schema module's shared Fory.
+final class MarshallerThreadSafetyTests: XCTestCase {
+ func testConcurrentRoundTrip() {
+ DispatchQueue.concurrentPerform(iterations: 2000) { i in
+ do {
+ let allocator = ByteBufferAllocator()
+ let request = GrpcFdl.GrpcFdlRequest(id: "n\(i)", count: Int32(i), payload: "p\(i)")
+ var buffer = allocator.buffer(capacity: 64)
+ try GrpcFdl_FdlGrpcServiceMessage(request).serialize(into: &buffer)
+ let back = try GrpcFdl_FdlGrpcServiceMessage<GrpcFdl.GrpcFdlRequest>(
+ serializedByteBuffer: &buffer)
+ XCTAssertEqual(back.value, request)
+ } catch {
+ XCTFail("marshaller round-trip failed: \(error)")
+ }
+ }
+ }
+
+ func testWireCompatibleWithModuleFory() throws {
+ let allocator = ByteBufferAllocator()
+ let probe = GrpcFdl.GrpcFdlRequest(id: "probe", count: 7, payload: "x")
+
+ let sharedBytes = try GrpcFdl.ForyModule.getFory().serialize(probe)
+ var inbound = allocator.buffer(capacity: sharedBytes.count)
+ inbound.writeBytes(sharedBytes)
+ let fromShared = try GrpcFdl_FdlGrpcServiceMessage<GrpcFdl.GrpcFdlRequest>(
+ serializedByteBuffer: &inbound)
+ XCTAssertEqual(fromShared.value, probe)
+
+ var outbound = allocator.buffer(capacity: 64)
+ try GrpcFdl_FdlGrpcServiceMessage(probe).serialize(into: &outbound)
+ let fromMarshaller: GrpcFdl.GrpcFdlRequest =
+ try GrpcFdl.ForyModule.getFory().deserialize(Data(outbound.readableBytesView))
+ XCTAssertEqual(fromMarshaller, probe)
+ }
+
+ func testSeparateModulesKeepOwnRuntimeOnOneThread() throws {
Review Comment:
Make the runtime-key regression test reproduce the old collision
`GrpcFdl` and `GrpcFbs` are both compiled into the single `ForyGrpcGenerated` Swift target, and the old textual keys were already different (`GrpcFdl.ForyModule` versus `GrpcFbs.ForyModule`). This test therefore passes even if the `String(reflecting:)` fix is removed. Please exercise two Swift targets/modules that expose the same textual helper path, ideally default-package `ForyModule`, so the test fails on the old implementation.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For additional commands, e-mail: commits-help@fory.apache.org
Re: [PR] feat(compiler): Add grpc support for Swift [fory]
Posted by "yash-agarwa-l (via GitHub)" <gi...@apache.org>.
yash-agarwa-l commented on code in PR #3776:
URL: https://github.com/apache/fory/pull/3776#discussion_r3838764924
##########
compiler/fory_compiler/generators/services/swift.py:
##########
@@ -0,0 +1,628 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+
+"""Swift gRPC service companion generator (grpc-swift v1)."""
+
+from fory_compiler.generators.base import GeneratedFile
+from fory_compiler.generators.services.base import StreamingMode, streaming_mode
+from fory_compiler.ir.ast import RpcMethod, Service
+
+# Availability gate matching grpc-swift's async/await APIs.
+_ASYNC_AVAILABLE = "@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)"
+
+# Members the generated provider and client expose through their protocols and
+# base types; an rpc whose Swift name matches one would override or clash with it.
+_SWIFT_GRPC_RESERVED_MEMBERS = {
+ "handle",
+ "serviceName",
+ "channel",
+ "defaultCallOptions",
+}
+
+
+class SwiftServiceMixin:
+ """Generates Swift gRPC service companions backed by Fory serialization."""
+
+ def generate_services(self) -> list[GeneratedFile]:
Review Comment:
thanks, I had ruff 0.16.0 locally while CI pins 0.15.22, and when I ran `ruff check --fix,` it perhaps rewrote the annotations. I have validated the ruff with the exact version mentioned in the ci file now.
Also after adding the 3.8/3.9 import check, it surfaced some same issues with the frontend files. I have fixed them.
##########
compiler/fory_compiler/generators/swift.py:
##########
@@ -201,6 +202,21 @@ def output_file_path(self) -> str:
return f"{package_path}/{file_name}"
return file_name
+ def swift_model_top_level_symbols(self) -> set[str]:
+ # In enum style with a package, types live nested under a shared namespace
+ # enum, so there are no collidable top-level names. Flatten and default
+ # packages put each type and the module helper at file scope.
+ components = self._package_components_for_schema(self.schema)
+ if self.get_namespace_style() == "enum" and components:
+ return set()
+ symbols: set[str] = set()
Review Comment:
Made the revision, I have also fixed duplicates nested inside a namespace enum, those were invisible to preflight before, PTAL.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For additional commands, e-mail: commits-help@fory.apache.org
Re: [PR] feat(compiler): Add grpc support for Swift [fory]
Posted by "chaokunyang (via GitHub)" <gi...@apache.org>.
chaokunyang commented on code in PR #3776:
URL: https://github.com/apache/fory/pull/3776#discussion_r3821741386
##########
docs/grpc/swift.md:
##########
@@ -0,0 +1,235 @@
+---
+title: Swift gRPC
+sidebar_position: 14
+id: swift
+license: |
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+---
+
+Fory can generate Swift gRPC service companions for schemas that define
+services. The companion provides the usual gRPC service providers, clients,
+method descriptors, and service metadata, while request and response objects are
+serialized with Fory instead of protobuf.
+
+Use this mode when both RPC peers are generated from the same Fory IDL, protobuf
+IDL, or FlatBuffers IDL and both sides expect Fory-encoded message bodies. Use
+normal protobuf gRPC generation for APIs that must be consumed by generic
+protobuf clients, reflection tools, or components that expect protobuf bytes.
+
+The companion targets [grpc-swift](https://github.com/grpc/grpc-swift) 1.x. That
+line keeps the same platform floor as the Fory Swift package (macOS 13, iOS 16);
+grpc-swift 2.x requires a newer floor.
+
+## Add Dependencies
+
+The `Fory` package does not depend on grpc-swift. Add grpc-swift in the package
+that compiles or runs the generated companions:
+
+```swift
+// Package.swift
+dependencies: [
+ .package(url: "https://github.com/apache/fory.git", from: "1.2.0"),
Review Comment:
Require a Fory version that contains this runtime support
`from: "1.2.0"` allows versions whose Swift `Serializer` lacks `Target`, and even 1.6.1 lacks this PR's tracked-value root ref-slot fix required for Java/Swift interoperability. Please use the first release containing both the compiler and runtime changes, or follow the existing `$version` convention.
##########
integration_tests/grpc_tests/swift/interop/Tests/ForyGrpcTests/RoundTripTests.swift:
##########
@@ -0,0 +1,144 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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 GRPC
+import NIOPosix
+import XCTest
+
+import ForyGrpcGenerated
+
+private func response(
+ _ request: GrpcFdl.GrpcFdlRequest, _ tag: String, _ offset: Int
+) -> GrpcFdl.GrpcFdlResponse {
+ GrpcFdl.GrpcFdlResponse(
+ id: "\(tag):\(request.id)",
+ count: request.count + Int32(offset),
+ payload: "\(tag):\(request.payload)")
+}
+
+private func aggregate(_ requests: [GrpcFdl.GrpcFdlRequest]) -> GrpcFdl.GrpcFdlResponse {
+ GrpcFdl.GrpcFdlResponse(
+ id: "client:" + requests.map(\.id).joined(separator: "+"),
+ count: requests.reduce(0) { $0 + $1.count },
+ payload: "client:" + requests.map(\.payload).joined(separator: "+"))
+}
+
+private func stream(_ requests: [GrpcFdl.GrpcFdlRequest]) -> AsyncStream<GrpcFdl.GrpcFdlRequest> {
+ AsyncStream { continuation in
+ for request in requests { continuation.yield(request) }
+ continuation.finish()
+ }
+}
+
+private final class FdlService: GrpcFdl_FdlGrpcServiceAsyncProvider {
+ func unaryMessage(request: GrpcFdl.GrpcFdlRequest, context: GRPCAsyncServerCallContext)
+ async throws -> GrpcFdl.GrpcFdlResponse
+ {
+ response(request, "unary", 10)
+ }
+ func serverStreamMessage(
+ request: GrpcFdl.GrpcFdlRequest,
+ responseStream: GrpcFdl_FdlGrpcServiceAsyncResponseStream<GrpcFdl.GrpcFdlResponse>,
+ context: GRPCAsyncServerCallContext
+ ) async throws {
+ for i in 0..<3 { try await responseStream.send(response(request, "server-\(i)", i)) }
+ }
+ func clientStreamMessage(
+ requestStream: GrpcFdl_FdlGrpcServiceAsyncRequestStream<GrpcFdl.GrpcFdlRequest>,
+ context: GRPCAsyncServerCallContext
+ ) async throws -> GrpcFdl.GrpcFdlResponse {
+ var requests: [GrpcFdl.GrpcFdlRequest] = []
+ for try await request in requestStream { requests.append(request) }
+ return aggregate(requests)
+ }
+ func bidiStreamMessage(
+ requestStream: GrpcFdl_FdlGrpcServiceAsyncRequestStream<GrpcFdl.GrpcFdlRequest>,
+ responseStream: GrpcFdl_FdlGrpcServiceAsyncResponseStream<GrpcFdl.GrpcFdlResponse>,
+ context: GRPCAsyncServerCallContext
+ ) async throws {
+ var index = 0
+ for try await request in requestStream {
+ try await responseStream.send(response(request, "bidi-\(index)", index))
+ index += 1
+ }
+ }
+ func unaryUnion(request: GrpcFdl.GrpcFdlUnion, context: GRPCAsyncServerCallContext)
+ async throws -> GrpcFdl.GrpcFdlUnion
+ {
+ request
+ }
+ func serverStreamUnion(
+ request: GrpcFdl.GrpcFdlUnion,
+ responseStream: GrpcFdl_FdlGrpcServiceAsyncResponseStream<GrpcFdl.GrpcFdlUnion>,
+ context: GRPCAsyncServerCallContext
+ ) async throws {
+ try await responseStream.send(request)
+ }
+ func clientStreamUnion(
+ requestStream: GrpcFdl_FdlGrpcServiceAsyncRequestStream<GrpcFdl.GrpcFdlUnion>,
+ context: GRPCAsyncServerCallContext
+ ) async throws -> GrpcFdl.GrpcFdlUnion {
+ var last = GrpcFdl.GrpcFdlUnion.response(GrpcFdl.GrpcFdlResponse())
+ for try await union in requestStream { last = union }
+ return last
+ }
+ func bidiStreamUnion(
+ requestStream: GrpcFdl_FdlGrpcServiceAsyncRequestStream<GrpcFdl.GrpcFdlUnion>,
+ responseStream: GrpcFdl_FdlGrpcServiceAsyncResponseStream<GrpcFdl.GrpcFdlUnion>,
+ context: GRPCAsyncServerCallContext
+ ) async throws {
+ for try await union in requestStream { try await responseStream.send(union) }
+ }
+}
+
+// Hosts the generated provider in-process and exercises the generated async client
+// across all four streaming modes (the relocated SwiftPM build-and-run fixture).
+final class RoundTripTests: XCTestCase {
+ func testInProcessAllStreamingModes() async throws {
+ let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
+ defer { try? group.syncShutdownGracefully() }
Review Comment:
Use async cleanup in the async test
The current Java/Swift CI job reports async-context warnings for this blocking shutdown, lines 118/124, and `main.swift:456`; Swift 6 upgrades at least one to an error. Please use awaited `get()` or structured async lifecycle cleanup while preserving failure-path cleanup. Swift changes are required to compile without warnings.
##########
.github/workflows/ci.yml:
##########
@@ -1016,6 +1016,49 @@ jobs:
cd integration_tests/grpc_tests/java
mvn -T16 --no-transfer-progress -Dtest=DartGrpcTest test
+ grpc_java_swift_tests:
+ name: Java/Swift gRPC Tests
+ needs: changes
+ if: needs.changes.outputs.grpc_tests == 'true' || needs.changes.outputs.swift == 'true'
+ runs-on: macos-latest
+ steps:
+ - uses: actions/checkout@v5
+ - name: Set up JDK 21
+ uses: actions/setup-java@v4
+ with:
+ java-version: 21
+ distribution: "temurin"
+ - name: Set up Python 3.11
+ uses: actions/setup-python@v5
+ with:
+ python-version: 3.11
+ cache: "pip"
+ - name: Cache Maven local repository
+ uses: actions/cache@v4
+ with:
+ path: ~/.m2/repository
+ key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}-maven-
+ - name: Install Java artifacts for gRPC tests
+ run: |
+ cd java
+ mvn -T16 --no-transfer-progress clean install -DskipTests -Dmaven.javadoc.skip=true -Dmaven.source.skip=true
+ - name: Generate gRPC test sources
+ run: python integration_tests/grpc_tests/generate_grpc.py
+ - name: Build Swift gRPC peer
+ run: |
+ cd integration_tests/grpc_tests/swift/interop
+ swift build -c release --product interop
+ - name: Run Swift gRPC package tests
+ run: |
+ cd integration_tests/grpc_tests/swift/interop
+ swift test
Review Comment:
Run the concurrency regression under ThreadSanitizer
This runs only plain `swift test`. The sole `--sanitize=thread` command is behind `FORY_SWIFT_TSAN=1` in another script that this workflow never invokes, so the test and PR description's TSan safety claim is not exercised by CI. Please add a sanitized invocation, preferably filtered to the marshaller concurrency test.
##########
integration_tests/grpc_tests/run_tests.sh:
##########
@@ -56,6 +56,19 @@ if has_test_class "DartGrpcTest"; then
dart analyze bin lib/generated/*/*_grpc.dart
dart format --output=none --set-exit-if-changed bin lib/generated/*/*_grpc.dart
fi
+# Swift toolchain tests (generated marshaller round-trip and concurrency). These
+# need the Swift toolchain rather than the JVM, so they run in their own package.
+if command -v swift >/dev/null 2>&1 && [ -d "${SCRIPT_DIR}/swift/interop" ]; then
Review Comment:
Honor the selected test classes for Swift work
This block ignores `TEST_CLASSES`, so `./run_tests.sh GoGrpcTest` still resolves and runs the Swift package whenever Swift is installed. Conversely, the default includes `SwiftGrpcTest` and later invokes `swift build` without checking that Swift exists. Please gate all Swift work on both the selected test class and tool availability, or make it an explicit opt-in.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For additional commands, e-mail: commits-help@fory.apache.org