Under The Microscope

Archive for March 11th, 2005

Applescripting Xcode

As I don’t like to fall too far behind with the times, today I decided to start updating our code base to use the new Objective-C @try/@catch exception handling system, replacing the old NS_HANDLER macros.To do this however, you need to enable the -fobjc-exceptions build option on each project that needs it. This wouldn’t be so terrible, except we have 20 some odd different project files in our repository currently. Updating each one by hand, which I’ve done in the past, gets to be real painful after about the third one.Luckily, Xcode has an amazingly complete AppleScript dictionary:

Xcode Dictionary

This seems to be a little known fact about Xcode, but it’s a real life saver in situations like this. Instead of spending the day manually clicking checkboxes, I just wrote the following Batch Updater script:Download XcodeProjectBatchUpdater.scpt(* Xcode Project Batch Updater *** (C) Copyright 2005, Rogue Amoeba Software, LLC    Configuration Options:        RelaunchXcodeForEachProject - Quit and relaunch Xcode for each project (or leave it open)*)property RelaunchXcodeForEachProject : trueon _updateProjectDocument(aPrjDoc)    tell application "Xcode"        repeat with aTarget in (every target of aPrjDoc)            --set newBuildSettings to {|GCC_ENABLE_OBJC_EXCEPTIONS|:"YES"}            --set build settings of aTarget to ((build settings of aTarget) & newBuildSettings)                    end repeat    end tellend _updateProjectDocumenton _updateProjectAtPath(aPrjPath)        set aPrjAlias to (POSIX file aPrjPath) as alias    tell application "Xcode"        open aPrjAlias        --set aPrjDoc to first item of (every project document whose path starts with aPrjPath        set aPrjDoc to current project document        my _updateProjectDocument(aPrjDoc)        if not RelaunchXcodeForEachProject then            close window of aPrjDoc        end if    end tell    end _updateProjectAtPathon _gatherUserAcceptedProjects(aSearchPath)    set allPrjs to _gatherAllProjects(aSearchPath)    return choose from list allPrjs with prompt "Choose the projects to update:" default items allPrjs OK button name "Update" with multiple selections allowed    end _gatherUserAcceptedProjectson _gatherAllProjects(aSearchPath)    set cmd to "find " & aSearchPath & " -type d \\( \\( -not \\( \\( -name .svn -and -prune \\) -or \\( -name build -and -prune \\) \\) \\) -and \\( -name '*.xcode' \\) \\) -print"        set results to do shell script cmd    set text item delimiters to ASCII character 13    return text items of resultsend _gatherAllProjectson run        set searchRootPath to POSIX path of (choose folder with prompt "Search for projects in:" default location (path to home folder))    if searchRootPath ends with "/" then        set text item delimiters to ""        set searchRootPath to (items 1 thru ((length of searchRootPath) - 1) of searchRootPath as list) as string            end if        repeat with aPrjPath in _gatherUserAcceptedProjects(searchRootPath)        if RelaunchXcodeForEachProject then            tell application "Xcode" to quit            delay 1        else            tell application "Xcode" to close every window        end if                try            _updateProjectAtPath(aPrjPath)        on error            set reply to display dialog "Failed to update project: '" & aPrjPath & "'" with icon caution buttons {"Stop", "Skip"} default button 2                        if button returned of reply is not "Skip" then                return            end if        end try    end repeat        if RelaunchXcodeForEachProject then        tell application "Xcode" to quit    end if    end runHeres a brief walkthrough of the script, since you need to modify the script to do anything useful with it.1. The run handler first prompts you for a folder in which to look for project files in (and below of, it’s recursive).2. _gatherAllProjects() uses do shell script and `find` to locate the project files.3. _gatherUserAcceptedProjects() prompts for which projects to update, since odds are there are some that shouldn’t be updated.4. _updateProjectAtPath() opens the project(s) in Xcode, and finds the “project document” reference and hands it off to _updateProjectDocument()5. _updateProjectDocument() does actual work on the project. In our example script above, it would turn the GCC_ENABLE_OBJC_EXCEPTIONS build setting on. This is probably the only handler you’ll need to modify.There is one bug workaround in the script, RelaunchXcodeForEachProject. I found that if you try and keep Xcode open the entire time, after about the 4th or 5th project, it would start throwing Internal Errors. Quitting and relaunching it for each and every project alleviates the problem (most of the time, anyhow).As with any batch update system, I advise to take some caution with using it, as you can quickly mangle all your projects. Having a version control system or recent backups in place before hacking away, is highly recommended.