Slide 1

Slide 1 text

Package Traits @ikesyo / Sho Ikeda Osaka.swift #1 2025-01-12 Sun #osaka_swift 1

Slide 2

Slide 2 text

@ikesyo / Sho Ikeda • ͍͚͠ΐʔʗ஑ా ᠳ • https://github.com/ikesyo • Swift Contributor • Formerly creator/maintainer of Himotoki, Carthage, ReactiveSwift, Quick, Nimble, ... • Other contributions: giginet/Scipio, Renovate, ... 2

Slide 3

Slide 3 text

Agenda • What is Package Traits • How to use it • Use cases • Recap 3

Slide 4

Slide 4 text

What is Package Traits SE-0450 Package Traits • Traits = ಛੑʢ㲈 Feature, ػೳʣ • Something like a feature flag • Will be shipped in Swift 6.1 4

Slide 5

Slide 5 text

What is Package Traits SE-450 Package Traits • Motivation • Pluggable dependencies • Configurable behavior • Replacing environment variables in Package manifests • Experimental APIs 5

Slide 6

Slide 6 text

How to use it Examples • https://github.com/ikesyo/SwiftPMPackageTraitsExample1 • https://github.com/ikesyo/SwiftPMPackageTraitsExample2 6

Slide 7

Slide 7 text

Define Traits SwiftPMPackageTraitsExample1/Package.swift // swift-tools-version: 6.1 import PackageDescription let package = Package( name: "SwiftPMPackageTraitsExample1", ... traits: [ "Foo", "Bar", .trait(name: "FooBar", enabledTraits: ["Foo", "Bar"]), .default(enabledTraits: []), ], 7

Slide 8

Slide 8 text

Conditional dependencies SwiftPMPackageTraitsExample1/Package.swift dependencies: [ .package( url: "https://github.com/apple/swift-collections", from: "1.0.0", ), .package( url: "https://github.com/apple/swift-numerics", from: "1.0.0", ), ], 8

Slide 9

Slide 9 text

Conditional dependencies SwiftPMPackageTraitsExample1/Package.swift targets: [ .target( name: "SwiftPMPackageTraitsExample1", dependencies: [ .product( name: "Collections", package: "swift-collections", condition: .when(traits: ["Foo"]) ), .product( name: "Numerics", package: "swift-numerics", condition: .when(traits: ["Bar"]) ), ] ), ] 9

Slide 10

Slide 10 text

Conditional compilation SwiftPMPackageTraitsExample1/Echo.swift #if Foo import Collections #endif #if Bar import Numerics #endif public func echo() { #if Foo print("Foo is enabled: using Collections' OrderedSet") print("OrderedSet: \(["Foo", "Bar", "Baz"] as OrderedSet)") #endif #if Bar print("Bar is enabled: using Numerics' Complex") print("Complex: \(Complex(1, 1))") #endif #if FooBar print("FooBar is enabled") #endif } 10

Slide 11

Slide 11 text

Execution Default (no traits): $ swift test Test Suite 'All tests' started at 2025-01-11 10:32:02.229. Test Suite 'All tests' passed at 2025-01-11 10:32:02.230. Executed 0 tests, with 0 failures (0 unexpected) in 0.000 (0.001) seconds ◇ Test run started. ↳ Testing Library Version: 6.1 (a9f21aa1a8cd486) ↳ Target Platform: arm64-apple-macosx ◇ Test example() started. ✔ Test example() passed after 0.001 seconds. ✔ Test run with 1 test passed after 0.001 seconds. 11

Slide 12

Slide 12 text

Execution Foo is enabled: $ swift test --traits Foo Test Suite 'All tests' started at 2025-01-11 10:32:24.621. Test Suite 'All tests' passed at 2025-01-11 10:32:24.622. Executed 0 tests, with 0 failures (0 unexpected) in 0.000 (0.001) seconds ◇ Test run started. ↳ Testing Library Version: 6.1 (a9f21aa1a8cd486) ↳ Target Platform: arm64-apple-macosx ◇ Test example() started. Foo is enabled: using Collections' OrderedSet OrderedSet: ["Foo", "Bar", "Baz"] ✔ Test example() passed after 0.001 seconds. ✔ Test run with 1 test passed after 0.001 seconds. 12

Slide 13

Slide 13 text

Execution Bar is enabled: $ swift test --traits Bar Test Suite 'All tests' started at 2025-01-11 10:32:41.077. Test Suite 'All tests' passed at 2025-01-11 10:32:41.078. Executed 0 tests, with 0 failures (0 unexpected) in 0.000 (0.001) seconds ◇ Test run started. ↳ Testing Library Version: 6.1 (a9f21aa1a8cd486) ↳ Target Platform: arm64-apple-macosx ◇ Test example() started. Bar is enabled: using Numerics' Complex Complex: (1.0, 1.0) ✔ Test example() passed after 0.001 seconds. ✔ Test run with 1 test passed after 0.001 seconds. 13

Slide 14

Slide 14 text

Execution FooBar is enabled: $ swift test --traits FooBar Test Suite 'All tests' started at 2025-01-11 10:32:54.602. Test Suite 'All tests' passed at 2025-01-11 10:32:54.603. Executed 0 tests, with 0 failures (0 unexpected) in 0.000 (0.001) seconds ◇ Test run started. ↳ Testing Library Version: 6.1 (a9f21aa1a8cd486) ↳ Target Platform: arm64-apple-macosx ◇ Test example() started. Foo is enabled: using Collections' OrderedSet OrderedSet: ["Foo", "Bar", "Baz"] Bar is enabled: using Numerics' Complex Complex: (1.0, 1.0) FooBar is enabled ✔ Test example() passed after 0.001 seconds. ✔ Test run with 1 test passed after 0.001 seconds. 14

Slide 15

Slide 15 text

Configure traits of dependencies SwiftPMPackageTraitsExample2/Package.swift let package = Package( name: "SwiftPMPackageTraitsExample2", ... traits: [ "Hoge", "Fuga", .trait( name: "HogeFuga", enabledTraits: ["Hoge", "Fuga"] ), .default(enabledTraits: []), ], 15

Slide 16

Slide 16 text

Configure traits of dependencies SwiftPMPackageTraitsExample2/Package.swift dependencies: [ .package( url: "https://github.com/ikesyo/SwiftPMPackageTraitsExample1", branch: "main", traits: [ // .default, "Foo", .trait(name: "Bar", condition: .when(traits: ["Fuga"])), .trait(name: "FooBar", condition: .when(traits: ["HogeFuga"])), ], ), ], 16

Slide 17

Slide 17 text

Use cases Pluggable dependencies • Example: Swift OpenAPI Generator • Separated packages for each implementation backend • Clients: • apple/swift-openapi-urlsession • swift-server/swift-openapi-async-http-client • Servers • swift-server/swift-openapi-vapor • swift-server/swift-openapi-hummingbird • swift-server/swift-openapi-lambda 17

Slide 18

Slide 18 text

Use cases Pluggable dependencies • Caveat: Currently all dependencies are resolved/checkout even if some dependencies are not used for enabled traits • This is listed in Future directions of the proposal: Consider traits during dependency resolution The implementation to this proposal only considers traits after the dependency resolution when constructing the module graph. This is inline with how platform specific dependencies are currently handled. In the future, both platform specific dependencies and traits can be taken into consideration during dependency resolution to avoid fetching an optional dependency that is not enabled by a trait. Changing this doesn't require a Swift evolution proposal since it is just an implementation detail of how dependency resolution currently works. 18

Slide 19

Slide 19 text

Use cases Configurable behavior Trait is like SWIFT_ACTIVE_COMPILATION_CONDITIONS which can be controlled from outside. • Enable/Disable logging • Feature flags • ... 19

Slide 20

Slide 20 text

Use cases Replacing environment variables in Package manifests A lot of packages are using environment variables in their Package.swift to configure their package. This has various reasons such as optional dependencies or setting certain defines for local development. Using environment variables inside Package.swift is not officially supported and with stricter sandboxing rules might break in the future. For example: • https://github.com/firebase/firebase-ios-sdk/blob/52d9152e5ac3ae6d2fefc872a3a6c1697d26d3e7/ Package.swift#L1328-L1558 • https://github.com/groue/GRDB.swift/blob/3ecb5c54553559217592d21a6d9841becb891b38/ Package.swift#L13-L28 20

Slide 21

Slide 21 text

Use cases Experimental APIs Some packages want to introduce new functionality without yet committing to a stable public API Currently, those modules and APIs are often underscored or specifically annotated. While this approach works it comes with downsides such as hiding the APIs in code completion. 21

Slide 22

Slide 22 text

Recap • Package Traits (hopefully) will be shipped in Swift 6.1 / Xcode 16.3 • Xcode 16.3 may have traits specification GUI on Package Dependencies? • Packages can define traits and use them for conditional dependencies and conditional compilation • Can customize traits of its dependencies unconditionally, or conditionally depending of their own traits • Consumers can use default traits, disable default traits, or enable specific traits • swift build/run/test commands now accepts trait-related options • --traits Trait1,Trait2 • --enable-all-traits • --disable-default-traits 22

Slide 23

Slide 23 text

! Happy Package Traits Life 23

Slide 24

Slide 24 text

Thank you @ikesyo / Sho Ikeda 24