Binary Frameworks with SwiftPM

I have been searching an answer to this, and I assume there is a way to do this but haven't succeed in finding it. I have a Package.swift which looks something like this, simplified for illustration purposes.

  let package = Package(
      name: "Core",
      platforms: [
          .macOS(.v10_14), .iOS(.v12)
      ],
      products: [
          .library(
              name: "Core",
              targets: ["Core"]
          )
      ],
      dependencies: [
           // some package dependencies here. 
     ],
     targets: [
         .target(
             name: "Core",
             dependencies: [
                 "BinaryFramework",
                 "BinaryFramework2",
             ]
         ),
         .testTarget(
             name: "CoreTests",
             dependencies: ["Core"]
         )
     ]
 )

The folder structure is like this on disk.

.
β”œβ”€β”€ Frameworks
β”‚   └── Build
β”‚       └── iOS
β”‚           β”œβ”€β”€ BinaryFramework.framework
β”‚           β”‚   β”œβ”€β”€ BinaryFramework
β”‚           β”‚   β”œβ”€β”€ Info.plist
β”‚           β”‚   └── Modules
β”‚           β”‚       └── module.modulemap
β”‚           └── BinaryFramework2.framework
β”‚               β”œβ”€β”€ BinaryFramework2
β”‚               β”œβ”€β”€ Headers
β”‚               β”‚   └── FrameworkHeader.h
β”‚               β”œβ”€β”€ Info.plist
β”‚               └── Modules
β”‚                    └── module.modulemap
β”œβ”€β”€ Package.resolved
β”œβ”€β”€ Package.swift
β”œβ”€β”€ README.md
β”œβ”€β”€ Core.xcodeproj
β”‚   β”œβ”€β”€ project.pbxproj
β”œβ”€β”€ Sources
β”‚   └── RideCore
β”‚       β”œβ”€β”€ Private
β”‚       β”‚   β”œβ”€β”€ Connector.swift
β”‚       β”‚   β”œβ”€β”€ Decodable.swift
β”‚       β”‚   └── JSONRequest.swift
β”‚       └── Public
β”‚           └── Common
β”‚                β”œβ”€β”€ AuthenticationService.swift
β”‚                └── Exceptions.swift
β”œβ”€β”€ Tests
β”‚   └── CoreTests
β”‚       β”œβ”€β”€ AuthenticateTests.swift
β”‚       └── BasicNetworkTests.swift
└── ToDo.txt

The frameworks are built already and I don't have the source so I can't import it as a package. These are not C system libraries. I tried looking at linkedFrameworks and importing dependencies as shown in the Package.swift file above but it does not find the files. Any assistance would be appreciated. Thanks.

Unfortunately, right now this doesn't work. SwiftPM cannot distribute packages containing binary frameworks. With the right combination of directives SwiftPM can build the folder structure you have, but it is ineligible to be a SwiftPM dependency because it would require the use of unsafeFlags for the linker, and you cannot use those in a package that is supposed to be a dependency.

What you can do, however, is distribute the binary frameworks separately and simply require that they be present and findable in order to build your SwiftPM package. To do that, you'd have to make some changes to the target Core. This is what you have today:

.target(
    name: "Core",
    dependencies: [
        "BinaryFramework",
        "BinaryFramework2",
    ]
)

This is what you'd want:

.target(
    name: "Core",
    linkerSettings: [
        .linkedFramework("BinaryFramework"),
        .linkedFramework("BinaryFramework2"),
    ],
)

This instructs SwiftPM that you have dependencies on the binary frameworks and you expect them to be present. Note that they must be on the default linker path on the system that builds Core, so you'll need to distribute and install them appropriately.

6 Likes

Thank you, it seems that I can do this using the linkedFramework you show above:

swift build -Xswiftc -FFrameworks/Build/iOS   
swift test -Xswiftc -FFrameworks/Build/iOS 

And it finds my frameworks, linking into the main app is similar in that I can add the frameworks path in the Xcode target. Alternatively, in the manifest:

 swiftSettings: [
        .unsafeFlags(["-FFrameworks/build/iOS"]),
 ]

and the command:

 swift build

This will work for now, but hope the discussion "SPM Support for Binaries Distribution" leads to something longer term. UnsafeFlags seems like it won't work past development phase.

My Core library now can't be a dependency because of the unsafe flags. Back to the drawing-board. Not sure what will work now.

I think I covered the suggestion above: require that the leaf package know how to find the binary frameworks, rather than distributing them as part of your package.

Sorry, I must be missing something, how do I control default framework path in a package imported by Xcode? The Core package must be compiled in Xcode and there are no Xcode settings on the Core target to define the frameworks search path on packages in Xcode. When I try to compile Core without the unsafeFlags it complains it can't find the frameworks so that it can be successfully compiled for Core. I don't see how to get to the next step of including the frameworks with the final product. Thank you for your patience.

I think it should be sufficient to adjust the settings on the project level.

Hi:

I am also in the need of linking a binary as a dependency of a Swift package. Concretely, I have an Xcode project that generates a product (a framework), and that product I need it linked as a dependency of one of the targets the Swift package. I had limited success, I tried 2 approaches.

Lets call the target in the Swift package SwiftPackageTarget and the target in the Xcode project generating the dependency XcodeProjectTarget.

In the first approach:
1- I added linkerSettings: [.linkedFramework("XcodeProjectTarget")] to the SwiftPackageTarget target inside the Package.swift.
2- I added SwiftPackageTarget and XcodeProjectTarget as dependencies of a target app.

As a result, building the target app built and linked XcodeProjectTarget and SwiftPackageTarget successfully. The problem of this approach is that if you try to build SwiftPackageTarget individually it will fail. The main issue is that compiling the app target usually will take a while, as such target will have multiple other dependencies.

In the second approach:
1- I added the Swift package and Xcode project of XcodeProjectTarget under the same Xcode workspace.

Considering that under the Xcode schemes the option Find Implicit Dependencies is on, I expected Xcode to successfully build the SwiftPackageTarget correctly. So far, for what I tested, this does not happen.

I am using Xcode Version 11.0 beta 6 (11M392r).

Edit:
I added an example repository here: https://github.com/acecilia/FindImplicitDependenciesBug
I also opened a rdar: https://openradar.appspot.com/radar?id=4983787662344192

linkedFramework is really only supposed to be working for system frameworks. I realise that the documentation didn't make this entirely clear, I have updated it in https://github.com/apple/swift-package-manager/pull/2331/

@NeoNacho Well, technically it can link any framework, not only system ones, as the example repository that I created shows. Adding a comment in the documentation is not going to change this fact.

On the SPM side, seems to me that this is a good way of adding binary dependencies to a Swift Package. The only think we would need is support on the Xcode side, so it automatically finds and builds the implicit dependencies.

I do not fully understand why you chose to limit this to system frameworks, instead of fixing Xcode to support it, so Swift Packages can link targets from Xcode projects or binaries.

I understand that this might work for some non-system frameworks, but that is not what this feature was designed for. Packages intentionally follow a model where dependencies are explicitly declared.

There is a pitch thread for support for binary dependencies in SwiftPM here: [PITCH] Support for binary dependencies

I'd like some clarification of what defines a "system" framework. Are you really limiting this to, for example, Apple frameworks? I have a lot of frameworks and libraries from 3rd party source and those that I have built myself that I have no intention of building Swift packages for. Are these considered "system" frameworks? Is anything that is located via the traditional linker search?

In theory, anything that can found by the default search paths would work using this, but I'd only consider libraries that actually ship with the system (e.g. with macOS) a good candidate, because otherwise you end up with an unexpressed dependency. We have the system library targets feature for declaring explicit dependencies on system libraries that are installed via other means, e.g. homebrew.

Terms of Service

Privacy Policy

Cookie Policy