From abeff2758d75844a409e9be81054d749821c2589 Mon Sep 17 00:00:00 2001 From: JP Simard Date: Thu, 9 Mar 2023 15:48:17 -0500 Subject: [PATCH] mobile: add flag to build Swift with `-enable-experimental-cxx-interop` (#25971) The Swift project has had experimental C++ interoperability support for a few years now. For example, a [workgroup](https://forums.swift.org/t/swift-and-c-interoperability-workgroup-announcement/54998/1) was started in early 2022. C++ interop is considered experimental because it requires passing an additional flag to the Swift compiler (`-enable-experimental-cxx-interop`) and the feature has no source or ABI stability guarantees. Envoy Mobile in particular would benefit greatly from Swift / C++ interop because it would allow us to shed the intermediate Objective-C layer that only exists to bridge Envoy's C++ APIs with the consumer- facing Swift API. This change proposes a build flag to optionally build the Swift parts of Envoy Mobile with C++ interop enabled, and enables it by default. Future changes will gradually add Swift code that interacts directly with C++ APIs. Please read the [C++ Interoperability Status](https://github.com/apple/swift/blob/main/docs/CppInteroperability/CppInteroperabilityStatus.md) to learn more about the current state of things. Signed-off-by: JP Simard --- .github/workflows/compile_time_options.yml | 1 + mobile/.bazelrc | 1 + mobile/bazel/BUILD | 5 +++++ mobile/library/swift/BUILD | 8 ++++++++ mobile/library/swift/HeadersBuilder.swift | 7 +++---- mobile/library/swift/Stream.swift | 2 +- mobile/library/swift/stats/Element.swift | 23 +++++++++++++++++++--- 7 files changed, 39 insertions(+), 8 deletions(-) diff --git a/.github/workflows/compile_time_options.yml b/.github/workflows/compile_time_options.yml index 91ea526d3c93..aa50a8dd6178 100644 --- a/.github/workflows/compile_time_options.yml +++ b/.github/workflows/compile_time_options.yml @@ -67,6 +67,7 @@ jobs: --remote_header="Authorization=Bearer $GITHUB_TOKEN" \ --define=admin_html=enabled \ --define=envoy_mobile_request_compression=disabled \ + --define=envoy_mobile_swift_cxx_interop=disabled \ --@envoy//bazel:http3=False \ --@com_envoyproxy_protoc_gen_validate//bazel:template-flavor= \ //library/swift:ios_framework diff --git a/mobile/.bazelrc b/mobile/.bazelrc index bd62306519d8..578f04eebb43 100644 --- a/mobile/.bazelrc +++ b/mobile/.bazelrc @@ -9,6 +9,7 @@ build --define=admin_html=disabled build --define=static_extension_registration=disabled build --define=admin_functionality=disabled build --define=library_autolink=disabled +build --define=envoy_mobile_swift_cxx_interop=enabled build --experimental_inmemory_dotd_files build --experimental_inmemory_jdeps_files build --features=debug_prefix_map_pwd_is_dot diff --git a/mobile/bazel/BUILD b/mobile/bazel/BUILD index dd8b74a8522d..aaf6ed2759b3 100644 --- a/mobile/bazel/BUILD +++ b/mobile/bazel/BUILD @@ -5,6 +5,11 @@ licenses(["notice"]) # Apache 2 envoy_package() +config_setting( + name = "envoy_mobile_swift_cxx_interop", + values = {"define": "envoy_mobile_swift_cxx_interop=enabled"}, +) + kt_jvm_library( name = "envoy_mobile_test_suite", srcs = [ diff --git a/mobile/library/swift/BUILD b/mobile/library/swift/BUILD index 4c7b98030456..6cd8b9a47df0 100644 --- a/mobile/library/swift/BUILD +++ b/mobile/library/swift/BUILD @@ -76,6 +76,14 @@ swift_library( "stats/Tags.swift", "stats/TagsBuilder.swift", ] + ["@envoy_mobile_extra_swift_sources//:extra_swift_srcs"], + copts = select({ + "//bazel:envoy_mobile_swift_cxx_interop": [ + "-enable-experimental-cxx-interop", + "-Xcc", + "-std=c++17", + ], + "//conditions:default": [], + }), defines = envoy_mobile_defines("@envoy"), features = [ "swift.emit_symbol_graph", diff --git a/mobile/library/swift/HeadersBuilder.swift b/mobile/library/swift/HeadersBuilder.swift index 787a790e259d..fb363fa048ea 100644 --- a/mobile/library/swift/HeadersBuilder.swift +++ b/mobile/library/swift/HeadersBuilder.swift @@ -3,10 +3,9 @@ import Foundation private let kRestrictedPrefixes = [":", "x-envoy-mobile"] private func isRestrictedHeader(name: String) -> Bool { - let isHostHeader = name.caseInsensitiveCompare("host") == .orderedSame - lazy var hasRestrictedPrefix = kRestrictedPrefixes - .contains { name.range(of: $0, options: [.caseInsensitive, .anchored]) != nil } - return isHostHeader || hasRestrictedPrefix + let lowercasedName = name.lowercased() + let isHostHeader = lowercasedName == "host" + return isHostHeader || kRestrictedPrefixes.contains(where: lowercasedName.starts(with:)) } /// Base builder class used to construct `Headers` instances. diff --git a/mobile/library/swift/Stream.swift b/mobile/library/swift/Stream.swift index 8be28f14cb05..dd34b0a401d6 100644 --- a/mobile/library/swift/Stream.swift +++ b/mobile/library/swift/Stream.swift @@ -37,7 +37,7 @@ public class Stream: NSObject { /// callback. /// /// - returns: This stream, for chaining syntax. - public func readData(_ byteCount: size_t) -> Stream { + public func readData(_ byteCount: Int) -> Stream { self.underlyingStream.readData(byteCount) return self } diff --git a/mobile/library/swift/stats/Element.swift b/mobile/library/swift/stats/Element.swift index ac1e1622bc99..67f5398664d2 100644 --- a/mobile/library/swift/stats/Element.swift +++ b/mobile/library/swift/stats/Element.swift @@ -9,9 +9,10 @@ public final class Element: NSObject, ExpressibleByStringLiteral { internal let value: String public init(stringLiteral value: String) { - guard value.range(of: kPattern, options: .regularExpression) != nil else { - preconditionFailure("Element values must conform to the regex '\(kPattern)'.") - } + precondition( + value.matchesStatsElementPattern, + "Element values must conform to the regex '\(kPattern)'." + ) self.value = value } @@ -19,3 +20,19 @@ public final class Element: NSObject, ExpressibleByStringLiteral { return (object as? Element)?.value == self.value } } + +extension String { + var matchesStatsElementPattern: Bool { + if #available(iOS 16.0, macOS 13.0, *) { + return self.contains(/^[A-Za-z_]+$/) + } + + // `std` is the name of the C++ stdlib when importing it into Swift. + // So effectively this checks if we're compiling with `-enable-experimental-cxx-interop`. +#if canImport(std) + return (self as NSString).range(of: kPattern, options: regularExpression).location != NSNotFound +#else + return self.range(of: kPattern, options: .regularExpression) != nil +#endif + } +}