diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 186fa22..cdbf6de 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -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 diff --git a/.github/workflows/soundness.yml b/.github/workflows/soundness.yml index d880e14..c26dd73 100644 --- a/.github/workflows/soundness.yml +++ b/.github/workflows/soundness.yml @@ -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" diff --git a/Package.swift b/Package.swift index 73f9cad..978748e 100644 --- a/Package.swift +++ b/Package.swift @@ -54,7 +54,7 @@ let defaultSwiftSettings: [SwiftSetting] = [ .enableUpcomingFeature("MemberImportVisibility"), ] -let targets: [Target] = [ +var targets: [Target] = [ // protoc plugin for grpc-swift .executableTarget( name: "protoc-gen-grpc-swift", @@ -143,6 +143,51 @@ let targets: [Target] = [ ), ] +// ------------------------------------------------------------------------------------------------- + +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" { + target.dependencies.append(.target(name: "CGRPCProtobuf")) + } + } +} + +// ------------------------------------------------------------------------------------------------- + let package = Package( name: "grpc-swift-protobuf", platforms: [ diff --git a/Sources/CGRPCProtobuf/CGRPCProtobuf.c b/Sources/CGRPCProtobuf/CGRPCProtobuf.c new file mode 100644 index 0000000..e9f454b --- /dev/null +++ b/Sources/CGRPCProtobuf/CGRPCProtobuf.c @@ -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; +} diff --git a/Sources/CGRPCProtobuf/include/CGRPCProtobuf.h b/Sources/CGRPCProtobuf/include/CGRPCProtobuf.h new file mode 100644 index 0000000..46f752c --- /dev/null +++ b/Sources/CGRPCProtobuf/include/CGRPCProtobuf.h @@ -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_ diff --git a/Sources/protoc-gen-grpc-swift/Version.swift b/Sources/protoc-gen-grpc-swift/Version.swift index 7df4cd6..66dd5a5 100644 --- a/Sources/protoc-gen-grpc-swift/Version.swift +++ b/Sources/protoc-gen-grpc-swift/Version.swift @@ -14,26 +14,17 @@ * limitations under the License. */ -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" +#if canImport(CGRPCProtobuf) +private import CGRPCProtobuf +#endif +internal enum Version { /// 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 } }