SwiftPM, conditional package inclusion for android

Hi all,

I’m experimenting with building various of my package dependencies for Android.

I’ve updated a target to conditionally include `OpenCombine` for the .android platform:

  targets: [
    .target(
      name: "CombineSchedulers",
      dependencies: [
        .product(name: "ConcurrencyExtras", package: "swift-concurrency-extras"),
        .product(name: "IssueReporting", package: "xctest-dynamic-overlay"),
        .product(
          name: "OpenCombineShim",
          package: "OpenCombine",
          condition: .when(platforms: [.linux, .android], traits: ["OpenCombineSchedulers"])
        ),
      ]
    ),

But when I invoke:

swiftly run swift build --swift-sdk aarch64-unknown-linux-android28 --static-swift-stdlib

I don’t get OpenCombine resolved.

Does anyone know it it’s supposed to be working?

Does anyone know if it’s called ‘Do anyone…’ or ‘Does anyone…’? :smile:

I’m using the `main-snapshot-2025-11-03` toolchain.

Thanks for any help!

This should work in theory. I've seen a number of packages do something like this to support Android, although I haven't seen any that have used traits to conditionally enable it yet.

Under what circumstances if the OpenCombineSchedulers trait enabled? If you manually set it to default to true, does it work?

Hi Marc,

Thank you for your comments. From them, I am suspecting that I am perhaps misunderstanding what the condition is supposed to do.

With the condition in place, `OpenCombine` isn’t resolved at all. When I remove the entire condition, it naturally is.

As I understand it, the trait is enabled when `Darwin` can’t be imported:

#if !canImport(Darwin)
  package.traits.insert(
    .default(enabledTraits: ["OpenCombineSchedulers"])
  )
#endif

From:

But now I think of it, the `Package.swift` is compiled in the build environment, right? So when I compile on a mac, I can import Darwin?

I am a little confused about the Package.swift in a cross compilation environment.

Yes, that's exactly the problem. The Package.swift is evaluated on the host platform. It would be nice if a trait could be conditionally enabled based on the target platform, but that isn't currently supported.

Why are you gating the dependency on both the traits: ["OpenCombineSchedulers"] and .when(platforms: [.linux, .android]) conditions? Wouldn't just the platform condition be sufficient?

2 Likes

It’s not my own package, but one from point-free that I am trying to get to build on Android. It’s the ‘combine-schedulers’ package which is a dependency of ‘swift-dependencies’ which is my actual goal.

I tried removing the trait, just keeping the platforms, but for some reason it still does not resolve the OpenCombine package with my build command.

For now my workaround is to make a branch with no conditions on the package.

Ahh, I see. The pointfreeco team has been amenable to adding Android support for a number of their other packages (e.g., see Android support by marcprux · Pull Request #939 · pointfreeco/swift-snapshot-testing · GitHub and Simplify Android CI with shared action by marcprux · Pull Request #53 · pointfreeco/swift-concurrency-extras · GitHub), so I'm sure they would be happy to get a contribution that gets this package building and testing.

A word of caution, though: in cases when the CI testing is run from Linux, then the original gating of the dependency on #if !canImport(Darwin) would happen to work. But it would still break when cross-compiling from a macOS host.

1 Like

Thanks again Marc.

Apologies, the platform condition works as it should.

I’d love to contribute with any fixes, but currently I can’t make the conditional dependency to get included even without the trait.

I’m guessing that the platform must be something other than .linux or .android when running:

swiftly run swift build --swift-sdk aarch64-unknown-linux-android28 --static-swift-stdlib

on macOS.

1 Like

A word of caution, though: in cases when the CI testing is run from Linux, then the original gating of the dependency on #if !canImport(Darwin) would happen to work. But it would still break when cross-compiling from a macOS host.

In general, the package manifest by design has no idea what platform you’re building for. This allows us to use the same package resolution to build for multiple platforms. That is why we have the platform conditionals. And, we should really look to make sure they’re sufficiently expressive (e.g. what about conditional on a specific Android API level?)

Using traits to represent platforms is another anti-pattern I’ve seen. Traits are supposed to represent optional features in a package. They are resolved during package resolution so if you change traits, you end up with a different package semantically. And that comes back to the previous point. If there is something platform-y you want to accomplish and feel pulled to use traits, let us know so we can fix it at the right spot in the architecture.

2 Likes

One thing I had been hopeful with traits would be the possibility of excluding a package dependency altogether based on the trait. That, along with the ability to enable/disable a trait based on the platform, would make it so people who build pointfreeco libraries (like TCA) would avoid having to see "OpenCombine" at all in their Xcode dependencies (which, believe it or not, is something that does bother people who are cautious about their dependencies, even when they are aware that the dependency isn't actually used for their target platform).

This has also come up in other places, like the saga of GRDB and SQLCipher: https://github.com/groue/GRDB.swift/pull/1827#issuecomment-3441350330

There has been work in that direction. It might be in 6.3? But, yes, we often get feedback about SwiftPM downloading too many dependencies when not needed and we have an eye on that.

1 Like

As a point of comparison, Rust’s Cargo does have the idea of target-conditional dependencies, with a limited syntax for selecting those targets.

1 Like