Skip to content
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

Add option to annotate imports with @_implementationOnly #1393

Merged
merged 3 commits into from
May 19, 2023
Merged
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
24 changes: 24 additions & 0 deletions Documentation/PLUGIN.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,30 @@ mapping {
The `proto_file_path` values here should match the paths used in the proto file
`import` statements.


##### Generation Option: `ImplementationOnlyImports` - `@_implementationOnly`-annotated imports

By default, SwiftProtobuf does not annotate any imports with `@_implementationOnly`.
However, in some scenarios, such as when distributing an `XCFramework`, imports
for types used only internally should be annotated as `@_implementationOnly` to
avoid exposing internal symbols to clients.
You can change this with the `ImplementationOnlyImports` option:

```
$ protoc --swift_opt=ImplementationOnlyImports=[value] --swift_out=. foo/bar/*.proto mumble/*.proto
```

The possible values for `ImplementationOnlyImports` are:

* `false` (default): The `@_implementationOnly` annotation will never be used.
* `true`: Imports of internal dependencies and any modules defined in the module
mappings will be annotated as `@_implementationOnly`.

**Important:** Modules cannot be imported as implementation-only if they're
exposed via public API, so even if `ImplementationOnlyImports` is set to `true`,
this will only work if the `Visibility` is set to `internal`.


### Building your project

After copying the `.pb.swift` files into your project, you will need
Expand Down
6 changes: 6 additions & 0 deletions Plugins/SwiftProtobufPlugin/plugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ struct SwiftProtobufPlugin: BuildToolPlugin {
var visibility: Visibility?
/// The file naming strategy to use.
var fileNaming: FileNaming?
/// Whether internal imports should be annotated as `@_implementationOnly`.
var implementationOnlyImports: Bool?
}

/// The path to the `protoc` binary.
Expand Down Expand Up @@ -156,6 +158,10 @@ struct SwiftProtobufPlugin: BuildToolPlugin {
var inputFiles = [Path]()
var outputFiles = [Path]()

if let implementationOnlyImports = invocation.implementationOnlyImports {
protocArgs.append("--swift_opt=ImplementationOnlyImports=\(implementationOnlyImports)")
}

for var file in invocation.protoFiles {
// Append the file to the protoc args so that it is used for generating
protocArgs.append("\(file)")
Expand Down
24 changes: 24 additions & 0 deletions Sources/protoc-gen-swift/Docs.docc/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,30 @@ mapping {
The `proto_file_path` values here should match the paths used in the proto file
`import` statements.


##### Generation Option: `ImplementationOnlyImports` - `@_implementationOnly`-annotated imports

By default, the code generator does not annotate any imports with `@_implementationOnly`.
However, in some scenarios, such as when distributing an `XCFramework`, imports
for types used only internally should be annotated as `@_implementationOnly` to
avoid exposing internal symbols to clients.
You can change this with the `ImplementationOnlyImports` option:

```
$ protoc --swift_opt=ImplementationOnlyImports=[value] --swift_out=. foo/bar/*.proto mumble/*.proto
```

The possible values for `ImplementationOnlyImports` are:

* `false` (default): The `@_implementationOnly` annotation will never be used.
* `true`: Imports of internal dependencies and any modules defined in the module
mappings will be annotated as `@_implementationOnly`.

**Important:** Modules cannot be imported as implementation-only if they're
exposed via public API, so even if `ImplementationOnlyImports` is set to `true`,
this will only work if the `Visibility` is set to `internal`.


### Building your project

After copying the `.pb.swift` files into your project, you will need
Expand Down
3 changes: 2 additions & 1 deletion Sources/protoc-gen-swift/Docs.docc/spm-plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ to the root of your target's source folder. An example configuration file looks
"protoFiles": [
"Foo.proto",
],
"visibility": "internal"
"visibility": "internal",
"implementationOnlyImports": true
},
{
"protoFiles": [
Expand Down
24 changes: 22 additions & 2 deletions Sources/protoc-gen-swift/FileGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -89,15 +89,35 @@ class FileGenerator {
}

p.print("\(comments)import Foundation")

if self.generatorOptions.implementationOnlyImports,
self.generatorOptions.visibility == .public {
errorString = """
Cannot use @_implementationOnly imports when the proto visibility is public.
Either change the visibility to internal, or disable @_implementationOnly imports.
"""
return
}

// Import all other imports as @_implementationOnly if the visiblity is
// internal and the option is set, to avoid exposing internal types to users.
let visibilityAnnotation: String = {
if self.generatorOptions.implementationOnlyImports,
self.generatorOptions.visibility == .internal {
return "@_implementationOnly "
} else {
return ""
}
}()
if !fileDescriptor.isBundledProto {
// The well known types ship with the runtime, everything else needs
// to import the runtime.
p.print("import \(namer.swiftProtobufModuleName)")
p.print("\(visibilityAnnotation)import \(namer.swiftProtobufModuleName)")
}
if let neededImports = generatorOptions.protoToModuleMappings.neededModules(forFile: fileDescriptor) {
p.print()
for i in neededImports {
p.print("import \(i)")
p.print("\(visibilityAnnotation)import \(i)")
}
}

Expand Down
4 changes: 2 additions & 2 deletions Sources/protoc-gen-swift/GenerationError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
// -----------------------------------------------------------------------------

enum GenerationError: Error {
/// Raised when parsing the parameter string and found an unknown key
/// Raised when parsing the parameter string and found an unknown key.
case unknownParameter(name: String)
/// Raised when a parameter was giving an invalid value
/// Raised when a parameter was given an invalid value.
case invalidParameterValue(name: String, value: String)
/// Raised to wrap another error but provide a context message.
case wrappedError(message: String, error: Error)
Expand Down
7 changes: 7 additions & 0 deletions Sources/protoc-gen-swift/GeneratorOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class GeneratorOptions {
let outputNaming: OutputNaming
let protoToModuleMappings: ProtoFileToModuleMappings
let visibility: Visibility
let implementationOnlyImports: Bool

/// A string snippet to insert for the visibility
let visibilitySourceSnippet: String
Expand All @@ -58,6 +59,7 @@ class GeneratorOptions {
var moduleMapPath: String?
var visibility: Visibility = .internal
var swiftProtobufModuleName: String? = nil
var implementationOnlyImports: Bool = false

for pair in parseParameter(string:parameter) {
switch pair.key {
Expand Down Expand Up @@ -88,6 +90,10 @@ class GeneratorOptions {
throw GenerationError.invalidParameterValue(name: pair.key,
value: pair.value)
}
case "ImplementationOnlyImports":
if let value = Bool(pair.value) {
implementationOnlyImports = value
}
default:
throw GenerationError.unknownParameter(name: pair.key)
}
Expand Down Expand Up @@ -115,5 +121,6 @@ class GeneratorOptions {
visibilitySourceSnippet = "public "
}

self.implementationOnlyImports = implementationOnlyImports
}
}