Pressing Run in Xcode hides a chain of decisions: which scheme to use, which targets to build and in what order, which build configuration, which device, where the output goes, and which of several hundred build settings apply. When a build behaves differently in Xcode than on the command line, or a setting you changed seems to do nothing, the cause is almost always one of those hidden choices.
This article makes each one visible with xcodebuild, the command-line tool
behind Xcode’s Run button, using the todo-list folder from
Build a Swift Package with Swift Package Manager on macOS.
You will read the project’s schemes, targets, configurations and destinations
from the terminal; find where each build lands; see which build settings apply
and where each value came from; and put your own settings in .xcconfig
files, plain-text files of build settings that Xcode layers under the
project’s own.
Those files give Debug builds the name To-Do Dev on the simulator’s home
screen and Release builds the name To-Do, so you can tell at a glance which
configuration you are running. The Makefile grows a CONFIG switch, a
settings make target that prints the settings that matter, and an archive
make target that produces the Release build you would later sign and upload. Every later
article in this series uses this Makefile.
This article does not sign or export the archive. Signing attaches a certificate that identifies who built the app, which iOS requires before it installs an app on a device or Apple accepts an upload; exporting packages a signed app for the App Store or for testers. Both need a development team, which comes with an Apple Developer account, and they come later in the series. Step 10 marks where the line falls.
What you end up with
todo-list/Config/, three.xcconfigfiles: one each for Debug and Release, which theTodoListproject uses as its base configurations, and a shared one both include.- A
Makefilewithmake settings,make build CONFIG=Release,make run CONFIG=Release, andmake archive, alongside the targets from the earlier articles. - An unsigned
TodoList.xcarchivebuilt from Release, with its debug symbols.
Prerequisites
- The finished project from
Build a Swift Package with Swift Package Manager on macOS:
the
todo-listfolder with theTodoListapp project, theTodoKitpackage, itsMakefile, and theTodoList-iPhonesimulator. - Xcode 27 or later on a Mac with Apple silicon. Validated on Xcode 27.1 (build 27A9275) with Swift 6.4, on macOS 27.0.1.
Every command runs from the todo-list folder.
Step 1: Ask the project what it can build
A build starts from a scheme: a named recipe that says which targets to
build and which configuration to use for each action, such as Run, Test, or
Archive. Before you can reason about a build, you need the names of the
schemes, targets, and configurations, and xcodebuild -list prints all three.
-quiet drops the lines about resolving packages, which include full paths
on your disk.
xcodebuild -list -quiet -project TodoList/TodoList.xcodeproj
2026-10-23 14:48:15.742 xcodebuild[39731:558568] [MT] IDERunDestination: Supported platforms for the buildables in the current scheme is empty.
Information about project "TodoList":
Targets:
TodoList
TodoListUITests
Build Configurations:
Debug
Release
If no build configuration is specified and -scheme is not passed then "Release" is used.
Schemes:
TodoKit
TodoList
The first line is a log message xcodebuild often prints on standard error,
failed or not; it did not appear on every run while this article was validated.
It is harmless, and the output blocks below leave it out. Three things in the rest matter later:
- Targets are what the project builds: the app, and the UI tests that
drive it.
TodoKitis not listed because it is a package, not a target in this project; Xcode builds it first because the app depends on it. - Build configurations are named sets of build settings. Debug favors
fast builds and debugging; Release favors fast, small apps. The note about
Release applies only when you name neither a scheme nor a configuration,
which the
Makefilenever does. - Schemes list
TodoList, the one everymaketarget uses, andTodoKit, which Xcode generated for the package so it can be built on its own. No scheme file is saved in the project folder; Xcode andxcodebuildgenerate both whenever they open the project.
The TodoList scheme uses Debug for Run and Test and Release for Archive. That
is why make test has never needed a -configuration flag, and why Step 10’s
archive comes out in Release without asking for it.
Step 2: Name the device to build for
A destination is the device a build is for. Pressing Run in Xcode uses the
device chosen in the toolbar; xcodebuild needs it spelled out with
-destination, and -showdestinations lists the ones that fit the scheme. The
full list includes every simulator on your Mac, and your Mac itself with its
hardware identifier, so this command keeps three lines, and sed removes the
tabs xcodebuild indents them with:
xcodebuild -showdestinations -quiet -project TodoList/TodoList.xcodeproj -scheme TodoList | grep -E 'TodoList-iPhone|Any iOS' | sed 's/^[[:space:]]*//'
{ platform:iOS, id:dvtdevice-DVTiPhonePlaceholder-iphoneos:placeholder, name:Any iOS Device }
{ platform:iOS Simulator, id:dvtdevice-DVTiOSDeviceSimulatorPlaceholder-iphonesimulator:placeholder, name:Any iOS Simulator Device }
{ platform:iOS Simulator, arch:arm64, id:ADED6804-7A8D-4BCC-A43F-897446B5AA35, OS:27.0, name:TodoList-iPhone }
The Makefile names a destination in one of two ways:
- A specific simulator,
platform=iOS Simulator,name=TodoList-iPhone, for anything that installs or runs the app:build,run, andtest. - A generic device,
generic/platform=iOS, matching the Any iOS Device line. It compiles for real iPhones without needing one connected. Archives use it: an archive is for devices, not for a simulator, and the generic destination needs no device plugged in.
The simulator’s identifier is random and differs on your Mac.
Step 3: Read the build settings that apply
A build setting is one named value that controls the build, such as the
app’s version number or the compiler’s optimization level. Each target in each
configuration resolves several hundred of them. -showBuildSettings prints the
resolved values; count them first:
xcodebuild -quiet -project TodoList/TodoList.xcodeproj -scheme TodoList -destination 'generic/platform=iOS' -showBuildSettings | grep -c ' = '
607
The exact count varies with the Xcode version and the Mac; another run during validation printed 609.
Each value comes from one of several layers, and a higher layer replaces a lower one:
- Xcode’s defaults for the platform and the SDK (the iOS frameworks and headers the build compiles against).
- The project’s settings, which apply to every target, plus any
.xcconfigfile the project uses as its base (Step 6 adds one). - Each target’s own settings, plus any
.xcconfigfile the target uses. - Settings given on the
xcodebuildcommand line.
Both layers 2 and 3 are stored in project.pbxproj, the file inside
TodoList.xcodeproj that records the project. Within each of them, a value
typed into Xcode’s Build Settings editor (select the project or a target, then
the Build Settings tab) replaces the same setting in that layer’s
.xcconfig file. -showBuildSettings prints only the final value, so to see the layers work, override one from the
command line:
xcodebuild -quiet -project TodoList/TodoList.xcodeproj -scheme TodoList -destination 'generic/platform=iOS' -showBuildSettings MARKETING_VERSION=9.9 | grep -o 'MARKETING_VERSION = .*'
MARKETING_VERSION = 9.9
MARKETING_VERSION is the version number users see, 1.0 in the project.
The 9.9 from the command line wins for this one command and changes nothing
on disk. The full output also contains paths with your user name in them,
which is one reason the Makefile in Step 7 prints only the settings it names.
Step 4: Find where each build lands
Every build writes its intermediate files and its products into a
DerivedData folder. Xcode puts one per project under your Library folder;
the sed command removes the indentation and shortens your home folder to ~:
xcodebuild -quiet -project TodoList/TodoList.xcodeproj -scheme TodoList -destination 'generic/platform=iOS' -showBuildSettings | grep -E '^ *BUILD_DIR =' | sed -e 's/^ *//' -e "s#$HOME#~#"
BUILD_DIR = ~/Library/Developer/Xcode/DerivedData/TodoList-duysjjjqokrlcugjoxsntflvtdri/Build/Products
The suffix after TodoList- is derived from the project’s path, so it differs
on your Mac. The Makefile passes -derivedDataPath build instead, so its
builds land in todo-list/build, where make clean can delete them and
.gitignore already ignores them. The two folders do not share builds: a
build in Xcode leaves build/ untouched, so make build starts no further
ahead, and a make build leaves Xcode’s folder untouched.
After the builds from the earlier articles, the build folder holds:
ls build build/Build/Products
build:
Build
CompilationCache.noindex
Index.noindex
info.plist
Logs
ModuleCache.noindex
SDKExplicitPrecompiledModules
SDKStatCaches.noindex
SourcePackages
TestResults
build/Build/Products:
Debug-iphonesimulator
Build/Products has one folder per configuration and platform, which is where
make run finds the app to install. SourcePackages holds what Xcode resolved
for packages, and Logs holds the build and test logs. The rest are caches and
indexes Xcode manages itself, including info.plist, which is Xcode’s record of
the folder, not the app’s Info.plist.
Step 5: Write the .xcconfig files
Settings typed into Xcode’s Build Settings editor live inside
project.pbxproj, mixed in with the rest of the project and hard to review
in a diff. An .xcconfig file holds the same NAME = value lines as plain
text. The three files below go in their own folder next to the project, not inside
TodoList/TodoList/: that folder is synchronized with the app target, so Xcode
would copy any file in it into the app.
Create the file
mkdir -p Config
touch Config/Shared.xcconfig Config/Debug.xcconfig Config/Release.xcconfig
Add the code: Config/Shared.xcconfig
// Settings for every target in the TodoList project, in every configuration.
SWIFT_TREAT_WARNINGS_AS_ERRORS = YES
Add the code: Config/Debug.xcconfig
#include "Shared.xcconfig"
// Debug builds show a different name on the home screen.
INFOPLIST_KEY_CFBundleDisplayName = To-Do Dev
Add the code: Config/Release.xcconfig
#include "Shared.xcconfig"
INFOPLIST_KEY_CFBundleDisplayName = To-Do
Detailed breakdown
Shared.xcconfigholds what every configuration shares.SWIFT_TREAT_WARNINGS_AS_ERRORS = YESmakes any Swift compiler warning in the project’s targets fail the build, so warnings get fixed instead of piling up. It does not reach theTodoKitpackage, which takes its settings from its own manifest.#includepulls one file into another. Debug and Release each include the shared file, then add their own lines. A setting defined in both places takes the value that comes last, here the configuration’s own.INFOPLIST_KEY_CFBundleDisplayNameis the name under the app’s icon. The app’sInfo.plistis the file of keys and values iOS reads about the app, such as its name and version. This project generates it from build settings: a setting namedINFOPLIST_KEY_plus a supported key becomes that key. The project does not set a display name anywhere else, so these values take effect: without them, the home screen shows the target name,TodoList.- Comments start with
//, the last piece of syntax these files use.
Step 6: Attach the files to the project in Xcode
Xcode reads an .xcconfig file only when the project refers to it and a
configuration is based on it. You set both in Xcode. Open the project:
open TodoList/TodoList.xcodeproj
First add the files to the project without adding them to a target:
- Choose File → Add Files to “TodoList”…, open the
todo-list/Configfolder, select all three files, and click Add. - In the options sheet, set Action to Reference files in place, and clear the TodoList checkbox under Targets so no target is ticked. Click Finish. A target would copy the files into the app.
Then base each configuration on its file:
- Click TodoList, the blue project icon at the top of the Project navigator, then the TodoList entry under PROJECT (not under TARGETS), and the Info tab.
- Under Configurations, expand Debug. The first TodoList row, with the blue project icon, is the project; set its menu to Debug.xcconfig.
- Expand Release and set its project row to Release.xcconfig.
Leave the target rows at None. The project row is layer 2 from Step 3, so its file applies to every target in that configuration.
Xcode records each choice as a baseConfigurationReference in
project.pbxproj. grep -o prints just the matching part of each line,
without the file’s indentation:
grep -o 'baseConfigurationReference.*' TodoList/TodoList.xcodeproj/project.pbxproj
baseConfigurationReference = 30BE926E308BE39600F0E3F8 /* Debug.xcconfig */;
baseConfigurationReference = 30BE926F308BE39600F0E3F8 /* Release.xcconfig */;
The long identifiers are Xcode’s object IDs for the two files and differ in your project.
Step 7: Extend the Makefile
The Makefile gains two make targets, settings (a short list of build
settings) and archive, plus a CONFIG variable so one make target builds
either configuration. Replace the whole Makefile.
Create the file
touch Makefile
Add the code: Makefile
PROJECT := TodoList/TodoList.xcodeproj
PACKAGE := TodoKit
SCHEME := TodoList
BUNDLE_ID := com.example.TodoList
SIM_NAME := TodoList-iPhone
DEVICE_TYPE := iPhone 17
DERIVED := build
# Debug or Release; override with `make build CONFIG=Release`.
CONFIG := Debug
ifeq ($(filter $(CONFIG),Debug Release),)
$(error CONFIG must be Debug or Release, not "$(CONFIG)")
endif
APP := $(DERIVED)/Build/Products/$(CONFIG)-iphonesimulator/TodoList.app
ARCHIVE := $(DERIVED)/TodoList.xcarchive
SIM := -destination 'platform=iOS Simulator,name=$(SIM_NAME)'
DEVICE := -destination 'generic/platform=iOS'
# Keep only result lines from the test logs: Swift Testing (✔/✘),
# XCTest ("Test Case ... passed"), errors, and xcodebuild's final verdict.
RESULTS := grep -E '(✔|✘) Test|Test Case .*(passed|failed)|error:|\*\* TEST'
# The build settings `make settings` prints, out of several hundred.
SETTINGS := CONFIGURATION|INFOPLIST_KEY_CFBundleDisplayName|MARKETING_VERSION|DEBUG_INFORMATION_FORMAT|SWIFT_TREAT_WARNINGS_AS_ERRORS
XCODEBUILD := xcodebuild -project $(PROJECT) -scheme $(SCHEME) -derivedDataPath $(DERIVED)
TEST_FLAGS := -parallel-testing-enabled NO -collect-test-diagnostics never
# bash, so the test recipes can use `set -o pipefail` (see below).
SHELL := /bin/bash
.DEFAULT_GOAL := help
.PHONY: help simulator settings build package-test test run screenshot archive clean delete-simulator
help: ## Show this help
@echo "Usage: make <target> [CONFIG=Debug|Release]"
@echo
@grep -E '^[a-z-]+:.*## ' $(MAKEFILE_LIST) | \
awk 'BEGIN {FS = ":.*## "}; {printf " %-17s %s\n", $$1, $$2}'
simulator: ## Create the TodoList-iPhone simulator if it is missing
@xcrun simctl list devices available | grep -q "$(SIM_NAME) (" || \
xcrun simctl create "$(SIM_NAME)" "$(DEVICE_TYPE)"
settings: ## Show the key build settings for CONFIG
@set -o pipefail; \
$(XCODEBUILD) $(DEVICE) -configuration $(CONFIG) -showBuildSettings 2>/dev/null | \
grep -E '^ *($(SETTINGS)) =' | sed 's/^ *//'
build: simulator ## Build the app for the simulator in CONFIG
$(XCODEBUILD) $(SIM) -configuration $(CONFIG) -quiet build
# pipefail makes `... | grep` fail when the command before it fails. It is set
# inside the recipe because macOS ships GNU Make 3.81, which ignores .SHELLFLAGS.
# SWT_SF_SYMBOLS_ENABLED=0 makes swift test print ✔/✘ instead of SF Symbols.
package-test: ## Run the TodoKit tests on the Mac (no simulator)
set -o pipefail; SWT_SF_SYMBOLS_ENABLED=0 swift test --package-path $(PACKAGE) \
--disable-xctest 2>&1 | $(RESULTS)
test: package-test simulator ## Run the package tests, then the UI test
set -o pipefail; $(XCODEBUILD) $(SIM) $(TEST_FLAGS) test 2>&1 | $(RESULTS)
run: build ## Boot the simulator, install the CONFIG build, and launch it
xcrun simctl bootstatus "$(SIM_NAME)" -b
open "$$(xcode-select -p)/../Applications/DeviceHub.app"
xcrun simctl install "$(SIM_NAME)" "$(APP)"
xcrun simctl launch "$(SIM_NAME)" $(BUNDLE_ID)
screenshot: ## Save a screenshot of the simulator to screenshots/
@mkdir -p screenshots
xcrun simctl io "$(SIM_NAME)" screenshot screenshots/todolist.png
# An archive is a Release build for real devices. It is left unsigned, because
# signing needs a development team; see the article.
archive: ## Archive an unsigned Release build for devices into build/
$(XCODEBUILD) $(DEVICE) -archivePath $(ARCHIVE) CODE_SIGNING_ALLOWED=NO -quiet archive
@plutil -extract ApplicationProperties json -o - $(ARCHIVE)/Info.plist; echo
clean: ## Delete the build folders
rm -rf $(DERIVED) $(PACKAGE)/.build
delete-simulator: ## Delete the TodoList-iPhone simulator
xcrun simctl delete "$(SIM_NAME)"
Detailed breakdown
CONFIG := Debugsets the default. A variable given on themakecommand line, as inmake build CONFIG=Release, replaces any assignment in theMakefile; aCONFIGvariable in your shell’s environment does not, so it cannot change builds by accident.APPusesCONFIGto find the matchingRelease-iphonesimulatororDebug-iphonesimulatorfolder.The
ifeqblock rejects any other value.xcodebuilddoes not reject an unknown configuration name. Given a misspelling such asRelase,-showBuildSettingsquietly reports the Release values, and a build fails with the misleadingUnable to resolve module dependency: 'TodoKit'. With the check,make settings CONFIG=Relasestops before running anything:Makefile:11: *** CONFIG must be Debug or Release, not "Relase". Stop.The destination moved out of
XCODEBUILD.SIMandDEVICEhold the two destinations from Step 2, soarchivecan build for devices while the other make targets use the simulator.settingsprints five settings out of about six hundred.SETTINGSis agreppattern of names, andsedremoves the four-space indentxcodebuildputs before each setting.set -o pipefailkeeps a failingxcodebuilda failure, as in the test recipes. The recipe uses the generic device destination, so it works without the simulator. Errors fromxcodebuildgo to/dev/nullto keep the output short; ifmake settingsprints onlymake: *** [settings] Error 1, run the same command without2>/dev/nullto see why.testtakes noCONFIG. The scheme’s Test action uses Debug and builds and installs the app itself, soCONFIGwould have no effect there.archiveturns signing off.CODE_SIGNING_ALLOWED=NOis a build setting on the command line, layer 4 from Step 3. Step 10 explains why it is needed. The recipe then prints the archive’s application properties as JSON.The help line names
CONFIG, so the option shows up in plainmake.
Check the help screen:
make
Usage: make <target> [CONFIG=Debug|Release]
help Show this help
simulator Create the TodoList-iPhone simulator if it is missing
settings Show the key build settings for CONFIG
build Build the app for the simulator in CONFIG
package-test Run the TodoKit tests on the Mac (no simulator)
test Run the package tests, then the UI test
run Boot the simulator, install the CONFIG build, and launch it
screenshot Save a screenshot of the simulator to screenshots/
archive Archive an unsigned Release build for devices into build/
clean Delete the build folders
delete-simulator Delete the TodoList-iPhone simulator
Step 8: Check where each setting came from
Before building anything, confirm the .xcconfig files took effect. Debug
first:
make settings
CONFIGURATION = Debug
DEBUG_INFORMATION_FORMAT = dwarf
INFOPLIST_KEY_CFBundleDisplayName = To-Do Dev
MARKETING_VERSION = 1.0
SWIFT_TREAT_WARNINGS_AS_ERRORS = YES
Then Release:
make settings CONFIG=Release
CONFIGURATION = Release
DEBUG_INFORMATION_FORMAT = dwarf-with-dsym
INFOPLIST_KEY_CFBundleDisplayName = To-Do
MARKETING_VERSION = 1.0
SWIFT_TREAT_WARNINGS_AS_ERRORS = YES
The display names and SWIFT_TREAT_WARNINGS_AS_ERRORS came from the new
files, since nothing else in the project sets them. DEBUG_INFORMATION_FORMAT
comes from the project’s own settings: Debug uses dwarf, debug information
inside the build, and Release uses dwarf-with-dsym, which also writes a
separate dSYM file of debug symbols that turns crash reports back into
function names. Step 10’s archive includes one.
A setting in the file that does nothing
A value in the project’s base .xcconfig file sits below every value typed
into the Build Settings editor, so it has no effect on a setting the project or
a target already defines. Add a version number to the shared file and check:
echo 'MARKETING_VERSION = 2.0' >> Config/Shared.xcconfig
make settings | grep MARKETING_VERSION
MARKETING_VERSION = 1.0
The version is still 1.0, because the TodoList target sets
MARKETING_VERSION itself (layer 3, stored in project.pbxproj). To manage a
setting like this one from a file, you also delete its value from each target
that sets it, here TodoList and TodoListUITests, in the Build Settings
editor (select the row and press Delete), so the file’s value can show through.
Take the line back out:
sed -i '' '/^MARKETING_VERSION/d' Config/Shared.xcconfig
make settings | grep MARKETING_VERSION
MARKETING_VERSION = 1.0
When a setting in an .xcconfig file seems to do nothing, make settings (or
the full -showBuildSettings) shows whether a higher layer sets it.
Step 9: Build and run both configurations
The display names make the configuration visible on the simulator. Build and launch the Debug build:
make run
xcrun simctl launch "TodoList-iPhone" com.example.TodoList
com.example.TodoList: 40080
The excerpt shows the last two lines. Quit the app, then screenshot the home screen:
xcrun simctl terminate TodoList-iPhone com.example.TodoList
make screenshot
xcrun simctl io "TodoList-iPhone" screenshot screenshots/todolist.png
Detected file type from extension: PNG
Note: No display specified. Defaulting to display: 459A9BC3-6621-403B-817E-B45E64657398 (screenID: 1, name: LCD)
A last line, Wrote screenshot to:, gives the full path. The home screen in
screenshots/todolist.png shows the app’s icon labeled To-Do Dev. The
display identifier differs on your Mac. The TodoListUITests icon next to it
is the runner app make test installed to drive the UI test.
Now build and launch Release:
make run CONFIG=Release
xcodebuild -project TodoList/TodoList.xcodeproj -scheme TodoList -derivedDataPath build -destination 'platform=iOS Simulator,name=TodoList-iPhone' -configuration Release -quiet build
xcrun simctl bootstatus "TodoList-iPhone" -b
Monitoring boot status for TodoList-iPhone (ADED6804-7A8D-4BCC-A43F-897446B5AA35).
Device already booted, nothing to do.
open "$(xcode-select -p)/../Applications/DeviceHub.app"
xcrun simctl install "TodoList-iPhone" "build/Build/Products/Release-iphonesimulator/TodoList.app"
xcrun simctl launch "TodoList-iPhone" com.example.TodoList
com.example.TodoList: 40308
The app opens with the same to-do list: both builds share the bundle
identifier com.example.TodoList, the name iOS tells apps apart by, so
installing Release replaced Debug. Check the name it shows by reading the
built app’s Info.plist:
plutil -extract CFBundleDisplayName raw build/Build/Products/Release-iphonesimulator/TodoList.app/Info.plist
To-Do
Quit the app again and the home screen shows To-Do:
xcrun simctl terminate TodoList-iPhone com.example.TodoList
Step 10: Test, then archive a Release build
The new SWIFT_TREAT_WARNINGS_AS_ERRORS applies to the app and the UI-test
target, so run the tests before archiving. They still pass:
make test
set -o pipefail; SWT_SF_SYMBOLS_ENABLED=0 swift test --package-path TodoKit \
--disable-xctest 2>&1 | grep -E '(✔|✘) Test|Test Case .*(passed|failed)|error:|\*\* TEST'
✔ Test summaryCountsOnlyUnfinishedItems() passed after 0.010 seconds.
✔ Test addTodoIgnoresBlankTitles() passed after 0.010 seconds.
✔ Test addTodoStoresTheCleanedTitle() passed after 0.010 seconds.
✔ Test blankTitlesAreRejected(raw:) with 3 test cases passed after 0.010 seconds.
✔ Test titlesAreTrimmed(raw:expected:) with 3 test cases passed after 0.010 seconds.
✔ Test run with 5 tests in 1 suite passed after 0.011 seconds.
set -o pipefail; xcodebuild -project TodoList/TodoList.xcodeproj -scheme TodoList -derivedDataPath build -destination 'platform=iOS Simulator,name=TodoList-iPhone' -parallel-testing-enabled NO -collect-test-diagnostics never test 2>&1 | grep -E '(✔|✘) Test|Test Case .*(passed|failed)|error:|\*\* TEST'
Test Case '-[TodoListUITests.TodoListUITests testTodoIsSavedAcrossLaunches]' passed (15.304 seconds).
** TEST SUCCEEDED **
An archive is what Xcode’s Product → Archive produces: a Release build
for devices, packaged with its dSYM files in an .xcarchive folder, ready to
be signed and uploaded. Without CODE_SIGNING_ALLOWED=NO, archiving stops at
signing. The error line starts with the path to the project file, followed by:
error: Signing for "TodoList" requires a development team. Select a development team in the Signing & Capabilities editor. (in target 'TodoList' from project 'TodoList')
A development team comes with an Apple Developer account, and this article does not assume one. With signing turned off, archiving succeeds:
make archive
xcodebuild -project TodoList/TodoList.xcodeproj -scheme TodoList -derivedDataPath build -destination 'generic/platform=iOS' -archivePath build/TodoList.xcarchive CODE_SIGNING_ALLOWED=NO -quiet archive
{"CFBundleShortVersionString":"1.0","CFBundleIdentifier":"com.example.TodoList","Team":"","ApplicationPath":"Applications\/TodoList.app","SigningIdentity":"","Architectures":["arm64"],"CFBundleVersion":"1"}
SigningIdentity and Team are empty because nothing was signed, and
Architectures lists arm64, the processor architecture of current iPhones.
CFBundleShortVersionString is MARKETING_VERSION from Step 3. Running
make archive again replaces the archive without asking.
Look inside:
find build/TodoList.xcarchive -maxdepth 3
plutil -extract CFBundleDisplayName raw build/TodoList.xcarchive/Products/Applications/TodoList.app/Info.plist
build/TodoList.xcarchive
build/TodoList.xcarchive/Products
build/TodoList.xcarchive/dSYMs
build/TodoList.xcarchive/Info.plist
build/TodoList.xcarchive/Products/Applications
build/TodoList.xcarchive/dSYMs/TodoList.app.dSYM
build/TodoList.xcarchive/Products/Applications/TodoList.app
build/TodoList.xcarchive/dSYMs/TodoList.app.dSYM/Contents
To-Do
The app inside carries the Release display name, and dSYMs holds the debug
symbols that dwarf-with-dsym produced. The next step in a real release,
xcodebuild -exportArchive, signs the app and packages it for the App Store or
for testers. It fails on this archive with No Team Found in Archive, and it
belongs with the articles that set up signing.
Troubleshooting
make settingsprints onlymake: *** [settings] Error 1.xcodebuildfailed, and its error went to/dev/null. Run the command from the recipe without2>/dev/null.CONFIG must be Debug or Release. The check is case-sensitive, althoughxcodebuilditself acceptsrelease: useCONFIG=Release.- A setting in an
.xcconfigfile has no effect. A higher layer sets it. Check withmake settingsor the full-showBuildSettings, then delete the value from each target or project that sets it, in the Build Settings editor. See Step 8. - The display name is still
TodoList. The configurations are not based on the files. Check Step 6: the file belongs on the project row, and thegrepthere should print two lines. - The
.xcconfigfiles appear inside the built app. They were added to theTodoListtarget or placed insideTodoList/TodoList/. Remove them from the target and keep them inConfig/. Signing for "TodoList" requires a development team. The archive ran withoutCODE_SIGNING_ALLOWED=NO. Usemake archive.
Recap
xcodebuild -list and -showdestinations name the pieces of a build that
Xcode’s Run button chooses for you: the scheme, its targets and
configurations, and the destination. -showBuildSettings shows the result of
four layers of build settings, and a command-line override or a value that
does nothing shows which layer applies. Builds land in DerivedData, in Xcode’s
folder or in build/ for make. Three .xcconfig files now hold the
settings this article added as reviewable text, two of them attached as the
project’s base configurations, and the display name on the home screen shows which
configuration is running. The Makefile builds, runs, and inspects either
configuration with CONFIG, and archives an unsigned Release build with its
debug symbols.
Next improvements:
- Move existing settings out of
project.pbxprojinto the files, deleting each one in the Build Settings editor as you go. - Add a
Config/Version.xcconfigfor the version numbers once the target values are removed, so a release changes one file. - Share the
TodoListscheme (Product → Scheme → Manage Schemes…, Shared) so it is saved in the project folder instead of generated.