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
- Prebuild Plugin: Berjalan sebelum kompilasi, output bisa berupa source files atau resources
- 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")
]
)
]
)