Skip to content

Add support for XcodeProjectPlugin #1621

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

Merged
merged 4 commits into from
Jul 4, 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
83 changes: 64 additions & 19 deletions Plugins/GRPCSwiftPlugin/plugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ import Foundation
import PackagePlugin

@main
struct GRPCSwiftPlugin: BuildToolPlugin {
struct GRPCSwiftPlugin {
/// Errors thrown by the `GRPCSwiftPlugin`
enum PluginError: Error {
/// Indicates that the target where the plugin was applied to was not `SourceModuleTarget`.
case invalidTarget
/// Indicates that the file extension of an input file was not `.proto`.
case invalidInputFileExtension
/// Indicates that there was no configuration file at the required location.
case noConfigFound
}

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

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

func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] {
// Let's check that this is a source target
guard let target = target as? SourceModuleTarget else {
throw PluginError.invalidTarget
/// Create build commands for the given arguments
/// - Parameters:
/// - pluginWorkDirectory: The path of a writable directory into which the plugin or the build
/// commands it constructs can write anything it wants.
/// - sourceFiles: The input files that are associated with the target.
/// - tool: The tool method from the context.
/// - Returns: The build commands configured based on the arguments.
func createBuildCommands(
pluginWorkDirectory: PackagePlugin.Path,
sourceFiles: FileList,
tool: (String) throws -> PackagePlugin.PluginContext.Tool
) throws -> [Command] {
guard let configurationFilePath = sourceFiles.first(
where: {
$0.path.lastComponent == Self.configurationFileName
}
)?.path else {
throw PluginError.noConfigFound
}

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

try self.validateConfiguration(configuration)

var importPaths: [Path] = [target.directory]
let targetDirectory = configurationFilePath.removingLastComponent()
var importPaths: [Path] = [targetDirectory]
if let configuredImportPaths = configuration.importPaths {
importPaths.append(contentsOf: configuredImportPaths.map { Path($0) })
}
Expand All @@ -96,20 +111,17 @@ struct GRPCSwiftPlugin: BuildToolPlugin {
protocPath = Path(environmentPath)
} else {
// The user didn't set anything so let's try see if SPM can find a binary for us
protocPath = try context.tool(named: "protoc").path
protocPath = try tool("protoc").path
}
let protocGenGRPCSwiftPath = try context.tool(named: "protoc-gen-grpc-swift").path

// This plugin generates its output into GeneratedSources
let outputDirectory = context.pluginWorkDirectory
let protocGenGRPCSwiftPath = try tool("protoc-gen-grpc-swift").path

return configuration.invocations.map { invocation in
self.invokeProtoc(
target: target,
directory: targetDirectory,
invocation: invocation,
protocPath: protocPath,
protocGenGRPCSwiftPath: protocGenGRPCSwiftPath,
outputDirectory: outputDirectory,
outputDirectory: pluginWorkDirectory,
importPaths: importPaths
)
}
Expand All @@ -118,15 +130,15 @@ struct GRPCSwiftPlugin: BuildToolPlugin {
/// Invokes `protoc` with the given inputs
///
/// - Parameters:
/// - target: The plugin's target.
/// - directory: The plugin's target directory.
/// - invocation: The `protoc` invocation.
/// - protocPath: The path to the `protoc` binary.
/// - protocGenSwiftPath: The path to the `protoc-gen-swift` binary.
/// - outputDirectory: The output directory for the generated files.
/// - importPaths: List of paths to pass with "-I <path>" to `protoc`
/// - Returns: The build command.
/// - Returns: The build command configured based on the arguments
private func invokeProtoc(
target: Target,
directory: Path,
invocation: Configuration.Invocation,
protocPath: Path,
protocGenGRPCSwiftPath: Path,
Expand Down Expand Up @@ -166,7 +178,7 @@ struct GRPCSwiftPlugin: BuildToolPlugin {
for var file in invocation.protoFiles {
// Append the file to the protoc args so that it is used for generating
protocArgs.append("\(file)")
inputFiles.append(target.directory.appending(file))
inputFiles.append(directory.appending(file))

// The name of the output file is based on the name of the input file.
// We validated in the beginning that every file has the suffix of .proto
Expand Down Expand Up @@ -202,3 +214,36 @@ struct GRPCSwiftPlugin: BuildToolPlugin {
}
}
}

extension GRPCSwiftPlugin: BuildToolPlugin {
func createBuildCommands(
context: PluginContext,
target: Target
) async throws -> [Command] {
guard let swiftTarget = target as? SwiftSourceModuleTarget else {
throw PluginError.invalidTarget
}
return try self.createBuildCommands(
pluginWorkDirectory: context.pluginWorkDirectory,
sourceFiles: swiftTarget.sourceFiles,
tool: context.tool
)
}
}

#if canImport(XcodeProjectPlugin)
import XcodeProjectPlugin

extension GRPCSwiftPlugin: XcodeBuildToolPlugin {
func createBuildCommands(
context: XcodePluginContext,
target: XcodeTarget
) throws -> [Command] {
return try self.createBuildCommands(
pluginWorkDirectory: context.pluginWorkDirectory,
sourceFiles: target.inputFiles,
tool: context.tool
)
}
}
#endif
39 changes: 20 additions & 19 deletions Sources/protoc-gen-grpc-swift/Docs.docc/spm-plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,8 @@ There are multiple ways to do this. Some of the easiest are:
1. If you are on macOS, installing it via `brew install protobuf`
2. Download the binary from [Google's github repository](https://github.com/protocolbuffers/protobuf).

### Adding the proto files to your target

Next, you need to add the `.proto` files for which you want to generate your Swift types to your target's
source directory. You should also commit these files to your git repository since the generated types
are now generated on demand.

> 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.
> Files **must** be contained within the target source directory.

### Adding the plugin to your manifest

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

Expand Down Expand Up @@ -61,23 +51,33 @@ let package = Package(

### Configuring the plugin

Lastly, after you have added the `.proto` files and modified your `Package.swift` manifest, you can now
configure the plugin to invoke the `protoc` compiler. This is done by adding a `grpc-swift-config.json`
to the root of your target's source folder. An example configuration file looks like this:
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.

Here's an example file structure that looks like this:

```text
Sources
├── main.swift
├── ProtoBuf
├── grpc-swift-config.json
├── foo.proto
└── Bar
└── Bar.proto
```

```json
{
"invocations": [
{
"protoFiles": [
"Path/To/Foo.proto",
"Foo.proto",
],
"visibility": "internal",
"server": false
},
{
"protoFiles": [
"Bar.proto"
"Bar/Bar.proto"
],
"visibility": "public",
"client": false,
Expand All @@ -87,11 +87,12 @@ to the root of your target's source folder. An example configuration file looks
}
```

> Note: paths to your `.proto` files will have to include the relative path from the target source to the `.proto` file location.
> Files **must** be contained within the target source directory.
> Note: paths to your `.proto` files will have to include the relative path from the config file directory to the `.proto` file location.
> Files **must** be contained within the same directory as the config file.

In the above configuration, you declared two invocations to the `protoc` compiler. The first invocation
is generating Swift types for the `Foo.proto` file with `internal` visibility. Notice the relative path to the `.proto` file.
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.
Here, you declared two invocations to the `protoc` compiler. The first invocation
is generating Swift types for the `Foo.proto` file with `internal` visibility.
We have also specified the `server` option and set it to false: this means that server code won't be generated for this proto.
The second invocation is generating Swift types for the `Bar.proto` file with the `public` visibility.
Notice the `client` option: it's been set to false, so no client code will be generated for this proto. We have also set
Expand Down