Section 6/181 menit

6. Build Tool Plugins

6. Build Tool Plugins

Build Tool Plugin berjalan selama fase build dan menghasilkan source code atau resources secara otomatis. Output plugin masuk ke derived data, bukan di-commit ke repo.

Dua Jenis Build Tool Plugin

  1. Prebuild Plugin: Berjalan sebelum kompilasi, output bisa berupa source files atau resources
  2. Build Command Plugin: Berjalan sesuai dependency rule (hanya jika input berubah)

Contoh: Protobuf Code Generation Plugin

swift
// Package.swift — definisi plugin package
import PackageDescription

let package = Package(
    name: "ProtoPlugin",
    products: [
        .plugin(name: "ProtobufGeneratorPlugin", targets: ["ProtobufGeneratorPlugin"])
    ],
    dependencies: [
        .package(url: "https://github.com/grpc/grpc-swift", from: "1.0.0")
    ],
    targets: [
        .plugin(
            name: "ProtobufGeneratorPlugin",
            capability: .buildTool(),  // atau .command(intent:)
            dependencies: [
                .product(name: "protoc-gen-swift", package: "grpc-swift")
            ]
        )
    ]
)
swift
// Sources/ProtobufGeneratorPlugin/ProtobufGeneratorPlugin.swift
import PackagePlugin
import Foundation

@main
struct ProtobufGeneratorPlugin: BuildToolPlugin {
    func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] {
        // Pastikan executable tersedia
        let protoc = try context.tool(named: "protoc")
        let protocGenSwift = try context.tool(named: "protoc-gen-swift")
        
        guard let sourceTarget = target as? SourceModuleTarget else {
            return []
        }
        
        // Cari semua file .proto di target
        let protoFiles = sourceTarget.sourceFiles(withSuffix: "proto").map(\.path)
        
        guard !protoFiles.isEmpty else { return [] }
        
        // Output directory: derived data folder
        let outputDir = context.pluginWorkDirectory.appending("Generated")
        
        // Satu command per file .proto
        return protoFiles.map { protoFile in
            Command.buildCommand(
                displayName: "Generating Swift from \(protoFile.lastComponent)",
                executable: protoc.path,
                arguments: [
                    "--swift_out=\(outputDir)",
                    "--plugin=protoc-gen-swift=\(protocGenSwift.path)",
                    "--proto_path=\(protoFile.removingLastComponent())",
                    protoFile.string
                ],
                inputFiles: [protoFile],
                outputFiles: [
                    outputDir.appending("\(protoFile.stem).pb.swift")
                ]
            )
        }
    }
}

// Untuk Xcode (XcodeBuildToolPlugin protocol)
#if canImport(XcodeProjectPlugin)
import XcodeProjectPlugin

extension ProtobufGeneratorPlugin: XcodeBuildToolPlugin {
    func createBuildCommands(context: XcodePluginContext, target: XcodeTarget) throws -> [Command] {
        let protoFiles = target.inputFiles
            .filter { $0.path.extension == "proto" }
            .map(\.path)
        
        guard !protoFiles.isEmpty else { return [] }
        
        let protoc = try context.tool(named: "protoc")
        let protocGenSwift = try context.tool(named: "protoc-gen-swift")
        let outputDir = context.pluginWorkDirectory.appending("Generated")
        
        return protoFiles.map { protoFile in
            Command.buildCommand(
                displayName: "Generating Swift from \(protoFile.lastComponent)",
                executable: protoc.path,
                arguments: [
                    "--swift_out=\(outputDir)",
                    "--plugin=protoc-gen-swift=\(protocGenSwift.path)",
                    protoFile.string
                ],
                inputFiles: [protoFile],
                outputFiles: [outputDir.appending("\(protoFile.stem).pb.swift")]
            )
        }
    }
}
#endif
swift
// Package.swift — konsumen plugin
let package = Package(
    name: "MyApp",
    dependencies: [
        .package(path: "../ProtoPlugin"),
        .package(url: "https://github.com/grpc/grpc-swift", from: "1.0.0")
    ],
    targets: [
        .target(
            name: "MyApp",
            dependencies: [
                .product(name: "GRPC", package: "grpc-swift")
            ],
            plugins: [
                .plugin(name: "ProtobufGeneratorPlugin", package: "ProtoPlugin")
            ]
        )
    ]
)