Use of SPM for internal modules?

How can I create and add a Swift Package to my app, where the source folder for the package is a subfolder within the same repo? How can I add it to my app in a way that is unversioned? (I.e. we only have the app's version; we want to use Swift Packages purely to define modules but don't need any of SPM's "dependency management" trappings.)

Here's some background if it matters:

My org's app is broken about 40 frameworks that comprise various features and shared code. Recently we migrated to a monorepo, so none of our own projects rely on any kind of per-framework versioning or dependency management system anymore.

Still though, having 40 frameworks in the workspace means lots of Xcode project files and so, refactors than move files around for example can cause nasty merge conflicts in the .pbproj files. And when someone makes a new project there is always some setting in the project that gets set wrong and we have build problems.

So now that project resources can be included to a Swift Package as of Swift 5.3, we are beginning to consider if it makes sense to migrate away from using frameworks and use Swift Packages for everything instead.

However when I was trying to convert one framework into a Swift Package, it seems that Swift Packages are really only intended for a situation where the Swift Package comes from its own git repo (and not just some local subfolder in the same repo as the app).

As well it seems like the "dependency management" aspect is rather baked into the concept of a Swift Package—it seems to expect that rebuilding only needs to happen if the package version changed.

But we don't want these packages to be versioned, and we don't want them to exist in another repo or for SPM/Xcode to look for them anywhere on a network. We just want to use them as modules, that's it.

How can we?

1 Like

If you create another directory and put the Package.swift and all of its source files in there you can drag that into your app. You will need to manually link the libraries when you do it this way. It has worked well overall for my current project.

I do this all the time for small tools and utilities I spin up. Swift Packages make it really easy to declare separate modules for things. You would have a root Package.swift manifest that can reference other packages by relative file path instead of a git repository URL. Those subpackages can be in the same repository (or completely outside of source control if you want).

In fact, you could merely have a single root Package.swift file and just declare separate modules in there and a single root product that depends on them. You don't even need to have a tree of local packages to get this up and running quickly.

With the latest versions of Xcode, if you double click on the Package.swift file, it will open up as a project you can manipulate.

1 Like

Wow, thanks for the prompt replies! You guys are awesome.

I will try this. Maybe what was confusing me was Xcode's UI for adding packages.

Maybe what was confusing me was Xcode's UI for adding packages.

You’re not the first to say that.

  1. As @Braden_Scothern said, when you want to use a local unversioned package, simply drag its directory into the file list on the left side in your client Xcode project. (Remember to drag the directory containing Package.swift, not the Package.swift file itself. That trips a lot of people.)
  2. Xcode will ask to create a workspace if you haven’t already. (Edit: Only if you drop it in the global scope.)
  3. After that, you’ll be able to add any of the package libraries to any target with the “+” under “Frameworks, Libraries, and Embedded Content”.

That’s all there is to it.

2 Likes

It's not necessary to create a workspace, local packages can also be referenced from projects.

3 Likes

Trying it now, it appears you are right and it depends on where precisely in the list your drag ends. I guess it never occurred to me to drop it anywhere besides the very top or bottom of the list, so I always got the prompt to create a workspace.

2 Likes

Yah, I think it is a little finicky and unless you know both are possible it's easy to miss. I'm going to think about how this could be made more obvious.

4 Likes

When you realize that you can add local packages similar to nested project files it is easy. I think Apple could clear this up by adding a local package option in the “Swift Packages” project settings since I think that is what someone would try first intuitively. It is unintuitive that there are two completely different workflows for adding packages based on local or remote. Once you get it, it works great. You can even run tests as usual.

2 Likes

This has been working great so far.

Only issue we have is that if we include a package's test target in a different scheme with other test targets, then @testable import Package never works from inside its own tests. No idea why not.

I tried to use local packages to organize our codebase, but found a limitation that it is not possible to mix local and remote dependencies (versionBasedDependencyContainsUnversionedDependency). It makes it impossible to use SPM for internal modules and expose something outside with "umbrella" Package.swift. Can someone comment if this is something fundamental or not?

This is a limitation that I would like to see us lift in the future. As far as I know, it is just policy.

2 Likes
Terms of Service

Privacy Policy

Cookie Policy