Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,30 +1,32 @@
# Provide configurable packages using traits.

Define one or more traits to offer default and configurable features for a package.
Define one or more package traits to offer default and configurable features for a package.

## Overview

Swift packages prior to Swift 6.1 offered a non-configurable API surface for each version.
With Swift 6.1, packages may offer traits, which express a configurable API surface for a package.
Starting with Swift 6.1, packages may offer traits, which express a configurable API surface for a package.

Use traits to enable additional API beyond the core API of the package.
For example, a trait may enable an experimental API, optional extended functionality that requires additional dependencies, or functionality that isn't critical that a developer may want to enable only in specific circumstances.
For example, a trait may enable an experimental API, optional extended functionality that requires additional dependencies, or functionality that isn't critical that you may want to enable only in specific circumstances.

When Swift Package Manager builds with a requested trait, it does so across the package dependency tree, propagating that request for traits throughout the dependencies.
Traits affect all targets being built in the package as well as any dependencies, not just targets within a package.
Comment on lines +13 to +14
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this one's true as written. Traits live in per-package namespaces. If root package Foo has trait T enabled, and wants trait T enabled in dependency Bar, it has to convert (namespacing explicitly here for illustration purposes) Foo.T to Bar.T explicitly in Package.swift. @FranzBusch, @bripeticca do I understand this correctly?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll let Bri or Franz respond for sure but I believe Max to be correct here. I will comment that Traits are highly misunderstood. We need to be very crisp in the documentation. I'll take a closer look when I get back from my break next week.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's why I kept this in Draft - I want to make sure this is dead on correct and nailed down.

My confusion came from what you invoke a build with traits, you're giving it a set of strings to represent the traits - if two packages happen to offer traits that use the same name, are both enabled?

Copy link
Contributor

@MaxDesiatov MaxDesiatov Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My confusion came from what you invoke a build with traits, you're giving it a set of strings to represent the traits - if two packages happen to offer traits that use the same name, are both enabled?

For two different packages the answer in general is "no" (unless you wire up traits with same names between packages explicitly in Package.swift), but in which package namespace they live depends on which is a root and which is a dependency.

The set of strings represents traits in the top-level (root) package namespace.

Only traits in the root package are enabled by CLI options, the dependency needs them passed down explicitly. If the dependency is transitive, they need to be passed down explicitly multiple times at each dependency level in each Package.swift.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The key thing I keep coming back to is that traits are a way to specify feature optionality for a given package. We don't support features that span multiple packages. As Max mentioned, a package may request a trait to be enabled in the dependency on that package but that's actually unrelated to the traits in that package. (That said, we should be allowing dependencies and their traits setting to be conditional on a package's traits as well).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Essentially what @MaxDesiatov mentioned, specifying traits to enable through the CLI will only affect the enabled traits of the root package. There isn't currently a way to specify enabled traits for dependencies through the CLI, but one can configure enabled traits of a dependency in the root package's manifest.


> Note: Traits should never "remove" or disable public API when a trait is enabled.

Within the package, traits express conditional compilation, and may be used to declare additional dependencies that are enabled when that trait is active.
Within the package, traits express conditional compilation, and you can use them to declare additional dependencies that are enabled when that trait is active.
Swift Package Manager exposes enabled traits as conditional blocks (for example, `#if YourTrait`) that you can use to conditionally enable imports or different compilation paths in code.

Traits are identified by their names, which are name-spaced within the package that hosts them.
Trait names are name-spaced within the package that hosts them. <!-- what's this mean, exactly - what does this enable or impact? pending question to Bri -->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Covered in the comment above, it means that trait T declared in multiple packages are actually different unrelated traits.

Trait names must be [valid swift identifiers](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/lexicalstructure#Identifiers) with the addition of the characters of `-` and `+`.
The trait names `default` and `defaults` (regardless of any capitalization) aren't allowed to avoid confusion with the default traits that a package defines.

Enabled traits are exposed as conditional blocks (for example, `#if YourTrait`) that you can use to conditionally enable imports or different compilation paths in code.

## Overview
Don't use the trait names `default` or `defaults` (regardless of capitalization) to avoid confusion with the default traits that a package defines.

### Declaring Traits

Create a trait to define a discrete amount of additional functionality, and define it in the [traits](https://docs.swift.org/swiftpm/documentation/packagedescription/package/traits) property of the package manifest.
Use [`.default(enabledTraits:)`](https://docs.swift.org/swiftpm/documentation/packagedescription/trait/default(enabledtraits:)) to provide the set of traits that the package uses as a default.
If you don't define a default set of traits to enable, no traits are enabled by default.
If you don't define a default set of traits to enable, Swift Package Manager enables no traits by default.

The following example illustrates a single trait, `FeatureA`, that is enabled by default:

Expand All @@ -37,7 +39,7 @@ traits: [
// ...
```

Traits may also be used to represent a set of other traits, which allows you to group features together.
Traits can also represent a set of other traits, which allows you to group features together.
The following example illustrates defining three traits, and an additional trait (`B-and-C`) that enables both traits `FeatureB` and `FeatureC`:

```swift
Expand All @@ -46,18 +48,21 @@ traits: [
.trait(name: "FeatureA"),
.trait(name: "FeatureB"),
.trait(name: "FeatureC"),
.trait(name: "B-and-C", enabledTraits: ["FeatureB", "FeatureC"]).
.trait(name: "B-and-C", enabledTraits: ["FeatureB", "FeatureC"]),
.default(enabledTraits: ["FeatureA"]),
],
// ...
```

The traits enabled by default for the example above is `FeatureA`.
For the example above, the default trait is `FeatureA`.

> Note: Changing the default set of traits for your package is a major semantic version change if it removes API surface.
> Adding additional traits is not a major version change.

#### Mutually Exclusive Traits
Swift Package Manager treats traits as purely additive, and enables traits across all packages within the build graph.
Design your traits such that they enable additional API (and their dependencies, if needed).

#### Defining mutually exclusive traits

The package manifest format doesn't support declaring mutually exclusive traits.
In the rare case that you need to offer mutually exclusive traits, protect that scenario in code:
Expand All @@ -74,7 +79,7 @@ In the rare case that you need to offer mutually exclusive traits, protect that

Use the name of a trait for conditional compilation.
Wrap the additional API surface for that trait within a conditional compilation block.
For example, if the trait `FeatureA` is defined and enabled, the compiler see and compile the function `additionalAPI()`:
For example, if the trait `FeatureA` is defined and enabled, the compiler sees and compiles the function `additionalAPI()`:

```swift
#if FeatureA
Expand Down
Loading