Why does SwiftPM refuse a dependency that uses unsafeFlags, and what should you do instead?
Predict first
What happens to the same unsafeFlags target as a URL dependency and as a local path dependency?
// === run.sh ===
export GIT_AUTHOR_NAME=t GIT_AUTHOR_EMAIL=t@t GIT_COMMITTER_NAME=t GIT_COMMITTER_EMAIL=t@t
W=$(mktemp -d); cd $W
mkdir -p Lib/Sources/Lib && cd Lib && git init -q -b main
cat > Package.swift <<'E'
// swift-tools-version: 6.0
import PackageDescription
let package = Package(name: "Lib", products: [.library(name: "Lib", targets: ["Lib"])],
targets: [.target(name: "Lib", swiftSettings: [.unsafeFlags(["-warn-concurrency"])])])
E
echo 'public let v = 1' > Sources/Lib/V.swift; git add -A; git commit -qm 1; git tag 1.0.0
cd ..
for kind in 'url: "file://'$W'/Lib", from: "1.0.0"' 'path: "../Lib"'; do
d=Root-$(echo "$kind" | cut -c1-3); mkdir -p $d/Sources/Root; cat > $d/Package.swift <<E
// swift-tools-version: 6.0
import PackageDescription
let package = Package(name: "Root", dependencies: [.package($kind)],
targets: [.target(name: "Root", dependencies: [.product(name: "Lib", package: "Lib")],
swiftSettings: [.unsafeFlags(["-warn-concurrency"])])])
E
echo 'import Lib' > $d/Sources/Root/R.swift
echo "${kind%%:*} dependency: $(cd $d; swift build 2>&1 | grep -E 'error:|Build complete' | head -1)"
done
- url: the build fails with "contains unsafe build flags"; path: Build complete
- Both fail
- Both succeed because the root also uses the flag
Mental model. It is a vendor rule: you can customise your own shop floor however you like, but anything you ship to other shops must fit the standard fixtures, because they cannot inspect what you bolted on.
Escape hatches such as swiftSettings: [.unsafeFlags([…])] (and the cSettings and linkerSettings equivalents) pass arbitrary compiler or linker options. SwiftPM allows them in the root package and in local (path:) dependencies, but rejects them in any dependency fetched by version or URL, because an arbitrary flag can change what other packages see, break the resolver’s assumption that a version is a reproducible unit, and cannot be reasoned about for the whole graph. In the check, the same package builds fine as a path: dependency but a url: dependency fails with “the target 'Lib' in product 'Lib' contains unsafe build flags”, while the root’s own use of the flag is fine in both runs.
So a library meant for others must express what it needs with the typed API: .define, .enableUpcomingFeature, .enableExperimentalFeature, .swiftLanguageMode, .headerSearchPath, .linkedFramework, strict-concurrency via the language mode, and traits (Q626) for optional features. If a flag is only needed for your app (a warning flag, a sanitizer, -Xfrontend -warn-long-function-bodies), put it in the root package, an xcconfig (OTHER_SWIFT_FLAGS, Q369) or the command line (swift build -Xswiftc …). When the flag is genuinely unavoidable (some performance or C interop switch), the options are to vendor the code into your repo as a path: package, ship it as a prebuilt binary target (Q366), or accept an Xcode project target for it. Treat unsafeFlags in a library as a signal to look for the supported spelling first.
Unsafe flags are allowed for the root and for local path dependencies, not for versioned remote ones.
Common traps
- Using
unsafeFlagsin a package you publish. Consumers by URL cannot even resolve it. - Putting app-wide warning flags in a dependency. Configure them in the app’s build settings instead.
They might follow up with
- Does a local
path:dependency get the exemption on purpose? Yes: it is part of your own checkout, so you control it. - How do I turn on strict concurrency for a package target? With the language mode or the typed feature setting, no unsafe flag needed (Q625).
Related questions
- 16.7.5 How do package mirrors work, and when do you use one for a company or offline CI?
- 16.8.3 How do per-target Swift language modes let a package migrate to Swift 6 one module at a time?
- 16.1.1 What are targets, products and modules in a Swift package, and what does a first Package.swift look like?
- 16.2.2 What is an XCFramework, what do module maps and library evolution have to do with it, and when do you use a…
- 16.4.1 What are build configurations and build settings, how do xcconfig files layer, and how does #if DEBUG work?
← Two dependencies need incompatible versions of a third. What does the… How do package mirrors work, and when do you use one for a company or… →