Opening
I once shipped a release where silently divergent local build scripts produced different generated sources for the same commit — and CI won while developers lost. Teams are quietly wrestling with build-tool drift: per-developer shell scripts, unversioned generators, and ad-hoc fixes that show up as “works on my machine” bugs at release time. This week I’m arguing for moving those tools into the package graph so behavior is reproducible and auditable.
This Week’s Big Story
Automating Xcode Tasks with Swift Package Plugins
Tooling drift breaks releases and slows development; Swift Package Plugins let you put generators, linters, and other build-time code under version control. Use BuildToolPlugin when outputs are required by the compiler and CommandPlugin for optional developer tasks — but treat both as first-class, testable parts of your package. If you care about reproducible builds and actionable CI logs, package plugins are worth a hard look.
Trend Signals
• Show HN: TRELLIS.2 brings image-to-3D generation running natively on Apple Silicon — an early sign that heavy ML workloads are becoming more viable on-device. [Source: HackerNews]
• Swift.org is expanding IDE support by enabling more editors (Cursor, VSCodium, and others) to tap into Swift tooling via Open VSX-compatible extensions, which broadens where teams can write Swift effectively. [Source: Swift.org Blog]
• Cross-platform mobile guides like the Capacitor walkthrough keep surfacing; teams still evaluate trade-offs between native SwiftUI and web-backed approaches for non-core features. [Source: Medium]
• Deep-dive pieces on SwiftUI patterns (declarative types vs POP) suggest the community continues to wrestle with architecture choices as UIs grow in complexity. [Source: Medium]
Swift Snippet of the Week
import PackagePlugin
import Foundation
@main
struct GenerateSourcesPlugin: CommandPlugin {
func perform(command: PluginContext, arguments: [String]) throws {
// Write deterministic generated source into the plugin work directory so it's
// versioned as part of the package graph and reproducible across machines.
let workDir = command.pluginWorkDirectory
let outDir = workDir.appending("GeneratedSources")
try? FileManager.default.createDirectory(atPath: outDir.string, withIntermediateDirectories: true)
let generated = """
// This file is machine-generated by GenerateSourcesPlugin
// Package: \(command.package.displayName)
// Toolchain: SwiftPM plugin run (reproducible)
import Foundation
public enum GeneratedFeature {
public static let enabled = true
public static let buildIdentifier = "\(command.package.directory.lastComponent)-\(Date().timeIntervalSince1970)"
}
"""
let outFile = outDir.appending("GeneratedFeature.swift")
try generated.write(to: URL(fileURLWithPath: outFile.string), atomically: true, encoding: .utf8)
// … (truncated for newsletter)
This pattern matters because it forces deterministic, versioned generation into the package graph so teams can test, CI-validate, and trace build-time behavior instead of relying on unshared local scripts.
Community Picks
More community links next issue.
Until Next Time
If you manage an iOS codebase, try extracting one small generator or linter into a PackagePlugin this week and run it from CI — you’ll learn more about rollout, rollback, and observable failures than you expect. Hit reply with how you handled plugin rollouts across Xcode versions or forward this to a teammate who maintains build scripts; I want to hear what worked and what blew up.