Skip to content

Split generated code into multiple files #1796

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions Sources/protoc-gen-swift/EnumGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -113,15 +113,15 @@ class EnumGenerator {
p.print("]")
}

func generateRuntimeSupport(printer p: inout CodePrinter) {
p.print(
func generateRuntimeSupport(filePrinter fp: inout FilePrinter) {
fp.nameProviding.print(
"",
"extension \(swiftFullName): \(namer.swiftProtobufModulePrefix)_ProtoNameProviding {"
)
p.withIndentation { p in
fp.nameProviding.withIndentation { p in
generateProtoNameProviding(printer: &p)
}
p.print("}")
fp.nameProviding.print("}")
}

/// Generates the cases or statics (for alias) for the values.
Expand Down
104 changes: 60 additions & 44 deletions Sources/protoc-gen-swift/FileGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ class FileGenerator {
private let generatorOptions: GeneratorOptions
private let namer: SwiftProtobufNamer

var outputFilename: String {
let ext = ".pb.swift"
func outputFilename(_ ext: String) -> String {
let pathParts = splitPath(pathname: fileDescriptor.name)
switch generatorOptions.outputNaming {
case .fullPath:
Expand Down Expand Up @@ -52,7 +51,7 @@ class FileGenerator {

/// Generate, if `errorString` gets filled in, then report error instead of using
/// what written into `printer`.
func generateOutputFile(printer p: inout CodePrinter, errorString: inout String?) {
func generateOutputFile(filePrinter fp: inout FilePrinter, errorString: inout String?) {
guard
fileDescriptor.options.swiftPrefix.isEmpty
|| isValidSwiftIdentifier(
Expand All @@ -64,20 +63,23 @@ class FileGenerator {
"\(fileDescriptor.name) has an 'swift_prefix' that isn't a valid Swift identifier (\(fileDescriptor.options.swiftPrefix))."
return
}
p.print(
"""
// DO NOT EDIT.
// swift-format-ignore-file
// swiftlint:disable all
//
// Generated by the Swift generator plugin for the protocol buffer compiler.
// Source: \(fileDescriptor.name)
//
// For information on using the generated types, please see the documentation:
// https://github.com/apple/swift-protobuf/

fp.forEach { p in
p.print(
"""
// DO NOT EDIT.
// swift-format-ignore-file
// swiftlint:disable all
//
// Generated by the Swift generator plugin for the protocol buffer compiler.
// Source: \(fileDescriptor.name)
//
// For information on using the generated types, please see the documentation:
// https://github.com/apple/swift-protobuf/

"""
)
"""
)
}

// Attempt to bring over the comments at the top of the .proto file as
// they likely contain copyrights/preamble/etc.
Expand Down Expand Up @@ -106,7 +108,7 @@ class FileGenerator {
if !comments.isEmpty {
// If the was a leading or tailing comment it won't have a blank
// line, after it, so ensure there is one.
p.print(comments, newlines: !comments.hasSuffix("\n\n"))
fp.main.print(comments, newlines: !comments.hasSuffix("\n\n"))
}
}

Expand All @@ -115,17 +117,23 @@ class FileGenerator {

var hasImports = false
if fileDescriptor.needsFoundationImport {
p.print("\(generatorOptions.importDirective.snippet) Foundation")
fp.forEach { p in
p.print("\(generatorOptions.importDirective.snippet) Foundation")
}
hasImports = true
}

if fileDescriptor.isBundledProto {
p.print(
"// 'import \(namer.swiftProtobufModuleName)' suppressed, this proto file is meant to be bundled in the runtime."
)
fp.forEach { p in
p.print(
"// 'import \(namer.swiftProtobufModuleName)' suppressed, this proto file is meant to be bundled in the runtime."
)
}
hasImports = true
} else if fileDefinesTypes {
p.print("\(generatorOptions.importDirective.snippet) \(namer.swiftProtobufModuleName)")
fp.forEach { p in
p.print("\(generatorOptions.importDirective.snippet) \(namer.swiftProtobufModuleName)")
}
hasImports = true
}

Expand All @@ -135,25 +143,31 @@ class FileGenerator {
reexportPublicImports: generatorOptions.visibility != .internal
)
if !neededImports.isEmpty {
if hasImports {
p.print()
fp.forEach { p in
if hasImports {
p.print()
}
p.print(neededImports)
}
p.print(neededImports)
hasImports = true
}

// If there is nothing to generate, then just record that and be done (usually means
// there just was one or more services).
guard fileDefinesTypes else {
if hasImports {
p.print()
fp.forEach { p in
if hasImports {
p.print()
}
p.print("// This file contained no messages, enums, or extensions.")
}
p.print("// This file contained no messages, enums, or extensions.")
return
}

p.print()
generateVersionCheck(printer: &p)
fp.forEach { p in
p.print()
generateVersionCheck(printer: &p)
}

let extensionSet =
ExtensionSetGenerator(
Expand All @@ -178,54 +192,56 @@ class FileGenerator {
}

for e in enums {
e.generateMainEnum(printer: &p)
e.generateMainEnum(printer: &fp.main)
}

for m in messages {
m.generateMainStruct(printer: &p, parent: nil, errorString: &errorString)
m.generateMainStruct(printer: &fp.main, parent: nil, errorString: &errorString)
}

if !extensionSet.isEmpty {
let pathParts = splitPath(pathname: fileDescriptor.name)
let filename = pathParts.base + pathParts.suffix
p.print(
fp.main.print(
"",
"// MARK: - Extension support defined in \(filename)."
)

// Generate the Swift Extensions on the Messages that provide the api
// for using the protobuf extension.
extensionSet.generateMessageSwiftExtensions(printer: &p)
extensionSet.generateMessageSwiftExtensions(printer: &fp.main)

// Generate a registry for the file.
extensionSet.generateFileProtobufExtensionRegistry(printer: &p)
extensionSet.generateFileProtobufExtensionRegistry(printer: &fp.main)

// Generate the Extension's declarations (used by the two above things).
//
// This is done after the other two as the only time developers will need
// these symbols is if they are manually building their own ExtensionMap;
// so the others are assumed more interesting.
extensionSet.generateProtobufExtensionDeclarations(printer: &p)
extensionSet.generateProtobufExtensionDeclarations(printer: &fp.main)
}

let protoPackage = fileDescriptor.package
let needsProtoPackage: Bool = !protoPackage.isEmpty && !messages.isEmpty
if needsProtoPackage || !enums.isEmpty || !messages.isEmpty {
p.print(
"",
"// MARK: - Code below here is support for the SwiftProtobuf runtime."
)
if needsProtoPackage {
fp.forEachRuntimeExtension { p in
p.print(
"",
"fileprivate let _protobuf_package = \"\(protoPackage)\""
"// MARK: - Code below here is support for the SwiftProtobuf runtime."
)
if needsProtoPackage {
p.print(
"",
"fileprivate let _protobuf_package = \"\(protoPackage)\""
)
}
}
for e in enums {
e.generateRuntimeSupport(printer: &p)
e.generateRuntimeSupport(filePrinter: &fp)
}
for m in messages {
m.generateRuntimeSupport(printer: &p, file: self, parent: nil)
m.generateRuntimeSupport(filePrinter: &fp, file: self, parent: nil)
}
}
}
Expand Down
27 changes: 27 additions & 0 deletions Sources/protoc-gen-swift/FilePrinter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Sources/protoc-gen-swift/MessageStorageDecision.swift
//
// Copyright (c) 2014 - 2016 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
//
// -----------------------------------------------------------------------------

import SwiftProtobufPluginLibrary

struct FilePrinter {
var main: CodePrinter
var hashable: CodePrinter
var nameProviding: CodePrinter

mutating func forEach(_ body: (_ p: inout CodePrinter) -> Void) {
body(&main)
forEachRuntimeExtension(body)
}

mutating func forEachRuntimeExtension(_ body: (_ p: inout CodePrinter) -> Void) {
body(&hashable)
body(&nameProviding)
}
}
33 changes: 24 additions & 9 deletions Sources/protoc-gen-swift/MessageGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -205,12 +205,12 @@ class MessageGenerator {
p.print("}")
}

func generateRuntimeSupport(printer p: inout CodePrinter, file: FileGenerator, parent: MessageGenerator?) {
p.print(
func generateRuntimeSupport(filePrinter fp: inout FilePrinter, file: FileGenerator, parent: MessageGenerator?) {
fp.main.print(
"",
"extension \(swiftFullName): \(namer.swiftProtobufModulePrefix)Message, \(namer.swiftProtobufModulePrefix)_MessageImplementationBase, \(namer.swiftProtobufModulePrefix)_ProtoNameProviding {"
"extension \(swiftFullName): \(namer.swiftProtobufModulePrefix)Message {"
)
p.withIndentation { p in
fp.main.withIndentation { p in
if let parent = parent {
p.print(
"\(visibility)static let protoMessageName: String = \(parent.swiftFullName).protoMessageName + \".\(descriptor.name)\""
Expand All @@ -222,7 +222,6 @@ class MessageGenerator {
} else {
p.print("\(visibility)static let protoMessageName: String = \"\(descriptor.name)\"")
}
generateProtoNameProviding(printer: &p)
if let storage = storage {
p.print()
storage.generateTypeDeclaration(printer: &p)
Expand All @@ -235,17 +234,33 @@ class MessageGenerator {
generateDecodeMessage(printer: &p)
p.print()
generateTraverse(printer: &p)
p.print()
}
fp.main.print("}")

fp.hashable.print(
"",
"extension \(swiftFullName): \(namer.swiftProtobufModulePrefix)_MessageImplementationBase {"
)
fp.hashable.withIndentation { p in
generateMessageEquality(printer: &p)
}
p.print("}")
fp.hashable.print("}")

fp.nameProviding.print(
"",
"extension \(swiftFullName): \(namer.swiftProtobufModulePrefix)_ProtoNameProviding {"
)
fp.nameProviding.withIndentation { p in
generateProtoNameProviding(printer: &p)
}
fp.nameProviding.print("}")

// Nested enums and messages
for e in enums {
e.generateRuntimeSupport(printer: &p)
e.generateRuntimeSupport(filePrinter: &fp)
}
for m in messages {
m.generateRuntimeSupport(printer: &p, file: file, parent: self)
m.generateRuntimeSupport(filePrinter: &fp, file: file, parent: self)
}
}

Expand Down
21 changes: 18 additions & 3 deletions Sources/protoc-gen-swift/SwiftGeneratorPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,29 @@ struct SwiftGeneratorPlugin: CodeGenerator {
var errorString: String? = nil
for fileDescriptor in files {
let fileGenerator = FileGenerator(fileDescriptor: fileDescriptor, generatorOptions: options)
var printer = CodePrinter(addNewlines: true)
fileGenerator.generateOutputFile(printer: &printer, errorString: &errorString)
var filePrinter = FilePrinter(
main: CodePrinter(addNewlines: true),
hashable: CodePrinter(addNewlines: true),
nameProviding: CodePrinter(addNewlines: true)
)
fileGenerator.generateOutputFile(filePrinter: &filePrinter, errorString: &errorString)
if let errorString = errorString {
// If generating multiple files, scope the message with the file that triggered it.
let fullError = files.count > 1 ? "\(fileDescriptor.name): \(errorString)" : errorString
throw GenerationError.message(message: fullError)
}
try generatorOutputs.add(fileName: fileGenerator.outputFilename, contents: printer.content)
try generatorOutputs.add(
fileName: fileGenerator.outputFilename(".pb.swift"),
contents: filePrinter.main.content
)
try generatorOutputs.add(
fileName: fileGenerator.outputFilename(".pb.hashable.swift"),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the SwiftProtobuf sources, we have the convention that a file declaring an extension of some type gets named "Type+Protocol.swift"

I wonder if some variant of that convention would be more appropriate here? I think that would end up giving us ".pb+Hashable.swift" which is admittedly a little weird looking. But at a minimum, Hashable and NameProviding in these filenames should be correctly capitalized.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my.pb+Hashable.swift
my.pb+NameProviding.swift

or

my+Hashable.pb.swift
my+NameProviding.pb.swift

?

contents: filePrinter.hashable.content
)
try generatorOutputs.add(
fileName: fileGenerator.outputFilename(".pb.nameproviding.swift"),
contents: filePrinter.nameProviding.content
)
}
}

Expand Down