How to make a SwiftPM target trait-only?

i have a Swift 6.2 SwiftPM project which uses package traits.

it has a target that only makes sense to build (when running swift build) if a particular trait is enabled.

that target has many files, and i do not want to have to #if #else guard every single file in that target.

how do i make it so that this target is only built if the prerequisite trait is enabled? (i tried asking an LLM, but it did not have a sane solution.)

You can use a wrapper target with a conditional dependency:

let package = Package(
    name: "Demo",
    products: [
        .library(
            name: "FooWrapper",
            type: .static,
            targets: ["FooWrapper"]
        ),
    ],
    traits: [
        "Foo",
        .default(enabledTraits: ["Foo"])
    ],
    targets: [
        .target(
            name: "FooWrapper",
            dependencies: [
                .target(
                    name: "FooImpl",
                    condition: .when(traits: ["Foo"])
                )
            ]
        ),
        .target(name: "FooImpl")
    ]
)

Practically, you would also need an "umbrella" file in Sources/FooWrapper:

// Sources/FooWrapper/dummy.swift
#if Foo
@_exported import FooImpl
#endif

The catch of this practice is that we must remember never to build or depend on FooImpl directly, we can only build or depend on FooWrapper. We can insert some lines in any file under Sources/FooImpl to get ourselves notified when the above rule is violated:

// Sources/FooImpl/xxx.swift
#if !Foo
#error("FooImpl is leaked into the build graph without trait 'Foo' being enabled")
#else
2 Likes

Putting conditions on targets as a whole is definitely a feature missing from the manifest model.

5 Likes