SwiftPM Traits not working correctly with multiple targets in same Xcode workspace (App + App Clip)

Hi, I've just migrated to Swift Tools 6.2 and package traits, and I'm encountering an issue when using traits with multiple targets in the same Xcode workspace.

Setup:

  • Main iOS app target
  • App Clip target
  • Both consume the same local packages (e.g., UIComponents)

What I'm trying to achieve:

  • Main app imports packages without the COMPACT_BUILD trait
  • App Clip imports packages with the COMPACT_BUILD trait enabled

Package configuration (simplified):

// UIComponents/Package.swift
let package = Package(
    name: "UIComponents",
    platforms: [.iOS(.v18)],
    traits: [
        .trait(name: "COMPACT_BUILD", description: "Minimal build for App Clips"),
    ],
    // ...
    targets: [
        .target(
            name: "UIComponents",
            dependencies: [...],
            swiftSettings: [
                .define("COMPACT_BUILD", .when(traits: ["COMPACT_BUILD"])),
            ]
        ),
    ]
)

In the code:

#if COMPACT_BUILD
// Excluded from App Clip
#endif

The consumer packages:

Main app's package imports without trait:

.package(path: "../UIComponents")

App Clip's package imports with trait:

.package(path: "../UIComponents", traits: ["COMPACT_BUILD"])

The problem:
When building the main app target, the COMPACT_BUILD compiler condition is unexpectedly active — even though the main app's dependency chain never enables that trait. It seems like the trait enabled by the App Clip target is "leaking" into the main app build.

I confirmed this by adding #error("COMPACT_BUILD is active") — it triggers when building the main app, which shouldn't happen.

If I disable the App Clip target from the build scheme, the main app builds correctly with COMPACT_BUILD not defined.

Environment:

  • Xcode 26.2
  • swift-tools-version: 6.2
  • iOS 26.2

Questions:

  1. Is this expected behavior? Are traits resolved workspace-wide rather than per-target?
  2. Is there a workaround to have different trait configurations for different targets consuming the same package?
  3. Or do I need to fall back to separate package targets (e.g., UIComponents and UIComponentsCompact) to achieve this?

Any guidance would be appreciated. Thanks!

Could you raise a

Yes, traits are workspace wide, or should I say package graph wide.

Traits are meant to provide optionality in the package graph, to add model elements, like build settings, or not based in the enablement of the trait. They are resolved when the package graph is created, long before we know what’s being built.

We’re probably missing the concept you are looking for. Please raise a Github Issue on swift-package-manager and we can give that some thought.

One common workaround I’ve seen is people checking environment variable values to alter the package definition in code. It’s ugly and we want something better, but may unblock you.


Thanks for confirming. Unfortunately I think the environment variable workaround doesn't help in my case — when building the main app, Xcode also builds the embedded App Clip, and it seems by that point the Package.swift has already been evaluated once for the entire workspace.

I'll open a GitHub issue. What would be most helpful to include — a minimal reproduction project, or is a description of the use case sufficient?

I think a description would suffice. Describe what were you trying to accomplish, what you were hoping Traits would provide, or a similar concept that would work for you, things like that.

Chatting with @bripeticca who’s working now on the Traits feature, we realized that traits being in with the other conditions was probably incorrect. Those conditions are really build conditions, where traits as I mentioned are a modelling concept and probably should have been a separate parameter. I was confused for a long time but makes sense now that I think of it that way. It hit home when I looked at the TraitCondition implementation in SwiftPM and it just returns true when evaluated against the build environment. Because if it was false, the whole setting with the TraitCondition wouldn’t even be there.

1 Like