Hey!
Wanted to follow up and refine what I suggested earlier. After some discussions with @FranzBusch and @kukushechkin, and actually working on this in code, I realized there is probably a better solution here. It's along similar lines to everything suggested in this thread earlier, but I think this new solution is much more robust and am looking forward to hearing about what you all think.
This is how I'd imagine things can be defined here:
// swift-tools-version: 6.4
import PackageDescription
let package = Package(
name: "MyPackage",
products: [
.library(
name: "MyPackage",
targets: ["MyTarget"]
),
],
traits: [
"SomeTrait",
"AnotherTrait",
],
targets: [
.target(
name: "MyTarget"
),
.testTarget(
name: "NoTraitTests",
dependencies: ["MyTarget"],
traitConfigurations: [.disableAllTraits]
),
// A target can declare several combinations and runs under each of them.
.testTarget(
name: "WithSomeTraitTests",
dependencies: ["MyTarget"],
traitConfigurations: [
.enabledTraits(["SomeTrait"]),
.enabledTraits(["SomeTrait", "AnotherTrait"]),
]
),
// Targets declaring the same combination share one build.
.testTarget(
name: "WithBothTraitsTests",
dependencies: ["MyTarget"],
traitConfigurations: [.enabledTraits(["AnotherTrait", "SomeTrait"])]
),
]
)
Running swift test --enable-trait-configurations would then do the following:
Running tests with all traits disabled
Test Suite 'NoTraitTests' passed ...
Running tests with traits: SomeTrait
Test Suite 'WithSomeTraitTests' passed ...
Running tests with traits: AnotherTrait, SomeTrait
Test Suite 'WithSomeTraitTests' passed ...
Test Suite 'WithBothTraitsTests' passed ...
This will run multiple builds, one for each unique trait combination specified across all test targets' traitConfigurations. For each combination's build, it'll find all the test targets that are relevant and will run those. The last two targets in the example declare the same combination, so they share one build, even though in a separate order. Passing in multiple configurations is also supported as shown above, which you can see in action above: WithSomeTraitTests runs twice under different configurations.
I initially considered an array of sets that would represent all applicable combinations, but instead landed on mirroring SwiftPM's existing TraitConfiguration. It lets you express .enableAllTraits without enlisting every trait manually, and lets you type .default instead of re-writing the default traits every time.
This makes it fully opt-in, and doesn't break any existing code or expectations that come with swift test, or anything else for that matter--which I now realize I wrongly suggested earlier.
Another build optimization, per @FranzBusch's suggestion, has been made. Instead of all configurations being in the same build directory, each one now builds into its own sub-folder. In my testing, a second run of a small example package dropped from 27 seconds to 4.
I've kept this to happen only for this flag in specific to avoid messing with other expectations elsewhere, but is definitely something that could be expanded beyond this feature in the future I suppose.
Curious to hear from others about what they think of this shape, and whether anything here feels off before I take it further! :)