Set an Xcode Project's DerivedData Location via Script
Say the words "Derived Data" to any Apple platform developer, and they will immediately cringe, remembering painful times of deleting the directory just to get Xcode to behave properly.
I'm here to tell you, there is a better way! (Or at least a slight improvement.)
Some Context
Lately, I've taken to setting my Xcode projects to keep their derived data folders alongside the project itself. Xcode calls this a "Project-relative Location", and it can be set in the UI by navigating to "File" > "Project Settings…" in the menu bar. You'll be presented with a window, where you can change the "Derived Data:" drop-down to "Project-relative Location".
Now whenever Xcode runs a build, it will place the derived data right next to your project. This has a number of advantages.
- If you need to clear derived data, you can do it on a per-project basis, without effecting any other project.
- Any Swift Package dependencies get placed in this directory (
DerivedData/<project_name>/SourcePackages/
). - Having a consistent location means you can more easily refer to or use any built artifacts (e.g. For launching development versions of a macOS app for testing outside of Xcode).
![The project settings window, with the "Project-relative Location" option set]({% asset_path per-user-project-settings-window.png %}) The project settings window
This setting is a "per-user" setting though, which ideally should be included in your .gitignore
file, which means it will not get checked in and saved.
Previously, I would go manually set this setting on the projects that I worked on, but why use a mouse when you could use a script instead!
The Solution
Here's the Bash script I came up with to perform the modifications needed on whichever project or workspace you pass to it:
#!/usr/bin/env bash
# This script sets the user-specific Derived Data location setting
# for the given Xcode project or workspace to be "project-relative"
# and next to the project or workspace.
set -o errexit # Exit on error
set -o nounset # Exit on unset variable
set -o pipefail # Exit on pipe failure
# Output extra debug logging if `TRACE` is set to `true`
if [[ "${TRACE:-false}" == true ]]; then
set -o xtrace # Trace the execution of the script (debug)
fi
help() {
echo "Usage: $0 <path/to/project[.xcodeproj | .xcworkspace]>"
}
main() {
if [[ $# -ne 1 ]]; then
help
exit 1
fi
case "$1" in
*.xcodeproj) ;;
*.xcworkspace) ;;
-h | --help)
help
exit 0
;;
*)
help
exit 1
;;
esac
set_local_derived_data "$1"
}
set_local_derived_data() {
# Absolute path to the `.xcodeproj` or `.xcworkspace` file
local PROJECT_FILE=$1
if [[ ! -d "$PROJECT_FILE" ]]; then
echo "Error: $PROJECT_FILE does not exist or is not a directory"
exit 1
fi
# Absolute path to the current user's `xcuserdatad` directory
local XCUSERDATAD_DIR
if [[ "$PROJECT_FILE" == *".xcodeproj" ]]; then
XCUSERDATAD_DIR="${PROJECT_FILE}/project.xcworkspace/xcuserdata/$(whoami).xcuserdatad"
elif [[ "$PROJECT_FILE" == *".xcworkspace" ]]; then
XCUSERDATAD_DIR="${PROJECT_FILE}/xcuserdata/$(whoami).xcuserdatad"
fi
# Create the `xcuserdatad` directory if it doesn't exist
mkdir -p "$XCUSERDATAD_DIR"
WORKSPACE_SETTINGS_PLIST_PATH="${XCUSERDATAD_DIR}/WorkspaceSettings.xcsettings"
# Create the `WorkspaceSettings.xcsettings` file if it doesn't exist
if [[ ! -f "$WORKSPACE_SETTINGS_PLIST_PATH" ]]; then
plutil -create xml1 "$WORKSPACE_SETTINGS_PLIST_PATH"
fi
# Set the Derived Data settings
plutil -replace BuildLocationStyle -string UseAppPreferences "$WORKSPACE_SETTINGS_PLIST_PATH"
plutil -replace CustomBuildLocationType -string RelativeToDerivedData "$WORKSPACE_SETTINGS_PLIST_PATH"
plutil -replace DerivedDataCustomLocation -string DerivedData "$WORKSPACE_SETTINGS_PLIST_PATH"
plutil -replace DerivedDataLocationStyle -string WorkspaceRelativePath "$WORKSPACE_SETTINGS_PLIST_PATH"
# Validate the `WorkspaceSettings.xcsettings` file
plutil -lint "$WORKSPACE_SETTINGS_PLIST_PATH"
}
main "$@"
Simply call the script and pass it the path to an .xcodeproj
or .xcworkspace
, and it will do the modifications for you.
./set-local-derived-data.sh <path/to/project.xcodeproj>
Enjoy your newly local DerivedData directory!