Compare commits
25 Commits
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 |
|
@ -13,9 +13,10 @@ jobs:
|
|||
with:
|
||||
linux_5_9_enabled: false
|
||||
linux_5_10_enabled: false
|
||||
linux_6_0_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
|
||||
linux_nightly_6_1_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
|
||||
linux_nightly_main_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_6_1_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -warnings-as-errors -Xswiftc -require-explicit-availability"
|
||||
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
|
||||
|
@ -32,7 +33,7 @@ jobs:
|
|||
env:
|
||||
MATRIX_LINUX_5_9_ENABLED: false
|
||||
MATRIX_LINUX_5_10_ENABLED: false
|
||||
MATRIX_LINUX_COMMAND: "curl -s https://raw.githubusercontent.com/rnro/grpc-swift-protobuf/refs/heads/build_plugin_integration_tests/dev/plugin-tests.sh | GITHUB_ACTIONS=true bash"
|
||||
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:
|
||||
|
@ -42,3 +43,7 @@ jobs:
|
|||
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
|
||||
with:
|
||||
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:
|
||||
name: Soundness
|
||||
|
@ -22,9 +25,10 @@ jobs:
|
|||
with:
|
||||
linux_5_9_enabled: false
|
||||
linux_5_10_enabled: false
|
||||
linux_6_0_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
|
||||
linux_nightly_6_1_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
|
||||
linux_nightly_main_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_6_1_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -warnings-as-errors -Xswiftc -require-explicit-availability"
|
||||
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
|
||||
|
@ -41,7 +45,7 @@ jobs:
|
|||
env:
|
||||
MATRIX_LINUX_5_9_ENABLED: false
|
||||
MATRIX_LINUX_5_10_ENABLED: false
|
||||
MATRIX_LINUX_COMMAND: "curl -s https://raw.githubusercontent.com/rnro/grpc-swift-protobuf/refs/heads/build_plugin_integration_tests/dev/plugin-tests.sh | GITHUB_ACTIONS=true bash"
|
||||
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:
|
||||
|
@ -58,3 +62,7 @@ jobs:
|
|||
with:
|
||||
linux_5_9_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
|
||||
run: |
|
||||
./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"
|
||||
|
|
|
@ -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,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()
|
||||
}
|
||||
}
|
|
@ -28,9 +28,8 @@ let package = Package(
|
|||
],
|
||||
dependencies: [
|
||||
// Dependency on grpc-swift-protobuf to be added by setup-plugin-tests.sh script
|
||||
|
||||
.package(
|
||||
url: "https://github.com/grpc/grpc-swift.git",
|
||||
url: "https://github.com/grpc/grpc-swift-2.git",
|
||||
from: "2.0.0"
|
||||
)
|
||||
],
|
||||
|
@ -38,8 +37,8 @@ let package = Package(
|
|||
.executableTarget(
|
||||
name: "grpc-adopter",
|
||||
dependencies: [
|
||||
.product(name: "GRPCCore", package: "grpc-swift"),
|
||||
.product(name: "GRPCInProcessTransport", package: "grpc-swift"),
|
||||
.product(name: "GRPCCore", package: "grpc-swift-2"),
|
||||
.product(name: "GRPCInProcessTransport", package: "grpc-swift-2"),
|
||||
.product(name: "GRPCProtobuf", package: "grpc-swift-protobuf"),
|
||||
],
|
||||
plugins: [
|
134
Package.swift
134
Package.swift
|
@ -23,18 +23,22 @@ let products: [Product] = [
|
|||
targets: ["GRPCProtobuf"]
|
||||
),
|
||||
.executable(
|
||||
name: "protoc-gen-grpc-swift",
|
||||
targets: ["protoc-gen-grpc-swift"]
|
||||
name: "protoc-gen-grpc-swift-2",
|
||||
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] = [
|
||||
.package(
|
||||
url: "https://github.com/grpc/grpc-swift.git",
|
||||
url: "https://github.com/grpc/grpc-swift-2.git",
|
||||
from: "2.0.0"
|
||||
),
|
||||
.package(
|
||||
|
@ -43,20 +47,36 @@ let dependencies: [Package.Dependency] = [
|
|||
),
|
||||
]
|
||||
|
||||
let defaultSwiftSettings: [SwiftSetting] = [
|
||||
.swiftLanguageMode(.v6),
|
||||
.enableUpcomingFeature("ExistentialAny"),
|
||||
.enableUpcomingFeature("InternalImportsByDefault"),
|
||||
.enableUpcomingFeature("MemberImportVisibility"),
|
||||
]
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
let targets: [Target] = [
|
||||
// protoc plugin for grpc-swift
|
||||
// This adds some build settings which allow us to map "@available(gRPCSwiftProtobuf 2.x, *)" to
|
||||
// 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(
|
||||
name: "protoc-gen-grpc-swift",
|
||||
name: "protoc-gen-grpc-swift-2",
|
||||
dependencies: [
|
||||
.target(name: "GRPCProtobufCodeGen"),
|
||||
.product(name: "GRPCCodeGen", package: "grpc-swift"),
|
||||
.product(name: "GRPCCodeGen", package: "grpc-swift-2"),
|
||||
.product(name: "SwiftProtobuf", package: "swift-protobuf"),
|
||||
.product(name: "SwiftProtobufPluginLibrary", package: "swift-protobuf"),
|
||||
],
|
||||
|
@ -67,7 +87,7 @@ let targets: [Target] = [
|
|||
.target(
|
||||
name: "GRPCProtobuf",
|
||||
dependencies: [
|
||||
.product(name: "GRPCCore", package: "grpc-swift"),
|
||||
.product(name: "GRPCCore", package: "grpc-swift-2"),
|
||||
.product(name: "SwiftProtobuf", package: "swift-protobuf"),
|
||||
],
|
||||
swiftSettings: defaultSwiftSettings
|
||||
|
@ -76,18 +96,18 @@ let targets: [Target] = [
|
|||
name: "GRPCProtobufTests",
|
||||
dependencies: [
|
||||
.target(name: "GRPCProtobuf"),
|
||||
.product(name: "GRPCCore", package: "grpc-swift"),
|
||||
.product(name: "GRPCInProcessTransport", package: "grpc-swift"),
|
||||
.product(name: "GRPCCore", package: "grpc-swift-2"),
|
||||
.product(name: "GRPCInProcessTransport", package: "grpc-swift-2"),
|
||||
.product(name: "SwiftProtobuf", package: "swift-protobuf"),
|
||||
],
|
||||
swiftSettings: defaultSwiftSettings
|
||||
),
|
||||
|
||||
// Code generator library for protoc-gen-grpc-swift
|
||||
// Code generator library for protoc-gen-grpc-swift-2
|
||||
.target(
|
||||
name: "GRPCProtobufCodeGen",
|
||||
dependencies: [
|
||||
.product(name: "GRPCCodeGen", package: "grpc-swift"),
|
||||
.product(name: "GRPCCodeGen", package: "grpc-swift-2"),
|
||||
.product(name: "SwiftProtobufPluginLibrary", package: "swift-protobuf"),
|
||||
],
|
||||
swiftSettings: defaultSwiftSettings
|
||||
|
@ -96,7 +116,7 @@ let targets: [Target] = [
|
|||
name: "GRPCProtobufCodeGenTests",
|
||||
dependencies: [
|
||||
.target(name: "GRPCProtobufCodeGen"),
|
||||
.product(name: "GRPCCodeGen", package: "grpc-swift"),
|
||||
.product(name: "GRPCCodeGen", package: "grpc-swift-2"),
|
||||
.product(name: "SwiftProtobuf", package: "swift-protobuf"),
|
||||
.product(name: "SwiftProtobufPluginLibrary", package: "swift-protobuf"),
|
||||
],
|
||||
|
@ -111,21 +131,81 @@ let targets: [Target] = [
|
|||
name: "GRPCProtobufGenerator",
|
||||
capability: .buildTool(),
|
||||
dependencies: [
|
||||
.target(name: "protoc-gen-grpc-swift"),
|
||||
.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(
|
||||
name: "grpc-swift-protobuf",
|
||||
platforms: [
|
||||
.macOS(.v15),
|
||||
.iOS(.v18),
|
||||
.tvOS(.v18),
|
||||
.watchOS(.v11),
|
||||
.visionOS(.v2),
|
||||
],
|
||||
products: products,
|
||||
dependencies: dependencies,
|
||||
targets: targets
|
||||
|
|
|
@ -16,8 +16,6 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
let configFileName = "grpc-swift-proto-generator-config.json"
|
||||
|
||||
/// The config of the build plugin.
|
||||
struct BuildPluginConfig: Codable {
|
||||
/// Config defining which components should be considered when generating source.
|
||||
|
@ -193,12 +191,14 @@ extension BuildPluginConfig.Protoc: Codable {
|
|||
|
||||
extension GenerationConfig {
|
||||
init(buildPluginConfig: BuildPluginConfig, configFilePath: URL, outputPath: URL) {
|
||||
self.server = buildPluginConfig.generate.servers
|
||||
self.client = buildPluginConfig.generate.clients
|
||||
self.message = buildPluginConfig.generate.messages
|
||||
// hard-code full-path to avoid collisions since this goes into a temporary directory anyway
|
||||
self.fileNaming = .fullPath
|
||||
self.visibility = buildPluginConfig.generatedSource.accessLevel
|
||||
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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2024, gRPC Authors All rights reserved.
|
||||
* 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.
|
||||
|
@ -14,13 +14,12 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
enum PluginError: Error {
|
||||
// Build plugin
|
||||
enum BuildPluginError: Error {
|
||||
case incompatibleTarget(String)
|
||||
case noConfigFilesFound
|
||||
}
|
||||
|
||||
extension PluginError: CustomStringConvertible {
|
||||
extension BuildPluginError: CustomStringConvertible {
|
||||
var description: String {
|
||||
switch self {
|
||||
case .incompatibleTarget(let target):
|
|
@ -19,10 +19,9 @@ import PackagePlugin
|
|||
|
||||
// Entry-point when using Package manifest
|
||||
extension GRPCProtobufGenerator: BuildToolPlugin {
|
||||
/// Create build commands, the entry-point when using a Package manifest.
|
||||
func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] {
|
||||
guard let swiftTarget = target as? SwiftSourceModuleTarget else {
|
||||
throw PluginError.incompatibleTarget(target.name)
|
||||
throw BuildPluginError.incompatibleTarget(target.name)
|
||||
}
|
||||
let configFiles = swiftTarget.sourceFiles(withSuffix: configFileName).map { $0.url }
|
||||
let inputFiles = swiftTarget.sourceFiles(withSuffix: ".proto").map { $0.url }
|
||||
|
@ -41,7 +40,6 @@ import XcodeProjectPlugin
|
|||
|
||||
// Entry-point when using Xcode projects
|
||||
extension GRPCProtobufGenerator: XcodeBuildToolPlugin {
|
||||
/// Create build commands, the entry-point when using an Xcode project.
|
||||
func createBuildCommands(context: XcodePluginContext, target: XcodeTarget) throws -> [Command] {
|
||||
let configFiles = target.inputFiles.filter {
|
||||
$0.url.lastPathComponent == configFileName
|
||||
|
@ -62,7 +60,7 @@ extension GRPCProtobufGenerator: XcodeBuildToolPlugin {
|
|||
|
||||
@main
|
||||
struct GRPCProtobufGenerator {
|
||||
/// Build plugin code common to both invocation types: package manifest Xcode project
|
||||
/// Build plugin common code
|
||||
func createBuildCommands(
|
||||
pluginWorkDirectory: URL,
|
||||
tool: (String) throws -> PluginContext.Tool,
|
||||
|
@ -72,13 +70,13 @@ struct GRPCProtobufGenerator {
|
|||
) throws -> [Command] {
|
||||
let configs = try readConfigFiles(configFiles, pluginWorkDirectory: pluginWorkDirectory)
|
||||
|
||||
let protocGenGRPCSwiftPath = try tool("protoc-gen-grpc-swift").url
|
||||
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 PluginError.noConfigFilesFound
|
||||
throw BuildPluginError.noConfigFilesFound
|
||||
}
|
||||
|
||||
let protocPath = try deriveProtocPath(using: config, tool: tool)
|
||||
|
@ -90,7 +88,7 @@ struct GRPCProtobufGenerator {
|
|||
}
|
||||
|
||||
// unless *explicitly* opted-out
|
||||
if config.client || config.server {
|
||||
if config.clients || config.servers {
|
||||
let grpcCommand = try protocGenGRPCSwiftCommand(
|
||||
inputFile: inputFile,
|
||||
config: config,
|
||||
|
@ -104,7 +102,7 @@ struct GRPCProtobufGenerator {
|
|||
}
|
||||
|
||||
// unless *explicitly* opted-out
|
||||
if config.message {
|
||||
if config.messages {
|
||||
let protoCommand = try protocGenSwiftCommand(
|
||||
inputFile: inputFile,
|
||||
config: config,
|
||||
|
@ -167,16 +165,16 @@ extension [URL: GenerationConfig] {
|
|||
}
|
||||
}
|
||||
|
||||
/// Construct the command to invoke `protoc` with the `protoc-gen-grpc-swift` plugin.
|
||||
/// 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`.
|
||||
/// - 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` plugin.
|
||||
/// - Returns: The command to invoke `protoc` with the `protoc-gen-grpc-swift-2` plugin.
|
||||
func protocGenGRPCSwiftCommand(
|
||||
inputFile: URL,
|
||||
config: GenerationConfig,
|
||||
|
@ -189,7 +187,7 @@ func protocGenGRPCSwiftCommand(
|
|||
let outputPathURL = URL(fileURLWithPath: config.outputPath)
|
||||
|
||||
let outputFilePath = deriveOutputFilePath(
|
||||
for: inputFile,
|
||||
protoFile: inputFile,
|
||||
baseDirectoryPath: baseDirectoryPath,
|
||||
outputDirectory: outputPathURL,
|
||||
outputExtension: "grpc.swift"
|
||||
|
@ -224,7 +222,7 @@ func protocGenGRPCSwiftCommand(
|
|||
/// - 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`.
|
||||
/// - 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(
|
||||
|
@ -239,7 +237,7 @@ func protocGenSwiftCommand(
|
|||
let outputPathURL = URL(fileURLWithPath: config.outputPath)
|
||||
|
||||
let outputFilePath = deriveOutputFilePath(
|
||||
for: inputFile,
|
||||
protoFile: inputFile,
|
||||
baseDirectoryPath: baseDirectoryPath,
|
||||
outputDirectory: outputPathURL,
|
||||
outputExtension: "pb.swift"
|
||||
|
@ -267,28 +265,32 @@ func protocGenSwiftCommand(
|
|||
)
|
||||
}
|
||||
|
||||
/// Derive the expected output file path to match the behavior of the `protoc-gen-swift` and `protoc-gen-grpc-swift` `protoc` plugins
|
||||
/// when using the `FullPath` naming scheme.
|
||||
/// 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:
|
||||
/// - inputFile: The input `.proto` file.
|
||||
/// - baseDirectoryPath: The root path to the source `.proto` files used as the reference for relative path naming schemes.
|
||||
/// - 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(
|
||||
for inputFile: URL,
|
||||
protoFile: URL,
|
||||
baseDirectoryPath: URL,
|
||||
outputDirectory: URL,
|
||||
outputExtension: String
|
||||
) -> URL {
|
||||
// The name of the output file is based on the name of the input file.
|
||||
// We validated in the beginning that every file has the suffix of .proto
|
||||
// This means we can just drop the last 5 elements and append the new suffix
|
||||
let lastPathComponentRoot = inputFile.lastPathComponent.dropLast(5)
|
||||
let lastPathComponent = String(lastPathComponentRoot + outputExtension)
|
||||
// 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 = inputFile.deletingLastPathComponent().pathComponents
|
||||
var relativePathComponents = protoFile.deletingLastPathComponent().pathComponents
|
||||
for protoDirectoryPathComponent in baseDirectoryPath.pathComponents {
|
||||
if relativePathComponents.first == protoDirectoryPathComponent {
|
||||
relativePathComponents.removeFirst()
|
||||
|
@ -297,10 +299,7 @@ func deriveOutputFilePath(
|
|||
}
|
||||
}
|
||||
|
||||
let outputFileComponents = relativePathComponents + [lastPathComponent]
|
||||
var outputFilePath = outputDirectory
|
||||
for outputFileComponent in outputFileComponents {
|
||||
outputFilePath.append(component: outputFileComponent)
|
||||
}
|
||||
return outputFilePath
|
||||
relativePathComponents.append(fileName)
|
||||
let path = relativePathComponents.joined(separator: "_")
|
||||
return outputDirectory.appending(path: path)
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -32,7 +32,7 @@ struct GenerationConfig {
|
|||
/// - `FullPath`: `foo/bar/baz.grpc.swift`
|
||||
/// - `PathToUnderscore`: `foo_bar_baz.grpc.swift`
|
||||
/// - `DropPath`: `baz.grpc.swift`
|
||||
enum FileNaming: String, Codable {
|
||||
enum FileNaming: String {
|
||||
/// Replicate the input file path with the output file(s).
|
||||
case fullPath = "FullPath"
|
||||
/// Convert path directory delimiters to underscores.
|
||||
|
@ -42,13 +42,13 @@ struct GenerationConfig {
|
|||
}
|
||||
|
||||
/// The visibility of the generated files.
|
||||
var visibility: AccessLevel
|
||||
var accessLevel: AccessLevel
|
||||
/// Whether server code is generated.
|
||||
var server: Bool
|
||||
var servers: Bool
|
||||
/// Whether client code is generated.
|
||||
var client: Bool
|
||||
var clients: Bool
|
||||
/// Whether message code is generated.
|
||||
var message: Bool
|
||||
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.
|
||||
|
@ -83,3 +83,18 @@ extension GenerationConfig.AccessLevel: Codable {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
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`.
|
||||
|
@ -63,7 +65,7 @@ func constructProtocGenSwiftArguments(
|
|||
protocArgs.append("--proto_path=\(path)")
|
||||
}
|
||||
|
||||
protocArgs.append("--swift_opt=Visibility=\(config.visibility.rawValue)")
|
||||
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 })
|
||||
|
@ -71,15 +73,15 @@ func constructProtocGenSwiftArguments(
|
|||
return protocArgs
|
||||
}
|
||||
|
||||
/// Construct the arguments to be passed to `protoc` when invoking the `protoc-gen-grpc-swift` `protoc` plugin.
|
||||
/// 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` `protoc` plugin.
|
||||
/// - 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` `protoc` plugin.
|
||||
/// - 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?,
|
||||
|
@ -97,9 +99,9 @@ func constructProtocGenGRPCSwiftArguments(
|
|||
protocArgs.append("--proto_path=\(path)")
|
||||
}
|
||||
|
||||
protocArgs.append("--grpc-swift_opt=Visibility=\(config.visibility.rawValue.capitalized)")
|
||||
protocArgs.append("--grpc-swift_opt=Server=\(config.server)")
|
||||
protocArgs.append("--grpc-swift_opt=Client=\(config.client)")
|
||||
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 })
|
||||
|
@ -117,3 +119,14 @@ extension URL {
|
|||
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-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
|
||||
|
|
|
@ -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
|
||||
|
||||
/// Serializes a Protobuf message into a sequence of bytes.
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
public struct ProtobufSerializer<Message: SwiftProtobuf.Message>: GRPCCore.MessageSerializer {
|
||||
public init() {}
|
||||
|
||||
|
@ -41,6 +42,7 @@ public struct ProtobufSerializer<Message: SwiftProtobuf.Message>: GRPCCore.Messa
|
|||
}
|
||||
|
||||
/// Deserializes a sequence of bytes into a Protobuf message.
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
public struct ProtobufDeserializer<Message: SwiftProtobuf.Message>: GRPCCore.MessageDeserializer {
|
||||
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
|
||||
/// refine `SwiftProtobufContiguousBytes` for the same reason.
|
||||
@usableFromInline
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
struct ContiguousBytesAdapter<
|
||||
Bytes: GRPCContiguousBytes
|
||||
>: GRPCContiguousBytes, SwiftProtobufContiguousBytes {
|
||||
|
|
|
@ -9,7 +9,7 @@ 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` plugin for `protoc`).
|
||||
- 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
|
||||
|
@ -73,6 +73,6 @@ 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` and
|
||||
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`).
|
||||
|
|
|
@ -56,18 +56,18 @@ You must name the file `grpc-swift-proto-generator-config.json` and structure it
|
|||
"generate": {
|
||||
"clients": true,
|
||||
"servers": true,
|
||||
"messages": true,
|
||||
"messages": true
|
||||
},
|
||||
"generatedSource": {
|
||||
"accessLevelOnImports": false,
|
||||
"accessLevel": "internal",
|
||||
}
|
||||
"protoc": {
|
||||
"executablePath": "/opt/homebrew/bin/protoc"
|
||||
"importPaths": [
|
||||
"../directory_1",
|
||||
],
|
||||
"accessLevel": "internal"
|
||||
},
|
||||
"protoc": {
|
||||
"executablePath": "/opt/homebrew/bin/protoc",
|
||||
"importPaths": [
|
||||
"../directory_1"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -87,7 +87,7 @@ The options do not need to be specified and each have default values.
|
|||
|
||||
‡ 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` and `protoc-gen-swift` options.
|
||||
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
|
||||
|
@ -96,73 +96,82 @@ 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`, a program which is a plugin for the Protocol Buffers compiler, `protoc`.
|
||||
`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_out=<DIRECTORY>` option:
|
||||
the `--grpc-swift-2_out=<DIRECTORY>` option:
|
||||
|
||||
```console
|
||||
protoc --grpc-swift_out=. my-service.proto
|
||||
protoc --grpc-swift-2_out=. my-service.proto
|
||||
```
|
||||
|
||||
> `protoc-gen-grpc-swift` only generates gRPC stubs, it doesn't generate messages. You must use
|
||||
> `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_out` tells `protoc` to use the `protoc-gen-grpc-swift` plugin. By
|
||||
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 --grpc-swift_out=. my-service.proto
|
||||
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` via `protoc` using
|
||||
the `--grpc-swift_opt` argument:
|
||||
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_opt=<OPTION_NAME>=<OPTION_VALUE> --grpc-swift_out=.
|
||||
protoc --grpc-swift-2_opt=<OPTION_NAME>=<OPTION_VALUE> --grpc-swift-2_out=.
|
||||
```
|
||||
|
||||
You can specify multiple options by passing the `--grpc-swift_opt` argument multiple times:
|
||||
You can specify multiple options by passing the `--grpc-swift-2_opt` argument multiple times:
|
||||
|
||||
```console
|
||||
protoc \
|
||||
--grpc-swift_opt=<OPTION_NAME1>=<OPTION_VALUE1> \
|
||||
--grpc-swift_opt=<OPTION_NAME2>=<OPTION_VALUE2> \
|
||||
--grpc-swift_out=.
|
||||
--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`, `PathToUnderscore`, `DropPath` | `FullPath` | How generated source files should be named. (See below.) |
|
||||
| `ProtoPathModuleMappings` | | | Path to module map `.asciipb` file. (See below.) |
|
||||
| `UseAccessLevelOnImports` | `True`, `False` | `False` | Whether imports should have explicit access levels. |
|
||||
| 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
|
||||
† 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`.
|
||||
- `PathToUnderscore`: `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`
|
||||
‡ 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` you use mustn't be newer than the version of
|
||||
> 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`
|
||||
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
|
||||
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
|
||||
|
|
|
@ -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.
|
|
@ -1,12 +1,12 @@
|
|||
# Understanding the generated code
|
||||
|
||||
Understand what code is generated by `protoc-gen-grpc-swift` from a `.proto`
|
||||
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`. The plugin is responsible
|
||||
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
|
||||
|
|
|
@ -7,7 +7,7 @@ A package integrating Swift Protobuf with gRPC Swift.
|
|||
This package provides three products:
|
||||
- ``GRPCProtobuf``, a module providing runtime serialization and deserialization components for
|
||||
[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
|
||||
`grpc-swift` documentation on the [Swift Package
|
||||
Index](https://swiftpackageindex.com/grpc/grpc-swift/documentation).
|
||||
|
@ -23,6 +23,7 @@ This package provides three products:
|
|||
- <doc:Generating-stubs>
|
||||
- <doc:API-stability-of-generated-code>
|
||||
- <doc:Understanding-the-generated-code>
|
||||
- <doc:Public-services-with-private-implementations>
|
||||
|
||||
### Serialization
|
||||
|
||||
|
|
|
@ -61,6 +61,7 @@ extension Google_Protobuf_Any {
|
|||
}
|
||||
}
|
||||
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
extension ErrorDetails {
|
||||
// Note: this type isn't packable into an 'Any' protobuf so doesn't conform
|
||||
// to 'GoogleProtobufAnyPackable' despite holding types which are packable.
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
extension ErrorDetails: CustomStringConvertible {
|
||||
public var description: String {
|
||||
switch self.wrapped {
|
||||
|
@ -46,54 +47,63 @@ extension ErrorDetails: CustomStringConvertible {
|
|||
// Some errors use protobuf messages as their storage so the default description isn't
|
||||
// representative
|
||||
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
extension ErrorDetails.ErrorInfo: CustomStringConvertible {
|
||||
public var description: String {
|
||||
"\(Self.self)(reason: \"\(self.reason)\", domain: \"\(self.domain)\", metadata: \(self.metadata))"
|
||||
}
|
||||
}
|
||||
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
extension ErrorDetails.DebugInfo: CustomStringConvertible {
|
||||
public var description: String {
|
||||
"\(Self.self)(stack: \(self.stack), detail: \"\(self.detail)\")"
|
||||
}
|
||||
}
|
||||
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
extension ErrorDetails.QuotaFailure.Violation: CustomStringConvertible {
|
||||
public var description: String {
|
||||
"\(Self.self)(subject: \"\(self.subject)\", violationDescription: \"\(self.violationDescription)\")"
|
||||
}
|
||||
}
|
||||
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
extension ErrorDetails.PreconditionFailure.Violation: CustomStringConvertible {
|
||||
public var description: String {
|
||||
"\(Self.self)(subject: \"\(self.subject)\", type: \"\(self.type)\", violationDescription: \"\(self.violationDescription)\")"
|
||||
}
|
||||
}
|
||||
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
extension ErrorDetails.BadRequest.FieldViolation: CustomStringConvertible {
|
||||
public var description: String {
|
||||
"\(Self.self)(field: \"\(self.field)\", violationDescription: \"\(self.violationDescription)\")"
|
||||
}
|
||||
}
|
||||
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
extension ErrorDetails.RequestInfo: CustomStringConvertible {
|
||||
public var description: String {
|
||||
"\(Self.self)(requestID: \"\(self.requestID)\", servingData: \"\(self.servingData)\")"
|
||||
}
|
||||
}
|
||||
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
extension ErrorDetails.ResourceInfo: CustomStringConvertible {
|
||||
public var description: String {
|
||||
"\(Self.self)(name: \"\(self.name)\", owner: \"\(self.owner)\", type: \"\(self.type)\", errorDescription: \"\(self.errorDescription)\")"
|
||||
}
|
||||
}
|
||||
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
extension ErrorDetails.Help.Link: CustomStringConvertible {
|
||||
public var description: String {
|
||||
"\(Self.self)(url: \"\(self.url)\", linkDescription: \"\(self.linkDescription)\")"
|
||||
}
|
||||
}
|
||||
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
extension ErrorDetails.LocalizedMessage: CustomStringConvertible {
|
||||
public var description: String {
|
||||
"\(Self.self)(locale: \"\(self.locale)\", message: \"\(self.message)\")"
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
internal import SwiftProtobuf
|
||||
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
extension ErrorDetails {
|
||||
/// 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"
|
||||
/// protobuf (`Google_Protobuf_Any`).
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
public struct ErrorDetails: Sendable, Hashable {
|
||||
enum Wrapped: Sendable, Hashable {
|
||||
case errorInfo(ErrorInfo)
|
||||
|
@ -198,6 +199,7 @@ public struct ErrorDetails: Sendable, Hashable {
|
|||
}
|
||||
}
|
||||
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
extension ErrorDetails {
|
||||
/// Returns error info if set.
|
||||
public var errorInfo: ErrorInfo? {
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
|
||||
public import GRPCCore
|
||||
internal import SwiftProtobuf
|
||||
public import SwiftProtobuf
|
||||
|
||||
/// 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
|
||||
/// > 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
|
||||
/// > is a "google.rpc.Status" protocol buffers message containing the status code, message, and
|
||||
/// > details.
|
||||
public struct GoogleRPCStatus: Error {
|
||||
/// > the serialized bytes of a "google.rpc.Status" protocol buffers message containing the status
|
||||
/// > code, message, and details.
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
public struct GoogleRPCStatus: Error, Hashable {
|
||||
/// A code representing the high-level domain of the error.
|
||||
public var code: RPCError.Code
|
||||
|
||||
|
@ -74,13 +74,33 @@ public struct GoogleRPCStatus: Error {
|
|||
}
|
||||
}
|
||||
|
||||
extension GoogleRPCStatus: GoogleProtobufAnyPackable {
|
||||
// See https://protobuf.dev/programming-guides/proto3/#any
|
||||
internal static var typeURL: String { "type.googleapis.com/google.rpc.Status" }
|
||||
|
||||
init?(unpacking any: Google_Protobuf_Any) throws {
|
||||
guard any.isA(Google_Rpc_Status.self) else { return nil }
|
||||
let status = try Google_Rpc_Status(serializedBytes: any.value)
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
extension GoogleRPCStatus {
|
||||
/// Creates a new message by decoding the given `SwiftProtobufContiguousBytes` value
|
||||
/// containing a serialized message in Protocol Buffer binary format.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - 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))
|
||||
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) }
|
||||
}
|
||||
|
||||
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 {
|
||||
$0.code = Int32(self.code.rawValue)
|
||||
$0.message = self.message
|
||||
$0.details = try self.details.map { try $0.pack() }
|
||||
}
|
||||
|
||||
return try .with {
|
||||
$0.typeURL = Self.typeURL
|
||||
$0.value = try status.serializedBytes()
|
||||
}
|
||||
return try status.serializedBytes(partial: partial, options: options)
|
||||
}
|
||||
}
|
||||
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
extension GoogleRPCStatus: RPCErrorConvertible {
|
||||
public var rpcErrorCode: RPCError.Code { self.code }
|
||||
public var rpcErrorMessage: String { self.message }
|
||||
public var rpcErrorMetadata: Metadata {
|
||||
do {
|
||||
let any = try self.pack()
|
||||
let bytes: [UInt8] = try any.serializedBytes()
|
||||
let bytes: [UInt8] = try self.serializedBytes()
|
||||
return [Metadata.statusDetailsBinKey: .binary(bytes)]
|
||||
} catch {
|
||||
// Failed to serialize error details. Not a lot can be done here.
|
||||
|
|
|
@ -17,10 +17,12 @@
|
|||
public import GRPCCore
|
||||
internal import SwiftProtobuf
|
||||
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
extension Metadata {
|
||||
static let statusDetailsBinKey = "grpc-status-details-bin"
|
||||
}
|
||||
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
extension RPCError {
|
||||
/// Unpack a ``GoogleRPCStatus`` error from the error metadata.
|
||||
///
|
||||
|
@ -31,8 +33,6 @@ extension RPCError {
|
|||
public func unpackGoogleRPCStatus() throws -> GoogleRPCStatus? {
|
||||
let values = self.metadata[binaryValues: Metadata.statusDetailsBinKey]
|
||||
guard let bytes = values.first(where: { _ in true }) else { return nil }
|
||||
|
||||
let any = try Google_Protobuf_Any(serializedBytes: bytes)
|
||||
return try GoogleRPCStatus(unpacking: any)
|
||||
return try GoogleRPCStatus(serializedBytes: bytes)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,19 +32,23 @@ internal import struct Foundation.IndexPath
|
|||
#endif
|
||||
|
||||
/// Parses a ``FileDescriptor`` object into a ``CodeGenerationRequest`` object.
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
package struct ProtobufCodeGenParser {
|
||||
let extraModuleImports: [String]
|
||||
let protoToModuleMappings: ProtoFileToModuleMappings
|
||||
let accessLevel: CodeGenerator.Config.AccessLevel
|
||||
let moduleNames: ProtobufCodeGenerator.Config.ModuleNames
|
||||
|
||||
package init(
|
||||
protoFileModuleMappings: ProtoFileToModuleMappings,
|
||||
extraModuleImports: [String],
|
||||
accessLevel: CodeGenerator.Config.AccessLevel
|
||||
accessLevel: CodeGenerator.Config.AccessLevel,
|
||||
moduleNames: ProtobufCodeGenerator.Config.ModuleNames
|
||||
) {
|
||||
self.extraModuleImports = extraModuleImports
|
||||
self.protoToModuleMappings = protoFileModuleMappings
|
||||
self.accessLevel = accessLevel
|
||||
self.moduleNames = moduleNames
|
||||
}
|
||||
|
||||
package func parse(descriptor: FileDescriptor) throws -> CodeGenerationRequest {
|
||||
|
@ -62,6 +66,7 @@ package struct ProtobufCodeGenParser {
|
|||
let leadingTrivia = """
|
||||
// DO NOT EDIT.
|
||||
// swift-format-ignore-file
|
||||
// swiftlint:disable all
|
||||
//
|
||||
// Generated by the gRPC Swift generator plugin for the protocol buffer compiler.
|
||||
// Source: \(descriptor.name)
|
||||
|
@ -86,15 +91,16 @@ package struct ProtobufCodeGenParser {
|
|||
dependencies: self.codeDependencies(file: descriptor),
|
||||
services: services,
|
||||
makeSerializerCodeSnippet: { messageType in
|
||||
"GRPCProtobuf.ProtobufSerializer<\(messageType)>()"
|
||||
"\(self.moduleNames.grpcProtobuf).ProtobufSerializer<\(messageType)>()"
|
||||
},
|
||||
makeDeserializerCodeSnippet: { messageType in
|
||||
"GRPCProtobuf.ProtobufDeserializer<\(messageType)>()"
|
||||
"\(self.moduleNames.grpcProtobuf).ProtobufDeserializer<\(messageType)>()"
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
extension ProtobufCodeGenParser {
|
||||
fileprivate func codeDependencies(file: FileDescriptor) -> [Dependency] {
|
||||
guard file.services.count > 0 else {
|
||||
|
@ -102,7 +108,7 @@ extension ProtobufCodeGenParser {
|
|||
}
|
||||
|
||||
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.
|
||||
//
|
||||
|
@ -113,7 +119,11 @@ extension ProtobufCodeGenParser {
|
|||
}
|
||||
|
||||
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
|
||||
|
@ -133,6 +143,7 @@ extension ProtobufCodeGenParser {
|
|||
}
|
||||
}
|
||||
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
extension GRPCCodeGen.ServiceDescriptor {
|
||||
fileprivate init(
|
||||
descriptor: SwiftProtobufPluginLibrary.ServiceDescriptor,
|
||||
|
@ -160,6 +171,7 @@ extension GRPCCodeGen.ServiceDescriptor {
|
|||
}
|
||||
}
|
||||
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
extension GRPCCodeGen.MethodDescriptor {
|
||||
fileprivate init(
|
||||
descriptor: SwiftProtobufPluginLibrary.MethodDescriptor,
|
||||
|
|
|
@ -17,11 +17,12 @@
|
|||
package import GRPCCodeGen
|
||||
package import SwiftProtobufPluginLibrary
|
||||
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
package struct ProtobufCodeGenerator {
|
||||
internal var config: GRPCCodeGen.CodeGenerator.Config
|
||||
internal var config: ProtobufCodeGenerator.Config
|
||||
|
||||
package init(
|
||||
config: GRPCCodeGen.CodeGenerator.Config
|
||||
config: ProtobufCodeGenerator.Config
|
||||
) {
|
||||
self.config = config
|
||||
}
|
||||
|
@ -29,17 +30,76 @@ package struct ProtobufCodeGenerator {
|
|||
package func generateCode(
|
||||
fileDescriptor: FileDescriptor,
|
||||
protoFileModuleMappings: ProtoFileToModuleMappings,
|
||||
extraModuleImports: [String]
|
||||
extraModuleImports: [String],
|
||||
availabilityOverrides: [(os: String, version: String)] = []
|
||||
) throws -> String {
|
||||
let parser = ProtobufCodeGenParser(
|
||||
protoFileModuleMappings: protoFileModuleMappings,
|
||||
extraModuleImports: extraModuleImports,
|
||||
accessLevel: self.config.accessLevel
|
||||
accessLevel: self.config.accessLevel,
|
||||
moduleNames: self.config.moduleNames
|
||||
)
|
||||
let codeGenerator = GRPCCodeGen.CodeGenerator(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 sourceFile = try codeGenerator.generate(codeGenerationRequest)
|
||||
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,6 +26,7 @@ import Foundation
|
|||
#endif
|
||||
|
||||
@main
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
final class GenerateGRPC: SwiftProtobufPluginLibrary.CodeGenerator {
|
||||
var version: String? {
|
||||
Version.versionString
|
||||
|
@ -55,37 +56,10 @@ final class GenerateGRPC: SwiftProtobufPluginLibrary.CodeGenerator {
|
|||
let options = try GeneratorOptions(parameter: parameter)
|
||||
|
||||
for descriptor in fileDescriptors {
|
||||
if options.generateReflectionData {
|
||||
try self.generateReflectionData(
|
||||
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(
|
||||
_ descriptor: FileDescriptor,
|
||||
options: GeneratorOptions,
|
||||
|
@ -96,18 +70,19 @@ final class GenerateGRPC: SwiftProtobufPluginLibrary.CodeGenerator {
|
|||
fileNamingOption: options.fileNaming
|
||||
)
|
||||
|
||||
let config = CodeGenerator.Config(options: options)
|
||||
let fileGenerator = ProtobufCodeGenerator(config: config)
|
||||
let fileGenerator = ProtobufCodeGenerator(config: options.config)
|
||||
let contents = try fileGenerator.generateCode(
|
||||
fileDescriptor: descriptor,
|
||||
protoFileModuleMappings: options.protoToModuleMappings,
|
||||
extraModuleImports: options.extraModuleImports
|
||||
extraModuleImports: options.extraModuleImports,
|
||||
availabilityOverrides: options.availabilityOverrides
|
||||
)
|
||||
|
||||
try outputs.add(fileName: fileName, contents: contents)
|
||||
}
|
||||
}
|
||||
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
extension GenerateGRPC {
|
||||
private func uniqueOutputFileName(
|
||||
fileDescriptor: FileDescriptor,
|
||||
|
@ -181,24 +156,3 @@ private func splitPath(pathname: String) -> (dir: String, base: String, suffix:
|
|||
}
|
||||
return (dir: dir, base: base, suffix: suffix)
|
||||
}
|
||||
|
||||
extension GRPCCodeGen.CodeGenerator.Config {
|
||||
init(options: GeneratorOptions) {
|
||||
let accessLevel: GRPCCodeGen.CodeGenerator.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.
|
||||
*/
|
||||
|
||||
import GRPCCodeGen
|
||||
import GRPCProtobufCodeGen
|
||||
import SwiftProtobufPluginLibrary
|
||||
|
||||
enum GenerationError: Error, CustomStringConvertible {
|
||||
|
@ -23,6 +25,8 @@ enum GenerationError: Error, CustomStringConvertible {
|
|||
case invalidParameterValue(name: String, value: String)
|
||||
/// Raised to wrap another error but provide a context message.
|
||||
case wrappedError(message: String, error: any Error)
|
||||
/// The parameter isn't supported.
|
||||
case unsupportedParameter(name: String, message: String)
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
|
@ -32,6 +36,8 @@ enum GenerationError: Error, CustomStringConvertible {
|
|||
return "Unknown value for generation parameter '\(name)': '\(value)'"
|
||||
case let .wrappedError(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"
|
||||
}
|
||||
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
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 fileNaming = FileNaming.fullPath
|
||||
private(set) var extraModuleImports: [String] = []
|
||||
private(set) var gRPCModuleName = "GRPC"
|
||||
private(set) var swiftProtobufModuleName = "SwiftProtobuf"
|
||||
private(set) var generateReflectionData = false
|
||||
private(set) var useAccessLevelOnImports = false
|
||||
private(set) var availabilityOverrides: [(os: String, version: String)] = []
|
||||
|
||||
private(set) var config: ProtobufCodeGenerator.Config = .defaults
|
||||
|
||||
init(parameter: any CodeGeneratorParameter) throws {
|
||||
try self.init(pairs: parameter.parsedPairs)
|
||||
|
@ -81,22 +65,22 @@ struct GeneratorOptions {
|
|||
for pair in pairs {
|
||||
switch pair.key {
|
||||
case "Visibility":
|
||||
if let value = Visibility(rawValue: pair.value) {
|
||||
self.visibility = value
|
||||
if let value = GRPCCodeGen.CodeGenerator.Config.AccessLevel(protocOption: pair.value) {
|
||||
self.config.accessLevel = value
|
||||
} else {
|
||||
throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
|
||||
}
|
||||
|
||||
case "Server":
|
||||
if let value = Bool(pair.value.lowercased()) {
|
||||
self.generateServer = value
|
||||
self.config.generateServer = value
|
||||
} else {
|
||||
throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
|
||||
}
|
||||
|
||||
case "Client":
|
||||
if let value = Bool(pair.value.lowercased()) {
|
||||
self.generateClient = value
|
||||
self.config.generateClient = value
|
||||
} else {
|
||||
throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
|
||||
}
|
||||
|
@ -129,28 +113,49 @@ struct GeneratorOptions {
|
|||
|
||||
case "GRPCModuleName":
|
||||
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 {
|
||||
throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
|
||||
}
|
||||
|
||||
case "SwiftProtobufModuleName":
|
||||
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 {
|
||||
throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
|
||||
}
|
||||
|
||||
case "ReflectionData":
|
||||
if let value = Bool(pair.value.lowercased()) {
|
||||
self.generateReflectionData = value
|
||||
} else {
|
||||
throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
|
||||
}
|
||||
throw GenerationError.unsupportedParameter(
|
||||
name: pair.key,
|
||||
message: """
|
||||
The reflection service uses descriptor sets. Refer to the protoc docs and the \
|
||||
'--descriptor_set_out' option for more information.
|
||||
"""
|
||||
)
|
||||
|
||||
case "UseAccessLevelOnImports":
|
||||
if let value = Bool(pair.value.lowercased()) {
|
||||
self.useAccessLevelOnImports = value
|
||||
self.config.accessLevelOnImports = value
|
||||
} else {
|
||||
throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
|
||||
}
|
||||
|
@ -187,6 +192,7 @@ struct GeneratorOptions {
|
|||
}
|
||||
}
|
||||
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
extension String.SubSequence {
|
||||
func trimmingWhitespaceAndNewlines() -> String {
|
||||
let trimmedSuffix = self.drop(while: { $0.isNewline || $0.isWhitespace })
|
||||
|
@ -194,3 +200,19 @@ extension String.SubSequence {
|
|||
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.
|
||||
*/
|
||||
|
||||
#if canImport(CGRPCProtobuf)
|
||||
private import CGRPCProtobuf
|
||||
#endif
|
||||
|
||||
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.
|
||||
internal static var versionString: String {
|
||||
let version = "\(Self.major).\(Self.minor).\(Self.patch)"
|
||||
if Self.label.isEmpty {
|
||||
return version
|
||||
} else {
|
||||
return version + "-" + Self.label
|
||||
}
|
||||
#if canImport(CGRPCProtobuf)
|
||||
String(cString: cgrprc_grpc_swift_protobuf_version())
|
||||
#else
|
||||
"unknown"
|
||||
#endif
|
||||
}
|
||||
}
|
Binary file not shown.
|
@ -27,25 +27,29 @@ struct ProtobufCodeGenParserTests {
|
|||
static let descriptorSetName = "test-service"
|
||||
static let fileDescriptorName = "test-service"
|
||||
|
||||
let codeGen: CodeGenerationRequest
|
||||
|
||||
init() throws {
|
||||
let descriptor = try #require(try Self.fileDescriptor)
|
||||
self.codeGen = try parseDescriptor(descriptor)
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
var codeGen: CodeGenerationRequest {
|
||||
get throws {
|
||||
let descriptor = try Self.fileDescriptor
|
||||
return try parseDescriptor(descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
@Test("Filename")
|
||||
func fileName() {
|
||||
#expect(self.codeGen.fileName == "test-service.proto")
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
func fileName() throws {
|
||||
#expect(try self.codeGen.fileName == "test-service.proto")
|
||||
}
|
||||
|
||||
@Test("Leading trivia")
|
||||
func leadingTrivia() {
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
func leadingTrivia() throws {
|
||||
let expected = """
|
||||
/// Leading trivia.
|
||||
|
||||
// DO NOT EDIT.
|
||||
// swift-format-ignore-file
|
||||
// swiftlint:disable all
|
||||
//
|
||||
// Generated by the gRPC Swift generator plugin for the protocol buffer compiler.
|
||||
// Source: test-service.proto
|
||||
|
@ -55,94 +59,112 @@ struct ProtobufCodeGenParserTests {
|
|||
|
||||
"""
|
||||
|
||||
#expect(self.codeGen.leadingTrivia == expected)
|
||||
#expect(try self.codeGen.leadingTrivia == expected)
|
||||
}
|
||||
|
||||
@Test("Dependencies")
|
||||
func dependencies() {
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
func dependencies() throws {
|
||||
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")
|
||||
struct Service {
|
||||
let service: GRPCCodeGen.ServiceDescriptor
|
||||
|
||||
init() throws {
|
||||
let request = try parseDescriptor(try #require(try TestService.fileDescriptor))
|
||||
try #require(request.services.count == 1)
|
||||
self.service = try #require(request.services.first)
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
var service: GRPCCodeGen.ServiceDescriptor {
|
||||
get throws {
|
||||
let request = try parseDescriptor(try TestService.fileDescriptor)
|
||||
try #require(request.services.count == 1)
|
||||
return try #require(request.services.first)
|
||||
}
|
||||
}
|
||||
|
||||
@Test("Name")
|
||||
func name() {
|
||||
#expect(self.service.name.identifyingName == "test.TestService")
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
func name() throws {
|
||||
#expect(try self.service.name.identifyingName == "test.TestService")
|
||||
}
|
||||
|
||||
@Suite("Methods")
|
||||
struct Methods {
|
||||
let unary: GRPCCodeGen.MethodDescriptor
|
||||
let clientStreaming: GRPCCodeGen.MethodDescriptor
|
||||
let serverStreaming: GRPCCodeGen.MethodDescriptor
|
||||
let bidiStreaming: GRPCCodeGen.MethodDescriptor
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
var service: GRPCCodeGen.ServiceDescriptor {
|
||||
get throws {
|
||||
let request = try parseDescriptor(try TestService.fileDescriptor)
|
||||
try #require(request.services.count == 1)
|
||||
return try #require(request.services.first)
|
||||
}
|
||||
}
|
||||
|
||||
init() throws {
|
||||
let request = try parseDescriptor(try #require(try TestService.fileDescriptor))
|
||||
#expect(request.services.count == 1)
|
||||
let service = try #require(request.services.first)
|
||||
|
||||
self.unary = service.methods[0]
|
||||
self.clientStreaming = service.methods[1]
|
||||
self.serverStreaming = service.methods[2]
|
||||
self.bidiStreaming = service.methods[3]
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
var unary: GRPCCodeGen.MethodDescriptor {
|
||||
get throws { try self.service.methods[0] }
|
||||
}
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
var clientStreaming: GRPCCodeGen.MethodDescriptor {
|
||||
get throws { try self.service.methods[1] }
|
||||
}
|
||||
@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")
|
||||
func documentation() {
|
||||
#expect(self.unary.documentation == "/// Unary docs.\n")
|
||||
#expect(self.clientStreaming.documentation == "/// Client streaming docs.\n")
|
||||
#expect(self.serverStreaming.documentation == "/// Server streaming docs.\n")
|
||||
#expect(self.bidiStreaming.documentation == "/// Bidirectional streaming docs.\n")
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
func documentation() throws {
|
||||
#expect(try self.unary.documentation == "/// Unary docs.\n")
|
||||
#expect(try self.clientStreaming.documentation == "/// Client streaming docs.\n")
|
||||
#expect(try self.serverStreaming.documentation == "/// Server streaming docs.\n")
|
||||
#expect(try self.bidiStreaming.documentation == "/// Bidirectional streaming docs.\n")
|
||||
}
|
||||
|
||||
@Test("Name")
|
||||
func name() {
|
||||
#expect(self.unary.name.identifyingName == "Unary")
|
||||
#expect(self.clientStreaming.name.identifyingName == "ClientStreaming")
|
||||
#expect(self.serverStreaming.name.identifyingName == "ServerStreaming")
|
||||
#expect(self.bidiStreaming.name.identifyingName == "BidirectionalStreaming")
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
func name() throws {
|
||||
try #expect(self.unary.name.identifyingName == "Unary")
|
||||
try #expect(self.clientStreaming.name.identifyingName == "ClientStreaming")
|
||||
try #expect(self.serverStreaming.name.identifyingName == "ServerStreaming")
|
||||
try #expect(self.bidiStreaming.name.identifyingName == "BidirectionalStreaming")
|
||||
}
|
||||
|
||||
@Test("Input")
|
||||
func input() {
|
||||
#expect(self.unary.inputType == "Test_TestInput")
|
||||
#expect(!self.unary.isInputStreaming)
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
func input() throws {
|
||||
#expect(try self.unary.inputType == "Test_TestInput")
|
||||
#expect(try !self.unary.isInputStreaming)
|
||||
|
||||
#expect(self.clientStreaming.inputType == "Test_TestInput")
|
||||
#expect(self.clientStreaming.isInputStreaming)
|
||||
#expect(try self.clientStreaming.inputType == "Test_TestInput")
|
||||
#expect(try self.clientStreaming.isInputStreaming)
|
||||
|
||||
#expect(self.serverStreaming.inputType == "Test_TestInput")
|
||||
#expect(!self.serverStreaming.isInputStreaming)
|
||||
#expect(try self.serverStreaming.inputType == "Test_TestInput")
|
||||
#expect(try !self.serverStreaming.isInputStreaming)
|
||||
|
||||
#expect(self.bidiStreaming.inputType == "Test_TestInput")
|
||||
#expect(self.bidiStreaming.isInputStreaming)
|
||||
#expect(try self.bidiStreaming.inputType == "Test_TestInput")
|
||||
#expect(try self.bidiStreaming.isInputStreaming)
|
||||
}
|
||||
|
||||
@Test("Output")
|
||||
func output() {
|
||||
#expect(self.unary.outputType == "Test_TestOutput")
|
||||
#expect(!self.unary.isOutputStreaming)
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
func output() throws {
|
||||
#expect(try self.unary.outputType == "Test_TestOutput")
|
||||
#expect(try !self.unary.isOutputStreaming)
|
||||
|
||||
#expect(self.clientStreaming.outputType == "Test_TestOutput")
|
||||
#expect(!self.clientStreaming.isOutputStreaming)
|
||||
#expect(try self.clientStreaming.outputType == "Test_TestOutput")
|
||||
#expect(try !self.clientStreaming.isOutputStreaming)
|
||||
|
||||
#expect(self.serverStreaming.outputType == "Test_TestOutput")
|
||||
#expect(self.serverStreaming.isOutputStreaming)
|
||||
#expect(try self.serverStreaming.outputType == "Test_TestOutput")
|
||||
#expect(try self.serverStreaming.isOutputStreaming)
|
||||
|
||||
#expect(self.bidiStreaming.outputType == "Test_TestOutput")
|
||||
#expect(self.bidiStreaming.isOutputStreaming)
|
||||
#expect(try self.bidiStreaming.outputType == "Test_TestOutput")
|
||||
#expect(try self.bidiStreaming.isOutputStreaming)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -153,51 +175,58 @@ struct ProtobufCodeGenParserTests {
|
|||
static let descriptorSetName = "foo-service"
|
||||
static let fileDescriptorName = "foo-service"
|
||||
|
||||
let codeGen: CodeGenerationRequest
|
||||
|
||||
init() throws {
|
||||
let descriptor = try #require(try Self.fileDescriptor)
|
||||
self.codeGen = try parseDescriptor(descriptor)
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
var codeGen: CodeGenerationRequest {
|
||||
get throws {
|
||||
let descriptor = try Self.fileDescriptor
|
||||
return try parseDescriptor(descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
@Test("Name")
|
||||
func name() {
|
||||
#expect(self.codeGen.fileName == "foo-service.proto")
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
func name() throws {
|
||||
#expect(try self.codeGen.fileName == "foo-service.proto")
|
||||
}
|
||||
|
||||
@Test("Dependencies")
|
||||
func dependencies() {
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
func dependencies() throws {
|
||||
let expected: [GRPCCodeGen.Dependency] = [
|
||||
.init(module: "GRPCProtobuf", accessLevel: .internal) // Always an internal import
|
||||
]
|
||||
#expect(self.codeGen.dependencies == expected)
|
||||
#expect(try self.codeGen.dependencies == expected)
|
||||
}
|
||||
|
||||
@Test("Service1")
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
func service1() throws {
|
||||
let service = self.codeGen.services[0]
|
||||
let service = try self.codeGen.services[0]
|
||||
#expect(service.name.identifyingName == "foo.FooService1")
|
||||
#expect(service.methods.count == 1)
|
||||
}
|
||||
|
||||
@Test("Service1.Method")
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
func service1Method() throws {
|
||||
let method = self.codeGen.services[0].methods[0]
|
||||
let method = try self.codeGen.services[0].methods[0]
|
||||
#expect(method.name.identifyingName == "Foo")
|
||||
#expect(method.inputType == "Foo_FooInput")
|
||||
#expect(method.outputType == "Foo_FooOutput")
|
||||
}
|
||||
|
||||
@Test("Service2")
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
func service2() throws {
|
||||
let service = self.codeGen.services[1]
|
||||
let service = try self.codeGen.services[1]
|
||||
#expect(service.name.identifyingName == "foo.FooService2")
|
||||
#expect(service.methods.count == 1)
|
||||
}
|
||||
|
||||
@Test("Service2.Method")
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
func service2Method() throws {
|
||||
let method = self.codeGen.services[1].methods[0]
|
||||
let method = try self.codeGen.services[1].methods[0]
|
||||
#expect(method.name.identifyingName == "Foo")
|
||||
#expect(method.inputType == "Foo_FooInput")
|
||||
#expect(method.outputType == "Foo_FooOutput")
|
||||
|
@ -209,18 +238,14 @@ struct ProtobufCodeGenParserTests {
|
|||
static var descriptorSetName: 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")
|
||||
func serviceName() {
|
||||
#expect(self.service.name.identifyingName == "BarService")
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
func serviceName() throws {
|
||||
let descriptor = try Self.fileDescriptor
|
||||
let codeGen = try parseDescriptor(descriptor)
|
||||
let service = try #require(codeGen.services.first)
|
||||
|
||||
#expect(service.name.identifyingName == "BarService")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -229,20 +254,17 @@ struct ProtobufCodeGenParserTests {
|
|||
static let descriptorSetName = "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")
|
||||
func dependencies() {
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
func dependencies() throws {
|
||||
let descriptor = try Self.fileDescriptor
|
||||
let codeGen = try parseDescriptor(descriptor)
|
||||
|
||||
let expected: [Dependency] = [
|
||||
Dependency(module: "GRPCProtobuf", 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 fileDescriptorName = "test-service"
|
||||
|
||||
@Test("Generate", arguments: [CodeGenerator.Config.AccessLevel.internal])
|
||||
func generate(accessLevel: GRPCCodeGen.CodeGenerator.Config.AccessLevel) throws {
|
||||
let generator = ProtobufCodeGenerator(
|
||||
config: CodeGenerator.Config(
|
||||
accessLevel: accessLevel,
|
||||
accessLevelOnImports: false,
|
||||
client: true,
|
||||
server: true,
|
||||
indentation: 2
|
||||
)
|
||||
)
|
||||
enum Availability {
|
||||
case `default`
|
||||
case fooOS
|
||||
|
||||
var override: [(String, String)] {
|
||||
switch self {
|
||||
case .default:
|
||||
return []
|
||||
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
|
||||
switch accessLevel {
|
||||
|
@ -49,10 +75,13 @@ struct ProtobufCodeGeneratorTests {
|
|||
fatalError()
|
||||
}
|
||||
|
||||
let expectedAvailability = availability.expected
|
||||
|
||||
let generated = try generator.generateCode(
|
||||
fileDescriptor: Self.fileDescriptor,
|
||||
protoFileModuleMappings: ProtoFileToModuleMappings(),
|
||||
extraModuleImports: []
|
||||
extraModuleImports: [],
|
||||
availabilityOverrides: availability.override
|
||||
)
|
||||
|
||||
let expected = """
|
||||
|
@ -60,6 +89,7 @@ struct ProtobufCodeGeneratorTests {
|
|||
|
||||
// DO NOT EDIT.
|
||||
// swift-format-ignore-file
|
||||
// swiftlint:disable all
|
||||
//
|
||||
// Generated by the gRPC Swift generator plugin for the protocol buffer compiler.
|
||||
// Source: test-service.proto
|
||||
|
@ -69,10 +99,12 @@ struct ProtobufCodeGeneratorTests {
|
|||
|
||||
import GRPCCore
|
||||
import GRPCProtobuf
|
||||
import SwiftProtobuf
|
||||
|
||||
// MARK: - test.TestService
|
||||
|
||||
/// Namespace containing generated types for the "test.TestService" service.
|
||||
@available(\(expectedAvailability), *)
|
||||
\(access) enum Test_TestService {
|
||||
/// Service descriptor for the "test.TestService" service.
|
||||
\(access) static let descriptor = GRPCCore.ServiceDescriptor(fullyQualifiedService: "test.TestService")
|
||||
|
@ -136,6 +168,7 @@ struct ProtobufCodeGeneratorTests {
|
|||
}
|
||||
}
|
||||
|
||||
@available(\(expectedAvailability), *)
|
||||
extension GRPCCore.ServiceDescriptor {
|
||||
/// Service descriptor for the "test.TestService" service.
|
||||
\(access) static let test_TestService = GRPCCore.ServiceDescriptor(fullyQualifiedService: "test.TestService")
|
||||
|
@ -143,6 +176,7 @@ struct ProtobufCodeGeneratorTests {
|
|||
|
||||
// MARK: test.TestService (server)
|
||||
|
||||
@available(\(expectedAvailability), *)
|
||||
extension Test_TestService {
|
||||
/// Streaming variant of the service protocol for the "test.TestService" service.
|
||||
///
|
||||
|
@ -404,6 +438,7 @@ struct ProtobufCodeGeneratorTests {
|
|||
}
|
||||
|
||||
// Default implementation of 'registerMethods(with:)'.
|
||||
@available(\(expectedAvailability), *)
|
||||
extension Test_TestService.StreamingServiceProtocol {
|
||||
\(access) func registerMethods<Transport>(with router: inout GRPCCore.RPCRouter<Transport>) where Transport: GRPCCore.ServerTransport {
|
||||
router.registerHandler(
|
||||
|
@ -454,6 +489,7 @@ struct ProtobufCodeGeneratorTests {
|
|||
}
|
||||
|
||||
// Default implementation of streaming methods from 'StreamingServiceProtocol'.
|
||||
@available(\(expectedAvailability), *)
|
||||
extension Test_TestService.ServiceProtocol {
|
||||
\(access) func unary(
|
||||
request: GRPCCore.StreamingServerRequest<Test_TestInput>,
|
||||
|
@ -490,6 +526,7 @@ struct ProtobufCodeGeneratorTests {
|
|||
}
|
||||
|
||||
// Default implementation of methods from 'ServiceProtocol'.
|
||||
@available(\(expectedAvailability), *)
|
||||
extension Test_TestService.SimpleServiceProtocol {
|
||||
\(access) func unary(
|
||||
request: GRPCCore.ServerRequest<Test_TestInput>,
|
||||
|
@ -554,6 +591,7 @@ struct ProtobufCodeGeneratorTests {
|
|||
|
||||
// MARK: test.TestService (client)
|
||||
|
||||
@available(\(expectedAvailability), *)
|
||||
extension Test_TestService {
|
||||
/// Generated client protocol for the "test.TestService" service.
|
||||
///
|
||||
|
@ -812,6 +850,7 @@ struct ProtobufCodeGeneratorTests {
|
|||
}
|
||||
|
||||
// Helpers providing default arguments to 'ClientProtocol' methods.
|
||||
@available(\(expectedAvailability), *)
|
||||
extension Test_TestService.ClientProtocol {
|
||||
/// Call the "Unary" method.
|
||||
///
|
||||
|
@ -927,6 +966,7 @@ struct ProtobufCodeGeneratorTests {
|
|||
}
|
||||
|
||||
// Helpers providing sugared APIs for 'ClientProtocol' methods.
|
||||
@available(\(expectedAvailability), *)
|
||||
extension Test_TestService.ClientProtocol {
|
||||
/// Call the "Unary" method.
|
||||
///
|
||||
|
@ -1062,6 +1102,36 @@ struct ProtobufCodeGeneratorTests {
|
|||
|
||||
#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)")
|
||||
|
@ -1070,16 +1140,13 @@ struct ProtobufCodeGeneratorTests {
|
|||
static let fileDescriptorName = "foo-messages"
|
||||
|
||||
@Test("Generate")
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
func generate() throws {
|
||||
let generator = ProtobufCodeGenerator(
|
||||
config: CodeGenerator.Config(
|
||||
accessLevel: .public,
|
||||
accessLevelOnImports: false,
|
||||
client: true,
|
||||
server: true,
|
||||
indentation: 2
|
||||
)
|
||||
)
|
||||
var config: ProtobufCodeGenerator.Config = .defaults
|
||||
config.accessLevel = .public
|
||||
config.indentation = 2
|
||||
|
||||
let generator = ProtobufCodeGenerator(config: config)
|
||||
|
||||
let generated = try generator.generateCode(
|
||||
fileDescriptor: Self.fileDescriptor,
|
||||
|
@ -1092,6 +1159,7 @@ struct ProtobufCodeGeneratorTests {
|
|||
|
||||
// DO NOT EDIT.
|
||||
// swift-format-ignore-file
|
||||
// swiftlint:disable all
|
||||
//
|
||||
// Generated by the gRPC Swift generator plugin for the protocol buffer compiler.
|
||||
// Source: foo-messages.proto
|
||||
|
|
|
@ -63,11 +63,12 @@ private func loadDescriptorSet(
|
|||
)
|
||||
|
||||
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)
|
||||
return DescriptorSet(proto: descriptorSet)
|
||||
}
|
||||
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
func parseDescriptor(
|
||||
_ descriptor: FileDescriptor,
|
||||
extraModuleImports: [String] = [],
|
||||
|
@ -76,7 +77,8 @@ func parseDescriptor(
|
|||
let parser = ProtobufCodeGenParser(
|
||||
protoFileModuleMappings: .init(),
|
||||
extraModuleImports: extraModuleImports,
|
||||
accessLevel: accessLevel
|
||||
accessLevel: accessLevel,
|
||||
moduleNames: .defaults
|
||||
)
|
||||
return try parser.parse(descriptor: descriptor)
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ struct DetailedErrorTests {
|
|||
(["Help", "Help", "Help"], [.help(.testValue), .help(.testValue), .help(.testValue)]),
|
||||
] as [([String], [ErrorDetails])]
|
||||
)
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
func rpcStatus(details: [String], expected: [ErrorDetails]) async throws {
|
||||
let inProcess = InProcessTransport()
|
||||
try await withGRPCServer(transport: inProcess.server, services: [ErrorThrowingService()]) { _ in
|
||||
|
@ -95,11 +96,27 @@ struct DetailedErrorTests {
|
|||
(.localizedMessage(.testValue), #"LocalizedMessage(locale: "l", message: "m")"#),
|
||||
] as [(ErrorDetails, String)]
|
||||
)
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
func errorInfoDescription(_ details: ErrorDetails, expected: String) {
|
||||
#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 {
|
||||
func throwError(
|
||||
request: ThrowInput,
|
||||
|
@ -170,14 +187,17 @@ private struct ErrorThrowingService: ErrorService.SimpleServiceProtocol {
|
|||
}
|
||||
}
|
||||
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
extension ErrorDetails.ErrorInfo {
|
||||
fileprivate static let testValue = Self(reason: "r", domain: "d", metadata: ["k": "v"])
|
||||
}
|
||||
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
extension ErrorDetails.RetryInfo {
|
||||
fileprivate static let testValue = Self(delay: .seconds(1))
|
||||
}
|
||||
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
extension ErrorDetails.DebugInfo {
|
||||
fileprivate static let testValue = Self(
|
||||
stack: ["foo.foo()", "foo.bar()"],
|
||||
|
@ -185,6 +205,7 @@ extension ErrorDetails.DebugInfo {
|
|||
)
|
||||
}
|
||||
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
extension ErrorDetails.QuotaFailure {
|
||||
fileprivate static let testValue = Self(
|
||||
violations: [
|
||||
|
@ -193,6 +214,7 @@ extension ErrorDetails.QuotaFailure {
|
|||
)
|
||||
}
|
||||
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
extension ErrorDetails.PreconditionFailure {
|
||||
fileprivate static let testValue = Self(
|
||||
violations: [
|
||||
|
@ -201,6 +223,7 @@ extension ErrorDetails.PreconditionFailure {
|
|||
)
|
||||
}
|
||||
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
extension ErrorDetails.BadRequest {
|
||||
fileprivate static let testValue = Self(
|
||||
violations: [
|
||||
|
@ -209,14 +232,17 @@ extension ErrorDetails.BadRequest {
|
|||
)
|
||||
}
|
||||
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
extension ErrorDetails.RequestInfo {
|
||||
fileprivate static let testValue = Self(requestID: "id", servingData: "d")
|
||||
}
|
||||
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
extension ErrorDetails.ResourceInfo {
|
||||
fileprivate static let testValue = Self(type: "t", name: "n", errorDescription: "d")
|
||||
}
|
||||
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
extension ErrorDetails.Help {
|
||||
fileprivate static let testValue = Self(
|
||||
links: [
|
||||
|
@ -225,6 +251,7 @@ extension ErrorDetails.Help {
|
|||
)
|
||||
}
|
||||
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
extension ErrorDetails.LocalizedMessage {
|
||||
fileprivate static let testValue = Self(locale: "l", message: "m")
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
// DO NOT EDIT.
|
||||
// swift-format-ignore-file
|
||||
// swiftlint:disable all
|
||||
//
|
||||
// Generated by the gRPC Swift generator plugin for the protocol buffer compiler.
|
||||
// Source: error-service.proto
|
||||
|
@ -29,6 +30,7 @@ internal import SwiftProtobuf
|
|||
// MARK: - ErrorService
|
||||
|
||||
/// Namespace containing generated types for the "ErrorService" service.
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
internal enum ErrorService {
|
||||
/// Service descriptor for the "ErrorService" service.
|
||||
internal static let descriptor = GRPCCore.ServiceDescriptor(fullyQualifiedService: "ErrorService")
|
||||
|
@ -53,6 +55,7 @@ internal enum ErrorService {
|
|||
}
|
||||
}
|
||||
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
extension GRPCCore.ServiceDescriptor {
|
||||
/// Service descriptor for the "ErrorService" service.
|
||||
internal static let ErrorService = GRPCCore.ServiceDescriptor(fullyQualifiedService: "ErrorService")
|
||||
|
@ -60,6 +63,7 @@ extension GRPCCore.ServiceDescriptor {
|
|||
|
||||
// MARK: ErrorService (server)
|
||||
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
extension ErrorService {
|
||||
/// Streaming variant of the service protocol for the "ErrorService" service.
|
||||
///
|
||||
|
@ -133,6 +137,7 @@ extension ErrorService {
|
|||
}
|
||||
|
||||
// Default implementation of 'registerMethods(with:)'.
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
extension ErrorService.StreamingServiceProtocol {
|
||||
internal func registerMethods<Transport>(with router: inout GRPCCore.RPCRouter<Transport>) where Transport: GRPCCore.ServerTransport {
|
||||
router.registerHandler(
|
||||
|
@ -150,6 +155,7 @@ extension ErrorService.StreamingServiceProtocol {
|
|||
}
|
||||
|
||||
// Default implementation of streaming methods from 'StreamingServiceProtocol'.
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
extension ErrorService.ServiceProtocol {
|
||||
internal func throwError(
|
||||
request: GRPCCore.StreamingServerRequest<ThrowInput>,
|
||||
|
@ -164,6 +170,7 @@ extension ErrorService.ServiceProtocol {
|
|||
}
|
||||
|
||||
// Default implementation of methods from 'ServiceProtocol'.
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
extension ErrorService.SimpleServiceProtocol {
|
||||
internal func throwError(
|
||||
request: GRPCCore.ServerRequest<ThrowInput>,
|
||||
|
@ -181,6 +188,7 @@ extension ErrorService.SimpleServiceProtocol {
|
|||
|
||||
// MARK: ErrorService (client)
|
||||
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
extension ErrorService {
|
||||
/// Generated client protocol for the "ErrorService" service.
|
||||
///
|
||||
|
@ -256,6 +264,7 @@ extension ErrorService {
|
|||
}
|
||||
|
||||
// Helpers providing default arguments to 'ClientProtocol' methods.
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
extension ErrorService.ClientProtocol {
|
||||
/// Call the "ThrowError" method.
|
||||
///
|
||||
|
@ -284,6 +293,7 @@ extension ErrorService.ClientProtocol {
|
|||
}
|
||||
|
||||
// Helpers providing sugared APIs for 'ClientProtocol' methods.
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
extension ErrorService.ClientProtocol {
|
||||
/// Call the "ThrowError" method.
|
||||
///
|
||||
|
|
|
@ -19,6 +19,7 @@ import GRPCProtobuf
|
|||
import SwiftProtobuf
|
||||
import XCTest
|
||||
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
final class ProtobufCodingTests: XCTestCase {
|
||||
func testSerializeDeserializeRoundtrip() throws {
|
||||
let message = Google_Protobuf_Timestamp.with {
|
||||
|
@ -73,6 +74,7 @@ final class ProtobufCodingTests: XCTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
@available(gRPCSwiftProtobuf 2.0, *)
|
||||
struct TestMessage: SwiftProtobuf.Message {
|
||||
var text: String = ""
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
|
|
@ -21,12 +21,12 @@ protoc=$(which protoc)
|
|||
|
||||
# Checkout and build the plugins.
|
||||
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.
|
||||
bin_path=$(swift build --package-path "$root" --show-bin-path)
|
||||
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.
|
||||
# Parameters:
|
||||
|
@ -36,10 +36,10 @@ protoc_gen_grpc_swift="$bin_path/protoc-gen-grpc-swift"
|
|||
# - $4 onwards: options to forward to the plugin
|
||||
function generate_grpc {
|
||||
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
|
||||
args+=("--grpc-swift_opt=$option")
|
||||
args+=("--grpc-swift-2_opt=$option")
|
||||
done
|
||||
|
||||
invoke_protoc "${args[@]}" "$proto"
|
||||
|
@ -94,7 +94,7 @@ function generate_error_service {
|
|||
output="$root/Tests/GRPCProtobufTests/Errors/Generated"
|
||||
|
||||
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 ------------------------------------------------------------
|
||||
|
@ -105,7 +105,9 @@ function generate_test_service_descriptor_set {
|
|||
proto_path="$(dirname "$proto")"
|
||||
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 {
|
||||
|
|
|
@ -3,6 +3,9 @@ syntax = "proto3";
|
|||
|
||||
package test;
|
||||
|
||||
// Using a WKT forces an "SwiftProtobuf" to be imported in generated code.
|
||||
import "google/protobuf/any.proto";
|
||||
|
||||
// Service docs.
|
||||
service TestService {
|
||||
// Unary docs.
|
||||
|
@ -15,5 +18,8 @@ service TestService {
|
|||
rpc BidirectionalStreaming (stream TestInput) returns (stream TestOutput) {}
|
||||
}
|
||||
|
||||
message TestInput {}
|
||||
message TestInput {
|
||||
google.protobuf.Any any = 1;
|
||||
}
|
||||
|
||||
message TestOutput {}
|
||||
|
|
|
@ -24,73 +24,185 @@ 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}/Package.swift" "${scratch_directory}/"
|
||||
cat >> "${scratch_directory}/Package.swift" <<- EOM
|
||||
cp "${resources_directory}/Sources/Package.swift" "${scratch_directory}/"
|
||||
cat >> "${package_manifest}" <<- EOM
|
||||
package.dependencies.append(
|
||||
.package(path: "$grpc_swift_protobuf_directory")
|
||||
)
|
||||
EOM
|
||||
|
||||
# test_01_top_level_config_file
|
||||
test_01_output_directory="${output_directory}/test_01_top_level_config_file"
|
||||
mkdir -p "${test_01_output_directory}/Sources/Protos"
|
||||
cp "${scratch_directory}/Package.swift" "${test_01_output_directory}/"
|
||||
cp "${resources_directory}/HelloWorldAdopter.swift" "${test_01_output_directory}/Sources/adopter.swift"
|
||||
cp "${resources_directory}/HelloWorld/HelloWorld.proto" "${test_01_output_directory}/Sources/Protos"
|
||||
cp "${resources_directory}/internal-grpc-swift-proto-generator-config.json" "${test_01_output_directory}/Sources/grpc-swift-proto-generator-config.json"
|
||||
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]}"
|
||||
}
|
||||
|
||||
# test_02_peer_config_file
|
||||
test_02_output_directory="${output_directory}/test_02_peer_config_file"
|
||||
mkdir -p "${test_02_output_directory}/Sources/Protos"
|
||||
cp "${scratch_directory}/Package.swift" "${test_02_output_directory}/"
|
||||
cp "${resources_directory}/HelloWorldAdopter.swift" "${test_02_output_directory}/Sources/adopter.swift"
|
||||
cp "${resources_directory}/HelloWorld/HelloWorld.proto" "${test_02_output_directory}/Sources/Protos/"
|
||||
cp "${resources_directory}/internal-grpc-swift-proto-generator-config.json" "${test_02_output_directory}/Sources/Protos/grpc-swift-proto-generator-config.json"
|
||||
function test_01_top_level_config_file {
|
||||
# .
|
||||
# ├── Package.swift
|
||||
# └── Sources
|
||||
# ├── HelloWorldAdopter.swift
|
||||
# ├── Protos
|
||||
# │ └── HelloWorld.proto
|
||||
# └── grpc-swift-proto-generator-config.json
|
||||
|
||||
# test_03_separate_service_message_protos
|
||||
test_03_output_directory="${output_directory}/test_03_separate_service_message_protos"
|
||||
mkdir -p "${test_03_output_directory}/Sources/Protos"
|
||||
cp "${scratch_directory}/Package.swift" "${test_03_output_directory}/"
|
||||
cp "${resources_directory}/HelloWorldAdopter.swift" "${test_03_output_directory}/Sources/adopter.swift"
|
||||
cp "${resources_directory}/internal-grpc-swift-proto-generator-config.json" "${test_03_output_directory}/Sources/Protos/grpc-swift-proto-generator-config.json"
|
||||
cp "${resources_directory}/HelloWorld/Service.proto" "${test_03_output_directory}/Sources/Protos/"
|
||||
cp "${resources_directory}/HelloWorld/Messages.proto" "${test_03_output_directory}/Sources/Protos/"
|
||||
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"
|
||||
}
|
||||
|
||||
# test_04_cross_directory_imports
|
||||
test_04_output_directory="${output_directory}/test_04_cross_directory_imports"
|
||||
mkdir -p "${test_04_output_directory}/Sources/Protos/directory_1"
|
||||
mkdir -p "${test_04_output_directory}/Sources/Protos/directory_2"
|
||||
cp "${scratch_directory}/Package.swift" "${test_04_output_directory}/"
|
||||
cp "${resources_directory}/HelloWorldAdopter.swift" "${test_04_output_directory}/Sources/adopter.swift"
|
||||
cp "${resources_directory}/internal-grpc-swift-proto-generator-config.json" "${test_04_output_directory}/Sources/Protos/directory_1/grpc-swift-proto-generator-config.json"
|
||||
cp "${resources_directory}/import-directory-1-grpc-swift-proto-generator-config.json" "${test_04_output_directory}/Sources/Protos/directory_2/grpc-swift-proto-generator-config.json"
|
||||
cp "${resources_directory}/HelloWorld/Service.proto" "${test_04_output_directory}/Sources/Protos/directory_2/"
|
||||
cp "${resources_directory}/HelloWorld/Messages.proto" "${test_04_output_directory}/Sources/Protos/directory_1/"
|
||||
function test_02_peer_config_file {
|
||||
# .
|
||||
# ├── Package.swift
|
||||
# └── Sources
|
||||
# ├── HelloWorldAdopter.swift
|
||||
# └── Protos
|
||||
# ├── HelloWorld.proto
|
||||
# └── grpc-swift-proto-generator-config.json
|
||||
|
||||
# test_05_two_definitions
|
||||
test_05_output_directory="${output_directory}/test_05_two_definitions"
|
||||
mkdir -p "${test_05_output_directory}/Sources/Protos/HelloWorld"
|
||||
mkdir -p "${test_05_output_directory}/Sources/Protos/Foo"
|
||||
cp "${scratch_directory}/Package.swift" "${test_05_output_directory}/"
|
||||
cp "${resources_directory}/FooHelloWorldAdopter.swift" "${test_05_output_directory}/Sources/adopter.swift"
|
||||
cp "${resources_directory}/HelloWorld/HelloWorld.proto" "${test_05_output_directory}/Sources/Protos/HelloWorld/"
|
||||
cp "${resources_directory}/internal-grpc-swift-proto-generator-config.json" "${test_05_output_directory}/Sources/Protos/grpc-swift-proto-generator-config.json"
|
||||
cp "${resources_directory}/Foo/foo-messages.proto" "${test_05_output_directory}/Sources/Protos/Foo/"
|
||||
cp "${resources_directory}/Foo/foo-service.proto" "${test_05_output_directory}/Sources/Protos/Foo/"
|
||||
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"
|
||||
}
|
||||
|
||||
# test_06_nested_definitions
|
||||
test_06_output_directory="${output_directory}/test_06_nested_definitions"
|
||||
mkdir -p "${test_06_output_directory}/Sources/Protos/HelloWorld/FooDefinitions/Foo"
|
||||
cp "${scratch_directory}/Package.swift" "${test_06_output_directory}/"
|
||||
cp "${resources_directory}/FooHelloWorldAdopter.swift" "${test_06_output_directory}/Sources/adopter.swift"
|
||||
cp "${resources_directory}/HelloWorld/HelloWorld.proto" "${test_06_output_directory}/Sources/Protos/HelloWorld/"
|
||||
cp "${resources_directory}/internal-grpc-swift-proto-generator-config.json" "${test_06_output_directory}/Sources/Protos/HelloWorld/grpc-swift-proto-generator-config.json"
|
||||
cp "${resources_directory}/public-grpc-swift-proto-generator-config.json" "${test_06_output_directory}/Sources/Protos/HelloWorld/FooDefinitions/grpc-swift-proto-generator-config.json"
|
||||
cp "${resources_directory}/Foo/foo-messages.proto" "${test_06_output_directory}/Sources/Protos/HelloWorld/FooDefinitions/Foo/"
|
||||
cp "${resources_directory}/Foo/foo-service.proto" "${test_06_output_directory}/Sources/Protos/HelloWorld/FooDefinitions/Foo/"
|
||||
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