Is it necessary to re-generate the Xcode project (`swift package generate-xcodeproj`) after performing local changes through e.g. `swift package`?

I wonder whether it is necessary to run swift package generate-xcodeproj again after modifying the project locally using e.g. swift package ..., swift build, etc., or editing project files like Package.swift?

Normally, I run swift package generate-xcodeproj after swift package init (+ initial Package.swift configuration), but I don't know whether later changes to the project, not performed through Xcode, will break the project?

1 Like

Yes, it is necessary to regenerate the Xcode project. If possible, it would be better to use the built-in SPM support in Xcode, which avoids this issue.

To be more precise:

Basically you need to regenerate the Xcode project after any operation that changes which files are needed, where they are located, or how they are dependent on one another (but files’ contents are unimportant). That boils down to any:

  • modifications to Package.swift
  • additions, removals or renaming of files
  • changes to the resolved dependency versions (which check out different files for dependencies). This includes swift package edit and swift package unedit.

Operations like swift build, swift test, swift package clean and the like have no such side effects. (Well, they can trigger initial dependency resolution if Package.resolved was never checked in and you already have an Xcode project acquired by checkout instead of local generation. But otherwise resolution would have been triggered when the Xcode project was generated.)

Any day now Xcode 11 will be released and make generate-xcodeproj mostly obsolete.

Thanks @SDGGiesbrecht! Will you elaborate what you mean by "checked in", "acquired by checkout", and why generate-xcodeproj will become obsolete?

Xcode 11 understands packages natively. It can load and manipulate them directly. It does not need you to convert them to Xcode projects first.

I have assumed you use source control. In the event you do not and those are terms you have never heard before, then you can simply ignore what follows, since you will never encounter the situation described. But if you understand the words and are instead asking why they are relevant, here is the longer explanation:

While swift build does not require regenerating Xcode 99% of the time, several distinct concepts can stack op together to create a corner case where it might end up necessary.

  • Package.resolved and its corresponding checked out dependency files will not be modified by swift build and similar commands as long as they remain valid. They would only become invalid if you change the version restrictions in the manifest (which would already have been a reason in and of itself to regenerate the Xcode project).
  • If there is no Package.resolved yet, then swift build will resolve dependencies and construct one.
  • Most developers (and the default .gitignore created by swift package init) have Package.resolved checked into Git, so the resolver only makes its decisions once and then they are persisted, even across repository clones. Every clone checks out the dependencies in the exact same arrangement.
  • swift package generate-xcodeproj is just like swift build in that it needs to either use an existing Package.resolved or make decisions to create a new one.
  • Most developers (and the default .gitignore created by swift package init) make Git ignore the Xcode project. So new clones do not come with an Xcode project and you have to generate your own. Generating it will create Package.resolved, so by the time you actually have an Xcode project, Package.resolved will already be finalized and in a valid and stable state.
  • But let’s say you do not belong to the “most developers” mentioned above. Let’s say you check in your generated Xcode project. After a fresh clone, you will have an Xcode project, but all the dependency files it expects will be missing, because the package manager hasn’t fetched them yet. You need to do swift package resolve so that the package manager fetches the dependencies (swift build or swift package generate-xcodeproj would also trigger this). Since it will respect the existing Package.resolved to the letter, all the files will end up back in the exact same spot and the Xcode project will be valid again even without regenerating it.
  • Let’s say you do things even more unusually and you make Git ignore your Package.resolved. When you make a fresh clone of that package, it comes with an Xcode project but no Package.resolved file. You need to do swift package resolve to fetch the dependency files to where they need to be. Most of the time, just like the last bullet point, they land right where the Xcode project expects them, so all is well.
  • Now let’s say one of your dependencies release a minor version. Some time after that, you make a fresh clone of your package. Its included Xcode project points at files that were there when you made the commit, and that the package manager has been faithfully putting back in the same place for a while now. But you are in for a surprise. The clone did not come with a Package.swift, so when you swift build, the package manager will make a new one. Since it prefers the more recent releases, and there is a shiny new one to try, it selects it and resolves the whole graph differently. Different versions mean different files in a different arrangement, so now Xcode is out of sync with the file system and swift package generate-xcodeproj is in order.

So yes, swift build can cause circumstances that change files behind Xcode’s back, but you really have to leave the beaten path to encounter it.

I suppose if you add “git checkouts or cloning” to the list in my previous comment then that list would cover absolutely everything.

I had to generate a .xcodeproj so that I could add support for cocoa pods to my package. Is there a way to accomplish this without a .xcodeproj?