Skip to content

Commit 473f466

Browse files
authored
Add support for XcodeProjectPlugin (#1621)
1 parent bbcc0d9 commit 473f466

File tree

2 files changed

+84
-38
lines changed

2 files changed

+84
-38
lines changed

Plugins/GRPCSwiftPlugin/plugin.swift

+64-19
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@ import Foundation
1818
import PackagePlugin
1919

2020
@main
21-
struct GRPCSwiftPlugin: BuildToolPlugin {
21+
struct GRPCSwiftPlugin {
2222
/// Errors thrown by the `GRPCSwiftPlugin`
2323
enum PluginError: Error {
2424
/// Indicates that the target where the plugin was applied to was not `SourceModuleTarget`.
2525
case invalidTarget
2626
/// Indicates that the file extension of an input file was not `.proto`.
2727
case invalidInputFileExtension
28+
/// Indicates that there was no configuration file at the required location.
29+
case noConfigFound
2830
}
2931

3032
/// The configuration of the plugin.
@@ -69,20 +71,33 @@ struct GRPCSwiftPlugin: BuildToolPlugin {
6971

7072
static let configurationFileName = "grpc-swift-config.json"
7173

72-
func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] {
73-
// Let's check that this is a source target
74-
guard let target = target as? SourceModuleTarget else {
75-
throw PluginError.invalidTarget
74+
/// Create build commands for the given arguments
75+
/// - Parameters:
76+
/// - pluginWorkDirectory: The path of a writable directory into which the plugin or the build
77+
/// commands it constructs can write anything it wants.
78+
/// - sourceFiles: The input files that are associated with the target.
79+
/// - tool: The tool method from the context.
80+
/// - Returns: The build commands configured based on the arguments.
81+
func createBuildCommands(
82+
pluginWorkDirectory: PackagePlugin.Path,
83+
sourceFiles: FileList,
84+
tool: (String) throws -> PackagePlugin.PluginContext.Tool
85+
) throws -> [Command] {
86+
guard let configurationFilePath = sourceFiles.first(
87+
where: {
88+
$0.path.lastComponent == Self.configurationFileName
89+
}
90+
)?.path else {
91+
throw PluginError.noConfigFound
7692
}
7793

78-
// We need to find the configuration file at the root of the target
79-
let configurationFilePath = target.directory.appending(subpath: Self.configurationFileName)
8094
let data = try Data(contentsOf: URL(fileURLWithPath: "\(configurationFilePath)"))
8195
let configuration = try JSONDecoder().decode(Configuration.self, from: data)
8296

8397
try self.validateConfiguration(configuration)
8498

85-
var importPaths: [Path] = [target.directory]
99+
let targetDirectory = configurationFilePath.removingLastComponent()
100+
var importPaths: [Path] = [targetDirectory]
86101
if let configuredImportPaths = configuration.importPaths {
87102
importPaths.append(contentsOf: configuredImportPaths.map { Path($0) })
88103
}
@@ -96,20 +111,17 @@ struct GRPCSwiftPlugin: BuildToolPlugin {
96111
protocPath = Path(environmentPath)
97112
} else {
98113
// The user didn't set anything so let's try see if SPM can find a binary for us
99-
protocPath = try context.tool(named: "protoc").path
114+
protocPath = try tool("protoc").path
100115
}
101-
let protocGenGRPCSwiftPath = try context.tool(named: "protoc-gen-grpc-swift").path
102-
103-
// This plugin generates its output into GeneratedSources
104-
let outputDirectory = context.pluginWorkDirectory
116+
let protocGenGRPCSwiftPath = try tool("protoc-gen-grpc-swift").path
105117

106118
return configuration.invocations.map { invocation in
107119
self.invokeProtoc(
108-
target: target,
120+
directory: targetDirectory,
109121
invocation: invocation,
110122
protocPath: protocPath,
111123
protocGenGRPCSwiftPath: protocGenGRPCSwiftPath,
112-
outputDirectory: outputDirectory,
124+
outputDirectory: pluginWorkDirectory,
113125
importPaths: importPaths
114126
)
115127
}
@@ -118,15 +130,15 @@ struct GRPCSwiftPlugin: BuildToolPlugin {
118130
/// Invokes `protoc` with the given inputs
119131
///
120132
/// - Parameters:
121-
/// - target: The plugin's target.
133+
/// - directory: The plugin's target directory.
122134
/// - invocation: The `protoc` invocation.
123135
/// - protocPath: The path to the `protoc` binary.
124136
/// - protocGenSwiftPath: The path to the `protoc-gen-swift` binary.
125137
/// - outputDirectory: The output directory for the generated files.
126138
/// - importPaths: List of paths to pass with "-I <path>" to `protoc`
127-
/// - Returns: The build command.
139+
/// - Returns: The build command configured based on the arguments
128140
private func invokeProtoc(
129-
target: Target,
141+
directory: Path,
130142
invocation: Configuration.Invocation,
131143
protocPath: Path,
132144
protocGenGRPCSwiftPath: Path,
@@ -166,7 +178,7 @@ struct GRPCSwiftPlugin: BuildToolPlugin {
166178
for var file in invocation.protoFiles {
167179
// Append the file to the protoc args so that it is used for generating
168180
protocArgs.append("\(file)")
169-
inputFiles.append(target.directory.appending(file))
181+
inputFiles.append(directory.appending(file))
170182

171183
// The name of the output file is based on the name of the input file.
172184
// We validated in the beginning that every file has the suffix of .proto
@@ -202,3 +214,36 @@ struct GRPCSwiftPlugin: BuildToolPlugin {
202214
}
203215
}
204216
}
217+
218+
extension GRPCSwiftPlugin: BuildToolPlugin {
219+
func createBuildCommands(
220+
context: PluginContext,
221+
target: Target
222+
) async throws -> [Command] {
223+
guard let swiftTarget = target as? SwiftSourceModuleTarget else {
224+
throw PluginError.invalidTarget
225+
}
226+
return try self.createBuildCommands(
227+
pluginWorkDirectory: context.pluginWorkDirectory,
228+
sourceFiles: swiftTarget.sourceFiles,
229+
tool: context.tool
230+
)
231+
}
232+
}
233+
234+
#if canImport(XcodeProjectPlugin)
235+
import XcodeProjectPlugin
236+
237+
extension GRPCSwiftPlugin: XcodeBuildToolPlugin {
238+
func createBuildCommands(
239+
context: XcodePluginContext,
240+
target: XcodeTarget
241+
) throws -> [Command] {
242+
return try self.createBuildCommands(
243+
pluginWorkDirectory: context.pluginWorkDirectory,
244+
sourceFiles: target.inputFiles,
245+
tool: context.tool
246+
)
247+
}
248+
}
249+
#endif

Sources/protoc-gen-grpc-swift/Docs.docc/spm-plugin.md

+20-19
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,8 @@ There are multiple ways to do this. Some of the easiest are:
2222
1. If you are on macOS, installing it via `brew install protobuf`
2323
2. Download the binary from [Google's github repository](https://github.com/protocolbuffers/protobuf).
2424

25-
### Adding the proto files to your target
26-
27-
Next, you need to add the `.proto` files for which you want to generate your Swift types to your target's
28-
source directory. You should also commit these files to your git repository since the generated types
29-
are now generated on demand.
30-
31-
> Note: imports on your `.proto` files will have to include the relative path from the target source to the `.proto` file you wish to import.
32-
> Files **must** be contained within the target source directory.
33-
3425
### Adding the plugin to your manifest
3526

36-
After adding the `.proto` files you can now add the plugin to the target inside your `Package.swift` manifest.
3727
First, you need to add a dependency on `grpc-swift`. Afterwards, you can declare the usage of the plugin
3828
for your target. Here is an example snippet of a `Package.swift` manifest:
3929

@@ -61,23 +51,33 @@ let package = Package(
6151

6252
### Configuring the plugin
6353

64-
Lastly, after you have added the `.proto` files and modified your `Package.swift` manifest, you can now
65-
configure the plugin to invoke the `protoc` compiler. This is done by adding a `grpc-swift-config.json`
66-
to the root of your target's source folder. An example configuration file looks like this:
54+
Configuring the plugin is done by adding a `grpc-swift-config.json` file anywhere in your target's sources. Before you start configuring the plugin, you need to add the `.proto` files to your sources. You should also commit these files to your git repository since the generated types are now generated on demand. It's also important to note that the proto files in your configuration should be in the same directory as the config file. Let's see an example to have a better understanding.
55+
56+
Here's an example file structure that looks like this:
57+
58+
```text
59+
Sources
60+
├── main.swift
61+
├── ProtoBuf
62+
├── grpc-swift-config.json
63+
├── foo.proto
64+
└── Bar
65+
└── Bar.proto
66+
```
6767

6868
```json
6969
{
7070
"invocations": [
7171
{
7272
"protoFiles": [
73-
"Path/To/Foo.proto",
73+
"Foo.proto",
7474
],
7575
"visibility": "internal",
7676
"server": false
7777
},
7878
{
7979
"protoFiles": [
80-
"Bar.proto"
80+
"Bar/Bar.proto"
8181
],
8282
"visibility": "public",
8383
"client": false,
@@ -87,11 +87,12 @@ to the root of your target's source folder. An example configuration file looks
8787
}
8888
```
8989

90-
> Note: paths to your `.proto` files will have to include the relative path from the target source to the `.proto` file location.
91-
> Files **must** be contained within the target source directory.
90+
> Note: paths to your `.proto` files will have to include the relative path from the config file directory to the `.proto` file location.
91+
> Files **must** be contained within the same directory as the config file.
9292
93-
In the above configuration, you declared two invocations to the `protoc` compiler. The first invocation
94-
is generating Swift types for the `Foo.proto` file with `internal` visibility. Notice the relative path to the `.proto` file.
93+
In the above configuration, notice the relative path of the `.proto` file with respect to the configuration file. If you add a file in the `Sources` folder, the plugin would be unable to access it as the path is computed relative to the `grpc-swift-config.json` file. So the `.proto` files have to be added within the `ProtoBuf` folder, with relative paths taken into consideration.
94+
Here, you declared two invocations to the `protoc` compiler. The first invocation
95+
is generating Swift types for the `Foo.proto` file with `internal` visibility.
9596
We have also specified the `server` option and set it to false: this means that server code won't be generated for this proto.
9697
The second invocation is generating Swift types for the `Bar.proto` file with the `public` visibility.
9798
Notice the `client` option: it's been set to false, so no client code will be generated for this proto. We have also set

0 commit comments

Comments
 (0)