Section 10/171 menit

10. Plugin: Build Tool dan Command

10. Plugin: Build Tool dan Command

Sejak Swift 5.6, SPM mendukung plugin — Swift code yang dijalankan saat build atau on-demand.

Build Tool Plugin

Jalan otomatis saat build, biasanya untuk codegen.

Use case: generate Swift code dari .proto, .graphql, atau .json schema.

swift
// Package.swift
targets: [
    .plugin(
        name: "ProtobufGenerator",
        capability: .buildTool(),
        dependencies: ["protoc"]
    ),
    .target(
        name: "MyApp",
        plugins: ["ProtobufGenerator"]
    ),
]
swift
// Plugins/ProtobufGenerator/plugin.swift
import PackagePlugin

@main
struct ProtobufGenerator: BuildToolPlugin {
    func createBuildCommands(
        context: PluginContext,
        target: Target
    ) async throws -> [Command] {
        guard let sourceTarget = target as? SourceModuleTarget else { return [] }
        let protoFiles = sourceTarget.sourceFiles.filter { $0.path.extension == "proto" }

        return protoFiles.map { file in
            let outputPath = context.pluginWorkDirectory
                .appending("\(file.path.stem).pb.swift")

            return .buildCommand(
                displayName: "Generating \(file.path.stem).pb.swift",
                executable: try context.tool(named: "protoc").path,
                arguments: [
                    "--swift_out=\(outputPath.removingLastComponent())",
                    file.path.string,
                ],
                inputFiles: [file.path],
                outputFiles: [outputPath]
            )
        }
    }
}

Command Plugin

Dijalankan on-demand via swift package <plugin-name> atau dari menu Xcode → File → Packages → ...

Use case: linting, formatting, code generation manual.

swift
.plugin(
    name: "SwiftFormatRun",
    capability: .command(
        intent: .sourceCodeFormatting(),
        permissions: [.writeToPackageDirectory(reason: "Format files")]
    )
)
swift
@main
struct SwiftFormatRun: CommandPlugin {
    func performCommand(context: PluginContext, arguments: [String]) async throws {
        let tool = try context.tool(named: "swiftformat")
        let process = Process()
        process.executableURL = URL(fileURLWithPath: tool.path.string)
        process.arguments = ["."]
        try process.run()
        process.waitUntilExit()
    }
}

Run:

swift
swift package plugin --allow-writing-to-package-directory SwiftFormatRun