Project and dependencies
Reading an Xcode project from a terminal usually means opening Xcode. These commands answer the same questions without it (what's in here, how does it build, what does it depend on) and, for dependencies, change the answer.
What's in the project
$ sweetpad project info
SweetpadCIApp (project)
path: /path/to/SweetpadCIApp.xcodeproj
targets:
SweetpadCIApp
SweetpadCIAppTests
SweetpadCIMac
configurations:
Debug
Release
schemes:
SweetpadCIApp
SweetpadCIMac
That's the orientation command for a repo you didn't set up. sweetpad scheme list prints just the
schemes, marking the one currently selected.
To start a project rather than inspect one, sweetpad project new scaffolds a minimal SwiftUI app.
See Starting a project.
Build settings
sweetpad settings show prints the fully resolved build settings for the scheme: the same values
xcodebuild will use, after every xcconfig, target, and project layer has been folded together:
sweetpad settings show
sweetpad settings show --target SweetpadCIAppTests
For one value, --key prints it bare, with no label, ready to capture:
$ sweetpad settings show --key PRODUCT_BUNDLE_IDENTIFIER
dev.sweetpad.ci.app
BUNDLE_ID=$(sweetpad settings show --key PRODUCT_BUNDLE_IDENTIFIER)
That's the resolved value. To see what your project file actually stores, before Xcode's defaults and any xcconfig get layered on, use the plumbing command below.
Swift Package dependencies
sweetpad dependency, or dep for short, reads and edits the Swift packages an Xcode project depends
on, including which product is linked into which target:
$ sweetpad dependency list
keychain-swift (remote)
https://github.com/evgenyneu/keychain-swift
requested: branch master
locked: master @ 5e1b02b
link: KeychainSwift → IceCubesApp
link: KeychainSwift → IceCubesNotifications
sfsafesymbols (remote)
https://github.com/SFSafeSymbols/SFSafeSymbols
requested: from 4.1.1
locked: 4.1.1
link: SFSafeSymbols → IceCubesApp
The requested line is what your project asks for; locked is what Package.resolved pinned. A
gap between them is usually the answer to "why is CI building a different version".
Adding a package
sweetpad dependency add https://github.com/apple/swift-collections --from 1.1.0
add resolves the package, then links a product into a target. Leave --product or --target out and
you'll be prompted after the resolve, once SweetPad knows what the package offers:
sweetpad dependency add https://github.com/apple/swift-collections \
--from 1.1.0 --product Collections --target MyApp
Both flags repeat, for a package whose products go into several targets.
The requirement flags mirror SwiftPM's own:
| Flag | SwiftPM equivalent |
|---|---|
--from 1.1.0 | from: "1.1.0", up to the next major. |
--up-to-next-minor-from 1.1.0 | .upToNextMinor(from:) |
--exact 1.1.0 | exact: "1.1.0" |
--branch main | branch: "main" |
--revision <sha> | revision: "<sha>" |
--from 1.1.0 --to 2.0.0 | A half-open 1.1.0..<2.0.0 range. |
A local package works too: pass a directory containing a Package.swift instead of a URL.
Updating and removing
update with no requirement flags re-resolves to the latest versions your requirements allow. With
one, it rewrites the requirement (a bump, a pin, or a downgrade) and then re-resolves:
sweetpad dependency update # everything
sweetpad dependency update swift-collections # one package
sweetpad dependency update swift-collections --exact 1.1.4
remove drops a whole package, or just unlinks one product from one target:
sweetpad dependency remove swift-collections
sweetpad dependency remove swift-collections --product Collections --target MyAppTests
sweetpad dependency resolve just refreshes Package.resolved without changing anything else, which is the
command for a fresh clone or a CI job. Every mutating command resolves on the way out; --no-resolve
skips that when you're making several changes in a row.
If your .xcodeproj is generated by XcodeGen or Tuist, editing it directly gets overwritten on the
next regenerate. SweetPad refuses these edits unless you pass --force. See
Tuist and XcodeGen; normally you want to edit the spec instead.
The pbxproj plumbing
sweetpad pbxproj edits project.pbxproj directly. It exists because the porcelain commands above
can't express everything an Xcode project can hold, and because agents and scripts sometimes need to
make a precise change rather than a convenient one.
It's genuinely low-level. Reach for it when nothing else fits:
| Group | What it operates on |
|---|---|
pbxproj settings | Stored buildSettings entries, per configuration: show, set, unset. |
pbxproj folder | A target's synchronized source folders: list, add, remove. |
pbxproj membership | Per-file target membership: list, add, remove, exclude, include. |
pbxproj fileref | File references: the objects saying a file exists at all. |
pbxproj group | The navigator group tree: where a file appears in Xcode. |
pbxproj resolve | Semantic merge of a conflicted .pbxproj. See Git merge drivers. |
Two distinctions the group names encode, and which trip people up in Xcode too:
A file reference is not membership. A file can exist in the project and be built by no target.
fileref creates the reference; membership decides what compiles it.
A group is not a folder. The navigator tree is where a file appears in Xcode's sidebar, which is a
separate question from where it lives on disk or what builds it. group moves things in the sidebar
and nothing else.
pbxproj settings show is the counterpart to settings show: it prints what the project file stores,
per configuration, rather than what those settings resolve to:
$ sweetpad pbxproj settings show
# project — Debug
ALWAYS_SEARCH_USER_PATHS = NO
CLANG_ANALYZER_NONNULL = YES
CLANG_CXX_LANGUAGE_STANDARD = gnu++14
…
The same generated-project guard applies here: these commands refuse to edit an XcodeGen or Tuist
project without --force. See Tuist and XcodeGen.