Debug Xcode Scheme Pre & Post Actions
Going along with my previous post about auto incrementing build numbers in Xcode, I thought it might be useful to share how I debugged Xcode scheme pre & post actions. Normally these actions don’t show error messages in an obvious way, so it can be extremely frustrating when the action fails silently with no explanation.
Here’s the script I used to log pre-action output to a file on my desktop named xcode-pre-action.txt
If you’re like me, you’ll be curious how the script works & want an explanation. Let’s break it down and hopefully learn a bit more about shell scripting.
In the above code we simply create a variable named LOGFILE and set it equal to the path of a text file on the desktop. Note that HOME is a variable that exists in every MacOS shell session & is the path to the current user’s home directory. We access HOME’s value & add it to our debug file path using quote expansion (surrounding the variable & the rest of the string with double quotes & prefixing the variable with $).
Next we define a couple functions to use later in the script. It’s extremely important to wrap any logic that may throw errors in a function, since functions give us access to both output and error messages (errors thrown in un-scoped or top level code in Xcode pre/post actions aren’t displayed anywhere obvious). Our first function print_pwd uses the echo command to create text output and append that text to the log file. Notice that we use $ along with parenthesis this time to actually execute the pwd command and insert its output. The >> symbols following our echo command redirects the output of echo to our file. A single > will replace the entire file contents while a double >> will append to the file. The second function bump_version performs the version bump we talked about in my previous post. I put the version bump logic inside the function in order to access any error thrown by agvtool bump (although I have since learned that it fails silently 😭).
Now we’re getting to the good stuff:
#1 - Use echo with a blank string to clear out the log file contents. Note how we use > to overwrite the entire file rather than >> which only appends to the file.
#2 - Use echo to add some text to the log file (using >> to append the text).
#3 - Call our print_pwd function to print the current working directory to our log file.
#4 - Use echo and quote expansion to log the contents of the PROJECT_DIR variable to the log file. Note that this PROJECT_DIR variable is provided by Xcode when we select our app target in the “Provide build settings from…” dropdown of the Scheme > Pre-actions > Run Script settings (see screenshot below).
#5 - Use echo again to add a descriptive entry to our log file.
#6 - Use cd to change the active directory to the project directory. Note that we have to use string expansion (double quotes and prefix the variable with $) in order to access the PROJECT_DIR variable contents.
#7 - Call our print_pwd function again to print the current working directory to our log file & verify that we’ve opened the correct directory.
#8 - Call the bump_version function and use 2>> to redirect any error output to our log file. This is showcasing the reason we use functions to perform important logic that may throw errors. Shell functions provide their return value in a variable named 1 and their error message in a variable named 2. We access the output and error message by using a 1 or 2 after the function call. In our case we’ve accessed the error by following the function call with 2 and forwarding the value to our log file, being careful to use a double >> so we only append rather than overwrite. I’d like to mention again that agvtool bump doesn’t throw errors when it fails, so this wasn’t necessarily helpful.
#9 - Use echo one more time to add an entry to our log file.
Bonus: If you’d like to see some real shell function error logging in action, just add the following code to your Run Script action (at the very bottom works just fine)
This will append the error message to your log file for easy debugging (unless you actually have a folder named non_existent_directory_name).
Here’s a link to a gist with the code we reviewed above.
Thanks for reading!