Xcode4: empty environment variables in custom build pre-action / post-action - xcode

I need to run a custom build script in xcode 4. The script is added as pre-action and executed properly. My problem is, that there are no environment variables available that could help me to access my project files.
I.e. I'm interested in ${PROJECT_DIR}, ${BUILT_PRODUCTS_DIR} and ${INFOPLIST_PATH}. Unfortunately the variables are not set. The whole environment looks like that:
Apple_PubSub_Socket_Render=/tmp/launch-pc2V6P/Render
BASH=/bin/sh
BASH_ARGC=()
BASH_ARGV=()
BASH_LINENO=([0]="0")
BASH_SOURCE=([0]="/Users/melle/Projects/rc-control/RC/Scripts/preBuild.sh")
BASH_VERSINFO=([0]="3" [1]="2" [2]="48" [3]="1" [4]="release" [5]="x86_64-apple-darwin10.0")
BASH_VERSION='3.2.48(1)-release'
COMMAND_MODE=unix2003
DIRSTACK=()
DISPLAY=/tmp/launch-VH6onm/org.x:0
EUID=502
GROUPS=()
HOME=/Users/melle
HOSTNAME=zack.fritz.box
HOSTTYPE=x86_64
IFS=' '
LOGNAME=melle
MACHTYPE=x86_64-apple-darwin10.0
OPTERR=1
OPTIND=1
OSTYPE=darwin10.0
PATH=/Developer/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin
PIPESTATUS=([0]="0")
POSIXLY_CORRECT=y
PPID=43073
PS4='+ '
PWD=/private/var/folders/h2/h2tdJ2dPF943jRjJXqjNX++++TM/-Tmp-
SCHEME_ACTION_NAME='Run Script'
SHELL=/bin/bash
SHELLOPTS=braceexpand:hashall:interactive-comments:posix
SHLVL=3
SSH_AUTH_SOCK=/tmp/launch-LXX6Lp/Listeners
TERM=dumb
TMPDIR=/var/folders/h2/h2tdJ2dPF943jRjJXqjNX++++TM/-Tmp-/
UID=502
USER=melle
_=PWD
__CF_USER_TEXT_ENCODING=0x1F6:0:0
Is this a bug in xcode4 or is it me, doing it totally wrong?
My current workaround is to abuse ${BASH_SOURCE} to find the way back to my project directory...
Update: It looks like this is a bug in xcode 4.0.1. If you enter the script directly into the Xcode window, at least the variable ${WORKSPACE_PATH} is available.

I found that adding the script to the target's build phase rather than as a pre-action was a better solution in my case:
Select the Project in the project navigator.
Select your target.
Go to the Build Phases tab.
Add a new build phase (script).
Drag it above the compile sources phase.
Add whatever script you need there. You can access environment variables such as ${PROJECT_DIR}.
You can even have it output all the environment variables in the Log Navigator for you.
Additionally, the working directory for any scripts you call will be set to the project's directory automatically.

Related

Xcode scheme pre-action script not running

Hit build and nothing gets printed in build log. What gives?
Xcode Version 8.2.1 (8C1002)
Pre-action takes place before the build, so output doesn't go to the build log, it goes to stdErr. You can copy the output to a file:
exec > ${PROJECT_DIR}/prebuild.log 2>&1
echo "hello world"
To get the environment variables to work, you also need to set "Provide build settings from" to the appropriate target.
That should write hello world to a file named "prebuild.log" in your project folder.
for XCode versions < 13.2
If you want these activities to end up in the build log, consider adding a run script to your target's build phase instead.
for XCode versions >= 13.2
The XCode build log now includes a Run pre-actions section. If you don't redirect to a file, those messages will end up as the last item in a 'Run custom shell script' in this section - access via the XCode Report Navigator.
To add to the answer, https://stackoverflow.com/a/42497746/1058199, it's important to not clutter up the project.pbxproj (where build scripts go) or your scheme file (where these scripts go) as much as possible.
With this in mind, I create a Build-Scripts folder in the project root where I put the app build scripts and pre-action scripts. This folder is dragged into the root of the project as a folder so that any script I add to it is automatically added to project.
Assuming that your pre-action script is called That_pre-action_script.sh, this is what I put in Pre-actions script based on the approved answer.
say "Pre build scripts running."
exec > "${PROJECT_DIR}/prebuild.log" 2>&1
echo "Starting build scheme Pre-actions"
"${PROJECT_DIR}/Build-Phases/That_pre-action_script.sh"
As a test, make sure to echo some message from your script so you can see it in the prebuild.log file.
Hope this helps.
And don't forget to chmod u+x your script in the terminal so that it will run.
The important part is if you can't make sure if your build script is running or not. This is where the say command us useful so that you know it's actually being issued before a build.
It's a good idea to put quotes around the path to the script in case there are any space characters (or otherwise) in the path to your project.

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.

Tagging XCode's Archive builds in Git or another SCM

I'd like my XCode Archive builds to automatically tag my SCM (Git in this project).
I've noticed that in the Schema Editor, Archive builds can run pre and post step build scripts. It would be ideal if post steps are only run if the build is successful, and then the tag could go there.
I'd like the tag name to refer to the name of the build configuration (I have TestFlight and AppStore configurations, as well as Debug and Release, which would not normally be archived), as well as the version number that was built, and also the build number. A tag might go something like: TestFlight_2.1.3_#11 or AppStore_2.9.0_#3.
In XCode's project settings, you can use variable substitutions, such as $(BUILD_CONFIGURATION). Can these be used in an Archive build script?
I'm also not sure if there is a variable for the current version string and build number of the App. I've not managed to find one if there is.
In XCode's project settings, you can use variable substitutions, such as $(BUILD_CONFIGURATION). Can these be used in an Archive build script?
Yes. Build settings are available as environment variables to scripts that run as part of a Run Script Build Phase. An easy way to see what variables are set (if you don't trust the build log output) is to just put env in a script to print them out, or env > environment.txt if you want to save them to a file.
I'm also not sure if there is a variable for the current version string and build number of the App. I've not managed to find one if there is.
There is not. However, if you want to retrieve the current version you can get it from the project's Info.plist file (and there should be an environment variable pointing to the file for the active project).
Xcode Archive doesn't have a concept of a current build number. Usually you use a script to figure out the build number from some other source (Xcode Server, a counter on your disk, whatever) and then set the CFBundleVersion key in Info.plist to that number.
I found this:
https://github.com/bejo/XcodeIconTagger
Obviously this is not exactly what you asked, as this will tag the icon, but you could probably use it to point you to the right direction.

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.

Xcode: Running a script before every build that modifies source code directly

What I did:
I have a script that
Read some configuration files to generate source code snippets
Find relevant Objective-C source files and
Replace some portions of the source code with the generated code in step 1.
and a Makefile that has a special timestamp file as a make target and the configuration files as target sources:
SRC = $(shell find ../config -iname "*.txt")
STAMP = $(PROJECT_TEMP_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME).stamp
$(STAMP): $(SRC)
python inject.py
touch $(STAMP)
I added this Makefile as a "Run Script Build Phase" on top of the stack of build phases for the project target.
What happened:
The script build phase was run before compiling the source.
However, since the script modifies source code during its execution, I needed to build twice to get the most recent version of the build product. Here is what I imagine to be happening:
1st run: Xcode collects dependency information ---> no changes
1st run: Xcode runs "Run Script Build Phase" ---> source is changed behind Xcode's back
1st run: Xcode finishes build, thinking nothing needs to be updated
2nd run: Xcode collects dependency information ---> source has changed, needs rebuild!
2nd run: Xcode runs Run Script Build Phase" ---> everything is up-to-date
2nd run: Xcode proceeds to compilation
After reading Xcode documentation on Build Phases, I tried adding a source file which is known to be updated every time the script is run as the output of "Run Script Build Phases", but nothing changed. Since the number of configuration files may vary in my project, I don't want to specify every input and output file.
Question:
How do I make Xcode aware of source file changes made during "Run Script Build Phase"?
Edit:
Added that I placed the script build phase before the other build phases
Every technique mentioned so far is an overkill. Reproducing steve kim's comment for visibility:
In the build phases tab, simply drag the "Run Script" step to a higher location (e.g. before "Compile Sources").
Tested on Xcode 6
This solution is probably outdated. See the higher voted answer instead; I no longer actively use Xcode and am not qualified to vet a solution.
Use "External Target":
Select "Project" > "New Target..." from the menu
Select "Mac OS X" > "Other" > "External Target" and add it to your project
Open its settings and fill in your script setup
Open the "General" tab of the main target's settings and add the new target as it's direct dependency
Now the new "External Target" runs before the main target even starts gathering dependency information, so that any changes made during the script execution should be included in the build.
There is another, slightly simpler option that doesn't require a separate target, but it's only viable if your script tends to modify the same source files every time.
First, here's a brief explanation for anyone who's confused about why Xcode sometimes requires you to build twice (or do a clean build) to see certain changes reflected in your target app. Xcode compiles a source file if the object file it produces is missing, or if the object file's last-modified date is earlier than the source file's last-modified date was at the beginning of the first build phase. If your project runs a script that modifies a source file in a pre-compilation build phase, Xcode won't notice that the source file's last-modified date has changed, so it won't bother to recompile it. It's only when you build the project a second time that Xcode will notice the date change and recompile the file.
Here's a simple solution if your script happens to modify the same source files every time. Just add a Run Script build phase at the end of your build process like this:
touch Classes/FirstModifiedFile.m Classes/SecondModifiedFile.m
exit $?
Running touch on these source files at the end of your build process guarantees that they will always have a later last-modified date than their object files, so Xcode will recompile them every time.
As of Xcode 4, it looks like if you add the generated files to the output section of the build phase, it will respect that setting, and not generate the ... has been modified since the precompiled header was built error messages.
This is a good option if your script is only generating a handful of files each time.
I as well struggled with this for a long time. The answer is to use ento's "External Target" solution. He is WHY this problem occurs and how we use it in practice...
Xcode 4 build steps do not execute until AFTER the plist has been compiled. This is silly, of course, because it means that any pre-build steps that modify the plist won't take effect. But if you think about it, they actually DO take effect...on the NEXT build. That's why some people have talked about "caching" of plist values or "I have to do 2 builds to make it work." What happens is the plist is built, then your script runs. Next time you build, the plist builds using your modified files, hence the second build.
ento's solution is the one way I've found to actually do a true pre-build step. Unfortunately I also found that it does not cause the plist to update without a clean build and I fixed that. Here is how we have data-driven user values in the plist:
Add an External Build System project that points to a python script and passes some arguments
Add user-defined build settings to the build. These are the arguments that you pass to python (more on why we do this later)
The python script reads some input JSON files and builds a plist preprocessor header file AND touches the main app plist
The main project has "preprocess plist files" turned on and points to this preprocessor file
Using touch on the main app plist file causes the main target to generate the plist every time. The reason we pass in build settings as parameters is so our command-line build can override settings:
Add a user-defined variable "foo" to the prebuild project.
In your prebuild you can use $(foo) to pass the value into the python script.
On the command-line you can add foo=test to pass in a new value.
The python script uses base settings files and allows for user-defined settings files to override the defaults. You make a change and immediately it ends up in the plist. We only use this for settings that MUST be in the plist. For anything else it's a waste of effort....generate a json file or something similar instead and load it at run-time :)
I hope this helps...it's been a couple rough days figuring this out.
The External Target solution from #ento no longer works as of Xcode 11.5. The solution is to add all files that will be changed under Output Files in the Run Script.
Another option is to create a subproject framework with your scripts and just add it as a dependency to all targets. The phase scripts of this subproject should now be executed before all targets.

Resources