How do swiftSettings and their .when conditions work in a package, and which targets do they reach?
Predict first
Which conditions are active in debug and release, and does App see Core’s define?
// === Package.swift ===
// swift-tools-version: 6.0
import PackageDescription
let package = Package(
name: "App",
platforms: [.macOS(.v13)],
targets: [
.target(name: "Core", swiftSettings: [
.define("STAGING"),
.define("VERBOSE", .when(configuration: .debug)),
.define("APPLE", .when(platforms: [.macOS, .iOS])),
]),
.executableTarget(name: "App", dependencies: ["Core"]),
]
)
// === Sources/Core/Core.swift ===
public func flags() -> [String] {
var f: [String] = []
#if STAGING
f.append("STAGING")
#endif
#if VERBOSE
f.append("VERBOSE")
#endif
#if DEBUG
f.append("DEBUG")
#endif
#if APPLE
f.append("APPLE")
#endif
return f
}
// === Sources/App/main.swift ===
import Core
print(flags())
#if STAGING
print("App also sees STAGING")
#else
print("App does not see STAGING")
#endif
// === run.sh ===
echo "debug: $(swift run 2>&1 | tail -2 | tr '\n' ' ')"
echo "release: $(swift run -c release 2>&1 | tail -2 | tr '\n' ' ')"
- debug: STAGING, VERBOSE, DEBUG, APPLE; release: STAGING, APPLE; App does not see STAGING
- both configurations print the same list
- App sees STAGING because it depends on Core
Mental model. Settings are labels stuck on one box of parts, not a notice on the whole warehouse: the box next door never reads them.
Each target has its own swiftSettings (and cSettings, linkerSettings), and a setting can carry a condition: .when(configuration: .debug) or .release, .when(platforms: [.macOS, .iOS]), and (tools 6.1) .when(traits:). In the check, the Core target defines STAGING always, VERBOSE only in debug and APPLE only on macOS or iOS. A debug run prints ["STAGING", "VERBOSE", "DEBUG", "APPLE"] and a release run ["STAGING", "APPLE"]: SwiftPM itself defines DEBUG for debug builds (Xcode does the same through SWIFT_ACTIVE_COMPILATION_CONDITIONS, Q369) and nothing for release. Crucially the App target does not see STAGING: a define applies to the module it is declared on, not to its dependents or dependencies, which is why a flag that should reach several modules has to be repeated or moved into a shared helper such as a function in one module.
Consequences for design. Compile-time conditions create different binaries, so keep them few and well tested in both configurations (a CI job that only builds debug will not see a release-only compile error). Do not use a condition for something you can decide at run time with a flag (Q372); use it for code that cannot compile everywhere (#if os(macOS) APIs) or for debug-only tooling. Conditions are also how one package targets several platforms: platforms: sets the minimum deployment version (next question), while .when(platforms:) turns individual settings or dependencies on and off. Remember that an .unsafeFlags escape is not allowed for dependencies (Q620) and that a manifest is evaluated once per package, so it cannot branch on the running machine except through #if on the host.
Conditional defines follow configuration and platform, and apply only to the target that declares them.
Common traps
- Defining a flag on the app target and expecting a library target to honour it. Each module is compiled with its own settings.
- Only ever building debug. A
#if !DEBUGbranch can hold a compile error nobody sees.
They might follow up with
- Where does DEBUG come from? SwiftPM adds it for debug builds; Xcode sets it in the build settings.
- How do I share a flag across modules? Repeat the define in each target, or read one value from a module they all import.
Related questions
- 16.8.2 What does the platforms: array in Package.swift mean, and why can a dependency stop your build?
- 16.7.4 Why does SwiftPM refuse a dependency that uses unsafeFlags, and what should you do instead?
- 16.8.4 What are package traits, and how do they differ from build configurations or separate packages for optional…
- 16.4.1 What are build configurations and build settings, how do xcconfig files layer, and how does #if DEBUG work?
- 16.5.2 How do you use feature flags: compile-time versus runtime, ownership, and modularisation by team?
What does the platforms: array in Package.swift mean, and why can a… →