-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Traits update #9554
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
heckj
wants to merge
2
commits into
swiftlang:main
Choose a base branch
from
heckj:traits-update
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+21
−16
Draft
Traits update #9554
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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. | ||
|
|
||
| > 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 --> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Covered in the comment above, it means that trait |
||
| 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: | ||
|
|
||
|
|
@@ -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 | ||
|
|
@@ -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: | ||
|
|
@@ -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 | ||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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
Foohas traitTenabled, and wants traitTenabled in dependencyBar, it has to convert (namespacing explicitly here for illustration purposes)Foo.TtoBar.Texplicitly inPackage.swift. @FranzBusch, @bripeticca do I understand this correctly?There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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.