How to add local Swift Package as dependency?

So the whole idea is that I could test it before it's deployed somewhere by using the local branch. I think this is still possible, just need to tool around with it a bit more.

I'm working on my libs by using them in my projects so I expect that my libs are connected locally so I can change their code on-the-fly. With Cocoapods I can do easily cause they put local pods to the specific folder named Development Pods and this is extremely useful.

I came here while trying to find out why dragging my Swift package into an Xcode project was not working. The package would show up in the project pane, but without the disclosure triangle, and the library it contained wouldn't show up when I tried to choose it with "Link Binary with Libraries".

The solution I found was to close the Xcode project after dropping the Swift package. On reopening the project, the disclosure triangle appeared, and everything else worked.

This is in Xcode 11.3 on MacOS 10.15.1. Hope this helps someone else.

7 Likes

If you're looking for a "development pods" type of experience, where you've got a local copy of the package source and you want to edit that source from inside the host project without having to commit and push your changes there's not currently a way to do that. However... I have found a hack that gets you halfway there. I'm using this approach in a sample project I include in one of my package's repositories.

Before proceeding, the following assumptions are made:

• you have your package repo cloned to your machine
• create a sample Xcode project in your Package repo

The root directory of your package repo should look like this, more or less:

Package.swift
Sources/
Tests/
SampleProject/

Then perform the following steps:

  1. Use the Xcode GUI to add your package to the sample project. You'll need to use the actual HTTPS URL to the repo origin. Specify a branch name to a branch you're comfortable working from regularly, e.g. a "develop" branch or something.

  2. Open the sample project's project.pbxproj file in a text editor.

  3. Find the XCRemoteSwiftPackageReference section.

  4. Edit the repositoryURL value so the line reads: repositoryURL = "../";

Now you should be able to build your sample project and run it normally.

The big caveat here is that it won't see any changes you haven't committed to the specified branch on your local clone. The good news is you don't have to push those changes to remote. Xcode resolves the ../ path to a local file URL and clones your local clone, not the remote branch.

3 Likes

This is the answer I was looking for, thank you. One small note is that latest committed changes are not made available immediately to the project, for that you need to select File > Swift Packages > Update

@Aciid As drag-n-drop approach does not seem to work for the case when application that uses the package is in the same folder with the package itself, can you consider it as a bug worth fixing? It seems to be a common scenario for libraries to provide a sample app along with the library itself.

Can you provide a sample project that isn't working?

For example this one GitHub - ilyapuchka/SwiftNIOMock: A web server based on SwiftNIO designed to be used as a mock server in UI automation tests
It works with an approach described by @jaredsinclair but when dragging the package folder as you suggested first Xcode didn't resolve it (it only showed it as a package reference in the project structure). Now when I try to repeat this Xcode crashes so I can't make a screenshot, but I committed the change in the branch "local-spm". I can see that the project now has an entry that points to the package:

B506677E23EF674200EC3738 /* SwiftNIOMock */ = {isa = PBXFileReference; lastKnownFileType = folder; name = SwiftNIOMock; path = ..; sourceTree = "<group>"; };

Something else must be going on here. That general layout has never given me any issues on other projects.

But I can verify that doing the same thing on your project results in an Xcode workspace that crashes Xcode if you try to open it. Since this is an Xcode issue, I would talk to the Xcode folks over at the Feedback Assistant or the Developer Forums.

Submitted it as FB7570938

Not sure if someone else already replied to this, but this is because you have two projects open at the same time, first one is your lib and second is your project you dropped your lib into. You have to close your lib project for it to work from the second project. ;)

2 Likes

This seems to be the best way of handling it. You can even modify the source code of the package from your main project, and it gets applied immediately.

With regard to the drag-and-drop approach, I've discovered that this works as long as only one project that uses the project is open at a time. As long as you do that, any project can edit, view, and import this package. Note, however, that this is not a true dependency. If you want that, you need to upload the package to a remote resource and serve it to yourself using the actual Add Package Dependency mechanism.

4 Likes

I filed a bug for this: Feedback Assistant

You can simply use the path argument instead of the URL argument to add a local swift package on your machine.

dependencies: [
        // Dependencies declare other packages that this package depends on.
        // .package(url: /* package url */, from: "1.0.0"),
        .package(path: "../Hello")
],
4 Likes

I was looking for a development pods experience, indeed jaredsinclair's solution works. But I think krishnaagarwal1994's would work too and should be better, coz no need to modify your .xcodeproj.

I missed the part where Jared mentioned that I will need to commit locally (no need to push to origin/remote) my changes to my package.

Thanks! Now I can code both in my sample project and in my package simultaneously.

While it's fine to use local packages as dependencies for the sake of developing a Swift package for distribution, I would recommend against using Swift packages as a replacement for Xcode Projects for normal development purposes, except for simple Swift-only modules that will only ever be used by Swift code.

At my organization, we were really tired of Xcode Project files due to the constant merge conflicts their randomized GUIDs cause. So, based on some advice given on this forum and in prior WWDC talks, we decided to start using Swift Packages for all our new modules.

However, with the benefit of hindsight, I would not make that decision again. We have been continually plagued by flaws and limitations with SPM and how it's integrated with Xcode, which have collectively been a bear to deal with. And we've been forced to keep using Xcode projects in several cases because Obj. C headers in an Xcode-project-defined module cannot take advantage of @objc Swift code that's defined a Swift module.

So my advice is, if your app has any Obj. C in it, don't use local Swift Packages. If your app needs to directly reference asset catalogs, don't put them in a Swift Package. If your code needs to link to a pre-built third-party binary framework, don't put it in a Swift Package. That's pretty limiting.

Any @objc Swift code in your packages won't be visible from Obj. C headers in your Xcode projects. There's no workaround or solution. Quite simply, Xcode doesn't embed a Headers folder in the .framework that it builds from a Swift Package, and it doesn't add the headers themselves either. To me this means there is a huge lack of test coverage around SPM's Xcode integration, or a very poor understanding of the use-cases for Xcode project files that people might try to use Swift Packages as replacements for.

Did I mention, the Asset Catalog support is woeful? Consuming modules won't be able to access your package's resource bundle directly, meaning you'll have to write a whole new API to allow consumers to access those resources. If you write that API in Swift, it won't work with Obj. C code in Xcode projects that consume the package, forcing you to make a whole separate package written in Obj. C with its own copy of the resources. Not to be harsh, but it's really ridiculous that it was released in this form.

Further, the ability to link to external binaries is useless, because .binaryTarget only works for binaries that are inside the same directory structure as the package itself. Why? Because it was only designed as a way to let people distribute a prebuilt framework in a Swift package wrapper—it wasn't designed with the use-case in mind of someone who wants their Swift package to simply link to an existing framework in their build folder or Carthage folder.

After many wasted hours, I did finally discover an extremely ugly hack to link a Swift package to a framework outside that package's directory structure. However, since:

  • linking a Swift package to an XCFramework via .linkedFramework is completely unsupported,
  • Swift packages' build conditions provide no way to specify different build settings between simulator and device builds; and
  • Xcode projects can't link to Swift packages that have unsafe flags (a bug);
  • and unsafe flags are the only way to set custom linker flags for a Swift package...

... therefore the only way to link to an external framework requires the kludgiest hack I've ever had to do in my entire career.

And the above is just the tip of the iceberg of problems and limitations with Swift packages.

Despite their flaws, Xcode projects remain by far the most flexible, powerful, bug-free, and useful way to define a local module. If you're not going to distribute code, and you're working on a Mac with Xcode, save yourself tons of headache, trials, and tribulations, and just use an Xcode Project file. The occasional merge conflicts are a small price to pay.

Note: I sincerely hope that the SPM/Xcode integration improves in the future, but given that Xcode 13 did not so far address any of the major issues that I'm aware of about Swift Packages then it doesn't feel like they are really prioritizing making SPM as a viable replacement for Xcode project files. That's why I proposed that we add something like "Swift Projects" so that the purpose of it is clearly stated as for it to be able to support everything Xcode projects currently support.

SPM was designed as a way to distribute non-mixed-source modules and manage such dependencies, and it's very serviceable in this regard for a small set of small dependencies. But even there, its propensity for extremely slow deep clones makes it not really a viable alternative to Carthage or Cocoapods yet since it takes 15-20 minutes after pasting the URL to GitHub - firebase/firebase-ios-sdk: Firebase iOS SDK into Xcode before you even get the menu of options of which libraries to add as dependencies, and then another 15-20 minutes after that to actually add them (plus the same delay anytime someone clones the project and opens it before they can start building, plus it adds every one of Firebase's many dependencies into your main workspace).

Sounds like a lot of complaining but I was one of the most gung-ho early adopters of Swift Packages, and I tried as hard as I could to make it work and communicate issues to Apple. But at the end of the day, Apple is in control of Xcode and SPM already meets their criteria for integration with the IDE. They would probably say, "If it doesn't meet your use case, then don't use it," and I agree. But also they are the ones who started recommending people to start using it instead of Xcode projects—without telling people about all the problems and limitations, and then only making a half-hearted attempt to actually fix those issues.

So I'm just saying, beware. It'll be your own valuable time that you'll be potentially wasting.

Like for example, now we can't run our tests on a device anymore because someone added a CombineTestExtensions package that depends on XCTest, and is a dependency of a bunch of our Swift package's test targets. Well, XCTest wasn't built with bitcode enabled, yet there's no way to disable Bitcode when building a Swift Package that's not a test target. So we literally can't compile and run our full test suite on a physical device anymore, because Xcode requires that if your module has Bitcode enabled, all its dependencies must also have Bitcode enabled.

Apple wants us to send bug reports about this stuff, but it's getting to the point where honestly, Apple shouldn't need me to tell them that every build setting needs to be configurable. Yet they've done the SPM/Xcode integration in such a way that many settings are automatically assumed, and cannot be changed or overridden. Or if you do change/override a setting, you have to use an "unsafeFlag" that makes your Swift Package unable to be linked to from an Xcode project or app. And you can't even inherit settings from an .xcconfig file or define a custom configuration name—there's just "Debug" and "Release" and "woe be unto you if your project uses a configuration name other than Debug or Release because now it's just going to automagically guess whether it should be Debug or Release and if it's wrong then it's up to you to hack the system to fix it or live with the consequences."

Quite a departure from Xcode projects.

4 Likes

Hi there,
I just wanted to chime in that my experiences are a bit different from yours, @1oo7 :slight_smile: .

Note that our own codebase is entirely in Swift, so that may be part of the reason. We do have quite a few external dependencies that are both objective C and also precompiled frameworks built from objective C with various different hacks apparently being used to build the frameworks (we are using bluetooth scanners, cash registers, payment terminals and receipt printers, and the drivers there are generally not of the best quality).
All of the external dependencies are still managed in the Xcode project, but for all of our internal frameworks I recently (5 months ago) refactored from having separate repos and being integrated through Carthage - to a mono repo where all the dependencies now live in one local package with many separate library targets.
We have also started building 'features' as separate local packages - one package per feature - and all feature packages having a similar layout with a UI target, a shared viewmodel definition and then the viewmodel implementation (which is the only target that has dependencies on the rest of our frameworks).

This means that UI previews in the Xcode preview canvas only need to build the UI for the feature, the very lightweight view model definition and one shared UI component that basically serves as our design styleguide. This makes iterating on SwiftUI views very fast and quite a joy to work with.

With regards to assets, we have structured it so that any shared assets are only available through components wrapping the assets, so we have not yet had the need for exposing bundles of one package directly to other packages.

We still have quite a lot of code included directly in the Xcode project, but my plan is to split more and more of that into local package targets and rely less and less on Xcode.

We are actually also using the firebase ios sdks and it's true that it takes quite a while to check out the code. We have 'hidden away' the inclusion of firebase in a single target - and it's interface is defined outside of that target, so we only need to build firebase when building the entire app. I do want to experiment with including firebase as binary dependencies in the future to avoid unnecessary extra recompilation since we don't update these dependencies very often.
Hopefully the advances with the package registry will allow us to check out more 'shallow' versions of packages so we don't need to clone a full repo.

An example of a large open source code base that almost entirely relies on local packages and very, very limited Xcode project structure is GitHub - pointfreeco/isowords: Open source game built in SwiftUI and the Composable Architecture.

So I guess the experiences with Xcode vs. SPM varies quite a bit - and the inclusion of ObjectiveC packages may be part of the reason.

But over all I am really, really happy about the switch. :slight_smile:

2 Likes

like you ive had (mostly) positive experience with spm

im curious though, have you had any issues with existing projects and getting build errors in local packages to show up in xcode?

with an existing project, for some reason build failures dont appear in the ui (only get build failed bezel notification) but if i created a new sample xcode app project and drag/drop the depenencies it works fine...

im wondering if anyone has had to deal with that and if there are known solutions?

Yep, had to close and reopen Xcode then I was able to link my package library