Using Local Swift Packages in Xcode, Without Making Them Git repos.
The situation
At the time of writing, Xcode forces you to have a separate git repository for each Swift package if you want to add it as a dependency. It can reside on your hard drive, and you can certainly use a file:// URL, but it still has to be a git repository:
One possible way to get around this is to forget about .xcodeproj altogether and use the Swift Package Manager for everything, including your final executable. Xcode 11 added support for SwiftPM projects.
Furthermore, you can specify relative dependencies between packages using the file system. No need to mess with specific versions or git tags:
let package = Package(
name: "MyProduct",
products: [
.library(
name: "MyProduct",
targets: ["MyProduct"]),
],
dependencies: [
.package(path: "../MySoundLibrary"),
.package(path: "../MyGraphicsLibrary")
],
targets: [
.target(
name: "Trade",
dependencies: ["MySoundLibrary","MyGraphicsLibrary"])
]
)
But the Swift Package Manager only has two types of projects: library and executable, whereas Xcode supports more than 20 different types of projects across four different platforms (iOS, watchOS, tvOS, and macOS). Even if you use SwiftPM projects for all your modules, odds are you’ll want to use an Xcode project for your final deliverable.
The solution
It just so happens that Xcode has a feature that allows you to use a local folder to override an existing dependency. In other words, if you previously imported a Swift package from a git repository, you can specify a local folder as a temporary replacement. Even better, your local folder doesn’t have to be a git repository!
All we need is a way to quickly generate a fake git repository to make Xcode happy. The following bash/zsh script will do the job:
temppack() {
if [ -z "$1" ]
then
echo "Usage: temppack PackageName"
else
mkdir temppack
cd temppack
mkdir $1
cd $1
swift package init --type library
git init
git add .
git commit -m "first"
echo ""
echo "file://$(pwd)" | pbcopy
echo "The repository path for $1 is now in your clipboard."
echo ""
[[ $BASH_VERSION ]] && read -p "Delete temp files?[Y/N]" -n 1 -r
[[ $ZSH_VERSION ]] && read -q "REPLY?Delete temp files?[Y/N]"
echo ""
cd ../..
if [[ $REPLY =~ ^[Yy]$ ]]
then
echo "Deleting temporary folder..."
rm -rf temppack
echo "Done."
fi
fi
}
You can add this script to your bash/zsh startup file. I only tested it on zsh (macOS 10.15) but I did my best to keep it backwards compatible with bash. The script expects a single argument which should obviously match the name of your actual Swift package:
> temppack MySwiftPackageCreating library package: MySwiftPackage
Creating Package.swift
Creating README.md
...
...
...The repository path for MySwiftPackage is in your clipboard.Delete temp files [Y/N]?
Don’t answer to the [Y/N] prompt just yet. Switch back to Xcode and use the URL in your clipboard to import the Swift package:
Make sure you select this option in the following screen:
After you’re done, the newly imported dependency should appear on the left-hand side, as well as on the Packages grid on the right.
Now, using Finder navigate to the folder with your real Swift Package and drag-n-drop it under your project. Make sure you drop it directly below your project, not inside other package or folder.
As soon as you do that, you’ll see how the git repository dependency disappears:
Don’t let that fool you, Xcode still has a reference to the repository, and it still shows up on the Packages grid.
If you have more packages to add, to back to your terminal window and answer No to the [Y/N] prompt. Repeat this process for all your Swift packages and answer Yes for the last one of them, in order to remove the temporary files.
One last thing
Since the git repository reference is not getting used, it’s tempting to remove it. Don’t. This reference is the only thing making this whole process work. Xcode needs to believe your folder is overriding something, if you remove the reference, it will stop recognizing your folder as a dependency.