SwiftPM - Binary target with sub-dependencies

Hi,

Xcode 12.0/Swift 5.3 toolchain introduced support for binary targets in Swift Package Manager.

While the feature works perfectly for single binary targets, is there a possibility to declare binary subdependencies for the binary targets?

In a scenario:
where binary package frameworkA is subdependency of binary package frameworkB and frameworkC
and furtherly all 3 binaries live in different repos

Im not able to find a way how to correctly declare the frameworkA as subdependency for frameworkB and frameworkC.

What I've tried sofar:

  1. use the dependencies parameter in Package declaration. e.g. for frameworkB to pass in subdependency of frameworkA, e.g.
dependencies: [
        .package(url: "url-to-repo-with-frameworkA", .exact("1.0.0"))
    ]

At the integration point, the Xcode 12.0 beta 6 won't pick the specified subdependency frameworkA, only the added binary target, in this case frameworkB, will be added, which results in broken integration.

  1. declare frameworkA as another binary target in package manifest for frameworkB and frameworkC.
    This works fine when importing either frameworkB or frameworkC to the project.
    Integrating both binary packages in Xcode 12.0 beta 6 results in "multiple targets named frameworkA in frameworkB, frameworkC" error.

Thanks for any help/guidance in advance!

Best,
Boris

1 Like

It looks like none of the .binaryTarget factory methods have a dependencies parameter, which is how you would theoretically hook up such a dependency graph.

I don’t know if that is intentional, or just an oversight. I can imagine either being the case. Two binary packages depending on the same source package could be problematic, so maybe it is disallowed to prevent that scenario. But if so, I would expect it to be mentioned in the evolution proposal and it isn’t.

@hartbit, you were the primary implementer. Do you know?

Hi, thanks for reply.

Does it mean, that the current solution can be used only in monorepo alike setups, where the integrator need to fetch all the binaries regardless if he actually only need 2 out of 3?

Just to clarify the details in the above scenario:
frameworkA, frameworkB and frameworkC are all binaries (xcframeworks).

I dont want the module stable binaries depend on non-module stable source packages :package:.

It means the parameter by which you would declare the dependency does not currently exist. So there is currently no way to make a binary target depend on anything.

I spent about half an hour hunting for a workaround to bypass access control and get at the private initializers that have both the binary target details and a dependencies parameter. I even thought of decoding a Target from a JSON literal, but alas discovered it was only Encodable, not Decodable. I came up empty.

However, thinking about it again after your second message, I suspect you could still trick SwiftPM into fetching all the needed pieces in a reliable manner:

  1. Specify the binary target A like normal.
  2. Specify a source target that is just an empty stub.
  3. Make the empty stub dependent on B and C.
  4. Specify the product A such that it includes both target A and the stub.

Then anyone who depends on product A will get B and C resolved as a side affect of the empty stub.

Let me know if it works.

We used a wrapper target to solve dependencies for a binary framework. See https://github.com/firebase/firebase-ios-sdk/blob/master/Package.swift#L203

2 Likes

Yes, that is almost what I described, except it puts the stub over A as well. I’d say that is sufficient evidence that it works. Thanks, @paulb777.

First of all, @paulb777 @SDGGiesbrecht thanks for the help folks. Without your guidance & real world example, I wouldn't be able to find the right solution!

My manifest file for frameworkC that's dependent on frameworkA now looks like this:

  1. It lists the binary target both for frameworkC and frameworkA
  2. It declares additional (stub) target FrameworkCTargets, that has 2 target dependencies -> frameworkC and frameworkA
  3. Additionally, the stub target FrameworkCTargets declares the custom path towards dummy(empty) source file.
  4. NOTE: I had to commit an empty source file (.m or .swift) to FrameworkCTargets subfolder on my repo, for the target FrameworkCTargets to be properly processed when integrated to project X. Without commiting the dummy source file for the stub target to the repo with the manifest, the libSwiftPM just thrown an error about missing source files.
  5. Product frameworkC's only target is the FrameworkCTargets
// swift-tools-version:5.3
import PackageDescription
let package = Package(
    name: "FrameworkC",
    platforms: [
        .iOS(.v13)
    ],
    products: [
        .library(
            name: "FrameworkC",
            targets: ["FrameworkCTargets"]
        )
    ],
    targets: [
        .binaryTarget(
            name: "FrameworkC",
            url: "url-to-framework-c",
            checksum: "checksum"
        ),
        .binaryTarget(
            name: "FrameworkA",
            url: "url-to-framework-a",
            checksum: "checksum"
        ),
        .target(name: "FrameworkCTargets",
                dependencies: [
                    .target(name: "FrameworkA", condition: .when(platforms: .some([.iOS]))),
                    .target(name: "FrameworkC", condition: .when(platforms: .some([.iOS])))
                ],
                path: "FrameworkCTargets"
        )
    ],
    swiftLanguageVersions: [.v5]
)

I hope this helps somebody else, too.

Furtherly, I believe, declaring binary subdepedencies in Package.swift could be done in a way more declarative/straightforward way by following the similar rules that are outlined for non-binary targets.
Should I use feedbackassistant to report this back to Apple team?

1 Like

Absolutely.

No. SwiftPM is part of the open‐source Swift project, so bug reports for it belong at bugs.swift.org.

1 Like
Terms of Service

Privacy Policy

Cookie Policy