Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Package Traits

Sho Ikeda
January 12, 2025

Package Traits

「Osaka.swift #1」での発表資料です #osaka_swift

- https://osaka-swift.connpass.com/event/336324/

Sho Ikeda

January 12, 2025
Tweet

More Decks by Sho Ikeda

Other Decks in Programming

Transcript

  1. @ikesyo / Sho Ikeda • ͍͚͠ΐʔʗ஑ా ᠳ • https://github.com/ikesyo •

    Swift Contributor • Formerly creator/maintainer of Himotoki, Carthage, ReactiveSwift, Quick, Nimble, ... • Other contributions: giginet/Scipio, Renovate, ... 2
  2. What is Package Traits SE-0450 Package Traits • Traits =

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

    Pluggable dependencies • Configurable behavior • Replacing environment variables in Package manifests • Experimental APIs 5
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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
  9. 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
  10. 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
  11. Configure traits of dependencies SwiftPMPackageTraitsExample2/Package.swift let package = Package( name:

    "SwiftPMPackageTraitsExample2", ... traits: [ "Hoge", "Fuga", .trait( name: "HogeFuga", enabledTraits: ["Hoge", "Fuga"] ), .default(enabledTraits: []), ], 15
  12. 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
  13. 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
  14. 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
  15. Use cases Configurable behavior Trait is like SWIFT_ACTIVE_COMPILATION_CONDITIONS which can

    be controlled from outside. • Enable/Disable logging • Feature flags • ... 19
  16. 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
  17. 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
  18. 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