Build script for automatically moving built AUs into components folder (XCode)

I'm not sure how much help this will be for anyone out there, but I've figured out a nice way to automatically copy over a built component into the /Library/Audio/Plug-Ins/Components folder. Since AUs have to be installed in this folder to be recognized by AU hosts (as opposed to VSTs, which can be linked to in other locations), this might speed testing of Audio Units by automatically moving the latest build to the correct directory.

I'm doing this in XCode 5 on OSX 10.9. I'm not familiar with earlier versions of XCode, although I'd imagine the process is the same across both XCode and OS versions.

The tricky part lies in the fact that /Library/Audio/Plug-Ins/Components is a protected directory, so moving or copying files to it requires sufficient privelege to do so. Manually dragging and dropping your built AU requires you to enter a password, which quickly becomes a bit of a pain.

To handle this, we're going to need to do a few things. First, open Keychain Access. We're going to create a new keychain that our build script can interact with to retrieve our password securely. Select File->New Keychain and create a new keychain, naming it whatever you choose. I chose to call mine Local User Accounts. When prompted to enter a password, choose anything you like - this will be a password to protect your system password, which we will store in this keychain. Once your keychain is created, select File->New Password Item or press the "+" icon to add a new password item to our Local User Accounts keychain. You'll be prompted to enter a Keychain Item Name, which I called xCode_BASH, an Account Name, which should be your administrator account name, cannoneyed in my case, and a Password, which should be your administrator account password. Now we've created a keychain that can store access to our system password and make it accessible to our build script. Here's what everything should look like when you're finished:

Next we'll write a simple askpass.sh script that will look for our newly created password item to supply login functionality for our Post-Build shell script. In a plain text editor, write the following code:

#!/bin/bash
/usr/bin/security find-generic-password -l xCode_BASH -a username -w

Substitute your Account Name for username, and your Keychain Item Name for xCode_BASH if you chose to call it something else. Save this file anywhere you'd like, although it's much easier to store it in a relatively simple place, since shell scripting can be EXTREMELY sensitive to typos and formatting errors and doesn't give very much feedback for debugging. I chose to save all of my scripts in a folder I created at /Users/username/bin/. One word of advice: if your Account Name or Keychain Item Name have more than one word (ie whitespace), it MUST be encased in single quotes, or the script will not work. The converse is true, if your account or keychain item are only one word, encasing them in single quotes will cause errors. Again, shell scripting is EXTREMELY finicky, so be very dilligent.

This script will be executed by our Post-Build script to return the administrator password after prompting the user for our newly created keychain's password. In order for it to be executed, we need to open a terminal window and type the following:

chmod 755 /Users/username/bin/askpass.sh

Obviously, substitute your askpass script's actual location for what I've shown. If you're not used to using terminal, you can simpy drag files into the terminal window to show their filename rather than typing it out manually. This command will set the file as executable for all users, allowing out build script to run it.

Finally, we're ready to write our build script. In another plain text editor, write the following code:

export SUDO_ASKPASS=/Users/username/bin/askpass.sh
sudo -A cp -r "$BUILT_PRODUCTS_DIR/$PRODUCT_NAME.component" "/Library/Audio/Plug-Ins/Components"

Save the script as a .sh file anywhere you like, although for simplicity I saved it at the same /Users/username/bin/ folder I saved my askpass.sh script in. I called my script auBuildScript.sh, but you can call it anything you'd like. Simply substitute the location of your askpass.sh file after SUDO_ASKPASS=.

So what exactly is this script doing? The second line starting at cp attempts to move the built .component file into the Components folder, using some of xCode's internal variables. The -r flag indicates a recursive transfer for multiple files in a folder (since a .component is actually a bundle of items). However, since the Components folder is password-protected, we need to have administrator priveleges to do this operation. We use the sudo command to achieve this. Normally, in the terminal, you'll get a prompt asking for the administrator password when attempting to execute a sudo command. However, the xCode Post-Build shell which will execute our script doesn't have a terminal to prompt us for our password. We fix this by using the -A flag, which tells sudo to use an alternately defined method for getting a password. We define this method as our keychain-accessing askpass.sh script in the first line! 

Just as the we did with the askpass.sh file, we'll need to set out auBuildScript.sh file to executable for xCode to be able to run it. Open up a terminal window and enter:

chmod 755 /Users/username/bin/auBuildScript.sh

Just to be sure we've set sufficient permissions on our files, you can type:

ls -l /Users/username/bin/auBuildScript.sh

This will show the permissions of the file. If correct, both files should display -rwxr-xr-x as their permission flags.

Now we're ready to implement our build script into xCode! I'm specifically using xCode 5.1.1, so I can't be sure how this process differs in earlier versions, but I'd imagine it's fairly similar: In the Project Navigator tab in the leftmost Navigator pane, click the name of the project (the uppermost item) to show the project settings. Along the top of the central window you'll find Build Phases. Searching through the pull-down menus, you should find one called Post-Build Script at the bottom. If not, you can create one by clicking the '+' icon and selecting New Run Script Build Phase. Simply drag your auBuildScript.sh file into the box that asks you to "Type or drag script file..." and your script will be set to run when you  build your component! Note you can also add your script to a a new project by copying your build script location into the IntroJucer's Post-Build Shell Script section, located in the Config panel in the XCode (Mac OSX) menu item.

Now assuming everything is set up properly (and believe me, this is no small feat - shell scripting is EXTREMELY finicky and difficult to debug), when you build your plugin you should be prompted the password of the keychain you set up at the beginning of this process. This keychain protects the administrator password you entered into the New Password Item at the beginning, and when you authenticate, it sends your administrator password to the shell script in XCode allowing it to move your newly built component! Voila! Note that you only need to enter your keychain password once for XCode to be able to move your new components, so you only have to "Allow" security to access your xCode_BASH password item. If you "Always Allow", you'll never be prompted for your keychain password again while running the askpass script, which could be a security risk.

Anyway, I hope this is helpful to someone out there. I got sucked down a pretty major wormhole last night trying to get this to work, and, as always, came out feeling both like I understand things a lot better and a lot worse. Terminal and shell scripting are two incredibly powerful tools on OSX, and you can use them to do some potentially cool things when partnering with XCode. Here's a few final thoughts:

- First off, I apologize profusely if this doesn't work right off the bat- I spent almost 3 hours last night hunting down a bug that turned out to be the use of single quotation marks. Remember - shell scripts and terminal work are EXTREMELY finicky and lack the more explicit syntax and debugging environment of C++ and other languages, so it's really really easy to miss a typo and not understand what's going wrong. Try, try, again, and keep digging until it works!

- So it turns out XCode uses its own shell with its own environment variables. This means that any changes you make to your .bash_profile, such as updating the PATH or defining environment variables, for regular shell work won't necessarily apply in XCode. This is why I had to explicity export SUDO_ASKPASS in my auBuildScript.sh script - if I tried to define that in my .bash_profile it wouldn't exist in XCode's shell.

- Also, XCode's shell will "remember" the last administrator login in the shell and thus be allowed to use sudo without prompting for a password if you've already authenticated in another shell (say, your terminal window). This can make it a little tough to see if your script is, in fact, working. I reccomend not using any sudo commands in terminal while trying to get this process to work, since it effectively bypasses our custom askpass and makes it look like it's working even if it may not yet be.

- If I'm missing some important concept as to why this might be really insecure, please tell me - I tried as hard as I could to find a method that would keep the administrator password protected at all times.

- Finally, big thanks to Dennis Conrad for his work at http://www.conrad.id.au/ - This guy's doing some pretty cool stuff to really get the most out of some of OSX's develope features.

One thing that I forgot to mention is that in order to see the Build Phases tab, you need to select your target in the top left corner of the project settings. If you click on your project in the leftmost Navigator window (make sure you have the Project Navigator ('folder') icon selected, the project settings will be displayed in the main window. In the top left corner of the main window should be a pulldown where you can select your target. Then the Build Phases tab will be displayed and you can select it to enter your Post-Build script.

Yes, I realize you can also simply build your components into the ~/Library/Audio/Plug-Ins/Components folder without any of this trouble, but where's the fun in that :P