Local swift package with local dependency - swift

I have a project that I plan on developing in modules, the final application will be any number of the modules built together based on a configuration. I have a swift package that has all of my common code it it, we can call that the platform package. I then went to create my first feature, this went just fine however when I created the wrapper application to pull in each feature, I got this error from SPM in xcode11:
package 'Platform' is required using a revision-based requirement and it depends on local package
'Feature1', which is not supported.
Looking at the code base for SPM here (line 72)
https://github.com/apple/swift-package-manager/blob/master/Sources/PackageGraph/DependencyResolver.swift
It looks like this is something that is just not supported, the mixing of local and remote dependencies? Is this a limitation of SPM / should I be trying to use another tool for this type of app architecture?

In my case, I was trying to add a package, which I was developing, and its Package.swift contained dependencies of the form:
dependencies: [
.package(path: "../PackageName"),
// etc
Changing the references to specific repos solved the problem:
dependencies: [
.package(path: "http://github.com/..."),
// etc

Related

Swift Package Manager (SPM) and Cocoapod Dependency Conflict

Overview
I have two dependencies, one available as a Swift Package, and the other only available as a Cocoapod. The problem is that each has a dependency on a third package, which results in undefined behavior when multiple versions exist.
At a basic level, here is a graphic of my dependencies
APP imports:
B (SPM) imports:
C (SPM) imports:
D (SPM) <-
E (Pod) imports:
D (Pod) <-
I would like to remove the D (Pod) version and point to the D (SPM) version either via a Podfile script or build script.
More specific information:
I have a NetworkingService Swift Package that imports Firebase, and my main app imports the NetworkingService. My Podfile imports GoogleMLKit/PoseDetection. Firebase and PoseDetection share dependencies that result in undefined behavior (runtime crash) when a duplicate is present.
Note: This error should be reproducible by removing the intermediary NetworkingService package and importing Firebase to the main app as a Swift package.
Podfile
platform :ios, '15.0'
target 'MyApp' do
use_frameworks!
pod 'GoogleMLKit/PoseDetection', '2.5.0'
end
In Package.swift
.package(
name: "Firebase",
url: "https://github.com/firebase/firebase-ios-sdk.git",
.upToNextMajor(from: "8.10.0")
),
They duplicate a few dependencies, including GoogleUtilities and FBLPromises. Launching the app after pod install crashes with runtime exception:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[FBLPromise HTTPBody]: unrecognized selector sent to instance 0x6000017685d0'
Searching that brought me to this GitHub issue where a contributor mentions:
The duplicate warnings are indicative of non-deterministic behavior. When there are multiple copies of a library, the right one may or may not be chosen.
I then attempted to refactor all Cocoapod dependencies with a modified version of this script I found linked on another StackOverflow post. The attempt was to make PoseDetection explicitly point to symbols from GoogleUtilitiesCopy and FBLPromisesCopy. But it seems that even the existence of these copies, despite pointing to the corresponding dependency, created undefined behavior.
Partial Workaround
If I run pod install followed by File > Packages > Update to latest package versions. The app will launch without an immediate runtime crash. However, I encounter other runtime crashes later within the app.***
Ideal Solution
I would like to remove the duplicate pod dependencies and point to the SPM versions either via the Podfile or a build phase script, but I'm not sure where to begin.
Firebase can be imported as a pod, but I do not want to do this because I have an existing SPM infrastructure that depends on Firebase. I'd prefer not to convert all of these packages into pods.
The main problem with SPM and Cocapods is that:
SPM is not aware of Cocapods and Cocapods not aware of SPM.
I had a similar situation where:
pod library depends on some Library networking
SPM library also depends on the same Library networking.
There are two solutions I think are worth trying.
1.) remove the pod and try to use the XCFramework version of it (should be available). that means you should manually add the library to your project and it should be selected as embed & sign and also as required.
2.) you can convert the pod library to SPM by using a simple binary target. Basically, you need to create a Package file that points to the XCFramework in its archived version (hence it should be a zip file). that way the SPM graph will handle it by itself.
I know it's not ideal, but these two approaches works for me.

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.

Exporting Package.swift for Swift Package Manager from existing Xcode project

I'm working with Xcode 11.3 on macOS Catalina 10.15.6.
I have an existing Xcode project which builds an app for iOS. I am interested in reusing some of the classes in an interactive session with the swift command line interpreter. The classes I want to work with are Core Data classes which are autogenerated from an Xcode data model and also some classes I've written which work with the Core Data classes. The app has UI screens and makes use of UIKit but I'm not trying to use any of those classes; I'm hoping that I can either compile those classes and then not refer to them, or somehow tell Swift Package Manager to ignore those classes altogether.
What I think I would like to do is to export a Package.swift for the existing Xcode project, such that swift build at the command line would be able to compile all of the project classes, or, failing that, at least the non-UI classes, and then swift run --repl would be able to load the classes via import.
I see a menu item in Xcode to create a new Swift package, but not to export an existing project. Is there a way to export an existing project?
There are no a menu command or utility to convert application to a static library, dynamic framework or swift package since they are different types of projects with different settings etc.
If you want to export a part of your project as a swift package you should make next steps manually:
1. Create Package.swift file in the root of your project
import PackageDescription
let package = Package(
name: “MyLib”,
products: [
.library(name: "MyLib", targets: ["MyLib"])
],
targets: [
.target(name: "MyLib"),
],
...
)
2. Make folder with subfolder ./Sources/MyLib under the projects’s root.
By default swift package structure requires to put all your sources files under Sources/LibraryName folder but you can change it below.
NOTE: you can simplify first two steps by using swift package init and it creates Package.swift, Sources and Test folders etc.
3. Include source files
a) Move the needed files to share from their current locations to MyLib folder.
For instance:
./Classes/MyEntity.swift -> ./Sources/MyLib/MyEntity.swift
Also you have to update locations of the moved files in your Xcode project to leave it compilable.
b) Use path, sources and exclude to point needed source files to your package from their current locations:
.target(name: "MyLib", path: "Classes"),
NOTE: Don't forget to make your classes public to access to them after import your package:
public class MyEntity {
...
}
After all you will have two working projects - old XCode's one and new Swift package.
4. REPL
Now you can use command line interpreter with your swift package:
swift run --repl
import MyLib
let entity = MyEntity()
...

Error saying "Module Not Found" when adding SPM which uses other SPMs as dependencies within itself

I have been creating a Swift Package Manager. It uses 2 other SPMs within itself. SPM compiles fine when compiled independently. As soon as the project is imported into an Xcode project I get a compiler error saying that:
No such module 'ModuleName'
Note: The ModuleName in the above error corresponds to the package imported within the package that is being imported to my project.
I have been stuck on this for a pretty while now and have tried the following:
Removed and readded the SPMs to dependencies to my SPM, and then tried importing my SPM to my project (I did this before and after each of the other steps too).
Checked to see where these packages where being added as dependencies. It shows up in the SPM main target Module -> Build Phases -> Link binary with libraries. I additionally added it to the Dependencies section to see if it changes anything.
Tried adding SPMs to ModulePackageDescription target to Dependencies section.
Added the dependencies in the Package.swift file as follows.
dependencies: [
// Dependencies declare other packages that this package depends on.
.package(url: "package1_url", .branch("master")),
.package(url: "package2_url", .branch("master"))
]
Adding this would import the other dependencies to my Xcode project. I don't exactly want this to happen because in case I try to use another version of the SPM that is being imported within my SPM, it would cause conflict between the two versions. But I'm willing to do this if it's the right way to go. But even adding dependencies in Package.swift didn't work for me. How would I resolve this issue? Let me know if anyone has faced the same issue.
Are the libraries public classes also need to contain constructors?
public struct NumbersA {
public init () {
}
}
also add them to the dependency Package.swft->dependencies: ["NumbersA"]),

How to expose an Objective-C Module properly using Bazel?

I'm trying to build RxSwift using Bazel in order to be able to use it in a dummy project as a dependency, for the sake of getting to know it a bit.
We don't use the latest RxSwift version at the moment, but version 4.4.2.
After reading through the docs, I've come to the point where I successfully add the specific release to my workspace as an http_archive, and supply my own BUILD file to build that external dependency RxSwift.
You can check out the BUILD file and WORKSPACE file here:
https://gist.github.com/daneov/487444109c703087862d830a3445ee86
Running
bazel build #rx_swift//:RxSwift
yields a Swift compilation error:
No such module: RxAtomic
So, this shows that my I'm missing something with regards to exposing the Objective-C module to a Swift library.
I currently supply these options to the objc_framework:
enable_modules = 1,
alwayslink = 1,
module_name = "RxAtomic"
Thanks in advance for any thoughts/pointers!
You should call bazel build with --experimental_objc_enable_module_maps option