Command line option to change Xcode DerivedData location - xcode

I know the way to change the location of DerivedData using Xcode (Preferences->Locations->DerivedData..).
However the Mac that I am trying to build on is in a remote location with only ssh access. I want to build a specific project where I want to keep the DerivedData location relative to the project.
Whenever I build the project using xcodebuild, the files end up generating under /Users/builduser/Library/Developer/Xcode/DerivedData however I want them under '$PROJECT/target/DerivedData'
What is the command line equivalent to changing the DerivedData location setting as can be done via XCode?

The Xcode UI's DerivedData setting is passed to xcodebuild via the -derivedDataPath argument. From man xcodebuild:
-derivedDataPath path
Overrides the folder that should be used for derived data when performing a build action on a scheme in a workspace.
While it isn't clear what your goal is with changing the DerivedData path, you should be aware that there are some additional settings you can adjust in your app's build configuration to affect where the final build gets deployed. One of the phases of the build is the install phase which can move the final artifact(s) elsewhere. Settings that control these behaviors can be found under the 'Deployment' build settings group. See DSTROOT, INSTALL_PATH, and DEPLOYMENT_LOCATION for additional options that may be helpful for reorganizing where your products get deployed.

You probably should set this on a project basis, but if you need to change the Xcode default without going to the UI:
There's a plist file under ~/Library/Preferences/com.apple.dt.Xcode.plist
You can see and the change content via PlistBuddy:
/usr/libexec/PlistBuddy -c print ~/Library/Preferences/com.apple.dt.Xcode.plist
and you can see the field:
IDECustomDerivedDataLocation = DerivedData
If it's not set, you can just add it using PlistBuddy:
/usr/libexec/PlistBuddy -c "Add IDECustomDerivedDataLocation string DerivedData" ~/Library/Preferences/com.apple.dt.Xcode.plist

Related

Xcode: how to include build number in app name

I'm rolling out beta releases of a Mac app, and would like to include the current build number in the app name, e.g. what the users see as app name in the dock etc.
if my app is named The App, I want it to show The App Beta 1234
so in the target's build config, there is "Product Name" which has a value of $(TARGET_NAME)
now, on a debug build, I would like to add the build number there. This should be possible using $(TARGET_NAME) Beta $(????).
what do I put there instead of the ????? Or is it not so simple?
e.g. there's this: Getting CFBundleVersion from within Jenkins to use it as a variable, something like ${APP_VERSION}
but, I'm not using Jenkins, and also it looks somehow wonky. Is that the only/proper way to access CFBundleVersion...?
There are multiple things you can do:
1) Modify CFBundleName using a build phase
Select the target you want to modify within Xcode
Open the Build Phases for the selected target
Add a new Run Script Build Phase
Paste the following code
BUILD_NUMBER=$(/usr/libexec/PlistBuddy -c "Print CFBundleVersion" "$INFOPLIST_FILE")
/usr/libexec/PlistBuddy -c "Set :CFBundleName $PRODUCT_NAME Beta $BUILD_NUMBER" "$TARGET_BUILD_DIR/$INFOPLIST_PATH"
The script makes use of PlistBuddy. First, it gets the CFBundleVersion from your Info.plist and stores it in a variable BUILD_NUMBER. In the second step it uses PlistBuddy to set CFBundleName to $PRODUCT_NAME Beta $BUILD_NUMBER. Note that the it will not modify the original plist but the one in your build directory so you wont be bothered by changes in your git repository.
The CFBundleName will be displayed in the Menu Bar when the app is running. It will not have an effect on the App's name in the Dock (that is derived from the app's file name).
Note: This will probably not work if you have localized your Bundle Name using CFBundleDisplayName. You may need to adapt the script above in that case.
2) Use Custom Build Setting
Select the target you want to modify
Open Build Settings for selected target
Editor --> Add Build Setting --> Add User-Defined Setting
Name your new setting CUSTOM_BUILD_VERSION and add a value
Search for PRODUCT_NAME in the Build Settings and change it to $(TARGET_NAME) Beta $(CUSTOM_BUILD_VERSION)
Open your target's Info.plist
Change CFBundleVersion to $(CUSTOM_BUILD_VERSION)
If you want to change the build version, change CUSTOM_BUILD_VERSION instead
Using this method Xcode will create an app named "SomeApp Beta ". Because the name in the dock is the same as the app bundle name, the dock will also use that name.
3) Rename App after building
Of course you could also just rename the app after building it with Xcode. If you are building your app from the command line using Gradle (see the gradle-xcodeplugin), Fastlane or a custom build script this can of course be automated.
It should be pretty easy to use PlistBuddy to extract the CFBundleVersion from the bundled app and then rename the bundle to include the extracted value. This should be fairly similar to the script from 1).
Use $(TARGET_NAME) Beta $(BUILD_NUMBER)

Copy build to a different directory after finishing building

When I build my project with Xcode 8, it saves the final build in ~/Library/Developer/Xcode/DerivedData/MyProject-[add-lots-of-random-chars-here]/Build/Products/Release-iphoneos. Is there any way to make Xcode copy the app bundle to a user-specified path after building it? e.g. how can I make Xcode copy the built app bundle to /MyBuilds after building it?
I know that I can change the path for storing derived data in my project's settings in Xcode but doing so will of course make Xcode store all data (including intermediate stuff like object code etc) in this location which I don't want. I really only want Xcode to copy the final, ready-for-distribution app bundle to a user-specified location without any intermediate files used in the build process.
How can I do that?
The solution using a script in "Build Phases" does not work properly since Xcode is not finished building the app when running the script. Here is a solution with a script that runs after all build tasks are finished:
Go to "Edit Scheme"
Click on the triangle next to "Build"
Select "Post-action"
Press the + button and select "New Run Script Option"
Select your app name in "Provide build settings from"
Add the following shell script:
Script:
PRODUCT="${BUILT_PRODUCTS_DIR}/${TARGET_NAME}.app"
cp -R "${PRODUCT}" ~/Desktop
Add a shell script to your build phases to copy the product:
Script:
PRODUCT="${BUILT_PRODUCTS_DIR}/${TARGET_NAME}.app"
cp -R "${PRODUCT}" ~/Desktop
Certainly replace ~/Desktop with a target directory of your choice.

Error dialog "The file 'exclude' doesn't exist" when creating a new git-backed XCode project [duplicate]

When I create a new project in XCode 6.0.1, I got this error message: The file "exclude" doesn't exist
It seems to only impact the versioning of the files i.e. the generated stub files are not committed into github after the project is created.
What can cause this problem?
For me the issue was caused because I had previously created a project with the same name, and Xcode still had record of that.
To clear it out,
go to Window -> Organizer in the menu bar
Remove all of the repositories highlighted in red
To add your repository (if it's not being tracked for some reason),
Click the + (still in Window -> Organizer from the steps above)
Enter the path of your file
Make sure to change to Git from Subversion (if Xcode has Subversion set as default - it did for me.
I usually get this error if I initialize an Xcode project with a git repository, delete it and try to recreate it with the same name (casing doens't appear to make it sufficiently 'different'). Turns out, "Well I'll just start over" can leave some issues as well.
Hope this helps.
I had this issue as well, and I tracked it down to the .git-template folder included with Thoughtbot's dotfiles. Basically, Xcode expects its template folder to have info/exclude, and Thoughtbot's dotfiles don't. Creating that directory and file fixed the problem, as so (in the Terminal):
cd ~/.git_template
mkdir info
cd info
touch exclude
If you're getting this issue without Thoughtbot's dotfiles, you could probably look at ~/.gitconfig and use whatever templatedir is getting set as instead of ~/.git_template in the first command.

How can I dynamically set my app's build number without dirtying my source tree?

I'm using git-svn and I'm trying to embed my revision number into my iOS app. At the moment, I have a build phase which runs the following script:
SVN_REVISION=$(git svn find-rev HEAD)
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $SVN_REVISION" "$INFOPLIST_FILE"
The problem with this is that, since the repo always contains the previous revision, the script always makes my Info.plist dirty.
Is it possible for me to dynamically set my app's build number without dirtying my source tree?
1) Add a new target to your project of type "Aggregate", e.g. you may name it "Update Info.plist Prefix Header"; just use that as "Product Name" in the dialog.
2) Add a Run Script build phase to this new target with the following source code:
#!/bin/sh
SVN_REVISION=$(git svn find-rev HEAD)
echo "#define SVN_REVISION $SVN_REVISION" > "$SCRIPT_OUTPUT_FILE_0"
3) Add an output file to your script, name it
$(CONFIGURATION_TEMP_DIR)/InfoPlist.pch
4) Open the Build Phases of your iOS app.
5) Add the aggregate target you created before as dependent target (add it to "Target Dependencies"). This means Xcode will always first build this target before it will build your iOS target.
6) Open the Build Settings of your iOS app.
7) Search for the setting "Info.plist Preprocessor Prefix File" and change it to exactly the same value you used for the output file in step (3).
8) Search for the setting "Preprocess Info.plist File" and make sure it is enabled.
9) Open your current Info.plist file and change the value of CFBundleVersion to SVN_REVISION. Do not use $(SVN_REVISION) or ${SVN_REVISION}; this is no build setting or environment variable replacement, this is a preprocessor replacement, so just use SVN_REVISION.
That's it. Each time you build your iOS app, Xcode first builds the aggregate target, which updates the PCH file, and when it builds your iOS app, it will run the Info.plist file through the C pre-processor (using the PCH file as prefix header) before copying it to your application. The pre-processor will replace SVN_REVISION since it is defined as a macro in your PCH file.
Important Notes
Some people may think it is a better idea to use $(DERIVED_FILE_DIR) instead of $(CONFIGURATION_TEMP_DIR). Well, in theory they are right, yet there is just one problem in practice: The derived file dir is different for every target, while the configuration temp dir is the same (it is only different for every build configuration). When using derived file dir, the PCH file is written to the derived file dir of the aggregate target, yet when building the iOS app, Xcode will search for this file in the derived file dir of the iOS app and thus it won't find the file.
Some people may also think it is a better idea to just add the Run Script phase that updates the prefix header as the first build phase of you iOS app instead of creating a separated target for it (this would also resolve the derived file dir issue mentioned above). Again, nice idea in theory but cannot work in practice: If preprocessing is requested, the Info.plist is preprocessed before the first script phase is even executed, so if the PCH file does not exist already or has not been updated already, either the build terminates with an error or an outdated SVN revision is written to the plist file. That's why you need a separate target for this task that is guaranteed to be build before your actual target is.
Mecki thank you for the excellent answer! I applied the same concept to set a version timestamp and the current git SHA for the build.
FYI I just ran into a small issue. It seems that, at least in Xcode 5, if you specify an output file the script step uses it as a cache, so no matter the changes I made to my actual app code the script reported that it had ran but the values were not the current ones...
I had to move the output file declaration to the script itself to solve the issue, i.e. added
SCRIPT_OUTPUT_FILE_0="$CONFIGURATION_TEMP_DIR/InfoPlist.pch"
to the top of my script.
Additionally the original plist should also be touched in order for the build step to copy the new values in, so I also added
`touch $SCRIPT_INPUT_FILE_0`
after the previous output file declaration. This touch operation does not make git detect the change as commit-able.
Cheers

How can I conditionally include a file based on build configuration in Xcode?

I have an Xcode project with a large number of targets where I would like to include a settings bundle for apps built under the Ad-hoc and Debug configurations, but not under the Release configuration.
Build Phases don't seem to allow for making themselves conditional on configuration (they can obviously be conditional on target, but doubling the number of targets in the project would make it completely unusable).
That leaves writing a custom Build Rule. My plan is to exclude the Settings.bundle from all targets, and create a build rule that conditionally copies it into the product package, but applicable examples are really hard to find.
The build rule I've started has the Process setting set to "Source files with names matching:" and Settings.bundle as the name. The Using setting is "Custom script:".
My custom script is as follows (with the caveat that my bash scripting is on a cargo cult level):
if [${CONFIGURATION} = 'Debug'] then
cp -r ${INPUT_FILE_PATH} ${DERIVED_FILES_DIR}/.
fi
Finally, I have ${DERIVED_FILES_DIR}/Settings.bundle listed as an output file.
Since I'm here, it should be obvious that it's not working. My first question is whether there is somewhere I can view the output of the build rules as the execute to make sure that 1) it's actually being executed and that 2) I don't have a stupid syntax error somewhere.
Also, what's the proper location (in the form of an environment variable) to copy the output to?
I finally figured it out.
For each target for which you want to conditionally include the settings bundle, choose its Project from the source list, choose the target, and switch to the "Build Phases" tab.
Click the "Add Build Phase" button and choose "Add Run Script".
Then enter the following for the script:
if [ "${CONFIGURATION}" == "Debug" ]; then
cp -r "${PROJECT_DIR}/Settings.bundle" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app"
fi
I know this question has been answered already, and the answer was very helpful to me, but I wanted to throw my own modified solution out there as well.
My requirement was to have different settings bundles for different build configurations, rather than just not including it at release. Assuming a simplistic approach of only Debug and Release configurations, here's how to do it:
Start by adding 2 settings bundles to the project, named Settings-debug.bundle and Settings-release.bundle and then remove these files from the Copy Bundle Resources build phase. Next add a user defined build setting called SETTINGS_BUNDLE, which has different values for each configuration:
Debug ${PROJECT_DIR}/relative/path/to/Settings-debug.bundle
Release ${PROJECT_DIR}/relative/path/to/Settings-release.bundle
Next add a run-script build phase (after Copy Bundle Resources) named Copy Settings Bundle with a modified version of the script in Frank's solution.
cp -r "${SETTINGS_BUNDLE}/" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/Settings.bundle"
The difference here is that the copied bundle is always named Settings.bundle regardless of the source name.
You then need to add another build phase script to prevent code signing errors when the only changes are in the settings bundles. It forces the code signing step to occur on every build. This should run before the Compile Source Files build phase. I called mine Force Codesign.
touch "${PROJECT_DIR}/relative/path/to/main.m"
For complied sources, there is a poorly documented user defined build setting that can be added. Files can be both excluded and included from compilation
Go to your target's Build Settings > Tap the + button > Add User-Defined Setting
The key is either INCLUDED_SOURCE_FILE_NAMES or EXCLUDED_SOURCE_FILE_NAMES
The value is a space separated list of file paths
See reference:
http://lists.apple.com/archives/xcode-users/2009/Jun/msg00153.html
(Tested with Xcode 9.3)
I can't find when Xcode included this feature but EXCLUDED_SOURCE_FILE_NAMES is now directly available in Build Settings > Build Options > Excluded Source File Names.
So you no longer need to create a User-Defined Setting.
See below:
It will automatically add this line in your .pbxproj.
Settings.bundle is always copied into destination area no matter whether Release or Debug configuration. So, maybe you need the following code:
if [ ${CONFIGURATION} == "Release" ]; then
rm -rf ${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/Settings.bundle
fi
I am no shell script expert but I think you need space between the square brackets and the condition. Also, quoting the variables may help:
if [ "${CONFIGURATION}" = "Debug" ] then
cp -r "${INPUT_FILE_PATH}" "${DERIVED_FILES_DIR}"/.
fi
As for the location, I use "$BUILT_PRODUCTS_DIR"/"$FULL_PRODUCT_NAME" for the root of my OS X app bundle.

Resources