Architecture • 16 August 2026 • Written by Lochan Chugh
SPM Conditional Plugins: Optimizing Multi-Platform Build Graphs
SPM Conditional Plugins: Optimizing Multi-Platform Build Graphs
When building multi-platform apps that compile for iOS, macOS, watchOS, or visionOS, target-level build tools can significantly impact compile times. If your package manifest applies code-generation or linter plugins (like SwiftLint or SwiftFormat) unconditionally, the package manager compiles and executes those tools for every target destination.
For resource-constrained platforms or embedded systems (like watchOS), compiling a heavy linter utility or helper CLI can cause compilation timeouts or build graph bloat. Swift 6.4 addresses this with Package Manager Conditional Plugins (SE-0542), allowing you to restrict plugin execution to specific platforms or configurations.
Restricting Plugins in Package.swift
SE-0542 introduces a target-level condition parameter, permitting declarative constraints on when a plugin is active.
Here is how you limit a code-generation plugin to run only on macOS and iOS, leaving watchOS builds clean and fast:
// swift-tools-version: 6.4
import PackageDescription
let package = Package(
name: "CoreEngine",
platforms: [
.macOS(.v14),
.iOS(.v17),
.watchOS(.v10)
],
dependencies: [
// We keep the plugin package dependency definition global
.package(url: "https://github.com/apple/swift-format", from: "0.50900.0")
],
targets: [
.target(
name: "CoreEngine",
dependencies: [],
plugins: [
// SE-0542: Restrict the formatting plugin to iOS and macOS.
// It is ignored entirely when compiling the watchOS target.
.plugin(
name: "SwiftFormatPlugin",
package: "swift-format",
condition: .when(platforms: [.iOS, .macOS])
)
]
)
]
)
Why Preprocessor Hacks Fail in Manifests
Before SE-0542, developers frequently tried to exclude plugins using standard preprocessor compiler checks inside the package manifest:
// FAILED ATTEMPT: Checking the host operating system
#if os(macOS)
let targetPlugins: [Target.PluginUsage] = [.plugin(name: "SwiftLintPlugin")]
#else
let targetPlugins: [Target.PluginUsage] = []
#endif
.target(
name: "SharedUI",
plugins: targetPlugins
)
This workaround compiles but fails at runtime. Because Package.swift is executed by the package manager on the host compiler (which is almost always macOS during local development or CI), os(macOS) always evaluates to true. When compiling the target for an iOS device or a watchOS simulator, the package manager still includes the plugin in the build graph. The compilation fails because the plugin’s underlying CLI is not compiled for the target architecture.
Summary
SE-0542 provides fine-grained control over your package build pipeline. By restricting heavy linters, formatters, and code generators to compatible platforms, you avoid build failures and speed up compilations. Keep in mind that default settings are scoped to the package itself; if another project imports your package as a dependency, your default settings won’t impact their targets.