Swift Package Manager dynamic library - swift

I've recently converted a bunch of my frameworks to use Swift Package Manager. My Package.swift looks something like this:
// swift-tools-version:5.1
import PackageDescription
let package = Package(
name: "MDFToolbox",
platforms: [
.macOS(.v10_13), .iOS(.v12), .tvOS(.v12), .watchOS(.v3)
],
products: [
.library(name: "MDFToolbox", targets: ["MDFToolbox"])
],
dependencies: [
.package(url: "git#github.com:Swinject/Swinject.git", from: "2.7.0"),
],
targets: [
.target(name: "MDFToolbox", dependencies: ["Swinject"]),
]
)
Since the library used to be a framework, I'd like to link it in my app as a dynamic library (.dylib). According to the library product definition in the Package documentation, I can specify the type of my library to be .dynamic if I want:
The optional type of the library that is used to determine how to link to the library. Leave this parameter unspecified to let to let the Swift Package Manager choose between static or dynamic linking (recommended). If you do not support both linkage types, use .static or .dynamic for this parameter.
If I leave it as nil, Xcode defaults to building a static library when I link this package in my app project, which is not what I want.
If I set the type to .dynamic in my library's Package.swift, Xcode builds a .dylib, but it doesn't get embedded in the application, leading to a linker error:
dyld: Library not loaded: #rpath/libMDFToolbox.dylib
Referenced from: /Users/mpdifran/Library/Developer/Xcode/DerivedData/Remind-eewbkbjpfrqbdwchjrbmrtxzsjew/Build/Products/Debug-maccatalyst/Remind.app/Contents/MacOS/Remind
Reason: no suitable image found. Did find:
/Users/mpdifran/Library/Developer/Xcode/DerivedData/Remind-eewbkbjpfrqbdwchjrbmrtxzsjew/Build/Products/Debug-maccatalyst/libMDFToolbox.dylib: code signature in (/Users/mpdifran/Library/Developer/Xcode/DerivedData/Remind-eewbkbjpfrqbdwchjrbmrtxzsjew/Build/Products/Debug-maccatalyst/libMDFToolbox.dylib) not valid for use in process using Library Validation: mapped file has no cdhash, completely unsigned? Code has to be at least ad-hoc signed.
I also see no easy way of adding the .dylib to a Copy Files Build Phase...
So what's the recommended way of asking SPM to build and link a dynamic library through Xcode? Is this something that is not yet supported?

I figured it out!
If you want to create a framework library, you need to force it to be one in the Package.swift like so:
.library(name: "MDFToolbox", type: .dynamic, targets: ["MDFToolbox"])
Once you've done that, you'll see an embed option in the Xcode project settings of the project linking to the library. When selecting the target, scroll down to the Frameworks, Libraries, and Embedded Content section. You should see an option to embed your dynamic library dependencies:

Related

No such module in a Swift Package using Xcode - package listed in dependencies

I create a blank template package:
> swift package init --name Temp
> open Package.swift
Xcode Version 13.2.1 (13C100) opens the package.
I add a dependency to the package.
dependencies: [
.package(url: "https://github.com/johnsundell/publish.git", from: "0.7.0")
],
Xcode > Product > Build succeeds at this point.
I edit Temp/Sources/Temp/Temp.swift to insert the first line the package that is defined in dependencies.
import Publish
A build now generates the following error:…/Temp/Sources/Temp/Temp.swift:1:8: error: no such module 'Publish'.
I feel certain this is an Apple bug. Or I could be missing something.
There are several posts about this problem when there is an xcodeproj and the additional structure that provides. Some of them hint at workarounds that help some people.
Has anyone seen this and/or know of how to resolve it?
Apple's Creating a Standalone Swift Package with Xcode document doesn't provide any insight.
thanks for the chatter in the comments, #Larme & #koen, it helped
The issue was user error (and/or a documentation lapse). Living on the (bleeding) edge.
Sometimes updates from changes are slow or require a clean or a relaunch.
Xcode auto-generates Schemes from the targets defined in your package. My build was targeting MyTarget.
Two things were missing:
name: "Publish" was not included in the package dependency - it's needed so you can reference it below (or maybe this can be derived, it's hard to tell because of Xcode refresh issues), and
a reference is needed in the dependencies for each target using the package-dependency, i needed to add dependencies: ["Publish"] in the related target
dependencies: [
.package(name: "Publish", url: "https://github.com/johnsundell/publish.git", from: "0.7.0")
],
…
targets: [
.target(
name: "MyTarget",
dependencies: ["Publish"]),
]

How can I enable ABI stability for a SwiftPM package?

Given my bog standard Package Description of
let package = Package(
name: "MyLib",
products: [
.library(name: "MyLib", targets: ["MyLib"]),
],
dependencies: [],
targets: [
.target(
name: "MiniRxSwift",
dependencies: [],
swiftSettings: [
.define("<see below>")
]),
...
I'm trying to get swiftpm to pass the -enable-library-evolution flag through to swiftc, but I've been unsuccessful.
Using swiftSettings of .define("-enable-library-evolution"), I get a compile error which states
"error: conditional compilation flags must be valid Swift identifiers (rather than '-enable-library-evolution')"
I get the same error if I omit the leading hyphen e.g. `.define("enable-library-evolution")
I've tried the Xcode setting of .define("BUILD_LIBRARIES_FOR_DISTRIBUTION") which doesn't result in a compile error, but also doesn't result in the flag getting set, it instead results in -DBUILD_LIBRARIES_FOR_DISTRIBUTION on the command line for swiftc, which doesn't do anything.
After a bit more research, I worked out that .define in swiftSettings is hardwired to produce things with -D - hence it's name.
Instead I needed to use unsafeFlags, which does indeed result in the flag getting passed correctly to the swift compiler:
swiftSettings: [
.unsafeFlags(["-enable-library-evolution"])
]
BUT then when I attempt to consume this package, Xcode fails to load the package, with the error:
The package product 'MyLib' cannot be used as a dependency of this target because it uses unsafe build flags.
If I can't enable library evolution without unsafe build flags, and I can't use unsafe build flags in a library, then what can I do? What is the point of having unsafeFlags if you can't use any libraries which set them?
Why do you need ABI stability if you have access to the package? You should avoid it if possible. That said, you should be able to use unsafeFlags with editable packages (ones you have dragged into the project) but not ones you include the normal way via URL.
If you need to vend someone a swift package as a binary you will need to build your package as an XCFramework and then you can put that in a place where SwiftPM can depend on it. That means it can be hosted at a static place where you give the URL or it can be embedded in the git repo with the Package.swift manifest that you have for vending the package. The only tool I know of to help with this is found here which works for Swift only package when I have used it.

Cant run/find Executable Swift Package

Bottom line I am trying to run an executable from a swift package I created and linked to my target.
I think I'm missing something here, so I've created an executable swift package with the content
// swift-tools-version:5.2
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "AutoLocalized",
platforms: [
.macOS(.v10_15), .iOS(.v12)
],
products: [
.executable(name: "autolocalized", targets: ["AutoLocalized"])
],
targets: [
.target(name: "AutoLocalized", dependencies: []),
.testTarget(name: "AutoLocalizedTests", dependencies: ["AutoLocalized"]),
]
)
so as I understand adding the ".executable" outputs a file I can then run via Xcode.
I am trying to run a build phase script using that output but I dont know how to access it.
When I try to add dependencies to my target I can find the "autolocalized" executable
but building the project I get an error:
After a lot of searching I found A way, maybe not the best but it workes.
So what I did is adding the package via SPM but without a dependency to any target, that means I had to make sure I build the package manually, finding it in the Build folder and using it directly from there, this was the only way I found to run mac os scripts using build phases in Xcode, I am positive others tried but no one found a good answer, so here it is.

How to use Swift Package Manager's binaryTarget?

I'm trying to use Swift Package Manager's binaryTarget to include the Stripe xcframework available here https://github.com/stripe/stripe-ios/releases/tag/v19.3.0. The package manager doesn't complain, and lets me link to it, but I can't import it im. I've made a sample repo to show it here https://github.com/martyu/StripePackageTest. Am I missing something?
First of all, your example is not testable because you have forgotten to provide a version tag, so this is not a real package.
Second, and more important, I think you have a misconception about how a package works as a binary target. You seem to think that your Swift Package can contain code that sees the XCFramework. (That's why you are trying to import in the framework module in the Sources code of the package.) That's wrong. It's the app that imports the framework module. The package is merely a way of distributing the framework.
In other words, you can write a source code package or a framework-bearing package. One package cannot be both.
But of course you can write a source code package that depends on a framework-bearing package.
First, you don't need a version tag for it to be "real package". You can specify package dependencies via commit SHA and branch as well. Also you can add local package repos in xcode via file://. Note, this is NOT the same as a local dev override.
I didn't have much luck with swift build but I did get it to work fine by creating an app in Xcode and adding the package to it. I think this is what #matt is getting at. You need to import it into a project (xcode, or another SP) and then xcode will assemble all the dependencies when it ~~builds~~ feels like it.
Here's the modified Package.swift I used. I changed the name to Example (as that's presumably the SDK you are building which depends on Stripe). You can include "Stripe" in the Example library's targets if you want it embedded in its framework. Otherwise the client app just needs to import it as well (via the tickboxes when you add it in Xcode, or via dependencies in another Package.swift).
// swift-tools-version:5.3
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "Example",
platforms: [
.iOS(.v11)
],
products: [
.library(
name: "Example",
type: .dynamic,
targets: ["Example"]),
.library(
name: "Stripe",
targets: ["Stripe"])
],
dependencies: [],
targets: [
// I thought this was defining the Stripe binaryTarget...
.binaryTarget(name: "Stripe",
url: "https://github.com/stripe/stripe-ios/releases/download/v19.3.0/Stripe.xcframework.zip",
checksum: "fe459dd443beee5140018388fd6933e09b8787d5b473ec9c2234d75ff0d968bd"),
// ... and then linking it to the Example project here via "dependencies" ...
.target(name: "Example", dependencies: ["Stripe"], path: "Sources")
// ... so when I'm in "Example" files, I thought I'd be able to import "Stripe" to them
]
)

Importing modules with Swift package manager

I am trying to use Swift's package manager to import external modules in my project. My first module come from the Vapor project. I cannot seem to get it working. I start with
swift package init
swift package generate-xcodeproj
My Package.swift looks like this:
import PackageDescription
let package = Package(
name: "OpenTools",
products: [
.library(
name: "OpenTools",
targets: ["OpenTools"]),
],
dependencies: [
.package(url: "https://github.com/vapor/json.git", from: "2.0.0")
],
targets: [
.target(name: "OpenTools", dependencies: ["JSON"]),
]
)
I then run
swift package update
swift package generate-xcodeproj # to regenerate with dependencies
and then try to import the JSON package in my main file
import JSON
The modules are there as shown below but the import gets back with an No such module 'JSON' error.
Any thoughts?
Probably the problem lies within Xcode, as it does not know yet that JSON exists, because it was not built yet. This can easily be solved by just building your project (with cmd-B). With the generated xcodeproj, Xcode should know that it first needs to build JSON and then the rest, because JSON is marked as a dependency for your target.
You can check this, by navigating in Xcode to your target (when you click on the project description file) and afterwards to "Build Phases". Under Target Dependencies you should find your JSON module.
In addition you should find a JSON module under your targets, which compiles the sources you gathered from github.
Your project should also build when executing swift build in your project root.
With Xcode 11 you should be able to open Package.swift directly which will give you a proving ground for verifying the package manifest (aka: the Package.swift file) and compiling the target. This should help see what is actually causing the error that's preventing the module from being compiled.