How can I enable ABI stability for a SwiftPM package? - swift

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.

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"]),
]

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
]
)

Swift Package Manager dynamic library

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:

Cleanly handling /usr/local/ with Swift package manager and libevent

I have 2 dependencies in my project libevent and libressl. Both of which are installed locally ( respectively under /usr/local/include and /usr/local/opt/libressl/include )
What I am looking for to achieve is for SPM to automatically understand to search in those directories.
I know I can pass flags to swift build to achieve this; but my ultimate goal is that I can properly generate xcode projects from the command line without having to constantly add custom build flags in Xcode.
I'm pretty sure it is possible, since I do not have to enter the custom settings for PostgreSQL.
Swift-tools version is at 4.0.x
Package.swift for reference:
// swift-tools-version:4.0
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "CEvent",
providers: [
.brew(["libevent"]),
.apt(["libevent-dev"])
],
products: [
// Products define the executables and libraries produced by a package, and make them visible to other packages.
.library(
name: "CEvent",
targets: ["CEvent"]),
],
dependencies: [
],
targets: [
.target(
name: "CEvent",
dependencies: []
),
]
)
Module map:
module CEvent [system] {
header "shim.h"
link "event"
export *
}
And my current build script ( build.sh ):
#!/usr/local/bin/fish
swift build -Xcc -O0 -Xcc -fblocks -Xswiftc -lbcrypt -Xswiftc -I/usr/local/include -Xswiftc -L/usr/local/lib -Xswiftc -ltls -Xswiftc -lcrypto -Xswiftc -lssl -Xswiftc -L/usr/local/opt/postgresql/lib -Xswi$
As for the reason that I want this. If I add/update/remove dependencies in swift I want to generate a new xcode project, and not have to fix its settings on respective build machines; as well as apt/ubuntu /usr/lib instead.
What you've found out, and documented in your answer, is a good start but not the full story. Yes, SwiftPM uses pkg-config to determine where certain libraries are installed. Yes, SwiftPM uses the pkgConfig name which it'll pass on to pkg-config. However the search paths are a bit more involved. On macOS it uses the following list as a base search path:
/usr/local/lib/pkgconfig
/usr/local/share/pkgconfig
/usr/lib/pkgconfig
/usr/share/pkgconfig
PKG_CONFIG_PATH environment variable
However SwiftPM doesn't use the pkg-config command, but instead parses .pc files directly. By setting the pkgConfig parameter on your package, it knows what filename to look for in the paths listed above. And, for the example in your answer, the story stops here. If there's a libevent.pc file found it parses that file, and any flags returned are passed on to the compiler and linker.
However if you were to define package providers, e.g.:
providers: [
.Brew("libsodium"),
.Apt("libsodium-dev")
]
Then SwiftPM adds additional search paths depending on the package provider for the platform it is building for. Continuing the example of macOS, SwiftPM will run brew --prefix. If this returns a path, the following path is added as a additional search path:
[brewPrefix]/opt/[packageName]/lib/pkgconfig
In my example of libsodium, SwiftPM is now able to infer the location of the library without requiring brew link or symlinks at all. In my verbose build output it lists the libsodium library path in my cellar: -L/usr/local/Cellar/libsodium/1.0.11/lib.
Alright so the thing I ignored from analysing other projects ( IBM-Swift/CLibpq in particular ) seems to be making use of the tool pkg-config which is not something I personally ever touched before.
pkg-config looks in /usr/lib/pkgconfig /usr/share/pkgconfig and the local variants for config files used in during the build process.
Inside Package.swift, after the name parameter you need to insert something for example:
let package = Package(
name: "CEvent",
pkgConfig: "libevent",
Some caveats I discovered with this:
The bcrypt library I am using does not have a full fletched install or build inside the makefile so I compiled it using the new options found in swift4 PM instead found here: BCrypt example on github and the Swift docs for more help here: SPM API Redesign
LibreSSL found in Homebrew will not install its pkgconfig on the system; so it is easiest or in my eyes best maintenance wise to either manually add it or to compile LibreSSL-portable from source.
Overall great learning experience for me today.