mirror of
https://github.com/apple/swift-protobuf.git
synced 2026-05-17 10:20:36 +00:00
722ca145d7
Now that Swift 5.9 is the baseline, make some things `package` visible and move off the `@testable` import. Opened #1784 to track doing this with for the main runtime as they can't be done yet as the Fuzz tests still have to build with an older Swift version.
192 lines
7.8 KiB
Swift
192 lines
7.8 KiB
Swift
// Sources/SwiftProtobufPluginLibrary/ProtoPathModuleMappings.swift - Helpers for module mappings option
|
|
//
|
|
// Copyright (c) 2014 - 2017 Apple Inc. and the project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See LICENSE.txt for license information:
|
|
// https://github.com/apple/swift-protobuf/blob/main/LICENSE.txt
|
|
//
|
|
// -----------------------------------------------------------------------------
|
|
///
|
|
/// Helper handling proto file to module mappings.
|
|
///
|
|
// -----------------------------------------------------------------------------
|
|
|
|
import Foundation
|
|
|
|
private let defaultSwiftProtobufModuleName = "SwiftProtobuf"
|
|
|
|
/// Handles the mapping of proto files to the modules they will be compiled into.
|
|
public struct ProtoFileToModuleMappings {
|
|
|
|
/// Errors raised from parsing mappings
|
|
public enum LoadError: Error, Equatable {
|
|
/// Raised if the path wasn't found.
|
|
case failToOpen(path: String)
|
|
/// Raised if an mapping entry in the protobuf doesn't have a module name.
|
|
/// mappingIndex is the index (0-N) of the mapping.
|
|
case entryMissingModuleName(mappingIndex: Int)
|
|
/// Raised if an mapping entry in the protobuf doesn't have any proto files listed.
|
|
/// mappingIndex is the index (0-N) of the mapping.
|
|
case entryHasNoProtoPaths(mappingIndex: Int)
|
|
/// The given proto path was listed for both modules.
|
|
case duplicateProtoPathMapping(path: String, firstModule: String, secondModule: String)
|
|
}
|
|
|
|
/// Proto file name to module name.
|
|
/// This is really `private` to this type, it is just `internal` so the tests can
|
|
/// access it to verify things.
|
|
package let mappings: [String: String]
|
|
|
|
/// A Boolean value that indicates that there were developer provided
|
|
/// mappings.
|
|
///
|
|
/// Since `mappings` will have the bundled proto files also, this is used
|
|
/// to track whether there are any provided mappings.
|
|
public let hasMappings: Bool
|
|
|
|
/// The name of the runtime module for SwiftProtobuf (usually "SwiftProtobuf").
|
|
/// We expect to find the WKTs in the module named here.
|
|
public let swiftProtobufModuleName: String
|
|
|
|
/// Loads and parses the given module mapping from disk. Raises LoadError
|
|
/// or TextFormatDecodingError.
|
|
public init(path: String) throws {
|
|
try self.init(path: path, swiftProtobufModuleName: nil)
|
|
}
|
|
|
|
/// Loads and parses the given module mapping from disk. Raises LoadError
|
|
/// or TextFormatDecodingError.
|
|
public init(path: String, swiftProtobufModuleName: String?) throws {
|
|
let content: String
|
|
do {
|
|
content = try String(contentsOfFile: path, encoding: String.Encoding.utf8)
|
|
} catch {
|
|
throw LoadError.failToOpen(path: path)
|
|
}
|
|
|
|
let mappingsProto = try SwiftProtobuf_GenSwift_ModuleMappings(textFormatString: content)
|
|
try self.init(moduleMappingsProto: mappingsProto, swiftProtobufModuleName: swiftProtobufModuleName)
|
|
}
|
|
|
|
/// Parses the given module mapping. Raises LoadError.
|
|
public init(moduleMappingsProto mappings: SwiftProtobuf_GenSwift_ModuleMappings) throws {
|
|
try self.init(moduleMappingsProto: mappings, swiftProtobufModuleName: nil)
|
|
}
|
|
|
|
/// Parses the given module mapping. Raises LoadError.
|
|
public init(
|
|
moduleMappingsProto mappings: SwiftProtobuf_GenSwift_ModuleMappings,
|
|
swiftProtobufModuleName: String?
|
|
) throws {
|
|
self.swiftProtobufModuleName = swiftProtobufModuleName ?? defaultSwiftProtobufModuleName
|
|
var builder = wktMappings(swiftProtobufModuleName: self.swiftProtobufModuleName)
|
|
let initialCount = builder.count
|
|
for (idx, mapping) in mappings.mapping.lazy.enumerated() {
|
|
if mapping.moduleName.isEmpty {
|
|
throw LoadError.entryMissingModuleName(mappingIndex: idx)
|
|
}
|
|
if mapping.protoFilePath.isEmpty {
|
|
throw LoadError.entryHasNoProtoPaths(mappingIndex: idx)
|
|
}
|
|
for path in mapping.protoFilePath {
|
|
if let existing = builder[path] {
|
|
if existing != mapping.moduleName {
|
|
throw LoadError.duplicateProtoPathMapping(
|
|
path: path,
|
|
firstModule: existing,
|
|
secondModule: mapping.moduleName
|
|
)
|
|
}
|
|
// Was a repeat, just allow it.
|
|
} else {
|
|
builder[path] = mapping.moduleName
|
|
}
|
|
}
|
|
}
|
|
self.mappings = builder
|
|
self.hasMappings = initialCount != builder.count
|
|
}
|
|
|
|
public init() {
|
|
try! self.init(moduleMappingsProto: SwiftProtobuf_GenSwift_ModuleMappings(), swiftProtobufModuleName: nil)
|
|
}
|
|
|
|
public init(swiftProtobufModuleName: String?) {
|
|
try! self.init(
|
|
moduleMappingsProto: SwiftProtobuf_GenSwift_ModuleMappings(),
|
|
swiftProtobufModuleName: swiftProtobufModuleName
|
|
)
|
|
}
|
|
|
|
/// Looks up the module a given file is in.
|
|
public func moduleName(forFile file: FileDescriptor) -> String? {
|
|
mappings[file.name]
|
|
}
|
|
|
|
/// Returns the list of modules that need to be imported for a given file based on
|
|
/// the dependencies it has.
|
|
public func neededModules(forFile file: FileDescriptor) -> [String]? {
|
|
guard hasMappings else { return nil }
|
|
if file.dependencies.isEmpty {
|
|
return nil
|
|
}
|
|
|
|
var collector = Set<String>()
|
|
|
|
for dependency in file.dependencies {
|
|
if let depModule = mappings[dependency.name] {
|
|
collector.insert(depModule)
|
|
}
|
|
}
|
|
|
|
// NOTE: This api is only used by gRPC (or things like it), with
|
|
// `import public` now re-exporting things, this likely can go away or just
|
|
// be reduced just the above loop, without the need for special casing the
|
|
// `import public` cases. It will come down to what should expectations
|
|
// be for protobuf messages, enums, and extensions with repsect to something
|
|
// that generates on top if it. i.e. - should they re-export things or
|
|
// should only the generated proto code do it?
|
|
|
|
// Protocol Buffers has the concept of "public imports", these are imports
|
|
// into a file that expose everything from within the file to the new
|
|
// context. From the docs -
|
|
// https://protobuf.dev/programming-guides/proto/#importing
|
|
// `import public` dependencies can be transitively relied upon by anyone
|
|
// importing the proto containing the import public statement.
|
|
// To properly expose the types for use, it means in each file, the public imports
|
|
// from the dependencies have to be hoisted and also imported.
|
|
var visited = Set<String>()
|
|
var toScan = file.dependencies
|
|
while let dep = toScan.popLast() {
|
|
for pubDep in dep.publicDependencies {
|
|
let pubDepName = pubDep.name
|
|
if visited.contains(pubDepName) { continue }
|
|
visited.insert(pubDepName)
|
|
toScan.append(pubDep)
|
|
if let pubDepModule = mappings[pubDepName] {
|
|
collector.insert(pubDepModule)
|
|
}
|
|
}
|
|
}
|
|
|
|
if let moduleForThisFile = mappings[file.name] {
|
|
collector.remove(moduleForThisFile)
|
|
}
|
|
|
|
// The library itself (happens if the import one of the WKTs).
|
|
collector.remove(self.swiftProtobufModuleName)
|
|
|
|
if collector.isEmpty {
|
|
return nil
|
|
}
|
|
|
|
return collector.sorted()
|
|
}
|
|
}
|
|
|
|
// Used to seed the mappings, the wkt are all part of the main library.
|
|
private func wktMappings(swiftProtobufModuleName: String) -> [String: String] {
|
|
SwiftProtobufInfo.bundledProtoFiles.reduce(into: [:]) { $0[$1] = swiftProtobufModuleName }
|
|
}
|