Adding platform-specific dependency to multi-platform Swift Package

Hello,

I am struggling with setting up a Swift Package that supports multiple platforms (iOS and tvOS in my case) and also depends on a platform-specific library (iOS in my case).

Despite adding a condition property to the platform-specific dependency, I can only build my library for the platform supported by the dependency.

My Package.swift looks like this:

// swift-tools-version:5.4
import PackageDescription

let package = Package(
  name: "Demo",
  platforms: [
    .iOS(.v14),
    .tvOS(.v14),
  ],
  products: [
    .library(
      name: "Demo",
      targets: ["Demo"]
    ),
  ],
  dependencies: [
    .package(
      name: "Firebase",
      url: "https://github.com/firebase/firebase-ios-sdk.git",
      .exact("8.1.1")
    ),
  ],
  targets: [
    .target(
      name: "Demo",
      dependencies: [
        .product(
          name: "FirebaseAnalytics",
          package: "Firebase",
          condition: .when(platforms: [.iOS])
        ),
      ]
    ),
    .testTarget(
      name: "DemoTests",
      dependencies: [
        .target(name: "Demo")
      ]
    ),
  ]
)

Note that FirebaseAnalytics only supports iOS platform, but not tvOS.

My "Demo" library builds fine for iOS, but when trying to build it for tvOS, I am getting the following error:

[...]/FirebaseAnalytics.xcframework:1:1: While building for tvOS Simulator, no library for this platform was found in '[...]/FirebaseAnalytics.xcframework'.

Not sure if such configuration is not supported, or I am doing something wrong here.

I've created an example project that demonstrates the issue:

I am using Apple Swift version 5.4 (swiftlang-1205.0.26.9 clang-1205.0.19.55) that comes with Xcode 12.5 (12E262).

All feedback and suggestions will be appreciated!

1 Like

A typical approach is to conditionalize your dependencies array so that it's empty on tvOS.

#if os(tvOS)
let dependencies: [Dependency] = []
#else
let dependencies: [Dependency] = ...
#endif

I'm not sure that's the right type for the array but you get the idea.

1 Like

That’s interesting. Thanks! I will definitely try it out, although I am not sure if this will work inside Package.swift file.

Oops, yeah, I keep forgetting it's conditioned on the running OS, not the targeted one. So this would work for Linux vs. Windows vs. macOS, but not for tvOS vs. iOS. What you're doing is supposed to work I think.

1 Like

Unfortunately, using #if os(...) / #endif won't help in this case as the platform is defined by the machine I am working on (macOS) and not the target platform I would like to build for (iOS/tvOS).

I have pushed updated demo project to a separate branch. It's closer to what I am working on. My app's source code is modularized using libraries defined in Swift Package, similarly to what PointFree does in their ISOWORDS game.

1 Like

Your manifest looks correct to me. (#if os(...) is an old workaround with bad side effects and should not be used in manifests anymore.)

I use it all the time, so I can also confirm that in most situations it works properly (including with 5.4 and 12.5).

Whatever is going wrong here must only be triggered by narrower set of conditions. For example, I have never attempted to conditionally use a binary dependency. Maybe there is a bug in the way the two features overlap? Anyway, it would be helpful to have a minimal demonstration where the dependency is less complicated than Firebase.

Regardless, it is supposed to work, so you can file a bug at bugs.swift.org if you can reproduce it in a native command line build (swift build), or with the Feedback Assistant if only Xcode has trouble.

This definitely looks like a bug to me. Your use case is exactly what conditional dependencies are for.

Encountered the same issue while trying to list binary target conditionally as a dependency.

1 Like

Was a bug ever filed for this issue? I just ran in to it myself.

Hey folks, I filed a bug for this issue [SR-15836] Platform Conditionals Don't Apply To Binary Frameworks · Issue #4351 · apple/swift-package-manager · GitHub as I recently ran into the same problem. Does anyone have any workarounds?

3 Likes

Bumping this as well.

Just ran into this exact issue when pulling in a package to a tvOS project with conditional package dependency set to iOS only.

Builds for iOS but does not for tvOS. Binary target with a reference xcframework for iOS only is the root issue as it still tries to be linked even though my package manifest says not to on tvOS.

On Swift 5.5

Any updates???

The only workaround I can imagine that might work, involves creating a target for iOS and a target for tvOS. Each target might be able to conditionally depend on platform and thus only the respective target gets linked for proper platform.

Again, this is hypothetical but worth a shot. I plan on taking this route to workaround the SPM bug if possible.

Hey, did this strategy work for you?

Unfortunately no. I ended up resorting to an entirely independent package for tvOS.

And the issue still exists. Does anyone found any solution or workaround?

The workaround I used this week was:

Clients package

  • Interface target
  • Implementation target (imports the iOS only binary)
  • iOS implementations target (@_exports all single platform binaries)

Features package

  • Feature target (imports the interface)
  • Dependencies target (imports all interfaces, no implementations)

App

  • Add Clients.iOS Implementations target to Frameworks and Libraries
  • Where dependencies are declared: #if os(iOS) import iOS Implementations target

I wish there was another way.

2 Likes

This is the way

I faced a similar issue today, but my case was using binary target.
I will post my solution here, since this post helped me to find the solution for my case.

// swift-tools-version: 5.10

import PackageDescription

let package = Package(
    name: "MyPackage",
    platforms: [
        .iOS(.v16),
        .tvOS(.v16),
    ],
    products: [
        .library(
            name: "MyPackage",
            targets: ["MyPackage"]
        ),
    ],
    targets: [
        .target(
            name: "MyPackage",
            dependencies: [
                .target(name: "MyFramework-iOS", condition: .when(platforms: [.iOS])),
                .target(name: "MyFramework-tvOS", condition: .when(platforms: [.tvOS]))
            ]
        ),
        .binaryTarget(
            name: "MyFramework-iOS",
            url: "https://mywebsetup/binaries/2.222.22/iOS/MyFramework.xcframework.zip",
            checksum: "255gjf6ab5dcc44a7c542344648e14e0f039a3121ab1144d676797d3c094b0bf1"
        ),
        .binaryTarget(
            name: "MyFramework-tvOS",
            url: "https://mywebsetup/binaries/2.222.22/tvOS/MyFramework-tvOS.xcframework.zip",
            checksum: "5aa0fd2cc75c17bcf1c226efe6e3681a1a8f84f4b10af2077c1af587318cc6g0"
        )
    ]
)