Spm

Swift Package Manager

Intro

Versatile yet out of beta package manager. Great support in built in Xcode IDE.
Still initial adoption pain back in 2020, 2021. I think so past two years it has matured quite a bit.

Setup

You can utilized the Xcode SPM functionality in built with version 11

Great for linking different packages and frameworks to Xcode with first class support.

No messy .xcodeproj / .xcworkspace file

Create

swift package 

apple docs | swift package - create a standalone package xcode

Sample Package.swift

let package = Package(
    name: "test-apple",
    platforms: [
        .iOS(.v16),
        .tvOS(.v16)
    ],
    products: [
		.library(
            name: "DummyUnit",
            targets: ["DummyUnit"]),
    ],
	dependencies: [
        .package(url: "git@github.com:repo_name/test.git",
        branch: "important-events")
    ],
    targets: [
	    .target(
            name: "DummyUnit",
            dependencies: [],
            path: "Sources/product_path"
        ),
    ]
)

Directory

Delete local build cache

If you want to delete the cache via CLI

swift package purge-cache

Note - this command is only applicable from the $pwd where there is a Package.swift file present.

Package.resolved file

If it's a traditional .xcodeProj we don't have the Package.swift / .resolved file either. It is hidden under the project file. You can right click and browse its contents, below is the location of it.

.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Options

Resolve Package Versions

This tries to take latest pull - origin kind of methodology. So if 9.3.3 tag had been published before with hash ending at 352EA and now the remote server deleted it and again push few commits and tagged it 9.3.3 it will have the latest commit with new hash like 231EB. Usually its recommended to not do this kind of tagging or releasing & just follow SemVer but sometimes you can't choose the cross team you're working with.

Digress: I wish I could be picky enough like Linus Torvalds, only liking to work with people I respect.

Reset Package Cache

This basically does a soft nuke of the cache and tries to pull it again if the package already had cache setup.

Update Package versions

This command will update the package depending on the ruleset defined in SPM with Semantic Versioning Major.Minor.Patch
So 9.3.3 has minor versions allowed in update rules could update to 9.x.x

Cache

SwiftPM cache

Cache directory for SPM

~/Library/Caches/org.swift.swiftpm/

Install size
These store the actual repositories of downloaded or cloned dependencies.

SwiftPM Metadata

This location stores the Repositories resolved meta data. You need to delete the meta data if some repository made a change on the same tag and couldn't even follow Semantic Versioning to make sure that the new change isn't propagated everywhere which makes / breaks the whole dependency chain.

~/Library/org.swift.swiftpm/security/fingerprints

swift pm | ReleaseNotes/5.4.md#package-dependency-caching

Delete Cache

Full Swift Package manager cache directory

rm -rf ~/Library/Caches/org.swift.swiftpm
rm -rf ~/Library/org.swift.swiftpm

Or a single repository cache

To reset the cache for a single package:

  • Navigate to ~/Library/Caches/org.swift.swiftpm/repositories and deleting the folder and lock file related to the package
  • Then, in Xcode, run File-->Swift Packages-->Reset Package Caches
## Dependency

Adding a branch as a dependency or a tag.

```swift
dependencies: [
	.package(url: "git@github.com:company-player/dracula.git", branch: "br-logging"), 
	 .package(url: "git@github.com:company-player/dracula.git", exact: "0.3.0"),
]

Similar SPM | Xcode cache issue

Bundling

A note with Swift package manager bundling directories.

Swift Package Manager world, bundling basically boils down to the following:

  • Bundle.module returns the bundle relative to the call site. so if you call that in FrameworkCore/AppInfo.swift it will return the FrameworkCore bundle since that file and type are associated with that bundle. if you call it in FrameworkCoreTestKit/Mock.swift it will return the test kit bundle since that's where that file lives. so on and so forth
  • if you call Bundle.main, that will return the "product" or the bundle containing the current executable. you generally don't want to call this anymore unless you know you want the app layer bundle

Fetching latest

You have a dependency pointed to specific branch and you push some commits on the dependency's branch. But Xcode and SPM would always use the cache version of that dependency. Since we didn't go for SemVer approach and just a branch SPM and xcode isn't smart enough to automatically pull in latest or fetch origin. Unless you nuke the cache and SPM manifest metadata with git commits for every package dependency located in ~/Library directory.

You can avoid that hassle by just updating your packages and invalidating previous cache and Xcode / SPM is smart enough to abide by those Semver rules or just update to the latest commit on the specified branch.
Note: Manually selecting the library and right clicking to select option Update doesn't update it for me Xcode 15 beta 6 with package dependency defined using a branch: "branch_name"

Updating all packages

You can do update by following this option in the menu bar.

Xcode ->  File -> 
Packages ->
Update to Latest Package versions

Updating individual package

You can also update a specific version on a package dependency by right clicking on it and updating specifically.

Cross Platform

// Package.swift
#if !os(Windows)

dependencies.append(.package(url: "https://github.com/danger/swift.git", from: "3.12.1"))

targets.append(.target(name: "DangerDeps", dependencies: [.product(name: "Danger", package: "swift")]))

products.append(.library(name: "DangerDeps", type: .dynamic, targets: ["DangerDeps"]))

#endif

platform-specific-code-in-swift-packages

Migrating to SPM

migrating-to-spm-from-mix-of-embedded-frameworks-and-static-libraries

spm-ifying-yapdatabase

SPM Errors

Notes

Xcode should be quit before deleting the references and cache of SPM. Since Xcode reactively tries to resolve those dependency midway while you're trying to resolve it manually.

Sometimes regenerating your SSH keys on your dev machine is helpful to isolate that permission issues, but since Xcode 14.1, it has been bubbling up better errors to the developer to show what failed and what went wrong.

This script from a stackoverflow user also goes through selectively deleting cache from Derived Data and SPM cache manifest files which is more efficient than nuking the whole cache as its more time consuming and resource intensive.
Relevant thread which is tracked on swift forum

Exposing Library & Target

When working with Swift Package managers you need to expose every folder as a different library in order to import them in the project itself. Swift Packages treat every directory or folder independent and you can't just directly access them if they are out of the default scope of Package_Name -> Sources library package name.

products: [
	.library(
		name: "DummyUnit",
		targets: ["DummyUnit"]),
],
targets: [
	.target(
		name: "DummyUnit",
		dependencies: [.product(name: "testLib", 
								package: "product_name")],
		path: "Sources/product_path"
	),
]

In order to access the directory as a library in your codebase, you would have to add dependencies if they are seperate repositories.

To import just use the following syntax as long as the library builds correctly and is embedded into the project product target in General Settings -> Frameworks, libraries and Embedded Content

import DummyUnit

For more information on Frameworks refer my mind map docs

Local Package dependency

You can point a swift package to local option in order to do faster prototyping instead of waiting for xcode to close / fetch / resolve SPM package directory and then make it usable to edit a file.

+1 Less .xcodeproj xcworkspace headaches.

Local dependency path in package.swift url takes both local and remote paths in its initializer.

dependencies: [
// Local Package
	.package(url: "/Users/ksave/git/cloud/packageName", branch: "28-events")
	.package(url: "file:///Users/ksave/git/cloud/packageName", from: "6.6.6")

// Remote package
    .package(url: "git@github.com:companyName/packageName.git", branch: "21-events")
],

Change set in .resolved file

"identity" : "dependency_name",
- "kind" : "remoteSourceControl",
- "location" : "git@github.com:org-name/dependency_name.git",
+ "kind" : "localSourceControl",
+ "location" : "/Users/username/git/cloud/dependency_name",

Sometimes it gives out an error or warning as follows if you have both local dependency and cloud dependency added as a package in Swift PM.

'project-parent-package-name' dependency on 'git@github.com:source/package_name_3.git' conflicts with dependency on '/Users/userName/git/cloud/package_name_3' which has the same identity 'package_name_3'. this will be escalated to an error in future versions of SwiftPM.

Add local SPM package doesn't work in Pure Package.swift project opened in Xcode 16 beta 3. But I could add local package in .xcworkspace or .xcproj file.

Another thing
For some reason my local package gets added via Package.swift with absolute URL local path and still doesn't reflect the right change-set. But if I add it via add package -> local GUI option on a .xcodeproj file in Xcode GUI Package Dependencies It reflects the local change-set appropriately. Maybe a cache issue? Don't know and don't want to bother learning more about it.

An unknown error not found (-1) issue

Pitfalls

Some pitfalls of using SPM and Build Configuration in Xcode

Unexpected Duplicate tasks

Multiple commands produce framework libraries. Probably deleting SPM cache and metadata. also .resolved SPM project helps me to get over this build error. Reset Package graph and Resolve Packages as well.

Circular dependency

Also experienced it in Carthage build command

SO resolve circular dependency swift PM
swift forums circular dependency

SO | resolve circular dependency

Best option is to refactor the code or move dependency one way rather than two ways. Or move it to third package and adopt one way dependencies on both A & B dependencies.

Timeline

Supporting Binary dependencies in SPM
Added in Swift 5.3
So carthage and cocoapods were used in lot of projects if your dependencies had images, data files, close source code, binaries.

Resources

SPM Basics

https://www.youtube.com/watch?v=QmBZ9wJguS4

Caching and Purge caching

SwiftPM: Same sources, multiple targets

Deleting cache SO post